平行座標系的介紹: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畫存在一個內部的空白框,畫之前一定要確定清楚。