Files
2024-02-15 17:01:50 +00:00

189 lines
5.1 KiB
TypeScript

import {Component, createRef} from 'react';
import type {Theme} from '@emotion/react';
import {withTheme} from '@emotion/react';
import type {PieSeriesOption} from 'echarts';
import type {ReactEchartsRef, Series} from 'sentry/types/echarts';
import Legend from './components/legend';
import PieSeries from './series/pieSeries';
import type {BaseChartProps} from './baseChart';
import BaseChart from './baseChart';
export interface PieChartSeries
extends Series,
Omit<PieSeriesOption, 'id' | 'color' | 'data'> {}
interface Props extends Omit<BaseChartProps, 'series'> {
series: PieChartSeries[];
theme: Theme;
selectOnRender?: boolean;
}
class PieChart extends Component<Props> {
componentDidMount() {
const {selectOnRender} = this.props;
if (!selectOnRender) {
return;
}
// Timeout is because we need to wait for rendering animation to complete
// And I haven't found a callback for this
this.highlightTimeout = window.setTimeout(() => this.highlight(0), 1000);
}
componentWillUnmount() {
window.clearTimeout(this.highlightTimeout);
}
highlightTimeout: number | undefined = undefined;
isInitialSelected = true;
selected = 0;
chart = createRef<ReactEchartsRef>();
// Select a series to highlight (e.g. shows details of series)
// This is the same event as when you hover over a series in the chart
highlight = dataIndex => {
if (!this.chart.current) {
return;
}
this.chart.current.getEchartsInstance().dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex,
});
};
// Opposite of `highlight`
downplay = dataIndex => {
if (!this.chart.current) {
return;
}
this.chart.current.getEchartsInstance().dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex,
});
};
// echarts Legend does not have access to percentages (but tooltip does :/)
getSeriesPercentages = (series: PieChartSeries) => {
const total = series.data.reduce((acc, {value}) => acc + value, 0);
return series.data
.map(({name, value}) => [name, Math.round((value / total) * 10000) / 100])
.reduce(
(acc, [name, value]) => ({
...acc,
[name]: value,
}),
{}
);
};
render() {
const {series, theme, ...props} = this.props;
if (!series || !series.length) {
return null;
}
if (series.length > 1) {
// eslint-disable-next-line no-console
console.warn('PieChart only uses the first series!');
}
// Note, we only take the first series unit!
const [firstSeries] = series;
const seriesPercentages = this.getSeriesPercentages(firstSeries);
return (
<BaseChart
ref={this.chart}
colors={
firstSeries?.data && [...theme.charts.getColorPalette(firstSeries.data.length)]
}
// when legend highlights it does NOT pass dataIndex :(
onHighlight={({name}) => {
if (
!this.isInitialSelected ||
!name ||
firstSeries.data[this.selected].name === name
) {
return;
}
// Unhighlight if not initial "highlight" event and
// if name exists (i.e. not dispatched from cDM) and
// highlighted series name is different than the initially selected series name
this.downplay(this.selected);
this.isInitialSelected = false;
}}
onMouseOver={({dataIndex}) => {
if (!this.isInitialSelected) {
return;
}
if (dataIndex === this.selected) {
return;
}
this.downplay(this.selected);
this.isInitialSelected = false;
}}
{...props}
legend={Legend({
theme,
orient: 'vertical',
align: 'left',
show: true,
left: 10,
top: 10,
bottom: 10,
formatter: name =>
`${name} ${
typeof seriesPercentages[name] !== 'undefined'
? `(${seriesPercentages[name]}%)`
: ''
}`,
})}
tooltip={{
formatter: data => {
return [
'<div class="tooltip-series">',
`<div><span class="tooltip-label">${data.marker}<strong>${data.name}</strong></span> ${data.percent}%</div>`,
'</div>',
`<div class="tooltip-footer">${data.value}</div>`,
'</div>',
'<div class="tooltip-arrow"></div>',
].join('');
},
}}
series={[
PieSeries({
name: firstSeries.seriesName,
data: firstSeries.data,
avoidLabelOverlap: false,
label: {
formatter: ({name, percent}) => `${name}\n${percent}%`,
show: false,
position: 'center',
fontSize: '18',
},
emphasis: {
label: {
show: true,
},
},
labelLine: {
show: false,
},
}),
]}
xAxis={null}
yAxis={null}
/>
);
}
}
export default withTheme(PieChart);