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.
236 lines
5.8 KiB
JavaScript
236 lines
5.8 KiB
JavaScript
|
3 months ago
|
import React, { useEffect, useState, useRef, useMemo } from 'react'
|
||
|
|
|
||
|
|
import PropTypes from 'prop-types'
|
||
|
|
|
||
|
|
import classnames from 'classnames'
|
||
|
|
|
||
|
|
import { deepMerge,deepClone } from '../../datav/usefull/index'
|
||
|
|
|
||
|
|
import { uuid } from '../../datav/util'
|
||
|
|
|
||
|
|
import './style.less'
|
||
|
|
|
||
|
|
const defaultConfig = {
|
||
|
|
/**
|
||
|
|
* @description Value
|
||
|
|
* @type {Number}
|
||
|
|
* @default value = 0
|
||
|
|
*/
|
||
|
|
value: 0,
|
||
|
|
/**
|
||
|
|
* @description Colors (hex|rgb|rgba|color keywords)
|
||
|
|
* @type {Array<String>}
|
||
|
|
* @default colors = ['#00BAFF', '#3DE7C9']
|
||
|
|
* @example colors = ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red']
|
||
|
|
*/
|
||
|
|
colors: ['#3DE7C9', '#00BAFF'],
|
||
|
|
/**
|
||
|
|
* @description Border width
|
||
|
|
* @type {Number}
|
||
|
|
* @default borderWidth = 3
|
||
|
|
*/
|
||
|
|
borderWidth: 3,
|
||
|
|
/**
|
||
|
|
* @description Gap between border and pond
|
||
|
|
* @type {Number}
|
||
|
|
* @default borderGap = 3
|
||
|
|
*/
|
||
|
|
borderGap: 3,
|
||
|
|
/**
|
||
|
|
* @description Line dash
|
||
|
|
* @type {Array<Number>}
|
||
|
|
* @default lineDash = [5, 1]
|
||
|
|
*/
|
||
|
|
lineDash: [5, 1],
|
||
|
|
/**
|
||
|
|
* @description Text color
|
||
|
|
* @type {String}
|
||
|
|
* @default textColor = '#fff'
|
||
|
|
*/
|
||
|
|
textColor: '#fff',
|
||
|
|
/**
|
||
|
|
* @description Border radius
|
||
|
|
* @type {Number}
|
||
|
|
* @default borderRadius = 5
|
||
|
|
*/
|
||
|
|
borderRadius: 5,
|
||
|
|
/**
|
||
|
|
* @description Local Gradient
|
||
|
|
* @type {Boolean}
|
||
|
|
* @default localGradient = false
|
||
|
|
* @example localGradient = false | true
|
||
|
|
*/
|
||
|
|
localGradient: false,
|
||
|
|
/**
|
||
|
|
* @description Formatter
|
||
|
|
* @type {String}
|
||
|
|
* @default formatter = '{value}%'
|
||
|
|
*/
|
||
|
|
formatter: '{value}%'
|
||
|
|
}
|
||
|
|
|
||
|
|
const PercentPond = ({ config = {}, className, style }) => {
|
||
|
|
const domRef = useRef(null)
|
||
|
|
|
||
|
|
const { gradientId1, gradientId2 } = useRef({
|
||
|
|
gradientId1: `percent-pond-gradientId1-${uuid()}`,
|
||
|
|
gradientId2: `percent-pond-gradientId2-${uuid()}`
|
||
|
|
}).current
|
||
|
|
|
||
|
|
const [{ width, height, mergedConfig }, setState] = useState({
|
||
|
|
width: 0,
|
||
|
|
height: 0,
|
||
|
|
|
||
|
|
mergedConfig: null
|
||
|
|
})
|
||
|
|
|
||
|
|
const rectWidth = useMemo(() => {
|
||
|
|
if (!mergedConfig) return 0
|
||
|
|
|
||
|
|
const { borderWidth } = mergedConfig
|
||
|
|
|
||
|
|
return width - borderWidth
|
||
|
|
}, [mergedConfig, width])
|
||
|
|
|
||
|
|
const rectHeight = useMemo(() => {
|
||
|
|
if (!mergedConfig) return 0
|
||
|
|
|
||
|
|
const { borderWidth } = mergedConfig
|
||
|
|
|
||
|
|
return height - borderWidth
|
||
|
|
}, [mergedConfig, height])
|
||
|
|
|
||
|
|
const points = useMemo(() => {
|
||
|
|
const halfHeight = height / 2
|
||
|
|
|
||
|
|
if (!mergedConfig) return `0, ${halfHeight} 0, ${halfHeight}`
|
||
|
|
|
||
|
|
const { borderWidth, borderGap, value } = mergedConfig
|
||
|
|
|
||
|
|
const polylineLength =
|
||
|
|
((width - (borderWidth + borderGap) * 2) / 100) * value
|
||
|
|
|
||
|
|
return `
|
||
|
|
${borderWidth + borderGap}, ${halfHeight}
|
||
|
|
${borderWidth + borderGap + polylineLength}, ${halfHeight + 0.001}
|
||
|
|
`
|
||
|
|
}, [mergedConfig, width, height])
|
||
|
|
|
||
|
|
const polylineWidth = useMemo(() => {
|
||
|
|
if (!mergedConfig) return 0
|
||
|
|
|
||
|
|
const { borderWidth, borderGap } = mergedConfig
|
||
|
|
|
||
|
|
return height - (borderWidth + borderGap) * 2
|
||
|
|
}, [mergedConfig, height])
|
||
|
|
|
||
|
|
const linearGradient = useMemo(() => {
|
||
|
|
if (!mergedConfig) return []
|
||
|
|
|
||
|
|
const { colors } = mergedConfig
|
||
|
|
|
||
|
|
const colorNum = colors.length
|
||
|
|
|
||
|
|
const colorOffsetGap = 100 / (colorNum - 1)
|
||
|
|
|
||
|
|
return colors.map((c, i) => [colorOffsetGap * i, c])
|
||
|
|
}, [mergedConfig])
|
||
|
|
|
||
|
|
const polylineGradient = useMemo(() => {
|
||
|
|
if (!mergedConfig) return gradientId2
|
||
|
|
|
||
|
|
if (mergedConfig.localGradient) return gradientId1
|
||
|
|
|
||
|
|
return gradientId2
|
||
|
|
}, [gradientId1, gradientId2, mergedConfig])
|
||
|
|
|
||
|
|
const gradient2XPos = useMemo(() => {
|
||
|
|
if (!mergedConfig) return '100%'
|
||
|
|
|
||
|
|
const { value } = mergedConfig
|
||
|
|
|
||
|
|
return `${200 - value}%`
|
||
|
|
}, [mergedConfig])
|
||
|
|
|
||
|
|
const details = useMemo(() => {
|
||
|
|
if (!mergedConfig) return ''
|
||
|
|
|
||
|
|
const { value, formatter } = mergedConfig
|
||
|
|
|
||
|
|
return formatter.replace('{value}', value)
|
||
|
|
}, [mergedConfig])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const { clientWidth: width, clientHeight: height } = domRef.current
|
||
|
|
|
||
|
|
setState({
|
||
|
|
width,
|
||
|
|
height,
|
||
|
|
mergedConfig: deepMerge(deepClone(defaultConfig, true), config || {})
|
||
|
|
})
|
||
|
|
}, [config])
|
||
|
|
|
||
|
|
const classNames = useMemo(() => classnames('dv-percent-pond', className), [
|
||
|
|
className
|
||
|
|
])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={classNames} style={style} ref={domRef}>
|
||
|
|
<svg>
|
||
|
|
<defs>
|
||
|
|
<linearGradient id={gradientId1} x1='0%' y1='0%' x2='100%' y2='0%'>
|
||
|
|
{linearGradient.map(lc => (
|
||
|
|
<stop key={lc[0]} offset={`${lc[0]}%`} stopColor={lc[1]} />
|
||
|
|
))}
|
||
|
|
</linearGradient>
|
||
|
|
|
||
|
|
<linearGradient
|
||
|
|
id={gradientId2}
|
||
|
|
x1='0%'
|
||
|
|
y1='0%'
|
||
|
|
x2={gradient2XPos}
|
||
|
|
y2='0%'
|
||
|
|
>
|
||
|
|
{linearGradient.map(lc => (
|
||
|
|
<stop key={lc[0]} offset={`${lc[0]}%`} stopColor={lc[1]} />
|
||
|
|
))}
|
||
|
|
</linearGradient>
|
||
|
|
</defs>
|
||
|
|
<rect
|
||
|
|
x={mergedConfig ? mergedConfig.borderWidth / 2 : '0'}
|
||
|
|
y={mergedConfig ? mergedConfig.borderWidth / 2 : '0'}
|
||
|
|
rx={mergedConfig ? mergedConfig.borderRadius : '0'}
|
||
|
|
ry={mergedConfig ? mergedConfig.borderRadius : '0'}
|
||
|
|
fill='transparent'
|
||
|
|
strokeWidth={mergedConfig ? mergedConfig.borderWidth : '0'}
|
||
|
|
stroke={`url(#${gradientId1})`}
|
||
|
|
width={rectWidth > 0 ? rectWidth : 0}
|
||
|
|
height={rectHeight > 0 ? rectHeight : 0}
|
||
|
|
/>
|
||
|
|
<polyline
|
||
|
|
strokeWidth={polylineWidth}
|
||
|
|
strokeDasharray={mergedConfig ? mergedConfig.lineDash.join(',') : '0'}
|
||
|
|
stroke={`url(#${polylineGradient})`}
|
||
|
|
points={points}
|
||
|
|
/>
|
||
|
|
<text
|
||
|
|
stroke={mergedConfig ? mergedConfig.textColor : '#fff'}
|
||
|
|
fill={mergedConfig ? mergedConfig.textColor : '#fff'}
|
||
|
|
x={width / 2}
|
||
|
|
y={height / 2}
|
||
|
|
>
|
||
|
|
{details}
|
||
|
|
</text>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
PercentPond.propTypes = {
|
||
|
|
config: PropTypes.object,
|
||
|
|
className: PropTypes.string,
|
||
|
|
style: PropTypes.object
|
||
|
|
}
|
||
|
|
|
||
|
|
export default PercentPond
|