Skip to content

Commit

Permalink
refactor(partition): adopt new rules and restructure for small multip…
Browse files Browse the repository at this point in the history
  • Loading branch information
monfera authored Feb 8, 2021
1 parent 5896cfa commit 89e8a64
Show file tree
Hide file tree
Showing 45 changed files with 1,076 additions and 805 deletions.
2 changes: 1 addition & 1 deletion packages/osd-charts/.playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import React from 'react';

import { Example } from '../stories/icicle/02_unix_flame';
import { Example } from '../stories/sunburst/9_sunburst_three_layers';

export class Playground extends React.Component {
render() {
Expand Down
4 changes: 2 additions & 2 deletions packages/osd-charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ export type ScaleType = $Values<typeof ScaleType>;

// Warning: (ae-missing-release-tag) "SectorGeomSpecY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export interface SectorGeomSpecY {
// Warning: (ae-forgotten-export) The symbol "Distance" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -2224,7 +2224,7 @@ export type TreeLevel = number;

// Warning: (ae-missing-release-tag) "TreeNode" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export interface TreeNode extends AngleFromTo {
// (undocumented)
fill?: Color;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ import createCachedSelector from 're-reselect';
import { Selector } from 'reselect';

import { ChartTypes } from '../../..';
import { SeriesIdentifier } from '../../../../common/series_id';
import { SettingsSpec, LayerValue } from '../../../../specs';
import { GlobalChartState, PointerState } from '../../../../state/chart_state';
import { getOnElementClickSelector } from '../../../../common/event_handler_selectors';
import { GlobalChartState, PointerStates } from '../../../../state/chart_state';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getLastClickSelector } from '../../../../state/selectors/get_last_click';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { isClicking } from '../../../../state/utils';
import { getSpecOrNull } from './goal_spec';
import { getPickedShapesLayerValues } from './picked_shapes';

Expand All @@ -39,32 +37,13 @@ import { getPickedShapesLayerValues } from './picked_shapes';
* @internal
*/
export function createOnElementClickCaller(): (state: GlobalChartState) => void {
let prevClick: PointerState | null = null;
const prev: { click: PointerStates['lastClick'] } = { click: null };
let selector: Selector<GlobalChartState, void> | null = null;
return (state: GlobalChartState) => {
if (selector === null && state.chartType === ChartTypes.Goal) {
selector = createCachedSelector(
[getSpecOrNull, getLastClickSelector, getSettingsSpecSelector, getPickedShapesLayerValues],
(spec, lastClick: PointerState | null, settings: SettingsSpec, pickedShapes): void => {
if (!spec) {
return;
}
if (!settings.onElementClick) {
return;
}
const nextPickedShapesLength = pickedShapes.length;
if (nextPickedShapesLength > 0 && isClicking(prevClick, lastClick) && settings && settings.onElementClick) {
const elements = pickedShapes.map<[Array<LayerValue>, SeriesIdentifier]>((values) => [
values,
{
specId: spec.id,
key: `spec{${spec.id}}`,
},
]);
settings.onElementClick(elements);
}
prevClick = lastClick;
},
getOnElementClickSelector(prev),
)(getChartIdSelector);
}
if (selector) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import createCachedSelector from 're-reselect';
import { Selector } from 'react-redux';

import { ChartTypes } from '../../..';
import { getOnElementOutSelector } from '../../../../common/event_handler_selectors';
import { GlobalChartState } from '../../../../state/chart_state';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
Expand All @@ -34,26 +35,13 @@ import { getPickedShapesLayerValues } from './picked_shapes';
* @internal
*/
export function createOnElementOutCaller(): (state: GlobalChartState) => void {
let prevPickedShapes: number | null = null;
const prev: { pickedShapes: number | null } = { pickedShapes: null };
let selector: Selector<GlobalChartState, void> | null = null;
return (state: GlobalChartState) => {
if (selector === null && state.chartType === ChartTypes.Goal) {
selector = createCachedSelector(
[getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector],
(spec, pickedShapes, settings): void => {
if (!spec) {
return;
}
if (!settings.onElementOut) {
return;
}
const nextPickedShapes = pickedShapes.length;

if (prevPickedShapes !== null && prevPickedShapes > 0 && nextPickedShapes === 0) {
settings.onElementOut();
}
prevPickedShapes = nextPickedShapes;
},
getOnElementOutSelector(prev),
)(getChartIdSelector);
}
if (selector) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,72 +21,28 @@ import createCachedSelector from 're-reselect';
import { Selector } from 'react-redux';

import { ChartTypes } from '../../..';
import { SeriesIdentifier } from '../../../../common/series_id';
import { getOnElementOverSelector } from '../../../../common/event_handler_selectors';
import { LayerValue } from '../../../../specs';
import { GlobalChartState } from '../../../../state/chart_state';
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { getSpecOrNull } from './goal_spec';
import { getPickedShapesLayerValues } from './picked_shapes';

function isOverElement(prevPickedShapes: Array<Array<LayerValue>> = [], nextPickedShapes: Array<Array<LayerValue>>) {
if (nextPickedShapes.length === 0) {
return;
}
if (nextPickedShapes.length !== prevPickedShapes.length) {
return true;
}
return !nextPickedShapes.every((nextPickedShapeValues, index) => {
const prevPickedShapeValues = prevPickedShapes[index];
if (prevPickedShapeValues === null) {
return false;
}
if (prevPickedShapeValues.length !== nextPickedShapeValues.length) {
return false;
}
return nextPickedShapeValues.every((layerValue, i) => {
const prevPickedValue = prevPickedShapeValues[i];
if (!prevPickedValue) {
return false;
}
return layerValue.value === prevPickedValue.value && layerValue.groupByRollup === prevPickedValue.groupByRollup;
});
});
}

/**
* Will call the onElementOver listener every time the following preconditions are met:
* - the onElementOver listener is available
* - we have a new set of highlighted geometries on our state
* @internal
*/
export function createOnElementOverCaller(): (state: GlobalChartState) => void {
let prevPickedShapes: Array<Array<LayerValue>> = [];
const prev: { pickedShapes: LayerValue[][] } = { pickedShapes: [] };
let selector: Selector<GlobalChartState, void> | null = null;
return (state: GlobalChartState) => {
if (selector === null && state.chartType === ChartTypes.Goal) {
selector = createCachedSelector(
[getSpecOrNull, getPickedShapesLayerValues, getSettingsSpecSelector],
(spec, nextPickedShapes, settings): void => {
if (!spec) {
return;
}
if (!settings.onElementOver) {
return;
}

if (isOverElement(prevPickedShapes, nextPickedShapes)) {
const elements = nextPickedShapes.map<[Array<LayerValue>, SeriesIdentifier]>((values) => [
values,
{
specId: spec.id,
key: `spec{${spec.id}}`,
},
]);
settings.onElementOver(elements);
}
prevPickedShapes = nextPickedShapes;
},
getOnElementOverSelector(prev),
)({
keySelector: getChartIdSelector,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ export interface AngleFromTo {
x1: Radian;
}

/** @internal */
export interface LayerFromTo {
y0: TreeLevel;
y1: TreeLevel;
}

/** potential internal */
export interface TreeNode extends AngleFromTo {
x0: Radian;
x1: Radian;
Expand All @@ -152,6 +159,7 @@ export interface TreeNode extends AngleFromTo {
fill?: Color;
}

/** potential internal */
export interface SectorGeomSpecY {
y0px: Distance;
y1px: Distance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@

import { TAU } from '../../../../common/constants';
import {
CirclineArc,
Circline,
CirclineArc,
CirclinePredicate,
Coordinate,
Distance,
PointObject,
Radian,
Radius,
RingSectorConstruction,
trueBearingToStandardPositionAngle,
} from '../../../../common/geometry';
import { Config } from '../types/config_types';
import { AngleFromTo, LayerFromTo, ShapeTreeNode } from '../types/viewmodel_types';

function euclideanDistance({ x: x1, y: y1 }: PointObject, { x: x2, y: y2 }: PointObject): Distance {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
Expand Down Expand Up @@ -109,7 +115,9 @@ function circlineValidSectors(refC: CirclinePredicate, c: CirclineArc): Circline
// imperative, slightly optimized buildup of `result` as it's in the hot loop:
const result = [];
for (let i = 0; i < breakpoints.length - 1; i++) {
// eslint-disable-next-line no-shadow
const from = breakpoints[i];
// eslint-disable-next-line no-shadow
const to = breakpoints[i + 1];
const midAngle = (from + to) / 2; // no winding clip ie. `meanAngle()` would be wrong here
const xx = x + r * Math.cos(midAngle);
Expand All @@ -135,3 +143,98 @@ export function conjunctiveConstraint(constraints: RingSectorConstruction, c: Ci
}
return valids;
}

/** @internal */
export const INFINITY_RADIUS = 1e4; // far enough for a sub-2px precision on a 4k screen, good enough for text bounds; 64 bit floats still work well with it

/** @internal */
export function angleToCircline(
midRadius: Radius,
alpha: Radian,
direction: 1 | -1 /* 1 for clockwise and -1 for anticlockwise circline */,
) {
const sectorRadiusLineX = Math.cos(alpha) * midRadius;
const sectorRadiusLineY = Math.sin(alpha) * midRadius;
const normalAngle = alpha + (direction * Math.PI) / 2;
const x = sectorRadiusLineX + INFINITY_RADIUS * Math.cos(normalAngle);
const y = sectorRadiusLineY + INFINITY_RADIUS * Math.sin(normalAngle);
return { x, y, r: INFINITY_RADIUS, inside: false, from: 0, to: TAU };
}

function ringSectorStartAngle(d: AngleFromTo): Radian {
return trueBearingToStandardPositionAngle(d.x0 + Math.max(0, d.x1 - d.x0 - TAU / 2) / 2);
}

function ringSectorEndAngle(d: AngleFromTo): Radian {
return trueBearingToStandardPositionAngle(d.x1 - Math.max(0, d.x1 - d.x0 - TAU / 2) / 2);
}

function ringSectorInnerRadius(innerRadius: Radian, ringThickness: Distance) {
return (d: LayerFromTo): Radius => innerRadius + d.y0 * ringThickness;
}

function ringSectorOuterRadius(innerRadius: Radian, ringThickness: Distance) {
return (d: LayerFromTo): Radius => innerRadius + (d.y0 + 1) * ringThickness;
}

/** @internal */
export function ringSectorConstruction(config: Config, innerRadius: Radius, ringThickness: Distance) {
return (ringSector: ShapeTreeNode): RingSectorConstruction => {
const {
circlePadding,
radialPadding,
fillOutside,
radiusOutside,
fillRectangleWidth,
fillRectangleHeight,
} = config;
const radiusGetter = fillOutside ? ringSectorOuterRadius : ringSectorInnerRadius;
const geometricInnerRadius = radiusGetter(innerRadius, ringThickness)(ringSector);
const innerR = geometricInnerRadius + circlePadding * 2;
const outerR = Math.max(
innerR,
ringSectorOuterRadius(innerRadius, ringThickness)(ringSector) - circlePadding + (fillOutside ? radiusOutside : 0),
);
const startAngle = ringSectorStartAngle(ringSector);
const endAngle = ringSectorEndAngle(ringSector);
const innerCircline = { x: 0, y: 0, r: innerR, inside: true, from: 0, to: TAU };
const outerCircline = { x: 0, y: 0, r: outerR, inside: false, from: 0, to: TAU };
const midRadius = (innerR + outerR) / 2;
const sectorStartCircle = angleToCircline(midRadius, startAngle - radialPadding, -1);
const sectorEndCircle = angleToCircline(midRadius, endAngle + radialPadding, 1);
const outerRadiusFromRectangleWidth = fillRectangleWidth / 2;
const outerRadiusFromRectanglHeight = fillRectangleHeight / 2;
const fullCircle = ringSector.x0 === 0 && ringSector.x1 === TAU && geometricInnerRadius === 0;
const sectorCirclines = [
...(fullCircle && innerRadius === 0 ? [] : [innerCircline]),
outerCircline,
...(fullCircle ? [] : [sectorStartCircle, sectorEndCircle]),
];
const rectangleCirclines =
outerRadiusFromRectangleWidth === Infinity && outerRadiusFromRectanglHeight === Infinity
? []
: [
{ x: INFINITY_RADIUS - outerRadiusFromRectangleWidth, y: 0, r: INFINITY_RADIUS, inside: true },
{ x: -INFINITY_RADIUS + outerRadiusFromRectangleWidth, y: 0, r: INFINITY_RADIUS, inside: true },
{ x: 0, y: INFINITY_RADIUS - outerRadiusFromRectanglHeight, r: INFINITY_RADIUS, inside: true },
{ x: 0, y: -INFINITY_RADIUS + outerRadiusFromRectanglHeight, r: INFINITY_RADIUS, inside: true },
];
return [...sectorCirclines, ...rectangleCirclines];
};
}
/** @internal */
export function makeRowCircline(
cx: Coordinate,
cy: Coordinate,
radialOffset: Distance,
rotation: Radian,
fontSize: number,
offsetSign: -1 | 0 | 1,
) {
const r = INFINITY_RADIUS;
const offset = (offsetSign * fontSize) / 2;
const topRadius = r - offset;
const x = cx + topRadius * Math.cos(-rotation + TAU / 4);
const y = cy + topRadius * Math.cos(-rotation + TAU / 2);
return { r: r + radialOffset, x, y };
}
Loading

0 comments on commit 89e8a64

Please sign in to comment.