Skip to content

Commit

Permalink
New: Add devicePixelRatio render property and handler
Browse files Browse the repository at this point in the history
  • Loading branch information
tonilastre committed Feb 24, 2023
1 parent 4e121b9 commit 1716092
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 29 deletions.
37 changes: 25 additions & 12 deletions docs/view-default.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ interface IOrbViewSettings {
};
// For canvas rendering and events
render: {
devicePixelRatio: number | null;
fps: number;
minZoom: number;
maxZoom: number;
Expand All @@ -73,6 +74,7 @@ interface IOrbViewSettings {
shadowOnEventIsEnabled: boolean;
contextAlphaOnEvent: number;
contextAlphaOnEventIsEnabled: boolean;
areCollapsedContainerDimensionsAllowed: boolean;
};
// For select and hover look-and-feel
strategy: {
Expand All @@ -84,7 +86,6 @@ interface IOrbViewSettings {
isOutOfBoundsDragEnabled: boolean;
areCoordinatesRounded: boolean;
isSimulationAnimated: boolean;
areCollapsedContainerDimensionsAllowed: boolean;
}
```

Expand Down Expand Up @@ -132,6 +133,7 @@ const defaultSettings = {
},
},
render: {
devicePixelRatio: window.devicePixelRatio,
fps: 60,
minZoom: 0.25,
maxZoom: 8,
Expand All @@ -142,6 +144,7 @@ const defaultSettings = {
shadowOnEventIsEnabled: true,
contextAlphaOnEvent: 0.3,
contextAlphaOnEventIsEnabled: true,
areCollapsedContainerDimensionsAllowed: false,
},
strategy: {
isDefaultSelectEnabled: true,
Expand All @@ -151,7 +154,6 @@ const defaultSettings = {
isOutOfBoundsDragEnabled: false,
areCoordinatesRounded: true,
isSimulationAnimated: true,
areCollapsedContainerDimensionsAllowed: false;
}
```

Expand Down Expand Up @@ -187,7 +189,7 @@ const edges: MyEdge[] = [
{ id: 0, start: 0, end: 0, label: "Edge Q" },
{ id: 1, start: 0, end: 1, label: "Edge W" },
{ id: 2, start: 0, end: 2, label: "Edge E" },
{ id: 3, start: 1, end: 2, label: "Edge T" },
{ id: 3, start: 1, end: 2, label: "Edge Tf" },
{ id: 4, start: 2, end: 2, label: "Edge Y" },
{ id: 5, start: 0, end: 1, label: "Edge V" },
];
Expand Down Expand Up @@ -258,6 +260,26 @@ Here you can use your original properties to indicate which ones represent your
Optional property `render` has several rendering options that you can tweak. Read more about them
on [Styling guide](./styles.md).

#### Property `render.devicePixelRatio`

`devicePixelRatio` is useful when dealing with the difference between rendering on a standard
display versus a HiDPI or Retina display, which uses more screen pixels to draw the same
objects, resulting in a sharper image. ([Reference: MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)).
Orb will listen for `devicePixelRatio` changes and handles them by default. You can override the
value with a settings property `render.devicePixelRatio`. Once a custom value is provided, Orb will
stop listening for `devicePixelRatio` changes.
If you want to return automatic `devicePixelRatio` handling, just set `render.devicePixelRatio`
to `null`.

#### Property `render.areCollapsedContainerDimensionsAllowed`

Enables setting the dimensions of the Orb container element to zero.
If the container element of Orb has collapsed dimensions (`width: 0;` or `height: 0;`),
Orb will expand the container by setting the values to `100%`.
If that doesn't work (the parent of the container also has collapsed dimensions),
Orb will set an arbitrary fixed dimension to the container.
Disabled by default (`false`).

### Property `strategy`

The optional property `strategy` has two properties that you can enable/disable:
Expand Down Expand Up @@ -336,15 +358,6 @@ Shows the process of simulation where the nodes are moved by the physics engine
converge to a stable position. If disabled, the graph will suddenly appear in its final position.
Enabled by default (`true`).

### Property `areCollapsedContainerDimensionsAllowed`

Enables setting the dimensions of the Orb container element to zero.
If the container element of Orb has collapsed dimensions (`width: 0;` or `height: 0;`),
Orb will expand the container by setting the values to `100%`.
If that doesn't work (the parent of the container also has collapsed dimensions),
Orb will set an arbitrary fixed dimension to the container.
Disabled by default (`false`).

## Settings

The above settings of the `OrbView` can be defined on view initialization, but also anytime
Expand Down
31 changes: 22 additions & 9 deletions docs/view-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ interface IOrbMapViewSettings {
getGeoPosition(node: INode): { lat: number; lng: number } | undefined;
// For canvas rendering and events
render: {
devicePixelRatio: number | null;
fps: number;
minZoom: number;
maxZoom: number;
Expand Down Expand Up @@ -146,6 +147,7 @@ The default settings that `OrbMapView` uses is:
```typescript
const defaultSettings = {
render: {
devicePixelRatio: window.devicePixelRatio,
fps: 60,
minZoom: 0.25,
maxZoom: 8,
Expand Down Expand Up @@ -189,6 +191,26 @@ Optional property `map` has two properties that you can set which are:
Optional property `render` has several rendering options that you can tweak. Read more about them
on [Styling guide](./styles.md).

#### Property `render.devicePixelRatio`

`devicePixelRatio` is useful when dealing with the difference between rendering on a standard
display versus a HiDPI or Retina display, which uses more screen pixels to draw the same
objects, resulting in a sharper image. ([Reference: MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)).
Orb will listen for `devicePixelRatio` changes and handles them by default. You can override the
value with a settings property `render.devicePixelRatio`. Once a custom value is provided, Orb will
stop listening for `devicePixelRatio` changes.
If you want to return automatic `devicePixelRatio` handling, just set `render.devicePixelRatio`
to `null`.

#### Property `render.areCollapsedContainerDimensionsAllowed`

Enables setting the dimensions of the Orb container element to zero.
If the container element of Orb has collapsed dimensions (`width: 0;` or `height: 0;`),
Orb will expand the container by setting the values to `100%`.
If that doesn't work (the parent of the container also has collapsed dimensions),
Orb will set an arbitrary fixed dimension to the container.
Disabled by default (`false`).

### Property `strategy`

The optional property `strategy` has two properties that you can enable/disable:
Expand Down Expand Up @@ -241,15 +263,6 @@ orb.events.on(OrbEventType.MOUSE_CLICK, (event) => {
});
```

### Property `areCollapsedContainerDimensionsAllowed`

Enables setting the dimensions of the Orb container element to zero.
If the container element of Orb has collapsed dimensions (`width: 0;` or `height: 0;`),
Orb will expand the container by setting the values to `100%`.
If that doesn't work (the parent of the container also has collapsed dimensions),
Orb will set an arbitrary fixed dimension to the container.
Disabled by default (`false`).

## Settings

The above settings of `OrbMapView` can be defined on view initialization, but also anytime after the
Expand Down
41 changes: 38 additions & 3 deletions src/renderer/canvas/canvas-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ import {
import { throttle } from '../../utils/function.utils';
import { getThrottleMsFromFPS } from '../../utils/math.utils';
import { copyObject } from '../../utils/object.utils';
import { appendCanvas, setupContainer } from '../../utils/html.utils';
import {
appendCanvas,
IObserveDPRUnsubscribe,
observeDevicePixelRatioChanges,
setupContainer,
} from '../../utils/html.utils';
import { OrbError } from '../../exceptions';
import { isNumber } from '../../utils/type.utils';

const DEBUG = false;
const DEBUG_RED = '#FF5733';
Expand Down Expand Up @@ -50,12 +56,14 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em
private _isInitiallyRendered = false;

private _throttleRender: (graph: IGraph<N, E>) => void;
private _dprObserveUnsubscribe?: IObserveDPRUnsubscribe;

constructor(container: HTMLElement, settings?: Partial<IRendererSettings>) {
super();
setupContainer(container, settings?.areCollapsedContainerDimensionsAllowed);
this._container = container;
this._canvas = appendCanvas(container);

const context = this._canvas.getContext('2d');
if (!context) {
throw new OrbError('Failed to create Canvas context.');
Expand All @@ -75,6 +83,10 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em
resizeObs.observe(this._container);
this._resize();

if (!isNumber(settings?.devicePixelRatio)) {
this._dprObserveUnsubscribe = observeDevicePixelRatioChanges(() => this._resize());
}

this._throttleRender = throttle((graph: IGraph<N, E>) => {
this._render(graph);
}, getThrottleMsFromFPS(this._settings.fps));
Expand Down Expand Up @@ -106,6 +118,9 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em

setSettings(settings: Partial<IRendererSettings>) {
const isFpsChanged = settings.fps && settings.fps !== this._settings.fps;
const previousDprValue = this._settings.devicePixelRatio;
const newDprValue = settings.devicePixelRatio;

this._settings = {
...this._settings,
...settings,
Expand All @@ -116,6 +131,17 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em
this._render(graph);
}, getThrottleMsFromFPS(this._settings.fps));
}

// Change DPR from automatic to manual handling or change DPR value manually
if (!isNumber(previousDprValue) && isNumber(newDprValue) && newDprValue !== previousDprValue) {
this._dprObserveUnsubscribe?.();
this._resize();
}

// Change DPR from manual to automatic handling
if (isNumber(previousDprValue) && newDprValue === null) {
this._dprObserveUnsubscribe = observeDevicePixelRatioChanges(() => this._resize());
}
}

render(graph: IGraph<N, E>) {
Expand Down Expand Up @@ -226,9 +252,17 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em
}

private _resize() {
const dpr = this._settings.devicePixelRatio || window.devicePixelRatio;

const containerSize = this._container.getBoundingClientRect();
this._canvas.width = containerSize.width;
this._canvas.height = containerSize.height;
this._canvas.style.width = `${containerSize.width}px`;
this._canvas.style.height = `${containerSize.height}px`;
this._canvas.width = containerSize.width * dpr;
this._canvas.height = containerSize.height * dpr;

// Normalize coordinate system to use CSS pixels
this._context.scale(dpr, dpr);

this._width = containerSize.width;
this._height = containerSize.height;
this.emit(RenderEventType.RESIZE, undefined);
Expand Down Expand Up @@ -317,6 +351,7 @@ export class CanvasRenderer<N extends INodeBase, E extends IEdgeBase> extends Em
}

destroy(): void {
this._dprObserveUnsubscribe?.();
this.removeAllListeners();
this._canvas.outerHTML = '';
}
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum RenderEventType {
}

export interface IRendererSettings {
devicePixelRatio: number | null;
fps: number;
minZoom: number;
maxZoom: number;
Expand Down Expand Up @@ -53,15 +54,12 @@ export interface IRenderer<N extends INodeBase, E extends IEdgeBase> extends IEm
get isInitiallyRendered(): boolean;

getSettings(): IRendererSettings;

setSettings(settings: Partial<IRendererSettings>): void;

render(graph: IGraph<N, E>): void;

reset(): void;

getFitZoomTransform(graph: IGraph<N, E>): ZoomTransform;

getSimulationPosition(canvasPoint: IPosition): IPosition;

/**
Expand All @@ -77,6 +75,7 @@ export interface IRenderer<N extends INodeBase, E extends IEdgeBase> extends IEm
}

export const DEFAULT_RENDERER_SETTINGS: IRendererSettings = {
devicePixelRatio: null,
fps: 60,
minZoom: 0.25,
maxZoom: 8,
Expand Down
29 changes: 27 additions & 2 deletions src/utils/html.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const setupContainer = (container: HTMLElement, areCollapsedDimensionsAll
"[Orb] The graph container element and its parent don't have defined width properties.",
'If you are using percentage values,',
'please make sure that the parent element of the graph container has a defined position and width.',
"Setting the width of the graph container to an arbirtrary value of '400px'...",
"Setting the width of the graph container to an arbitrary value of '400px'...",
);
} else {
console.warn("[Orb] The graph container element doesn't have defined width. Setting width to 100%...");
Expand Down Expand Up @@ -54,7 +54,32 @@ export const appendCanvas = (container: HTMLElement): HTMLCanvasElement => {
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';

container.appendChild(canvas);
return canvas;
};

export type IObserveDPRCallback = (devicePixelRatio: number) => void;
export type IObserveDPRUnsubscribe = () => void;

export const observeDevicePixelRatioChanges = (callback: IObserveDPRCallback): IObserveDPRUnsubscribe => {
let currentDpr = window.devicePixelRatio;
let unsubscribe: IObserveDPRUnsubscribe = () => {
return;
};

const listenForDPRChanges = () => {
unsubscribe();

const media = matchMedia(`(resolution: ${currentDpr}dppx)`);
media.addEventListener('change', listenForDPRChanges);
unsubscribe = () => media.removeEventListener('change', listenForDPRChanges);

if (window.devicePixelRatio !== currentDpr) {
currentDpr = window.devicePixelRatio;
callback(currentDpr);
}
};

listenForDPRChanges();
return () => unsubscribe();
};

0 comments on commit 1716092

Please sign in to comment.