diff --git a/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-heterogeneous-visually-looks-correct-1-snap.png b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-heterogeneous-visually-looks-correct-1-snap.png new file mode 100644 index 000000000000..1401f6c71685 Binary files /dev/null and b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-heterogeneous-visually-looks-correct-1-snap.png differ diff --git a/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-negative-visually-looks-correct-1-snap.png b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-negative-visually-looks-correct-1-snap.png index deefe8491f98..9770ba7a14ef 100644 Binary files a/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-negative-visually-looks-correct-1-snap.png and b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-negative-visually-looks-correct-1-snap.png differ diff --git a/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-not-a-number-visually-looks-correct-1-snap.png b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-not-a-number-visually-looks-correct-1-snap.png new file mode 100644 index 000000000000..9770ba7a14ef Binary files /dev/null and b/packages/osd-charts/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-not-a-number-visually-looks-correct-1-snap.png differ diff --git a/packages/osd-charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts b/packages/osd-charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts index 6d8478905600..2b72f183d559 100644 --- a/packages/osd-charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts +++ b/packages/osd-charts/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts @@ -144,7 +144,7 @@ export function shapeViewModel( textMeasure: TextMeasure, config: Config, layers: Layer[], - facts: Relation, + rawFacts: Relation, rawTextGetter: RawTextGetter, valueAccessor: ValueAccessor, specifiedValueFormatter: ValueFormatter, @@ -176,11 +176,16 @@ export function shapeViewModel( const aggregator = aggregators.sum; - // don't render anything if there are no tuples, or some are negative, or the total is not positive + const facts = rawFacts.filter((n) => { + const value = valueAccessor(n); + return Number.isFinite(value) && value >= 0; + }); + + // don't render anything if the total, the width or height is not positive if ( - facts.length === 0 || - facts.some((n) => valueAccessor(n) < 0) || - facts.reduce((p: number, n) => aggregator.reducer(p, valueAccessor(n)), aggregator.identity()) <= 0 + facts.reduce((p: number, n) => aggregator.reducer(p, valueAccessor(n)), aggregator.identity()) <= 0 || + !(width > 0) || + !(height > 0) ) { return nullShapeViewModel(config, diskCenter); } @@ -207,15 +212,21 @@ export function shapeViewModel( ? treemap(tree, treemapAreaAccessor, paddingAccessor, { x0: -width / 2, y0: -height / 2, width, height }) : sunburst(tree, sunburstAreaAccessor, { x0: 0, y0: -1 }, clockwiseSectors, specialFirstInnermostSector); + const shownChildNodes = rawChildNodes.filter((n: Part) => { + const layerIndex = entryValue(n.node).depth - 1; + const layer = layers[layerIndex]; + return !layer || !layer.showAccessor || layer.showAccessor(entryKey(n.node)); + }); + // use the smaller of the two sizes, as a circle fits into a square const circleMaximumSize = Math.min(innerWidth, innerHeight); const outerRadius: Radius = (outerSizeRatio * circleMaximumSize) / 2; const innerRadius: Radius = outerRadius - (1 - emptySizeRatio) * outerRadius; - const treeHeight = rawChildNodes.reduce((p: number, n: any) => Math.max(p, entryValue(n.node).depth), 0); // 1: pie, 2: two-ring donut etc. + const treeHeight = shownChildNodes.reduce((p: number, n: any) => Math.max(p, entryValue(n.node).depth), 0); // 1: pie, 2: two-ring donut etc. const ringThickness = (outerRadius - innerRadius) / treeHeight; const quadViewModel = makeQuadViewModel( - rawChildNodes.slice(1).map( + shownChildNodes.slice(1).map( (n: Part): ShapeTreeNode => { const node: ArrayEntry = n.node; return { diff --git a/packages/osd-charts/src/chart_types/partition_chart/specs/index.ts b/packages/osd-charts/src/chart_types/partition_chart/specs/index.ts index 45189e6524ee..9e6ceaaa3a5c 100644 --- a/packages/osd-charts/src/chart_types/partition_chart/specs/index.ts +++ b/packages/osd-charts/src/chart_types/partition_chart/specs/index.ts @@ -25,7 +25,14 @@ import { Spec, SpecTypes } from '../../../specs/index'; import { Config, FillLabelConfig } from '../layout/types/config_types'; import { ShapeTreeNode, ValueGetter } from '../layout/types/viewmodel_types'; import { AGGREGATE_KEY } from '../layout/utils/group_by_rollup'; -import { Datum, LabelAccessor, RecursivePartial, ValueAccessor, ValueFormatter } from '../../../utils/commons'; +import { + Datum, + LabelAccessor, + RecursivePartial, + ShowAccessor, + ValueAccessor, + ValueFormatter, +} from '../../../utils/commons'; import { NodeColorAccessor } from '../layout/types/viewmodel_types'; import { PrimitiveValue } from '../layout/utils/group_by_rollup'; @@ -33,6 +40,7 @@ export interface Layer { groupByRollup: IndexedAccessorFn; nodeLabel?: LabelAccessor; fillLabel?: Partial; + showAccessor?: ShowAccessor; shape?: { fillColor: string | NodeColorAccessor }; } @@ -48,6 +56,7 @@ const defaultProps = { { groupByRollup: (d: Datum, i: number) => i, nodeLabel: (d: PrimitiveValue) => String(d), + showAccessor: () => true, fillLabel: {}, }, ], diff --git a/packages/osd-charts/src/utils/commons.ts b/packages/osd-charts/src/utils/commons.ts index ef5663b43688..4917523f656e 100644 --- a/packages/osd-charts/src/utils/commons.ts +++ b/packages/osd-charts/src/utils/commons.ts @@ -194,3 +194,4 @@ export function isNumberArray(value: unknown): value is number[] { export type ValueFormatter = (value: number) => string; export type ValueAccessor = (d: Datum) => number; export type LabelAccessor = (value: PrimitiveValue) => string; +export type ShowAccessor = (value: PrimitiveValue) => boolean; diff --git a/packages/osd-charts/stories/sunburst/27_heterogeneous_depth.tsx b/packages/osd-charts/stories/sunburst/27_heterogeneous_depth.tsx new file mode 100644 index 000000000000..5340a0844d59 --- /dev/null +++ b/packages/osd-charts/stories/sunburst/27_heterogeneous_depth.tsx @@ -0,0 +1,98 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { Chart, Datum, Partition, PartitionLayout } from '../../src'; +import { mocks } from '../../src/mocks/hierarchical/index'; +import { config } from '../../src/chart_types/partition_chart/layout/config/config'; +import React from 'react'; +import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/types/viewmodel_types'; +import { + categoricalFillColor, + colorBrewerCategoricalStark9, + countryLookup, + productLookup, + regionLookup, +} from '../utils/utils'; +import { PrimitiveValue } from '../../src/chart_types/partition_chart/layout/utils/group_by_rollup'; + +export const example = () => ( + + d.exportVal as number} + valueFormatter={(d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`} + layers={[ + { + groupByRollup: (d: Datum) => d.sitc1, + nodeLabel: (d: PrimitiveValue) => d !== null && productLookup[d].name, + shape: { + fillColor: (d: ShapeTreeNode) => { + return categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex); + }, + }, + }, + { + groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.substr(0, 2), + nodeLabel: (d: PrimitiveValue) => d !== null && regionLookup[d].regionName, + shape: { + fillColor: (d: ShapeTreeNode) => { + return categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex); + }, + }, + }, + { + groupByRollup: (d: Datum) => d.dest, + nodeLabel: (d: PrimitiveValue) => d !== null && countryLookup[d].name, + showAccessor: (d: PrimitiveValue) => (['chn', 'hkg', 'jpn', 'kor'] as PrimitiveValue[]).indexOf(d) === -1, + shape: { + fillColor: (d: ShapeTreeNode) => { + return categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex); + }, + }, + }, + ]} + config={{ + partitionLayout: PartitionLayout.sunburst, + linkLabel: { + maxCount: 0, + fontSize: 14, + }, + fontFamily: 'Arial', + fillLabel: { + valueFormatter: (d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`, + fontStyle: 'italic', + textInvertible: true, + fontWeight: 900, + valueFont: { + fontFamily: 'Menlo', + fontStyle: 'normal', + fontWeight: 100, + }, + }, + margin: { top: 0, bottom: 0, left: 0, right: 0 }, + minFontSize: 1, + idealFontSizeJump: 1.1, + outerSizeRatio: 1, + emptySizeRatio: 0, + circlePadding: 4, + backgroundColor: 'rgba(229,229,229,1)', + }} + /> + +); diff --git a/packages/osd-charts/stories/sunburst/28_not_a_number.tsx b/packages/osd-charts/stories/sunburst/28_not_a_number.tsx new file mode 100644 index 000000000000..73b5a7d8ebc1 --- /dev/null +++ b/packages/osd-charts/stories/sunburst/28_not_a_number.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { Chart, Datum, Partition, PartitionLayout } from '../../src'; +import { mocks } from '../../src/mocks/hierarchical/index'; +import { config } from '../../src/chart_types/partition_chart/layout/config/config'; +import React from 'react'; +import { indexInterpolatedFillColor, interpolatorCET2s, productLookup } from '../utils/utils'; + +export const example = () => ( + + ({ ...s, exportVal: (undefined as unknown) as number }))) + .concat(mocks.pie.slice(3))} + valueAccessor={(d: Datum) => d.exportVal as number} + valueFormatter={(d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`} + layers={[ + { + groupByRollup: (d: Datum) => d.sitc1, + nodeLabel: (d: Datum) => productLookup[d].name, + fillLabel: { textInvertible: true }, + shape: { + fillColor: indexInterpolatedFillColor(interpolatorCET2s), + }, + }, + ]} + config={{ partitionLayout: PartitionLayout.sunburst }} + /> + +); diff --git a/packages/osd-charts/stories/sunburst/sunburst.stories.tsx b/packages/osd-charts/stories/sunburst/sunburst.stories.tsx index 2568620122da..d27b3649f261 100644 --- a/packages/osd-charts/stories/sunburst/sunburst.stories.tsx +++ b/packages/osd-charts/stories/sunburst/sunburst.stories.tsx @@ -53,3 +53,5 @@ export { example as clockwiseNoSpecial } from './23_clockwise'; export { example as linkedLabelsOnly } from './24_linked_label'; export { example as noLabels } from './25_no_labels'; export { example as percentage } from './26_percentage'; +export { example as heterogeneous } from './27_heterogeneous_depth'; +export { example as notANumber } from './28_not_a_number';