您的位置:首页 > 其它

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

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

总体设计思想:主要是采用类似树形图结合柱状图、线图、散点图、面积图。树形图主要展示的是维度的值,和各个维度值之间的关系。如图(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];

b9c3
//得到显示区域的宽度大小
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)

如果你注意到一个不准确或似乎不太正确的地方,请让我知道。谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐