Skip to content

Commit d8c8459

Browse files
authored
fix(renderer): stroke opacity (#335)
* Make stroke opacity independent from fill opacity * Changed shared opacity style to be an opacity multiplication __factor__ not a replaced value
1 parent 70b0aa1 commit d8c8459

File tree

9 files changed

+560
-278
lines changed

9 files changed

+560
-278
lines changed

.playground/playgroud.tsx

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,63 @@
11
import React from 'react';
2-
import { Axis, Chart, getAxisId, getSpecId, Position, ScaleType, Settings, BarSeries } from '../src';
2+
3+
import { Axis, Chart, getAxisId, getSpecId, niceTimeFormatter, Position, ScaleType, Settings, BarSeries } from '../src';
34
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';
5+
import { CursorEvent } from '../src/specs/settings';
6+
import { CursorUpdateListener } from '../src/chart_types/xy_chart/store/chart_state';
47

58
export class Playground extends React.Component {
9+
ref1 = React.createRef<Chart>();
10+
ref2 = React.createRef<Chart>();
11+
ref3 = React.createRef<Chart>();
12+
13+
onCursorUpdate: CursorUpdateListener = (event?: CursorEvent) => {
14+
this.ref1.current!.dispatchExternalCursorEvent(event);
15+
this.ref2.current!.dispatchExternalCursorEvent(event);
16+
this.ref3.current!.dispatchExternalCursorEvent(event);
17+
};
18+
619
render() {
720
return (
8-
<div className="chart">
9-
<Chart>
10-
<Settings showLegend={true} />
11-
<Axis id={getAxisId('y')} position={Position.Left} />
12-
<Axis id={getAxisId('x')} position={Position.Bottom} />
13-
<BarSeries
14-
id={getSpecId('bar')}
15-
yScaleType={ScaleType.Linear}
16-
xScaleType={ScaleType.Time}
17-
xAccessor={0}
18-
yAccessors={[1]}
19-
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
20-
/>
21-
</Chart>
22-
</div>
21+
<Chart>
22+
<Settings tooltip={{ type: 'vertical' }} debug={false} legendPosition={Position.Right} showLegend={true} />
23+
<Axis
24+
id={getAxisId('timestamp')}
25+
title="timestamp"
26+
position={Position.Bottom}
27+
tickFormat={niceTimeFormatter([1555819200000, 1555905600000])}
28+
/>
29+
<Axis id={getAxisId('count')} title="count" position={Position.Left} tickFormat={(d) => d.toFixed(2)} />
30+
<BarSeries
31+
id={getSpecId('dataset B')}
32+
xScaleType={ScaleType.Time}
33+
yScaleType={ScaleType.Linear}
34+
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
35+
xAccessor={0}
36+
yAccessors={[1]}
37+
stackAccessors={[0]}
38+
barSeriesStyle={{
39+
rectBorder: {
40+
strokeOpacity: 1,
41+
strokeWidth: 4,
42+
stroke: 'blue',
43+
visible: true,
44+
},
45+
rect: {
46+
opacity: 0.25,
47+
fill: 'red',
48+
},
49+
}}
50+
/>
51+
<BarSeries
52+
id={getSpecId('dataset C')}
53+
xScaleType={ScaleType.Time}
54+
yScaleType={ScaleType.Linear}
55+
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 15)}
56+
xAccessor={0}
57+
yAccessors={[1]}
58+
stackAccessors={[0]}
59+
/>
60+
</Chart>
2361
);
2462
}
2563
}

src/chart_types/xy_chart/rendering/rendering.test.ts

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { DEFAULT_GEOMETRY_STYLES } from '../../../utils/themes/theme_commons';
21
import { getSpecId } from '../../../utils/ids';
32
import {
43
BarGeometry,
@@ -8,7 +7,7 @@ import {
87
getStyleOverrides,
98
GeometryId,
109
} from './rendering';
11-
import { BarSeriesStyle } from '../../../utils/themes/theme';
10+
import { BarSeriesStyle, SharedGeometryStyle } from '../../../utils/themes/theme';
1211
import { DataSeriesDatum } from '../utils/series';
1312
import { RecursivePartial, mergePartial } from '../../../utils/commons';
1413

@@ -116,67 +115,58 @@ describe('Rendering utils', () => {
116115
},
117116
};
118117

119-
const sharedThemeStyle = DEFAULT_GEOMETRY_STYLES;
120-
const specOpacity = 0.66;
121-
122-
const defaultStyle = getGeometryStyle(geometryId, null, sharedThemeStyle);
118+
const sharedThemeStyle: SharedGeometryStyle = {
119+
default: {
120+
opacity: 1,
121+
},
122+
highlighted: {
123+
opacity: 0.5,
124+
},
125+
unhighlighted: {
126+
opacity: 0.25,
127+
},
128+
};
123129

124130
// no highlighted elements
125-
expect(defaultStyle).toEqual({ opacity: 1 });
126-
127-
const customDefaultStyle = getGeometryStyle(geometryId, null, sharedThemeStyle, specOpacity);
128-
129-
// no highlighted elements with custom spec opacity
130-
expect(customDefaultStyle).toEqual({ opacity: 0.66 });
131-
132-
const highlightedStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedThemeStyle);
131+
const defaultStyle = getGeometryStyle(geometryId, null, sharedThemeStyle);
132+
expect(defaultStyle).toBe(sharedThemeStyle.default);
133133

134134
// should equal highlighted opacity
135-
expect(highlightedStyle).toEqual({ opacity: 1 });
136-
137-
const unhighlightedStyle = getGeometryStyle(geometryId, unhighlightedLegendItem, sharedThemeStyle);
135+
const highlightedStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedThemeStyle);
136+
expect(highlightedStyle).toBe(sharedThemeStyle.highlighted);
138137

139138
// should equal unhighlighted opacity
140-
expect(unhighlightedStyle).toEqual({ opacity: 0.25 });
141-
142-
const customHighlightedStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedThemeStyle, specOpacity);
139+
const unhighlightedStyle = getGeometryStyle(geometryId, unhighlightedLegendItem, sharedThemeStyle);
140+
expect(unhighlightedStyle).toBe(sharedThemeStyle.unhighlighted);
143141

144142
// should equal custom spec highlighted opacity
145-
expect(customHighlightedStyle).toEqual({ opacity: 0.66 });
146-
147-
const customUnhighlightedStyle = getGeometryStyle(
148-
geometryId,
149-
unhighlightedLegendItem,
150-
sharedThemeStyle,
151-
specOpacity,
152-
);
143+
const customHighlightedStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedThemeStyle);
144+
expect(customHighlightedStyle).toBe(sharedThemeStyle.highlighted);
153145

154146
// unhighlighted elements remain unchanged with custom opacity
155-
expect(customUnhighlightedStyle).toEqual({ opacity: 0.25 });
147+
const customUnhighlightedStyle = getGeometryStyle(geometryId, unhighlightedLegendItem, sharedThemeStyle);
148+
expect(customUnhighlightedStyle).toBe(sharedThemeStyle.unhighlighted);
156149

157150
// has individual highlight
158-
const hasIndividualHighlight = getGeometryStyle(geometryId, null, sharedThemeStyle, undefined, {
151+
const hasIndividualHighlight = getGeometryStyle(geometryId, null, sharedThemeStyle, {
159152
hasHighlight: true,
160153
hasGeometryHover: true,
161154
});
162-
163-
expect(hasIndividualHighlight).toEqual({ opacity: 1 });
155+
expect(hasIndividualHighlight).toBe(sharedThemeStyle.highlighted);
164156

165157
// no highlight
166-
const noHighlight = getGeometryStyle(geometryId, null, sharedThemeStyle, undefined, {
158+
const noHighlight = getGeometryStyle(geometryId, null, sharedThemeStyle, {
167159
hasHighlight: false,
168160
hasGeometryHover: true,
169161
});
170-
171-
expect(noHighlight).toEqual({ opacity: 0.25 });
162+
expect(noHighlight).toBe(sharedThemeStyle.unhighlighted);
172163

173164
// no geometry hover
174-
const noHover = getGeometryStyle(geometryId, null, sharedThemeStyle, undefined, {
165+
const noHover = getGeometryStyle(geometryId, null, sharedThemeStyle, {
175166
hasHighlight: true,
176167
hasGeometryHover: false,
177168
});
178-
179-
expect(noHover).toEqual({ opacity: 1 });
169+
expect(noHover).toBe(sharedThemeStyle.highlighted);
180170
});
181171

182172
describe('getStyleOverrides', () => {

src/chart_types/xy_chart/rendering/rendering.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export interface GeometryValue {
3333

3434
/** Shared style properties for varies geometries */
3535
export interface GeometryStyle {
36+
/**
37+
* Opacity multiplier
38+
*
39+
* if set to `0.5` all given opacities will be halfed
40+
*/
3641
opacity: number;
3742
}
3843

@@ -482,34 +487,26 @@ export function renderArea(
482487
export function getGeometryStyle(
483488
geometryId: GeometryId,
484489
highlightedLegendItem: LegendItem | null,
485-
sharedThemeStyle: SharedGeometryStyle,
486-
specOpacity?: number,
490+
sharedGeometryStyle: SharedGeometryStyle,
487491
individualHighlight?: { [key: string]: boolean },
488492
): GeometryStyle {
489-
const sharedStyle =
490-
specOpacity == null
491-
? sharedThemeStyle
492-
: {
493-
...sharedThemeStyle,
494-
highlighted: { opacity: specOpacity },
495-
default: { opacity: specOpacity },
496-
};
493+
const { default: defaultStyles, highlighted, unhighlighted } = sharedGeometryStyle;
497494

498495
if (highlightedLegendItem != null) {
499496
const isPartOfHighlightedSeries = belongsToDataSeries(geometryId, highlightedLegendItem.value);
500497

501-
return isPartOfHighlightedSeries ? sharedStyle.highlighted : sharedStyle.unhighlighted;
498+
return isPartOfHighlightedSeries ? highlighted : unhighlighted;
502499
}
503500

504501
if (individualHighlight) {
505502
const { hasHighlight, hasGeometryHover } = individualHighlight;
506503
if (!hasGeometryHover) {
507-
return sharedStyle.highlighted;
504+
return highlighted;
508505
}
509-
return hasHighlight ? sharedStyle.highlighted : sharedStyle.unhighlighted;
506+
return hasHighlight ? highlighted : unhighlighted;
510507
}
511508

512-
return sharedStyle.default;
509+
return defaultStyles;
513510
}
514511

515512
export function isPointOnGeometry(

src/components/react_canvas/area_geometries.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,7 @@ export class AreaGeometries extends React.PureComponent<AreaGeometriesDataProps,
5757
acc.push(...this.renderAreaLines(glyph, i, sharedStyle, highlightedLegendItem));
5858
}
5959
if (seriesPointStyle.visible) {
60-
const customOpacity = seriesPointStyle ? seriesPointStyle.opacity : undefined;
61-
const geometryStyle = getGeometryStyle(
62-
geometryId,
63-
this.props.highlightedLegendItem,
64-
sharedStyle,
65-
customOpacity,
66-
);
60+
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle);
6761
const pointStyleProps = buildPointStyleProps(glyph.color, seriesPointStyle, geometryStyle);
6862
acc.push(...this.renderPoints(glyph.points, i, pointStyleProps, glyph.geometryId));
6963
}
@@ -76,8 +70,7 @@ export class AreaGeometries extends React.PureComponent<AreaGeometriesDataProps,
7670
highlightedLegendItem: LegendItem | null,
7771
): JSX.Element => {
7872
const { area, color, transform, geometryId, seriesAreaStyle } = glyph;
79-
const customOpacity = seriesAreaStyle ? seriesAreaStyle.opacity : undefined;
80-
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle, customOpacity);
73+
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle);
8174
const key = getGeometryIdKey(geometryId, 'area-');
8275
const areaProps = buildAreaRenderProps(transform.x, area, color, seriesAreaStyle, geometryStyle);
8376
return <Path {...areaProps} key={key} />;
@@ -89,7 +82,7 @@ export class AreaGeometries extends React.PureComponent<AreaGeometriesDataProps,
8982
highlightedLegendItem: LegendItem | null,
9083
): JSX.Element[] => {
9184
const { lines, color, geometryId, transform, seriesAreaLineStyle } = glyph;
92-
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle, seriesAreaLineStyle.opacity);
85+
const geometryStyle = getGeometryStyle(geometryId, highlightedLegendItem, sharedStyle);
9386

9487
return lines.map((linePath, lineIndex) => {
9588
const key = getGeometryIdKey(geometryId, `area-line-${areaIndex}-${lineIndex}`);

src/components/react_canvas/bar_geometries.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { animated, Spring } from 'react-spring/renderprops-konva.cjs';
55
import { LegendItem } from '../../chart_types/xy_chart/legend/legend';
66
import { BarGeometry, getGeometryStyle } from '../../chart_types/xy_chart/rendering/rendering';
77
import { SharedGeometryStyle } from '../../utils/themes/theme';
8-
import { buildBarRenderProps } from './utils/rendering_props_utils';
8+
import { buildBarRenderProps, buildBarBorderRenderProps } from './utils/rendering_props_utils';
99

1010
interface BarGeometriesDataProps {
1111
animated?: boolean;
@@ -55,7 +55,6 @@ export class BarGeometries extends React.PureComponent<BarGeometriesDataProps, B
5555
bar.geometryId,
5656
this.props.highlightedLegendItem,
5757
sharedStyle,
58-
seriesStyle.rect.opacity,
5958
individualHighlight,
6059
);
6160
const key = `bar-${index}`;
@@ -65,6 +64,15 @@ export class BarGeometries extends React.PureComponent<BarGeometriesDataProps, B
6564
<Group key={index}>
6665
<Spring native from={{ y: y + height, height: 0 }} to={{ y, height }}>
6766
{(props: { y: number; height: number }) => {
67+
const barPropsBorder = buildBarBorderRenderProps(
68+
x,
69+
props.y,
70+
width,
71+
props.height,
72+
seriesStyle.rect,
73+
seriesStyle.rectBorder,
74+
geometryStyle,
75+
);
6876
const barProps = buildBarRenderProps(
6977
x,
7078
props.y,
@@ -76,12 +84,26 @@ export class BarGeometries extends React.PureComponent<BarGeometriesDataProps, B
7684
geometryStyle,
7785
);
7886

79-
return <animated.Rect {...barProps} key={key} />;
87+
return (
88+
<React.Fragment key={key}>
89+
<animated.Rect {...barProps} />
90+
{barPropsBorder && <animated.Rect {...barPropsBorder} />}
91+
</React.Fragment>
92+
);
8093
}}
8194
</Spring>
8295
</Group>
8396
);
8497
} else {
98+
const barPropsBorder = buildBarBorderRenderProps(
99+
x,
100+
y,
101+
width,
102+
height,
103+
seriesStyle.rect,
104+
seriesStyle.rectBorder,
105+
geometryStyle,
106+
);
85107
const barProps = buildBarRenderProps(
86108
x,
87109
y,
@@ -92,9 +114,11 @@ export class BarGeometries extends React.PureComponent<BarGeometriesDataProps, B
92114
seriesStyle.rectBorder,
93115
geometryStyle,
94116
);
117+
95118
return (
96-
<React.Fragment key={index}>
97-
<Rect {...barProps} key={key} />
119+
<React.Fragment key={key}>
120+
<Rect {...barProps} />
121+
{barPropsBorder && <Rect {...barPropsBorder} />}
98122
</React.Fragment>
99123
);
100124
}

src/components/react_canvas/line_geometries.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,14 @@ export class LineGeometries extends React.PureComponent<LineGeometriesDataProps,
8080

8181
getLineToRender(glyph: LineGeometry, sharedStyle: SharedGeometryStyle, key: string) {
8282
const { line, color, transform, geometryId, seriesLineStyle } = glyph;
83-
const customOpacity = seriesLineStyle ? seriesLineStyle.opacity : undefined;
84-
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle, customOpacity);
83+
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle);
8584
const lineProps = buildLineRenderProps(transform.x, line, color, seriesLineStyle, geometryStyle);
8685
return <Path {...lineProps} key={key} />;
8786
}
8887

8988
getPointToRender(glyph: LineGeometry, sharedStyle: SharedGeometryStyle, key: string) {
9089
const { points, color, geometryId, seriesPointStyle } = glyph;
91-
const customOpacity = seriesPointStyle ? seriesPointStyle.opacity : undefined;
92-
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle, customOpacity);
90+
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle);
9391
const pointStyleProps = buildPointStyleProps(color, seriesPointStyle, geometryStyle);
9492
return this.renderPoints(points, key, pointStyleProps);
9593
}

0 commit comments

Comments
 (0)