Skip to content

Commit ca04f50

Browse files
authored
feat(fit): allow custom zoom level limits (#1134)
They are optional and default to the same values as before (that is no breaking change), they are limited to values above 0 (zero would lead to infinities, NaNs etc. and I have no idea what negative zoom should mean) and max can't be lower than min. There's visual regression test included.
1 parent 02eb2cf commit ca04f50

File tree

8 files changed

+206
-41
lines changed

8 files changed

+206
-41
lines changed

__snapshots__/test/package.test.ts.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ exports['Package Exported files 1'] = {
5757
" declarations/network/modules/selection/index.d.ts.map",
5858
" declarations/network/modules/selection/selection-accumulator.d.ts",
5959
" declarations/network/modules/selection/selection-accumulator.d.ts.map",
60+
" declarations/network/modules/view-handler/index.d.ts",
61+
" declarations/network/modules/view-handler/index.d.ts.map",
6062
" declarations/network/options.d.ts",
6163
" declarations/network/shapes.d.ts",
6264
" declarations/network/shapes.d.ts.map",
@@ -129,6 +131,8 @@ exports['Package Exported files 1'] = {
129131
" dist/types/network/modules/selection/index.d.ts.map",
130132
" dist/types/network/modules/selection/selection-accumulator.d.ts",
131133
" dist/types/network/modules/selection/selection-accumulator.d.ts.map",
134+
" dist/types/network/modules/view-handler/index.d.ts",
135+
" dist/types/network/modules/view-handler/index.d.ts.map",
132136
" dist/types/network/options.d.ts",
133137
" dist/types/network/shapes.d.ts",
134138
" dist/types/network/shapes.d.ts.map",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
context("Fit", (): void => {
2+
beforeEach((): void => {
3+
cy.visVisitUniversal(
4+
{
5+
nodes: [
6+
{ id: 1, label: "Node 1" },
7+
{ id: 2, label: "Node 2" },
8+
{ id: 3, label: "Node 3" },
9+
{ id: 4, label: "Node 4" },
10+
{ id: 5, label: "Node 5" },
11+
],
12+
edges: [
13+
{ from: 1, to: 3 },
14+
{ from: 1, to: 2 },
15+
{ from: 2, to: 4 },
16+
{ from: 2, to: 5 },
17+
{ from: 3, to: 3 },
18+
],
19+
},
20+
{ requireNewerVersionThan: "8.4.2" }
21+
);
22+
});
23+
24+
it("No params", (): void => {
25+
cy.visRun(({ network }): void => {
26+
network.fit();
27+
});
28+
cy.visSnapshotOpenedPage("no-params", { moveTo: false });
29+
});
30+
31+
it("High max", (): void => {
32+
cy.visRun(({ network }): void => {
33+
network.fit({
34+
maxZoomLevel: 20,
35+
});
36+
});
37+
cy.visSnapshotOpenedPage("high-max", { moveTo: false });
38+
});
39+
40+
it("Low max", (): void => {
41+
cy.visRun(({ network }): void => {
42+
network.fit({
43+
maxZoomLevel: 0.2,
44+
});
45+
});
46+
cy.visSnapshotOpenedPage("low-max", { moveTo: false });
47+
});
48+
49+
it("Impossible to fit min and max", (): void => {
50+
cy.visRun(({ network }): void => {
51+
network.fit({
52+
minZoomLevel: 5,
53+
maxZoomLevel: 10,
54+
});
55+
});
56+
cy.visSnapshotOpenedPage("impossible-to-fit-min-and-max", {
57+
moveTo: false,
58+
});
59+
});
60+
61+
it("One node", (): void => {
62+
cy.visRun(({ network }): void => {
63+
network.fit({ nodes: [3] });
64+
});
65+
cy.visSnapshotOpenedPage("one-node", { moveTo: false });
66+
});
67+
68+
it("One node with custom limits", (): void => {
69+
cy.visRun(({ network }): void => {
70+
network.fit({
71+
nodes: [3],
72+
minZoomLevel: 6,
73+
maxZoomLevel: 15,
74+
});
75+
});
76+
cy.visSnapshotOpenedPage("one-node-with-custom-limits", { moveTo: false });
77+
});
78+
});

cypress/support/commands/vis-snapshot-opened-page.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,33 @@ declare global {
2020
}
2121

2222
export interface VisSnapshotOpenedPageOptions extends VisVisitPageOptions {
23-
moveTo?: {
24-
position?: { x?: number; y?: number };
25-
scale?: number;
26-
};
23+
moveTo?:
24+
| false
25+
| {
26+
position?: { x?: number; y?: number };
27+
scale?: number;
28+
};
2729
}
2830

2931
export function visSnapshotOpenedPage(
3032
label: number | string,
3133
options: VisSnapshotOpenedPageOptions = {}
3234
): void {
33-
cy.visRun(({ network }): void => {
34-
network.moveTo(
35-
deepObjectAssign<MoveToOptions>(
36-
{
37-
position: { x: 0, y: 0 },
38-
scale: 1,
39-
},
40-
options.moveTo ?? {}
41-
)
42-
);
43-
});
35+
const moveTo = options.moveTo;
36+
if (moveTo !== false) {
37+
cy.visRun(({ network }): void => {
38+
network.moveTo(
39+
deepObjectAssign<MoveToOptions>(
40+
{
41+
position: { x: 0, y: 0 },
42+
scale: 1,
43+
},
44+
moveTo ?? {}
45+
)
46+
);
47+
});
48+
}
49+
4450
cy.get("#mynetwork canvas").compareSnapshot(
4551
typeof label === "string" ? label : ("" + label).padStart(3, "0")
4652
);

docs-kr/network/index.html

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,16 +1888,21 @@ <h2 id="methods">메소드</h2>
18881888
옵션을 제공 할 수 있습니다:
18891889
<pre class="code">
18901890
{
1891-
nodes:[Array of nodeIds],
1892-
animation: { // -------------------> can be a boolean too!
1893-
duration: Number
1894-
easingFunction: String
1891+
nodes?: (string | number)[],
1892+
minZoomLevel?: number,
1893+
maxZoomLevel?: number,
1894+
animation?: boolean | {
1895+
duration?: number
1896+
easingFunction?: string
18951897
}
18961898
}
18971899
</pre
18981900
>
18991901
Node를 사용하여 뷰의 특정 Node에만 맞도록 확대 / 축소 할 수
1900-
있습니다. <br /><br />
1902+
있습니다.<br />
1903+
최소 및 최대 확대 / 축소 수준을 사용하여 주어진 수준 내에서 맞춤을
1904+
제한 할 수 있습니다. 기본값은 0 (포함되지 않음)에서
1905+
1까지입니다.<br /><br />
19011906
다른 옵션들은 <code>moveTo()</code>에 자세히 설명되어 있습니다.
19021907
모든 방법은 적합한 방법에 대한 선택 사항입니다.
19031908
</td>

docs/network/index.html

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,19 +1975,23 @@ <h2 id="methods">Methods</h2>
19751975
to customize this:
19761976
<pre class="code">
19771977
{
1978-
nodes:[Array of nodeIds],
1979-
animation: { // -------------------> can be a boolean too!
1980-
duration: Number
1981-
easingFunction: String
1978+
nodes?: (string | number)[],
1979+
minZoomLevel?: number,
1980+
maxZoomLevel?: number,
1981+
animation?: boolean | {
1982+
duration?: number
1983+
easingFunction?: string
19821984
}
19831985
}
19841986
</pre
19851987
>
19861988
The nodes can be used to zoom to fit only specific nodes in the
1987-
view. <br /><br />
1988-
The other options are explained in the
1989-
<code>moveTo()</code> description below. All options are optional
1990-
for the fit method.
1989+
view.<br />
1990+
The min and max zoom levels can be used to constrain the fit
1991+
within given levels. The default is from zero (not included)
1992+
through one.<br /><br />
1993+
The other options are explained in the <code>moveTo()</code>
1994+
description below. All options are optional for the fit method.
19911995
</td>
19921996
</tr>
19931997
<tr

lib/network/modules/View.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { easingFunctions } from "vis-util/esnext";
22

33
import NetworkUtil from "../NetworkUtil";
4+
import { normalizeFitOptions } from "./view-handler";
45

56
/**
67
* The view
@@ -48,17 +49,14 @@ class View {
4849
* @param {object} [options={{nodes=Array}}]
4950
* @param {boolean} [initialZoom=false] | zoom based on fitted formula or range, true = fitted, default = false;
5051
*/
51-
fit(options = { nodes: [] }, initialZoom = false) {
52-
let range;
53-
let zoomLevel;
54-
options = Object.assign({}, options);
55-
if (options.nodes === undefined || options.nodes.length === 0) {
56-
options.nodes = this.body.nodeIndices;
57-
}
52+
fit(options, initialZoom = false) {
53+
options = normalizeFitOptions(options, this.body.nodeIndices);
5854

5955
const canvasWidth = this.canvas.frame.canvas.clientWidth;
6056
const canvasHeight = this.canvas.frame.canvas.clientHeight;
6157

58+
let range;
59+
let zoomLevel;
6260
if (canvasWidth === 0 || canvasHeight === 0) {
6361
// There's no point in trying to fit into zero sized canvas. This could
6462
// potentially even result in invalid values being computed. For example
@@ -105,10 +103,10 @@ class View {
105103
zoomLevel = xZoomLevel <= yZoomLevel ? xZoomLevel : yZoomLevel;
106104
}
107105

108-
if (zoomLevel > 1.0) {
109-
zoomLevel = 1.0;
110-
} else if (zoomLevel === 0) {
111-
zoomLevel = 1.0;
106+
if (zoomLevel > options.maxZoomLevel) {
107+
zoomLevel = options.maxZoomLevel;
108+
} else if (zoomLevel < options.minZoomLevel) {
109+
zoomLevel = options.minZoomLevel;
112110
}
113111

114112
const center = NetworkUtil.findCenter(range);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
type IdType = string | number;
2+
3+
export interface ViewFitOptions {
4+
nodes: IdType[];
5+
minZoomLevel: number;
6+
maxZoomLevel: number;
7+
}
8+
9+
/**
10+
* Validate the fit options, replace missing optional values by defaults etc.
11+
*
12+
* @param rawOptions - The raw options.
13+
* @param allNodeIds - All node ids that will be used if nodes are omitted in
14+
* the raw options.
15+
*
16+
* @returns Options with everything filled in and validated.
17+
*/
18+
export function normalizeFitOptions(
19+
rawOptions: Partial<ViewFitOptions>,
20+
allNodeIds: IdType[]
21+
): ViewFitOptions {
22+
const options = Object.assign<ViewFitOptions, Partial<ViewFitOptions>>(
23+
{
24+
nodes: allNodeIds,
25+
minZoomLevel: Number.MIN_VALUE,
26+
maxZoomLevel: 1,
27+
},
28+
rawOptions ?? {}
29+
);
30+
31+
if (!Array.isArray(options.nodes)) {
32+
throw new TypeError("Nodes has to be an array of ids.");
33+
}
34+
if (options.nodes.length === 0) {
35+
options.nodes = allNodeIds;
36+
}
37+
38+
if (!(typeof options.minZoomLevel === "number" && options.minZoomLevel > 0)) {
39+
throw new TypeError("Min zoom level has to be a number higher than zero.");
40+
}
41+
42+
if (
43+
!(
44+
typeof options.maxZoomLevel === "number" &&
45+
options.minZoomLevel <= options.maxZoomLevel
46+
)
47+
) {
48+
throw new TypeError(
49+
"Max zoom level has to be a number higher than min zoom level."
50+
);
51+
}
52+
53+
return options;
54+
}

types/network/Network.d.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,13 +642,29 @@ export interface FitOptions {
642642
/**
643643
* The nodes can be used to zoom to fit only specific nodes in the view.
644644
*/
645-
nodes?: string[];
645+
nodes?: IdType[];
646+
647+
/**
648+
* How far away can be zoomed out, the default is just above 0.
649+
*
650+
* @remarks
651+
* Values less than 1 mean zooming out, more than 1 means zooming in.
652+
*/
653+
minZoomLevel?: number;
654+
655+
/**
656+
* How close can be zoomed in, the default is 1.
657+
*
658+
* @remarks
659+
* Values less than 1 mean zooming out, more than 1 means zooming in.
660+
*/
661+
maxZoomLevel?: number;
646662

647663
/**
648664
* For animation you can either use a Boolean to use it with the default options or
649665
* disable it or you can define the duration (in milliseconds) and easing function manually.
650666
*/
651-
animation: TimelineAnimationType;
667+
animation?: TimelineAnimationType;
652668
}
653669

654670
export interface SelectionOptions {

0 commit comments

Comments
 (0)