《Getting Started with D3》填坑之旅(六):第三章(下)

cover

Chapter 3. Scales, Axes and Lines(比例尺、坐标轴与线)

(接上篇:《Getting Started with D3》填坑之旅(五):第三章(上)

示例2:绘制两个地点的转门日均流量对比图

这里的转门指的是出入纽约地铁站的闸机旋转门,通过 1 人次则转数加 1。目标是将纽约两大地铁站——时代广场与大中央广场,每日进出闸机转门的平均流量(或人次数)对比情况,绘制在一张基于时间的散点图上,并进一步绘制散点折线图。

本例相当于在前一案例的基础上新增了 时间轴折线 两个知识点。操作步骤如下:

  1. 取值范围:由于两组数据共用一个绘图区,计算 x 轴与 y 轴的实际取值范围时,要把两个站点的所有数据点都考虑进 d3.extent()
  2. 比例尺
    1. 流量数 y 轴还是用线性比例尺(旧版:d3.scale.linear();新版:d3.scaleLinear());
    2. x 轴计算时间比例尺时,旧版用d3.time.scale(),新版用 d3.scaleTime()
  3. 坐标轴:y 轴与示例一相同;x 轴则使用时间轴(旧版:d3.svg.axis().scale(time_scale);新版:d3.axisBottom(time_scale));
  4. 坐标标签:无变化;
  5. 基本界面:需要创建两个 circle 组,分别代表时代广场和大中央广场,其余不变。
  6. 样式设置:给不同的站点数据设置不同的颜色加以区分。

效果如下:

在这里插入图片描述

代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
    <title>Ch3 - Example 2 | Getting Started with D3</title>
</head>

<body>
    <h2>Ch3 - Example 2 | Graphing Turnstile Traffic</h2>
    <script src="/demos/js/d3.js"></script>
    <script>
        let json = null;
        function draw(data) {
            "use strict";

            // badass visualization code goes here
            json = data;

            // Viewport setup
            var margin = 40,
                width = 700 - margin,
                height = 300 - margin;
            
            var chart = d3.select('body')
                .append('svg')
                    .attr('width', width + margin)
                    .attr('height', height + margin)
                .append('g')
                    .attr('class', 'chart');
            
            chart.selectAll('circle.times_square')
                .data(data.times_square)
                .enter()
                .append('circle')
                    .attr('class', 'times_square');

            chart.selectAll('circle.grand_central')
                .data(data.grand_central)
                .enter()
                .append('circle')
                    .attr('class', 'grand_central');

            // scales
            // y_scale
            var count_extent = d3.extent(
                data.times_square.concat(data.grand_central),
                function(d) { return d.count }
            );
            var count_scale = d3.scale.linear()
                .domain(count_extent)
                .range([height, margin]);
            // x_scale
            var time_extent = d3.extent(
                data.times_square.concat(data.grand_central),
                function(d) { return d.time }
            );
            var time_scale = d3.time.scale()
                .domain(time_extent)
                .range([margin, width]);
            
            // draw circles
            chart.selectAll('circle')
                .attr('cx', function(d) { return time_scale(d.time) })
                .attr('cy', function(d) { return count_scale(d.count) })
                .attr('r', 3);

            // Axes
            // x 
            var time_axis = d3.svg.axis().scale(time_scale);
            chart.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0, '+height+')')
                .call(time_axis);
            // y
            var count_axis = d3.svg.axis().scale(count_scale).orient('left');
            chart.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate('+margin+', 0)')
                .call(count_axis);
        }

        d3.json("/demos/data/turnstile_traffic.json", draw);
    </script>
    <style>
        .axis {
            font-family: Arial;
            font-size: 0.6em;
        }
        path {
            fill: none;
            stroke: black;
            stroke-width: 2px;
        }
        .tick {
            fill: none;
            stroke: black;
        }
        circle {
            stroke: black;
            stroke-width: 0.5px;
        }
        circle.times_square {
            fill: DeepPink;
        }
        circle.grand_central {
            fill: MediumSeaGreen;
        }
    </style>
</body>

</html>

这里和书中的示例源码相比略有调整。首先是用变量引用替换了多次从 d3.select() 引用 SVG 元素。然后是实操过程中,发现第 31 行创建的 chart 组是多余的。其实应该将所有的绘图元素都放到 chart 里,使其成为一个整体。于是有了以 chart 变量开头的微调(L33,L39,L64,L78)。

接下来引入 path 元素,将点连成线。path 的关键属性 d,其值为一组绘图指令,负责将多个点按指令顺次连成一段折线。D3 对 d 属性的取值封装了一个折线构造函数,利用该函数,只需要指定源数据和位置信息,即可快速绘制折线段。D3 的这一强大的工具函数,旧版为 d3.svg.line(),新版为 d3.line()。返回的结果函数,包含一个 x(fn)y(fn) 方法,参数都是一个访问函数 fn,负责将对应轴上的数据点,分别映射到该比例尺下的绘图区。

加入以下语句绘制折线:

// paths
var line = d3.svg.line()
    .x(function(d) { return time_scale(d.time) })
    .y(function(d) { return count_scale(d.count) });

// make lines
svg.append('path')
    .attr('class', 'times_square')
    .attr('d', line(data.times_square));
svg.append('path')
    .attr('class', 'grand_central')
    .attr('d', line(dat

在加上折线的 CSS 样式:

path.times_square {
    stroke: DeepPink;
}
path.grand_central {
    stroke: MediumSeaGreen;
}

得到两站点的折线散点图:

在这里插入图片描述

再加上与示例一类似的坐标轴标签,即可得到最终效果:

// axis labels
// y
chart.select('.y.axis')
    .append('text')
        .text('mean number of turnstile revolutions')
        .attr('transform', 'rotate(90, '+ (-margin) +', 0)')
        .attr('x', 20)
        .attr('y', 0);
// x
chart.select('.x.axis')
    .append('text')
        .text('time')
        .attr('x', function(){ return (width / 1.6) - margin })
        .attr('y', margin / 1.5);

在这里插入图片描述

有了示例一的填坑经历,示例二中的新版本改写的坑也就不再是坑了:

  1. 坐标轴标签默认不显示:需对 text 标签设置填充颜色:.attr('fill', 'black')
  2. 坐标轴标签位置偏移:y 轴标签旋转后需加入垂直平移量,具体根据实际确定(这里取 translate(155));
  3. 坐标轴刻度标签过粗:注释 CSS 样式:.tick{ stroke: black; }

连点成线后,就可以进行简单的数据分析了:时代广场的日均流量滞后于大中央广场;工作日早高峰时段的流量峰值方面,大中央广场更胜一筹……

虽然缺了点图例标识哪条线是哪个站点,不过上手阶段不必一步到位,后续章节应该有扩展的。

新版 D3 完整代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
    <title>Ch3 - Example 2 | Getting Started with D3</title>
</head>

<body>
    <h2>Ch3 - Example 2 | Graphing Turnstile Traffic</h2>
    <script src="/demos/js/d3.v6.js"></script>
    <script>
        let json = null;
        function draw(data) {
            "use strict";

            // badass visualization code goes here
            json = data;

            // Viewport setup
            const margin = 40,
                width = 700 - margin,
                height = 300 - margin;
            
            const chart = d3.select('body')
                .append('svg')
                    .attr('width', width + margin)
                    .attr('height', height + margin)
                .append('g')
                    .attr('class', 'chart');
            
            chart.selectAll('circle.times_square')
                .data(data.times_square)
                .join(enter => enter.append('circle')
                    .attr('class', 'times_square')
                );

            chart.selectAll('circle.grand_central')
                .data(data.grand_central)
                .join(enter => enter.append('circle')
                    .attr('class', 'grand_central')
                );

            // scales
            // y_scale
            const count_extent = d3.extent(
                data.times_square.concat(data.grand_central),
                d => d.count
            );
            const count_scale = d3.scaleLinear()
                .domain(count_extent)
                .range([height, margin]);
            // x_scale
            const time_extent = d3.extent(
                data.times_square.concat(data.grand_central),
                d => d.time
            );
            const time_scale = d3.scaleTime()
                .domain(time_extent)
                .range([margin, width]);
            
            // draw circles
            d3.selectAll('circle')
                .attr('cx', d => time_scale(d.time))
                .attr('cy', d => count_scale(d.count))
                .attr('r', 3);

            // Axes
            // x 
            const time_axis = d3.axisBottom(time_scale);
            chart.append('g')
                .attr('class', 'x axis')
                .attr('transform', `translate(0, ${height})`)
                .call(time_axis);
            // y
            const count_axis = d3.axisLeft(count_scale);
            chart.append('g')
                .attr('class', 'y axis')
                .attr('transform', `translate(${margin}, 0)`)
                .call(count_axis);

            // paths
            const line = d3.line()
                .x(d => time_scale(d.time) )
                .y(d => count_scale(d.count) );

            // make lines
            chart.append('path')
                .attr('class', 'times_square')
                .attr('d', line(data.times_square));
            chart.append('path')
                .attr('class', 'grand_central')
                .attr('d', line(data.grand_central));

            // axis labels
            // y
            d3.select('.y.axis')
                .append('text')
                    .text('mean number of turnstile revolutions')
                    .attr('fill', 'black')
                    .attr('transform', `rotate(90, ${-margin}, 0) translate(155)`)
                    .attr('x', 20)
                    .attr('y', 0);
            // x
            d3.select('.x.axis')
                .append('text')
                    .text('time')
                    .attr('fill', 'black')
                    .attr('x', (width / 1.6) - margin)
                    .attr('y', margin / 1.5);

        }

        d3.json("/demos/data/turnstile_traffic.json").then(draw)
            .catch(console.error);
    </script>
    <style>
        .axis {
            font-family: Arial;
            font-size: 0.6em;
        }
        path {
            fill: none;
            stroke: black;
            stroke-width: 2px;
        }
        path.times_square {
            stroke: DeepPink;
        }
        path.grand_central {
            stroke: MediumSeaGreen;
        }
        .tick {
            fill: none;
            /* stroke: black; */
        }
        circle {
            stroke: black;
            stroke-width: 0.5px;
        }
        circle.times_square {
            fill: DeepPink;
        }
        circle.grand_central {
            fill: MediumSeaGreen;
        }
    </style>
</body>

</html>
内容概要:本文深入探讨了多种高级格兰杰因果检验方法,包括非线性格兰杰因果检验、分位数格兰杰因果检验、混频格兰杰因果检验以及频域因果检验。每种方法都有其独特之处,适用于不同类型的时间序列数据。非线性格兰杰因果检验分为非参数方法、双变量和多元检验,能够在不假设数据分布的情况下处理复杂的关系。分位数格兰杰因果检验则关注不同分位数下的因果关系,尤其适合经济数据的研究。混频格兰杰因果检验解决了不同频率数据之间的因果关系分析问题,而频域因果检验则专注于不同频率成分下的因果关系。文中还提供了具体的Python和R代码示例,帮助读者理解和应用这些方法。 适合人群:从事时间序列分析、经济学、金融学等领域研究的专业人士,尤其是对非线性因果关系感兴趣的学者和技术人员。 使用场景及目标:①研究复杂非线性时间序列数据中的因果关系;②分析不同分位数下的经济变量因果关系;③处理不同频率数据的因果关系;④识别特定频率成分下的因果关系。通过这些方法,研究人员可以获得更全面、细致的因果关系洞察。 阅读建议:由于涉及较多数学公式和编程代码,建议读者具备一定的统计学和编程基础,特别是对时间序列分析有一定了解。同时,建议结合具体案例进行实践操作,以便更好地掌握这些方法的实际应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安冬的码畜日常

您的鼓励是我持续优质内容的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值