VChart icon indicating copy to clipboard operation
VChart copied to clipboard

仪表图分区标签防重叠方案

Open skie1997 opened this issue 9 months ago • 0 comments

大屏仪表图配置区间后,区间标签由extensionMark构造,无防重叠效果,寻求优化方案。

Image

可能的方案:

  1. 底层抽象出标签防重叠接口,支持图表库在生成extensionMark后调用
  2. 用轴代替区间标签,但轴要支持自定义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;

skie1997 avatar Mar 06 '25 12:03 skie1997