想法
比较可视化有三种基本方法:并置、叠加、显式编码。常见的比较布局基本都是将这三种基本方法混合使用。下面是一些常见的比较布局的例子:
下面图片引用自论文 Comparative Layouts Revisited: Design Space, Guidelines, and Future Directions。感兴趣的可以去看看论文。
对于涉及到不同组的多个变量对进行一一比较时,常用的就是矩形形式的比较布局,如上图I-L。
那么使用Echarts,如何实现这种矩形形式的比较布局呢?
实现
观察可以发现,这种矩形形式的布局跟热力图很像,只是每个单元格内的图形样式不一样。因此可以在热力图的基础上进行设计,具体每个单元格显示什么图形,可自由发挥。
下面给出我设计实现的一种矩形形式的比较布局。
由于考虑到颜色编码不能够清晰比较两组的差距,因此我重新设计了一种类似的矩形比较布局。具体来说,使用矩形条来代替颜色进行比较,矩形条的高度编码变量对的值。颜色编码不同的组。
注意:不同场景下有不同的分析任务需求,本文只是给了一种最基本最简单的实现代码。如果有更复杂但与此相似的需求,只需要在下面代码的基础上修改对应的数据。下面就直接贴出ECharts的核心代码和效果图。如果大家有什么问题和想法,欢迎评论交流学习。
核心代码
var airPList = ['PM2.5', 'PM10', 'SO2', 'NO2', 'CO', 'O3'];
var data1 = [
{
"s": "NO2",
"t": "CO",
"p": "0.001"
},
{
"s": "PM2.5",
"t": "CO",
"p": "0.0016"
},
{
"s": "PM10",
"t": "CO",
"p": "0.008"
},
{
"s": "SO2",
"t": "CO",
"p": "0.00177"
},
{
"s": "CO",
"t": "NO2",
"p": "0.0001"
},
{
"s": "SO2",
"t": "NO2",
"p": "0.0027"
},
{
"s": "CO",
"t": "O3",
"p": "0.004"
},
{
"s": "CO",
"t": "PM2.5",
"p": "0.0000"
},
{
"s": "CO",
"t": "PM10",
"p": "0.003"
},
{
"s": "SO2",
"t": "PM10",
"p": "0.0013"
},
{
"s": "CO",
"t": "SO2",
"p": "0.0393"
},
{
"s": "PM2.5",
"t": "SO2",
"p": "0.0127"
},
];
var data2 = [
{
"s": "NO2",
"t": "CO",
"p": "0.00000"
},
{
"s": "PM2.5",
"t": "CO",
"p": "0.006"
},
{
"s": "CO",
"t": "NO2",
"p": "0.00000"
},
{
"s": "SO2",
"t": "NO2",
"p": "0.002"
},
{
"s": "CO",
"t": "O3",
"p": "0.02"
},
{
"s": "CO",
"t": "PM10",
"p": "0.032"
},
{
"s": "SO2",
"t": "PM10",
"p": "0.01322"
},
{
"s": "CO",
"t": "SO2",
"p": "0.0047"
},
{
"s": "PM2.5",
"t": "SO2",
"p": "0.02932"
}
];
// 根据矩形内单元格的高度自定义修改
let [minH, maxH] = [0, 100];
let domain = [0, 1]; // p 值范围
let hScale = function(value) {
return minH + (value - domain[0]) * (maxH / (domain[1] - domain[0]));
}
// 根据初始数据计算生成对应格式的数据
function calc(data) {
let newData = [];
for (let i = 0; i < data.length; i++) {
let pValue = data[i]['p'];
let curStrength = Number(((0.05 - pValue) / 0.05).toFixed(4));
// 每条数据的 条形高度 和 偏移量Y
let curBarH = Number(hScale(curStrength).toFixed(4));
let symbolOffsetY = Number(((maxH - curBarH) / 2).toFixed(4));
newData.push({
value: [data[i]['t'], data[i]['s'], curStrength],
symbolSize: [0, curBarH],
symbolOffset: [0, symbolOffsetY],
});
}
return newData;
}
let newData1 = calc(data1);
let newData2 = calc(data2);
// 随着单元的大小变化--自定义
let symbolSize = [40, 100]; // 矩形条的宽和高
let symbolOffsetX1 = '-60%'; // 偏移量X
let symbolOffsetX2 = '60%';
for (let i = 0; i < newData1.length; i++) {
newData1[i].symbolSize[0] = symbolSize[0];
newData1[i].symbolOffset[0] = symbolOffsetX1;
}
for (let i = 0; i < newData2.length; i++) {
newData2[i].symbolSize[0] = symbolSize[0];
newData2[i].symbolOffset[0] = symbolOffsetX2;
}
option = {
tooltip: {
formatter: function (params) {
return '<div style="text-align: center;">'
+ params.seriesName + '</div>'
+ '<p>关系: ' + params.value[1] + ' → ' + params.value[0] + '</p>'
+ '<p>强度: ' + params.value[2] + '</p>';
}
},
grid: {
top: 20,
left: 20,
bottom: 5,
right: 5,
containLabel: true
},
xAxis: {
show: true,
position: 'top',
type: 'category',
data: airPList,
boundaryGap: true,
axisLine: {
show: false
},
axisLabel: {
color: '#000',
rotate: 90,
margin: 5,
},
axisTick: {
show: false,
lineStyle: {
color: '#FFFFFF'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#000'
}
}
},
yAxis: {
type: 'category',
data: airPList,
// boundaryGap: 0,
inverse: true,
axisLabel: { // 坐标轴刻度标签的相关设置
margin: 5, // 刻度标签与轴线之间的距离
// 刻度标签文字的颜色
color: '#000',
textStyle: {
align: 'right',
},
},
axisTick: {
show: false,
},
axisLine: {
show: false,
},
splitLine: {
show: true,
lineStyle: {
color: '#000'
}
}
},
series: [
{
name: '1',
type: 'scatter',
data: newData1,
symbol: "rect", // 将散点设计成矩形形状
// symbolSize: symbolSize, // 统一设计,由于每个单元格不一样,所以应定制化设计
// symbolOffset: ['-60%', 0],
label: {
show: false,
color: '#000',
},
emphasis: {
shadowBlur: 10,
shadowColor: "rgba(255, 255, 255, 0.5)"
}
},
{
name: '2',
type: 'scatter',
data: newData2,
symbol: "rect", // 将散点设计成矩形形状
// symbolSize: symbolSize,
// symbolOffset: ['60%', 0],
label: {
show: false,
color: '#000',
},
emphasis: {
shadowBlur: 10,
shadowColor: "rgba(255, 255, 255, 0.5)"
}
}
]
};
效果图
如有问题,请指正我修改。欢迎评论交流!!!觉得不错请给个赞👍吧😁