本期大纲
1、确定纵坐标的范围并绘制
2、根据真实数据绘制折线
相关阅读:
关注我的 项目 查看完整代码。
确定纵坐标的范围并绘制
为了避免纵坐标的刻度出现小数的情况,我们把纵坐标分为5个区块,我们取最小单位刻度为例如10(能够被5整除),当然真实情况会比这复杂,待会儿我们再讨论。
所以我们的处理输入输出应该是下面的结果
(5, 34.1) => (10, 40)(10, 34) => (10, 40)(-5.1, 40) => (-10, 40)
// 确定Y轴取值范围function findRange (num, type, limit) { limit = limit || 10; // upper向上查找,lower向下查找 type = type ? type : 'upper'; // 进行取整操作,避免while时进入死循环 if (type === 'upper') { num = Math.ceil(num); } else { num = Math.floor(num); } while (num % limit !== 0) { if (type === 'upper') { num++; } else { num--; } } return num;}
好了,初步的确定范围已经完成了,但是细想一下这个范围还是不是很理想,比如用户传入的数据都是小数级别的,比如 (0.2, 0.8)
,我们输出的范围是(0, 5)
这个范围偏大,图表展现的效果则会是上面有大部分的留白,同样用户输入的数据很大,比如(10000, 18000)
,我们得到的范围是(10000, 18010)
,这个范围则没什么意义,所以我们需要根据传入的数据的范围来分别确定我们的最小单位刻度。
规定我们的参数格式是这样的:
opts = { ... series: [{ ... data: [15, 20, 45, 37, 4, 80] }, { ... data: [70, 40, 65, 100, 34, 18] } ]}
让我们继续进行优化
// 合并数据,将series中的每项data整合到一个数组当中function dataCombine(series) { return series.reduce(function(a, b) { return (a.data ? a.data : a).concat(b.data); }, []);}// 根据数据范围确定最小单位刻度function getLimit (maxData, minData) var limit = 0; var range = maxData - minData; if (range >= 10000) { limit = 1000; } else if (range >= 1000) { limit = 100; } else if (range >= 100) { limit = 10; } else if (range >= 10) { limit = 5; } else if (range >= 1) { limit = 1; } else if (range >= 0.1) { limit = 0.1; } else { limit = 0.01; }}var dataList = dataCombine(opts.series);// 获取传入数据的最小值var minData = Math.min.apply(this, dataList);// 获取传入数据的最大值var maxData = Math.max.apply(this, dataList);var limit = getLimit(maxData, minData);var minRange = findRange(minData, 'lower', limit);var maxRange = findRange(maxData, 'upper', limit);
现在我们动态的确定除了合适的最小刻度范围,接下来我们接着优化一下上面的findRange
方法,主要是增加对小数的支持
function findRange (num, type, limit) { limit = limit || 10; type = type ? type : 'upper'; var multiple = 1; while (limit < 1) { limit *= 10; multiple *= 10; } if (type === 'upper') { num = Math.ceil(num * multiple); } else { num = Math.floor(num * multiple); } while (num % limit !== 0) { if (type === 'upper') { num++; } else { num--; } } return num / multiple;}
现在我们已经确定好了Y轴的取值范围,关于如何画出Y轴可以参看 中X轴的绘制方法,此处不再累赘。
Y轴效果图:
opts = { ... series: [{ ... data: [15, 20, 45, 37, 4, 80] }, { ... data: [70, 40, 65, 100, 34, 18] } ]}
opts = { ... series: [{ ... data: [0.15, 0.2, 0.45, 0.37, 0.4, 0.8] }, { ... data: [0.30, 0.37, 0.65, 0.78, 0.69, 0.94] } ]}
效果还不错,我们接着往下
根据真实数据绘制折线
问题的关键在于确定每个数据点的(x, y)
坐标,x
坐标比较好确定,我们根据画布的宽度以及opts.categories
即可确定。
规定我们的配置为:
config = { xAxisHeight: 30, // X轴高度 yAxisWdith: 30 // Y轴宽度}
var data = [15, 20, 45, 37, 4, 80];var xPoints = [];var validWidth = opts.width - config.yAxisWidth;var eachSpace = validWidth / opts.categories.length;var start = config.yAxisWidth;data.forEach(function (item, index) { xPoints.push(start + (index + 0.5) * eachSpace);});
y
坐标稍微会复杂一点,需要根据Y轴的范围已经本身的数值进行计算得出。
所以我们计算出的y
应该为
y = validHeight * (data - min) / (max - min);// 由于canvas画布是左上角为原点坐标,故我们变化一下// 得到最终的y绘制点y = valideHeight - y;
代码如下:
var data = [15, 20, 45, 37, 4, 80];var yPoints = [];var validHeight = opts.height - config.xAxisHeight;data.forEach(function(item) { var y = validHeight * (item - min) / (max - min); y = validHeight - y; yPoints.push(y);}
现在我们已经确定了数据点在画布上的绘制坐标,关于如何绘制折现请查看 中相关内容,此处不再累赘。
最终效果图如下:
预告:下一部分我们一起讨论绘制过程中的一些技巧、动画效果和如何工程化我们的项目。