0. 缘起
照理来说,春节过后的我现在应该还在快乐地摸鱼划水,但小测试猛地发来测试文档和示例,我对了一波之后对其中有个Echarts的label显示超过20截断有了些许冷汗泠泠的感觉。遂看了一波原本的代码,感慨下开山祖师爷细节处理的到位。
1. 柱状图数值显示的大致情况
xAxis
竖向
对于按照高度限制竖直文本高度的情况,需要按照 Math.floor(限制高度/行高),计算出限制字数
横向
全显示
其他方向
根据垂直高度及倾斜角,计算倾斜状态下的最大字符宽度
yAxis
y轴数值是什么就显示什么
2. 对xAxis的处理
xAxis配置中注意两个属性axisLabel
与nameTextStyle
axisLabel: {
// interval: 0,
show: config.showLabel,
rotate: getRotate(config.labelRotate),
formatter: getFormatter(config.labelRotate, aAxisHeight),
rich: {
text: {
align: 'right',
fontSize: LABEL_FONT_SIZE,
lineHeight: LABEL_FONT_SIZE,
},
ellipsis: verticalEllipsis.style,
},
},
nameTextStyle: {
// 当显示label时,让name稍微下移,不然与label在同一行,容易混淆
// 隐藏label时,让name上移,否则会超出视口
lineHeight: config.showLabel ? 50 : 25,
},
3. 关键函数
getFormatter
对xAxis
的label
进行格式化,是入口函数
/**
* get xAxis label formatter
* */
const getFormatter = (rotate: XAxisLabelRotate, height: number) => {
/**
* echarts会根据xaxis label 高度动态的调整底部间距,我们需要做的
* 是限制xaxis label 高度,不让其超过 “20个中文字符高度数值” 的高度
*
* 1. 默认遵从 从左到右原则
*
* 2. 对于竖直排列的文本,要这样展示i
* -----------------------------
* 乐
* 盘
* 游
*
*
* */
const formatter = (v: any) => {
const text = String(v);
// 竖直排列
// 对于按照高度限制竖直文本高度的情况,只需要按照 Math.floor(限制高度/行高)
// 计算出限制字数,然后对文本进行截取即可 (因为中英文字符高度相同)
if (rotate === XAxisLabelRotate.Vertical) {
const chartCount = Math.floor(height / LABEL_FONT_SIZE);
return renderVerticalText({
text,
count: chartCount,
});
}
// 水平
if (rotate === XAxisLabelRotate.Horizontal) {
return `{text|${text}}`;
}
// 其他角度
// 根据垂直高度及倾斜角,计算倾斜状态下的最大字符宽度
const stringWidth = height / Math.sin(Math.PI * (Math.abs(rotate) / 180));
// 单个中文字符宽度
const charWidth = textRuler.measureText('乐', {
fontSize: `${LABEL_FONT_SIZE}px`,
});
// 计算最大字符数
const chartCount = Math.floor(stringWidth / charWidth);
// 截取字符
const sub = subString(text, chartCount);
return `{text|${sub}${sub !== text ? '...' : ''}}`;
};
return formatter;
};
renderVerticalText
将横向文本渲染成竖向的
/**
* 将普通的字符串按照格式化成如下格式用于echarts渲染:
*
* "乐盘游"
* ↓↓↓↓↓↓
* --------------
* 乐
* 盘
* 游
*
* NOTE: 需要结合echarts rich属性使用
*
* 对于按照高度限制竖直文本高度的情况,只需要按照 Math.floor(限制高度/行高)
* 计算出限制字数即可
*
* */
export default function renderVerticalText({ text, count }: Config): string {
const shouldSlice = text.length > count;
const subText = shouldSlice ? text.substring(0, count) : text;
const verticalText = subText.split('').join('\n');
if (shouldSlice) {
return `{text|${verticalText}}\n{ellipsis|${verticalEllipsis.text}}`;
}
return `{text|${verticalText}}`;
}
export const verticalEllipsis = {
text: '.\n.\n.',
style: {
lineHeight: 4,
fontSize: 12,
},
};
subString
截取指定宽度字符
/**
* 截取 **指定数量中文字符宽度** 的字符串,超过的部分舍弃
*
* */
export default function subString(str: string, len: number): string {
if (str.length < len) {
return str;
}
// 按照实际长度进行截取
const subStr = str.substring(0, len);
// 在截取后的字符串中查找,属于ascii编码的字符,因为这些字符宽度只有中文一半
// (其他半角字符基本不会出现,所以不做考虑)
const enChars = subStr.match(/[\u20-\u7f]/g);
// 如果ascii编码的字符数量大于1个,则实际截取的字符串长度会比预想的
// 长度小很多,所以需要补齐
if (enChars && enChars.length > 1) {
const restLen = Math.floor(enChars.length / 2);
return subStr + subString(str.substring(len), restLen);
}
return subStr;
}
4. 倾斜时的类:
倾斜时还有一些细节处理,看了真感觉叹为观止。
先算单个字符,再算最大字符数,最后截取这一部分显示
textRulerInstance.ts
import createTextRuler from './textRuler';
export default createTextRuler();
textRuler.ts
import { setCSSFont, CSSFont } from './cssFont';
/**
* 创建一个测量字符绘制像素宽度的尺子📏
*
* */
export default function createTextRuler() {
const canvas = document.createElement('canvas');
const ctx2D = canvas.getContext('2d');
if (!ctx2D) {
throw Error('该浏览器不支持canvas,请使用现代浏览器');
}
/**
* 计算通过css样式设置的字体绘制出的字符的宽度。
*
* */
const measureText = (char: string, font?: CSSFont): number => {
ctx2D.font = setCSSFont(font);
const text = ctx2D.measureText(char);
return text.width;
};
const get2DContext = () => {
return ctx2D;
};
return {
measureText,
get2DContext,
};
}
配套的还有个工具类
cssFont.ts
export interface CSSFont {
fontSize?: string;
fontFamily?: string;
fontWeight?: string;
fontStyle?: string; // normal | italic | oblique <angle>?
fontVariant?: string;
lineHeight?: string;
fontStretch?: string;
}
/**
* 规则:
*
* + 必须包含以下值:
* - <font-size>
* - <font-family>
* + 可以选择性包含以下值:
* - <font-style>
* - <font-variant>
* - <font-weight>
* - <line-height>
* + font-style, font-variant 和 font-weight 必须在 font-size 之前
* + 在 CSS 2.1 中 font-variant 只可以是 normal 和 small-caps
* + line-height 必须跟在 font-size 后面,由 "/" 分隔,例如 "16px/3"
* + font-family 必须最后指定
*
* 正式语法:
*
* `[ [ <'font-style'> || <font-variant-css21> || <'font-weight'> || <'font-stretch'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ]`
*
* */
export const setCSSFont = (
{
fontWeight = 'normal',
fontSize = '16px',
fontFamily = 'sans-serif',
fontStyle = 'normal',
fontVariant = 'normal',
lineHeight = '1',
fontStretch = 'normal',
}: CSSFont = {} as CSSFont
): string => {
return `${fontStyle} ${fontVariant} ${fontWeight} ${fontStretch} ${fontSize}/${lineHeight} ${fontFamily}`;
};
人生到处知何似,应似飞鸿踏雪泥。