Skip to content

Commit

Permalink
fix(zoom): Prevent error for out of range
Browse files Browse the repository at this point in the history
- When given zoom-in range isn't handled, do not perform next steps
- Initialize drag zoom related event binding when only 'drag' type is specified

Fix #3895

Co-authored-by: netil <[email protected]>
  • Loading branch information
netil and netil authored Oct 14, 2024
1 parent 3d4392a commit 6f69e97
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 7 deletions.
8 changes: 5 additions & 3 deletions src/ChartInternal/Axis/AxisRendererHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ export default class AxisRendererHelper {
value => `translate(0,${value})`;

return (selection, scale) => {
selection.attr("transform", d => (
isValue(d) ? fn(Math.ceil(scale(d))) : null
));
selection.attr("transform", d => {
const x = scale(d);

return isValue(d) ? fn(Math.ceil(x)) : null;
});
};
}

Expand Down
14 changes: 11 additions & 3 deletions src/ChartInternal/interactions/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export default {
$$.scale.zoom = null;

$$.generateZoom();
$$.initZoomBehaviour();

$$.config.zoom_type === "drag" &&
$$.initZoomBehaviour();
},

/**
Expand Down Expand Up @@ -72,6 +74,7 @@ export default {
const ratio = diffDomain($$.scale.x.orgDomain()) / diffDomain($$.getZoomDomain());
const extent = this.orgScaleExtent();

// https://d3js.org/d3-zoom#zoom_scaleExtent
this.scaleExtent([extent[0] * ratio, extent[1] * ratio]);

return this;
Expand All @@ -96,6 +99,11 @@ export default {
isRotated ? "rescaleY" : "rescaleX"
](org.xScale || scale.x);

// prevent drag zoom to be out of range
if (newScale.domain().some(v => /(Invalid Date|NaN)/.test(v.toString()))) {
return;
}

const domain = $$.trimXDomain(newScale.domain());
const rescale = config.zoom_rescale;

Expand Down Expand Up @@ -381,7 +389,7 @@ export default {
.attr("height", isRotated ? 0 : state.height);
}

start = getPointer(event, <SVGElement>this)[prop.index];
start = getPointer(event, this as SVGAElement)[prop.index];
end = start;

zoomRect
Expand All @@ -391,7 +399,7 @@ export default {
$$.onZoomStart(event);
})
.on("drag", function(event) {
end = getPointer(event, <SVGElement>this)[prop.index];
end = getPointer(event, this as SVGAElement)[prop.index];

zoomRect
.attr(prop.axis, Math.min(start, end))
Expand Down
77 changes: 77 additions & 0 deletions test/api/zoom-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,4 +632,81 @@ describe("API zoom", function() {
expect($$.scale.zoom).to.be.null;
});
});

describe("zoom extent", () => {
beforeAll(() => {
chart = util.generate({
data: {
json: [
{"date":"2023-09-30 00:00:00","ek_house":0},
{"date":"2023-10-14 00:00:00","ek_house":0},
{"date":"2023-10-21 00:00:00","ek_house":0},
{"date":"2023-10-28 00:00:00","ek_house":0},
{"date":"2023-11-04 00:00:00","ek_house":0},
],
keys: {
x: "date",
value: ["ek_house"],
},
},
axis: {
x: {
type: "timeseries",
tick: {
format: "%Y-%m-%d"
},
},
y: {
show: false
}
},
zoom: {
enabled: true
}
});
});

it("shouldn't throw error for timeseries x axis, when is given out of range.", () => new Promise(done => {
chart.zoom([1697701666380, 1697702008724]);

setTimeout(() => {
chart.$.circles.each(function() {
expect(this.getAttribute("cx") !== "NaN").to.be.true;
});

done(1);
}, 300);
}));

it("shouldn't throw error for indexed x axis, when is given out of range.", () => new Promise(done => {
chart = util.generate({
data: {
columns: [
["data2", 130, 100, 140, 200, 150, 50, 120, 100, 80, 90]
],
},
zoom: {
enabled: true
},
axis: {
y: {
show: false
}
}
});

chart.zoom([
4.908784864317814,
4.908812566017803
]);

setTimeout(() => {
chart.$.circles.each(function() {
expect(this.getAttribute("cx") !== "NaN").to.be.true;
});

done(1);
}, 300);
}));
});
});
2 changes: 1 addition & 1 deletion test/internals/bb-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ describe("Interface & initialization", () => {
}));

it("check lazy rendering on callbacks", () => new Promise(done => {
const el = <HTMLDivElement>document.body.querySelector("#chart");
const el = document.body.querySelector("#chart") as HTMLDivElement;

// hide to lazy render
el.style.display = "none";
Expand Down

0 comments on commit 6f69e97

Please sign in to comment.