开发环境:node
开发语言:javascript
开发依赖的包:
图表的宽度是1000
先把最终要呈现的效果贴出来,见下图:
查看charts配置项及实例,给图表增加额外的文本块,只有一个属性graphic【ˈgrafik】
似乎可以实现,它指的是原生图形元素组件,可以由多种类型组成,如image, text, circle, sector, ring, polygon, polyline, ...
,结合给的实例,看起来很不错,so easy!马上开干。
代码如下:
var node_echarts = require("node-echarts");
var Path = require("path");
const text = '我的电脑只有4G运行内存,采用默认的idea配置,内存在30分钟内会飚到 >80% ,同时会发生OOM!Chrome就不敢打开!通过上面的配置可以将内存使用降低一半以上,只有idea和chrome 的话,内存才刚刚 40% 。下面的可以看也可以不看了,下面的分析是别人就行了分析,通过阅读可见他的电脑内存的确不小(16G的macbook pro),对于我们学生党,默默的使用着4G内存的电脑,就不多说上面了!不过,参与讨论的一位开发者给笔者发了一份他的设置,虽然是针对同个项目,该设置却极其复杂。笔者对自己的设置并无不满,但非常好奇,这些完全不同的设置对比 JetBrains 提供的默认设置。';
const config = {
legend: { bottom: 0, show: true, data: ["身份证数量", "环比增长率"] },
xAxis: [
{
type: "category",
data: [
"201701",
"201702",
"201703",
"201704",
"201705",
"201706",
"201707",
"201708",
"201709",
"201710",
"201711",
"201712",
"201801"
],
axisLabel: { interval: 0 },
axisPointer: { type: "shadow" },
splitLine:{show: false}
}
],
yAxis: [
{ type: "value", axisLabel: { formatter: null } },
{ type: "value", axisLabel: { formatter: "{value}%" }}
],
series: [
{
type: "bar",
name: "身份证数量",
data: [
23620000,
21060000,
26420000,
30180000,
31430000,
34100000,
33740000,
40170000,
39910000,
38420000,
49300000,
50710000,
46550000
],
yAxisIndex: 0
},
{
type: "line",
name: "环比增长率",
data: [
-23,
-12.13,
20.26,
12.46,
4,
7.82,
-1.09,
16.02,
-0.65,
-3.88,
22.05,
2.79,
-8.95
],
yAxisIndex: 1
}
],
color: ["#4498f6", "#d9e96c"],
graphic: [
{
type: 'text',
left: '10%',
bottom: 'bottom',
style: {
fill: '#333',
text: text,
font: '14px Microsoft YaHei'
}
}
],
};
node_echarts({
width: 1000,
height: 400,
option: config,
path: Path.join(__dirname, "./charts.png")
});
process.exit();
效果如下图:
问题有俩:
- 只有一行,后面的文字都被截断了
- 覆盖了图例的文字,没有间距
第2个问题很好解决,熟悉echarts配置的话,给grid的bottom,legend的bottom设个值,但这个值是动态的,和文案的行数有关系,先定一个基值,假如只有一行,grid的bottom为90,legend的bottom为35,每多一行,就多加15,伪代码如下:
const config = {
legend: { bottom: 3 * 15 + 35, show: true, data: ["身份证数量", "环比增长率"] },
grid: {bottom: 3 * 15 + 90}
...
要得到具体的行数,其实是解决第1个问题,所以最核心的问题是在限定的宽度里怎么计算一段文字的行数?
先算下一行能放多少个字符,中英文都算一个字符,以都是中文来算,这样算下得到结果是63,行数 = Math.ceil(字符总长度/63)。得到行数后,还要知道每行的字符是什么,毕竟换行是要这样的:
...
graphic: [
{
type: 'text',
left: '10%',
bottom: 'bottom',
style: {
fill: '#333',
text: ['text','text2', 'text3'].join('\n'),
font: '14px Microsoft YaHei'
}
}
],
...
想了半天,代码出来了
...
const buildText = (result=text) => {
const graphicY = 15;
let texts = [];
let gridBottom = 90;
let legendBottom = 35;
const rlen = 63; // 一行最多的字符长度
const len = result.length; // 所有字符长度
// 大于一行
if (len > rlen) {
const temp = Math.ceil(len / rlen); // 总行数
const arr = result.split(""); // 把文案分割为每个字符
const newArrs = {};
// 循环总行数
for (let k = 0; k < temp; k++) {
newArrs[k] = []; // 存储每行的字符
for (let i = rlen * k; i < rlen * (k + 1); i++) {
if(arr[i] != undefined)
newArrs[k].push(arr[i]);
}
}
for(let j in newArrs){
texts.push(newArrs[j].join(''));
}
const lastLen = texts.length-1;
gridBottom = lastLen * graphicY + gridBottom;
legendBottom = lastLen * graphicY + legendBottom;
} else {
texts = [result];
}
// console.log(texts);
return {
graphic: [
{
type: "text",
left: "10%",
bottom: "bottom",
style: {
fill: "#333",
text: texts.join("\n"),
font: "14px Microsoft YaHei"
}
}
],
gridBottom: gridBottom,
legendBottom: legendBottom
};
}
const texts = buildText();
const config = {
...
legend: { bottom: texts.legendBottom, show: true, data: ["身份证数量", "环比增长率"] },
grid:{ bottom: texts.gridBottom},
...
}
config.graphic = texts.graphic;
得到效果如下:
换行了,但是每行的字符不一样,而且没行间距,挤得慌。说明计算每一行的长度的方法不对,中文和英文的宽度不一样,需要知道每个字符的宽度才行,然后graphic不是支持image嘛,我就换个思路,把文字换成图片吧,因为和canvas相关,google一把“canvas 文字换行”后,找到这篇canvas文本绘制自动换行、字间距、竖排等实现,再此安利下这位作者,他写的博文都是满满的干货,canvas中有一个很有用的API:measureText
,用来计算字符宽度,把文字变成图片的代码如下:
var fs = require('fs')
var path = require('path')
var Canvas = require('canvas')
const maxWidth = 1000;
let height = 20;
const text = '我的电脑只有4G运行内存,采用默认的idea配置,内存在30分钟内会飚到 >80% ,同时会发生OOM!Chrome就不敢打开!通过上面的配置可以将内存使用降低一半以上,只有idea和chrome 的话,内存才刚刚 40% 。下面的可以看也可以不看了,下面的分析是别人就行了分析,通过阅读可见他的电脑内存的确不小(16G的macbook pro),对于我们学生党,默默的使用着4G内存的电脑,就不多说上面了!不过,参与讨论的一位开发者给笔者发了一份他的设置,虽然是针对同个项目,该设置却极其复杂。笔者对自己的设置并无不满,但非常好奇,这些完全不同的设置对比 JetBrains 提供的默认设置。';
const rlen = 63; // 一行最多的字符长度
const len = text.length; // 所有字符长度
const temp = Math.ceil(len / rlen); // 总行数
var canvas = Canvas.createCanvas(maxWidth, temp * height)
var ctx = canvas.getContext('2d')
ctx.globalAlpha = 1
ctx.font = '14px Microsoft Yahei'
ctx.lineWidth = 1
ctx.fillStyle = '#000'
const arrText = text.split('');
let line = '';
let y = 20;
const lineHeight = 25;
// 核心思路是这段代码,循环每个字符,当字符宽度大于最大宽度就换行,且Y坐标也增加
for (var n = 0; n < arrText.length; n++) {
var testLine = line + arrText[n];
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, 0, y);
line = arrText[n];
y += lineHeight;
} else {
line = testLine;
}
// console.log(line)
}
console.log(line)
ctx.fillText(line, 0, y);
canvas.createPNGStream().pipe(fs.createWriteStream(path.join(__dirname, 'text.png')))
拿到的图片如下:
然后修改graphic
...
graphic:[{
type: 'image',
left: '10%',
bottom: 'bottom',
style: {
image: path.join(__dirname, 'text.png')
width: 1000,
height: 200
}
}],
...
可是图片没有生成,上面这段代码没有发生作用。查了源代码,也没找到原因,只好提个issue。这又回到起点,不过计算字符长度的方法采用上述方法
const buildText = (text=text) => {
const graphicY = 15;
let gridBottom = 90;
let legendBottom = 35;
const maxWidth = 900;
const canvas = createCanvas(maxWidth,100);
const ctx = canvas.getContext("2d");
ctx.font = "normal 14px SongTi";
const arrText = text.split('');
let line = '';
const newArrs = [];
for (var n = 0; n < arrText.length; n++) {
var testLine = line + arrText[n];
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth & n>0) {
line = arrText[n];
newArrs.push(testLine.substr(0, testLine.length-1));
} else {
line = testLine;
}
}
newArrs.push(line);
//console.log(newArrs);
const row = newArrs.length;
if (row > 1) {
gridBottom = row * graphicY + gridBottom;
legendBottom = row * graphicY + legendBottom;
}
return {
graphic:[
{
type: "text",
left: "10%",
bottom: "bottom",
style: {
fill: "#333",
text: newArrs.join("\n"),
font: "14px Songti"
}
}
],
gridBottom: gridBottom,
legendBottom: legendBottom
};
}
得到的效果如下:
行间距的问题没解决,想到graphic既然是数组,把每行拆开作为单独的对象,bottom的值都不一样。
const buildText = (text=text) =>{
const graphicY = 15;
const lineY = 20; // 设置每行文字bottom的基值
let gridBottom = 90;
let legendBottom = 35;
const maxWidth = 900;
const canvas = createCanvas(maxWidth,100);
const ctx = canvas.getContext("2d");
ctx.font = "normal 14px SongTi";
const arrText = text.split('');
let line = '';
const newArrs = [];
for (var n = 0; n < arrText.length; n++) {
var testLine = line + arrText[n];
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth & n>0) {
line = arrText[n];
newArrs.push(testLine.substr(0, testLine.length-1));
} else {
line = testLine;
}
}
newArrs.push(line);
//console.log(newArrs);
const row = newArrs.length; // 总行数
if (row > 1) {
gridBottom = row * graphicY + gridBottom;
legendBottom = row * graphicY + legendBottom;
}
let graphics = [];
// 循环每行文字
for (let k=0; k < row; k++){
const temp = {
type: "text",
left: "5%",
bottom: (row-1-k) * lineY, // 数值越大,越靠前
style: {
fill: "#333",
text: [`${newArrs[k]}`].join("\n"),
font: "14px SongTi"
}
}
graphics.push(temp);
}
// console.log(graphics);
return {
graphic: graphics,
gridBottom: gridBottom,
legendBottom: legendBottom
};
至此,问题都解决了。
最终的源代码传送门:github
总结
了解echarts坐标系
熟悉echarts基本配置
熟悉echarts graphic配置
了解canvas基本API
熟悉数学
对于数字要保持敏感,不要写死了
多搜索,多尝试,多思考才会变通