Skip to content

Commit

Permalink
feat: remove konva and add native canvas rendering (#540)
Browse files Browse the repository at this point in the history
This commit remove the Konva and React-Konva dependencies in favour of a native canvas rendering. In the way we are refactoring the chart-library there weren't any benefit on using this library. 
The `LineStyle` style has a new property called `dash` to allow dashed lines styles in theme and through styleAccessor.
We also fixed some rendering issues: points in stacked area charts are now rendered on top of the areas and the opacity of the bar borders is now correctly rendered.
  • Loading branch information
markov00 authored Feb 12, 2020
1 parent 75ff2ad commit 08a4d5d
Show file tree
Hide file tree
Showing 99 changed files with 2,277 additions and 3,169 deletions.
6 changes: 1 addition & 5 deletions .playground/playground.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import React from 'react';

import { Chart, ScaleType, Position, Axis, getAxisId, timeFormatter, getSpecId, AreaSeries } from '../src';
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';
export class Playground extends React.Component<{}, { isSunburstShown: boolean }> {
export class Playground extends React.Component {
chartRef: React.RefObject<Chart> = React.createRef();
state = {
isSunburstShown: true,
};
onBrushEnd = (min: number, max: number) => {
// eslint-disable-next-line no-console
console.log({ min, max });
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"typecheck:all": "tsc -p ./tsconfig.json --noEmit",
"playground": "cd .playground && webpack-dev-server",
"playground:ie": "cd .playground && webpack-dev-server --host=0.0.0.0 --disable-host-check --useLocalIp",
"jest:integration": "TZ=UTC JEST_PUPPETEER_CONFIG=integration/jest-puppeteer.config.js jest --verbose --rootDir=integration -c=integration/jest.config.js --runInBand",
"jest:integration": "rm -rf ./integration/tests/__image_snapshots__/__diff_output__ && TZ=UTC JEST_PUPPETEER_CONFIG=integration/jest-puppeteer.config.js jest --verbose --rootDir=integration -c=integration/jest.config.js --runInBand",
"test:browsers": "cd .playground && jest -c=../browsers/jest.config.js ../browsers/browsers.test.ts",
"ts:prune": "ts-prune",
"backport": "backport"
Expand Down Expand Up @@ -165,13 +165,11 @@
"d3-color": "^1.4.0",
"d3-scale": "^1.0.7",
"d3-shape": "^1.3.4",
"konva": "^4.0.18",
"newtype-ts": "^0.2.4",
"path2d-polyfill": "^0.4.2",
"prop-types": "^15.7.2",
"re-reselect": "^3.4.0",
"react-konva": "16.10.1-0",
"react-redux": "^7.1.0",
"react-spring": "^8.0.8",
"redux": "^4.0.4",
"reselect": "^4.0.0",
"resize-observer-polyfill": "^1.5.1",
Expand Down
13 changes: 13 additions & 0 deletions src/chart_types/partition_chart/layout/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ export interface Font {

export type PartialFont = Partial<Font>;

export const TEXT_ALIGNS = Object.freeze(['start', 'end', 'left', 'right', 'center'] as const);
export type TextAlign = typeof TEXT_ALIGNS[number];

export const TEXT_BASELINE = Object.freeze([
'top',
'hanging',
'middle',
'alphabetic',
'ideographic',
'bottom',
] as const);
export type TextBaseline = typeof TEXT_BASELINE[number];

export interface Box extends Font {
text: string;
}
Expand Down
5 changes: 5 additions & 0 deletions src/chart_types/partition_chart/layout/utils/d3_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ export function argsToRGBString(r: number, g: number, b: number, opacity: number
// d3.rgb returns an Rgb instance, which has a specialized `toString` method
return argsToRGB(r, g, b, opacity).toString();
}

export function RGBtoString(rgb: RgbObject): string {
const { r, g, b, opacity } = rgb;
return argsToRGBString(r, g, b, opacity);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Coordinate, Pixels } from '../../layout/types/geometry_types';
import { Pixels } from '../../layout/types/geometry_types';
import { addOpacity } from '../../layout/utils/calcs';
import {
LinkLabelVM,
Expand All @@ -11,35 +11,12 @@ import {
import { TAU } from '../../layout/utils/math';
import { PartitionLayout } from '../../layout/types/config_types';
import { cssFontShorthand } from '../../layout/utils/measure';
import { withContext, renderLayers, clearCanvas } from '../../../../renderers/canvas';

// 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
const TAPER_OFF_LIMIT = 50; // taper off within a radius of TAPER_OFF_LIMIT to avoid burnout in the middle of the pie when there are hundreds of pies

// withContext abstracts out the otherwise error-prone save/restore pairing; it can be nested and/or put into sequence
// The idea is that you just set what's needed for the enclosed snippet, which may temporarily override values in the
// outer withContext. Example: we use a +y = top convention, so when doing text rendering, y has to be flipped (ctx.scale)
// otherwise the text will render upside down.
function withContext(ctx: CanvasRenderingContext2D, fun: (ctx: CanvasRenderingContext2D) => void) {
ctx.save();
fun(ctx);
ctx.restore();
}

function clearCanvas(
ctx: CanvasRenderingContext2D,
width: Coordinate,
height: Coordinate /*, backgroundColor: string*/,
) {
withContext(ctx, (ctx) => {
// two steps, as the backgroundColor may have a non-one opacity
// todo we should avoid `fillRect` by setting the <canvas> element background via CSS
ctx.clearRect(-width, -height, 2 * width, 2 * height); // remove past contents
// ctx.fillStyle = backgroundColor;
// ctx.fillRect(-width, -height, 2 * width, 2 * height); // new background
});
}

function renderTextRow(ctx: CanvasRenderingContext2D, { fontSize, fillTextColor, rotation }: RowSet) {
return (currentRow: TextRow) => {
const crx = currentRow.rowCentroidX - (Math.cos(rotation) * currentRow.length) / 2;
Expand Down Expand Up @@ -145,11 +122,6 @@ function renderRectangles(ctx: CanvasRenderingContext2D, quadViewModel: QuadView
});
}

// order of rendering is important; determined by the order of layers in the array
function renderLayers(ctx: CanvasRenderingContext2D, layers: Array<(ctx: CanvasRenderingContext2D) => void>) {
layers.forEach((renderLayer) => renderLayer(ctx));
}

function renderFillOutsideLinks(
ctx: CanvasRenderingContext2D,
outsideLinksViewModel: OutsideLinksViewModel[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ class PartitionComponent extends React.Component<PartitionProps> {
// the DOM element has just been appended, and getContext('2d') is always non-null,
// so we could use a couple of ! non-null assertions but no big plus
this.tryCanvasContext();
this.drawCanvas();
if (this.props.initialized) {
this.drawCanvas();
this.props.onChartRendered();
}
}

render() {
Expand Down
7 changes: 3 additions & 4 deletions src/chart_types/xy_chart/annotations/annotation_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ export function computeAnnotationDimensions(

// Annotations should always align with the axis line in histogram mode
const xScaleOffset = computeXScaleOffset(xScale, enableHistogramMode, HistogramModeAlignments.Start);

annotations.forEach((annotationSpec) => {
const { id } = annotationSpec;
if (isLineAnnotation(annotationSpec)) {
Expand Down Expand Up @@ -195,7 +194,7 @@ export function computeAnnotationDimensions(

export function computeAnnotationTooltipState(
cursorPosition: Point,
annotationDimensions: Map<AnnotationId, any>,
annotationDimensions: Map<AnnotationId, AnnotationDimensions>,
annotationSpecs: AnnotationSpec[],
chartRotation: Rotation,
axesSpecs: AxisSpec[],
Expand All @@ -216,7 +215,7 @@ export function computeAnnotationTooltipState(
}
const lineAnnotationTooltipState = computeLineAnnotationTooltipState(
cursorPosition,
annotationDimension,
annotationDimension as AnnotationLineProps[],
groupId,
spec.domainType,
axesSpecs,
Expand All @@ -228,7 +227,7 @@ export function computeAnnotationTooltipState(
} else if (isRectAnnotation(spec)) {
const rectAnnotationTooltipState = computeRectAnnotationTooltipState(
cursorPosition,
annotationDimension,
annotationDimension as AnnotationRectProps[],
chartRotation,
chartDimensions,
spec.renderTooltip,
Expand Down
38 changes: 38 additions & 0 deletions src/chart_types/xy_chart/renderer/canvas/annotations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AnnotationDimensions } from '../../../annotations/annotation_utils';
import { AnnotationSpec, isLineAnnotation, isRectAnnotation } from '../../../utils/specs';
import { getSpecsById } from '../../../state/utils';
import { AnnotationId } from '../../../../../utils/ids';
import { mergeWithDefaultAnnotationLine, mergeWithDefaultAnnotationRect } from '../../../../../utils/themes/theme';
import { renderLineAnnotations } from './lines';
import { AnnotationLineProps } from '../../../annotations/line_annotation_tooltip';
import { renderRectAnnotations } from './rect';
import { AnnotationRectProps } from '../../../annotations/rect_annotation_tooltip';

interface AnnotationProps {
annotationDimensions: Map<AnnotationId, AnnotationDimensions>;
annotationSpecs: AnnotationSpec[];
}
export function renderAnnotations(
ctx: CanvasRenderingContext2D,
props: AnnotationProps,
renderOnBackground: boolean = true,
) {
const { annotationDimensions, annotationSpecs } = props;

annotationDimensions.forEach((annotation, id) => {
const spec = getSpecsById<AnnotationSpec>(annotationSpecs, id);
if (!spec) {
return null;
}
const isBackground = !spec.zIndex || (spec.zIndex && spec.zIndex <= 0);
if ((isBackground && renderOnBackground) || (!isBackground && !renderOnBackground)) {
if (isLineAnnotation(spec)) {
const lineStyle = mergeWithDefaultAnnotationLine(spec.style);
renderLineAnnotations(ctx, annotation as AnnotationLineProps[], lineStyle);
} else if (isRectAnnotation(spec)) {
const rectStyle = mergeWithDefaultAnnotationRect(spec.style);
renderRectAnnotations(ctx, annotation as AnnotationRectProps[], rectStyle);
}
}
});
}
32 changes: 32 additions & 0 deletions src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Stroke, Line } from '../../../../../geoms/types';
import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils';
import { AnnotationLineProps } from '../../../annotations/line_annotation_tooltip';
import { LineAnnotationStyle } from '../../../../../utils/themes/theme';
import { renderMultiLine } from '../primitives/line';

export function renderLineAnnotations(
ctx: CanvasRenderingContext2D,
annotations: AnnotationLineProps[],
lineStyle: LineAnnotationStyle,
) {
const lines = annotations.map<Line>((annotation) => {
const {
start: { x1, y1 },
end: { x2, y2 },
} = annotation.linePathPoints;
return {
x1,
y1,
x2,
y2,
};
});
const strokeColor = stringToRGB(lineStyle.line.stroke);
strokeColor.opacity = strokeColor.opacity * lineStyle.line.opacity;
const stroke: Stroke = {
color: strokeColor,
width: lineStyle.line.strokeWidth,
};

renderMultiLine(ctx, lines, stroke);
}
36 changes: 36 additions & 0 deletions src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { renderRect } from '../primitives/rect';
import { Rect, Fill, Stroke } from '../../../../../geoms/types';
import { AnnotationRectProps } from '../../../annotations/rect_annotation_tooltip';
import { RectAnnotationStyle } from '../../../../../utils/themes/theme';
import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils';
import { withContext } from '../../../../../renderers/canvas';

export function renderRectAnnotations(
ctx: CanvasRenderingContext2D,
annotations: AnnotationRectProps[],
rectStyle: RectAnnotationStyle,
) {
const rects = annotations.map<Rect>((annotation) => {
return annotation.rect;
});
const fillColor = stringToRGB(rectStyle.fill);
fillColor.opacity = fillColor.opacity * rectStyle.opacity;
const fill: Fill = {
color: fillColor,
};
const strokeColor = stringToRGB(rectStyle.stroke);
strokeColor.opacity = strokeColor.opacity * rectStyle.opacity;
const stroke: Stroke = {
color: strokeColor,
width: rectStyle.strokeWidth,
};

const rectsLength = rects.length;

for (let i = 0; i < rectsLength; i++) {
const rect = rects[i];
withContext(ctx, (ctx) => {
renderRect(ctx, rect, fill, stroke);
});
}
}
Loading

0 comments on commit 08a4d5d

Please sign in to comment.