平行坐标系的介绍:http://www.zhaiqianfeng.com/2016/09/parallel-coordinates.html
数据来自:https://www.worldbank.org/
平行坐标系是用来解决同时展示多个坐标轴的数据集的可视化问题。平行坐标系是被广泛使用的可视化技术之一,是分析多维数据的强有力的工具。
这里我在做的时候采用了最简单的方法制作的,即创建多个等长的比例尺,然后等距竖向排列在svg画布中,再用一条一条的折线将其连接起来。
效果图:
各坐标轴所示数据分别为:国家id,年龄15 - 64人口比例,0 - 14人口比例,人均gdp,65+人口比例。
不同国家的数据用不同的颜色表示,左下角为不同国家的颜色对应。
代码:
<script src="https://d3js.org/d3.v5.js"></script>
<script>
let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
w *= 0.98;
h *= 0.9;
let NAME = ["id", "15-64(%)", "0-14(%)", "gdp($)", "65+(%)"];
let inner = {top: 50, right: 50, bottom: 50, left: 50};
let Data = new Array();
let T = new Array();
let LineData = new Array();
let Scale = new Array();
let Axis = new Array();
let CNAME = new Array();
// 定义所有坐标轴
for (let i = 0; i < 5; ++i) {
Scale[i] = d3.scaleLinear();
if (i === 0) Scale[i].domain([1, 13]);
else if (i != 3) Scale[i].domain([0, 100]);
else Scale[i].domain([0, 60000]);
Scale[i].range([h - inner.top - inner.bottom, 0])
.nice();
Axis[i] = d3.axisLeft(Scale[i]);
}
let svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g");
svg.selectAll("text")
.data(NAME)
.enter()
.append("text")
.attr("transform", function (d, i) {
return "translate(" + (inner.left * 0.7 + (w / 6) * i) + "," + (inner.top * 0.7) + ")";
})
.attr("fill", 'black')
.attr("font-size", 10)
.text(function (d, i) {
return NAME[i];
});
initialize();
// 连线
let Line = d3.line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
});
let LineGraph = new Array();
for (let i = 0; i < 13; ++i) {
LineGraph[i] = svg.append("path")
.attr("d", Line(LineData[i]))
.attr("stroke", function (d) {
return T[i];
})
.attr("stroke-width", 2)
.attr("fill", "none");
}
for (let i = 0; i < 5; ++i) {
svg.append("g")
.attr("transform", "translate(" + (inner.left + (w / 6) * i) + "," + (inner.top) + ")")
.call(Axis[i]);
}
// 定义国家构成的方块以说明
let svg2 = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg2.selectAll("rect")
.data(CNAME)
.enter()
.append("rect")
.attr("y", function (d, i) {
return i * 20;
})
.attr("x", function (d) {
return 20;
})
.attr("width", 20)
.attr("height", 20)
.attr("fill", function (d, i) {
return T[i];
});
svg2.selectAll("text")
.data(CNAME)
.enter()
.append("text")
.attr("transform", function (d, i) {
return "translate(" + (40) + "," + (i * 20 + 15) + ")";
})
.attr("fill", 'black')
.attr("font-size", 10)
.text(function (d, i) {
return CNAME[i];
});
</script>
代码解释:
svg.selectAll("text")
.data(NAME)
.enter()
.append("text")
.attr("transform", function (d, i) {
return "translate(" + (inner.left * 0.7 + (w / 6) * i) + "," + (inner.top * 0.7) + ")";
})
.attr("fill", 'black')
.attr("font-size", 10)
.text(function (d, i) {
return NAME[i];
});
将名称设置到每个坐标轴的上方,transform可以将位置进行平移。
font-size设置字体大小。
let Line = d3.line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
});
首先创建直线生成器,就可将数据信息准换成svg路径信息。
let LineGraph = new Array();
for (let i = 0; i < 13; ++i) {
LineGraph[i] = svg.append("path")
.attr("d", Line(LineData[i]))
.attr("stroke", function (d) {
return T[i];
})
.attr("stroke-width", 1)
.attr("fill", "none");
}
随后是画线过程。stroke-with可以控制线的宽度。stroke控制线的颜色,T数组预先生成好rgba的颜色变量。
Initialize部分,即原始数据集以及画线的坐标处理:
function initialize() {
CNAME = [
"Bangladesh",
"Brazil",
"China",
"Germany",
"Indonesia",
"India",
"Iran",
"Japan",
"Mexico",
"Nigeria",
"Pakistan",
"Russian Federation",
"United States",
];
Data = [
{id: 1, d2: 28.2246, d1: 66.6259, d3: 5.14948, d4: 1563.99},
{id: 2, d2: 21.6838, d1: 69.7107, d3: 8.60549, d4: 9880.95},
{id: 3, d2: 17.9374, d1: 71.7175, d3: 10.3451, d4: 8759.04},
{id: 4, d2: 13.4507, d1: 65.1778, d3: 21.3715, d4: 44240},
{id: 5, d2: 26.9149, d1: 67.4032, d3: 5.68195, d4: 3836.91},
{id: 6, d2: 27.4787, d1: 66.5382, d3: 5.98311, d4: 1981.27},
{id: 7, d2: 24.2478, d1: 69.7135, d3: 6.03871, d4: 5627.75},
{id: 8, d2: 12.8144, d1: 60.0761, d3: 27.1095, d4: 38332},
{id: 9, d2: 26.9392, d1: 66.02, d3: 7.04077, d4: 9278.42},
{id: 10, d2: 44.0131, d1: 53.2377, d3: 2.74914, d4: 1968.56},
{id: 11, d2: 35.4786, d1: 60.2075, d3: 4.31384, d4: 1464.99},
{id: 12, d2: 17.634, d1: 68.1027, d3: 14.2633, d4: 10750.6},
{id: 13, d2: 18.8585, d1: 65.7221, d3: 15.4194, d4: 59927.9},];
for (i = 0; i < 13; i++) {
let x = Math.random() * 256;
let y = Math.random() * 256;
let z = Math.random() * 256;
T[i] = 'rgba(' + x + ',' + y + ',' + z + ',0.8)';
let H = h - inner.top - inner.bottom;
LineData[i] = new Array();
LineData[i][0] = {"x": (inner.left + (w / 6) * 0), "y": (-inner.bottom + h - H * (Data[i].id - 1) / 12)};
LineData[i][1] = {"x": (inner.left + (w / 6) * 1), "y": (-inner.bottom + h - H * (Data[i].d1 - 1) / 100)};
LineData[i][2] = {"x": (inner.left + (w / 6) * 2), "y": (-inner.bottom + h - H * (Data[i].d2 - 1) / 100)};
LineData[i][3] = {"x": (inner.left + (w / 6) * 3), "y": (-inner.bottom + h - H * (Data[i].d4 - 1) / 60000)};
LineData[i][4] = {"x": (inner.left + (w / 6) * 4), "y": (-inner.bottom + h - H * (Data[i].d3 - 1) / 100)};
}
}
注意LineData数组存储的是每条线的节点信息,要注意计算清楚数据值占比例尺的比例以及应当平移的量,这里的svg画存在一个内部的空白框,画之前一定要确定清楚。