(2)基于Echarts插件的多维数据可视化设计和实现

来源:互联网 发布:python classmethod 编辑:程序博客网 时间:2024/05/21 15:47

前言:本文针对多维离散数据和多维连续数据,分别构建多维可视化图表。由于离散和连续图表展示的效果差别大,故多维图表的可视化,分为两大部分进行设计和实现。

总体设计思想:主要是采用类似树形图结合柱状图、线图、散点图、面积图。树形图主要展示的是维度的值,和各个维度值之间的关系。如图(1)所示。第一条数据:[“企业”, “个人政府征订”,”奉化站”,”2013-07”, 6500],在图表中与这条数据相关的内容,除了第一根蓝色方形,还包括红线相连的名称(企业,个人政府征订,奉化站)。如果把图中所有的名称相互连接起来,像图中红线一样,是不是感觉像一棵树?特别要注意的是(企业-个人政府征订-奉化站) 可以看成一个分组,在这个分组里可能有不止一个值,它分布着多条数据。

这里写图片描述

图1:多维数据展示

总体实现方式:要实现如图1所示的效果,可以采用Echarts插件的各种组件经过一系列的计算和整合,当然其中肯定涉及插件若干接口的改造和新增。最终生成的图表才能展示出我们需要的效果。

多维离散型数据的设计思路

多维离散型数据的定义:所有的维度都是离散的,不存在任何连续型维度。对于多维离散型数据,由于在每一条数据相互之间在轴上并没直接关系,关系同属于一个分组。对于同一个度量,各个分组展示出来的图表类型应该是一致的。(特殊情况,对于三个维度的数据,类目气泡图也是可以展示的)

基于Echarts插件的多维离散型数据可视化实现
一、问题阐释
1. 基于Echarts插件,要实现多维数据可视化,是否有官方接口,如果有官方接口不就轻松搞定?
通过查看Echarts的文档,发现确实有一个图表类型,可以展示多维数据,如图2所示,但是这种展示方式,貌似并不能适合所有多维数据,而且可视化的效果有点差强人意。
这里写图片描述

图2 Echarts的多维数据展示

2. 如果要实现我们设计的树形加普通图表的展示方式,如何实现树形?如何进行分组?每一个分组高和宽占用整个图表高和宽多少?每一个分组由于是同一个度量,是不是应该共享同一个度量轴?
对于这些问题,暂时不做回答。接下来,我会手把手教大家如何解决这些问题,最终实现整个多维图表。

二 多维数据的结构
要实现整个设计,一共需要三个数据结果,我把它们取名:data,measure, dimension。具体的数据例子,如下所示,data:是一个二级数组[[d1,..,dn,m1,..,mn],…,[d1,..,dn,m1,..,mn]] (dn:表示第n个维度的信息,而mn表示n个度量的信息),它存储着图表要显示的多维数据详情,每一条数据时一个数组,数组的前几位是维度的值,后几位是度量的值。measure:是一个数组,它存储着度量字段的相关信息,数组内每条数据时一个js对象,对象的key属性,是度量的关键字,用于区分其他度量;alias属性时度量的别名,用于显示度量的中文名称;state属性表示度量所对应的图表类型。dimension :也是一个数组,构造跟measure差不多,公共的属性表示的含义和measure是一样的,continuity字段表示的是维度的连续性,’11’表示是连续的。colType:表示维度的数据类型。如果是离散的,维度的数据类型一般都是字符串型的。

var data = [    [        "企业",        "余姚站",        "2013-05",        0.015    ],      ...    [        "普通客户",        "仓基站",        "2013-01",        0.019    ]];var dimension = [    {        "key":"cust_type",               "alias":"cust_type",        "continuity":"00",        "colType":"string"    },    ....    {        "key":"Month",             "alias":"Month",         "continuity":"11",        "colType":"date"    }];var measure =      [    {        "key":"huan_bi",             "alias":"总和_huan_bi",                    "state":"bar"    },    ...     {        "key":"tong_bi",        "alias":"总和_tong_bi",                          "state":"bar"    }];

三、Echarts接口的扩展和新增

为了解决(1)提出的问题,在Echarts的接口一共需要扩展一个接口,新增2个接口。
扩展的接口主要是
Axis.prototype.dataToCoord;该接口主要负责计算名称或者叫标签的坐标值;在Echarts中标签值叫label,而字符串的label一般用category离散的轴表示,每一个label之间的距离是等距的。这个与我们要求是有出入的,因为在多维图表中,每一个label之间并不一定是等距的,所以我们要定距给其一个值,使之满足我们的要求。而这个值的计算是在Echarts插件外部进行的,由函数calLabelProperty完成。

calLabelProperty函数的功能,主要是计算跟label定位和显示相关的三个属性值,line:表示两个label之间的分割线;area表示每条label信息在图表中所占的宽度;curBit表示每条label在图表上的位置。代码中normalizeData变量保存着每一个label在一条轴上的比率值,介于0和1之间。

 dataToCoord: function (data, clamp) {                var extent = this._extent;                var scale = this.scale;                var normalizeData = this.model.get('normalizeData');                //沈才良 @face                if (normalizeData) {                    data = normalizeData[data];                } else {                    data = scale.normalize(data);                }                if (this.onBand && scale.type === 'ordinal') {                    extent = extent.slice();                    fixExtentWithBands(extent, scale.count());                }                return linearMap(data, normalizedExtent, extent, clamp);            },
calLabelProperty: function(chartLabelData, isContinue) {    var categoryLabel = [];    var sum = 0, labelArray, percentArray, areaArray;    var curSum = 0, curBit = 0;    var curBitArray = [];    var classNum = chartLabelData.length;    for (var i = 0; i < classNum; i++) {                        //如果图表是离散的,则是label的总和        sum = isContinue ? chartLabelData[classNum - 1].labelData.length : chartLabelData[i].sum;        //当前的统计和        curSum = 0;        categoryLabel[i] = {};        /*           labelArray: 保存每一个维度上的label           lineArray: 保存各个label之间的分隔线的位置           curBitArray: 保存各个label的位置           areaArray: 保存各个label在图表上所占的区域宽        */        labelArray = categoryLabel[i].label = [];        lineArray = categoryLabel[i].line = [];        curBitArray = categoryLabel[i].percent = [];        areaArray = categoryLabel[i].area = [];        //求出每一个类别子项的显示位置的值        for(var j = 0, dataItem; j < chartLabelData[i].labelData.length; j++) {            dataItem = chartLabelData[i].labelData[j];              //连续的时候,离散的最后一个维度计算curSum等于 j + 1                           if (isContinue && i == (classNum - 1)) {                curSum = j + 1;            } else {                curSum += dataItem.count;            }                //删除label里面的换行符号            labelArray.push((dataItem.labelName + '').replace(/\r\n/g, ' '));            lineArray.push(curSum / sum);        }        //每一个维度的第一个分组信息计算,跟其他分组不一样        curBitArray[0] = lineArray[0] / 2;        areaArray[0] = lineArray[0];        for ( j = 1; j < lineArray.length; j++) {            areaArray.push(lineArray[j] - lineArray[j-1]);            curBitArray.push(lineArray[j - 1] + (lineArray[j] - lineArray[j - 1]) / 2);        }    }    return categoryLabel;},

新增的接口主要是:label之间的分割线、悬浮label所占区域时候区域背景色改变的变化接口。
新增接口的定义在AxisBuilder构造函数所属区域中builders.axisLabel函数中。

   //添加标签的hover事件    buildHoverRect({        group: this.group,         pos: pos,         textEl: textEl,         axisModel: axisModel,         contentBit: areaData[index]    });    /**      * 当用户悬浮在label上面      * 则显示整个label分组所占的空间      * 由于zrender默认的hide和show函数      * 当数据超过100多时,性能差,故采用      * 设置opacity达到显示和隐藏的效果    */    textEl.on('mouseover', function (event) {        this.hoverRect.setStyle('opacity', 0.24);    });    /**       * opacity = 0表示隐藏该label有颜色背景       * textEl:表示每一个label在图表上的实体    */    textEl.on('mouseout', function (event) {        this.hoverRect.setStyle('opacity', 0);    });    /**        * 生成hover时候出现的矩形和背景        * @param {Object} params: userful params to build hover rect        * @param {Number} contentBit: 每一个label所在的区域长度,[0,1]之间        * @param {Array.<Number>} pos: label的坐标值,二维的    */    function buildHoverRect(params) {           var axisModel = params.axisModel;        var coordinateRect = axisModel.coordinateSystem && axisModel.coordinateSystem._rect;        var extent = axisModel.axis.getExtent();             var axisWidth = extent[1] - extent[0];          //得到显示区域的宽度大小               var contentWidth = Math.ceil(axisWidth * params.contentBit);        var rect = new graphic.Rect({            shape: {                x: params.pos[0] + coordinateRect.x - contentWidth / 2,                y: params.pos[1] + coordinateRect.y - 7,                 height: 28,                width: contentWidth            },            z: 2,            style: {                 fill:'green',                 opacity: 0,                 lineWidth: 1            },            silent: false        });        //添加到group中,以便echarts统一绘制        params.group.add(rect);        //方便隐藏和显示        params.textEl.hoverRect = rect;    }

第二个重要的接口新增是buildSplitLine函数,主要负责构建label之间的分隔线。具体代码和注释如下所示:

/**  * 生成label之间的分割线,方便用户识别不同的label分组   * {Array} group:分组组件,生成的line只有放入这个组件中,才能统一绘制   * {Object} axisModel: 坐标轴的model模型,主要保存与坐标轴相关的信息*/function buildSplitLine(group, axisModel) {    var axisType = axisModel.mainType;    var show = axisModel.get('classSplitLine.show');    if (axisType != "singleAxis" || show !== true) {        return;    }    //获取分割线的所需的数据,由外部接口calLabelProperty计算出    var data = axisModel.get('classSplitLine.data');    var coordinateRect = axisModel.coordinateSystem && axisModel.coordinateSystem._rect;    var extent = axisModel.axis.getExtent();         var axisWidth = extent[1] - extent[0];      var point1 = [];    var point2 = [];    for (var i = 0 ; i < data.length - 1; i++) {        //求出分隔线两个端点的坐标值        point1[0] = data[i] * axisWidth + coordinateRect.x;        point1[1] = coordinateRect.y;        point2[0] = data[i] * axisWidth + coordinateRect.x;        point2[1] = coordinateRect.y + 26;          var line = new graphic.Line(graphic.subPixelOptimizeLine({            shape: {                x1: point1[0],                y1: point1[1],                x2: point2[0],                y2: point2[1]            },            style: {                 stroke: "#d3d3d3",                 color: '#666',                // lineDash: [4, 4],                 opacity: 0.5,                 lineWidth: 1            },            z2: 3,            silent: true        }));        group.add(line);    }}

这几个接口具体定义的位置,请参考我详细的源码。
源码下载地址:http://download.csdn.net/download/mulumeng981/9985030 (基于Echarts最新版3.7.1)

如果你注意到一个不准确或似乎不太正确的地方,请让我知道。谢谢!

阅读全文
1 0
原创粉丝点击