datavjs Version: 0.1.1 By @DataV

DataV.js is a JavaScript library for data visualization

datav: API索引


DataV

DataV全局命名空间对象定义

函数 DataV() DataV
代码
var DataV = function () {};

version

版本号

属性 DataV.version DataV version
代码
DataV.version = "0.1.0";

Themes

全局主题对象

属性 DataV.Themes DataV Themes
代码
DataV.Themes = {};

get

获取当前主题的属性

方法 DataV.Themes.get() DataV.Themes get
返回 Mix 返回当前主题的属性值
代码
DataV.Themes.get = function (key) {
        var themeName = DataV.Themes.current || "default";
        if (!DataV.Themes._currentTheme) {
            DataV.Themes._currentTheme = DataV.Themes[themeName];
        }
        return DataV.Themes._currentTheme[key];
    };

add

添加自定义主题

方法 DataV.Themes.add() DataV.Themes add
参数 themeName(String) 主题名称
参数 theme(Object) 主题对象json, contain attribute "COLOR_ARGS", theme.COLOR_ARGS is a 2-d array;
代码
DataV.Themes.add = function () {
        var args = [].slice.call(arguments, 0);
        var theme = args.pop();
        if (arguments.length < 2) {
            throw new Error("Arguments format error. should be: (themsName, theme)");
        } else if (typeof theme !== "object") {
            throw new Error("second argument theme should be a json object");
        } else if (!theme["COLOR_ARGS"]) {
            throw new Error("theme.COLOR_ARGS needed");
        } else if (!theme["COLOR_ARGS"] instanceof Array) {
            throw new Error("theme.COLOR_ARGS should be an array");
        } else if (!(theme["COLOR_ARGS"][0] instanceof Array)) {
            throw new Error("theme.COLOR_ARGS[0] should be an array");
        }
        for (var i = 0, l = args.length; i < l; i++) {
            var _themeName = args[i];
            if (DataV.Themes.hasOwnProperty(_themeName)) {
                throw new Error("The " + _themeName + " has been defined");
            }
            DataV.Themes[_themeName] = theme;
        }
    };

changeTheme

切换当前主题

方法 DataV.changeTheme() DataV changeTheme
参数 themeName(String) 主题名称
返回 Boolean 返回是否切换成功
代码
DataV.changeTheme = function (themeName) {
        var ret = DataV.Themes[themeName];
        if (ret) {
            DataV.Themes.current = themeName;
            DataV.Themes._currentTheme = null;
        }
        return !!ret;
    };

getColor

获取当前主题的颜色配置

方法 DataV.getColor() DataV getColor
返回 Array 颜色参数列表
代码
DataV.getColor = function () {
        var theme = DataV.Themes;
        var color = theme.get("COLOR_ARGS");
        return color;
    };

getDiscreteColor

根据当前主题的颜色配置方案,获取生成离散颜色的函数

方法 DataV.getDiscreteColor() DataV getDiscreteColor
返回 Function 离散函数
代码
DataV.getDiscreteColor = function () {
        var color = DataV.getColor();
        if (!_.isArray(color)) {
            throw new Error("The color should be Array");
        }
        var colorCount = color.length;
        var gotColor = [];

        if (_.isArray(color[0])) {
            for (var i = 0, l = color[i].length; i < l; i++) {
                gotColor.push(color[i][0]);
            }
        } else {
            gotColor = color;
        }

        return function (num) {
            return gotColor[num % colorCount];
        };
    };

gradientColor

获取渐变颜色,用于生成渐变效果

方法 DataV.gradientColor() DataV gradientColor
参数 color(Array) 颜色数组
参数 method(String) 生成渐变色的方法,默认值为normal。如果为normal将采用D3的interpolateRgb算法,如果为special,则用Rapheal的HSB算法
返回 Function 返回生成算法
代码
DataV.gradientColor = function (color, method) {
        if (!_.isArray(color)) {
            throw new Error("The color should be Array");
        }

        var startColor = color[0];
        var endColor;
        var colorCount = color.length;

        var hsb;
        if (colorCount === 1) {
            hsb = Raphael.color(color[0]);
            endColor = Raphael.hsb(hsb.h / 360, (hsb.s -30) / 100, 1);
        } else {
            endColor = color[colorCount - 1];
        }

        method = method || "normal ";

        if (method === "special") {
            return function (num) {
                var startHSB = Raphael.color(startColor);
                var endHSB = Raphael.color(endColor);
                var startH = startHSB.h * 360;
                var endH = endHSB.h * 360;
                var startNum = startHSB.h * 20;
                var endNum = endHSB.h * 20;

                var dH;
                var dNum;
                if (startNum >= endNum) {
                    dH = 360 - startH + endH;
                    dNum = colorCount - startNum + endNum;
                } else {
                    dH = endH - startH;
                    dNum = endNum - startNum;
                }
                
                var h = (startH + dH * num) / 360;
                var s = (70 + Math.abs(4 - (startNum + dNum * num) % 8) * 5) / 100;
                var b = (100 - Math.abs(4 - (startNum + dNum * num) % 8) * 5) / 100;

                return Raphael.hsb(h, s, b);
            };
        } else {
            return d3.interpolateRgb.apply(null, [startColor, endColor]);
        }
    };

json

请求一个JSON文件

属性 DataV.json DataV json
参数 url(String) JSON文件地址
参数 callback(Function) 回调函数
代码
DataV.json = d3.json;

请求一个CSV文件,并解析

参数 url(String) CSV文件地址
参数 callback(Function) 回调函数,得到解析后的结果
代码
// DataV.csv = d3.csv;
    DataV.csv = function (url, callback) {
        d3.text(url, "text/csv", function (text) {
            callback(text && d3.csv.parseRows(text));
        });
    };

detect

侦测数据,检测是二维表(带表头否),还是JSON对象数组

方法 DataV.detect() DataV detect
参数 input(Array) 输入的数组对象,元素可能为数组,也可能是对象
代码
DataV.detect = function (input) {
        var first = input[0];
        if (_.isArray(first)) {
            var withHead = _.all(first, function (item) {
                return !DataV.isNumeric(item);
            });
            return withHead ? "Table_WITH_HEAD" : "Table";
        } else if (_.isObject(first)) {
            return "List";
        } else {
            return "Unknown";
        }
    };

tablify

将一个对象集合转化为二维表格,第一行为key,后续为每个对象的数据

Examples

 [
   {"username": "JacksonTian", "nick": "朴灵", "hometown": "Chongqing"},
   {"username": "Fengmk2", "nick": "苏千", "hometown": "Guangzhou"}
 ];
=>
 [
   ["username", "nick", "hometown"],
   ["JacksonTian", "朴灵", "Chongqing"],
   ["Fengmk2", "苏千", "Guangzhou"]
 ]
方法 DataV.tablify() DataV tablify
参数 list(Array) 待转化的二维表集合
代码
DataV.tablify = function (list) {
      if (!list.length) {
        return [];
      }
      var keys = _.keys(list[0]);
      var ret = [keys];
      _.each(list, function (obj) {
        ret.push(_.values(obj));
      });
      return ret;
    };

collectionify

tablify的反向工程,如果不传入head,那么第一行取出作为key,后续当作数据

Examples

 [
   ["username", "nick", "hometown"],
   ["JacksonTian", "朴灵", "Chongqing"],
   ["Fengmk2", "苏千", "Guangzhou"]
 ]
=>
 [
   {"username": "JacksonTian", "nick": "朴灵", "hometown": "Chongqing"},
   {"username": "Fengmk2", "nick": "苏千", "hometown": "Guangzhou"}
 ];
方法 DataV.collectionify() DataV collectionify
参数 table(Array) 二维表数据
参数 head(Array) 可选的表头数组,如果不指定,将取出二维表数据第一行作为表头
代码
DataV.collectionify = function (table, head) {
      var ret = [];
      if (table.length < 2) {
        return ret;
      }
      var keys = head || table[0];
      _.each(table.slice(1), function (row) {
        var obj = {};
        _.each(keys, function (key, index) {
          obj[key] = row[index];
        });
        ret.push(obj);
      });
      return ret;
    };

isNumeric

判断输入是否是数字

方法 DataV.isNumeric() DataV isNumeric
参数 obj(Mix) 输入内容
返回 Boolean 返回输入是否是数字
代码
DataV.isNumeric = function (obj) {
        return !isNaN(parseFloat(obj)) && isFinite(obj);
    };

limit

添加数值边缘检测

方法 DataV.limit() DataV limit
参数 number(Number) 数字
参数 min(Number) 下边缘
参数 max(Number) 上边缘
返回 Boolean 返回边缘检测后的数值
代码
DataV.limit = function (number, min, max) {
        var ret;
        if (typeof min !== 'undefined') {
            ret = number < min ? min : number;
        }
        if (typeof max !== 'undefined') {
            if (max < min) {
                throw new Error('The max value should bigger than min value');
            }
            ret = number > max ? max: number;
        }
        return ret;
    };

    jQuery.fn.wall = function () {
        return $(this).each(function () {
            $(this).css('visibility', 'hidden');
        });
    };
    jQuery.fn.unwall = function () {
        return $(this).each(function () {
            $(this).css('visibility', 'visible');
        });
    };

extend

继承

方法 DataV.extend() DataV extend
参数 parent(Function) 父类
参数 properties(Object) 新属性
返回 Function 新的子类
代码
DataV.extend = function (parent, properties) {
        if (typeof parent !== "function") {
            properties = parent;
            parent = function () {};
        }

        properties = properties || {};
        var sub = function () {
            // Call the parent constructor.
            parent.apply(this, arguments);
            // Only call initialize in self constructor.
            if (this.constructor === parent && this.initialize) {
                this.initialize.apply(this, arguments);
            }
        };
        sub.prototype = new parent();
        sub.prototype.constructor = parent;
        $.extend(sub.prototype, properties);
        return sub;
    };

Chart

所有Chart的源定义

Examples

   var Stream = DataV.extend(DataV.Chart, {
       initialize: function () {
           this.type = "Stream";
       },
       clearCanvas: function () {
           this.canvas.clear();
           this.legend.innerHTML = "";
       }
   });
声明 Chart Chart
代码
var Chart = DataV.extend(EventProxy, {
        type: "Chart",
        initialize: function () {
            // 默认设置
            this.defaults = {};
            // 插件
            this.plugins = {};
            // 纬度
            this.dimension = {};
            // 格式化
            this.formatter = {};
            // 挂件
            this.widgets = [];
        }
    });

getType

返回当前Chart的类型

方法 Chart.prototype.getType() getType
返回 String Chart类型
代码
Chart.prototype.getType = function () {
        return this.type;
    };

getFormatter

优先返回用户传入的值或者方法,如果不存在,取实例方法返回

方法 Chart.prototype.getFormatter() getFormatter
参数 key(String) 方法或值的名称
参数 值或方法(Mix)
代码
Chart.prototype.getFormatter = function (key) {
        var noop = function (input) {
            // 转为字符串
            return '' + input;
        };
        return this.defaults[key] || this.formatter[key] || noop;
    };

checkContainer

如果node是字符串,会当作ID进行查找。
如果是DOM元素,直接返回该元素。
如果是jQuery对象,返回对象中的第一个元素。
如果节点不存在,则抛出异常

Examples

chart.checkContainer("id");
chart.checkContainer(document.getElementById("id"));
chart.checkContainer($("#id"));
方法 Chart.prototype.checkContainer() checkContainer
参数 node(Mix) The element Id or Dom element
返回 Object 返回找到的DOM节点
代码
Chart.prototype.checkContainer = function (node) {
        var ret = null;

        if (typeof node === "string") {
            ret = document.getElementById(node);
        } else if (node.nodeName) { //DOM-element
            ret = node;
        } else if (node instanceof jQuery && node.size() > 0) {
            ret = node[0];
        }
        if (!ret) {
            throw new Error("Please specify which node to render.");
        }
        return ret;
    };

setOptions

设置自定义选项

Examples

Set width 500px, height 600px;

{"width": 500, "height": 600}
方法 Chart.prototype.setOptions() setOptions
参数 options(Object) 自定义选项对象
返回 Object 覆盖后的图表选项对象
代码
Chart.prototype.setOptions = function (options) {
        return _.extend(this.defaults, options);
    };

plug

添加插件方法到实例对象上

方法 Chart.prototype.plug() plug
参数 name(String) plugin name
参数 fn(Function) plugin function
返回 Object A reference to the host object
代码
Chart.prototype.plug = function (name, fn) {
        this[name] = fn;
        this.plugins[name] = fn;
        return this;
    };

unplug

从实例上移除插件方法

方法 Chart.prototype.unplug() unplug
参数 plugin(String) The namespace of the plugin
返回 Object A reference to the host object
代码
Chart.prototype.unplug = function (name) {
        if (this.plugins.hasOwnProperty(name)) {
            delete this.plugins[name];
            delete this[name];
        }
        return this;
    };

map

数据源映射

方法 Chart.prototype.map() map
代码
Chart.prototype.map = function (map) {
        var that = this;
        _.forEach(map, function (val, key) {
            if (that.dimension.hasOwnProperty(key)) {
                that.dimension[key].index = map[key];
            }
        });
        var ret = {};
        _.forEach(that.dimension, function (val, key) {
            ret[key] = val.index;
        });

        ret.hasField = _.any(ret, function (val) {
            return typeof val === 'string';
        });
        this.mapping = ret;
        return ret;
    };

own

拥有一个组件

方法 Chart.prototype.own() own
代码
Chart.prototype.own = function (widget) {
        // 传递defaults给子组件
        widget.setOptions(this.defaults);
        widget.owner = this;
        this.widgets.push(widget);
        return widget;
    };

    Chart.prototype.show = function () {
        $(this.node).unwall();
        return this;
    };

    Chart.prototype.hidden = function () {
        $(this.node).wall();
        return this;
    };

    DataV.Chart = Chart;

FloatTag

浮动标签

方法 DataV.FloatTag() DataV FloatTag
代码
DataV.FloatTag = function () {
        //set floatTag location, warning: the html content must be set before call this func,
        // because jqNode's width and height depend on it's content;
        var _changeLoc = function (m) {
            //m is mouse location, example: {x: 10, y: 20}
            var x = m.x;
            var y = m.y;
            var floatTagWidth = jqNode.outerWidth();
            var floatTagHeight = jqNode.outerHeight();
            if (floatTagWidth + x + 2 * mouseToFloatTag.x <=  $(container).width()) {
                x += mouseToFloatTag.x;
            } else {
                x = x - floatTagWidth - mouseToFloatTag.x;
            }
            if (y >= floatTagHeight + mouseToFloatTag.y) {
                y = y - mouseToFloatTag.y - floatTagHeight;
            } else {
                y += mouseToFloatTag.y;
            }
            jqNode.css("left",  x  + "px");
            jqNode.css("top",  y + "px");
        };
        var _mousemove = function (e) {
            var offset = $(container).offset();
            if (!(e.pageX && e.pageY)) {return false;}
            var x = e.pageX - offset.left,
                y = e.pageY - offset.top;

            setContent.call(this);
            _changeLoc({'x': x, 'y': y});
        };

        var mouseToFloatTag = {x: 20, y: 20};
        var setContent = function () {};
        var jqNode;
        var container;

        var floatTag = function (cont) {
            container = cont;
            jqNode = $("<div/>").css({
                "border": "1px solid",
                "border-color": $.browser.msie ? "rgb(0, 0, 0)" : "rgba(0, 0, 0, 0.8)",
                "background-color": $.browser.msie ? "rgb(0, 0, 0)" : "rgba(0, 0, 0, 0.75)",
                "color": "white",
                "border-radius": "2px",
                "padding": "12px 8px",
                //"line-height": "170%",
                //"opacity": 0.7,
                "font-size": "12px",
                "box-shadow": "3px 3px 6px 0px rgba(0,0,0,0.58)",
                "font-familiy": "宋体",
                "z-index": 10000,
                "text-align": "center",
                "visibility": "hidden",
                "position": "absolute"
            });
            $(container).append(jqNode)
                .mousemove(_mousemove);
            jqNode.creator = floatTag;
            return jqNode;
        };

        floatTag.setContent = function (sc) {
            if (arguments.length === 0) {
                return setContent;
            }
            setContent = sc;
            return floatTag;
        };

        floatTag.mouseToFloatTag = function (m) {
            if (arguments.length === 0) {
                return mouseToFloatTag;
            }
            mouseToFloatTag = m;
            return floatTag;
        };

        floatTag.changeLoc = _changeLoc;

        return floatTag;
    };

    DataV.sum = function (list, iterator) {
        var count = 0;
        var i, l;
        if (typeof iterator === 'undefined') {
            for (i = 0, l = list.length; i < l; i++) {
                count += list[i];
            }
        } else if (typeof iterator === "function") {
            for (i = 0, l = list.length; i < l; i++) {
                count += iterator(list[i]);
            }
        } else if (typeof iterator === "string" || typeof iterator === 'number') {
            for (i = 0, l = list.length; i < l; i++) {
                count += list[i][iterator];
            }
        } else {
            throw new Error("iterator error");
        }
        return count;
    };

    DataV.more = function (list, level, step, last) {
        var selected;
        var start = level * step, end;
        last = last || function (remains) {
            return remains[0];
        };

        var needMoreRow;
        if (start + step >= list.length) {
            needMoreRow = false;
            end = list.length - 1;
        } else {
            needMoreRow = true;
            end = start + step - 1;
        }
        if (!needMoreRow) {
            selected = list.slice(start);
        } else {
            selected = list.slice(start, end);
            selected.push(last(list.slice(end)));
        }
        return selected;
    };

    return DataV;
});

charts/axis: API索引


d3_scaleExtent

function from d3, get scaleRange of an ordinal scale

函数 d3_scaleExtent() d3_scaleExtent
参数 domain(Array) ordinal scale's range
代码
function d3_scaleExtent(domain) {
        var start = domain[0], stop = domain[domain.length - 1];
        return start < stop ? [start, stop] : [stop, start];
    }

d3_scaleRange

function from d3, get scaleRange of a scale

函数 d3_scaleRange() d3_scaleRange
代码
function d3_scaleRange(scale) {
        return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
    }

d3_svg_axisSubdivide

function from d3, get subticks

函数 d3_svg_axisSubdivide() d3_svg_axisSubdivide
参数 scale(scale,)
参数 major(ticks,) ticks of scale
参数 number(m,) of subdivide
代码
function d3_svg_axisSubdivide(scale, ticks, m) {
        var subticks = [];
        if (m && ticks.length > 1) {
            var extent = d3_scaleExtent(scale.domain()),
                i = -1,
                n = ticks.length,
                d = (ticks[1] - ticks[0]) / ++m,
                j,
                v;
            while (++i < n) {
                for (j = m; --j > 0;) {
                    if ((v = +ticks[i] - j * d) >= extent[0]) {
                        subticks.push(v);
                    }
                }
            }
            for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) {
                subticks.push(v);
            }
        }
        return subticks;
    }

    var Axis = function () {
        var scale = d3.scale.linear(),
            orient = "bottom",
            tickMajorSize = 6,
            tickMinorSize = 6,
            tickEndSize = 6,
            tickPadding = 3,
            tickArguments_ = [10],
            tickFormat_,
            tickSubdivide = 0,

            tickAttr_ = {},
            tickTextAttr_ = {},
            minorTickAttr_ = {},
            domainAttr_ = {};

axis

@param paper: raphael's paper object.

函数 axis() axis
返回 axisSet: raphael's set object.
代码
function axis(paper) {
            // Ticks for quantitative scale, or domain values for ordinal scale.
            var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(),
                tickFormat = tickFormat_ === undefined ?
                    (scale.tickFormat ?
                        scale.tickFormat.apply(scale, tickArguments_)
                        : String)
                    : tickFormat_;

            var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide);
            var range = d3_scaleRange(scale);
        
            var axisSet = paper.set();

            switch (orient) {
            case "bottom":
                subticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + tickMinorSize + "V0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + tickMajorSize + "V0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(tickX,  Math.max(tickMajorSize, 0) + tickPadding + 2,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "middle"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize)
                    .attr(domainAttr_));
                break;

            case "top":
                subticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + -tickMinorSize + "V0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + -tickMajorSize + "V0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(tickX,  -(Math.max(tickMajorSize, 0) + tickPadding + 2),
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "middle"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize)
                    .attr(domainAttr_));
                break;

            case "left":
                subticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + -tickMinorSize + "," + tickY + "H0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + -tickMajorSize + "," + tickY + "H0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(-(Math.max(tickMajorSize, 0) + tickPadding),  tickY,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "end"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize)
                    .attr(domainAttr_));
                break;

            case "right":
                subticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickMinorSize + "," + tickY + "H0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickMajorSize + "," + tickY + "H0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(Math.max(tickMajorSize, 0) + tickPadding,  tickY,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "start"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize)
                    .attr(domainAttr_));
                break;
            }

            return axisSet;
        }

scale

get or set axis' scale.

方法 axis.scale() axis scale
代码
axis.scale = function (x) {
            if (!arguments.length) {
                return scale;
            }
            scale = x;
            return axis;
        };

orient

get or set axis' orinet: "bottom", "top", "left", "right", default orient is bottom.

方法 axis.orient() axis orient
代码
axis.orient = function (x) {
            if (!arguments.length) {
                return orient;
            }
            orient = x;
            return axis;
        };

ticks

get or set axis' ticks number.

方法 axis.ticks() axis ticks
代码
axis.ticks = function () {
            if (!arguments.length) {
                return tickArguments_;
            }
            tickArguments_ = arguments;
            return axis;
        };

tickFormat

get or set axis' ticks format function, it's a function change format style.
from one string format to another string format.

方法 axis.tickFormat() axis tickFormat
代码
axis.tickFormat = function (x) {
            if (!arguments.length) {
                return tickFormat_;
            }
            tickFormat_ = x;
            return axis;
        };

tickSize

get or set axis' tick size(length of tick line, unit: px).

方法 axis.tickSize() axis tickSize
参数 ===(arguments.length) 0, get axis' major tick size.
参数 ===(arguments.length) 1, set axis' all tick sizes as x.
参数 ===(arguments.length) 2, get axis' major tick size as x, minor and end size as y.
参数 ===(arguments.length) 3, get axis' major tick size as x, minor size as y, end size as z.
代码
axis.tickSize = function (x, y, z) {
            if (!arguments.length) {
                return tickMajorSize;
            }
            var n = arguments.length - 1;
            tickMajorSize = +x;
            tickMinorSize = n > 1 ? +y : tickMajorSize;
            tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
            return axis;
        };

tickPadding

get or set axis' tick padding(the distance between tick text and axis).

方法 axis.tickPadding() axis tickPadding
参数 is(x) a number, unit is px;
代码
axis.tickPadding = function (x) {
            if (!arguments.length) {
                return tickPadding;
            }
            tickPadding = +x;
            return axis;
        };

tickSubdivide

get or set axis' sub tick divide number(divide number between two major ticks).

方法 axis.tickSubdivide() axis tickSubdivide
代码
axis.tickSubdivide = function (x) {
            if (!arguments.length) {
                return tickSubdivide;
            }
            tickSubdivide = +x;
            return axis;
        };

tickAttr

get or set axis' tick attribute(Raphael format).

方法 axis.tickAttr() axis tickAttr
代码
axis.tickAttr = function (x) {
            if (!arguments.length) {
                return tickAttr_;
            }
            tickAttr_ = x;
            return axis;
        };

tickTextAttr

get or set axis' tick text attribute(Raphael format).

方法 axis.tickTextAttr() axis tickTextAttr
代码
axis.tickTextAttr = function (x) {
            if (!arguments.length) {
                return tickTextAttr_;
            }
            tickTextAttr_ = x;
            return axis;
        };

minorTickAttr

get or set axis' minor tick attribute(Raphael format).

方法 axis.minorTickAttr() axis minorTickAttr
代码
axis.minorTickAttr = function (x) {
            if (!arguments.length) {
                return minorTickAttr_;
            }
            minorTickAttr_ = x;
            return axis;
        };

domainAttr

get or set axis' domain(axis line) attribute(Raphael format).

方法 axis.domainAttr() axis domainAttr
代码
axis.domainAttr = function (x) {
            if (!arguments.length) {
                return domainAttr_;
            }
            domainAttr_ = x;
            return axis;
        };
      
        return axis;
    };

    return Axis;
});

charts/bar: API索引


Bar

Bar构造函数
Creates Bar in a DOM node with id "chart", default width is 522; height is 522px;

Options

  • width 宽度,默认为节点宽度
  • yBase 横坐标的基线值,有的以0为起始值,有的则以数据中的最小值为起始值
  • gap 组与组之间的缝隙宽度
  • barColor 设置每组bar的颜色,数组
  • xTickNumber 横轴刻度数量
  • yTickNumber 纵轴刻度数量
  • legendWidth 设置图例宽度
  • showLegend 设置是否显示图例
  • formatLabel 纵轴刻度格式化函数,传入纵轴刻度值,
  • formatXScale 横轴轴刻度格式化函数,传入横轴刻度值,
  • formatValue 数值格式化函数,传入数值,
  • formatLegend 图里的文字格式化函数,传入图例文字

Examples

var bar = new Bar("chart", {"width": 500, "height": 600);
声明 Bar Bar
参数 node(Mix) The dom node or dom node Id
参数 options(Object) options json object for determin bar style.
代码
var Bar = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
        this.type = "Bar";
        this.node = this.checkContainer(node);

柱纬度

代码
this.dimension.bar = {
            type: "string",
            required: true,
            index: 0
        };

横向纬度

代码
this.dimension.x = {
            type: "string",
            required: true,
            index: 1
        };

值纬度

代码
this.dimension.value = {
            type: "number",
            required: true,
            index: 2
        };

        this.defaults.typeNames = [];
        // canvas parameters
        this.defaults.width = 522;
        this.defaults.height = 522;
        this.defaults.margin = [10, 20, 30, 50];
        this.defaults.gap = 15;
        this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"];
        this.defaults.xTickNumber = 5;
        this.defaults.yTickNumber = 5;
        this.defaults.legendWidth = 100;
        this.defaults.yBase = 0;
        this.defaults.showLegend = true;
        this.barSet = [];
        this.formatLabel = function (text) {
            return text;
        };
        this.formatXScale = function (text) {
            return text;
        };
        this.formatValue = function (value) {
            return value;
        };
        this.formatLegend = function (legend) {
            return legend;
        }
        this.setOptions(options);
        this.createCanvas();
        this.initEvents();
    }
  });

createCanvas

创建画布

方法 Bar.prototype.createCanvas() createCanvas
代码
Bar.prototype.createCanvas = function () {
    var conf = this.defaults;
    this.node.style.position = "relative";
    this.canvas = new Raphael(this.node, conf.width, conf.height);
  };

  Bar.prototype.initEvents = function () {
    var that = this;
    this.on('legendOver', function (barIndex) {
      that.barSet.forEach(function (set, index) {
        if (index !== barIndex) {
          set.attr({
            "fill-opacity": 0.3
          });
        }
      });
    });

    this.on('legendOut', function (barIndex) {
      that.barSet.forEach(function (set, index) {
        set.attr({
          "fill-opacity": 1
        });
      });
    });

    this.on('legendClick', function (clicked, barIndex) {
      that.clicked = clicked;
      that.clickedBarIndex = barIndex;
      that.barSet.forEach(function (set, index) {
        if (index !== barIndex) {
          if (clicked) {
            set.attr({"fill-opacity": 0.1});
          } else {
            set.attr({"fill-opacity": 0.5});
          }
        } else {
          set.attr({"fill-opacity": 1});
        }
      });
    });
  };

setSource

设置数据源
Examples:

bar.setSource(source , {
        bar: 0,
        x: 1,
        value: 2
    });
方法 Bar.prototype.setSource() setSource
参数 source(Array) 数据源 第一列为排布在y轴的数据,后n列为排布在x轴的数据
参数 map(Object) 映射方式
代码
Bar.prototype.setSource = function (source, map) {
    var conf = this.defaults;
    map = this.map(map);
    var dataTable;
    if (DataV.detect(source) === 'Table_WITH_HEAD') {
      dataTable = DataV.collectionify(source);
    } else {
      dataTable = source;
    }
    this.bars = _.groupBy(dataTable, map.bar);
    this.barCount = _.keys(this.bars).length;

    conf.yAxisData = _.pluck(_.first(_.values(this.bars)), map.x);
    conf.yTickNumber = Math.min(conf.yAxisData.length, conf.yTickNumber);
    // 横坐标的范围
    conf.xExtent = d3.extent(dataTable, function (item) {
      return item[map.value];
    });
    // 横坐标基线值
    if (conf.xBase !== undefined) {
      conf.xExtent.push(conf.xBase);
      conf.xExtent = d3.extent(conf.xExtent);
    }
  };

setAxis

设置坐标轴

方法 Bar.prototype.setAxis() setAxis
代码
Bar.prototype.setAxis = function () {
    var conf = this.defaults;
    if (conf.showLegend) {
        conf.legendArea = [conf.width - conf.legendWidth, 0, conf.width, conf.height];
    } else {
        conf.legendWidth = 0;
        conf.legendArea = [0, 0, 0, 0];
    }
    var tagWidth = conf.width / 5 > 50 ? 50 : conf.width / 5;
    var margin = conf.margin;
    conf.diagramArea = [margin[3], margin[0], conf.width - conf.legendWidth - margin[1], conf.height - margin[2]];

    //设置x轴
    this.value = d3.scale.linear().domain(conf.xExtent).range([conf.diagramArea[0], conf.diagramArea[2]]);
    //设置y轴
    this.y = d3.scale.linear().domain([0, conf.yAxisData.length]).range([conf.diagramArea[3], conf.diagramArea[1]]);
    var valueRange = this.value.range();
    var yRange = this.y.range();
    var axis = this.axisPosition = {
      left: valueRange[0],
      right: valueRange[1],
      up: yRange[1],
      down: yRange[0]
    };
    var barsMaxLen = _.max(this.bars, function (bar) {
      return bar.length;
    }).length;
    this.barWidth = (axis.down - axis.up - barsMaxLen * conf.gap) / barsMaxLen / _.keys(this.bars).length;
  };

drawAxis

绘制坐标

方法 Bar.prototype.drawAxis() drawAxis
代码
Bar.prototype.drawAxis = function () {
    var that = this;
    var conf = this.defaults;
    var paper = this.canvas;
    var i, j, k, l;
    //画坐标轴
    var axisLines = paper.set();
    var tickText = paper.set();
    var axis = this.axisPosition;
    var ticks;
    // X轴
    var formatXScale = conf.formatXScale || that.formatXScale;
    ticks = this.value.ticks(conf.xTickNumber);
    for (j = 0; j < ticks.length; j++) {
      tickText.push(paper.text(this.value(ticks[j]), axis.down + 14, formatXScale(ticks[j])).attr({
        "fill": "#878791",
        "fill-opacity": 0.7,
        "font-size": 12,
        "text-anchor": "middle"
      }).rotate(0, axis.right + 6, this.value(ticks[j])));
      axisLines.push(paper.path("M" + this.value(ticks[j]) + "," + axis.down + "L" + this.value(ticks[j]) + "," + (axis.down + 5)));
    }
    axisLines.push(paper.path("M" + axis.left + "," + axis.up + "L" + axis.left + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });

    var numOfHLine = d3.round((axis.down - axis.up) / 30 - 1);
    var hLines = paper.set();
    for (j = 1; j <= numOfHLine; j++) {
      var hLinesPos = axis.right - j * 30;
      hLines.push(paper.path("M" + hLinesPos + "," + axis.up + "L" + hLinesPos + "," + axis.down));
    }
    hLines.attr({
      "stroke": "#ECECEC",
      "stroke-width": 1
    });
    
    //Y轴
    ticks = this.y.ticks(conf.yTickNumber);
    //console.log(ticks);
    var range = this.y.range();
    var formatLabel = conf.formatLabel || that.formatLabel;
    // 修复显示不从第一个x轴单位显示的bug
    for (j = 0; j < ticks.length; j++) {
      // 修改x轴单位显示在所有Bar组的中间位置
      // 修复x轴单位对于柱位置的偏移
      var y = this.y(ticks[j]) - conf.gap / 2 - this.barCount * Math.floor(this.barWidth) / 2;
      tickText.push(paper.text(axis.left - 10, y, formatLabel(conf.yAxisData[ticks[j]])).rotate(45, axis.left - 10, y).attr({
          "fill": "#878791",
          "fill-opacity": 0.7,
          "font-size": 12,
          "text-anchor": "end"
        }));
      axisLines.push(paper.path("M" + axis.left + "," + y + "L" + (axis.left - 5) + "," + y));
    }
    axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });
  };

drawDiagram

进行柱状图的绘制

方法 Bar.prototype.drawDiagram() drawDiagram
代码
Bar.prototype.drawDiagram = function () {
    var that = this;
    var conf = this.defaults;
    var axis = this.axisPosition;
    var paper = this.canvas;
    var dim = that.dimension;
    //bars
    var barWidth = this.barWidth;
    var barCount = this.barCount;
    var barSet = this.barSet;
    var values = _.values(this.bars);
    var tagSet = paper.set();

    //bars
    var mouseOverBar = function (event) {
        var barIndex = this.data('bar');
        var yIndex = this.data('index');
        if (that.clicked && that.clickedBarIndex !== barIndex) {
            return;
        }
        tagSet.remove();
        var currentSet = barSet.filter(function (set, barIndex) {
            return that.clicked ? that.clickedBarIndex === barIndex : true;
        });
        currentSet.forEach(function (set, barIndex) {
            set.forEach(function (bar, index) {
                if(yIndex == index) {
                    bar.stop().attr({
                        "fill-opacity": 1
                    });
                } else {
                    bar.stop().animate({
                        "fill-opacity": 0.3
                    },100);
                }
            });
        });

        var hovered = currentSet.map(function (set) {
            return set[yIndex];
        });
        var yPos = _.max(hovered, function (item) {
            return item.attrs.y;
        }).attrs.y + barWidth + 8;

        var x = _.map(hovered, function (item) {
            return item.attrs.x + item.attrs.width;
        });
        // TODO: 防遮罩算法
        for (var i = 1; i < x.length; i++) {
            for (var j = i - 1; j >= 0; j--) {
                var overlapped = x.filter(function (item, index) {
                    return index < i && Math.abs(item - x[i]) < 45;
                });
                if (overlapped.length > 0) {
                    var extent = d3.extent(overlapped);
                    if (x[i] <= extent[0]) {
                        x[i] = extent[0] - 45;
                    } else {
                    x[i] = extent[1] + 45;
                    }
                }
            }
        }
        hovered.forEach(function (item, barIndex) {
            var xPos = x[barIndex];
            var formatValue = conf.formatValue || that.formatValue;
            var valueLabel = formatValue('' + values[barIndex][yIndex][dim.value.index]);
            var textWidth = 5 * valueLabel.length + 20;
            var rect = paper.rect(xPos - textWidth / 2, yPos, textWidth, 20, 2).attr({
                "fill": conf.barColor[barIndex],
                "fill-opacity": 1,
                "stroke": "none"
            });
            var path = paper.path("M" + (xPos - 4) + "," + yPos + "L" + xPos + "," + (yPos - 5) + "L" + (xPos +4) + "," + yPos + "H" + (xPos - 4) + "Z").attr({
                "fill" : conf.barColor[barIndex],
                "stroke" : conf.barColor[barIndex]
            });
            var text = paper.text(xPos, yPos + 10, valueLabel).attr({
                "fill": "#ffffff",
                "fill-opacity": 1,
                "font-weight": "bold",
                "font-size": 12,
                "text-anchor": "middle"
            });
            tagSet.push(rect, path, text);
        });

        yPos = hovered.reduce(function (pre, cur) {
            return pre + cur.attrs.y;
        }, 0) / hovered.length + barWidth / 2;
        var formatLabel = conf.formatLabel || that.formatLabel;
        var yLabel = formatLabel('' + values[barIndex][yIndex][dim.x.index]);
        var textWidth = 6 * yLabel.length + 20;
        //axis y rect
        var rect = paper.rect(axis.left - textWidth, yPos - barWidth + 2, textWidth, 20, 2).attr({
            "fill": "#5f5f5f",
            "fill-opacity": 1,
            "stroke": "none"
        }).rotate(45, axis.left, yPos - barWidth + 2, textWidth);
        // axis y text
        var text = paper.text(axis.left - 2, yPos + 2, yLabel).attr({
            "fill": "#ffffff",
            "fill-opacity": 1,
            "font-weight": "bold",
            "font-size": 12,
            "text-anchor": "end"
      }).rotate(45, axis.left - 2, yPos - barWidth + 2, textWidth);
      tagSet.push(rect, text);
    };

    var mouseOutBar = function (event) {
        var barIndex = this.data('bar');
        var yIndex = this.data('index');
        var currentSet = barSet.filter(function (set, barIndex) {
            return that.clicked ? that.clickedBarIndex === barIndex : true;
        });
        tagSet.stop().animate({"opacity": 0}, 300, function () {
            tagSet.remove();
        });
        currentSet.forEach(function (set) {
            set.stop().animate({"fill-opacity": 1}, 100);
        });
    };

    values.forEach(function (bar, index) {
      barSet[index] = paper.set();
      bar.forEach(function (row, i) {
        var value = row[dim.value.index];
        var height = that.value(value);
        var y = that.y(i);
        var rect = paper.rect(axis.left, y - barWidth * (index + 1) - conf.gap / 2, height - axis.left, barWidth).attr({
          "fill": conf.barColor[index],
          "fill-opacity": 1,
          "stroke": "none"
        });
        rect.data('bar', index).data('index', i);
        rect.mouseover(_.debounce(mouseOverBar, 300));
        rect.mouseout(_.debounce(mouseOutBar, 300));
        barSet[index].push(rect);
      });
    });
  };

drawLegend

绘制图例

方法 Bar.prototype.drawLegend() drawLegend
代码
Bar.prototype.drawLegend = function () {
    var that = this;
    var paper = this.canvas;
    var legendSet = paper.set();
    var bgSet = paper.set();
    var conf = this.defaults;
    var legendArea = conf.legendArea;
    var barCount = this.barCount;
    if(!conf.showLegend) {
        return;
    }
    //legend
    var mouseOverLegend = function (event) {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({
        "fill-opacity":0.5
      });
      that.fire('legendOver', this.data('type'));
    };

    var mouseOutLegend = function (event) {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({"fill-opacity": 0});
      that.fire('legendOut', this.data('type'));
    };

    var clickLegend = function (event) {
      if (legendSet.clicked && legendSet.clickedBar === this.data('type')) {
        legendSet.clicked = false;
      } else {
        legendSet.clicked = true;
        legendSet.clickedBar = this.data('type');
      }
      bgSet.attr({"fill-opacity": 0});
      bgSet[this.data('type')].attr({
        "fill-opacity": legendSet.clicked ? 1 : 0
      });
      that.fire('legendClick', legendSet.clicked, this.data('type'));
    };

    var labels = _.keys(this.bars);
    for (var i = 0; i < labels.length; i++) {
      //底框
      bgSet.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "fill-opacity": 0,
        "stroke": "none"
      }));
      // 色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": conf.barColor[i],
        "stroke": "none"
      });
      // 文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, that.formatLegend(labels[i])).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12,
        "text-anchor": "start"
      });
      // 选框
      var rect = paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
      }).data("type", i);
      rect.mouseover(mouseOverLegend);
      rect.mouseout(mouseOutLegend);
      rect.click(clickLegend);
      legendSet.push(rect);
    }
  };

render

绘制柱状图

Options

  • width 宽度,默认为节点宽度
  • typeNames 指定y轴上数据类目

Examples

bar.render({"width": 1024})
方法 Bar.prototype.render() render
参数 options(Object) options json object for determin bar style.
代码
Bar.prototype.render = function (options) {
    this.setOptions(options);
    this.canvas.clear();
    this.setAxis();
    this.drawAxis();
    this.drawDiagram();
    this.drawLegend();
  };

charts/brush: API索引


d3_svg_brushRedrawX

set foreground and resizers' x and width;

函数 d3_svg_brushRedrawX() d3_svg_brushRedrawX
代码
function d3_svg_brushRedrawX(brushEls, extent) {
        brushEls.fg.attr({"x": extent[0][0],
                        "width": extent[1][0] - extent[0][0] });
        brushEls.resizerSet.forEach(function (el) {
            var orient = el.data("resizeOrient");

            if (orient === "n" ||
                    orient === "s" ||
                    orient === "w" ||
                    orient === "nw" ||
                    orient === "sw") {
                el.attr({"x": extent[0][0] - 2});
            } else { // "e" "ne" "se"
                el.attr({"x": extent[1][0] - 2});
            }
            if (orient === "n" || orient === "s") {
                el.attr({"width": extent[1][0] - extent[0][0]});
            }
        });
    }

d3_svg_brushRedrawY

set foreground and resizers' y and height;

函数 d3_svg_brushRedrawY() d3_svg_brushRedrawY
代码
function d3_svg_brushRedrawY(brushEls, extent) {
        brushEls.fg.attr({"y": extent[0][1],
                        "height": extent[1][1] - extent[0][1] });
        brushEls.resizerSet.forEach(function (el) {
            var orient = el.data("resizeOrient");
            if (orient === "n" ||
                    orient === "e" ||
                    orient === "w" ||
                    orient === "nw" ||
                    orient === "ne") {
                el.attr({"y": extent[0][1] - 3});
            } else { // "s" "se" "sw"
                el.attr({"y": extent[1][1] - 4});
            }
            if (orient === "e" || orient === "w") {
                el.attr({"height": extent[1][1] - extent[0][1]});
            }
        });
    }

d3_scaleExtent

function from d3, get scaleRange of an ordinal scale

函数 d3_scaleExtent() d3_scaleExtent
参数 ordinal(domain,) scale's range
代码
function d3_scaleExtent(domain) {
        var start = domain[0], stop = domain[domain.length - 1];
        return start < stop ? [start, stop] : [stop, start];
    }

d3_scaleRange

function from d3, get scaleRange of a scale

函数 d3_scaleRange() d3_scaleRange
代码
function d3_scaleRange(scale) {
        return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
    }

d3_svg_brushMove1

function from d3, called by d3_svg_brushMove, compute new brush extent after brush moved

函数 d3_svg_brushMove1() d3_svg_brushMove1
代码
function d3_svg_brushMove1(mouse, scale, i) {
        var range = d3_scaleRange(scale),
            r0 = range[0],
            r1 = range[1],
            offset = d3_svg_brushOffset[i],
            size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i],
            min,
            max;
      
        // When dragging, reduce the range by the extent size and offset.
        if (d3_svg_brushDrag) {
            r0 -= offset;
            r1 -= size + offset;
        }
      
        // Clamp the mouse so that the extent fits within the range extent.
        min = Math.max(r0, Math.min(r1, mouse[i]));
      
        // Compute the new extent bounds.
        if (d3_svg_brushDrag) {
            max = (min += offset) + size;
        } else {
            // If the ALT key is pressed, then preserve the center of the extent.
            if (d3_svg_brushCenter) {
                offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min));
            }
        
            // Compute the min and max of the offset and mouse.
            if (offset < min) {
                max = min;
                min = offset;
            } else {
                max = offset;
            }
        }

        // Update the stored bounds.
        d3_svg_brushExtent[0][i] = min;
        d3_svg_brushExtent[1][i] = max;
    }

d3_svg_brushMove

function from d3, after brush moved, compute new brush extent
and redraw foreground and resizer.

函数 d3_svg_brushMove() d3_svg_brushMove
代码
function d3_svg_brushMove(e) {
        if (d3_svg_brushOffset) {
            var bgOffset = $(d3_svg_brushTarget).offset();
            var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top];
            
            if (!d3_svg_brushDrag) {
                // If needed, determine the center from the current extent.
                if (e.altKey) {
                    if (!d3_svg_brushCenter) {
                        d3_svg_brushCenter = [
                            (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2,
                            (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2
                        ];
                    }
            
                    // Update the offset, for when the ALT key is released.
                    d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0];
                    d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1];
                } else {
                    // When the ALT key is released, we clear the center.
                    d3_svg_brushCenter = null;
                }
            }
        
            // Update the brush extent for each dimension.
            if (d3_svg_brushX) {
                d3_svg_brushMove1(mouse, d3_svg_brushX, 0);
                d3_svg_brushRedrawX(d3_svg_brushEls, d3_svg_brushExtent);
            }
            if (d3_svg_brushY) {
                d3_svg_brushMove1(mouse, d3_svg_brushY, 1);
                d3_svg_brushRedrawY(d3_svg_brushEls, d3_svg_brushExtent);
            }
        
            // Notify listeners.
            d3_svg_brushDispatch("brush");
        }
    }

d3_svg_brushKeydown

function from d3,
reset brush offset if user presses "space" key while brushing a new area,
to ensure foreground's size unchanged while position changing.

函数 d3_svg_brushKeydown() d3_svg_brushKeydown
代码
function d3_svg_brushKeydown(e) {
        if (e.keyCode === 32 && d3_svg_brushTarget && !d3_svg_brushDrag) {
            d3_svg_brushCenter = null;
            d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0];
            d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1];
            d3_svg_brushDrag = 2;
            e.stopPropagation();
        }
    }

d3_svg_brushKeyup

function from d3,
reset brush offset if "space" key up to restore normal drush state.

函数 d3_svg_brushKeyup() d3_svg_brushKeyup
代码
function d3_svg_brushKeyup(e) {
        if (e.keyCode === 32 && d3_svg_brushDrag === 2) {
            d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0];
            d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1];
            d3_svg_brushDrag = 0;
            e.stopPropagation();
        }
    }

d3_svg_brushUp

function from d3,
mouse up and stop brushing.

函数 d3_svg_brushUp() d3_svg_brushUp
代码
function d3_svg_brushUp(e) {
        if (d3_svg_brushOffset) {
            d3_svg_brushMove(e);
            d3_svg_brushEls.resizerSet.forEach(function (resizer) {
                //adjust all resizers
                var orient = resizer.data("resizeOrient");
                var size = d3_svg_brush.empty() ? 0 : 6;
                if (orient === "n" || orient === "s") {
                    resizer.attr({"height": size});
                } else {
                    resizer.attr({"width": size});
                }
            });
            d3_svg_brushDispatch("brushend");
            d3_svg_brush =
                d3_svg_brushDispatch =
                d3_svg_brushTarget =
                d3_svg_brushX =
                d3_svg_brushY =
                d3_svg_brushExtent =
                d3_svg_brushDrag =
                d3_svg_brushResize =
                d3_svg_brushCenter =
                d3_svg_brushOffset =
                d3_svg_brushEls = null;
            e.stopPropagation();
        }
    }
    
    var d3_svg_brushCursor = {
        n: "ns-resize",
        e: "ew-resize",
        s: "ns-resize",
        w: "ew-resize",
        nw: "nwse-resize",
        ne: "nesw-resize",
        se: "nwse-resize",
        sw: "nesw-resize"
    };
    var vml_brushCursor = {
        n: "row-resize",
        e: "col-resize",
        s: "row-resize",
        w: "col-resize",
        nw: "all-scroll",
        ne: "all-scroll",
        se: "all-scroll",
        sw: "all-scroll"
    };

    var Brush  = function () {
        var event = d3.dispatch("brushstart", "brush", "brushend"),
            x, // x-scale, optional
            y, // y-scale, optional
            extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1]
            e,
            left,
            top,
            width,
            height,
            backgroundAttr = {
                "fill": "#dddddd",
                "stroke": "none",
                "cursor": "crosshair"
            },
            foregroundAttr = {
                "fill": "steelblue",
                "stroke": "none",
                "cursor": "move"
            },
            brushStart = function () {},
            brushing = function () {},
            brushEnd = function () {},

            brushEls = {},
            brushClass;

charts/bubble: API索引


Bubble

Recently, bubble graph can represent five dimensions by xaxis,yaxis,size,color and time.
You can stop animation by pause() method, start animation by initControls method;
you can change animation start time by using global variable this.startTime;
you can visualize a time point's data by generatePaths(time point) method;
an inside method drawAllTime(key) is designed for interaction.

Options

  • width, 图表宽度,默认800
  • height, 图表高度, 默认600
  • minRadius, 最小圆角值
  • meshInterval, 背景网格的间距, 默认20
  • showLegend, 是否显示图例,默认显示
  • margin, 主图的间距,[上, 右, 下, 左], 默认为[30, 0, 80, 200]。当不显示图例时,可调节此处
  • tipStyle, 提示框样式
  • skeletonLineAttr, 龙骨线属性
  • skeletonCircleAttr, 龙骨点属性
声明 Bubble Bubble
代码
var Bubble = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Bubble";
            this.node = this.checkContainer(node);

            // setting display width and height, also they can be changed by options
            this.defaults.width = 600;
            this.defaults.height = 360;
            this.defaults.minRadius = 10;
            this.defaults.maxRadius = 40;
            this.defaults.meshInterval = 20;
            this.defaults.margin = [30, 0, 80, 100];
            this.defaults.allDimensions = [];
            this.defaults.dimensions = [];
            this.defaults.dimensionType = {};
            this.defaults.dimensionDomain = {};
            this.defaults.dotStrokeColor = {"stroke": "#fff"};
            this.defaults.colorBarHeight = 27;
            this.defaults.showLegend = true;
            this.defaults.skeletonCircleAttr = {
                "fill": "#000",
                "fill-opacity": 0.6,
                "stroke-width": 0
            };
            this.defaults.skeletonLineAttr = {
                "stroke": "#000",
                "stroke-width": 0.5,
                "stroke-opacity": 0.5
            };
            this.defaults.tipStyle = {
                "textAlign": "left",
                "margin": "auto",
                "color": "#ffffff"
            };

tipFormat

@param {String} key 关键字

方法 this.formatter.tipFormat() this.formatter tipFormat
参数 x0(Number) 横轴值
参数 y0(Number) 纵轴值
参数 r0(Number) 半径值
参数 c0(Number) 分组值
参数 time(String) 时间值
代码
this.formatter.tipFormat = function (key, x0, y0, r0, c0, time) {
                var tpl = '<b>' + this.keyDimen + ':{key}</b><br />' + 
                    '<b>' + this.xDimen + ':{xDimen}</b><br/>' +
                    '<b>' + this.yDimen + ':{yDimen}</b><br/>' +
                    '<b>' + this.sizeDimen + ':{sizeDimen}</b><br/>' + 
                    '<b>' + this.colorDimen + ':{colorDimen}</b><br/>' + 
                    '<b>' + this.timeDimen + ':{timeDimen}</b>';

                var tip = tpl.replace('{key}', key);
                tip = tip.replace('{xDimen}', x0);
                tip = tip.replace('{yDimen}', y0);
                tip = tip.replace('{sizeDimen}', r0);
                tip = tip.replace('{colorDimen}', c0);
                tip = tip.replace('{timeDimen}', time);
                return tip;
            };

            this.setOptions(options);
            this.createCanvas();
        }
    });

    Bubble.prototype.getTip = function (key, index) {
        var timeKeys = this.timeKeys;
        var value = timeKeys[index];
        var x = this.interpolateData(value, timeKeys, this.getKeyData(this.xDimen, key)),
            y = this.interpolateData(value, timeKeys, this.getKeyData(this.yDimen, key)),
            size = this.interpolateData(value, timeKeys, this.getKeyData(this.sizeDimen, key)),
            color = this.getColorData(key);
        var formatter = this.getFormatter("tipFormat");
        return formatter.call(this, key, x, y, size, color, this.times[value]);
    };

createCanvas

Creates a backCanvas for the visualization

方法 Bubble.prototype.createCanvas() createCanvas
代码
Bubble.prototype.createCanvas = function () {
        var conf = this.defaults;
        var margin = conf.margin;
        this.backCanvas = new Raphael(this.node, conf.width, conf.height);
        this.foreCanvas = new Raphael(this.node, conf.width, conf.height);

        $(this.node).css("position", "relative");
        $(this.foreCanvas.canvas).css({
            "position": "absolute",
            "zIndex": 2,
            "left": margin[3],
            "top": margin[0]
        });

        this.floatTag = DataV.FloatTag()(this.node);

        this.floatTag.css({"visibility": "hidden"});

        $(this.node).append(this.floatTag);
    };

chooseDimensions

Chooses bubble graph setted visualization dimens orderly

方法 Bubble.prototype.chooseDimensions() chooseDimensions
代码
Bubble.prototype.chooseDimensions = function (dimen) {
        var conf = this.defaults;
        conf.dimensions = dimen.filter(function (item) {
            return _.indexOf(conf.allDimensions, item) !== -1;
        });

        this.timeDimen = conf.dimensions[0];
        this.keyDimen = conf.dimensions[1];
        this.xDimen = conf.dimensions[2];
        this.yDimen = conf.dimensions[3];
        this.sizeDimen = conf.dimensions[4];
        this.colorDimen = conf.dimensions[5];

        this.keys = [];
        this.times = [];
        this.timeKeys = [];
        for (var i = 0, l = this.source.length; i < l; i++) {
            this.keys.push(this.source[i][this.keyDimen]);
            this.times.push(this.source[i][this.timeDimen]);
        }

        this.times = _.uniq(this.times);
        this.keys = _.uniq(this.keys);
        this.timeKeys = _.range(this.times.length);
        this.startTime = 0;
    };

setSource

set source, get dimensions data, dimension type, and dimension domain
default visualization dimension is setted here

方法 Bubble.prototype.setSource() setSource
代码
Bubble.prototype.setSource = function (source) {
        var conf = this.defaults;
        conf.allDimensions = source[0];
        // by default all dimensions show
        conf.dimensions = source[0];

        this.source = [];
        for (var i = 1, l = source.length; i < l; i++){
            var dot = {},
                dimen = conf.allDimensions;
            for (var j=0, ll=dimen.length; j < ll; j++){
                dot[dimen[j]] = source[i][j];
            }
            this.source.push(dot);
        }

        // judge dimesions type auto
        conf.dimensionType = {};
        for (var i = 0, l = conf.allDimensions.length; i < l; i++){
            var type = "quantitative";
            for (var j = 1, ll = source.length; j < ll; j++){
                var d = source[j][i];
                if(d && (!DataV.isNumeric(d))){
                    type = "ordinal";
                    break;
                }
            }
            conf.dimensionType[conf.allDimensions[i]] = type;
        }

        // set default dimensionDomain
        for (var i = 0, l = conf.allDimensions.length; i < l; i++){
            var dimen = conf.allDimensions[i];
            if (conf.dimensionType[dimen] === "quantitative") {
                conf.dimensionDomain[dimen] = d3.extent(this.source, function (p) {
                    return Math.abs(p[dimen]);
                });
            } else {
                conf.dimensionDomain[dimen] = this.source.map(function(p){
                    return p[dimen];
                });
            }
        }

        this.timeDimen = conf.dimensions[0];
        this.keyDimen = conf.dimensions[1];
        this.xDimen = conf.dimensions[2];
        this.yDimen = conf.dimensions[3];
        this.sizeDimen = conf.dimensions[4];
        this.colorDimen = conf.dimensions[5];

        this.keys = [];
        this.times = [];
        this.timeKeys = [];
        for (var i = 0, l = this.source.length; i < l; i++) {
            this.keys.push(this.source[i][this.keyDimen]);
            this.times.push(this.source[i][this.timeDimen]);
        }

        this.times = _.uniq(this.times);
        this.keys = _.uniq(this.keys);
        for (var i = 0, l = this.times.length; i < l; i++) {
            this.timeKeys.push(i);
        }
        this.startTime = 0;
    };

drawLegend

绘制图例

方法 Bubble.prototype.drawLegend() drawLegend
代码
Bubble.prototype.drawLegend = function () {
        var conf = this.defaults;
        var that = this;
        var colorData = _.uniq(this.keys.map(function (key) {
            return that.getColorData(key);
        }));
        // draw colorbar
        var tagArea = [20, (conf.height - conf.margin[2] - colorData.length * 23), 200, 220];
        var backCanvas = this.backCanvas;
        var rectBn = this.backCanvas.set();
        var underBn = [];
        for (var i = 0, l = colorData.length; i < l; i++) {
            var c = this.c[this.colorDimen](colorData[i]);
            // background to add interaction
            underBn.push(backCanvas.rect(tagArea[0] + 10, tagArea[1] + 10 + (20 + 3) * i, 120, 20)
                .attr({"fill": "#ebebeb", "stroke": "none"}).hide());
            // real colorbar
            backCanvas.rect(tagArea[0] + 10 + 3, tagArea[1] + 10 + (20 + 3) * i + 6, 16, 8)
                .attr({"fill": c, "stroke": "none"});
            // colorbar text
            backCanvas.text(tagArea[0] + 10 + 3 + 16 + 8, tagArea[1] + 10 + (20 + 3) * i + 10, colorData[i])
                .attr({"fill": "black", "fill-opacity": 1, "font-family": "Verdana", "font-size": 12})
                .attr({"text-anchor": "start"});
            // just for interaction -- selction
            rectBn.push(backCanvas.rect(tagArea[0] + 10, tagArea[1] + 10 + (20 + 3) * i, 50, 20)
                .attr({"fill": "white", "fill-opacity": 0, "stroke": "none"})
                .data("type", i).data("colorType", colorData[i]));
        }

        // add interaction for colorbar
        this.interactionType = null;
        rectBn.forEach(function (d) {
            d.hover(function () {
                if (!that.interval) {
                    for (var i = 0, l = underBn.length; i < l; i++) {
                        if (i === d.data("type")) {
                            underBn[i].show();
                            that.interactionType = d.data("colorType");
                            that.generatePaths(Math.ceil(that.startTime));
                        }
                    }
                }
            },
            function () {
                for (var i = 0, l = underBn.length; i < l; i++) {
                    if (i === d.data("type")) {
                        underBn[i].hide();
                        that.interactionType = null;
                    }
                }
            });
        });

    };

getScale

different visualization scale is defined here

方法 Bubble.prototype.getScale() getScale
代码
Bubble.prototype.getScale = function() {
        var that = this;
        var conf = this.defaults,
            margin = conf.margin,
            w = conf.width - margin[3] - margin[1],
            h = conf.height - margin[0] - margin[2],
            maxRadius = conf.maxRadius,
            minRadius = conf.minRadius;
        var backCanvas = this.backCanvas,
            xDimen = this.xDimen,
            yDimen = this.yDimen,
            sizeDimen = this.sizeDimen,
            colorDimen = this.colorDimen,
            xMin = conf.dimensionDomain[xDimen][0],
            yMin = conf.dimensionDomain[yDimen][0],
            xMax = conf.dimensionDomain[xDimen][1],
            yMax = conf.dimensionDomain[yDimen][1],
            xBorder = (maxRadius + 30) * (xMax - xMin)/w,
            yBorder = (maxRadius + 30) * (yMax - yMin)/h,
            xDomain = [xMin - xBorder, xMax + xBorder],
            yDomain = [yMin - yBorder, yMax + yBorder];

        this.x = {};
        this.x[xDimen] = d3.scale.linear()
            .domain(xDomain).range([margin[3], margin[3] + w]);
        this.y = {};
        this.y[yDimen] = d3.scale.linear()
            .domain(yDomain).range([h, 0]);
        this.z = {};
        this.z[sizeDimen] = d3.scale.linear()
            .domain(conf.dimensionDomain[sizeDimen]).range([minRadius, maxRadius]);
        this.c = {};
        this.c[colorDimen] = this.colorDB({mode: "random", ratio: 0.5});

        if (conf.showLegend) {
            this.drawLegend();
        }

        var playButtonBack = backCanvas.rect(0,0,24,24,2).attr({"stroke": "none","fill": "#d6d6d6"});
        var startPatternPath = "M7,18L19,12L7,6V18z";
        var stopPatternPathL = "M7,7sh4v10sh-4z";
        var stopPatternPathR = "M13,7sh4v10sh-4z";

        var startPattern = backCanvas.path(startPatternPath).attr({
            "stroke-width": 0,
            "stroke-linejoin": "round",
            "fill": "#606060"
        });
        var stopPattern = backCanvas.set();
        stopPattern.push(backCanvas.path(stopPatternPathL));
        stopPattern.push(backCanvas.path(stopPatternPathR));
        stopPattern.attr({
            "stroke-width": 0,
            "stroke-linejoin": "round",
            "fill": "#606060"
        });

        playButtonBack.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
        startPattern.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
        stopPattern.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
        startPattern.attr({
            "stroke-width": 0,
            "stroke-linejoin": "round",
            "fill-opacity": 0
        });

        var playButton = backCanvas.set();
        playButton.push(playButtonBack);
        playButton.push(startPattern);
        playButton.push(stopPattern);
        playButton.dblclick(function() {
            that.clearAnimation();
            that.render();
        });
        playButton.click(function() {
            if (that.interval) {
                stopPattern.attr({"fill-opacity": 0});
                startPattern.attr({"fill-opacity": 1});
                that.pause();
            } else {
                startPattern.attr({"fill-opacity": 0});
                stopPattern.attr({"fill-opacity": 1});
                that.initControls();
            }
        });
        playButton.hover(
            function() {
                startPattern.attr({"fill": "#ffffff"});
                stopPattern.attr({"fill": "#ffffff"});
            },
            function() {
                startPattern.attr({"fill": "#606060"});
                stopPattern.attr({"fill": "#606060"});
            }
        );

    };

renderAxis

draw x-axis, y-axis and related parts

方法 Bubble.prototype.renderAxis() renderAxis
代码
Bubble.prototype.renderAxis = function () {
        var conf = this.defaults,
            margin = conf.margin,
            w = conf.width - margin[3] - margin[1],
            h = conf.height - margin[0] - margin[2],
            maxRadius = conf.maxRadius,
            yaxis = Axis().orient("left"),
            xaxis = Axis().orient("bottom"),
            backCanvas = this.backCanvas,
            xDimen = this.xDimen,
            yDimen = this.yDimen,
            xMin = conf.dimensionDomain[xDimen][0],
            yMin = conf.dimensionDomain[yDimen][0],
            xMax = conf.dimensionDomain[xDimen][1],
            yMax = conf.dimensionDomain[yDimen][1],
            xBorder = (maxRadius + 30) * (xMax - xMin)/w,
            yBorder = (maxRadius + 30) * (yMax - yMin)/h,
            xDomain = [xMin - xBorder, xMax + xBorder],
            yDomain = [yMin - yBorder, yMax + yBorder],
            axixX = d3.scale.linear().domain(xDomain).range([0, w]),
            axixY = d3.scale.linear().domain(yDomain).range([h, 0]);

        backCanvas.clear();

        xaxis.scale(axixX)
            .tickSubdivide(1)
            .tickSize(6, 3, 0)
            .tickPadding(5)
            .tickAttr({"stroke": "#929292"})
            .tickTextAttr({"font-size": "10px", "fill": "#929292"})
            .minorTickAttr({"stroke": "#929292"})
            .domainAttr({"stroke-width": 1, "stroke": "#929292"})
            (backCanvas).attr({transform: "t" + margin[3] + "," + (margin[0] + h)});

        yaxis.scale(axixY)
            .tickSubdivide(1)
            .tickSize(6, 3, 0)
            .tickPadding(5)
            .tickAttr({"stroke": "#929292"})
            .tickTextAttr({"font-size": "10px", "fill": "#929292"})
            .minorTickAttr({"stroke": "#929292"})
            .domainAttr({"stroke-width": 1, "stroke": "#929292"})
            (backCanvas).attr({transform: "t" + margin[3] + "," + margin[0]});

        var xText = backCanvas.text(margin[3] + w/2, margin[0] + h + 40, this.xDimen);
        xText.attr({"font-size": "15px", "font-family": "Arial", "fill": "#000000"});
        var yText = backCanvas.text(margin[3] - 50, margin[0] + h/2, this.yDimen);
        yText.attr({"font-size": "15px", "font-family": "Arial", "fill": "#000000"}).transform("r-90");
    };

colorDB

color database

方法 Bubble.prototype.colorDB() colorDB
代码
Bubble.prototype.colorDB = function (colorJson) {
        var colorMatrix = DataV.getColor();
        var color;
        var colorStyle = colorJson || {};
        var colorMode = colorStyle.mode || 'default';
        var i, l;

        switch (colorMode) {
        case "gradient":
            var index = colorJson.index || 0;
            index = index < 0 ? 0 : Math.min(index, colorMatrix.length - 1);
            color = d3.interpolateRgb.apply(null, [colorMatrix[index][0], colorMatrix[index][1]]);
            break;
        case "random":
        case "default":
            var ratio = colorStyle.ratio || 0;
            if (ratio < 0) { ratio = 0; }
            if (ratio > 1) { ratio = 1; }
            var colorArray = [];
            for (i = 0, l = colorMatrix.length; i < l; i++) {
                var colorFunc = d3.interpolateRgb.apply(null, [colorMatrix[i][0], colorMatrix[i][1]]);
                colorArray.push(colorFunc(ratio));
            }
            color = d3.scale.ordinal().range(colorArray);
            break;
        }
        return color;
    };

generatePaths

main visualization method where bubble is drawed inside
a time point is the method's only parameter

方法 Bubble.prototype.generatePaths() generatePaths
代码
Bubble.prototype.generatePaths = function (time) {
        var conf = this.defaults,
            margin = conf.margin,
            meshInterval = conf.meshInterval,
            realWidth = conf.width - margin[3] - margin[1],
            realHeight = conf.height - margin[0] - margin[2],
            labelSize = 18,
            foreCanvas = this.foreCanvas,
            skeletonRadius = 2,
            timeKeys = this.timeKeys,
            keys = this.keys,
            dotBubbleSet = [];

        var that = this;
        if (time < this.times.length - 1) {
            this.startTime = time;
        } else {
            this.startTime = 0;
        }
        
        foreCanvas.clear();

        // draw mesh
        var meshes = foreCanvas.set(),
            verticleMeshNum = realWidth / meshInterval,
            horizontalMeshNUm = realHeight / meshInterval;
        var i;
        for (i = 1;i < verticleMeshNum;i++) {
            meshes.push(foreCanvas.path("M"+(i * meshInterval)+" "+0+"L"+(i * meshInterval)+
                " "+(realHeight-1)).attr({"stroke": "#ebebeb", "stroke-width": 1}));
        }
        for (i = 1; i < horizontalMeshNUm; i++) {
            meshes.push(foreCanvas.path("M"+1+" "+(realHeight - (i * meshInterval))+"L"+realWidth+
                " "+(realHeight - (i * meshInterval))).attr({"stroke": "#ebebeb", "stroke-dasharray": "-", "stroke-width": 0.5}));
        }
        var dots = [];
        // get all data by time and key dimension data
        for (var i = 0, l = keys.length; i < l; i++) {
            var x0 = this.interpolateData(time, timeKeys, this.getKeyData(this.xDimen, keys[i]));
            var y0 = this.interpolateData(time, timeKeys, this.getKeyData(this.yDimen, keys[i]));
            var r0 = this.interpolateData(time, timeKeys, this.getKeyData(this.sizeDimen, keys[i]));
            var c0 = this.getColorData(keys[i]);

            var dot = {key: keys[i], x0: x0, y0: y0, r0: r0, c0: c0};
            dots.push(dot);
        }

        var floatTag = this.floatTag;

        // control the time label
        var label = foreCanvas.text(20, margin[0] + realHeight + 15, this.times[Math.floor(time)]);
        label.attr({"font-size": labelSize, "fill": "#606060", "text-anchor": "start"});

        dots.sort(function(b,a) { return a.r0 < b.r0 ? -1 : a.r0 > b.r0 ? 1 : 0; });

        // draw the circles
        for (var i = 0, l = dots.length; i < l; i++) {
            var dot = dots[i],
                x = this.x[this.xDimen](dot.x0) - margin[3],
                y = this.y[this.yDimen](dot.y0),
                r = this.z[this.sizeDimen](dot.r0),
                c = this.c[this.colorDimen](dot.c0),
                dotBubble = foreCanvas.circle(x, y, r);
            dotBubble.attr({"stroke-width":0, "fill": c, "fill-opacity": 0.5})
                .data("key", dot.key).data("colorType", dot.c0);
            dotBubbleSet.push(dotBubble);
        }

        // add hover and click effect for all circles
        dotBubbleSet.forEach(function (d, i) {
            (function (i) {
                d.hover(function () {
                    floatTag.html(that.getTip(that.keys[i], Math.floor(time))).css(conf.tipStyle);
                    floatTag.css({"visibility" : "visible"});
                    if (!that.choose) {
                        d.attr({"stroke-width": 1, "stroke": "#f00", "fill-opacity": 0.8});
                        meshes.attr({"stroke": "#d6d6d6", "stroke-dasharray": "-", "stroke-width": 1});
                        for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
                            if (j !== i) {
                               dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.2});
                            }
                        }
                    }
                }, function () {
                    floatTag.css({"visibility" : "hidden"});
                    if (!that.choose) {
                        d.attr({"stroke-width": 0, "fill-opacity": 0.5});
                        meshes.attr({"stroke": "#ebebeb", "stroke-dasharray": "-", "stroke-width": 1});
                        for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
                            if (j !== i) {
                               dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.5});
                            }
                        }
                    }
                });

                d.click(function() {
                    if (time === Math.ceil(time)) {
                        drawAllTime(this.data("key"), i);
                    } else {
                        drawAllTime(this.data("key"), i);
                        this.remove();
                    }
                });
            }(i));
        });

        // colorbar interaction for showing all same color history data
        if (this.interactionType) {
            dotBubbleSet.forEach(function (d) {
                if (d.data("colorType") === that.interactionType) {
                    drawAllTime(d.data("key"));
                }
            });
        }

        // an inside method to visualize a key's all time data
        function drawAllTime (key, num) {
            if (!that.interval) {
                that.choose = true;
                var floatTag = that.floatTag;

                for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
                    if (j !== num) {
                        dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.2});
                    }
                }

                meshes.attr({"stroke": "#d6d6d6", "stroke-dasharray": "-"});
                var i;

                for (i = 0, l = timeKeys.length; i < l; i++) {
                    (function (i) {
                        var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
                            y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
                            r0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.sizeDimen, key)),
                            c0 = that.getColorData(key),
                            x = that.x[that.xDimen](x0) - margin[3],
                            y = that.y[that.yDimen](y0),
                            r = that.z[that.sizeDimen](r0),
                            c = that.c[that.colorDimen](c0),
                            fOpacity = 0.1 + Math.pow(1.5, i)/Math.pow(1.5, l);
                        var historyBubble = foreCanvas.circle(x, y, r);
                        historyBubble.attr({"stroke-width": 0, "fill": c, "fill-opacity": fOpacity});

                        if (timeKeys[i] === Math.ceil(time)) {
                            historyBubble.attr({"stroke-width": 1, "stroke": "#f00"});
                            historyBubble.hover(function () {
                                floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
                                floatTag.css({"visibility" : "visible"});
                            }, function () {
                                floatTag.css({"visibility" : "hidden"});
                            });
                        } else {
                            historyBubble.hover(function () {
                                this.attr({"stroke-width": 1, "stroke": "#f00"});
                                floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
                                floatTag.css({"visibility" : "visible"});
                            }, function () {
                                this.attr({"stroke-width": 0});
                                floatTag.css({"visibility" : "hidden"});
                            });
                        }

                        historyBubble.click(function () {
                            that.generatePaths(Math.ceil(time));
                            that.choose = false;
                        });

                    }(i));
                }

                var skeletonLineSet = foreCanvas.set();
                for (i = 1, l = timeKeys.length; i < l; i++) {
                    var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
                        y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
                        x = that.x[that.xDimen](x0) - margin[3],
                        y = that.y[that.yDimen](y0),
                        x1 = that.interpolateData(timeKeys[i-1], timeKeys, that.getKeyData(that.xDimen, key)),
                        y1 = that.interpolateData(timeKeys[i-1], timeKeys, that.getKeyData(that.yDimen, key)),
                        x2 = that.x[that.xDimen](x1) - margin[3],
                        y2 = that.y[that.yDimen](y1);
                    var skeletonLine = foreCanvas.path("M"+x2+" "+y2+"L"+x+" "+y);
                        skeletonLine.attr(conf.skeletonLineAttr);
                        skeletonLineSet.push(skeletonLine);
                }

                var skeletonCircleSet = foreCanvas.set();
                for (i = 0, l = timeKeys.length; i < l; i++) {
                    (function (i) {
                        var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
                            y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
                            c0 = that.getColorData(key),
                            x = that.x[that.xDimen](x0) - margin[3],
                            y = that.y[that.yDimen](y0),
                            c = that.c[that.colorDimen](c0);

                        var skeletonCircle = foreCanvas.circle(x,y,skeletonRadius);
                        skeletonCircle.attr(conf.skeletonCircleAttr).attr({"stroke": c});
                        skeletonCircleSet.push(skeletonCircle);

                        if (timeKeys[i] === Math.ceil(time)) {
                            skeletonCircle.attr({"fill": "#f00"});
                            skeletonCircle.click(function () {
                                that.generatePaths(Math.ceil(time));
                            });
                            skeletonCircle.hover(function () {
                                floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
                                floatTag.css({"visibility" : "visible"});
                                skeletonCircleSet.attr({"fill-opacity": 0.35});
                                this.attr({"fill-opacity": 1, "r": 5});
                                skeletonLineSet.attr({"opacity": 0.35});
                            }, function () {
                                floatTag.css({"visibility" : "hidden"});
                                this.attr(conf.dotStrokeColor);
                                skeletonCircleSet.attr({"fill-opacity": 0.7});
                                this.attr({"r": skeletonRadius});
                                // meshes.attr({"stroke": "#ebebeb", "stroke-dasharray": "-"});
                                skeletonLineSet.attr({"opacity": 0.7});
                            });
                        } else {
                            skeletonCircle.hover(function () {
                                floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
                                floatTag.css({"visibility" : "visible"});
                                skeletonCircleSet.attr({"fill-opacity": 0.35});
                                this.attr({"fill-opacity": 1, "r": 5});
                                skeletonLineSet.attr({"opacity": 0.35});
                            }, function () {
                                floatTag.css({"visibility" : "hidden"});
                                this.attr(conf.dotStrokeColor);
                                skeletonCircleSet.attr({"fill-opacity": 0.7});
                                this.attr({"r": skeletonRadius});
                                skeletonLineSet.attr({"opacity": 0.7});
                            });
                        }
                    }(i));
                }
            }
        }
    };

getKeyData

get key's specific dimension data which include all time points

方法 Bubble.prototype.getKeyData() getKeyData
代码
Bubble.prototype.getKeyData = function(dimen, key) {
        var that = this;
        return _.map(_.filter(this.source, function (item) {
            return item[that.keyDimen] === key;
        }), function (item) {
            return item[dimen];
        });
    };

getColorData

get a unique color specified by key

方法 Bubble.prototype.getColorData() getColorData
代码
Bubble.prototype.getColorData = function(key) {
        for (var i = 0; i < this.source.length; i++) {
            if (this.source[i][this.keyDimen] === key) {
                return this.source[i][this.colorDimen];
            }
        }
    };

initControls

set up an animation

方法 Bubble.prototype.initControls() initControls
代码
Bubble.prototype.initControls = function() {  
        var that = this,
            len = this.times.length -1;
        var value = this.startTime;

        this.interval = setInterval(function() {
            if (value <= len) {
                that.generatePaths(value);
                value += 0.25;
            } else {
                clearInterval(that.interval);
                that.interval = 0;
            }
        }, 250);
    };

interpolateData

interpolated some data between neibourh data point for the animation

方法 Bubble.prototype.interpolateData() interpolateData
代码
Bubble.prototype.interpolateData = function(year, years, values) {
        var index = Math.ceil(year);
        if (year === years[index]) {
            return values[index];
        }
        var lowerIndex = Math.max(0,index-1);
        var lower = values[lowerIndex];
        var higherIndex = index;
        var higher = values[higherIndex];
        var lowYear = years[lowerIndex];
        var highYear = years[higherIndex];
        var p = (year - lowYear) / (highYear - lowYear);
        var value = +lower + (higher - lower) * p;
        return value;
    };

clearAnimation

clear animation and related artifacts

方法 Bubble.prototype.clearAnimation() clearAnimation
代码
Bubble.prototype.clearAnimation = function () {
        clearInterval(this.interval);
        this.interval = 0;
        this.backCanvas.clear();
        this.foreCanvas.clear();
    };

pause

pause the interval

方法 Bubble.prototype.pause() pause
代码
Bubble.prototype.pause = function () {
        clearInterval(this.interval);
        this.interval = 0;
    };

render

set the rendering process

方法 Bubble.prototype.render() render
代码
Bubble.prototype.render = function (options) {
        clearInterval(this.interval);
        this.setOptions(options);
        if (!this.interval) {
            this.renderAxis();
        }
        this.foreCanvas.clear();
        this.getScale();
        this.initControls();
    };

    return Bubble;
});

charts/bullet: API索引


Bullet

Bullet构造函数

Options

  • width 数字,图片宽度,默认为200,表示图片高200px
  • height 数字,图片高度,默认为80
  • orient string,图片方向,默认为"horizonal",表示水平方向。若为"vertical",则表示垂直方向
  • axisStyle string, 坐标系类型,默认为"linear",表示坐标系为线性坐标系。若为"log",则表示对数坐标系
  • logBase 数字, 采用对数坐标系时的对数基,默认为Math.E
  • tickDivide 数字,表示坐标的标尺的分段数,默认为5
  • margin 数字数组,表示图片上、右、下、左的边距,默认为 [20, 20, 20, 20]
  • centerBarRatio 数字,表示中间的测度条的高度与背景条高度的比值, 默认为0.3
  • markerWidth 数字,表示标记条的宽度, 默认为4,单位为像素
  • markerRatio 数字,表示标记条的高度与背景条高度的比值,默认为0.7
  • titleRatio 数字,表示子弹图title的高度与背景条高度的比值,默认为0.6,此时title与subtitle的比值
  • backgroundColor string数组,表示背景条颜色的渐变数组,默认为["#666", "#ddd"],背景条颜色就是这两种颜色的渐变
  • measureColor string数组,表示测度条颜色的渐变数组,默认为["steelblue", "#B0C4DE"],测度条颜色就是这两种颜色的渐变
  • markerColor string,表示标记条的颜色,默认为"#000"

Examples

//Create bullet in a dom node with id "chart", width is 500; height is 600px;
var bullet = new Bullet("chart", {
    "width": 500,
    "height": 600,
    "margin": [10, 10, 20, 70]
});
//Create bullet with log base;
var log = new Bullet("chart2", {
    width: 300,
    height: 60,
    margin: [10, 10, 20, 70],
    backgroundColor: ["#66f", "#ddf"],
    measureColor: ["#000", "#000"],
    markerColor: "#44f",
    axisStyle: "log",
    logBase: 10
});
声明 Bullet Bullet
参数 node(Mix) The dom node or dom node Id
参数 options(Object) options json object for determin stream style.
代码
var Bullet = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Bullet";
            this.node = this.checkContainer(node);
            // Properties
            this.defaults.orient = "horizonal"; // "horizonal", "vertical"
            this.defaults.axisStyle = "linear"; // "linear", "log"
            this.defaults.logBase = Math.E;
            this.defaults.tickDivide = 5;
            this.defaults.margin = [10, 10, 20, 80];//top, right, bottom, left
            this.defaults.centerBarRatio = 0.3;
            this.defaults.markerWidth = 4;
            this.defaults.markerRatio = 0.7;
            this.defaults.titleRatio = 0.6; //title's text height : subtitle's text height = 6:4
            this.defaults.backgroundColor = ["#666", "#ddd"]; //dark, light
            this.defaults.measureColor = ["steelblue", "#B0C4DE"]; //dark, light
            this.defaults.markerColor = "#000";

            // canvas
            this.defaults.width = 200;
            this.defaults.height = 80;

            this.setOptions(options);
            this.createCanvas();
        }
    });

setSource

设置数据源

Examples

bullet.setSource({
   title: "Sample",
   subtitle: "ratio",
   ranges: [0, 0.5, 0.8, 1],
   measures: [0.7, 0.9],
   markers: [0.6],
   rangeTitles: ["below 50%", "top 20% - 50%", "top 20%"],
   measureTitles: ["value is 0.7", "value is 0.9"],
   markerTitles: ["mean is 0.6"]
})
方法 Bullet.prototype.setSource() setSource
参数 source(Object) 数据源
代码
Bullet.prototype.setSource = function (source) {
        var conf = this.defaults,
            range,
            axisOrient;
        this.data = source;
        if (conf.orient === "horizonal") {
            axisOrient = "bottom";
            range = [conf.margin[3], conf.width - conf.margin[1]];
        } else if (conf.orient === "vertical") {
            axisOrient = "left";
            range = [conf.height - conf.margin[2], conf.margin[0]];
        }

        if (conf.axisStyle === "linear") {
            this.scale = d3.scale.linear();
        } else if (conf.axisStyle === "log") {
            this.scale = d3.scale.log();
        }

        this.data.min = this.data.ranges[0];
        this.data.max = this.data.ranges[this.data.ranges.length - 1];
        this.scale.domain([this.data.min, this.data.max])
            .range(range);

        if (conf.axisStyle === "linear") {
            this.axis = Axis().scale(this.scale).orient(axisOrient).ticks(conf.tickDivide).domainAttr({"stroke": "none"});
        } else if (conf.axisStyle === "log") {
            this.logScale = d3.scale.linear()
                .domain([Math.log(this.data.min)/Math.log(conf.logBase), Math.log(this.data.max)/Math.log(conf.logBase)])
                .range(range);
            this.axis = Axis()
                .orient(axisOrient)
                .scale(this.logScale)
                .ticks(conf.tickDivide)
                .tickFormat(function (d) {return Math.round(Math.pow(conf.logBase, d));})
                .domainAttr({"stroke": "none"});
        }
    };

render

render bullet

方法 Bullet.prototype.render() render
代码
Bullet.prototype.render = function (options) {
        this.setOptions(options);
        this.generatePaths();
    };

    return Bullet;
});

charts/bundle: API索引


Bundle

构造函数,node参数表示在html的哪个容器中绘制该组件
options对象为用户自定义的组件的属性,比如画布大小

声明 Bundle Bundle
代码
var Bundle = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Bundle";
            this.node = this.checkContainer(node);
            this.json = {};
    
            // 图的半径
            this.defaults.diameter = 960;
            this.defaults.radius = this.defaults.diameter / 2;
            this.defaults.innerRadius = this.defaults.radius - 120;
            this.defaults.tension = 0.85;
    
            this.defaults.color = {
                defaultLineColor: "#4065AF",
                defaultWordColor: "#000000",
                lineHoverColor: "#02B0ED",
                nodeHoverColor: "#02B0ED",
                importNodesColor: "#5DA714", //被引用的节点
                exportNodesColor: "#FE3919" //引用当前节点的节点
            };
    
            this.setOptions(options);
            this.createCanvas();
        }
    });

setOptions

设置用户自定义属性

方法 Bundle.prototype.setOptions() setOptions
代码
Bundle.prototype.setOptions = function (options) {
        if (options) {
            var prop;
            for (prop in options) {
                if (options.hasOwnProperty(prop)) {
                    this.defaults[prop] = options[prop];
                    if (prop === "diameter") {
                        this.defaults.radius = this.defaults.diameter / 2;
                        this.defaults.innerRadius = this.defaults.radius - 120;
                    } else if (prop === "radius") {
                        this.defaults.diameter = this.defaults.radius * 2;
                        this.defaults.innerRadius = this.defaults.radius - 120;
                    } else if (prop === "innerRadius") {
                        this.defaults.radius = this.defaults.innerRadius + 120;
                        this.defaults.diameter = this.defaults.radius * 2;
                    } else if (prop === "width") {
                        this.defaults.diameter = this.defaults.width;
                        this.defaults.radius = this.defaults.diameter / 2;
                        this.defaults.innerRadius = this.defaults.radius - 120;
                    }
                }
            }
        }
    };

setSource

对原始数据进行处理
TODO: 改进为获取值时运算

方法 Bundle.prototype.setSource() setSource
代码
Bundle.prototype.setSource = function (source) {
        if (source[0] && source[0] instanceof Array) {
            // csv or 2d array source
            if (source[0][0] === "name") {
                source = source.slice(1); // 从第一行开始,第0行舍去
            }
            var nData = [];
            var imports = [];
            //var isNode = true;
            var nodeNum;
            var that = this;
            source.forEach(function (d, i) {
                if (d[0] === "") {
                    throw new Error("name can not be empty(line:" + (i + 1) + ").");
                }
                if (d[1] !== "") {
                    imports = d[1].split(" ");
                }
                nData[i] = {
                    name: d[0],
                    imports: imports
                };
            });
            this.json = nData;
        } else {
            // json source
            this.json = source;
        }
    };

createCanvas

创建画布

方法 Bundle.prototype.createCanvas() createCanvas
代码
Bundle.prototype.createCanvas = function () {
        var conf = this.defaults;
        this.canvas = new Raphael(this.node, conf.diameter, conf.diameter);

        //var c = this.canvas.circle(50, 50, 40);
    };

layout

布局

方法 Bundle.prototype.layout() layout
代码
Bundle.prototype.layout = function () {
        var packages = {
            // Lazily construct the package hierarchy from class names.
            root: function (classes) {
                var map = {};
                function construct(name, data) {
                    var node = map[name], i;
                    if (!node) {
                        node = map[name] = data || {name: name, children: []};
                        if (name.length) {
                            node.parent = construct(name.substring(0, i = name.lastIndexOf(".")));
                            node.parent.children.push(node);
                            node.key = name.substring(i + 1);
                        }
                    }
                    return node;
                }
          
                classes.forEach(function (d) {
                    construct(d.name, d);
                });
          
                return map[""];
            },
        
            // Return a list of imports for the given array of nodes.
            imports: function (nodes) {
                var map = {},
                    imports = [];
          
                // Compute a map from name to node.
                nodes.forEach(function (d) {
                    map[d.name] = d;
                });
          
                // For each import, construct a link from the source to target node.
                nodes.forEach(function (d) {
                    if (d.imports) {
                        d.imports.forEach(function (i) {imports.push({source: map[d.name], target: map[i]});
                            });
                    }
                });
          
                return imports;
            }
        };
        
        var cluster = d3.layout.cluster()
            .size([360, this.defaults.innerRadius]) //.size(角度,半径)
            .sort(null)
            .value(function (d) {
                return d.size;
            });
        this.nodes = cluster.nodes(packages.root(this.json));
        this.links = packages.imports(this.nodes);
    };

render

渲染弦图

方法 Bundle.prototype.render() render
代码
Bundle.prototype.render = function () {
        this.layout();
        this.generatePaths();
    };

generatePaths

生成路径

方法 Bundle.prototype.generatePaths() generatePaths
代码
Bundle.prototype.generatePaths = function (options) {
        var that = this;

        var canvas = this.canvas;
        var rNodes = canvas.set();
        var rLinks = canvas.set();

        var bundle = d3.layout.bundle();

        var line = d3.svg.line.radial()
            .interpolate("bundle")
            .tension(this.defaults.tension)
            .radius(function (d) {
                return d.y;
            })
            .angle(function (d) {
                return d.x / 180 * Math.PI;
            });

        //定义图中的弦和节点
        var nodes = this.nodes;
        var links = this.links;
        var linksCount = links.length;
        var paths = bundle(links);

        var locateStr = ""; //对文字进行平移和旋转
        var locateBBox = ""; //对文字的bounding box进行平移和旋转
        var r = 0;
        var angle = 0;
        var xTrans = 0;
        var yTrans = 0;
        var anchor; //text-anchor: start or end
        var rotateStr = "";

        //element data cache
        var nodeRelatedElements = {};// {key: {targetLink: [], sourceLink: [], targetNode: [], sourceNode: []}}
        var nodeElements = {}; //{key: Els}
        var bBoxElements = {}; //{key: Els}

        var i,
            j,
            key,
            textEl,
            bBox,
            bBoxNew,
            tCenterX,
            tCenterY,
            bBoxEl,
            linkEl;

        var mouseoverLink = function () {
            var current = this;
            //var color = that.data("color");
            if (rLinks.preLink) {
                rLinks.preLink.attr("stroke", that.defaults.color.defaultLineColor)
                    .attr("stroke-width", 1)
                    .attr("stroke-opacity", 0.6);

            }
            rLinks.preLink = this;

            current.attr("stroke", that.defaults.color.lineHoverColor)
                .attr("stroke-width", 2)
                .attr("stroke-opacity", 1.0)
                .toFront(); //把当前弦移到画布最上层
        };

        var mouseoverNode = function () {
            var relatedEl = this.data("relatedElements");
            //高亮所选节点的文字颜色
            this.data("relatedNode").attr({"fill": that.defaults.color.nodeHoverColor,
                "fill-opacity": 1.0, "font-weight": "600"});
            //将包围盒颜色设为透明
            this.attr({"fill": that.defaults.color.nodeHoverColor, "fill-opacity": 0.0

"font-weight": "600"

代码
);
            
            relatedEl.sourceLink.forEach(function (d) { //set green
                d.attr({"stroke": that.defaults.color.importNodesColor, "stroke-width": 1, "stroke-opacity": 0.9})
                    .toFront();
            });
            relatedEl.sourceNode.forEach(function (d) {
                d.attr({"fill": that.defaults.color.importNodesColor, "font-weight": "600"});
            });
            relatedEl.targetLink.forEach(function (d) { //set red
                d.attr({"stroke": that.defaults.color.exportNodesColor, "stroke-width": 1, "stroke-opacity": 0.9})
                    .toFront();
            });
            relatedEl.targetNode.forEach(function (d) {
                d.attr({"fill": that.defaults.color.exportNodesColor, "font-weight": "600"});
            });
        };

        var mouseoutNode = function () {
            var relatedEl = this.data("relatedElements");
            this.data("relatedNode").attr({"fill": that.defaults.color.defaultWordColor,
                "font-weight": "400", "fill-opacity": 1.0});
            relatedEl.targetLink.forEach(function (d) {
                d.attr({"stroke": that.defaults.color.defaultLineColor, "stroke-width": 1, "stroke-opacity": 0.6});
            });
            relatedEl.targetNode.forEach(function (d) {
                d.attr({"fill": that.defaults.color.defaultWordColor, "font-weight": "400"});
            });
            relatedEl.sourceLink.forEach(function (d) {
                d.attr({"stroke": that.defaults.color.defaultLineColor, "stroke-width": 1, "stroke-opacity": 0.6});
            });
            relatedEl.sourceNode.forEach(function (d) {
                d.attr({"fill": that.defaults.color.defaultWordColor, "font-weight": "400"});
            });
        };

        for (j = 0; j < nodes.length; j++) {
            //若为叶子节点
            if (!nodes[j].children) {
                locateStr = "T" + that.defaults.radius + "," + that.defaults.radius + "R"; //使用大写T、R、S--绝对,not相对

                //半径: add a padding between lines and words
                r = nodes[j].y + 20;

                //计算旋转角度和水平、竖直方向所需平移的距离
                angle = (nodes[j].x - 90) * Math.PI / 180;
                xTrans = r * Math.cos(angle);
                yTrans = r * Math.sin(angle);

                //计算text-anchor
                if (nodes[j].x < 180) {
                    anchor = "start";
                } else {
                    anchor = "end";
                }

                //计算文字方向是否需要旋转180度
                if (nodes[j].x < 180) {
                    rotateStr = "";
                } else {
                    rotateStr = "R180";
                }

                //计算文字需要如何经过平移和旋转被排列在圆周上
                locateStr += (nodes[j].x - 90) + rotateStr + "T" + xTrans + "," + yTrans;

                //绘制文字
                textEl = canvas.text()
                    .attr("font", "11px arial")
                    .data("color", that.defaults.color)
                    .attr("text", nodes[j].key)
                    //.attr("title", nodes[j].size)
                    .transform(locateStr)
                    .attr("text-anchor", anchor)
                    .attr("fill", that.defaults.color.defaultWordColor);

                //获取旋转平移之前文字的bounding box
                bBox = textEl.getBBox(true);

                //canvas.rect(bBox.x, bBox.y, bBox.width, bBox.height);
                //获取旋转平移之后文字的bounding box
                bBoxNew = textEl.getBBox();
                //adjust vml box center
                if (Raphael.vml) {
                    //vml's word bbox is not related to text-anchor, always middle;
                    //svg's word bbox is related to text-anchor;
                    bBoxNew.x = bBoxNew.x + bBox.width / 2 * Math.cos(angle);
                    bBoxNew.y = bBoxNew.y + bBox.width / 2 * Math.sin(angle);
                }
                //canvas.rect(bBoxNew.x, bBoxNew.y, bBoxNew.width, bBoxNew.height);

                //新旧bounding box的中心坐标变化
                tCenterX = bBoxNew.x + bBoxNew.width / 2 - bBox.x - bBox.width / 2;
                tCenterY = bBoxNew.y + bBoxNew.height / 2 - bBox.y - bBox.height / 2;
                //对bounding box进行平移和旋转
                locateBBox = "T" + tCenterX + "," + tCenterY + "R" + (nodes[j].x - 90) + rotateStr;

                // 包围盒
                bBoxEl = canvas.rect(bBox.x, bBox.y, bBox.width, bBox.height)
                    .transform(locateBBox)
                    .data("relatedNode", textEl)
                    .attr({"fill": "#fff", "opacity": 0.01});
                
                key = nodes[j].key;
                nodeElements[key] = textEl;
                bBoxElements[key] = bBoxEl;
                nodeRelatedElements[key] = {targetLink: [], sourceLink: [], targetNode: [], sourceNode: []};

                rNodes.push(textEl);
            }
        }

        //绘制曲线
        for (i = 0; i < linksCount; i++) {
            var l = paths[i];

            //对paths数组中的每一项进行计算,由路径节点信息得到坐标值
            var spline = line(l);
            var sourceKey = links[i].source.key;
            var targetKey = links[i].target.key;
            var tips = "link source: " + sourceKey  + "\n"
                        + "link target: " + targetKey;

            linkEl = canvas.path(spline)
                //.attr("stroke", that.defaults.defaultLineColor)
                .attr("stroke-opacity", 0.6)
                .attr("title", tips)
                .attr("d", spline)
                .attr("stroke", that.defaults.color.defaultLineColor)
                .translate(that.defaults.radius, that.defaults.radius)
                .mouseover(mouseoverLink);
                //.mouseout(mouseoutLink);
            linkEl[0].el = linkEl;

            nodeRelatedElements[sourceKey].targetLink.push(linkEl);
            nodeRelatedElements[sourceKey].targetNode.push(nodeElements[targetKey]);
            nodeRelatedElements[targetKey].sourceLink.push(linkEl);
            nodeRelatedElements[targetKey].sourceNode.push(nodeElements[sourceKey]);
            rLinks.push(linkEl);
        }

        $(this.canvas.canvas).mousemove(function (e) {
                    if(!e.target.el && rLinks.preLink){
                        rLinks.preLink.attr("stroke", that.defaults.color.defaultLineColor)
                            .attr("stroke-width", 1)
                            .attr("stroke-opacity", 0.6);
                        rLinks.preLink = undefined;
                        //console.log("a");
                    }
                });

        //bind text words hover event
        for (key in bBoxElements) {
            if (bBoxElements.hasOwnProperty(key)) {
                bBoxElements[key].data("relatedElements", nodeRelatedElements[key])
                    .mouseover(mouseoverNode)
                    .mouseout(mouseoutNode);
            }
        }

    };

    return Bundle;
});

charts/chinamap: API索引


Chinamap

Chinamap构造函数,继承自Chart

Options

  • width 数字,图宽度,默认为600,表示图高600px
  • height 数字,图高度,默认为500
  • geoDataPath 字符串,地图数据的文件路径,如"../../lib/charts/data/chinaMap/"
  • mapId 字符串,地图区域的Id,用于载入相应的地图文件。全国时,mapId为"0";新疆为"65"。其他各省Id可参考this.geoData.areaIdIndex。可调用getMapId()来获取某地区的mapId。
  • showWords 布尔值,是否显示省名或市名的文字。默认为true,显示。
  • levelChangeable 布尔值,是否开启全国与省之间的缩放。默认为true,开启。
  • zoomAnimate 布尔值,缩放时是否显示动画。默认为true, 开启(因性能原因,IE6-8始终不开启)。
  • colorModel 字符串,表示取色模式,可取"discrete"(默认)或"gradient"。"discrete"时颜色在colors中从头至尾循环选取,"gradient"时依照colors中颜色的渐变插值计算。
  • colors 颜色字符串数组。默认为discrete模式下的离散颜色数组。
  • defaultAreacolor 颜色字符串。默认为"#dddddd"。当区域没有数据时默认显示的颜色。
  • wordStyle object。默认为{}。省名或城市名的文字的Raphael样式。具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr
  • areaStyle object。默认为{}。省或城市的区域的Raphael样式。具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr

Events

  • areaHoverIn 函数,表示鼠标移上某个区域的事件响应,默认为空函数;
  • areaHoverOut 函数,表示鼠标移出某个区域的事件响应,默认为空函数;
  • areaClick 函数,表示点击某个区域的事件响应,默认为空函数;
  • wordHoverIn 函数,表示鼠标移上某个地名文字时的事件响应;
  • wordHoverOut 函数,表示鼠标移出某个地名文字时的事件响应;
  • wordClick 函数,表示点击某个地名文字的事件响应,默认为空函数;

Examples

create chinamap in a dom node with id "chart", width is 500; height is 600px;

var chinamap = new Chinamap("chart", {"width": 500, "height": 600});
声明 Chinamap Chinamap
参数 node(Object) The dom node or dom node Id
参数 options(Object) JSON object for determin chinamap style
代码
var Chinamap = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Chinamap";
      this.node = this.checkContainer(node);

      // Properties

viewBox

this.floatTag;//浮框对象,这是个可操作的对象。

属性 this.viewBox this viewBox
代码
this.viewBox = [];
      this.mapCache = {};
      this.states = [];
      this.words = [];
      this.projection = function () {}; // d3 map prejection function
      this.getAreaPath = function () {}; // input geojson feature, return area path string
      this.colorGenerator = function () {}; // produce gradient or discrete color;
      this.sourceData = {};
      this.geoData = {};
      // 区域与编号的对应关系
      this.geoData.areaIdIndex = {
        '全国': 0,
        '新疆': 65,
        '西藏': 54,
        '内蒙古': 15,
        '青海': 63,
        '四川': 51,
        '黑龙江': 23,
        '甘肃': 62,
        '云南': 53,
        '广西': 45,
        '湖南': 43,
        '陕西': 61,
        '广东': 44,
        '吉林': 22,
        '河北': 13,
        '湖北': 42,
        '贵州': 52,
        '山东': 37,
        '江西': 36,
        '河南': 41,
        '辽宁': 21,
        '山西': 14,
        '安徽': 34,
        '福建': 35,
        '浙江': 33,
        '江苏': 32,
        '重庆': 50,
        '宁夏': 64,
        '海南': 46,
        '台湾': 71,
        '北京': 11,
        '天津': 12,
        '上海': 31,
        '香港': 81,
        '澳门': 82
      };
      // 各区域的矢量图矩形边界
      this.geoData.areaBoxes = {
        //x, y, width, height when projection scale is 4000
        0: [-1174.6445229087194, -1437.3577680805693, 3039.3970214233723, 2531.19589698184],
        65: [-1174.9404317915883, -1136.0130934711678, 1216.4169237052663, 939.4360818385251],
        54: [-1061.2905098655508, -273.40253896102865, 1182.4138890465167, 728.4762434212385],
        15: [81.92106433333947, -1404.5655158641246, 1337.913665139638, 1168.7030286278964],
        63: [-398.0407413665446, -404.86540158240564, 770.5429460357634, 553.4881569694239],
        51: [34.77351011413543, -24.727858097581816, 654.265749584143, 581.5837904142871],
        23: [1185.0861642873883, -1435.9087566254907, 680.9449423479143, 618.3772597960831],
        62: [-197.5222870378875, -631.2015222269291, 884.6861134736321, 734.2542202456989],
        53: [-4.030270169151834, 326.89754492870105, 561.4971786143803, 565.9079094851168],
        45: [444.4355364538484, 524.7911424174906, 490.6548359068431, 384.1667316158848],
        43: [716.7125751678784, 265.3988842488122, 346.1702652872375, 377.50144051998274],
        61: [508.5948583446903, -399.56997062473215, 321.038690321553, 559.1002147021181],
        44: [790.2032875493967, 572.9640361040085, 494.8279567104971, 388.7112686526252],
        22: [1287.5729431804648, -950.943295028444, 504.33243011403374, 354.162667814153],
        13: [940.0156020671719, -646.4007207319194, 325.33903805510784, 477.4542727272415],
        42: [683.8325394595918, 45.82949601748078, 468.66717545627034, 295.2142095820616],
        52: [392.5021834497175, 337.4483828727408, 375.50579966539516, 320.9420464446699],
        37: [1035.7855473594757, -382.19242168799906, 412.5747391303373, 313.152767793266],
        36: [1012.6841751377355, 236.50140310944056, 295.599802392515, 400.86430917822287],
        41: [785.5419798731749, -185.2911232263814, 362.6977821251186, 340.3902676066224],
        21: [1203.0641741691293, -757.0946871553339, 352.71788824534656, 357.71276541155214],
        14: [776.5185040689469, -493.6204506126494, 212.68572802329425, 448.08485211774945],
        34: [1054.014965660052, -80.43770626104327, 295.73127466484925, 352.03731065611606],
        35: [1172.0955040211252, 341.81292779438445, 288.99462739279807, 339.42845011348845],
        33: [1272.1789620983063, 123.46272678646208, 286.17816622252326, 286.73860446060394],
        32: [1125.161343490302, -134.97368204682834, 356.1806346879009, 291.4961628010442],
        50: [497.78832088614774, 127.0051229616378, 291.91221530072164, 280.8880182020781],
        64: [441.193675072408, -376.31946967355213, 183.76989823787306, 293.0024551112753],
        46: [723.8031601361929, 946.050886515855, 183.33374783084207, 147.66048518654895],
        71: [1459.925544038912, 519.7445429876257, 103.06085087505835, 237.80851484008463],
        11: [1031.6052083127613, -530.1928574952913, 103.23943439987329, 114.66079087790081],
        12: [1106.9649995752443, -479.16508616378724, 71.21176554916747, 120.01987096046025],
        31: [1420.334836525578, 71.79837578328207, 70.41721601016525, 81.99461244072737],
        81: [1061.983645387268, 769.0837862603122, 50.65584483626753, 32.17422147262721],
        82: [1043.1350056914507, 798.0786255550063, 5.387452843479423, 7.564113979470676]
      };
      // 省的索引,包含经纬度和全名信息
      this.geoData.provinceIndex = {
        '新疆': {'loc': [84.9023, 41.748], 'fullName': '新疆'},
        '西藏': {'loc': [88.7695, 31.6846], 'fullName': '西藏'},
        '内蒙': {'loc': [117.5977, 44.3408], 'fullName': '内蒙古'},
        '青海': {'loc': [96.2402, 35.4199], 'fullName': '青海'},
        '四川': {'loc': [102.9199, 30.1904], 'fullName': '四川'},
        '黑龙': {'loc': [128.1445, 48.5156], 'fullName': '黑龙江'},
        '甘肃': {'loc': [95.7129, 40.166], 'fullName': '甘肃'},
        '云南': {'loc': [101.8652, 25.1807], 'fullName': '云南'},
        '广西': {'loc': [108.2813, 23.6426], 'fullName': '广西'},
        '湖南': {'loc': [111.5332, 27.3779], 'fullName': '湖南'},
        '陕西': {'loc': [109.5996, 35.6396], 'fullName': '陕西'},
        '广东': {'loc': [113.4668, 22.8076], 'fullName': '广东'},
        '吉林': {'loc': [126.4746, 43.5938], 'fullName': '吉林'},
        '河北': {'loc': [115.4004, 37.9688], 'fullName': '河北'},
        '湖北': {'loc': [112.2363, 31.1572], 'fullName': '湖北'},
        '贵州': {'loc': [106.6113, 26.9385], 'fullName': '贵州'},
        '山东': {'loc': [118.7402, 36.4307], 'fullName': '山东'},
        '江西': {'loc': [116.0156, 27.29], 'fullName': '江西'},
        '河南': {'loc': [113.4668, 33.8818], 'fullName': '河南'},
        '辽宁': {'loc': [122.3438, 41.0889], 'fullName': '辽宁'},
        '山西': {'loc': [112.4121, 37.6611], 'fullName': '山西'},
        '安徽': {'loc': [117.2461, 32.0361], 'fullName': '安徽'},
        '福建': {'loc': [118.3008, 25.9277], 'fullName': '福建'},
        '浙江': {'loc': [120.498, 29.0918], 'fullName': '浙江'},
        '江苏': {'loc': [120.0586, 32.915], 'fullName': '江苏'},
        '重庆': {'loc': [107.7539, 30.1904], 'fullName': '重庆'},
        '宁夏': {'loc': [105.9961, 37.3096], 'fullName': '宁夏'},
        '海南': {'loc': [109.9512, 19.2041], 'fullName': '海南'},
        '台湾': {'loc': [121.0254, 23.5986], 'fullName': '台湾'},
        '北京': {'loc': [116.4551, 40.2539], 'fullName': '北京'},
        '天津': {'loc': [117.4219, 39.4189], 'fullName': '天津'},
        '上海': {'loc': [121.4648, 31.2891], 'fullName': '上海'},
        '香港': {'loc': [114.2578, 22.3242], 'fullName': '香港'},
        '澳门': {'loc': [113.5547, 22.1484], 'fullName': '澳门'}
      };
      this.geoData.cityIndex = {//市的索引,包含经纬度和全名信息
        '重庆': {'loc': [107.7539, 30.1904], 'fullName': '重庆市'},
        '北京': {'loc': [116.4551, 40.2539], 'fullName': '北京市'},
        '天津': {'loc': [117.4219, 39.4189], 'fullName': '天津市'},
        '上海': {'loc': [121.4648, 31.2891], 'fullName': '上海市'},
        '香港': {'loc': [114.2578, 22.3242], 'fullName': '香港'},
        '澳门': {'loc': [113.5547, 22.1484], 'fullName': '澳门'},
        '巴音': {'loc': [88.1653, 39.6002], 'fullName': '巴音郭楞蒙古自治州'},
        '和田': {'loc': [81.167, 36.9855], 'fullName': '和田地区'},
        '哈密': {'loc': [93.7793, 42.9236], 'fullName': '哈密地区'},
        '阿克': {'loc': [82.9797, 41.0229], 'fullName': '阿克苏地区'},
        '阿勒': {'loc': [88.2971, 47.0929], 'fullName': '阿勒泰地区'},
        '喀什': {'loc': [77.168, 37.8534], 'fullName': '喀什地区'},
        '塔城': {'loc': [86.6272, 45.8514], 'fullName': '塔城地区'},
        '昌吉': {'loc': [89.6814, 44.4507], 'fullName': '昌吉回族自治州'},
        '克孜': {'loc': [74.6301, 39.5233], 'fullName': '克孜勒苏柯尔克孜自治州'},
        '吐鲁': {'loc': [89.6375, 42.4127], 'fullName': '吐鲁番地区'},
        '伊犁': {'loc': [82.5513, 43.5498], 'fullName': '伊犁哈萨克自治州'},
        '博尔': {'loc': [81.8481, 44.6979], 'fullName': '博尔塔拉蒙古自治州'},
        '乌鲁': {'loc': [87.9236, 43.5883], 'fullName': '乌鲁木齐市'},
        '克拉': {'loc': [85.2869, 45.5054], 'fullName': '克拉玛依市'},
        '阿拉尔': {'loc': [81.2769, 40.6549], 'fullName': '阿拉尔市'},
        '图木': {'loc': [79.1345, 39.8749], 'fullName': '图木舒克市'},
        '五家': {'loc': [87.5391, 44.3024], 'fullName': '五家渠市'},
        '石河': {'loc': [86.0229, 44.2914], 'fullName': '石河子市'},
        '那曲': {'loc': [88.1982, 33.3215], 'fullName': '那曲地区'},
        '阿里': {'loc': [82.3645, 32.7667], 'fullName': '阿里地区'},
        '日喀': {'loc': [86.2427, 29.5093], 'fullName': '日喀则地区'},
        '林芝': {'loc': [95.4602, 29.1138], 'fullName': '林芝地区'},
        '昌都': {'loc': [97.0203, 30.7068], 'fullName': '昌都地区'},
        '山南': {'loc': [92.2083, 28.3392], 'fullName': '山南地区'},
        '拉萨': {'loc': [91.1865, 30.1465], 'fullName': '拉萨市'},
        '呼伦': {'loc': [120.8057, 50.2185], 'fullName': '呼伦贝尔市'},
        '阿拉善': {'loc': [102.019, 40.1001], 'fullName': '阿拉善盟'},
        '锡林': {'loc': [115.6421, 44.176], 'fullName': '锡林郭勒盟'},
        '鄂尔': {'loc': [108.9734, 39.2487], 'fullName': '鄂尔多斯市'},
        '赤峰': {'loc': [118.6743, 43.2642], 'fullName': '赤峰市'},
        '巴彦': {'loc': [107.5562, 41.3196], 'fullName': '巴彦淖尔市'},
        '通辽': {'loc': [121.4758, 43.9673], 'fullName': '通辽市'},
        '乌兰': {'loc': [112.5769, 41.77], 'fullName': '乌兰察布市'},
        '兴安': {'loc': [121.3879, 46.1426], 'fullName': '兴安盟'},
        '包头': {'loc': [110.3467, 41.4899], 'fullName': '包头市'},
        '呼和': {'loc': [111.4124, 40.4901], 'fullName': '呼和浩特市'},
        '乌海': {'loc': [106.886, 39.4739], 'fullName': '乌海市'},
        '海西': {'loc': [94.9768, 37.1118], 'fullName': '海西蒙古族藏族自治州'},
        '玉树': {'loc': [93.5925, 33.9368], 'fullName': '玉树藏族自治州'},
        '果洛': {'loc': [99.3823, 34.0466], 'fullName': '果洛藏族自治州'},
        '海南': {'loc': [100.3711, 35.9418], 'fullName': '海南藏族自治州'},
        '海北': {'loc': [100.3711, 37.9138], 'fullName': '海北藏族自治州'},
        '黄南': {'loc': [101.5686, 35.1178], 'fullName': '黄南藏族自治州'},
        '海东': {'loc': [102.3706, 36.2988], 'fullName': '海东地区'},
        '西宁': {'loc': [101.4038, 36.8207], 'fullName': '西宁市'},
        '甘孜': {'loc': [99.9207, 31.0803], 'fullName': '甘孜藏族自治州'},
        '阿坝': {'loc': [102.4805, 32.4536], 'fullName': '阿坝藏族羌族自治州'},
        '凉山': {'loc': [101.9641, 27.6746], 'fullName': '凉山彝族自治州'},
        '绵阳': {'loc': [104.7327, 31.8713], 'fullName': '绵阳市'},
        '达州': {'loc': [107.6111, 31.333], 'fullName': '达州市'},
        '广元': {'loc': [105.6885, 32.2284], 'fullName': '广元市'},
        '雅安': {'loc': [102.6672, 29.8938], 'fullName': '雅安市'},
        '宜宾': {'loc': [104.6558, 28.548], 'fullName': '宜宾市'},
        '乐山': {'loc': [103.5791, 29.1742], 'fullName': '乐山市'},
        '南充': {'loc': [106.2048, 31.1517], 'fullName': '南充市'},
        '巴中': {'loc': [107.0618, 31.9977], 'fullName': '巴中市'},
        '泸州': {'loc': [105.4578, 28.493], 'fullName': '泸州市'},
        '成都': {'loc': [103.9526, 30.7617], 'fullName': '成都市'},
        '资阳': {'loc': [104.9744, 30.1575], 'fullName': '资阳市'},
        '攀枝': {'loc': [101.6895, 26.7133], 'fullName': '攀枝花市'},
        '眉山': {'loc': [103.8098, 30.0146], 'fullName': '眉山市'},
        '广安': {'loc': [106.6333, 30.4376], 'fullName': '广安市'},
        '德阳': {'loc': [104.48, 31.1133], 'fullName': '德阳市'},
        '内江': {'loc': [104.8535, 29.6136], 'fullName': '内江市'},
        '遂宁': {'loc': [105.5347, 30.6683], 'fullName': '遂宁市'},
        '自贡': {'loc': [104.6667, 29.2786], 'fullName': '自贡市'},
        '黑河': {'loc': [127.1448, 49.2957], 'fullName': '黑河市'},
        '大兴': {'loc': [124.1016, 52.2345], 'fullName': '大兴安岭地区'},
        '哈尔': {'loc': [127.9688, 45.368], 'fullName': '哈尔滨市'},
        '齐齐': {'loc': [124.541, 47.5818], 'fullName': '齐齐哈尔市'},
        '牡丹': {'loc': [129.7815, 44.7089], 'fullName': '牡丹江市'},
        '绥化': {'loc': [126.7163, 46.8018], 'fullName': '绥化市'},
        '伊春': {'loc': [129.1992, 47.9608], 'fullName': '伊春市'},
        '佳木': {'loc': [133.0005, 47.5763], 'fullName': '佳木斯市'},
        '鸡西': {'loc': [132.7917, 45.7361], 'fullName': '鸡西市'},
        '双鸭': {'loc': [133.5938, 46.7523], 'fullName': '双鸭山市'},
        '大庆': {'loc': [124.7717, 46.4282], 'fullName': '大庆市'},
        '鹤岗': {'loc': [130.4407, 47.7081], 'fullName': '鹤岗市'},
        '七台': {'loc': [131.2756, 45.9558], 'fullName': '七台河市'},
        '酒泉': {'loc': [96.2622, 40.4517], 'fullName': '酒泉市'},
        '张掖': {'loc': [99.7998, 38.7433], 'fullName': '张掖市'},
        '甘南': {'loc': [102.9199, 34.6893], 'fullName': '甘南藏族自治州'},
        '武威': {'loc': [103.0188, 38.1061], 'fullName': '武威市'},
        '陇南': {'loc': [105.304, 33.5632], 'fullName': '陇南市'},
        '庆阳': {'loc': [107.5342, 36.2], 'fullName': '庆阳市'},
        '白银': {'loc': [104.8645, 36.5076], 'fullName': '白银市'},
        '定西': {'loc': [104.5569, 35.0848], 'fullName': '定西市'},
        '天水': {'loc': [105.6445, 34.6289], 'fullName': '天水市'},
        '兰州': {'loc': [103.5901, 36.3043], 'fullName': '兰州市'},
        '平凉': {'loc': [107.0728, 35.321], 'fullName': '平凉市'},
        '临夏': {'loc': [103.2715, 35.5737], 'fullName': '临夏回族自治州'},
        '金昌': {'loc': [102.074, 38.5126], 'fullName': '金昌市'},
        '嘉峪': {'loc': [98.1738, 39.8035], 'fullName': '嘉峪关市'},
        '普洱': {'loc': [100.7446, 23.4229], 'fullName': '普洱市'},
        '红河': {'loc': [103.0408, 23.6041], 'fullName': '红河哈尼族彝族自治州'},
        '文山': {'loc': [104.8865, 23.5712], 'fullName': '文山壮族苗族自治州'},
        '曲靖': {'loc': [103.9417, 25.7025], 'fullName': '曲靖市'},
        '楚雄': {'loc': [101.6016, 25.3619], 'fullName': '楚雄彝族自治州'},
        '大理': {'loc': [99.9536, 25.6805], 'fullName': '大理白族自治州'},
        '临沧': {'loc': [99.613, 24.0546], 'fullName': '临沧市'},
        '迪庆': {'loc': [99.4592, 27.9327], 'fullName': '迪庆藏族自治州'},
        '昭通': {'loc': [104.0955, 27.6031], 'fullName': '昭通市'},
        '昆明': {'loc': [102.9199, 25.4663], 'fullName': '昆明市'},
        '丽江': {'loc': [100.448, 26.955], 'fullName': '丽江市'},
        '西双': {'loc': [100.8984, 21.8628], 'fullName': '西双版纳傣族自治州'},
        '保山': {'loc': [99.0637, 24.9884], 'fullName': '保山市'},
        '玉溪': {'loc': [101.9312, 23.8898], 'fullName': '玉溪市'},
        '怒江': {'loc': [99.1516, 26.5594], 'fullName': '怒江傈僳族自治州'},
        '德宏': {'loc': [98.1299, 24.5874], 'fullName': '德宏傣族景颇族自治州'},
        '百色': {'loc': [106.6003, 23.9227], 'fullName': '百色市'},
        '河池': {'loc': [107.8638, 24.5819], 'fullName': '河池市'},
        '桂林': {'loc': [110.5554, 25.318], 'fullName': '桂林市'},
        '南宁': {'loc': [108.479, 23.1152], 'fullName': '南宁市'},
        '柳州': {'loc': [109.3799, 24.9774], 'fullName': '柳州市'},
        '崇左': {'loc': [107.3364, 22.4725], 'fullName': '崇左市'},
        '来宾': {'loc': [109.7095, 23.8403], 'fullName': '来宾市'},
        '玉林': {'loc': [110.2148, 22.3792], 'fullName': '玉林市'},
        '梧州': {'loc': [110.9949, 23.5052], 'fullName': '梧州市'},
        '贺州': {'loc': [111.3135, 24.4006], 'fullName': '贺州市'},
        '钦州': {'loc': [109.0283, 22.0935], 'fullName': '钦州市'},
        '贵港': {'loc': [109.9402, 23.3459], 'fullName': '贵港市'},
        '防城': {'loc': [108.0505, 21.9287], 'fullName': '防城港市'},
        '北海': {'loc': [109.314, 21.6211], 'fullName': '北海市'},
        '怀化': {'loc': [109.9512, 27.4438], 'fullName': '怀化市'},
        '永州': {'loc': [111.709, 25.752], 'fullName': '永州市'},
        '邵阳': {'loc': [110.9619, 26.8121], 'fullName': '邵阳市'},
        '郴州': {'loc': [113.2361, 25.8673], 'fullName': '郴州市'},
        '常德': {'loc': [111.4014, 29.2676], 'fullName': '常德市'},
        '湘西': {'loc': [109.7864, 28.6743], 'fullName': '湘西土家族苗族自治州'},
        '衡阳': {'loc': [112.4121, 26.7902], 'fullName': '衡阳市'},
        '岳阳': {'loc': [113.2361, 29.1357], 'fullName': '岳阳市'},
        '益阳': {'loc': [111.731, 28.3832], 'fullName': '益阳市'},
        '长沙': {'loc': [113.0823, 28.2568], 'fullName': '长沙市'},
        '株洲': {'loc': [113.5327, 27.0319], 'fullName': '株洲市'},
        '张家界': {'loc': [110.5115, 29.328], 'fullName': '张家界市'},
        '娄底': {'loc': [111.6431, 27.7185], 'fullName': '娄底市'},
        '湘潭': {'loc': [112.5439, 27.7075], 'fullName': '湘潭市'},
        '榆林': {'loc': [109.8743, 38.205], 'fullName': '榆林市'},
        '延安': {'loc': [109.1052, 36.4252], 'fullName': '延安市'},
        '汉中': {'loc': [106.886, 33.0139], 'fullName': '汉中市'},
        '安康': {'loc': [109.1162, 32.7722], 'fullName': '安康市'},
        '商洛': {'loc': [109.8083, 33.761], 'fullName': '商洛市'},
        '宝鸡': {'loc': [107.1826, 34.3433], 'fullName': '宝鸡市'},
        '渭南': {'loc': [109.7864, 35.0299], 'fullName': '渭南市'},
        '咸阳': {'loc': [108.4131, 34.8706], 'fullName': '咸阳市'},
        '西安': {'loc': [109.1162, 34.2004], 'fullName': '西安市'},
        '铜川': {'loc': [109.0393, 35.1947], 'fullName': '铜川市'},
        '清远': {'loc': [112.9175, 24.3292], 'fullName': '清远市'},
        '韶关': {'loc': [113.7964, 24.7028], 'fullName': '韶关市'},
        '湛江': {'loc': [110.3577, 20.9894], 'fullName': '湛江市'},
        '梅州': {'loc': [116.1255, 24.1534], 'fullName': '梅州市'},
        '河源': {'loc': [114.917, 23.9722], 'fullName': '河源市'},
        '肇庆': {'loc': [112.1265, 23.5822], 'fullName': '肇庆市'},
        '惠州': {'loc': [114.6204, 23.1647], 'fullName': '惠州市'},
        '茂名': {'loc': [111.0059, 22.0221], 'fullName': '茂名市'},
        '江门': {'loc': [112.6318, 22.1484], 'fullName': '江门市'},
        '阳江': {'loc': [111.8298, 22.0715], 'fullName': '阳江市'},
        '云浮': {'loc': [111.7859, 22.8516], 'fullName': '云浮市'},
        '广州': {'loc': [113.5107, 23.2196], 'fullName': '广州市'},
        '汕尾': {'loc': [115.5762, 23.0438], 'fullName': '汕尾市'},
        '揭阳': {'loc': [116.1255, 23.313], 'fullName': '揭阳市'},
        '珠海': {'loc': [113.7305, 22.1155], 'fullName': '珠海市'},
        '佛山': {'loc': [112.8955, 23.1097], 'fullName': '佛山市'},
        '潮州': {'loc': [116.7847, 23.8293], 'fullName': '潮州市'},
        '汕头': {'loc': [117.1692, 23.3405], 'fullName': '汕头市'},
        '深圳': {'loc': [114.5435, 22.5439], 'fullName': '深圳市'},
        '东莞': {'loc': [113.8953, 22.901], 'fullName': '东莞市'},
        '中山': {'loc': [113.4229, 22.478], 'fullName': '中山市'},
        '延边': {'loc': [129.397, 43.2587], 'fullName': '延边朝鲜族自治州'},
        '吉林': {'loc': [126.8372, 43.6047], 'fullName': '吉林市'},
        '白城': {'loc': [123.0029, 45.2637], 'fullName': '白城市'},
        '松原': {'loc': [124.0906, 44.7198], 'fullName': '松原市'},
        '长春': {'loc': [125.8154, 44.2584], 'fullName': '长春市'},
        '白山': {'loc': [127.2217, 42.0941], 'fullName': '白山市'},
        '通化': {'loc': [125.9583, 41.8579], 'fullName': '通化市'},
        '四平': {'loc': [124.541, 43.4894], 'fullName': '四平市'},
        '辽源': {'loc': [125.343, 42.7643], 'fullName': '辽源市'},
        '承德': {'loc': [117.5757, 41.4075], 'fullName': '承德市'},
        '张家口': {'loc': [115.1477, 40.8527], 'fullName': '张家口市'},
        '保定': {'loc': [115.0488, 39.0948], 'fullName': '保定市'},
        '唐山': {'loc': [118.4766, 39.6826], 'fullName': '唐山市'},
        '沧州': {'loc': [116.8286, 38.2104], 'fullName': '沧州市'},
        '石家': {'loc': [114.4995, 38.1006], 'fullName': '石家庄市'},
        '邢台': {'loc': [114.8071, 37.2821], 'fullName': '邢台市'},
        '邯郸': {'loc': [114.4775, 36.535], 'fullName': '邯郸市'},
        '秦皇': {'loc': [119.2126, 40.0232], 'fullName': '秦皇岛市'},
        '衡水': {'loc': [115.8838, 37.7161], 'fullName': '衡水市'},
        '廊坊': {'loc': [116.521, 39.0509], 'fullName': '廊坊市'},
        '恩施': {'loc': [109.5007, 30.2563], 'fullName': '恩施土家族苗族自治州'},
        '十堰': {'loc': [110.5115, 32.3877], 'fullName': '十堰市'},
        '宜昌': {'loc': [111.1707, 30.7617], 'fullName': '宜昌市'},
        '襄樊': {'loc': [111.9397, 31.9263], 'fullName': '襄樊市'},
        '黄冈': {'loc': [115.2686, 30.6628], 'fullName': '黄冈市'},
        '荆州': {'loc': [113.291, 30.0092], 'fullName': '荆州市'},
        '荆门': {'loc': [112.6758, 30.9979], 'fullName': '荆门市'},
        '咸宁': {'loc': [114.2578, 29.6631], 'fullName': '咸宁市'},
        '随州': {'loc': [113.4338, 31.8768], 'fullName': '随州市'},
        '孝感': {'loc': [113.9502, 31.1188], 'fullName': '孝感市'},
        '武汉': {'loc': [114.3896, 30.6628], 'fullName': '武汉市'},
        '黄石': {'loc': [115.0159, 29.9213], 'fullName': '黄石市'},
        '神农': {'loc': [110.4565, 31.5802], 'fullName': '神农架林区'},
        '天门': {'loc': [113.0273, 30.6409], 'fullName': '天门市'},
        '仙桃': {'loc': [113.3789, 30.3003], 'fullName': '仙桃市'},
        '潜江': {'loc': [112.7637, 30.3607], 'fullName': '潜江市'},
        '鄂州': {'loc': [114.7302, 30.4102], 'fullName': '鄂州市'},
        '遵义': {'loc': [106.908, 28.1744], 'fullName': '遵义市'},
        '黔东': {'loc': [108.4241, 26.4166], 'fullName': '黔东南苗族侗族自治州'},
        '毕节': {'loc': [105.1611, 27.0648], 'fullName': '毕节地区'},
        '黔南': {'loc': [107.2485, 25.8398], 'fullName': '黔南布依族苗族自治州'},
        '铜仁': {'loc': [108.6218, 28.0096], 'fullName': '铜仁地区'},
        '黔西': {'loc': [105.5347, 25.3949], 'fullName': '黔西南布依族苗族自治州'},
        '六盘': {'loc': [104.7546, 26.0925], 'fullName': '六盘水市'},
        '安顺': {'loc': [105.9082, 25.9882], 'fullName': '安顺市'},
        '贵阳': {'loc': [106.6992, 26.7682], 'fullName': '贵阳市'},
        '烟台': {'loc': [120.7397, 37.5128], 'fullName': '烟台市'},
        '临沂': {'loc': [118.3118, 35.2936], 'fullName': '临沂市'},
        '潍坊': {'loc': [119.0918, 36.524], 'fullName': '潍坊市'},
        '青岛': {'loc': [120.4651, 36.3373], 'fullName': '青岛市'},
        '菏泽': {'loc': [115.6201, 35.2057], 'fullName': '菏泽市'},
        '济宁': {'loc': [116.8286, 35.3375], 'fullName': '济宁市'},
        '德州': {'loc': [116.6858, 37.2107], 'fullName': '德州市'},
        '滨州': {'loc': [117.8174, 37.4963], 'fullName': '滨州市'},
        '聊城': {'loc': [115.9167, 36.4032], 'fullName': '聊城市'},
        '东营': {'loc': [118.7073, 37.5513], 'fullName': '东营市'},
        '济南': {'loc': [117.1582, 36.8701], 'fullName': '济南市'},
        '泰安': {'loc': [117.0264, 36.0516], 'fullName': '泰安市'},
        '威海': {'loc': [121.9482, 37.1393], 'fullName': '威海市'},
        '日照': {'loc': [119.2786, 35.5023], 'fullName': '日照市'},
        '淄博': {'loc': [118.0371, 36.6064], 'fullName': '淄博市'},
        '枣庄': {'loc': [117.323, 34.8926], 'fullName': '枣庄市'},
        '莱芜': {'loc': [117.6526, 36.2714], 'fullName': '莱芜市'},
        '赣州': {'loc': [115.2795, 25.8124], 'fullName': '赣州市'},
        '吉安': {'loc': [114.884, 26.9659], 'fullName': '吉安市'},
        '上饶': {'loc': [117.8613, 28.7292], 'fullName': '上饶市'},
        '九江': {'loc': [115.4224, 29.3774], 'fullName': '九江市'},
        '抚州': {'loc': [116.4441, 27.4933], 'fullName': '抚州市'},
        '宜春': {'loc': [115.0159, 28.3228], 'fullName': '宜春市'},
        '南昌': {'loc': [116.0046, 28.6633], 'fullName': '南昌市'},
        '景德': {'loc': [117.334, 29.3225], 'fullName': '景德镇市'},
        '萍乡': {'loc': [113.9282, 27.4823], 'fullName': '萍乡市'},
        '鹰潭': {'loc': [117.0813, 28.2349], 'fullName': '鹰潭市'},
        '新余': {'loc': [114.95, 27.8174], 'fullName': '新余市'},
        '南阳': {'loc': [112.4011, 33.0359], 'fullName': '南阳市'},
        '信阳': {'loc': [114.8291, 32.0197], 'fullName': '信阳市'},
        '洛阳': {'loc': [112.0605, 34.3158], 'fullName': '洛阳市'},
        '驻马': {'loc': [114.1589, 32.9041], 'fullName': '驻马店市'},
        '周口': {'loc': [114.873, 33.6951], 'fullName': '周口市'},
        '商丘': {'loc': [115.741, 34.2828], 'fullName': '商丘市'},
        '三门': {'loc': [110.8301, 34.3158], 'fullName': '三门峡市'},
        '新乡': {'loc': [114.2029, 35.3595], 'fullName': '新乡市'},
        '平顶': {'loc': [112.9724, 33.739], 'fullName': '平顶山市'},
        '郑州': {'loc': [113.4668, 34.6234], 'fullName': '郑州市'},
        '安阳': {'loc': [114.5325, 36.0022], 'fullName': '安阳市'},
        '开封': {'loc': [114.5764, 34.6124], 'fullName': '开封市'},
        '焦作': {'loc': [112.8406, 35.1508], 'fullName': '焦作市'},
        '许昌': {'loc': [113.6975, 34.0466], 'fullName': '许昌市'},
        '濮阳': {'loc': [115.1917, 35.799], 'fullName': '濮阳市'},
        '漯河': {'loc': [113.8733, 33.6951], 'fullName': '漯河市'},
        '鹤壁': {'loc': [114.3787, 35.744], 'fullName': '鹤壁市'},
        '大连': {'loc': [122.2229, 39.4409], 'fullName': '大连市'},
        '朝阳': {'loc': [120.0696, 41.4899], 'fullName': '朝阳市'},
        '丹东': {'loc': [124.541, 40.4242], 'fullName': '丹东市'},
        '铁岭': {'loc': [124.2773, 42.7423], 'fullName': '铁岭市'},
        '沈阳': {'loc': [123.1238, 42.1216], 'fullName': '沈阳市'},
        '抚顺': {'loc': [124.585, 41.8579], 'fullName': '抚顺市'},
        '葫芦': {'loc': [120.1575, 40.578], 'fullName': '葫芦岛市'},
        '阜新': {'loc': [122.0032, 42.2699], 'fullName': '阜新市'},
        '锦州': {'loc': [121.6626, 41.4294], 'fullName': '锦州市'},
        '鞍山': {'loc': [123.0798, 40.6055], 'fullName': '鞍山市'},
        '本溪': {'loc': [124.1455, 41.1987], 'fullName': '本溪市'},
        '营口': {'loc': [122.4316, 40.4297], 'fullName': '营口市'},
        '辽阳': {'loc': [123.4094, 41.1383], 'fullName': '辽阳市'},
        '盘锦': {'loc': [121.9482, 41.0449], 'fullName': '盘锦市'},
        '忻州': {'loc': [112.4561, 38.8971], 'fullName': '忻州市'},
        '吕梁': {'loc': [111.3574, 37.7325], 'fullName': '吕梁市'},
        '临汾': {'loc': [111.4783, 36.1615], 'fullName': '临汾市'},
        '晋中': {'loc': [112.7747, 37.37], 'fullName': '晋中市'},
        '运城': {'loc': [111.1487, 35.2002], 'fullName': '运城市'},
        '大同': {'loc': [113.7854, 39.8035], 'fullName': '大同市'},
        '长治': {'loc': [112.8625, 36.4746], 'fullName': '长治市'},
        '朔州': {'loc': [113.0713, 39.6991], 'fullName': '朔州市'},
        '晋城': {'loc': [112.7856, 35.6342], 'fullName': '晋城市'},
        '太原': {'loc': [112.3352, 37.9413], 'fullName': '太原市'},
        '阳泉': {'loc': [113.4778, 38.0951], 'fullName': '阳泉市'},
        '六安': {'loc': [116.3123, 31.8329], 'fullName': '六安市'},
        '安庆': {'loc': [116.7517, 30.5255], 'fullName': '安庆市'},
        '滁州': {'loc': [118.1909, 32.536], 'fullName': '滁州市'},
        '宣城': {'loc': [118.8062, 30.6244], 'fullName': '宣城市'},
        '阜阳': {'loc': [115.7629, 32.9919], 'fullName': '阜阳市'},
        '宿州': {'loc': [117.5208, 33.6841], 'fullName': '宿州市'},
        '黄山': {'loc': [118.0481, 29.9542], 'fullName': '黄山市'},
        '巢湖': {'loc': [117.7734, 31.4978], 'fullName': '巢湖市'},
        '亳州': {'loc': [116.1914, 33.4698], 'fullName': '亳州市'},
        '池州': {'loc': [117.3889, 30.2014], 'fullName': '池州市'},
        '合肥': {'loc': [117.29, 32.0581], 'fullName': '合肥市'},
        '蚌埠': {'loc': [117.4109, 33.1073], 'fullName': '蚌埠市'},
        '芜湖': {'loc': [118.3557, 31.0858], 'fullName': '芜湖市'},
        '淮北': {'loc': [116.6968, 33.6896], 'fullName': '淮北市'},
        '淮南': {'loc': [116.7847, 32.7722], 'fullName': '淮南市'},
        '马鞍': {'loc': [118.6304, 31.5363], 'fullName': '马鞍山市'},
        '铜陵': {'loc': [117.9382, 30.9375], 'fullName': '铜陵市'},
        '南平': {'loc': [118.136, 27.2845], 'fullName': '南平市'},
        '三明': {'loc': [117.5317, 26.3013], 'fullName': '三明市'},
        '龙岩': {'loc': [116.8066, 25.2026], 'fullName': '龙岩市'},
        '宁德': {'loc': [119.6521, 26.9824], 'fullName': '宁德市'},
        '福州': {'loc': [119.4543, 25.9222], 'fullName': '福州市'},
        '漳州': {'loc': [117.5757, 24.3732], 'fullName': '漳州市'},
        '泉州': {'loc': [118.3228, 25.1147], 'fullName': '泉州市'},
        '莆田': {'loc': [119.0918, 25.3455], 'fullName': '莆田市'},
        '厦门': {'loc': [118.1689, 24.6478], 'fullName': '厦门市'},
        '丽水': {'loc': [119.5642, 28.1854], 'fullName': '丽水市'},
        '杭州': {'loc': [119.5313, 29.8773], 'fullName': '杭州市'},
        '温州': {'loc': [120.498, 27.8119], 'fullName': '温州市'},
        '宁波': {'loc': [121.5967, 29.6466], 'fullName': '宁波市'},
        '舟山': {'loc': [122.2559, 30.2234], 'fullName': '舟山市'},
        '台州': {'loc': [121.1353, 28.6688], 'fullName': '台州市'},
        '金华': {'loc': [120.0037, 29.1028], 'fullName': '金华市'},
        '衢州': {'loc': [118.6853, 28.8666], 'fullName': '衢州市'},
        '绍兴': {'loc': [120.564, 29.7565], 'fullName': '绍兴市'},
        '嘉兴': {'loc': [120.9155, 30.6354], 'fullName': '嘉兴市'},
        '湖州': {'loc': [119.8608, 30.7782], 'fullName': '湖州市'},
        '盐城': {'loc': [120.2234, 33.5577], 'fullName': '盐城市'},
        '徐州': {'loc': [117.5208, 34.3268], 'fullName': '徐州市'},
        '南通': {'loc': [121.1023, 32.1625], 'fullName': '南通市'},
        '淮安': {'loc': [118.927, 33.4039], 'fullName': '淮安市'},
        '苏州': {'loc': [120.6519, 31.3989], 'fullName': '苏州市'},
        '宿迁': {'loc': [118.5535, 33.7775], 'fullName': '宿迁市'},
        '连云': {'loc': [119.1248, 34.552], 'fullName': '连云港市'},
        '扬州': {'loc': [119.4653, 32.8162], 'fullName': '扬州市'},
        '南京': {'loc': [118.8062, 31.9208], 'fullName': '南京市'},
        '泰州': {'loc': [120.0586, 32.5525], 'fullName': '泰州市'},
        '无锡': {'loc': [120.3442, 31.5527], 'fullName': '无锡市'},
        '常州': {'loc': [119.4543, 31.5582], 'fullName': '常州市'},
        '镇江': {'loc': [119.4763, 31.9702], 'fullName': '镇江市'},
        '吴忠': {'loc': [106.853, 37.3755], 'fullName': '吴忠市'},
        '中卫': {'loc': [105.4028, 36.9525], 'fullName': '中卫市'},
        '固原': {'loc': [106.1389, 35.9363], 'fullName': '固原市'},
        '银川': {'loc': [106.3586, 38.1775], 'fullName': '银川市'},
        '石嘴': {'loc': [106.4795, 39.0015], 'fullName': '石嘴山市'},
        '儋州': {'loc': [109.3291, 19.5653], 'fullName': '儋州市'},
        '文昌': {'loc': [110.8905, 19.7823], 'fullName': '文昌市'},
        '乐东': {'loc': [109.0283, 18.6301], 'fullName': '乐东黎族自治县'},
        '三亚': {'loc': [109.3716, 18.3698], 'fullName': '三亚市'},
        '琼中': {'loc': [109.8413, 19.0736], 'fullName': '琼中黎族苗族自治县'},
        '东方': {'loc': [108.8498, 19.0414], 'fullName': '东方市'},
        '海口': {'loc': [110.3893, 19.8516], 'fullName': '海口市'},
        '万宁': {'loc': [110.3137, 18.8388], 'fullName': '万宁市'},
        '澄迈': {'loc': [109.9937, 19.7314], 'fullName': '澄迈县'},
        '白沙': {'loc': [109.3703, 19.211], 'fullName': '白沙黎族自治县'},
        '琼海': {'loc': [110.4208, 19.224], 'fullName': '琼海市'},
        '昌江': {'loc': [109.0407, 19.2137], 'fullName': '昌江黎族自治县'},
        '临高': {'loc': [109.6957, 19.8063], 'fullName': '临高县'},
        '陵水': {'loc': [109.9924, 18.5415], 'fullName': '陵水黎族自治县'},
        '屯昌': {'loc': [110.0377, 19.362], 'fullName': '屯昌县'},
        '定安': {'loc': [110.3384, 19.4698], 'fullName': '定安县'},
        '保亭': {'loc': [109.6284, 18.6108], 'fullName': '保亭黎族苗族自治县'},
        '五指': {'loc': [109.5282, 18.8299], 'fullName': '五指山市'}
      };

      // Canvas
      this.defaults.geoDataPath = "";
      this.defaults.width = 600;
      this.defaults.height = 500;
      this.defaults.mapId = "0";
      this.defaults.showWords = true; // show words or not
      this.defaults.levelChangeable = true; // show words or not
      this.defaults.zoomAnimate = true; // show words or not
      this.defaults.colorModel = "discrete"; // discrete or gradient color
      this.defaults.colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
      this.defaults.defaultAreaColor = "#dddddd"; // if area with no data show this color
      this.defaults.wordStyle = {};
      this.defaults.areaStyle = {};

      this.renderCallback = function () {};

      this.setOptions(options);
      this.createCanvas();
    }
  });

_searchIndex

get value from indexes

方法 Chinamap.prototype._searchIndex() _searchIndex
代码
Chinamap.prototype._searchIndex = function (key, name, regionType) {
    var map = this;
    var result;
    var search = function (regionType, name) {
      var shortName = name.substr(0, 2);
      if (regionType === 'city') {
        //prevent duplicate,张家口市,张家界市,阿拉善盟, 阿拉尔市
        if (shortName === '阿拉' || shortName === '张家') {
          shortName = name.substr(0, 3);
        }
      }
      var hash = regionType === 'city' ? map.geoData.cityIndex : map.geoData.provinceIndex;
      var result = hash[shortName];
      return result && result[key] || undefined;
    };

    if (typeof regionType === 'undefined') {
      //province, then city
      if (name === '吉林市' || name === '海南藏族自治州') {
        //吉林省, 吉林市; 海南省,海南藏族自治州
        result = search("city", name);
      } else {
        result = search("province", name) || search("city", name);
      }
    } else {
      if (regionType === 'province') {
        //province
        result = search("province", name);
      } else if (regionType === 'city') {
        //city
        result = search("city", name);
      }
    }

    return result;
  };

getLoc

get longitude and latitude center by city or porvince name
regionType is optional, if it's undefined, then first search province, then city

方法 Chinamap.prototype.getLoc() getLoc
代码
Chinamap.prototype.getLoc = function (name, regionType) {
    return this._searchIndex('loc', name, regionType);
  };

getProvinceCenter

get longitude and latitude center by porvince name

方法 Chinamap.prototype.getProvinceCenter() getProvinceCenter
代码
Chinamap.prototype.getProvinceCenter = function (name) {
    return this.getLoc(name, 'province');
  };

getCityCenter

get longitude and latitude center by city name

方法 Chinamap.prototype.getCityCenter() getCityCenter
代码
Chinamap.prototype.getCityCenter = function (name) {
    return this.getLoc(name, 'city');
  };

getFormatName

get format name by city or porvince name
regionType is optional, if it's undefined, then first search province, then city

方法 Chinamap.prototype.getFormatName() getFormatName
代码
Chinamap.prototype.getFormatName = function (name, regionType) {
    return this._searchIndex('fullName', name, regionType);
  };

getProvinceFormatName

get fullName by porvince name

方法 Chinamap.prototype.getProvinceFormatName() getProvinceFormatName
代码
Chinamap.prototype.getProvinceFormatName = function (name) {
    return this.getFormatName(name, 'province');
  };

getCityFormatName

get fullName by city name

方法 Chinamap.prototype.getCityFormatName() getCityFormatName
代码
Chinamap.prototype.getCityFormatName = function (name) {
    return this.getFormatName(name, 'city');
  };

createCanvas

Create dom node relate to chinamap

方法 Chinamap.prototype.createCanvas() createCanvas
代码
Chinamap.prototype.createCanvas = function () {
    var conf = this.defaults,
      canvasStyle,
      container = this.node;

    this.canvas = document.createElement("div");
    canvasStyle = this.canvas.style;
    canvasStyle.position = "relative";
    canvasStyle.width = conf.width + "px";
    canvasStyle.height = conf.height + "px";
    container.appendChild(this.canvas);

    this.paper = new Raphael(this.canvas, conf.width, conf.height);

    this.floatTag = DataV.FloatTag()(this.canvas);
    this.floatTag.css({"visibility": "hidden"});
  };

getMapId

get mapId by area name

方法 Chinamap.prototype.getMapId() getMapId
代码
Chinamap.prototype.getMapId = function (areaName) {
    return this.geoData.areaIdIndex[areaName.substr(0, 2)];
  };

getColor

获取区域颜色。内部函数。用户设置colorModel和colors来设置区域颜色。输入为地图区域对象,内部对象,包含某个地图区域的信息。
内部的实现流程是:如果颜色生成函数不存在,则根据colorModel生成颜色生成函数。"discrete"时颜色在colors中从头至尾循环选取,"gradient"时依照colors中颜色的渐变插值计算。离散色时颜色有区域的分类决定。渐变色时颜色由区域的值决定(最小值和最大值间进行插值)。

方法 Chinamap.prototype.getColor() getColor
代码
Chinamap.prototype.getColor = function (d) {
    var colors = this.defaults.colors;
    var value;
    if (typeof this.colorGenerator.range === 'undefined') {
      if (this.defaults.colorModel === 'discrete') {
        this.colorGenerator = d3.scale.ordinal().range(colors);
      } else {
        this.colorGenerator = d3.scale.linear()
          .range(colors.length === 1 ? colors.concat(colors) : colors)
          .domain(d3.range(0, 1, 1 / (colors.length - 1)).concat([1])); 
      }
      this.colorGenerator.min = d3.min(d3.values(this.sourceData));
      this.colorGenerator.domainBand = d3.max(d3.values(this.sourceData)) - this.colorGenerator.min;
    }
    value = this.sourceData[d.properties.name];
    if (typeof value === 'undefined') {
      //no data area color
      return this.defaults.defaultAreaColor;
    } else {
      if (this.defaults.colorModel === 'discrete') {
        return this.colorGenerator(value);
      } else {
        var val = this.colorGenerator.domainBand === 0 ? 1
          : (value - this.colorGenerator.min) / this.colorGenerator.domainBand;
        return this.colorGenerator(val);
      }
    }
  };

setSource

设置数据源

Examples

输入为object。key为地区名称。当区域分成多个类时,value为类别名。当区域有值时,value为数字。

 //示例一,区域有值,全国各省人口数据,适合用渐变色显示。
 chinamap.setSource({
   '北京': 19612368,
   '天津': 12938224,
   '河北': 71854202,
   '山西': 35712111,
   '内蒙': 24706321,
   '辽宁': 43746323,
   '吉林': 27462297,
   '黑龙': 38312224,
   '上海': 23019148,
   '江苏': 78659903,
   '浙江': 54426891,
   '安徽': 59500510,
   '福建': 36894216,
   '江西': 44567475,
   '山东': 95793065,
   '河南': 94023567,
   '湖北': 57237740,
   '湖南': 65683722,
   '广东': 104303132,
   '广西': 46026629,
   '海南': 8671518,
   '重庆': 28846170,
   '四川': 80418200,
   '贵州': 34746468,
   '云南': 45966239,
   '西藏': 3002166,
   '陕西': 37327378,
   '甘肃': 25575254,
   '青海': 5626722,
   '宁夏': 6301350,
   '新疆': 21813334,
   '香港': 7097600,
   '澳门': 552300,
   '台湾': 23162123
 });
 //示例二,区域分成各个类,适合用渐变色显示。
 chinamap.setSource({
   '北京': '华北',
   '天津': '华北',
   '河北': '华北',
   '山西': '华北',
   '内蒙古': '华北',
   '上海': '华东',
   '江苏': '华东',
   '浙江': '华东',
   '山东': '华东',
   '安徽': '华东',
   '辽宁': '东北',
   '吉林': '东北',
   '黑龙江': '东北',
   '湖北': '华中',
   '湖南': '华中',
   '河南': '华中',
   '江西': '华中',
   '广东': '华南',
   '广西': '华南',
   '海南': '华南',
   '福建': '华南',
   '四川': '西南',
   '重庆': '西南',
   '贵州': '西南',
   '云南': '西南',
   '西藏': '西南',
   '陕西': '西北',
   '甘肃': '西北',
   '新疆': '西北',
   '青海': '西北',
   '宁夏': '西北',
   '香港': '港澳台',
   '澳门': '港澳台',
   '台湾 ': '港澳台'
 });
方法 Chinamap.prototype.setSource() setSource
参数 area(Object) json
代码
Chinamap.prototype.setSource = function (source) {
    var key, formatName;
    for (key in source) {
      formatName = this.getFormatName(key);
      if (typeof formatName !== 'undefined') {
        this.sourceData[formatName] = source[key];
      }
    }
  };

getAreaByName

根据省份名字,获取省份的对象

方法 Chinamap.prototype.getAreaByName() getAreaByName
代码
Chinamap.prototype.getAreaByName = function (name) {
    var cache = this.mapCache[this.areaId];
    var index = _.indexOf(cache.names, name);
    return cache.states[index];
  };

clearCanvas

清除画布

方法 Chinamap.prototype.clearCanvas() clearCanvas
代码
Chinamap.prototype.clearCanvas = function () {
    this.paper.clear();
  };

render

计算布局位置,并渲染图表

方法 Chinamap.prototype.render() render
代码
Chinamap.prototype.render = function (callback) {
    this.renderCallback = callback || this.renderCallback;
    this.clearCanvas();
    this.layout();
    this.generatePaths();
  };

_scaleLocToPixelLoc

将点在矢量图中的位置 转化为 实际显示点相对于图片左上角的位置(像素距离)

方法 Chinamap.prototype._scaleLocToPixelLoc() _scaleLocToPixelLoc
代码
Chinamap.prototype._scaleLocToPixelLoc = function (scaleLoc) {
    var map = this;
    var scale = map.viewBoxShift.scale;
    var viewCenter = {
      'x': map.viewBox[0] + map.viewBox[2] / 2,
      'y': map.viewBox[1] + map.viewBox[3] / 2
    };
    return {
      'x': (scaleLoc.x - viewCenter.x) * scale + map.defaults.width / 2,
      'y': (scaleLoc.y - viewCenter.y) * scale + map.defaults.height / 2
    };
  };

_pixelLocToScaleLoc

将实际显示点相对于图片左上角的位置(像素距离) 转化为 点在矢量图中的位置

方法 Chinamap.prototype._pixelLocToScaleLoc() _pixelLocToScaleLoc
代码
Chinamap.prototype._pixelLocToScaleLoc = function (pixelLoc) {
    var map = this;
    var scale = map.viewBoxShift.scale;
    var viewCenter = {
      'x': map.viewBox[0] + map.viewBox[2] / 2,
      'y': map.viewBox[1] + map.viewBox[3] / 2
    };
    return {
      'x': (pixelLoc.x - map.defaults.width / 2) / scale + viewCenter.x,
      'y': (pixelLoc.y - map.defaults.height / 2) / scale + viewCenter.y
    };
  };

createCityPoints

渲染城市点

方法 Chinamap.prototype.createCityPoints() createCityPoints
代码
Chinamap.prototype.createCityPoints = function (cities, callback) {
    var map = this;
    var point;
    var points = [];
    var cb = callback || function (city) {
      return this.paper.circle(city.coord[0], city.coord[1], 20).attr({
        "fill": "steelblue",
        "fill-opacity": 0.5
      });
    };

    cities.forEach(function (d) {
      //get format name
      var formatName = map.getCityFormatName(d.name);
      if (typeof formatName === 'undefined') {
        if (typeof d.lanlon === 'undefined') {
          return;
        } else {
          d.formatName = d.name;
        }
      } else {
        d.formatName = formatName;
        //get loc (lan, lon), use user provided lanlon by default;
        d.lanlon = d.lanlon || map.getCityCenter(d.formatName);
      }
      // process loc (geo projection)
      d.coord = map.projection(d.lanlon);
      // x and y of circle center in the container;
      d.pointLoc = map._scaleLocToPixelLoc({
        'x': d.coord[0],
        'y': d.coord[1]
      });
      // callback
      point = cb.call(map, d);
      points.push(point);
    });
    return points;
  };

charts/chord: API索引


Chord

构造函数

声明 Chord Chord
参数 node(Object) 表示在html的哪个容器中绘制该组件
参数 options(Object) 为用户自定义的组件的属性,比如画布大小
代码
var Chord = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Chord";
      this.node = this.checkContainer(node);
      this.matrix = [];
      this.groupNames = []; //数组:记录每个group的名字

      //图的大小设置
      this.defaults.legend = true;
      this.defaults.width = 800;
      this.defaults.height = 800;

      this.dimension.from = {
        type: "string",
        required: true,
        index: 0,
      };

      this.dimension.to = {
        type: "string",
        required: true,
        index: 1,
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 2
      };


      //设置用户指定的属性
      this.setOptions(options);

      this.legendArea = [20, (this.defaults.height - 20 - 220), 200, 220];
      if (this.defaults.legend) {
        this.xOffset = this.legendArea[2];
      } else {
        this.xOffset = 0;
      }

      this.defaults.innerRadius = Math.min((this.defaults.width - this.xOffset), this.defaults.height) * 0.38;
      this.defaults.outerRadius = this.defaults.innerRadius * 1.10;
      //创建画布
      this.createCanvas();
    }
  });

createCanvas

创建画布

方法 Chord.prototype.createCanvas() createCanvas
代码
Chord.prototype.createCanvas = function () {
    this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height);
    canvasStyle = this.node.style;
    canvasStyle.position = "relative";
    this.floatTag = DataV.FloatTag()(this.node);
    this.floatTag.css({
      "visibility": "hidden"
    });
  };

getColor

获取颜色

方法 Chord.prototype.getColor() getColor
参数 i(Number) 元素类别编号
返回 String 返回颜色值
代码
Chord.prototype.getColor = function (i) {
    var color = DataV.getColor();
    return color[i % color.length][0];
  };

render

绘制弦图

方法 Chord.prototype.render() render
代码
Chord.prototype.render = function () {
    this.layout();
    if (this.defaults.legend) {
      this.drawLegend();
    }
  };

drawLegend

绘制图例

方法 Chord.prototype.drawLegend() drawLegend
代码
Chord.prototype.drawLegend = function () {
    var that = this;
    var paper = this.canvas;
    var legendArea = this.legendArea;
    var rectBn = paper.set();
    this.underBn = [];
    var underBn = this.underBn;
    for (i = 0; i <= this.groupNum; i++) {
      //底框
      underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "stroke": "none"
        //"r": 3
      }).hide());
      //色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": this.getColor(i),
        "stroke": "none"
      });
      //文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, this.groupNames[i]).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12
      }).attr({
        "text-anchor": "start"
      });
      //选框
      rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
        //"r": 3
      })).data("clicked", 0);
    }
    rectBn.forEach(function (d, i) {
      d.mouseover(function () {
        if (d.data("clicked") === 0) {
          underBn[i].attr('opacity', 0.5);
          underBn[i].show();
        }
      }).mouseout(function () {
        if (d.data("clicked") === 0) {
          underBn[i].hide();
        }
      });
      d.click(function () {
        for (j = 0; j < underBn.length; j++) {
          if (j === i) {
            underBn[j].show();
          } else {
            underBn[j].hide();
          }
        }
        rectBn.forEach(function (eachBn) {
          if (eachBn !== d) {
            eachBn.data("clicked", 0);
          }
        });
        if (d.data("clicked") === 0) {
          underBn[i].attr('opacity', 1);
          underBn[i].show();
          that.chordGroups.forEach(function (d) {
            if (d.data('source') !== i && d.data('target') !== i) {
              d.attr({
                'fill-opacity': 0.1
              });
            } else {
              d.attr({
                'fill-opacity': 0.6
              });
            }
          });

          d.data("clicked", 1);
        } else if (d.data("clicked") === 1) {
          underBn[i].hide();
          d.data("clicked", 0);
          that.chordGroups.forEach(function (d) {
            d.attr({
              'fill-opacity': 0.6
            });
          });
        }
      });
    });
  };

setSource

对原始数据进行处理

方法 Chord.prototype.setSource() setSource
参数 table(Array) 将要被绘制成饼图的二维表数据
代码
Chord.prototype.setSource = function (table, map) {
    map = this.map(map);
    var that = this;

    var index = {};
    var groupNum = 0;
    table = table.slice(1); // 从第一行开始,第0行舍去

    table.forEach(function (d) {
      var from = d[map.from];
      if (typeof index[from] === 'undefined') {
        index[from] = groupNum++;
        that.groupNames.push(from);
      }
    });

    table.forEach(function (d) {
      var to = d[map.to];
      if (typeof index[to] === 'undefined') {
        index[to] = groupNum++;
        that.groupNames.push(to);
      }
    });

    this.groupNum = groupNum;

    for (var i = 0; i < groupNum; i++) {
      this.matrix[i] = [];
      for (var j = 0; j < groupNum; j++) {
        this.matrix[i][j] = 0;
      };
    };

    table.forEach(function (d) {
      that.matrix[index[d[map.from]]][index[d[map.to]]] += Number(d[map.value]);
    });
  };

layout

创建chord布局

方法 Chord.prototype.layout() layout
代码
Chord.prototype.layout = function () {
    var floatTag = this.floatTag;
    var that = this;

    that.canvas.clear();

chordLayout

ar see = [
[11975, 5871, 8916, 2868],
[1951, 10048, 2060, 6171],
[8010, 16145, 8090, 8045],
[1013, 990, 940, 6907]
];

声明 chordLayout chordLayout
代码
var chordLayout = d3.layout.chord().padding(0.05) //chord segments之间的padding间隔
    .sortSubgroups(d3.descending) //chord segments细分后的排序规则
    .matrix(that.matrix);

ar fillColor = d3.scale.ordinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);

代码
//groups数组:获取每个组的起始角度、数值、索引等属性
    var groups = chordLayout.groups();

    //由内外半径、起始角度计算路径字符串
    var pathCalc = d3.svg.arc().innerRadius(that.defaults.innerRadius).outerRadius(that.defaults.outerRadius).startAngle(function (d) {
      return d.startAngle;
    }).endAngle(function (d) {
      return d.endAngle;
    });

    var chords = chordLayout.chords();

    //计算弦的路径曲线
    var chordCalc = d3.svg.chord().radius(that.defaults.innerRadius);

    //Raphael: Paper.path()
    var donutEle;
    //获取每个环形的字符串表示
    var spline;
    //表示每条弦的element
    var chordEle;
    //每条弦的字符串表示
    var belt;

    var num; //每个group分割小格数
    var unitAngle; //每个group所占的角度
    var angle;
    var radian;
    var tickLine;
    var tickStr; //每个tick的路径
    var xTrans, yTrans;
    var aX, aY, bX, bY; //每个tick起始端点的坐标
    var anchor;
    var rotateStr;
    var wordStr;
    var word;
    var textEl;
    var wXTrans, wYTrans;
    var tips;
    var minValue = 1000;
    that.chordGroups = that.canvas.set();
    that.donutGroups = that.canvas.set();

    $(this.node).append(this.floatTag);

    //计算某条弦被赋值为target或source的颜色
    var colorCalc = function (index) {
      var i = chords[index].target.value > chords[index].source.value ? chords[index].target.index : chords[index].source.index;
      return i;
    };

    //添加透明效果

    var mouseOverDonut = function () {
      floatTag.html('<div style = "text-align: center;margin:auto;color:'
      //+ jqNode.color
      +
        "#ffffff" + '">' + this.data('text') + '</div>');
      floatTag.css({
        "visibility": "visible"
      });
      that.underBn.forEach(function (d) {
        d.hide();
      });
      index = this.data("donutIndex");
      that.chordGroups.forEach(function (d) {
        if (d.data('source') !== index && d.data('target') !== index) {
          d.attr({
            'fill-opacity': 0.1
          });
        } else {
          d.attr({
            'fill-opacity': 0.6
          });
        }
      });
      //fade(this.data("donutIndex"), 0.2);
      that.underBn[index].attr('opacity', 0.5).show();

    };

    var mouseOutDonut = function () {
      floatTag.css({
        "visibility": "hidden"
      });
      index = this.data("donutIndex");
      that.chordGroups.forEach(function (d) {
        if (d.data('source') !== index && d.data('target') !== index) {
          d.attr({
            'fill-opacity': 0.6
          });
        }
      });
      //fade(this.data("donutIndex"), 0.6);
      that.underBn[index].hide();
    };

    var mouseoverChord = function () {
      floatTag.html('<div style="text-align: center;margin:auto;color:#ffffff">' + this.data('text') + '</div>');
      floatTag.css({
        "visibility": "visible"
      });
      that.underBn.forEach(function (d) {
        d.hide();
      });
      that.chordGroups.forEach(function (d) {
        d.attr("fill-opacity", 0.1);
      });
      if (navigator.appName !== "Microsoft Internet Explorer") {
        this.toFront(); //把当前弦移到画布最上层
      }
      this.attr("fill-opacity", 0.7);
      that.underBn[this.data('source')].attr('opacity', 0.5).show();
    };

    var mouseoutChord = function () {
      floatTag.css({
        "visibility": "hidden"
      });
      //alert("***");
      that.chordGroups.forEach(function (d) {
        d.attr("fill-opacity", 0.6);
      });
      //this.attr("fill-opacity", 0.6);
      that.underBn[this.data('source')].hide();
    };

    //画弦*********************************************************
    var t;
    for (t = 0; t <= chords.length - 1; t++) {
      //alert(chords.length);
      belt = chordCalc(chords[t]);
      //hover到弦上时的效果
      tips = that.groupNames[chords[t].source.index] + " to " + that.groupNames[chords[t].target.index] + ": " + that.matrix[chords[t].source.index][chords[t].target.index] + "," + that.groupNames[chords[t].target.index] + " to " + that.groupNames[chords[t].source.index] + ": " + that.matrix[chords[t].target.index][chords[t].source.index];

      chordEle = that.canvas.path(belt).
      translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).attr({
        "path": belt,
        "fill": that.getColor(colorCalc(t)),
        "fill-opacity": 0.6,
        "stroke": "#d6d6d6",
        "stroke-opacity": 0.1
      }).hover(mouseoverChord, mouseoutChord).data("source", chords[t].source.index).data("target", chords[t].target.index);
      //.attr("fill", fillColor(chords[t].target.index))
      chordEle.data('text', tips);
      that.chordGroups.push(chordEle);
    }



    //画圆弧*********************************************************
    var i, r;
    var donutName;
    var nameStr;
    var nameX, nameY;
    var ro, a;
    var sum = 0;
    for (r = 0; r <= groups.length - 1; r++) {
      sum += groups[r].value;
    }

    for (i = 0; i <= groups.length - 1; i++) {
      //画外圈的pie图**************************************
      //计算每个group的path
      spline = pathCalc(groups[i]);
      tips = that.groupNames[i] + ": " + Math.round(groups[i].value) + " " + (groups[i].value * 100 / sum).toFixed(2) + "%";

      donutEle = that.canvas.path(spline).translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, that.defaults.height / 2).data("donutIndex", i).attr({
        "path": spline,
        "fill": that.getColor(i),
        "stroke": that.getColor(i)
      }).mouseover(mouseOverDonut).mouseout(mouseOutDonut);
      donutEle.data('text', tips);
      that.donutGroups.push(donutEle);

      //每个donut上显示名称
      ro = groups[i].startAngle * 180 / Math.PI - 86 + 90;
      a = (groups[i].startAngle * 180 / Math.PI - 86) * Math.PI / 180;
      nameX = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.cos(a);
      nameY = ((that.defaults.outerRadius - that.defaults.innerRadius) / 2 + that.defaults.innerRadius) * Math.sin(a);
      nameStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + ro + "T" + nameX + "," + nameY;

      if ((groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI > 10) {
        donutName = that.canvas.text().attr("font", "12px Verdana").attr("text", that.groupNames[i]).transform(nameStr);
      }

      //画刻度和刻度值**************************************
      num = groups[i].value / 5000;
      //最细分的每个小格代表的数值大小
      unitAngle = (groups[i].endAngle - groups[i].startAngle) * 180 / Math.PI / num;

      var j;
      for (j = 0; j <= num; j++) {
        //计算旋转角度和水平、竖直方向所需平移的距离
        radian = ((groups[i].startAngle * 180 / Math.PI - 90) + j * unitAngle);
        angle = radian * Math.PI / 180;
        xTrans = that.defaults.outerRadius * Math.cos(angle);
        yTrans = that.defaults.outerRadius * Math.sin(angle);

        tickStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "T" + xTrans + "," + yTrans;

        //刻度线的起点终点坐标
        aX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + xTrans;
        aY = that.defaults.height / 2 + yTrans;
        bX = ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + (that.defaults.outerRadius + 6) * Math.cos(angle);
        bY = that.defaults.height / 2 + (that.defaults.outerRadius + 6) * Math.sin(angle);

        tickLine = "M" + aX + "," + aY + "L" + bX + "," + bY;
        that.canvas.path(tickLine).attr({
          'stroke': "#929292",
          "stroke-width": '1px'
        }); //绘制刻度

        //每隔五个刻度,绘制一次文字
        if (j % 2 === 0) {
          //计算text-anchor
          if (radian + 90 < 180) {
            anchor = "start";
          } else {
            anchor = "end";
          }

          //计算文字方向是否需要旋转180度
          if (radian + 90 < 180) {
            rotateStr = null;
          } else {
            rotateStr = "R180";
          }

          wXTrans = (that.defaults.outerRadius + 10) * Math.cos(angle);
          wYTrans = (that.defaults.outerRadius + 10) * Math.sin(angle);

          word = j % 2 ? "" : Math.round(((groups[i].value / num) * j) / 1000);

          wordStr = "T" + ((that.defaults.width - that.xOffset) / 2 + that.xOffset) + "," + that.defaults.height / 2 + "R" + radian

groups[i].startAngle * 180 / Math.PI - 90)

代码
+ rotateStr + "T" + wXTrans + "," + wYTrans;

          //绘制文字
          textEl = that.canvas.text(0, 0, word).attr("font", "12px Verdana").transform(wordStr).attr("text-anchor", anchor).attr('fill', "#929292");
        }
      }
    }

his.canvas.text().attr("font", "12px arial").translate((that.defaults.width - this.xOffset) / 2 + this.xOffset, this.defaults.height).attr("text", "The unit of the scale on the periphery is 1000. \n 刻度值的单位为1000。");

代码
};

  return Chord;
});

charts/column: API索引


Column

Column构造函数
Creates Column in a DOM node with id "chart"

Options

  • width 宽度,默认为522,单位像素
  • height 高度,默认为522,单位像素
  • yBase 纵坐标的基线值,默认为0,可以设置为任意数值;为undefined时,以数据中的最小值为起始值
  • barWidth 柱子的宽度
  • showLegend 是否显示图例
  • legendWidth 图例的宽度
  • margin 图表的间距,依次为上右下左
  • xTickNumber 横轴刻度数
  • yTickNumber 纵轴刻度数
  • formatLabel 横轴提示格式化函数,传入横轴值,默认函数传出原始值
  • formatYScale 纵轴刻度格式化函数,传入纵轴刻度值
  • formatValue 值格式化函数

Examples

var column = new Column("chart", {"width": 500, "height": 600, "typeNames": ["Y", "Z"]});
声明 Column Column
参数 node(Mix) The dom node or dom node Id
参数 options(Object) options json object for determin column style.
代码
var Column = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Column";
      this.node = this.checkContainer(node);

柱纬度

代码
this.dimension.column = {
        type: "string",
        required: true,
        index: undefined
      };

横向纬度

代码
this.dimension.x = {
        type: "string",
        required: true,
        index: 1
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 2
      };

      this.defaults.typeNames = [];
      // canvas parameters
      this.defaults.width = 522;
      this.defaults.height = 522;

      this.defaults.margin = [50, 50, 50, 50];

      this.defaults.barWidth = 10;
      this.defaults.circleR = 3;
      this.defaults.barColor = ["#308BE6","#8EEC00","#DDDF0D"];
      this.defaults.xTickNumber = 5;
      this.defaults.yTickNumber = 5;

      this.defaults.yBase = 0;

      this.defaults.showLegend = true;
      this.defaults.legendWidth = 100;
      //图例区域的左上顶点坐标x,y,宽,高
      this.defaults.legendArea = [422, 50, 472, 220];
      this.columnSet = [];
      this.legendCluster = [];

      this.setOptions(options);
      this.createCanvas();
      this.initEvents();

    }
  });

createCanvas

创建画布

方法 Column.prototype.createCanvas() createCanvas
代码
Column.prototype.createCanvas = function () {
    var conf = this.defaults;
    this.node.style.position = "relative";
    this.canvas = new Raphael(this.node, conf.width, conf.height);
  };

  Column.prototype.initEvents = function () {
    var that = this;
    this.on('legendOver', function (columnIndex) {
      that.columnSet.forEach(function (set, index) {
        if (index !== columnIndex) {
          set.attr({
            "fill-opacity": 0.5
          });
        }
      });
    });

    this.on('legendOut', function (columnIndex) {
      that.columnSet.forEach(function (set, index) {
        set.attr({
          "fill-opacity": 1
        });
      });
    });

    this.on('legendClick', function (clicked, columnIndex) {
      that.clicked = clicked;
      that.clickedColumnIndex = columnIndex;
      that.columnSet.forEach(function (set, index) {
        if (index !== columnIndex) {
          if (clicked) {
            set.attr({"fill-opacity": 0.1});
          } else {
            set.attr({"fill-opacity": 0.5});
          }
        } else {
          set.attr({"fill-opacity": 1});
        }
      });
    });
  };

setSource

设置数据源
Examples:

column.setSource(source);
方法 Column.prototype.setSource() setSource
参数 source(Array) 数据源 第一列为排布在x轴的数据,后n列为排布在y轴的数据
代码
Column.prototype.setSource = function (source, map) {
    var conf = this.defaults;
    map = this.map(map);
    var dataTable;
    if (DataV.detect(source) === 'Table_WITH_HEAD') {
      dataTable = DataV.collectionify(source);
    } else {
      dataTable = source;
    }
    // 不指定列,将当前数据作为一列
    this.columns = (typeof map.column === 'undefined') ? {column: dataTable} : _.groupBy(dataTable, map.column);
    var that = this;
    _.each(this.columns, function (group, key) {
      that.columns[key] = _.sortBy(group, map.x);
    });
    this.columnCount = _.keys(this.columns).length;
    conf.xAxisData = _.pluck(_.first(_.values(this.columns)), map.x);
    conf.xTickNumber = Math.min(conf.xAxisData.length, conf.xTickNumber);
    // 纵坐标的范围
    var yExtent = d3.extent(dataTable, function (item) {
      return item[map.value];
    });
    // 纵坐标基线值
    if (typeof conf.yBase !== 'undefined') {
      yExtent.push(conf.yBase);
    }
    yExtent = d3.extent(yExtent);
    // 最大值放大1/10
    conf.yExtent = [yExtent[0], yExtent[1] * 1.1];
  };

setAxis

设置坐标轴

方法 Column.prototype.setAxis() setAxis
代码
Column.prototype.setAxis = function () {
    var conf = this.defaults;
    if (conf.showLegend) {
      conf.legendArea = [conf.width - conf.legendWidth, 0, conf.width, conf.height];
    } else {
      conf.legendWidth = 0;
      conf.legendArea = [0, 0, 0, 0];
    }

    var margin = conf.margin;
    var diagramArea = this.diagramArea = [margin[3], margin[0], conf.width - conf.legendWidth - margin[1], conf.height - margin[2]];

    //设置x轴
    this.x = d3.scale.linear().domain([0, conf.xAxisData.length]).range([diagramArea[0], diagramArea[2]]);
    //设置y轴
    this.value = d3.scale.linear().domain(conf.yExtent).range([diagramArea[3], diagramArea[1]]);
    var xRange = this.x.range();
    var valueRange = this.value.range();
    this.axisPosition = {
      left: xRange[0],
      right: xRange[1],
      up: valueRange[1],
      down: valueRange[0]
    };
    this.clusterCount = _.max(this.columns, function (column) {
      return column.length;
    }).length;
    var width = diagramArea[2] - diagramArea[0];
    this.clusterWidth = width / this.clusterCount;
    this.gap = (this.clusterWidth - this.columnCount * conf.barWidth) / 2;
  };

drawAxis

绘制坐标

方法 Column.prototype.drawAxis() drawAxis
代码
Column.prototype.drawAxis = function () {
    var conf = this.defaults;
    var paper = this.canvas;
    var j;
    //画坐标轴
    var axisLines = paper.set();
    var tickText = paper.set();
    var axis = this.axisPosition;
    // X轴
    var ticks = this.x.ticks(conf.xTickNumber);
    var formatLabel = this.getFormatter('formatLabel');

    // 修复显示不从第一个x轴单位显示的bug
    for (j = 0; j < ticks.length; j++) {
      // 修改x轴单位显示在所有Column组的中间位置
      // 修复x轴单位对于柱位置的偏移
      var x = this.x(ticks[j]) + this.clusterWidth / 2;
      var text = conf.xAxisData[ticks[j]];
      // 会存在刻度大于数组长度的情况,边界处理
      if (typeof text !== "undefined") {
        tickText.push(paper.text(x, axis.down + 14, formatLabel(text)).rotate(0, x, axis.up));
        // 画x轴刻度线
        axisLines.push(paper.path("M" + x + "," + axis.down + "L" + x + "," + (axis.down + 5)));
      }
    }

    tickText.attr({
      "fill": "#878791",
      "fill-opacity": 0.7,
      "font-size": 12,
      "text-anchor": "middle"
    });

    // 绘制Y轴
    axisLines.push(paper.path("M" + axis.left + "," + axis.up + "L" + axis.left + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });
    //Y轴
    ticks = this.value.ticks(conf.yTickNumber);
    var formatYScale = this.getFormatter('formatYScale');
    for (j = 0; j < ticks.length; j++) {
      tickText.push(paper.text(axis.left - 8, this.value(ticks[j]), formatYScale(ticks[j])).attr({
        "fill": "#878791",
        "fill-opacity": 0.7,
        "font-size": 12,
        "text-anchor": "end"
      }).rotate(0, axis.right + 6, this.value(ticks[j])));
      axisLines.push(paper.path("M" + axis.left + "," + this.value(ticks[j]) + "L" + (axis.left - 5) + "," + this.value(ticks[j])));
    }
    axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });

    var numOfHLine = d3.round((axis.down - axis.up) / 30 - 1);
    var hLines = paper.set();
    for (j = 1; j <= numOfHLine; j++) {
      var hLinesPos = axis.down - j * 30;
      hLines.push(paper.path("M" + axis.left + "," + hLinesPos + "L" + axis.right + "," + hLinesPos));
    }
    hLines.attr({
      "stroke": "#ECECEC",
      "stroke-width": 0.1
    });
  };

drawDiagram

进行柱状图的绘制

方法 Column.prototype.drawDiagram() drawDiagram
代码
Column.prototype.drawDiagram = function () {
    var that = this;
    var conf = this.defaults;
    var axis = this.axisPosition;
    var paper = this.canvas;
    //bars
    var barWidth = conf.barWidth;
    var columnSet = this.columnSet = [];
    var values = _.values(this.columns);

    values.forEach(function (column, index) {
      columnSet[index] = paper.set();
      column.forEach(function (row, i) {
        var value = row[that.mapping.value];
        var height = that.value(value);
        var x = that.x(i);
        var rect = paper.rect(x + barWidth * index + that.gap, height, barWidth, axis.down - height).attr({
          "fill": conf.barColor[index],
          "fill-opacity": 1,
          "stroke": "none"
        });
        rect.data('column', index).data('index', i);
        columnSet[index].push(rect);
      });
    });

  };

  Column.prototype.setEvents = function () {
    var that = this;
    var conf = this.defaults;
    var columnSet = this.columnSet;
    var barWidth = conf.barWidth;
    var paper = this.canvas;
    //var tagSet = paper.set();
    var axis = this.axisPosition;
    var values = _.values(this.columns);

    var currentClusterIndex = -1;
    var currentColumnIndex = -1;
    var area = this.diagramArea;

    var hideLegend = function (columnIndex) {
      if (typeof that.legendCluster[columnIndex] === 'undefined') {
        return;
      }
      that.legendCluster[columnIndex].bottom.attr({"opacity": 0});
      that.legendCluster[columnIndex].side.forEach(function (d) {
        d.attr({"opacity": 0});
      });
    };

    var createLegend = function (clusterIndex) {
      // hovered cluster
      var legend = {
        bottom: paper.set(),
        side: [] //array of set
      };
      var hovered = columnSet.map(function (set) {
        return set[clusterIndex];
      });
      var xPos = _.max(hovered, function (item) {
        return item.attrs.x;
      }).attrs.x + barWidth + 8;

      var formatValue = that.getFormatter('formatValue');
      hovered.forEach(function (item, columnIndex) {
        var singleSideLegend = paper.set();
        var yPos = 0;
        var valueLabel = '' + formatValue(values[columnIndex][clusterIndex][that.mapping.value]);
        var textWidth = 5 * valueLabel.length + 20;

        var rect = paper.rect(xPos, yPos - 10, textWidth, 20, 2).attr({
          "fill": conf.barColor[columnIndex],
          "fill-opacity": 1,
          "stroke": "none"
        });
        var path = paper.path("M" + xPos + "," + (yPos - 4) + "L" + (xPos - 8) + "," + yPos + "L" + xPos + "," + (yPos + 4) + "V" + yPos + "Z").attr({
          "fill" : conf.barColor[columnIndex],
          "stroke" : conf.barColor[columnIndex]
        });
        var text = paper.text(xPos + textWidth / 2, yPos, valueLabel).attr({
          "fill": "#ffffff",
          "fill-opacity": 1,
          "font-weight": "bold",
          "font-size": 12,
          "text-anchor": "middle"
        });
        legend.side.push(singleSideLegend.push(rect, path, text));
      });

      xPos = hovered.reduce(function (pre, cur) {
        return pre + cur.attrs.x;
      }, 0) / hovered.length + barWidth / 2;
      var formatLabel = that.getFormatter("formatLabel");
      var xLabel = formatLabel(values[0][clusterIndex][that.mapping.x]);
      var textWidth = 6 * xLabel.length + 20;
      //axis x rect
      var rect = paper.rect(xPos - textWidth / 2, axis.down + 8, textWidth, 20, 2).attr({
        "fill": "#5f5f5f",
        "fill-opacity": 1,
        "stroke": "none"
      });
      // axis x text
      var text = paper.text(xPos, axis.down + 18, xLabel).attr({
        "fill": "#ffffff",
        "fill-opacity": 1,
        "font-weight": "bold",
        "font-size": 12,
        "text-anchor": "middle"
      });
      var arrow = paper.path("M" + (xPos - 4) + "," + (axis.down + 8) + "L" + xPos + "," + axis.down +
        "L" + (xPos + 4) + "," + (axis.down + 8) + "H" + xPos + "Z").attr({
        "fill": "#5F5F5F",
        "stroke": "#5F5F5F"
      });
      legend.bottom.push(rect, text, arrow);
      that.legendCluster[clusterIndex] = legend;
    };

    //bars
    this.on("mouseOverBar", function (clusterIndex, columnIndex, oldClusterIndex, oldColumnIndex) {
      if (that.clicked && that.clickedColumnIndex !== columnIndex) {
        return;
      }
      currentColumnIndex = columnIndex;
      currentClusterIndex = clusterIndex;

      var currentSet = columnSet.filter(function (set, columnIndex) {
        return that.clicked ? that.clickedColumnIndex === columnIndex : true;
      });
      // column color change and recover
      currentSet.forEach(function (set) {
        set[clusterIndex].attr({
          opacity: 0.6
        });
        if (oldClusterIndex >= 0) {
          set[oldClusterIndex].attr({
            opacity: 1
          });
        }
      });

      hideLegend(oldClusterIndex);
      // hovered cluster
      var hovered = currentSet.map(function (set) {
        return set[clusterIndex];
      });
      var xPos = _.max(hovered, function (item) {
        return item.attrs.x;
      }).attrs.x + barWidth + 8;

      var y = _.map(hovered, function (item) {
        return item.attrs.y;
      });
      // TODO: 防遮罩算法 
      for (var i = 1; i < y.length; i++) {
        for (var j = i - 1; j >= 0; j--) {
          var overlapped = y.filter(function (item, index) {
            return index < i && Math.abs(item - y[i]) < 20;
          });
          if (overlapped.length > 0) {
            var extent = d3.extent(overlapped);
            if (y[i] <= extent[0]) {
              y[i] = extent[0] - 20;
            } else {
              y[i] = extent[1] + 20;
            }
          }
        }
      }

      if (typeof that.legendCluster[clusterIndex] === 'undefined') {
        createLegend(clusterIndex);
      }
      var legend = that.legendCluster[clusterIndex];
      //show legend
      legend.bottom.attr({"opacity": 1});
      if (that.clicked) {//single column
        legend.side.forEach(function (d, i) {
          if (i === columnIndex) {
            d.attr({
              "opacity": 1,
              "transform": "t" + (-that.defaults.barWidth * (that.columnSet.length - 1 - columnIndex)) + "," + y[0]
            });
          } else {
            d.attr({"opacity": 0});
          }
        });
      } else {
        legend.side.forEach(function (d, i) {
          d.attr({
            "opacity": 1,
            "transform": "t0," + y[i]
          });
        });
      }
    });

    that.on('mouseOutBar', function () {
      if (currentColumnIndex >= 0 && currentClusterIndex >= 0) {
        var clusterIndex = currentClusterIndex;
        var currentSet = columnSet.filter(function (set, columnIndex) {
          return that.clicked ? that.clickedColumnIndex === columnIndex : true;
        });
        hideLegend(currentClusterIndex);

        currentSet.forEach(function (set) {
          set[clusterIndex].attr({
            opacity: 1
          });
        });
        currentColumnIndex = -1;
        currentClusterIndex = -1;
      }
    });

    $(paper.canvas).bind("mousemove", function (event, pageX, pageY) {
      var offset = $(this).parent().offset();
      var x = (event.pageX || pageX) - offset.left,
          y = (event.pageY || pageY) - offset.top;
      var mod;
      var clusterIndex;
      var columnIndex;
      // mouse in?
      if (x > area[0] && x < area[2] && y > area[1] && y < area[3]) {
        mod = (x - area[0]) % that.clusterWidth;
        clusterIndex = Math.floor((x - area[0]) / that.clusterWidth);
        if (mod <= that.gap) {
          // handle mouse on left gap
          columnIndex = 0;
        } else if (mod >= that.clusterWidth - that.gap) {
          // handle mouse on right gap
          columnIndex = that.columnSet.length - 1;
        } else {
          // between gap
          columnIndex = Math.floor((mod - that.gap) / barWidth);
        }
        if (currentClusterIndex !== clusterIndex) {
          that.fire('mouseOverBar', clusterIndex, columnIndex, currentClusterIndex, currentColumnIndex);
        }
      } else {
        that.fire('mouseOutBar');
      }
    });
  };

drawLegend

绘制图例

方法 Column.prototype.drawLegend() drawLegend
代码
Column.prototype.drawLegend = function () {
    var that = this;
    var paper = this.canvas;
    var legendSet = paper.set();
    var bgSet = paper.set();
    var conf = this.defaults;
    var legendArea = conf.legendArea;
    //legend
    var mouseOverLegend = function () {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({
        "fill-opacity":0.5
      });
      that.fire('legendOver', this.data('type'));
    };

    var mouseOutLegend = function () {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({"fill-opacity": 0});
      that.fire('legendOut', this.data('type'));
    };

    var clickLegend = function () {
      if (legendSet.clicked && legendSet.clickedColumn === this.data('type')) {
        legendSet.clicked = false;
      } else {
        legendSet.clicked = true;
        legendSet.clickedColumn = this.data('type');
      }
      bgSet.attr({"fill-opacity": 0});
      bgSet[this.data('type')].attr({
        "fill-opacity": legendSet.clicked ? 1 : 0
      });
      that.fire('legendClick', legendSet.clicked, this.data('type'));
    };

    var labels = _.keys(this.columns);
    for (var i = 0; i < labels.length; i++) {
      //底框
      bgSet.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "fill-opacity": 0,
        "stroke": "none"
      }));
      // 色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": conf.barColor[i],
        "stroke": "none"
      });
      // 文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, labels[i]).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12,
        "text-anchor": "start"
      });
      // 选框
      var rect = paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
      }).data("type", i);
      rect.mouseover(mouseOverLegend);
      rect.mouseout(mouseOutLegend);
      rect.click(clickLegend);
      legendSet.push(rect);
    }
  };

render

绘制柱状图

Options

  • width 宽度,默认为节点宽度
  • typeNames 指定y轴上数据类目

Examples

column.render({"width": 1024})
方法 Column.prototype.render() render
参数 options(Object) options json object for determin column style.
代码
Column.prototype.render = function (options) {
    var conf = this.defaults;
    this.setOptions(options);
    this.canvas.clear();
    this.setAxis();
    this.drawAxis();
    this.drawDiagram();
    this.setEvents();
    if (conf.showLegend) {
      this.drawLegend();
    }
  };

charts/cover: API索引


charts/diff: API索引


Diff

构造函数

声明 Diff Diff
参数 node(Object) 表示在html的哪个容器中绘制该组件
参数 options(Object) 为用户自定义的组件的属性,比如画布大小
代码
var Diff = DataV.extend(DataV.Chart, {
        type: "Diff",
        initialize: function (node, options) {
            this.node = this.checkContainer(node);

            //图的大小设置
            this.defaults.width = 900;
            this.defaults.height = 800;

            //设置用户指定的属性
            this.setOptions(options);

            //创建画布
            this.createCanvas();
        }
    });

createCanvas

创建画布

方法 Diff.prototype.createCanvas() createCanvas
代码
Diff.prototype.createCanvas = function () {
        this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height);
    };

render

绘制弦图

方法 Diff.prototype.render() render
代码
Diff.prototype.render = function () {
        this.layout();
    };

    // 计算顺序的相似度
    var diffMap = function (list1, list2) {
      var map = [];
      var hit = 0;
      var lastIndex = -1;
      for (var i = 0; i < list1.length; i++) {
        var index = _.indexOf(list2, list1[i]);
        if (index === -1) {
          continue;
        } else {
          if (index > lastIndex) {
            lastIndex = index;
            map.push([i, index]);
          }
          hit++;
        }
      }
      console.log(map);
      console.log(map.length / list1.length);
      console.log(hit / list1.length);
      return map;
    };

setSource

对原始数据进行处理

方法 Diff.prototype.setSource() setSource
参数 table(Array) 将要被绘制成饼图的二维表数据
代码
Diff.prototype.setSource = function (table1, table2) {
        this.rawData = [table1, table2];
        this.diffMap = diffMap(table1, table2);
    };

layout

创建chord布局

方法 Diff.prototype.layout() layout
代码
Diff.prototype.layout = function () {
        var that = this;
        var canvas = that.canvas;

        var paddingLeft = 10;
        var paddingTop = 10;
        var height = 20;
        var distance = 50;
        var width = (this.defaults.width - 2 * paddingLeft - distance) / 2;

        for (var j = 0, k = this.rawData.length; j < k; j++) {
            var maped = _.pluck(this.diffMap, j);
            for (var i = 0, l = this.rawData[j].length; i < l; i++) {
                canvas.rect(paddingLeft + j * (width + distance), paddingTop + height * i, width, height).attr({fill: _.indexOf(maped, i) !== -1 ? "#00ff00" : "#ff0000"});
                canvas.text(paddingLeft + j * (width + distance), paddingTop + height * i + height / 2, this.rawData[j][i]).attr({'text-anchor': 'start'});
            }
        }
        for (var i = 0, l = this.diffMap.length; i < l; i++) {
            var line = this.diffMap[i];
            canvas.path("M" + (paddingLeft + width) + ' ' + (paddingTop + height * line[0] + height / 2) + "L" + (paddingLeft + width + distance) + " " + (paddingTop + height * line[1] + height / 2)).attr({stroke: '#00ff00'});
        }
    };

    return Diff;
});

charts/flow: API索引


Flow

Flow构造函数

声明 Flow Flow
代码
var Flow = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Flow";
            this.node = this.checkContainer(node);

            // Properties
            this.font = {};

            // Canvas
            this.defaults.width = 500;
            this.defaults.height = 400;
            this.defaults.deep = 150;
            this.defaults.radius = 50;
            this.defaults.xStep = 300;
            this.defaults.xStart = 200;
            this.defaults.yStart = 50;

            this.setOptions(options);
            this.createCanvas();
        }
    });

    Flow.prototype.setSource = function (source) {
        var conf = this.defaults;

        this.rawData = source;
        this.source = this.remapSource(source);
    };

    Flow.prototype.remapSource = function (data) {
        console.log(data);
        var dataLength = data.length;
        var remapData = [];

        var total;
        var i;
        for (i = 0; i < dataLength; i++){
            if (!data[i][3]) {
                total = data[i][2];
                remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0});
            } else {
                remapData.push({id: data[i][0], name: data[i][1], value: data[i][2], pid: data[i][3], child: [], deep: 0});
            }
        }

        var conf = this.defaults;
        var width = conf.width;
        var height = conf.height;
        var radius = conf.radius;
        var xStep = conf.xStep;
        var xStart = conf.xStart;
        var yStart = conf.yStart;
        var depth = 0;

        for (i = 0; i < dataLength; i++){
            if (remapData[i].pid) {
                remapData[i].deep = remapData[remapData[i].pid - 1].deep + 1;
                remapData[remapData[i].pid - 1].child.push(remapData[i].id - 1);
                if (remapData[i].deep > depth) {
                    depth = remapData[i].deep;
                }
            }
            // remapData[remapData[i].pid].child.push(remapData[i].id);
        }

        this.depth = depth;
        radius = Math.min(Math.min((width - xStep * (depth - 1) - xStart * 2) / depth, height*0.55), radius);
        console.log("r:" + radius);
        for (i = 0; i < dataLength; i++){
            remapData[i].percent = remapData[i].value / total;
            remapData[i].radius = radius * remapData[i].percent;
        }
        return remapData;
        // return data;
    };

    Flow.prototype.layout = function () {
        var conf = this.defaults;
        var width = conf.width;
        var height = conf.height;
        var xStart = conf.xStart;
        var yStart = conf.yStart;
        var xStep = conf.xStep;
        var remapData = this.source;

        //console.log(this.source);
        var circleData = [];

        circleData.push({x: width * 0.24, y: height * 0.42, radius: Math.max(10, remapData[0].radius), deep: remapData[0].deep, name: remapData[0].name, value: remapData[0].value});
        circleData.push({x: width * 0.5, y: height * 0.245, radius: Math.max(10, remapData[1].radius), deep: remapData[1].deep, name: remapData[1].name, value: remapData[1].value});
        circleData.push({x: width * 0.5, y: height * 0.6, radius: Math.max(10, remapData[2].radius), deep: remapData[2].deep, name: remapData[2].name, value: remapData[2].value});
        circleData.push({x: width * 0.72, y: height * 0.5, radius: Math.max(10, remapData[3].radius), deep: remapData[3].deep, name: remapData[3].name, value: remapData[3].value});
        circleData.push({x: width * 0.72, y: height * 0.817, radius: Math.max(10, remapData[4].radius), deep: remapData[4].deep, name: remapData[4].name, value: remapData[4].value});

        for (i = 0;i < circleData.length; i++) {
            console.log(circleData[i].x);
        }

        this.circleData = circleData;
    };

    Flow.prototype.getColor = function () {

        var colorMatrix = DataV.getColor();
        
        return color;
    };

    // Tree.prototype.getFont = function () {
    //     //var conf = this.defaults;

    //     return DataV.getFont();
    // };

    Flow.prototype.createCanvas = function () {
        var conf = this.defaults;
        this.canvas = new Raphael(this.node, conf.width, conf.height);

        this.DOMNode = $(this.canvas.canvas);
        var that = this;
        this.DOMNode.click(function (event) {
            that.trigger("click", event);
        });
        this.DOMNode.dblclick(function (event) {
            that.trigger("dblclick", event);
        });

        var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll";
        this.DOMNode.bind(mousewheel, function (event) {
            that.trigger("mousewheel", event);
        });

        this.DOMNode.bind("contextmenu", function (event) {
            that.trigger("contextmenu", event);
        });

        this.DOMNode.delegate("circle", "click", function (event) {
            that.trigger("circle_click", event);
        });

        this.DOMNode.delegate("circle", "mouseover", function (event) {
            that.trigger("circle_mouseover", event);
        });

        this.DOMNode.delegate("circle", "mouseout", function (event) {
            that.trigger("circle_mouseout", event);
        });

        //console.log(this.canvas);
    };


    Flow.prototype.getLinkPath = function (fx, fy, tx, ty) {
        var conf = this.defaults;

        var c1x = fx + (tx - fx) / 1.5;
        var c1y = fy;
        var c2x = tx - (tx - fx) / 4;
        var c2y = ty - (ty - fy) / 2;

        var link_path = [["M", fx, fy],
            ["S", c1x, c1y, tx, ty]];

        return link_path;
    };

    Flow.prototype.generatePaths = function () {
        var canvas = this.canvas;
        var source = this.source;
        var conf = this.defaults;
        var radius = conf.radius;
        //canvas.clear();
        // var font = this.getFont();
        var font_family = '微软雅黑';
        var font_size = 8;
        var depth = this.depth;
        var crilceData = this.circleData;
        var l = crilceData.length;
        var getLinkPath = this.getLinkPath;

        canvas.path().attr({stroke:  "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[1].x, crilceData[1].y)});
        canvas.path().attr({stroke:  "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[0].x, crilceData[0].y, crilceData[2].x, crilceData[2].y)});
        canvas.path().attr({stroke:  "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[3].x, crilceData[3].y)});
        canvas.path().attr({stroke:  "#cdcdcd", "stroke-width": 2}).attr({path: getLinkPath(crilceData[2].x, crilceData[2].y, crilceData[4].x, crilceData[4].y)});

        var i, d;
        var thisRadius;
        var thisColor;
        var titelPath = [];
        var valuePath = [];
        for (i = 0 ; i < l ; i++) {
            d = crilceData[i];
            thisRadius = Math.max(27, d.radius);
            if (i === 1) {
                thisColor = "#b4e481";
            } else if (i === 4) {
                thisColor = "#cd3a19";
            } else {
                thisColor = "#ffe79d";
            }
            canvas.circle(d.x, d.y, thisRadius)
            .attr({"stroke": "none", fill: thisColor});
            titelPath.push(canvas.text(0, 0, d.name).attr({'font-size': 12}));
            if (i < l - 2) {
                titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5));
            } else {
                titelPath[i].transform("t" + d.x + "," + (d.y - thisRadius - titelPath[i].getBBox().height/2 - 5)).attr({"text-anchor": "start"});
            }

            if (i < l - 1) {
                valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({'font-size': 12}));
            } else {
                valuePath.push(canvas.text(d.x, d.y, d.value + "人").attr({fill: "#ffffff", 'font-size': 12}));
            }
        }

        var n = 0;

        var node;
        var num = 0;

        var nodes = canvas.set();
        var path = [];
        var textpath = [];
        
        var tree = this;
    };

    Flow.prototype.render = function (options) {
        var st = new Date().getTime();
        this.canvas.clear();
        this.setOptions(options);
        this.layout();
        var st2 = new Date().getTime();
        console.log(st2 - st);

        this.generatePaths();
        var et = new Date().getTime();
        console.log(et - st2);
        //this.canvas.renderfix();
    };

    return Flow;
});

charts/force: API索引


Force

构造函数

声明 Force Force
参数 node(Object) 表示在html的哪个容器中绘制该组件
参数 options(Object) 为用户自定义的组件的属性,比如画布大小
代码
var Force = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Force";
      this.node = this.checkContainer(node);
      this.net = {};
      this.linkValeMin = 0;
      this.linkValeMax = 1;
      this.nodeValueMin = 0;
      this.nodeValueMax = 1;
      this.clicked = false;
      this.clickedNum = -1;
      this.legendClicked = false;

      // Properties
      this.font = {};

节点id

代码
this.dimension.nodeId = {
        type: "number",
        required: true,
        index: 0,
      };

节点名称

代码
this.dimension.nodeName = {
        type: "string",
        required: true,
        index: 1
      };

节点值

代码
this.dimension.nodeValue = {
        type: "number",
        required: true,
        index: 2
      };

边源头节点

代码
this.dimension.linkSource = {
        type: "number",
        required: true,
        index: 3
      };

边指向节点

代码
this.dimension.linkTarget = {
        type: "number",
        required: true,
        index: 4
      };

边值

代码
this.dimension.linkValue = {
        type: "number",
        required: true,
        index: 5
      };
      // Canvas
      this.defaults.legend = true;
      this.defaults.width = 500;
      this.defaults.height = 500;
      this.defaults.linkLength = 50;
      this.defaults.linkWidth = 2;
      this.defaults.classNum = 6;
      this.defaults.forceValue = 10;
      this.defaults.iterate = 100;
      this.defaults.browserName = navigator.appName;

      this.setOptions(options);
      this.defaults.charge = -(this.defaults.width + this.defaults.height) / this.defaults.forceValue;
      this.legendArea = [20, (this.defaults.height - 20 - this.defaults.classNum * 20), 200, 220];
      if (this.defaults.legend) {
        this.xOffset = this.legendArea[2];
      } else {
        this.xOffset = 0;
      }

      this.createCanvas();
    }
  });

setSource

Set CSV content to force-directed net

方法 Force.prototype.setSource() setSource
参数 table(Array) the csv table to be rendered
代码
Force.prototype.setSource = function (table, map) {
    map = this.map(map);
    //this.net = json;
    if (table[0][0] === "node") {
      table = table.slice(1);
    }
    var nData = [];
    var lData = [];
    var isNode = true;
    var nodeNum;
    var that = this;
    table.forEach(function (d, i) {
      var value;
      if (isNode) {
        if (d[map.nodeId] === "link") {
          isNode = false;
          nodeNum = i + 1;
        } else {
          if (d[map.nodeId] === "") {
            throw new Error("ID can not be empty(line:" + (i + 1) + ").");
          }
          value = that._toNum(d[map.nodeValue]);
          nData[i] = {
            name: d[map.nodeName],
            nodeValue: value
          };
          if (i === 0) {
            that.nodeValueMin = value;
            that.nodeValueMax = value;
          }
          that.nodeValueMin = (value < that.nodeValueMin) ? value : that.nodeValueMin;
          that.nodeValueMax = (value > that.nodeValueMax) ? value : that.nodeValueMax;
        }
      } else {
        if (d[map.linkSource - 3] === "") {
          throw new Error("Source can not be empty(line:" + (i + 1) + ").");
        }
        if (d[map.linkTarget - 3] === "") {
          throw new Error("Target can not be empty(line:" + (i + 1) + ").");
        }
        value = that._toNum(d[map.linkValue - 3]);
        lData[i - nodeNum] = {
          source: that._toNum(d[map.linkSource - 3]),
          target: that._toNum(d[map.linkTarget - 3]),
          value: that._toNum(d[map.linkValue - 3])
        };
        if (i === nodeNum) {
          that.linkValueMin = value;
          that.linkValueMax = value;
        }
        that.linkValueMin = (value < that.linkValueMin) ? value : that.linkValueMin;
        that.linkValueMax = (value > that.linkValueMax) ? value : that.linkValueMax;
      }
    });
    this.net.nodes = nData;
    this.net.links = lData;
    this.nodeValueMax++;
    this.linkValueMax++;
  };

createCanvas

创建画布

方法 Force.prototype.createCanvas() createCanvas
代码
Force.prototype.createCanvas = function () {
    var conf = this.defaults;
    this.canvas = new Raphael(this.node, conf.width, conf.height);
    //var c = this.canvas.circle(50, 50, 40);
  };

getColor

获取节点颜色

方法 Force.prototype.getColor() getColor
参数 i(Number) 元素类别编号
返回 String 返回颜色值
代码
Force.prototype.getColor = function (i) {
    var color = DataV.getColor(this.classNum);
    //var k = color.length * (i - this.nodeValueMin-0.1) / (this.nodeValueMax - this.nodeValueMin);
    //if (k < 0) k = 0;
    return color[i % color.length][0];
  };

getRadius

获取节点的半径

方法 Force.prototype.getRadius() getRadius
参数 value(Number) 元素对应的数据值
返回 Number 返回半径值
代码
Force.prototype.getRadius = function (value) {
    var conf = this.defaults;
    return 16.0 * (value - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin) + 8;
  };

getOpacity

获取节点透明度

方法 Force.prototype.getOpacity() getOpacity
参数 value(Number) 元素类别编号
返回 Number 返回透明度值
代码
Force.prototype.getOpacity = function (value) {
    return 0.083 * (value - this.linkValueMin) / (this.linkValueMax - this.linkValueMin) + 0.078;
  };

update

update the layout by modify the attributes of nodes and links

方法 Force.prototype.update() update
代码
Force.prototype.update = function () {
    var that = this;
    var conf = this.defaults;
    var canvas = this.canvas;

    this.nodes = this.canvas.set();
    this.links = this.canvas.set();
    var nodes = this.nodes;
    var links = this.links;
    var i, j, temp;
    this.force.charge(conf.charge).nodes(this.net.nodes).links(this.net.links).start();

    var nodesData = this.net.nodes;
    var linksData = this.net.links;
    var nodesNum = nodesData.length;
    var linksNum = linksData.length;
    var connectMatrix = [];
    var linkMatrix = [];
    conf.iterate = (nodesNum + linksNum) * 2;

    var onMouseClick = function () {
      that.legendClicked = false;
      that.underBn.forEach(function (d) {
        d.hide();
        d.data('clicked', false);
      });
      that.clicked = true;
      if (!this.data('clicked')) {
        if (conf.browserName !== "Microsoft Internet Explorer") {
          that.force.linkDistance(conf.linkLength * 2).charge(conf.charge * 2).start();
        }
        that.nodes.forEach(function (d) {
          d.data('rect').hide();
          d.data('text').hide();
          d.attr({
            "opacity": 0.2
          });
          d.data('clicked', false);
          d.data('showText', false);
        });
        that.links.forEach(function (d) {
          d.attr({
            'stroke-opacity': 0.0
          });
        });
        that.clickedNum = this.data('index');
        this.data('clicked', true);
        this.data("link").forEach(function (d) {
          d.attr({
            "stroke-opacity": d.data('opacity')
          });
        });
        this.data("node").forEach(function (d) {
          d.attr({
            "opacity": 0.9
          });
          d.data('showText', true);
        });
        that.underBn[this.data('colorType')].data('clicked', true).attr('opacity', 1).show();
      } else {
        that.clicked = false;
        if (conf.browserName !== "Microsoft Internet Explorer") {
          that.force.linkDistance(conf.linkLength).charge(conf.charge).start();
        }
        nodes.forEach(function (d) {
          d.attr({
            "opacity": 0.9
          });
          if (d.data('big')) {
            d.data('showText', true);
          } else {
            d.data('rect').hide();
            d.data('text').hide();
            d.data('showText', false);
          }
        });
        links.forEach(function (d) {
          d.attr({
            'stroke-opacity': d.data('opacity')
          });
        });
        this.data('clicked', false);
        that.underBn[this.data('colorType')].hide();
      }
    };

    var onCanvasClick = function () {
      that.legendClicked = false;
      that.underBn.forEach(function (d) {
        d.hide();
        d.data('clicked', false);
      });
      that.clicked = false;
      if (conf.browserName !== "Microsoft Internet Explorer") {
        that.force.linkDistance(conf.linkLength).charge(conf.charge).start();
      } else {
        that.force.resume();
      }
      nodes.forEach(function (d) {
        d.attr({
          "opacity": 0.9
        });
        if (d.data('big')) {
          d.data('showText', true);
        } else {
          d.data('rect').hide();
          d.data('text').hide();
          d.data('showText', false);
        }
      });
      links.forEach(function (d) {
        d.attr({
          'stroke-opacity': d.data('opacity')
        });
      });
    };

    var topValue = [];
    var topId = [];
    var topNum = 10;
    if (nodesNum < 10) {
      topNum = nodesNum;
    }
    for (i = 0; i < topNum; i++) {
      topValue[i] = nodesData[i].nodeValue;
      topId[i] = i;
    }
    for (i = 0; i < topNum; i++) {
      for (j = 1; j < topNum - i; j++) {
        if (topValue[j] < topValue[j - 1]) {
          temp = topValue[j];
          topValue[j] = topValue[j - 1];
          topValue[j - 1] = temp;
          temp = topId[j];
          topId[j] = topId[j - 1];
          topId[j - 1] = temp;
        }
      }
    }
    //rapheal绘制部分
    for (i = 0; i < nodesNum; i++) {
      nodesData[i].x = (conf.width + this.xOffset) / 2;
      nodesData[i].y = conf.height / 2;
      var n = nodesData[i];
      var k = Math.floor(conf.classNum * (n.nodeValue - this.nodeValueMin) / (this.nodeValueMax - this.nodeValueMin));
      if (k >= conf.classNum) k = conf.classNum - 1;
      var radius = this.getRadius(n.nodeValue);
      var cnode = canvas.circle(n.x, n.y, radius).attr({
        fill: this.getColor(k),
        'stroke': "#ffffff",
        'opacity': 0.9
        //title: n.name
      });
      var nodeText = canvas.text(n.x, n.y - radius, n.name).attr({
        'opacity': 1,
        //'font-family': "微软雅黑",
        'font': '12px Verdana'
      }).hide();
      var nodeRect = canvas.rect(n.x, n.y, nodeText.getBBox().width, nodeText.getBBox().height, 2).attr({
        'fill': "#000000",
        'stroke-opacity': 0,
        'fill-opacity': 0.1
      }).hide();
      cnode.data('r', radius);
      cnode.data("name", n.name);
      cnode.data('text', nodeText);
      cnode.data('rect', nodeRect);
      cnode.data('colorType', k);
      cnode.data('clicked', false);
      cnode.data('index', i);
      cnode.data('big', false);

      if (i >= topNum && topValue[0] < nodesData[i].nodeValue) {
        topValue[0] = nodesData[i].nodeValue;
        topId[0] = i;
        for (j = 1; j < topNum; j++) {
          if (topValue[j] < topValue[j - 1]) {
            temp = topValue[j];
            topValue[j] = topValue[j - 1];
            topValue[j - 1] = temp;
            temp = topId[j];
            topId[j] = topId[j - 1];
            topId[j - 1] = temp;
          } else {
            break;
          }
        }
      }

      nodes.push(cnode);
      connectMatrix[i] = [];
      linkMatrix[i] = [];
      connectMatrix[i].push(nodes[i]);
    }

    for (i = 0; i < topNum; i++) {
      nodes[topId[i]].data("big", true);
    }


    for (i = 0; i < linksNum; i++) {
      var l = linksData[i];
      var clink = canvas.path("M" + l.source.x + "," + l.source.y + "L" + l.target.x + "," + l.target.y).attr({
        'stroke-width': conf.linkWidth,
        'stroke-opacity': this.getOpacity(l.value)
      }).toBack();
      clink.data('opacity', this.getOpacity(l.value));
      links.push(clink);
      connectMatrix[l.source.index].push(nodes[l.target.index]);
      connectMatrix[l.target.index].push(nodes[l.source.index]);
      linkMatrix[l.source.index].push(links[i]);
      linkMatrix[l.target.index].push(links[i]);
    }

    var background = canvas.rect(0, 0, conf.width, conf.height).attr({
      'fill': '#ffffff',
      'stroke-opacity': 0
    }).toBack();
    background.click(onCanvasClick);

    nodes.forEach(function (d, i) {
      d.data("node", connectMatrix[i]);
      d.data("link", linkMatrix[i]);
      if (d.data('big')) {
        d.data('showText', true);
      } else {
        d.data('showText', false);
      }
      d.drag(function (dx, dy) {
        d.data('x', this.ox + dx);
        d.data('y', this.oy + dy);
      }, function () {
        that.force.resume();
        this.ox = this.attr("cx");
        this.oy = this.attr("cy");
        d.data('x', this.ox);
        d.data('y', this.oy);
        d.data('drag', true);
      }, function () {
        that.force.resume();
        d.data('drag', false);
      });
      d.click(onMouseClick); //.mouseup(onmouseup);
      d.mouseover(function () {
        if (conf.browserName !== "Microsoft Internet Explorer") {
          that.force.resume();
        }
        this.attr({
          'r': d.data('r') + 5
        });
        if (!this.data('showText')) {
          //this.attr('title', "");
          this.data('showText', true);
          this.data('hover', true);
        }
        if (!that.underBn[this.data('colorType')].data('clicked')) {
          that.underBn[this.data('colorType')].attr('opacity', 0.5).show();
        }
      }).mouseout(function () {
        this.attr({
          'r': d.data('r')
        });
        //this.attr('title', this.data('name'));
        if (this.data('hover') && !this.data('clicked')) {
          d.data('rect').hide();
          d.data('text').hide();
          this.data('showText', false);
          this.data('hover', false);
        }
        if (!that.underBn[this.data('colorType')].data('clicked')) {
          that.underBn[this.data('colorType')].hide();
        }
      });
    });

  };

legend

绘制图例

方法 Force.prototype.legend() legend
代码
Force.prototype.legend = function () {
    var that = this;
    var conf = this.defaults;
    var paper = this.canvas;
    var legendArea = this.legendArea;
    var rectBn = paper.set();
    this.underBn = [];
    var underBn = this.underBn;
    for (i = 0; i <= conf.classNum - 1; i++) {
      //底框
      underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "stroke": "none",
        'opacity': 1
      }).data('clicked', false).hide());
      //色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": that.getColor(i),
        "stroke": "none"
      });
      //文字
      var min = Math.floor(this.nodeValueMin + i * (this.nodeValueMax - this.nodeValueMin) / conf.classNum);
      var max = Math.floor(min + (this.nodeValueMax - this.nodeValueMin) / conf.classNum);
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, min + " ~ " + max).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12
      }).attr({
        "text-anchor": "start"
      });
      //选框
      rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
        //"r": 3
      })).data("clicked", 0);
    }
    rectBn.forEach(function (d, i) {
      d.mouseover(function () {
        if (!underBn[i].data('clicked')) {
          underBn[i].attr('opacity', 0.5);
          underBn[i].show();
        }
      }).mouseout(function () {
        if (!underBn[i].data('clicked')) {
          underBn[i].hide();
        }
      });
      d.click(function () {
        that.clicked = false;
        if (conf.browserName !== "Microsoft Internet Explorer") {
          that.force.linkDistance(conf.linkLength).charge(conf.charge).start();
        }
        for (j = 0; j < underBn.length; j++) {
          if (j === i) {
            underBn[j].show();
          } else {
            underBn[j].hide();
          }
        }
        rectBn.forEach(function (eachBn) {
          if (eachBn !== d) {
            eachBn.data("clicked", 0);
          }
        });
        if (d.data("clicked") === 0) {
          that.legendClicked = true;
          underBn[i].attr('opacity', 1);
          underBn[i].data('clicked', true);
          underBn[i].show();
          that.nodes.forEach(function (d) {
            if (d.data('colorType') === i) {
              d.attr({
                "opacity": 0.9
              });
              d.data('showText', true);
            } else {
              d.attr({
                "opacity": 0.2
              });
              d.data('rect').hide();
              d.data('text').hide();
              d.data('showText', false);
            }
          });
          that.links.forEach(function (d) {
            d.attr({
              "stroke-opacity": 0
            });
          });
          d.data("clicked", 1);
        } else if (d.data("clicked") === 1) {
          that.legendClicked = false;
          underBn[i].data('clicked', false);
          underBn[i].hide();
          d.data("clicked", 0);
          that.nodes.forEach(function (d) {
            d.attr({
              "opacity": 0.9
            });
            if (d.data('big')) {
              d.data('showText', true);
            } else {
              d.data('rect').hide();
              d.data('text').hide();
              d.data('showText', false);
            }
          });
          that.links.forEach(function (d) {
            d.attr({
              "stroke-opacity": d.data('opacity')
            });
          });
        }
      });
    });
  };

layout

create the force-direct layout

方法 Force.prototype.layout() layout
代码
Force.prototype.layout = function () {
    var conf = this.defaults;
    this.force = d3.layout.force().linkDistance(conf.linkLength).size([conf.width + this.xOffset, conf.height]).theta(1.5);
  };

animate

update the force-direct layout animation

方法 Force.prototype.animate() animate
代码
Force.prototype.animate = function () {
    var conf = this.defaults;
    var nodes = this.nodes;
    var links = this.links;
    var tick = 0;
    var that = this;

    var nodesData = this.net.nodes;
    var linksData = this.net.links;

    this.force.on("tick", function () {
      if (conf.browserName !== "Microsoft Internet Explorer" || tick > conf.iterate) {
        if (tick % 2 === 0) {
          nodes.forEach(function (d, i) {
            var margin = d.data('r');
            var nd = nodesData[i];
            if (d.data('drag')) {
              nd.x = d.data('x');
              nd.y = d.data('y');
            }
            nd.x = (nd.x < margin + that.xOffset) ? (margin + that.xOffset) : nd.x;
            nd.x = (nd.x > conf.width - margin) ? conf.width - margin : nd.x;
            nd.y = (nd.y < margin) ? margin : nd.y;
            nd.y = (nd.y > conf.height - margin) ? conf.height - margin : nd.y;
            var bx = d.data('text').getBBox().width / 2;
            var by = d.data('text').getBBox().height / 2;

            if (that.clicked) {
              var mx = nodesData[that.clickedNum].x;
              var my = nodesData[that.clickedNum].y;
              var tx, ty;
              if (d.data('clicked')) {

                if (conf.browserName !== "Microsoft Internet Explorer") {
                  nd.x = (conf.width + that.xOffset) / 2;
                  nd.y = conf.height / 2;
                }
                d.data('rect').attr({
                  'x': nd.x - bx,
                  'y': nd.y + d.data('r')
                });
                d.data('text').attr({
                  'x': nd.x,
                  'y': nd.y + by + d.data('r')
                });
                d.data('rect').show();
                d.data('text').show();
              } else if (d.data('showText')) {
                var lx = (nd.x - mx);
                var ly = (nd.y - my);
                var length = Math.sqrt(lx * lx + ly * ly);
                tx = nd.x + bx * lx / length;
                ty = nd.y + by * ly / length;
                tx = (nd.x < mx) ? tx - d.data('r') : tx + d.data('r');
                ty = (nd.y < my) ? ty - d.data('r') : ty + d.data('r');
                tx = (tx < margin + that.xOffset) ? (margin + that.xOffset) : tx;
                tx = (tx > conf.width - margin) ? conf.width - margin : tx;
                ty = (ty < margin) ? margin : ty;
                ty = (ty > conf.height - margin) ? conf.height - margin : ty;
                d.data('rect').attr({
                  'x': tx - bx,
                  'y': ty - by
                });
                d.data('text').attr({
                  'x': tx,
                  'y': ty
                });
                d.data('rect').show();
                d.data('text').show();
              }
            } else if (d.data('showText')) {
              d.data('rect').attr({
                'x': nd.x - bx,
                'y': nd.y - by + 4 + d.data('r')
              });
              d.data('text').attr({
                'x': nd.x,
                'y': nd.y + 4 + d.data('r')
              });
              try {
                d.data('rect').show();
                d.data('text').show();
              } catch (e) {}

            }
            d.attr({
              'cx': nd.x,
              'cy': nd.y
            });
          });
          links.forEach(function (d, i) {
            d.attr('path', "M" + linksData[i].source.x + "," + linksData[i].source.y + "L" + linksData[i].target.x + "," + linksData[i].target.y);
          });
        }
      }++tick;
    });
  };

render

render the force-directed net on the canvas and keep updating

方法 Force.prototype.render() render
参数 options(Object) user options
代码
Force.prototype.render = function (options) {
    this.setOptions(options);
    this.canvas.clear();
    this.layout();
    if (this.defaults.legend) {
      this.legend();
    }
    this.update();
    this.animate();
  };

  return Force;
});

charts/hover_line: API索引


charts/legend: API索引


类型纬度

代码
this.dimension.type = {
          type: "string",
          required: true,
          index: 1
      };

时间纬度

代码
this.dimension.x = {
          type: "string",
          required: true,
          index: 0
      };

值纬度

代码
this.dimension.value = {
          type: "number",
          required: true,
          index: 2
      };
      this.defaults.highlightStyle = {"backgroundColor": "#dddddd"};
      this.defaults.lowlightStyle = {"backgroundColor": "white"};
      this.formatLabel = function (text) {
        return text;
      };
      this.init();
    }
  });

  Legend.prototype.init = function () {
    var conf = this.defaults;
    this.legend = $("<div></div>");
    this.legend.css({
      "overflow": "hidden",
      "padding": "10px 0 10px 0",
      "width": conf.leftLegendWidth - this.legendIndent + "px"
    });
    this.node.append(this.legend);
    this.initEvents();
  };

  Legend.prototype.setSource = function (source, map) {
    map = this.map(map);
    var groupedByType = _.groupBy(source, map.type);
    var sorted = _.sortBy(groupedByType, function (group) {
      return -DataV.sum(group, map.value);
    });
    //this.list = _.keys();
    this.list = sorted.map(function (d) { return d[0][map.type]; });
  };

  Legend.prototype.initEvents = function () {
    var that = this;
    that.on('hoverIn', function (index) {
      that.highlight(index);
    }).on('hoverOut', function (index) {
      that.lowlight(index);
    }).on('level_changed', function (start, end, needMore) {
      that.render(start, end, needMore);
    });
  };

  Legend.prototype.render = function (level) {
    var conf = this.defaults;
    conf.level = level || 0;
    var that = this;
    this.clear();
    this.legends = [];
    var colorFunc = conf.colorFunc,
      hoverIn = function (e) {
        var index = e.data.index;
        that.fire('hoverIn', index);
        this.highlight(index);
      },
      hoverOut = function (e) {
        var index = e.data.index;
        that.fire('hoverOut', index);
        this.lowlight(index);
      };
    var ul = $("<ul></ul>").css({
      "margin": "0 0 0 10px",
      "paddingLeft": 0
    });

    var selected;
    if (!conf.more) {
      selected = this.list.slice(0);
    } else {
      selected = DataV.more(this.list, conf.level, conf.max, function () {
        return conf.moreLabel;
      });
    }

    var formatLabel = conf.formatLabel || this.formatLabel;
    for (var i = 0, l = selected.length; i < l; i++) {
      var color = colorFunc(i);
      var li = $('<li style="color: ' + color + '"><span style="color: black" title="' + selected[i] + '">' + formatLabel(selected[i]) + '</span></li>');
      li.mouseenter({"index": i}, $.proxy(hoverIn, this)).mouseleave({"index": i}, $.proxy(hoverOut, this));
      ul.append(li);
      this.legends.push(li);
    }

    ul.find("li").css({
      "list-style-type": "square",
      "list-style-position": "inside",
      "white-space": "nowrap",
      "padding-left": 5
    });
    this.legend.append(ul);
  };

  Legend.prototype.highlight = function (index) {
    if (typeof index !== 'undefined') {
      this.legends[index].css(this.defaults.highlightStyle);
    }
  };
  Legend.prototype.lowlight = function (index) {
    if (typeof index !== 'undefined') {
      this.legends[index].css(this.defaults.lowlightStyle);
    }
  };
  Legend.prototype.clear = function () {
    this.legend.empty();
  };

  var TopLegend = DataV.extend(DataV.Widget, {
    initialize: function (container) {
      this.node = $(container);
      this.defaults.r0 = 5;
      this.defaults.r1 = 7;
    }
  });
  TopLegend.prototype.init = function () {
    var conf = this.owner.defaults;
    this.legend = $("<div></div>").css({
      "width": conf.width,
      "backgroundColor": "#f4f4f4"
    });
    this.node.append(this.legend);
  };

  TopLegend.prototype.render = function () {
    this.init();
    var that = this;
    var owner = this.owner,
      conf = owner.defaults;
    var r0 = this.defaults.r0;
    this.legends = [];
    this.paper = new Raphael(this.legend[0], conf.width, 50);
    var paper = this.paper;

    var m = [10, 20, 10, 20],
      left = m[3],
      top = m[0],
      lineHeight = 25,
      legendInterval = 10,
      lineWidth = conf.width,
      circleW = 18,
      colorFunc = owner.getColor();
    var hoverIn = function () {
      var index = this.data("index");
      that.owner.fire('hoverIn', index);
      that.highlight(index);
    };
    var hoverOut = function () {
      var index = this.data("index");
      that.owner.fire('hoverOut', index);
      that.lowlight(index);
    };

    that.on('hoverIn', function (index) {
      that.highlight(index);
    }).on('hoverOut', function (index) {
      that.lowlight(index);
    });

    var colorArray = owner.displayData.allInfos.map(function (item, index) {
      return colorFunc(index);
    });

    for (var i = 0, l = owner.displayData.allInfos.length; i < l; i++) {
      var text = paper.text(0, 0, owner.getDisplayRowInfo(i).rowName).attr({
        "font-size": conf.fontSize,
        "text-anchor": "start",
        "font-family": "微软雅黑"
      });
      var box = text.getBBox();
      if (left + circleW + box.width >= lineWidth - m[1]) {
        //new line
        left = m[3];
        top += lineHeight;
      }
      var color = colorArray[owner.displayData.rowIndex[i].slicedData];
      var circle = paper.circle(left + circleW / 2, top + lineHeight / 2, r0)
        .attr({
          "stroke": "none",
          "fill": color
        })
        .data("index", i)
        .hover(hoverIn, hoverOut);
      text.transform("t" + (left + circleW) + "," + (top + lineHeight / 2));
      paper.rect(left + circleW, top, box.width, lineHeight).attr({
        "stroke": "none",
        "fill": "#000",
        "opacity": 0
      })
      .data("index", i)
      .hover(hoverIn, hoverOut);

      that.legends.push({"text": text, "circle": circle});

      left += legendInterval + circleW + box.width;
    }
    paper.setSize(lineWidth, top + lineHeight + m[2]);
  };

  TopLegend.prototype.highlight = function (index) {
    this.legends[index].circle.animate({"r": this.defaults.r1, "opacity": 0.5}, 300);
  };
  TopLegend.prototype.lowlight = function (index) {
    this.legends[index].circle.animate({"r": this.defaults.r0, "opacity": 1}, 300);
  };

  return {
    Legend: Legend,
    TopLegend: TopLegend
  };
});

charts/line: API索引


lobal Raphael, _, $

代码
;(function (name, definition) {
  if (typeof define === 'function') {
    define(definition);
  } else {
    this[name] = definition(function (id) { return this[id];});
  }
})('Line', function (require) {
  var DataV = require('DataV');
  var theme = DataV.Themes;

Line

Line构造函数,继承自Chart

Options

  • width 数字,画布宽度,默认为960,表示图片高960px
  • height 数字,画布高度,默认为500
  • margin 数组,这个折线图在画布中四周的留白,长度为4,分别代表[top, right, bottom, left], 默认值为[10, 40, 40, 40]
  • title 字符串,一级标题, 默认值为null
  • subtitle 字符串,副标题, 默认值为null
  • `imagePath 字符串,折线图提供背景图片加载路径,默认值为null
  • `clickMode 布尔值,是否使用默认的点击事件,默认点击事件为点击折线即选中当前折线,点击空白部分取消选中,默认值为true
  • `hoverMode 布尔值,是否使用默认的悬停事件,默认悬停事件为当前悬停所在折线高亮,如果在节点上悬停则节点高亮,同一X维度节点也高亮, 默认值为true
  • `nodeMode 布尔值,是否需要显示节点,默认值为true
  • `lineSize 数字,折线粗细,默认值为2
  • `gridSize 数字,网格线粗细,默认值为1
  • `nodeRadius 数字,节点圆形半径,默认值为2
  • `hasXAxis 布尔值,是否需要绘制x坐标轴,默认值为true
  • `hasYAxis 布尔值,是否需要绘制y坐标轴,默认值为true
  • `xAxisTick 数字,x轴刻度的跨度,默认值为10
  • `yAxisTick 数字,y轴刻度的跨度,默认值为10
  • `xAxisOrient 字符串,x坐标轴位置,可选值为"top"和"bottom",默认值为"bottom"
  • `yAxisOrient 字符串,x坐标轴位置,可选值为"left"和"right",默认值为"left"
  • `xAxisPadding 数字,x轴与x轴刻度值说明文字的距离,默认值为10
  • `yAxisPadding 数字,y轴与y轴刻度值说明文字的距离,默认值为10
  • `xAxisFontSize 数字,x轴说明文字字号,默认值为10
  • `yAxisFontSize 数字,y轴说明文字字号,默认值为10
  • `xAxisStartDx 数字,x轴刻度起始位置与坐标轴最左端水平距离,默认值为20
  • `yAxisStartDy 数字,y轴刻度起始位置与坐标轴最下端垂直距离,默认值为10
  • `xAxisEndDx 数字,x轴刻度起始位置与坐标轴最右端水平距离,默认值为10
  • `yAxisEndDy 数字,y轴刻度起始位置与坐标轴最上端水平距离,默认值为10
  • `textLean 布尔值,是否将x轴说明文字倾斜摆放,默认值为false
  • `hasXGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true
  • `hasYGrid 布尔值,是否需要绘制与x坐标轴平行的网格线,默认值为true
  • `backgroundColor 字符串,折线图背景填充色,默认值为"#fff"
  • `titleColor 字符串,标题文字颜色,默认值为"#000"
  • `subtitleColor 字符串,副标题文字颜色,默认值为"#000"
  • `AxisColor 字符串,坐标轴填充颜色,默认值为"#000"
  • `gridColor 字符串,网格线填充颜色,默认值为"#000"
  • `unchosen0pacity 数字,当有折线被选中时,其他淡出折线的透明度,默认值为0.15
  • `grid0pacity 数字,网格线透明度,默认值为0.1
  • `chosenGrid0pacity 数字,高亮网格线透明度,默认值为0.5

Create line in a dom node with id "chart", width is 500px; height is 600px;

Examples

var line = new Line("chart", {"width": 500, "height": 600});
声明 Line Line
参数 node(Mix) The dom node or dom node Id
参数 options(Object) options json object for determin line style.
代码
var Line = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Line";
      this.node = this.checkContainer(node);

线纬度

代码
this.dimension.line = {
          type: "string",
          required: true,
          index: 0
      };

值纬度

代码
this.dimension.x = {
          type: "string",
          required: true,
          index: 1
      };

值纬度

代码
this.dimension.value = {
          type: "number",
          required: true,
          index: 2
      };

      this.defaults.width = 960;
      this.defaults.height = 500;

      //Properties
      this.defaults.margin = [10, 40, 40, 40];
      this.defaults.title = null;
      this.defaults.subtitle = null;
      this.defaults.imagePath = null;

      this.defaults.clickMode = true;
      this.defaults.hoverMode = true;

      //绘制属性
      this.defaults.lineSize = 2;
      this.defaults.gridSize = 1;
      this.defaults.nodeMode = true;
      this.defaults.nodeRadius = 2;
      this.defaults.hasXAxis = true;
      this.defaults.hasYAxis = true;
      this.defaults.xAxisTick = 10;
      this.defaults.yAxisTick = 4;
      this.defaults.xAxisOrient = "bottom";
      this.defaults.yAxisOrient = "left";
      this.defaults.xAxisPadding = 10;
      this.defaults.yAxisPadding = 10;
      this.defaults.xAxisFontSize = 10;
      this.defaults.yAxisFontSize = 10;
      this.defaults.xAxisStartDx = 20;
      this.defaults.yAxisStartDy = 10;
      this.defaults.xAxisEndDx = 10;
      this.defaults.yAxisEndDy = 10;

      this.defaults.textLean = false;

      this.defaults.hasXGrid = true;
      this.defaults.hasYGrid = true;

      //Color
      this.defaults.backgroundColor = "#fff";
      this.defaults.titleColor = "#000";
      this.defaults.subtitleColor = "#000";
      this.defaults.AxisColor = "#000";
      this.defaults.gridColor = "#000";
      this.defaults.unchosen0pacity = 0.15;
      this.defaults.grid0pacity = 0.1;
      this.defaults.chosenGrid0pacity = 0.5;

      //设置用户指定属性
      this.setOptions(options);

      //创建画布
      this.createCanvas();
    }
  });

createCanvas

创建画布

方法 Line.prototype.createCanvas() createCanvas
代码
Line.prototype.createCanvas = function () {
    var conf = this.defaults;

    this.canvas = new Raphael(this.node, conf.width, conf.height);

    this.canvasF = this.node;

    var canvasStyle = this.canvasF.style;
    canvasStyle.position = "relative";
    this.floatTag = DataV.FloatTag()(this.canvasF);
    this.floatTag.css({
      "visibility": "hidden"
    });
  };

setSource

对原始数据进行处理

方法 Line.prototype.setSource() setSource
代码
Line.prototype.setSource = function (source, map) {
    map = this.map(map);
    var dataTable;
    if (DataV.detect(source) === 'Table_WITH_HEAD') {
      dataTable = DataV.collectionify(source);
    } else {
      dataTable = source;
    }

    var lines = _.groupBy(dataTable, map.line);
    var linesData = [];

    var max, min;
    var maxList = [], minList = [];
    this.maxLength = Math.max.apply(null, _.map(lines, function (line) {
      return line.length;
    }));

    var titles;
    _.forEach(lines, function (points, name) {
      // initialize the nodes of line
      var line = {name: name, id: name, data: [], tags: []};
      line.data = _.pluck(points, map.value);
      titles = _.pluck(points, map.x);
      linesData.push(line);
      maxList[name] = Math.max.apply(null, line.data);
      minList[name] = Math.min.apply(null, line.data);
    });

    var conf = this.defaults;
    var margin = conf.margin;
    this.xWidth = conf.width - margin[1] - margin[3];
    this.yHeight = conf.height - margin[0] - margin[2];

    this.titles = titles;
    this.linesData = linesData;
    this.max = Math.max.apply(null, _.values(maxList));
    this.min = Math.min.apply(null, _.values(minList));
    this.maxList = maxList;
    this.minList = minList;

    this.chosen = false;
    this.chosenNum = -1;
  };

getColor

获取颜色函数

方法 Line.prototype.getColor() getColor
返回 function DataV根据主题获取随机离散色函数
代码
Line.prototype.getColor = function () {
    var colorFunction = DataV.getDiscreteColor();
    return colorFunction;
  };

setXaxis

绘制X轴

方法 Line.prototype.setXaxis() setXaxis
代码
Line.prototype.setXaxis = function (titles) {
    var conf = this.defaults;
    var canvas = this.canvas;

    if (conf.hasXAxis) {
      var lineSize = conf.lineSize;
      var gridSize = conf.gridSize;
      var axisColor = conf.AxisColor;
      var gridColor = conf.gridColor;
      var grid0pacity = conf.grid0pacity;
      var xAxisPadding = conf.xAxisPadding;
      var margin = conf.margin;
      var xAxisStartDx = conf.xAxisStartDx;
      var xAxisEndDx = conf.xAxisEndDx;
      var startX = margin[3] + xAxisStartDx;
      var startY = conf.height - margin[2];
      var xWidth = this.xWidth;
      var xDataArea = xWidth - xAxisStartDx - xAxisEndDx;
      var maxLength = this.maxLength;
      var yHeight = this.yHeight;
      var fontSize = conf.xAxisFontSize;
      var textLean = conf.textLean;

      var tickLength;
      if (conf.hasXGrid) {
        tickLength = yHeight;
      } else {
        tickLength = 5;
      }

      var tick = conf.xAxisTick;
      var tickStep;
      if (tick % maxLength === 0) {
        tickStep = xDataArea / (tick - 1);
      } else {
        tickStep = xDataArea / (this.maxLength - 1);
        tick = this.maxLength;
      }

      var xAxis = canvas.set();
      var xGrid = canvas.set();
      var xAxisText = canvas.set();
      xAxis.push(canvas.path("M" + (startX - xAxisStartDx) + "," + startY + "L" + (xWidth + margin[3]) + "," + startY));

      var l = titles.length;
      for (var i = 0; i < tick; i++) {
        xGrid.push(canvas.path("M" + (startX + i * tickStep) + "," + startY + "L" + (startX + i * tickStep) + "," + (startY - tickLength)));
        if (i < l) {
          var thisText = canvas.text((startX + i * tickStep), startY + xAxisPadding, titles[i]);
          if (textLean) {
            var d = thisText.getBBox().width / 2;
            var angle = 45 / 360 * Math.PI;
            thisText.transform("r45t" + Math.cos(angle) * d + "," + Math.sin(angle) * d);
          }
          xAxisText.push(thisText);
        }
      }

      xAxis.attr({fill: axisColor, "stroke-width": lineSize});
      xGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity});
      xAxisText.attr({"font-size": fontSize});
      this.xAxis = xAxis;
      this.xGrid = xGrid;
      this.xAxisText = xAxisText;
      this.xTick = xDataArea / (maxLength - 1);
    }
  };

setYaxis

绘制Y轴

方法 Line.prototype.setYaxis() setYaxis
代码
Line.prototype.setYaxis = function () {
    var conf = this.defaults;
    var canvas = this.canvas;

    if (conf.hasYAxis) {
      var lineSize = conf.lineSize;
      var gridSize = conf.gridSize;
      var axisColor = conf.AxisColor;
      var gridColor = conf.gridColor;
      var grid0pacity = conf.grid0pacity;
      var margin = conf.margin;
      var yAxisStartDy = conf.yAxisStartDy;
      var yAxisEndDy = conf.yAxisEndDy;
      var yHeight = this.yHeight;
      var yDataArea = this.yHeight - yAxisStartDy - yAxisEndDy;
      var xWidth = this.xWidth;
      var startX = margin[3];
      var startY = margin[0] + yHeight - yAxisStartDy;
      var yAxisPadding = conf.yAxisPadding;
      var max = this.max;
      var min = this.min;
      var fontSize = conf.yAxisFontSize;

      var tick = conf.yAxisTick;
      var tickStep = yDataArea / (tick - 1);

      var d = (max - min) % (tick - 1);
      if (d !== 0) {
        max = max + (tick - 1) - d;
      }
      d = (max - min) / (tick - 1);

      var tickLength;
      if (conf.hasYGrid) {
        tickLength = xWidth;
      } else {
        tickLength = 5;
      }

      var yAxis = canvas.set();
      var yGrid = canvas.set();
      var yAxisText = canvas.set();
      yAxis.push(canvas.path("M" + startX + "," + (startY + yAxisStartDy) + "L" + startX + "," + margin[0]));

      for (var i = 0; i < tick; i++) {
        yGrid.push(canvas.path("M" + startX + "," + (startY - i * tickStep) + "L" + (startX + tickLength) + "," + (startY - i * tickStep)));
        yAxisText.push(canvas.text(startX - yAxisPadding, (startY - i * tickStep), (min + i * d)));
      }

      yAxis.attr({fill: axisColor, "stroke-width": lineSize});
      yGrid.attr({"stroke": gridColor, "stroke-width": gridSize, "stroke-opacity": grid0pacity});
      yAxisText.attr({"font-size": fontSize});
      this.yAxis = yAxis;
      this.yGrid = yGrid;
      this.yAxisText = yAxisText;
      this.yDataArea = yDataArea;

      this.yMatchNum = tickStep / d;
      this.y0 = startY;
    }
  };

setBackground

绘制背景

方法 Line.prototype.setBackground() setBackground
代码
Line.prototype.setBackground = function () {
    var conf = this.defaults;
    var canvas = this.canvas;
    var backgroundColor = conf.backgroundColor;
    var imagePath = conf.imagePath;
    var xWidth = this.xWidth;
    var yHeight = this.yHeight;
    var yAxisStartDy = conf.yAxisStartDy;
    var x0 = conf.margin[3];
    var y0 = this.y0 + yAxisStartDy;

    var rect;
    if (imagePath !== null) {
      rect = canvas.image(imagePath, x0, (y0 - yHeight), xWidth, yHeight);
    } else {
      rect = canvas.rect(x0, (y0 - yHeight), xWidth, yHeight).attr({"fill": backgroundColor,"fill-opacity": 0, "stroke": "none"});
    }

    rect.toBack();
    this.background = rect;
  };

render

渲染折线图

Examples

var line = new Line("chart");
line.setSource(source);
line.render();
方法 Line.prototype.render() render
参数 options(object) options json object for determin line style.
代码
Line.prototype.render = function () {
    this.canvas.clear();
    this.createCanvas();
    var conf = this.defaults;
    var linesData = this.linesData;
    var nodesList = [];

    this.setXaxis(this.titles);
    this.setYaxis();

    this.setBackground();

    var canvas = this.canvas;
    var nodeMode = conf.nodeMode;
    var getColor = this.getColor();
    var lineSize = conf.lineSize;
    var min = this.min;
    var xAxisStartDx = conf.xAxisStartDx;
    var xTick = this.xTick;
    var yMatchNum = this.yMatchNum;
    var x0 = conf.margin[3] + xAxisStartDx;
    var y0 = this.y0;
    var radius = conf.nodeRadius;

    var lines = canvas.set();
    linesData.forEach(function (d, i) {
      var nodeData = d.data;
      var nodes = canvas.set();
      var color = getColor(i);
      var linePath = "M";

      for (var j = 0, l = nodeData.length; j < l; j++) {
        var x = x0 + xTick * (j);
        var y = y0 - ((nodeData[j] - min) * yMatchNum);

        linePath = linePath + x + "," + y;
        
        if (j < l - 1) {
          linePath = linePath + "L";
        }

        if (nodeMode) {
          var thisNode = canvas.circle(x, y, radius * 2).attr({fill: color, "stroke": "none"});
          thisNode.data('num', j);
          thisNode.data('lineNum', i);
          thisNode.data('data', nodeData[j]);
          nodes.push(thisNode);
        }
      }

      lines.push(canvas.path(linePath).attr({"stroke": color, "stroke-width": lineSize}).data('num', i));
      if (nodeMode) {
        nodesList.push(nodes.toFront());
      }
    });

    this.lines = lines;
    this.nodesList = nodesList;

    this.interactive();
  };

interactive

添加交互选项

方法 Line.prototype.interactive() interactive
代码
Line.prototype.interactive = function () {
    var that = this;
    var conf = this.defaults;
    var hoverMode = conf.hoverMode;
    var clickMode = conf.clickMode;
    var nodeMode = conf.nodeMode;
    var chosen = this.chosen;
    var chosenNum = this.chosenNum;
    var lines = this.lines;
    var uo = conf.unchosen0pacity;
    var nodesList = this.nodesList;
    var xGrid = this.xGrid;
    var grid0pacity = conf.grid0pacity;
    var cgo = conf.chosenGrid0pacity;
    var xAxisText = this.xAxisText;
    var xAxisFontSize = conf.xAxisFontSize;

    var highLight = function (num) {
      var line = lines[num];
      lines.attr({"stroke-opacity": uo});

      nodesList.forEach(function (d) {
        d.attr({"fill-opacity": uo});
      });

      line.attr({"stroke-opacity": 1}).toFront();
      nodesList[num].attr({"fill-opacity": 1}).toFront();
    };

    var unhighLinght = function () {
      lines.forEach(function (d) {
        d.attr({"stroke-opacity": 1});
      });
      nodesList.forEach(function (d) {
        d.attr({"fill-opacity": 1});
      });
    };

    var background = this.background;

    if (clickMode){
      background.click(function () {
        if (chosen) {
          unhighLinght();
          chosen = false;
          that.chosen = chosen;
        }
      });
    }

    var floatTag = this.floatTag;
    $(this.node).append(this.floatTag);
    // if (hoverMode) {
    //  background.mouseover(function () {
        
  //    }).mouseout(function () {
  //      floatTag.css({"visibility" : "hidden"});
  //    });
    // }

    lines.forEach(function (d) {
      if (hoverMode) {
        d.mouseover(function () {
          if (!chosen) {
            highLight(d.data('num'));
          }
        }).mouseout(function () {
          if (!chosen) {
            unhighLinght();
          }
        });
      }

      if (clickMode){
        d.click(function () {
          chosenNum = d.data('num');
          highLight(chosenNum);

          chosen = true;
          that.chosen = chosen;
        });
      }
    });

    if (nodeMode){
      var radius = conf.nodeRadius;

      nodesList.forEach(function (d) {
        d.forEach (function (d) {
          if (hoverMode) {
            var nodeNum = d.data('num');
            d.mouseover(function () {
              d.animate({r: (radius + 2) * 2}, 100);
              xGrid[nodeNum].animate({'stroke-opacity': cgo}, 100);
              xAxisText[nodeNum].animate({'font-size': xAxisFontSize * 2}, 100);
              floatTag.html('<div style="text-align: center;margin:auto;color:#ffffff">' + d.data('data') + '</div>');
                floatTag.css({"visibility": "visible"});
              if (!chosen) {
                highLight(d.data("lineNum"));
                nodesList.forEach(function (d) {
                  if (nodeNum < d.length) {
                    d[nodeNum].attr({"fill-opacity": 1});
                  }
                });
              }
            }).mouseout(function () {
              d.animate({r: radius * 2}, 100);
              xGrid[nodeNum].animate({'stroke-opacity': grid0pacity}, 100);
              xAxisText[nodeNum].animate({'font-size': xAxisFontSize}, 100);
              floatTag.css({"visibility": "hidden"});
              if (!chosen) {
                unhighLinght();
              }
            });
          }

          if (clickMode){
            d.click(function () {
              chosenNum = d.data('lineNum');
              highLight(chosenNum);

              chosen = true;
              that.chosen = chosen;
            });
          }
        });
      });
    }
  };

  return Line;
});

charts/matrix: API索引


Matrix

构造函数

声明 Matrix Matrix
代码
var Matrix = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Matrix";
            this.node = this.checkContainer(node);

            // Properties
            this.font = {};

            // Canvas
            this.defaults.width = 1200;
            this.defaults.height = 1200;
            this.defaults.axisWidth = 40;

            this.setOptions(options);
            this.createCanvas();
            this.move = false;
        }
    });

    Matrix.prototype.getDataTable = function (table) {
        var title = table[0];
        table = table.slice(1);

        var titleLength = title.length;
        var tableWidth = table[0].length;
        var tableHeight = table.length;

        this.tableWidth = tableWidth;
        this.tableHeight = tableHeight;

        //for symmetric matrix
        if (tableWidth !== title.length || tableHeight !== title.length) {
            throw new Error("This matrix is not symmetric matrix!!!");
        } else {
            this.tableWidth = tableWidth;
            this.tableHeight = tableHeight;
        }

        this.title = title;
        return table;
    };

    Matrix.prototype.setSource = function (source) {
        var conf = this.defaults;

        this.source = this.getDataTable(source);
        this.hasSort = false;
        // this.source = this.remapSource(source);
    };

    Matrix.prototype.layout = function () {
        var conf = this.defaults;
        var width = conf.width;
        var height = conf.height;
        var tableWidth = this.tableWidth;
        var tableHeight = this.tableHeight;
        var axisWidth = conf.axisWidth;

        this.cellWidth = Math.min((width - axisWidth) / tableWidth, (height - axisWidth) / tableHeight);

        var startX;
        var startY;
        var bRectWidth;
        var matrixWidth;

        if (width > height) {
            startX = (width - height)/2 + axisWidth;
            startY = axisWidth;
            bRectWidth = height - axisWidth;
            matrixWidth = bRectWidth - axisWidth;
        } else if (height > width) {
            startX = axisWidth;
            startY = (height - width) + axisWidth;
            bRectWidth = width - axisWidth;
        } else {
            startX = axisWidth;
            startY = axisWidth;
            bRectWidth = width - axisWidth;
            matrixWidth = bRectWidth - axisWidth;
        }

        this.startX = startX;
        this.startY = startY;
        this.bRectWidth = bRectWidth;
        this.matrixWidth = matrixWidth;
    };

    Matrix.prototype.getColor = function (i) {
        var colorMatrix = DataV.getColor();
        var length = colorMatrix.length;
        var num = i % length;
        //var color = '#939598';
        var color = '#FFFFFF';

        if (num !== 0) {
            color = colorMatrix[num][0];
        }

        return color;
    };

    Matrix.prototype.createCanvas = function () {
        var conf = this.defaults;
        this.canvas = new Raphael(this.node, conf.width, conf.height);

        this.DOMNode = $(this.canvas.canvas);
        var that = this;
        this.DOMNode.click(function (event) {
            that.trigger("click", event);
            that.update();
        });
        this.DOMNode.dblclick(function (event) {
            that.trigger("dblclick", event);
        });

        var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll";
        this.DOMNode.bind(mousewheel, function (event) {
            that.trigger("mousewheel", event);
        });

        this.DOMNode.bind("contextmenu", function (event) {
            that.trigger("contextmenu", event);
        });

        this.DOMNode.delegate("circle", "click", function (event) {
            that.trigger("circle_click", event);
        });

        this.DOMNode.delegate("circle", "mouseover", function (event) {
            that.trigger("circle_mouseover", event);
        });

        this.DOMNode.delegate("circle", "mouseout", function (event) {
            that.trigger("circle_mouseout", event);
        });
    };

    Matrix.prototype.generatePaths = function () {
        var canvas = this.canvas;
        var source = this.source;
        var conf = this.defaults;
        var width = conf.width;
        var height = conf.height;
        var startX = this.startX;
        var startY = this.startY;
        var cellWidth = this.cellWidth;
        var tableWidth = this.tableWidth;
        var tableHeight = this.tableHeight;
        var bRectWidth = this.bRectWidth;
        var matrixWidth = this.matrixWidth;

        //canvas.clear();
        // var color = this.getColor();
        // var font = this.getFont();
        var font_family = '微软雅黑';
        var font_size = 8;

        var title = this.title;

        var row = [];
        var columnLine = [];
        var columnText = [];

        var backgroundRect = canvas.rect(startX, startY, bRectWidth, bRectWidth);
        //backgroundRect.attr({fill: "#939598", stroke: "none", "fill-opacity": 0.8});
        backgroundRect.attr({fill: "#ffffff", stroke: "none", "fill-opacity": 0.8});
        backgroundRect.toBack();

        var sort;
        if (this.hasSort) {
            sort = this.sort;
        }
        var i, j, a, b, color, rect;
        var rects = [];  //for column change move rect
        for (i = 0; i < tableHeight; i++) {
            if (!this.hasSort){
                a = i;
            } else {
                for (j = 0; j < sort.length; j++) {
                    if (sort[j] === i) {
                        a = j;
                    }
                }
            }
            var rowRect = canvas.set();
            canvas.path("M" + startX + " " + (startY + cellWidth * i) + "L" + (startX + matrixWidth + 10 + cellWidth) + " "
             + (startY + cellWidth * i)).attr({stroke: "#D1D1D1", "stroke-width": 1});
            rowRect.push(canvas.text(-20, cellWidth / 2, title[i])
                .attr({"fill": "#000000",
                    "fill-opacity": 0.7,
                    "font-family": "Verdana",
                    //"font-weight": "bold",
                    "font-size": 12}));

            for (j = 0; j < tableWidth; j++) {
                if (!this.hasSort) {
                    color = this.getColor(source[i][j]);
                } else {
                    color = this.getColor(source[i][sort[j]]);
                }
                rect = canvas.rect(cellWidth * j, 0, cellWidth, cellWidth)
                    .attr({stroke: "none", fill: color, "fill-opacity": 0.8});
                rowRect.push(rect);
                rects.push(rect);
            }

            rowRect.transform("t" + startX + ", " + (startY + cellWidth * a));
            row.push(rowRect);
        }

        canvas.path("M" + startX + " " + (startY + cellWidth * tableHeight) + "L" + (startX + matrixWidth + 10 + cellWidth) + " "
             + (startY + cellWidth * tableHeight)).attr({stroke: "#D1D1D1", "stroke-width": 1});

        for (i = 0; i < tableWidth; i++) {
            // var columnLine = canvas.set();
            // var columnText = canvas.set();
            if (!this.hasSort){
                a = i;
            } else {
                for (j = 0; j < sort.length; j++) {
                    if (sort[j] === i) {
                        a = j;
                    }
                }
            }
            columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth)
                .attr({stroke: "#D1D1D1", "stroke-width": 1})
                .transform("t" + (startX + cellWidth * a) + ", " + startY));
            columnText.push(canvas.text(cellWidth / 2, -20, title[i])
                .attr({"fill": "#000000",
                    "fill-opacity": 0.7,
                    "font-family": "Verdana",
                    //"font-weight": "bold",
                    "font-size": 12})
                .transform("t" + (startX + cellWidth * a) + ", " + startY + "r90"));
        }

        columnLine.push(canvas.path("M0 0L0 " + matrixWidth + 10 + cellWidth)
                .attr({stroke: "#D1D1D1", "stroke-width": 1})
                .transform("t" + (startX + cellWidth * tableWidth) + ", " + startY));

        this.row = row;
        this.columnText = columnText;
        this.columnLine = columnLine;
        this.rects = rects;
    };

    Matrix.prototype.getSort = function (source) {
        var sumQueue = [];
        var sort = [];
        var rowData;
        var rowLength;
        var sum;
        var means;
        var matrixD = [];
        var quareSum;
        var rowquareSum = [];

        var i, j, k;
        for (i = 0 ; i < source.length ; i++) {
            rowData = source[i];
            rowLength = rowData.length;
            sum = 0;
            quareSum = 0;

            for (j = 0 ; j < rowLength ; j++) {
                sum = sum + rowData[j];
            }

            means = sum / rowLength;
            for (j = 0 ; j < rowLength ; j++) {
                rowData[j] = rowData[j] - means;
                quareSum = quareSum + Math.pow(rowData[j], 2);
            }

            quareSum = Math.sqrt(quareSum);

            rowquareSum.push(quareSum);
            matrixD.push(rowData);
        }

        var rowI;
        var rowJ;
        var matrixR = [];

        for (i = 0 ; i < source.length ; i++) {
            matrixR[i] = [];
            for (j = 0 ; j < source.length ; j++) {
                matrixR[i][j] = 0;
            }
        }

        for (i = 0 ; i < source.length ; i++) {
            rowI = matrixD[i];
            matrixR[i][i] = source[i][i];
            for (j = i + 1 ; j < source.length ; j++) {
                sum = 0;
                rowJ = matrixD[j];
                for (k = 0; k < rowLength; k++) {
                    sum = sum + rowI[k] * rowJ[k];
                }

                sum = sum / (rowquareSum[i] * rowquareSum[j]);
                matrixR[i][j] = sum;
                matrixR[j][i] = sum;
            }
        }



        return matrixR;
    };
    
    Matrix.prototype.update = function () {
        var i, j;
        var source = [];
        for(i = 0; i < this.source.length ; i++){
            source[i] = this.source[i].concat();
        }

        var sort = [];
        for (i = 0; i < source[0].length; i++) {
            sort.push(i);
        }

        if (this.hasSort) {
            this.sort = sort;
            this.hasSort = false;
        } else {
            var getSort = this.getSort;
            var i, j;
            var pt;
            var nowSort = [];
            var iterations = 12;

            for (i = 0; i < iterations; i++) {
                source = getSort(source);
            }

            nowSort = source[0];

            var a, b;
            for (i = 1; i < sort.length; i++) {
                a = sort[i];
                for (j = i + 1; j < sort.length; j++) {
                    b = sort[j];
                    if (nowSort[a] < nowSort[b]) {
                        pt = sort[i];
                        sort[i] = sort[j];
                        sort[j] = pt;
                    }
                }
            }
            sort = [0,7,5,2,8,3,1,9,6,14,15,4,13,10,16,11,12];
            this.sort = sort;
            this.hasSort = true;
        }

        if (!this.move) {
            this.move = true;
            var rects = this.rects;
            var num;
            var startX = this.startX;
            var startY = this.startY;
            var cellWidth = this.cellWidth;

            var rowAnim;
            var columnLineAnim;
            var columnTextAnim;
            var anim;

            for (i = 0; i < sort.length; i++) {
                num = sort[i];
                // if (num != i) {
                rowAnim = Raphael.animation({transform: ["t", startX, (startY + cellWidth * i)]}, 200, "<>");
                this.row[num].animate(rowAnim.delay(100 * i));
                // }
            }

            var that = this;
            var moveEnd = function () {
                that.move = false;
            };

            for (i = 0; i < sort.length; i++) {
                num = sort[i];
                // if (num != i) {
                //columnLineAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY]}, 1000, "<>");
                columnTextAnim = Raphael.animation({transform: ["t", (startX + cellWidth * i), startY, "r", 90]},
                    200, "<>");
                //this.columnLine[num].animate(columnLineAnim.delay(500 * (i + sort.length + 1)));
                this.columnText[num].animate(columnTextAnim.delay(100 * (i + sort.length + 1)));

                for (j = 0; j < sort.length; j++) {
                    if (i === sort.length - 1 && j === sort.length - 1) {
                        anim = Raphael.animation({'x': cellWidth * i}, 200, "<>", moveEnd);
                    } else {
                        anim = Raphael.animation({'x': cellWidth * i}, 200, "<>");
                    }
                    rects[j * sort.length + num].animate(anim.delay(100 * (i + sort.length + 1)));
                }
                // }
            }
        }
    };

    Matrix.prototype.render = function (options) {
        if (!this.move) {
            this.canvas.clear();
            this.setOptions(options);
            this.layout();
            this.generatePaths();
        }
    };

    return Matrix;
});

charts/navi: API索引


charts/parallel: API索引


Parallel

构造函数

Options

  • width 数字,图片宽度,默认为750,表示图片高750px
  • height 数字,图片高度,默认为500
  • marginWidth 数组,表示图片上、右、下、左的边距,默认为 [20, 20, 20, 20]
  • backgroundAttr 对象,没有选中的线的样式,默认为{"fill": "none", "stroke": "#ccc", "stroke-opacity": 0.4}, 具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr
  • foregroundAttr 对象,被选中的线的样式,默认为{"fill": "none", "stroke": "steelblue", "stroke-opacity": 0.7}, 具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr
  • axisStyle 对象,设置坐标轴属性。3中坐标轴属性:domainAttr表示坐标轴线属性。tickAttr表示坐标轴标尺属性。tickTextAttr表示坐标轴文字属性。具体设置方式请参考Raphael手册:http://raphaeljs.com/reference.html#Element.attr
  • customEvent 函数对象,其中有3个自定义函数。brushstart 函数,表示刚开始拖选区间的事件响应,默认为空函数; brushend 函数,表示拖选结束后的事件响应,默认为空函数; brush 函数,表示拖选时的事件响应,默认为空函数; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on()函数单独设置。
声明 Parallel Parallel
参数 node(Node,String,jQuery) 容器节点,文档节点、ID或者通过jQuery查询出来的对象
代码
var Parallel = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Parallel";
            this.node = this.checkContainer(node);

            // Properties
            this.allDimensions = [];
            this.dimensions = [];
            this.dimensionType = {};
            this.dimensionDomain = {};
            this.dimensionExtent = {};

            // Canvas
            this.defaults.width = 750;
            this.defaults.height = 500;
            this.defaults.marginWidth = [20, 20, 20, 20];
            this.defaults.backgroundAttr = {"fill": "none", "stroke": "#ccc", "stroke-opacity": 0.4};
            this.defaults.foregroundAttr = {"fill": "none", "stroke": "steelblue", "stroke-opacity": 0.7};

            this.defaults.axisStyle = {
                domainAttr : {"stroke": "#000"},//坐标轴线
                tickAttr : {"stroke": "#000"},//坐标轴标尺
                tickTextAttr : {}//坐标轴文字
            }

            this.defaults.customEvent = {
                "brushstart": function () {},
                "brushend": function () {},
                "brush": function () {}
            };

            this.setOptions(options);
            this.createCanvas();
        }
    });

chooseDimensions

choose dimension

方法 Parallel.prototype.chooseDimensions() chooseDimensions
参数 dimen(array) Array of column names
代码
Parallel.prototype.chooseDimensions = function (dimen) {
        var conf = this.defaults;
        this.dimensions = [];
        for (var i = 0, l = dimen.length; i<l; i++) {
            if ($.inArray(dimen[i], this.allDimensions) !== -1) {
                this.dimensions.push(dimen[i]);
            }
        }
    };

setDimensionType

set dimension type, ordinal or quantitative

Examples

 parallel.setDimensionType({"cylinders": "ordinal", "year": "quantitative"});
方法 Parallel.prototype.setDimensionType() setDimensionType
参数 dimenType(Object) dimension type obj
代码
Parallel.prototype.setDimensionType = function (dimenType) {
        var conf = this.defaults,
            prop,
            type;
        if (dimenType) {
            for (prop in dimenType) {
                if (dimenType.hasOwnProperty(prop) && this.dimensionType[prop]) {
                    var type = dimenType[prop];
                    if (type !== "quantitative" && type !== "ordinal") {
                        throw new Error('Dimension type should be "quantitative" or "ordinal".');
                    }
                    if (this.dimensionType[prop] !== type) {
                        this.dimensionType[prop] = type;
                        this._setDefaultDimensionDomain(prop);
                    }
                }
            }
        }
    };

getDimensionExtents

get dimensions extents

方法 Parallel.prototype.getDimensionExtents() getDimensionExtents
返回 Object {key: dimension name(column name); value: dimenType("ordinal" or "quantitativ")}
代码
Parallel.prototype.getDimensionExtents = function () {
        return $.extend({}, this.dimensionExtent);
    };

setDimensionExtent

set dimension extent, if chart has been rendered, then refresh the chart;

Examples

 parallel.setDimensionExtent({
   "cylinders": ["6", "3"],
   "economy (mpg)": [35, 20]
 });
方法 Parallel.prototype.setDimensionExtent() setDimensionExtent
参数 dimenExtent(Object) {key: dimension name(column name); value: extent array;}
代码
Parallel.prototype.setDimensionExtent = function (dimenExtent) {
        var conf = this.defaults;
        var dimen, i, l, extent;
        var rebrushNeeded = false;
        var ordinalExtent = [];

        if (arguments.length === 0) {
            // clean all extent
            this.dimensionExtent = {};
        } else {
            for (prop in dimenExtent) {
                if (dimenExtent.hasOwnProperty(prop) && this.dimensionType[prop]) {
                    extent = dimenExtent[prop];
                    if (!(extent instanceof Array)) {
                        throw new Error("extent should be an array");
                    } else {
                        if (extent.length !== 2) {
                            throw new Error("extent should be an array with two items, for example: [num1, num2]");
                        } else if (this.dimensionType[prop] === "quantitative") {
                            this.dimensionExtent[prop] = extent;
                            rebrushNeeded = true;
                            if (this.brush) {
                                this.y[prop].brush.extent(extent);
                                this.y[prop].brush.refresh();
                            }
                        } else if (this.dimensionType[prop] === "ordinal") {
                            if (typeof this.dimensionDomain[prop].itemIndex[extent[0]] === 'undefined'
                                    || typeof this.dimensionDomain[prop].itemIndex[extent[1]] === 'undefined') {
                                throw new Error(prop + " does not have value: " + extent[0] + " or " + extent[1]); 
                            } else {
                                rebrushNeeded = true;
                                ordinalExtent = this._getOrdinalExtent(prop, extent);
                                this.dimensionExtent[prop] = extent;
                                if (this.brush) {
                                    this.y[prop].brush.extent(ordinalExtent);
                                    this.y[prop].brush.refresh();
                                }
                            }
                        }
                    }
                }
            }
            if (rebrushNeeded && this.brush) {
                this.brush();
            }
        }
    };

getDimensionTypes

get dimension types

方法 Parallel.prototype.getDimensionTypes() getDimensionTypes
返回 Object {key: dimension name(column name); value: dimenType("ordinal" or "quantitativ")}
代码
Parallel.prototype.getDimensionTypes = function () {
        return $.extend({}, this.dimensionType);
    };

getDimensionDomains

get dimension domain

方法 Parallel.prototype.getDimensionDomains() getDimensionDomains
返回 Object {key: dimension name(column name); value: extent array;}
代码
Parallel.prototype.getDimensionDomains = function () {
        return $.extend({}, this.dimensionDomain);
    };

setDimensionDomain

set dimension domain

Examples

 parallel.setDimensionDomain({
   "cylinders": [4, 8], //quantitative
   "year": ["75", "79", "80"] //ordinal
 });
方法 Parallel.prototype.setDimensionDomain() setDimensionDomain
参数 dimenDomain(Object) {key: dimension name(column name); value: domain array (quantitative domain is digit array whose length is 2, ordinal domain is string array whose length could be larger than 2;}
代码
Parallel.prototype.setDimensionDomain = function (dimenDomain) {
        //set default dimensionDomain, extent for quantitative type, item array for ordinal type
        var conf = this.defaults;
        var dimen, i, l, domain;

        if (arguments.length === 0) {
            for (i = 0, l = this.allDimensions.length; i < l; i++) {
                dimen = this.allDimensions[i];
                this._setDefaultDimensionDomain(dimen);
            }
        } else {
            for (prop in dimenDomain) {
                if (dimenDomain.hasOwnProperty(prop) && this.dimensionType[prop]) {
                    domain = dimenDomain[prop];
                    if (!(domain instanceof Array)) {
                        throw new Error("domain should be an array");
                    } else {
                        if (this.dimensionType[prop] === "quantitative" && domain.length !== 2) {
                            throw new Error("quantitative's domain should be an array with two items, for example: [num1, num2]");
                        }
                        if (this.dimensionType[prop] === "quantitative") {
                            this.dimensionDomain[prop] = domain;
                        } else if (this.dimensionType[prop] === "ordinal") {
                            this.dimensionDomain[prop] = this._setOrdinalDomain(domain);
                        }
                    }
                }
            }
        }
    };

on

侦听自定义事件

方法 Parallel.prototype.on() on
代码
Parallel.prototype.on = function (eventName, callback) {
        if ($.inArray(eventName, ["brushstart", "brushend", "brush"]) !== -1) {
            this.defaults.customEvent[eventName] = callback;
        }
    };

setSource

设置数据源

Examples

第一行为列名

[
 ["name", "weight", "year"],
 ["AMC", "2000", "79"],
 ["Buick", "2100", "80"]
]
方法 Parallel.prototype.setSource() setSource
参数 source(Array) 二维数组的数据源
代码
Parallel.prototype.setSource = function (source) {
        //source is 2-dimension array

        var conf = this.defaults;
        this.allDimensions = source[0];

        //by default all dimensions show
        this.dimensions = source[0];

        //this.source is array of line; key is dimension, value is line's value in that dimension
        this.source = [];
        for(var i=1, l=source.length; i<l; i++){
            var line = {},
                dimen = this.allDimensions;
            for(var j=0, ll=dimen.length; j<ll; j++){
                line[dimen[j]] = source[i][j];
            }
            this.source.push(line);
        }

        //judge dimesions type auto
        //if all number, quantitative else ordinal
        this.dimensionType = {};
        for (var i = 0, l = this.allDimensions.length; i < l; i++) {
            var type = "quantitative";
            for (var j=1, ll = source.length; j<ll; j++) {
                var d = source[j][i];
                if(d && (!DataV.isNumeric(d))){
                    type = "ordinal";
                    break;
                }
            }
            this.dimensionType[this.allDimensions[i]] = type;
        }

        this.setDimensionDomain();

    };

render

绘制图表

方法 Parallel.prototype.render() render
代码
Parallel.prototype.render = function (options) {
        this.setOptions(options);
        this.layout();
        this.generatePaths();
    };

charts/path_label: API索引


类型纬度

代码
this.dimension.type = {
        type: "string",
        required: true,
        index: 1
      };

时间纬度

代码
this.dimension.x = {
        type: "string",
        required: true,
        index: 0
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 2
      };
    }
  });

  PathLabel.prototype.render = function () {
    this.clear();
    var that = this;
    var owner = this.owner;
    var paths = owner.paths;
    var conf = this.defaults;
    var pathSource = owner.pathSource;
    var labels = [];
    var getLabelLocation = function (locArray, el) {
      var x = 0,
        y = 0,
        i;
      var ratioMargin = 0.15;
      var index = 0;
      var max = 0;
      var box = el.getBBox();
      var xInterval;
      var minTop, maxBottom;
      var showLabel = true;
      var loc;
      var height;

      xInterval = Math.ceil(box.width / (locArray[1].x - locArray[0].x) / 2);
      if (xInterval === 0) {
        xInterval = 1;
      }

      locArray.forEach(function (d, i, array) {
        var m = Math.max(ratioMargin * array.length, xInterval);
        if (i >= m && i <= array.length - m) {
          if (d.y > max) {
            minTop = d.y0 - d.y;
            maxBottom = d.y0;
            max = d.y;
            index = i;
          }
        }
      });
      for (i = index - xInterval; i <= index + xInterval; i++) {
        if (i < 0 || i >= locArray.length) {
            height = 0;
            showLabel = false;
            break;
        }
        loc = locArray[i];
        //top's y is small
        if (loc.y0 - loc.y > minTop) {
            minTop = loc.y0 - loc.y;
        }
        if (loc.y0 < maxBottom) {
            maxBottom = loc.y0;
        }
      }

      if (showLabel && maxBottom - minTop >= box.height * 0.8) {
        x = locArray[index].x;
        y = (minTop + maxBottom) / 2;
      } else {
        showLabel = false;
      }

      return {
        x: x,
        y: y,
        showLabel: showLabel
      };
    };

    var getPathLabel = this.defaults.getPathLabel || this.getPathLabel;
    var selected;
    //var values = _.values(this.groupedByType);
    var values = _.values(this.sorted);
    if (!conf.more) {
      selected = values.slice(0);
    } else {
      selected = DataV.more(values, conf.level, conf.max, function (remains) {
        var obj = {};
        obj.type = conf.moreLabel;
        obj.rank = remains[0].rank;
        obj.sum = DataV.sum(remains, "sum");
        return obj;
      });
    }
    for (var i = 0, l = paths.length; i < l; i++) {
      var path = paths[i];
      var row = selected[i];
      var obj = {
        type: row.type,
        rank: row.rank,
        sum: row.sum,
        total: this.total
      };
      var text = getPathLabel.call(this, obj);
      var label = owner.paper.text(0, 0, text).attr({
        "textAnchor": "middle",
        "fill": "white",
        "fontSize": conf.fontSize,
        "fontFamily": "微软雅黑"
      });
      label.labelLoc = getLabelLocation(pathSource[i], label);

      if (label.labelLoc.showLabel) {
        label.attr({
          "x": label.labelLoc.x,
          "y": label.labelLoc.y
        });
      } else {
        label.attr({"opacity": 0});
      }

      path.attr({"cursor": "auto"});
      label.attr({"cursor": "auto"});
      labels.push(label);
    }
    this.labels = labels;
  };

getPathLabel

生成标签的默认方法,可以通过setOption({getPathLable: function});覆盖。

Properties

  • type, 条带类型
  • rank, 条带排名
  • sum, 当前条带总值
  • total, 所有条带总值
方法 PathLabel.prototype.getPathLabel() getPathLabel
参数 obj(Object) 当前条带的对象
代码
PathLabel.prototype.getPathLabel = function (obj) {
    return obj.type + " " + "排名: 第" + obj.rank;
  };

  PathLabel.prototype.hidden = function () {
    this.labels.forEach(function (d) {
      d.hide();
    });
  };

  PathLabel.prototype.show = function () {
    this.labels.forEach(function (d) {
      if (d.labelLoc.showLabel) {
        d.show();
      }
    });
  };

  PathLabel.prototype.clear = function () {
    if (this.labels) {
      this.labels.forEach(function (d) {
        d.remove();
      });
    }
  };

  PathLabel.prototype.setSource = function (source, map) {
    var that = this;
    this.map(map);
    this.groupedByType = _.groupBy(source, this.mapping.type);
    var sorted = _.sortBy(this.groupedByType, function (group, type) {
      var sum = DataV.sum(group, that.mapping.value);
      that.groupedByType[type].sum = sum;
      that.groupedByType[type].type = type;
      return -sum;
    });
    this.sorted = sorted;
    this.types = _.keys(this.groupedByType);
    _.each(sorted, function (list, index) {
      that.groupedByType[list[0][that.mapping.type]].rank = index + 1;
    });
    this.total = DataV.sum(_.map(that.groupedByType, function (group) {
      return group.sum;
    }));
  };

  return PathLabel;
});

charts/percentage: API索引


类型纬度

代码
this.dimension.type = {
          type: "string",
          required: true,
          index: 1
      };

值纬度

代码
this.dimension.value = {
          type: "number",
          required: true,
          index: 2
      };
    }
  });

  Percentage.prototype.init = function () {
    var conf = this.defaults;
    this.paper = new Raphael(this.node[0], conf.percentageWidth, conf.chartHeight);
    this.node.css({
      "width": conf.percentageWidth,
      "height": conf.chartHeight,
      "float": "left",
      "margin-bottom": "0px",
      "border-bottom": "0px",
      "padding-bottom": "0px"
    });
  };

  Percentage.prototype.setSource = function (source, map) {
    map = this.map(map);
    this.grouped = _.groupBy(source, map.type);
    this.types = _.keys(this.grouped);
    if (this.types.length > this.limit) {
      this.to = this.limit;
    }
  };

  Percentage.prototype.render = function () {
    this.init();
    var conf = this.defaults;
    var y = conf.fontSize * 2 / 3;
    if (!this.rect) {//init
      this.rect = this.paper.rect(0, 0, conf.percentageWidth, conf.chartHeight)
      .attr({
        "fill": "#f4f4f4",
        "stroke": "#aaa",
        "stroke-width": 0.5
      });
      this.text = this.paper.text(conf.percentageWidth / 2, y, Math.round(100) + "%")
      .attr({"text-anchor": "middle"});
    }
    // this.rect.animate({"y": (1 - maxY) * conf.chartHeight, "height": maxY * conf.chartHeight}, 750);
    // this.text.attr({
    //   "text": Math.round(maxY * 100) + "%"
    // }).animate({"y": y}, 300);
  };

  return Percentage;
});

charts/pie: API索引


Pie

构造函数

Options

  • width 图片宽度,默认为800,表示图片高800px
  • height 图片高度,默认为800
  • showLegend 图例是否显示,默认为 true, 显示;设为false则不显示
  • showText 是否显示文字,默认为true
  • ms 动画持续时间,默认300
  • easing 动画类型,默认“bounce”。详见rapheal相关文档,可以使用“linear”,“easeIn”,“easeOut”,“easeInOut”,“backIn”,“backOut”,“elastic”,“bounce”

Examples

create Radar Chart in a dom node with id "chart", width is 500; height is 600px;

var radar = new Radar("chart", {"width": 500, "height": 600});
声明 Pie Pie
参数 container(Object) 表示在html的哪个容器中绘制该组件
参数 options(Object) 为用户自定义的组件的属性,比如画布大小
代码
var Pie = DataV.extend(DataV.Chart, {
    type: "Pie",
    initialize: function (node, options) {
      this.node = this.checkContainer(node);
      this.sum = 0;
      this.groupNames = []; //数组:记录每个group的名字
      this.groupValue = [];
      this.groups = [];
      this.click = 0;

标签纬度

代码
this.dimension.label = {
        type: "string",
        required: false,
        index: 0,
        value: "" // 未指定下标时,使用该值作为默认值
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 1
      };

      //图的大小设置
      this.defaults.showLegend = true;
      this.defaults.showText = true;
      this.defaults.width = 800;
      this.defaults.height = 800;
      this.defaults.ms = 300;
      this.defaults.easing = "bounce";
      this.defaults.tipStyle = {
        "textAlign": "center",
        "margin": "auto",
        "color": "#ffffff"
      };

      this.formatter.tipFormat = function (name, value, sum) {
        return name + ": " + Math.round(value) + " " + (value * 100 / sum).toFixed(2) + "%";
      };

      //设置用户指定的属性
      this.setOptions(options);

      this.legendArea = [20, (this.defaults.height - 20 - 220), 200, 220];
      if (this.defaults.showLegend) {
        this.xOffset = this.legendArea[2];
      } else {
        this.xOffset = 0;
      }

      this.defaults.radius = Math.min((this.defaults.width - this.xOffset), this.defaults.height) * 0.3;
      this.defaults.protrude = this.defaults.radius * 0.1;
      //创建画布
      this.createCanvas();
    }
  });

createCanvas

创建画布

方法 Pie.prototype.createCanvas() createCanvas
代码
Pie.prototype.createCanvas = function () {
    this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height);
    var canvasStyle = this.node.style;
    canvasStyle.position = "relative";
    this.floatTag = DataV.FloatTag()(this.node);
    this.floatTag.css({
      "visibility": "hidden"
    });
  };

getColor

获取颜色

方法 Pie.prototype.getColor() getColor
参数 i(Number) 元素类别编号
返回 String 返回颜色值
代码
Pie.prototype.getColor = function (i) {
    var color = DataV.getColor();
    return color[i % color.length][0];
  };

render

绘制饼图

方法 Pie.prototype.render() render
代码
Pie.prototype.render = function () {
    this.layout();
    var conf = this.defaults;
    var floatTag = this.floatTag;
    var that = this;
    var groups = this.groups;

    //由内外半径、起始角度计算路径字符串
    var pathCalc = d3.svg.arc()
    .innerRadius(conf.radius)
    .outerRadius(conf.radius * 0.2)
    .startAngle(function (d) {
      return d.startAngle;
    }).endAngle(function (d) {
      return d.endAngle;
    });
    var donutEle;
    //获取每个环形的字符串表示
    var spline;
    var tips;
    that.donutGroups = that.canvas.set();

    $(this.node).append(this.floatTag);

    //添加透明效果
    var mouseOver = function () {
      floatTag.html(this.data('text')).css(conf.tipStyle);
      floatTag.css({
        "visibility": "visible"
      });
      var index = this.data("donutIndex");
      if (!this.data('click')) {
        that.underBn[index].attr('opacity', 0.5).show();
      }
      if (that.click === 0) {
        that.donutGroups.forEach(function (d) {
          if (index !== d.data("donutIndex")) {
            d.attr('fill-opacity', 0.5);
          }
        });
      }
      this.attr('fill-opacity', 1);
    };

    var mouseOut = function () {
      floatTag.css({
        "visibility": "hidden"
      });
      var index = this.data("donutIndex");
      //fade(this.data("donutIndex"), 0.6);
      if (!this.data('click')) {
        that.underBn[index].hide();
      }
      if (that.click === 0) {
        that.donutGroups.forEach(function (d) {
          d.attr('fill-opacity', 1);
        });
      } else if (!this.data('click')) {
        this.attr('fill-opacity', 0.5);
      }
    };

    var mouseClick = function () {
      var index = this.data("donutIndex");
      var fan = groups[index];
      var flag = !this.data('click');
      this.data('click', flag);
      var ro = (fan.startAngle + fan.endAngle) * 90 / Math.PI;
      var angle = 0.5 * ((fan.startAngle + fan.endAngle) - Math.PI);
      var center = {
        x: ((conf.width - that.xOffset) / 2 + that.xOffset),
        y: conf.height / 2
      };
      var namePos = {
        x: conf.protrude * Math.cos(angle),
        y: conf.protrude * Math.sin(angle)
      };
      var radius = {
        x: conf.radius * Math.cos(angle) + namePos.x,
        y: conf.radius * Math.sin(angle) + namePos.y
      };
      var offSetPos = {
        x: this.data('nameTag').getBBox().width / 2 * Math.cos(angle),
        y: this.data('nameTag').getBBox().height / 2 * Math.sin(angle)
      };
      if (flag) {
        if (that.click === 0) {
          that.donutGroups.forEach(function (d) {
            if (!d.data('click')) {
              d.attr('fill-opacity', 0.5);
            }
          });
        }
        that.underBn[index].attr('opacity', 1).show();
        this.attr('fill-opacity', 1);
        if (conf.showText) {
          this.data('nameTag').stop().animate({
            transform: "t" + (center.x + radius.x + 2 * namePos.x + offSetPos.x) + " " + (center.y + radius.y + 2 * namePos.y + offSetPos.y)
          }, conf.ms, conf.easing);

          this.data('line').stop().animate({
            transform: "t" + (center.x + radius.x + namePos.x) + " " + (center.y + radius.y + namePos.y) + "r" + ro
          }, conf.ms, conf.easing);

          if (!this.data('hiddenTag')) {
            this.data('nameTag').show();
            this.data('line').show();
          }
        }
        this.stop().animate({
          transform: "t" + (center.x + namePos.x) + " " + (center.y + namePos.y)
        }, conf.ms, conf.easing);
        //this.translate(nameX, nameY);
        that.click += 1;
      } else {
        if (conf.showText) {
          this.data('nameTag').stop().animate({
            transform: "t" + (center.x + radius.x + namePos.x + offSetPos.x) + " " + (center.y + radius.y + namePos.y + offSetPos.y)
          }, conf.ms, conf.easing);
          this.data('line').stop().animate({
            transform: "t" + (center.x + radius.x) + " " + (center.y + radius.y) + "r" + ro
          }, conf.ms, conf.easing);

          if (!this.data('hiddenTag')) {
            this.data('nameTag').hide();
            this.data('line').hide();
          }
        }
        this.stop().animate({
          transform: "t" + center.x + " " + center.y
        }, conf.ms, conf.easing);
        //this.translate(-nameX, - nameY);
        that.click -= 1;
        if (that.click > 0) {
          this.attr('fill-opacity', 0.5);
        }
      }
    };

    //画圆弧
    var i;
    var nameStr;
    var nameX, nameY;
    var ro, angle;
    for (i = 0; i <= groups.length - 1; i++) {
      var fan = groups[i];
      //画外圈的pie图
      //计算每个group的path
      spline = pathCalc(fan);
      var tipFormat = this.getFormatter("tipFormat");
      tips = tipFormat.call(this, fan.nameTag, fan.value, this.sum);
      donutEle = that.canvas.path(spline)
      .translate((conf.width - this.xOffset) / 2 + this.xOffset, conf.height / 2)
      .data("donutIndex", i)
      .data('text', tips)
      .data('click', false)
      .attr({
        "path": spline,
        "fill": that.getColor(i),
        "stroke": '#ffffff'
      })
      .mouseover(mouseOver)
      .mouseout(mouseOut)
      .click(mouseClick);

      //每个donut上显示名称
      ro = (fan.startAngle + fan.endAngle) * 90 / Math.PI;
      angle = 0.5 * ((fan.startAngle + fan.endAngle) - Math.PI);

      if (conf.showText) {
        nameX = (conf.radius + 2 * conf.protrude) * Math.cos(angle);
        nameY = (conf.radius + 2 * conf.protrude) * Math.sin(angle);
        nameStr = "T" + ((conf.width - that.xOffset) / 2 + that.xOffset) + "," + conf.height / 2 + "R" + ro + "T" + nameX + "," + nameY;

        var line = that.canvas.path("M,0,-" + conf.protrude + "L0," + conf.protrude).transform(nameStr).translate(0, conf.protrude);
        var nameTag = that.canvas.text().attr({
          "font": "18px Verdana",
          "text": fan.nameTag
        })
        var offSetPos = {
          x: nameTag.getBBox().width / 2 * Math.cos(angle),
          y: nameTag.getBBox().height / 2 * Math.sin(angle)
        };
        nameTag.translate(((conf.width - that.xOffset) / 2 + that.xOffset) + nameX + offSetPos.x, conf.height / 2 + nameY + offSetPos.y);
        donutEle.data('hiddenTag', true);
        if (i > 0) {
          var fanPrev = groups[i - 1];
          var angleBet = angle - 0.5 * ((fanPrev.startAngle + fanPrev.endAngle) - Math.PI);
          if (that.donutGroups[i - 1].data('hiddenTag')) {
            if (Math.tan(angleBet / 2) * conf.radius * 4 < (that.donutGroups[i - 1].data('nameTag').getBBox().width + nameTag.getBBox().width)) {
              donutEle.data('hiddenTag', false);
              nameTag.hide();
              line.hide();
            }
          }
        }
        donutEle.data('nameTag', nameTag).data('line', line);
      }


      that.donutGroups.push(donutEle);
    }

    if (conf.showLegend) {
      this.drawLegend();
    }
  };

drawLegend

绘制图例

方法 Pie.prototype.drawLegend() drawLegend
代码
Pie.prototype.drawLegend = function () {
    var paper = this.canvas;
    var legendArea = this.legendArea;
    this.rectBn = paper.set();
    var rectBn = this.rectBn;
    this.underBn = [];
    var underBn = this.underBn;
    for (var i = 0, l = this.groups.length; i < l; i++) {
      //底框
      underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "stroke": "none"
      }).hide());
      //色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": this.getColor(i),
        "stroke": "none"
      });
      //文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, this.groups[i].nameTag).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12,
        "text-anchor": "start"
      });
      //选框
      rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
        //"r": 3
      }));
    }
    this.initLegendEvents();
  };

  Pie.prototype.initLegendEvents = function () {
    var rectBn = this.rectBn;
    var underBn = this.underBn;
    var groups = this.groups;
    var that = this;
    var conf = this.defaults;
    rectBn.forEach(function (d, i) {
      var fan = groups[i];
      d.mouseover(function () {
        if (!that.donutGroups[i].data("click")) {
          underBn[i].attr('opacity', 0.5).show();
        }
      }).mouseout(function () {
        if (!that.donutGroups[i].data("click")) {
          underBn[i].hide();
        }
      }).click(function () {
        var ro = (fan.startAngle + fan.endAngle) * 90 / Math.PI;
        var angle = 0.5 * ((fan.startAngle + fan.endAngle) - Math.PI);
        var center = {
          x: ((conf.width - that.xOffset) / 2 + that.xOffset),
          y: conf.height / 2
        };
        var namePos = {
          x: conf.protrude * Math.cos(angle),
          y: conf.protrude * Math.sin(angle)
        };
        var radius = {
          x: conf.radius * Math.cos(angle) + namePos.x,
          y: conf.radius * Math.sin(angle) + namePos.y
        };
        var donut = that.donutGroups[i];
        var offSetPos = {
          x: donut.data('nameTag').getBBox().width / 2 * Math.cos(angle),
          y: donut.data('nameTag').getBBox().height / 2 * Math.sin(angle)
        };
        if (!donut.data("click")) {
          if (that.click === 0) {
            that.donutGroups.forEach(function (d) {
              if (!d.data('click')) {
                d.attr('fill-opacity', 0.5);
              }
            });
          }
          underBn[i].attr('opacity', 1).show();
          donut.data("click", true).attr('fill-opacity', 1);
          if (conf.showText) {
            donut.data('nameTag').stop().animate({
              transform: "t" + (center.x + radius.x + 2 * namePos.x + offSetPos.x) + " " + (center.y + radius.y + 2 * namePos.y + offSetPos.y)
            }, conf.ms, conf.easing);
            donut.data('line').stop().animate({
              transform: "t" + (center.x + radius.x + namePos.x) + " " + (center.y + radius.y + namePos.y) + "r" + ro
            }, conf.ms, conf.easing);

            if (!donut.data('hiddenTag')) {
              donut.data('nameTag').show();
              donut.data('line').show();
            }
          }
          donut.stop().animate({
            transform: "t" + (center.x + namePos.x) + " " + (center.y + namePos.y)
          }, conf.ms, conf.easing);
          that.click += 1;
        } else {
          if (conf.showText) {
            donut.data('nameTag').stop().animate({
              transform: "t" + (center.x + radius.x + namePos.x + offSetPos.x) + " " + (center.y + radius.y + namePos.y + offSetPos.y)
            }, conf.ms, conf.easing);
            donut.data('line').stop().animate({
              transform: "t" + (center.x + radius.x) + " " + (center.y + radius.y) + "r" + ro
            }, conf.ms, conf.easing);

            if (!donut.data('hiddenTag')) {
              donut.data('nameTag').hide();
              donut.data('line').hide();
            }
          }
          donut.stop().animate({
            transform: "t" + center.x + " " + center.y
          }, conf.ms, conf.easing);
          that.click -= 1;
          if (that.click > 0) {
            donut.attr('fill-opacity', 0.5);
          } else {
            that.donutGroups.forEach(function (d) {
              d.attr('fill-opacity', 1);
            });
          }
          underBn[i].hide();
          donut.data("click", false);
        }
      });
    });
  };

setSource

对原始数据进行处理

方法 Pie.prototype.setSource() setSource
参数 table(Array) 将要被绘制成饼图的二维表数据
代码
Pie.prototype.setSource = function (table, map) {
    map = this.map(map);
    this.groupNames = _.pluck(table, map.label);
    this.groupValue = _.pluck(table, map.value).map(function (item) {
      return parseFloat(item);
    });
  };

layout

创建pie布局

方法 Pie.prototype.layout() layout
代码
Pie.prototype.layout = function () {
    var that = this;

    that.canvas.clear();

    this.sum = DataV.sum(this.groupValue);
    var sum = this.sum;
    this.groups = this.groupValue.map(function (item, index) {
      var ret = {
        index: index,
        value: item,
        nameTag: that.groupNames[index]
      };
      return ret;
    });
    this.groups = _.sortBy(that.groups, function (d) {
      return -d.value;
    });
    var acc = 0;
    this.groups.forEach(function (d) {
      d.startAngle = 2 * acc * Math.PI / sum;
      acc += d.value;
      d.endAngle = 2 * acc * Math.PI / sum;
    });
  };

  return Pie;
});

charts/radar: API索引


Radar

构造函数

Options

  • width 数字,图片宽度,默认为800,表示图片高800px
  • height 数字,图片高度,默认为800
  • legend 布尔值,图例是否显示,默认为 true, 显示;设为false则不显示
  • radius 数字,雷达图半径,默认是画布高度的40%

Examples

create Radar Chart in a dom node with id "chart", width is 500; height is 600px;

var radar = new Radar("chart", {"width": 500, "height": 600});
声明 Radar Radar
参数 container(Object) 表示在html的哪个容器中绘制该组件
参数 options(Object) 为用户自定义的组件的属性,比如画布大小
代码
var Radar = DataV.extend(DataV.Chart, {
    type: "Radar",
    initialize: function (container, options) {
      this.node = this.checkContainer(container);
      this.click = 0;
      this.clickedNum = 0;

标签纬度

代码
this.dimension.label = {
        type: "string",
        required: false,
        index: 0,
        value: "" // 未指定下标时,使用该值作为默认值
      };

维度名称

代码
this.dimension.dimName = {
        type: "string",
        required: true,
        index: 1
      };

维度值

代码
this.dimension.dimValue = {
        type: "object",
        required: true,
        index: 2
      };

      // Properties
      //this.source is array of line; key is dimension, value is line's value in that dimension
      this.source = [];
      this.allDimensions = [];
      this.dimensionType = {};
      this.dimensionDomain = {};

      this.axises = [];
      //图的大小设置
      this.defaults.legend = true;
      this.defaults.width = 800;
      this.defaults.height = 800;

      //设置用户指定的属性
      this.setOptions(options);

      this.legendArea = [20, this.defaults.height, 200, 220];
      if (this.defaults.legend) {
        this.defaults.xOffset = this.legendArea[2];
      } else {
        this.defaults.xOffset = 0;
      }

      this.defaults.radius = Math.min((this.defaults.width - this.defaults.xOffset), this.defaults.height) * 0.4;
      //创建画布
      this.createCanvas();
      this.groups = this.canvas.set();
    }
  });

createCanvas

创建画布

方法 Radar.prototype.createCanvas() createCanvas
代码
Radar.prototype.createCanvas = function () {
    this.canvas = new Raphael(this.node, this.defaults.width, this.defaults.height);
    this.node.style.position = "relative";
    this.floatTag = DataV.FloatTag()(this.node);
    this.floatTag.css({
      "visibility": "hidden"
    });
  };

getColor

获取颜色

方法 Radar.prototype.getColor() getColor
参数 i(Number) 元素类别编号
返回 String 返回颜色值
代码
Radar.prototype.getColor = function (i) {
    var color = DataV.getColor();
    return color[i % color.length][0];
  };

render

绘制radar chart

方法 Radar.prototype.render() render
代码
Radar.prototype.render = function () {
    var conf = this.defaults;
    var that = this;
    this.canvas.clear();
    var groups = this.groups;
    var paper = this.canvas;
    var axises = this.axises;

    var lNum = this.allDimensions.length - 1;
    var axisloopStr = "";
    //console.log(lNum);
    for (var i = 0; i < lNum; ++i) {
      var cos = (conf.radius) * Math.cos(2 * Math.PI * i / lNum) * 0.9;
      var sin = (conf.radius) * Math.sin(2 * Math.PI * i / lNum) * 0.9;
      var axis = paper.path("M,0,0,L," + cos + "," + sin).attr({
        'stroke-opacity': 0.5,
        'stroke-width': 1
      });
      axis.data("x", cos).data("y", sin).transform("T" + (conf.radius + conf.xOffset) + "," + conf.radius);
      axises.push(axis);
      var axisText = paper.text().attr({
        "font-family": "Verdana",
        "font-size": 12,
        "text": this.allDimensions[i + 1],
        'stroke-opacity': 1
      }).transform("T" + (conf.radius + cos + conf.xOffset) + "," + (conf.radius + sin));
      axisText.translate(axisText.getBBox().width * cos / 2 / conf.radius, axisText.getBBox().height * sin / 2 / conf.radius); // + "R" + (360 * i / lNum + 90)
      if (i === 0) {
        axisloopStr += "M";
      } else {
        axisloopStr += "L";
      }
      axisloopStr += axises[i].data('x') + " " + axises[i].data('y');
    }
    axisloopStr += "Z";
    paper.circle(conf.radius + conf.xOffset, conf.radius, conf.radius * 0.3).attr({
      'stroke-opacity': 0.5,
      'stroke-width': 1
    });
    paper.circle(conf.radius + conf.xOffset, conf.radius, conf.radius * 0.6).attr({
      'stroke-opacity': 0.5,
      'stroke-width': 1
    });
    paper.circle(conf.radius + conf.xOffset, conf.radius, conf.radius * 0.9).attr({
      'stroke-opacity': 0.5,
      'stroke-width': 1
    });

    var mouseOver = function () {
      if (!this.data('clicked')) {
        if (that.clickedNum === 0) {
          groups.attr({
            'stroke-opacity': 0.5
          });
        }
        var index = this.data('index');
        this.attr({
          'stroke-width': 5,
          'stroke-opacity': 1
        }).toFront();
        that.underBn[index].attr({
          'opacity': 0.5
        }).show();
      }
    }
    var mouseOut = function () {
      if (!this.data('clicked')) {
        if (that.clickedNum === 0) {
          groups.attr({
            'stroke-opacity': 1
          });
        } else {
          this.attr({
            'stroke-opacity': 0.5
          });
        }
        var index = this.data('index');
        this.attr({
          'stroke-width': 2
        });
        that.underBn[index].hide();
      }
    }
    var mouseClick = function () {
      var index = this.data('index');
      if (!this.data('clicked')) {
        if (that.clickedNum === 0) {
          groups.attr({
            'stroke-opacity': 0.5
          });
        }
        this.attr({
          'fill': that.getColor(index),
          'stroke-opacity': 1,
          'fill-opacity': 0.1
        }).toFront();
        that.underBn[index].attr({
          'opacity': 1
        }).show();
        this.data('clicked', true);
        that.clickedNum++;
      } else {
        that.clickedNum--;
        if (that.clickedNum === 0) {
          groups.attr({
            'stroke-opacity': 1
          });
        } else {
          this.attr({
            'stroke-opacity': 0.5
          });
        }
        this.attr({
          'fill': "",
          'fill-opacity': 0
        });
        that.underBn[index].hide();
        this.data('clicked', false);
      }
    }

    var source = this.source;
    var allDimensions = this.allDimensions;
    var dimensionDomain = this.dimensionDomain;

    source.forEach(function (d, i) {
      var pathStr = "";
      allDimensions.forEach(function (prop, j) {
        if (prop !== "name") {
          var rate = 0.1 + 0.8 * (d[prop] - dimensionDomain[prop][0]) / (dimensionDomain[prop][1] - dimensionDomain[prop][0]);
          //console.log(source[i][allDimensions[j]]+","+dimensionDomain[allDimensions[j]][0]+","+dimensionDomain[allDimensions[j]][1]);
          if (j != 1) {
            pathStr += ",L";
          } else {
            pathStr += "M";
          }
          pathStr += rate * axises[j - 1].data('x') + " " + rate * axises[j - 1].data('y');
        }
      });
      pathStr += "Z";
      var loop = paper.path(pathStr).transform("T" + (conf.radius + conf.xOffset) + "," + conf.radius).attr({
        'stroke': that.getColor(i),
        'stroke-width': 2,
        'fill-opacity': 0
      }).data('name', source[i].name).data('index', i).mouseover(mouseOver).mouseout(mouseOut).click(mouseClick);
      groups.push(loop);
    });

    if (conf.legend) {
      this.legend();
    }
  };

getDimensionTypes

get dimension types

方法 Radar.prototype.getDimensionTypes() getDimensionTypes
返回 Object {key: dimension name(column name); value: dimenType("ordinal" or "quantitativ")}
代码
Radar.prototype.getDimensionTypes = function () {
    return $.extend({}, this.dimensionType);
  };

getDimensionDomains

get dimension domain

方法 Radar.prototype.getDimensionDomains() getDimensionDomains
返回 Object {key: dimension name(column name); value: extent array;}
代码
Radar.prototype.getDimensionDomains = function () {
    return $.extend({}, this.dimensionDomain);
  };

setSource

对原始数据进行处理

方法 Radar.prototype.setSource() setSource
参数 table(Array) 将要被绘制成磊达图的二维表数据
代码
Radar.prototype.setSource = function (table, map) {
    var map = this.map(map);
    var that = this;
    //source is 2-dimension array
    var conf = this.defaults;
    this.allDimensions = [];
    this.allDimensions[0] = "name";
    that.dimensionType.name = "ordinal";

    var source = this.source;

    table.forEach(function (row, i) {
      if (that.allDimensions.indexOf(row[map.dimName]) === -1) {
        that.allDimensions.push(row[map.dimName]);
        that.dimensionType[row[map.dimName]] = "quantitative";
      }
      var j = 0;
      for (; j < source.length; j++) {
        if (source[j].name === row[map.label]) {
          break;
        }
      }
      if (j >= source.length) {
        var obj = {};
        obj.name = row[map.label];
        source.push(obj);
      }
      var d = row[map.dimValue];
      if (DataV.isNumeric(row[map.dimValue])) {
        source[j][row[map.dimName]] = parseFloat(row[map.dimValue]);
      } else {
        source[j][row[map.dimName]] = row[map.dimValue];
        that.dimensionType[that.allDimensions[i]] = "ordinal";
      }
    });

this.source = DataV.collectionify(table);

//judge dimesions type auto
//if all number, quantitative else ordinal
this.dimensionType = {};
for (var i = 0, l = this.allDimensions.length; i < l; i++) {
  var type = "quantitative";
  for (var j = 1, ll = table.length; j < ll; j++) {
    var d = table[j][i];
    if (d && (!DataV.isNumeric(d))) {
      type = "ordinal";
      break;
    }
  }
  this.dimensionType[this.allDimensions[i]] = type;
}
代码
this.setDimensionDomain();

  };

setDimensionDomain

set dimension domain

Examples

 parallel.setDimensionDomain({
   "cylinders": [4, 8], //quantitative
   "year": ["75", "79", "80"] //ordinal
 });
方法 Radar.prototype.setDimensionDomain() setDimensionDomain
参数 dimenDomain(Object) {key: dimension name(column name); value: domain array (quantitative domain is digit array whose length is 2, ordinal domain is string array whose length could be larger than 2;}
代码
Radar.prototype.setDimensionDomain = function (dimenDomain) {
    //set default dimensionDomain, extent for quantitative type, item array for ordinal type
    var conf = this.defaults;
    var dimen, i, l, domain;

    if (arguments.length === 0) {
      for (i = 0, l = this.allDimensions.length; i < l; i++) {
        dimen = this.allDimensions[i];
        this._setDefaultDimensionDomain(dimen);
      }
    } else {
      for (prop in dimenDomain) {
        if (dimenDomain.hasOwnProperty(prop) && this.dimensionType[prop]) {
          domain = dimenDomain[prop];
          if (!(domain instanceof Array)) {
            throw new Error("domain should be an array");
          } else {
            if (this.dimensionType[prop] === "quantitative" && domain.length !== 2) {
              throw new Error("quantitative's domain should be an array with two items, for example: [num1, num2]");
            }
            if (this.dimensionType[prop] === "quantitative") {
              this.dimensionDomain[prop] = domain;
            } else if (this.dimensionType[prop] === "ordinal") {
              this.dimensionDomain[prop] = this._setOrdinalDomain(domain);
            }
          }
        }
      }
    }
  };

legend

绘制图例

方法 Radar.prototype.legend() legend
代码
Radar.prototype.legend = function () {
    var that = this;
    var conf = this.defaults;
    var paper = this.canvas;
    var legendArea = this.legendArea;
    this.rectBn = paper.set();
    var rectBn = this.rectBn;
    this.underBn = [];
    var underBn = this.underBn;
    var groups = this.groups;

    for (var i = 0, l = this.groups.length; i < l; i++) {
      //底框
      underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] - 17 - (20 + 3) * i, 190, 20).attr({
        "fill": "#ebebeb",
        "stroke": "none"
      }).hide());
      //色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] - 6 - (20 + 3) * i - 6, 16, 8).attr({
        "fill": this.getColor(i),
        "stroke": "none"
      });
      //文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] - 10 - (20 + 3) * i, this.groups[i].data('name')).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12,
        "text-anchor": "start"
      });
      //选框
      rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] - 16 - (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
      }));
    }
    rectBn.forEach(function (d, i) {
      // TODO 这里的事件建议采用事件委托
      d.mouseover(function () {
        if (!groups[i].data("clicked")) {

          if (that.clickedNum === 0) {
            groups.attr({
              'stroke-opacity': 0.5
            });
          }
          groups[i].attr({
            'stroke-width': 5,
            'stroke-opacity': 1
          });
          underBn[i].attr('opacity', 0.5);
          underBn[i].show();
        }
      }).mouseout(function () {
        if (!groups[i].data("clicked")) {

          if (that.clickedNum === 0) {
            groups.attr({
              'stroke-opacity': 1
            });
          } else {
            groups[i].attr({
              'stroke-opacity': 0.5
            });
          }
          groups[i].attr({
            'stroke-width': 2
          });
          underBn[i].hide();
        }
      });
      d.click(function () {
        if (groups[i].data('clicked')) {
          that.clickedNum--;
          if (that.clickedNum === 0) {
            groups.attr({
              'stroke-opacity': 1
            });
          } else {
            groups[i].attr({
              'stroke-opacity': 0.5
            });
          }
          groups[i].data('clicked', false).attr({
            'stroke-width': 2,
            'fill': "",
            'fill-opacity': 0
          });
          underBn[i].hide();
        } else {
          if (that.clickedNum === 0) {
            groups.attr({
              'stroke-opacity': 0.5
            });
          }
          groups[i].data('clicked', true).attr({
            'stroke-width': 5,
            'stroke-opacity': 1,
            'fill': that.getColor(i),
            'fill-opacity': 0.1
          }).toFront();
          underBn[i].attr({
            'opacity': 1
          }).show();
          that.clickedNum++;
        }

      });
    });
  };

  return Radar;
});

charts/scatterplotMatrix: API索引


setDimensionsX

设置X轴的维度

方法 ScatterplotMatrix.prototype.setDimensionsX() setDimensionsX
代码
ScatterplotMatrix.prototype.setDimensionsX = function (dimen) {
        if (!dimen) {
            throw new Error("Please specify the dimensions.");
        }
        var conf = this.defaults;
        conf.dimensionsX = [];
        var i = 0,
            l = 0;
        for (i = 0, l = dimen.length; i < l; i++) {
            if (_.indexOf(conf.allDimensions, dimen[i]) !== -1) {
                conf.dimensionsX.push(dimen[i]);
            }
        }
    };

setDimensionsY

设置Y轴的维度

方法 ScatterplotMatrix.prototype.setDimensionsY() setDimensionsY
代码
ScatterplotMatrix.prototype.setDimensionsY = function (dimen) {
        if (!dimen) {
            throw new Error("Please specify the dimensions.");
        }
        var conf = this.defaults;
        conf.dimensionsY = [];
        var i = 0,
            l = 0;
        for (i = 0, l = dimen.length; i < l; i++) {
            if (_.indexOf(conf.allDimensions, dimen[i]) !== -1) {
                conf.dimensionsY.push(dimen[i]);
            }
        }
    };

    //设置类型的名字
    ScatterplotMatrix.prototype.setTypeName = function (types) {
        this.defaults.typeNames = types;
    };

    //设置源数据
    ScatterplotMatrix.prototype.setSource = function (source) {
        var i, j, l, ll;
        var conf = this.defaults;

        var xTemp = [],
            yTemp = [];
        for (i = 1; i < source[0].length; i++) {
            xTemp[i - 1] = source[0][i];
            yTemp[i - 1] = source[0][i];
        }
        conf.allDimensions = source[0];
        // 默认情况下,所有维度都显示
        conf.dimensionsX = xTemp;
        conf.dimensionsY = yTemp;

        // this.source is array of line; key is dimension, value is line's value in that dimension
        this.source = [];

        for (i = 1, l = source.length; i < l; i++) {
            var line = {}, dimenT = conf.allDimensions;
            for (j = 0, ll = dimenT.length; j < ll; j++) {
                line[dimenT[j]] = source[i][j]; //each line is an array, contains value for each dimension
            }
            this.source.push(line);
        }

        // 设置默认的定义域
        var getExtent = function (s, dimen) {
            return d3.extent(s, function (p) {
                return +p[dimen];
            });
        };
        var dimen;
        for (i = 0, l = conf.allDimensions.length; i < l; i++) {
            dimen = conf.allDimensions[i];
            conf.dimensionDomain[dimen] = getExtent(this.source, dimen);
        }
    };

setAxis

设置X轴和Y轴

方法 ScatterplotMatrix.prototype.setAxis() setAxis
代码
ScatterplotMatrix.prototype.setAxis = function () {
        var conf = this.defaults;

        //图例区域的左上顶点坐标x,y,宽,高
        conf.legendArea = [20, (conf.height - 20 - 220), conf.legendWidth, 220];
        //简介区域的左上角顶点坐标x,y,宽,高
        conf.introArea = [20, 20, conf.legendWidth, 200];
        //散点矩阵区域的左上顶点坐标x,y,宽,高
        conf.diagramArea = [conf.legendWidth + 20 * 2, 20, (conf.width - conf.legendWidth - 20 * 2), (conf.height - 40)];


        var w = conf.diagramArea[2] - 2 * conf.margin,
            h = conf.diagramArea[3] - conf.margin,
            g = conf.gap,
            nX = conf.dimensionsX.length,
            nY = conf.dimensionsY.length,
            wX = d3.round((w - (nX - 1) * g) / nX),
            wY = d3.round((h - (nY - 1) * g) / nY),
            sw = d3.min([wX, wY]);

        this.defaults.squareWidth = sw;
        this.defaults.dX = conf.dimensionsX[0];
        this.defaults.dY = conf.dimensionsY[0];

        this.x = {};
        this.y = {};
        var x = this.x,
            y = this.y;
        var tickAr = [5];

        //设置X轴
        var i, l, dimen, begin, end;
        for (i = 0, l = conf.dimensionsX.length; i < l; i++) {
            dimen = conf.dimensionsX[i];
            begin = i * (sw + g) + conf.diagramArea[0] + 30;
            end = begin + sw;
            x[dimen] = d3.scale.linear().domain(conf.dimensionDomain[dimen]).range([begin, end]);
            x[dimen].ticks = x[dimen].ticks.apply(x[dimen], tickAr);
        }
        //设置Y轴
        for (i = 0, l = conf.dimensionsY.length; i < l; i++) {
            dimen = conf.dimensionsY[i];
            end = i * (sw + g) + conf.diagramArea[1] + 30;
            begin = end + sw;
            y[dimen] = d3.scale.linear().domain(conf.dimensionDomain[dimen]).range([begin, end]);
            y[dimen].ticks = y[dimen].ticks.apply(y[dimen], tickAr);
        }
    };

    //画散点矩阵
    ScatterplotMatrix.prototype.drawDiagram = function () {
        var i, j, k, z, ticks;
        var conf = this.defaults,
            x = this.x,
            y = this.y,
            sw = conf.squareWidth,
            g = conf.gap,
            cR = conf.circleR;

        var paper = this.canvas;
        var sourceData = this.source;

        var dimensionsX = conf.dimensionsX,
            dimensionsY = conf.dimensionsY,
            lx = dimensionsX.length,
            ly = dimensionsY.length;

        var browserName = navigator.appName;
        var that = this;

        $(this.node).append(this.floatTag);

        //画背景点
        var circlesBg = paper.set(); //背景点
        var centerPos;

        if (browserName !== "Microsoft Internet Explorer") {
            for (k = 0; k < sourceData.length; k++) {
                for (i = 0; i < lx; i++) {
                    for (j = 0; j < ly; j++) {
                        centerPos = this.circleCenter(k, dimensionsX[i], dimensionsY[j]);
                        circlesBg.push(paper.circle(centerPos[0], centerPos[1], cR).attr({
                            "fill": "gray",
                            "stroke": "none",
                            "opacity": 0.2
                        }));
                    }
                }
            }
        }

        // 画矩形框
        var squares = paper.set();
        var x1, y1;
        for (i = 0; i < lx; i++) {
            for (j = 0; j < ly; j++) {
                x1 = x[dimensionsX[i]].range()[0];
                y1 = y[dimensionsY[j]].range()[1];
                squares.push(paper.rect(x1 - 1, y1 - 1, sw + 2, sw + 2));
            }
        }
        squares.attr({
            "fill": "white",
            "fill-opacity": 0.5, //背景点的蒙版
            "stroke": "#d6d6d6",
            "stroke-width": '1px'
        });

        //画虚线
        var reLines = paper.set(),
            tickText = paper.set();
        var tickAr = [10], //set the number of ticks
            leftPos = x[dimensionsX[0]].range()[0],
            rightPos = x[dimensionsX[lx - 1]].range()[1],
            upPos = y[dimensionsY[0]].range()[1],
            downPos = y[dimensionsY[ly - 1]].range()[0];

        var reLineGap = sw / 7; //每个矩形框中画6条虚线
        var reLinePos;

        //画纵向的虚线
        for (i = 0; i < lx; i++) {
            ticks = x[dimensionsX[i]].ticks;
            for (j = 0; j < ticks.length; j++) {
                tickText.push(paper.text((x[dimensionsX[i]](ticks[j])), downPos + 6, ticks[j]).attr({
                    "fill": "#aaaaaa",
                    "fill-opacity": 0.7,
                    "font-family": "雅黑",
                    "font-size": 12
                }).attr({
                    "text-anchor": "end"
                }).rotate(-45, x[dimensionsX[i]](ticks[j]), downPos + 6));
            }
            for (z = 1; z < 7; z++) {
                reLinePos = x[dimensionsX[i]].range()[0] + z * reLineGap;
                reLines.push(paper.path("M" + (reLinePos) + "," + (upPos) + "L" + (reLinePos) + "," + (downPos)).attr({
                    "stroke": "#ebebeb",
                    "stroke-dasharray": "-"
                }));
            }
        }
        //画横向的虚线
        for (i = 0; i < ly; i++) {
            //draw reference lines
            ticks = y[dimensionsY[i]].ticks;
            for (j = 0; j < ticks.length; j++) {
                tickText.push(paper.text(rightPos + 6, y[dimensionsY[i]](ticks[j]), ticks[j]).attr({
                    "fill": "#aaaaaa",
                    "fill-opacity": 0.7,
                    "font-family": "雅黑",
                    "font-size": 12
                }).attr({
                    "text-anchor": "start"
                }).rotate(315, rightPos + 6, y[dimensionsY[i]](ticks[j])));
            }
            for (z = 1; z < 7; z++) {
                reLinePos = y[dimensionsY[i]].range()[1] + z * reLineGap;
                reLines.push(paper.path("M" + (leftPos) + "," + (reLinePos) + "L" + (rightPos) + "," + (reLinePos)).attr({
                    "stroke": "#ebebeb",
                    "stroke-dasharray": "-"
                }));
            }
        }

        //坐标轴名称
        var axText = paper.set();
        var xPos, yPos;
        var pos = y[dimensionsY[0]].range()[1] - 10;
        for (i = 0; i < lx; i++) {
            xPos = x[dimensionsX[i]].range()[0] + sw / 2;
            axText.push(paper.text(xPos, pos, dimensionsX[i]).attr({
                "fill": "#000000",
                "fill-opacity": 0.7,
                "font-family": "Verdana",
                //"font-weight": "bold",
                "font-size": 12
            }).attr({
                "text-anchor": "middle"
            }));
        }

        pos = x[dimensionsX[0]].range()[0] - 10;
        for (i = 0; i < ly; i++) {
            yPos = y[dimensionsY[i]].range()[1] + sw / 2;
            axText.push(paper.text(pos, yPos, dimensionsY[i]).attr({
                "fill": "#000000",
                "fill-opacity": 0.7,
                "font-family": "Verdana",
                //"font-weight": "bold",
                "font-size": 12
            }).attr({
                "text-anchor": "middle"
            }).rotate(-90, pos, yPos));
        }

        // 画前景点
        var circlesFg = []; //circles in foreground
        var circleType = -1;
        var typeMax = -1;

        this.preIndex = "start";
        this.linePosition = [0,0];
        //水平虚线
        that.lineH = paper.path("M" + (leftPos) + "," + (0) + "L" + (rightPos) + "," + (0)).attr({
            "stroke-dasharray": "- ",
            'stroke': '#000000'
        }).hide();
        //垂直虚线
        that.lineV = paper.path("M" + (0) + "," + (upPos) + "L" + (0) + "," + (downPos)).attr({
            "stroke-dasharray": "- ",
            'stroke': '#000000'
        }).hide();
        var hoverTag;
        var circle;
        for (k = 0; k < sourceData.length; k++) {
            if (conf.typeName !== "NoTypeDefinition") { //classify the circles according to their types
                circleType = sourceData[k][conf.typeName] - 1;
                typeMax = Math.max(typeMax, circleType);
            } else {
                circleType = 0;
            }
            for (i = 0; i < lx; i++) {
                for (j = 0; j < ly; j++) {
                    centerPos = this.circleCenter(k, dimensionsX[i], dimensionsY[j]);
                    //前景点
                    circle = paper.circle(centerPos[0], centerPos[1], cR)
                    .data("type", circleType)
                    .data("canHover", 0)
                    .data("position", centerPos)
                    .data('colorType', circleType)
                    .attr({
                        "fill": "#800",
                        "stroke": "none",
                        "opacity": 0.5
                    }).attr({
                        "fill": this.getColor(circleType)
                    });
                    //如果制定了hover要显示的文字,则hover显示的文字
                    if (conf.legendDimen !== "NoTagDimen") {
                        hoverTag = conf.legendDimen + ": " + sourceData[k][conf.legendDimen];
                        circle.data("legend", hoverTag);
                    }
                    circlesFg.push(circle);
                }
            }
        }

        //图例
        var legendArea = this.defaults.legendArea;
        var rectBn = paper.set();
        var underBn = [];
        for (i = 0; i <= typeMax; i++) {
            //底框
            underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
                "fill": "#ebebeb",
                "stroke": "none"
            }).hide());
            //色框
            paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
                "fill": this.getColor(i),
                "stroke": "none"
            });
            //文字
            paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, conf.typeNames[i]).attr({
                "fill": "black",
                "fill-opacity": 1,
                "font-family": "Verdana",
                "font-size": 12
            }).attr({
                "text-anchor": "start"
            });
            //选框
            rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
                "fill": "white",
                "fill-opacity": 0,
                "stroke": "none"
                //"r": 3
            }).data("type", i)).data("clicked", 0);
        }

        if (browserName !== "Microsoft Internet Explorer") {
            rectBn.forEach(function (d, i) {
                underBn[i].data('legendclicked', false);
                d.mouseover(function () {
                    if (underBn[i].data('legendclicked') === false) {
                        underBn[i].attr('opacity', 0.5).show();
                    }
                }).mouseout(function () {
                    if (underBn[i].data('legendclicked') === false) {
                        underBn[i].hide();
                    }
                });
                d.click(function () {
                    for (j = 0; j < underBn.length; j++) {
                        if (j === i) {
                            underBn[j].show();
                        } else {
                            underBn[j].hide();
                        }
                    }
                    rectBn.forEach(function (eachBn) {
                        if (eachBn !== d) {
                            eachBn.data("clicked", 0);
                        }

                    });
                    if (d.data("clicked") === 0) {
                        underBn[i].attr('opacity', 1).show();
                        underBn[i].data('legendclicked', true);
                        circlesFg.forEach(function (ec) {
                            if (ec.data("type") !== d.data("type")) {
                                ec.hide();
                                ec.data("canHover", 0);
                            } else {
                                ec.show();
                                ec.data("canHover", 1);
                            }
                        });
                        d.data("clicked", 1);
                    } else if (d.data("clicked") === 1) {
                        underBn[i].hide();
                        underBn[i].data('legendclicked', false);
                        d.data("clicked", 0);
                        circlesFg.forEach(function (ec) {
                            ec.show();
                            ec.data("canHover", 0);
                        });
                    }
                });
            });

            //Bursh函数定义
            var curBrush;

            function brushstart() {
                if (curBrush !== undefined && curBrush !== d3.event.target) {
                    curBrush.clear();
                }
                var i;
                for (i = 0; i < circlesFg.length; i++) {
                    circlesFg[i].hide();
                    circlesFg[i].data("canHover", 0);
                }
                underBn.forEach(function (ub) {
                    ub.hide();
                });
                rectBn.forEach(function (rb) {
                    rb.data("clicked", 0);
                });
            }

            function brush() {
                curBrush = d3.event.target;

                var e = curBrush.extent(),
                    dimX = d3.event.target.dimX,
                    dimY = d3.event.target.dimY,
                    tempX,
                    tempY,
                    count = lx * ly,
                    i,
                    z;

                for (i = 0; i < sourceData.length; i++) {
                    tempX = sourceData[i][dimX];
                    tempY = sourceData[i][dimY];
                    if (e[0][0] - 1 <= tempX && tempX <= e[1][0] + 1 && e[0][1] - 1 <= tempY && tempY <= e[1][1] + 1) {
                        for (z = 0; z < count; z++) {
                            circlesFg[i * count + z].show();
                        }
                    } else {
                        for (z = 0; z < count; z++) {
                            circlesFg[i * count + z].hide();
                        }
                    }
                }
            }

            function brushend() {
                if (d3.event.target.empty()) {
                    circlesFg.forEach(function (d) {
                        d.show();
                    });
                }
            }

            //Brush交互
            var brushes = [];
            var b;
            for (i = 0; i < lx; i++) {
                for (j = 0; j < ly; j++) {
                    b = Brush().x(x[dimensionsX[i]]).y(y[dimensionsY[j]]).backgroundAttr({
                        "opacity": 0, //背景颜色:白色、全透明
                        "fill": "white"
                    }).foregroundAttr({ //选框颜色
                        "opacity": 0.2,
                        "fill": "#fff700"
                    }).on("brushstart", brushstart).on("brush", brush).on("brushend", brushend);
                    b(paper);
                    b.dimX = dimensionsX[i];
                    b.dimY = dimensionsY[j];
                    brushes.push(b);
                }
            }
            //hover交互
            //var preIndex = "start";
            var floatTag = this.floatTag;
            $(paper.canvas).bind("mousemove", function (e) {
                var bgOffset = $(this).parent().offset();
                var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top];
                var location = [Math.floor((mouse[0] - leftPos) / (sw + g)), Math.floor((mouse[1] - upPos) / (sw + g))];
                if (that.preIndex !== "start") {
                    that.lineV.hide();
                    that.lineH.hide();
                    if (conf.legendDimen !== "NoTagDimen") {
                        floatTag.css({"visibility" : "hidden"});
                    }
                }
                if (location[0] >= 0 && location[0] <= lx && location[1] >= 0 && location[1] <= ly) {
                    for (i = location[0] * ly + location[1]; i < circlesFg.length; i = i + lx * ly) {
                        var center = circlesFg[i].data("position");
                        var canHover = circlesFg[i].data("canHover");
                        if ((canHover === 1) && (Math.abs(mouse[0] - center[0]) <= cR) && (Math.abs(mouse[1] - center[1]) <= cR)) {
                            that.lineV.translate(center[0] - that.linePosition[0], 0).attr('stroke', that.getColor(circlesFg[i].data('colorType'))).show();
                            that.lineH.translate(0, center[1] - that.linePosition[1]).attr('stroke', that.getColor(circlesFg[i].data('colorType'))).show();
                            that.linePosition = center;
                            if (conf.legendDimen !== "NoTagDimen") {
                                floatTag.html('<div style="text-align: center;margin:auto;color:#ffffff">' + circlesFg[i].data("legend") + '</div>');
                                floatTag.css({"visibility" : "visible"});
                            }
                            that.preIndex = i;
                            break;
                        }
                    }
                }
            });
        }
    };

createCanvas

创建canvas

方法 ScatterplotMatrix.prototype.createCanvas() createCanvas
代码
ScatterplotMatrix.prototype.createCanvas = function () {
        var conf = this.defaults;
        this.node.style.position = "relative";
        this.canvas = new Raphael(this.node, conf.width, conf.height);
        this.floatTag = DataV.FloatTag()(this.node);
        this.floatTag.css({"visibility": "hidden"});
    };

    //根据不同类别得到颜色值
    ScatterplotMatrix.prototype.getColor = function (circleType) {
        var color = DataV.getColor();
        return color[circleType % color.length][0];
    };

    //绘制函数
    ScatterplotMatrix.prototype.render = function (options) {
        this.setOptions(options);
        this.canvas.clear();
        this.setAxis();
        this.drawDiagram();
        //var dEnd = new Date();
        //alert(dEnd.getTime() - dBegin.getTime());
    };

    //计算每个circle的圆心位置
    ScatterplotMatrix.prototype.circleCenter = function (index, xDimen, yDimen) {
        var conf = this.defaults,
            source = this.source,
            y = this.y,
            x = this.x,
            dimensionsX = conf.dimensionsX,
            dimensionsY = conf.dimensionsY,
            dimensionType = conf.dimensionType;

        var xPos = x[xDimen](source[index][xDimen]),
            yPos = y[yDimen](source[index][yDimen]);

        return [xPos, yPos];
    };

    return ScatterplotMatrix;
});

charts/stack: API索引


Stack

Stack构造函数
Creates Stack in a DOM node with id "chart"

Options

  • width 宽度,默认为522,单位像素
  • height 高度,默认为522,单位像素
  • yBase 纵坐标的基线值,默认为0,可以设置为任意数值;为undefined时,以数据中的最小值为起始值
  • barWidth 柱子的宽度
  • showLegend 是否显示图例
  • legendWidth 图例的宽度
  • margin 图表的间距,依次为上右下左
  • showPercentage 显示绝对值或百分比
  • xTickNumber 横轴刻度数
  • yTickNumber 纵轴刻度数
  • formatLabel 横轴提示格式化函数,传入横轴值,默认函数传出原始值
  • formatYScale 纵轴刻度格式化函数,传入纵轴刻度值
  • formatValue 值格式化函数

Examples

var stack = new Stack("chart", {"width": 500, "height": 600, "typeNames": ["Y", "Z"]});
声明 Stack Stack
参数 node(Mix) The dom node or dom node Id
参数 options(Object) options json object for determin stack style.
代码
var Stack = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Stack";
      this.node = this.checkContainer(node);

柱纬度

代码
this.dimension.stack = {
        type: "string",
        required: true,
        index: undefined
      };

横向纬度

代码
this.dimension.x = {
        type: "string",
        required: true,
        index: 1
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 2
      };

      this.defaults.typeNames = [];
      // canvas parameters
      this.defaults.width = 522;
      this.defaults.height = 522;

      this.defaults.margin = [50, 50, 50, 50];

      this.defaults.barWidth = 10;
      this.defaults.circleR = 3;
      this.defaults.barColor = ["#308BE6", "#8EEC00", "#DDDF0D"];
      this.defaults.xTickNumber = 5;
      this.defaults.yTickNumber = 5;
      this.showPercentage = false;

      this.defaults.yBase = 0;

      this.defaults.showLegend = true;
      this.defaults.legendWidth = 100;
      //图例区域的左上顶点坐标x,y,宽,高
      this.defaults.legendArea = [422, 50, 472, 220];
      this.StackSet = [];
      this.legendCluster = [];

      this.defaults.tipStyle = {
        "textAlign": "center",
        "margin": "auto",
        "color": "#ffffff"
      };

      this.setOptions(options);
      this.createCanvas();
      this.initEvents();

    }
  });

createCanvas

创建画布

方法 Stack.prototype.createCanvas() createCanvas
代码
Stack.prototype.createCanvas = function () {
    var conf = this.defaults;
    this.node.style.position = "relative";
    this.canvas = new Raphael(this.node, conf.width, conf.height);
    this.floatTag = DataV.FloatTag()(this.node);
    this.floatTag.css({
      "visibility": "hidden"
    });
  };

  Stack.prototype.initEvents = function () {
    var that = this;
    this.on('legendOver', function (StackIndex) {
      that.StackSet.forEach(function (set, index) {
        if (index !== StackIndex) {
          set.data('colorRect').attr({
            "fill-opacity": 0.5
          });
        }
      });
    });

    this.on('legendOut', function (StackIndex) {
      that.StackSet.forEach(function (set, index) {
        set.data('colorRect').attr({
          "fill-opacity": 0
        });
      });
    });

    this.on('legendClick', function (clicked, StackIndex) {
      that.clicked = clicked;
      that.clickedStackIndex = StackIndex;
      that.StackSet.forEach(function (set, index) {
        if (index !== StackIndex) {
          if (clicked) {
            set.data('colorRect').attr({
              "fill-opacity": 0.7
            });
          } else {
            set.data('colorRect').attr({
              "fill-opacity": 0.5
            });
          }
        } else {
          set.data('colorRect').attr({
            "fill-opacity": 0
          });
        }
      });
    });
  };

setSource

设置数据源
Examples:

stack.setSource(source);
方法 Stack.prototype.setSource() setSource
参数 source(Array) 数据源 第一列为排布在x轴的数据,后n列为排布在y轴的数据
代码
Stack.prototype.setSource = function (source, map) {
    var conf = this.defaults;
    map = this.map(map);
    var dataTable; // = source;
    if (DataV.detect(source) === 'Table_WITH_HEAD') {
      dataTable = DataV.collectionify(source);
    } else {
      dataTable = source;
    }
    // 不指定列,将当前数据作为一列
    this.Stacks = (typeof map.stack === 'undefined') ? {
      stack: dataTable
    } : _.groupBy(dataTable, map.stack);
    var that = this;
    _.each(this.Stacks, function (group, key) {
      that.Stacks[key] = _.sortBy(group, map.x);
    });
    this.StackCount = _.keys(this.Stacks).length;
    conf.xAxisData = _.pluck(_.first(_.values(this.Stacks)), map.x);
    conf.xTickNumber = Math.min(conf.xAxisData.length, conf.xTickNumber);
    // 纵坐标的范围

    var values = _.values(this.Stacks);
    var yExtent = [0, 0];
    for (var i = values[0].length - 1; i >= 0; i--) {
      var total = 0;
      for (var j = 0; j < values.length; j++) {
        total += values[j][i][this.mapping.value]
      }
      if (total > yExtent[1]) {
        yExtent[1] = total;
      }
    };

d3.extent(dataTable, function (item)
{
return item[map.value];
});

代码
// 纵坐标基线值
    if (typeof conf.yBase !== 'undefined') {
      yExtent.push(conf.yBase);
    }
    yExtent = d3.extent(yExtent);
    // 最大值放大1/10
    conf.yExtent = [yExtent[0], yExtent[1] * 1.1];
  };

setAxis

设置坐标轴

方法 Stack.prototype.setAxis() setAxis
代码
Stack.prototype.setAxis = function () {
    var conf = this.defaults;
    if (conf.showLegend) {
      conf.legendArea = [conf.width - conf.legendWidth, 0, conf.width, conf.height];
    } else {
      conf.legendWidth = 0;
      conf.legendArea = [0, 0, 0, 0];
    }

    var margin = conf.margin;
    var diagramArea = this.diagramArea = [margin[3], margin[0], conf.width - conf.legendWidth - margin[1], conf.height - margin[2]];

    //设置x轴
    this.x = d3.scale.linear().domain([0, conf.xAxisData.length]).range([diagramArea[0], diagramArea[2]]);
    //设置y轴
    this.value = d3.scale.linear().domain(conf.yExtent).range([diagramArea[3], diagramArea[1]]);
    var xRange = this.x.range();
    var valueRange = this.value.range();
    this.axisPosition = {
      left: xRange[0],
      right: xRange[1],
      up: valueRange[1],
      down: valueRange[0]
    };
    this.clusterCount = _.max(this.Stacks, function (stack) {
      return stack.length;
    }).length;
    var width = diagramArea[2] - diagramArea[0];
    this.clusterWidth = width / this.clusterCount;
    this.gap = (this.clusterWidth - conf.barWidth) / 2;
  };

drawAxis

绘制坐标

方法 Stack.prototype.drawAxis() drawAxis
代码
Stack.prototype.drawAxis = function () {
    var conf = this.defaults;
    var paper = this.canvas;
    var j;
    //画坐标轴
    var axisLines = paper.set();
    var tickText = paper.set();
    var axis = this.axisPosition;
    // X轴
    var ticks = this.x.ticks(conf.xTickNumber);
    var formatLabel = this.getFormatter('formatLabel');

    // 修复显示不从第一个x轴单位显示的bug
    for (j = 0; j < ticks.length; j++) {
      // 修改x轴单位显示在所有Stack组的中间位置
      // 修复x轴单位对于柱位置的偏移
      var x = this.x(ticks[j]) + this.clusterWidth / 2;
      var text = conf.xAxisData[ticks[j]];
      // 会存在刻度大于数组长度的情况,边界处理
      if (typeof text !== "undefined") {
        tickText.push(paper.text(x, axis.down + 14, formatLabel(text)).rotate(0, x, axis.up));
        // 画x轴刻度线
        axisLines.push(paper.path("M" + x + "," + axis.down + "L" + x + "," + (axis.down + 5)));
      }
    }

    tickText.attr({
      "fill": "#878791",
      "fill-opacity": 0.7,
      "font-size": 12,
      "text-anchor": "middle"
    });

    // 绘制Y轴
    axisLines.push(paper.path("M" + axis.left + "," + axis.up + "L" + axis.left + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });
    //Y轴
    ticks = this.value.ticks(conf.yTickNumber);
    var formatYScale = this.getFormatter('formatYScale');
    for (j = 0; j < ticks.length; j++) {
      tickText.push(paper.text(axis.left - 8, this.value(ticks[j]), formatYScale(ticks[j])).attr({
        "fill": "#878791",
        "fill-opacity": 0.7,
        "font-size": 12,
        "text-anchor": "end"
      }).rotate(0, axis.right + 6, this.value(ticks[j])));
      axisLines.push(paper.path("M" + axis.left + "," + this.value(ticks[j]) + "L" + (axis.left - 5) + "," + this.value(ticks[j])));
    }
    axisLines.push(paper.path("M" + axis.left + "," + axis.down + "L" + axis.right + "," + axis.down));
    axisLines.attr({
      "stroke": "#D7D7D7",
      "stroke-width": 2
    });

    var numOfHLine = d3.round((axis.down - axis.up) / 30 - 1);
    var hLines = paper.set();
    for (j = 1; j <= numOfHLine; j++) {
      var hLinesPos = axis.down - j * 30;
      hLines.push(paper.path("M" + axis.left + "," + hLinesPos + "L" + axis.right + "," + hLinesPos));
    }
    hLines.attr({
      "stroke": "#ECECEC",
      "stroke-width": 0.1
    });
  };

drawDiagram

进行柱状图的绘制

方法 Stack.prototype.drawDiagram() drawDiagram
代码
Stack.prototype.drawDiagram = function () {
    var that = this;

    var floatTag = this.floatTag;
    var conf = this.defaults;
    var axis = this.axisPosition;
    var paper = this.canvas;
    //bars
    var barWidth = conf.barWidth;
    var StackSet = this.StackSet = [];
    var values = _.values(this.Stacks);
    var formatValue = that.getFormatter('formatValue');
    var labels = _.keys(this.Stacks);


    $(this.node).append(this.floatTag);

    var mouseOver = function () {
      floatTag.html(this.data('text')).css(conf.tipStyle);
      floatTag.css({
        "visibility": "visible"
      });
      var cRect = this;
      that.StackSet.forEach(function (stack) {
        stack.forEach(function (rect, index) {
          if (index === cRect.data('index')) {
            rect.data('colorRect').attr({
              'fill-opacity': 0.6
            });
          }
        });
      });

    }

    var mouseOut = function () {
      floatTag.css({
        "visibility": "hidden"
      });
      that.StackSet.forEach(function (stack) {
        stack.forEach(function (rect) {
          rect.data('colorRect').attr({
            'fill-opacity': 1
          });
        });
      });
    }

    values.forEach(function (stack, index) {
      StackSet[index] = paper.set();
      stack.forEach(function (row, i) {
        var value1 = row[that.mapping.value];
        var tValue = 0;
        var valueLabel = '';
        var value = 0;
        for (var j = 0; j < that.StackCount; j++) {
          if (j <= index) {
            value += values[j][i][that.mapping.value];
          }
          tValue += values[j][i][that.mapping.value];
        }
        if (tValue === 0) {
          tValue = 1;
        }
        for (var j = 0; j < that.StackCount; j++) {
          if (conf.showPercentage) {
            valueLabel += labels[j] + ': ' + formatValue(values[j][i][that.mapping.value] * 100 / tValue) + '%<br>';
          } else {
            valueLabel += labels[j] + ': ' + formatValue(values[j][i][that.mapping.value]) + '<br>';
          }
        }
        var barHeight = that.value(value1);
        var height = that.value(value);
        var x = that.x(i);

        var yPos = height;
        var textWidth = 5 * valueLabel.length + 20;
        var xPos = x + barWidth + that.gap + 8;

        var colorRect = paper.rect(x + that.gap, height, barWidth, axis.down - barHeight).attr({
          "fill": conf.barColor[index],
          "fill-opacity": 1,
          "stroke": "none"
        });

        var rect = paper.rect(x, height, that.gap * 2 + barWidth, axis.down - barHeight).attr({
          "fill": "#ffffff",
          "fill-opacity": 0.1,
          "stroke": "none"
        });
        rect.data('stack', index).data('index', i).data('height', height).data('text', valueLabel).data('colorRect', colorRect);
        StackSet[index].push(rect);
      });
    });


    this.StackSet.forEach(function (stack) {
      stack.forEach(function (rect, index) {
        rect.mouseover(mouseOver).mouseout(mouseOut);
      });
    });

  };

drawLegend

绘制图例

方法 Stack.prototype.drawLegend() drawLegend
代码
Stack.prototype.drawLegend = function () {
    var that = this;
    var paper = this.canvas;
    var legendSet = paper.set();
    var bgSet = paper.set();
    var conf = this.defaults;
    var legendArea = conf.legendArea;
    //legend
    var mouseOverLegend = function () {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({
        "fill-opacity": 0.5
      });
      that.fire('legendOver', this.data('type'));
    };

    var mouseOutLegend = function () {
      if (legendSet.clicked) {
        return;
      }
      bgSet[this.data('type')].attr({
        "fill-opacity": 0
      });
      that.fire('legendOut', this.data('type'));
    };

    var clickLegend = function () {
      if (legendSet.clicked && legendSet.clickedStack === this.data('type')) {
        legendSet.clicked = false;
      } else {
        legendSet.clicked = true;
        legendSet.clickedStack = this.data('type');
      }
      bgSet.attr({
        "fill-opacity": 0
      });
      bgSet[this.data('type')].attr({
        "fill-opacity": legendSet.clicked ? 1 : 0
      });
      that.fire('legendClick', legendSet.clicked, this.data('type'));
    };

    var labels = _.keys(this.Stacks);
    for (var i = 0; i < labels.length; i++) {
      //底框
      bgSet.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "#ebebeb",
        "fill-opacity": 0,
        "stroke": "none"
      }));
      // 色框
      paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
        "fill": conf.barColor[i],
        "stroke": "none"
      });
      // 文字
      paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, labels[i]).attr({
        "fill": "black",
        "fill-opacity": 1,
        "font-family": "Verdana",
        "font-size": 12,
        "text-anchor": "start"
      });
      // 选框
      var rect = paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
        "fill": "white",
        "fill-opacity": 0,
        "stroke": "none"
      }).data("type", i);
      //rect.mouseover(mouseOverLegend);
      //rect.mouseout(mouseOutLegend);
      //rect.click(clickLegend);
      legendSet.push(rect);
    }
  };

render

绘制柱状图

Options

  • width 宽度,默认为节点宽度
  • typeNames 指定y轴上数据类目

Examples

stack.render({"width": 1024})
方法 Stack.prototype.render() render
参数 options(Object) options json object for determin stack style.
代码
Stack.prototype.render = function (options) {
    var conf = this.defaults;
    this.setOptions(options);
    this.canvas.clear();
    this.setAxis();
    this.drawAxis();
    this.drawDiagram();
    if (conf.showLegend) {
      this.drawLegend();
    }
  };

charts/stream.bak: API索引


his.userConfig.rootName

代码
: "第" + (i + 1) + "层")
        .appendTo($(this.naviTrace));
      if (i !== level) {
        span.css({"cursor": "pointer", "color": "#1E90FF"})
        .addClass("navi");
      }
    }
    this.naviBack.style.visibility = level > 0 ? "visible" : "hidden";
  };
  Navi.prototype.clear = function () {
    $(this.naviTrace).empty();
  };

  //hoverline
  var HoverLine = function (stream) {
    this.stream = stream;
  };

  HoverLine.prototype.render = function () {
    this.clear();
    var paper = this.stream.paper;
    var conf = this.stream.defaults;
    this.indicatorLine = paper.path("M0 0V" + conf.chartHeight)
      .attr({stroke: "none", "stroke-width": 1, "stroke-dasharray": "- "});
    this.highlightLine = paper.path("M0 0V" + conf.chartHeight)
      .attr({stroke: "none", "stroke-width": 2});
  };
  HoverLine.prototype.hidden = function () {
    this.indicatorLine.attr({"stroke": "none"});
    this.highlightLine.attr({"stroke": "none"});
  };
  HoverLine.prototype.show = function () {
    this.indicatorLine.attr({"stroke": "#000"});
    this.highlightLine.attr({"stroke": "white"});
  };

  HoverLine.prototype.refresh = function (xIdx, pathIndex) {
    //refresh lines' position
    var stream = this.stream;
    var pathSource = stream.chart.pathSource;
    var lineX = stream.defaults.chartWidth * xIdx / (stream.chart.source[0].length - 1);
    var pathSourceCell = pathSource[pathSource.length - 1][xIdx];
    this.indicatorLine.attr({
      path: "M" + lineX + " " + (pathSourceCell.y0 - pathSourceCell.y) + "V" + pathSource[0][xIdx].y0
    });

    pathSourceCell = pathSource[pathIndex][xIdx];
    this.highlightLine.attr({
      path: "M" + lineX + " " + (pathSourceCell.y0 - pathSourceCell.y) + "V" + pathSourceCell.y0
    });
  
    if (pathIndex === 0 && stream.getDisplayRowInfo(pathIndex).rowIndex === -1) {
      this.highlightLine.attr({"cursor": "pointer"});
    } else {
      this.highlightLine.attr({"cursor": "auto"});
    }
  };

  HoverLine.prototype.clear = function () {
    this.indicatorLine && this.indicatorLine.remove();
    this.highlightLine && this.highlightLine.remove();
  };

  //pathLabel
  var PathLabel = function (stream) {
      this.stream = stream;
  };
  PathLabel.prototype.render = function () {
    this.clear();
    var stream = this.stream;
    var paths = stream.chart.paths;
    var conf = stream.defaults;
    var pathSource = stream.chart.pathSource;
    var labels = [];
    var getLabelLocation = function (locArray, el) {
      var x = 0,
        y = 0,
        i;
      var ratioMargin = 0.15;
      var index = 0;
      var max = 0;
      var box = el.getBBox();
      var xInterval;
      var minTop, maxBottom;
      var showLabel = true;
      var loc;
      var height;

      xInterval = Math.ceil(box.width / (locArray[1].x - locArray[0].x) / 2);
      if (xInterval === 0) {
        xInterval = 1;
      }

      locArray.forEach(function (d, i, array) {
        var m = Math.max(ratioMargin * array.length, xInterval);
        if (i >= m && i <= array.length - m) {
          if (d.y > max) {
            minTop = d.y0 - d.y;
            maxBottom = d.y0;
            max = d.y;
            index = i;
          }
        }
      });
      for (i = index - xInterval; i <= index + xInterval; i++) {
        if (i < 0 || i >= locArray.length) {
            height = 0;
            showLabel = false;
            break;
        }
        loc = locArray[i];
        //top's y is small
        if (loc.y0 - loc.y > minTop) {
            minTop = loc.y0 - loc.y;
        }
        if (loc.y0 < maxBottom) {
            maxBottom = loc.y0;
        }
      }

      if (showLabel && maxBottom - minTop >= box.height * 0.8) {
        x = locArray[index].x;
        y = (minTop + maxBottom) / 2;
      } else {
        showLabel = false;
      }

      return {
        x: x,
        y: y,
        showLabel: showLabel
      };
    };

    stream.labels = labels;
    var i, l, label, path;
    for (i = 0, l = paths.length; i < l; i++) {
      path = paths[i];
      label = stream.paper.text(0, 0,
        conf.pathLabel ?
          stream.getDisplayRowInfo(i).rowName + " " + (Math.round(stream.getDisplayRowInfo(i).rowSum * 10000) / 100) + "%" : "")
        .attr({
          "text-anchor": "middle",
          "fill": "white",
          "font-size": conf.fontSize,
          "font-family": "微软雅黑"
        });
      label.labelLoc = getLabelLocation(pathSource[i], label);

      if (label.labelLoc.showLabel) {
        label.attr({
          "x": label.labelLoc.x,
          "y": label.labelLoc.y
        });
      } else {
        label.attr({"opacity": 0});
      }
      if (i === 0 && stream.getDisplayRowInfo(i).rowIndex === -1) {
        path.attr({"cursor": "pointer"});
        label.attr({"cursor": "pointer"});
      } else {
        path.attr({"cursor": "auto"});
        label.attr({"cursor": "auto"});
      }
      labels.push(label);
    }
  };
  PathLabel.prototype.hidden = function () {
    this.stream.labels.forEach(function (d) {
      d.hide();
    });
  };
  PathLabel.prototype.show = function () {
    this.stream.labels.forEach(function (d) {
      if (d.labelLoc.showLabel) {
        d.show();
      }
    });
  };
  PathLabel.prototype.clear = function () {
    var stream = this.stream;
    if (stream.labels) {
      stream.labels.forEach(function (d) {
        d.remove();
      });
    }
  };

  //floatTag
  var FloatTag = DataV.extend(Widget, {
    initialize: function (container) {
      this.container = container;
      this.node = DataV.FloatTag()(this.container);
      //$(this.container).append(this.floatTag);//?
      this.hidden();
    }
  });

  FloatTag.prototype.setContent = function (content) {
    this.node.html(content);
  };
  FloatTag.prototype.setCss = function (cssJson) {
    this.node.css(cssJson);
  };

  //cover
  var Cover = DataV.extend(Widget, {
    initialize: function (stream, container) {
      var conf = stream.defaults;
      this.node = $(container);
      this.node.css({
        "position": "absolute",
        "left": "0px",
        "top": "0px",
        "width": conf.chartWidth + "px",
        "height": conf.chartHeight + "px",
        "zIndex": 100,
        "visibility": "hidden"
      }).bind("mousemove", $.proxy(function (e) {
        this.mouse = {x: e.pageX, y: e.pageY};
        e.stopPropagation();
      }, this)).bind("mouseleave", $.proxy(function () {
        this.mouse = undefined;
      }, this));
    }
  });

Stream

constructor

声明 Stream Stream
参数 the(node) dom node or dom node Id
undefined undefined undefined undefined
示例 undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
代码
var Stream = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Stream";
      this.node = this.checkContainer(node);
      this.defaults = {};
      // Properties
      this.defaults.offset = "zero";//zero, expand, silhou-ette, wiggle;(d3 stack offset)
      this.defaults.order = "default";//default, reverse, descending, ascending, inside-out(d3 stack order, sort by index of maximum value, then use balanced weighting.), inside-out-reverse(inside-out like, sort by index of maximum value, not descending but ascending);
      this.defaults.normalized = false;//false, true; //ratio data or not;
      //this.defaults.rowDataOrder = "default"; //default, descending, ascending(according to digitdata row sum value);
      this.defaults.columnNameUsed = "auto";
      this.defaults.rowNameUsed = "auto";
      this.defaults.pathLabel = true;
      this.defaults.fontSize = 12;
      //this.defaults.axisTickNumber = 8; // axis ticks number

      this.defaults.indexMargin = 3; // if dates.length < indexMargin * 2 + 1, do not show label

      //this.userConfig = {"more": true, "max": 20, "other": 0.1};
      this.defaults.moreConfig = {"more": true, "level": 0, "max": 20, "other": 0.1};

      this.timeRange = [];
      // paper

      this.defaults.width = 800;
      this.defaults.height = 560;//if only width has value and autoHeight is true, then height will be width * heightWidthRatio.
      this.defaults.autoHeight = true;
      this.defaults.heightWidthRatio = 0.6;

      this.defaults.legendPosition = "top";//"top", "left"
      this.defaults.topLegendHeight = 50;
      this.defaults.leftLegendWidth = 150;
      this.defaults.showLegend = true;

      this.defaults.legendBesidesWidth = undefined;
      this.defaults.legendBesidesHeight = undefined;

      this.defaults.chartWidth = undefined;//depends on width, do not recommend to change
      this.defaults.chartHeight = undefined;// depends on height, do not recommend to change

      this.defaults.naviHeight = 20;//do not recommend to change
      this.defaults.showNavi = undefined;//ture if moreConfig.more == true, else false;

      this.defaults.axisHeight = 30;//do not recommend to change
      this.defaults.showAxis = true;

      this.defaults.showPercentage = undefined;//true if moreConfig.more == true, else false;
      this.defaults.percentageWidth = 40;

      this.defaults.customEventHandle = {"mousemove": null};

      //data
      this.rawData = {};//raw data user sets
      this.statisticData = {};//add statistic info from rawData
      this.processedData = {};//normalized and sorted
      this.slicedData = {};//slice data from processed data according to timerange and more
      this.displayData = {};//adjust from slicedData according to user's interactive like dropping;

      this.setOptions(options);
      //this.createPaper();
    }
  });

  Stream.prototype.createPaper = function () {};

  Stream.prototype.setOptions = function (options) {
    var prop;
    var conf = this.defaults;
    var that = this;
    var setMoreConfig = function (options) {
      var prop;
      var mc = that.defaults.moreConfig;
      if (options) {
        for (prop in options) {
          if (options.hasOwnProperty(prop)) {
            mc[prop] = options[prop];
          }
        }
      }
    };
    if (options) {
      for (prop in options) {
        if (options.hasOwnProperty(prop)) {
          //moreConfig;
          if (prop === "moreConfig") {
            setMoreConfig(options[prop]);
            continue;
          }
          conf[prop] = options[prop];
        }
      }
    }

    if (options.width && !options.height) {
      if (conf.autoHeight) {
        conf.height = conf.width * conf.heightWidthRatio;
      }
    } else if (options.height) {
      conf.autoHeight = false;
    }
  };

hasRowName

@param source The data source.

方法 Stream.prototype.hasRowName() hasRowName
示例 undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
代码
Stream.prototype.hasRowName = function () {
    var firstColumn = [],
      source = this.rawData.rawData;

    if ((typeof this.defaults.rowNameUsed) === "boolean") {
      return this.defaults.rowNameUsed;
    }
    //first column from 2nd row
    for (var i = 1, l = source.length; i < l; i++) {
      firstColumn[i] = source[i][0];
    }
    return !firstColumn.every(DataV.isNumeric);
  };

  Stream.prototype.hasColumnName = function () {
    var firstRow;
    if ((typeof this.defaults.columnNameUsed) === "boolean") {
        return this.defaults.columnNameUsed;
    }
    //first row from 2nd column
    firstRow = this.rawData.rawData[0].slice(1);
    return !firstRow.every(DataV.isNumeric);
  };

  Stream.prototype.sort = function (source) {
    var i, j, l, ll;
    var rowSum = [];
    var columnSum = [];
    var newSource = [];
    var rowName = [];
    var that = this;

    for (j = 0, ll = source[0].length; j < ll; j++) {
      columnSum[j] = 0;
    }

    for (i = 0, l = source.length; i < l; i++) {
      rowSum[i] = 0;
      for (j = 0, ll = source[0].length; j < ll; j++) {
        rowSum[i] += source[i][j];
        columnSum[j] += source[i][j];
      }
      rowSum[i] = [rowSum[i]];
      rowSum[i].index = i;
    }

    rowSum.sort(function (a, b) {
      return b[0] - a[0];
    });

    rowSum.forEach(function (d, i) {
      newSource[i] = source[d.index];
      if (that.rowName) {
        rowName[i] = that.rowName[d.index];
      }
    });

    for (i = 0, l = rowSum.length; i < l; i++) {
      rowSum[i] = rowSum[i][0];
    }

    this.rowName = rowName;
    this.rowSum = rowSum;
    this.columnSum = columnSum;
    this.total = d3.sum(this.rowSum);

    return newSource;
  };

  Stream.prototype.getRawData = function (source, isRawInfos) {
    var rawData = this.rawData;
    //get column name, row name and digitData;
    var conf = this.defaults,
      firstRow = source[0],
      firstColumn,
      digitData;

    var i, j, l, ll;

    if (!isRawInfos) {
      rawData.rawData = source;
    }

    firstColumn = source.map(function (d) {
      return d[0];
    });

    if (this.hasRowName()) {
      if (this.hasColumnName()) {
        //row names, column names
        digitData = source.map(function (d) {
            return d.slice(1);
        }).slice(1);
        rawData.rowName = firstColumn.slice(1);
        rawData.columnName = firstRow.slice(1);
      } else {
        //row names, no column names
        digitData = source.map(function (d) {
            return d.slice(1);
        });
        rawData.rowName = firstColumn;
        //rawData.columnName = undefined;
        rawData.columnName = d3.range(digitData[0].length)
          .map(function () {
            return "";
          });
      }
    } else {
      if (this.hasColumnName()) {
        //no row names, column names
        digitData = source.slice(1);
        rawData.rowName = d3.range(digitData.length)
          .map(function () {
            return "";
          });
        rawData.columnName = firstRow;
      } else {
        //no row names, no column names
        if (conf.columnNameUsed === "auto" && conf.rowNameUsed === "auto" && !DataV.isNumeric(source[0][0])) {
            //row or column name may be number, can not judge by automatically, need user to specify
            throw new Error("Row or column name may be numbers, program can not judge automatically, Please specify whether there are column names or row names"); 
        }
        digitData = source;
        rawData.rowName = d3.range(digitData.length)
          .map(function () {return "";});
        rawData.columnName = d3.range(digitData[0].length)
          .map(function () {return "";});
      }
    }

    if (!isRawInfos) {
      for (i = 0, l = digitData.length; i < l; i++) {
        for (j = 0, ll = digitData[0].length; j < ll; j++) {
          digitData[i][j] = parseFloat(digitData[i][j]);
        }
      }
      rawData.digitData = digitData;
    } else {
      rawData.rawInfos = digitData;
    }
  };

  Stream.prototype.getStatisticData = function () {
    //get statistic data;  dataRelation;
    var statData = this.statisticData = {};
    var rawData = this.rawData;
    var rowSum = [];
    var columnSum = [];
    var totalSum;
    var columnRatioMatrix = [];
    var digitData;
    var i, j, l, ll;
    //data
    //rowName, columnName, digitData, rawInfos
    statData.rowName = rawData.rowName;
    statData.columnName = rawData.columnName;
    statData.digitData = digitData = rawData.digitData;
    statData.rawInfos = rawData.rawInfos;

    //rowSum, columnSum
    for (j = 0, ll = digitData[0].length; j < ll; j++) {
        columnSum[j] = 0;
    }
    for (i = 0, l = digitData.length; i < l; i++) {
      rowSum[i] = 0;
      for (j = 0, ll = digitData[0].length; j < ll; j++) {
        rowSum[i] += digitData[i][j];
        columnSum[j] += digitData[i][j];
      }
    }
    statData.rowSum = rowSum;
    statData.columnSum = columnSum;

    //totalSum
    statData.totalSum = totalSum = d3.sum(rowSum);

    //rowRatio, columnRatio;
    statData.rowRatio = rowSum.slice();
    statData.rowRatio.forEach(function (d, i, arr) {
      arr[i] = d / totalSum;
    });
    statData.columnRatio = columnSum.slice();
    statData.columnRatio.forEach(function (d, i, arr) {
      arr[i] = d / totalSum;
    });

    //rowIndex
    statData.rowIndex = [];
    for (i = 0, l = digitData.length; i < l; i++) {
      statData.rowIndex[i] = {rawData: i};
    }

    //columnRatioMatrix;
    statData.columnRatioMatrix = columnRatioMatrix = [];
    for (i = 0, l = digitData.length; i < l; i++) {
      columnRatioMatrix[i] = [];
      for (j = 0, ll = digitData[0].length; j < ll; j++) {
        columnRatioMatrix[i][j] = digitData[i][j] / columnSum[j];
      }
    }
  };

  Stream.prototype.getProcessedData = function () {
    //get processed data;  adjust options;  dataRelation;
    var prosData = this.processedData = {};
    var statData = this.statisticData;
    var conf = this.defaults;
    var i, j, l, ll;
    var digitData;
    //data
    prosData.rowName = statData.rowName;
    prosData.rowSum = statData.rowSum;
    prosData.rowRatio = statData.rowRatio;
    prosData.columnName = statData.columnName;
    prosData.columnSum = statData.columnSum;
    prosData.columnRatio = statData.columnRatio;
    prosData.columnRatioMatrix = statData.columnRatioMatrix;
    prosData.totalSum = statData.totalSum;
    prosData.rawInfos = statData.rawInfos;

    digitData = statData.digitData.slice();

    //rowIndex(sort)
    digitData.forEach(function (d, i) {
      d.index = i;
      d.rowSum = prosData.rowSum[i];
    });
    (function () {
      var descending = function (a, b) {
        return b.rowSum - a.rowSum;
      }; 
      var insideOut = function (digitData, rowSum, reverse) {
        var getRowMaxIndex = function (array) {
          var i = 1,
            j = 0,
            v = array[0],
            k,
            n = array.length;
          for (; i < n; ++i) {
            if ((k = array[i]) > v) {
              j = i;
              v = k;
            }
          }
          return j;
        };
        var digitDataSort = [];
        var n = digitData.length,
            i,
            j,
            max = digitData.map(getRowMaxIndex),
            sums = rowSum,
            index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }),
            top = 0,
            bottom = 0,
            tops = [],
            bottoms = [];
        for (i = 0; i < n; ++i) {
          j = index[i];
          if (top < bottom) {
            top += sums[j];
            tops.push(j);
          } else {
            bottom += sums[j];
            bottoms.push(j);
          }
        }
        index = !reverse ? bottoms.reverse().concat(tops) : bottoms.concat(tops.reverse());

        for (i = 0; i < n; ++i) {
          digitDataSort[i] = digitData[index[i]];
        }
        return digitDataSort;
      };
      var rowDataOrderAdjust = function () {
        var rowName = prosData.rowName = [];
        var rowSum = prosData.rowSum = [];
        var rowRatio = prosData.rowRatio = [];
        var columnRatioMatrix = prosData.columnRatioMatrix = [];
        var rawInfos = prosData.rawInfos = [];
        var statRowName = statData.rowName;
        var statRowSum = statData.rowSum;
        var statRowRatio = statData.rowRatio;
        var statColumnRatioMatrix = statData.columnRatioMatrix;
        var statRawInfos = statData.rawInfos;

        digitData.forEach(function (d, i) {
          rowName[i] = statRowName[d.index];
          rowSum[i] = statRowSum[d.index];
          rowRatio[i] = statRowRatio[d.index];
          columnRatioMatrix[i] = statColumnRatioMatrix[d.index];
          rawInfos[i] = statRawInfos[d.index];
        });
      };
      var getProcessedRowIndex = function () {
        var rowIndex = [];
        digitData.forEach(function (d, i) {
          rowIndex[i] = $.extend({}, statData.rowIndex[d.index]);
          rowIndex[i].statisticData = d.index;
        });
        prosData.rowIndex = rowIndex;
      };
      switch (conf.order) {
        case 'reverse':
          digitData.reverse();
          rowDataOrderAdjust();
          break;
        case 'descending':
          digitData.sort(descending);
          rowDataOrderAdjust();
          break;
        case 'ascending':
          digitData.sort(descending).reverse();
          rowDataOrderAdjust();
          break;
        case 'inside-out':
          digitData = insideOut(digitData, prosData.rowSum);
          rowDataOrderAdjust();
          break;
        case 'inside-out-reverse':
          digitData = insideOut(digitData, prosData.rowSum, true);
          rowDataOrderAdjust();
          break;
        default:
      }
      getProcessedRowIndex();
    }());

    //allInfos
    (function (){
      var allInfos = [];
      var rowInfos = [];
      var columnInfos = [];
      var columnDescending = function (a, b) {
        return b.value - a.value;
      }; 
      //rowinfo
      for (i = 0, l = digitData.length; i < l; i++) {
        rowInfos[i] = {
          "rowName": prosData.rowName[i],
          "rowRatio": prosData.rowRatio[i],
          "rowSum": prosData.rowSum[i],
          "rowIndex": i
        };
      }
      //columninfo
      for (j = 0, ll = digitData[0].length; j < ll; j++) {
        columnInfos[j] = {
          "columnName": prosData.columnName[j],
          "columnRatio": prosData.columnRatio[j],
          "columnSum": prosData.columnSum[j],
          "columnIndex": j
        };
      }
      //allInfo
      for (i = 0, l = digitData.length; i < l; i++) {
        allInfos[i] = [];
        for (j = 0, ll = digitData[0].length; j < ll; j++) {
          allInfos[i][j] = {
            "value": digitData[i][j],
            "ratioInColumn": prosData.columnRatioMatrix[i][j], 
            "rowInfo": rowInfos[i],
            "columnInfo": columnInfos[j]
          };
        }
      }
      //allInfos rank in column
      for (j = 0, ll = digitData[0].length; j < ll; j++) {
        var column = [];
        for (i = 0, l = digitData.length; i < l; i++) {
          column[i] = {"value": digitData[i][j]};
          column[i].index = i;
        }
        column.sort(columnDescending);

        for (i = 0, l = column.length; i < l; i++) {
          allInfos[column[i].index][j].rank = i;
        }
      }
      prosData.allInfos = allInfos;
    }());

    //digitData(origin or normalized)
    prosData.digitData = conf.normalized ? prosData.columnRatioMatrix: digitData;
  };

  Stream.prototype.getSlicedData = function () {
    //get sliced data;  timeRange; more;  
    var slicedData = this.slicedData = {};
    var prosData = this.processedData;
    var digitData, allInfos, topRowRatioSum, rowIndex;
    var conf = this.defaults;
    var moreConfig = conf.moreConfig;
    var i, l;
    var that = this;
    //data
    //digitData, allInfos
    slicedData.digitData = digitData = prosData.digitData;
    slicedData.allInfos = allInfos = prosData.allInfos;
    slicedData.rowIndex = rowIndex = [];

    //time Range Slice
    var timeRangeSlice = function (data) {
      var tr = that.timeRange;
      var sliceData = [];
      if (tr[0] === 0 && tr[1] === digitData.length - 1) {
        return data;
      } else {
        data.forEach(function (d, i) {
          sliceData[i] = d.slice(tr[0], tr[1] + 1);
        });
        return sliceData;
      }
    };
    digitData = slicedData.digitData = timeRangeSlice(digitData);
    allInfos = slicedData.allInfos = timeRangeSlice(allInfos);

    //no more
    if (moreConfig.more !== true) {
      //rowIndex without more
      for (i = 0, l = prosData.rowIndex.length; i < l; i++) {
        rowIndex[i] = $.extend({}, prosData.rowIndex[i]);
        rowIndex[i].processedData = i;
      }
      return;
    }
    //more
    topRowRatioSum = slicedData.topRowRatioSum = [];
    var rowRatio = prosData.rowRatio;
    topRowRatioSum[0] = rowRatio[0];
    for (i = 1, l = rowRatio.length; i < l; i++) {
      topRowRatioSum[i] = topRowRatioSum[i - 1] + rowRatio[i];
    }
    //more's digitData and allInfos
    (function () {
      var rowStart = moreConfig.level * (moreConfig.max - 1),
        max = moreConfig.max,
        rowEnd,
        needMoreRow,
        i, j, l, k,
        moreSum,
        moreRowSum,
        moreRow = [],
        datas = [],
        infos = [],
        moreRowInfo = [];
  
      if (rowStart >= digitData.length) {
        //prevent level is too large after setSource;
        moreConfig.level = 0;
        rowStart = 0;
      }
      if (rowStart + max >= digitData.length) {
        rowEnd = digitData.length;
        needMoreRow = false;
      } else {
        rowEnd = rowStart + max - 1;
        needMoreRow = true;
      }
      for (i = rowStart; i < rowEnd; i++) {
        k = i - rowStart;
        datas[k] = digitData[i];
        infos[k] = allInfos[i];
      }
      if (needMoreRow) {
        moreRowSum = 1 - topRowRatioSum[rowEnd - 1];
        for (j = 0, l = digitData[0].length; j < l; j++) {
          moreSum = 0;
          for (i = digitData.length - 1; i >= rowEnd; i--) {
            moreSum += digitData[i][j];
          }
          moreRow[j] = moreSum;
          moreRowInfo[j] = {
            "value": moreSum,
            "ratioInColumn": moreSum / prosData.columnSum[j],
            "rowInfo": {
              "rowName": "more",
              "rowRatio": moreSum / moreRowSum,
              "rowSum": moreRowSum,
              "rowIndex": -1 // -1, clickable; -2, not clickable
            },
            "columnInfo": $.extend({}, allInfos[0][j])
          };
          moreRowInfo[j].columnInfo.columnRatio = moreSum / moreRowInfo[j].columnInfo.columnSum;
          //if (moreRowSum < this.userConfig.other) {
          if (moreRowSum < conf.moreConfig.other) {
             moreRowInfo[j].rowInfo.rowIndex = -2; // not clickable
          }
        }
        datas = [moreRow].concat(datas);
        infos = [moreRowInfo].concat(infos);
      }
      digitData = slicedData.digitData = datas;
      allInfos = slicedData.allInfos = infos;

      //row Index with more
      for (i = rowStart; i < rowEnd; i++) {
        k = i - rowStart;
        rowIndex[k] = $.extend({}, prosData.rowIndex[i]);
        rowIndex[k].processedData = i;
      }
      if (needMoreRow) {
        rowIndex = [{"processedData": "more"}].concat(rowIndex);
      }
      slicedData.rowIndex = rowIndex;
    }());
  };

  //options examples:
  //undefined, same as init without orderchange
  //{"type": "init", "order": [0,1,2,3,4,...]} ,init and change order
  //{"type": "changeOrder", "order": [0,1,2,3,4,...]}
  Stream.prototype.getDisplayData = function (options) {
    //get display data;  timeRange; more;
    var type = (options && options.type) || "init";
    var order = options && options.order;

    var displayData = this.displayData;
    var slicedData = this.slicedData;
    var digitData, allInfos, rowIndex;

    if (type === "init") {
      displayData.digitData = digitData = slicedData.digitData;
      displayData.allInfos = allInfos = slicedData.allInfos;
      displayData.rowIndex = rowIndex = [];
      if (typeof order === 'undefined') {
        d3.range(digitData.length).forEach(function (d, i) {
          rowIndex[i] = $.extend({}, slicedData.rowIndex[d]);
          rowIndex[i].slicedData = d;
        });
      } else {
        if (order.length !== digitData.length) {
          throw new Error("order's length is different from row number");
        } else {
          digitData = [];
          allInfos = [];
          order.forEach(function (d, i) {
            digitData[i] = slicedData.digitData[d];
            allInfos[i] = slicedData.allInfos[d];
            rowIndex[i] = $.extend({}, slicedData.rowIndex[d]);
            rowIndex[i].slicedData = d;
          });
          displayData.digitData = digitData;
          displayData.allInfos = allInfos;
        }
      }
    } else {//changeOrder
      digitData = displayData.digitData.slice();
      allInfos = displayData.allInfos.slice();
      rowIndex = displayData.rowIndex.slice();
      order.forEach(function (d, i) {
        digitData[i] = displayData.digitData[d];
        allInfos[i] = displayData.allInfos[d];
        rowIndex[i] = displayData.rowIndex[d];
      });
      displayData.digitData = digitData;
      displayData.allInfos = allInfos;
      displayData.rowIndex = rowIndex;
    }
  };

  Stream.prototype.setSource = function (source, rawInfos) {
    this.rawData = {};
    this.getRawData(source);
    if (rawInfos) {
      this.getRawData(rawInfos, true);
    }
    this.timeRange = [0, this.rawData.columnName.length - 1];
    this.date = this.rawData.columnName;
  };

  //if useSting is true, start and end are date string, else start and end are index number;
  Stream.prototype.setTimeRange = function (start, end, useString) {
    var idx1, idx2, temp;
    if (useString) {
      idx1 = this.date.indexOf(start);
      if (idx1 === "") {
        throw new Error(start + " is not found");
      }
      idx2 = this.date.indexOf(end);
      if (idx2 === "") {
        throw new Error(end + " is not found");
      }
    } else {
      idx1 = start;
      idx2 = end;
    }
    if (idx1 > idx2) {
      temp = idx1;
      idx1 = idx2;
      idx2 = temp;
    }
    if (idx1 === idx2) {
      throw new Error("start index and end index can not be same.");
    }
    if (idx2 > this.date.length - 1) {
      throw new Error("start index or end index is beyond the time range.");
    }
    this.timeRange = [idx1, idx2];
    this.getLevelSource();
  };

  Stream.prototype.getDataByTimeRange = function () {
    if (this.timeRange[0] === 0 && this.timeRange[1] === this.date.length - 1) {
      return this.digitData;
    } else {
      var tr = this.timeRange;
      return this.digitData.map(function (d) {
        return d.slice(tr[0], tr[1] + 1);
      });
    }
  };

  Stream.prototype.getColor = function (colorJson) {
    var colorMatrix = DataV.getColor();
    var color;
    var colorStyle = colorJson || {};
    var colorMode = colorStyle.mode || 'default';
    var i, l;

    switch (colorMode) {
    case "gradient":
      l = this.source.length;
      // 最大为 colorMatrix.length - 1
      var colorL = Math.min(Math.round(l / 5), colorMatrix.length - 1);
      var testColor = [colorMatrix[0][0], colorMatrix[colorL][0]];
      var test1 = DataV.gradientColor(testColor, "special");
      var testColorMatrix = [];
      var testColorMatrix1 = [];
      for (i = 0; i < l; i++) {
        testColorMatrix.push([test1(i / (l - 1)), test1(i / (l - 1))]);
      }

      for (i = l - 1; i >= 0; i--) {
        testColorMatrix1.push(testColorMatrix[i]);
      }
      colorMatrix = testColorMatrix;
      break;
    case "random":
    case "default":
      break;
    }

    var ratio = colorStyle.ratio || 0;
    ratio = Math.max(ratio, 0);
    ratio = Math.min(ratio, 1);

    var colorArray = colorMatrix.map(function () {
      return d3.interpolateRgb.apply(null, [colorMatrix[i][0], colorMatrix[i][1]])(ratio);
    });
    color = d3.scale.ordinal().range(colorArray);

    return color;
  };

getColor

方法 Stream.prototype.getColor() getColor
代码
Stream.prototype.getColor = function () {
    var count = this.layoutData ? this.layoutData.length : this.labels.length;
    var color = this.defaults.gradientColor || ["#8be62f", "#1F4FD8"];
    var gradientColor = DataV.gradientColor(color, "special");
    var percent = 1 / count;
    var gotColors = [];

    for (var i = 0; i < count; i++) {
      gotColors.push(gradientColor(i * percent));
    }

    var midderNum = Math.round(count / 2);
    return function (num) {
      return num % 2 === 0 ? gotColors[midderNum + num / 2] : gotColors[midderNum - (num + 1) / 2];
    };
  };

  Stream.prototype.generatePaths = function () {};

  Stream.prototype.createLegend = function () {};

  Stream.prototype.createNavi = function () {};

  Stream.prototype.getMaxPercentage = function () {};

  Stream.prototype.createPercentage = function () {};

  Stream.prototype.createStreamPaths = function () {};

  Stream.prototype.createAxis = function () {};

  Stream.prototype.getMaxY = function () {
    return d3.max(this.source, function (d) {
      return d3.max(d, function (d) {
        return d.y0 + d.y;
      });
    });
  };

  Stream.prototype.mapPathSource = function () {
    var conf = this.defaults,
      maxX = this.source[0].length - 1,//this.digitData[0].length - 1,
      maxY = this.getMaxY(), 
      width = conf.chartWidth,
      height = conf.height;
    var i, j, l, l2, s, ps;
    this.pathSource = [];
    for (i = 0, l = this.source.length; i < l; i++) {
      this.pathSource[i] = [];
      for (j = 0, l2 = this.source[0].length; j < l2; j++) {
        s = this.source[i][j];
        ps = this.pathSource[i][j] = {};
        ps.x = s.x * width / maxX;
        ps.y0 = height - s.y0 * height / maxY;
        ps.y = s.y * height / maxY;
      }
    }
  };

  Stream.prototype.generateArea = function () {
    this.mapPathSource();
    var area = d3.svg.area().x(function (d) {
        return d.x;
      }).y0(function (d) {
        return d.y0;
      }).y1(function (d) {
        return d.y0 - d.y; 
      });
    return area;
  };

  Stream.prototype.clearCanvas = function () {};

  Stream.prototype.reRender = function () {
    this.clearCanvas();
    this.layout();
    this.generatePaths();
    this.canAnimate = true;
  };

  Stream.prototype.processData = function (stage) {
    switch (stage) {
      case undefined:
      case "statisticData":
        this.getStatisticData();
        // break;
      case "processedData":
        this.getProcessedData();
        // break;
      case "slicedData":
        this.getSlicedData();
        // break;
      case "displayData":
        this.getDisplayData();
        // break;
      default:
        break;
    }
  };

  Stream.prototype.createComponents = function () {
    var conf = this.defaults,
      canvasFatherContainer;

    //components height and width compute
    if (conf.moreConfig.more) {
      conf.showNavi = true;
      conf.showPercentage = true;
    } else {
      conf.showNavi = false;
      conf.showPercentage = false;
    }
    if (!conf.showLegend) {
      conf.legendBesidesWidth = conf.width;
      conf.legendBesidesHeight = conf.height;
    } else {
      if (conf.legendPosition === "left") {
        conf.legendBesidesWidth = conf.width - conf.leftLegendWidth;
        conf.legendBesidesHeight = conf.height;
      } else {
        conf.legendBesidesWidth = conf.width;
        conf.legendBesidesHeight = conf.height - conf.topLegendHeight;
      }
    }
    conf.chartWidth = conf.legendBesidesWidth - 2 * conf.percentageWidth;
    conf.chartHeight = conf.legendBesidesHeight - (conf.showNavi ? conf.naviHeight : 0)
        - (conf.showAxis ? conf.axisHeight : 0);

    this.node.style.position = "relative";
    this.node.style.width = conf.width + "px";

    this.canvasContainer = document.createElement("div");
    canvasFatherContainer = document.createElement("div");
    $(this.canvasContainer).css({
      "position": "relative",
      "float": "left",
      "width": conf.chartWidth + "px",
      "height": conf.chartHeight + "px",
      "margin-bottom": "0px",
      "border-bottom": "0px",
      "padding-bottom": "0px"
    }).append($(canvasFatherContainer).css({"position": "relative"}));

    //chart and paper
    this.chart = new StreamChart(canvasFatherContainer, {"width": conf.chartWidth, "height": conf.chartHeight});
    this.chart.getColor = this.getColor;
    this.chart.defaults.gradientColor = this.defaults.gradientColor;
    this.paper = this.chart.paper;

    this.legendContainer = document.createElement("div");
    this.legendBesidesContainer = document.createElement("div");

    //legend
    this.legend = conf.legendPosition === "left" ? new Legend(this, this.legendContainer)
      : new TopLegend(this, this.legendContainer);
    
    //aixs
    this.axisContainer = document.createElement("div");
    this.axis = new Axis(this, this.axisContainer);

    //percentage
    this.percentageContainer = document.createElement("div");
    this.percentage = new Percentage(this, this.percentageContainer);

    //navi
    this.naviContainer = document.createElement("div");
    this.navi = new Navi(this, this.naviContainer);

    //hoverLine
    this.hoverLine = new HoverLine(this);

    //pathLabel
    this.pathLabel = new PathLabel(this);

    //floatTag
    this.floatTag = new FloatTag(canvasFatherContainer);
        
    // cover can block stream paper when animating to prevent some default mouse event
    this.coverContainer = document.createElement("div");
    this.cover = new Cover(this, this.coverContainer);

    this.legendBesidesContainer.appendChild(this.naviContainer);
    this.middleContainer = document.createElement("div");
    $(this.middleContainer).css({
      "height": conf.chartHeight
    });
    this.middleContainer.appendChild(this.percentageContainer);
    this.middleContainer.appendChild(this.canvasContainer);

    this.canvasContainer.appendChild(this.coverContainer);

    this.legendBesidesContainer.appendChild(this.middleContainer);
    this.legendBesidesContainer.appendChild(this.axisContainer);

    if (conf.legendPosition === "left") {
      this.node.appendChild(this.legendBesidesContainer);
      this.node.appendChild(this.legendContainer);
      $(this.legendBesidesContainer).css({
        "float": "right",
        "width": conf.legendBesidesWidth
      });
      $(this.legendContainer).css({
        "width": conf.leftLegendWidth - 4 + "px",
        "overflow-x": "hidden"
      });
    } else {
      this.node.appendChild(this.legendContainer);
      this.node.appendChild(this.legendBesidesContainer);
      $(this.legendBesidesContainer).css({"width": conf.legendBesidesWidth});
      $(this.legendContainer).css({"width": conf.leftLegendWidth + "px"});
    }
  };

  Stream.prototype.renderComponents = function (animate) {
    this.chart.setSource(this.displayData.digitData);
    this.chart.render(animate);// animate if animate === "animate"

    if (this.defaults.pathLabel) {
      this.pathLabel.render();
    }

    this.hoverLine.render();//lines should be to front, so at last

    this.axis.render();
    this.percentage.render();
    this.navi.render();
    //legend should be after legend besides to get right height
    this.legend.render();
  };

  Stream.prototype.createInteractive = function () {
    this.paths = this.chart.paths;
    $(this.paper.canvas).unbind();//prevent event rebind.

    //refactor stream chart's animate function, especially change the callback
    var stream = this;
    this.chart.animateCallback = function () {
      var newOrderPaths = [];
      var that = this;
      if (typeof this.defaults.animateOrder !== 'undefined') {
        this.defaults.animateOrder.forEach(function (d, i) {
          newOrderPaths[i] = that.paths[d];
        });
        this.paths = newOrderPaths;
      }

      stream.cover.hidden();
      if (typeof stream.cover.mouse !== 'undefined') {
        stream.hoverLine.show();
        stream.floatTag.show();
        $(stream.paper.canvas).trigger("mousemove",
            [stream.cover.mouse.x, stream.cover.mouse.y]);
        $(stream.floatTag).trigger("mousemove",
            [stream.cover.mouse.x, stream.cover.mouse.y]);
        stream.cover.mouse = undefined;
      }

      stream.pathLabel.show();
    };

    //chart mouseenter
    var mouseenter = function (e) {
      var stream = e.data.stream;
      stream.hoverLine.show();
      stream.floatTag.show();
      stream.axis.showTab();
    };

    //chart mouseleave
    var mouseleave = function (e) {
      var stream = e.data.stream;

      stream.hoverLine.hidden();
      stream.floatTag.hidden();

      stream.axis.hideTab();
      //recover prepath;
      if (typeof stream.prePath !== 'undefined') {
        stream.prePath.attr({"opacity": 1, "stroke-width": 1});
        // set legend
        stream.legend.lowlight(stream.prePath.index);
        stream.prePath = undefined;
      }
    };

    //chart click
    var click = function (e) {
      var stream = e.data.stream;
      var count = stream.paths.length;
      var animateCallback = function () {
        count -= 1;
        if (count > 0) {
          return;
        }
        stream.cover.hidden();
        if (typeof stream.cover.mouse !== 'undefined') {
          stream.hoverLine.show();
          stream.floatTag.show();
          var canvas = $(stream.paper.canvas);
          var cover = stream.cover;
          canvas.trigger("mousemove", [cover.mouse.x, cover.mouse.y]);
          canvas.trigger("mousemove", [cover.mouse.x, cover.mouse.y]);
          cover.mouse = undefined;
        }
        stream.pathLabel.show();
      };

      //more expand
      var path = stream.prePath;
      if (typeof path !== 'undefined' && path.index === 0 && stream.getDisplayRowInfo(path.index).rowIndex === -1) {
        stream.defaults.moreConfig.level += 1;
        stream.cover.show();
        stream.cover.mouse = {x: e.pageX, y: e.pageY};
        //redraw
        stream.processData("slicedData");
        stream.render("renderComponents");

        //hidden
        stream.hoverLine.hidden();
        stream.floatTag.hidden();

        stream.pathLabel.hidden();
        stream.paths.forEach(function (d) {
          d.attr({transform: "s1,0.001,0," + stream.defaults.chartHeight});
          d.animate({transform: "t0,0"}, 750, "linear", animateCallback);
        });
      }

      //drop
      if (typeof stream.prePath !== 'undefined' && stream.prePath.index > 0) {
        (function (index) {
          var order = d3.range(stream.displayData.digitData.length);
          order.forEach(function (d, i, array) {
            if (i === 0) {
              array[i] = index;
            } else if (i <= index) {
              array[i] = i - 1;
            }
          });

          stream.cover.show();
          stream.cover.mouse = {x: e.pageX, y: e.pageY};

          //stream.displayDataDropReorder(stream.prePath.index);
          stream.getDisplayData({"type": "changeOrder", "order": order});
          stream.chart.setOptions({"animateOrder": order});
          stream.render("renderComponents", "animate");

          stream.pathLabel.hidden();

        }(stream.prePath.index));
      }
    };

    //chart mousemove
    var mousemove = function (e, pageX, pageY) {
      var stream = e.data.stream;
      var offset = $(this).parent().offset();
      var x = (e.pageX || pageX) - offset.left,
        y = (e.pageY || pageY) - offset.top;
      var path,
        pathSource = stream.pathSource,
        pathIndex;
      var xIdx = Math.floor((x / (stream.defaults.chartWidth / (stream.chart.source[0].length - 1) / 2) + 1) / 2);
      var lineX;
      //get path and pathIndex
      pathSource = stream.chart.pathSource;
      for (var i = 0, l = pathSource.length; i < l; i++) {
        if (y >= pathSource[i][xIdx].y0 - pathSource[i][xIdx].y && y <= pathSource[i][xIdx].y0) {
          path = stream.chart.paths[i];
          pathIndex = i;
          break;
        }
      }
      if (typeof path === 'undefined') {
        return;
      }

      //recover prepath;
      if (typeof stream.prePath !== 'undefined') {
        stream.prePath.attr({"opacity": 1, "stroke-width": 1});
        // set legend
        stream.legend.lowlight(stream.prePath.index);
      }
      //change new path;
      stream.prePath = path;
      stream.prePath.index = pathIndex;
      path.attr({"opacity": 0.5, "stroke-width": 0});

      // set legend
      stream.legend.highlight(stream.prePath.index);
      //set indicator and highlight line new position
      stream.hoverLine.refresh(xIdx, pathIndex);

      //set floatTag content
      stream.floatTag.setContent(stream.getFloatTagContent(stream.displayData.allInfos[i][0]));
      //axis pop bubble
      lineX = stream.defaults.chartWidth * xIdx / (stream.chart.source[0].length - 1);
      stream.axis.refreshTab(stream.date[xIdx + stream.timeRange[0]], lineX + stream.defaults.percentageWidth);

      //customevent;
      if (stream.defaults.customEventHandle.mousemove) {
        stream.defaults.customEventHandle.mousemove.call(stream,
          {"timeIndex": xIdx, "pathIndex": pathIndex});
      }
    };
    var canvas = $(this.paper.canvas);
    canvas.bind("mouseenter", {"stream": this}, mouseenter)
      .bind("mouseleave", {"stream": this}, mouseleave)
      .bind("click", {"stream": this}, click)
      .bind("mousemove", {"stream": this}, mousemove);
  };

  Stream.prototype.getFloatTagContent = function (info) {
    return "<b>" + info.rowInfo.rowName + "</b><br/>" +
          "<b>" + (Math.floor(info.ratioInColumn * 1000) / 100) + "%</b>";
  };

  Stream.prototype.getDisplayRowInfo = function (index) {
    return this.displayData.allInfos[index][0].rowInfo;
  };
  
  Stream.prototype.getDisplayColumnInfo = function (index) {
    return this.displayData.allInfos[index][0].columnInfo;
  };
  
  Stream.prototype.render = function (stage, animate) {
    switch (stage) {
      case undefined:
      case "processData":
        this.processData();
        // break;
      case "createComponents":
        //clear old components, layout, create, css
        this.node.innerHTML = '';
        this.createComponents();
        // break;
      case "renderComponents":
        //clear old component content, render new content
        this.clearCanvas();
        this.renderComponents(animate);
        // break;
      case "createInteractive":
        this.createInteractive();
        // break;
      default:
        break;
    }
  };

  Stream.prototype.resize = function () {};
    
  Stream.prototype.setCustomEvent = function (eventName, callback) {
    if (typeof this.defaults.customEventHandle[eventName] !== 'undefined') {
      this.defaults.customEventHandle[eventName] = callback;
    }
  };

  Stream.prototype.animate = function (options, timeDuration) {
    //must after render if new Source has been set;
    if (!this.canAnimate) {
      throw new Error("Function animate must be called after render if new Source has been set.");
    }
    var time = 0;
    if (arguments.length > 1) {
      time = timeDuration;
    }

    if (options.offset || options.order) {
      this.source = this.remapSource(this.digitData);
      this.layout();
    }
    var area = this.generateArea();
    var color = this.getColor();
    for (var i = 0, l = this.source.length; i < l; i++) {
      var _area = area(this.source[i]);
      var anim = Raphael.animation({path: _area, fill: color(i)}, time);
      this.paths[i].animate(anim);
    }
  };

charts/stream: API索引


类型纬度

代码
this.dimension.type = {
          type: "string",
          required: true,
          index: 1
      };

时间纬度

代码
this.dimension.x = {
          type: "string",
          required: true,
          index: 0
      };

值纬度

代码
this.dimension.value = {
          type: "number",
          required: true,
          index: 2
      };

      this.defaults.width = 500;
      this.defaults.height = 300;
      this.defaults.offset = "expand";//zero, expand, silhou-ette, wiggle;
      this.defaults.order = "default";//default, reverse, inside-out //in this Stream application, it will always be default, the real order is adjusted in Stream's data-process.
      this.defaults.animateDuration = 750;
      this.defaults.animateOrder = undefined;
      this.paths = undefined;
      this.source = undefined;
      this.layoutData = undefined;
      this.pathSource = undefined; 
      this.setOptions(options);
      this.createPaper();
    }
  });

  Stream.prototype.createPaper = function () {
    var conf = this.defaults;
    this.paper = new Raphael(this.node, conf.width, conf.height);
  };

  Stream.prototype.setSource = function (source, map) {
    this.map(map);
    this.rawData = source;
    this.rawMap = map;
    var that = this;
    // 按类型分组
    var grouped = _.groupBy(source, this.mapping.type);
    this.rowCount = _.keys(grouped).length;
    this.columnCount = _.keys(_.groupBy(source, this.mapping.x)).length;
    // 组内按横轴排序
    _.forEach(grouped, function (group, type) {
      grouped[type] = _.sortBy(group, that.mapping.x);
    });
    this.sorted = _.sortBy(grouped, function (group) {
      return 0 - DataV.sum(group, that.mapping.value);
    });

    this.remaped = this.remapSource();
    this.layoutData = this.getLayoutData();
  };

  Stream.prototype.remapSource = function () {
    var sorted = this.sorted;
    var remap = [];
    for (var j = 0; j < this.columnCount; j++) {
      var plucked = _.pluck(sorted, j);
      var sum = DataV.sum(plucked, this.mapping.value);
      for (var i = 0; i < this.rowCount; i++) {
        remap[i] = remap[i] || [];
        remap[i][j] = {};
        remap[i][j].x = j;
        var rate = sorted[i][j][this.mapping.value] / sum;
        remap[i][j].y = rate;
        sorted[i][j].rate = rate;
      }
    }
    return remap;
  };

getColor

方法 Stream.prototype.getColor() getColor
代码
Stream.prototype.getColor = function () {
    var count = this.layoutData.length;
    var color = this.defaults.gradientColor || ["#8be62f", "#1F4FD8"];
    var gradientColor = DataV.gradientColor(color, "special");
    var percent = 1 / count;
    var gotColors = [];

    for (var i = 0; i < count; i++) {
      gotColors.push(gradientColor(i * percent));
    }
    var midderNum = Math.floor(count / 2);
    return function (num) {
      return num % 2 === 0 ? gotColors[midderNum + num / 2] : gotColors[midderNum - (num + 1) / 2];
    };
  };

  Stream.prototype.getMaxY = function () {
    return d3.max(this.layoutData, function (d) {
      return d3.max(d, function (d) {
        return d.y0 + d.y;
      });
    });
  };

  Stream.prototype.mapPathSource = function () {
    var conf = this.defaults,
      maxX = this.layoutData[0].length - 1,
      maxY = this.getMaxY(), 
      width = conf.chartWidth,
      height = conf.chartHeight;

    this.pathSource = [];
    for (var i = 0, l = this.layoutData.length; i < l; i++) {
      this.pathSource[i] = [];
      for (var j = 0, l2 = this.layoutData[0].length; j < l2; j++) {
        var s = this.layoutData[i][j];
        var ps = this.pathSource[i][j] = {};
        ps.x = s.x * width / maxX;
        ps.y0 = height - s.y0 * height / maxY;
        ps.y = s.y * height / maxY;
      }
    }
  };

  Stream.prototype.generateArea = function () {
    this.mapPathSource();
    var area = d3.svg.area().x(function (d) {
      return d.x;
    }).y0(function (d) {
      return d.y0;
    }).y1(function (d) {
      return d.y0 - d.y; 
    });
    return area;
  };

  Stream.prototype.highlight = function (index) {
    if (typeof index !== 'undefined') {
      this.paths[index].attr({"opacity": 0.5, "stroke-width": 0});
    }
  };
  Stream.prototype.lowlight = function (index) {
    if (typeof index !== 'undefined') {
      this.paths[index].attr({"opacity": 1, "stroke-width": 1});
    }
  };

  Stream.prototype.createInteractive = function () {
    $(this.paper.canvas).unbind();//prevent event rebind.

    //refactor stream chart's animate function, especially change the callback
    var stream = this;
    this.animateCallback = function () {
      var newOrderPaths = [];
      var that = this;
      if (typeof this.defaults.animateOrder !== 'undefined') {
        this.defaults.animateOrder.forEach(function (d, i) {
          newOrderPaths[i] = that.paths[d];
        });
        this.paths = newOrderPaths;
      }

      stream.cover.hidden();
      if (typeof stream.cover.mouse !== 'undefined') {
        stream.hoverLine.show();
        stream.floatTag.show();
        var mouse = stream.cover.mouse;
        $(stream.paper.canvas).trigger("mousemove", [mouse.x, mouse.y]);
        $(stream.floatTag).trigger("mousemove", [mouse.x, mouse.y]);
        stream.cover.mouse = undefined;
      }

      stream.pathLabel.show();
    };

    //chart mouseenter
    var mouseenter = function () {
      stream.hoverLine.show();
      stream.fire('enter');
    };

    //chart mouseleave
    var mouseleave = function () {
      stream.hoverLine.hidden();
      //recover prepath;
      if (typeof stream.preIndex !== 'undefined') {
        stream.lowlight(stream.preIndex);
      }
      stream.fire('leave', stream.preIndex);
      stream.preIndex = undefined;
    };

    //chart click
    var click = function () {};

    //chart mousemove
    var mousemove = function (e, pageX, pageY) {
      var offset = $(this).parent().offset();
      var x = (e.pageX || pageX) - offset.left,
        y = (e.pageY || pageY) - offset.top;
      var pathSource = stream.pathSource,
        rowIndex;
      var columnIndex = Math.floor((x / (stream.defaults.chartWidth / (stream.columnCount - 1) / 2) + 1) / 2);
      //get path and pathIndex
      for (var i = 0, l = pathSource.length; i < l; i++) {
        if (y >= pathSource[i][columnIndex].y0 - pathSource[i][columnIndex].y && y <= pathSource[i][columnIndex].y0) {
          rowIndex = i;
          break;
        }
      }

      //recover prepath;
      if (typeof stream.preIndex !== 'undefined') {
        stream.lowlight(stream.preIndex);
      }
      stream.highlight(rowIndex);

      stream.fire('move', stream.preIndex, rowIndex, columnIndex);
      //set indicator and highlight line new position
      stream.hoverLine.refresh(columnIndex, rowIndex);
      //customevent;
      if (stream.defaults.customEventHandle.mousemove) {
        stream.defaults.customEventHandle.mousemove.call(stream,
          {"timeIndex": columnIndex, "rowIndex": rowIndex});
      }
      //change new path;
      stream.preIndex = rowIndex;
    };
    $(this.paper.canvas).bind("mouseenter", mouseenter)
      .bind("mouseleave", mouseleave)
      .bind("click", click)
      .bind("mousemove", mousemove);
  };

  return Stream;
});

charts/stream_axis: API索引


时间纬度

代码
this.dimension.x = {
        type: "string",
        required: true,
        index: 0
      };
    }
  });

  Axis.prototype.setSource = function (source, map) {
    map = this.map(map);
    this.grouped = _.groupBy(source, map.x);
    this.axis = _.keys(this.grouped);
    this.range = [0, this.axis.length - 1];
  };

  Axis.prototype.init = function () {
    var conf = this.defaults;
    this.paper = new Raphael(this.node[0], conf.legendBesidesWidth, conf.axisHeight);
    this.node.css({
      "margin-top": "0px",
      "border-top": "1px solid #ddd",
      "height": conf.axisHeight + "px"
    });
  };

  Axis.prototype.render = function () {
    this.init();
    this.clear();
    //all date strings' format are same, string length are same 
    var conf = this.defaults,
      that = this;
    var getPopPath = function (El) {
        //down pop
        var x = 0,
          y = 0,
          size = 4,
          cw = 23,
          bb = {height: 8};
        if (El) {
          bb = El.getBBox();
          bb.height *= 0.6;
          cw = bb.width / 2 - size;
        }
        return [
          'M', x, y,
          'l', size, size, cw, 0,
          'a', size, size, 0, 0, 1, size, size,
          'l', 0, bb.height,
          'a', size, size, 0, 0, 1, -size, size,
          'l', -(size * 2 + cw * 2), 0,
          'a', size, size, 0, 0, 1, -size, -size,
          'l', 0, -bb.height,
          'a', size, size, 0, 0, 1, size, -size,
          'l', cw, 0,
          'z'
        ].join(',');
      };
    var left = conf.percentageWidth,
      right = conf.legendBesidesWidth - conf.percentageWidth;
    var tempWord = this.paper.text(0, 0, this.axis[0]);
    var tickNumber = Math.floor((right - left) / tempWord.getBBox().width / 2) + 1;
    tempWord.remove();

    this.dateScale = d3.scale.linear()
      .domain([0, this.axis.length - 1])
      .range([left, right]);
    DataV.Axis().scale(this.dateScale)
      .ticks(tickNumber)
      .tickSize(6, 3, 3)
      .tickAttr({"stroke": "none"})
      .minorTickAttr({"stroke": "none"})
      .domainAttr({"stroke": "none"})
      .tickFormat(function (d) {
        return that.axis[d] || "";
      })(this.paper);

    this.axisPopText = this.paper.text(0, 11, this.axis[0])
      .attr({
        "text-anchor": "middle",
        "fill": "#fff",
        "transform": "t" + left + ",0"
      }).hide();
    this.axisPopBubble = this.paper.path(getPopPath(this.axisPopText))
      .attr({
        "fill": "#000",
        "transform": "t" + (-10000) + ",0"
      }).toBack()
      .hide();
  };
  Axis.prototype.hideTab = function () {
    this.axisPopText.hide();
    this.axisPopBubble.hide();
  };
  Axis.prototype.showTab = function () {
    this.axisPopText.show();
    this.axisPopBubble.show();
  };
  Axis.prototype.refreshTab = function (index) {
    var conf = this.defaults;
    var x = conf.chartWidth * index / (this.axis.length - 1);
    var transX = x + this.defaults.percentageWidth;
    this.axisPopText.attr({
      "text": this.axis[index + this.range[0]]
    }).transform("t" + transX + ",0");
    this.axisPopBubble.transform("t" + transX + ",0");
  };
  Axis.prototype.clear = function () {
    this.paper.clear();
  };
  return Axis;
});

charts/tip: API索引


类型纬度

代码
this.dimension.type = {
        type: "string",
        required: true,
        index: 1
      };

时间纬度

代码
this.dimension.x = {
        type: "string",
        required: true,
        index: 0
      };

值纬度

代码
this.dimension.value = {
        type: "number",
        required: true,
        index: 2
      };
    },
    getContent: function (obj) {
      return obj[this.mapping.x];
    }
  });
  Tip.prototype.setSource = function (source, map) {
    var that = this;
    this.map(map);
    this.rawData = source;
    this.groupedByX = _.groupBy(source, this.mapping.x);
    this.groupedByType = _.groupBy(source, this.mapping.type);
    var sorted = _.sortBy(this.groupedByType, function (group) {
      return -DataV.sum(group, that.mapping.value);
    });
    this.sorted = sorted;
    _.each(sorted, function (list, index) {
      that.groupedByType[list[0][that.mapping.type]].finalRank = index + 1;
    });
    this.axis = _.keys(this.groupedByX);
  };

  Tip.prototype.render = function () {
    this.hidden();
    this.node.css(this.defaults.tipStyle);
  };

  Tip.prototype.setContent = function (rowIndex, columnIndex) {
    var that = this;
    var conf = this.defaults;
    var getContent = conf.getContent || this.getContent;
    var column = this.groupedByX[this.axis[columnIndex]];
    var values = this.sorted;//_.values(this.groupedByType);
    var types;
    if (!conf.more) {
      types = values;
    } else {
      types = DataV.more(values, conf.level, conf.max, function (remains) {
        var row = [];
        for (var i = 0; i < that.axis.length; i++) {
          var col = {};
          col[that.mapping.type] = conf.moreLabel;
          col[that.mapping.x] = that.axis[i];
          col[that.mapping.value] = NaN;// DataV.sum(_.pluck(remains, i), that.mapping.value);
          col.rate = DataV.sum(_.pluck(remains, i), "rate");
          row.push(col);
        }
        return row;
      });
    }
    var row = types[rowIndex];
    var obj = row[columnIndex];

    var index = _.indexOf(_.map(column, function (item) {
      return item[that.mapping.value];
    }).sort(function (a, b) {
      return a > b ? -1 : 1;
    }), obj[that.mapping.value]);
    obj.rank = index === -1 ? NaN : index + 1;
    var html = getContent.call(this, obj);
    this.node.html(html);
  };

  return Tip;
});

charts/tree: API索引


Tree

Tree的构造函数

Examples

var tree = new Tree("container");
tree.setSource(source);
tree.render();

Options

  • width: 画布的宽度
  • height: 画布的高度
声明 Tree Tree
代码
var Tree = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Tree";
            this.node = this.checkContainer(node);

            this.addlink = {};

            // Properties
            this.treeDepth = 0;
            this.font = {};

            // Canvas
            this.defaults.width = 750;
            this.defaults.height = 760;
            this.defaults.deep = 180;
            this.defaults.radius = 15;

            this.setOptions(options);
            this.createCanvas();
        }
    });

dimension

饼图纬度描述

属性 Tree.dimension Tree dimension
代码
Tree.dimension = {};

ID标签

代码
Tree.dimension.id = {
        type: "string",
        required: true
    };

父ID标签

代码
Tree.dimension.pid = {
        type: "string",
        required: true
    };

    Tree.prototype.hierarchyTableToJson = function (table) {
        if (table[0][0] === "ID") {
            table = table.slice(1);
        }

        var rootID;
        var hierarchy = {};
        var addlink = {}; //for multi-fathernode
        // var ids = _.pluck(table, 0);
        // var pids = _.pluck(table, 3);
        // var roots = _.difference(pids, ids);
        // if (roots.length === 0) {
        //     throw new Error("root node is empty");
        // } else if (roots.length > 1) {
        //     throw new Error("root nodes are too many");
        // }

        table.forEach(function (d, i) {
            if (d[0] === "") {
                throw new Error("ID can not be empty(line:" + (i + 1) + ").");
            }
            if (!d[3]) {
                if (rootID) {
                    throw new Error("2 or more lines have an empty parentID(line:" + (i + 1) + ").");
                } else {
                    rootID = d[0];
                }
            }
            if (hierarchy[d[0]]) {
                throw new Error("2 or more lines have same ID: " + d[0] + "(line:" + (i + 1) + ").");
            }

            var value = "";
            var j, length;
            if (d.length > 4) {
                for (j = 4, length = d.length; j < length; j++) {
                    if (j < length - 1) {
                        value = value + d[j] + ",";
                    } else {
                        value = value + d[j];
                    }
                }
            }
            hierarchy[d[0]] = {name: d[1], size: d[2], child: [], id: d[0], value: value};
        });
        if (!rootID) {
            throw new Error("No root node defined.");
        }
        table.forEach(function (d, i) {
            if (d[3]) {
                var record;
                var ids = d[3].split(',');
                if (ids.length === 1) {
                    record = hierarchy[d[3]];
                    record.child.push(d[0]);
                } else {
                    record = hierarchy[ids[0]];
                    record.child.push(d[0]);
                    addlink[d[0]] = {child: [], path: [], pnode: []};

                    var j, length;
                    for (j = 1, length = ids.length; j < length;  j++) {
                        addlink[d[0]].child.push(ids[j]);
                    }
                }
                if (!record) {
                    throw new Error("Can not find parent with ID " + d[3] + "(line:" + (i + 1) + ").");
                }
            }
        });

        this.addlink = addlink;

        var recurse = function (rootID) {
            var record = hierarchy[rootID];
            if (record.child.length === 0) {
                if (isNaN(parseFloat(record.size))) {
                    throw new Error("Leaf node's size is not a number(ID:" + (rootID + 1) + ").");
                } else {
                    return {
                        name: record.name,
                        size: record.size,
                        num: record.id,
                        children: null,
                        draw: false,
                        value: record.value
                    };
                }
            } else {
                var childNode = [];
                record.child.forEach(function (d) {
                    childNode.push(recurse(d));
                });
                return {name: record.name, children: childNode, num: record.id, draw: false, value: record.value};
            }
        };

        return recurse(rootID);
    };

    Tree.prototype.setSource = function (source) {
        var conf = this.defaults;

        this.rawData = this.hierarchyTableToJson(source);
        this.source = this.remapSource(source);
        
        this.source.x0 = conf.width / 2;
        this.source.y0 = conf.radius * 10;

        this.source.children.forEach(function collapse(d) {
            if (d.children) {
                // d._children = d.children;
                // d._children.forEach(collapse);
                // d.children = null;
                d._children = null;
                d.children.forEach(collapse);
            }
        });
    };

    Tree.prototype.remapSource = function (data) {
        return this.hierarchyTableToJson(data);
        // return data;
    };

    Tree.prototype.layout = function () {
        var conf = this.defaults;
        var tree = d3.layout.tree()
            .size([conf.width, conf.height]);

        this.nodesData = tree.nodes(this.source);

        var treedepth = 0;
        var id = 0;

        this.nodesData.forEach(function (d) {
            if (d.depth > treedepth) {
                treedepth = d.depth;
            }
        });

        this.treeDepth = treedepth;
        conf.deep = conf.height / (treedepth + 1);

        this.nodesData.forEach(function (d) {
            d.y = conf.radius * 3 + d.depth * conf.deep;
            d.id = id;
            id++;
        });
    };

    Tree.prototype.getColor = function () {
        var colorMatrix = DataV.getColor();
        var color;
        if (colorMatrix.length > 1 && colorMatrix[0].length > 1) {
            color = [colorMatrix[0][0], colorMatrix[1][0]];
        } else {
            color = colorMatrix[0];
        }

        return DataV.gradientColor(color, "special");
    };

    Tree.prototype.createCanvas = function () {
        var conf = this.defaults;
        this.canvas = new Raphael(this.node, conf.width, conf.height);
        this.node.style.position = "relative";
        this.floatTag = DataV.FloatTag()(this.node);

        this.floatTag.css({"visibility": "hidden"});

        this.DOMNode = $(this.canvas.canvas);
        var that = this;
        this.DOMNode.click(function (event) {
            that.trigger("click", event);
        });
        this.DOMNode.dblclick(function (event) {
            that.trigger("dblclick", event);
        });

        var mousewheel = document.all ? "mousewheel" : "DOMMouseScroll";
        this.DOMNode.bind(mousewheel, function (event) {
            that.trigger("mousewheel", event);
        });

        this.DOMNode.bind("contextmenu", function (event) {
            that.trigger("contextmenu", event);
        });

        this.DOMNode.delegate("circle", "click", function (event) {
            that.trigger("circle_click", event);
        });

        this.DOMNode.delegate("circle", "mouseover", function (event) {
            that.trigger("circle_mouseover", event);
        });

        this.DOMNode.delegate("circle", "mouseout", function (event) {
            that.trigger("circle_mouseout", event);
        });
    };

    Tree.prototype.zoom = function (d) {
        var multiple = d || 2;
        var conf = this.defaults;
        conf.width = conf.width * multiple;

        if (conf.height <= this.treeDepth * conf.deep) {
            conf.height = conf.height * multiple;
        }

        //this.createCanvas();
        this.canvas.setSize(conf.width, conf.height);
        this.canvas.setViewBox(0, 0, conf.width, 800);
        this.defaults = conf;

        this.render();
    };


    Tree.prototype.getLinkPath = function (fx, fy, tx, ty) {
        var conf = this.defaults;

        var c1x = fx;
        var c1y = fy + (ty - fy) / 2;
        var c2x = tx;
        var c2y = ty - (ty - fy) / 2;

        var link_path = [["M", fx, fy + conf.radius],
            ["C", c1x, c1y, c2x, c2y, tx, ty - conf.radius]];

        return link_path;
    };

    Tree.prototype.generatePaths = function () {
        var canvas = this.canvas;
        var source = this.source;
        var conf = this.defaults;
        var radius = conf.radius;
        //canvas.clear();
        var color = this.getColor();
        // var font = this.getFont();
        var font_family = '微软雅黑';
        var font_size = 8;
        var treedepth = this.treeDepth;
        var nodesData = this.nodesData;

        var n = 0;

        var addlink = this.addlink;
        var node;
        var num = 0;

        var nodes = canvas.set();
        var path = [];
        var textpath = [];
        
        var tree = this;
        var nodeupdate = function () {
            tree.update(this.data("num"));
        };

        $(this.node).append(this.floatTag);

        var i, nodesLength;
        for (i = 0, nodesLength = nodesData.length; i < nodesLength;  i++) {
            var d =  nodesData[i];
            var parent = d.parent;

            if (addlink[d.num]) {
                var j, k, childLength;
                for (j = 0, childLength = addlink[d.num].child.length; j < childLength; j++) {
                    for (k = 0; k < nodesLength;  k++) {
                        if (nodesData[k].num === addlink[d.num].child[j]) {
                            addlink[d.num].pnode[j] = k;
                            addlink[d.num].path[j] = canvas.path()
                                .attr({ stroke:  "#939598", "stroke-width": 0.5});
                        }
                    }
                }
            }

            var startX;
            var startY;

            if (parent && d.draw) {
                startX = parent.x;
                startY = parent.y;
            } else {
                startX = d.x;
                startY = d.y;
            }
            if (parent) {
                path.push(canvas.path().attr({stroke:  "#939598", "stroke-width": 0.5}));
            }

            nodes.push(
                canvas.circle(startX, startY, radius)
                    .attr({fill: color(d.depth / treedepth),
                        stroke: "#ffffff",
                        "stroke-width": 1,
                        "fill-opacity": 0.4,
                        "data": 12})
                    .data("num", i)
                    .animate({cx: d.x, cy: d.y}, 500, "backOut")
            );

            if (d.children || d._children) {
                nodes[i].click(nodeupdate);
            }

            if (d._children) {
                nodes[i].attr({
                    stroke: color(d.depth / treedepth),
                    "stroke-width": radius,
                    "stroke-opacity": 0.4,
                    "fill-opacity": 1,
                    "r": radius / 2
                });
            }

            if (d.children) {
                textpath.push(canvas.text(d.x, d.y - radius - 7, d.name).attr({'font-size': 12}));
            } else {
                textpath.push(canvas.text(d.x, d.y + radius + 7, d.name).attr({'font-size': 12}));
            }
        }

        // var back = function(pid, x, y){
        //     s.forEach(function (d, i){
        //         if (d.data('pid') == pid){
        //             d.animate({cx: x, cy: y}, 200, "backOut");
        //             if (nodes[i].children)
        //             back(d.data('num'), d.attr('cx'), d.attr('cy'));
        //         }
        //     });
        // };

        // s.forEach(function(d, i) {
        //     d.click(function(){
        //         if (nodes[i].children)
        //         back(d.data('num'), d.attr('cx'), d.attr('cy'));
        //         tree.update(d.data("num"));
        //     });
        // });
        var floatTag = this.floatTag;
        nodes.forEach(function (d, i) {
            $(d.node).attr('value', nodesData[i].value);
            var textY = textpath[i].attr('y');
            var thisradius = d.attr('r');
            var thisstrokewidth = d.attr('stroke-width');
            d.mouseover(function () {
                if (!nodesData[i]._children) {
                    this.animate({r: thisradius + 2, "fill-opacity": 0.75}, 100);
                } else {
                    this.animate({r: thisradius + 2, "stroke-opacity": 0.75}, 100);
                }

                textpath[i].attr({'font-size': 20});

                if (i > 0) {
                    if (!nodesData[i].children) {
                        textpath[i].animate({'y': textY + 12}, 100, "backOut");
                    } else {
                        textpath[i].animate({'y': textY - 12}, 100, "backOut");
                    }
                }

                var getFline = function (node, num) {
                    var parent = node.parent;
                    if (parent) {
                        path[node.id - 1].attr({"stroke-width": 4, "stroke-opacity": num});
                        if ( num > 0.5) {
                            num = num - 0.1;
                        }
                        getFline(parent, num);
                    }
                };

                getFline(nodesData[i], 0.9);

                var thisparent = nodesData[i].parent;
                var j, textpathLength;
                for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) {
                    var parent = nodesData[j].parent;
                    if (parent === thisparent && j !== i) {
                        textpath[j].animate({'fill-opacity': 0.4});
                    }
                }

                console.log(nodesData[i]);
                floatTag.html('<div style = "text-align: center;margin:auto;color:#ffffff">' + nodesData[i].name + '</div>');
                floatTag.css({"visibility" : "visible"});
            })
            .mouseout(function () {
                floatTag.css({"visibility" : "hidden"});
                if (!nodesData[i]._children) {
                    this.animate({r: thisradius, "fill-opacity": 0.4}, 100);
                } else {
                    this.animate({r: thisradius, "stroke-width": thisstrokewidth, "stroke-opacity": 0.4}, 100);
                }
                textpath[i].attr({'font-size': 12});
                textpath[i].animate({'y': textY}, 100, "backOut");

                var getFline = function (node) {
                    var parent = node.parent;
                    if (parent) {
                        path[node.id - 1].attr({"stroke-width": 0.5, "stroke-opacity": 1});
                        getFline(parent);
                    }
                };
                getFline(nodesData[i]);

                var thisparent = nodesData[i].parent;
                var j, textpathLength;
                for (j = 0, textpathLength = textpath.length; j < textpathLength; j++) {
                    var parent = nodesData[j].parent;
                    if (parent === thisparent && j !== i) {
                        textpath[j].animate({'fill-opacity': 1});
                    }
                }
            });
        });

        nodes.onAnimation(function () {
            var pathNum = 0;
            var i, nodeslength;
            
            for (i = 1, nodeslength = nodes.length; i < nodeslength;  i++) {
                var d = nodes[i];
                var node = nodesData[i];
                var parent = node.parent;
                
                path[pathNum]
                    .attr({path: tree.getLinkPath(parent.x, parent.y, d.attr("cx"), d.attr("cy"))});
                    
                pathNum++;

                if (addlink[node.num]) {
                    var j, k, linkchildLength, nodesLength;
                    for (j = 0, linkchildLength = addlink[node.num].child.length; j < linkchildLength; j++) {
                        for (k = 0, nodesLength = nodesData.length; k < nodesLength;  k++) {
                            var anparent = nodesData[k];
                            if (anparent.num === addlink[node.num].child[j]) {
                                var link_path = tree.getLinkPath(anparent.x, anparent.y, d.attr("cx"), d.attr("cy"));
                                addlink[node.num].path[j].attr({path: link_path});
                            }
                        }
                    }
                }
            }
        });

        this.nodes = nodes;
        this.path = path;
        this.textpath = textpath;
    };
    
    Tree.prototype.update = function (i) {
        var source = this.source;
        var conf = this.defaults;

        source.children.forEach(function clearDraw(d) {
            d.draw = false;
            if (d.children) {
                d.children.forEach(clearDraw);
            }
        });

        source.children.forEach(function find(d) {
            if (d.id === i) {
                if (d.children) {
                    d._children = d.children;
                    d.children = null;
                } else {
                    d.children = d._children;
                    if (d.children) {
                        d.children.forEach(function drawn(child) {
                            child.draw = true;
                            if (child.children) {
                                child.children.forEach(drawn);
                            }
                        });
                    }
                    d._children = null;
                }
            } else {
                if (d.children) {
                    d.children.forEach(find);
                }
            }
        });
        this.source = source;
        this.source.x0 = conf.width / 2;
        this.source.y0 = conf.radius * 2;
        this.render();
    };

render

渲染Tree

方法 Tree.prototype.render() render
代码
Tree.prototype.render = function (options) {
        this.canvas.clear();
        this.setOptions(options);
        this.layout();
        // var st2 = new Date().getTime();
        this.generatePaths();
        // var et = new Date().getTime();
        //this.canvas.renderfix();
    };

    return Tree;
});

charts/treemap: API索引


Treemap

Treemap构造函数,继承自Chart

Options

  • width 数字,图片宽度,默认为750,表示图片高750px
  • height 数字,图片高度,默认为500
  • showBackTag 布尔值,回退操作导航条是否显示,默认为 true, 显示;设为false则不显示
  • backHeight 数字,回退操作导航条宽度,默认为20
  • level1BorderWidth 数字,一级方框的边框宽度,默认为1(1px),不建议修改
  • level2BorderWidth 数字,二级方框的边框宽度,默认为1(1px),不建议修改
  • fontSizeRatio 数字,表示图中文字大小。默认为1.0(1倍), 若设为2.0,字体大小会加倍;
  • customEvent 函数对象,其中有4个自定义函数。leafNodeClick 函数,表示点击叶子节点的事件响应,默认为空函数; hoverIn 函数,表示鼠标移进方框的事件响应,默认为空函数; hoverOut 函数,表示鼠标移出方框的事件响应,默认为空函数; mouseover 函数,表示在方框内移动鼠标的事件响应,默认为设置浮框的内容,可以替换它修改浮框内容; 这些函数可以在创建对象或setOption()时一起设置,也可以通过on函数单独设置。

Examples

create treemap in a dom node with id "chart", width is 500; height is 600px;

var treemap = new Treemap("chart", {"width": 500, "height": 600});
声明 Treemap Treemap
参数 node(Object) The dom node or dom node Id
参数 options(Object) JSON object for determin treemap style
代码
var Treemap = DataV.extend(DataV.Chart, {
        initialize: function (node, options) {
            this.type = "Treemap";
            this.node = this.checkContainer(node);

            // Properties
            this.selectedTreeNodes = [];//array of nodes on the path from root to recent node
            this.treeNodeJson = {};
            this.level_ = 2;

            this.floatTag;//浮框对象,这是个可操作的对象。

            // Canvas
            this.defaults.width = 750;
            this.defaults.height = 500;

            this.defaults.showBackTag = true;
            this.defaults.backHeight = 20;

            this.defaults.level1BorderWidth = 1;
            this.defaults.level2BorderWidth = 1;
            this.defaults.fontSizeRatio = 1.0;

            //event
            this.defaults.customEvent = {
                leafNodeClick : function () {},
                hoverIn : function () {},
                hoverOut : function () {},
                mousemove : function () {
                    var jqNode = this.jqNode,
                        treemap = jqNode.treemap,
                        floatTag = treemap.floatTag;
    
                    //set floatTag content
                    floatTag.html('<div style = "text-align: center;margin:auto;color:' +
                        //+ jqNode.color
                        '"#fff">' + jqNode.treemapNode.name + '</div>' +
                        '<div style = "text-align: center; margin:auto;color:' +
                        '"#fff">' + jqNode.treemapNode.value + '</div>');
                }
            };

            this.setOptions(options);
            this.createCanvas();
        }
    });

createCanvas

Create dom node relate to treemap

方法 Treemap.prototype.createCanvas() createCanvas
代码
Treemap.prototype.createCanvas = function () {
        var conf = this.defaults,
            floatStyle,
            container = this.node,
            backStyle,
            canvasStyle;

        this.node.style.position = "relative";

        if (conf.showBackTag) {
            this.backTag = document.createElement("div");
            backStyle = this.backTag.style;
            backStyle.width = conf.width + "px";
            backStyle.height = conf.backHeight + "px";
            backStyle.paddingLeft = "5px";
            container.appendChild(this.backTag);
        }

        this.canvas = document.createElement("div");
        canvasStyle = this.canvas.style;
        canvasStyle.position = "relative";
        canvasStyle.width = conf.width + "px";
        canvasStyle.height = conf.height + "px";
        container.appendChild(this.canvas);

        this.floatTag = DataV.FloatTag()(this.canvas);

        this.floatTag.css({"visibility": "hidden"});

        //this.canvas.appendChild(this.floatTag);
    };

getColor

获取颜色

Examples

// 获取第二种颜色的渐变色。
{mode: "gradient", index: 1}
// 获取最深的离散色。
{mode: "random", ratio: 0}
// 获取最浅的离散色。
{mode: "random", ratio: 1}
// 获取适中的离散色。
{mode: "random", ratio: 0.5}
方法 Treemap.prototype.getColor() getColor
参数 colorJson(Object) Way to get color from color theme matrix
返回 Array 返回颜色数组
代码
Treemap.prototype.getColor = function (colorJson) {
        var colorMatrix = DataV.getColor();
        var color;
        var colorStyle = colorJson || {};
        var colorMode = colorStyle.mode || 'default';
        var i, l;

        switch (colorMode) {
        case "multiColorGradient":
            //color = d3.interpolateHsl.apply(null, ["red", "blue"]);
            //color = d3.interpolateHsl.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]);
            //color = DataV.gradientColor(["#f5f5f6", "#f6f5f5"], 'special');
            //color = DataV.gradientColor([colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]], 'special');
            //color = d3.interpolateRgb.apply(null, [colorMatrix[0][0], colorMatrix[colorMatrix.length - 1][0]]);

            color = (function () {
                var c = [];
                colorMatrix.forEach(function (d, i) {
                    c.push(d[0]);
                });
                return function (ratio) {
                    var index = (c.length - 1) * ratio;
                    var floor = Math.floor(index);
                    var ceil = Math.ceil(index);
                    if (floor === ceil) {
                        return c[floor];
                    } else {
                        return d3.interpolateRgb.apply(null, [c[floor], c[ceil]])(index - floor);
                    }
                };
            }());
            //color = d3.interpolateRgb.apply(null, ["green", "purple"]);
            break;
        case "gradient":
            var index = colorJson.index || 0;
            index = index < 0 ? 0 : Math.min(index, colorMatrix.length - 1);
            color = d3.interpolateRgb.apply(null, [colorMatrix[index][0], colorMatrix[index][1]]);
            break;
        case "random":
        case "default":
            var ratio = colorStyle.ratio || 0;
            if (ratio < 0) { ratio = 0; }
            if (ratio > 1) { ratio = 1; }
            var colorArray = [];
            for (i = 0, l = colorMatrix.length; i < l; i++) {
                var colorFunc = d3.interpolateRgb.apply(null, [colorMatrix[i][0], colorMatrix[i][1]]);
                colorArray.push(colorFunc(ratio));
            }
            color = d3.scale.ordinal().range(colorArray);
            break;
        }
        return color;
    };

setSource

设置数据源

Examples

treemap数据输入的格式可以是二维数组。例如下面的数组表示2000年4个季度的天数。
第1季度下面还列出了1-3月的天数。数组的第一行为四个固定的字符串"ID","name","size"和"parentID"。
四列数据分别表示层次数据集中各结点的ID,名称,大小和父节点ID。叶子节点必须有大小,根结点不能有父节点ID。各结点的ID、名称必须要有。

 [
     ["ID", "name", "size", "parentID"],
     [0, "2000",  ,  ],
     [1, "season1",  , 0],
     [2, "January", 31, 1],
     [3, "February", 29, 1],
     [4, "Match", 31, 1],
     [5, "season2", 91, 0],
     [6, "season3", 92, 0],
     [7, "season4", 92, 0]
 ]

数据还可以是json格式。每个结点都有name,如果是父节点则还有children,如果为叶节点则还有size。以上数组数据对应的json数据如下:

{
  "name": "2000",
  "children": [
     {
      "name": "season1",
      "children": [
           {"name": "January", "size": 31},
           {"name": "February", "size": 29},
           {"name": "Match", "size": 31}
         ]
     },
     {"name": "season2", "size": 91},
     {"name": "season3", "size": 92},
     {"name": "season4", "size": 92},
  ]
}
方法 Treemap.prototype.setSource() setSource
参数 source(Array,Object) json or 2-d array
代码
Treemap.prototype.setSource = function (source) {
        if (source instanceof Array) {
            this.rawData = this._arrayToJson(source);
        } else {
            this.rawData = source;
        }
        this.source = this._remapSource(this.rawData);
        this.selectedTreeNodes = [this.source[0]];
    };

clearCanvas

清除画布

方法 Treemap.prototype.clearCanvas() clearCanvas
代码
Treemap.prototype.clearCanvas = function () {
        var canvas = this.canvas;
        canvas.innerHTML = "";
    };

reRender

计算布局,并重新渲染图表

方法 Treemap.prototype.reRender() reRender
代码
Treemap.prototype.reRender = function (options) {
        this.clearCanvas();
        this.setOptions(options);
        this._create2LevelJson(this.selectedTreeNodes[this.selectedTreeNodes.length - 1]);
        this.layout();
        this._setBackTag();
        this.generatePaths();
    };

render

计算布局位置,并渲染图表

方法 Treemap.prototype.render() render
代码
Treemap.prototype.render = function (options) {
        this.clearCanvas();
        this.setOptions(options);
        this._getNodeTheme = undefined;
        this.selectedTreeNodes = this.selectedTreeNodes.slice(0, 1);
        this._create2LevelJson(this.selectedTreeNodes[0]);
        this.layout();
        this._setBackTag();
        this.generatePaths();
    };

on

设置自定义事件

方法 Treemap.prototype.on() on
代码
Treemap.prototype.on = function (eventName, callback) {
        if ($.inArray(eventName, ["leafNodeClick", "hoverIn", "hoverOut", "mousemove"]) !== -1) {
            this.defaults.customEvent[eventName] = callback;
        }
    };

components/stream: API索引


StreamComponent

constructor

声明 StreamComponent StreamComponent
参数 the(node) dom node or dom node Id
undefined undefined undefined undefined
示例 undefined undefined undefined
undefined undefined undefined undefined
undefined undefined undefined undefined
代码
var StreamComponent = DataV.extend(DataV.Chart, {
    initialize: function (node, options) {
      this.type = "Stream";
      this.node = this.checkContainer(node);
      this.defaults = {};
      // Properties
      this.defaults.offset = "zero";//zero, expand, silhou-ette, wiggle;(d3 stack offset)
      this.defaults.order = "default";//default, reverse, descending, ascending, inside-out(d3 stack order, sort by index of maximum value, then use balanced weighting.), inside-out-reverse(inside-out like, sort by index of maximum value, not descending but ascending);
      this.defaults.normalized = false;//false, true; //ratio data or not;
      //this.defaults.rowDataOrder = "default"; //default, descending, ascending(according to digitdata row sum value);
      this.defaults.columnNameUsed = "auto";
      this.defaults.rowNameUsed = "auto";
      this.defaults.pathLabel = true;
      this.defaults.fontSize = 12;
      this.defaults.colorCount = 20;
      //this.defaults.axisTickNumber = 8; // axis ticks number

      this.defaults.indexMargin = 3; // if dates.length < indexMargin * 2 + 1, do not show label

      this.timeRange = [];
      // paper

      this.defaults.width = 800;
      this.defaults.height = 560;//if only width has value and autoHeight is true, then height will be width * heightWidthRatio.
      this.defaults.autoHeight = true;
      this.defaults.heightWidthRatio = 0.6;

      this.defaults.legendPosition = "top";//"top", "left"
      this.defaults.topLegendHeight = 50;
      this.defaults.leftLegendWidth = 150;
      this.defaults.showLegend = true;

      this.defaults.legendBesidesWidth = undefined;
      this.defaults.legendBesidesHeight = undefined;

      this.defaults.more = false;
      this.defaults.moreLabel = "more";
      this.defaults.max = 20;
      this.defaults.level = 0;

      this.defaults.chartWidth = undefined;//depends on width, do not recommend to change
      this.defaults.chartHeight = undefined;// depends on height, do not recommend to change

      this.defaults.naviHeight = 20;//do not recommend to change
      this.defaults.showNavi = undefined;//ture if moreConfig.more == true, else false;

      this.defaults.axisHeight = 30;//do not recommend to change
      this.defaults.showAxis = true;

      this.defaults.showPercentage = undefined;//true if moreConfig.more == true, else false;
      this.defaults.percentageWidth = 40;

      this.defaults.customEventHandle = {"mousemove": null};

      this.defaults.tipStyle = {};

      this.setOptions(options);
    }
  });

  StreamComponent.prototype.init = function () {
    var that = this;
    var getBack = function () {
      var naviCallBack = function () {
        that.cover.hidden();
        if (typeof that.cover.mouse !== 'undefined') {
          that.hoverLine.show();
          that.tip.show();
          $(that.paper.canvas).trigger("mousemove",[that.cover.mouse.x, that.cover.mouse.y]);
          that.cover.mouse = undefined;
        }
        that.pathLabel.show();
      };

      that.cover.show();
      that.cover.mouse = undefined;

      that.processData("slicedData");
      that.render("renderComponents");

      //hidden
      that.hoverLine.hidden();
      that.tip.hidden();

      that.pathLabel.hidden();
      that.paths.forEach(function (d) {
        d.attr({transform: "s1,0.001,0,0"});
        d.animate({transform: "t0,0"}, 750, "linear", naviCallBack);
      });
    };
    that.on('changeLevelTo', function (level) {
      that.defaults.level = level;
      getBack(that.defaults.moreConfig.level);
    });
    that.on('back', function () {
      that.defaults.level = that.defaults.level - 1;
      getBack(that.defaults.level);
    });
    that.legend.on('hoverIn', function (index) {
      that.stream.highlight(index);
    }).on('hoverOut', function (index) {
      that.stream.lowlight(index);
    });
    that.stream.on('enter', function () {
      that.axis.showTab();
      that.tip.show();
    }).on('leave', function (index) {
      that.axis.hideTab();
      that.tip.hidden();
      if (index !== undefined) {
        that.legend.lowlight(index);
      }
    }).on('move', function (pre, rowIndex, columnIndex) {
      if (pre !== undefined) {
        that.legend.lowlight(pre);
      }
      if (typeof rowIndex === "undefined" || typeof columnIndex === "undefined") {
        return;
      }
      that.legend.highlight(rowIndex);
      that.tip.setContent(rowIndex, columnIndex);
      //axis pop bubble
      that.axis.refreshTab(columnIndex);
    }).on('level_changed', function (start, end, needMore) {
      that.legend.fire('level_changed', start, end, needMore);
    });
  };

  StreamComponent.prototype.setSource = function (source, map) {
    this.source = source;
    this.map = map;
  };

  StreamComponent.prototype.layout = function () {
    var conf = this.defaults;
    if (!conf.showLegend) {
      conf.legendBesidesWidth = conf.width;
      conf.legendBesidesHeight = conf.height;
    } else {
      if (conf.legendPosition === "left") {
        conf.legendBesidesWidth = conf.width - conf.leftLegendWidth;
        conf.legendBesidesHeight = conf.height;
      } else {
        conf.legendBesidesWidth = conf.width;
        conf.legendBesidesHeight = conf.height - conf.topLegendHeight;
      }
    }
    conf.chartWidth = conf.legendBesidesWidth - 2 * conf.percentageWidth;
    conf.chartHeight = conf.legendBesidesHeight - (conf.showNavi ? conf.naviHeight : 0) - (conf.showAxis ? conf.axisHeight : 0);
    var node = $(this.node).css({
      position: "relative",
      width: conf.width
    });
    // 创建DOM节点
    this.streamBox = $("<div></div>").addClass("stream");
    this.legendBox = $("<div></div>").addClass("legend");
    this.axisBox = $("<div></div>").addClass("axis");
    this.naviBox = $("<div></div>").addClass("navi");
    this.percentageBox = $("<div></div>").addClass("percentage");
    this.container = $("<div></div>").addClass("container");
    this.rightBox = $("<div></div>").addClass("right");
    // cover can block stream paper when animating to prevent some default mouse event
    this.coverBox = $("<div></div>").addClass("cover");
    // 插入DOM
    this.streamBox.append(this.coverBox);
    this.container.append(this.percentageBox).append(this.streamBox);
    this.rightBox.append(this.naviBox).append(this.container).append(this.axisBox);
    node.append(this.legendBox).append(this.rightBox);
    // 设置各个节点大小
    this.streamBox.css({
      "position": "relative",
      "float": "left",
      "width": conf.chartWidth,
      "height": conf.chartHeight
    });
    this.percentageBox.css({

    });
    this.container.css({
      "height": conf.chartHeight
    });
    this.rightBox.css({
      "float": "right",
      "width": conf.legendBesidesWidth
    });
    this.legendBox.css({
      "width": conf.leftLegendWidth - 4,
      "float": "left",
      "overflowX": "hidden"
    });
  };

  StreamComponent.prototype.draw = function () {
    var conf = this.defaults;
    //chart and paper
    this.stream = this.own(new Stream(this.streamBox, {"width": conf.chartWidth, "height": conf.chartHeight}));
    this.stream.setSource(this.source, this.map);
    this.stream.render();

    this.legend = this.own(new Legend.Legend(this.legendBox));
    this.legend.setOptions({
      "colorFunc": this.stream.getColor()
    });
    this.legend.setSource(this.source, this.map);
    this.legend.render();

    this.percentage = this.own(new Percentage(this.percentageBox));
    this.percentage.setSource(this.source, this.map);
    this.percentage.render();

    this.axis = this.own(new Axis(this.axisBox));
    this.axis.setSource(this.source, this.map);
    this.axis.render();

    this.navi = this.own(new Navi(this.naviBox));
    this.navi.render();

    // cover can block stream paper when animating to prevent some default mouse event
    this.cover = this.own(new Cover(this.coverBox));

    //floatTag
    this.tip = this.own(new Tip(this.streamBox));
    this.tip.setSource(this.source, this.map);
    this.tip.render();
  };

  StreamComponent.prototype.render = function () {
    this.layout();
    this.draw();
    this.init();
  };

  StreamComponent.prototype.setCustomEvent = function (eventName, callback) {
    this.defaults.customEventHandle[eventName] = callback;
  };