mirror of
https://github.com/dataease/dataease.git
synced 2026-06-16 20:42:07 +08:00
feat(图表): 支持柱线组合图
This commit is contained in:
@@ -380,6 +380,51 @@ declare interface ChartBasicStyle {
|
||||
* 圆形填充图间距
|
||||
*/
|
||||
circlePadding: number
|
||||
/**
|
||||
* 副值轴透明度
|
||||
*/
|
||||
subAlpha: number
|
||||
/**
|
||||
* 副值轴配色方案
|
||||
*/
|
||||
subColorScheme: string
|
||||
/**
|
||||
* 副值轴系列颜色
|
||||
*/
|
||||
subSeriesColor: {
|
||||
/**
|
||||
* 序列识别id,多指标就是轴id,分组或者堆叠就是类别值
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* 显示名称
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* 序列颜色
|
||||
*/
|
||||
color: string
|
||||
}[]
|
||||
/**
|
||||
* 副值轴颜色
|
||||
*/
|
||||
subColors: string[]
|
||||
/**
|
||||
* 副轴线条宽度
|
||||
*/
|
||||
leftLineWidth: number
|
||||
/**
|
||||
* 副轴线条形状
|
||||
*/
|
||||
leftLineSymbol: string
|
||||
/**
|
||||
* 副轴折点大小
|
||||
*/
|
||||
leftLineSymbolSize: number
|
||||
/**
|
||||
* 副轴平滑折线开关
|
||||
*/
|
||||
leftLineSmooth: boolean
|
||||
}
|
||||
/**
|
||||
* 表头属性
|
||||
|
||||
@@ -1724,7 +1724,25 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
|
||||
circleBorderWidth: 0,
|
||||
circlePadding: 0,
|
||||
quotaPosition: 'col',
|
||||
quotaColLabel: t('dataset.value')
|
||||
quotaColLabel: t('dataset.value'),
|
||||
subAlpha: 100,
|
||||
subColorScheme: 'fast',
|
||||
subSeriesColor: [],
|
||||
subColors: [
|
||||
'#fae800',
|
||||
'#00c039',
|
||||
'#0482dc',
|
||||
'#bb9581',
|
||||
'#ff7701',
|
||||
'#9c5ec3',
|
||||
'#00ccdf',
|
||||
'#00c039',
|
||||
'#ff7701'
|
||||
],
|
||||
leftLineWidth: 2,
|
||||
leftLineSymbol: 'circle',
|
||||
leftLineSymbolSize: 4,
|
||||
leftLineSmooth: true
|
||||
}
|
||||
|
||||
export const BASE_VIEW_CONFIG = {
|
||||
|
||||
@@ -450,7 +450,7 @@ export class BidirectionalHorizontalBar extends G2ChartView {
|
||||
|
||||
protected configYAxis(chart: Chart, options: G2Spec): G2Spec {
|
||||
const [firstMark, secondMark] = options.children[0].children
|
||||
const { xAxis, yAxis, yAxisExt } = parseJson(chart.customStyle)
|
||||
const { yAxis, yAxisExt } = parseJson(chart.customStyle)
|
||||
const { basicStyle } = parseJson(chart.customAttr)
|
||||
if (!yAxis.show) {
|
||||
firstMark.axis.y = false
|
||||
@@ -538,55 +538,6 @@ export class BidirectionalHorizontalBar extends G2ChartView {
|
||||
return options
|
||||
}
|
||||
|
||||
private getAxis(axis: DeepPartial<ChartAxisStyle>): AxisComponent {
|
||||
let lineLineDash = undefined
|
||||
if (axis.axisLine.lineStyle.style === 'dashed') {
|
||||
lineLineDash = [10, 8]
|
||||
}
|
||||
if (axis.axisLine.lineStyle.style === 'dotted') {
|
||||
lineLineDash = [1, 2]
|
||||
}
|
||||
let gridLineDash = [0, 0]
|
||||
if (axis.splitLine.lineStyle.style === 'dashed') {
|
||||
gridLineDash = [10, 8]
|
||||
}
|
||||
if (axis.splitLine.lineStyle.style === 'dotted') {
|
||||
gridLineDash = [1, 2]
|
||||
}
|
||||
const axisOption = {
|
||||
position: axis.position,
|
||||
title: axis.nameShow === false ? false : isEmpty(axis.name) ? false : axis.name,
|
||||
titleFontSize: axis.fontSize,
|
||||
titleFill: axis.color,
|
||||
line: axis.axisLine.show,
|
||||
lineStroke: axis.axisLine.lineStyle.color,
|
||||
lineStrokeOpacity: 1,
|
||||
lineLineWidth: axis.axisLine.lineStyle.width,
|
||||
lineLineDash,
|
||||
label: axis.axisLabel.show,
|
||||
labelFill: axis.axisLabel.color,
|
||||
labelFillOpacity: 1,
|
||||
labelFontSize: axis.axisLabel.fontSize,
|
||||
grid: axis.splitLine.show,
|
||||
gridStroke: axis.splitLine.lineStyle.color,
|
||||
gridStrokeOpacity: 1,
|
||||
gridLineWidth: axis.splitLine.lineStyle.width,
|
||||
gridLineDash,
|
||||
labelTransform: `rotate(${axis.axisLabel.rotate || 0})`,
|
||||
transform: [
|
||||
{
|
||||
type: 'hide',
|
||||
keepHeader: true,
|
||||
keepTail: true
|
||||
}
|
||||
],
|
||||
labelFormatter: d => {
|
||||
return valueFormatter(d, axis.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
return axisOption
|
||||
}
|
||||
|
||||
protected configTooltip(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { tooltip: tooltipAttr, basicStyle } = parseJson(chart.customAttr)
|
||||
const { yAxis, yAxisExt } = chart
|
||||
|
||||
@@ -277,12 +277,15 @@ export class Area extends G2ChartView {
|
||||
const pointStyleOpt = {
|
||||
encode: {
|
||||
shape: basicStyle.lineSymbol,
|
||||
size: basicStyle.lineSymbolSize
|
||||
size: basicStyle.lineSymbolSize ? basicStyle.lineSymbolSize : 0.01
|
||||
},
|
||||
style: {
|
||||
opacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
fillOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
strokeOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
lineWidth: 0
|
||||
}
|
||||
}
|
||||
if (basicStyle.lineSymbolSize === 0) {
|
||||
pointStyleOpt.encode.shape = 'none'
|
||||
}
|
||||
defaultsDeep(pointMark, pointStyleOpt)
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -269,12 +269,15 @@ export class Line extends G2ChartView {
|
||||
const pointStyleOpt = {
|
||||
encode: {
|
||||
shape: basicStyle.lineSymbol,
|
||||
size: basicStyle.lineSymbolSize
|
||||
size: basicStyle.lineSymbolSize ? basicStyle.lineSymbolSize : 0.01
|
||||
},
|
||||
style: {
|
||||
opacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
fillOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
strokeOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
lineWidth: 0
|
||||
}
|
||||
}
|
||||
if (basicStyle.lineSymbolSize === 0) {
|
||||
pointStyleOpt.encode.shape = 'none'
|
||||
}
|
||||
defaultsDeep(pointMark, pointStyleOpt)
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -0,0 +1,808 @@
|
||||
import { G2ChartView, G2DrawOptions } from '../../../types/impl/g2'
|
||||
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
|
||||
import { cloneDeep, defaultsDeep, isEmpty, merge } from 'lodash-es'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Chart as G2Chart, G2Spec } from '@antv/g2'
|
||||
import {
|
||||
DEFAULT_BASIC_STYLE,
|
||||
DEFAULT_YAXIS_STYLE
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { setGradientColor, TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '../../../common/common_antv'
|
||||
|
||||
const { t } = useI18n()
|
||||
/**
|
||||
* 柱线混合图
|
||||
*/
|
||||
export class ColumnLineMix extends G2ChartView {
|
||||
properties: EditorProperty[] = [
|
||||
'background-overall-component',
|
||||
'border-style',
|
||||
'dual-basic-style-selector',
|
||||
'x-axis-selector',
|
||||
'dual-y-axis-selector',
|
||||
'title-selector',
|
||||
'legend-selector',
|
||||
'label-selector',
|
||||
'tooltip-selector',
|
||||
'assist-line',
|
||||
'function-cfg',
|
||||
'jump-set',
|
||||
'linkage'
|
||||
]
|
||||
propertyInner: EditorPropertyInner = {
|
||||
'background-overall-component': ['all'],
|
||||
'border-style': ['all'],
|
||||
'label-selector': ['vPosition', 'seriesLabelFormatter'],
|
||||
'tooltip-selector': [
|
||||
'fontSize',
|
||||
'color',
|
||||
'backgroundColor',
|
||||
'show',
|
||||
'seriesTooltipFormatter',
|
||||
'carousel'
|
||||
],
|
||||
'dual-basic-style-selector': [
|
||||
'colors',
|
||||
'alpha',
|
||||
'gradient',
|
||||
'lineWidth',
|
||||
'lineSymbol',
|
||||
'lineSymbolSize',
|
||||
'lineSmooth',
|
||||
'radiusColumnBar',
|
||||
'subSeriesColor',
|
||||
'seriesColor',
|
||||
'columnWidthRatio'
|
||||
],
|
||||
'x-axis-selector': [
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'position',
|
||||
'axisLabel',
|
||||
'axisLine',
|
||||
'splitLine'
|
||||
],
|
||||
'dual-y-axis-selector': [
|
||||
'name',
|
||||
'color',
|
||||
'fontSize',
|
||||
'axisLabel',
|
||||
'axisLine',
|
||||
'splitLine',
|
||||
'axisValue',
|
||||
'axisLabelFormatter'
|
||||
],
|
||||
'title-selector': [
|
||||
'title',
|
||||
'fontSize',
|
||||
'color',
|
||||
'hPosition',
|
||||
'isItalic',
|
||||
'isBolder',
|
||||
'remarkShow',
|
||||
'fontFamily',
|
||||
'letterSpace',
|
||||
'fontShadow'
|
||||
],
|
||||
'legend-selector': ['icon', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition'],
|
||||
'function-cfg': ['emptyDataStrategy']
|
||||
}
|
||||
|
||||
axis: AxisType[] = [
|
||||
'xAxis',
|
||||
'yAxis',
|
||||
'drill',
|
||||
'filter',
|
||||
'extLabel',
|
||||
'extTooltip',
|
||||
'xAxisExtRight',
|
||||
'yAxisExt'
|
||||
]
|
||||
|
||||
axisConfig: AxisConfig = {
|
||||
xAxis: {
|
||||
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
|
||||
type: 'd'
|
||||
},
|
||||
yAxis: {
|
||||
name: `${t('chart.drag_block_value_axis_left')} / ${t('chart.column_quota')}`,
|
||||
limit: 1,
|
||||
type: 'q'
|
||||
},
|
||||
extBubble: {
|
||||
//用这个字段存放右轴分类
|
||||
name: `${t('chart.drag_block_type_axis_right')} / ${t('chart.dimension')}`,
|
||||
limit: 1,
|
||||
type: 'd',
|
||||
allowEmpty: true
|
||||
},
|
||||
yAxisExt: {
|
||||
name: `${t('chart.drag_block_value_axis_right')} / ${t('chart.line_quota')}`,
|
||||
limit: 1,
|
||||
type: 'q',
|
||||
allowEmpty: true
|
||||
}
|
||||
}
|
||||
|
||||
protected getLeftType(): string {
|
||||
return 'column'
|
||||
}
|
||||
protected getRightType(): string {
|
||||
return 'line'
|
||||
}
|
||||
|
||||
async drawChart(drawOptions: G2DrawOptions<G2Chart>): Promise<G2Chart> {
|
||||
const { chart, action, container } = drawOptions
|
||||
chart.container = container
|
||||
if (!chart.data?.left?.data?.length && !chart.data?.right?.data?.length) {
|
||||
return
|
||||
}
|
||||
const [left] = cloneDeep(chart.data?.left?.data)
|
||||
const [right] = cloneDeep(chart.data?.right?.data)
|
||||
|
||||
// options
|
||||
const initOptions: G2Spec = {
|
||||
type: 'view',
|
||||
autoFit: true,
|
||||
children: [
|
||||
{
|
||||
type: 'interval',
|
||||
data: left.data,
|
||||
encode: {
|
||||
x: 'field',
|
||||
y: 'value',
|
||||
color: {
|
||||
type: 'transform',
|
||||
value: () => chart.yAxis[0]?.chartShowName ?? chart.yAxis[0]?.name
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
y: {
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
scale: {
|
||||
y: {
|
||||
key: 'left',
|
||||
independent: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
data: right.data,
|
||||
encode: {
|
||||
x: 'field',
|
||||
y: 'value',
|
||||
series: 'category',
|
||||
color: 'category'
|
||||
},
|
||||
scale: {
|
||||
y: {
|
||||
key: 'right',
|
||||
independent: true
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
y: {
|
||||
position: 'right'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'point',
|
||||
data: right.data,
|
||||
encode: {
|
||||
x: 'field',
|
||||
y: 'value',
|
||||
color: 'category'
|
||||
},
|
||||
axis: {
|
||||
y: false
|
||||
},
|
||||
scale: {
|
||||
y: {
|
||||
key: 'right'
|
||||
}
|
||||
},
|
||||
tooltip: false
|
||||
}
|
||||
]
|
||||
}
|
||||
const newChart = new G2Chart({ container })
|
||||
const options = this.setupOptions(chart, initOptions, { chartObj: newChart })
|
||||
|
||||
newChart.on('point:click', action)
|
||||
newChart.on('interval:click', action)
|
||||
newChart.options(options)
|
||||
// extremumEvt(newChart, chart, options, container)
|
||||
// configPlotTooltipEvent(chart, newChart)
|
||||
return newChart
|
||||
}
|
||||
|
||||
protected configBasicStyle(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { basicStyle } = parseJson(chart.customAttr)
|
||||
let leftColor = hexColorToRGBA(basicStyle.colors?.[0], basicStyle.alpha)
|
||||
const leftSeriesMap = basicStyle.seriesColor?.find(c => c.id === chart.yAxis[0]?.id)
|
||||
if (leftSeriesMap) {
|
||||
leftColor = hexColorToRGBA(leftSeriesMap.color, basicStyle.alpha)
|
||||
}
|
||||
merge(options, {
|
||||
scale: {
|
||||
color: {
|
||||
type: 'ordinal',
|
||||
relations: [[chart.yAxis[0]?.chartShowName ?? chart.yAxis[0]?.name, leftColor]]
|
||||
}
|
||||
}
|
||||
})
|
||||
if (basicStyle.subSeriesColor?.length) {
|
||||
const { yAxisExt, extBubble } = chart
|
||||
const relations = [options.scale?.color?.relations?.[0]]
|
||||
if (extBubble?.length) {
|
||||
basicStyle.subSeriesColor.reduce((acc, cur) => {
|
||||
acc[cur.id] = cur.color
|
||||
return acc
|
||||
}, {})
|
||||
basicStyle.subSeriesColor.forEach(c =>
|
||||
relations.push([c.id, hexColorToRGBA(c.color, basicStyle.subAlpha)])
|
||||
)
|
||||
} else {
|
||||
const rightColor = basicStyle.subSeriesColor.find(c => c.id === yAxisExt[0]?.id)?.color
|
||||
if (rightColor) {
|
||||
relations.push([
|
||||
yAxisExt[0]?.chartShowName ?? yAxisExt[0]?.name,
|
||||
hexColorToRGBA(rightColor, basicStyle.subAlpha)
|
||||
])
|
||||
}
|
||||
}
|
||||
merge(options, {
|
||||
scale: {
|
||||
color: {
|
||||
relations
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const colors = basicStyle.subColors.map(c => hexColorToRGBA(c, basicStyle.subAlpha))
|
||||
merge(options, {
|
||||
scale: {
|
||||
color: {
|
||||
range: colors
|
||||
}
|
||||
}
|
||||
})
|
||||
const [intervalMark, lineMark, pointMark] = options.children
|
||||
if (basicStyle.gradient) {
|
||||
leftColor = setGradientColor(leftColor, true, 270)
|
||||
}
|
||||
merge(intervalMark, {
|
||||
style: {
|
||||
fill: leftColor,
|
||||
columnWidthRatio: basicStyle.columnWidthRatio / 100
|
||||
}
|
||||
})
|
||||
if (basicStyle.radiusColumnBar === 'roundAngle') {
|
||||
merge(intervalMark, {
|
||||
style: {
|
||||
radius: 20
|
||||
}
|
||||
})
|
||||
}
|
||||
if (basicStyle.radiusColumnBar === 'topRoundAngle') {
|
||||
merge(intervalMark, {
|
||||
style: {
|
||||
radiusTopLeft: 20,
|
||||
radiusTopRight: 20
|
||||
}
|
||||
})
|
||||
}
|
||||
merge(lineMark, {
|
||||
style: {
|
||||
lineWidth: basicStyle.lineWidth
|
||||
},
|
||||
encode: {
|
||||
shape: basicStyle.lineSmooth ? 'smooth' : 'line'
|
||||
}
|
||||
})
|
||||
merge(pointMark, {
|
||||
encode: {
|
||||
shape: basicStyle.lineSymbol,
|
||||
size: basicStyle.lineSymbolSize ? basicStyle.lineSymbolSize : 0.01
|
||||
},
|
||||
style: {
|
||||
opacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
fillOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
strokeOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1,
|
||||
lineWidth: 0
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
|
||||
protected configLegend(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { legend } = parseJson(chart.customStyle)
|
||||
if (!legend.show) {
|
||||
return { ...options, legend: false }
|
||||
}
|
||||
const baseLegend = this.getLegend(chart)
|
||||
const tmpLegend = {
|
||||
legend: {
|
||||
color: {
|
||||
...baseLegend,
|
||||
itemMarkerSize: legend.size,
|
||||
itemMarker: legend.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
defaultsDeep(options, tmpLegend)
|
||||
return options
|
||||
}
|
||||
|
||||
protected configLabel(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { label } = parseJson(chart.customAttr)
|
||||
if (!label.show) {
|
||||
return options
|
||||
}
|
||||
const seriesMap = label.seriesLabelFormatter?.reduce((acc, cur) => {
|
||||
acc[cur.id] = cur
|
||||
return acc
|
||||
}, {})
|
||||
const labelOpt = {
|
||||
labels: [
|
||||
{
|
||||
text: d => {
|
||||
if (!label.seriesLabelFormatter?.length) {
|
||||
return d.value
|
||||
}
|
||||
const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter
|
||||
if (!labelCfg) {
|
||||
return d.value
|
||||
}
|
||||
if (!labelCfg.show) {
|
||||
return ''
|
||||
}
|
||||
return valueFormatter(d.value, labelCfg.formatterCfg)
|
||||
},
|
||||
style: {
|
||||
fillOpacity: 1,
|
||||
fontSize: d => {
|
||||
if (!label.seriesLabelFormatter?.length) {
|
||||
return 12
|
||||
}
|
||||
const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter
|
||||
if (!labelCfg) {
|
||||
return 12
|
||||
}
|
||||
if (!labelCfg.show) {
|
||||
return 0
|
||||
}
|
||||
return labelCfg.fontSize
|
||||
},
|
||||
fill: d => {
|
||||
if (!label.seriesLabelFormatter?.length) {
|
||||
return 'black'
|
||||
}
|
||||
const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter
|
||||
if (!labelCfg?.show) {
|
||||
return 'black'
|
||||
}
|
||||
return labelCfg.color
|
||||
},
|
||||
position: label.position === 'middle' ? 'inside' : label.position
|
||||
},
|
||||
textBaseline: {
|
||||
top: 'bottom',
|
||||
middle: 'middle',
|
||||
bottom: 'top'
|
||||
}[label.position],
|
||||
transform: label.fullDisplay
|
||||
? [{ type: 'exceedAdjust' }]
|
||||
: [{ type: 'overlapHide' }, { type: 'overlapDodgeY' }, { type: 'exceedAdjust' }],
|
||||
fontFamily: chart.fontFamily
|
||||
}
|
||||
]
|
||||
}
|
||||
const [intervalMark, _, pointMark] = options.children
|
||||
if (!label.seriesLabelFormatter?.length) {
|
||||
defaultsDeep(intervalMark, labelOpt)
|
||||
defaultsDeep(pointMark, labelOpt)
|
||||
} else {
|
||||
const showLeft = label.seriesLabelFormatter.some(c => c.id === chart.yAxis[0]?.id && c.show)
|
||||
const showRight = label.seriesLabelFormatter.some(
|
||||
c => c.id === chart.yAxisExt[0]?.id && c.show
|
||||
)
|
||||
if (showLeft) {
|
||||
defaultsDeep(intervalMark, labelOpt)
|
||||
}
|
||||
if (showRight) {
|
||||
defaultsDeep(pointMark, labelOpt)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
protected configTooltip(chart: Chart, options: G2Spec, context: Record<string, any>): G2Spec {
|
||||
const { tooltip } = parseJson(chart.customAttr)
|
||||
const [intervalMark, lineMark] = options.children
|
||||
if (!tooltip.show) {
|
||||
defaultsDeep(intervalMark, { tooltip: false })
|
||||
defaultsDeep(lineMark, { tooltip: false })
|
||||
return options
|
||||
}
|
||||
const chartObj = context.chartObj as G2Chart
|
||||
const formatterMap = tooltip.seriesTooltipFormatter
|
||||
?.filter(i => i.show)
|
||||
.reduce((pre, next) => {
|
||||
pre[next.id] = next
|
||||
return pre
|
||||
}, {}) as Record<string, SeriesFormatter>
|
||||
let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER')
|
||||
if (!g2TooltipWrapper) {
|
||||
g2TooltipWrapper = document.createElement('div')
|
||||
g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER'
|
||||
g2TooltipWrapper.style.position = 'absolute'
|
||||
g2TooltipWrapper.style.pointerEvents = 'none'
|
||||
g2TooltipWrapper.style.zIndex = '9999'
|
||||
document.body.appendChild(g2TooltipWrapper)
|
||||
}
|
||||
const yAxis = chart.yAxis
|
||||
const tooltipOptions: G2Spec = {
|
||||
tooltip: d => d,
|
||||
interaction: {
|
||||
tooltip: {
|
||||
crosshairsLineDash: [4, 4],
|
||||
mount: g2TooltipWrapper,
|
||||
css: {
|
||||
'.g2-tooltip': {
|
||||
background: tooltip.backgroundColor
|
||||
},
|
||||
'.g2-tooltip-title': {
|
||||
color: tooltip.color,
|
||||
'font-size': `${tooltip.fontSize}px`
|
||||
},
|
||||
'.g2-tooltip-list-item-name-label': {
|
||||
color: tooltip.color,
|
||||
'font-size': `${tooltip.fontSize}px`
|
||||
},
|
||||
'.g2-tooltip-list-item-value': {
|
||||
color: tooltip.color,
|
||||
'font-size': `${tooltip.fontSize}px`
|
||||
}
|
||||
},
|
||||
render: (_, { title, items }) => {
|
||||
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
|
||||
if (tooltip.seriesTooltipFormatter?.length) {
|
||||
items = items.filter(i => formatterMap[i.quotaList[0].id])
|
||||
}
|
||||
const result = []
|
||||
const [view] = chartObj.getContext().views
|
||||
items.forEach(item => {
|
||||
const formatterCfg =
|
||||
formatterMap[item.quotaList[0].id]?.formatterCfg ?? yAxis[0].formatterCfg
|
||||
const value = valueFormatter(item.value, formatterCfg)
|
||||
const color = view.scale.color.map(item.category) ?? item.color
|
||||
const name = item.category
|
||||
result.push({ value, color, name })
|
||||
})
|
||||
const itemsHtml = result
|
||||
.map(item => {
|
||||
const marker = item.color
|
||||
const label = item.name
|
||||
const value = item.value
|
||||
return TOOLTIP_ITEM_TPL.replace('{marker}', marker)
|
||||
.replace('{label}', label)
|
||||
.replace('{value}', value)
|
||||
})
|
||||
.join('')
|
||||
const listHtml = `<ul class="g2-tooltip-list" style="margin: 0px; list-style-type: none; padding: 0px;">${itemsHtml}</ul>`
|
||||
return `${titleHtml}${listHtml}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
defaultsDeep(lineMark, tooltipOptions)
|
||||
defaultsDeep(intervalMark, { tooltip: d => d })
|
||||
return options
|
||||
}
|
||||
|
||||
protected configXAxis(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { xAxis } = parseJson(chart.customStyle)
|
||||
if (!xAxis.show) {
|
||||
const axisHide = {
|
||||
axis: {
|
||||
x: false
|
||||
}
|
||||
}
|
||||
return defaultsDeep(options, axisHide)
|
||||
}
|
||||
let lineLineDash = undefined
|
||||
if (xAxis.axisLine.lineStyle.style === 'dashed') {
|
||||
lineLineDash = [10, 8]
|
||||
}
|
||||
if (xAxis.axisLine.lineStyle.style === 'dotted') {
|
||||
lineLineDash = [1, 2]
|
||||
}
|
||||
let gridLineDash = undefined
|
||||
if (xAxis.splitLine.lineStyle.style === 'dashed') {
|
||||
gridLineDash = [10, 8]
|
||||
}
|
||||
if (xAxis.splitLine.lineStyle.style === 'dotted') {
|
||||
gridLineDash = [1, 2]
|
||||
}
|
||||
const axisStyle = {
|
||||
axis: {
|
||||
x: {
|
||||
position: xAxis.position,
|
||||
title: xAxis.nameShow === false || isEmpty(xAxis.name) ? false : xAxis.name,
|
||||
titleFontSize: xAxis.fontSize,
|
||||
titleFill: xAxis.color,
|
||||
line: xAxis.axisLine.show,
|
||||
lineStroke: xAxis.axisLine.lineStyle.color,
|
||||
lineStrokeOpacity: 1,
|
||||
lineLineWidth: xAxis.axisLine.lineStyle.width,
|
||||
lineLineDash,
|
||||
label: xAxis.axisLabel.show,
|
||||
labelFill: xAxis.axisLabel.color,
|
||||
labelFillOpacity: 1,
|
||||
labelFontSize: xAxis.axisLabel.fontSize,
|
||||
grid: xAxis.splitLine.show,
|
||||
gridStroke: xAxis.splitLine.lineStyle.color,
|
||||
gridStrokeOpacity: 1,
|
||||
gridLineWidth: xAxis.splitLine.lineStyle.width,
|
||||
gridLineDash,
|
||||
transform: xAxis.axisLabel.rotate
|
||||
? [
|
||||
{
|
||||
type: 'rotate',
|
||||
optionalAngles: [xAxis.axisLabel.rotate],
|
||||
recoverWhenFailed: false
|
||||
}
|
||||
]
|
||||
: []
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultsDeep(options, axisStyle)
|
||||
}
|
||||
|
||||
protected configYAxis(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { yAxis, yAxisExt } = parseJson(chart.customStyle)
|
||||
const [intervalMark, lineMark, pointMark] = options.children
|
||||
if (!yAxis.show) {
|
||||
intervalMark.axis.y = false
|
||||
lineMark.axis.y = false
|
||||
return options
|
||||
}
|
||||
const yAxisOption = this.getAxis(yAxis)
|
||||
const yAxisExtOption = this.getAxis(yAxisExt)
|
||||
merge(intervalMark, {
|
||||
axis: {
|
||||
y: {
|
||||
...yAxisOption,
|
||||
position: 'left'
|
||||
}
|
||||
}
|
||||
})
|
||||
merge(lineMark, {
|
||||
axis: {
|
||||
y: {
|
||||
...yAxisExtOption,
|
||||
position: 'right'
|
||||
}
|
||||
}
|
||||
})
|
||||
if (yAxis.axisValue.auto === false) {
|
||||
merge(intervalMark, {
|
||||
scale: {
|
||||
y: {
|
||||
domain: [yAxis.axisValue.min, yAxis.axisValue.max]
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
y: {
|
||||
tickCount: yAxis.axisValue.splitCount,
|
||||
tickMethod: (min, max, count) => {
|
||||
const step = (max - min) / (count - 1)
|
||||
const ticks = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
ticks.push(min + i * step)
|
||||
}
|
||||
return ticks
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (yAxisExt.axisValue.auto === false) {
|
||||
const scaleOpt = {
|
||||
scale: {
|
||||
y: {
|
||||
independent: true,
|
||||
domain: [yAxisExt.axisValue.min, yAxisExt.axisValue.max]
|
||||
}
|
||||
}
|
||||
}
|
||||
merge(lineMark, scaleOpt, {
|
||||
axis: {
|
||||
y: {
|
||||
tickCount: yAxisExt.axisValue.splitCount,
|
||||
tickMethod: (min, max, count) => {
|
||||
const step = (max - min) / (count - 1)
|
||||
const ticks = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
ticks.push(min + i * step)
|
||||
}
|
||||
return ticks
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
merge(pointMark, scaleOpt)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
protected configAssistLine(chart: Chart, options: G2Spec): G2Spec {
|
||||
const { assistLineCfg } = parseJson(chart.senior)
|
||||
if (!assistLineCfg.enable || !assistLineCfg.assistLine?.length) {
|
||||
return options
|
||||
}
|
||||
const splitLineData = [[], []]
|
||||
const splitDynamicFields = [[], []]
|
||||
assistLineCfg.assistLine?.forEach(item => {
|
||||
const lineData = splitLineData[item.yAxisType === 'left' ? 0 : 1]
|
||||
const dynamicFields = splitDynamicFields[item.yAxisType === 'left' ? 0 : 1]
|
||||
// 固定值
|
||||
if (item.field === '0') {
|
||||
lineData.push({ ...item, value: parseFloat(item.value) })
|
||||
}
|
||||
// 动态值
|
||||
if (item.field === '1') {
|
||||
dynamicFields.push(item.fieldId)
|
||||
}
|
||||
})
|
||||
const assistLineData = [
|
||||
...(chart.data.left.dynamicAssistLines ?? []),
|
||||
...(chart.data.right.dynamicAssistLines ?? [])
|
||||
]
|
||||
assistLineData.forEach(d => {
|
||||
const fields = d.yAxisType === 'left' ? splitDynamicFields[0] : splitDynamicFields[1]
|
||||
if (fields.includes(d.fieldId)) {
|
||||
splitLineData[d.yAxisType === 'left' ? 0 : 1].push({ ...d, value: parseFloat(d.value) })
|
||||
}
|
||||
})
|
||||
const { yAxis, yAxisExt } = parseJson(chart.customStyle)
|
||||
const yAxisFormatterCfg = yAxis.axisLabelFormatter ?? DEFAULT_YAXIS_STYLE.axisLabelFormatter
|
||||
const yAxisExtFormatterCfg =
|
||||
yAxisExt.axisLabelFormatter ?? DEFAULT_YAXIS_STYLE.axisLabelFormatter
|
||||
splitLineData.forEach((lineData, index) => {
|
||||
if (lineData.length) {
|
||||
const assistLineMark: G2Spec = {
|
||||
type: 'lineY',
|
||||
encode: { y: 'value' },
|
||||
scale: {
|
||||
y: {
|
||||
key: index === 0 ? 'left' : 'right'
|
||||
}
|
||||
},
|
||||
axis: {
|
||||
y: false
|
||||
},
|
||||
data: lineData,
|
||||
style: {
|
||||
stroke: d => d.color,
|
||||
lineDash: d =>
|
||||
d.lineType === 'solid' ? [] : d.lineType === 'dashed' ? [10, 8] : [1, 2],
|
||||
opacity: 1
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: d => {
|
||||
const value = valueFormatter(
|
||||
parseFloat(d.value),
|
||||
index === 0 ? yAxisFormatterCfg : yAxisExtFormatterCfg
|
||||
)
|
||||
return d.name ? `${d.name}: ${value}` : value
|
||||
},
|
||||
style: {
|
||||
fontSize: d => parseInt(d.fontSize),
|
||||
fill: d => d.color,
|
||||
fillOpacity: 1
|
||||
},
|
||||
textBaseline: 'bottom',
|
||||
position: index === 0 ? 'left' : 'right',
|
||||
transform: [{ type: 'overlapHide' }, { type: 'exceedAdjust' }],
|
||||
fontFamily: chart.fontFamily
|
||||
}
|
||||
]
|
||||
}
|
||||
options.children.push(assistLineMark)
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
|
||||
public setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
const { customAttr, senior } = chart
|
||||
if (
|
||||
senior.functionCfg.emptyDataStrategy == undefined ||
|
||||
senior.functionCfg.emptyDataStrategy === 'ignoreData'
|
||||
) {
|
||||
senior.functionCfg.emptyDataStrategy = 'breakLine'
|
||||
}
|
||||
return chart
|
||||
}
|
||||
|
||||
public setupSubSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||
const result: ChartBasicStyle['seriesColor'] = []
|
||||
const seriesSet = new Set<string>()
|
||||
const colors = chart.customAttr.basicStyle.subColors ?? CHART_MIX_DEFAULT_BASIC_STYLE.subColors
|
||||
const { yAxisExt, extBubble } = chart
|
||||
if (extBubble?.length) {
|
||||
data?.forEach(d => {
|
||||
if (d.value === null || d.category === null || seriesSet.has(d.category)) {
|
||||
return
|
||||
}
|
||||
seriesSet.add(d.category)
|
||||
result.push({
|
||||
id: d.category,
|
||||
name: d.category,
|
||||
color: colors[(seriesSet.size - 1) % colors.length]
|
||||
})
|
||||
})
|
||||
} else {
|
||||
yAxisExt?.forEach(axis => {
|
||||
if (seriesSet.has(axis.id)) {
|
||||
return
|
||||
}
|
||||
seriesSet.add(axis.id)
|
||||
result.push({
|
||||
id: axis.id,
|
||||
name: axis.chartShowName ?? axis.name,
|
||||
color: colors[(seriesSet.size - 1) % colors.length]
|
||||
})
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
protected setupOptions(chart: Chart, options: G2Spec, context: Record<string, any>): G2Spec {
|
||||
return flow(
|
||||
this.configBasicStyle,
|
||||
this.configLegend,
|
||||
this.configLabel,
|
||||
this.configTooltip,
|
||||
this.configXAxis,
|
||||
this.configYAxis,
|
||||
this.configAssistLine
|
||||
)(chart, options, context, this)
|
||||
}
|
||||
|
||||
constructor(name = 'chart-mix') {
|
||||
super(name, [])
|
||||
}
|
||||
}
|
||||
|
||||
export const CHART_MIX_DEFAULT_BASIC_STYLE = {
|
||||
...DEFAULT_BASIC_STYLE,
|
||||
subAlpha: 100,
|
||||
subColorScheme: 'fast',
|
||||
subSeriesColor: [],
|
||||
subColors: [
|
||||
'#fae800',
|
||||
'#00c039',
|
||||
'#0482dc',
|
||||
'#bb9581',
|
||||
'#ff7701',
|
||||
'#9c5ec3',
|
||||
'#00ccdf',
|
||||
'#00c039',
|
||||
'#ff7701'
|
||||
],
|
||||
leftLineWidth: 2,
|
||||
leftLineSymbol: 'circle',
|
||||
leftLineSymbolSize: 4,
|
||||
leftLineSmooth: true
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { G2Spec, type Chart as G2Chart } from '@antv/g2'
|
||||
import { AxisComponent, G2Spec, type Chart as G2Chart } from '@antv/g2'
|
||||
import {
|
||||
AntVAbstractChartView,
|
||||
AntVDrawOptions,
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
} from '@/views/chart/components/js/panel/types'
|
||||
import { configEmptyDataStyle } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { parseJson, setupSeriesColor } from '../../../util'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { valueFormatter } from '../../../formatter'
|
||||
|
||||
export interface G2DrawOptions<O> extends AntVDrawOptions<O> {
|
||||
/**
|
||||
@@ -86,6 +88,55 @@ export abstract class G2ChartView<
|
||||
return legend
|
||||
}
|
||||
|
||||
protected getAxis(axis: DeepPartial<ChartAxisStyle>): AxisComponent {
|
||||
let lineLineDash = undefined
|
||||
if (axis.axisLine.lineStyle.style === 'dashed') {
|
||||
lineLineDash = [10, 8]
|
||||
}
|
||||
if (axis.axisLine.lineStyle.style === 'dotted') {
|
||||
lineLineDash = [1, 2]
|
||||
}
|
||||
let gridLineDash = [0, 0]
|
||||
if (axis.splitLine.lineStyle.style === 'dashed') {
|
||||
gridLineDash = [10, 8]
|
||||
}
|
||||
if (axis.splitLine.lineStyle.style === 'dotted') {
|
||||
gridLineDash = [1, 2]
|
||||
}
|
||||
const axisOption = {
|
||||
position: axis.position,
|
||||
title: axis.nameShow === false ? false : isEmpty(axis.name) ? false : axis.name,
|
||||
titleFontSize: axis.fontSize,
|
||||
titleFill: axis.color,
|
||||
line: axis.axisLine.show,
|
||||
lineStroke: axis.axisLine.lineStyle.color,
|
||||
lineStrokeOpacity: 1,
|
||||
lineLineWidth: axis.axisLine.lineStyle.width,
|
||||
lineLineDash,
|
||||
label: axis.axisLabel.show,
|
||||
labelFill: axis.axisLabel.color,
|
||||
labelFillOpacity: 1,
|
||||
labelFontSize: axis.axisLabel.fontSize,
|
||||
grid: axis.splitLine.show,
|
||||
gridStroke: axis.splitLine.lineStyle.color,
|
||||
gridStrokeOpacity: 1,
|
||||
gridLineWidth: axis.splitLine.lineStyle.width,
|
||||
gridLineDash,
|
||||
labelTransform: `rotate(${axis.axisLabel.rotate || 0})`,
|
||||
transform: [
|
||||
{
|
||||
type: 'hide',
|
||||
keepHeader: true,
|
||||
keepTail: true
|
||||
}
|
||||
],
|
||||
labelFormatter: d => {
|
||||
return valueFormatter(d, axis.axisLabelFormatter)
|
||||
}
|
||||
}
|
||||
return axisOption
|
||||
}
|
||||
|
||||
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||
return setupSeriesColor(chart, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user