Skip to content

Commit

Permalink
feat(series): dot accessor
Browse files Browse the repository at this point in the history
  • Loading branch information
nickofthyme committed Mar 31, 2020
1 parent 3994439 commit 1d937ce
Show file tree
Hide file tree
Showing 33 changed files with 794 additions and 218 deletions.
109 changes: 63 additions & 46 deletions .playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,74 @@
* under the License. */

import React from 'react';
import {
Chart,
ScaleType,
Position,
Axis,
LineSeries,
LineAnnotation,
RectAnnotation,
AnnotationDomainTypes,
LineAnnotationDatum,
RectAnnotationDatum,
} from '../src';
import { SeededDataGenerator } from '../src/mocks/utils';

export class Playground extends React.Component<{}, { isSunburstShown: boolean }> {
render() {
const dg = new SeededDataGenerator();
const data = dg.generateGroupedSeries(10, 2).map((item) => ({
...item,
y1: item.y + 100,
}));
const lineDatum: LineAnnotationDatum[] = [{ dataValue: 321321 }];
const rectDatum: RectAnnotationDatum[] = [{ coordinates: { x1: 100 } }];
import { Delaunay } from 'd3-delaunay';
import { getRandomNumberGenerator } from '../src/mocks/utils';

export class Playground extends React.Component<{}, { ready: boolean }> {
private readonly canvasRef: React.RefObject<HTMLCanvasElement>;
private ctx: CanvasRenderingContext2D | null;
private delaunay: Delaunay<any> | null;
private points: number[][] = [];

constructor(props: any) {
super(props);
this.canvasRef = React.createRef();
this.ctx = null;
this.delaunay = null;

this.state = { ready: false };
}

componentDidMount() {
if (!this.ctx) {
this.tryCanvasContext();
this.setState({ ready: true });
}
}

tryCanvasContext() {
const canvas = this.canvasRef.current;
const ctx = canvas && canvas.getContext('2d');

if (ctx) {
const rng = getRandomNumberGenerator();
this.ctx = ctx;

this.points = new Array(10).fill(1).map(() => [rng(0, 100), rng(0, 100)]);
console.table(this.points);

this.delaunay = Delaunay.from(this.points);
const voronoi = this.delaunay.voronoi();

this.ctx.beginPath();
voronoi.render(this.ctx);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = 'blue';
this.ctx.stroke();
}
}

handleHover = (event: React.MouseEvent) => {
if (this.delaunay) {
const index = this.delaunay.find(event.nativeEvent.offsetX, event.nativeEvent.offsetY);

console.log(this.points[index]);
}
};

render() {
return (
<>
<div className="chart">
<Chart>
<Axis id="y1" position={Position.Left} title="y1" />
<Axis id="y2" domain={{ fit: true }} groupId="g2" position={Position.Right} title="y2" />
<Axis id="x" position={Position.Bottom} title="x" />
<LineSeries
id="line1"
xScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['g']}
data={data}
/>
<LineSeries
id="line2"
groupId="g2"
xScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y1']}
splitSeriesAccessors={['g']}
data={data}
/>
<LineAnnotation id="sss" dataValues={lineDatum} domainType={AnnotationDomainTypes.XDomain} />
<RectAnnotation id="111" dataValues={rectDatum} />
</Chart>
<canvas
onMouseMove={this.handleHover}
style={{
height: '100%',
width: '100%',
}}
ref={this.canvasRef}
></canvas>
</div>
</>
);
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,13 @@
"webpack-dev-server": "^3.3.1"
},
"dependencies": {
"@types/d3-delaunay": "^4.1.0",
"@types/d3-shape": "^1.3.1",
"classnames": "^2.2.6",
"d3-array": "^1.2.4",
"d3-collection": "^1.0.7",
"d3-color": "^1.4.0",
"d3-delaunay": "^5.2.1",
"d3-scale": "^1.0.7",
"d3-shape": "^1.3.4",
"eslint-plugin-unicorn": "^16.1.1",
Expand Down
22 changes: 15 additions & 7 deletions src/chart_types/xy_chart/renderer/canvas/areas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface AreaGeometriesProps {
export function renderAreas(ctx: CanvasRenderingContext2D, props: AreaGeometriesProps) {
withContext(ctx, (ctx) => {
const { sharedStyle, highlightedLegendItem, areas, clippings } = props;

withClip(ctx, clippings, (ctx: CanvasRenderingContext2D) => {
ctx.save();

Expand All @@ -58,16 +59,22 @@ export function renderAreas(ctx: CanvasRenderingContext2D, props: AreaGeometries
ctx.clip();
ctx.restore();
});
for (let i = 0; i < areas.length; i++) {
const glyph = areas[i];
const { seriesPointStyle, seriesIdentifier } = glyph;

areas.forEach((area) => {
const { seriesPointStyle, seriesIdentifier } = area;
if (seriesPointStyle.visible) {
const geometryStateStyle = getGeometryStateStyle(seriesIdentifier, highlightedLegendItem, sharedStyle);
withContext(ctx, () => {
renderPoints(ctx, glyph.points, seriesPointStyle, geometryStateStyle);
});
withClip(
ctx,
clippings,
(ctx) => {
renderPoints(ctx, area.points, seriesPointStyle, geometryStateStyle);
},
// TODO: add padding over clipping
area.points[0]?.value.dot !== null,
);
}
}
});
});
}

Expand All @@ -83,6 +90,7 @@ function renderArea(
const fill = buildAreaStyles(color, seriesAreaStyle, geometryStateStyle);
renderAreaPath(ctx, transform.x, area, fill, clippedRanges, clippings);
}

function renderAreaLines(
ctx: CanvasRenderingContext2D,
glyph: AreaGeometry,
Expand Down
16 changes: 11 additions & 5 deletions src/chart_types/xy_chart/renderer/canvas/lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { getGeometryStateStyle } from '../../rendering/rendering';
import { LineGeometry } from '../../../../utils/geometry';
import { SharedGeometryStateStyle } from '../../../../utils/themes/theme';
import { LegendItem } from '../../legend/legend';
import { withContext } from '../../../../renderers/canvas';
import { withContext, withClip } from '../../../../renderers/canvas';
import { renderPoints } from './points';
import { renderLinePaths } from './primitives/path';
import { Rect } from '../../../../geoms/types';
Expand All @@ -47,10 +47,16 @@ export function renderLines(ctx: CanvasRenderingContext2D, props: LineGeometries
}

if (seriesPointStyle.visible) {
withContext(ctx, (ctx) => {
const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, highlightedLegendItem, sharedStyle);
renderPoints(ctx, line.points, line.seriesPointStyle, geometryStyle);
});
withClip(
ctx,
clippings,
(ctx) => {
const geometryStyle = getGeometryStateStyle(line.seriesIdentifier, highlightedLegendItem, sharedStyle);
renderPoints(ctx, line.points, line.seriesPointStyle, geometryStyle);
},
// TODO: add padding over clipping
line.points[0]?.value.dot !== null,
);
}
});
});
Expand Down
30 changes: 19 additions & 11 deletions src/chart_types/xy_chart/renderer/canvas/points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { PointGeometry } from '../../../../utils/geometry';
import { PointStyle, GeometryStateStyle } from '../../../../utils/themes/theme';
import { renderCircle } from './primitives/arc';
import { Circle } from '../../../../geoms/types';
import { Circle, Stroke, Fill } from '../../../../geoms/types';
import { buildPointStyles } from './styles/point';

export function renderPoints(
Expand All @@ -28,16 +28,24 @@ export function renderPoints(
themeStyle: PointStyle,
geometryStateStyle: GeometryStateStyle,
) {
return points.map((point) => {
const { x, y, color, transform, styleOverrides } = point;
const { fill, stroke, radius } = buildPointStyles(color, themeStyle, geometryStateStyle, styleOverrides);
points
.map<[Circle, Fill, Stroke]>((point) => {
const { x, y, color, radius, transform, styleOverrides } = point;
const { fill, stroke, radius: radiusOverride } = buildPointStyles(
color,
themeStyle,
geometryStateStyle,
styleOverrides,
);

const circle: Circle = {
x: x + transform.x,
y,
radius,
};
const circle: Circle = {
x: x + transform.x,
y,
radius: radius || radiusOverride,
};

renderCircle(ctx, circle, fill, stroke);
});
return [circle, fill, stroke];
})
.sort(([{ radius: a }], [{ radius: b }]) => b - a)
.forEach((args) => renderCircle(ctx, ...args));
}
24 changes: 20 additions & 4 deletions src/chart_types/xy_chart/renderer/canvas/renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function renderXYChartCanvas2d(
chartTransform,
chartRotation,
geometries,
geometriesIndex,
theme,
highlightedLegendItem,
annotationDimensions,
Expand Down Expand Up @@ -160,18 +161,21 @@ export function renderXYChartCanvas2d(
);
});
},
// rendering debugger
(ctx: CanvasRenderingContext2D) => {
if (!debug) {
return;
}
withContext(ctx, (ctx) => {
const { left, top, width, height } = chartDimensions;

renderDebugRect(
ctx,
{
x: chartDimensions.left,
y: chartDimensions.top,
width: chartDimensions.width,
height: chartDimensions.height,
x: left,
y: top,
width,
height,
},
{
color: stringToRGB('transparent'),
Expand All @@ -182,6 +186,18 @@ export function renderXYChartCanvas2d(
dash: [4, 4],
},
);

const voronoi = geometriesIndex.voronoi([0, 0, width, height]);

if (voronoi) {
ctx.beginPath();
ctx.translate(left, top);
ctx.setLineDash([5, 5]);
voronoi.render(ctx);
ctx.lineWidth = 1;
ctx.strokeStyle = 'blue';
ctx.stroke();
}
});
},
]);
Expand Down
4 changes: 4 additions & 0 deletions src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ import { renderXYChartCanvas2d } from './renderers';
import { isChartEmptySelector } from '../../state/selectors/is_chart_empty';
import { deepEqual } from '../../../../utils/fast_deep_equal';
import { Rotation } from '../../../../utils/commons';
import { IndexedGeometryMap } from '../../utils/indexed_geometry_map';

export interface ReactiveChartStateProps {
initialized: boolean;
debug: boolean;
isChartEmpty: boolean;
geometries: Geometries;
geometriesIndex: IndexedGeometryMap;
theme: Theme;
chartContainerDimensions: Dimensions;
chartRotation: Rotation;
Expand Down Expand Up @@ -180,6 +182,7 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
lines: [],
points: [],
},
geometriesIndex: new IndexedGeometryMap(),
theme: LIGHT_THEME,
chartContainerDimensions: {
width: 0,
Expand Down Expand Up @@ -222,6 +225,7 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
isChartEmpty: isChartEmptySelector(state),
debug: getSettingsSpecSelector(state).debug,
geometries: computeSeriesGeometriesSelector(state).geometries,
geometriesIndex: computeSeriesGeometriesSelector(state).geometriesIndex,
theme: getChartThemeSelector(state),
chartContainerDimensions: getChartContainerDimensionsSelector(state),
highlightedLegendItem: getHighlightedSeriesSelector(state),
Expand Down
4 changes: 3 additions & 1 deletion src/chart_types/xy_chart/renderer/dom/highlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Rotation } from '../../../../utils/commons';
import { Transform } from '../../state/utils';
import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation';
import { computeChartDimensionsSelector } from '../../state/selectors/compute_chart_dimensions';
import { DEFAULT_HIGHLIGHT_PADDING } from '../../rendering/rendering';

interface HighlighterProps {
initialized: boolean;
Expand Down Expand Up @@ -64,10 +65,11 @@ class HighlighterComponent extends React.Component<HighlighterProps> {
key={i}
cx={x + geom.transform.x}
cy={y}
r={geom.radius}
r={geom.radius + DEFAULT_HIGHLIGHT_PADDING}
stroke={color}
strokeWidth={4}
fill="transparent"
clipPath={geom.value.dot !== null ? `url(#${clipPathId})` : undefined}
/>
);
}
Expand Down
Loading

0 comments on commit 1d937ce

Please sign in to comment.