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

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