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): add tooltip #544

Merged
merged 19 commits into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 9 additions & 4 deletions src/chart_types/partition_chart/layout/types/viewmodel_types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Config } from './config_types';
import { Coordinate, Distance, PointObject, PointTuple, Radian } from './geometry_types';
import { Coordinate, Distance, Pixels, PointObject, PointTuple, Radian } from './geometry_types';
import { Font } from './types';
import { config } from '../config/config';
import { ArrayNode, HierarchyOfArrays } from '../utils/group_by_rollup';
Expand Down Expand Up @@ -54,23 +54,28 @@ export interface OutsideLinksViewModel {
points: Array<PointTuple>;
}

export type PickFunction = (x: Pixels, y: Pixels) => Array<QuadViewModel>;

export type ShapeViewModel = {
config: Config;
quadViewModel: QuadViewModel[];
rowSets: RowSet[];
linkLabelViewModels: LinkLabelVM[];
outsideLinksViewModel: OutsideLinksViewModel[];
diskCenter: PointObject;
pickQuads: PickFunction;
};

export const nullSectorViewModel = (): ShapeViewModel => ({
config,
export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointObject): ShapeViewModel => ({
config: specifiedConfig || config,
quadViewModel: [],
rowSets: [],
linkLabelViewModels: [],
outsideLinksViewModel: [],
diskCenter: { x: 0, y: 0 },
diskCenter: diskCenter || { x: 0, y: 0 },
pickQuads: () => [],
});

type TreeLevel = number;

interface AngleFromTo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { Datum } from '../../../../utils/commons';
export const AGGREGATE_KEY = 'value'; // todo later switch back to 'aggregate'
export const DEPTH_KEY = 'depth';
export const CHILDREN_KEY = 'children';
export const INPUT_KEY = 'inputIndex';
export const PARENT_KEY = 'parent';
export const SORT_INDEX_KEY = 'sortIndex';

interface NodeDescriptor {
[AGGREGATE_KEY]: number;
[DEPTH_KEY]: number;
[INPUT_KEY]?: Array<number>;
}

export type ArrayEntry = [Key, ArrayNode];
Expand Down Expand Up @@ -71,11 +73,13 @@ export function groupByRollup(
const keyExists = pointer.has(key);
const last = i === keyCount - 1;
const node = keyExists && pointer.get(key);
const inputIndices = node ? node[INPUT_KEY] : [];
const childrenMap = node ? node[CHILDREN_KEY] : new Map();
const aggregate = node ? node[AGGREGATE_KEY] : identity();
const reductionValue = reducer(aggregate, valueAccessor(n));
pointer.set(key, {
[AGGREGATE_KEY]: reductionValue,
[INPUT_KEY]: [...inputIndices, index],
[DEPTH_KEY]: i,
...(!last && { [CHILDREN_KEY]: childrenMap }),
});
Expand All @@ -91,7 +95,7 @@ export function groupByRollup(

function getRootArrayNode(): ArrayNode {
const children: HierarchyOfArrays = [];
const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children };
const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children, [INPUT_KEY]: [] as number[] };
Object.assign(bootstrap, { [PARENT_KEY]: bootstrap });
const result: ArrayNode = bootstrap as ArrayNode;
return result;
Expand All @@ -109,6 +113,7 @@ export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter): Hierarc
[DEPTH_KEY]: NaN,
[SORT_INDEX_KEY]: NaN,
[PARENT_KEY]: parent,
[INPUT_KEY]: [],
};
const newValue: ArrayNode = Object.assign(
resultNode,
Expand Down
26 changes: 17 additions & 9 deletions src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { sunburst } from '../utils/sunburst';
import { IndexedAccessorFn } from '../../../../utils/accessor';
import { argsToRGBString, stringToRGB } from '../utils/d3_utils';
import {
nullShapeViewModel,
OutsideLinksViewModel,
PickFunction,
QuadViewModel,
RawTextGetter,
RowSet,
Expand Down Expand Up @@ -117,7 +119,7 @@ export function makeOutsideLinksViewModel(
})
.filter(({ points }: OutsideLinksViewModel) => points.length > 1);
}
// todo break up this long function

export function shapeViewModel(
textMeasure: TextMeasure,
config: Config,
Expand Down Expand Up @@ -158,14 +160,7 @@ export function shapeViewModel(
facts.some((n) => valueAccessor(n) < 0) ||
facts.reduce((p: number, n) => aggregator.reducer(p, valueAccessor(n)), aggregator.identity()) <= 0
) {
return {
config,
diskCenter,
quadViewModel: [],
rowSets: [],
linkLabelViewModels: [],
outsideLinksViewModel: [],
};
return nullShapeViewModel(config, diskCenter);
}

// We can precompute things invariant of how the rectangle is divvied up.
Expand Down Expand Up @@ -273,6 +268,18 @@ export function shapeViewModel(
valueFormatter,
);

const pickQuads: PickFunction = (x, y) => {
return quadViewModel.filter(
treemapLayout
? ({ 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;
const yPx = Math.sqrt(x * x + y * y);
return x0 <= angleX && angleX <= x1 && y0px <= yPx && yPx <= y1px;
},
);
};

// combined viewModel
return {
config,
Expand All @@ -281,5 +288,6 @@ export function shapeViewModel(
rowSets,
linkLabelViewModels,
outsideLinksViewModel,
pickQuads,
};
}
40 changes: 37 additions & 3 deletions src/chart_types/partition_chart/renderer/canvas/partition.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import React, { MouseEvent } from 'react';
import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';
import { onChartRendered } from '../../../../state/actions/chart';
import { isInitialized } from '../../../../state/selectors/is_initialized';
import { GlobalChartState } from '../../../../state/chart_state';
import { Dimensions } from '../../../../utils/dimensions';
import { partitionGeometries } from '../../state/selectors/geometries';
import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import { nullShapeViewModel, QuadViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import { renderPartitionCanvas2d } from './canvas_renderers';
import { INPUT_KEY } from '../../layout/utils/group_by_rollup';

interface ReactiveChartStateProps {
initialized: boolean;
Expand Down Expand Up @@ -69,6 +70,38 @@ class PartitionComponent extends React.Component<PartitionProps> {
}
}

handleMouseMove(e: MouseEvent<HTMLCanvasElement>) {
const {
initialized,
chartContainerDimensions: { width, height },
} = this.props;
if (!this.canvasRef.current || !this.ctx || !initialized || width === 0 || height === 0) {
return;
}
const picker = this.props.geometries.pickQuads;
const box = this.canvasRef.current.getBoundingClientRect();
const diskCenter = this.props.geometries.diskCenter;
const x = e.clientX - box.left - diskCenter.x;
const y = e.clientY - box.top - diskCenter.y;
const pickedShapes: Array<QuadViewModel> = picker(x, y);
const datumIndices = new Set();
pickedShapes.forEach((shape) => {
const node = shape.parent;
const shapeNode = node.children.find(([key]) => key === shape.dataName);
if (shapeNode) {
const indices = shapeNode[1][INPUT_KEY] || [];
indices.forEach((i) => datumIndices.add(i));
}
});
/*
console.log(
pickedShapes.map((s) => s.value),
[...datumIndices.values()],
);
*/
return pickedShapes; // placeholder
}

render() {
const {
initialized,
Expand All @@ -84,6 +117,7 @@ class PartitionComponent extends React.Component<PartitionProps> {
className="echCanvasRenderer"
width={width * this.devicePixelRatio}
height={height * this.devicePixelRatio}
onMouseMove={this.handleMouseMove.bind(this)}
style={{
width,
height,
Expand All @@ -103,7 +137,7 @@ const mapDispatchToProps = (dispatch: Dispatch): ReactiveChartDispatchProps =>

const DEFAULT_PROPS: ReactiveChartStateProps = {
initialized: false,
geometries: nullSectorViewModel(),
geometries: nullShapeViewModel(),
chartContainerDimensions: {
width: 0,
height: 0,
Expand Down
31 changes: 22 additions & 9 deletions src/chart_types/partition_chart/state/chart_state.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { InternalChartState } from '../../../state/chart_state';
import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state';
import { ChartTypes } from '../..';
import { Partition } from '../renderer/canvas/partition';
import { isTooltipVisibleSelector } from '../state/selectors/is_tooltip_visible';
import { getTooltipInfoSelector } from '../state/selectors/tooltip';
import { Tooltip } from '../../../components/tooltip';

const EMPTY_MAP = new Map();
export class PartitionState implements InternalChartState {
Expand All @@ -21,19 +24,29 @@ export class PartitionState implements InternalChartState {
getLegendItemsValues() {
return EMPTY_MAP;
}
chartRenderer() {
return <Partition />;
chartRenderer(containerRef: BackwardRef) {
return (
<>
<Tooltip getChartContainerRef={containerRef} />
<Partition />
</>
);
}
getPointerCursor() {
return 'default';
}
isTooltipVisible() {
return false;
isTooltipVisible(globalState: GlobalChartState) {
return isTooltipVisibleSelector(globalState);
}
getTooltipInfo() {
return undefined;
getTooltipInfo(globalState: GlobalChartState) {
return getTooltipInfoSelector(globalState);
}
getTooltipAnchor() {
return null;
getTooltipAnchor(state: GlobalChartState) {
const position = state.interactions.pointer.current.position;
return {
isRotated: false,
x1: position.x,
y1: position.y,
};
}
}
4 changes: 2 additions & 2 deletions src/chart_types/partition_chart/state/selectors/geometries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GlobalChartState } from '../../../../state/chart_state';
import { getSpecsFromStore } from '../../../../state/utils';
import { ChartTypes } from '../../..';
import { render } from './scenegraph';
import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import { PartitionSpec } from '../../specs/index';
import { SpecTypes } from '../../../../specs/settings';

Expand All @@ -15,6 +15,6 @@ export const partitionGeometries = createCachedSelector(
[getSpecs, getParentDimensions],
(specs, parentDimensions): ShapeViewModel => {
const pieSpecs = getSpecsFromStore<PartitionSpec>(specs, ChartTypes.Partition, SpecTypes.Series);
return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullSectorViewModel();
return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullShapeViewModel();
},
)((state) => state.chartId);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import createCachedSelector from 're-reselect';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';

import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { TooltipType, getTooltipType } from '../../../../specs';
import { getTooltipInfoSelector } from './tooltip';

/**
* The brush is available only for Ordinal xScales charts and
* if we have configured an onBrushEnd listener
*/
export const isTooltipVisibleSelector = createCachedSelector(
[getSettingsSpecSelector, getTooltipInfoSelector],
(settingsSpec, tooltipInfo): boolean => {
if (getTooltipType(settingsSpec) === TooltipType.None) {
return false;
}
return tooltipInfo.values.length > 0;
},
)(getChartIdSelector);
11 changes: 2 additions & 9 deletions src/chart_types/partition_chart/state/selectors/scenegraph.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dimensions } from '../../../../utils/dimensions';
import { shapeViewModel } from '../../layout/viewmodel/viewmodel';
import { measureText } from '../../layout/utils/measure';
import { ShapeTreeNode, ShapeViewModel, RawTextGetter } from '../../layout/types/viewmodel_types';
import { ShapeTreeNode, ShapeViewModel, RawTextGetter, nullShapeViewModel } from '../../layout/types/viewmodel_types';
import { DEPTH_KEY } from '../../layout/utils/group_by_rollup';
import { PartitionSpec, Layer } from '../../specs/index';
import { identity, mergePartial, RecursivePartial } from '../../../../utils/commons';
Expand All @@ -23,14 +23,7 @@ export function render(partitionSpec: PartitionSpec, parentDimensions: Dimension
const partialConfig: RecursivePartial<Config> = { ...specConfig, width, height };
const config: Config = mergePartial(defaultConfig, partialConfig);
if (!textMeasurerCtx) {
return {
config,
quadViewModel: [],
rowSets: [],
linkLabelViewModels: [],
outsideLinksViewModel: [],
diskCenter: { x: width / 2, y: height / 2 },
};
return nullShapeViewModel(config, { x: width / 2, y: height / 2 });
}
return shapeViewModel(
measureText(textMeasurerCtx),
Expand Down
Loading