Merge pull request #13775 from dataease/pr@dev-v2@chart-quadrant-yaxis-title-style

fix(图表): 优化象限图纵轴名称为长度过长时截取显示
This commit is contained in:
jianneng-fit2cloud 2024-12-03 16:18:04 +08:00 committed by GitHub
commit 92692232de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 162 additions and 38 deletions

View File

@ -8,7 +8,13 @@ import { valueFormatter } from '@/views/chart/components/js/formatter'
import { useI18n } from '@/hooks/web/useI18n'
import { defaults, isEmpty, map } from 'lodash-es'
import { cloneDeep, defaultTo } from 'lodash-es'
import { configPlotTooltipEvent, getTooltipContainer, TOOLTIP_TPL } from '../../common/common_antv'
import {
configAxisLabelLengthLimit,
configPlotTooltipEvent,
configYaxisTitleLengthLimit,
getTooltipContainer,
TOOLTIP_TPL
} from '../../common/common_antv'
import { DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
const { t } = useI18n()
@ -204,13 +210,15 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
stroke: '#bbb'
}
}
chart.container = container
const options = this.setupOptions(chart, baseOptions)
const { Scatter: G2Scatter } = await import('@antv/g2plot/esm/plots/scatter')
const newChart = new G2Scatter(container, options)
newChart.on('point:click', action)
newChart.on('click', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
newChart.on('afterrender', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
configYaxisTitleLengthLimit(chart, newChart)
configAxisLabelLengthLimit(chart, newChart, 'axis-title')
configPlotTooltipEvent(chart, newChart)
return newChart
}

View File

@ -1,4 +1,4 @@
import { hexColorToRGBA, isAlphaColor, isTransparent, parseJson } from '../../util'
import { hexColorToRGBA, isAlphaColor, isTransparent, measureText, parseJson } from '../../util'
import {
DEFAULT_BASIC_STYLE,
DEFAULT_LEGEND_STYLE,
@ -1435,77 +1435,170 @@ const AXIS_LABEL_TOOLTIP_STYLE = {
}
const AXIS_LABEL_TOOLTIP_TPL =
'<div class="g2-axis-label-tooltip">' + '<div class="g2-tooltip-title">{title}</div>' + '</div>'
export function configAxisLabelLengthLimit(chart, plot) {
export function configAxisLabelLengthLimit(chart, plot, triggerObjName) {
// 设置触发事件的名称如果未传入则默认为 'axis-label'
const triggerName = triggerObjName || 'axis-label'
// 判断是否是Y轴标题
const isYaxisTitle = triggerName === 'axis-title'
// 解析图表的自定义样式和属性
const { customStyle, customAttr } = parseJson(chart)
const { lengthLimit, fontSize, color, show } = customStyle.yAxis.axisLabel
const { tooltip } = customAttr
if (!lengthLimit || !show || !customStyle.yAxis.show || chart.type === 'bidirectional-bar') {
// 如果不是标题判断没有设置长度限制没有显示或Y轴不显示或图表类型为双向条形图则不执行后续操作
if (
!isYaxisTitle &&
(!lengthLimit || !show || !customStyle.yAxis.show || chart.type === 'bidirectional-bar')
)
return
}
plot.on('axis-label:mouseenter', e => {
// 鼠标进入事件
plot.on(triggerName + ':mouseenter', e => {
const field = e.target.cfg.delegateObject.component.cfg.field
// 不分图表纵轴通过位置判断左右为纵轴目前仅热力图
const position = e.target.cfg.delegateObject.component.cfg.position
const isYaxis = position === 'left' || position === 'right'
// 先只处理竖轴
if (field !== 'field' && field !== 'title' && !isYaxis) {
return
}
// 如果不是 'field' 'title'且不是Y轴直接返回
if (field !== 'field' && field !== 'title' && !isYaxis) return
// 获取轴标签的实际内容
const realContent = e.target.attrs.text
if (realContent.length < lengthLimit || !(realContent?.slice(-3) === '...')) {
// 不是标题时判断标签长度小于限制或已经省略'...'结尾则不显示 tooltip
if (
isYaxisTitle ? false : realContent.length < lengthLimit || !(realContent.slice(-3) === '...')
)
return
}
// 获取当前鼠标事件的坐标
const { x, y } = e
const parentNode = e.event.target.parentNode
let labelTooltipDom = parentNode.getElementsByClassName('g2-axis-label-tooltip')?.[0]
// 获取父节点中是否已有 tooltip
let labelTooltipDom = parentNode.getElementsByClassName('g2-axis-label-tooltip')[0]
// 获取轴的标题
const title =
e.target.cfg.delegateObject.item?.name ||
e.target.cfg.delegateObject.axis.cfg.title.originalText
// 如果没有 tooltip创建新的 tooltip DOM 元素
if (!labelTooltipDom) {
const title = e.target.cfg.delegateObject.item.name
const domStr = substitute(AXIS_LABEL_TOOLTIP_TPL, { title })
labelTooltipDom = createDom(domStr)
// 设置 tooltip 的样式
AXIS_LABEL_TOOLTIP_STYLE.backgroundColor = tooltip.backgroundColor
AXIS_LABEL_TOOLTIP_STYLE.boxShadow = tooltip.backgroundColor + ' 0px 0px 5px'
AXIS_LABEL_TOOLTIP_STYLE.boxShadow = `${tooltip.backgroundColor} 0px 0px 5px`
AXIS_LABEL_TOOLTIP_STYLE.maxWidth = '200px'
_.assign(labelTooltipDom.style, AXIS_LABEL_TOOLTIP_STYLE)
// tooltip 添加到父节点
parentNode.appendChild(labelTooltipDom)
} else {
labelTooltipDom.getElementsByClassName('g2-tooltip-title')[0].innerHTML =
e.target.cfg.delegateObject.item.name
// 如果已有 tooltip更新其标题并使其可见
labelTooltipDom.getElementsByClassName('g2-tooltip-title')[0].innerHTML = title
labelTooltipDom.style.visibility = 'visible'
}
// 获取父节点的尺寸和 tooltip 的尺寸
const { height, width } = parentNode.getBoundingClientRect()
const { offsetHeight, offsetWidth } = labelTooltipDom
// 如果 tooltip 的尺寸超出了父节点的尺寸则将其位置重置为 (0, 0)
if (offsetHeight > height || offsetWidth > width) {
labelTooltipDom.style.left = labelTooltipDom.style.top = `0px`
labelTooltipDom.style.left = labelTooltipDom.style.top = '0px'
return
}
// 计算 tooltip 的初始位置
const initPosition = { left: x + 10, top: y + 15 }
if (initPosition.left + offsetWidth > width) {
initPosition.left = width - offsetWidth - 10
}
if (initPosition.top + offsetHeight > height) {
initPosition.top -= offsetHeight + 15
}
// 调整位置避免 tooltip 超出边界
if (initPosition.left + offsetWidth > width) initPosition.left = width - offsetWidth - 10
if (initPosition.top + offsetHeight > height) initPosition.top -= offsetHeight + 15
// 设置 tooltip 的位置和样式
labelTooltipDom.style.left = `${initPosition.left}px`
labelTooltipDom.style.top = `${initPosition.top}px`
labelTooltipDom.style.color = color
labelTooltipDom.style.fontSize = `${fontSize}px`
})
plot.on('axis-label:mouseleave', e => {
// 鼠标离开事件
plot.on(triggerName + ':mouseleave', e => {
const field = e.target.cfg.delegateObject.component.cfg.field
// 不分图表纵轴通过位置判断左右为纵轴目前仅热力图
const position = e.target.cfg.delegateObject.component.cfg.position
const isYaxis = position === 'left' || position === 'right'
// 先只处理竖轴
if (field !== 'field' && field !== 'title' && !isYaxis) {
return
}
// 如果不是 'field' 'title'且不是Y轴直接返回
if (field !== 'field' && field !== 'title' && !isYaxis) return
// 获取轴标签的实际内容
const realContent = e.target.attrs.text
if (realContent.length < lengthLimit || !(realContent?.slice(-3) === '...')) {
// 如果标签长度小于限制或已经省略'...'结尾则不显示 tooltip
if (
isYaxisTitle ? false : realContent.length < lengthLimit || !(realContent.slice(-3) === '...')
)
return
}
// 获取父节点中的 tooltip
const parentNode = e.event.target.parentNode
const labelTooltipDom = parentNode.getElementsByClassName('g2-axis-label-tooltip')?.[0]
if (labelTooltipDom) {
labelTooltipDom.style.visibility = 'hidden'
}
const labelTooltipDom = parentNode.getElementsByClassName('g2-axis-label-tooltip')[0]
// 如果 tooltip 存在隐藏它
if (labelTooltipDom) labelTooltipDom.style.visibility = 'hidden'
})
}
/**
* y轴标题截取
* @param chart
* @param plot
*/
export function configYaxisTitleLengthLimit(chart, plot) {
// 监听图表渲染前事件
plot.on('beforerender', ev => {
// 获取图表的Y轴自定义样式
const { yAxis } = parseJson(chart.customStyle)
// 计算最大可用空间高度80% 为最大高度比
const maxHeightRatio =
0.8 * (ev.view.canvas.cfg.height - (ev.view.canvas.cfg.height < 120 ? 60 : 30))
// 计算Y轴标题的每行高度
const titleHeight = measureText(
chart,
yAxis.name,
{ fontSize: yAxis.fontSize, fontFamily: chart.fontFamily },
'height'
)
// 用于存储截取后的标题
let wrappedTitle = ''
// 循环截取标题内容直到超过最大高度
for (
let charIndex = 0;
charIndex < yAxis.name.length && (charIndex + 1) * titleHeight <= maxHeightRatio;
charIndex++
) {
wrappedTitle += yAxis.name[charIndex]
}
// 如果标题被截断添加省略号
if (yAxis.name.length > wrappedTitle.length) {
wrappedTitle =
wrappedTitle.length > 2
? wrappedTitle.slice(0, wrappedTitle.length - 2) + '...'
: wrappedTitle + '...'
}
// 更新Y轴标题的原始文本和截断后的文本
ev.view.options.axes.yAxisExt.title.originalText = yAxis.name
ev.view.options.axes.yAxisExt.title.text = wrappedTitle
})
}

View File

@ -1119,3 +1119,26 @@ export function getLineLabelColorByCondition(conditions, value, fieldId) {
}
return color
}
/**
* 获取文本在画布中的测量信息
* @param chart 图表内容
* @param text 测量文本
* @param font 文本样式
* @param type 测量类型高度宽度
**/
export const measureText = (chart, text, font, type) => {
const container = document.getElementById(chart.container)
const canvas = container.querySelector('canvas')
const ctx = canvas.getContext('2d')
const { fontWeight, fontSize, fontFamily } = font
ctx.font = [fontWeight, `${fontSize}px`, fontFamily].join(' ').trim()
const textMetrics = ctx.measureText(text)
if (type === 'height') {
return textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent
}
if (type === 'width') {
return textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft
}
return 0
}