From d99ba2856b348ff494ef72455afde772ef93152f Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Tue, 11 Aug 2026 16:49:13 -0700 Subject: [PATCH 1/6] feat: Add tooltip for current connect by feature --- src/components/PopoverCard/index.tsx | 12 +++- src/containers/MainPlotContainer/index.tsx | 11 ++++ src/containers/MainPlotContainer/selectors.ts | 61 ++++++++++++++----- src/state/selection/types.ts | 1 + src/state/types.ts | 2 + 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/components/PopoverCard/index.tsx b/src/components/PopoverCard/index.tsx index 80afa132..5e35b1a8 100644 --- a/src/components/PopoverCard/index.tsx +++ b/src/components/PopoverCard/index.tsx @@ -14,6 +14,8 @@ export interface PopoverCardProps { xValue?: string; yLabel?: string; yValue?: string; + lineLabel?: string; + lineValue?: string; } const PopoverCard: React.FC = (props) => { @@ -46,7 +48,9 @@ const PopoverCard: React.FC = (props) => { return ( - {(props.xValue || props.yValue) && ( + {(props.xValue !== undefined || + props.yValue !== undefined || + props.lineValue !== undefined) && (
{props.xValue && (
@@ -60,6 +64,12 @@ const PopoverCard: React.FC = (props) => { {props.yValue}
)} + {props.lineValue && ( +
+ line:{props.lineLabel} + {props.lineValue} +
+ )}
)}
diff --git a/src/containers/MainPlotContainer/index.tsx b/src/containers/MainPlotContainer/index.tsx index 2421888c..068d8670 100644 --- a/src/containers/MainPlotContainer/index.tsx +++ b/src/containers/MainPlotContainer/index.tsx @@ -47,6 +47,8 @@ import { getXAxisRange, getYAxisRange, getAnnotations, + getConnectByDisplayName, + getFormattedHoveredLineValue, } from "./selectors"; import { getFeatureDefTooltip } from "../../state/selection/selectors"; import { formatThumbnailSrc } from "../../state/util"; @@ -66,11 +68,13 @@ interface PropsFromState { hoveredPointData: SelectedPointData | null; hoveredXValue: string; hoveredYValue: string; + hoveredLineValue: string; mousePosition: MousePosition; plotDataArray: any; thumbnailRoot: string; xDisplayName: string; yDisplayName: string; + connectByDisplayName: string; xDropDownValue: string; yDropDownValue: string; yDropDownOptions: MeasuredFeatureDef[]; @@ -203,6 +207,7 @@ class MainPlotContainer extends React.Component ) ); @@ -383,6 +392,8 @@ function mapStateToProps(state: State): PropsFromState { thumbnailRoot: selectionStateBranch.selectors.getThumbnailRoot(state), xDisplayName: getXDisplayName(state), yDisplayName: getYDisplayName(state), + connectByDisplayName: getConnectByDisplayName(state), + hoveredLineValue: getFormattedHoveredLineValue(state), xDropDownOptions: getXDisplayOptions(state), xDropDownValue: selectionStateBranch.selectors.getPlotByOnX(state), xTickConversion: getXTickConversion(state), diff --git a/src/containers/MainPlotContainer/selectors.ts b/src/containers/MainPlotContainer/selectors.ts index 6f11dcf3..ea459ba7 100644 --- a/src/containers/MainPlotContainer/selectors.ts +++ b/src/containers/MainPlotContainer/selectors.ts @@ -44,6 +44,7 @@ import { getShowConnectLines, getFilteredConnectByCategoryValues, getLineMovingAverageWindow, + getConnectByFeature, } from "../../state/selection/selectors"; import { MainPlotSettings, SelectedPointData, TickConversion } from "../../state/selection/types"; import { @@ -103,16 +104,18 @@ export const handleNullValues = ( }; export const getPlotlyCustomData = createSelector( - [getFilteredCellData], - (filteredCellData: DataForPlot): PlotlyCustomData[] => { + [getFilteredCellData, getConnectByFeature], + (filteredCellData: DataForPlot, connectByFeature: string): PlotlyCustomData[] => { const thumbnailPaths = filteredCellData.labels.thumbnailPaths; const srcPaths = filteredCellData.labels.sourcePaths; const indices = filteredCellData.indices; + const connectByFeatureValues = filteredCellData.values[connectByFeature]; return map(indices, (cellIndex, i) => { return { index: cellIndex, thumbnailPath: thumbnailPaths[i], srcPath: srcPaths?.[i], + connectByFeature: connectByFeatureValues?.[i], }; }); } @@ -549,26 +552,30 @@ const makeNumberAxis = (): TickConversion => { }; }; +const getFeatureTickConversion = ( + featureKey: string, + featureDefs: MeasuredFeatureDef[] +): TickConversion => { + const feature = find(featureDefs, { key: featureKey }); + if (feature && feature.discrete) { + return makeNumberToTextConversion(feature.options); + } + return makeNumberAxis(); +}; + export const getXTickConversion = createSelector( [getPlotByOnX, getMeasuredFeaturesDefs], - (plotByOnX, measuredFeaturesDefs: MeasuredFeatureDef[]): TickConversion => { - const feature = findFeature(measuredFeaturesDefs, plotByOnX); - if (feature && feature.discrete) { - return makeNumberToTextConversion(feature.options); - } - return makeNumberAxis(); - } + getFeatureTickConversion ); export const getYTickConversion = createSelector( [getPlotByOnY, getMeasuredFeaturesDefs], - (plotByOnY, measuredFeaturesDefs: MeasuredFeatureDef[]): TickConversion => { - const feature = find(measuredFeaturesDefs, { key: plotByOnY }); - if (feature && feature.discrete) { - return makeNumberToTextConversion(feature.options); - } - return makeNumberAxis(); - } + getFeatureTickConversion +); + +export const getConnectByTickConversion = createSelector( + [getConnectByFeature, getMeasuredFeaturesDefs], + getFeatureTickConversion ); export const getDataForOverlayCard = createSelector( @@ -630,6 +637,14 @@ export const getYDisplayName = createSelector( } ); +export const getConnectByDisplayName = createSelector( + [getConnectByFeature, getMeasuredFeaturesDefs], + (connectByFeature, featureDefs): string => { + const feature = findFeature(featureDefs, connectByFeature); + return feature?.displayName ?? connectByFeature; + } +); + function formatAxisValue( value: number | string | undefined, isCategorical: boolean, @@ -666,3 +681,17 @@ export const getFormattedHoveredYValue = createSelector( ); } ); + +export const getFormattedHoveredLineValue = createSelector( + [ + getHoveredPointData, + getCategoricalFeatureKeys, + getConnectByFeature, + getConnectByTickConversion, + ], + (hoveredPointData, categoricalFeatures, connectByKey, tickConversion): string => { + let value = hoveredPointData?.connectByValue; + value = value === null ? undefined : value; + return formatAxisValue(value, includes(categoricalFeatures, connectByKey), tickConversion); + } +); diff --git a/src/state/selection/types.ts b/src/state/selection/types.ts index 23d97224..fab36857 100755 --- a/src/state/selection/types.ts +++ b/src/state/selection/types.ts @@ -127,6 +127,7 @@ export interface SelectedPointData { groupBy?: string; xValue?: number | string; yValue?: number | string; + connectByValue?: number | string | null; } export interface ChangeHoveredPointAction { diff --git a/src/state/types.ts b/src/state/types.ts index 21fe8014..ab1e349b 100755 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -68,6 +68,8 @@ export interface SelectedGroupDatum { export interface PlotlyCustomData { thumbnailPath: string; index: number; + srcPath?: string; + connectFeature?: number | null; } export enum DataType { From d95a416e2e1d519c606dbb409c747d94ca6bbd9b Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Tue, 11 Aug 2026 16:50:33 -0700 Subject: [PATCH 2/6] feat: Show tooltip only when lines are enabled --- src/containers/MainPlotContainer/selectors.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/containers/MainPlotContainer/selectors.ts b/src/containers/MainPlotContainer/selectors.ts index ea459ba7..f3faac17 100644 --- a/src/containers/MainPlotContainer/selectors.ts +++ b/src/containers/MainPlotContainer/selectors.ts @@ -104,12 +104,18 @@ export const handleNullValues = ( }; export const getPlotlyCustomData = createSelector( - [getFilteredCellData, getConnectByFeature], - (filteredCellData: DataForPlot, connectByFeature: string): PlotlyCustomData[] => { + [getFilteredCellData, getShowConnectLines, getConnectByFeature], + ( + filteredCellData: DataForPlot, + showConnectByLines: boolean, + connectByFeature: string + ): PlotlyCustomData[] => { const thumbnailPaths = filteredCellData.labels.thumbnailPaths; const srcPaths = filteredCellData.labels.sourcePaths; const indices = filteredCellData.indices; - const connectByFeatureValues = filteredCellData.values[connectByFeature]; + const connectByFeatureValues = showConnectByLines + ? filteredCellData.values[connectByFeature] + : undefined; return map(indices, (cellIndex, i) => { return { index: cellIndex, From 6715f86ddc28fb12b70e5bfcf32ec17ea9662c8b Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Tue, 11 Aug 2026 16:52:17 -0700 Subject: [PATCH 3/6] refactor: Remove label --- src/components/PopoverCard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverCard/index.tsx b/src/components/PopoverCard/index.tsx index 5e35b1a8..bee4691a 100644 --- a/src/components/PopoverCard/index.tsx +++ b/src/components/PopoverCard/index.tsx @@ -66,7 +66,7 @@ const PopoverCard: React.FC = (props) => { )} {props.lineValue && (
- line:{props.lineLabel} + {props.lineLabel} {props.lineValue}
)} From f36e0b462ae73cc868d8610628dd1034509235e9 Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Wed, 12 Aug 2026 13:45:22 -0700 Subject: [PATCH 4/6] refactor: Renamed variables for consistency --- src/components/PopoverCard/index.tsx | 6 +++--- src/containers/MainPlotContainer/index.tsx | 20 +++++++++---------- src/containers/MainPlotContainer/selectors.ts | 11 +++++----- src/state/selection/types.ts | 2 +- src/state/types.ts | 2 +- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/components/PopoverCard/index.tsx b/src/components/PopoverCard/index.tsx index bee4691a..f7e93dad 100644 --- a/src/components/PopoverCard/index.tsx +++ b/src/components/PopoverCard/index.tsx @@ -52,19 +52,19 @@ const PopoverCard: React.FC = (props) => { props.yValue !== undefined || props.lineValue !== undefined) && (
- {props.xValue && ( + {props.xValue !== undefined && (
x:{props.xLabel} {props.xValue}
)} - {props.yValue && ( + {props.yValue !== undefined && (
y:{props.yLabel} {props.yValue}
)} - {props.lineValue && ( + {props.lineValue !== undefined && (
{props.lineLabel} {props.lineValue} diff --git a/src/containers/MainPlotContainer/index.tsx b/src/containers/MainPlotContainer/index.tsx index 068d8670..56136705 100644 --- a/src/containers/MainPlotContainer/index.tsx +++ b/src/containers/MainPlotContainer/index.tsx @@ -47,8 +47,8 @@ import { getXAxisRange, getYAxisRange, getAnnotations, - getConnectByDisplayName, - getFormattedHoveredLineValue, + getConnectByFeatureDisplayName, + getFormattedHoveredConnectByFeatureValue, } from "./selectors"; import { getFeatureDefTooltip } from "../../state/selection/selectors"; import { formatThumbnailSrc } from "../../state/util"; @@ -68,13 +68,13 @@ interface PropsFromState { hoveredPointData: SelectedPointData | null; hoveredXValue: string; hoveredYValue: string; - hoveredLineValue: string; + hoveredConnectByFeatureValue: string; mousePosition: MousePosition; plotDataArray: any; thumbnailRoot: string; xDisplayName: string; yDisplayName: string; - connectByDisplayName: string; + connectByFeatureDisplayName: string; xDropDownValue: string; yDropDownValue: string; yDropDownOptions: MeasuredFeatureDef[]; @@ -256,8 +256,8 @@ class MainPlotContainer extends React.Component ) ); @@ -392,8 +392,8 @@ function mapStateToProps(state: State): PropsFromState { thumbnailRoot: selectionStateBranch.selectors.getThumbnailRoot(state), xDisplayName: getXDisplayName(state), yDisplayName: getYDisplayName(state), - connectByDisplayName: getConnectByDisplayName(state), - hoveredLineValue: getFormattedHoveredLineValue(state), + connectByFeatureDisplayName: getConnectByFeatureDisplayName(state), + hoveredConnectByFeatureValue: getFormattedHoveredConnectByFeatureValue(state), xDropDownOptions: getXDisplayOptions(state), xDropDownValue: selectionStateBranch.selectors.getPlotByOnX(state), xTickConversion: getXTickConversion(state), diff --git a/src/containers/MainPlotContainer/selectors.ts b/src/containers/MainPlotContainer/selectors.ts index f3faac17..7809aef0 100644 --- a/src/containers/MainPlotContainer/selectors.ts +++ b/src/containers/MainPlotContainer/selectors.ts @@ -579,7 +579,7 @@ export const getYTickConversion = createSelector( getFeatureTickConversion ); -export const getConnectByTickConversion = createSelector( +export const getConnectByFeatureTickConversion = createSelector( [getConnectByFeature, getMeasuredFeaturesDefs], getFeatureTickConversion ); @@ -643,7 +643,7 @@ export const getYDisplayName = createSelector( } ); -export const getConnectByDisplayName = createSelector( +export const getConnectByFeatureDisplayName = createSelector( [getConnectByFeature, getMeasuredFeaturesDefs], (connectByFeature, featureDefs): string => { const feature = findFeature(featureDefs, connectByFeature); @@ -688,16 +688,15 @@ export const getFormattedHoveredYValue = createSelector( } ); -export const getFormattedHoveredLineValue = createSelector( +export const getFormattedHoveredConnectByFeatureValue = createSelector( [ getHoveredPointData, getCategoricalFeatureKeys, getConnectByFeature, - getConnectByTickConversion, + getConnectByFeatureTickConversion, ], (hoveredPointData, categoricalFeatures, connectByKey, tickConversion): string => { - let value = hoveredPointData?.connectByValue; - value = value === null ? undefined : value; + const value = hoveredPointData?.connectByFeatureValue ?? undefined; return formatAxisValue(value, includes(categoricalFeatures, connectByKey), tickConversion); } ); diff --git a/src/state/selection/types.ts b/src/state/selection/types.ts index fab36857..07e2477f 100755 --- a/src/state/selection/types.ts +++ b/src/state/selection/types.ts @@ -127,7 +127,7 @@ export interface SelectedPointData { groupBy?: string; xValue?: number | string; yValue?: number | string; - connectByValue?: number | string | null; + connectByFeatureValue?: number | string | null; } export interface ChangeHoveredPointAction { diff --git a/src/state/types.ts b/src/state/types.ts index ab1e349b..de9016a9 100755 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -69,7 +69,7 @@ export interface PlotlyCustomData { thumbnailPath: string; index: number; srcPath?: string; - connectFeature?: number | null; + connectByFeatureValue?: number | null; } export enum DataType { From a3010afed7e12c9b211c7ec9c1c778293d66c792 Mon Sep 17 00:00:00 2001 From: Peyton Lee Date: Wed, 12 Aug 2026 14:09:52 -0700 Subject: [PATCH 5/6] fix: Fixed missing tooltip --- src/containers/MainPlotContainer/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/MainPlotContainer/index.tsx b/src/containers/MainPlotContainer/index.tsx index 56136705..f7605c72 100644 --- a/src/containers/MainPlotContainer/index.tsx +++ b/src/containers/MainPlotContainer/index.tsx @@ -207,8 +207,8 @@ class MainPlotContainer extends React.Component Date: Wed, 12 Aug 2026 14:41:53 -0700 Subject: [PATCH 6/6] fix: Fix popover not hiding line feature --- src/components/PopoverCard/index.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/PopoverCard/index.tsx b/src/components/PopoverCard/index.tsx index f7e93dad..bfb66f5a 100644 --- a/src/components/PopoverCard/index.tsx +++ b/src/components/PopoverCard/index.tsx @@ -48,23 +48,21 @@ const PopoverCard: React.FC = (props) => { return ( - {(props.xValue !== undefined || - props.yValue !== undefined || - props.lineValue !== undefined) && ( + {(props.xValue || props.yValue || props.lineValue !== undefined) && (
- {props.xValue !== undefined && ( + {props.xValue && (
x:{props.xLabel} {props.xValue}
)} - {props.yValue !== undefined && ( + {props.yValue && (
y:{props.yLabel} {props.yValue}
)} - {props.lineValue !== undefined && ( + {props.lineValue && (
{props.lineLabel} {props.lineValue}