资讯专栏INFORMATION COLUMN

node环境下给echarts图表加文案

shuibo / 1730人阅读

摘要:开发环境开发语言开发依赖的包图表的宽度是先把最终要呈现的效果贴出来,见下图查看配置项及实例,给图表增加额外的文本块,只有一个属性似乎可以实现,它指的是原生图形元素组件,可以由多种类型组成,如,结合给的实例,看起来很不错,马上开干。

开发环境:node
开发语言:javascript
开发依赖的包:

node-charts

canvas

图表的宽度是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("
"),
                     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("
"),
            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("
"),
            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("
"),
        font: "14px SongTi"
      }
    }
    graphics.push(temp);
  }
 // console.log(graphics);
  return {
    graphic: graphics,
    gridBottom: gridBottom,
    legendBottom: legendBottom
  };

至此,问题都解决了。

最终的源代码传送门:github

总结

了解echarts坐标系
熟悉echarts基本配置
熟悉echarts graphic配置
了解canvas基本API
熟悉数学
对于数字要保持敏感,不要写死了

多搜索,多尝试,多思考才会变通

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93630.html

相关文章

  • 使用vue框架开发两年以上工作经验的前端工程师面试题

    摘要:浏览器兼容性事件绑定一框架使用的是什么如何避免样式污染,以及改变其样式引入插件的方式如何按需引入安装二的生命周期的使用生命周期钩子函数包含的组件缓存与排除的组件不缓存,优先级大于的作用中一般处理什么问题三如何封装组件组件封装过么如何 浏览器兼容性(ie)1.事件绑定 vue一. UI框架使用的是什么? 1.1 如何避免样式污染,以及改变其样式;1.2 vue-cli 引入插件的方式? ...

    wind3110991 评论0 收藏0
  • 每周前端开源推荐第六期

    摘要:每周前端开源推荐第六期从名字就可以很容易的看出该项目的作用,解压缩。同时支持浏览器和。是任务调度的项目。初始化定义人物每三分钟触发一次触发一个交互式学习的方式。强烈建议大家先去体验一下的介绍是由百度团队开发的一款开源图表项目。 每周前端开源推荐第六期 43081j / rar.js Pure-JavaScript RAR reader using AJAX, File API...

    Salamander 评论0 收藏0
  • 每周前端开源推荐第六期

    摘要:每周前端开源推荐第六期从名字就可以很容易的看出该项目的作用,解压缩。同时支持浏览器和。是任务调度的项目。初始化定义人物每三分钟触发一次触发一个交互式学习的方式。强烈建议大家先去体验一下的介绍是由百度团队开发的一款开源图表项目。 每周前端开源推荐第六期 43081j / rar.js Pure-JavaScript RAR reader using AJAX, File API...

    channg 评论0 收藏0
  • 每周前端开源推荐第六期

    摘要:每周前端开源推荐第六期从名字就可以很容易的看出该项目的作用,解压缩。同时支持浏览器和。是任务调度的项目。初始化定义人物每三分钟触发一次触发一个交互式学习的方式。强烈建议大家先去体验一下的介绍是由百度团队开发的一款开源图表项目。 每周前端开源推荐第六期 43081j / rar.js Pure-JavaScript RAR reader using AJAX, File API...

    worldligang 评论0 收藏0
  • React Native使用百度Echarts显示图表

    摘要:今天我就来介绍下在中如何使用来显示各种图表。首先需要在我们的项目中安装组件,该组件是兼容和安卓双平台的。组件主要有三个属性图表的相关配置和数据。图表的高度,默认值是。解决方法将中的代码修改为同时将文件拷贝到安卓项目下面的文件夹中。 本文原创首发于公众号:ReactNative开发圈 Echarts是百度推出的免费开源的图表组件,功能丰富,涵盖各行业图表。相信很多同学在网页端都使用过。今...

    Lucky_Boy 评论0 收藏0

发表评论

0条评论

shuibo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<