初始化 antd-pro
This commit is contained in:
21
admin-web/src/components/Charts/Pie/index.d.ts
vendored
Normal file
21
admin-web/src/components/Charts/Pie/index.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
export interface IPieProps {
|
||||
animate?: boolean;
|
||||
color?: string;
|
||||
colors?: string[];
|
||||
height: number;
|
||||
hasLegend?: boolean;
|
||||
padding?: [number, number, number, number];
|
||||
percent?: number;
|
||||
data?: Array<{
|
||||
x: string | string;
|
||||
y: number;
|
||||
}>;
|
||||
total?: React.ReactNode | number | (() => React.ReactNode | number);
|
||||
title?: React.ReactNode;
|
||||
tooltip?: boolean;
|
||||
valueFormat?: (value: string) => string | React.ReactNode;
|
||||
subTitle?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class Pie extends React.Component<IPieProps, any> {}
|
||||
271
admin-web/src/components/Charts/Pie/index.js
Normal file
271
admin-web/src/components/Charts/Pie/index.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
|
||||
import { DataView } from '@antv/data-set';
|
||||
import { Divider } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import ReactFitText from 'react-fittext';
|
||||
import Debounce from 'lodash-decorators/debounce';
|
||||
import Bind from 'lodash-decorators/bind';
|
||||
import autoHeight from '../autoHeight';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
/* eslint react/no-danger:0 */
|
||||
@autoHeight()
|
||||
class Pie extends Component {
|
||||
state = {
|
||||
legendData: [],
|
||||
legendBlock: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
() => {
|
||||
this.requestRef = requestAnimationFrame(() => this.resize());
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(preProps) {
|
||||
const { data } = this.props;
|
||||
if (data !== preProps.data) {
|
||||
// because of charts data create when rendered
|
||||
// so there is a trick for get rendered time
|
||||
this.getLegendData();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.cancelAnimationFrame(this.requestRef);
|
||||
window.removeEventListener('resize', this.resize);
|
||||
this.resize.cancel();
|
||||
}
|
||||
|
||||
getG2Instance = chart => {
|
||||
this.chart = chart;
|
||||
requestAnimationFrame(() => {
|
||||
this.getLegendData();
|
||||
this.resize();
|
||||
});
|
||||
};
|
||||
|
||||
// for custom lengend view
|
||||
getLegendData = () => {
|
||||
if (!this.chart) return;
|
||||
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
|
||||
if (!geom) return;
|
||||
const items = geom.get('dataArray') || []; // 获取图形对应的
|
||||
|
||||
const legendData = items.map(item => {
|
||||
/* eslint no-underscore-dangle:0 */
|
||||
const origin = item[0]._origin;
|
||||
origin.color = item[0].color;
|
||||
origin.checked = true;
|
||||
return origin;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
legendData,
|
||||
});
|
||||
};
|
||||
|
||||
handleRoot = n => {
|
||||
this.root = n;
|
||||
};
|
||||
|
||||
handleLegendClick = (item, i) => {
|
||||
const newItem = item;
|
||||
newItem.checked = !newItem.checked;
|
||||
|
||||
const { legendData } = this.state;
|
||||
legendData[i] = newItem;
|
||||
|
||||
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
legendData,
|
||||
});
|
||||
};
|
||||
|
||||
// for window resize auto responsive legend
|
||||
@Bind()
|
||||
@Debounce(300)
|
||||
resize() {
|
||||
const { hasLegend } = this.props;
|
||||
const { legendBlock } = this.state;
|
||||
if (!hasLegend || !this.root) {
|
||||
window.removeEventListener('resize', this.resize);
|
||||
return;
|
||||
}
|
||||
if (this.root.parentNode.clientWidth <= 380) {
|
||||
if (!legendBlock) {
|
||||
this.setState({
|
||||
legendBlock: true,
|
||||
});
|
||||
}
|
||||
} else if (legendBlock) {
|
||||
this.setState({
|
||||
legendBlock: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
valueFormat,
|
||||
subTitle,
|
||||
total,
|
||||
hasLegend = false,
|
||||
className,
|
||||
style,
|
||||
height,
|
||||
forceFit = true,
|
||||
percent,
|
||||
color,
|
||||
inner = 0.75,
|
||||
animate = true,
|
||||
colors,
|
||||
lineWidth = 1,
|
||||
} = this.props;
|
||||
|
||||
const { legendData, legendBlock } = this.state;
|
||||
const pieClassName = classNames(styles.pie, className, {
|
||||
[styles.hasLegend]: !!hasLegend,
|
||||
[styles.legendBlock]: legendBlock,
|
||||
});
|
||||
|
||||
const {
|
||||
data: propsData,
|
||||
selected: propsSelected = true,
|
||||
tooltip: propsTooltip = true,
|
||||
} = this.props;
|
||||
|
||||
let data = propsData || [];
|
||||
let selected = propsSelected;
|
||||
let tooltip = propsTooltip;
|
||||
|
||||
const defaultColors = colors;
|
||||
data = data || [];
|
||||
selected = selected || true;
|
||||
tooltip = tooltip || true;
|
||||
let formatColor;
|
||||
|
||||
const scale = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
range: [0, 1],
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (percent || percent === 0) {
|
||||
selected = false;
|
||||
tooltip = false;
|
||||
formatColor = value => {
|
||||
if (value === '占比') {
|
||||
return color || 'rgba(24, 144, 255, 0.85)';
|
||||
}
|
||||
return '#F0F2F5';
|
||||
};
|
||||
|
||||
data = [
|
||||
{
|
||||
x: '占比',
|
||||
y: parseFloat(percent),
|
||||
},
|
||||
{
|
||||
x: '反比',
|
||||
y: 100 - parseFloat(percent),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const tooltipFormat = [
|
||||
'x*percent',
|
||||
(x, p) => ({
|
||||
name: x,
|
||||
value: `${(p * 100).toFixed(2)}%`,
|
||||
}),
|
||||
];
|
||||
|
||||
const padding = [12, 0, 12, 0];
|
||||
|
||||
const dv = new DataView();
|
||||
dv.source(data).transform({
|
||||
type: 'percent',
|
||||
field: 'y',
|
||||
dimension: 'x',
|
||||
as: 'percent',
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={this.handleRoot} className={pieClassName} style={style}>
|
||||
<ReactFitText maxFontSize={25}>
|
||||
<div className={styles.chart}>
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={height}
|
||||
forceFit={forceFit}
|
||||
data={dv}
|
||||
padding={padding}
|
||||
animate={animate}
|
||||
onGetG2Instance={this.getG2Instance}
|
||||
>
|
||||
{!!tooltip && <Tooltip showTitle={false} />}
|
||||
<Coord type="theta" innerRadius={inner} />
|
||||
<Geom
|
||||
style={{ lineWidth, stroke: '#fff' }}
|
||||
tooltip={tooltip && tooltipFormat}
|
||||
type="intervalStack"
|
||||
position="percent"
|
||||
color={['x', percent || percent === 0 ? formatColor : defaultColors]}
|
||||
selected={selected}
|
||||
/>
|
||||
</Chart>
|
||||
|
||||
{(subTitle || total) && (
|
||||
<div className={styles.total}>
|
||||
{subTitle && <h4 className="pie-sub-title">{subTitle}</h4>}
|
||||
{/* eslint-disable-next-line */}
|
||||
{total && (
|
||||
<div className="pie-stat">{typeof total === 'function' ? total() : total}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ReactFitText>
|
||||
|
||||
{hasLegend && (
|
||||
<ul className={styles.legend}>
|
||||
{legendData.map((item, i) => (
|
||||
<li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
|
||||
<span
|
||||
className={styles.dot}
|
||||
style={{
|
||||
backgroundColor: !item.checked ? '#aaa' : item.color,
|
||||
}}
|
||||
/>
|
||||
<span className={styles.legendTitle}>{item.x}</span>
|
||||
<Divider type="vertical" />
|
||||
<span className={styles.percent}>
|
||||
{`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
|
||||
</span>
|
||||
<span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Pie;
|
||||
94
admin-web/src/components/Charts/Pie/index.less
Normal file
94
admin-web/src/components/Charts/Pie/index.less
Normal file
@@ -0,0 +1,94 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.pie {
|
||||
position: relative;
|
||||
.chart {
|
||||
position: relative;
|
||||
}
|
||||
&.hasLegend .chart {
|
||||
width: ~'calc(100% - 240px)';
|
||||
}
|
||||
.legend {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
min-width: 200px;
|
||||
margin: 0 20px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
transform: translateY(-50%);
|
||||
li {
|
||||
height: 22px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 22px;
|
||||
cursor: pointer;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dot {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.line {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
background-color: @border-color-split;
|
||||
}
|
||||
.legendTitle {
|
||||
color: @text-color;
|
||||
}
|
||||
.percent {
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
.value {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.total {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-height: 62px;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
& > h4 {
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
color: @text-color-secondary;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
& > p {
|
||||
display: block;
|
||||
height: 32px;
|
||||
color: @heading-color;
|
||||
font-size: 1.2em;
|
||||
line-height: 32px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.legendBlock {
|
||||
&.hasLegend .chart {
|
||||
width: 100%;
|
||||
margin: 0 0 32px 0;
|
||||
}
|
||||
.legend {
|
||||
position: relative;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user