VChart
VChart copied to clipboard
仪表图分区标签防重叠方案
大屏仪表图配置区间后,区间标签由extensionMark构造,无防重叠效果,寻求优化方案。
可能的方案:
- 底层抽象出标签防重叠接口,支持图表库在生成extensionMark后调用
- 用轴代替区间标签,但轴要支持自定义tick
Spec
const minValue = 0
const maxValue = 1
const startAngle = -180
const endAngle = -0
const innerRadius = 0.75
const outerRadius = 0.9
const offsetLayoutRadius = 0.2
const offsetRadius = 5
const degreeToRadian = degree => (degree / 180) * Math.PI
const getAngle = (value, min, max, startAngle, endAngle) =>
degreeToRadian(((value - min) / (max - min)) * (endAngle - startAngle) + startAngle)
const getLayoutRadius = vchart => {
const region = vchart.getChart().getRegionsInIndex(0)[0]
const { width, height } = region.getLayoutRect()
return Math.min(width / 2, height / 2)
}
const computeTick = (value, ctx) => {
const { vchart } = ctx
// customMark无法直接获取center, 需要从轴上获取center, 用于tick计算
const getCenter = () => ctx.vchart.getChart().getAllSeries()[0].angleAxisHelper.center()
const layoutRadius = getLayoutRadius(vchart)
const center = getCenter()
const angle = getAngle(value, minValue, maxValue, startAngle, endAngle)
const radius = (innerRadius + offsetLayoutRadius) * layoutRadius + offsetRadius // 后续可以增加配置来判断label放在外侧还是内侧, 目前放在外侧
const x = center.x + radius * Math.cos(angle)
const y = center.y + radius * Math.sin(angle)
return [x, y, angle]
}
const spec = {
"type": "gauge",
"theme": {},
"padding": 15,
"categoryField": "name",
"valueField": "measureValue",
"startAngle": -180,
"endAngle": 0,
"outerRadius": 0.9,
"innerRadius": 0.75,
"data": [
{
"id": "data",
"values": [
{
"current": 0.8138999999999998,
"name": "Sales Proportion",
"emptyName": "",
"percent": "-",
"measurePercent": 0.8138999999999998,
"measureValue": 0.8138999999999998,
"prevMeasureValue": null
}
]
},
{
"id": "sections",
"values": [
{
"gaugeColorKey": 0,
"current": 0.33,
"name": "",
"color": "#006EFF",
"measureValue": 0.33,
"prevMeasureValue": 0
},
{
"gaugeColorKey": 1,
"current": 0.37,
"name": "",
"color": "#f5a623",
"measureValue": 0.37,
"prevMeasureValue": 0.33
},
{
"gaugeColorKey": 2,
"current": 0.6,
"name": "",
"color": "rgb(32,228,228)",
"measureValue": 0.6,
"prevMeasureValue": 0.37
},
{
"gaugeColorKey": 3,
"current": 1,
"name": "",
"color": "rgb(45,226,226)",
"measureValue": 1,
"prevMeasureValue": 0.6
}
]
}
],
"axes": [
{
"visible": true,
"type": "linear",
"orient": "angle",
"inside": true,
"min": 0,
"max": 1,
"innerRadius": 0.9,
"interactive": false,
"label": {
"visible": true,
"inside": true,
"space": 8,
"style": {
"fill": "rgba(255,255,255,0.5)",
"fontSize": 12,
"fontFamily": "D-DIN",
"fontWeight": "normal",
"fillOpacity": 0
},
"autoHideSeparation": 4
},
"tick": {
"visible": false,
"tickSize": 11.700000000000001,
"style": {
"stroke": "rgba(255,255,255,0.3)",
"lineWidth": 1
}
},
"subTick": {
"visible": false,
"tickSize": 4,
"tickCount": 6,
"style": {
"stroke": "rgba(255,255,255,0.3)"
}
},
"grid": {
"visible": false
},
"subGrid": {
"visible": false
},
"ticks": false
}
],
"color": {
"type": "ordinal",
"range": [
"#006EFF",
"#f5a623",
"rgb(32,228,228)",
"rgb(45,226,226)"
],
"domain": [
0,
1,
2,
3
],
"field": "gaugeColorKey"
},
"gauge": {
"type": "gauge",
"seriesField": "gaugeColorKey",
"categoryField": "gaugeColorKey",
"valueField": "current",
"dataIndex": 1,
"progress": {
"style": {
"cornerRadius": 0
}
}
},
"pointer": {
"visible": true,
"interactive": false,
"zIndex": 9999,
"style": {
"fill": "rgb(230,247,255)"
}
},
"pin": {
"visible": false,
"interactive": false,
"width": 0.025,
"height": 0.025,
"style": {
"fill": "rgba(230,247,255,0.3)"
}
},
"pinBackground": {
"visible": true,
"width": 0.08,
"height": 0.08,
"style": {
"fill": "rgba(230,247,255,0.3)"
}
},
"extensionMark": [
{
"type": "text",
"visible": true,
"style": {
"fill": "rgb(255,255,255)",
"fontSize": 12,
"fontFamily": "D-DIN",
"fontWeight": "normal",
x: (datum, ctx) => {
console.log('ctx', ctx, computeTick(0.33, ctx))
return computeTick(0.33, ctx)[0]
},
y: (datum, ctx) => {
return computeTick(0.33, ctx)[1]
},
text: 0.33
}
},
{
"type": "text",
"visible": true,
"dataIndex": 1,
"style": {
"fill": "rgba(255,255,255,0.5)",
"fontSize": 12,
"fontFamily": "D-DIN",
"fontWeight": "normal",
x: (datum, ctx) => {
console.log('ctx', ctx, computeTick(0.33, ctx))
return computeTick(0.37, ctx)[0]
},
y: (datum, ctx) => {
return computeTick(0.37, ctx)[1]
},
text: 0.37
}
}
],
"legends": [
{
"type": "discrete",
"visible": false,
"select": false,
"hover": false,
"zIndex": 500.3568476877166
}
],
"tooltip": {
"visible": true,
"renderMode": "canvas",
"mark": {
"visible": true
},
"style": {
"panel": {
"padding": {
"top": 5,
"bottom": 10,
"left": 10,
"right": 10
},
"backgroundColor": "rgba(8, 28, 48, 0.95)",
"border": {
"color": "#CFCFCF",
"width": 0,
"radius": 2
},
"shadow": {
"x": 0,
"y": 4,
"blur": 12,
"spread": 0,
"color": "rgba(0, 0, 0, 0.2)"
}
},
"titleLabel": {
"fontSize": 14,
"fontColor": "#FFF",
"fontWeight": "bold",
"fontFamily": "D-DIN",
"align": "left",
"lineHeight": 18
},
"keyLabel": {
"fontSize": 12,
"fontColor": "rgba(255,255,255,0.65)",
"fontWeight": "normal",
"fontFamily": "SourceHanSansCN-Normal",
"align": "left",
"lineHeight": 18
},
"valueLabel": {
"fontSize": 12,
"fontColor": "#FFF",
"fontWeight": "normal",
"fontFamily": "D-DIN",
"align": "right",
"lineHeight": 18
},
"shape": {
"size": 10,
"spacing": 10,
"shapeLineWidth": 0
},
"spaceRow": 10
},
"dimension": {
"visible": true
}
},
"background": "rgba(0, 0, 0, 1)",
"crosshair": {
"xField": {
"line": {
"style": {
"fillOpacity": 1,
"fill": "rgba(80,156,255,0.1)"
}
},
"visible": false
},
"yField": {
"line": {
"style": {
"fillOpacity": 1,
"fill": "rgba(80,156,255,0.1)"
}
},
"visible": false
}
},
"morph": {
"enable": false
},
"plotLayout": {
"clip": false
},
"select": {
"enable": true
},
"series": [
{
"type": "gauge",
"angleField": "measureValue",
"outerRadius": 0.9,
"innerRadius": 0.75,
"padAngle": 0,
"seriesField": "gaugeColorKey",
"categoryField": "gaugeColorKey",
"dataIndex": 1,
"segment": {
"style": {
"cornerRadius": 0
}
},
"animation": false,
"animationAppear": {
"segment": {
"loop": false,
"duration": 1000,
"easing": "circInOut"
}
},
"animationNormal": {
"segment": {
"loop": true,
"startTime": 5000,
"delayAfter": 5000,
"duration": 1000,
"easing": "circInOut",
"controlOptions": {
"immediatelyApply": false
},
"channel": {
"startAngle": {},
"endAngle": {}
}
}
},
"animationEnter": {
"duration": 1000,
"easing": "circInOut"
},
"animationUpdate": {
"duration": 1000,
"easing": "circInOut"
},
"animationExit": {}
},
{
"type": "gaugePointer",
"valueField": "measureValue",
"innerRadius": 0,
"pin": {
"visible": false,
"interactive": false,
"width": 0.025,
"height": 0.025,
"style": {
"fill": "rgba(230,247,255,0.3)"
}
},
"pinBackground": {
"visible": true,
"width": 0.08,
"height": 0.08,
"style": {
"fill": "rgba(230,247,255,0.3)"
}
},
"pointer": {
"visible": true,
"interactive": false,
"zIndex": 9999,
"style": {
"fill": "rgb(230,247,255)"
},
"width": 0.4,
"height": 0.3
},
"animation": false,
"animationAppear": {
"segment": {
"loop": false,
"duration": 1000,
"easing": "circInOut"
}
},
"animationNormal": {
"segment": {
"loop": true,
"startTime": 5000,
"delayAfter": 5000,
"duration": 1000,
"easing": "circInOut",
"controlOptions": {
"immediatelyApply": false
},
"channel": {
"startAngle": {},
"endAngle": {}
}
}
},
"animationEnter": {
"duration": 1000,
"easing": "circInOut"
},
"animationUpdate": {
"duration": 1000,
"easing": "circInOut"
},
"animationExit": {}
}
],
"hash": "77536063193c2899c95726c3d8c2490f",
"width": 400,
"height": 186,
"animation": false
}
const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();
// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;