Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui-charts: make picking elements modular #963

Merged
merged 4 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions ui-charts/src/spaceTimeChart/components/ConflictLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from 'react';

import { useDraw, usePicking } from '../hooks/useCanvas';
import { type DrawingFunction, type PickingDrawingFunction } from '../lib/types';
import type { DrawingFunction, PickingDrawingFunction, PickingElement } from '../lib/types';
import { drawAliasedRect } from '../utils/canvas';
import { indexToColor, hexToRgb } from '../utils/colors';

Expand All @@ -12,6 +12,17 @@ export type Conflict = {
spaceEnd: number;
};

export type ConflictPickingElement = PickingElement & {
type: 'conflict';
conflictIndex: number;
};

export function isConflictPickingElement(
element: PickingElement
): element is ConflictPickingElement {
return element.type === 'conflict';
}

export type ConflictLayerProps = {
conflicts: Conflict[];
};
Expand Down Expand Up @@ -58,7 +69,8 @@ export const ConflictLayer = ({ conflicts }: ConflictLayerProps) => {
const height = getSpacePixel(conflict.spaceEnd) - y;
const border = BORDERS[0].size;

const index = registerPickingElement({ type: 'conflict', conflictIndex });
const pickingElement: ConflictPickingElement = { type: 'conflict', conflictIndex };
const index = registerPickingElement(pickingElement);
const color = hexToRgb(indexToColor(index));

drawAliasedRect(
Expand Down
28 changes: 24 additions & 4 deletions ui-charts/src/spaceTimeChart/components/PathLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type OperationalPoint,
type PathData,
type PickingDrawingFunction,
type PickingElement,
type Point,
type SpaceTimeChartContextType,
} from '../lib/types';
Expand All @@ -28,6 +29,23 @@ const DEFAULT_PICKING_TOLERANCE = 5;
const PAUSE_THICKNESS = 7;
const PAUSE_OPACITY = 0.2;

export type PointPickingElement = PickingElement & { type: 'point'; pathId: string; point: Point };

export type SegmentPickingElement = PickingElement & {
type: 'segment';
pathId: string;
from: Point;
to: Point;
};

export function isPointPickingElement(element: PickingElement): element is PointPickingElement {
return element.type === 'point';
}

export function isSegmentPickingElement(element: PickingElement): element is SegmentPickingElement {
return element.type === 'segment';
}

type PathStyle = {
width: number;
endWidth: number;
Expand Down Expand Up @@ -396,12 +414,13 @@ export const PathLayer = ({
getPathSegments(stcContext).forEach((point, i, a) => {
if (i) {
const previousPoint = a[i - 1];
const index = registerPickingElement({
const pickingElement: SegmentPickingElement = {
type: 'segment',
pathId: path.id,
from: previousPoint,
to: point,
});
};
const index = registerPickingElement(pickingElement);
const lineColor = hexToRgb(indexToColor(index));
drawAliasedLine(
imageData,
Expand All @@ -416,11 +435,12 @@ export const PathLayer = ({

// Draw snap points:
getSnapPoints(stcContext).forEach((point) => {
const index = registerPickingElement({
const pickingElement: PointPickingElement = {
type: 'point',
pathId: path.id,
point,
});
};
const index = registerPickingElement(pickingElement);
const lineColor = hexToRgb(indexToColor(index));
drawAliasedDisc(
imageData,
Expand Down
5 changes: 1 addition & 4 deletions ui-charts/src/spaceTimeChart/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,7 @@ export const LAYERS = ['background', 'graduations', 'paths', 'overlay', 'caption
export type LayerType = (typeof LAYERS)[number];

// PICKING SPECIFIC TYPES:
export type PickingElement =
| { type: 'point'; pathId: string; point: Point }
| { type: 'segment'; pathId: string; from: Point; to: Point }
| { type: 'conflict'; conflictIndex: number };
export type PickingElement = { type: string };
export type HoveredItem = { layer: PickingLayerType; element: PickingElement };

export type DrawingFunction = (
Expand Down
3 changes: 2 additions & 1 deletion ui-charts/src/spaceTimeChart/stories/layers.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
OccupancyBlockLayer,
SpaceTimeChart,
PathLayer,
isConflictPickingElement,
} from '..';
import { OPERATIONAL_POINTS, PATHS, START_DATE } from './lib/paths';
import { X_ZOOM_LEVEL, Y_ZOOM_LEVEL } from './lib/utils';
Expand Down Expand Up @@ -123,7 +124,7 @@ const Wrapper = () => {
yOffset={0}
onHoveredChildUpdate={({ item }) => {
let conflict = null;
if (item?.element?.type === 'conflict') {
if (item && isConflictPickingElement(item.element)) {
conflict = CONFLICTS[item.element.conflictIndex];
}
setHoveredConflict(conflict);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta } from '@storybook/react';
import cx from 'classnames';
import { keyBy } from 'lodash';

import { SpaceTimeChart, PathLayer } from '..';
import { SpaceTimeChart, PathLayer, isPointPickingElement, isSegmentPickingElement } from '..';
import { OPERATIONAL_POINTS, PATHS } from './lib/paths';
import { X_ZOOM_LEVEL, Y_ZOOM_LEVEL, zoom } from './lib/utils';
import { type PathData, type Point } from '../lib/types';
Expand Down Expand Up @@ -115,7 +115,10 @@ const Wrapper = ({
}
}}
onHoveredChildUpdate={({ item }) => {
const hoveredPathId = item && 'pathId' in item.element ? item.element.pathId : null;
const hoveredPathId =
item && (isPointPickingElement(item.element) || isSegmentPickingElement(item.element))
? item.element.pathId
: null;
setState((prev) => ({ ...prev, hoveredPathId }));
}}
onPan={({ initialPosition, position, initialData, data, isPanning }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta } from '@storybook/react';
import cx from 'classnames';
import { keyBy } from 'lodash';

import { SpaceTimeChart, PathLayer } from '..';
import { SpaceTimeChart, PathLayer, isPointPickingElement, isSegmentPickingElement } from '..';
import { OPERATIONAL_POINTS, PATHS } from './lib/paths';
import { X_ZOOM_LEVEL, Y_ZOOM_LEVEL, zoom } from './lib/utils';
import { type Point } from '../lib/types';
Expand Down Expand Up @@ -79,7 +79,10 @@ const Wrapper = ({ spaceScaleType }: WrapperProps) => {
}));
}}
onHoveredChildUpdate={({ item }) => {
const hoveredPathId = item && 'pathId' in item.element ? item.element.pathId : null;
const hoveredPathId =
item && (isPointPickingElement(item.element) || isSegmentPickingElement(item.element))
? item.element.pathId
: null;
setState((s) => ({ ...s, hoveredPathId }));
}}
onPan={({ initialPosition, position, isPanning }) => {
Expand Down
22 changes: 11 additions & 11 deletions ui-charts/src/spaceTimeChart/utils/snapping.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isPointPickingElement, isSegmentPickingElement } from '../components/PathLayer';
import { type HoveredItem, type Point } from '../lib/types';

/**
Expand Down Expand Up @@ -48,16 +49,15 @@ export function getClosestPointOnSegment(
export function snapPosition(mousePosition: Point, hoveredItem: HoveredItem | null) {
if (!mousePosition || !hoveredItem) return mousePosition;

switch (hoveredItem.element.type) {
case 'point':
return hoveredItem.element.point;
case 'segment':
return getClosestPointOnSegment(
mousePosition,
hoveredItem.element.from,
hoveredItem.element.to
);
default:
return mousePosition;
if (isPointPickingElement(hoveredItem.element)) {
return hoveredItem.element.point;
} else if (isSegmentPickingElement(hoveredItem.element)) {
return getClosestPointOnSegment(
mousePosition,
hoveredItem.element.from,
hoveredItem.element.to
);
} else {
return mousePosition;
}
}