Skip to content
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

feat(partition): drilldown #995

Merged
merged 14 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 13 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
4 changes: 2 additions & 2 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2346,8 +2346,8 @@ export type YDomainRange = YDomainBase & DomainRange;
// src/chart_types/heatmap/layout/types/config_types.ts:29: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:61: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:62: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:132: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:134:5 - (ae-forgotten-export) The symbol "AnimKeyframe" needs to be exported by the entry point index.d.ts
// src/common/series_id.ts:40:3 - (ae-forgotten-export) The symbol "SeriesKey" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)
Expand Down
4 changes: 4 additions & 0 deletions src/chart_types/partition_chart/layout/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export const configMetadata: Record<string, ConfigItem> = {
type: 'string',
values: Object.keys(PartitionLayout),
},
drilldown: {
dflt: false,
type: 'boolean',
},

// fill text layout config
circlePadding: { dflt: 2, min: 0.0, max: 8, type: 'number' },
Expand Down
7 changes: 7 additions & 0 deletions src/chart_types/partition_chart/layout/types/config_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export interface StaticConfig extends FillFontSizeRange {
clockwiseSectors: boolean;
specialFirstInnermostSector: boolean;
partitionLayout: PartitionLayout;
/** @alpha */
drilldown: boolean;

// general text config
fontFamily: FontFamily;
Expand Down Expand Up @@ -116,14 +118,19 @@ export interface StaticConfig extends FillFontSizeRange {
export type EasingFunction = (x: Ratio) => Ratio;

export interface AnimKeyframe {
/** @alpha */
markov00 marked this conversation as resolved.
Show resolved Hide resolved
time: number;
/** @alpha */
easingFunction: EasingFunction;
/** @alpha */
keyframeConfig: Partial<StaticConfig>;
}

export interface Config extends StaticConfig {
animation: {
/** @alpha */
duration: TimeMs;
/** @alpha */
keyframes: Array<AnimKeyframe>;
};
}
49 changes: 30 additions & 19 deletions src/chart_types/partition_chart/layout/utils/group_by_rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,33 +108,44 @@ export function groupByRollup(
identity: () => any;
},
factTable: Relation,
drilldown: boolean,
drilldownSelection: CategoryKey[],
): HierarchyOfMaps {
const statistics: Statistics = {
globalAggregate: NaN,
};
const reductionMap: HierarchyOfMaps = factTable.reduce((p: HierarchyOfMaps, n, index) => {
const keyCount = keyAccessors.length;
let pointer: HierarchyOfMaps = p;
keyAccessors.forEach((keyAccessor, i) => {
const key: Key = keyAccessor(n, index);
const last = i === keyCount - 1;
const node = pointer.get(key);
const inputIndices = node?.[INPUT_KEY] ?? [];
const childrenMap = node?.[CHILDREN_KEY] ?? new Map();
const aggregate = node?.[AGGREGATE_KEY] ?? identity();
const reductionValue = reducer(aggregate, valueAccessor(n));
pointer.set(key, {
[AGGREGATE_KEY]: reductionValue,
[STATISTICS_KEY]: statistics,
[INPUT_KEY]: [...inputIndices, index],
[DEPTH_KEY]: i,
...(!last && { [CHILDREN_KEY]: childrenMap }),
keyAccessors
.filter(
() =>
!drilldown ||
keyAccessors
.slice(0, drilldownSelection.length)
.map((keyAccessor) => keyAccessor(n, index))
.join(' | ') === drilldownSelection.slice(0, drilldownSelection.length).join(' | '),
markov00 marked this conversation as resolved.
Show resolved Hide resolved
)
.forEach((keyAccessor, i) => {
const key: Key = keyAccessor(n, index);
const last = i === keyCount - 1;
const node = pointer.get(key);
const inputIndices = node?.[INPUT_KEY] ?? [];
const childrenMap = node?.[CHILDREN_KEY] ?? new Map();
const aggregate = node?.[AGGREGATE_KEY] ?? identity();
const reductionValue = reducer(aggregate, valueAccessor(n));
pointer.set(key, {
[AGGREGATE_KEY]: reductionValue,
[STATISTICS_KEY]: statistics,
[INPUT_KEY]: [...inputIndices, index],
[DEPTH_KEY]: i,
...(!last && { [CHILDREN_KEY]: childrenMap }),
});
if (childrenMap) {
// will always be true except when exiting from forEach, ie. upon encountering the leaf node
pointer = childrenMap;
}
});
if (childrenMap) {
// will always be true except when exiting from forEach, ie. upon encountering the leaf node
pointer = childrenMap;
}
});
return p;
}, new Map());
if (reductionMap.get(HIERARCHY_ROOT_KEY) !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const groupByRollupAccessors = [() => null, (d: any) => d.sitc1];

describe('Test', () => {
test('getHierarchyOfArrays should omit zero and negative values', () => {
const outerResult = getHierarchyOfArrays(rawFacts, valueAccessor, groupByRollupAccessors);
const outerResult = getHierarchyOfArrays(rawFacts, valueAccessor, groupByRollupAccessors, null, false, []);
expect(outerResult.length).toBe(1);

const results = outerResult[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import { CategoryKey } from '../../../../common/category';
import { Relation } from '../../../../common/text_utils';
import { IndexedAccessorFn } from '../../../../utils/accessor';
import { ValueAccessor } from '../../../../utils/common';
Expand All @@ -36,6 +37,8 @@ export function getHierarchyOfArrays(
valueAccessor: ValueAccessor,
groupByRollupAccessors: IndexedAccessorFn[],
sorter: Sorter | null = childOrders.descending,
drilldown: boolean,
drilldownSelection: CategoryKey[],
): HierarchyOfArrays {
const aggregator = aggregators.sum;

Expand All @@ -53,7 +56,7 @@ export function getHierarchyOfArrays(
// By introducing `scale`, we no longer need to deal with the dichotomy of
// size as data value vs size as number of pixels in the rectangle
return mapsToArrays(
groupByRollup(groupByRollupAccessors, valueAccessor, aggregator, facts),
groupByRollup(groupByRollupAccessors, valueAccessor, aggregator, facts, drilldown, drilldownSelection),
sorter && aggregateComparator(mapEntryValue, sorter),
);
}
15 changes: 12 additions & 3 deletions src/chart_types/partition_chart/state/selectors/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,31 @@ import { PartitionSpec } from '../../specs';

const getSpecs = (state: GlobalChartState) => state.specs;

const getDrilldownSelection = (state: GlobalChartState) => state.interactions.drilldown || [];

/** @internal */
export const getTree = createCachedSelector(
[getSpecs],
(specs): HierarchyOfArrays => {
[getSpecs, getDrilldownSelection],
(specs, drilldownSelection): HierarchyOfArrays => {
const pieSpecs = getSpecsFromStore<PartitionSpec>(specs, ChartTypes.Partition, SpecTypes.Series);
if (pieSpecs.length !== 1) {
return [];
}
const { data, valueAccessor, layers } = pieSpecs[0];
const {
data,
valueAccessor,
layers,
config: { drilldown },
} = pieSpecs[0];
const layout = pieSpecs[0].config.partitionLayout ?? configMetadata.partitionLayout.dflt;
const sorter = isTreemap(layout) || isSunburst(layout) ? childOrders.descending : null;
return getHierarchyOfArrays(
data,
valueAccessor,
[() => HIERARCHY_ROOT_KEY, ...layers.map(({ groupByRollup }) => groupByRollup)],
sorter,
Boolean(drilldown),
drilldownSelection,
);
},
)((state) => state.chartId);
5 changes: 4 additions & 1 deletion src/state/chart_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { HeatmapState } from '../chart_types/heatmap/state/chart_state';
import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup';
import { PartitionState } from '../chart_types/partition_chart/state/chart_state';
import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state';
import { CategoryKey } from '../common/category';
import { LegendItem, LegendItemExtraValues } from '../common/legend';
import { SeriesIdentifier, SeriesKey } from '../common/series_id';
import { TooltipAnchorPosition, TooltipInfo } from '../components/tooltip/types';
Expand Down Expand Up @@ -186,6 +187,7 @@ export interface InteractionsState {
highlightedLegendPath: LegendPath;
deselectedDataSeries: SeriesIdentifier[];
hoveredDOMElement: DOMElement | null;
drilldown: CategoryKey[];
markov00 marked this conversation as resolved.
Show resolved Hide resolved
}

/** @internal */
Expand Down Expand Up @@ -275,6 +277,7 @@ export const getInitialState = (chartId: string): GlobalChartState => ({
highlightedLegendPath: [],
deselectedDataSeries: [],
hoveredDOMElement: null,
drilldown: [],
},
externalEvents: {
pointer: null,
Expand Down Expand Up @@ -391,7 +394,7 @@ export const chartStoreReducer = (chartId: string) => {
return getInternalIsInitializedSelector(state) === InitStatus.Initialized
? {
...state,
interactions: interactionsReducer(state.interactions, action, getLegendItemsSelector(state)),
interactions: interactionsReducer(state, action, getLegendItemsSelector(state)),
}
: state;
}
Expand Down
24 changes: 20 additions & 4 deletions src/state/reducers/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
* under the License.
*/

import { ChartTypes } from '../../chart_types';
import { getPickedShapesLayerValues } from '../../chart_types/partition_chart/state/selectors/picked_shapes';
import { getSeriesIndex } from '../../chart_types/xy_chart/utils/series';
import { LegendItem } from '../../common/legend';
import { SeriesIdentifier } from '../../common/series_id';
import { LayerValue } from '../../specs';
import { getDelta } from '../../utils/point';
import { DOMElementActions, ON_DOM_ELEMENT_ENTER, ON_DOM_ELEMENT_LEAVE } from '../actions/dom_element';
import { KeyActions, ON_KEY_UP } from '../actions/key';
Expand All @@ -30,8 +33,8 @@ import {
ON_TOGGLE_DESELECT_SERIES,
ToggleDeselectSeriesAction,
} from '../actions/legend';
import { MouseActions, ON_MOUSE_DOWN, ON_MOUSE_UP, ON_POINTER_MOVE } from '../actions/mouse';
import { InteractionsState } from '../chart_state';
import { ON_MOUSE_DOWN, ON_MOUSE_UP, ON_POINTER_MOVE, MouseActions } from '../actions/mouse';
import { GlobalChartState, InteractionsState } from '../chart_state';
import { getInitialPointerState } from '../utils';

/**
Expand All @@ -46,10 +49,11 @@ const DRAG_DETECTION_PIXEL_DELTA = 4;

/** @internal */
export function interactionsReducer(
state: InteractionsState,
globalState: GlobalChartState,
action: LegendActions | MouseActions | KeyActions | DOMElementActions,
legendItems: LegendItem[],
): InteractionsState {
const { interactions: state } = globalState;
switch (action.type) {
case ON_KEY_UP:
if (action.key === 'Escape') {
Expand Down Expand Up @@ -81,6 +85,7 @@ export function interactionsReducer(
case ON_MOUSE_DOWN:
return {
...state,
drilldown: getDrilldownData(globalState),
pointer: {
...state.pointer,
dragging: false,
Expand Down Expand Up @@ -169,7 +174,10 @@ export function interactionsReducer(
}
}

/** @internal */
/**
* Helper functions that currently depend on chart type eg. xy or partition
*/

function toggleDeselectedDataSeries(
{ legendItemId: id, negate }: ToggleDeselectSeriesAction,
deselectedDataSeries: SeriesIdentifier[],
Expand All @@ -194,3 +202,11 @@ function toggleDeselectedDataSeries(
}
return [...deselectedDataSeries, id];
}

function getDrilldownData(globalState: GlobalChartState) {
if (globalState.chartType !== ChartTypes.Partition) {
return [];
}
const layerValues: LayerValue[] = getPickedShapesLayerValues(globalState)[0];
return layerValues ? layerValues[layerValues.length - 1].path.map((n) => n.value) : [];
}
markov00 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion stories/icicle/01_unix_icicle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const Example = () => {
valueAccessor={(d: Datum) => d.value as number}
valueFormatter={() => ''}
layers={getLayerSpec(color)}
config={{ ...config, partitionLayout: PartitionLayout.icicle }}
config={{ ...config, partitionLayout: PartitionLayout.icicle, drilldown: true }}
/>
</Chart>
);
Expand Down
6 changes: 1 addition & 5 deletions stories/icicle/02_unix_flame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,14 @@ export const Example = () => {
legendStrategy={LegendStrategy.PathWithDescendants}
legendMaxDepth={maxDepth}
theme={STORYBOOK_LIGHT_THEME}
onElementClick={(e) => {
// eslint-disable-next-line no-console
console.log(e);
}}
/>
<Partition
id="spec_1"
data={getFlatData()}
valueAccessor={(d: Datum) => d.value as number}
valueFormatter={() => ''}
layers={getLayerSpec(color)}
config={{ ...config, partitionLayout: PartitionLayout.flame }}
config={{ ...config, partitionLayout: PartitionLayout.flame, drilldown: true }}
/>
</Chart>
);
Expand Down