Skip to content

Sunburst #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/RscChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -52,6 +54,7 @@ import {
LegendDescription,
MarkBounds,
RscChartProps,
SunburstProps,
TooltipAnchor,
TooltipPlacement,
} from './types';
Expand Down Expand Up @@ -112,6 +115,16 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(

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, parentKey, metric = 'value' } = sunburstProps;
createLeafValues(data, id, parentKey, metric);
}
});

// THE MAGIC, builds our spec
const spec = useSpec({
backgroundColor,
Expand Down
35 changes: 35 additions & 0 deletions src/alpha/components/Sunburst/Sunburst.tsx
Original file line number Diff line number Diff line change
@@ -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<SunburstProps> = ({
children,
color = DEFAULT_COLOR,
metric = DEFAULT_METRIC,
name,
id,
parentKey,
}) => {
return null;
};

// displayName is used to validate the component type in the spec builder
Sunburst.displayName = 'Sunburst';

export { Sunburst };
13 changes: 13 additions & 0 deletions src/alpha/components/Sunburst/index.ts
Original file line number Diff line number Diff line change
@@ -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';
3 changes: 2 additions & 1 deletion src/alpha/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
* governing permissions and limitations under the License.
*/

export * from './Combo'
export * from './Combo';
export * from './Sunburst';
22 changes: 19 additions & 3 deletions src/specBuilder/chartSpecBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -54,6 +54,7 @@ import {
Opacities,
SanitizedSpecProps,
ScatterElement,
SunburstElement,
SymbolShapes,
SymbolSize,
TitleElement,
Expand All @@ -80,6 +81,7 @@ import {
getVegaSymbolSizeFromRscSymbolSize,
initializeSpec,
} from './specUtils';
import { addSunburst } from './sunburst/sunburstSpecBuilder';
import { addTitle } from './title/titleSpecBuilder';

export function buildSpec(props: SanitizedSpecProps) {
Expand Down Expand Up @@ -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))
Expand All @@ -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, {
Expand Down Expand Up @@ -202,6 +217,7 @@ const initializeComponentCounts = () => {
barCount: -1,
comboCount: -1,
donutCount: -1,
sunburstCount: -1,
legendCount: -1,
lineCount: -1,
scatterCount: -1,
Expand Down
11 changes: 9 additions & 2 deletions src/specBuilder/chartTooltip/chartTooltipUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down
5 changes: 4 additions & 1 deletion src/specBuilder/marks/markUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
OpacityFacet,
ProductionRuleTests,
ScaleType,
SunburstSpecProps,
SymbolSizeFacet,
} from '../../types';

Expand Down Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions src/specBuilder/sunburst/sunburstDataModificationUtils.test.ts
Original file line number Diff line number Diff line change
@@ -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,
});
});
});
26 changes: 26 additions & 0 deletions src/specBuilder/sunburst/sunburstDataModificationUtils.ts
Original file line number Diff line number Diff line change
@@ -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, parentKey: string, metric: string) => {
data.forEach((element) => {
element[`${metric}_childSum`] = data
.filter((e) => e[parentKey] === 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;
}
});
};
Loading
Loading