You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
4.4 KiB
JavaScript

import React, { useMemo, forwardRef } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { deepMerge,deepClone } from '../../datav/usefull/index'
import useAutoResize from '../../datav/use/autoResize'
import './style.less'
const defaultConfig = {
/**
* @description Chart data
* @type {Array<Object>}
* @default data = []
*/
data: [],
/**
* @description Chart img
* @type {Array<String>}
* @default img = []
*/
img: [],
/**
* @description Chart font size
* @type {Number}
* @default fontSize = 12
*/
fontSize: 12,
/**
* @description Img side length
* @type {Number}
* @default imgSideLength = 30
*/
imgSideLength: 30,
/**
* @description Column color
* @type {String}
* @default columnColor = 'rgba(0, 194, 255, 0.4)'
*/
columnColor: 'rgba(0, 194, 255, 0.4)',
/**
* @description Text color
* @type {String}
* @default textColor = '#fff'
*/
textColor: '#fff',
/**
* @description Show value
* @type {Boolean}
* @default showValue = false
*/
showValue: false
}
function getData(mergedConfig) {
let { data } = mergedConfig
data = deepClone(data, true)
data.sort(({ value: a }, { value: b }) => {
if (a > b) return -1
if (a < b) return 1
if (a === b) return 0
})
const max = data[0] ? data[0].value : 10
data = data.map(item => ({
...item,
percent: item.value / max
}))
return data
}
const ConicalColumnChart = forwardRef(({ config = {}, className, style }, ref) => {
const { width, height, domRef } = useAutoResize(ref)
const { mergedConfig, column } = useMemo(calcData, [config, width, height])
function calcData() {
const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {})
mergedConfig.data = getData(mergedConfig)
return { mergedConfig, column: getColumn(mergedConfig) }
}
function getColumn(mergedConfig) {
const { imgSideLength, fontSize, data } = mergedConfig
const itemNum = data.length
const gap = width / (itemNum + 1)
const useAbleHeight = height - imgSideLength - fontSize - 5
const svgBottom = height - fontSize - 5
return data.map((item, i) => {
const { percent } = item
const middleXPos = gap * (i + 1)
const leftXPos = gap * i
const rightXpos = gap * (i + 2)
const middleYPos = svgBottom - useAbleHeight * percent
const controlYPos = useAbleHeight * percent * 0.6 + middleYPos
const d = `
M${leftXPos}, ${svgBottom}
Q${middleXPos}, ${controlYPos} ${middleXPos},${middleYPos}
M${middleXPos},${middleYPos}
Q${middleXPos}, ${controlYPos} ${rightXpos},${svgBottom}
L${leftXPos}, ${svgBottom}
Z
`
const textY = (svgBottom + middleYPos) / 2 + fontSize / 2
return {
...item,
d,
x: middleXPos,
y: middleYPos,
textY
}
})
}
const classNames = useMemo(
() => classnames('dv-conical-column-chart', className),
[className]
)
return (
<div className={classNames} style={style} ref={domRef}>
<svg width={width} height={height}>
{column.map((item, i) => (
<g key={i}>
<path d={item.d} fill={mergedConfig.columnColor} />
<text
style={{ fontSize: `${mergedConfig.fontSize}px` }}
fill={mergedConfig.textColor}
x={item.x}
y={height - 4}
>
{item.name}
</text>
{!!mergedConfig.img.length && (
<image
href={mergedConfig.img[i % mergedConfig.img.length]}
width={mergedConfig.imgSideLength}
height={mergedConfig.imgSideLength}
x={item.x - mergedConfig.imgSideLength / 2}
y={item.y - mergedConfig.imgSideLength}
/>
)}
{mergedConfig.showValue && (
<text
style={{ fontSize: `${mergedConfig.fontSize}px` }}
fill={mergedConfig.textColor}
x={item.x}
y={item.textY}
>
{item.value}
</text>
)}
</g>
))}
</svg>
</div>
)
})
ConicalColumnChart.propTypes = {
config: PropTypes.object,
className: PropTypes.string,
style: PropTypes.object
}
export default ConicalColumnChart