1

I'm a freshman using js and d3. How can I zoom a scatter plot matrix chart?

What I did,

  • I used svg to show the scatter plot matrix, following the example https://bl.ocks.org/Fil/6d9de24b31cb870fed2e6178a120b17d
  • Since the performance was too bad when the records over 10 thousands and the matrix size is 10*10, I changed the point draw with canvas
  • Axes are using svg and the dots are drew by canvas
  • Even if it spends some time to draw the chart with canvas, the page will not hang and oom when running 10*10 matrix over 10 thousands records

I'm not sure it's a formal way to do this. On the other hand, I want to zoom this chart but I have no idea how can I do this.

From my understanding,

  • The axes splits into several parts according to the number of matrix, such as 10, including x-axis and y-axis
  • If I want to rescale each matrix cell, such as the cell on row 0 and column 0, how can I do this? I just tired with d3.event.transform.rescaleX/rescaleY to rescale the axes, this seems to work but how can I do on the canvas, how can I get the valid dots to redraw?
  • On the other hand, if I only want to zoom the whole chart not the single cell(That means, if I click on cell(0,0), this cell will zoom until it fill the whole chart and other cells will not be seen), how can I do this? I used modal to show the scaled large svg by viewBox="0 0 ' + width * scalar + ' ' + height, is there any other way to show image in large format?

draw_spm = function(data) {
  var width = 700, traits = d3.keys(data[0]),
      domain = {}, n = traits.length,
      pointRadius = 1;

  var size = width / n,
      padding = size / 10;

  var x = d3.scaleLinear().range([padding / 2, size - padding / 2]),
      y = d3.scaleLinear().range([size - padding / 2, padding / 2]);

  var x_axis = d3.axisBottom().scale(x).ticks(6),
      y_axis = d3.axisLeft().scale(y).ticks(6);

  traits.forEach(function(t) {
    domain[t] = d3.extent(data, function(d) { return d[t]; });
  });

  x_axis.tickSize(size * n);
  y_axis.tickSize(-size * n);

  var zoom = d3.zoom()
      .on('zoom', zoomed);

  var svg = d3.select('#spm-svg')
      .attr('class', 'plot svg-scatterplot')
      .attr('width', size * n + 4*padding)
      .attr('height', size * n + 4*padding)
    .append('g')
      .attr('transform', 'translate('+4*padding+','+padding/2+')');

  var x_axis_svg = svg.selectAll('.x.axis')
      .data(traits)
    .enter().append('g')
      .attr('class', 'x axis')
      .attr('transform', function(d, i) { return 'translate('+size*i+',0)'; })
      .each(function(d) { x.domain(domain[d]); d3.select(this).call(x_axis); });

  var y_axis_svg = svg.selectAll('.y.axis')
      .data(traits)
    .enter().append('g')
      .attr('class', 'y axis')
      .attr('transform', function(d, i) { return 'translate(0,'+size*i+')'; })
      .each(function(d) { y.domain(domain[d]); d3.select(this).call(y_axis); });

  var canvas = d3.select('#spm-canvas')
      .attr('width', size*n+4*padding)
      .attr('height', size*n+4*padding)
      .style('transform', 'translate('+4*padding+','+padding/2+')');

  var ctx = canvas.node().getContext('2d');
  ctx.fillStyle = 'steelblue';

  var cell = svg.selectAll('.cell')
      .data(cross(traits, traits))
    .enter().append('g')
      .attr('class', 'cell')
      .attr('transform', function(d) {
        return 'translate(' + d.i*size + ',' + d.j*size + ')';
      })
      .each(draw);

  canvas.call(zoom).on('dblclick.zoom', null);

  function draw(p) {
    var cell = d3.select(this);

    ctx.resetTransform();
    ctx.transform(1, 0, 0, 1, p.i*size+4*padding, p.j*size+padding/2);
    x.domain(domain[p.x]);
    y.domain(domain[p.y]);

    function draw_point(d) {
      ctx.beginPath();
      ctx.arc(x(d[p.x]), y(d[p.y]), pointRadius, 0, 2*Math.PI);
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
    }

    cell.append('rect')
       .attr('class', 'frame')
       .attr('x', padding / 2)
       .attr('y', padding / 2)
       .attr('width', size - padding)
       .attr('height', size - padding);

    data.forEach(function(d) {
      draw_point(d);
    });
  }

  function zoomed() {
    // how to do this?
  };
  
  function cross(a, b) {
    var c = [], n = a.length, m = b.length, i, j;
    for (i = -1; ++i < n;)
      for (j = -1; ++j < m;)
        c.push({x: a[i], i: i, y: b[j], j: j});
    return c;
  }
};

cols = ['x0','x1','x2','x3','x4'];

function _data() {
  var d = {};
  for (var i = 0; i < cols.length; i++) {
    d[cols[i]] = Math.floor(Math.random() * 10000);
  }
  return d;
}

var data = [];
for (var i = 0; i < 1000; i++) {
  data[i] = _data();
}

draw_spm(data);
.svg-scatterplot .axis,.frame {shape-rendering:crispEdges;}
.svg-scatterplot .axis line {stroke:#ddd;}
.svg-scatterplot .axis path {display:none;}
.svg-scatterplot .cell text {font-weight:bold;text-transform: capitalize;fill: black;}
.svg-scatterplot .frame {fill:none;stroke:#aaa;}
.svg-scatterplot circle {fill-opacity:.7;}
.svg-scatterplot circle.hidden {fill:#ccc !important;}
.svg-scatterplot .extent {fill:#000;fill-opacity:.125;stroke:#fff;}

.plot {position: absolute;}
#spm-canvas {z-index: 2;}
#spm-svg {z-index: 1;}
<script src="https://d3js.org/d3.v4.min.js"></script>

<svg id="spm-svg" class="plot"></svg>
<canvas id="spm-canvas" class="plot"></canvas>

Thanks for your help

qingdaojunzuo
  • 416
  • 1
  • 3
  • 13
  • If I understood correctly a combination of these 2 examples should suit your needs: zooming per mousewheel: http://bl.ocks.org/sxv/4485778 and zoom on click: https://bl.ocks.org/iamkevinv/0a24e9126cd2fa6b283c6f2d774b69a2 – chriopp Sep 29 '18 at 10:15

0 Answers0