diff --git a/demos/index.html b/demos/index.html
index 9939bcc9..b35dcf1e 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -70,7 +70,8 @@
Drawing
Zooming
Different zoom variants (adaptive, uni/omnidirectional)
Fetch & update data on zoom
- Secondary sync'd overview chart for zoom ranging
+ Secondary sync'd overview chart for x-axis zoom ranging
+ Secondary sync'd overview chart for x/y-axis zoom ranging
Pinch zooming/panning plugin
Mouswheel zooming plugin
diff --git a/demos/zoom-ranger-xy.html b/demos/zoom-ranger-xy.html
new file mode 100644
index 00000000..fda3b289
--- /dev/null
+++ b/demos/zoom-ranger-xy.html
@@ -0,0 +1,123 @@
+
+
+
+
+ Zoom Ranger XY
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/zoom-ranger.html b/demos/zoom-ranger.html
index d61b0b03..c1048c45 100644
--- a/demos/zoom-ranger.html
+++ b/demos/zoom-ranger.html
@@ -34,13 +34,11 @@
let initXmin = 1;
let initXmax = 4.5;
- let viaRanger = false;
- let viaZoom = false;
-
const rangerOpts = {
width: 800,
height: 100,
cursor: {
+ y: false,
points: {
show: false,
},
@@ -49,6 +47,9 @@
x: true,
y: false,
},
+ sync: {
+ key: "moo"
+ }
},
legend: {
show: false
@@ -65,22 +66,11 @@
}
],
hooks: {
- setSelect: [
- uRanger => {
- if (!viaZoom) {
- viaRanger = true;
- let min = uRanger.posToVal(uRanger.select.left, 'x');
- let max = uRanger.posToVal(uRanger.select.left + uRanger.select.width, 'x');
- uZoomed.setScale('x', {min, max});
- viaRanger = false;
- }
- }
- ],
ready: [
uRanger => {
let left = Math.round(uRanger.valToPos(initXmin, 'x'));
let width = Math.round(uRanger.valToPos(initXmax, 'x')) - left;
- let height = uRanger.root.querySelector(".over").getBoundingClientRect().height;
+ let height = uRanger.bbox.height / devicePixelRatio;
uRanger.setSelect({left, width, height}, false);
}
]
@@ -93,6 +83,15 @@
// title: "Zoomed Area",
width: 800,
height: 400,
+ cursor: {
+ drag: {
+ x: true,
+ y: false
+ },
+ sync: {
+ key: "moo"
+ }
+ },
scales: {
x: {
time: false,
@@ -108,20 +107,7 @@
label: "sin(x)",
stroke: "red",
}
- ],
- hooks: {
- setScale: [
- (uZoomed, key) => {
- if (key == 'x' && !viaRanger) {
- viaZoom = true;
- let left = Math.round(uRanger.valToPos(uZoomed.scales.x.min, 'x'));
- let right = Math.round(uRanger.valToPos(uZoomed.scales.x.max, 'x'));
- uRanger.setSelect({left, width: right - left});
- viaZoom = false;
- }
- }
- ]
- }
+ ]
};
let uZoomed = new uPlot(zoomedOpts, data, document.body);
diff --git a/dist/uPlot.d.ts b/dist/uPlot.d.ts
index e1d98f2a..864f65f6 100644
--- a/dist/uPlot.d.ts
+++ b/dist/uPlot.d.ts
@@ -115,6 +115,8 @@ declare class uPlot {
declare namespace uPlot {
export type AlignedData = readonly (number | null)[][];
+ export type SyncScales = [string, string];
+
export type MinMax = [number, number];
export interface DateNames {
@@ -245,6 +247,8 @@ declare namespace uPlot {
key: string;
/** determines if series toggling and focus via cursor is synced across charts */
setSeries?: boolean; // true
+ /** sets the x and y scales to sync by values. null will sync by relative (%) position */
+ scales?: SyncScales; // [xScaleKey, null]
};
/** focus series closest to cursor */
diff --git a/src/uPlot.js b/src/uPlot.js
index cbb25e94..ccb22cd5 100644
--- a/src/uPlot.js
+++ b/src/uPlot.js
@@ -1343,7 +1343,7 @@ export default function uPlot(opts, data, then) {
const drag = FEAT_CURSOR && cursor.drag;
let dragX = FEAT_CURSOR && drag.x;
- let dragY = FEAT_CURSOR && drag.y;;
+ let dragY = FEAT_CURSOR && drag.y;
if (FEAT_CURSOR && cursor.show) {
let c = "cursor-";
@@ -1477,8 +1477,14 @@ export default function uPlot(opts, data, then) {
}
function scaleValueAtPos(pos, scale) {
- let dim = scale == xScaleKey ? plotWidCss : plotHgtCss;
- let pct = clamp(pos / dim, 0, 1);
+ let dim = plotWidCss;
+ if (scale != xScaleKey) {
+ dim = plotHgtCss;
+ // invert the pos on the y axis
+ pos = dim - pos;
+ }
+
+ let pct = pos / dim;
let sc = scales[scale];
let d = sc.max - sc.min;
@@ -1492,7 +1498,7 @@ export default function uPlot(opts, data, then) {
self.valToIdx = val => closestIdx(val, data[0]);
self.posToIdx = closestIdxFromXpos;
- self.posToVal = (pos, scale) => scaleValueAtPos(scale == xScaleKey ? pos : plotHgtCss - pos, scale);
+ self.posToVal = scaleValueAtPos;
self.valToPos = (val, scale, can) => (
scale == xScaleKey ?
getXPos(val, scales[scale],
@@ -1532,7 +1538,7 @@ export default function uPlot(opts, data, then) {
let cursorRaf = 0;
- function updateCursor(ts) {
+ function updateCursor(ts, src) {
if (inBatch) {
shouldUpdateCursor = true;
return;
@@ -1612,51 +1618,86 @@ export default function uPlot(opts, data, then) {
}
// nit: cursor.drag.setSelect is assumed always true
- if (mouseLeft1 >= 0 && select.show && dragging) {
- // setSelect should not be triggered on move events
-
- dragX = drag.x;
- dragY = drag.y;
+ if (select.show && dragging) {
+ if (src != null) {
+ let [xKey, yKey] = syncOpts.scales;
- let uni = drag.uni;
+ if (xKey) {
+ let sc = scales[xKey];
+ let srcLeft = src.posToVal(src.select[LEFT], xKey);
+ let srcRight = src.posToVal(src.select[LEFT] + src.select[WIDTH], xKey);
- if (uni != null) {
- let dx = abs(mouseLeft0 - mouseLeft1);
- let dy = abs(mouseTop0 - mouseTop1);
+ select[LEFT] = getXPos(srcLeft, sc, plotWidCss, 0);
+ select[WIDTH] = abs(select[LEFT] - getXPos(srcRight, sc, plotWidCss, 0));
- dragX = dx >= uni;
- dragY = dy >= uni;
+ setStylePx(selectDiv, LEFT, select[LEFT]);
+ setStylePx(selectDiv, WIDTH, select[WIDTH]);
- // force unidirectionality when both are under uni limit
- if (!dragX && !dragY) {
- if (dy > dx)
- dragY = true;
- else
- dragX = true;
+ if (!yKey) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
}
- }
- if (dragX) {
- let minX = min(mouseLeft0, mouseLeft1);
- let maxX = max(mouseLeft0, mouseLeft1);
- setStylePx(selectDiv, LEFT, select[LEFT] = minX);
- setStylePx(selectDiv, WIDTH, select[WIDTH] = maxX - minX);
+ if (yKey) {
+ let sc = scales[yKey];
+ let srcTop = src.posToVal(src.select[TOP], yKey);
+ let srcBottom = src.posToVal(src.select[TOP] + src.select[HEIGHT], yKey);
- if (uni != null && !dragY) {
- setStylePx(selectDiv, TOP, select[TOP] = 0);
- setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
- }
- }
+ select[TOP] = getYPos(srcTop, sc, plotHgtCss, 0);
+ select[HEIGHT] = abs(select[TOP] - getYPos(srcBottom, sc, plotHgtCss, 0));
+
+ setStylePx(selectDiv, TOP, select[TOP]);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT]);
- if (dragY) {
- let minY = min(mouseTop0, mouseTop1);
- let maxY = max(mouseTop0, mouseTop1);
- setStylePx(selectDiv, TOP, select[TOP] = minY);
- setStylePx(selectDiv, HEIGHT, select[HEIGHT] = maxY - minY);
+ if (!xKey) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
- if (uni != null && !dragX) {
- setStylePx(selectDiv, LEFT, select[LEFT] = 0);
- setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ } else {
+ // setSelect should not be triggered on move events
+ let uni = drag.uni;
+
+ if (uni != null) {
+ let dx = abs(mouseLeft0 - mouseLeft1);
+ let dy = abs(mouseTop0 - mouseTop1);
+
+ dragX = dx >= uni;
+ dragY = dy >= uni;
+
+ // force unidirectionality when both are under uni limit
+ if (!dragX && !dragY) {
+ if (dy > dx)
+ dragY = true;
+ else
+ dragX = true;
+ }
+ }
+
+ if (dragX) {
+ let minX = min(mouseLeft0, mouseLeft1);
+ let maxX = max(mouseLeft0, mouseLeft1);
+ setStylePx(selectDiv, LEFT, select[LEFT] = minX);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = maxX - minX);
+
+ if (!dragY) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (dragY) {
+ let minY = min(mouseTop0, mouseTop1);
+ let maxY = max(mouseTop0, mouseTop1);
+ setStylePx(selectDiv, TOP, select[TOP] = minY);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = maxY - minY);
+
+ if (!dragX) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
}
}
}
@@ -1707,17 +1748,32 @@ export default function uPlot(opts, data, then) {
cursorRaf = rAF(updateCursor);
}
else
- updateCursor();
+ updateCursor(null, src);
}
function cacheMouse(e, src, _x, _y, _w, _h, _i, initial, snap) {
+ if (_x < 0 || _y < 0) {
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+ return;
+ }
+
if (e != null) {
_x = e.clientX - rect.left;
_y = e.clientY - rect.top;
}
else {
- _x = plotWidCss * (_x/_w);
- _y = plotHgtCss * (_y/_h);
+ let [xKey, yKey] = syncOpts.scales;
+
+ if (xKey != null)
+ _x = getXPos(src.posToVal(_x, xKey), scales[xKey], plotWidCss, 0);
+ else
+ _x = plotWidCss * (_x/_w);
+
+ if (yKey != null)
+ _y = getYPos(src.posToVal(_y, yKey), scales[yKey], plotHgtCss, 0);
+ else
+ _y = plotHgtCss * (_y/_h);
}
if (snap) {
@@ -1740,19 +1796,16 @@ export default function uPlot(opts, data, then) {
function hideSelect() {
setSelect({
- width: !drag.x ? plotWidCss : 0,
- height: !drag.y ? plotHgtCss : 0,
+ width: 0,
+ height: 0,
}, false);
}
function mouseDown(e, src, _x, _y, _w, _h, _i) {
- if (e == null || filtMouse(e)) {
+ if (src != null || filtMouse(e)) {
dragging = true;
- cacheMouse(e, src, _x, _y, _w, _h, _i, true, true);
-
- if (select.show && (drag.x || drag.y))
- hideSelect();
+ cacheMouse(e, src, _x, _y, _w, _h, _i, true, false);
if (e != null) {
on(mouseup, doc, mouseUp);
@@ -1762,51 +1815,54 @@ export default function uPlot(opts, data, then) {
}
function mouseUp(e, src, _x, _y, _w, _h, _i) {
- if ((e == null || filtMouse(e))) {
+ if (src != null || filtMouse(e)) {
dragging = false;
cacheMouse(e, src, _x, _y, _w, _h, _i, false, true);
+ setSelect(select);
- if (mouseLeft1 != mouseLeft0 || mouseTop1 != mouseTop0) {
- setSelect(select);
+ if (drag.setScale && (select[WIDTH] || select[HEIGHT])) {
- if (drag.setScale) {
- batch(() => {
- if (dragX) {
- _setScale(xScaleKey,
- scaleValueAtPos(select[LEFT], xScaleKey),
- scaleValueAtPos(select[LEFT] + select[WIDTH], xScaleKey),
- );
- }
+ if (syncKey != null) {
+ dragX = drag.x;
+ dragY = drag.y;
+ }
+
+ batch(() => {
+ if (dragX) {
+ _setScale(xScaleKey,
+ scaleValueAtPos(select[LEFT], xScaleKey),
+ scaleValueAtPos(select[LEFT] + select[WIDTH], xScaleKey)
+ );
+ }
- if (dragY) {
- for (let k in scales) {
- let sc = scales[k];
+ if (dragY) {
+ for (let k in scales) {
+ let sc = scales[k];
- if (k != xScaleKey && sc.from == null) {
- _setScale(k,
- scaleValueAtPos(plotHgtCss - select[TOP] - select[HEIGHT], k),
- scaleValueAtPos(plotHgtCss - select[TOP], k),
- );
- }
+ if (k != xScaleKey && sc.from == null) {
+ _setScale(k,
+ scaleValueAtPos(select[TOP] + select[HEIGHT], k),
+ scaleValueAtPos(select[TOP], k)
+ );
}
}
- });
+ }
+ });
- hideSelect();
- }
+ hideSelect();
}
else if (cursor.lock) {
- cursor.locked = !cursor.locked
+ cursor.locked = !cursor.locked;
if (!cursor.locked)
updateCursor();
}
+ }
- if (e != null) {
- off(mouseup, doc, mouseUp);
- sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
- }
+ if (e != null) {
+ off(mouseup, doc, mouseUp);
+ sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
}
}
@@ -1821,6 +1877,18 @@ export default function uPlot(opts, data, then) {
function dblClick(e, src, _x, _y, _w, _h, _i) {
autoScaleX();
+
+ if (src != null && select.show && (drag.x || drag.y)) {
+ if (drag.setScale)
+ hideSelect();
+ else
+ setSelect({
+ [LEFT]: 0,
+ [WIDTH]: plotWidCss,
+ [TOP]: 0,
+ [HEIGHT]: plotHgtCss
+ });
+ }
if (e != null)
sync.pub(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
@@ -1873,6 +1941,7 @@ export default function uPlot(opts, data, then) {
const syncOpts = FEAT_CURSOR && assign({
key: null,
setSeries: false,
+ scales: [xScaleKey, null]
}, cursor.sync);
const syncKey = FEAT_CURSOR && syncOpts.key;
@@ -1936,4 +2005,4 @@ uPlot.rangeNum = rangeNum;
if (FEAT_TIME) {
uPlot.fmtDate = fmtDate;
uPlot.tzDate = tzDate;
-}
\ No newline at end of file
+}