I'm trying to use the zoomCallback
function to set up interaction between my dygrpahs chart and a map chart. My x values are timestamps in seconds but since the sample rate is about 100Hz the timestamps are stored as float numbers.
The goal is that when dygraphs chart is zoomed in, the new x1
and x2
will be used to extract a piece of GPS track (lat, lng points). The extracted track will be used to re-fit the map boundaries - this will look like a "zoom in" on the map chart.
In my dygraphs options I specified the callback:
zoomCallback: function(x1,x2) {
let x1Index = graphHolder.getRowForX(x1);
let x2Index = graphHolder.getRowForX(x2);
// further code
}
But it looks like the zoom is not "snapped" to existing timestamp points so both x1Index
and x2Index
are null. Only when I zoom out, they'll correctly point to row 0 and the last row of data.
So the question is - is there a way to make the zoom snap only to the nearest existing x value so the row number can be returned? Or, is there an alternative to do what I want?
Thanks for any insights!
You can access the x-axis values via g.getValue(row, 0)
. From this you can either do a linear scan to find the first row in the range or (fancier but faster) use a binary search.
Here's a way to do the linear scan:
const [x1, x2] = g.xAxisRange();
let letRow = null, highRow = null;
for (let i = 0; i < g.numRows(); i++) {
if (g.getValue(i, 0) >= x1) {
lowRow = i;
break;
}
}
for (let i = g.numRows() - 1; i >= 0; i--) {
if (g.getValue(i, 0) <= x2) {
highRow = i;
break;
}
}
const dataX1 = g.getValue(lowRow, 0);
const dataX2 = g.getValue(highRow, 0);
For larger data sets you might want to do a binary search using something like lodash's _.sortedIndex
.
Update Here's a binary search implementation. No promises about the exact behavior on the boundaries (i.e. whether it always returns indices that are inside the visible range or indices which contain the visible range).
function dygraphBinarySearch(g, x) {
let low = 0;
let high = g.numRows() - 1;
while (high > low) {
let i = Math.floor(low + (high - low) / 2);
const xi = g.getValue(i, 0);
if (xi < x) {
low = i + 1;
} else if (xi > x) {
high = i - 1;
} else {
return i;
}
}
return low;
}
function getVisibleDataRange(g) {
const [x1, x2] = g.xAxisRange();
let lowI = dygraphBinarySearch(g, x1);
let highI = dygraphBinarySearch(g, x2);
return [lowI, highI];
}