Skip to content

Commit

Permalink
ui-spacetimechart: fixes picking with dpr<1
Browse files Browse the repository at this point in the history
This commit fixes #915.

Details:
- Cleans unused and poorly named "number" argument from drawAliasedLine
- Adds a new optional "devicePixelRatio" argument to each canvas aliased
  drawing function
- Adds new devicePixelRatio argument to PickingDrawingFunction, and
  updates useCanvas accordingly
- Gives dpr from PickingDrawingFunction to aliased functions in every
  places where usePicking is called

Signed-off-by: Alexis Jacomy <[email protected]>
  • Loading branch information
jacomyal committed Feb 25, 2025
1 parent fe7272c commit 8a9f9b8
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 30 deletions.
5 changes: 3 additions & 2 deletions ui-spacetimechart/src/components/ConflictLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const ConflictLayer = ({ conflicts }: ConflictLayerProps) => {
useDraw('paths', drawConflictLayer);

const drawPicking = useCallback<PickingDrawingFunction>(
(imageData, { registerPickingElement, getTimePixel, getSpacePixel }) => {
(imageData, { registerPickingElement, getTimePixel, getSpacePixel }, dpr) => {
for (const [conflictIndex, conflict] of conflicts.entries()) {
const x = getTimePixel(conflict.timeStart);
const y = getSpacePixel(conflict.spaceStart);
Expand All @@ -66,7 +66,8 @@ export const ConflictLayer = ({ conflicts }: ConflictLayerProps) => {
{ x: x - border, y: y - border },
width + 2 * border,
height + 2 * border,
color
color,
dpr
);
}
},
Expand Down
8 changes: 5 additions & 3 deletions ui-spacetimechart/src/components/PathLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export const PathLayer = ({
useDraw('paths', drawAll);

const drawPicking = useCallback<PickingDrawingFunction>(
(imageData, stcContext) => {
(imageData, stcContext, dpr) => {
const { registerPickingElement } = stcContext;

// Draw segments:
Expand All @@ -413,7 +413,8 @@ export const PathLayer = ({
point,
lineColor,
STYLES[level].width + pickingTolerance,
true
true,
dpr
);
}
});
Expand All @@ -431,7 +432,8 @@ export const PathLayer = ({
point,
(STYLES[level].width + pickingTolerance) * 2,
lineColor,
false
false,
dpr
);
});
},
Expand Down
13 changes: 10 additions & 3 deletions ui-spacetimechart/src/hooks/useCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r

import { isEqual } from 'lodash';

import { useDevicePixelRatio } from './useDevicePixelRatio';
import { getDevicePixelRatio, useDevicePixelRatio } from './useDevicePixelRatio';
import { useSize } from './useSize';
import { CanvasContext } from '../lib/context';
import {
Expand Down Expand Up @@ -78,7 +78,8 @@ export function useCanvas(
ctx.clearRect(0, 0, width, height);

const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
set.forEach((fn) => fn(imageData, stcContext));
const dpr = getDevicePixelRatio();
set.forEach((fn) => fn(imageData, stcContext, dpr));
ctx.putImageData(imageData, 0, 0);
}
});
Expand Down Expand Up @@ -249,10 +250,16 @@ export function useCanvas(
// Read picking layer on position change:
useEffect(() => {
let newHoveredItem: HoveredItem | null = null;
const dpr = getDevicePixelRatio();
PICKING_LAYERS.some((layer) => {
const ctx = contextsRef.current[`${PICKING}-${layer}`];
if (ctx && position) {
const [r, g, b, a] = ctx.getImageData(position.x, position.y, 1, 1).data;
const [r, g, b, a] = ctx.getImageData(
Math.round(position.x * dpr),
Math.round(position.y * dpr),
1,
1
).data;
if (a === 255) {
const color = rgbToHex(r, g, b);
const index = colorToIndex(color);
Expand Down
8 changes: 6 additions & 2 deletions ui-spacetimechart/src/hooks/useDevicePixelRatio.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useEffect, useState } from 'react';

export function getDevicePixelRatio() {
return window.devicePixelRatio || 1;
}

export function useDevicePixelRatio() {
const [ratio, setRatio] = useState(window.devicePixelRatio || 1);
const [ratio, setRatio] = useState(getDevicePixelRatio());

useEffect(() => {
if (!window.matchMedia || !window.devicePixelRatio) {
return undefined;
}

const handleChange = () => {
setRatio(window.devicePixelRatio);
setRatio(getDevicePixelRatio());
};

const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
Expand Down
3 changes: 2 additions & 1 deletion ui-spacetimechart/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export type DrawingFunction = (

export type PickingDrawingFunction = (
imageData: ImageData,
stcContext: SpaceTimeChartContextType
stcContext: SpaceTimeChartContextType,
dpr: number
) => void;

export type DrawingFunctionHandler = (
Expand Down
65 changes: 46 additions & 19 deletions ui-spacetimechart/src/utils/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,61 @@ import { type PathEnd, type Point, type RGBAColor, type RGBColor } from '../lib/
*/
export function drawAliasedLine(
imageData: ImageData,
from: Point,
to: Point,
{ x: fromX, y: fromY }: Point,
{ x: toX, y: toY }: Point,
[r, g, b]: RGBColor | RGBAColor,
thickness: number,
drawOnBottom: boolean,
number: number = Math.ceil(thickness / 2)
devicePixelRatio = 1
): void {
if (from.x > to.x)
return drawAliasedLine(imageData, to, from, [r, g, b], thickness, drawOnBottom);
if (fromX > toX)
return drawAliasedLine(
imageData,
{ x: toX, y: toY },
{ x: fromX, y: fromY },
[r, g, b],
thickness,
drawOnBottom
);

// Handle resolution:
fromX = Math.round(fromX * devicePixelRatio);
fromY = Math.round(fromY * devicePixelRatio);
toX = Math.round(toX * devicePixelRatio);
toY = Math.round(toY * devicePixelRatio);
thickness = Math.round(thickness * devicePixelRatio);

const width = imageData.width;
const height = imageData.height;
const dx = to.x - from.x;
const dy = to.y - from.y;
const dx = toX - fromX;
const dy = toY - fromY;
const len = Math.sqrt(dx * dx + dy * dy);

// Calculate perpendicular vector
const normX = -dy / len;
const normY = dx / len;

// Calculate the four corners of the rectangle
const halfThickness = number;
const halfThickness = Math.ceil(thickness / 2);

const corner1 = {
x: from.x + (+normX - dx / len) * halfThickness,
y: from.y + (+normY - dy / len) * halfThickness,
x: fromX + (+normX - dx / len) * halfThickness,
y: fromY + (+normY - dy / len) * halfThickness,
};
const corner2 = {
x: from.x + (-normX - dx / len) * halfThickness,
y: from.y + (-normY - dy / len) * halfThickness,
x: fromX + (-normX - dx / len) * halfThickness,
y: fromY + (-normY - dy / len) * halfThickness,
};
const corner3 = {
x: to.x + (-normX + dx / len) * halfThickness,
y: to.y + (-normY + dy / len) * halfThickness,
x: toX + (-normX + dx / len) * halfThickness,
y: toY + (-normY + dy / len) * halfThickness,
};
const corner4 = {
x: to.x + (+normX + dx / len) * halfThickness,
y: to.y + (+normY + dy / len) * halfThickness,
x: toX + (+normX + dx / len) * halfThickness,
y: toY + (+normY + dy / len) * halfThickness,
};

const ascending = from.y < to.y;
const ascending = fromY < toY;
const top = ascending ? corner4 : corner1;
const left = ascending ? corner1 : corner2;
const right = ascending ? corner3 : corner4;
Expand Down Expand Up @@ -139,8 +153,14 @@ export function drawAliasedDisc(
{ x: centerX, y: centerY }: Point,
radius: number,
[r, g, b]: RGBColor | RGBAColor,
drawOnBottom: boolean
drawOnBottom: boolean,
devicePixelRatio: number = 1
): void {
// Handle resolution:
centerX = Math.round(centerX * devicePixelRatio);
centerY = Math.round(centerY * devicePixelRatio);
radius = Math.round(radius * devicePixelRatio);

centerX = Math.round(centerX);
centerY = Math.round(centerY);
radius = Math.ceil(radius);
Expand Down Expand Up @@ -176,8 +196,15 @@ export function drawAliasedRect(
{ x, y }: Point,
width: number,
height: number,
[r, g, b]: RGBColor | RGBAColor
[r, g, b]: RGBColor | RGBAColor,
devicePixelRatio = 1
) {
// Handle resolution:
x = Math.round(x * devicePixelRatio);
y = Math.round(y * devicePixelRatio);
width = Math.round(width * devicePixelRatio);
height = Math.round(height * devicePixelRatio);

const xMin = clamp(x, 0, imageData.width);
const yMin = clamp(y, 0, imageData.height);
const xMax = clamp(x + width, 0, imageData.width);
Expand Down

0 comments on commit 8a9f9b8

Please sign in to comment.