@jiaminghi/charts
Version:
Lightweight charting
605 lines (431 loc) • 13.9 kB
JavaScript
import { doUpdate } from '../class/updater.class'
import { gaugeConfig } from '../config/gauge'
import { getCircleRadianPoint } from '@jiaminghi/c-render/lib/plugin/util'
import { deepMerge, initNeedSeries, radianToAngle } from '../util'
import { getRgbaValue } from '@jiaminghi/color'
export function gauge (chart, option = {}) {
let { series } = option
if (!series) series = []
let gauges = initNeedSeries(series, gaugeConfig, 'gauge')
gauges = calcGaugesCenter(gauges, chart)
gauges = calcGaugesRadius(gauges, chart)
gauges = calcGaugesDataRadiusAndLineWidth(gauges, chart)
gauges = calcGaugesDataAngles(gauges, chart)
gauges = calcGaugesDataGradient(gauges, chart)
gauges = calcGaugesAxisTickPosition(gauges, chart)
gauges = calcGaugesLabelPositionAndAlign(gauges, chart)
gauges = calcGaugesLabelData(gauges, chart)
gauges = calcGaugesDetailsPosition(gauges, chart)
gauges = calcGaugesDetailsContent(gauges, chart)
doUpdate({
chart,
series: gauges,
key: 'gaugeAxisTick',
getGraphConfig: getAxisTickConfig
})
doUpdate({
chart,
series: gauges,
key: 'gaugeAxisLabel',
getGraphConfig: getAxisLabelConfig
})
doUpdate({
chart,
series: gauges,
key: 'gaugeBackgroundArc',
getGraphConfig: getBackgroundArcConfig,
getStartGraphConfig: getStartBackgroundArcConfig
})
doUpdate({
chart,
series: gauges,
key: 'gaugeArc',
getGraphConfig: getArcConfig,
getStartGraphConfig: getStartArcConfig,
beforeChange: beforeChangeArc
})
doUpdate({
chart,
series: gauges,
key: 'gaugePointer',
getGraphConfig: getPointerConfig,
getStartGraphConfig: getStartPointerConfig
})
doUpdate({
chart,
series: gauges,
key: 'gaugeDetails',
getGraphConfig: getDetailsConfig
})
}
function calcGaugesCenter (gauges, chart) {
const { area } = chart.render
gauges.forEach(gaugeItem => {
let { center } = gaugeItem
center = center.map((pos, i) => {
if (typeof pos === 'number') return pos
return parseInt(pos) / 100 * area[i]
})
gaugeItem.center = center
})
return gauges
}
function calcGaugesRadius (gauges, chart) {
const { area } = chart.render
const maxRadius = Math.min(...area) / 2
gauges.forEach(gaugeItem => {
let { radius } = gaugeItem
if (typeof radius !== 'number') {
radius = parseInt(radius) / 100 * maxRadius
}
gaugeItem.radius = radius
})
return gauges
}
function calcGaugesDataRadiusAndLineWidth (gauges, chart) {
const { area } = chart.render
const maxRadius = Math.min(...area) / 2
gauges.forEach(gaugeItem => {
const { radius, data, arcLineWidth } = gaugeItem
data.forEach(item => {
let { radius: arcRadius, lineWidth } = item
if (!arcRadius) arcRadius = radius
if (typeof arcRadius !== 'number') arcRadius = parseInt(arcRadius) / 100 * maxRadius
item.radius = arcRadius
if (!lineWidth) lineWidth = arcLineWidth
item.lineWidth = lineWidth
})
})
return gauges
}
function calcGaugesDataAngles (gauges, chart) {
gauges.forEach(gaugeItem => {
const { startAngle, endAngle, data, min, max } = gaugeItem
const angleMinus = endAngle - startAngle
const valueMinus = max - min
data.forEach(item => {
const { value } = item
const itemAngle = Math.abs((value - min) / valueMinus * angleMinus)
item.startAngle = startAngle
item.endAngle = startAngle + itemAngle
})
})
return gauges
}
function calcGaugesDataGradient (gauges, chart) {
gauges.forEach(gaugeItem => {
const { data } = gaugeItem
data.forEach(item => {
let { color, gradient } = item
if (!gradient || !gradient.length) gradient = color
if (!(gradient instanceof Array)) gradient = [gradient]
item.gradient = gradient
})
})
return gauges
}
function calcGaugesAxisTickPosition (gauges, chart) {
gauges.forEach(gaugeItem => {
const { startAngle, endAngle, splitNum, center, radius, arcLineWidth, axisTick } = gaugeItem
const { tickLength, style: { lineWidth } } = axisTick
const angles = endAngle - startAngle
const outerRadius = radius - (arcLineWidth / 2)
const innerRadius = outerRadius - tickLength
const angleGap = angles / (splitNum - 1)
const arcLength = 2 * Math.PI * radius * angles / (Math.PI * 2)
const offset = Math.ceil(lineWidth / 2) / arcLength * angles
gaugeItem.tickAngles = []
gaugeItem.tickInnerRadius = []
gaugeItem.tickPosition = new Array(splitNum).fill(0)
.map((foo, i) => {
let angle = startAngle + angleGap * i
if (i === 0) angle += offset
if (i === splitNum - 1) angle -= offset
gaugeItem.tickAngles[i] = angle
gaugeItem.tickInnerRadius[i] = innerRadius
return [
getCircleRadianPoint(...center, outerRadius, angle),
getCircleRadianPoint(...center, innerRadius, angle)
]
})
})
return gauges
}
function calcGaugesLabelPositionAndAlign (gauges, chart) {
gauges.forEach(gaugeItem => {
const { center, tickInnerRadius, tickAngles, axisLabel: { labelGap } } = gaugeItem
const position = tickAngles.map((angle, i) => getCircleRadianPoint(
...center,
tickInnerRadius[i] - labelGap,
tickAngles[i]
))
const align = position.map(([x, y]) => ({
textAlign: x > center[0] ? 'right' : 'left',
textBaseline: y > center[1] ? 'bottom' : 'top'
}))
gaugeItem.labelPosition = position
gaugeItem.labelAlign = align
})
return gauges
}
function calcGaugesLabelData (gauges, chart) {
gauges.forEach(gaugeItem => {
const { axisLabel, min, max, splitNum } = gaugeItem
let { data, formatter } = axisLabel
const valueGap = (max - min) / (splitNum - 1)
const value = new Array(splitNum).fill(0).map((foo, i) => parseInt(min + valueGap * i))
const formatterType = typeof formatter
data = deepMerge(value, data).map((v, i) => {
let label = v
if (formatterType === 'string') {
label = formatter.replace('{value}', v)
}
if (formatterType === 'function') {
label = formatter({ value: v, index: i })
}
return label
})
axisLabel.data = data
})
return gauges
}
function calcGaugesDetailsPosition (gauges, chart) {
gauges.forEach(gaugeItem => {
const { data, details, center } = gaugeItem
const { position, offset } = details
const detailsPosition = data.map(({ startAngle, endAngle, radius }) => {
let point = null
if (position === 'center') {
point = center
} else if (position === 'start') {
point = getCircleRadianPoint(...center, radius, startAngle)
} else if (position === 'end') {
point = getCircleRadianPoint(...center, radius, endAngle)
}
return getOffsetedPoint(point, offset)
})
gaugeItem.detailsPosition = detailsPosition
})
return gauges
}
function calcGaugesDetailsContent (gauges, chart) {
gauges.forEach(gaugeItem => {
const { data, details } = gaugeItem
const { formatter } = details
const formatterType = typeof formatter
const contents = data.map(dataItem => {
let content = dataItem.value
if (formatterType === 'string') {
content = formatter.replace('{value}', '{nt}')
content = content.replace('{name}', dataItem.name)
}
if (formatterType === 'function') content = formatter(dataItem)
return content.toString()
})
gaugeItem.detailsContent = contents
})
return gauges
}
function getOffsetedPoint ([x, y], [ox, oy]) {
return [x + ox, y + oy]
}
function getAxisTickConfig (gaugeItem) {
const { tickPosition, animationCurve, animationFrame, rLevel } = gaugeItem
return tickPosition.map((foo, i) => ({
name: 'polyline',
index: rLevel,
visible: gaugeItem.axisTick.show,
animationCurve,
animationFrame,
shape: getAxisTickShape(gaugeItem, i),
style: getAxisTickStyle(gaugeItem, i)
}))
}
function getAxisTickShape (gaugeItem, i) {
const { tickPosition } = gaugeItem
return { points: tickPosition[i] }
}
function getAxisTickStyle (gaugeItem, i) {
const { axisTick: { style } } = gaugeItem
return style
}
function getAxisLabelConfig (gaugeItem) {
const { labelPosition, animationCurve, animationFrame, rLevel } = gaugeItem
return labelPosition.map((foo, i) => ({
name: 'text',
index: rLevel,
visible: gaugeItem.axisLabel.show,
animationCurve,
animationFrame,
shape: getAxisLabelShape(gaugeItem, i),
style: getAxisLabelStyle(gaugeItem, i)
}))
}
function getAxisLabelShape (gaugeItem, i) {
const { labelPosition, axisLabel: { data } } = gaugeItem
return {
content: data[i].toString(),
position: labelPosition[i]
}
}
function getAxisLabelStyle (gaugeItem, i) {
const { labelAlign, axisLabel } = gaugeItem
const { style } = axisLabel
return deepMerge({ ...labelAlign[i] }, style)
}
function getBackgroundArcConfig (gaugeItem) {
const { animationCurve, animationFrame, rLevel } = gaugeItem
return [{
name: 'arc',
index: rLevel,
visible: gaugeItem.backgroundArc.show,
animationCurve,
animationFrame,
shape: getGaugeBackgroundArcShape(gaugeItem),
style: getGaugeBackgroundArcStyle(gaugeItem)
}]
}
function getGaugeBackgroundArcShape (gaugeItem) {
let { startAngle, endAngle, center, radius } = gaugeItem
return {
rx: center[0],
ry: center[1],
r: radius,
startAngle,
endAngle
}
}
function getGaugeBackgroundArcStyle (gaugeItem) {
const { backgroundArc, arcLineWidth } = gaugeItem
const { style } = backgroundArc
return deepMerge({ lineWidth: arcLineWidth }, style)
}
function getStartBackgroundArcConfig (gaugeItem) {
const config = getBackgroundArcConfig(gaugeItem)[0]
const shape = { ...config.shape }
shape.endAngle = config.shape.startAngle
config.shape = shape
return [config]
}
function getArcConfig (gaugeItem) {
const { data, animationCurve, animationFrame, rLevel } = gaugeItem
return data.map((foo, i) => ({
name: 'agArc',
index: rLevel,
animationCurve,
animationFrame,
shape: getGaugeArcShape(gaugeItem, i),
style: getGaugeArcStyle(gaugeItem, i)
}))
}
function getGaugeArcShape (gaugeItem, i) {
let { data, center, endAngle: gradientEndAngle } = gaugeItem
const { radius, startAngle, endAngle, localGradient } = data[i]
if (localGradient) gradientEndAngle = endAngle
return {
rx: center[0],
ry: center[1],
r: radius,
startAngle,
endAngle,
gradientEndAngle
}
}
function getGaugeArcStyle (gaugeItem, i) {
const { data, dataItemStyle } = gaugeItem
let { lineWidth, gradient } = data[i]
gradient = gradient.map(c => getRgbaValue(c))
return deepMerge({ lineWidth, gradient }, dataItemStyle)
}
function getStartArcConfig (gaugeItem) {
const configs = getArcConfig(gaugeItem)
configs.map(config => {
const shape = { ...config.shape }
shape.endAngle = config.shape.startAngle
config.shape = shape
})
return configs
}
function beforeChangeArc (graph, config) {
const graphGradient = graph.style.gradient
const cacheNum = graphGradient.length
const needNum = config.style.gradient.length
if (cacheNum > needNum) {
graphGradient.splice(needNum)
} else {
const last = graphGradient.slice(-1)[0]
graphGradient.push(...new Array(needNum - cacheNum).fill(0).map(foo => [...last]))
}
}
function getPointerConfig (gaugeItem) {
const { animationCurve, animationFrame, center, rLevel } = gaugeItem
return [{
name: 'polyline',
index: rLevel,
visible: gaugeItem.pointer.show,
animationCurve,
animationFrame,
shape: getPointerShape(gaugeItem),
style: getPointerStyle(gaugeItem),
setGraphCenter (foo, graph) { graph.style.graphCenter = center }
}]
}
function getPointerShape (gaugeItem) {
const { center } = gaugeItem
return {
points: getPointerPoints(center),
close: true
}
}
function getPointerStyle (gaugeItem) {
const { startAngle, endAngle, min, max, data, pointer, center } = gaugeItem
const { valueIndex, style } = pointer
let value = data[valueIndex] ? data[valueIndex].value : 0
const angle = (value - min) / (max - min) * (endAngle - startAngle) + startAngle + Math.PI / 2
return deepMerge({ rotate: radianToAngle(angle), scale: [1, 1], graphCenter: center }, style)
}
function getPointerPoints ([x, y]) {
const point1 = [x, y - 40]
const point2 = [x + 5, y]
const point3 = [x, y + 10]
const point4 = [x - 5, y]
return [point1, point2, point3, point4]
}
function getStartPointerConfig (gaugeItem) {
const { startAngle } = gaugeItem
const config = getPointerConfig(gaugeItem)[0]
config.style.rotate = radianToAngle(startAngle + Math.PI / 2)
return [config]
}
function getDetailsConfig (gaugeItem) {
const { detailsPosition, animationCurve, animationFrame, rLevel } = gaugeItem
const visible = gaugeItem.details.show
return detailsPosition.map((foo, i) => ({
name: 'numberText',
index: rLevel,
visible,
animationCurve,
animationFrame,
shape: getDetailsShape(gaugeItem, i),
style: getDetailsStyle(gaugeItem, i)
}))
}
function getDetailsShape (gaugeItem, i) {
const { detailsPosition, detailsContent, data, details } = gaugeItem
const position = detailsPosition[i]
const content = detailsContent[i]
const dataValue = data[i].value
const toFixed = details.valueToFixed
return {
number: [dataValue],
content,
position,
toFixed
}
}
function getDetailsStyle (gaugeItem, i) {
const { details, data } = gaugeItem
const { style } = details
const { color } = data[i]
return deepMerge({ fill: color }, style)
}