Skip to content

Commit c56a252

Browse files
feat(legend/series): add hover interaction on legend items (#31)
addresses #24
1 parent c94dd22 commit c56a252

File tree

13 files changed

+477
-33
lines changed

13 files changed

+477
-33
lines changed

src/components/_legend.scss

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ $euiChartLegendMaxHeight: $euiSize * 4 + $euiSize;
4848
top: 0;
4949
bottom: 0;
5050
left: 0;
51-
width: $euiChartLegendMaxWidth;
51+
width: $euiChartLegendMaxWidth;
5252
order: 1;
5353
.euiChartLegend__listItem {
5454
min-width: 100%;
@@ -58,7 +58,7 @@ $euiChartLegendMaxHeight: $euiSize * 4 + $euiSize;
5858
top: 0;
5959
bottom: 0;
6060
right: 0;
61-
width: $euiChartLegendMaxWidth;
61+
width: $euiChartLegendMaxWidth;
6262
.euiChartLegend__listItem {
6363
min-width: 100%;
6464
}
@@ -93,4 +93,9 @@ $euiChartLegendMaxHeight: $euiSize * 4 + $euiSize;
9393
.euiChartLegendListItem__title {
9494
width: $euiChartLegendMaxWidth - 4 * $euiSize;
9595
max-width: $euiChartLegendMaxWidth - 4 * $euiSize;
96+
}
97+
.euiChartLegendList__item {
98+
&:hover {
99+
text-decoration: underline;
100+
}
96101
}

src/components/legend.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
EuiFlexItem,
55
EuiIcon,
66
EuiText,
7-
EuiToolTip,
87
} from '@elastic/eui';
98
import classNames from 'classnames';
109
import { inject, observer } from 'mobx-react';
@@ -86,8 +85,15 @@ class LegendComponent extends React.Component<ReactiveChartProps> {
8685
responsive={false}
8786
>
8887
{legendItems.map((item, index) => {
88+
const legendItemProps = {
89+
key: index,
90+
className: 'euiChartLegendList__item',
91+
onMouseEnter: this.onLegendItemMouseover(index),
92+
onMouseLeave: this.onLegendItemMouseout,
93+
};
94+
8995
return (
90-
<EuiFlexItem key={index} className="euiChartLegendList__item">
96+
<EuiFlexItem {...legendItemProps}>
9197
<LegendElement color={item.color} label={item.label} />
9298
</EuiFlexItem>
9399
);
@@ -97,6 +103,14 @@ class LegendComponent extends React.Component<ReactiveChartProps> {
97103
</div>
98104
);
99105
}
106+
107+
private onLegendItemMouseover = (legendItemIndex: number) => () => {
108+
this.props.chartStore!.onLegendItemOver(legendItemIndex);
109+
}
110+
111+
private onLegendItemMouseout = () => {
112+
this.props.chartStore!.onLegendItemOut();
113+
}
100114
}
101115
function LegendElement({ color, label }: Partial<LegendItem>) {
102116
return (
@@ -105,13 +119,11 @@ function LegendElement({ color, label }: Partial<LegendItem>) {
105119
<EuiIcon type="dot" color={color} />
106120
</EuiFlexItem>
107121
<EuiFlexItem grow={false}>
108-
<EuiToolTip position="right" content={<EuiText size="xs">{label}</EuiText>}>
109-
<EuiFlexItem grow={true} className="euiChartLegendListItem__title">
110-
<EuiText size="xs" className="eui-textTruncate">
111-
{label}
112-
</EuiText>
113-
</EuiFlexItem>
114-
</EuiToolTip>
122+
<EuiFlexItem grow={true} className="euiChartLegendListItem__title" title={label}>
123+
<EuiText size="xs" className="eui-textTruncate">
124+
{label}
125+
</EuiText>
126+
</EuiFlexItem>
115127
</EuiFlexItem>
116128
</EuiFlexGroup>
117129
);

src/components/react_canvas/area_geometries.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { IAction } from 'mobx';
33
import React from 'react';
44
import { Circle, Group, Path } from 'react-konva';
55
import { animated, Spring } from 'react-spring/konva';
6-
import { AreaGeometry, GeometryValue, PointGeometry } from '../../lib/series/rendering';
6+
import { LegendItem } from '../../lib/series/legend';
7+
import { AreaGeometry, GeometryValue, getGeometryStyle, PointGeometry } from '../../lib/series/rendering';
78
import { AreaSeriesStyle } from '../../lib/themes/theme';
89
import { ElementClickListener, TooltipData } from '../../state/chart_state';
910

@@ -15,14 +16,15 @@ interface AreaGeometriesDataProps {
1516
onElementClick?: ElementClickListener;
1617
onElementOver: ((tooltip: TooltipData) => void) & IAction;
1718
onElementOut: (() => void) & IAction;
19+
highlightedLegendItem: LegendItem | null;
1820
}
1921
interface AreaGeometriesDataState {
2022
overPoint?: PointGeometry;
2123
}
2224
export class AreaGeometries extends React.PureComponent<
2325
AreaGeometriesDataProps,
2426
AreaGeometriesDataState
25-
> {
27+
> {
2628
static defaultProps: Partial<AreaGeometriesDataProps> = {
2729
animated: false,
2830
num: 1,
@@ -131,10 +133,14 @@ export class AreaGeometries extends React.PureComponent<
131133
);
132134
});
133135
}
136+
134137
private renderAreaGeoms = (): JSX.Element[] => {
135138
const { areas } = this.props;
136139
return areas.map((glyph, i) => {
137-
const { area, color, transform } = glyph;
140+
const { area, color, transform, geometryId } = glyph;
141+
142+
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem);
143+
138144
if (this.props.animated) {
139145
return (
140146
<Group key={`area-group-${i}`} x={transform.x}>
@@ -145,8 +151,9 @@ export class AreaGeometries extends React.PureComponent<
145151
data={props.area}
146152
fill={color}
147153
listening={false}
148-
// areaCap="round"
149-
// areaJoin="round"
154+
{...geometryStyle}
155+
// areaCap="round"
156+
// areaJoin="round"
150157
/>
151158
)}
152159
</Spring>
@@ -159,8 +166,9 @@ export class AreaGeometries extends React.PureComponent<
159166
data={area}
160167
fill={color}
161168
listening={false}
162-
// areaCap="round"
163-
// areaJoin="round"
169+
{...geometryStyle}
170+
// areaCap="round"
171+
// areaJoin="round"
164172
/>
165173
);
166174
}

src/components/react_canvas/bar_geometries.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { IAction } from 'mobx';
33
import React from 'react';
44
import { Group, Rect } from 'react-konva';
55
import { animated, Spring } from 'react-spring/konva';
6-
import { BarGeometry, GeometryValue } from '../../lib/series/rendering';
6+
import { LegendItem } from '../../lib/series/legend';
7+
import { BarGeometry, GeometryValue, getGeometryStyle } from '../../lib/series/rendering';
78
import { ElementClickListener, TooltipData } from '../../state/chart_state';
89

910
interface BarGeometriesDataProps {
@@ -12,14 +13,15 @@ interface BarGeometriesDataProps {
1213
onElementClick?: ElementClickListener;
1314
onElementOver: ((tooltip: TooltipData) => void) & IAction;
1415
onElementOut: (() => void) & IAction;
16+
highlightedLegendItem: LegendItem | null;
1517
}
1618
interface BarGeometriesDataState {
1719
overBar?: BarGeometry;
1820
}
1921
export class BarGeometries extends React.PureComponent<
2022
BarGeometriesDataProps,
2123
BarGeometriesDataState
22-
> {
24+
> {
2325
static defaultProps: Partial<BarGeometriesDataProps> = {
2426
animated: false,
2527
};
@@ -70,14 +72,22 @@ export class BarGeometries extends React.PureComponent<
7072
});
7173
onElementOut();
7274
}
75+
7376
private renderBarGeoms = (bars: BarGeometry[]): JSX.Element[] => {
7477
const { overBar } = this.state;
7578
return bars.map((bar, i) => {
7679
const { x, y, width, height, color, value } = bar;
77-
let opacity = 1;
78-
if (overBar && overBar !== bar) {
79-
opacity = 0.6;
80-
}
80+
81+
// Properties to determine if we need to highlight individual bars depending on hover state
82+
const hasGeometryHover = overBar != null;
83+
const hasHighlight = overBar === bar;
84+
const individualHighlight = {
85+
hasGeometryHover,
86+
hasHighlight,
87+
};
88+
89+
const geometryStyle = getGeometryStyle(bar.geometryId, this.props.highlightedLegendItem, individualHighlight);
90+
8191
if (this.props.animated) {
8292
return (
8393
<Group key={i}>
@@ -91,11 +101,11 @@ export class BarGeometries extends React.PureComponent<
91101
height={props.height}
92102
fill={color}
93103
strokeWidth={0}
94-
opacity={opacity}
95104
perfectDrawEnabled={true}
96105
onMouseOver={this.onOverBar(bar)}
97106
onMouseLeave={this.onOutBar}
98107
onClick={this.onElementClick(value)}
108+
{...geometryStyle}
99109
/>
100110
)}
101111
</Spring>
@@ -111,11 +121,11 @@ export class BarGeometries extends React.PureComponent<
111121
height={height}
112122
fill={color}
113123
strokeWidth={0}
114-
opacity={opacity}
115124
perfectDrawEnabled={false}
116125
onMouseOver={this.onOverBar(bar)}
117126
onMouseLeave={this.onOutBar}
118127
onClick={this.onElementClick(bar.value)}
128+
{...geometryStyle}
119129
/>
120130
);
121131
}

src/components/react_canvas/line_geometries.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { IAction } from 'mobx';
33
import React from 'react';
44
import { Circle, Group, Path } from 'react-konva';
55
import { animated, Spring } from 'react-spring/konva';
6-
import { GeometryValue, LineGeometry, PointGeometry } from '../../lib/series/rendering';
6+
import { LegendItem } from '../../lib/series/legend';
7+
import { GeometryValue, getGeometryStyle, LineGeometry, PointGeometry } from '../../lib/series/rendering';
78
import { LineSeriesStyle } from '../../lib/themes/theme';
89
import { ElementClickListener, TooltipData } from '../../state/chart_state';
910

@@ -14,14 +15,15 @@ interface LineGeometriesDataProps {
1415
onElementClick?: ElementClickListener;
1516
onElementOver: ((tooltip: TooltipData) => void) & IAction;
1617
onElementOut: (() => void) & IAction;
18+
highlightedLegendItem: LegendItem | null;
1719
}
1820
interface LineGeometriesDataState {
1921
overPoint?: PointGeometry;
2022
}
2123
export class LineGeometries extends React.PureComponent<
2224
LineGeometriesDataProps,
2325
LineGeometriesDataState
24-
> {
26+
> {
2527
static defaultProps: Partial<LineGeometriesDataProps> = {
2628
animated: false,
2729
};
@@ -129,10 +131,14 @@ export class LineGeometries extends React.PureComponent<
129131
);
130132
});
131133
}
134+
132135
private renderLineGeoms = (): JSX.Element[] => {
133136
const { style, lines } = this.props;
134137
return lines.map((glyph, i) => {
135-
const { line, color, transform } = glyph;
138+
const { line, color, transform, geometryId } = glyph;
139+
140+
const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem);
141+
136142
if (this.props.animated) {
137143
return (
138144
<Group key={i} x={transform.x}>
@@ -146,6 +152,7 @@ export class LineGeometries extends React.PureComponent<
146152
listening={false}
147153
lineCap="round"
148154
lineJoin="round"
155+
{...geometryStyle}
149156
/>
150157
)}
151158
</Spring>
@@ -161,6 +168,7 @@ export class LineGeometries extends React.PureComponent<
161168
listening={false}
162169
lineCap="round"
163170
lineJoin="round"
171+
{...geometryStyle}
164172
/>
165173
);
166174
}

src/components/react_canvas/reactive_chart.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,16 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
7171
if (!geometries) {
7272
return;
7373
}
74+
const highlightedLegendItem = this.getHighlightedLegendItem();
75+
7476
return (
7577
<BarGeometries
7678
animated={canDataBeAnimated}
7779
bars={geometries.bars}
7880
onElementOver={onOverElement}
7981
onElementOut={onOutElement}
8082
onElementClick={onElementClickListener}
83+
highlightedLegendItem={highlightedLegendItem}
8184
/>
8285
);
8386
}
@@ -93,6 +96,9 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
9396
if (!geometries) {
9497
return;
9598
}
99+
100+
const highlightedLegendItem = this.getHighlightedLegendItem();
101+
96102
return (
97103
<LineGeometries
98104
animated={canDataBeAnimated}
@@ -101,6 +107,7 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
101107
onElementOver={onOverElement}
102108
onElementOut={onOutElement}
103109
onElementClick={onElementClickListener}
110+
highlightedLegendItem={highlightedLegendItem}
104111
/>
105112
);
106113
}
@@ -116,6 +123,9 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
116123
if (!geometries) {
117124
return;
118125
}
126+
127+
const highlightedLegendItem = this.getHighlightedLegendItem();
128+
119129
return (
120130
<AreaGeometries
121131
animated={canDataBeAnimated}
@@ -124,6 +134,7 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
124134
onElementOver={onOverElement}
125135
onElementOut={onOutElement}
126136
onElementClick={onElementClickListener}
137+
highlightedLegendItem={highlightedLegendItem}
127138
/>
128139
);
129140
}
@@ -353,6 +364,10 @@ class Chart extends React.Component<ReactiveChartProps, ReactiveChartState> {
353364
/>
354365
);
355366
}
367+
368+
private getHighlightedLegendItem = () => {
369+
return this.props.chartStore!.highlightedLegendItem.get();
370+
}
356371
}
357372

358373
export const ReactiveChart = inject('chartStore')(observer(Chart));

0 commit comments

Comments
 (0)