naver / billboard.js

📊 Re-usable, easy interface JavaScript chart library based on D3.js
https://naver.github.io/billboard.js/
MIT License
5.84k stars 353 forks source link

Chart.zoom() does not always zoom to specified range #2269

Open arajcok opened 3 years ago

arajcok commented 3 years ago

Description

The zoom API does not always zoom to the specified range. It is close, but sometimes the first data point lies outside the graph window (to the left of the y-axis).

Steps to check or reproduce

var chart = bb.generate({
  data: {
    columns: [
    ["sample", 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 150, 250, 150, 200, 170, 240, 100, 150, 250, 150, 200, 170, 240, 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 350, 220, 250, 300, 270, 140, 150, 90, 150, 50, 120, 70, 40]
    ],
    type: "line", // for ESM specify as: line()
  },
  zoom: {
    enabled: true, // for ESM specify as: zoom()
  },
  clipPath: false,
  bindto: "#zoom"
});

setTimeout(() => chart.zoom([12, 18]), 1000)

The above code produces the following (after the timeout). Note how the first data point is outside the graph window: image

This can have unintended side-effects with zooming when you try to add rescaling, as can be seen with the below timeseries chart:

var chart = bb.generate({
  data: {
    x: "x",
    columns: [
    ["x", "2013-01-01", "2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05", "2013-01-06", "2013-01-07", "2013-01-08", "2013-01-09","2013-01-10","2013-01-11","2013-01-12","2013-01-13","2013-01-14","2013-01-15", "2013-01-16","2013-01-17","2013-01-18","2013-01-19","2013-01-20"],
    ["data1", 10, 20, 30, 40, 50, 60, 70, 90, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 2000],
    ],
    type: "line", // for ESM specify as: line()
  },
  axis: {
    x: {
      type: "timeseries",
      tick: {
        format: "%Y-%m-%d"
      }
    }
  },
  clipPath: false,
  zoom: { 
    enabled: true,
    rescale: true
  },
  bindto: "#timeseriesChart"
});

setTimeout(function() {
    chart.zoom(["2013-01-03", "2013-01-05"]);
}, 1000);

image

netil commented 3 years ago

Hi @arajcok, is because of clipPath: false. clipPath option will make chart shape to not being clipped. Remove clipPath option.

arajcok commented 3 years ago

Having the clipPath was to show how the leftmost point (the first value for the zoom range) is outside the graph window (to the left of the y-axis). Removing the clipPath does not seem to fix the issue. For example, the timeseries chart, the first data point (at time 2013-01-03) is not visible on the chart. image

netil commented 3 years ago

to mitigate this, set axis.y.min value.

axis: {
    y: {
       min: 0
    }
}
arajcok commented 3 years ago

Setting axis.y.min = 0 does not seem to mitigate the issue, as the data point still lies outside the graph window occasionally. Also, I am not sure what effect setting the axis.y.min = 0 would have for graphs that have negative values.

arajcok commented 3 years ago

I think is due to the fact that the zoom range isn't finite, and instead approximates to the range specified.

// for ESM environment, need to import modules as:
// import bb, {line, zoom} from "billboard.js"

var chart = bb.generate({
  data: {
    columns: [
    ["sample", 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 6500]
    ],
    type: "area-spline", // for ESM specify as: line()
  },
  spline: {
    interpolation: {
      type: "monotone-x"
    }
  },
  clipPath: false, // to see the point to the left of the y-axis
  zoom: {
    enabled: true, // for ESM specify as: zoom()
  },
  bindto: "#zoom"
});
setTimeout(() => {
  chart.zoom([8, 11]);
}, 1000);
setTimeout(() => {
  // prints [8.000794459336127, 11.00068966529944]
  console.log(chart.zoom());
}, 2000);
setTimeout(() => {
  chart.zoom([9, 11]);
}, 3000);
setTimeout(() => {
  // prints [9.009057993290602, 11.00068966529944]
  console.log(chart.zoom());
}, 4000);

First zoom: image

Second zoom: image