diff --git a/.eslintrc.js b/.eslintrc.js index 1a61524f2d..5838f5f875 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,7 +81,8 @@ module.exports = { 'global-require': 1, 'import/no-dynamic-require': 1, 'no-shadow': 1, - 'no-param-reassign': 1, + 'no-param-reassign': [1, { props: false }], + '@typescript-eslint/comma-spacing': 0, 'react/no-array-index-key': 1, 'react/prefer-stateless-function': 1, 'react/require-default-props': 'off', @@ -342,7 +343,7 @@ module.exports = { 'prefer-destructuring': [ 'warn', { - array: true, + array: false, object: true, }, { diff --git a/.playground/playground.tsx b/.playground/playground.tsx index 13036f2204..3c9f2ee240 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -38,39 +38,10 @@ import React from 'react'; -import { Chart, Settings, Partition, PartitionLayout } from '../src'; +import { Example } from '../stories/icicle/01_unix_icicle'; export class Playground extends React.Component { render() { - return ( -
- - - d.val as number} - layers={[ - { - groupByRollup: (d: any) => d.cat1, - }, - { - groupByRollup: (d: any) => d.cat2, - }, - ]} - config={{ - partitionLayout: PartitionLayout.sunburst, - }} - /> - -
- ); + return ; } } diff --git a/api/charts.api.md b/api/charts.api.md index 5c1859c6e8..dea40bc007 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -1258,6 +1258,8 @@ export interface PartitionLayer { export const PartitionLayout: Readonly<{ sunburst: "sunburst"; treemap: "treemap"; + icicle: "icicle"; + flame: "flame"; }>; // @public (undocumented) @@ -1958,8 +1960,8 @@ export type YDomainRange = YDomainBase & DomainRange; // src/chart_types/heatmap/layout/types/config_types.ts:28:13 - (ae-forgotten-export) The symbol "SizeRatio" needs to be exported by the entry point index.d.ts // src/chart_types/heatmap/layout/types/config_types.ts:60:5 - (ae-forgotten-export) The symbol "TextAlign" needs to be exported by the entry point index.d.ts // src/chart_types/heatmap/layout/types/config_types.ts:61:5 - (ae-forgotten-export) The symbol "TextBaseline" needs to be exported by the entry point index.d.ts -// src/chart_types/partition_chart/layout/types/config_types.ts:126:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts -// src/chart_types/partition_chart/layout/types/config_types.ts:127:5 - (ae-forgotten-export) The symbol "AnimKeyframe" needs to be exported by the entry point index.d.ts +// src/chart_types/partition_chart/layout/types/config_types.ts:128:5 - (ae-forgotten-export) The symbol "TimeMs" needs to be exported by the entry point index.d.ts +// src/chart_types/partition_chart/layout/types/config_types.ts:129:5 - (ae-forgotten-export) The symbol "AnimKeyframe" needs to be exported by the entry point index.d.ts // src/chart_types/partition_chart/specs/index.ts:48:13 - (ae-forgotten-export) The symbol "NodeColorAccessor" needs to be exported by the entry point index.d.ts // src/commons/series_id.ts:39:3 - (ae-forgotten-export) The symbol "SeriesKey" needs to be exported by the entry point index.d.ts diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-flame-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-flame-chart-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..b4a00fc2e4 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-flame-chart-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-icicle-chart-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-icicle-chart-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..f23daebae8 Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-icicle-chart-visually-looks-correct-1-snap.png differ diff --git a/src/chart_types/partition_chart/layout/types/config_types.ts b/src/chart_types/partition_chart/layout/types/config_types.ts index 1307bbb76d..8bcec0dabd 100644 --- a/src/chart_types/partition_chart/layout/types/config_types.ts +++ b/src/chart_types/partition_chart/layout/types/config_types.ts @@ -27,6 +27,8 @@ import { Font, FontFamily, PartialFont } from './types'; export const PartitionLayout = Object.freeze({ sunburst: 'sunburst' as const, treemap: 'treemap' as const, + icicle: 'icicle' as const, + flame: 'flame' as const, }); /** @public */ diff --git a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts index 76cbb0de28..f39943ac4c 100644 --- a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts +++ b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts @@ -57,7 +57,9 @@ interface MapNode extends NodeDescriptor { export type PrimitiveValue = string | number | null; // there could be more but sufficient for now type Key = PrimitiveValue; -type Sorter = (a: number, b: number) => number; + +export type Sorter = (a: number, b: number) => number; + type NodeSorter = (a: ArrayEntry, b: ArrayEntry) => number; export const entryKey = ([key]: ArrayEntry) => key; @@ -99,7 +101,7 @@ export function groupByRollup( const statistics: Statistics = { globalAggregate: NaN, }; - const reductionMap = factTable.reduce((p: HierarchyOfMaps, n, index) => { + const reductionMap: HierarchyOfMaps = factTable.reduce((p: HierarchyOfMaps, n, index) => { const keyCount = keyAccessors.length; let pointer: HierarchyOfMaps = p; keyAccessors.forEach((keyAccessor, i) => { @@ -132,22 +134,22 @@ export function groupByRollup( function getRootArrayNode(): ArrayNode { const children: HierarchyOfArrays = []; - const bootstrap = { + const bootstrap: Omit = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children, [INPUT_KEY]: [] as number[], [PATH_KEY]: [] as number[], + [SORT_INDEX_KEY]: 0, + [STATISTICS_KEY]: { globalAggregate: 0 }, }; - Object.assign(bootstrap, { [PARENT_KEY]: bootstrap }); - const result: ArrayNode = bootstrap as ArrayNode; - return result; + return { ...bootstrap, [PARENT_KEY]: bootstrap } as ArrayNode; // TS doesn't yet handle bootstrapping but the `Omit` above retains guarantee for all props except `[PARENT_KEY` } /** @internal */ -export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter): HierarchyOfArrays { - const groupByMap = (node: HierarchyOfMaps, parent: ArrayNode) => - Array.from( +export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter | null): HierarchyOfArrays { + const groupByMap = (node: HierarchyOfMaps, parent: ArrayNode) => { + const items = Array.from( node, ([key, value]: [Key, MapNode]): ArrayEntry => { const valueElement = value[CHILDREN_KEY]; @@ -168,12 +170,15 @@ export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter): Hierarc ); return [key, newValue]; }, - ) - .sort(sorter) - .map((n: ArrayEntry, i) => { - entryValue(n).sortIndex = i; - return n; - }); // with the current algo, decreasing order is important + ); + if (sorter !== null) { + items.sort(sorter); + } + return items.map((n: ArrayEntry, i) => { + entryValue(n).sortIndex = i; + return n; + }); + }; // with the current algo, decreasing order is important const tree = groupByMap(root, getRootArrayNode()); const buildPaths = ([, mapNode]: ArrayEntry, currentPath: number[]) => { const newPath = [...currentPath, mapNode[SORT_INDEX_KEY]]; diff --git a/src/chart_types/partition_chart/layout/utils/sunburst.ts b/src/chart_types/partition_chart/layout/utils/sunburst.ts index fc4415410c..911f8c4e0c 100644 --- a/src/chart_types/partition_chart/layout/utils/sunburst.ts +++ b/src/chart_types/partition_chart/layout/utils/sunburst.ts @@ -22,11 +22,12 @@ import { ArrayEntry, childrenAccessor, HierarchyOfArrays } from './group_by_roll /** @internal */ export function sunburst( - nodes: HierarchyOfArrays, + outerNodes: HierarchyOfArrays, areaAccessor: (e: ArrayEntry) => number, - { x0, y0 }: Origin, + { x0: outerX0, y0: outerY0 }: Origin, clockwiseSectors: boolean, specialFirstInnermostSector: boolean, + heightStep: number = 1, ): Array { const result: Array = []; const laySubtree = (nodes: HierarchyOfArrays, { x0, y0 }: Origin, depth: number) => { @@ -36,14 +37,14 @@ export function sunburst( const index = clockwiseSectors ? i : nodeCount - i - 1; const node = nodes[depth === 1 && specialFirstInnermostSector ? (index + 1) % nodeCount : index]; const area = areaAccessor(node); - result.push({ node, x0: currentOffsetX, y0, x1: currentOffsetX + area, y1: y0 + 1 }); + result.push({ node, x0: currentOffsetX, y0, x1: currentOffsetX + area, y1: y0 + heightStep }); const children = childrenAccessor(node); - if (children && children.length) { - laySubtree(children, { x0: currentOffsetX, y0: y0 + 1 }, depth + 1); + if (children.length > 0) { + laySubtree(children, { x0: currentOffsetX, y0: y0 + heightStep }, depth + 1); } currentOffsetX += area; } }; - laySubtree(nodes, { x0, y0 }, 0); + laySubtree(outerNodes, { x0: outerX0, y0: outerY0 }, 0); return result; } diff --git a/src/chart_types/partition_chart/layout/utils/treemap.ts b/src/chart_types/partition_chart/layout/utils/treemap.ts index 2b5dbb2969..7bfc456c3b 100644 --- a/src/chart_types/partition_chart/layout/utils/treemap.ts +++ b/src/chart_types/partition_chart/layout/utils/treemap.ts @@ -101,20 +101,25 @@ export function treemap( areaAccessor: (e: ArrayEntry) => number, topPaddingAccessor: (e: ArrayEntry) => number, paddingAccessor: (e: ArrayEntry) => number, - { x0, y0, width, height }: { x0: number; y0: number; width: number; height: number }, + { + x0: outerX0, + y0: outerY0, + width: outerWidth, + height: outerHeight, + }: { x0: number; y0: number; width: number; height: number }, ): Array { if (nodes.length === 0) return []; // some bias toward horizontal rectangles with a golden ratio of width to height - const vertical = width / GOLDEN_RATIO <= height; - const independentSize = vertical ? width : height; + const vertical = outerWidth / GOLDEN_RATIO <= outerHeight; + const independentSize = vertical ? outerWidth : outerHeight; const vectorElements = bestVector(nodes, independentSize, areaAccessor); - const vector = vectorNodeCoordinates(vectorElements, x0, y0, vertical); + const vector = vectorNodeCoordinates(vectorElements, outerX0, outerY0, vertical); const { dependentSize } = vectorElements; return vector .concat( ...vector.map(({ node, x0, y0, x1, y1 }) => { const childrenNodes = entryValue(node)[CHILDREN_KEY]; - if (!childrenNodes || !childrenNodes.length) { + if (childrenNodes.length === 0) { return []; } const fullWidth = x1 - x0; @@ -148,8 +153,8 @@ export function treemap( topPaddingAccessor, paddingAccessor, vertical - ? { x0, y0: y0 + dependentSize, width, height: height - dependentSize } - : { x0: x0 + dependentSize, y0, width: width - dependentSize, height }, + ? { x0: outerX0, y0: outerY0 + dependentSize, width: outerWidth, height: outerHeight - dependentSize } + : { x0: outerX0 + dependentSize, y0: outerY0, width: outerWidth - dependentSize, height: outerHeight }, ), ); } diff --git a/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts b/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts index 919dbaa61b..5514e931c0 100644 --- a/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts +++ b/src/chart_types/partition_chart/layout/viewmodel/hierarchy_of_arrays.ts @@ -28,12 +28,14 @@ import { groupByRollup, mapEntryValue, mapsToArrays, + Sorter, } from '../utils/group_by_rollup'; export function getHierarchyOfArrays( rawFacts: Relation, valueAccessor: ValueAccessor, groupByRollupAccessors: IndexedAccessorFn[], + sorter: Sorter | null = childOrders.descending, ): HierarchyOfArrays { const aggregator = aggregators.sum; @@ -52,6 +54,6 @@ export function getHierarchyOfArrays( // size as data value vs size as number of pixels in the rectangle return mapsToArrays( groupByRollup(groupByRollupAccessors, valueAccessor, aggregator, facts), - aggregateComparator(mapEntryValue, childOrders.descending), + sorter && aggregateComparator(mapEntryValue, sorter), ); } diff --git a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts index d4a33c8f82..714f67c804 100644 --- a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts @@ -103,7 +103,7 @@ export function makeQuadViewModel( const opacityMultiplier = 1; // could alter in the future, eg. in response to interactions const layer = layers[node.depth - 1]; const fillColorSpec = layer && layer.shape && layer.shape.fillColor; - const fill = fillColorSpec || 'rgba(128,0,0,0.5)'; + const fill = fillColorSpec ?? 'rgba(128,0,0,0.5)'; const shapeFillColor = typeof fill === 'function' ? fill(node, node.sortIndex, node.parent.children) : fill; const { r, g, b, opacity } = stringToRGB(shapeFillColor); const fillColor = argsToRGBString(r, g, b, opacity * opacityMultiplier); @@ -149,9 +149,9 @@ export interface RectangleConstruction { y1: Pixels; } -function rectangleConstruction(treeHeight: number, topGroove: number) { - return function (node: ShapeTreeNode): RectangleConstruction { - return node.depth < treeHeight +function rectangleConstruction(treeHeight: number, topGroove: number | null) { + return function rectangleConstructionClosure(node: ShapeTreeNode): RectangleConstruction { + return node.depth < treeHeight && topGroove !== null ? { x0: node.x0, y0: node.y0px, @@ -167,6 +167,66 @@ function rectangleConstruction(treeHeight: number, topGroove: number) { }; } +const rawChildNodes = ( + partitionLayout: PartitionLayout, + tree: HierarchyOfArrays, + topGroove: number, + width: number, + height: number, + clockwiseSectors: boolean, + specialFirstInnermostSector: boolean, + maxDepth: number, +): Array => { + const totalValue = tree.reduce((p: number, n: ArrayEntry): number => p + mapEntryValue(n), 0); + switch (partitionLayout) { + case PartitionLayout.sunburst: + const sunburstValueToAreaScale = TAU / totalValue; + const sunburstAreaAccessor = (e: ArrayEntry) => sunburstValueToAreaScale * mapEntryValue(e); + return sunburst(tree, sunburstAreaAccessor, { x0: 0, y0: -1 }, clockwiseSectors, specialFirstInnermostSector); + + case PartitionLayout.treemap: + const treemapInnerArea = isTreemap(partitionLayout) ? width * height : 1; // assuming 1 x 1 unit square + const treemapValueToAreaScale = treemapInnerArea / totalValue; + const treemapAreaAccessor = (e: ArrayEntry) => treemapValueToAreaScale * mapEntryValue(e); + return treemap(tree, treemapAreaAccessor, topGrooveAccessor(topGroove), grooveAccessor, { + x0: -width / 2, + y0: -height / 2, + width, + height, + }); + + case PartitionLayout.icicle: + case PartitionLayout.flame: + const icicleLayout = isIcicle(partitionLayout); + const multiplier = icicleLayout ? -1 : 1; + const icicleValueToAreaScale = width / totalValue; + const icicleAreaAccessor = (e: ArrayEntry) => icicleValueToAreaScale * mapEntryValue(e); + const icicleRowHeight = height / maxDepth; + const result = sunburst( + tree, + icicleAreaAccessor, + { x0: -width / 2, y0: (multiplier * height) / 2 - icicleRowHeight }, + true, + false, + icicleRowHeight, + ); + return icicleLayout + ? result + : result.map(({ y0, y1, ...rest }) => ({ y0: height - y1, y1: height - y0, ...rest })); + + default: + // Let's ensure TS complains if we add a new PartitionLayout type in the future without creating a `case` for it + // Hopefully, a future TS version will do away with the need for this boilerplate `default`. Now TS even needs a `default` even if all possible cases are covered. + // Even in runtime it does something sensible (returns the empty set); explicit throwing is avoided as it can deopt the function + return ((layout: never) => layout ?? [])(partitionLayout); + } +}; + +export const isTreemap = (partitionLayout: PartitionLayout) => partitionLayout === PartitionLayout.treemap; +export const isSunburst = (partitionLayout: PartitionLayout) => partitionLayout === PartitionLayout.sunburst; +const isIcicle = (partitionLayout: PartitionLayout) => partitionLayout === PartitionLayout.icicle; +const isFlame = (partitionLayout: PartitionLayout) => partitionLayout === PartitionLayout.flame; + /** @internal */ export function shapeViewModel( textMeasure: TextMeasure, @@ -207,25 +267,25 @@ export function shapeViewModel( return nullShapeViewModel(config, diskCenter); } - const totalValue = tree.reduce((p: number, n: ArrayEntry): number => p + mapEntryValue(n), 0); - - const sunburstValueToAreaScale = TAU / totalValue; - const sunburstAreaAccessor = (e: ArrayEntry) => sunburstValueToAreaScale * mapEntryValue(e); - const treemapLayout = partitionLayout === PartitionLayout.treemap; - const treemapInnerArea = treemapLayout ? width * height : 1; // assuming 1 x 1 unit square - const treemapValueToAreaScale = treemapInnerArea / totalValue; - const treemapAreaAccessor = (e: ArrayEntry) => treemapValueToAreaScale * mapEntryValue(e); - - const rawChildNodes: Array = treemapLayout - ? treemap(tree, treemapAreaAccessor, topGrooveAccessor(topGroove), grooveAccessor, { - x0: -width / 2, - y0: -height / 2, - width, - height, - }) - : sunburst(tree, sunburstAreaAccessor, { x0: 0, y0: -1 }, clockwiseSectors, specialFirstInnermostSector); + const treemapLayout = isTreemap(partitionLayout); + const sunburstLayout = isSunburst(partitionLayout); + const icicleLayout = isIcicle(partitionLayout); + const flameLayout = isFlame(partitionLayout); + const longestPath = ([, { children, path }]: ArrayEntry): number => + children.length > 0 ? children.reduce((p, n) => Math.max(p, longestPath(n)), 0) : path.length; + const maxDepth = longestPath(tree[0]) - 2; // don't include the root node + const childNodes = rawChildNodes( + partitionLayout, + tree, + topGroove, + width, + height, + clockwiseSectors, + specialFirstInnermostSector, + maxDepth, + ); - const shownChildNodes = rawChildNodes.filter((n: Part) => { + const shownChildNodes = childNodes.filter((n: Part) => { const layerIndex = entryValue(n.node).depth - 1; const layer = layers[layerIndex]; return !layer || !layer.showAccessor || layer.showAccessor(entryKey(n.node)); @@ -237,7 +297,7 @@ export function shapeViewModel( const innerRadius: Radius = outerRadius - (1 - emptySizeRatio) * outerRadius; const treeHeight = shownChildNodes.reduce((p: number, n: Part) => Math.max(p, entryValue(n.node).depth), 0); // 1: pie, 2: two-ring donut etc. const ringThickness = (outerRadius - innerRadius) / treeHeight; - const partToShapeFn = partToShapeTreeNode(treemapLayout, innerRadius, ringThickness); + const partToShapeFn = partToShapeTreeNode(!sunburstLayout, innerRadius, ringThickness); const quadViewModel = makeQuadViewModel( shownChildNodes.slice(1).map(partToShapeFn), layers, @@ -248,32 +308,31 @@ export function shapeViewModel( // fill text const roomCondition = (n: ShapeTreeNode) => { const diff = n.x1 - n.x0; - return treemapLayout - ? n.x1 - n.x0 > minFontSize && n.y1px - n.y0px > minFontSize - : (diff < 0 ? TAU + diff : diff) * ringSectorMiddleRadius(n) > Math.max(minFontSize, linkLabel.maximumSection); + return sunburstLayout + ? (diff < 0 ? TAU + diff : diff) * ringSectorMiddleRadius(n) > Math.max(minFontSize, linkLabel.maximumSection) + : n.x1 - n.x0 > minFontSize && n.y1px - n.y0px > minFontSize; }; const nodesWithRoom = quadViewModel.filter(roomCondition); - const outsideFillNodes = fillOutside && !treemapLayout ? nodesWithRoom : []; + const outsideFillNodes = fillOutside && sunburstLayout ? nodesWithRoom : []; - const textFillOrigins = nodesWithRoom.map(treemapLayout ? rectangleFillOrigins : sectorFillOrigins(fillOutside)); + const textFillOrigins = nodesWithRoom.map(sunburstLayout ? sectorFillOrigins(fillOutside) : rectangleFillOrigins); const valueFormatter = valueGetter === percentValueGetter ? specifiedPercentFormatter : specifiedValueFormatter; - const getRowSets = treemapLayout + const getRowSets = sunburstLayout ? fillTextLayout( - rectangleConstruction(treeHeight, topGroove), - getRectangleRowGeometry, - () => 0, - containerBackgroundColor, - ) - : fillTextLayout( ringSectorConstruction(config, innerRadius, ringThickness), getSectorRowGeometry, inSectorRotation(config.horizontalTextEnforcer, config.horizontalTextAngleThreshold), containerBackgroundColor, + ) + : fillTextLayout( + rectangleConstruction(treeHeight, treemapLayout ? topGroove : null), + getRectangleRowGeometry, + () => 0, + containerBackgroundColor, ); - const rowSets: RowSet[] = getRowSets( textMeasure, rawTextGetter, @@ -283,7 +342,7 @@ export function shapeViewModel( config, layers, textFillOrigins, - treemapLayout, + !sunburstLayout, !treemapLayout, ); @@ -294,13 +353,13 @@ export function shapeViewModel( const currentY = [-height, -height, -height, -height]; const nodesWithoutRoom = - fillOutside || treemapLayout + fillOutside || treemapLayout || icicleLayout || flameLayout ? [] // outsideFillNodes and linkLabels are in inherent conflict due to very likely overlaps : quadViewModel.filter((n: ShapeTreeNode) => { const id = nodeId(n); const foundInFillText = rowSets.find((r: RowSet) => r.id === id); // successful text render if found, and has some row(s) - return !(foundInFillText && foundInFillText.rows.length !== 0); + return !(foundInFillText && foundInFillText.rows.length > 0); }); const maxLinkedLabelTextLength = config.linkLabel.maxTextLength; const linkLabelViewModels = linkTextLayout( @@ -321,7 +380,7 @@ export function shapeViewModel( const pickQuads: PickFunction = (x, y) => quadViewModel.filter( - treemapLayout + treemapLayout || icicleLayout || flameLayout ? ({ x0, y0, x1, y1 }) => x0 <= x && x <= x1 && y0 <= y && y <= y1 : ({ x0, y0px, x1, y1px }) => { const angleX = (Math.atan2(y, x) + TAU / 4 + TAU) % TAU; diff --git a/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts b/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts index 6834206f51..119f4a59b7 100644 --- a/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts +++ b/src/chart_types/partition_chart/renderer/canvas/canvas_renderers.ts @@ -19,7 +19,6 @@ import { clearCanvas, renderLayers, withContext } from '../../../../renderers/canvas'; import { Color } from '../../../../utils/commons'; -import { PartitionLayout } from '../../layout/types/config_types'; import { Pixels } from '../../layout/types/geometry_types'; import { LinkLabelVM, @@ -33,6 +32,7 @@ import { addOpacity } from '../../layout/utils/calcs'; import { TAU } from '../../layout/utils/constants'; import { cssFontShorthand } from '../../layout/utils/measure'; import { LinkLabelsViewModelSpec } from '../../layout/viewmodel/link_text_layout'; +import { isSunburst } from '../../layout/viewmodel/viewmodel'; // the burnout avoidance in the center of the pie const LINE_WIDTH_MULT = 10; // border can be a maximum 1/LINE_WIDTH_MULT - th of the sector angle, otherwise the border would dominate @@ -266,9 +266,7 @@ export function renderPartitionCanvas2d( // bottom layer: sectors (pie slices, ring sectors etc.) (ctx: CanvasRenderingContext2D) => - config.partitionLayout === PartitionLayout.treemap - ? renderRectangles(ctx, quadViewModel) - : renderSectors(ctx, quadViewModel), + isSunburst(config.partitionLayout) ? renderSectors(ctx, quadViewModel) : renderRectangles(ctx, quadViewModel), // all the fill-based, potentially multirow text, whether inside or outside the sector (ctx: CanvasRenderingContext2D) => renderRowSets(ctx, rowSets, linkLineColor), diff --git a/src/chart_types/partition_chart/renderer/dom/highlighter.tsx b/src/chart_types/partition_chart/renderer/dom/highlighter.tsx index c94f4a273c..729f5e2845 100644 --- a/src/chart_types/partition_chart/renderer/dom/highlighter.tsx +++ b/src/chart_types/partition_chart/renderer/dom/highlighter.tsx @@ -20,10 +20,12 @@ import React from 'react'; import { Dimensions } from '../../../../utils/dimensions'; +import { configMetadata } from '../../layout/config/config'; import { PartitionLayout } from '../../layout/types/config_types'; import { PointObject } from '../../layout/types/geometry_types'; import { QuadViewModel } from '../../layout/types/viewmodel_types'; import { TAU } from '../../layout/utils/constants'; +import { isSunburst, isTreemap } from '../../layout/viewmodel/viewmodel'; /** @internal */ export interface HighlighterProps { @@ -97,26 +99,12 @@ function renderSector(geometry: QuadViewModel, key: string, style: SVGStyle) { return ; } -function renderGeometries(geometries: QuadViewModel[], partitionLayout: PartitionLayout, style: SVGStyle) { - let maxDepth = -1; +function renderGeometries(geoms: QuadViewModel[], partitionLayout: PartitionLayout, style: SVGStyle) { + const maxDepth = geoms.reduce((acc, geom) => Math.max(acc, geom.depth), 0); // we should render only the deepest geometries of the tree to avoid overlaying highlighted geometries - if (partitionLayout === PartitionLayout.treemap) { - maxDepth = geometries.reduce((acc, geom) => Math.max(acc, geom.depth), 0); - } - return geometries - .filter((geometry) => { - if (maxDepth !== -1) { - return geometry.depth >= maxDepth; - } - return true; - }) - .map((geometry, index) => { - if (partitionLayout === PartitionLayout.sunburst) { - return renderSector(geometry, `${index}`, style); - } - - return renderRectangles(geometry, `${index}`, style); - }); + const highlightedGeoms = isTreemap(partitionLayout) ? geoms.filter((g) => g.depth >= maxDepth) : geoms; + const renderGeom = isSunburst(partitionLayout) ? renderSector : renderRectangles; + return highlightedGeoms.map((geometry, index) => renderGeom(geometry, `${index}`, style)); } /** @internal */ @@ -143,7 +131,7 @@ export class HighlighterComponent extends React.Component { - {partitionLayout === PartitionLayout.sunburst && ( + {isSunburst(partitionLayout) ? ( { mask={`url(#${maskId})`} className="echHighlighter__mask" /> - )} - {partitionLayout === PartitionLayout.treemap && ( + ) : ( )} @@ -201,5 +188,5 @@ export const DEFAULT_PROPS: HighlighterProps = { }, outerRadius: 10, renderAsOverlay: false, - partitionLayout: PartitionLayout.sunburst, + partitionLayout: configMetadata.partitionLayout.dflt, }; diff --git a/src/chart_types/partition_chart/state/selectors/tree.ts b/src/chart_types/partition_chart/state/selectors/tree.ts index e8c0f76d02..8d744be838 100644 --- a/src/chart_types/partition_chart/state/selectors/tree.ts +++ b/src/chart_types/partition_chart/state/selectors/tree.ts @@ -20,11 +20,13 @@ import createCachedSelector from 're-reselect'; import { ChartTypes } from '../../..'; -import { SpecTypes } from '../../../../specs/constants'; +import { SpecTypes } from '../../../../specs'; import { GlobalChartState } from '../../../../state/chart_state'; import { getSpecsFromStore } from '../../../../state/utils'; -import { HierarchyOfArrays } from '../../layout/utils/group_by_rollup'; +import { configMetadata } from '../../layout/config/config'; +import { childOrders, HierarchyOfArrays } from '../../layout/utils/group_by_rollup'; import { getHierarchyOfArrays } from '../../layout/viewmodel/hierarchy_of_arrays'; +import { isSunburst, isTreemap } from '../../layout/viewmodel/viewmodel'; import { PartitionSpec } from '../../specs'; const getSpecs = (state: GlobalChartState) => state.specs; @@ -38,6 +40,13 @@ export const getTree = createCachedSelector( return []; } const { data, valueAccessor, layers } = pieSpecs[0]; - return getHierarchyOfArrays(data, valueAccessor, [() => null, ...layers.map(({ groupByRollup }) => groupByRollup)]); + const layout = pieSpecs[0].config.partitionLayout ?? configMetadata.partitionLayout.dflt; + const sorter = isTreemap(layout) || isSunburst(layout) ? childOrders.descending : null; + return getHierarchyOfArrays( + data, + valueAccessor, + [() => null, ...layers.map(({ groupByRollup }) => groupByRollup)], + sorter, + ); }, )((state) => state.chartId); diff --git a/src/mocks/hierarchical/index.ts b/src/mocks/hierarchical/index.ts index dbd0d18fe7..ae729fd636 100644 --- a/src/mocks/hierarchical/index.ts +++ b/src/mocks/hierarchical/index.ts @@ -19,6 +19,7 @@ import { manyPieMock } from './many_pie'; import { miniSunburstMock } from './mini_sunburst'; +import { observabilityTreeMock } from './observability_tree'; import { pieMock } from './pie'; import { sunburstMock } from './sunburst'; @@ -27,4 +28,5 @@ export const mocks = { sunburst: sunburstMock, miniSunburst: miniSunburstMock, manyPie: manyPieMock, + observabilityTree: observabilityTreeMock, }; diff --git a/src/mocks/hierarchical/observability_tree.ts b/src/mocks/hierarchical/observability_tree.ts new file mode 100644 index 0000000000..6498c78329 --- /dev/null +++ b/src/mocks/hierarchical/observability_tree.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +// source of data: Martin Spier's https://github.com/spiermar/d3-flame-graph + +// prettier-ignore +export const observabilityTreeMock = {c:[{n:'genunix`syscall_mstate',v:89},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'unix`page_lookup_create',v:1}],n:'unix`page_lookup',v:1}],n:'ufs`ufs_getpage',v:1}],n:'genunix`fop_getpage',v:1},{c:[{c:[{c:[{c:[{c:[{n:'genunix`pvn_plist_init',v:1},{n:'unix`lgrp_mem_choose',v:1},{c:[{c:[{c:[{n:'unix`mutex_enter',v:1}],n:'unix`page_get_mnode_freelist',v:1}],n:'unix`page_get_freelist',v:1}],n:'unix`page_create_va',v:1},{c:[{n:'unix`page_lookup_create',v:1}],n:'unix`page_lookup',v:1}],n:'genunix`swap_getapage',v:4}],n:'genunix`swap_getpage',v:4}],n:'genunix`fop_getpage',v:4},{c:[{c:[{n:'unix`hwblkclr',v:3}],n:'unix`pfnzero',v:3}],n:'unix`pagezero',v:3}],n:'genunix`anon_zero',v:7}],n:'genunix`segvn_faultpage',v:7},{n:'ufs`ufs_getpage',v:1},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'unix`hment_compare',v:1}],n:'genunix`avl_find',v:1}],n:'genunix`avl_add',v:1}],n:'unix`hment_insert',v:2}],n:'unix`hment_assign',v:2}],n:'unix`hati_pte_map',v:2}],n:'unix`hati_load_common',v:2}],n:'unix`hat_memload',v:2}],n:'unix`hat_memload_region',v:2}],n:'genunix`segvn_fault',v:11}],n:'genunix`as_fault',v:12},{n:'genunix`segvn_fault',v:1}],n:'unix`pagefault',v:13}],n:'unix`trap',v:13}],n:'unix`0xfffffffffb8001d6',v:13},{n:'unix`0xfffffffffb800c7c',v:42},{n:'unix`0xfffffffffb800c81',v:2},{c:[{n:'genunix`gethrtime_unscaled',v:4},{c:[{c:[{n:'unix`tsc_gethrtimeunscaled',v:11},{n:'unix`tsc_read',v:186}],n:'genunix`gethrtime_unscaled',v:203},{n:'unix`tsc_gethrtimeunscaled',v:13}],n:'genunix`syscall_mstate',v:355},{n:'unix`atomic_add_64',v:110}],n:'unix`0xfffffffffb800c86',v:472},{c:[{n:'genunix`audit_getstate',v:27},{n:'genunix`clear_stale_fd',v:10},{n:'genunix`disp_lock_exit',v:27},{c:[{n:'FSS`fss_preempt',v:1},{n:'genunix`audit_getstate',v:15},{n:'genunix`clear_stale_fd',v:44},{c:[{n:'unix`clear_int_flag',v:39},{n:'unix`do_splx',v:1993},{c:[{c:[{c:[{n:'unix`do_splx',v:1}],n:'genunix`disp_lock_exit_nopreempt',v:1}],n:'unix`preempt',v:1}],n:'unix`kpreempt',v:1}],n:'genunix`disp_lock_exit',v:2096},{n:'genunix`sigcheck',v:1},{c:[{n:'unix`clear_int_flag',v:180},{n:'unix`splr',v:400}],n:'genunix`thread_lock',v:670},{n:'unix`do_splx',v:31},{n:'unix`i_ddi_splhigh',v:23},{n:'unix`lock_clear_splx',v:28},{n:'unix`lock_try',v:778},{n:'unix`lwp_getdatamodel',v:6},{c:[{c:[{c:[{c:[{c:[{n:'unix`tsc_gethrtimeunscaled',v:1}],n:'genunix`mstate_thread_onproc_time',v:1}],n:'unix`caps_charge_adjust',v:1}],n:'unix`cpucaps_charge',v:3},{c:[{n:'unix`cmt_balance',v:1},{c:[{n:'unix`bitset_in_set',v:1}],n:'unix`cpu_wakeup_mwait',v:1}],n:'unix`setbackdq',v:5}],n:'FSS`fss_preempt',v:8},{n:'unix`do_splx',v:1},{c:[{n:'genunix`disp_lock_exit_high',v:1},{c:[{n:'unix`membar_enter',v:1}],n:'unix`disp',v:1},{n:'unix`do_splx',v:1},{c:[{c:[{n:'genunix`schedctl_save',v:1}],n:'genunix`savectx',v:2}],n:'unix`resume',v:2}],n:'unix`swtch',v:5}],n:'unix`preempt',v:14},{n:'unix`prunstop',v:36},{n:'unix`splr',v:92},{n:'unix`splx',v:6}],n:'genunix`post_syscall',v:4245},{n:'genunix`thread_lock',v:33},{n:'unix`lwp_getdatamodel',v:3},{n:'unix`prunstop',v:2}],n:'unix`0xfffffffffb800c91',v:4361},{c:[{n:'genunix`gethrtime_unscaled',v:7},{c:[{c:[{n:'unix`tsc_gethrtimeunscaled',v:17},{n:'unix`tsc_read',v:160}],n:'genunix`gethrtime_unscaled',v:182},{n:'unix`tsc_gethrtimeunscaled',v:12}],n:'genunix`syscall_mstate',v:412},{n:'unix`atomic_add_64',v:95}],n:'unix`0xfffffffffb800ca0',v:517},{n:'unix`_sys_rtt',v:6},{c:[{c:[{c:[{c:[{c:[{c:[{n:'genunix`cpu_decay',v:1}],n:'genunix`cpu_grow',v:1}],n:'genunix`cpu_update_pct',v:1}],n:'genunix`new_mstate',v:1}],n:'unix`trap',v:1}],n:'unix`sys_rtt_common',v:1}],n:'unix`_sys_rtt_ints_disabled',v:1},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'doorfs`door_close',v:1}],n:'namefs`nm_close',v:1}],n:'genunix`fop_close',v:1}],n:'genunix`closef',v:1}],n:'genunix`close_exec',v:1}],n:'genunix`exec_common',v:1}],n:'genunix`exece',v:1}],n:'unix`_sys_sysenter_post_swapgs',v:1},{c:[{n:'genunix`gethrtime_unscaled',v:11},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'unix`mtype_func',v:1},{n:'unix`mutex_enter',v:1}],n:'unix`page_get_mnode_freelist',v:2}],n:'unix`page_get_freelist',v:2}],n:'unix`page_create_va',v:3}],n:'genunix`pvn_read_kluster',v:3}],n:'ufs`ufs_getpage_ra',v:3}],n:'ufs`ufs_getpage',v:3}],n:'genunix`fop_getpage',v:3}],n:'genunix`segvn_faulta',v:3}],n:'genunix`as_faulta',v:3}],n:'genunix`memcntl',v:3},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'unix`htable_lookup',v:1}],n:'unix`htable_walk',v:1}],n:'unix`hat_unload_callback',v:1}],n:'genunix`segvn_unmap',v:1}],n:'genunix`as_unmap',v:1}],n:'unix`mmapobj_map_elf',v:1}],n:'unix`mmapobj_map_interpret',v:1}],n:'unix`mmapobj',v:1}],n:'genunix`mmapobjsys',v:1},{c:[{n:'genunix`copen',v:7},{c:[{n:'genunix`audit_getstate',v:62},{c:[{n:'genunix`audit_falloc',v:8},{c:[{c:[{c:[{c:[{c:[{n:'unix`swtch',v:1}],n:'unix`preempt',v:1}],n:'unix`kpreempt',v:1}],n:'unix`sys_rtt_common',v:1}],n:'unix`_sys_rtt_ints_disabled',v:1}],n:'genunix`audit_getstate',v:66},{n:'genunix`audit_unfalloc',v:32},{n:'genunix`crfree',v:9},{n:'genunix`crhold',v:5},{n:'genunix`cv_broadcast',v:16},{c:[{c:[{n:'genunix`kmem_cache_alloc',v:11},{c:[{n:'genunix`kmem_cache_alloc',v:66},{n:'unix`mutex_enter',v:122},{n:'unix`mutex_exit',v:46}],n:'genunix`kmem_zalloc',v:280},{n:'unix`bzero',v:8}],n:'genunix`audit_falloc',v:313},{n:'genunix`crhold',v:11},{n:'genunix`kmem_cache_alloc',v:49},{n:'genunix`kmem_zalloc',v:13},{c:[{n:'genunix`fd_find',v:13},{n:'genunix`fd_reserve',v:9},{c:[{n:'genunix`fd_find',v:161},{n:'genunix`fd_reserve',v:15}],n:'genunix`ufalloc_file',v:294},{n:'unix`mutex_enter',v:197},{n:'unix`mutex_exit',v:29}],n:'genunix`ufalloc',v:551},{n:'genunix`ufalloc_file',v:20},{n:'unix`atomic_add_32',v:134},{n:'unix`mutex_enter',v:99},{n:'unix`mutex_exit',v:58}],n:'genunix`falloc',v:1363},{n:'genunix`fd_reserve',v:8},{n:'genunix`kmem_cache_alloc',v:9},{n:'genunix`kmem_cache_free',v:5},{n:'genunix`lookupnameat',v:69},{n:'genunix`set_errno',v:24},{c:[{n:'genunix`audit_getstate',v:31},{n:'genunix`cv_broadcast',v:25},{n:'genunix`fd_reserve',v:35}],n:'genunix`setf',v:187},{n:'genunix`ufalloc',v:10},{c:[{c:[{n:'genunix`kmem_cache_free',v:5},{c:[{n:'genunix`kmem_cache_free',v:73},{n:'unix`mutex_enter',v:111},{n:'unix`mutex_exit',v:55}],n:'genunix`kmem_free',v:288}],n:'genunix`audit_unfalloc',v:340},{n:'genunix`crfree',v:13},{n:'genunix`kmem_cache_free',v:51},{n:'genunix`kmem_free',v:11},{n:'unix`atomic_add_32_nv',v:100},{n:'unix`mutex_enter',v:97},{n:'unix`mutex_exit',v:56}],n:'genunix`unfalloc',v:729},{c:[{c:[{c:[{c:[{n:'genunix`audit_getstate',v:16},{n:'genunix`fop_lookup',v:55},{c:[{n:'genunix`audit_getstate',v:21},{n:'genunix`crgetmapped',v:55},{n:'genunix`fop_inactive',v:39},{c:[{n:'genunix`crgetmapped',v:57},{n:'genunix`dnlc_lookup',v:26},{n:'genunix`fop_lookup',v:85},{n:'genunix`kmem_alloc',v:73},{n:'genunix`traverse',v:30},{n:'genunix`vfs_matchops',v:28},{c:[{c:[{n:'genunix`kmem_cache_alloc',v:241},{n:'unix`mutex_enter',v:366},{n:'unix`mutex_exit',v:149}],n:'genunix`kmem_alloc',v:934},{n:'genunix`kmem_cache_alloc',v:32}],n:'genunix`vn_setpath',v:1969},{c:[{n:'genunix`crgetmapped',v:36},{c:[{n:'genunix`crgetmapped',v:58},{n:'genunix`dnlc_lookup',v:70},{n:'genunix`vn_rele',v:14},{n:'ufs`ufs_iaccess',v:91},{c:[{n:'genunix`crgetuid',v:30},{c:[{n:'genunix`memcmp',v:38},{c:[{n:'genunix`memcmp',v:277}],n:'unix`bcmp',v:295}],n:'genunix`dnlc_lookup',v:1843},{n:'genunix`secpolicy_vnode_access2',v:72},{n:'genunix`vn_rele',v:39},{c:[{n:'genunix`crgetuid',v:22},{n:'genunix`secpolicy_vnode_access2',v:217}],n:'ufs`ufs_iaccess',v:648},{n:'unix`bcmp',v:42},{n:'unix`mutex_enter',v:980},{n:'unix`mutex_exit',v:350},{n:'unix`rw_enter',v:525},{n:'unix`rw_exit',v:439}],n:'ufs`ufs_lookup',v:5399}],n:'genunix`fop_lookup',v:6470},{n:'genunix`kmem_cache_alloc',v:39},{c:[{n:'genunix`rwst_exit',v:18},{n:'genunix`rwst_tryenter',v:32},{n:'genunix`vn_mountedvfs',v:11},{n:'genunix`vn_vfslocks_getlock',v:62},{n:'genunix`vn_vfslocks_rele',v:50},{c:[{n:'genunix`kmem_alloc',v:32},{n:'genunix`rwst_enter_common',v:32},{n:'genunix`rwst_init',v:28},{c:[{n:'genunix`rwst_enter_common',v:264},{n:'unix`mutex_enter',v:337},{n:'unix`mutex_exit',v:105}],n:'genunix`rwst_tryenter',v:734},{c:[{n:'genunix`cv_init',v:53},{c:[{c:[{n:'genunix`kmem_cpu_reload',v:2}],n:'genunix`kmem_cache_alloc',v:168},{n:'unix`mutex_enter',v:379},{n:'unix`mutex_exit',v:155}],n:'genunix`kmem_alloc',v:795},{n:'genunix`kmem_cache_alloc',v:29},{c:[{n:'genunix`cv_init',v:65},{n:'unix`mutex_init',v:53}],n:'genunix`rwst_init',v:236},{n:'unix`mutex_init',v:46}],n:'genunix`vn_vfslocks_getlock',v:1357},{n:'unix`mutex_enter',v:727},{n:'unix`mutex_exit',v:371}],n:'genunix`vn_vfsrlock',v:3342},{c:[{n:'genunix`cv_broadcast',v:25},{n:'genunix`kmem_free',v:35},{n:'genunix`rwst_destroy',v:32},{c:[{n:'genunix`cv_broadcast',v:40}],n:'genunix`rwst_exit',v:167},{n:'genunix`vn_vfslocks_getlock',v:120},{c:[{n:'genunix`cv_destroy',v:77},{n:'genunix`kmem_cache_free',v:22},{c:[{n:'genunix`kmem_cache_free',v:154},{n:'unix`mutex_enter',v:316},{n:'unix`mutex_exit',v:148}],n:'genunix`kmem_free',v:693},{c:[{n:'genunix`cv_destroy',v:42},{n:'unix`mutex_destroy',v:176}],n:'genunix`rwst_destroy',v:296},{n:'unix`mutex_destroy',v:31}],n:'genunix`vn_vfslocks_rele',v:1420},{n:'unix`mutex_enter',v:1202},{n:'unix`mutex_exit',v:512}],n:'genunix`vn_vfsunlock',v:3578}],n:'genunix`traverse',v:7243},{n:'genunix`vfs_getops',v:21},{c:[{n:'genunix`vfs_getops',v:157},{n:'unix`membar_consumer',v:123}],n:'genunix`vfs_matchops',v:336},{n:'genunix`vn_alloc',v:20},{n:'genunix`vn_exists',v:17},{n:'genunix`vn_mountedvfs',v:30},{n:'genunix`vn_setops',v:41},{n:'genunix`vn_vfsrlock',v:13},{n:'genunix`vn_vfsunlock',v:40},{n:'lofs`lfind',v:26},{n:'lofs`lsave',v:27},{n:'lofs`makelfsnode',v:28},{c:[{n:'genunix`kmem_cache_alloc',v:234},{n:'genunix`kmem_cpu_reload',v:1},{c:[{n:'genunix`kmem_cache_alloc',v:179},{n:'genunix`vn_recycle',v:33},{c:[{c:[{n:'genunix`vsd_free',v:155}],n:'genunix`vn_recycle',v:319},{n:'genunix`vsd_free',v:14}],n:'genunix`vn_reinit',v:424},{n:'unix`mutex_enter',v:318},{n:'unix`mutex_exit',v:142}],n:'genunix`vn_alloc',v:1189},{n:'genunix`vn_exists',v:50},{n:'genunix`vn_reinit',v:48},{n:'genunix`vn_setops',v:160},{n:'lofs`lfind',v:278},{n:'lofs`lsave',v:162},{n:'lofs`makelfsnode',v:82},{n:'lofs`table_lock_enter',v:220},{n:'unix`atomic_cas_64',v:318},{n:'unix`membar_consumer',v:237},{n:'unix`mutex_enter',v:640},{n:'unix`mutex_exit',v:138}],n:'lofs`makelonode',v:4212},{n:'lofs`table_lock_enter',v:43},{n:'ufs`ufs_lookup',v:46},{n:'unix`atomic_add_32',v:325},{n:'unix`mutex_exit',v:26}],n:'lofs`lo_lookup',v:19887},{n:'lofs`makelonode',v:39},{n:'unix`bcopy',v:896},{n:'unix`mutex_enter',v:947},{n:'unix`mutex_exit',v:337},{c:[{c:[{c:[{n:'unix`dispatch_hilevel',v:1}],n:'unix`do_interrupt',v:1}],n:'unix`_interrupt',v:1}],n:'unix`strlen',v:2659},{n:'zfs`specvp_check',v:10},{n:'zfs`zfs_fastaccesschk_execute',v:4},{c:[{n:'genunix`crgetuid',v:6},{c:[{n:'genunix`memcmp',v:3},{c:[{n:'genunix`memcmp',v:38}],n:'unix`bcmp',v:45}],n:'genunix`dnlc_lookup',v:263},{n:'unix`bcmp',v:11},{n:'unix`mutex_enter',v:309},{n:'unix`mutex_exit',v:135},{n:'zfs`specvp_check',v:20},{c:[{n:'genunix`crgetuid',v:2}],n:'zfs`zfs_fastaccesschk_execute',v:50}],n:'zfs`zfs_lookup',v:946}],n:'genunix`fop_lookup',v:29216},{n:'genunix`fsop_root',v:62},{n:'genunix`pn_fixslash',v:44},{n:'genunix`pn_getcomponent',v:454},{c:[{c:[{n:'lofs`lo_root',v:80},{n:'unix`mutex_enter',v:95},{n:'unix`mutex_exit',v:59}],n:'genunix`fsop_root',v:297},{n:'genunix`rwst_exit',v:12},{n:'genunix`rwst_tryenter',v:37},{n:'genunix`vn_mountedvfs',v:20},{n:'genunix`vn_rele',v:19},{n:'genunix`vn_vfslocks_getlock',v:47},{n:'genunix`vn_vfslocks_rele',v:34},{c:[{n:'genunix`kmem_alloc',v:11},{n:'genunix`rwst_enter_common',v:28},{n:'genunix`rwst_init',v:13},{c:[{n:'genunix`rwst_enter_common',v:314},{n:'unix`mutex_enter',v:238},{n:'unix`mutex_exit',v:49}],n:'genunix`rwst_tryenter',v:628},{c:[{n:'genunix`cv_init',v:56},{c:[{n:'genunix`kmem_cache_alloc',v:126},{n:'unix`mutex_enter',v:252},{n:'unix`mutex_exit',v:95}],n:'genunix`kmem_alloc',v:533},{n:'genunix`kmem_cache_alloc',v:17},{c:[{n:'genunix`cv_init',v:49},{n:'unix`mutex_init',v:38}],n:'genunix`rwst_init',v:173},{n:'unix`mutex_init',v:31}],n:'genunix`vn_vfslocks_getlock',v:973},{n:'unix`mutex_enter',v:455},{n:'unix`mutex_exit',v:250}],n:'genunix`vn_vfsrlock',v:2414},{c:[{n:'genunix`cv_broadcast',v:14},{n:'genunix`kmem_free',v:17},{n:'genunix`rwst_destroy',v:20},{c:[{n:'genunix`cv_broadcast',v:19}],n:'genunix`rwst_exit',v:110},{n:'genunix`vn_vfslocks_getlock',v:79},{c:[{n:'genunix`cv_destroy',v:81},{n:'genunix`kmem_cache_free',v:18},{c:[{n:'genunix`kmem_cache_free',v:116},{n:'unix`mutex_enter',v:195},{n:'unix`mutex_exit',v:90}],n:'genunix`kmem_free',v:457},{c:[{n:'genunix`cv_destroy',v:31},{n:'unix`mutex_destroy',v:53}],n:'genunix`rwst_destroy',v:146},{n:'unix`mutex_destroy',v:17}],n:'genunix`vn_vfslocks_rele',v:903},{n:'unix`mutex_enter',v:823},{n:'unix`mutex_exit',v:356}],n:'genunix`vn_vfsunlock',v:2372},{n:'lofs`lo_root',v:31},{n:'unix`mutex_enter',v:95},{n:'unix`mutex_exit',v:56}],n:'genunix`traverse',v:5557},{n:'genunix`vn_mountedvfs',v:43},{c:[{n:'genunix`crgetmapped',v:31},{c:[{n:'genunix`crgetmapped',v:41},{n:'lofs`freelonode',v:35},{c:[{n:'genunix`kmem_cache_free',v:29},{n:'genunix`vn_free',v:26},{n:'genunix`vn_invalid',v:20},{n:'genunix`vn_rele',v:25},{c:[{c:[{n:'genunix`kmem_cpu_reload',v:1}],n:'genunix`kmem_cache_free',v:184},{n:'genunix`kmem_free',v:115},{c:[{c:[{n:'genunix`kmem_cpu_reload',v:4}],n:'genunix`kmem_cache_free',v:215},{n:'genunix`kmem_cpu_reload',v:5},{c:[{n:'genunix`kmem_cache_free',v:209},{n:'unix`mutex_enter',v:299},{n:'unix`mutex_exit',v:160}],n:'genunix`kmem_free',v:785},{n:'genunix`vsd_free',v:48},{n:'unix`mutex_enter',v:314},{n:'unix`mutex_exit',v:171}],n:'genunix`vn_free',v:1663},{n:'genunix`vn_invalid',v:47},{n:'genunix`vn_rele',v:64},{n:'genunix`vsd_free',v:17},{n:'lofs`table_lock_enter',v:189},{n:'unix`membar_consumer',v:106},{n:'unix`mutex_enter',v:905},{n:'unix`mutex_exit',v:358},{n:'unix`strlen',v:1238}],n:'lofs`freelonode',v:5313},{n:'lofs`table_lock_enter',v:44},{n:'unix`atomic_add_32',v:292},{n:'unix`mutex_enter',v:279},{n:'unix`mutex_exit',v:212}],n:'lofs`lo_inactive',v:6307}],n:'genunix`fop_inactive',v:6689},{n:'lofs`lo_inactive',v:21}],n:'genunix`vn_rele',v:6943},{n:'genunix`vn_setpath',v:58},{n:'genunix`vn_vfsrlock',v:12},{n:'genunix`vn_vfsunlock',v:20},{n:'lofs`lo_lookup',v:65},{n:'unix`mutex_enter',v:575},{n:'unix`mutex_exit',v:379},{n:'unix`strlen',v:107},{n:'zfs`zfs_lookup',v:22}],n:'genunix`lookuppnvp',v:44242},{n:'genunix`pn_fixslash',v:14},{n:'genunix`pn_getcomponent',v:41},{n:'genunix`traverse',v:17},{n:'genunix`vn_mountedvfs',v:56},{n:'genunix`vn_rele',v:73},{c:[{n:'unix`mutex_delay_default',v:1},{n:'unix`tsc_read',v:1}],n:'unix`mutex_vector_enter',v:2}],n:'genunix`lookuppnatcred',v:44681},{n:'genunix`lookuppnvp',v:10},{c:[{n:'unix`copyinstr',v:25},{n:'unix`copystr',v:598}],n:'genunix`pn_get_buf',v:687},{n:'unix`copyinstr',v:18},{n:'unix`mutex_enter',v:320},{n:'unix`mutex_exit',v:163}],n:'genunix`lookupnameatcred',v:45978},{n:'genunix`lookuppnatcred',v:12},{n:'genunix`pn_get_buf',v:13}],n:'genunix`lookupnameat',v:46075},{n:'genunix`lookupnameatcred',v:22}],n:'genunix`vn_openat',v:46342},{n:'unix`mutex_enter',v:303},{n:'unix`mutex_exit',v:38}],n:'genunix`copen',v:49444},{n:'genunix`falloc',v:36},{n:'genunix`set_errno',v:9},{n:'genunix`setf',v:16},{n:'genunix`unfalloc',v:39},{n:'genunix`vn_openat',v:14}],n:'genunix`openat',v:49647}],n:'genunix`open',v:49669},{n:'genunix`openat',v:17},{c:[{c:[{c:[{n:'genunix`dotoprocs',v:1}],n:'genunix`doprio',v:1}],n:'genunix`priocntl_common',v:1}],n:'genunix`priocntlsys',v:1},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'genunix`dnlc_lookup',v:1}],n:'ufs`ufs_lookup',v:1}],n:'genunix`fop_lookup',v:1}],n:'lofs`lo_lookup',v:1}],n:'genunix`fop_lookup',v:1}],n:'genunix`lookuppnvp',v:1}],n:'genunix`lookuppnatcred',v:1}],n:'genunix`lookuppn',v:1}],n:'genunix`resolvepath',v:1},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'genunix`kmem_cache_free',v:1}],n:'genunix`kmem_free',v:1}],n:'genunix`removectx',v:1}],n:'genunix`schedctl_lwp_cleanup',v:1}],n:'genunix`exitlwps',v:1},{c:[{c:[{c:[{c:[{c:[{c:[{c:[{n:'unix`hment_compare',v:2}],n:'genunix`avl_find',v:2}],n:'unix`hment_remove',v:2},{n:'unix`page_numtopp_nolock',v:1}],n:'unix`hat_pte_unmap',v:3}],n:'unix`hat_unload_callback',v:3}],n:'genunix`segvn_unmap',v:3}],n:'genunix`as_free',v:3}],n:'genunix`relvm',v:3},{c:[{c:[{c:[{c:[{n:'genunix`vmem_free',v:1}],n:'genunix`segkp_release_internal',v:1}],n:'genunix`segkp_release',v:1}],n:'genunix`schedctl_freepage',v:1}],n:'genunix`schedctl_proc_cleanup',v:1}],n:'genunix`proc_exit',v:5}],n:'genunix`exit',v:5}],n:'genunix`rexit',v:5},{c:[{c:[{n:'unix`tsc_gethrtimeunscaled',v:43},{n:'unix`tsc_read',v:367}],n:'genunix`gethrtime_unscaled',v:420},{n:'unix`tsc_gethrtimeunscaled',v:59}],n:'genunix`syscall_mstate',v:1336},{n:'unix`atomic_add_64',v:205}],n:'unix`sys_syscall',v:51908}],n:'root',v:57412}; diff --git a/stories/icicle/01_unix_icicle.tsx b/stories/icicle/01_unix_icicle.tsx new file mode 100644 index 0000000000..d4bbecdf3b --- /dev/null +++ b/stories/icicle/01_unix_icicle.tsx @@ -0,0 +1,43 @@ +/* + * 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 React from 'react'; + +import { Chart, Datum, Partition, PartitionLayout, Settings } from '../../src'; +import { STORYBOOK_LIGHT_THEME } from '../shared'; +import { config, getFlatData, getLayerSpec, maxDepth } from '../utils/hierarchical_input_utils'; +import { viridis18 as palette } from '../utils/utils'; + +const color = palette.slice().reverse(); + +export const Example = () => { + return ( + + + d.value as number} + valueFormatter={() => ''} + layers={getLayerSpec(color)} + config={{ ...config, partitionLayout: PartitionLayout.icicle }} + /> + + ); +}; diff --git a/stories/icicle/02_unix_flame.tsx b/stories/icicle/02_unix_flame.tsx new file mode 100644 index 0000000000..b76a714eab --- /dev/null +++ b/stories/icicle/02_unix_flame.tsx @@ -0,0 +1,43 @@ +/* + * 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 React from 'react'; + +import { Chart, Datum, Partition, PartitionLayout, Settings } from '../../src'; +import { STORYBOOK_LIGHT_THEME } from '../shared'; +import { config, getFlatData, getLayerSpec, maxDepth } from '../utils/hierarchical_input_utils'; +import { plasma18 as palette } from '../utils/utils'; + +const color = palette.slice().reverse(); + +export const Example = () => { + return ( + + + d.value as number} + valueFormatter={() => ''} + layers={getLayerSpec(color)} + config={{ ...config, partitionLayout: PartitionLayout.flame }} + /> + + ); +}; diff --git a/stories/icicle/icicle.stories.tsx b/stories/icicle/icicle.stories.tsx new file mode 100644 index 0000000000..82e96bf81b --- /dev/null +++ b/stories/icicle/icicle.stories.tsx @@ -0,0 +1,30 @@ +/* + * 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 { SB_SOURCE_PANEL } from '../utils/storybook'; + +export default { + title: 'Flame (@alpha)', + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; + +export { Example as flameChart } from './02_unix_flame'; +export { Example as icicleChart } from './01_unix_icicle'; diff --git a/stories/interactions/4_sunburst_slice_clicks.tsx b/stories/interactions/4_sunburst_slice_clicks.tsx index 5aa1a54122..1ac43f5d46 100644 --- a/stories/interactions/4_sunburst_slice_clicks.tsx +++ b/stories/interactions/4_sunburst_slice_clicks.tsx @@ -26,7 +26,7 @@ import { STORYBOOK_LIGHT_THEME } from '../shared'; import { indexInterpolatedFillColor, interpolatorCET2s, - categoricalFillColor, + discreteColor, colorBrewerCategoricalPastel12, } from '../utils/utils'; @@ -87,7 +87,7 @@ export const Example = () => { // pick color from color palette based on mean angle - rather distinct colors in the inner ring return indexInterpolatedFillColor(interpolatorCET2s)(d, (d.x0 + d.x1) / 2 / (2 * Math.PI), []); } - return categoricalFillColor(colorBrewerCategoricalPastel12)(d.sortIndex); + return discreteColor(colorBrewerCategoricalPastel12)(d.sortIndex); }, }, }, @@ -100,7 +100,7 @@ export const Example = () => { // pick color from color palette based on mean angle - rather distinct colors in the inner ring return indexInterpolatedFillColor(interpolatorCET2s)(d, (d.x0 + d.x1) / 2 / (2 * Math.PI), []); } - return categoricalFillColor(colorBrewerCategoricalPastel12)(d.sortIndex); + return discreteColor(colorBrewerCategoricalPastel12)(d.sortIndex); }, }, }, diff --git a/stories/legend/10_sunburst.tsx b/stories/legend/10_sunburst.tsx index 448678c414..0cf4951048 100644 --- a/stories/legend/10_sunburst.tsx +++ b/stories/legend/10_sunburst.tsx @@ -26,7 +26,7 @@ import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/type import { mocks } from '../../src/mocks/hierarchical'; import { STORYBOOK_LIGHT_THEME } from '../shared'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -54,15 +54,14 @@ export const Example = () => { groupByRollup: (d: Datum) => d.sitc1, nodeLabel: (d: any) => productLookup[d].name, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.slice(0, 2), nodeLabel: (d: any) => regionLookup[d].regionName, shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -70,7 +69,7 @@ export const Example = () => { nodeLabel: (d: any) => countryLookup[d].name, shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/stylings/20_partition_background.tsx b/stories/stylings/20_partition_background.tsx index bccab7e430..2db37ff4bc 100644 --- a/stories/stylings/20_partition_background.tsx +++ b/stories/stylings/20_partition_background.tsx @@ -25,7 +25,7 @@ import { config } from '../../src/chart_types/partition_chart/layout/config/conf import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/types/viewmodel_types'; import { mocks } from '../../src/mocks/hierarchical'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -55,15 +55,14 @@ export const Example = () => { groupByRollup: (d: Datum) => d.sitc1, nodeLabel: (d: any) => productLookup[d].name, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.slice(0, 2), nodeLabel: (d: any) => regionLookup[d].regionName, shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -71,7 +70,7 @@ export const Example = () => { nodeLabel: (d: any) => countryLookup[d].name, shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/sunburst/15_single_sunburst.tsx b/stories/sunburst/15_single_sunburst.tsx index 124ac6d25f..94400fb77d 100644 --- a/stories/sunburst/15_single_sunburst.tsx +++ b/stories/sunburst/15_single_sunburst.tsx @@ -25,7 +25,7 @@ import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/type import { mocks } from '../../src/mocks/hierarchical'; import { STORYBOOK_LIGHT_THEME } from '../shared'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -45,15 +45,14 @@ export const Example = () => ( groupByRollup: (d: Datum) => d.sitc1, nodeLabel: (d: any) => productLookup[d].name, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.slice(0, 2), nodeLabel: (d: any) => regionLookup[d].regionName.replace(/\s/g, '\u00A0'), shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -61,7 +60,7 @@ export const Example = () => ( nodeLabel: (d: any) => countryLookup[d].name.replace(/\s/g, '\u00A0'), shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/sunburst/26_percentage.tsx b/stories/sunburst/26_percentage.tsx index 5c7dd3add4..db53cb0405 100644 --- a/stories/sunburst/26_percentage.tsx +++ b/stories/sunburst/26_percentage.tsx @@ -25,7 +25,7 @@ import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/type import { mocks } from '../../src/mocks/hierarchical'; import { STORYBOOK_LIGHT_THEME } from '../shared'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -47,15 +47,14 @@ export const Example = () => ( groupByRollup: (d: Datum) => d.sitc1, nodeLabel: (d: any) => productLookup[d].name, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.slice(0, 2), nodeLabel: (d: any) => regionLookup[d].regionName, shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -63,7 +62,7 @@ export const Example = () => ( nodeLabel: (d: any) => countryLookup[d].name, shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/sunburst/27_heterogeneous_depth.tsx b/stories/sunburst/27_heterogeneous_depth.tsx index a12411cfb0..6cf4fa06e0 100644 --- a/stories/sunburst/27_heterogeneous_depth.tsx +++ b/stories/sunburst/27_heterogeneous_depth.tsx @@ -26,7 +26,7 @@ import { PrimitiveValue } from '../../src/chart_types/partition_chart/layout/uti import { mocks } from '../../src/mocks/hierarchical'; import { STORYBOOK_LIGHT_THEME } from '../shared'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -46,15 +46,14 @@ export const Example = () => ( groupByRollup: (d: Datum) => d.sitc1, nodeLabel: (d: PrimitiveValue) => d !== null && productLookup[d].name, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.slice(0, 2), nodeLabel: (d: PrimitiveValue) => d !== null && regionLookup[d].regionName, shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -63,7 +62,7 @@ export const Example = () => ( showAccessor: (d: PrimitiveValue) => !(['chn', 'hkg', 'jpn', 'kor'] as PrimitiveValue[]).includes(d), shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/sunburst/3_value_formatted_2.tsx b/stories/sunburst/3_value_formatted_2.tsx index 401f0f0667..4fc81533b7 100644 --- a/stories/sunburst/3_value_formatted_2.tsx +++ b/stories/sunburst/3_value_formatted_2.tsx @@ -23,7 +23,7 @@ import { Chart, Datum, Partition } from '../../src'; import { config } from '../../src/chart_types/partition_chart/layout/config/config'; import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/types/viewmodel_types'; import { mocks } from '../../src/mocks/hierarchical'; -import { categoricalFillColor, colorBrewerCategoricalPastel12, productLookup } from '../utils/utils'; +import { discreteColor, colorBrewerCategoricalPastel12, productLookup } from '../utils/utils'; export const Example = () => ( @@ -47,7 +47,7 @@ export const Example = () => ( }, }, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalPastel12)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalPastel12)(d.sortIndex), }, }, ]} diff --git a/stories/sunburst/9_sunburst_three_layers.tsx b/stories/sunburst/9_sunburst_three_layers.tsx index 8a8b0b8aae..f3218e8d38 100644 --- a/stories/sunburst/9_sunburst_three_layers.tsx +++ b/stories/sunburst/9_sunburst_three_layers.tsx @@ -26,7 +26,7 @@ import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/type import { mocks } from '../../src/mocks/hierarchical'; import { STORYBOOK_LIGHT_THEME } from '../shared'; import { - categoricalFillColor, + discreteColor, colorBrewerCategoricalStark9, countryLookup, productLookup, @@ -47,7 +47,7 @@ export const Example = () => ( nodeLabel: (d: any) => productLookup[d].name, fillLabel: { maximizeFontSize: boolean('Maximize font size layer 1', true) }, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.7)(d.sortIndex), }, }, { @@ -55,8 +55,7 @@ export const Example = () => ( nodeLabel: (d: any) => regionLookup[d].regionName, fillLabel: { maximizeFontSize: boolean('Maximize font size layer 2', true) }, shape: { - fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalStark9, 0.5)(d.parent.sortIndex), }, }, { @@ -65,7 +64,7 @@ export const Example = () => ( fillLabel: { maximizeFontSize: boolean('Maximize font size layer 3', true) }, shape: { fillColor: (d: ShapeTreeNode) => - categoricalFillColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), + discreteColor(colorBrewerCategoricalStark9, 0.3)(d.parent.parent.sortIndex), }, }, ]} diff --git a/stories/treemap/2_one_layer_2.tsx b/stories/treemap/2_one_layer_2.tsx index 41ffac1305..d01b4f0023 100644 --- a/stories/treemap/2_one_layer_2.tsx +++ b/stories/treemap/2_one_layer_2.tsx @@ -25,7 +25,7 @@ import { ShapeTreeNode } from '../../src/chart_types/partition_chart/layout/type import { arrayToLookup } from '../../src/chart_types/partition_chart/layout/utils/calcs'; import { mocks } from '../../src/mocks/hierarchical'; import { productDimension } from '../../src/mocks/hierarchical/dimension_codes'; -import { categoricalFillColor, colorBrewerCategoricalPastel12 } from '../utils/utils'; +import { discreteColor, colorBrewerCategoricalPastel12 } from '../utils/utils'; const productLookup = arrayToLookup((d: Datum) => d.sitc1, productDimension); @@ -48,7 +48,7 @@ export const Example = () => ( }, }, shape: { - fillColor: (d: ShapeTreeNode) => categoricalFillColor(colorBrewerCategoricalPastel12)(d.sortIndex), + fillColor: (d: ShapeTreeNode) => discreteColor(colorBrewerCategoricalPastel12)(d.sortIndex), }, }, ]} diff --git a/stories/utils/hierarchical_input_utils.tsx b/stories/utils/hierarchical_input_utils.tsx new file mode 100644 index 0000000000..3d738036a8 --- /dev/null +++ b/stories/utils/hierarchical_input_utils.tsx @@ -0,0 +1,84 @@ +/* + * 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 { Datum, RecursivePartial } from '../../src'; +import { Config } from '../../src/chart_types/partition_chart/layout/types/config_types'; +import { PrimitiveValue } from '../../src/chart_types/partition_chart/layout/utils/group_by_rollup'; +import { mocks } from '../../src/mocks/hierarchical'; +import { discreteColor } from './utils'; + +const raw = mocks.observabilityTree; + +interface Node { + c?: Node[]; + n: string; + v: number; +} + +type Row = { [layerKey: string]: unknown; value: number; depth: number }; + +const flatTree = ({ c, n, v }: Node, depth: number): Row[] => { + if (!c) { + return [{ [`layer_${depth}`]: n, value: v, depth }]; + } + // as of writing this, the test runner can't run c.flatMap(...) + const childrenRows = c.reduce((a, child) => [...a, ...flatTree(child, depth + 1)], []); + const childrenTotal = childrenRows.reduce((p, { value }) => p + value, 0); + const missing = Math.max(0, v - childrenTotal); + if (missing > 0) { + childrenRows.unshift({ [`layer_${depth + 1}`]: undefined, value: missing / 2, depth }); + childrenRows.push({ [`layer_${depth + 1}`]: undefined, value: missing / 2, depth }); + } + childrenRows.forEach((innerChild) => { + innerChild[`layer_${depth}`] = n; + }); + return childrenRows; +}; + +/** @internal */ +export const getFlatData = () => flatTree(raw, 0); + +/** @internal */ +export const maxDepth = getFlatData().reduce((p, n) => Math.max(p, n.depth), 0); + +/** @internal */ +export const getLayerSpec = (color: [string, string, string][]) => + [...new Array(maxDepth + 1)].map((_, depth) => ({ + groupByRollup: (d: Datum) => d[`layer_${depth}`], + nodeLabel: (d: PrimitiveValue) => String(d), + showAccessor: (d: PrimitiveValue) => d !== undefined, + shape: { + fillColor: () => discreteColor(color, 0.8)(depth), + }, + })); + +/** @internal */ +export const config: RecursivePartial = { + fontFamily: 'Arial', + fillLabel: { + valueFormatter: (d: number) => d, + textInvertible: true, + fontWeight: 500, + }, + margin: { top: 0, bottom: 0, left: 0, right: 0 }, + minFontSize: 5, + maxFontSize: 9, + idealFontSizeJump: 1.01, + backgroundColor: 'rgba(229,229,229,1)', +}; diff --git a/stories/utils/utils.ts b/stories/utils/utils.ts index 975526d58f..5894548a28 100644 --- a/stories/utils/utils.ts +++ b/stories/utils/utils.ts @@ -36,7 +36,122 @@ export const indexInterpolatedFillColor = (colorMaker: ColorMaker) => (d: any, i // colorbrewer2.org based, categorical color example type RGBStrings = [string, string, string][]; const colorBrewerExportMatcher = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/; -const colorStringToTuple = (s: string) => (colorBrewerExportMatcher.exec(s) as string[]).slice(1); +const rgbStringToTuple = (s: string) => (colorBrewerExportMatcher.exec(s) as string[]).slice(1); +const hexStringToTuple = (s: string) => [ + String(parseInt(s.slice(1, 3), 16)), + String(parseInt(s.slice(3, 5), 16)), + String(parseInt(s.slice(5, 7), 16)), +]; + +export const plasma18 = [ + '#0d0887', + '#2f0596', + '#4903a0', + '#6100a7', + '#7801a8', + '#8e0ca4', + '#a21d9a', + '#b42e8d', + '#c43e7f', + '#d24f71', + '#de6164', + '#e97257', + '#f3854b', + '#f99a3e', + '#fdaf31', + '#fdc627', + '#f8df25', + '#f0f921', +].map(hexStringToTuple) as RGBStrings; + +export const viridis18 = [ + '#440154', + '#481769', + '#472a7a', + '#433d84', + '#3d4e8a', + '#355e8d', + '#2e6d8e', + '#297b8e', + '#23898e', + '#1f978b', + '#21a585', + '#2eb37c', + '#46c06f', + '#65cb5e', + '#89d548', + '#b0dd2f', + '#d8e219', + '#fde725', +].map(hexStringToTuple) as RGBStrings; + +export const cividis18 = [ + '#002051', + '#002b64', + '#0f356c', + '#23406e', + '#374a6e', + '#4b556d', + '#5c606e', + '#6c6b70', + '#797673', + '#858176', + '#928d78', + '#9f9978', + '#aea575', + '#bfb26f', + '#d2bf66', + '#e4cd5a', + '#f4db4e', + '#fdea45', +].map(hexStringToTuple) as RGBStrings; + +export const inferno18 = [ + '#000004', + '#0a0722', + '#1e0c45', + '#380962', + '#510e6c', + '#69166e', + '#801f6c', + '#982766', + '#b0315b', + '#c63d4d', + '#d94d3d', + '#e9612b', + '#f47918', + '#fa9407', + '#fcb014', + '#f8cd37', + '#f2ea69', + '#fcffa4', +].map(hexStringToTuple) as RGBStrings; + +export const colorBrewerSequential9: RGBStrings = [ + 'rgb(255,247,251)', + 'rgb(236,231,242)', + 'rgb(208,209,230)', + 'rgb(166,189,219)', + 'rgb(116,169,207)', + 'rgb(54,144,192)', + 'rgb(5,112,176)', + 'rgb(4,90,141)', + 'rgb(2,56,88)', +].map(rgbStringToTuple) as RGBStrings; + +export const colorBrewerDiverging11: RGBStrings = [ + 'rgb(158,1,66)', + 'rgb(213,62,79)', + 'rgb(244,109,67)', + 'rgb(253,174,97)', + 'rgb(254,224,139)', + 'rgb(255,255,191)', + 'rgb(230,245,152)', + 'rgb(171,221,164)', + 'rgb(102,194,165)', + 'rgb(50,136,189)', + 'rgb(94,79,162)', +].map(rgbStringToTuple) as RGBStrings; export const colorBrewerCategorical12: RGBStrings = [ 'rgb(166,206,227)', @@ -51,7 +166,7 @@ export const colorBrewerCategorical12: RGBStrings = [ 'rgb(106,61,154)', 'rgb(255,255,153)', 'rgb(177,89,40)', -].map(colorStringToTuple) as RGBStrings; +].map(rgbStringToTuple) as RGBStrings; export const colorBrewerCategoricalPastel12: RGBStrings = [ 'rgb(166,206,227)', @@ -66,7 +181,7 @@ export const colorBrewerCategoricalPastel12: RGBStrings = [ 'rgb(106,61,154)', 'rgb(255,255,153)', 'rgb(177,89,40)', -].map(colorStringToTuple) as RGBStrings; +].map(rgbStringToTuple) as RGBStrings; export const colorBrewerCategoricalStark9: RGBStrings = [ 'rgb(228,26,28)', @@ -78,9 +193,9 @@ export const colorBrewerCategoricalStark9: RGBStrings = [ 'rgb(166,86,40)', 'rgb(247,129,191)', 'rgb(153,153,153)', -].map(colorStringToTuple) as RGBStrings; +].map(rgbStringToTuple) as RGBStrings; -export const categoricalFillColor = (categoricalColors: RGBStrings, opacity = 1) => (i: number) => +export const discreteColor = (categoricalColors: RGBStrings, opacity = 1) => (i: number) => `rgba(${categoricalColors[i % categoricalColors.length].concat([opacity.toString()]).join(',')})`; export const decreasingOpacityCET2 = (opacity: number) => (d: any, i: number, a: any[]) =>