D3 - 平行坐标系

平行坐标系的介绍:http://www.zhaiqianfeng.com/2016/09/parallel-coordinates.html
数据来自:https://www.worldbank.org/

平行坐标系是用来解决同时展示多个坐标轴的数据集的可视化问题。平行坐标系是被广泛使用的可视化技术之一,是分析多维数据的强有力的工具。

这里我在做的时候采用了最简单的方法制作的,即创建多个等长的比例尺,然后等距竖向排列在svg画布中,再用一条一条的折线将其连接起来。

效果图:
D3 - 平行坐标系_第1张图片
各坐标轴所示数据分别为:国家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画存在一个内部的空白框,画之前一定要确定清楚。

你可能感兴趣的:(#,D3)