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

feature: Show vector values in tooltip #479

Merged
merged 6 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
53 changes: 41 additions & 12 deletions src/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
ScatterPlotConfig,
TabType,
Track,
VECTOR_KEY_MOTION_DELTA,
VectorTooltipMode,
ViewerConfig,
} from "./colorizer";
import { AnalyticsEvent, triggerAnalyticsEvent } from "./colorizer/utils/analytics";
Expand Down Expand Up @@ -853,6 +855,44 @@ function Viewer(): ReactElement {
}
}

// TODO: Move to a separate component?
const hoverTooltipContent = [
<p key="track_id">Track ID: {lastHoveredId && dataset?.getTrackId(lastHoveredId)}</p>,
<p key="feature_value">
{dataset?.getFeatureName(featureKey) || "Feature"}:{" "}
<span style={{ whiteSpace: "nowrap" }}>{hoveredFeatureValue}</span>
</p>,
];

if (config.vectorConfig.visible && lastHoveredId !== null && motionDeltas) {
const vectorKey = config.vectorConfig.key;
const vectorName = vectorKey === VECTOR_KEY_MOTION_DELTA ? "Avg. motion delta" : vectorKey;
const motionDelta = [motionDeltas[2 * lastHoveredId], motionDeltas[2 * lastHoveredId + 1]];
if (Number.isNaN(motionDelta[0]) || Number.isNaN(motionDelta[1])) {
// {vector}:
hoverTooltipContent.push(<p key="vector_empty">{vectorName}:</p>);
} else if (config.vectorConfig.tooltipMode === VectorTooltipMode.MAGNITUDE) {
const magnitude = Math.sqrt(motionDelta[0] ** 2 + motionDelta[1] ** 2);
const angleDegrees = (360 + Math.atan2(-motionDelta[1], motionDelta[0]) * (180 / Math.PI)) % 360;

// {vector}: {magnitude} {unit}, {angle}°
hoverTooltipContent.push(
<p key="vector_magnitude">
{vectorName}: {numberToStringDecimal(magnitude, 3)} px, {numberToStringDecimal(angleDegrees, 1)}°
</p>
);
} else {
// {vector}: ({x}, {y}) {unit}
const allowIntegerTruncation = Number.isInteger(motionDelta[0]) && Number.isInteger(motionDelta[1]);
hoverTooltipContent.push(
<p key="vector_components">
{vectorName}: ({numberToStringDecimal(motionDelta[0], 3, allowIntegerTruncation)},{" "}
{numberToStringDecimal(motionDelta[1], 3, allowIntegerTruncation)}) px
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't typically like to have this much data grooming mixed up with rendering. I think this would be a lot easier to follow if you had a separate function that checked for and got the appropriate data and another one that created the

and pushed it to the array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

);
}
}

return (
<div>
<div ref={notificationContainer}>{notificationContextHolder}</div>
Expand Down Expand Up @@ -1003,18 +1043,7 @@ function Viewer(): ReactElement {
</div>
</FlexRowAlignCenter>
</div>
<HoverTooltip
tooltipContent={
<>
<p>Track ID: {lastHoveredId && dataset?.getTrackId(lastHoveredId)}</p>
<p>
{dataset?.getFeatureName(featureKey) || "Feature"}:{" "}
<span style={{ whiteSpace: "nowrap" }}>{hoveredFeatureValue}</span>
</p>
</>
}
disabled={!showHoveredId}
>
<HoverTooltip tooltipContent={hoverTooltipContent} disabled={!showHoveredId}>
<CanvasWrapper
loading={isDatasetLoading}
loadingProgress={datasetLoadProgress}
Expand Down
11 changes: 10 additions & 1 deletion src/colorizer/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Color } from "three";

import { DrawMode, PlotRangeType, ScatterPlotConfig, TabType, VectorConfig, ViewerConfig } from "./types";
import {
DrawMode,
PlotRangeType,
ScatterPlotConfig,
TabType,
VectorConfig,
VectorTooltipMode,
ViewerConfig,
} from "./types";

export const BACKGROUND_COLOR_DEFAULT = 0xffffff;
export const OUTLINE_COLOR_DEFAULT = 0xff00ff;
Expand All @@ -15,6 +23,7 @@ export const getDefaultVectorConfig = (): VectorConfig => ({
timeIntervals: 5,
color: new Color(0x000000),
scaleFactor: 4,
tooltipMode: VectorTooltipMode.MAGNITUDE,
});

export const getDefaultViewerConfig = (): ViewerConfig => ({
Expand Down
6 changes: 6 additions & 0 deletions src/colorizer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,19 @@ export type DrawSettings = {
color: Color;
};

export enum VectorTooltipMode {
MAGNITUDE = "m",
COMPONENTS = "c",
}

export type VectorConfig = {
visible: boolean;
key: string;
/** Number of time intervals to average over when calculating motion deltas. 5 by default. */
timeIntervals: number;
color: Color;
scaleFactor: number;
tooltipMode: VectorTooltipMode;
};

// TODO: This should live in the viewer and not in `colorizer`. Same with `url_utils`.
Expand Down
16 changes: 14 additions & 2 deletions src/components/Tabs/Settings/VectorFieldSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Color as AntdColor } from "@rc-component/color-picker";
import { Card, Checkbox, ColorPicker } from "antd";
import { Card, Checkbox, ColorPicker, Radio } from "antd";
import React, { ReactElement } from "react";
import { Color, ColorRepresentation } from "three";

import { VECTOR_KEY_MOTION_DELTA } from "../../../colorizer/constants";
import { VectorConfig, ViewerConfig } from "../../../colorizer/types";
import { VectorConfig, VectorTooltipMode, ViewerConfig } from "../../../colorizer/types";
import { DEFAULT_OUTLINE_COLOR_PRESETS } from "./constants";

import SelectionDropdown from "../../Dropdowns/SelectionDropdown";
Expand Down Expand Up @@ -107,6 +107,18 @@ export default function VectorFieldSettings(props: VectorFieldSettingsProps): Re
></ColorPicker>
</div>
</SettingsItem>
<SettingsItem label="Show vector in tooltip as" labelStyle={{ height: "fit-content" }}>
<div style={{ width: "fit-content" }}>
<Radio.Group
value={props.config.vectorConfig.tooltipMode}
onChange={(e) => updateVectorConfig({ tooltipMode: e.target.value })}
disabled={!props.config.vectorConfig.visible}
>
<Radio value={VectorTooltipMode.MAGNITUDE}>Magnitude and angle</Radio>
<Radio value={VectorTooltipMode.COMPONENTS}>XY components</Radio>
</Radio.Group>
</div>
</SettingsItem>
</>
);
}
7 changes: 4 additions & 3 deletions src/components/Tabs/SettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const NO_BACKDROP = {
};

export const SETTINGS_INDENT_PX = 24;
const SETTINGS_GAP_PX = 8;
export const MAX_SLIDER_WIDTH = "250px";

type SettingsTabProps = {
Expand All @@ -47,7 +48,7 @@ export default function SettingsTab(props: SettingsTabProps): ReactElement {
return (
<FlexColumn $gap={5}>
<CustomCollapse label="Backdrop">
<SettingsContainer indentPx={SETTINGS_INDENT_PX} gapPx={8}>
<SettingsContainer indentPx={SETTINGS_INDENT_PX} gapPx={SETTINGS_GAP_PX}>
<SettingsItem label="Backdrop images">
<SelectionDropdown
selected={props.selectedBackdropKey || NO_BACKDROP.key}
Expand Down Expand Up @@ -93,7 +94,7 @@ export default function SettingsTab(props: SettingsTabProps): ReactElement {
</CustomCollapse>

<CustomCollapse label="Objects">
<SettingsContainer indentPx={SETTINGS_INDENT_PX} gapPx={8}>
<SettingsContainer indentPx={SETTINGS_INDENT_PX} gapPx={SETTINGS_GAP_PX}>
<SettingsItem label="Outline color">
<div>
<ColorPicker
Expand Down Expand Up @@ -174,7 +175,7 @@ export default function SettingsTab(props: SettingsTabProps): ReactElement {
</CustomCollapse>

<CustomCollapse label="Vector arrows">
<SettingsContainer indentPx={SETTINGS_INDENT_PX}>
<SettingsContainer indentPx={SETTINGS_INDENT_PX} gapPx={SETTINGS_GAP_PX}>
<VectorFieldSettings config={props.config} updateConfig={props.updateConfig} />
</SettingsContainer>
</CustomCollapse>
Expand Down
Loading