上篇博客提到,如何在Salesforce Lightning组件下借助ChartJS来实现数据可视化,并以Line Chart完成了一个案例,接下来我们进一步优化这个案例
之前的博客中,ChartJS显示的画面如下,
在这张表当中,很显然,2014与2020对应的两个数据点并不能将数值显示完全,同时可以看到,2020的最高点被遮住了一部分,对应的数字更是没有显示出来,所以我们修复下这几个问题:
- 最高点显示数据不完整
- 最左边更最右边的数据显示不完整
同时需要优化的地方包括:
- 显示的数字因为是货币,所以给加上对应的货币分隔符
- 动态显示画布的大小
问题1:最高点显示数据不完整
数值最大的点显示不完整,其本质上是这个数值达到了Y轴的最高点,所以,解决方法就是设置Y的高度始终高于数据集中的最大值,而考虑到数据的量级可能是个位数,又或者百万甚至千万,直接设置Y轴的大小显然并不可取,那么我们可以将Y轴最大值 = 数据集最大值 * 130%,这样动态的保证数据点用于囊括在Y轴里面,从而确保了最高点不会被遮住,那么上代码:
// 取出数据集中的最大值 var MULTIPLE = 1.3; // 设置Y轴 在最大值的基础上延长多少 var maxValue = temp[0]["firstValue"]; for(var a=0; a< temp.length; a++){ label.push(temp[a]["label"]); firstValue.push(temp[a]["firstValue"]); secondValue.push(temp[a]["secondValue"]); if(maxValue < temp[a]["firstValue"]) maxValue = temp[a]["firstValue"]; if(maxValue < temp[a]["secondValue"]) maxValue = temp[a]["secondValue"]; }
// 设置Y轴参数 yAxes: [{ gridLines: {display:false}, display: false, // 隐藏Y轴 ticks: { // 设置Y轴最大最小坐标点 min: 0, max: maxValue * MULTIPLE, }, }]
展示效果:
问题2:最左边更最右边的数据显示不完整
X轴左右两边的数据点显示不完整,根据问题一的结果,第一反应毫无疑问是延长X轴线来解决问题,但是在查阅了相关文档后,并没有合适的,可以用来操作的空间,所以常规手段,将最左边的点往右挪,最右边的点往左挪,不过这种情况就需要注意,当数据点仅有一个时该如何显示,话不多说,上代码
animation:{ "duration":1, "onComplete": function() { var chartInstance = this.chart, ctx = chartInstance.ctx; ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; this.data.datasets.forEach(function(dataset, i) { var meta = chartInstance.controller.getDatasetMeta(i); var len = meta.data.length; meta.data.forEach(function(bar, index) { var data = '$' + dataset.data[index]; var Offset = data.length * 2; // 偏移量
// 注意,Y轴上 - 5是向上移动 if(len >= 2){ if(index == 0){// 第一个数据点的偏移 ctx.fillText(data, bar._model.x + Offset, bar._model.y - 5); }else if(index == len -1){ // 最后一个点的偏移 ctx.fillText(data, bar._model.x - Offset, bar._model.y) - 5; }else{ ctx.fillText(data, bar._model.x, bar._model.y - 5); } }else{// 当只有一个数据点的时候 ctx.fillText(data, bar._model.x + Offset, bar._model.y - 5); } }); }); } }
这里面我引入了一个偏移量的概念,原理同问题一,数据量级的问题,个位数和百位千位十万位需要挪动的像素大小是完全不一样的,所以我根据当前数据本身的长度,分别向左右各偏移两倍的长度,这样显示的效果在各种量级下自然没有差别,当然你喜欢也可以自己设定合适的大小
展示效果:
做到这里,为了测试数据展示的效果,我尝试只传入一个点的时候发现一个问题,
可以看到,当只有一条数据时,Line类型的图表会将数据点显示在Y轴上,虽然数据显示没问题,但是在展示的时候,会显得非常奇怪,相对来说,将这个点移动到图表的中间效果更合适,但是在我翻遍文档后,发现这一点并不能通过配置来实现,所以只能另想办法,最后我将只有一个点的时候,传入两个空值,在前端显示时,隐藏空值的点,这样根据三个点的显示效果,这唯一一个数据点就会出现在正中间了,那么怎么做呢?
首先,ChartJS中,坐标点传入 null值是不会显示的,所以利用这一点,我们在第一个点的位置前后各传递一个null,因为 LineChartVar下的fisrtValue和secondValue都是Integer的数据,所以假设传入值为-1,在Aura组件的JS文件中将-1替换成null即可
Controller & Helper 核心代码:
// 此处仅作为展示,假设数据不包括 -1 ,实际项目中应改成其他值,因为LineChartVar的中firstValue都是整数类型,而Integer类型不支持传null进去,
// 所以我设置值为-1,在JS中将-1替换掉 List<LineChartVar> myLineChartVarList = new List<LineChartVar>(); myLineChartVarList.add(new LineChartVar('ISNULL', -1, -1)); myLineChartVarList.add(new LineChartVar('2014', 100,120)); myLineChartVarList.add(new LineChartVar('ISNULL', -1, -1)); return JSON.Serialize(myLineChartVarList);
var MULTIPLE = 1.3; // 设置Y轴 在最大值的基础上延长多少 // 注意 -1 是假设的的值,所以最大值不能取 var maxValue = temp[0]["firstValue"] == -1 ? 0 : temp[0]["firstValue"]; for(var a=0; a < temp.length; a++){ // label为ISNULL说明是我们传入的占位点,所以将Label设置为'' var tempLabel = temp[a]["label"] == 'ISNULL' ? '' : temp[a]["label"]; if(tempLabel == '') MULTIPLE = 2; label.push(tempLabel); // value为-1时,说明是传入的占位符,所以将其设定为null var tempValue = temp[a]["firstValue"] == -1 ? null : temp[a]["firstValue"]; firstValue.push(tempValue); var tempValue2 = temp[a]["secondValue"] == -1 ? null : temp[a]["secondValue"]; secondValue.push(tempValue2); // setting the yAxes ticks var FValue = temp[a]["firstValue"] == -1 ? 0 : temp[a]["firstValue"]; var SValue = temp[a]["secondValue"] == -1 ? 0 : temp[a]["secondValue"]; if(FValue > maxValue) maxValue = FValue; // get MaxValue if(SValue > maxValue) maxValue = MValue; // get MaxValue }
为了方便查看演示效果,我将X轴的网格线重新打开,可以看到,现在的唯一的数据点就被放在了中间位置,效果比起默认的情况就好很多了
然后我们继续最开始的优化,想要让数据点显示货币分隔符号,其实只需要在HelpJS中对数据进行简单的处理就好
meta.data.forEach(function(bar, index) { var tempdata = dataset.data[index]; // 数据点的值有可能为null,所以先加上判断 if(tempdata != null){ // 考虑传入的数据不一定全是整数,当传入一个浮点数时,我们以 "." 进行分隔 var arry = tempdata.toString().split("."); // split分隔出来的数组,第一个数字一定是整数部分,直接通过正则表达式对其添加货币分隔符号 tempdata = arry[0].toString().replace(/(d)(?=(?:d{3})+$)/g, '$1,'); // 如果是浮点数,那么 split分隔的数组就有两个值,第二个值就是小数部门,直接拼接即可 if(arry.length > 1) tempdata = tempdata + '.' + arry[1]; } // 这里添加 “$” 是为了避免 null 值时,data.length 这个报错,毕竟空指针问题 var data = '$' + tempdata; // var data = '$' + dataset.data[index]; var Offset = data.length * 2; ....... }
查看显示效果:
看上去还不错,分隔符的显示很完美,那么接下来最后一个优化,让画布的大小自适应,这个其实比较简单,上一篇提到,改变画布的大小有两种方式:
// 方式一:在Helper中直接渲染 var el = cmp.find('lineChart').getElement(); var ctx = el.getContext('2d'); ctx.canvas.width = '700'; ctx.canvas.height = '300'; // 方式二:在cmp中,直接设定宽高 <canvas aura:id="lineChart" id="lineChart" width="600" height="300"/>
要使画布大小自适应,那么显然在Helper中直接渲染是可行的,所以我们获取画布外的div大小,然后将div的宽高赋值给画布,就能保证在画布渲染出来时,是符合div大小的,所以,代码如下:
// 获取容器div的大小 var divChart = document.getElementById('chartContainer'); var el = cmp.find('lineChart').getElement(); var ctx = el.getContext('2d'); // 将容器div的大小赋值给画布 ctx.canvas.width = divChart.offsetWidth; ctx.canvas.height = divChart.offsetHeight;
将div的宽度设置为100%,高度设置为50%
<aura:application > <div id="chartContainer" style="100%;height:50%;"> <c:ChartDemo/> </div> </aura:application>
演示效果如下:
到这里为止,针对ChartJS数据展过程中的优化点做了更细致的整理和解决,希望对看到这里的你有所帮助
最后,友情提示,ChartJS的版本是在不断迭代的,如果你看到有的代码你没法运行,或者运行的展示结果并不一样,那么我推荐你换一个版本过来进行尝试,比如在ChartJS中对tooltip的样式属性:displayColors,borderColor,borderWidth这些,都有提供自定义选项,但是如果版本不够新,这些属性并不能正常工作,所以在使用ChartJS时,你使用的版本也很重要
参考内容:
附上完整的Helper Controller代码
({ createLineGraph : function(cmp, temp) { var label = []; var firstValue = []; var secondValue = []; var MULTIPLE = 1.3; // 设置Y轴 在最大值的基础上延长多少 // 注意 -1 是假设的的值,所以最大值不能取 var maxValue = temp[0]["firstValue"] == -1 ? 0 : temp[0]["firstValue"]; for(var a=0; a < temp.length; a++){ // label为ISNULL说明是我们传入的占位点,所以将Label设置为'' var tempLabel = temp[a]["label"] == 'ISNULL' ? '' : temp[a]["label"]; if(tempLabel == '') MULTIPLE = 2; label.push(tempLabel); // value为-1时,说明是传入的占位符,所以将其设定为null var tempValue = temp[a]["firstValue"] == -1 ? null : temp[a]["firstValue"]; firstValue.push(tempValue); var tempValue2 = temp[a]["secondValue"] == -1 ? null : temp[a]["secondValue"]; secondValue.push(tempValue2); // setting the yAxes ticks var FValue = temp[a]["firstValue"] == -1 ? 0 : temp[a]["firstValue"]; var SValue = temp[a]["secondValue"] == -1 ? 0 : temp[a]["secondValue"]; if(FValue > maxValue) maxValue = FValue; // get MaxValue if(SValue > maxValue) maxValue = SValue; // get MaxValue } var divChart = document.getElementById('chartContainer'); var el = cmp.find('lineChart').getElement(); var ctx = el.getContext('2d'); ctx.canvas.width = divChart.offsetWidth; ctx.canvas.height = divChart.offsetHeight; var data = { labels: label, datasets: [{ label: 'USD Sent', data: firstValue, backgroundColor: "rgba(153,255,51,0.4)",// 填充的颜色 pointRadius:5, pointStyle:'circle', borderColor:'#bbd9b7',// 折线的颜色设置 pointBorderColor:'#00ff00', // 顶点圈圈的颜色 pointBackgroundColor:'#00ff00' // 顶点的颜色 }, { label: 'USD Recieved', data: secondValue, backgroundColor: "rgba(255,153,0,0.4)",// 填充的颜色 pointRadius:5, pointStyle:'circle', borderColor:'#bbd9b7',// 折线的颜色设置 pointBorderColor:'#ff0000', // 顶点圈圈的颜色 pointBackgroundColor:'#ff0000' // 顶点的颜色 }] }; var options = { elements: { line: {tension: 0} // 设置折线图 }, legend: {display: false}, // 设置不显示图例 responsive: false, // responsive和maintainAspectRatio设置为false才可以调整图标的宽高 maintainAspectRatio: false, scales: { xAxes: [{ gridLines: {display:false} // 隐藏网格线 }], yAxes: [{ gridLines: {display:false}, display: false, // 隐藏Y轴 ticks: { // 设置Y轴最大最小坐标点 min: 0, max: maxValue * MULTIPLE, }, }] }, // 设置显示数据的顶点 hover:{animationDuration:0},//防止鼠标移动到顶点时的闪烁效果 animation:{ "duration":1, "onComplete": function() { var chartInstance = this.chart, ctx = chartInstance.ctx; ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; this.data.datasets.forEach(function(dataset, i) { var meta = chartInstance.controller.getDatasetMeta(i); var len = meta.data.length; meta.data.forEach(function(bar, index) { var tempdata = dataset.data[index]; if(tempdata != null){ var arry = tempdata.toString().split("."); tempdata = arry[0].toString().replace(/(d)(?=(?:d{3})+$)/g, '$1,'); if(arry.length > 1) tempdata = tempdata + '.' + arry[1]; } var data = '$' + tempdata; // var data = '$' + dataset.data[index]; var Offset = data.length * 2; var Offset = data.length * 2; // 偏移量 if(len >= 2){ if(index == 0){//first point style ctx.fillText(data, bar._model.x + Offset, bar._model.y - 5); }else if(index == len -1){ // last point style ctx.fillText(data, bar._model.x - Offset, bar._model.y - 5); }else{ ctx.fillText(data, bar._model.x, bar._model.y - 5); } }else{// only one point style ctx.fillText(data, bar._model.x + Offset, bar._model.y - 5); } }); }); } } }; new Chart(ctx, { type: 'line', data: data, options: options }); } })