Skip to content

Commit

Permalink
feat(fit): allow custom zoom level limits (#1134)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Thomaash committed Oct 25, 2020
1 parent 02eb2cf commit ca04f50
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 41 deletions.
4 changes: 4 additions & 0 deletions __snapshots__/test/package.test.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ exports['Package Exported files 1'] = {
" declarations/network/modules/selection/index.d.ts.map",
" declarations/network/modules/selection/selection-accumulator.d.ts",
" declarations/network/modules/selection/selection-accumulator.d.ts.map",
" declarations/network/modules/view-handler/index.d.ts",
" declarations/network/modules/view-handler/index.d.ts.map",
" declarations/network/options.d.ts",
" declarations/network/shapes.d.ts",
" declarations/network/shapes.d.ts.map",
Expand Down Expand Up @@ -129,6 +131,8 @@ exports['Package Exported files 1'] = {
" dist/types/network/modules/selection/index.d.ts.map",
" dist/types/network/modules/selection/selection-accumulator.d.ts",
" dist/types/network/modules/selection/selection-accumulator.d.ts.map",
" dist/types/network/modules/view-handler/index.d.ts",
" dist/types/network/modules/view-handler/index.d.ts.map",
" dist/types/network/options.d.ts",
" dist/types/network/shapes.d.ts",
" dist/types/network/shapes.d.ts.map",
Expand Down
78 changes: 78 additions & 0 deletions cypress/integration/visual/fit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
context("Fit", (): void => {
beforeEach((): void => {
cy.visVisitUniversal(
{
nodes: [
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" },
],
edges: [
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 },
],
},
{ requireNewerVersionThan: "8.4.2" }
);
});

it("No params", (): void => {
cy.visRun(({ network }): void => {
network.fit();
});
cy.visSnapshotOpenedPage("no-params", { moveTo: false });
});

it("High max", (): void => {
cy.visRun(({ network }): void => {
network.fit({
maxZoomLevel: 20,
});
});
cy.visSnapshotOpenedPage("high-max", { moveTo: false });
});

it("Low max", (): void => {
cy.visRun(({ network }): void => {
network.fit({
maxZoomLevel: 0.2,
});
});
cy.visSnapshotOpenedPage("low-max", { moveTo: false });
});

it("Impossible to fit min and max", (): void => {
cy.visRun(({ network }): void => {
network.fit({
minZoomLevel: 5,
maxZoomLevel: 10,
});
});
cy.visSnapshotOpenedPage("impossible-to-fit-min-and-max", {
moveTo: false,
});
});

it("One node", (): void => {
cy.visRun(({ network }): void => {
network.fit({ nodes: [3] });
});
cy.visSnapshotOpenedPage("one-node", { moveTo: false });
});

it("One node with custom limits", (): void => {
cy.visRun(({ network }): void => {
network.fit({
nodes: [3],
minZoomLevel: 6,
maxZoomLevel: 15,
});
});
cy.visSnapshotOpenedPage("one-node-with-custom-limits", { moveTo: false });
});
});
36 changes: 21 additions & 15 deletions cypress/support/commands/vis-snapshot-opened-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,33 @@ declare global {
}

export interface VisSnapshotOpenedPageOptions extends VisVisitPageOptions {
moveTo?: {
position?: { x?: number; y?: number };
scale?: number;
};
moveTo?:
| false
| {
position?: { x?: number; y?: number };
scale?: number;
};
}

export function visSnapshotOpenedPage(
label: number | string,
options: VisSnapshotOpenedPageOptions = {}
): void {
cy.visRun(({ network }): void => {
network.moveTo(
deepObjectAssign<MoveToOptions>(
{
position: { x: 0, y: 0 },
scale: 1,
},
options.moveTo ?? {}
)
);
});
const moveTo = options.moveTo;
if (moveTo !== false) {
cy.visRun(({ network }): void => {
network.moveTo(
deepObjectAssign<MoveToOptions>(
{
position: { x: 0, y: 0 },
scale: 1,
},
moveTo ?? {}
)
);
});
}

cy.get("#mynetwork canvas").compareSnapshot(
typeof label === "string" ? label : ("" + label).padStart(3, "0")
);
Expand Down
15 changes: 10 additions & 5 deletions docs-kr/network/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1888,16 +1888,21 @@ <h2 id="methods">메소드</h2>
옵션을 제공 할 수 있습니다:
<pre class="code">
{
nodes:[Array of nodeIds],
animation: { // -------------------> can be a boolean too!
duration: Number
easingFunction: String
nodes?: (string | number)[],
minZoomLevel?: number,
maxZoomLevel?: number,
animation?: boolean | {
duration?: number
easingFunction?: string
}
}
</pre
>
Node를 사용하여 뷰의 특정 Node에만 맞도록 확대 / 축소 할 수
있습니다. <br /><br />
있습니다.<br />
최소 및 최대 확대 / 축소 수준을 사용하여 주어진 수준 내에서 맞춤을
제한 할 수 있습니다. 기본값은 0 (포함되지 않음)에서
1까지입니다.<br /><br />
다른 옵션들은 <code>moveTo()</code>에 자세히 설명되어 있습니다.
모든 방법은 적합한 방법에 대한 선택 사항입니다.
</td>
Expand Down
20 changes: 12 additions & 8 deletions docs/network/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1975,19 +1975,23 @@ <h2 id="methods">Methods</h2>
to customize this:
<pre class="code">
{
nodes:[Array of nodeIds],
animation: { // -------------------> can be a boolean too!
duration: Number
easingFunction: String
nodes?: (string | number)[],
minZoomLevel?: number,
maxZoomLevel?: number,
animation?: boolean | {
duration?: number
easingFunction?: string
}
}
</pre
>
The nodes can be used to zoom to fit only specific nodes in the
view. <br /><br />
The other options are explained in the
<code>moveTo()</code> description below. All options are optional
for the fit method.
view.<br />
The min and max zoom levels can be used to constrain the fit
within given levels. The default is from zero (not included)
through one.<br /><br />
The other options are explained in the <code>moveTo()</code>
description below. All options are optional for the fit method.
</td>
</tr>
<tr
Expand Down
20 changes: 9 additions & 11 deletions lib/network/modules/View.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { easingFunctions } from "vis-util/esnext";

import NetworkUtil from "../NetworkUtil";
import { normalizeFitOptions } from "./view-handler";

/**
* The view
Expand Down Expand Up @@ -48,17 +49,14 @@ class View {
* @param {object} [options={{nodes=Array}}]
* @param {boolean} [initialZoom=false] | zoom based on fitted formula or range, true = fitted, default = false;
*/
fit(options = { nodes: [] }, initialZoom = false) {
let range;
let zoomLevel;
options = Object.assign({}, options);
if (options.nodes === undefined || options.nodes.length === 0) {
options.nodes = this.body.nodeIndices;
}
fit(options, initialZoom = false) {
options = normalizeFitOptions(options, this.body.nodeIndices);

const canvasWidth = this.canvas.frame.canvas.clientWidth;
const canvasHeight = this.canvas.frame.canvas.clientHeight;

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

if (zoomLevel > 1.0) {
zoomLevel = 1.0;
} else if (zoomLevel === 0) {
zoomLevel = 1.0;
if (zoomLevel > options.maxZoomLevel) {
zoomLevel = options.maxZoomLevel;
} else if (zoomLevel < options.minZoomLevel) {
zoomLevel = options.minZoomLevel;
}

const center = NetworkUtil.findCenter(range);
Expand Down
54 changes: 54 additions & 0 deletions lib/network/modules/view-handler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
type IdType = string | number;

export interface ViewFitOptions {
nodes: IdType[];
minZoomLevel: number;
maxZoomLevel: number;
}

/**
* Validate the fit options, replace missing optional values by defaults etc.
*
* @param rawOptions - The raw options.
* @param allNodeIds - All node ids that will be used if nodes are omitted in
* the raw options.
*
* @returns Options with everything filled in and validated.
*/
export function normalizeFitOptions(
rawOptions: Partial<ViewFitOptions>,
allNodeIds: IdType[]
): ViewFitOptions {
const options = Object.assign<ViewFitOptions, Partial<ViewFitOptions>>(
{
nodes: allNodeIds,
minZoomLevel: Number.MIN_VALUE,
maxZoomLevel: 1,
},
rawOptions ?? {}
);

if (!Array.isArray(options.nodes)) {
throw new TypeError("Nodes has to be an array of ids.");
}
if (options.nodes.length === 0) {
options.nodes = allNodeIds;
}

if (!(typeof options.minZoomLevel === "number" && options.minZoomLevel > 0)) {
throw new TypeError("Min zoom level has to be a number higher than zero.");
}

if (
!(
typeof options.maxZoomLevel === "number" &&
options.minZoomLevel <= options.maxZoomLevel
)
) {
throw new TypeError(
"Max zoom level has to be a number higher than min zoom level."
);
}

return options;
}
20 changes: 18 additions & 2 deletions types/network/Network.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,13 +642,29 @@ export interface FitOptions {
/**
* The nodes can be used to zoom to fit only specific nodes in the view.
*/
nodes?: string[];
nodes?: IdType[];

/**
* How far away can be zoomed out, the default is just above 0.
*
* @remarks
* Values less than 1 mean zooming out, more than 1 means zooming in.
*/
minZoomLevel?: number;

/**
* How close can be zoomed in, the default is 1.
*
* @remarks
* Values less than 1 mean zooming out, more than 1 means zooming in.
*/
maxZoomLevel?: number;

/**
* For animation you can either use a Boolean to use it with the default options or
* disable it or you can define the duration (in milliseconds) and easing function manually.
*/
animation: TimelineAnimationType;
animation?: TimelineAnimationType;
}

export interface SelectionOptions {
Expand Down

0 comments on commit ca04f50

Please sign in to comment.