Skip to content

Commit

Permalink
fix: zIndex order for areas, lines and points (#290)
Browse files Browse the repository at this point in the history
This commit change the rendering order of the line series and area series.
For lines we render: for each series the line and each data point, instead of rendering first all the lines and then all the points.
For areas, depending if the area is stacked or not: 
- if stacked, we render fist all the areas, than all the lines and than all the points (this will avoid having a lines partially rendered below the above area)
- if non stacked, we render groups of areas, line, points. so each group id rendered on a different layer.
This PR also remove completely the animation from lines and areas as its currently disabled in the library. We will reintegrate the animation in a future release.

fix #287
  • Loading branch information
markov00 committed Aug 13, 2019
1 parent 305a3bc commit 6a4c1b1
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 165 deletions.
60 changes: 46 additions & 14 deletions .playground/playgroud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
Chart,
getAxisId,
getSpecId,
LineSeries,
niceTimeFormatter,
Position,
ScaleType,
Settings,
mergeWithDefaultTheme,
AreaSeries,
} from '../src';
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';

Expand All @@ -21,10 +21,6 @@ export class Playground extends React.Component {
renderChart(legendPosition: Position) {
const theme = mergeWithDefaultTheme({
lineSeriesStyle: {
// area: {
// fill: 'green',
// opacity:0.2
// },
line: {
stroke: 'violet',
strokeWidth: 4,
Expand All @@ -49,28 +45,64 @@ export class Playground extends React.Component {
tickFormat={niceTimeFormatter([1555819200000, 1555905600000])}
/>
<Axis id={getAxisId('count')} title="count" position={Position.Left} tickFormat={(d) => d.toFixed(2)} />
<LineSeries
id={getSpecId('dataset A with long title')}

<AreaSeries
id={getSpecId('dataset B')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
xAccessor={0}
lineSeriesStyle={{
yAccessors={[1]}
stackAccessors={[0]}
areaSeriesStyle={{
line: {
stroke: 'red',
opacity: 1,
// opacity:1,
strokeWidth: 10,
},
point: {
visible: true,
strokeWidth: 3,
radius: 10,
},
}}
yAccessors={[1]}
/>
<LineSeries
id={getSpecId('dataset B')}
<AreaSeries
id={getSpecId('dataset C')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
areaSeriesStyle={{
line: {
// opacity:1,
strokeWidth: 10,
},
point: {
visible: true,
strokeWidth: 3,
radius: 10,
},
}}
/>
<AreaSeries
id={getSpecId('dataset A with long title')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
xAccessor={0}
areaSeriesStyle={{
point: {
visible: true,
strokeWidth: 3,
radius: 10,
},
line: {
strokeWidth: 10,
},
}}
yAccessors={[1]}
/>
</Chart>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/chart_types/xy_chart/rendering/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface AreaGeometry {
seriesAreaStyle: AreaStyle;
seriesAreaLineStyle: LineStyle;
seriesPointStyle: PointStyle;
isStacked: boolean;
}

export function isPointGeometry(ig: IndexedGeometry): ig is PointGeometry {
Expand Down Expand Up @@ -411,6 +412,7 @@ export function renderArea(
seriesKey: any[],
xScaleOffset: number,
seriesStyle: AreaSeriesStyle,
isStacked: boolean = false,
): {
areaGeometry: AreaGeometry;
indexedGeometries: Map<any, IndexedGeometry[]>;
Expand Down Expand Up @@ -469,6 +471,7 @@ export function renderArea(
seriesAreaStyle: seriesStyle.area,
seriesAreaLineStyle: seriesStyle.line,
seriesPointStyle: seriesStyle.point,
isStacked,
};
return {
areaGeometry,
Expand Down Expand Up @@ -527,3 +530,7 @@ export function isPointOnGeometry(
const { width, height } = indexedGeometry;
return yCoordinate >= y && yCoordinate <= y + height && xCoordinate >= x && xCoordinate <= x + width;
}

export function getGeometryIdKey(geometryId: GeometryId, prefix?: string, postfix?: string) {
return `${prefix || ''}spec:${geometryId.specId}_${geometryId.seriesKey.join('::-::')}${postfix || ''}`;
}
1 change: 1 addition & 0 deletions src/chart_types/xy_chart/store/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ export function renderGeometries(
ds.key,
xScaleOffset,
areaSeriesStyle,
isStacked,
);
areaGeometriesIndex = mergeGeometriesIndexes(areaGeometriesIndex, renderedAreas.indexedGeometries);
areas.push(renderedAreas.areaGeometry);
Expand Down
189 changes: 101 additions & 88 deletions src/components/react_canvas/area_geometries.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Group as KonvaGroup } from 'konva';
import React from 'react';
import { Circle, Group, Path } from 'react-konva';
import { animated, Spring } from 'react-spring/renderprops-konva.cjs';
import { LegendItem } from '../../chart_types/xy_chart/legend/legend';
import { AreaGeometry, getGeometryStyle, PointGeometry } from '../../chart_types/xy_chart/rendering/rendering';
import {
AreaGeometry,
getGeometryStyle,
PointGeometry,
getGeometryIdKey,
GeometryId,
} from '../../chart_types/xy_chart/rendering/rendering';
import { SharedGeometryStyle } from '../../utils/themes/theme';
import {
buildAreaRenderProps,
Expand Down Expand Up @@ -38,110 +43,118 @@ export class AreaGeometries extends React.PureComponent<AreaGeometriesDataProps,
return (
<Group ref={this.barSeriesRef} key={'bar_series'}>
{this.renderAreaGeoms()}
{this.renderAreaLines()}
{this.renderAreaPoints()}
</Group>
);
}
private renderAreaPoints = (): JSX.Element[] => {
const { areas } = this.props;
return areas.reduce(
(acc, glyph, i) => {
const { points, seriesPointStyle, color } = glyph;

if (!seriesPointStyle.visible) {
return acc;
private renderAreaGeoms = (): JSX.Element[] => {
const { sharedStyle, highlightedLegendItem } = this.props;
const areas = this.props.areas.reduce<{
stacked: AreaGeometry[];
nonStacked: AreaGeometry[];
}>(
(acc, area) => {
if (area.isStacked) {
acc.stacked.push(area);
} else {
acc.nonStacked.push(area);
}

const pointStyleProps = buildPointStyleProps(color, seriesPointStyle);

return [...acc, ...this.renderPoints(points, i, pointStyleProps)];
return acc;
},
[] as JSX.Element[],

{ stacked: [], nonStacked: [] },
);

return [
...this.renderStackedAreas(areas.stacked, sharedStyle, highlightedLegendItem),
...this.renderNonStackedAreas(areas.nonStacked, sharedStyle, highlightedLegendItem),
];
};
private renderPoints = (
areaPoints: PointGeometry[],
areaIndex: number,
pointStyleProps: PointStyleProps,
renderStackedAreas = (
areas: AreaGeometry[],
sharedStyle: SharedGeometryStyle,
highlightedLegendItem: LegendItem | null,
): JSX.Element[] => {
const areaPointElements: JSX.Element[] = [];

areaPoints.forEach((areaPoint, pointIndex) => {
const { x, y, transform } = areaPoint;
const key = `area-point-${areaIndex}-${pointIndex}`;

if (this.props.animated) {
areaPointElements.push(
<Group key={`area-point-group-${areaIndex}-${pointIndex}`} x={transform.x}>
<Spring native from={{ y }} to={{ y }}>
{() => {
const pointProps = buildPointRenderProps(x, y, pointStyleProps);
return <animated.Circle {...pointProps} key={key} />;
}}
</Spring>
</Group>,
);
} else {
const pointProps = buildPointRenderProps(transform.x + x, y, pointStyleProps);
areaPointElements.push(<Circle {...pointProps} key={key} />);
const elements: JSX.Element[] = [];
areas.forEach((glyph) => {
const { seriesAreaStyle } = glyph;
if (seriesAreaStyle.visible) {
elements.push(this.renderArea(glyph, sharedStyle, highlightedLegendItem));
}
});
return areaPointElements;
};

private renderAreaGeoms = (): JSX.Element[] => {
const { areas, sharedStyle } = this.props;
const areasToRender: JSX.Element[] = [];

areas.forEach((glyph, i) => {
const { area, color, transform, geometryId, seriesAreaStyle } = glyph;
if (!seriesAreaStyle.visible) {
return;
const { seriesAreaLineStyle } = glyph;
if (seriesAreaLineStyle.visible) {
elements.push(...this.renderAreaLines(glyph, i, sharedStyle, highlightedLegendItem));
}
const customOpacity = seriesAreaStyle ? seriesAreaStyle.opacity : undefined;
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle, customOpacity);
const key = `area-${i}`;
if (this.props.animated) {
areasToRender.push(
<Group key={`area-group-${i}`} x={transform.x}>
<Spring native from={{ area }} to={{ area }}>
{(props: { area: string }) => {
const areaProps = buildAreaRenderProps(0, props.area, color, seriesAreaStyle, geometryStyle);
return <animated.Path {...areaProps} key={key} />;
}}
</Spring>
</Group>,
);
} else {
const areaProps = buildAreaRenderProps(transform.x, area, color, seriesAreaStyle, geometryStyle);
areasToRender.push(<Path {...areaProps} key={key} />);
});
areas.forEach((glyph, i) => {
const { seriesPointStyle } = glyph;
if (seriesPointStyle.visible) {
const pointStyleProps = buildPointStyleProps(glyph.color, seriesPointStyle);
elements.push(...this.renderPoints(glyph.points, i, pointStyleProps, glyph.geometryId));
}
});
return areasToRender;
return elements;
};
private renderAreaLines = (): JSX.Element[] => {
const { areas, sharedStyle } = this.props;
const linesToRender: JSX.Element[] = [];
areas.forEach((glyph, areaIndex) => {
const { lines, color, geometryId, transform, seriesAreaLineStyle } = glyph;
if (!seriesAreaLineStyle.visible) {
return;
renderNonStackedAreas = (
areas: AreaGeometry[],
sharedStyle: SharedGeometryStyle,
highlightedLegendItem: LegendItem | null,
): JSX.Element[] => {
return areas.reduce<JSX.Element[]>((acc, glyph, i) => {
const { seriesAreaLineStyle, seriesAreaStyle, seriesPointStyle } = glyph;
if (seriesAreaStyle.visible) {
acc.push(this.renderArea(glyph, sharedStyle, highlightedLegendItem));
}
if (seriesAreaLineStyle.visible) {
acc.push(...this.renderAreaLines(glyph, i, sharedStyle, highlightedLegendItem));
}
if (seriesPointStyle.visible) {
const pointStyleProps = buildPointStyleProps(glyph.color, seriesPointStyle);
acc.push(...this.renderPoints(glyph.points, i, pointStyleProps, glyph.geometryId));
}
return acc;
}, []);
};
private renderArea = (
glyph: AreaGeometry,
sharedStyle: SharedGeometryStyle,
highlightedLegendItem: LegendItem | null,
): JSX.Element => {
const { area, color, transform, geometryId, seriesAreaStyle } = glyph;
const customOpacity = seriesAreaStyle ? seriesAreaStyle.opacity : undefined;
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle, customOpacity);
const key = getGeometryIdKey(geometryId, 'area-');
const areaProps = buildAreaRenderProps(transform.x, area, color, seriesAreaStyle, geometryStyle);
return <Path {...areaProps} key={key} />;
};
private renderAreaLines = (
glyph: AreaGeometry,
areaIndex: number,
sharedStyle: SharedGeometryStyle,
highlightedLegendItem: LegendItem | null,
): JSX.Element[] => {
const { lines, color, geometryId, transform, seriesAreaLineStyle } = glyph;
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle, seriesAreaLineStyle.opacity);

const geometryStyle = getGeometryStyle(
geometryId,
this.props.highlightedLegendItem,
sharedStyle,
seriesAreaLineStyle.opacity,
);
return lines.map((linePath, lineIndex) => {
const key = getGeometryIdKey(geometryId, `area-line-${areaIndex}-${lineIndex}`);
const lineProps = buildLineRenderProps(transform.x, linePath, color, seriesAreaLineStyle, geometryStyle);
return <Path {...lineProps} key={key} />;
});
};

lines.forEach((linePath, lineIndex) => {
const key = `area-${areaIndex}-line-${lineIndex}`;
const lineProps = buildLineRenderProps(transform.x, linePath, color, seriesAreaLineStyle, geometryStyle);
linesToRender.push(<Path {...lineProps} key={key} />);
});
private renderPoints = (
areaPoints: PointGeometry[],
areaIndex: number,
pointStyleProps: PointStyleProps,
geometryId: GeometryId,
): JSX.Element[] => {
return areaPoints.map((areaPoint, pointIndex) => {
const { x, y, transform } = areaPoint;
const key = getGeometryIdKey(geometryId, `area-point-${areaIndex}-${pointIndex}-`);
const pointProps = buildPointRenderProps(transform.x + x, y, pointStyleProps);
return <Circle {...pointProps} key={key} />;
});
return linesToRender;
};
}
Loading

0 comments on commit 6a4c1b1

Please sign in to comment.