Skip to content

Commit

Permalink
Merge pull request #107 from center-for-threat-informed-defense/AF-18…
Browse files Browse the repository at this point in the history
…0_account_for_device_pixel_ratio

AF-180: Account for Device Pixel Ratio when Rendering Graphics
  • Loading branch information
mikecarenzo committed Aug 30, 2023
2 parents 781d683 + 6691ac4 commit ceb448a
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import * as d3 from "d3";
import { Browser } from "../../Browser";
import { RasterCache } from "./RasterCache";
import { ViewportRegion } from "./ViewportRegion";
import { CameraLocation } from "./Camera";
import { DiagramObjectMover } from "./DiagramObjectMover";
import { DiagramDisplaySettings } from "./DiagramDisplaySettings";
import { CameraLocation } from "./Camera";
import {
resizeAndTransformContext,
resizeContext,
transformContext } from "../Utilities/Canvas";
import {
EventEmitter,
MouseClick
Expand All @@ -28,7 +33,7 @@ import {
PositionSetByUser
} from "../Attributes";

export class BlockDiagram extends EventEmitter {
export class BlockDiagram extends EventEmitter<DiagramEvents> {

/**
* The viewport's padding.
Expand Down Expand Up @@ -173,20 +178,24 @@ export class BlockDiagram extends EventEmitter {
this._canvas = d3.select(container)
.append("canvas")
.attr("style", "display:block;")
.attr("width", this._elWidth)
.attr("height", this._elHeight)
.on("mousemove", (event) => {
this.onHoverSubject(...d3.pointer(event));
})
.on("contextmenu", (e: any) => e.preventDefault());
this._context = this._canvas.node()!.getContext("2d", { alpha: false });

// Size context
resizeContext(this._context!, this._elWidth, this._elHeight);

// Configure resize observer
this._resizeObserver = new ResizeObserver(
entries => this.onCanvasResize(entries[0].target)
);
this._resizeObserver.observe(container);

// Configure dppx change behavior
Browser.on("dppx-change", this.onDevicePixelRatioChange, this);

// Configure canvas interactions
this._canvas
.call(d3.drag<HTMLCanvasElement, unknown>()
Expand All @@ -208,61 +217,12 @@ export class BlockDiagram extends EventEmitter {
this._context = null;
this.removeAllListeners();
this._resizeObserver?.disconnect();
Browser.removeEventListenersWithContext(this);
}


///////////////////////////////////////////////////////////////////////////
// 2. Event Subscription ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


/**
* Adds an event listener to the diagram.
* @param event
* The event to subscribe to.
* @param callback
* The function to call once the event has fired.
*/
public override on<K extends keyof DiagramEvents>(event: K, callback: DiagramEvents[K]): void {
super.on(event, callback);
}

/**
* Adds an event listener to the diagram that will be fired once and then
* removed.
* @param event
* The event to subscribe to.
* @param callback
* The function to call once the event has fired.
*/
public override once<K extends keyof DiagramEvents>(event: K, callback: DiagramEvents[K]): void {
super.once(event, callback);
}

/**
* Removes all event listeners associated with a given event. If no event
* name is specified, all event listeners are removed.
* @param event
* The name of the event.
*/
public override removeAllListeners<K extends keyof DiagramEvents>(event?: K): void {
super.removeAllListeners(event);
}

/**
* Dispatches the event listeners associated with a given event.
* @param event
* The name of the event to raise.
* @param args
* The arguments to pass to the event listeners.
*/
protected override emit<K extends keyof DiagramEvents>(event: K, ...args: Parameters<DiagramEvents[K]>): void {
super.emit(event, ...args);
}


///////////////////////////////////////////////////////////////////////////
// 3. Rendering /////////////////////////////////////////////////////////
// 2. Rendering /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


Expand Down Expand Up @@ -361,7 +321,7 @@ export class BlockDiagram extends EventEmitter {


///////////////////////////////////////////////////////////////////////////
// 4. Canvas Interactions ///////////////////////////////////////////////
// 3. Canvas Interactions ///////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


Expand Down Expand Up @@ -622,10 +582,12 @@ export class BlockDiagram extends EventEmitter {
this._transform = event.transform;
// Update viewport
this.updateViewportBounds();
this._context?.setTransform(
this._transform.k, 0, 0,
this._transform.k, this._transform.x, this._transform.y
);
if(this._context) {
transformContext(
this._context, this._transform.k,
this._transform.x, this._transform.y
);
}
// If no source event, then we are already
// running inside a requestAnimationFrame()
if(event.sourceEvent === null) {
Expand Down Expand Up @@ -665,21 +627,42 @@ export class BlockDiagram extends EventEmitter {
// Update dimensions
this._elWidth = newWidth;
this._elHeight = newHeight;
this._canvas
?.attr("width", this._elWidth)
?.attr("height", this._elHeight);
// Update viewport
this.updateViewportBounds();
// Adjust viewport
this._context?.setTransform(
this._transform.k, 0, 0,
this._transform.k, this._transform.x, this._transform.y
);
if(this._context) {
resizeAndTransformContext(
this._context, this._elWidth, this._elHeight,
this._transform.k, this._transform.x, this._transform.y
)
}
// Immediately redraw diagram to context, if possible
if(this._context)
this.executeRenderPipeline();
}

/**
* Device pixel ratio change behavior.
* @remarks
* The device's pixel ratio can change when dragging the window to and
* from a monitor with high pixel density (like Apple Retina displays).
*/
private onDevicePixelRatioChange() {
// Update cache
let k = this._transform.k * this._display.ssaaScale;
this._rasterCache.setScale(k);
if(!this._context) {
return;
}
// Resize and transform context
resizeAndTransformContext(
this._context, this._elWidth, this._elHeight,
this._transform.k, this._transform.x, this._transform.y
)
// Render
this.render();
}

/**
* Recalculates the viewport's bounds based on the container's current
* dimensions.
Expand All @@ -696,7 +679,7 @@ export class BlockDiagram extends EventEmitter {


///////////////////////////////////////////////////////////////////////////
// 5. Data //////////////////////////////////////////////////////////////
// 4. Data //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class RasterCache {
* Creates a new {@link RasterCache}.
*/
constructor() {
this._scale = 1;
this._scale = window.devicePixelRatio;
this._cache = new Map();
}

Expand Down Expand Up @@ -74,7 +74,7 @@ export class RasterCache {
* The new scale value.
*/
public setScale(scale: number) {
this._scale = scale;
this._scale = scale * window.devicePixelRatio;
this._cache.clear();
}

Expand All @@ -84,7 +84,7 @@ export class RasterCache {
* The cache's current scale.
*/
public getScale(): number {
return this._scale;
return this._scale / window.devicePixelRatio;
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Resizes a canvas context according to the current screen's pixel ratio.
* @param context
* The context to resize.
* @param width
* The new width of the context.
* @param height
* The new height of the context.
*/
export function resizeContext(
context: CanvasRenderingContext2D,
width: number,
height: number
) {
context.canvas.width = width * window.devicePixelRatio;
context.canvas.height = height * window.devicePixelRatio;
context.canvas.style.width = `${ width }px`;
context.canvas.style.height = `${ height }px`;
context.scale(window.devicePixelRatio, window.devicePixelRatio);
}

/**
* Transforms a canvas context according to the current screen's pixel ratio.
* @param context
* The context to resize.
* @param k
* The context's scale.
* @param x
* The context's x translation.
* @param y
* The context's y translation.
*/
export function transformContext(
context: CanvasRenderingContext2D,
k: number,
x: number,
y: number
) {
k *= window.devicePixelRatio,
x *= window.devicePixelRatio,
y *= window.devicePixelRatio;
context.setTransform(
k, 0, 0,
k, x, y
);
}

/**
* Resizes and transforms a canvas context according to the current screen's
* pixel ratio.
* @param context
* The context to resize.
* @param width
* The new width of the context.
* @param height
* The new height of the context.
* @param k
* The context's scale.
* @param x
* The context's x translation.
* @param y
* The context's y translation.
*/
export function resizeAndTransformContext(
context: CanvasRenderingContext2D,
width: number,
height: number,
k: number,
x: number,
y: number
) {
context.canvas.width = width * window.devicePixelRatio;
context.canvas.height = height * window.devicePixelRatio;
context.canvas.style.width = `${ width }px`;
context.canvas.style.height = `${ height }px`;
transformContext(context, k, x, y);
}
Loading

0 comments on commit ceb448a

Please sign in to comment.