Skip to content

Commit d1d1900

Browse files
authored
Merge pull request #632 from adobe/highlightingRefactor
refactor: the start of switching to hoveredItem
2 parents bf4d835 + e6f84da commit d1d1900

21 files changed

+169
-33
lines changed

packages/constants/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@ export const FILTERED_TABLE = 'filteredTable';
6565
// vega data field names
6666
export const GROUP_DATA = 'rscGroupData';
6767
export const MARK_ID = 'rscMarkId';
68+
export const GROUP_ID = 'rscGroupId';
6869
export const SERIES_ID = 'rscSeriesId';
6970
export const STACK_ID = 'rscStackId';
7071
export const COMPONENT_NAME = 'rscComponentName';
7172
export const TRENDLINE_VALUE = 'rscTrendlineValue';
7273

7374
// signal names
75+
export const HOVERED_ITEM = 'hoveredItem'; // hovered item suffix
7476
export const HIGHLIGHTED_ITEM = 'highlightedItem'; // data point
7577
export const HIGHLIGHTED_GROUP = 'highlightedGroup'; // data point
7678
export const HIGHLIGHTED_SERIES = 'highlightedSeries'; // series
@@ -125,6 +127,7 @@ export const DEFAULT_VENN_METRIC = 'size';
125127
export const DEFAULT_VENN_LABEL = 'label';
126128

127129
// ratio that each opacity is divded by when hovering or highlighting from legend
130+
// TODO: invert this ratio so we don't have to do 1 / HIGHLIGHT_CONTRAST_RATIO every time
128131
export const HIGHLIGHT_CONTRAST_RATIO = 5;
129132

130133
// legend tooltips

packages/react-spectrum-charts/src/hooks/useTooltipInteractions.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { FC } from 'react';
1414
import { renderToStaticMarkup } from 'react-dom/server';
1515
import { Position, Options as TooltipOptions } from 'vega-tooltip';
1616

17-
import { COMPONENT_NAME, FILTERED_TABLE, GROUP_DATA } from '@spectrum-charts/constants';
17+
import { COMPONENT_NAME, FILTERED_TABLE, GROUP_DATA, GROUP_ID } from '@spectrum-charts/constants';
1818
import { ColorScheme, LegendDescription, TooltipAnchor, TooltipPlacement } from '@spectrum-charts/vega-spec-builder';
1919

2020
import { useChartContext } from '../context/RscChartContext';
@@ -60,14 +60,14 @@ const useTooltipsInteractions = (props: RscChartProps, sanitizedChildren: ChartC
6060
chartView.current?.signal(controlledHoveredIdSignal.current.name, value?.[idKey] ?? null);
6161
}
6262
if (controlledHoveredGroupSignal.current) {
63-
const key = Object.keys(value).find((k) => k.endsWith('_highlightGroupId'));
63+
const key = Object.keys(value).find((k) => k.endsWith(GROUP_ID));
6464
if (key) {
6565
chartView.current?.signal(controlledHoveredGroupSignal.current.name, value[key]);
6666
}
6767
}
6868
if (tooltip.highlightBy && tooltip.highlightBy !== 'item') {
6969
const tableData = chartView.current?.data(FILTERED_TABLE);
70-
const groupId = `${tooltip.name}_highlightGroupId`;
70+
const groupId = `${tooltip.name}_${GROUP_ID}`;
7171
value[GROUP_DATA] = tableData?.filter((d) => d[groupId] === value[groupId]);
7272
}
7373
return renderToStaticMarkup(

packages/vega-spec-builder/src/area/areaSpecBuilder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
DEFAULT_METRIC,
2121
DEFAULT_TIME_DIMENSION,
2222
FILTERED_TABLE,
23+
GROUP_ID,
2324
HIGHLIGHTED_ITEM,
2425
SELECTED_ITEM,
2526
SELECTED_SERIES,
@@ -164,7 +165,7 @@ export const getAreaHighlightedData = (
164165
): SourceData => {
165166
let expr = '';
166167
if (hasGroupId) {
167-
expr += `${name}_controlledHoveredGroup === datum.${name}_highlightGroupId`;
168+
expr += `${name}_controlledHoveredGroup === datum.${name}_${GROUP_ID}`;
168169
} else {
169170
expr += `isArray(${HIGHLIGHTED_ITEM}) && indexof(${HIGHLIGHTED_ITEM}, datum.${idKey}) > -1 || ${HIGHLIGHTED_ITEM} === datum.${idKey}`;
170171
if (hasTooltip) {

packages/vega-spec-builder/src/bar/barSpecBuilder.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
import { getDualAxisScaleNames } from '../scale/scaleUtils';
4646
import {
4747
addHighlightedItemSignalEvents,
48+
addHoveredItemSignal,
4849
getFirstRscSeriesIdSignal,
4950
getGenericValueSignal,
5051
getLastRscSeriesIdSignal,
@@ -147,6 +148,7 @@ export const addSignals = produce<Signal[], [BarSpecOptions]>((signals, options)
147148
barAnnotations,
148149
chartTooltips,
149150
chartPopovers,
151+
hasOnClick,
150152
idKey,
151153
name,
152154
paddingRatio,
@@ -161,10 +163,11 @@ export const addSignals = produce<Signal[], [BarSpecOptions]>((signals, options)
161163
signals.push(getFirstRscSeriesIdSignal(), getLastRscSeriesIdSignal(), getMouseOverSeriesSignal(name));
162164
}
163165

164-
if (!barAnnotations.length && !chartPopovers.length && !chartTooltips.length && !trendlines.length) {
166+
if (!barAnnotations.length && !chartPopovers.length && !chartTooltips.length && !trendlines.length && !hasOnClick) {
165167
return;
166168
}
167169
addHighlightedItemSignalEvents(signals, name, idKey, 1, chartTooltips[0]?.excludeDataKeys);
170+
addHoveredItemSignal(signals, name)
168171
addTooltipSignals(signals, options);
169172
setTrendlineSignals(signals, options);
170173
});

packages/vega-spec-builder/src/bar/dodgedBarUtils.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
FILTERED_TABLE,
2525
HIGHLIGHTED_ITEM,
2626
HIGHLIGHT_CONTRAST_RATIO,
27+
HOVERED_ITEM,
2728
MARK_ID,
2829
} from '@spectrum-charts/constants';
2930

@@ -118,6 +119,10 @@ const defaultMarkWithTooltip: Mark = {
118119
...defaultDodgedXEncodings,
119120
...defaultBarStrokeEncodings,
120121
opacity: [
122+
{
123+
test: `isValid(bar0_${HOVERED_ITEM})`,
124+
signal: `bar0_${HOVERED_ITEM}.${MARK_ID} === datum.${MARK_ID} ? 1 : 1 / ${HIGHLIGHT_CONTRAST_RATIO}`,
125+
},
121126
{
122127
test: `isArray(${HIGHLIGHTED_ITEM}) && length(${HIGHLIGHTED_ITEM}) > 0 && indexof(${HIGHLIGHTED_ITEM}, datum.rscMarkId) === -1`,
123128
value: 1 / HIGHLIGHT_CONTRAST_RATIO,

packages/vega-spec-builder/src/chartTooltip/chartTooltipUtils.test.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
* OF ANY KIND, either express or implied. See the License for the specific language
1010
* governing permissions and limitations under the License.
1111
*/
12-
import { Data, Signal } from 'vega';
12+
import { Data, NumericValueRef, Signal } from 'vega';
1313

14-
import { HIGHLIGHTED_GROUP } from '@spectrum-charts/constants';
14+
import { DEFAULT_OPACITY_RULE, GROUP_ID, HIGHLIGHTED_GROUP, HOVERED_ITEM } from '@spectrum-charts/constants';
1515

1616
import { defaultBarOptions } from '../bar/barTestUtils';
1717
import { defaultScatterOptions } from '../scatter/scatterTestUtils';
@@ -20,6 +20,7 @@ import { baseData } from '../specUtils';
2020
import { BarSpecOptions, ChartTooltipOptions, LineSpecOptions } from '../types';
2121
import {
2222
addHighlightMarkOpacityRules,
23+
addHoveredItemOpacityRules,
2324
addTooltipData,
2425
addTooltipSignals,
2526
applyTooltipPropDefaults,
@@ -70,19 +71,19 @@ describe('addTooltipData()', () => {
7071
const markOptions = getDefautltMarkOptions({ highlightBy: 'dimension' });
7172
addTooltipData(data, markOptions);
7273
expect(data[1].transform?.length).toBe(1);
73-
expect(data[1].transform?.[0]).toHaveProperty('as', 'bar0_highlightGroupId');
74+
expect(data[1].transform?.[0]).toHaveProperty('as', `bar0_${GROUP_ID}`);
7475
});
7576
test('should add the group id transform if highlightBy is `series`', () => {
7677
const markOptions = getDefautltMarkOptions({ highlightBy: 'series' });
7778
addTooltipData(data, markOptions);
7879
expect(data[1].transform?.length).toBe(1);
79-
expect(data[1].transform?.[0]).toHaveProperty('as', 'bar0_highlightGroupId');
80+
expect(data[1].transform?.[0]).toHaveProperty('as', `bar0_${GROUP_ID}`);
8081
});
8182
test('should add the group id transform if highlightBy is a key array', () => {
8283
const markOptions = getDefautltMarkOptions({ highlightBy: ['operatingSystem'] });
8384
addTooltipData(data, markOptions);
8485
expect(data[1].transform?.length).toBe(1);
85-
expect(data[1].transform?.[0]).toHaveProperty('as', 'bar0_highlightGroupId');
86+
expect(data[1].transform?.[0]).toHaveProperty('as', `bar0_${GROUP_ID}`);
8687
});
8788
test('should not add highlightedData for the mark if false', () => {
8889
const dataLength = data.length;
@@ -170,3 +171,32 @@ describe('addTooltipMarkOpacityRules()', () => {
170171
expect(opacityRules).toHaveLength(3);
171172
});
172173
});
174+
175+
176+
describe('addHoveredItemOpacityRules()', () => {
177+
test('should add hovered item opacity rules', () => {
178+
const opacityRules = [];
179+
addHoveredItemOpacityRules(opacityRules, getDefautltMarkOptions());
180+
expect(opacityRules).toHaveLength(1);
181+
});
182+
test('should add hovered item opacity rule at the correct index', () => {
183+
let opacityRules: ({ test?: string, description?: string } & NumericValueRef)[] = [DEFAULT_OPACITY_RULE];
184+
addHoveredItemOpacityRules(opacityRules, getDefautltMarkOptions());
185+
expect(opacityRules).toHaveLength(2);
186+
expect(opacityRules[0].test).toContain(HOVERED_ITEM);
187+
expect(opacityRules[1]).toBe(DEFAULT_OPACITY_RULE);
188+
189+
opacityRules = [DEFAULT_OPACITY_RULE, { description: 'Hover', test: 'this is a test' }];
190+
addHoveredItemOpacityRules(opacityRules, getDefautltMarkOptions());
191+
expect(opacityRules).toHaveLength(3);
192+
expect(opacityRules[0]).toBe(DEFAULT_OPACITY_RULE);
193+
expect(opacityRules[1]).toHaveProperty('description', 'Hover');
194+
expect(opacityRules[2].test).toContain(HOVERED_ITEM);
195+
});
196+
test('should use group id if highlighted by group', () => {
197+
const opacityRules: ({ test?: string, description?: string, signal?: string } & NumericValueRef)[] = [];
198+
addHoveredItemOpacityRules(opacityRules, getDefautltMarkOptions({ highlightBy: 'dimension' }));
199+
expect(opacityRules).toHaveLength(1);
200+
expect(opacityRules[0].signal).toContain(GROUP_ID);
201+
});
202+
});

packages/vega-spec-builder/src/chartTooltip/chartTooltipUtils.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import { Data, FormulaTransform, NumericValueRef, Signal, SourceData } from 'veg
1414
import {
1515
DEFAULT_OPACITY_RULE,
1616
FILTERED_TABLE,
17+
GROUP_ID,
1718
HIGHLIGHTED_GROUP,
1819
HIGHLIGHTED_ITEM,
1920
HIGHLIGHT_CONTRAST_RATIO,
21+
HOVERED_ITEM,
2022
INTERACTION_MODE,
2123
SERIES_ID,
2224
} from '@spectrum-charts/constants';
@@ -105,7 +107,7 @@ export const addTooltipData = (data: Data[], markOptions: TooltipParentOptions,
105107
export const getGroupIdTransform = (highlightBy: string[], markName: string): FormulaTransform => {
106108
return {
107109
type: 'formula',
108-
as: `${markName}_highlightGroupId`,
110+
as: `${markName}_${GROUP_ID}`,
109111
expr: highlightBy.map((facet) => `datum.${facet}`).join(' + " | " + '),
110112
};
111113
};
@@ -121,7 +123,7 @@ const getMarkHighlightedData = (markName: string): SourceData => ({
121123
transform: [
122124
{
123125
type: 'filter',
124-
expr: `${HIGHLIGHTED_GROUP} === datum.${markName}_highlightGroupId`,
126+
expr: `${HIGHLIGHTED_GROUP} === datum.${markName}_${GROUP_ID}`,
125127
},
126128
],
127129
});
@@ -155,7 +157,7 @@ export const addTooltipSignals = (signals: Signal[], markOptions: TooltipParentO
155157
const highlightedGroupSignal = signals.find((signal) => signal.name === HIGHLIGHTED_GROUP) as Signal;
156158

157159
let markName = markOptions.name;
158-
let update = `datum.${markName}_highlightGroupId`;
160+
let update = `datum.${markName}_${GROUP_ID}`;
159161

160162
if ('interactionMode' in markOptions && markOptions.interactionMode === INTERACTION_MODE.ITEM) {
161163
getHoverMarkNames(markName).forEach((name) => {
@@ -211,8 +213,26 @@ export const addHighlightMarkOpacityRules = (
211213
if (isHighlightedByGroup(markOptions)) {
212214
const { name: markName } = markOptions;
213215
opacityRules.unshift({
214-
test: `${HIGHLIGHTED_GROUP} === datum.${markName}_highlightGroupId`,
216+
test: `${HIGHLIGHTED_GROUP} === datum.${markName}_${GROUP_ID}`,
215217
...DEFAULT_OPACITY_RULE,
216218
});
217219
}
218220
};
221+
222+
export const addHoveredItemOpacityRules = (opacityRules: ({ test?: string, description?: string } & NumericValueRef)[], markOptions: TooltipParentOptions) => {
223+
const { name: markName } = markOptions;
224+
// find the index of the first hover rule
225+
const startIndex = opacityRules.findIndex((rule) => rule.description?.startsWith(`Hover`)) + 1;
226+
227+
const hoveredItemSignal = `${markName}_${HOVERED_ITEM}`;
228+
229+
let key = markOptions.idKey;
230+
if (isHighlightedByGroup(markOptions)) {
231+
key = `${markName}_${GROUP_ID}`;
232+
}
233+
234+
opacityRules.splice(startIndex, 0, {
235+
test: `isValid(${hoveredItemSignal})`,
236+
signal: `${hoveredItemSignal}.${key} === datum.${key} ? 1 : 1 / ${HIGHLIGHT_CONTRAST_RATIO}`,
237+
});
238+
}

packages/vega-spec-builder/src/donut/donutSpecBuilder.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('addData', () => {
4040
describe('addSignals()', () => {
4141
test('should add hover events when tooltip is present', () => {
4242
const signals = addSignals(defaultSignals, { ...defaultDonutOptions, chartTooltips: [{}] });
43-
expect(signals).toHaveLength(defaultSignals.length);
43+
expect(signals).toHaveLength(defaultSignals.length + 1);
4444
expect(signals[0]).toHaveProperty('name', HIGHLIGHTED_ITEM);
4545
expect(signals[0].on).toHaveLength(2);
4646
expect(signals[0].on?.[0]).toHaveProperty('events', '@testName:mouseover');
@@ -51,7 +51,7 @@ describe('addSignals()', () => {
5151
...defaultDonutOptions,
5252
chartTooltips: [{ excludeDataKeys: ['excludeFromTooltip'] }],
5353
});
54-
expect(signals).toHaveLength(defaultSignals.length);
54+
expect(signals).toHaveLength(defaultSignals.length + 1);
5555
expect(signals[0]).toHaveProperty('name', HIGHLIGHTED_ITEM);
5656
expect(signals[0].on).toHaveLength(2);
5757
expect(signals[0].on?.[0]).toHaveProperty('events', '@testName:mouseover');

packages/vega-spec-builder/src/donut/donutSpecBuilder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { toCamelCase } from '@spectrum-charts/utils';
2323

2424
import { isInteractive } from '../marks/markUtils';
2525
import { addFieldToFacetScaleDomain } from '../scale/scaleSpecBuilder';
26-
import { addHighlightedItemSignalEvents } from '../signal/signalSpecBuilder';
26+
import { addHighlightedItemSignalEvents, addHoveredItemSignal } from '../signal/signalSpecBuilder';
2727
import { ColorScheme, DonutOptions, DonutSpecOptions, HighlightedItem, ScSpec } from '../types';
2828
import {
2929
getDonutSummaryData,
@@ -150,5 +150,6 @@ export const addSignals = produce<Signal[], [DonutSpecOptions]>((signals, option
150150
const { chartTooltips, idKey, name } = options;
151151
signals.push(...getDonutSummarySignals(options));
152152
if (!isInteractive(options)) return;
153+
addHoveredItemSignal(signals, name);
153154
addHighlightedItemSignalEvents(signals, name, idKey, 1, chartTooltips[0]?.excludeDataKeys);
154155
});

packages/vega-spec-builder/src/legend/legendHighlightUtils.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Mark } from 'vega';
1313

1414
import {
1515
DEFAULT_OPACITY_RULE,
16+
GROUP_ID,
1617
HIGHLIGHTED_GROUP,
1718
HIGHLIGHTED_SERIES,
1819
HIGHLIGHT_CONTRAST_RATIO,
@@ -48,7 +49,7 @@ describe('getHighlightOpacityRule()', () => {
4849
const opacityRule = getHighlightOpacityRule(['key1'], 'legend0');
4950
expect(opacityRule).toHaveProperty(
5051
'test',
51-
`isValid(${HIGHLIGHTED_GROUP}) && ${HIGHLIGHTED_GROUP} !== datum.legend0_highlightGroupId`
52+
`isValid(${HIGHLIGHTED_GROUP}) && ${HIGHLIGHTED_GROUP} !== datum.legend0_${GROUP_ID}`
5253
);
5354
});
5455
});

0 commit comments

Comments
 (0)