javascript - D3: Zooming/Panning Line Graph in SVG is not working in Canvas -
i created zooming/panning graph d3 using svg. i'm trying create same exact graph canvas. issue is, when comes zooming , panning of canvas graph, graph disappearing , cannot figure out why. created 2 jsbin's show code of both. can assist me.
my svg zoom code looks this:
// zoom components zoom = d3.zoom() .scaleextent([1, daydiff*12]) .translateextent([[0, 0], [width, height]]) .extent([[0, 0], [width, height]]) .on("zoom", zoomed); function zoomed(){ t = d3.event.transform; xscale.domain(t.rescalex(x2).domain()); xaxis = d3.axisbottom(xscale).ticksize(0).tickformat(d3.timeformat('%b')); focus.select(".axis--x").call(xaxis); //xaxis changes usagelinepath.attr('d',line); //line path reference, regenerate } my canvas zoom code looks this:
// zoom components zoom = d3.zoom() .scaleextent([1, daydiff*12]) .translateextent([[0, 0], [width, height]]) .extent([[0, 0], [width, height]]) .on("zoom", zoomed); function zoomed() { t = d3.event.transform; x.domain(t.rescalex(x2).domain()); context.save(); context.clearrect(0, 0, width, height); draw(); context.restore(); } function draw() { xaxis(); yaxis(); context.beginpath(); line(data); context.linewidth = 1.5; context.strokestyle = "steelblue"; context.stroke(); }
there 1 major source of grief causes line disappear, , triggered on zoom:
function zoomed() { t = d3.event.transform; x.domain(t.rescalex(x2).domain()); // here ... } rescaling won't work on x2 haven't defined domain. x2 reference scale used set x on each zoom, should same x start with. however, default domain d3.timescale() january 1, 2000 january 2, 2000 (see api docs), won't work out data data not overlap period.
you need set domain of x2 x. if after set initial domain x with: x2.domain(x.domain()), should chart updates (jsbin) have domain overlaps data.
however, now, issue need clip line, in svg example not canvas. use like:
function draw() { xaxis(); yaxis(); // save context without clip apth context.save(); // create clip path: context.beginpath() context.rect(0, 0, width, height); context.clip(); // draw line in clip path context.beginpath() line(data); context.linewidth = 1.5; context.strokestyle = "steelblue"; context.stroke(); // restore context without clip path context.restore(); } see jsbin
and shouldn't let axes overwrite themselves: here's jsbin erases previous axes (with commented out code block redefines y domain based on values contained in selected x domain).
for measure, here's snippet of last jsbin (scaled down snippet view):
var data = getdata().map(function (d) { return d; }); var canvas = document.queryselector("canvas"), context = canvas.getcontext("2d"); var margin = { top: 20, right: 20, bottom: 30, left: 50 }, width = canvas.width - margin.left - margin.right, height = canvas.height - margin.top - margin.bottom; var parsetime = d3.timeparse("%d-%b-%y"); // setup scales var x = d3.scaletime() .range([0, width]); var x2 = d3.scaletime().range([0, width]); var y = d3.scalelinear() .range([height, 0]); // setup domain x.domain(d3.extent(data, function (d) { return moment(d.ind, 'yyyymm'); })); y.domain(d3.extent(data, function (d) { return d.ksum; })); x2.domain(x.domain()); // day range var daydiff = daydiff(x.domain()[0],x.domain()[1]); // line generator var line = d3.line() .x(function (d) { return x(moment(d.ind, 'yyyymm')); }) .y(function (d) { return y(d.ksum); }) .curve(d3.curvemonotonex) .context(context); // zoom var zoom = d3.zoom() .scaleextent([1, daydiff]) .translateextent([[0, 0], [width, height]]) .extent([[0, 0], [width, height]]) .on("zoom", zoomed); d3.select("canvas").call(zoom) context.translate(margin.left, margin.top); draw(); // function draw() { // remove everything: context.clearrect(-margin.left, -margin.top, canvas.width, canvas.height); /* // calculate y axis domain across selected x domain: newydomain = d3.extent(data, function(d) { if ( (x(moment(d.ind, 'yyyymm')) > 0) && (x(moment(d.ind, 'yyyymm')) < width) ) { return d.ksum; } }); // don't update y axis if there no points set new domain, keep old domain. if ((newydomain[0] !== undefined) && (newydomain[0] != newydomain[1])) { y.domain(newydomain); } //*/ // draw axes: xaxis(); yaxis(); // save context without clip apth context.save(); // create clip path: context.beginpath() context.rect(0, 0, width, height); context.clip(); // draw line in clip path context.beginpath() line(data); context.linewidth = 1.5; context.strokestyle = "steelblue"; context.stroke(); // restore context without clip path context.restore(); } function zoomed() { t = d3.event.transform; x.domain(t.rescalex(x2).domain()); draw(); } function xaxis() { var tickcount = 10, ticksize = 6, ticks = x.ticks(tickcount), tickformat = x.tickformat(); context.beginpath(); ticks.foreach(function (d) { context.moveto(x(d), height); context.lineto(x(d), height + ticksize); }); context.strokestyle = "black"; context.stroke(); context.textalign = "center"; context.textbaseline = "top"; ticks.foreach(function (d) { context.filltext(tickformat(d), x(d), height + ticksize); }); } function yaxis() { var tickcount = 10, ticksize = 6, tickpadding = 3, ticks = y.ticks(tickcount), tickformat = y.tickformat(tickcount); context.beginpath(); ticks.foreach(function (d) { context.moveto(0, y(d)); context.lineto(-6, y(d)); }); context.strokestyle = "black"; context.stroke(); context.beginpath(); context.moveto(-ticksize, 0); context.lineto(0.5, 0); context.lineto(0.5, height); context.lineto(-ticksize, height); context.strokestyle = "black"; context.stroke(); context.textalign = "right"; context.textbaseline = "middle"; ticks.foreach(function (d) { context.filltext(tickformat(d), -ticksize - tickpadding, y(d)); }); context.save(); context.rotate(-math.pi / 2); context.textalign = "right"; context.textbaseline = "top"; context.font = "bold 10px sans-serif"; context.filltext("price (us$)", -10, 10); context.restore(); } function getdate(d) { return new date(d.ind); } function daydiff(first, second) { return math.round((second - first) / (1000 * 60 * 60 * 24)); } function getdata() { return [ { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201501, "tmin": 30.43, "tmax": 77.4, "kmin": 0.041, "kmax": 1.364, "ksum": 625.08 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201502, "tmin": 35.3, "tmax": 81.34, "kmin": 0.036, "kmax": 1.401, "ksum": 542.57 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201503, "tmin": 32.58, "tmax": 81.32, "kmin": 0.036, "kmax": 1.325, "ksum": 577.83 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201504, "tmin": 54.54, "tmax": 86.55, "kmin": 0.036, "kmax": 1.587, "ksum": 814.62 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201505, "tmin": 61.35, "tmax": 88.61, "kmin": 0.036, "kmax": 1.988, "ksum": 2429.56 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201506, "tmin": 69.5, "tmax": 92.42, "kmin": 0.037, "kmax": 1.995, "ksum": 2484.93 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201507, "tmin": 71.95, "tmax": 98.62, "kmin": 0.037, "kmax": 1.864, "ksum": 2062.05 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201508, "tmin": 76.13, "tmax": 99.59, "kmin": 0.045, "kmax": 1.977, "ksum": 900.05 }, { "briteid": "bi-43dd32fe-ecbc-48d4-a8dc-e1f66110a542", "ind": 201509, "tmin": 70, "tmax": 91.8, "kmin": 0.034, "kmax": 1.458, "ksum": 401.39 }]; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script> <canvas width="500" height="200"></canvas> wiki
Comments
Post a Comment