Merge branch 'piechart-react' of https://github.com/CorpGlory/grafana into CorpGlory-piechart-react

This commit is contained in:
Torkel Ödegaard
2019-03-20 19:01:37 +01:00
17 changed files with 682 additions and 2 deletions

View File

@@ -21,6 +21,7 @@
"@torkelo/react-select": "2.1.1",
"@types/react-color": "^2.14.0",
"classnames": "^2.2.5",
"d3": "^5.7.0",
"jquery": "^3.2.1",
"lodash": "^4.17.10",
"moment": "^2.22.2",
@@ -43,6 +44,7 @@
"@storybook/addon-knobs": "^4.1.7",
"@storybook/react": "^4.1.4",
"@types/classnames": "^2.2.6",
"@types/d3": "^5.7.0",
"@types/jest": "^23.3.2",
"@types/jquery": "^1.10.35",
"@types/lodash": "^4.14.119",

View File

@@ -0,0 +1,42 @@
import { storiesOf } from '@storybook/react';
import { number, text, object } from '@storybook/addon-knobs';
import { PieChart, PieChartType } from './PieChart';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const getKnobs = () => {
return {
datapoints: object('datapoints', [
{
value: 100,
name: '100',
color: '#7EB26D',
},
{
value: 200,
name: '200',
color: '#6ED0E0',
},
]),
pieType: text('pieType', PieChartType.PIE),
strokeWidth: number('strokeWidth', 1),
unit: text('unit', 'ms'),
};
};
const PieChartStories = storiesOf('UI/PieChart/PieChart', module);
PieChartStories.addDecorator(withCenteredStory);
PieChartStories.add('Pie type: pie', () => {
const { datapoints, pieType, strokeWidth, unit } = getKnobs();
return renderComponentWithTheme(PieChart, {
width: 200,
height: 400,
datapoints,
pieType,
strokeWidth,
unit,
});
});

View File

@@ -0,0 +1,149 @@
import React, { PureComponent } from 'react';
import { select, pie, arc, event } from 'd3';
import { sum } from 'lodash';
import { GrafanaThemeType } from '../../types';
import { Themeable } from '../../index';
export enum PieChartType {
PIE = 'pie',
DONUT = 'donut',
}
export interface PieChartDataPoint {
value: number;
name: string;
color: string;
}
export interface Props extends Themeable {
height: number;
width: number;
datapoints: PieChartDataPoint[];
unit: string;
pieType: PieChartType;
strokeWidth: number;
}
export class PieChart extends PureComponent<Props> {
containerElement: any;
svgElement: any;
tooltipElement: any;
tooltipValueElement: any;
static defaultProps = {
pieType: 'pie',
format: 'short',
stat: 'current',
strokeWidth: 1,
theme: GrafanaThemeType.Dark,
};
componentDidMount() {
this.draw();
}
componentDidUpdate() {
this.draw();
}
draw() {
const { datapoints, pieType, strokeWidth } = this.props;
if (datapoints.length === 0) {
return;
}
const data = datapoints.map(datapoint => datapoint.value);
const names = datapoints.map(datapoint => datapoint.name);
const colors = datapoints.map(datapoint => datapoint.color);
const total = sum(data) || 1;
const percents = data.map((item: number) => (item / total) * 100);
const width = this.containerElement.offsetWidth;
const height = this.containerElement.offsetHeight;
const radius = Math.min(width, height) / 2;
const outerRadius = radius - radius / 10;
const innerRadius = pieType === PieChartType.PIE ? 0 : radius - radius / 3;
const svg = select(this.svgElement)
.html('')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
const pieChart = pie();
const customArc = arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.padAngle(0);
svg
.selectAll('path')
.data(pieChart(data))
.enter()
.append('path')
.attr('d', customArc as any)
.attr('fill', (d: any, idx: number) => colors[idx])
.style('fill-opacity', 0.15)
.style('stroke', (d: any, idx: number) => colors[idx])
.style('stroke-width', `${strokeWidth}px`)
.on('mouseover', (d: any, idx: any) => {
select(this.tooltipElement).style('opacity', 1);
select(this.tooltipValueElement).text(`${names[idx]} (${percents[idx].toFixed(2)}%)`);
})
.on('mousemove', () => {
select(this.tooltipElement)
.style('top', `${event.pageY - height / 2}px`)
.style('left', `${event.pageX}px`);
})
.on('mouseout', () => {
select(this.tooltipElement).style('opacity', 0);
});
}
render() {
const { height, width, datapoints } = this.props;
if (datapoints.length > 0) {
return (
<div className="piechart-panel">
<div
ref={element => (this.containerElement = element)}
className="piechart-container"
style={{
height: `${height * 0.9}px`,
width: `${Math.min(width, height * 1.3)}px`,
}}
>
<svg ref={element => (this.svgElement = element)} />
</div>
<div className="piechart-tooltip" ref={element => (this.tooltipElement = element)}>
<div className="piechart-tooltip-time">
<div
id="tooltip-value"
className="piechart-tooltip-value"
ref={element => (this.tooltipValueElement = element)}
/>
</div>
</div>
</div>
);
} else {
return (
<div className="piechart-panel">
<div className="datapoints-warning">
<span className="small">No data points</span>
</div>
</div>
);
}
}
}
export default PieChart;

View File

@@ -25,6 +25,7 @@ export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
export { Switch } from './Switch/Switch';
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { PieChart, PieChartDataPoint, PieChartType } from './PieChart/PieChart';
export { UnitPicker } from './UnitPicker/UnitPicker';
export { Input, InputStatus } from './Input/Input';