From 0e143340e5742ded987b8a4658760aa744210425 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Mon, 9 Dec 2024 11:03:42 -0700 Subject: [PATCH 01/15] feat: introduce sunburst files --- src/alpha/components/Sunburst/Sunburst.tsx | 35 ++++++ src/alpha/components/Sunburst/index.ts | 13 ++ src/alpha/components/index.ts | 3 +- .../components/Sunburst/Sunburst.story.tsx | 50 ++++++++ src/stories/components/Sunburst/data.ts | 116 ++++++++++++++++++ src/types/Chart.ts | 8 ++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/alpha/components/Sunburst/Sunburst.tsx create mode 100644 src/alpha/components/Sunburst/index.ts create mode 100644 src/stories/components/Sunburst/Sunburst.story.tsx create mode 100644 src/stories/components/Sunburst/data.ts diff --git a/src/alpha/components/Sunburst/Sunburst.tsx b/src/alpha/components/Sunburst/Sunburst.tsx new file mode 100644 index 000000000..3ba63a32c --- /dev/null +++ b/src/alpha/components/Sunburst/Sunburst.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { FC } from 'react'; + +import { DEFAULT_COLOR, DEFAULT_METRIC } from '@constants'; + +import { SunburstProps } from '../../../types'; + +// destructure props here and set defaults so that storybook can pick them up +const Sunburst: FC = ({ + children, + color = DEFAULT_COLOR, + metric = DEFAULT_METRIC, + name, + key, + parentKey, +}) => { + return null; +}; + +// displayName is used to validate the component type in the spec builder +Sunburst.displayName = 'Sunburst'; + +export { Sunburst }; diff --git a/src/alpha/components/Sunburst/index.ts b/src/alpha/components/Sunburst/index.ts new file mode 100644 index 000000000..e9398f530 --- /dev/null +++ b/src/alpha/components/Sunburst/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export * from './Sunburst'; diff --git a/src/alpha/components/index.ts b/src/alpha/components/index.ts index e29b443f4..e67f63310 100644 --- a/src/alpha/components/index.ts +++ b/src/alpha/components/index.ts @@ -10,4 +10,5 @@ * governing permissions and limitations under the License. */ -export * from './Combo' \ No newline at end of file +export * from './Combo'; +export * from './Sunburst'; diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx new file mode 100644 index 000000000..c733ed339 --- /dev/null +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { ReactElement } from 'react'; + +import useChartProps from '@hooks/useChartProps'; +import { Chart, ChartProps, SunburstProps } from '@rsc'; +import { Sunburst } from '@rsc/alpha'; +import { StoryFn } from '@storybook/react'; +import { bindWithProps } from '@test-utils'; + +import { basicSunburstData } from './data'; + +export default { + title: 'RSC/Sunburst', + component: Sunburst, +}; + +const defaultChartProps: ChartProps = { + data: basicSunburstData, + width: 350, + height: 350, +}; + +const SunburstStory: StoryFn = (args): ReactElement => { + const { width, height, ...donutProps } = args; + const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 350, height: height ?? 350 }); + return ( + + + + ); +}; + +const Basic = bindWithProps(SunburstStory); +Basic.args = { + metric: 'count', + parentKey: 'parent', + key: 'id', +}; + +export { Basic }; diff --git a/src/stories/components/Sunburst/data.ts b/src/stories/components/Sunburst/data.ts new file mode 100644 index 000000000..5475778c8 --- /dev/null +++ b/src/stories/components/Sunburst/data.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export const basicSunburstData = [ + { + id: 1, + name: 'Root', + }, + { + id: 2, + name: 'Chrome', + parent: 1, + }, + { + id: 3, + name: 'V 130', + parent: 2, + value: 200, + }, + { + id: 4, + parent: 2, + name: 'V 131', + value: 100, + }, + { + id: 5, + parent: 2, + name: 'V 132', + value: 500, + }, + { + id: 6, + parent: 1, + name: 'Firefox', + }, + { + id: 7, + parent: 6, + name: 'Alpha', + value: 100, + }, + { + id: 8, + parent: 6, + name: 'Beta', + value: 200, + }, + { + id: 9, + parent: 6, + name: 'Prod', + value: 300, + }, + { + id: 10, + parent: 1, + name: 'Safari', + }, + { + id: 11, + parent: 10, + name: '12.4.64', + value: 50, + }, + { + id: 12, + parent: 10, + name: '12.4.65', + value: 50, + }, + { + id: 13, + parent: 10, + name: '12.5.0', + value: 50, + }, + { + id: 14, + parent: 1, + name: 'Edge', + }, + { + id: 15, + parent: 14, + name: '1', + value: 300, + }, + { + id: 16, + parent: 14, + name: '2', + value: 300, + }, + { + id: 17, + parent: 14, + name: '3', + value: 300, + }, + { + id: 18, + parent: 14, + name: '4', + value: 300, + }, +]; diff --git a/src/types/Chart.ts b/src/types/Chart.ts index 02cb50c90..3dbf1342a 100644 --- a/src/types/Chart.ts +++ b/src/types/Chart.ts @@ -242,6 +242,14 @@ export interface DonutSummaryProps { label?: string; } +export interface SunburstProps extends MarkProps { + /** identifier for a data element */ + key: string; + + /** identifies the key of this elements parent, if any parent exists */ + parentKey: string; +} + export interface SegmentLabelProps { /** Sets the key in the data that has the segment label. Defaults to the `color` key set on the `Donut` is undefined. */ labelKey?: string; From 0947e9fd804c1e2b9b0988e3587a758b0a25ccb7 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Mon, 9 Dec 2024 13:40:04 -0700 Subject: [PATCH 02/15] feat: hook Sunburst into spec builder --- src/alpha/components/Sunburst/Sunburst.tsx | 4 +- src/specBuilder/chartSpecBuilder.ts | 22 ++++++-- .../sunburst/sunburstSpecBuilder.ts | 53 +++++++++++++++++++ .../components/Sunburst/Sunburst.story.tsx | 8 +-- src/types/Chart.ts | 7 ++- src/types/specBuilderTypes.ts | 12 +++++ src/utils/utils.ts | 9 +++- 7 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/specBuilder/sunburst/sunburstSpecBuilder.ts diff --git a/src/alpha/components/Sunburst/Sunburst.tsx b/src/alpha/components/Sunburst/Sunburst.tsx index 3ba63a32c..f2b1777c5 100644 --- a/src/alpha/components/Sunburst/Sunburst.tsx +++ b/src/alpha/components/Sunburst/Sunburst.tsx @@ -23,8 +23,8 @@ const Sunburst: FC = ({ color = DEFAULT_COLOR, metric = DEFAULT_METRIC, name, - key, - parentKey, + id, + parentId, }) => { return null; }; diff --git a/src/specBuilder/chartSpecBuilder.ts b/src/specBuilder/chartSpecBuilder.ts index a141fe163..5fc7e08fd 100644 --- a/src/specBuilder/chartSpecBuilder.ts +++ b/src/specBuilder/chartSpecBuilder.ts @@ -29,7 +29,7 @@ import { TABLE, } from '@constants'; import { Area, Axis, Bar, Legend, Line, Scatter, Title } from '@rsc'; -import { Combo } from '@rsc/alpha'; +import { Combo, Sunburst } from '@rsc/alpha'; import { BigNumber, Donut } from '@rsc/rc'; import colorSchemes from '@themes/colorSchemes'; import { produce } from 'immer'; @@ -54,6 +54,7 @@ import { Opacities, SanitizedSpecProps, ScatterElement, + SunburstElement, SymbolShapes, SymbolSize, TitleElement, @@ -80,6 +81,7 @@ import { getVegaSymbolSizeFromRscSymbolSize, initializeSpec, } from './specUtils'; +import { addSunburst } from './sunburst/sunburstSpecBuilder'; import { addTitle } from './title/titleSpecBuilder'; export function buildSpec(props: SanitizedSpecProps) { @@ -110,14 +112,24 @@ export function buildSpec(props: SanitizedSpecProps) { buildOrder.set(Bar, 0); buildOrder.set(Line, 0); buildOrder.set(Donut, 0); + buildOrder.set(Sunburst, 0); buildOrder.set(Scatter, 0); buildOrder.set(Combo, 0); buildOrder.set(Legend, 1); buildOrder.set(Axis, 2); buildOrder.set(Title, 3); - let { areaCount, axisCount, barCount, comboCount, donutCount, legendCount, lineCount, scatterCount } = - initializeComponentCounts(); + let { + areaCount, + axisCount, + barCount, + comboCount, + donutCount, + sunburstCount, + legendCount, + lineCount, + scatterCount, + } = initializeComponentCounts(); const specProps = { colorScheme, idKey, highlightedItem }; spec = [...children] .sort((a, b) => buildOrder.get(a.type) - buildOrder.get(b.type)) @@ -144,6 +156,9 @@ export function buildSpec(props: SanitizedSpecProps) { case Donut.displayName: donutCount++; return addDonut(acc, { ...(cur as DonutElement).props, ...specProps, index: donutCount }); + case Sunburst.displayName: + sunburstCount++; + return addSunburst(acc, { ...(cur as SunburstElement).props, ...specProps, index: sunburstCount }); case Legend.displayName: legendCount++; return addLegend(acc, { @@ -202,6 +217,7 @@ const initializeComponentCounts = () => { barCount: -1, comboCount: -1, donutCount: -1, + sunburstCount: -1, legendCount: -1, lineCount: -1, scatterCount: -1, diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts new file mode 100644 index 000000000..9f6e5e335 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC } from '@constants'; +import { sanitizeMarkChildren, toCamelCase } from '@utils'; +import { produce } from 'immer'; +import { Spec } from 'vega'; + +import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; + +export const addSunburst = produce< + Spec, + [SunburstProps & { colorScheme?: ColorScheme; highlightedItem?: HighlightedItem; index?: number; idKey: string }] +>( + ( + spec, + { + children, + color = DEFAULT_COLOR, + colorScheme = DEFAULT_COLOR_SCHEME, + index = 0, + metric = DEFAULT_METRIC, + name, + id = 'id', + parentId = 'parent', + ...props + } + ) => { + // put props back together now that all defaults are set + const sunburstProps: SunburstSpecProps = { + children: sanitizeMarkChildren(children), + color, + colorScheme, + index, + markType: 'sunburst', + metric, + id, + parentId, + name: toCamelCase(name ?? `donut${index}`), + ...props, + }; + + console.log('props are', sunburstProps); + } +); diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index c733ed339..91ad1198a 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -31,11 +31,11 @@ const defaultChartProps: ChartProps = { }; const SunburstStory: StoryFn = (args): ReactElement => { - const { width, height, ...donutProps } = args; + const { width, height, ...sunburstProps } = args; const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 350, height: height ?? 350 }); return ( - + ); }; @@ -43,8 +43,8 @@ const SunburstStory: StoryFn>; export type ChartTooltipElement = ReactElement>; export type DonutElement = ReactElement>; +export type SunburstElement = ReactElement>; export type DonutSummaryElement = ReactElement>; export type LegendElement = ReactElement>; export type LineElement = ReactElement>; @@ -244,10 +245,10 @@ export interface DonutSummaryProps { export interface SunburstProps extends MarkProps { /** identifier for a data element */ - key: string; + id: string; /** identifies the key of this elements parent, if any parent exists */ - parentKey: string; + parentId: string; } export interface SegmentLabelProps { @@ -880,6 +881,8 @@ export type ChartChildElement = | LineElement | ScatterElement | TitleElement + | DonutElement + | SunburstElement | ComboElement; export type MarkChildElement = | AnnotationElement diff --git a/src/types/specBuilderTypes.ts b/src/types/specBuilderTypes.ts index bdf8bb556..bf058d2b3 100644 --- a/src/types/specBuilderTypes.ts +++ b/src/types/specBuilderTypes.ts @@ -39,6 +39,7 @@ import { ScatterPathProps, ScatterProps, SegmentLabelProps, + SunburstProps, TrendlineAnnotationProps, TrendlineChildElement, TrendlineProps, @@ -150,6 +151,17 @@ export interface DonutSpecProps extends PartiallyRequired { + children: MarkChildElement[]; + colorScheme: ColorScheme; + highlightedItem?: HighlightedItem; + idKey: string; + index: number; + markType: 'sunburst'; +} + type DonutSummaryPropsWithDefaults = 'numberFormat'; export interface DonutSummarySpecProps extends PartiallyRequired { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f6636281c..4340a21f4 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -30,7 +30,7 @@ import { Trendline, TrendlineAnnotation, } from '@rsc'; -import { Combo } from '@rsc/alpha'; +import { Combo, Sunburst } from '@rsc/alpha'; import { BigNumber, Donut, DonutSummary, SegmentLabel } from '@rsc/rc'; import { View } from 'vega'; @@ -54,6 +54,7 @@ import { MarkChildElement, RscElement, ScatterElement, + SunburstElement, TrendlineElement, } from '../types'; @@ -64,6 +65,7 @@ type ElementCounts = { axisAnnotation: number; bar: number; donut: number; + sunburst: number; legend: number; line: number; scatter: number; @@ -97,6 +99,7 @@ export const sanitizeRscChartChildren = (children: unknown): ChartChildElement[] Axis.displayName, Bar.displayName, Donut.displayName, + Sunburst.displayName, Legend.displayName, Line.displayName, Scatter.displayName, @@ -339,6 +342,9 @@ const getElementName = (element: unknown, elementCounts: ElementCounts) => { case Donut.displayName: elementCounts.donut++; return getComponentName(element as DonutElement, `donut${elementCounts.donut}`); + case Sunburst.displayName: + elementCounts.sunburst++; + return getComponentName(element as SunburstElement, `sunburst${elementCounts.donut}`); case Legend.displayName: elementCounts.legend++; return getComponentName(element as LegendElement, `legend${elementCounts.legend}`); @@ -377,6 +383,7 @@ const initElementCounts = (): ElementCounts => ({ axisAnnotation: -1, bar: -1, donut: -1, + sunburst: -1, legend: -1, line: -1, scatter: -1, From 2ce91e03b82348106eb86cc5908c8ec7701dee46 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Tue, 10 Dec 2024 08:30:47 -0700 Subject: [PATCH 03/15] feat: add data transforms to sunburst --- .../sunburst/sunburstSpecBuilder.ts | 33 +++++++++++++++++-- .../components/Sunburst/Sunburst.story.tsx | 2 +- src/stories/components/Sunburst/data.ts | 2 +- src/types/Chart.ts | 2 +- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 9f6e5e335..5656ae534 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -9,10 +9,10 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC } from '@constants'; +import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, FILTERED_TABLE } from '@constants'; import { sanitizeMarkChildren, toCamelCase } from '@utils'; import { produce } from 'immer'; -import { Spec } from 'vega'; +import { Data, PartitionTransform, Spec, StratifyTransform } from 'vega'; import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; @@ -48,6 +48,33 @@ export const addSunburst = produce< ...props, }; - console.log('props are', sunburstProps); + spec.data = addData(spec.data ?? [], sunburstProps); } ); + +export const addData = produce((data, props) => { + const filteredTableIndex = data.findIndex((d) => d.name === FILTERED_TABLE); + + //set up transforms + data[filteredTableIndex].transform = data[filteredTableIndex].transform ?? []; + data[filteredTableIndex].transform?.push(...getSunburstDataTransforms(props)); +}); + +const getSunburstDataTransforms = ({ + id, + parentId, + metric, +}: SunburstSpecProps): (StratifyTransform | PartitionTransform)[] => [ + { + type: 'stratify', + key: id, + parentKey: parentId, + }, + { + type: 'partition', + field: metric, + sort: { field: metric }, + size: [{ signal: '2 * PI' }, { signal: 'width / 2' }], + as: ['a0', 'r0', 'a1', 'r1', 'depth', 'children'], + }, +]; diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index 91ad1198a..cfce3888c 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -42,7 +42,7 @@ const SunburstStory: StoryFn Date: Tue, 10 Dec 2024 09:17:26 -0700 Subject: [PATCH 04/15] feat: add marks to sunburst --- src/specBuilder/sunburst/sunburstMarkUtils.ts | 47 +++++++++++++++++++ .../sunburst/sunburstSpecBuilder.ts | 14 ++++-- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/specBuilder/sunburst/sunburstMarkUtils.ts diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts new file mode 100644 index 000000000..095b82459 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { FILTERED_TABLE } from '@constants'; +import { getTooltip } from '@specBuilder/marks/markUtils'; +import { ArcMark } from 'vega'; + +import { SunburstSpecProps } from '../../types'; + +export const getArcMark = (props: SunburstSpecProps): ArcMark => { + const { children, name } = props; + return { + type: 'arc', + name, + from: { data: FILTERED_TABLE }, + encode: { + enter: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + fill: { scale: 'color', field: 'depth' }, + tooltip: getTooltip(children, name), + }, + update: { + startAngle: { field: 'a0' }, + endAngle: { field: 'a1' }, + innerRadius: { field: 'r0' }, + outerRadius: { field: 'r1' }, + stroke: { value: 'white' }, + strokeWidth: { value: 0.5 }, + zindex: { value: 0 }, + }, + hover: { + stroke: { value: 'red' }, + strokeWidth: { value: 2 }, + zindex: { value: 1 }, + }, + }, + }; +}; diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 5656ae534..e81cc2325 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 Adobe. All rights reserved. + * Copyright 2024 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 @@ -12,9 +12,10 @@ import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, FILTERED_TABLE } from '@constants'; import { sanitizeMarkChildren, toCamelCase } from '@utils'; import { produce } from 'immer'; -import { Data, PartitionTransform, Spec, StratifyTransform } from 'vega'; +import { Data, Mark, PartitionTransform, Spec, StratifyTransform } from 'vega'; import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; +import { getArcMark } from './sunburstMarkUtils'; export const addSunburst = produce< Spec, @@ -44,11 +45,14 @@ export const addSunburst = produce< metric, id, parentId, - name: toCamelCase(name ?? `donut${index}`), + name: toCamelCase(name ?? `sunburst${index}`), ...props, }; spec.data = addData(spec.data ?? [], sunburstProps); + spec.marks = addMarks(spec.marks ?? [], sunburstProps); + + console.log('spec is', JSON.stringify(spec, null, 2)); } ); @@ -78,3 +82,7 @@ const getSunburstDataTransforms = ({ as: ['a0', 'r0', 'a1', 'r1', 'depth', 'children'], }, ]; + +export const addMarks = produce((marks, props) => { + marks.push(getArcMark(props)); +}); From a6a452d0c6806cc7d003bc987ac8cc52bf6ccc57 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Tue, 10 Dec 2024 10:09:55 -0700 Subject: [PATCH 05/15] feat: add initial scales to sunburst --- src/specBuilder/sunburst/sunburstMarkUtils.ts | 16 +++++----------- src/specBuilder/sunburst/sunburstSpecBuilder.ts | 14 +++++++++----- .../components/Sunburst/Sunburst.story.tsx | 4 ++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts index 095b82459..ea6f1475b 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -9,24 +9,23 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { FILTERED_TABLE } from '@constants'; -import { getTooltip } from '@specBuilder/marks/markUtils'; +import { TABLE } from '@constants'; import { ArcMark } from 'vega'; import { SunburstSpecProps } from '../../types'; export const getArcMark = (props: SunburstSpecProps): ArcMark => { - const { children, name } = props; + const { name } = props; return { type: 'arc', name, - from: { data: FILTERED_TABLE }, + from: { data: TABLE }, encode: { enter: { x: { signal: 'width / 2' }, y: { signal: 'height / 2' }, - fill: { scale: 'color', field: 'depth' }, - tooltip: getTooltip(children, name), + fill: { value: 'red' }, + fillOpacity: { scale: 'opacity', field: 'depth' }, }, update: { startAngle: { field: 'a0' }, @@ -37,11 +36,6 @@ export const getArcMark = (props: SunburstSpecProps): ArcMark => { strokeWidth: { value: 0.5 }, zindex: { value: 0 }, }, - hover: { - stroke: { value: 'red' }, - strokeWidth: { value: 2 }, - zindex: { value: 1 }, - }, }, }; }; diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index e81cc2325..68abb1a0d 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -9,10 +9,11 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, FILTERED_TABLE } from '@constants'; +import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, TABLE } from '@constants'; +import { addFieldToFacetScaleDomain } from '@specBuilder/scale/scaleSpecBuilder'; import { sanitizeMarkChildren, toCamelCase } from '@utils'; import { produce } from 'immer'; -import { Data, Mark, PartitionTransform, Spec, StratifyTransform } from 'vega'; +import { Data, Mark, PartitionTransform, Scale, Spec, StratifyTransform } from 'vega'; import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; import { getArcMark } from './sunburstMarkUtils'; @@ -50,14 +51,13 @@ export const addSunburst = produce< }; spec.data = addData(spec.data ?? [], sunburstProps); + spec.scales = addScales(spec.scales ?? [], sunburstProps); spec.marks = addMarks(spec.marks ?? [], sunburstProps); - - console.log('spec is', JSON.stringify(spec, null, 2)); } ); export const addData = produce((data, props) => { - const filteredTableIndex = data.findIndex((d) => d.name === FILTERED_TABLE); + const filteredTableIndex = data.findIndex((d) => d.name === TABLE); //set up transforms data[filteredTableIndex].transform = data[filteredTableIndex].transform ?? []; @@ -83,6 +83,10 @@ const getSunburstDataTransforms = ({ }, ]; +export const addScales = produce((scales) => { + addFieldToFacetScaleDomain(scales, 'opacity', 'depth'); +}); + export const addMarks = produce((marks, props) => { marks.push(getArcMark(props)); }); diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index cfce3888c..f0d89a002 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -32,9 +32,9 @@ const defaultChartProps: ChartProps = { const SunburstStory: StoryFn = (args): ReactElement => { const { width, height, ...sunburstProps } = args; - const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 350, height: height ?? 350 }); + const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 600, height: height ?? 600 }); return ( - + ); From d8a7f8bdefbbb23fb1322d7b3c954e23a05d6798 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Tue, 10 Dec 2024 10:36:58 -0700 Subject: [PATCH 06/15] feat: stratify color and opacity sunburst --- src/specBuilder/sunburst/sunburstMarkUtils.ts | 7 +- .../sunburst/sunburstSpecBuilder.ts | 12 ++- .../components/Sunburst/Sunburst.story.tsx | 1 + src/stories/components/Sunburst/data.ts | 102 +++++++++++++++++- src/types/Chart.ts | 3 + src/types/specBuilderTypes.ts | 2 +- 6 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts index ea6f1475b..3af4db6a6 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -15,7 +15,7 @@ import { ArcMark } from 'vega'; import { SunburstSpecProps } from '../../types'; export const getArcMark = (props: SunburstSpecProps): ArcMark => { - const { name } = props; + const { name, segmentKey } = props; return { type: 'arc', name, @@ -24,7 +24,10 @@ export const getArcMark = (props: SunburstSpecProps): ArcMark => { enter: { x: { signal: 'width / 2' }, y: { signal: 'height / 2' }, - fill: { value: 'red' }, + fill: { + scale: 'color', + field: segmentKey, + }, fillOpacity: { scale: 'opacity', field: 'depth' }, }, update: { diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 68abb1a0d..bc914fffc 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -33,6 +33,7 @@ export const addSunburst = produce< name, id = 'id', parentId = 'parent', + segmentKey = 'segment', ...props } ) => { @@ -46,6 +47,7 @@ export const addSunburst = produce< metric, id, parentId, + segmentKey, name: toCamelCase(name ?? `sunburst${index}`), ...props, }; @@ -57,11 +59,11 @@ export const addSunburst = produce< ); export const addData = produce((data, props) => { - const filteredTableIndex = data.findIndex((d) => d.name === TABLE); + const tableIndex = data.findIndex((d) => d.name === TABLE); //set up transforms - data[filteredTableIndex].transform = data[filteredTableIndex].transform ?? []; - data[filteredTableIndex].transform?.push(...getSunburstDataTransforms(props)); + data[tableIndex].transform = data[tableIndex].transform ?? []; + data[tableIndex].transform?.push(...getSunburstDataTransforms(props)); }); const getSunburstDataTransforms = ({ @@ -83,8 +85,10 @@ const getSunburstDataTransforms = ({ }, ]; -export const addScales = produce((scales) => { +export const addScales = produce((scales, props) => { + const { segmentKey } = props; addFieldToFacetScaleDomain(scales, 'opacity', 'depth'); + addFieldToFacetScaleDomain(scales, 'color', segmentKey); }); export const addMarks = produce((marks, props) => { diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index f0d89a002..f80929c26 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -45,6 +45,7 @@ Basic.args = { metric: 'value', parentId: 'parent', id: 'id', + segmentKey: 'segment', }; export { Basic }; diff --git a/src/stories/components/Sunburst/data.ts b/src/stories/components/Sunburst/data.ts index ee8cae8b2..05317a182 100644 --- a/src/stories/components/Sunburst/data.ts +++ b/src/stories/components/Sunburst/data.ts @@ -19,98 +19,192 @@ export const basicSunburstData = [ id: 2, name: 'Chrome', parent: 1, + segment: 'chrome', }, { id: 3, name: 'V 130', parent: 2, value: 200, + segment: 'chrome', }, { id: 4, parent: 2, name: 'V 131', value: 100, + segment: 'chrome', }, { id: 5, parent: 2, name: 'V 132', value: 500, + segment: 'chrome', }, { id: 6, parent: 1, name: 'Firefox', + segment: 'firefox', }, { id: 7, parent: 6, name: 'Alpha', value: 100, + segment: 'firefox', }, { id: 8, parent: 6, name: 'Beta', value: 200, + segment: 'firefox', }, { id: 9, parent: 6, name: 'Prod', value: 300, + segment: 'firefox', }, { id: 10, parent: 1, name: 'Safari', + segment: 'safari', }, { id: 11, parent: 10, name: '12.4.64', value: 50, + segment: 'safari', }, { id: 12, parent: 10, name: '12.4.65', value: 50, + segment: 'safari', }, { id: 13, parent: 10, name: '12.5.0', value: 50, + segment: 'safari', }, { id: 14, parent: 1, name: 'Edge', + segment: 'edge', }, { id: 15, parent: 14, name: '1', - value: 300, + value: 200, + segment: 'edge', }, { id: 16, parent: 14, name: '2', - value: 300, + value: 200, + segment: 'edge', }, { id: 17, parent: 14, name: '3', - value: 300, + value: 200, + segment: 'edge', }, { id: 18, parent: 14, name: '4', - value: 300, + value: 200, + segment: 'edge', + }, + { + id: 19, + parent: 18, + name: '4.1', + value: 85, + segment: 'edge', + }, + { + id: 20, + parent: 19, + name: '4.1.1', + value: 40, + segment: 'edge', + }, + { + id: 21, + parent: 19, + name: '4.1.2', + value: 45, + segment: 'edge', + }, + { + id: 22, + parent: 3, + name: 'V 130.1', + value: 100, + segment: 'chrome', + }, + { + id: 23, + parent: 3, + name: 'V 130.2', + value: 100, + segment: 'chrome', + }, + { + id: 24, + parent: 7, + name: 'Alpha 1', + value: 50, + segment: 'firefox', + }, + { + id: 25, + parent: 7, + name: 'Alpha 2', + value: 50, + segment: 'firefox', + }, + { + id: 26, + parent: 8, + name: 'Beta 1', + value: 100, + segment: 'firefox', + }, + { + id: 27, + parent: 8, + name: 'Beta 2', + value: 100, + segment: 'firefox', + }, + { + id: 28, + parent: 9, + name: 'Prod 1', + value: 150, + segment: 'firefox', + }, + { + id: 29, + parent: 9, + name: 'Prod 2', + value: 150, + segment: 'firefox', }, ]; diff --git a/src/types/Chart.ts b/src/types/Chart.ts index 3c2c408d7..5574d58da 100644 --- a/src/types/Chart.ts +++ b/src/types/Chart.ts @@ -249,6 +249,9 @@ export interface SunburstProps extends MarkProps { /** identifies the key of this elements parent, if any parent exists */ parentId: string; + + /** identifies which segment each element is part of */ + segmentKey: string; } export interface SegmentLabelProps { diff --git a/src/types/specBuilderTypes.ts b/src/types/specBuilderTypes.ts index bf058d2b3..f0fccf977 100644 --- a/src/types/specBuilderTypes.ts +++ b/src/types/specBuilderTypes.ts @@ -151,7 +151,7 @@ export interface DonutSpecProps extends PartiallyRequired { children: MarkChildElement[]; From 9b278d30c90d660a7f9a2f64429441d7b26b03be Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Tue, 10 Dec 2024 13:34:32 -0700 Subject: [PATCH 07/15] feat: sunburst popovers --- src/specBuilder/sunburst/sunburstMarkUtils.ts | 4 ++- .../sunburst/sunburstSpecBuilder.ts | 11 ++++++- .../components/Sunburst/Sunburst.story.tsx | 31 +++++++++++++++++-- src/stories/components/Sunburst/data.ts | 6 ---- src/utils/utils.ts | 2 +- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts index 3af4db6a6..71df340d6 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -10,12 +10,13 @@ * governing permissions and limitations under the License. */ import { TABLE } from '@constants'; +import { getTooltip } from '@specBuilder/marks/markUtils'; import { ArcMark } from 'vega'; import { SunburstSpecProps } from '../../types'; export const getArcMark = (props: SunburstSpecProps): ArcMark => { - const { name, segmentKey } = props; + const { name, children, segmentKey } = props; return { type: 'arc', name, @@ -29,6 +30,7 @@ export const getArcMark = (props: SunburstSpecProps): ArcMark => { field: segmentKey, }, fillOpacity: { scale: 'opacity', field: 'depth' }, + tooltip: getTooltip(children, name), }, update: { startAngle: { field: 'a0' }, diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index bc914fffc..589440bf5 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -10,10 +10,12 @@ * governing permissions and limitations under the License. */ import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, TABLE } from '@constants'; +import { getTooltipProps, hasInteractiveChildren } from '@specBuilder/marks/markUtils'; import { addFieldToFacetScaleDomain } from '@specBuilder/scale/scaleSpecBuilder'; +import { addHighlightedItemSignalEvents } from '@specBuilder/signal/signalSpecBuilder'; import { sanitizeMarkChildren, toCamelCase } from '@utils'; import { produce } from 'immer'; -import { Data, Mark, PartitionTransform, Scale, Spec, StratifyTransform } from 'vega'; +import { Data, Mark, PartitionTransform, Scale, Signal, Spec, StratifyTransform } from 'vega'; import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; import { getArcMark } from './sunburstMarkUtils'; @@ -55,6 +57,7 @@ export const addSunburst = produce< spec.data = addData(spec.data ?? [], sunburstProps); spec.scales = addScales(spec.scales ?? [], sunburstProps); spec.marks = addMarks(spec.marks ?? [], sunburstProps); + spec.signals = addSignals(spec.signals ?? [], sunburstProps); } ); @@ -94,3 +97,9 @@ export const addScales = produce((scales, props) = export const addMarks = produce((marks, props) => { marks.push(getArcMark(props)); }); + +export const addSignals = produce((signals, props) => { + const { children, idKey, name } = props; + if (!hasInteractiveChildren(children)) return; + addHighlightedItemSignalEvents(signals, name, idKey, 1, getTooltipProps(children)?.excludeDataKeys); +}); diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index f80929c26..ae2ded0bd 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -12,11 +12,13 @@ import { ReactElement } from 'react'; import useChartProps from '@hooks/useChartProps'; -import { Chart, ChartProps, SunburstProps } from '@rsc'; +import { Chart, ChartPopover, ChartProps, ChartTooltip, Datum, SunburstProps } from '@rsc'; import { Sunburst } from '@rsc/alpha'; import { StoryFn } from '@storybook/react'; import { bindWithProps } from '@test-utils'; +import { Content } from '@adobe/react-spectrum'; + import { basicSunburstData } from './data'; export default { @@ -40,6 +42,22 @@ const SunburstStory: StoryFn { + return ( + +
Browser: {datum.segment}
+
Users: {datum.value}
+
+ ); +}; + +const interactiveChildren = [ + {dialogContent}, + + {dialogContent} + , +]; + const Basic = bindWithProps(SunburstStory); Basic.args = { metric: 'value', @@ -48,4 +66,13 @@ Basic.args = { segmentKey: 'segment', }; -export { Basic }; +const WithPopovers = bindWithProps(SunburstStory); +WithPopovers.args = { + metric: 'value', + parentId: 'parent', + id: 'id', + segmentKey: 'segment', + children: interactiveChildren, +}; + +export { Basic, WithPopovers }; diff --git a/src/stories/components/Sunburst/data.ts b/src/stories/components/Sunburst/data.ts index 05317a182..b79031cb0 100644 --- a/src/stories/components/Sunburst/data.ts +++ b/src/stories/components/Sunburst/data.ts @@ -25,7 +25,6 @@ export const basicSunburstData = [ id: 3, name: 'V 130', parent: 2, - value: 200, segment: 'chrome', }, { @@ -52,21 +51,18 @@ export const basicSunburstData = [ id: 7, parent: 6, name: 'Alpha', - value: 100, segment: 'firefox', }, { id: 8, parent: 6, name: 'Beta', - value: 200, segment: 'firefox', }, { id: 9, parent: 6, name: 'Prod', - value: 300, segment: 'firefox', }, { @@ -127,14 +123,12 @@ export const basicSunburstData = [ id: 18, parent: 14, name: '4', - value: 200, segment: 'edge', }, { id: 19, parent: 18, name: '4.1', - value: 85, segment: 'edge', }, { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4340a21f4..91ec58fba 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -344,7 +344,7 @@ const getElementName = (element: unknown, elementCounts: ElementCounts) => { return getComponentName(element as DonutElement, `donut${elementCounts.donut}`); case Sunburst.displayName: elementCounts.sunburst++; - return getComponentName(element as SunburstElement, `sunburst${elementCounts.donut}`); + return getComponentName(element as SunburstElement, `sunburst${elementCounts.sunburst}`); case Legend.displayName: elementCounts.legend++; return getComponentName(element as LegendElement, `legend${elementCounts.legend}`); From fb1e65ae876caf822f7d5919490435740b7b4f05 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 09:42:35 -0700 Subject: [PATCH 08/15] feat: child nodes fill correct percent of parent --- src/RscChart.tsx | 13 ++ .../sunburst/sunburstDataModificationUtils.ts | 26 +++ .../sunburst/sunburstSpecBuilder.ts | 2 +- .../components/Sunburst/Sunburst.story.tsx | 29 ++- src/stories/components/Sunburst/data.ts | 213 +++++++++++------- 5 files changed, 204 insertions(+), 79 deletions(-) create mode 100644 src/specBuilder/sunburst/sunburstDataModificationUtils.ts diff --git a/src/RscChart.tsx b/src/RscChart.tsx index a6f956521..86e4edc7f 100644 --- a/src/RscChart.tsx +++ b/src/RscChart.tsx @@ -29,6 +29,7 @@ import useSpec from '@hooks/useSpec'; import useSpecProps from '@hooks/useSpecProps'; import useTooltips from '@hooks/useTooltips'; import { getColorValue } from '@specBuilder/specUtils'; +import { createLeafValues } from '@specBuilder/sunburst/sunburstDataModificationUtils'; import { getChartConfig } from '@themes/spectrumTheme'; import { debugLog, @@ -37,6 +38,7 @@ import { sanitizeRscChartChildren, setSelectedSignals, } from '@utils'; +import { Sunburst } from 'alpha/components'; import { renderToStaticMarkup } from 'react-dom/server'; import { Item } from 'vega'; import { Handler, Position, Options as TooltipOptions } from 'vega-tooltip'; @@ -52,6 +54,7 @@ import { LegendDescription, MarkBounds, RscChartProps, + SunburstProps, TooltipAnchor, TooltipPlacement, } from './types'; @@ -112,6 +115,16 @@ export const RscChart = forwardRef( const sanitizedChildren = sanitizeRscChartChildren(props.children); + //NOTE: I don't love this pattern. I don't want to modify user data outside transforms. I just couldn't figure out the right transforms to get the data in the right way + // to make this work. If this wasn't a garage week project, I'd take more time to do transforms to get the data right instead of manually doing this + sanitizedChildren.forEach((child) => { + if (child.type.name === Sunburst.displayName) { + const sunburstProps = child.props as unknown as SunburstProps; + const { id, parentId, metric = 'value' } = sunburstProps; + createLeafValues(data, id, parentId, metric); + } + }); + // THE MAGIC, builds our spec const spec = useSpec({ backgroundColor, diff --git a/src/specBuilder/sunburst/sunburstDataModificationUtils.ts b/src/specBuilder/sunburst/sunburstDataModificationUtils.ts new file mode 100644 index 000000000..c5b418f64 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstDataModificationUtils.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { Datum } from 'vega'; + +export const createLeafValues = (data: Datum[], id: string, parentId: string, metric: string) => { + data.forEach((element) => { + element[`${metric}_childSum`] = data + .filter((e) => e[parentId] === element[id]) + .reduce((acc, e) => acc + e[metric], 0); + }); + data.forEach((element) => { + element[`${metric}_leafValue`] = element[metric] - element[`${metric}_childSum`]; + if (element[`${metric}_leafValue`] < 0) { + element[`${metric}_leafValue`] = 0; + } + }); +}; diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 589440bf5..887cc8c4d 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -81,7 +81,7 @@ const getSunburstDataTransforms = ({ }, { type: 'partition', - field: metric, + field: `${metric}_leafValue`, sort: { field: metric }, size: [{ signal: '2 * PI' }, { signal: 'width / 2' }], as: ['a0', 'r0', 'a1', 'r1', 'depth', 'children'], diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index ae2ded0bd..5c75c0c6c 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -19,7 +19,7 @@ import { bindWithProps } from '@test-utils'; import { Content } from '@adobe/react-spectrum'; -import { basicSunburstData } from './data'; +import { basicSunburstData, simpleSunburstData } from './data'; export default { title: 'RSC/Sunburst', @@ -32,6 +32,12 @@ const defaultChartProps: ChartProps = { height: 350, }; +const smallChartProps: ChartProps = { + data: simpleSunburstData, + width: 350, + height: 350, +}; + const SunburstStory: StoryFn = (args): ReactElement => { const { width, height, ...sunburstProps } = args; const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 600, height: height ?? 600 }); @@ -42,6 +48,16 @@ const SunburstStory: StoryFn = (args): ReactElement => { + const { width, height, ...sunburstProps } = args; + const chartProps = useChartProps({ ...smallChartProps, width: width ?? 600, height: height ?? 600 }); + return ( + + + + ); +}; + const dialogContent = (datum: Datum) => { return ( @@ -66,6 +82,15 @@ Basic.args = { segmentKey: 'segment', }; +const Small = bindWithProps(SmallSunburstStory); +Small.args = { + metric: 'value', + parentId: 'parent', + id: 'id', + segmentKey: 'segment', + children: interactiveChildren, +}; + const WithPopovers = bindWithProps(SunburstStory); WithPopovers.args = { metric: 'value', @@ -75,4 +100,4 @@ WithPopovers.args = { children: interactiveChildren, }; -export { Basic, WithPopovers }; +export { Small, Basic, WithPopovers }; diff --git a/src/stories/components/Sunburst/data.ts b/src/stories/components/Sunburst/data.ts index b79031cb0..b03c84fde 100644 --- a/src/stories/components/Sunburst/data.ts +++ b/src/stories/components/Sunburst/data.ts @@ -13,192 +13,253 @@ export const basicSunburstData = [ { id: 1, - name: 'Browsers', + name: 'All browsers', + value: 2235, }, { id: 2, name: 'Chrome', parent: 1, + value: 800, segment: 'chrome', }, { id: 3, name: 'V 130', parent: 2, + value: 200, segment: 'chrome', }, { id: 4, + parent: 3, + name: 'V 130.1', + value: 30, + segment: 'chrome', + }, + { + id: 5, + parent: 3, + name: 'V 130.2', + value: 100, + segment: 'chrome', + }, + { + id: 6, parent: 2, name: 'V 131', value: 100, segment: 'chrome', }, { - id: 5, + id: 7, parent: 2, name: 'V 132', value: 500, segment: 'chrome', }, { - id: 6, + id: 8, parent: 1, name: 'Firefox', + value: 600, segment: 'firefox', }, { - id: 7, - parent: 6, + id: 9, + parent: 8, name: 'Alpha', + value: 100, segment: 'firefox', }, { - id: 8, - parent: 6, + id: 10, + parent: 9, + name: 'Alpha 1', + value: 50, + segment: 'firefox', + }, + { + id: 11, + parent: 9, + name: 'Alpha 2', + value: 50, + segment: 'firefox', + }, + { + id: 12, + parent: 8, name: 'Beta', + value: 200, segment: 'firefox', }, { - id: 9, - parent: 6, + id: 13, + parent: 12, + name: 'Beta 1', + value: 40, + segment: 'firefox', + }, + { + id: 14, + parent: 12, + name: 'Beta 2', + value: 100, + segment: 'firefox', + }, + { + id: 15, + parent: 8, name: 'Prod', + value: 300, segment: 'firefox', }, { - id: 10, + id: 16, + parent: 15, + name: 'Prod 1', + value: 100, + segment: 'firefox', + }, + { + id: 17, + parent: 15, + name: 'Prod 2', + value: 150, + segment: 'firefox', + }, + { + id: 18, parent: 1, name: 'Safari', + value: 150, segment: 'safari', }, { - id: 11, - parent: 10, + id: 19, + parent: 18, name: '12.4.64', - value: 50, + value: 20, segment: 'safari', }, { - id: 12, - parent: 10, + id: 20, + parent: 18, name: '12.4.65', value: 50, segment: 'safari', }, { - id: 13, - parent: 10, + id: 21, + parent: 18, name: '12.5.0', - value: 50, + value: 60, segment: 'safari', }, { - id: 14, + id: 22, parent: 1, name: 'Edge', + value: 685, segment: 'edge', }, { - id: 15, - parent: 14, + id: 23, + parent: 22, name: '1', value: 200, segment: 'edge', }, { - id: 16, - parent: 14, + id: 24, + parent: 22, name: '2', value: 200, segment: 'edge', }, { - id: 17, - parent: 14, + id: 25, + parent: 22, name: '3', value: 200, segment: 'edge', }, { - id: 18, - parent: 14, + id: 26, + parent: 22, name: '4', + value: 85, segment: 'edge', }, { - id: 19, - parent: 18, + id: 27, + parent: 26, name: '4.1', + value: 85, segment: 'edge', }, { - id: 20, - parent: 19, + id: 28, + parent: 27, name: '4.1.1', - value: 40, + value: 30, segment: 'edge', }, { - id: 21, - parent: 19, + id: 29, + parent: 27, name: '4.1.2', - value: 45, + value: 30, segment: 'edge', }, +]; + +export const simpleSunburstData = [ { - id: 22, - parent: 3, - name: 'V 130.1', - value: 100, - segment: 'chrome', - }, - { - id: 23, - parent: 3, - name: 'V 130.2', + id: 1, value: 100, - segment: 'chrome', + name: 'root', }, { - id: 24, - parent: 7, - name: 'Alpha 1', - value: 50, - segment: 'firefox', + id: 2, + parent: 1, + value: 40, + name: 'A', + segment: 'A', }, { - id: 25, - parent: 7, - name: 'Alpha 2', - value: 50, - segment: 'firefox', + id: 3, + parent: 1, + value: 60, + name: 'B', + segment: 'B', }, { - id: 26, - parent: 8, - name: 'Beta 1', - value: 100, - segment: 'firefox', + id: 4, + parent: 2, + value: 30, + name: 'A 1', + segment: 'A', }, { - id: 27, - parent: 8, - name: 'Beta 2', - value: 100, - segment: 'firefox', + id: 5, + parent: 3, + value: 10, + name: 'B 1', + segment: 'B', }, { - id: 28, - parent: 9, - name: 'Prod 1', - value: 150, - segment: 'firefox', + id: 6, + parent: 3, + value: 20, + name: 'B 2', + segment: 'B', }, { - id: 29, - parent: 9, - name: 'Prod 2', - value: 150, - segment: 'firefox', + id: 7, + parent: 5, + value: 10, + name: 'B 1 ^', + segment: 'B', }, ]; From 39339230322808bb92d73cdf2898224afb8b9716 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 14:09:55 -0700 Subject: [PATCH 09/15] feat: add ability to hover legend --- .../chartTooltip/chartTooltipUtils.ts | 11 +++- src/specBuilder/marks/markUtils.ts | 5 +- src/specBuilder/sunburst/sunburstMarkUtils.ts | 5 +- .../sunburst/sunburstSpecBuilder.ts | 2 + .../components/Sunburst/Sunburst.story.tsx | 53 ++++++++++++---- src/stories/components/Sunburst/data.ts | 63 ++++++++++--------- src/types/Chart.ts | 3 + src/types/specBuilderTypes.ts | 2 +- 8 files changed, 98 insertions(+), 46 deletions(-) diff --git a/src/specBuilder/chartTooltip/chartTooltipUtils.ts b/src/specBuilder/chartTooltip/chartTooltipUtils.ts index b11276973..38d742577 100644 --- a/src/specBuilder/chartTooltip/chartTooltipUtils.ts +++ b/src/specBuilder/chartTooltip/chartTooltipUtils.ts @@ -32,9 +32,16 @@ import { DonutSpecProps, LineSpecProps, ScatterSpecProps, + SunburstSpecProps, } from '../../types'; -type TooltipParentProps = AreaSpecProps | BarSpecProps | DonutSpecProps | LineSpecProps | ScatterSpecProps; +type TooltipParentProps = + | AreaSpecProps + | BarSpecProps + | DonutSpecProps + | LineSpecProps + | ScatterSpecProps + | SunburstSpecProps; /** * gets all the tooltips @@ -79,7 +86,7 @@ export const addTooltipData = (data: Data[], markProps: TooltipParentProps, addH if (!filteredTable.transform) { filteredTable.transform = []; } - if (highlightBy === 'dimension' && markProps.markType !== 'donut') { + if (highlightBy === 'dimension' && markProps.markType !== 'donut' && markProps.markType !== 'sunburst') { filteredTable.transform.push(getGroupIdTransform([markProps.dimension], markName)); } else if (highlightBy === 'series') { filteredTable.transform.push(getGroupIdTransform([SERIES_ID], markName)); diff --git a/src/specBuilder/marks/markUtils.ts b/src/specBuilder/marks/markUtils.ts index 2939cade3..f348e7ce3 100644 --- a/src/specBuilder/marks/markUtils.ts +++ b/src/specBuilder/marks/markUtils.ts @@ -67,6 +67,7 @@ import { OpacityFacet, ProductionRuleTests, ScaleType, + SunburstSpecProps, SymbolSizeFacet, } from '../../types'; @@ -413,7 +414,9 @@ const getHoverSizeSignal = (size: number): SignalRef => ({ * @param props * @returns */ -export const getMarkOpacity = (props: BarSpecProps | DonutSpecProps): ({ test?: string } & NumericValueRef)[] => { +export const getMarkOpacity = ( + props: BarSpecProps | DonutSpecProps | SunburstSpecProps +): ({ test?: string } & NumericValueRef)[] => { const { children, highlightedItem, idKey, name: markName } = props; const rules: ({ test?: string } & NumericValueRef)[] = [DEFAULT_OPACITY_RULE]; // if there aren't any interactive components, then we don't need to add special opacity rules diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts index 71df340d6..3dfb3b03c 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -10,13 +10,13 @@ * governing permissions and limitations under the License. */ import { TABLE } from '@constants'; -import { getTooltip } from '@specBuilder/marks/markUtils'; +import { getMarkOpacity, getTooltip } from '@specBuilder/marks/markUtils'; import { ArcMark } from 'vega'; import { SunburstSpecProps } from '../../types'; export const getArcMark = (props: SunburstSpecProps): ArcMark => { - const { name, children, segmentKey } = props; + const { name, children, segmentKey, muteElementsOnHover } = props; return { type: 'arc', name, @@ -40,6 +40,7 @@ export const getArcMark = (props: SunburstSpecProps): ArcMark => { stroke: { value: 'white' }, strokeWidth: { value: 0.5 }, zindex: { value: 0 }, + opacity: muteElementsOnHover ? getMarkOpacity(props) : undefined, }, }, }; diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 887cc8c4d..948bfb11c 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -36,6 +36,7 @@ export const addSunburst = produce< id = 'id', parentId = 'parent', segmentKey = 'segment', + muteElementsOnHover = false, ...props } ) => { @@ -51,6 +52,7 @@ export const addSunburst = produce< parentId, segmentKey, name: toCamelCase(name ?? `sunburst${index}`), + muteElementsOnHover, ...props, }; diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index 5c75c0c6c..2d121cb4e 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -12,7 +12,7 @@ import { ReactElement } from 'react'; import useChartProps from '@hooks/useChartProps'; -import { Chart, ChartPopover, ChartProps, ChartTooltip, Datum, SunburstProps } from '@rsc'; +import { Chart, ChartPopover, ChartProps, ChartTooltip, Datum, Legend, SunburstProps } from '@rsc'; import { Sunburst } from '@rsc/alpha'; import { StoryFn } from '@storybook/react'; import { bindWithProps } from '@test-utils'; @@ -38,22 +38,32 @@ const smallChartProps: ChartProps = { height: 350, }; +const SimpleSunburstStory: StoryFn = (args): ReactElement => { + const { width, height, ...sunburstProps } = args; + const chartProps = useChartProps({ ...smallChartProps, width: width ?? 600, height: height ?? 600 }); + return ( + + + + ); +}; + const SunburstStory: StoryFn = (args): ReactElement => { const { width, height, ...sunburstProps } = args; const chartProps = useChartProps({ ...defaultChartProps, width: width ?? 600, height: height ?? 600 }); return ( - + ); }; -const SmallSunburstStory: StoryFn = (args): ReactElement => { - const { width, height, ...sunburstProps } = args; - const chartProps = useChartProps({ ...smallChartProps, width: width ?? 600, height: height ?? 600 }); +const SunburstLegendStory: StoryFn = (args): ReactElement => { + const chartProps = useChartProps({ ...defaultChartProps, height: 400, width: 600 }); return ( - + + ); }; @@ -61,8 +71,9 @@ const SmallSunburstStory: StoryFn { return ( -
Browser: {datum.segment}
+
Browser: {datum.name}
Users: {datum.value}
+ {datum.segment &&
Details: {datum.segment}
}
); }; @@ -74,7 +85,7 @@ const interactiveChildren = [ , ]; -const Basic = bindWithProps(SunburstStory); +const Basic = bindWithProps(SimpleSunburstStory); Basic.args = { metric: 'value', parentId: 'parent', @@ -82,13 +93,12 @@ Basic.args = { segmentKey: 'segment', }; -const Small = bindWithProps(SmallSunburstStory); -Small.args = { +const Complex = bindWithProps(SunburstStory); +Complex.args = { metric: 'value', parentId: 'parent', id: 'id', segmentKey: 'segment', - children: interactiveChildren, }; const WithPopovers = bindWithProps(SunburstStory); @@ -100,4 +110,23 @@ WithPopovers.args = { children: interactiveChildren, }; -export { Small, Basic, WithPopovers }; +const WithLegend = bindWithProps(SunburstLegendStory); +WithLegend.args = { + metric: 'value', + parentId: 'parent', + id: 'id', + segmentKey: 'segment', + children: interactiveChildren, +}; + +const WithLegendAndMuting = bindWithProps(SunburstLegendStory); +WithLegendAndMuting.args = { + metric: 'value', + parentId: 'parent', + id: 'id', + segmentKey: 'segment', + muteElementsOnHover: true, + children: interactiveChildren, +}; + +export { Basic, Complex, WithPopovers, WithLegend, WithLegendAndMuting }; diff --git a/src/stories/components/Sunburst/data.ts b/src/stories/components/Sunburst/data.ts index b03c84fde..61d3b7ea2 100644 --- a/src/stories/components/Sunburst/data.ts +++ b/src/stories/components/Sunburst/data.ts @@ -21,196 +21,203 @@ export const basicSunburstData = [ name: 'Chrome', parent: 1, value: 800, - segment: 'chrome', + segment: 'Chrome', }, { id: 3, name: 'V 130', parent: 2, value: 200, - segment: 'chrome', + segment: 'Chrome', }, { id: 4, parent: 3, name: 'V 130.1', value: 30, - segment: 'chrome', + segment: 'Chrome', }, { id: 5, parent: 3, name: 'V 130.2', value: 100, - segment: 'chrome', + segment: 'Chrome', }, { id: 6, parent: 2, name: 'V 131', value: 100, - segment: 'chrome', + segment: 'Chrome', }, { id: 7, parent: 2, name: 'V 132', value: 500, - segment: 'chrome', + segment: 'Chrome', }, { id: 8, parent: 1, name: 'Firefox', value: 600, - segment: 'firefox', + segment: 'Firefox', }, { id: 9, parent: 8, name: 'Alpha', value: 100, - segment: 'firefox', + segment: 'Firefox', }, { id: 10, parent: 9, name: 'Alpha 1', value: 50, - segment: 'firefox', + segment: 'Firefox', }, { id: 11, parent: 9, name: 'Alpha 2', value: 50, - segment: 'firefox', + segment: 'Firefox', }, { id: 12, parent: 8, name: 'Beta', value: 200, - segment: 'firefox', + segment: 'Firefox', }, { id: 13, parent: 12, name: 'Beta 1', value: 40, - segment: 'firefox', + segment: 'Firefox', }, { id: 14, parent: 12, name: 'Beta 2', value: 100, - segment: 'firefox', + segment: 'Firefox', }, { id: 15, parent: 8, name: 'Prod', value: 300, - segment: 'firefox', + segment: 'Firefox', }, { id: 16, parent: 15, name: 'Prod 1', value: 100, - segment: 'firefox', + segment: 'Firefox', }, { id: 17, parent: 15, name: 'Prod 2', value: 150, - segment: 'firefox', + segment: 'Firefox', }, { id: 18, parent: 1, name: 'Safari', value: 150, - segment: 'safari', + segment: 'Safari', }, { id: 19, parent: 18, name: '12.4.64', value: 20, - segment: 'safari', + segment: 'Safari', }, { id: 20, parent: 18, name: '12.4.65', value: 50, - segment: 'safari', + segment: 'Safari', }, { id: 21, parent: 18, name: '12.5.0', value: 60, - segment: 'safari', + segment: 'Safari', }, { id: 22, parent: 1, name: 'Edge', value: 685, - segment: 'edge', + segment: 'Edge', }, { id: 23, parent: 22, name: '1', value: 200, - segment: 'edge', + segment: 'Edge', }, { id: 24, parent: 22, name: '2', value: 200, - segment: 'edge', + segment: 'Edge', }, { id: 25, parent: 22, name: '3', value: 200, - segment: 'edge', + segment: 'Edge', }, { id: 26, parent: 22, name: '4', value: 85, - segment: 'edge', + segment: 'Edge', }, { id: 27, parent: 26, name: '4.1', value: 85, - segment: 'edge', + segment: 'Edge', }, { id: 28, parent: 27, name: '4.1.1', value: 30, - segment: 'edge', + segment: 'Edge', }, { id: 29, parent: 27, name: '4.1.2', value: 30, - segment: 'edge', + segment: 'Edge', + }, + { + id: 30, + parent: 25, + name: '3.0.1', + value: 30, + segment: 'Edge', }, ]; diff --git a/src/types/Chart.ts b/src/types/Chart.ts index 5574d58da..afd33c56a 100644 --- a/src/types/Chart.ts +++ b/src/types/Chart.ts @@ -252,6 +252,9 @@ export interface SunburstProps extends MarkProps { /** identifies which segment each element is part of */ segmentKey: string; + + /** determines if other elements are muted when hovering a given element */ + muteElementsOnHover?: boolean; } export interface SegmentLabelProps { diff --git a/src/types/specBuilderTypes.ts b/src/types/specBuilderTypes.ts index f0fccf977..5f246aba2 100644 --- a/src/types/specBuilderTypes.ts +++ b/src/types/specBuilderTypes.ts @@ -151,7 +151,7 @@ export interface DonutSpecProps extends PartiallyRequired { children: MarkChildElement[]; From 3e78d333fc1052044945ad52fa256fbc4bde3180 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 14:19:11 -0700 Subject: [PATCH 10/15] chore: change keys --- src/RscChart.tsx | 4 ++-- src/alpha/components/Sunburst/Sunburst.tsx | 2 +- .../sunburst/sunburstDataModificationUtils.ts | 4 ++-- src/specBuilder/sunburst/sunburstSpecBuilder.ts | 14 +++++++------- src/stories/components/Sunburst/Sunburst.story.tsx | 10 +++++----- src/types/Chart.ts | 2 +- src/types/specBuilderTypes.ts | 9 ++++++++- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/RscChart.tsx b/src/RscChart.tsx index 86e4edc7f..c69ced50d 100644 --- a/src/RscChart.tsx +++ b/src/RscChart.tsx @@ -120,8 +120,8 @@ export const RscChart = forwardRef( sanitizedChildren.forEach((child) => { if (child.type.name === Sunburst.displayName) { const sunburstProps = child.props as unknown as SunburstProps; - const { id, parentId, metric = 'value' } = sunburstProps; - createLeafValues(data, id, parentId, metric); + const { id, parentKey, metric = 'value' } = sunburstProps; + createLeafValues(data, id, parentKey, metric); } }); diff --git a/src/alpha/components/Sunburst/Sunburst.tsx b/src/alpha/components/Sunburst/Sunburst.tsx index f2b1777c5..338a23c6a 100644 --- a/src/alpha/components/Sunburst/Sunburst.tsx +++ b/src/alpha/components/Sunburst/Sunburst.tsx @@ -24,7 +24,7 @@ const Sunburst: FC = ({ metric = DEFAULT_METRIC, name, id, - parentId, + parentKey, }) => { return null; }; diff --git a/src/specBuilder/sunburst/sunburstDataModificationUtils.ts b/src/specBuilder/sunburst/sunburstDataModificationUtils.ts index c5b418f64..8b94fa493 100644 --- a/src/specBuilder/sunburst/sunburstDataModificationUtils.ts +++ b/src/specBuilder/sunburst/sunburstDataModificationUtils.ts @@ -11,10 +11,10 @@ */ import { Datum } from 'vega'; -export const createLeafValues = (data: Datum[], id: string, parentId: string, metric: string) => { +export const createLeafValues = (data: Datum[], id: string, parentKey: string, metric: string) => { data.forEach((element) => { element[`${metric}_childSum`] = data - .filter((e) => e[parentId] === element[id]) + .filter((e) => e[parentKey] === element[id]) .reduce((acc, e) => acc + e[metric], 0); }); data.forEach((element) => { diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index 948bfb11c..ad8cbea0a 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -9,7 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, TABLE } from '@constants'; +import { COLOR_SCALE, DEFAULT_COLOR, DEFAULT_COLOR_SCHEME, DEFAULT_METRIC, OPACITY_SCALE, TABLE } from '@constants'; import { getTooltipProps, hasInteractiveChildren } from '@specBuilder/marks/markUtils'; import { addFieldToFacetScaleDomain } from '@specBuilder/scale/scaleSpecBuilder'; import { addHighlightedItemSignalEvents } from '@specBuilder/signal/signalSpecBuilder'; @@ -34,7 +34,7 @@ export const addSunburst = produce< metric = DEFAULT_METRIC, name, id = 'id', - parentId = 'parent', + parentKey = 'parent', segmentKey = 'segment', muteElementsOnHover = false, ...props @@ -49,7 +49,7 @@ export const addSunburst = produce< markType: 'sunburst', metric, id, - parentId, + parentKey, segmentKey, name: toCamelCase(name ?? `sunburst${index}`), muteElementsOnHover, @@ -73,13 +73,13 @@ export const addData = produce((data, props) => { const getSunburstDataTransforms = ({ id, - parentId, + parentKey, metric, }: SunburstSpecProps): (StratifyTransform | PartitionTransform)[] => [ { type: 'stratify', key: id, - parentKey: parentId, + parentKey: parentKey, }, { type: 'partition', @@ -92,8 +92,8 @@ const getSunburstDataTransforms = ({ export const addScales = produce((scales, props) => { const { segmentKey } = props; - addFieldToFacetScaleDomain(scales, 'opacity', 'depth'); - addFieldToFacetScaleDomain(scales, 'color', segmentKey); + addFieldToFacetScaleDomain(scales, OPACITY_SCALE, 'depth'); + addFieldToFacetScaleDomain(scales, COLOR_SCALE, segmentKey); }); export const addMarks = produce((marks, props) => { diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index 2d121cb4e..f59e6429f 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -88,7 +88,7 @@ const interactiveChildren = [ const Basic = bindWithProps(SimpleSunburstStory); Basic.args = { metric: 'value', - parentId: 'parent', + parentKey: 'parent', id: 'id', segmentKey: 'segment', }; @@ -96,7 +96,7 @@ Basic.args = { const Complex = bindWithProps(SunburstStory); Complex.args = { metric: 'value', - parentId: 'parent', + parentKey: 'parent', id: 'id', segmentKey: 'segment', }; @@ -104,7 +104,7 @@ Complex.args = { const WithPopovers = bindWithProps(SunburstStory); WithPopovers.args = { metric: 'value', - parentId: 'parent', + parentKey: 'parent', id: 'id', segmentKey: 'segment', children: interactiveChildren, @@ -113,7 +113,7 @@ WithPopovers.args = { const WithLegend = bindWithProps(SunburstLegendStory); WithLegend.args = { metric: 'value', - parentId: 'parent', + parentKey: 'parent', id: 'id', segmentKey: 'segment', children: interactiveChildren, @@ -122,7 +122,7 @@ WithLegend.args = { const WithLegendAndMuting = bindWithProps(SunburstLegendStory); WithLegendAndMuting.args = { metric: 'value', - parentId: 'parent', + parentKey: 'parent', id: 'id', segmentKey: 'segment', muteElementsOnHover: true, diff --git a/src/types/Chart.ts b/src/types/Chart.ts index afd33c56a..9ad1b6b4b 100644 --- a/src/types/Chart.ts +++ b/src/types/Chart.ts @@ -248,7 +248,7 @@ export interface SunburstProps extends MarkProps { id: string; /** identifies the key of this elements parent, if any parent exists */ - parentId: string; + parentKey: string; /** identifies which segment each element is part of */ segmentKey: string; diff --git a/src/types/specBuilderTypes.ts b/src/types/specBuilderTypes.ts index 5f246aba2..9dd1cbc43 100644 --- a/src/types/specBuilderTypes.ts +++ b/src/types/specBuilderTypes.ts @@ -151,7 +151,14 @@ export interface DonutSpecProps extends PartiallyRequired { children: MarkChildElement[]; From 3ebc7a953ba7d4b530351d5cd1f2ca100e2948e1 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 15:43:44 -0700 Subject: [PATCH 11/15] test: add tests for sunburstSpecBuilder --- .../sunburst/sunburstSpecBuilder.test.tsx | 89 +++++++++++++++++++ src/specBuilder/sunburst/sunburstTestUtils.ts | 29 ++++++ 2 files changed, 118 insertions(+) create mode 100644 src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx create mode 100644 src/specBuilder/sunburst/sunburstTestUtils.ts diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx b/src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx new file mode 100644 index 000000000..a2c39e701 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { createElement } from 'react'; + +import { COLOR_SCALE, HIGHLIGHTED_ITEM, OPACITY_SCALE, TABLE } from '@constants'; +import { ChartTooltip } from '@rsc'; +import { defaultSignals } from '@specBuilder/specTestUtils'; +import { initializeSpec } from '@specBuilder/specUtils'; + +import { addData, addMarks, addScales, addSignals, addSunburst } from './sunburstSpecBuilder'; +import { defaultSunburstProps } from './sunburstTestUtils'; + +describe('sunburstSpecBuilder', () => { + test('should return a spec', () => { + const spec = addSunburst(initializeSpec(), defaultSunburstProps); + expect(spec).toHaveProperty('data'); + expect(spec).toHaveProperty('scales'); + expect(spec).toHaveProperty('marks'); + expect(spec).toHaveProperty('signals'); + }); + + describe('addData', () => { + test('should add data tranforms correctly', () => { + const data = addData(initializeSpec().data ?? [], { ...defaultSunburstProps }); + + expect(data).toHaveLength(2); + expect(data[0].transform).toHaveLength(3); + expect(data[0].transform?.[0].type).toBe('identifier'); //this is added to all specs + expect(data[0].transform?.[1].type).toBe('stratify'); + expect(data[0].transform?.[2].type).toBe('partition'); + }); + + test('should add data transform even if there was no transform array yet', () => { + const data = addData([{ name: TABLE }], { ...defaultSunburstProps }); + expect(data).toHaveLength(1); + expect(data[0].transform).toHaveLength(2); + expect(data[0].transform?.[0].type).toBe('stratify'); + expect(data[0].transform?.[1].type).toBe('partition'); + }); + }); + + describe('addScales', () => { + test('should add scales correctly', () => { + const scales = addScales(initializeSpec().scales ?? [], defaultSunburstProps); + expect(scales).toHaveLength(2); + expect(scales[0]).toHaveProperty('name', OPACITY_SCALE); + expect(scales[0].domain).toHaveProperty('fields', ['depth']); + expect(scales[1]).toHaveProperty('name', COLOR_SCALE); + expect(scales[1].domain).toHaveProperty('fields', [defaultSunburstProps.segmentKey]); + }); + }); + + describe('addMarks', () => { + //more tests are specified in the sunburstMarkUtils.test.ts + test('should add arc marks correctly', () => { + const marks = addMarks(initializeSpec().marks ?? [], defaultSunburstProps); + expect(marks).toHaveLength(1); + expect(marks[0]).toHaveProperty('type', 'arc'); + }); + }); + + describe('addSignals', () => { + test('adds no signals if no interactive children', () => { + const signals = addSignals(initializeSpec().signals ?? [], defaultSunburstProps); + expect(signals).toHaveLength(0); + }); + + test('should add hover events when tooltip is present', () => { + const signals = addSignals(defaultSignals, { + ...defaultSunburstProps, + children: [createElement(ChartTooltip)], + }); + expect(signals).toHaveLength(defaultSignals.length); + expect(signals[0]).toHaveProperty('name', HIGHLIGHTED_ITEM); + expect(signals[0].on).toHaveLength(2); + expect(signals[0].on?.[0]).toHaveProperty('events', '@testName:mouseover'); + expect(signals[0].on?.[1]).toHaveProperty('events', '@testName:mouseout'); + }); + }); +}); diff --git a/src/specBuilder/sunburst/sunburstTestUtils.ts b/src/specBuilder/sunburst/sunburstTestUtils.ts new file mode 100644 index 000000000..1e465a8f7 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstTestUtils.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { MARK_ID } from '@constants'; + +import { SunburstSpecProps } from '../../types'; + +export const defaultSunburstProps: SunburstSpecProps = { + children: [], + color: 'testColor', + colorScheme: 'light', + idKey: MARK_ID, + index: 0, + markType: 'sunburst', + metric: 'value', + parentKey: 'parent', + id: 'id', + segmentKey: 'segment', + muteElementsOnHover: false, + name: 'testName', +}; From 1a743cef9709d7ee1bbbe746ab0868f729d336ab Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 15:50:51 -0700 Subject: [PATCH 12/15] test: add tests to sunburstMarkUtils --- .../sunburst/sunburstMarkUtils.test.ts | 105 ++++++++++++++++++ ...r.test.tsx => sunburstSpecBuilder.test.ts} | 0 .../components/Sunburst/Sunburst.story.tsx | 2 +- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/specBuilder/sunburst/sunburstMarkUtils.test.ts rename src/specBuilder/sunburst/{sunburstSpecBuilder.test.tsx => sunburstSpecBuilder.test.ts} (100%) diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.test.ts b/src/specBuilder/sunburst/sunburstMarkUtils.test.ts new file mode 100644 index 000000000..1656f8764 --- /dev/null +++ b/src/specBuilder/sunburst/sunburstMarkUtils.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { createElement } from 'react'; + +import { TABLE } from '@constants'; +import { ChartTooltip } from '@rsc'; +import { ArcMark } from 'vega'; + +import { getArcMark } from './sunburstMarkUtils'; +import { defaultSunburstProps } from './sunburstTestUtils'; + +describe('getArcMark', () => { + test('should return a valid ArcMark object', () => { + const expectedArcMark: ArcMark = { + type: 'arc', + name: defaultSunburstProps.name, + from: { data: TABLE }, + encode: { + enter: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + fill: { + scale: 'color', + field: 'segment', + }, + fillOpacity: { scale: 'opacity', field: 'depth' }, + tooltip: undefined, + }, + update: { + startAngle: { field: 'a0' }, + endAngle: { field: 'a1' }, + innerRadius: { field: 'r0' }, + outerRadius: { field: 'r1' }, + stroke: { value: 'white' }, + strokeWidth: { value: 0.5 }, + zindex: { value: 0 }, + opacity: undefined, + }, + }, + }; + + const arcMark = getArcMark(defaultSunburstProps); + expect(arcMark).toEqual(expectedArcMark); + }); + + test('should include tooltip and update opacity when proper props are passed', () => { + const expectedArcMark: ArcMark = { + type: 'arc', + name: defaultSunburstProps.name, + from: { data: TABLE }, + encode: { + enter: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + fill: { + scale: 'color', + field: 'segment', + }, + fillOpacity: { scale: 'opacity', field: 'depth' }, + tooltip: { + signal: "merge(datum, {'rscComponentName': 'testName'})", + }, + }, + update: { + startAngle: { field: 'a0' }, + endAngle: { field: 'a1' }, + innerRadius: { field: 'r0' }, + outerRadius: { field: 'r1' }, + stroke: { value: 'white' }, + strokeWidth: { value: 0.5 }, + zindex: { value: 0 }, + opacity: [ + { + test: 'isArray(highlightedItem) && length(highlightedItem) > 0 && indexof(highlightedItem, datum.rscMarkId) === -1', + value: 0.2, + }, + { + test: '!isArray(highlightedItem) && isValid(highlightedItem) && highlightedItem !== datum.rscMarkId', + value: 0.2, + }, + { + value: 1, + }, + ], + }, + }, + }; + + const arcMark = getArcMark({ + ...defaultSunburstProps, + children: [createElement(ChartTooltip)], + muteElementsOnHover: true, + }); + expect(arcMark).toEqual(expectedArcMark); + }); +}); diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx b/src/specBuilder/sunburst/sunburstSpecBuilder.test.ts similarity index 100% rename from src/specBuilder/sunburst/sunburstSpecBuilder.test.tsx rename to src/specBuilder/sunburst/sunburstSpecBuilder.test.ts diff --git a/src/stories/components/Sunburst/Sunburst.story.tsx b/src/stories/components/Sunburst/Sunburst.story.tsx index f59e6429f..c2db84cfe 100644 --- a/src/stories/components/Sunburst/Sunburst.story.tsx +++ b/src/stories/components/Sunburst/Sunburst.story.tsx @@ -61,7 +61,7 @@ const SunburstStory: StoryFn = (args): ReactElement => { const chartProps = useChartProps({ ...defaultChartProps, height: 400, width: 600 }); return ( - + From 40f369e27c1d6200b3981adb088443dd1df3124a Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Thu, 12 Dec 2024 15:58:27 -0700 Subject: [PATCH 13/15] test: add test to sunburstDataModificationUtil --- .../sunburstDataModificationUtils.test.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/specBuilder/sunburst/sunburstDataModificationUtils.test.ts diff --git a/src/specBuilder/sunburst/sunburstDataModificationUtils.test.ts b/src/specBuilder/sunburst/sunburstDataModificationUtils.test.ts new file mode 100644 index 000000000..2b5a8c2fa --- /dev/null +++ b/src/specBuilder/sunburst/sunburstDataModificationUtils.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { Datum } from 'vega'; + +import { createLeafValues } from './sunburstDataModificationUtils'; + +describe('createLeafValues', () => { + test('should correctly calculate childSum and leafValue', () => { + const data: Datum[] = [ + { id: '1', metric: 10 }, + { id: '2', parent: '1', metric: 3 }, + { id: '3', parent: '1', metric: 2 }, + { id: '4', parent: '2', metric: 1 }, + ]; + + createLeafValues(data, 'id', 'parent', 'metric'); + + expect(data[0]).toEqual({ + id: '1', + metric: 10, + metric_childSum: 5, + metric_leafValue: 5, + }); + + expect(data[1]).toEqual({ + id: '2', + parent: '1', + metric: 3, + metric_childSum: 1, + metric_leafValue: 2, + }); + + expect(data[2]).toEqual({ + id: '3', + parent: '1', + metric: 2, + metric_childSum: 0, + metric_leafValue: 2, + }); + + expect(data[3]).toEqual({ + id: '4', + parent: '2', + metric: 1, + metric_childSum: 0, + metric_leafValue: 1, + }); + }); + + test('should set leafValue to 0 if it is negative', () => { + const data: Datum[] = [ + { id: '1', parent: null, metric: 2 }, + { id: '2', parent: '1', metric: 3 }, + ]; + + createLeafValues(data, 'id', 'parent', 'metric'); + + expect(data[0]).toEqual({ + id: '1', + parent: null, + metric: 2, + metric_childSum: 3, + metric_leafValue: 0, + }); + + expect(data[1]).toEqual({ + id: '2', + parent: '1', + metric: 3, + metric_childSum: 0, + metric_leafValue: 3, + }); + }); +}); From b9cb70546d08e76358db1ffba01954ff10b9b2be Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Fri, 13 Dec 2024 10:24:56 -0700 Subject: [PATCH 14/15] feat: add value display to sunburst --- .../sunburst/sunburstMarkUtils.test.ts | 82 ++++++++++++++++++- src/specBuilder/sunburst/sunburstMarkUtils.ts | 30 ++++++- .../sunburst/sunburstSpecBuilder.ts | 3 +- 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.test.ts b/src/specBuilder/sunburst/sunburstMarkUtils.test.ts index 1656f8764..56fac8afa 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.test.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.test.ts @@ -15,7 +15,7 @@ import { TABLE } from '@constants'; import { ChartTooltip } from '@rsc'; import { ArcMark } from 'vega'; -import { getArcMark } from './sunburstMarkUtils'; +import { getArcMark, getTextMark } from './sunburstMarkUtils'; import { defaultSunburstProps } from './sunburstTestUtils'; describe('getArcMark', () => { @@ -103,3 +103,83 @@ describe('getArcMark', () => { expect(arcMark).toEqual(expectedArcMark); }); }); + +describe('getTextMark', () => { + test('should return a valid TextMark object', () => { + const expectedTextMark = { + type: 'text', + name: `${defaultSunburstProps.name}_text`, + from: { data: TABLE }, + encode: { + enter: { + text: { field: defaultSunburstProps.metric }, + fontSize: { value: 9 }, + baseline: { value: 'middle' }, + align: { value: 'center' }, + tooltip: undefined, + }, + update: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + radius: { signal: "(datum['r0'] == 0 ? 0 : datum['r0'] + datum['r1']) / 2" }, + theta: { signal: "(datum['a0'] + datum['a1']) / 2" }, + angle: { + signal: "datum['r0'] == 0 ? 0 : ((datum['a0'] + datum['a1']) / 2) * 180 / PI + (inrange(((datum['a0'] + datum['a1']) / 2) % (2 * PI), [0, PI]) ? 270 : 90)", + }, + opacity: undefined, + }, + }, + }; + + const textMark = getTextMark(defaultSunburstProps); + expect(textMark).toEqual(expectedTextMark); + }); + + test('should include tooltip and update opacity when proper props are passed', () => { + const expectedTextMark = { + type: 'text', + name: `${defaultSunburstProps.name}_text`, + from: { data: TABLE }, + encode: { + enter: { + text: { field: defaultSunburstProps.metric }, + fontSize: { value: 9 }, + baseline: { value: 'middle' }, + align: { value: 'center' }, + tooltip: { + signal: "merge(datum, {'rscComponentName': 'testName'})", + }, + }, + update: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + radius: { signal: "(datum['r0'] == 0 ? 0 : datum['r0'] + datum['r1']) / 2" }, + theta: { signal: "(datum['a0'] + datum['a1']) / 2" }, + angle: { + signal: "datum['r0'] == 0 ? 0 : ((datum['a0'] + datum['a1']) / 2) * 180 / PI + (inrange(((datum['a0'] + datum['a1']) / 2) % (2 * PI), [0, PI]) ? 270 : 90)", + }, + opacity: [ + { + test: 'isArray(highlightedItem) && length(highlightedItem) > 0 && indexof(highlightedItem, datum.rscMarkId) === -1', + value: 0.2, + }, + { + test: '!isArray(highlightedItem) && isValid(highlightedItem) && highlightedItem !== datum.rscMarkId', + value: 0.2, + }, + { + value: 1, + }, + ], + }, + }, + }; + + const textMark = getTextMark({ + ...defaultSunburstProps, + children: [createElement(ChartTooltip)], + muteElementsOnHover: true, + }); + expect(textMark).toEqual(expectedTextMark); + }); +}); diff --git a/src/specBuilder/sunburst/sunburstMarkUtils.ts b/src/specBuilder/sunburst/sunburstMarkUtils.ts index 3dfb3b03c..63746a3c2 100644 --- a/src/specBuilder/sunburst/sunburstMarkUtils.ts +++ b/src/specBuilder/sunburst/sunburstMarkUtils.ts @@ -11,7 +11,7 @@ */ import { TABLE } from '@constants'; import { getMarkOpacity, getTooltip } from '@specBuilder/marks/markUtils'; -import { ArcMark } from 'vega'; +import { ArcMark, TextMark } from 'vega'; import { SunburstSpecProps } from '../../types'; @@ -45,3 +45,31 @@ export const getArcMark = (props: SunburstSpecProps): ArcMark => { }, }; }; + +export const getTextMark = (props: SunburstSpecProps): TextMark => { + const { metric, children, name, muteElementsOnHover } = props; + return { + type: 'text', + name: `${name}_text`, + from: { data: TABLE }, + encode: { + enter: { + text: { field: metric }, + fontSize: { value: 9 }, + baseline: { value: 'middle' }, + align: { value: 'center' }, + tooltip: getTooltip(children, name), + }, + update: { + x: { signal: 'width / 2' }, + y: { signal: 'height / 2' }, + radius: { signal: "(datum['r0'] == 0 ? 0 : datum['r0'] + datum['r1']) / 2" }, + theta: { signal: "(datum['a0'] + datum['a1']) / 2" }, + angle: { + signal: "datum['r0'] == 0 ? 0 : ((datum['a0'] + datum['a1']) / 2) * 180 / PI + (inrange(((datum['a0'] + datum['a1']) / 2) % (2 * PI), [0, PI]) ? 270 : 90)", + }, + opacity: muteElementsOnHover ? getMarkOpacity(props) : undefined, + }, + }, + }; +}; diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.ts index ad8cbea0a..b6a476c1d 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.ts @@ -18,7 +18,7 @@ import { produce } from 'immer'; import { Data, Mark, PartitionTransform, Scale, Signal, Spec, StratifyTransform } from 'vega'; import { ColorScheme, HighlightedItem, SunburstProps, SunburstSpecProps } from '../../types'; -import { getArcMark } from './sunburstMarkUtils'; +import { getArcMark, getTextMark } from './sunburstMarkUtils'; export const addSunburst = produce< Spec, @@ -98,6 +98,7 @@ export const addScales = produce((scales, props) = export const addMarks = produce((marks, props) => { marks.push(getArcMark(props)); + marks.push(getTextMark(props)); }); export const addSignals = produce((signals, props) => { From 659286b573b8dbfffdb453507ee4af087db47c80 Mon Sep 17 00:00:00 2001 From: JadenHowell Date: Fri, 13 Dec 2024 11:01:10 -0700 Subject: [PATCH 15/15] test: fix tests --- src/specBuilder/sunburst/sunburstSpecBuilder.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/specBuilder/sunburst/sunburstSpecBuilder.test.ts b/src/specBuilder/sunburst/sunburstSpecBuilder.test.ts index a2c39e701..dfa6e3a7e 100644 --- a/src/specBuilder/sunburst/sunburstSpecBuilder.test.ts +++ b/src/specBuilder/sunburst/sunburstSpecBuilder.test.ts @@ -63,8 +63,9 @@ describe('sunburstSpecBuilder', () => { //more tests are specified in the sunburstMarkUtils.test.ts test('should add arc marks correctly', () => { const marks = addMarks(initializeSpec().marks ?? [], defaultSunburstProps); - expect(marks).toHaveLength(1); + expect(marks).toHaveLength(2); expect(marks[0]).toHaveProperty('type', 'arc'); + expect(marks[1]).toHaveProperty('type', 'text'); }); });