開發環境:node
開發語言:javascript
開發依賴的包:javascript
圖表的寬度是1000
先把最終要呈現的效果貼出來,見下圖:html
查看charts配置項及實例,給圖表增長額外的文本塊,只有一個屬性graphic【ˈgrafik】
彷佛能夠實現,它指的是原生圖形元素組件,能夠由多種類型組成,如image, text, circle, sector, ring, polygon, polyline, ...
,結合給的實例,看起來很不錯,so easy!立刻開幹。java
代碼以下:node
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();
效果以下圖:git
問題有倆:github
第2個問題很好解決,熟悉echarts配置的話,給grid的bottom,legend的bottom設個值,但這個值是動態的,和文案的行數有關係,先定一個基值,假如只有一行,grid的bottom爲90,legend的bottom爲35,每多一行,就多加15,僞代碼以下:chrome
const config = { legend: { bottom: 3 * 15 + 35, show: true, data: ["身份證數量", "環比增加率"] }, grid: {bottom: 3 * 15 + 90} ...
要獲得具體的行數,實際上是解決第1個問題,因此最核心的問題是在限定的寬度裏怎麼計算一段文字的行數?
先算下一行能放多少個字符,中英文都算一個字符,以都是中文來算,這樣算下獲得結果是63,行數 = Math.ceil(字符總長度/63)。獲得行數後,還要知道每行的字符是什麼,畢竟換行是要這樣的:canvas
... 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;
獲得效果以下:echarts
換行了,可是每行的字符不同,並且沒行間距,擠得慌。說明計算每一行的長度的方法不對,中文和英文的寬度不同,須要知道每一個字符的寬度才行,而後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
熟悉數學
對於數字要保持敏感,不要寫死了
多搜索,多嘗試,多思考纔會變通