interwebjill
interwebjill

Reputation: 950

d3: canvas zooming with multiple images

I am using d3 to ultimately create an area chart with a times series domain. Along with the chart, I would like to include a series of images aligned with the dates. The images are too wide to be shown full width and align with the x axis, so I am clipping them. I would like the clipping to expand as I zoom in. I tried this with svg image elements, but the zooming was too slow. So now I am trying it with canvas. But I am stuck because I don't know how to redraw the multiple images without reloading them. Here is my code:

  var imageWidth = 427,
  imageHeight = 240;

  var imageDirectory = "data/solarImages/solarImage",
    imageExtension = "jpg";

  var parseDate = d3.timeParse("%Y/%m/%d %H:%M");

  var canvas = d3.select('canvas');
  var context = canvas.node().getContext("2d");
  var width = canvas.property("width");
  var height = canvas.property("height");

  // svg = ...

  var x =
    d3.scaleTime()
      .range([0, width]);

  var y =
    d3.scaleLinear()
      .range([height, 0]);

  // data
  d3.csv("data/kW_test.csv", type).then(function(data) {
    var dataLength = data.length,
    clipWidth = width/dataLength,
    sx = (imageWidth - clipWidth)/2,
    zoomLimit = imageWidth/clipWidth;

    x.domain(d3.extent(data, function(d) { return d.date; }));

    y.domain([0, height]);

    canvas
      .call(d3.zoom().scaleExtent([1, zoomLimit]).on("zoom", zoomed));

    // loading and drawing the clipped images
    data.forEach(function(o){
      d3.image(o.image).then(function(img) {
        context.drawImage(img, sx, 0, clipWidth, imageHeight, x(o.date) - clipWidth / 2, 0, clipWidth, imageHeight);
      });
    });

    function zoomCanvas() {
      var xdom = x.domain();
      var zoomedDataArray = data.filter(function(d) {
        return d.date >= xdom[0] && d.date <= xdom[1];
      });
      dataLength = zoomedDataArray.length;
      context.clearRect(0, 0, width, height);
      clipWidth = width/dataLength;
      sx = (imageWidth - clipWidth)/2;

      // how do I access the images in the zoomed domain??
      context.drawImage(img, sx, 0, clipWidth, imageHeight, x(o.date) - clipWidth / 2, 0, clipWidth, imageHeight);
    }

    function zoomed() {
      zoomCanvas();
    }
  });

  //helper functions
  function splitDateString(dateString) {
    var stringArray = dateString.split(/\/|:|\s/);
    stringArray[0] = stringArray[0].substring(2);
    return stringArray;
  }

  function type(d, _, columns) {
    var dateArray = splitDateString(d.date);
    d.image = `${imageDirectory}${dateArray[1]}_${dateArray[2]}_${dateArray[0]}_${dateArray[3]}.${imageExtension}`;
    d.date = parseDate(d.date);
    for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
    return d;
  }

Upvotes: 0

Views: 418

Answers (1)

robertkrahn
robertkrahn

Reputation: 132

How about using a Map?

const imageCache = new Map();
...

    d3.image(o.image).then(function(img) {
      imageCache.set(o.image, img);
...

  function zoomCanvas() {
...
    zoomedDataArray.forEach(o => {
      const img = imageCache.get(o.image);
      context.drawImage(img, sx, 0, clipWidth, imageHeight,
         ...
     })
...

Upvotes: 1

Related Questions