highchart源码学习

作者: cuiy245 | 来源:发表于2016-04-27 17:16 被阅读672次

    一、highchart的组成

    大致浏览一遍源代码,Highchart作为一个对象,会有大致以下几个构造函数。

    
    Highchart = {
     Tooltip: function () {},
     Pointer: function () {},
     Legend: function () {},
     Chart: function () {},
     Series: function () {}
     ...
    }
    
    1. 每个构造函数通过prototype添加一些操作函数
      -这里可以一一写几个重要的函数
      -这里可以一一写几个重要的函数
    2. 每个构造函数都接受chart、option做为参数,这样取参数和操作chart对象就很方便。

    二、chart对象的创建

    1. 通过 new Highchart.Chart()调用了构造函数,创建实例对象Highcharts.Chart
        if (win.jQuery) {
            win.jQuery.fn.highcharts = function () {
                var args = [].slice.call(arguments);
    
                if (this[0]) { // this[0] is the renderTo div
    
                    // Create the chart
                    if (args[0]) {
                        new Highcharts[ // eslint-disable-line no-new
                            isString(args[0]) ? args.shift() : 'Chart' // Constructor defaults to Chart
                        ](this[0], args[0], args[1]);
                        return this;
                    }
    
                    // When called without parameters or with the return argument, return an existing chart
                    return charts[attr(this[0], 'data-highcharts-chart')];
                }
            };
        }
    
    1. 通过getArgs()收集参数,得到:
    • 参数数组args=[div#container, 用户配置的参数对象, ...];
    • renderTo:渲染图表的地方
    1. 进行chart.init()初始化。
    • 得到chart对象的相关属性
    var chart = this;
    this.userOptions // 用户配置的参数
    this.margin
    this.spacing // 图表四周的间距
    this.option // merge后的最终完整配置,具体有哪些配置项见highchart官网
    this.axes // 存放刻度值?
    this.series = []
    charts // 图表数组,存放所有图表
    chart.xAxis = [] // 存放图表的所有x轴
    chart.yAxis = [] // 存放图表的所有y轴
    chart.animation // 是否有图表动画
    
    • 开始chart.firstRender()
    chart.firstRender()```
    4. firstRender做以下工作:
     - 获取container,并给container添加一些属性:例如“data-highcharts-chart="0"”
    

    chart.getContainer();

      -为container添加了width、height(默认400px)等css样式
      -通过SVGrender()画svg,同时对svg添加了一些说明等
    
     - 重置margin
     - 设置chart图表的尺寸
    ```javascript
    chart.setChartSize();
    

    -设置了chart.clipBox、chart.plotBox

    • 设置chart的series属性(目前没觉得有用?)
    chart.propFromSeries();
    
    • 获取刻度值(坐标轴)
     chart.getAxes();
    

    遍历配置option中的所有x轴和y轴的配置信息,为每一个轴创建Axis对象

    each(optionsArray, function (axisOptions) {
            new Axis(chart, axisOptions); 
    });
    

    现在重点说一下Axis对象的创建,见第三部分

    • 初始化series
     each(options.series || [], function (serieOptions) {
              chart.initSeries(serieOptions);
    });
    

    -为series[xData]、series[yData]存放x数据和y数据
    -将y数据存放之series.option.data中
    -chartSeries存放series对象

    • 链接series
    chart.linkSeries();
    
    • 创建Pointer对象
    if (Highcharts.Pointer) {
              chart.pointer = new Pointer(chart, options);
    }
    

    -创建了tooltip对象

    if (Highcharts.Tooltip && options.tooltip.enabled) {
            chart.tooltip = new Tooltip(chart, options.tooltip);
            this.followTouchMove = pick(options.tooltip.followTouchMove, true);
    }
     this.setDOMEvents(); // 为point对象绑定了事件
    

    此时,请注意,已经将图表应该具有的DOM节点就都有了,包括:chart图大小、xy轴、xy轴label、点坐标、柱状图条、tooltip提示、图例legend,那么就要开始往这些节点中添加真正的数字或者样式了。

    • 画图表(见第四部分)
    chart.render();
    

    三、Axis对象的创建

    1.Axis对象的一些属性必须知道:

    axis = this;  //x轴的信息配置
    this.option = {
    categories:Array[13] // x轴上的标注
    dateTimeLabelFormats:Object
    endOnTick:false
    gridLineColor:"#D8D8D8"
    index:0
    isX:true
    labels:Object
    lineColor:"#C0D0E0"
    lineWidth:1
    maxPadding:0.01
    minPadding:0.01
    minorGridLineColor:"#E0E0E0"
    minorGridLineWidth:1
    minorTickColor:"#A0A0A0"
    minorTickLength:2
    minorTickPosition:"outside"
    startOfWeek:1
    startOnTick:false
    tickColor:"#C0D0E0"
    tickLength:10
    tickPixelInterval:100
    tickPosition:"outside"
    tickmarkPlacement:"between"
    title:Object
    type:"linear"
    __proto__:Object
    }
    axis.minPixelPadding = 0;
    axis.categories // 所有x轴上的分类
    axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
    axis.range = options.range;
    axis.offset = options.offset || 0;
    
    axis = this; // y轴配置信息
    this.option = {
    alternateGridColor:null
    dateTimeLabelFormats:Object
    endOnTick:true
    gridLineColor:"rgba(151, 151, 151, .1)"
    gridLineWidth:0
    index:0
    labels:Object
    lineColor:"#C0D0E0"
    lineWidth:0
    maxPadding:0.05
    min:14
    minPadding:0
    minorGridLineColor:"rgba(255,255,255,0.07)"
    minorGridLineWidth:1
    minorTickColor:"#A0A0A0"
    minorTickInterval:null
    minorTickLength:2
    minorTickPosition:"outside"
    opposite:false
    showLastLabel:true
    stackLabels:Object
    startOfWeek:1
    startOnTick:true
    tickColor:"#C0D0E0"
    tickLength:10
    tickPixelInterval:72
    tickPosition:"outside"
    tickWidth:0
    tickmarkPlacement:"between"
    title:Object
    type:"linear"
    __proto__:Object
    }
    

    向chart.axes数组添加所有的Highchart.Axis坐标轴对象;
    向chart[xAxis]存放x轴信息(这里是一个x轴)、chart[yAxis]存放y轴信息(这里是两个y轴)


    四、chart.render()

    1. 画-图标题
    chart.setTitle();
    
    1. 画-legend
    chart.legend = new Legend(chart, options.legend);
    
    1. 画-图的大小尺寸
    chart.setChartSize();
    
    1. 画-范围,根据data中的最大值与最小值
     each(axes, function (axis) {
         axis.setScale();
     });
    

    -计算刻度线数目
    可以看到,刻度线数目计算出来之后,与4比较大小,比4小就设成5,比4大就是本身。

            getTickAmount: function () {
                var options = this.options,
                    tickAmount = options.tickAmount,
                    tickPixelInterval = options.tickPixelInterval;
    
                if (!defined(options.tickInterval) && this.len < tickPixelInterval && !this.isRadial &&
                        !this.isLog && options.startOnTick && options.endOnTick) {
                    tickAmount = 2;
                }
    
                if (!tickAmount && this.alignToOthers()) {
                    // Add 1 because 4 tick intervals require 5 ticks (including first and last)
                    tickAmount = mathCeil(this.len / tickPixelInterval) + 1;
                }
    
                // For tick amounts of 2 and 3, compute five ticks and remove the intermediate ones. This
                // prevents the axis from adding ticks that are too far away from the data extremes.
                if (tickAmount < 4) { //感觉这个4是自己定的呀?这里还说明一下这么做是为了防止极端数据里轴太远,没太明白4怎么来的
                    this.finalTickAmt = tickAmount;
                    tickAmount = 5;
                }
    
                this.tickAmount = tickAmount;
            }
    

    -设置y轴的最大值或者最小值

     if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
    // 原始y数据最大值 - 原始y数据最小数
           length = axis.max - axis.min;
           if (length) {
                 if (!defined(hardMin) && minPadding) {
                            axis.min -= length * minPadding;
                 }
                 if (!defined(hardMax)  && maxPadding) {
    // 获取y轴的最大值,可能带小数点
                            axis.max += length * maxPadding;
                 }
           }
    }
    

    -获取间距值
    对于tickPixelInterval,默认x轴为100,y轴为72

    // get tickInterval
    if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
         axis.tickInterval = 1;
    } else if (isLinked && !tickIntervalOption &&
         tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
         axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval;
       } else {
     //获取最初的tickInterval,因为可能会带小数点,所以需要后面处理
           axis.tickInterval = pick(
                        tickIntervalOption,
                        this.tickAmount ? ((axis.max - axis.min) / mathMax(this.tickAmount - 1, 1)) : undefined,
                        categories ? // for categoried axis, 1 is default, for linear axis use tickPix
                            1 :
                            // don't let it be more than the data range
                 (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption)
                    );
    }
    
     if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
    // 将原始的tickInterval处理为整数
                    axis.tickInterval = normalizeTickInterval(
                        axis.tickInterval,
                        null,
                        getMagnitude(axis.tickInterval),
                        // If the tick interval is between 0.5 and 5 and the axis max is in the order of
                        // thousands, chances are we are dealing with years. Don't allow decimals. #3363.
                        pick(options.allowDecimals, !(axis.tickInterval > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)),
                        !!this.tickAmount
                    );
                }
    

    其中格式化tickInterval的函数normalizeTickInterval会有1、2、2.5、5、10共五个档来得到interval,怎么会有这样的档,目前还不清楚。

    function normalizeTickInterval(interval, multiples, magnitude, allowDecimals, preventExceed) {
            var normalized,
                i,
                retInterval = interval;
    
            // round to a tenfold of 1, 2, 2.5 or 5
            magnitude = pick(magnitude, 1);
            normalized = interval / magnitude;
    
            // multiples for a linear scale
            if (!multiples) {
                multiples = [1, 2, 2.5, 5, 10];
    
                // the allowDecimals option
                if (allowDecimals === false) {
                    if (magnitude === 1) {
                        multiples = [1, 2, 5, 10];
                    } else if (magnitude <= 0.1) {
                        multiples = [1 / magnitude];
                    }
                }
            }
    
            // normalize the interval to the nearest multiple
            for (i = 0; i < multiples.length; i++) {
                retInterval = multiples[i];
                if ((preventExceed && retInterval * magnitude >= interval) || // only allow tick amounts smaller than natural
                        (!preventExceed && (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2))) {
                    break;
                }
            }
    
            // multiply back to the correct magnitude
            retInterval *= magnitude;
    
            return retInterval;
        }
    

    -设置好刻度线位置

        setTickPositions: function () {
                var options = this.options,
                    tickPositions,
                    tickPositionsOption = options.tickPositions,
                    tickPositioner = options.tickPositioner,
                    startOnTick = options.startOnTick,
                    endOnTick = options.endOnTick,
                    single;
    
                // Set the tickmarkOffset
                this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' &&
                    this.tickInterval === 1) ? 0.5 : 0; // #3202
    
    
                // get minorTickInterval
                this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ?
                    this.tickInterval / 5 : options.minorTickInterval;
    
                // Find the tick positions
                this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565)
                if (!tickPositions) {
    
                    if (this.isDatetimeAxis) {
                        tickPositions = this.getTimeTicks(
                            this.normalizeTimeTickInterval(this.tickInterval, options.units),
                            this.min,
                            this.max,
                            options.startOfWeek,
                            this.ordinalPositions,
                            this.closestPointRange,
                            true
                        );
                    } else if (this.isLog) {
                        tickPositions = this.getLogTickPositions(this.tickInterval, this.min, this.max);
                    } else {
                        tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max); //获取刻度值
                    }
    
                    // Too dense ticks, keep only the first and last (#4477)
                    if (tickPositions.length > this.len) {
                        tickPositions = [tickPositions[0], tickPositions.pop()];
                    }
    
                    this.tickPositions = tickPositions;
    
                    // Run the tick positioner callback, that allows modifying auto tick positions.
                    if (tickPositioner) {
                        tickPositioner = tickPositioner.apply(this, [this.min, this.max]);
                        if (tickPositioner) {
                            this.tickPositions = tickPositions = tickPositioner;
                        }
                    }
    
                }
    
                if (!this.isLinked) {
    
                    // reset min/max or remove extremes based on start/end on tick
                    this.trimTicks(tickPositions, startOnTick, endOnTick);
    
                    // When there is only one point, or all points have the same value on this axis, then min
                    // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding
                    // in order to center the point, but leave it with one tick. #1337.
                    if (this.min === this.max && defined(this.min) && !this.tickAmount) {
                        // Substract half a unit (#2619, #2846, #2515, #3390)
                        single = true;
                        this.min -= 0.5;
                        this.max += 0.5;
                    }
                    this.single = single;
    
                    if (!tickPositionsOption && !tickPositioner) {
                        this.adjustTickAmount();
                    }
                }
            }
    

    其中getLinearTickPositions()函数可以算出刻度线数组,例如[0,2500,5000,7500,10000,12500],因为要包含所有的series,就需要去比min还小的数roundedMin,比max还大的数roundedMax,(这里的min和max在前面的代码片段“设置y轴的最大值或者最小值”中已经求出来了),所以就会多出两个刻度。例如,series中最大数为9525,按照2500的tickInterval来算,只要到10000即可,但是前面算出来axis.max为10000.55,所以10000不够,需要再加一个2500,成为12500,这样就多出两个刻度线来。

    getLinearTickPositions: function (tickInterval, min, max) {
                var pos,
                    lastPos,
                    roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
                    roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
                    tickPositions = [];
    
                // For single points, add a tick regardless of the relative position (#2662)
                if (min === max && isNumber(min)) {
                    return [min];
                }
    
                // Populate the intermediate values
                pos = roundedMin;
                while (pos <= roundedMax) {
    
                    // Place the tick on the rounded value
                    tickPositions.push(pos);
    
                    // Always add the raw tickInterval, not the corrected one.
                    pos = correctFloat(pos + tickInterval);
    
                    // If the interval is not big enough in the current min - max range to actually increase
                    // the loop variable, we need to break out to prevent endless loop. Issue #619
                    if (pos === lastPos) {
                        break;
                    }
    
                    // Record the last value
                    lastPos = pos;
                }
                return tickPositions;
            }
    

    -调节刻度线数目
    当刻度线过多时,将tickInterval加倍,来减少刻度线数目,重新得出刻度线数组[0,5000,10000,15000],但又因为刻度线数目小于5,所以需要子啊push一个元素构成5个元素,即变为[0,5000,10000,15000,20000]。

    adjustTickAmount: function () {
                var tickInterval = this.tickInterval,
                    tickPositions = this.tickPositions,
                    tickAmount = this.tickAmount,
                    finalTickAmt = this.finalTickAmt,
                    currentTickAmount = tickPositions && tickPositions.length,
                    i,
                    len;
    
                if (currentTickAmount < tickAmount) {
                    while (tickPositions.length < tickAmount) {
                        tickPositions.push(correctFloat(
                            tickPositions[tickPositions.length - 1] + tickInterval
                        ));
                    }
                    this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
                    this.max = tickPositions[tickPositions.length - 1];
    
                // We have too many ticks, run second pass to try to reduce ticks
                } else if (currentTickAmount > tickAmount) {
                    this.tickInterval *= 2;  //间距加倍
                    this.setTickPositions();
                }
    
                // The finalTickAmt property is set in getTickAmount
                if (defined(finalTickAmt)) {
                    i = len = tickPositions.length;
                    while (i--) {
                        if (
                            (finalTickAmt === 3 && i % 2 === 1) || // Remove every other tick
                            (finalTickAmt <= 2 && i > 0 && i < len - 1) // Remove all but first and last
                        ) {
                            tickPositions.splice(i, 1);
                        }
                    }
                    this.finalTickAmt = UNDEFINED;
                }
            }
    

    5.画-图表的border和background

    chart.drawChartBox();
    

    6.画-xy轴

     // Axes
                if (chart.hasCartesianSeries) {
                    each(axes, function (axis) {
                        if (axis.visible) {
                            axis.render();
                        }
                    });
                }
    

    7.画-series

    相关文章

      网友评论

      本文标题:highchart源码学习

      本文链接:https://www.haomeiwen.com/subject/alvcrttx.html