Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/s2-line-hovered-item-signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spectrum-charts/vega-spec-builder-s2': patch
---

Declare a null hoveredItem signal for highlightedItem only S2 lines
42 changes: 42 additions & 0 deletions packages/vega-spec-builder-s2/src/line/lineSpecBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,33 @@ describe('lineSpecBuilder', () => {
// so a highlightedItem-only line still gets both controlled-highlight rules wired.
const rules = (hoverTargetData?.transform as { as?: string }[] | undefined)?.map((t) => t.as);
expect(rules).toEqual(expect.arrayContaining(['controlledTableMatch', 'controlledSeriesMatch']));

// encodings and hoveredMatch reference line0_hoveredItem even with no hover UI
const hoveredItemSignal = spec.signals?.find((s) => s.name === `line0_${HOVERED_ITEM}`);
expect(hoveredItemSignal).toEqual(
expect.objectContaining({
name: `line0_${HOVERED_ITEM}`,
value: null,
})
);
expect(hoveredItemSignal?.on).toBeUndefined();
});

test('highlightedItem alone with animations off still declares a null hoveredItem signal', () => {
const spec = addLine(startingSpec, {
idKey: MARK_ID,
color: DEFAULT_COLOR,
markType: 'line',
highlightedItem: 'abc123',
animations: false,
});
expect(spec.usermeta?.animatedMarks).toBeUndefined();
expect(spec.signals?.find((s) => s.name === `line0_${HOVERED_ITEM}`)).toEqual(
expect.objectContaining({
name: `line0_${HOVERED_ITEM}`,
value: null,
})
);
});
});

Expand Down Expand Up @@ -1299,6 +1326,21 @@ describe('lineSpecBuilder', () => {
expect(addSignals([], { ...defaultLineOptions, staticPoint: 'staticPoint' })).toStrictEqual([]);
});

test('declares a null hoveredItem signal when highlightedItem makes the line the interactive mark target', () => {
const signals = addSignals([], {
...defaultLineOptions,
interactiveMarkName: 'line0',
highlightedItem: 'abc123',
});
expect(signals).toEqual([
{
description: 'Tracks the hovered item for line0',
name: `line0_${HOVERED_ITEM}`,
value: null,
},
]);
});

test('adds hover signals with metric range when displayPointMark is not undefined', () => {
const signals = addSignals(defaultSignals, {
...defaultLineOptions,
Expand Down
22 changes: 20 additions & 2 deletions packages/vega-spec-builder-s2/src/line/lineSpecBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
DEFAULT_TIME_DIMENSION,
DRAW_IN_PREV_DATA,
FILTERED_TABLE,
HOVERED_ITEM,
INTERACTION_MODE,
LAST_RSC_SERIES_ID,
LINE_TYPE_SCALE,
Expand Down Expand Up @@ -59,7 +60,12 @@ import { getInteractiveMarkName, isInteractive } from '../marks/markUtils';
import { getMetricRangeData, getMetricRangeGroupMarks, getMetricRanges } from '../metricRange/metricRangeUtils';
import { addContinuousDimensionScale, addFieldToFacetScaleDomain, addMetricScale } from '../scale/scaleSpecBuilder';
import { getDualAxisScaleNames } from '../scale/scaleUtils';
import { addHoveredItemSignal, getFirstRscSeriesIdSignal, getLastRscSeriesIdSignal } from '../signal/signalSpecBuilder';
import {
addHoveredItemSignal,
getFirstRscSeriesIdSignal,
getLastRscSeriesIdSignal,
hasSignalByName,
} from '../signal/signalSpecBuilder';
import { addUserMetaAnimatedMark, addUserMetaInteractiveMark, getFacetsFromOptions } from '../specUtils';
import { addTrendlineData, getTrendlineMarks, getTrendlineScales, setTrendlineSignals } from '../trendline';
import {
Expand Down Expand Up @@ -414,7 +420,19 @@ export const addSignals = produce<Signal[], [LineSpecOptions]>((signals, options
addLineDrawInAnimationSignals(signals, options);
}

if (!isInteractive(options)) return;
if (!isInteractive(options)) {
// highlightedItem makes this the interactive mark target, so opacity rules and
// hover animation reference `${name}_hoveredItem`. Declare a null placeholder
// since there is no hover UI to drive the signal.
if (options.interactiveMarkName === name && !hasSignalByName(signals, `${name}_${HOVERED_ITEM}`)) {
signals.push({
description: `Tracks the hovered item for ${name}`,
name: `${name}_${HOVERED_ITEM}`,
value: null,
});
}
return;
}
const { primarySeries } = options;
// datum.datum because the voronoi mark uses datumOrder=2
addHoveredItemSignal(
Expand Down
Loading