20 changed files with 3603 additions and 12918 deletions
File diff suppressed because it is too large
@ -1,122 +0,0 @@ |
|||
import { Axis, Chart, Geom, Tooltip } from 'bizcharts'; |
|||
import Debounce from 'lodash.debounce'; |
|||
import React, { useCallback, useEffect, useRef, useState } from 'react'; |
|||
import useStyles from '../index.style'; |
|||
|
|||
export type BarProps = { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
padding?: [number, number, number, number]; |
|||
height?: number; |
|||
data: { |
|||
x: string; |
|||
y: number; |
|||
}[]; |
|||
forceFit?: boolean; |
|||
autoLabel?: boolean; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
const Bar: React.FC<BarProps> = ({ |
|||
height = 1, |
|||
title, |
|||
forceFit = true, |
|||
data, |
|||
color = 'rgba(24, 144, 255, 0.85)', |
|||
padding, |
|||
autoLabel = true, |
|||
}) => { |
|||
const { styles } = useStyles(); |
|||
const [autoHideXLabels, setAutoHideXLabels] = useState(false); |
|||
const rootRef = useRef<HTMLDivElement | null>(null); |
|||
const nodeRef = useRef<HTMLDivElement | null>(null); |
|||
|
|||
const resize = useCallback( |
|||
Debounce(() => { |
|||
if (!nodeRef.current || !nodeRef.current.parentNode) { |
|||
return; |
|||
} |
|||
const canvasWidth = nodeRef.current.parentNode?.clientWidth || 0; |
|||
if (!autoLabel) { |
|||
return; |
|||
} |
|||
const minWidth = data?.length * 30; |
|||
if (canvasWidth <= minWidth) { |
|||
if (!autoHideXLabels) { |
|||
setAutoHideXLabels(true); |
|||
} |
|||
} else if (autoHideXLabels) { |
|||
setAutoHideXLabels(false); |
|||
} |
|||
}, 500), |
|||
[autoHideXLabels, autoLabel, data?.length], |
|||
); |
|||
|
|||
useEffect(() => { |
|||
window.addEventListener('resize', resize, { |
|||
passive: true, |
|||
}); |
|||
return () => { |
|||
window.removeEventListener('resize', resize); |
|||
}; |
|||
}, [resize]); |
|||
|
|||
const handleRoot = useCallback((n: HTMLDivElement) => { |
|||
rootRef.current = n; |
|||
}, []); |
|||
|
|||
const handleRef = useCallback((n: HTMLDivElement) => { |
|||
nodeRef.current = n; |
|||
}, []); |
|||
|
|||
const scale = { |
|||
x: { |
|||
type: 'cat', |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
}, |
|||
}; |
|||
|
|||
const tooltip = [ |
|||
'x*y', |
|||
(x: string, y: string) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
|
|||
return ( |
|||
<div className={styles.chart} style={{ height }} ref={handleRoot}> |
|||
<div ref={handleRef}> |
|||
{title && ( |
|||
<h4 |
|||
style={{ |
|||
marginBottom: 20, |
|||
}} |
|||
> |
|||
{title} |
|||
</h4> |
|||
)} |
|||
<Chart |
|||
scale={scale} |
|||
height={title ? height - 41 : height} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding || 'auto'} |
|||
> |
|||
<Axis |
|||
name="x" |
|||
title={false} |
|||
label={autoHideXLabels ? undefined : {}} |
|||
tickLine={autoHideXLabels ? undefined : {}} |
|||
/> |
|||
<Axis name="y" min={0} /> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} /> |
|||
</Chart> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default Bar; |
|||
@ -1,179 +0,0 @@ |
|||
import { Axis, Chart, Coord, Geom, Guide, Shape } from 'bizcharts'; |
|||
|
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
const { Arc, Html, Line } = Guide; |
|||
|
|||
export type GaugeProps = { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
height?: number; |
|||
bgColor?: number; |
|||
percent: number; |
|||
forceFit?: boolean; |
|||
style?: React.CSSProperties; |
|||
formatter: (value: string) => string; |
|||
}; |
|||
|
|||
const defaultFormatter = (val: string): string => { |
|||
switch (val) { |
|||
case '2': |
|||
return '差'; |
|||
case '4': |
|||
return '中'; |
|||
case '6': |
|||
return '良'; |
|||
case '8': |
|||
return '优'; |
|||
default: |
|||
return ''; |
|||
} |
|||
}; |
|||
|
|||
if (Shape.registerShape) { |
|||
Shape.registerShape('point', 'pointer', { |
|||
drawShape(cfg: any, group: any) { |
|||
let point = cfg.points[0]; |
|||
point = (this as any).parsePoint(point); |
|||
const center = (this as any).parsePoint({ |
|||
x: 0, |
|||
y: 0, |
|||
}); |
|||
group.addShape('line', { |
|||
attrs: { |
|||
x1: center.x, |
|||
y1: center.y, |
|||
x2: point.x, |
|||
y2: point.y, |
|||
stroke: cfg.color, |
|||
lineWidth: 2, |
|||
lineCap: 'round', |
|||
}, |
|||
}); |
|||
return group.addShape('circle', { |
|||
attrs: { |
|||
x: center.x, |
|||
y: center.y, |
|||
r: 6, |
|||
stroke: cfg.color, |
|||
lineWidth: 3, |
|||
fill: '#fff', |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
const Gauge: React.FC<GaugeProps> = (props) => { |
|||
const { |
|||
title, |
|||
height = 1, |
|||
percent, |
|||
forceFit = true, |
|||
formatter = defaultFormatter, |
|||
color = '#2F9CFF', |
|||
bgColor = '#F0F2F5', |
|||
} = props; |
|||
const cols = { |
|||
value: { |
|||
type: 'linear', |
|||
min: 0, |
|||
max: 10, |
|||
tickCount: 6, |
|||
nice: true, |
|||
}, |
|||
}; |
|||
const data = [{ value: percent / 10 }]; |
|||
const renderHtml = () => ` |
|||
<div style="width: 300px;text-align: center;font-size: 12px!important;"> |
|||
<div style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</div> |
|||
<div style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;"> |
|||
${(data[0].value * 10).toFixed(2)}% |
|||
</div> |
|||
</div>`;
|
|||
const textStyle: { |
|||
fontSize: number; |
|||
fill: string; |
|||
textAlign: 'center'; |
|||
} = { |
|||
fontSize: 12, |
|||
fill: 'rgba(0, 0, 0, 0.65)', |
|||
textAlign: 'center', |
|||
}; |
|||
|
|||
return ( |
|||
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}> |
|||
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} /> |
|||
<Axis name="1" line={undefined} /> |
|||
<Axis |
|||
line={undefined} |
|||
tickLine={undefined} |
|||
subTickLine={undefined} |
|||
name="value" |
|||
zIndex={2} |
|||
label={{ |
|||
offset: -12, |
|||
formatter, |
|||
textStyle, |
|||
}} |
|||
/> |
|||
<Guide> |
|||
<Line |
|||
start={[3, 0.905]} |
|||
end={[3, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 2, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[5, 0.905]} |
|||
end={[5, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[7, 0.905]} |
|||
end={[7, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Arc |
|||
start={[0, 0.965]} |
|||
end={[10, 0.965]} |
|||
style={{ |
|||
stroke: bgColor, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Arc |
|||
start={[0, 0.965]} |
|||
end={[data[0].value, 0.965]} |
|||
style={{ |
|||
stroke: color, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Html position={['50%', '95%']} html={renderHtml()} /> |
|||
</Guide> |
|||
<Geom |
|||
line={false} |
|||
type="point" |
|||
position="value*1" |
|||
shape="pointer" |
|||
color={color} |
|||
active={false} |
|||
/> |
|||
</Chart> |
|||
); |
|||
}; |
|||
|
|||
export default autoHeight()(Gauge); |
|||
@ -1,141 +0,0 @@ |
|||
import type { AxisProps } from 'bizcharts'; |
|||
import { Axis, Chart, Geom, Tooltip } from 'bizcharts'; |
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
import useStyles from '../index.style'; |
|||
export type MiniAreaProps = { |
|||
color?: string; |
|||
height?: number; |
|||
borderColor?: string; |
|||
line?: boolean; |
|||
animate?: boolean; |
|||
xAxis?: AxisProps; |
|||
forceFit?: boolean; |
|||
scale?: { |
|||
x?: { |
|||
tickCount: number; |
|||
}; |
|||
y?: { |
|||
tickCount: number; |
|||
}; |
|||
}; |
|||
yAxis?: Partial<AxisProps>; |
|||
borderWidth?: number; |
|||
data: { |
|||
x: number | string; |
|||
y: number; |
|||
}[]; |
|||
}; |
|||
const MiniArea: React.FC<MiniAreaProps> = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { |
|||
height = 1, |
|||
data = [], |
|||
forceFit = true, |
|||
color = 'rgba(24, 144, 255, 0.2)', |
|||
borderColor = '#1089ff', |
|||
scale = { |
|||
x: {}, |
|||
y: {}, |
|||
}, |
|||
borderWidth = 2, |
|||
line, |
|||
xAxis, |
|||
yAxis, |
|||
animate = true, |
|||
} = props; |
|||
const padding: [number, number, number, number] = [36, 5, 30, 5]; |
|||
const scaleProps = { |
|||
x: { |
|||
type: 'cat', |
|||
range: [0, 1], |
|||
...scale.x, |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
...scale.y, |
|||
}, |
|||
}; |
|||
const tooltip: [ |
|||
string, |
|||
(...args: any[]) => { |
|||
name?: string; |
|||
value: string; |
|||
}, |
|||
] = [ |
|||
'x*y', |
|||
(x: string, y: string) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
const chartHeight = height + 54; |
|||
return ( |
|||
<div |
|||
className={styles.miniChart} |
|||
style={{ |
|||
height, |
|||
}} |
|||
> |
|||
<div className={styles.chartContent}> |
|||
{height > 0 && ( |
|||
<Chart |
|||
animate={animate} |
|||
scale={scaleProps} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
> |
|||
<Axis |
|||
key="axis-x" |
|||
name="x" |
|||
label={null} |
|||
line={null} |
|||
tickLine={null} |
|||
grid={null} |
|||
{...xAxis} |
|||
/> |
|||
<Axis |
|||
key="axis-y" |
|||
name="y" |
|||
label={null} |
|||
line={null} |
|||
tickLine={null} |
|||
grid={null} |
|||
{...yAxis} |
|||
/> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom |
|||
type="area" |
|||
position="x*y" |
|||
color={color} |
|||
tooltip={tooltip} |
|||
shape="smooth" |
|||
style={{ |
|||
fillOpacity: 1, |
|||
}} |
|||
/> |
|||
{line ? ( |
|||
<Geom |
|||
type="line" |
|||
position="x*y" |
|||
shape="smooth" |
|||
color={borderColor} |
|||
size={borderWidth} |
|||
tooltip={false} |
|||
/> |
|||
) : ( |
|||
<span |
|||
style={{ |
|||
display: 'none', |
|||
}} |
|||
/> |
|||
)} |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default autoHeight()(MiniArea); |
|||
@ -1,59 +0,0 @@ |
|||
import { Chart, Geom, Tooltip } from 'bizcharts'; |
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
import useStyles from '../index.style'; |
|||
export type MiniBarProps = { |
|||
color?: string; |
|||
height?: number; |
|||
data: { |
|||
x: number | string; |
|||
y: number; |
|||
}[]; |
|||
forceFit?: boolean; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
const MiniBar: React.FC<MiniBarProps> = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { height = 0, forceFit = true, color = '#1890FF', data = [] } = props; |
|||
const scale = { |
|||
x: { |
|||
type: 'cat', |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
}, |
|||
}; |
|||
const padding: [number, number, number, number] = [36, 5, 30, 5]; |
|||
const tooltip: [ |
|||
string, |
|||
(...args: any[]) => { |
|||
name?: string; |
|||
value: string; |
|||
}, |
|||
] = [ |
|||
'x*y', |
|||
(x: string, y: string) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
|
|||
// for tooltip not to be hide
|
|||
const chartHeight = height + 54; |
|||
return ( |
|||
<div |
|||
className={styles.miniChart} |
|||
style={{ |
|||
height, |
|||
}} |
|||
> |
|||
<div className={styles.chartContent}> |
|||
<Chart scale={scale} height={chartHeight} forceFit={forceFit} data={data} padding={padding}> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} /> |
|||
</Chart> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default autoHeight()(MiniBar); |
|||
@ -1,305 +0,0 @@ |
|||
import { DataView } from '@antv/data-set'; |
|||
import { Divider } from 'antd'; |
|||
import { Chart, Coord, Geom, Tooltip } from 'bizcharts'; |
|||
import classNames from 'classnames'; |
|||
import Debounce from 'lodash.debounce'; |
|||
import React, { Component } from 'react'; |
|||
import ReactFitText from 'react-fittext'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
export type PieProps = { |
|||
animate?: boolean; |
|||
color?: string; |
|||
colors?: string[]; |
|||
selected?: boolean; |
|||
height?: number; |
|||
margin?: [number, number, number, number]; |
|||
hasLegend?: boolean; |
|||
padding?: [number, number, number, number]; |
|||
percent?: number; |
|||
data?: { |
|||
x: string | string; |
|||
y: number; |
|||
}[]; |
|||
inner?: number; |
|||
lineWidth?: number; |
|||
forceFit?: boolean; |
|||
style?: React.CSSProperties; |
|||
className?: string; |
|||
total?: React.ReactNode | number | (() => React.ReactNode | number); |
|||
title?: React.ReactNode; |
|||
tooltip?: boolean; |
|||
valueFormat?: (value: string) => string | React.ReactNode; |
|||
subTitle?: React.ReactNode; |
|||
}; |
|||
type PieState = { |
|||
legendData: { |
|||
checked: boolean; |
|||
x: string; |
|||
color: string; |
|||
percent: number; |
|||
y: string; |
|||
}[]; |
|||
legendBlock: boolean; |
|||
}; |
|||
class Pie extends Component<PieProps, PieState> { |
|||
state: PieState = { |
|||
legendData: [], |
|||
legendBlock: false, |
|||
}; |
|||
requestRef: number | undefined = undefined; |
|||
root: HTMLDivElement | undefined = undefined; |
|||
chart: G2.Chart | undefined = undefined; |
|||
|
|||
// for window resize auto responsive legend
|
|||
resize = Debounce(() => { |
|||
const { hasLegend } = this.props; |
|||
const { legendBlock } = this.state; |
|||
if (!hasLegend || !this.root) { |
|||
window.removeEventListener('resize', this.resize); |
|||
return; |
|||
} |
|||
if ( |
|||
this.root && |
|||
this.root.parentNode && |
|||
(this.root.parentNode as HTMLElement).clientWidth <= 380 |
|||
) { |
|||
if (!legendBlock) { |
|||
this.setState({ |
|||
legendBlock: true, |
|||
}); |
|||
} |
|||
} else if (legendBlock) { |
|||
this.setState({ |
|||
legendBlock: false, |
|||
}); |
|||
} |
|||
}, 400); |
|||
componentDidMount() { |
|||
window.addEventListener( |
|||
'resize', |
|||
() => { |
|||
this.requestRef = requestAnimationFrame(() => this.resize()); |
|||
}, |
|||
{ |
|||
passive: true, |
|||
}, |
|||
); |
|||
} |
|||
componentDidUpdate(preProps: PieProps) { |
|||
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() { |
|||
if (this.requestRef) { |
|||
window.cancelAnimationFrame(this.requestRef); |
|||
} |
|||
window.removeEventListener('resize', this.resize); |
|||
if (this.resize) { |
|||
(this.resize as any).cancel(); |
|||
} |
|||
} |
|||
getG2Instance = (chart: G2.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 as any).get('dataArray') || []; // 获取图形对应的
|
|||
|
|||
const legendData = items.map( |
|||
( |
|||
item: { |
|||
color: any; |
|||
_origin: any; |
|||
}[], |
|||
) => { |
|||
/* 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: HTMLDivElement) => { |
|||
this.root = n; |
|||
}; |
|||
handleLegendClick = (item: any, i: string | number) => { |
|||
const newItem = item; |
|||
newItem.checked = !newItem.checked; |
|||
const { legendData } = this.state; |
|||
const key = i as unknown as number; |
|||
legendData[key] = 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, |
|||
}); |
|||
}; |
|||
render() { |
|||
const { |
|||
valueFormat, |
|||
subTitle, |
|||
total, |
|||
hasLegend = false, |
|||
className, |
|||
style, |
|||
height = 0, |
|||
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: string) => { |
|||
if (value === '占比') { |
|||
return color || 'rgba(24, 144, 255, 0.85)'; |
|||
} |
|||
return '#F0F2F5'; |
|||
}; |
|||
data = [ |
|||
{ |
|||
x: '占比', |
|||
y: parseFloat(`${percent}`), |
|||
}, |
|||
{ |
|||
x: '反比', |
|||
y: 100 - parseFloat(`${percent}`), |
|||
}, |
|||
]; |
|||
} |
|||
const tooltipFormat: [ |
|||
string, |
|||
(...args: any[]) => { |
|||
name?: string; |
|||
value: string; |
|||
}, |
|||
] = [ |
|||
'x*percent', |
|||
(x: string, p: number) => ({ |
|||
name: x, |
|||
value: `${(p * 100).toFixed(2)}%`, |
|||
}), |
|||
]; |
|||
const padding = [12, 0, 12, 0] as [number, number, number, number]; |
|||
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 : undefined} |
|||
type="intervalStack" |
|||
position="percent" |
|||
color={['x', percent || percent === 0 ? formatColor : defaultColors] as any} |
|||
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 autoHeight()(Pie); |
|||
@ -1,209 +0,0 @@ |
|||
import DataSet from '@antv/data-set'; |
|||
import { Chart, Coord, Geom, Shape, Tooltip } from 'bizcharts'; |
|||
import classNames from 'classnames'; |
|||
import Debounce from 'lodash.debounce'; |
|||
import React, { Component } from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
/* eslint no-underscore-dangle: 0 */ |
|||
/* eslint no-param-reassign: 0 */ |
|||
|
|||
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'; |
|||
export type TagCloudProps = { |
|||
data: { |
|||
name: string; |
|||
value: number; |
|||
}[]; |
|||
height?: number; |
|||
className?: string; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
type TagCloudState = { |
|||
dv: any; |
|||
height?: number; |
|||
width: number; |
|||
}; |
|||
class TagCloud extends Component<TagCloudProps, TagCloudState> { |
|||
state = { |
|||
dv: null, |
|||
height: 0, |
|||
width: 0, |
|||
}; |
|||
isUnmount: boolean = false; |
|||
requestRef: number = 0; |
|||
root: HTMLDivElement | undefined = undefined; |
|||
imageMask: HTMLImageElement | undefined = undefined; |
|||
componentDidMount() { |
|||
requestAnimationFrame(() => { |
|||
this.initTagCloud(); |
|||
this.renderChart(this.props); |
|||
}); |
|||
window.addEventListener('resize', this.resize, { |
|||
passive: true, |
|||
}); |
|||
} |
|||
componentDidUpdate(preProps?: TagCloudProps) { |
|||
const { data } = this.props; |
|||
if (preProps && JSON.stringify(preProps.data) !== JSON.stringify(data)) { |
|||
this.renderChart(this.props); |
|||
} |
|||
} |
|||
componentWillUnmount() { |
|||
this.isUnmount = true; |
|||
window.cancelAnimationFrame(this.requestRef); |
|||
window.removeEventListener('resize', this.resize); |
|||
} |
|||
resize = () => { |
|||
this.requestRef = requestAnimationFrame(() => { |
|||
this.renderChart(this.props); |
|||
}); |
|||
}; |
|||
saveRootRef = (node: HTMLDivElement) => { |
|||
this.root = node; |
|||
}; |
|||
initTagCloud = () => { |
|||
function getTextAttrs(cfg: { |
|||
x?: any; |
|||
y?: any; |
|||
style?: any; |
|||
opacity?: any; |
|||
origin?: any; |
|||
color?: any; |
|||
}) { |
|||
return { |
|||
...cfg.style, |
|||
fillOpacity: cfg.opacity, |
|||
fontSize: cfg.origin._origin.size, |
|||
rotate: cfg.origin._origin.rotate, |
|||
text: cfg.origin._origin.text, |
|||
textAlign: 'center', |
|||
fontFamily: cfg.origin._origin.font, |
|||
fill: cfg.color, |
|||
textBaseline: 'Alphabetic', |
|||
}; |
|||
} |
|||
(Shape as any).registerShape('point', 'cloud', { |
|||
drawShape( |
|||
cfg: { |
|||
x: any; |
|||
y: any; |
|||
}, |
|||
container: { |
|||
addShape: ( |
|||
arg0: string, |
|||
arg1: { |
|||
attrs: any; |
|||
}, |
|||
) => void; |
|||
}, |
|||
) { |
|||
const attrs = getTextAttrs(cfg); |
|||
return container.addShape('text', { |
|||
attrs: { |
|||
...attrs, |
|||
x: cfg.x, |
|||
y: cfg.y, |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
}; |
|||
renderChart = Debounce((nextProps: TagCloudProps) => { |
|||
// const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
|
|||
const { data, height } = nextProps || this.props; |
|||
if (data.length < 1 || !this.root) { |
|||
return; |
|||
} |
|||
const h = height; |
|||
const w = this.root.offsetWidth; |
|||
const onload = () => { |
|||
const dv = new DataSet.View().source(data); |
|||
const range = dv.range('value'); |
|||
const [min, max] = range; |
|||
dv.transform({ |
|||
type: 'tag-cloud', |
|||
fields: ['name', 'value'], |
|||
imageMask: this.imageMask, |
|||
font: 'Verdana', |
|||
size: [w, h], |
|||
// 宽高设置最好根据 imageMask 做调整
|
|||
padding: 0, |
|||
timeInterval: 5000, |
|||
// max execute time
|
|||
rotate() { |
|||
return 0; |
|||
}, |
|||
fontSize(d: { value: number }) { |
|||
const size = ((d.value - min) / (max - min)) ** 2; |
|||
return size * (17.5 - 5) + 5; |
|||
}, |
|||
}); |
|||
if (this.isUnmount) { |
|||
return; |
|||
} |
|||
this.setState({ |
|||
dv, |
|||
width: w, |
|||
height: h, |
|||
}); |
|||
}; |
|||
if (!this.imageMask) { |
|||
this.imageMask = new Image(); |
|||
this.imageMask.crossOrigin = ''; |
|||
this.imageMask.src = imgUrl; |
|||
this.imageMask.onload = onload; |
|||
} else { |
|||
onload(); |
|||
} |
|||
}, 500); |
|||
render() { |
|||
const { className, height } = this.props; |
|||
const { dv, width, height: stateHeight } = this.state; |
|||
return ( |
|||
<div |
|||
className={classNames(styles.tagCloud, className)} |
|||
style={{ |
|||
width: '100%', |
|||
height, |
|||
}} |
|||
ref={this.saveRootRef} |
|||
> |
|||
{dv && ( |
|||
<Chart |
|||
width={width} |
|||
height={stateHeight} |
|||
data={dv} |
|||
padding={0} |
|||
scale={{ |
|||
x: { |
|||
nice: false, |
|||
}, |
|||
y: { |
|||
nice: false, |
|||
}, |
|||
}} |
|||
> |
|||
<Tooltip showTitle={false} /> |
|||
<Coord reflect="y" /> |
|||
<Geom |
|||
type="point" |
|||
position="x*y" |
|||
color="text" |
|||
shape="cloud" |
|||
tooltip={[ |
|||
'text*value', |
|||
function trans(text, value) { |
|||
return { |
|||
name: text, |
|||
value, |
|||
}; |
|||
}, |
|||
]} |
|||
/> |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
export default autoHeight()(TagCloud); |
|||
@ -1,149 +0,0 @@ |
|||
import DataSet from '@antv/data-set'; |
|||
import { Axis, Chart, Geom, Legend, Tooltip } from 'bizcharts'; |
|||
import Slider from 'bizcharts-plugin-slider'; |
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
import useStyles from './index.style'; |
|||
export type TimelineChartProps = { |
|||
data: { |
|||
x: number; |
|||
y1: number; |
|||
y2: number; |
|||
}[]; |
|||
title?: string; |
|||
titleMap: { |
|||
y1: string; |
|||
y2: string; |
|||
}; |
|||
padding?: [number, number, number, number]; |
|||
height?: number; |
|||
style?: React.CSSProperties; |
|||
borderWidth?: number; |
|||
}; |
|||
const TimelineChart: React.FC<TimelineChartProps> = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { |
|||
title, |
|||
height = 400, |
|||
padding = [60, 20, 40, 40] as [number, number, number, number], |
|||
titleMap = { |
|||
y1: 'y1', |
|||
y2: 'y2', |
|||
}, |
|||
borderWidth = 2, |
|||
data: sourceData, |
|||
} = props; |
|||
const data = Array.isArray(sourceData) |
|||
? sourceData |
|||
: [ |
|||
{ |
|||
x: 0, |
|||
y1: 0, |
|||
y2: 0, |
|||
}, |
|||
]; |
|||
data.sort((a, b) => a.x - b.x); |
|||
let max; |
|||
if (data[0] && data[0].y1 && data[0].y2) { |
|||
max = Math.max( |
|||
[...data].sort((a, b) => b.y1 - a.y1)[0].y1, |
|||
[...data].sort((a, b) => b.y2 - a.y2)[0].y2, |
|||
); |
|||
} |
|||
const ds = new DataSet({ |
|||
state: { |
|||
start: data[0].x, |
|||
end: data[data.length - 1].x, |
|||
}, |
|||
}); |
|||
const dv = ds.createView(); |
|||
dv.source(data) |
|||
.transform({ |
|||
type: 'filter', |
|||
callback: (obj: { x: string }) => { |
|||
const date = obj.x; |
|||
return date <= ds.state.end && date >= ds.state.start; |
|||
}, |
|||
}) |
|||
.transform({ |
|||
type: 'map', |
|||
callback(row: { y1: string; y2: string }) { |
|||
const newRow = { |
|||
...row, |
|||
}; |
|||
newRow[titleMap.y1 as 'y1'] = row.y1; |
|||
newRow[titleMap.y2 as 'y2'] = row.y2; |
|||
return newRow; |
|||
}, |
|||
}) |
|||
.transform({ |
|||
type: 'fold', |
|||
fields: [titleMap.y1, titleMap.y2], |
|||
// 展开字段集
|
|||
key: 'key', |
|||
// key字段
|
|||
value: 'value', // value字段
|
|||
}); |
|||
|
|||
const timeScale = { |
|||
type: 'time', |
|||
tickInterval: 60 * 60 * 1000, |
|||
mask: 'HH:mm', |
|||
range: [0, 1], |
|||
}; |
|||
const cols = { |
|||
x: timeScale, |
|||
value: { |
|||
max, |
|||
min: 0, |
|||
}, |
|||
}; |
|||
const SliderGen = () => ( |
|||
<Slider |
|||
padding={[0, padding[1] + 20, 0, padding[3]]} |
|||
width="auto" |
|||
height={26} |
|||
xAxis="x" |
|||
yAxis="y1" |
|||
scales={{ |
|||
x: timeScale, |
|||
}} |
|||
data={data} |
|||
start={ds.state.start} |
|||
end={ds.state.end} |
|||
backgroundChart={{ |
|||
type: 'line', |
|||
}} |
|||
onChange={({ startValue, endValue }: { startValue: string; endValue: string }) => { |
|||
ds.setState('start', startValue); |
|||
ds.setState('end', endValue); |
|||
}} |
|||
/> |
|||
); |
|||
return ( |
|||
<div |
|||
className={styles.timelineChart} |
|||
style={{ |
|||
height: height + 30, |
|||
}} |
|||
> |
|||
<div> |
|||
{title && <h4>{title}</h4>} |
|||
<Chart height={height} padding={padding} data={dv} scale={cols} forceFit> |
|||
<Axis name="x" /> |
|||
<Tooltip /> |
|||
<Legend name="key" position="top" /> |
|||
<Geom type="line" position="x*value" size={borderWidth} color="key" /> |
|||
</Chart> |
|||
<div |
|||
style={{ |
|||
marginRight: -20, |
|||
}} |
|||
> |
|||
<SliderGen /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default autoHeight()(TimelineChart); |
|||
@ -1,180 +0,0 @@ |
|||
import { Axis, Chart, Coord, Geom, Guide, Shape } from 'bizcharts'; |
|||
|
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
const { Arc, Html, Line } = Guide; |
|||
|
|||
export type GaugeProps = { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
height?: number; |
|||
bgColor?: number; |
|||
percent: number; |
|||
forceFit?: boolean; |
|||
style?: React.CSSProperties; |
|||
formatter?: (value: string) => string; |
|||
}; |
|||
|
|||
const defaultFormatter = (val: string): string => { |
|||
switch (val) { |
|||
case '2': |
|||
return '差'; |
|||
case '4': |
|||
return '中'; |
|||
case '6': |
|||
return '良'; |
|||
case '8': |
|||
return '优'; |
|||
default: |
|||
return ''; |
|||
} |
|||
}; |
|||
|
|||
if (Shape.registerShape) { |
|||
Shape.registerShape('point', 'pointer', { |
|||
drawShape(cfg: any, group: any) { |
|||
let point = cfg.points[0]; |
|||
point = (this as any).parsePoint(point); |
|||
const center = (this as any).parsePoint({ |
|||
x: 0, |
|||
y: 0, |
|||
}); |
|||
group.addShape('line', { |
|||
attrs: { |
|||
x1: center.x, |
|||
y1: center.y, |
|||
x2: point.x, |
|||
y2: point.y, |
|||
stroke: cfg.color, |
|||
lineWidth: 2, |
|||
lineCap: 'round', |
|||
}, |
|||
}); |
|||
return group.addShape('circle', { |
|||
attrs: { |
|||
x: center.x, |
|||
y: center.y, |
|||
r: 6, |
|||
stroke: cfg.color, |
|||
lineWidth: 3, |
|||
fill: '#fff', |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
const Gauge: React.FC<GaugeProps> = (props) => { |
|||
const { |
|||
title, |
|||
height = 1, |
|||
percent, |
|||
forceFit = true, |
|||
formatter = defaultFormatter, |
|||
color = '#2F9CFF', |
|||
bgColor = '#F0F2F5', |
|||
} = props; |
|||
const cols = { |
|||
value: { |
|||
type: 'linear', |
|||
min: 0, |
|||
max: 10, |
|||
tickCount: 6, |
|||
nice: true, |
|||
}, |
|||
}; |
|||
const data = [{ value: percent / 10 }]; |
|||
|
|||
const renderHtml = () => ` |
|||
<div style="width: 300px;text-align: center;font-size: 12px!important;"> |
|||
<div style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</div> |
|||
<div style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;"> |
|||
${(data[0].value * 10).toFixed(2)}% |
|||
</div> |
|||
</div>`;
|
|||
|
|||
const textStyle: { |
|||
fontSize: number; |
|||
fill: string; |
|||
textAlign: 'center'; |
|||
} = { |
|||
fontSize: 12, |
|||
fill: 'rgba(0, 0, 0, 0.65)', |
|||
textAlign: 'center', |
|||
}; |
|||
return ( |
|||
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}> |
|||
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} /> |
|||
<Axis name="1" line={undefined} /> |
|||
<Axis |
|||
line={undefined} |
|||
tickLine={undefined} |
|||
subTickLine={undefined} |
|||
name="value" |
|||
zIndex={2} |
|||
label={{ |
|||
offset: -12, |
|||
formatter, |
|||
textStyle, |
|||
}} |
|||
/> |
|||
<Guide> |
|||
<Line |
|||
start={[3, 0.905]} |
|||
end={[3, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 2, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[5, 0.905]} |
|||
end={[5, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[7, 0.905]} |
|||
end={[7, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: undefined, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Arc |
|||
start={[0, 0.965]} |
|||
end={[10, 0.965]} |
|||
style={{ |
|||
stroke: bgColor, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Arc |
|||
start={[0, 0.965]} |
|||
end={[data[0].value, 0.965]} |
|||
style={{ |
|||
stroke: color, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Html position={['50%', '95%']} html={renderHtml()} /> |
|||
</Guide> |
|||
<Geom |
|||
line={false} |
|||
type="point" |
|||
position="value*1" |
|||
shape="pointer" |
|||
color={color} |
|||
active={false} |
|||
/> |
|||
</Chart> |
|||
); |
|||
}; |
|||
|
|||
export default autoHeight()(Gauge); |
|||
@ -1,141 +0,0 @@ |
|||
import type { AxisProps } from 'bizcharts'; |
|||
import { Axis, Chart, Geom, Tooltip } from 'bizcharts'; |
|||
import React from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
import useStyles from '../index.style'; |
|||
export type MiniAreaProps = { |
|||
color?: string; |
|||
height?: number; |
|||
borderColor?: string; |
|||
line?: boolean; |
|||
animate?: boolean; |
|||
xAxis?: AxisProps; |
|||
forceFit?: boolean; |
|||
scale?: { |
|||
x?: { |
|||
tickCount: number; |
|||
}; |
|||
y?: { |
|||
tickCount: number; |
|||
}; |
|||
}; |
|||
yAxis?: Partial<AxisProps>; |
|||
borderWidth?: number; |
|||
data: { |
|||
x: number | string; |
|||
y: number; |
|||
}[]; |
|||
}; |
|||
const MiniArea: React.FC<MiniAreaProps> = (props) => { |
|||
const { styles } = useStyles(); |
|||
const { |
|||
height = 1, |
|||
data = [], |
|||
forceFit = true, |
|||
color = 'rgba(24, 144, 255, 0.2)', |
|||
borderColor = '#1089ff', |
|||
scale = { |
|||
x: {}, |
|||
y: {}, |
|||
}, |
|||
borderWidth = 2, |
|||
line, |
|||
xAxis, |
|||
yAxis, |
|||
animate = true, |
|||
} = props; |
|||
const padding: [number, number, number, number] = [36, 5, 30, 5]; |
|||
const scaleProps = { |
|||
x: { |
|||
type: 'cat', |
|||
range: [0, 1], |
|||
...scale.x, |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
...scale.y, |
|||
}, |
|||
}; |
|||
const tooltip: [ |
|||
string, |
|||
(...args: any[]) => { |
|||
name?: string; |
|||
value: string; |
|||
}, |
|||
] = [ |
|||
'x*y', |
|||
(x: string, y: string) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
const chartHeight = height + 54; |
|||
return ( |
|||
<div |
|||
className={styles.miniChart} |
|||
style={{ |
|||
height, |
|||
}} |
|||
> |
|||
<div className={styles.chartContent}> |
|||
{height > 0 && ( |
|||
<Chart |
|||
animate={animate} |
|||
scale={scaleProps} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
> |
|||
<Axis |
|||
key="axis-x" |
|||
name="x" |
|||
label={null} |
|||
line={null} |
|||
tickLine={null} |
|||
grid={null} |
|||
{...xAxis} |
|||
/> |
|||
<Axis |
|||
key="axis-y" |
|||
name="y" |
|||
label={null} |
|||
line={null} |
|||
tickLine={null} |
|||
grid={null} |
|||
{...yAxis} |
|||
/> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom |
|||
type="area" |
|||
position="x*y" |
|||
color={color} |
|||
tooltip={tooltip} |
|||
shape="smooth" |
|||
style={{ |
|||
fillOpacity: 1, |
|||
}} |
|||
/> |
|||
{line ? ( |
|||
<Geom |
|||
type="line" |
|||
position="x*y" |
|||
shape="smooth" |
|||
color={borderColor} |
|||
size={borderWidth} |
|||
tooltip={false} |
|||
/> |
|||
) : ( |
|||
<span |
|||
style={{ |
|||
display: 'none', |
|||
}} |
|||
/> |
|||
)} |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default autoHeight()(MiniArea); |
|||
@ -1,311 +0,0 @@ |
|||
import { DataView } from '@antv/data-set'; |
|||
import { Divider } from 'antd'; |
|||
import { Chart, Coord, Geom, Tooltip } from 'bizcharts'; |
|||
import classNames from 'classnames'; |
|||
import Debounce from 'lodash.debounce'; |
|||
import React, { Component } from 'react'; |
|||
import ReactFitText from 'react-fittext'; |
|||
import autoHeight from '../autoHeight'; |
|||
export type PieProps = { |
|||
animate?: boolean; |
|||
color?: string; |
|||
colors?: string[]; |
|||
selected?: boolean; |
|||
height?: number; |
|||
margin?: [number, number, number, number]; |
|||
hasLegend?: boolean; |
|||
padding?: [number, number, number, number]; |
|||
percent?: number; |
|||
data?: { |
|||
x: string | string; |
|||
y: number; |
|||
}[]; |
|||
inner?: number; |
|||
lineWidth?: number; |
|||
forceFit?: boolean; |
|||
style?: React.CSSProperties; |
|||
className?: string; |
|||
total?: React.ReactNode | number | (() => React.ReactNode | number); |
|||
title?: React.ReactNode; |
|||
tooltip?: boolean; |
|||
valueFormat?: (value: string) => string | React.ReactNode; |
|||
subTitle?: React.ReactNode; |
|||
}; |
|||
type PieState = { |
|||
legendData: { |
|||
checked: boolean; |
|||
x: string; |
|||
color: string; |
|||
percent: number; |
|||
y: string; |
|||
}[]; |
|||
legendBlock: boolean; |
|||
}; |
|||
class Pie extends Component<PieProps, PieState> { |
|||
state: PieState = { |
|||
legendData: [], |
|||
legendBlock: false, |
|||
}; |
|||
chart: G2.Chart | undefined = undefined; |
|||
root: HTMLDivElement | undefined = undefined; |
|||
requestRef: number | undefined = 0; |
|||
|
|||
// for window resize auto responsive legend
|
|||
resize = Debounce(() => { |
|||
const { hasLegend } = this.props; |
|||
const { legendBlock } = this.state; |
|||
if (!hasLegend || !this.root) { |
|||
window.removeEventListener('resize', this.resize); |
|||
return; |
|||
} |
|||
if ( |
|||
this.root && |
|||
this.root.parentNode && |
|||
(this.root.parentNode as HTMLElement).clientWidth <= 380 |
|||
) { |
|||
if (!legendBlock) { |
|||
this.setState({ |
|||
legendBlock: true, |
|||
}); |
|||
} |
|||
} else if (legendBlock) { |
|||
this.setState({ |
|||
legendBlock: false, |
|||
}); |
|||
} |
|||
}, 300); |
|||
componentDidMount() { |
|||
window.addEventListener( |
|||
'resize', |
|||
() => { |
|||
this.requestRef = requestAnimationFrame(() => this.resize()); |
|||
}, |
|||
{ |
|||
passive: true, |
|||
}, |
|||
); |
|||
} |
|||
componentDidUpdate(preProps: PieProps) { |
|||
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() { |
|||
if (this.requestRef) { |
|||
window.cancelAnimationFrame(this.requestRef); |
|||
} |
|||
window.removeEventListener('resize', this.resize); |
|||
if (this.resize) { |
|||
(this.resize as any).cancel(); |
|||
} |
|||
} |
|||
getG2Instance = (chart: G2.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; |
|||
// g2 的类型有问题
|
|||
const items = (geom as any).get('dataArray') || []; // 获取图形对应的
|
|||
|
|||
const legendData = items.map( |
|||
( |
|||
item: { |
|||
color: any; |
|||
_origin: any; |
|||
}[], |
|||
) => { |
|||
/* 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: HTMLDivElement) => { |
|||
this.root = n; |
|||
}; |
|||
handleLegendClick = ( |
|||
item: { |
|||
checked: boolean; |
|||
}, |
|||
i: string | number, |
|||
) => { |
|||
const newItem = { |
|||
...item, |
|||
}; |
|||
newItem.checked = !newItem.checked; |
|||
const { legendData } = this.state; |
|||
legendData[i as unknown as number] = newItem as any; |
|||
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, |
|||
}); |
|||
}; |
|||
render() { |
|||
const { |
|||
valueFormat, |
|||
subTitle, |
|||
total, |
|||
hasLegend = false, |
|||
className, |
|||
style, |
|||
height = 0, |
|||
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: string) => { |
|||
if (value === '占比') { |
|||
return color || 'rgba(24, 144, 255, 0.85)'; |
|||
} |
|||
return '#F0F2F5'; |
|||
}; |
|||
data = [ |
|||
{ |
|||
x: '占比', |
|||
y: parseFloat(`${percent}`), |
|||
}, |
|||
{ |
|||
x: '反比', |
|||
y: 100 - parseFloat(`${percent}`), |
|||
}, |
|||
]; |
|||
} |
|||
const tooltipFormat: [ |
|||
string, |
|||
(...args: any[]) => { |
|||
name?: string; |
|||
value: string; |
|||
}, |
|||
] = [ |
|||
'x*percent', |
|||
(x: string, p: number) => ({ |
|||
name: x, |
|||
value: `${(p * 100).toFixed(2)}%`, |
|||
}), |
|||
]; |
|||
const padding = [12, 0, 12, 0] as [number, number, number, number]; |
|||
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 : undefined} |
|||
type="intervalStack" |
|||
position="percent" |
|||
color={['x', percent || percent === 0 ? formatColor : defaultColors] as any} |
|||
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 autoHeight()(Pie); |
|||
@ -1,209 +0,0 @@ |
|||
import DataSet from '@antv/data-set'; |
|||
import { Chart, Coord, Geom, Shape, Tooltip } from 'bizcharts'; |
|||
import classNames from 'classnames'; |
|||
import Debounce from 'lodash.debounce'; |
|||
import React, { Component } from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
/* eslint no-underscore-dangle: 0 */ |
|||
/* eslint no-param-reassign: 0 */ |
|||
|
|||
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'; |
|||
export type TagCloudProps = { |
|||
data: { |
|||
name: string; |
|||
value: string; |
|||
}[]; |
|||
height?: number; |
|||
className?: string; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
type TagCloudState = { |
|||
dv: any; |
|||
height?: number; |
|||
width: number; |
|||
}; |
|||
class TagCloud extends Component<TagCloudProps, TagCloudState> { |
|||
state = { |
|||
dv: null, |
|||
height: 0, |
|||
width: 0, |
|||
}; |
|||
requestRef: number = 0; |
|||
isUnmount: boolean = false; |
|||
root: HTMLDivElement | undefined = undefined; |
|||
imageMask: HTMLImageElement | undefined = undefined; |
|||
componentDidMount() { |
|||
requestAnimationFrame(() => { |
|||
this.initTagCloud(); |
|||
this.renderChart(this.props); |
|||
}); |
|||
window.addEventListener('resize', this.resize, { |
|||
passive: true, |
|||
}); |
|||
} |
|||
componentDidUpdate(preProps?: TagCloudProps) { |
|||
const { data } = this.props; |
|||
if (preProps && JSON.stringify(preProps.data) !== JSON.stringify(data)) { |
|||
this.renderChart(this.props); |
|||
} |
|||
} |
|||
componentWillUnmount() { |
|||
this.isUnmount = true; |
|||
window.cancelAnimationFrame(this.requestRef); |
|||
window.removeEventListener('resize', this.resize); |
|||
} |
|||
resize = () => { |
|||
this.requestRef = requestAnimationFrame(() => { |
|||
this.renderChart(this.props); |
|||
}); |
|||
}; |
|||
saveRootRef = (node: HTMLDivElement) => { |
|||
this.root = node; |
|||
}; |
|||
initTagCloud = () => { |
|||
function getTextAttrs(cfg: { |
|||
x?: any; |
|||
y?: any; |
|||
style?: any; |
|||
opacity?: any; |
|||
origin?: any; |
|||
color?: any; |
|||
}) { |
|||
return { |
|||
...cfg.style, |
|||
fillOpacity: cfg.opacity, |
|||
fontSize: cfg.origin._origin.size, |
|||
rotate: cfg.origin._origin.rotate, |
|||
text: cfg.origin._origin.text, |
|||
textAlign: 'center', |
|||
fontFamily: cfg.origin._origin.font, |
|||
fill: cfg.color, |
|||
textBaseline: 'Alphabetic', |
|||
}; |
|||
} |
|||
(Shape as any).registerShape('point', 'cloud', { |
|||
drawShape( |
|||
cfg: { |
|||
x: any; |
|||
y: any; |
|||
}, |
|||
container: { |
|||
addShape: ( |
|||
arg0: string, |
|||
arg1: { |
|||
attrs: any; |
|||
}, |
|||
) => void; |
|||
}, |
|||
) { |
|||
const attrs = getTextAttrs(cfg); |
|||
return container.addShape('text', { |
|||
attrs: { |
|||
...attrs, |
|||
x: cfg.x, |
|||
y: cfg.y, |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
}; |
|||
renderChart = Debounce((nextProps: TagCloudProps) => { |
|||
// const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
|
|||
const { data, height } = nextProps || this.props; |
|||
if (data.length < 1 || !this.root) { |
|||
return; |
|||
} |
|||
const h = height; |
|||
const w = this.root.offsetWidth; |
|||
const onload = () => { |
|||
const dv = new DataSet.View().source(data); |
|||
const range = dv.range('value'); |
|||
const [min, max] = range; |
|||
dv.transform({ |
|||
type: 'tag-cloud', |
|||
fields: ['name', 'value'], |
|||
imageMask: this.imageMask, |
|||
font: 'Verdana', |
|||
size: [w, h], |
|||
// 宽高设置最好根据 imageMask 做调整
|
|||
padding: 0, |
|||
timeInterval: 5000, |
|||
// max execute time
|
|||
rotate() { |
|||
return 0; |
|||
}, |
|||
fontSize(d: { value: number }) { |
|||
const size = ((d.value - min) / (max - min)) ** 2; |
|||
return size * (17.5 - 5) + 5; |
|||
}, |
|||
}); |
|||
if (this.isUnmount) { |
|||
return; |
|||
} |
|||
this.setState({ |
|||
dv, |
|||
width: w, |
|||
height: h, |
|||
}); |
|||
}; |
|||
if (!this.imageMask) { |
|||
this.imageMask = new Image(); |
|||
this.imageMask.crossOrigin = ''; |
|||
this.imageMask.src = imgUrl; |
|||
this.imageMask.onload = onload; |
|||
} else { |
|||
onload(); |
|||
} |
|||
}, 200); |
|||
render() { |
|||
const { className, height } = this.props; |
|||
const { dv, width, height: stateHeight } = this.state; |
|||
return ( |
|||
<div |
|||
className={classNames(styles.tagCloud, className)} |
|||
style={{ |
|||
width: '100%', |
|||
height, |
|||
}} |
|||
ref={this.saveRootRef} |
|||
> |
|||
{dv && ( |
|||
<Chart |
|||
width={width} |
|||
height={stateHeight} |
|||
data={dv} |
|||
padding={0} |
|||
scale={{ |
|||
x: { |
|||
nice: false, |
|||
}, |
|||
y: { |
|||
nice: false, |
|||
}, |
|||
}} |
|||
> |
|||
<Tooltip showTitle={false} /> |
|||
<Coord reflect="y" /> |
|||
<Geom |
|||
type="point" |
|||
position="x*y" |
|||
color="text" |
|||
shape="cloud" |
|||
tooltip={[ |
|||
'text*value', |
|||
function trans(text, value) { |
|||
return { |
|||
name: text, |
|||
value, |
|||
}; |
|||
}, |
|||
]} |
|||
/> |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
export default autoHeight()(TagCloud); |
|||
@ -1,174 +0,0 @@ |
|||
import { Col, Row } from 'antd'; |
|||
import { Axis, Chart, Coord, Geom, Tooltip } from 'bizcharts'; |
|||
import React, { useEffect, useRef, useState } from 'react'; |
|||
|
|||
export type RadarProps = { |
|||
title?: React.ReactNode; |
|||
height?: number; |
|||
padding?: [number, number, number, number]; |
|||
hasLegend?: boolean; |
|||
data: { |
|||
name: string; |
|||
label: string; |
|||
value: string | number; |
|||
}[]; |
|||
colors?: string[]; |
|||
animate?: boolean; |
|||
forceFit?: boolean; |
|||
tickCount?: number; |
|||
style?: React.CSSProperties; |
|||
}; |
|||
type RadarState = { |
|||
legendData: { |
|||
checked: boolean; |
|||
name: string; |
|||
color: string; |
|||
percent: number; |
|||
value: string; |
|||
}[]; |
|||
}; |
|||
|
|||
const defaultColors = [ |
|||
'#1890FF', |
|||
'#FACC14', |
|||
'#2FC25B', |
|||
'#8543E0', |
|||
'#F04864', |
|||
'#13C2C2', |
|||
'#fa8c16', |
|||
'#a0d911', |
|||
]; |
|||
|
|||
const Radar: React.FC<RadarProps> = (props) => { |
|||
const { |
|||
data = [], |
|||
height = 0, |
|||
title, |
|||
hasLegend = false, |
|||
forceFit = true, |
|||
tickCount = 5, |
|||
padding = [35, 30, 16, 30] as [number, number, number, number], |
|||
animate = true, |
|||
colors = defaultColors, |
|||
} = props; |
|||
|
|||
const chartRef = useRef<G2.Chart>(); |
|||
const [legendData, setLegendData] = useState<RadarState['legendData']>([]); |
|||
|
|||
const getLegendData = () => { |
|||
if (!chartRef.current) return; |
|||
const geom = chartRef.current.getAllGeoms()[0]; |
|||
if (!geom) return; |
|||
const items = (geom as any).get('dataArray') || []; |
|||
|
|||
const legendData = items.map((item: any[]) => { |
|||
const origins = item.map((t) => t._origin); |
|||
const result = { |
|||
name: origins[0].name, |
|||
color: item[0].color, |
|||
checked: true, |
|||
value: origins.reduce((p, n) => p + n.value, 0), |
|||
}; |
|||
return result; |
|||
}); |
|||
setLegendData(legendData); |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
getLegendData(); |
|||
}, [data]); |
|||
|
|||
const handleLegendClick = (item: any, i: string | number) => { |
|||
const newItem = { ...item, checked: !item.checked }; |
|||
const newLegendData = [...legendData]; |
|||
newLegendData[i as number] = newItem; |
|||
const filteredLegendData = newLegendData.filter((l) => l.checked).map((l) => l.name); |
|||
if (chartRef.current) { |
|||
chartRef.current.filter('name', (val) => filteredLegendData.indexOf(`${val}`) > -1); |
|||
chartRef.current.repaint(); |
|||
} |
|||
setLegendData(newLegendData); |
|||
}; |
|||
|
|||
const scale = { |
|||
value: { |
|||
min: 0, |
|||
tickCount, |
|||
}, |
|||
}; |
|||
|
|||
const chartHeight = height - (hasLegend ? 80 : 22); |
|||
|
|||
return ( |
|||
<div style={{ height }}> |
|||
{title && <h4>{title}</h4>} |
|||
<Chart |
|||
scale={scale} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
animate={animate} |
|||
onGetG2Instance={(chart: G2.Chart) => { |
|||
chartRef.current = chart; |
|||
}} |
|||
> |
|||
<Tooltip /> |
|||
<Coord type="polar" /> |
|||
<Axis |
|||
name="label" |
|||
line={undefined} |
|||
tickLine={undefined} |
|||
grid={{ |
|||
lineStyle: { |
|||
lineDash: undefined, |
|||
}, |
|||
hideFirstLine: false, |
|||
}} |
|||
/> |
|||
<Axis |
|||
name="value" |
|||
grid={{ |
|||
type: 'polygon', |
|||
lineStyle: { |
|||
lineDash: undefined, |
|||
}, |
|||
}} |
|||
/> |
|||
<Geom type="line" position="label*value" color={['name', colors]} size={1} /> |
|||
<Geom |
|||
type="point" |
|||
position="label*value" |
|||
color={['name', colors]} |
|||
shape="circle" |
|||
size={3} |
|||
/> |
|||
</Chart> |
|||
{hasLegend && ( |
|||
<Row> |
|||
{legendData.map((item, i) => ( |
|||
<Col |
|||
span={24 / legendData.length} |
|||
key={item.name} |
|||
onClick={() => handleLegendClick(item, i)} |
|||
> |
|||
<div> |
|||
<p> |
|||
<span |
|||
style={{ |
|||
backgroundColor: !item.checked ? '#aaa' : item.color, |
|||
}} |
|||
/> |
|||
<span>{item.name}</span> |
|||
</p> |
|||
<h6>{item.value}</h6> |
|||
</div> |
|||
</Col> |
|||
))} |
|||
</Row> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default Radar; |
|||
Loading…
Reference in new issue