Reputation: 950
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
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