D3 - 平行座標系

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章