山东大学可视化2024第一次实验

问题:

画出美国1900与2000人口分布

介绍:

只是一个非常粗糙的可视化模板,注意后续改一下颜色什么的~

步骤:

1. 安装vscode

2.下载安装图片中插件

3.新建一个文件夹并添加到工作区

4.创建一个html文件

5.将数据粘贴到文件夹中

6.将以下代码粘贴到html文件中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>美国人口分布1900与2000</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        .bar {
            fill-opacity: 0.5; /*透明度*/
        }
        .bar:hover {
            fill-opacity: 1;
        }
        .axis text {
            font: 10px sans-serif; /*行字体大小*/
        }
        .legend text {
            font: 12px sans-serif; /*类别字体大小*/
        }
        .label {                
            font-size: 5px;     /*标签字体大小*/
            text-anchor: middle;
            fill: #333;
        }
    </style>
</head>
<body>
    <script>
        // 加载csv文件
        d3.csv('census2000.csv').then(data => {
            // 把各属性字符转换成数字类型
            data.forEach(d => {
                d.Sex = +d.Sex; 
                d.Year = +d.Year; 
                d.Age = +d.Age; 
                d.People = +d.People; 
            });

            // 设置SVG图表的尺寸和边缘留白
            const margin = {top: 100, right: 30, bottom: 100, left: 60},
                  width = 2000 - margin.left - margin.right,
                  height = 1000 - margin.top - margin.bottom;

            // 创建SVG容器
            const svg = d3.select('body').append('svg')
                .attr('width', width + margin.left + margin.right)
                .attr('height', height + margin.top + margin.bottom)
              .append('g')
                .attr('transform', `translate(${margin.left},${margin.top})`);

            // 设置D3.js中的X,Y轴比例尺
            const x0 = d3.scaleBand()
                .domain([...new Set(data.map(d => d.Year))])
                .range([0, width])
                .padding(0.1);

            const x1 = d3.scaleBand()
                .domain([...new Set(data.map(d => d.Age))])
                .range([0, x0.bandwidth()])
                .padding(0.05);

            // 让y轴从中间开始,设置两个y轴
            const y = d3.scaleLinear()
                .domain([0, d3.max(data, d => Math.abs(d.People))])
                .nice()
                .range([height / 2, 0]); // Male

            const yBottom = d3.scaleLinear()
                .domain([0, d3.max(data, d => Math.abs(d.People))])
                .nice()
                .range([height / 2, height]); // Female

            const color = d3.scaleOrdinal()
                .domain([1, 2])
                .range(['steelblue', 'pink']);

            // 创造柱子与定义标签
            const groups = svg.append('g')
                .selectAll('g')
                .data(d3.group(data, d => d.Year))
              .enter().append('g')
                .attr('transform', d => `translate(${x0(d[0])},0)`);

            groups.selectAll('rect')
                .data(d => d[1])
              .enter().append('rect')
                .attr('x', d => x1(d.Age))
                .attr('y', d => d.Sex === 1 ? y(d.People) : height / 2) // Male (Sex==1) bars go upward, Female (Sex==2) bars start from the middle and go downward
                .attr('width', x1.bandwidth())
                .attr('height', d => d.Sex === 1 ? height / 2 - y(d.People) : yBottom(d.People) - height / 2) // Adjust height based on Sex
                .attr('fill', d => color(d.Sex))
                .attr('class', 'bar');

            // 把标签贴到柱子上
            groups.selectAll('text')
                .data(d => d[1])
              .enter().append('text')
                .attr('x', d => x1(d.Age) + x1.bandwidth() / 2)
                .attr('y', d => d.Sex === 1 ? y(d.People) - 5 : yBottom(d.People) + 15) // Adjust text position above or below the bar
                .attr('class', 'label')
                .text(d => `${d.Age} yrs\n${Math.abs(d.People).toLocaleString()}`);

            // 添加x轴
            svg.append('g')
                .attr('class', 'x-axis')
                .attr('transform', `translate(0,${height / 2})`) // Center x-axis
                .call(d3.axisBottom(x0).tickSize(0))
              .selectAll('text')
                .attr('transform', 'rotate(-45)')
                .style('text-anchor', 'end');

            // 添加y轴
            svg.append('g')
                .attr('class', 'y-axis')
                .call(d3.axisLeft(y).ticks(5));

            svg.append('g')
                .attr('class', 'y-axis-bottom')
                .attr('transform', `translate(0,${0})`)
                .call(d3.axisLeft(yBottom).ticks(5));

            // 添加标签
            const legend = svg.append('g')
                .attr('class', 'legend')
                .attr('transform', `translate(${width - 150},0)`);

            legend.selectAll('rect')
                .data([1, 2])
              .enter().append('rect')
                .attr('x', 0)
                .attr('y', (d, i) => i * 20)
                .attr('width', 18)
                .attr('height', 18)
                .attr('fill', d => color(d));

            legend.selectAll('text')
                .data([1, 2])
              .enter().append('text')
                .attr('x', 30)
                .attr('y', (d, i) => i * 20 + 9)
                .attr('dy', '0.32em')
                .text(d => d === 1 ? 'Male' : 'Female');
        });
    </script>
</body>
</html>

7.点击图中选项

8.获得结果

实验总结:

本实验让我学习了html与javascript的基本语法并对D3可视化库的使用有了初步的理解,如果想要创建一个柱状图步骤如下:

  • 输入!形成html模板
  • head中加入<script src="https://d3js.org/d3.v7.min.js"></script>导入d3库
  • 定义style模块
  • <style>
    
            .bar {
    
                fill-opacity: 0.5; /*透明度*/
            }
            .bar:hover {
                fill-opacity: 1;
            }
            .axis text {
                font: 10px sans-serif; /*行字体大小*/
            }
            .legend text {
                font: 12px sans-serif; /*类别字体大小*/
            }
            .label {                
                font-size: 5px;     /*标签字体大小*/
                text-anchor: middle;
                fill: #333;
            }
        </style>
  • style可以定义的东西:
  • 加载数据并进行格式转换
  •  // 加载csv文件
            d3.csv('census2000.csv').then(data => {
                // 把各属性字符转换成数字类型
                data.forEach(d => {
                    d.Sex = +d.Sex; 
                    d.Year = +d.Year; 
                    d.Age = +d.Age; 
                    d.People = +d.People; 
                });

  • 设置SVG图表的尺寸和边缘留白
const margin = {top: 100, right: 30, bottom: 100, left: 60},
                  width = 2000 - margin.left - margin.right,
                  height = 1000 - margin.top - margin.bottom;

  • 创建SVG容器
 // 创建SVG容器
            const svg = d3.select('body').append('svg')
                .attr('width', width + margin.left + margin.right)
                .attr('height', height + margin.top + margin.bottom)
              .append('g')
                .attr('transform', `translate(${margin.left},${margin.top})`);
  • 定义比例尺,以x0为例定义比例尺的形状:scaleBand,定义比例尺由什么决定:year。domain方法确定了比例尺的输入范围,即数据集中出现的所有唯一年份。range方法指定了输出范围。padding 方法指定比例尺间距。
 // 设置D3.js中的X,Y轴比例尺
            const x0 = d3.scaleBand()
                .domain([...new Set(data.map(d => d.Year))])
                .range([0, width])
                .padding(0.1);

            const x1 = d3.scaleBand()
                .domain([...new Set(data.map(d => d.Age))])
                .range([0, x0.bandwidth()])
                .padding(0.05);

            // 让y轴从中间开始,设置两个y轴
            const y = d3.scaleLinear()
                .domain([0, d3.max(data, d => Math.abs(d.People))])
                .nice()
                .range([height / 2, 0]); // Male

            const yBottom = d3.scaleLinear()
                .domain([0, d3.max(data, d => Math.abs(d.People))])
                .nice()
                .range([height / 2, height]); // Female

            const color = d3.scaleOrdinal()
                .domain([1, 2])
                .range(['steelblue', 'pink']);
  • 创建SVG组元素
  • const groups = svg.append('g');

    这行代码在SVG容器中添加一个新的<g>元素。<g>元素是一个容器,用于将其他SVG元素分组在一起,方便对这些元素进行变换操作。

  • 选择所有<g>元素

  • .selectAll('g');

    这行代码准备选择所有的<g>元素,但实际上此时还没有这些元素存在,因此这只是准备操作。

  • 绑定数据:

    .data(d3.group(data, d => d.Year));

    这里使用d3.group函数对原始数据集data进行分组,根据每个数据项的Year属性。d3.group函数返回一个Map对象,其中键是分组的依据(在这里是年份),值是一个数组,包含所有该年份的数据项。

  • 进入模式:

    .enter().append('g');

    进入模式是D3.js中的一个重要概念,用于处理数据绑定到DOM元素时的新增情况。这里为每个分组的数据创建一个新的<g>元素。如果之前没有相应的<g>元素存在,则会创建它们。

  • 创建矩形并为矩形指定位置
                groups.selectAll('rect')
                    .data(d => d[1])
                  .enter().append('rect')
                    .attr('x', d => x1(d.Age))
                    .attr('y', d => d.Sex === 1 ? y(d.People) : height / 2) // Male (Sex==1) bars go upward, Female (Sex==2) bars start from the middle and go downward
                    .attr('width', x1.bandwidth())
                    .attr('height', d => d.Sex === 1 ? height / 2 - y(d.People) : yBottom(d.People) - height / 2) // Adjust height based on Sex
                    .attr('fill', d => color(d.Sex))
                    .attr('class', 'bar');

    selectAll('rect'):创建矩形,.data(d => d[1]):将数据与矩形绑定,.enter().append('rect'):如果矩形数量不够就生成新的矩形,.attr('x', d => x1(d.Age)):设置矩形的左边缘位置,使用x1比例尺将年龄映射到X轴上的位置。.attr('y', d => d.Sex === 1 ? y(d.People) : height / 2):设置矩形的顶部位置。如果是男性(Sex == 1),使用y比例尺映射人数到Y轴,并且条形图向上延伸;如果是女性(Sex == 2),条形图的顶部固定在中间位置(height / 2),并且条形图向下延申。.attr('fill', d => color(d.Sex)): 设置矩形的颜色。使用之前定义的color比例尺根据性别分配颜色。.attr('class', 'bar'):为每个矩形添加一个类名'bar',便于后续的CSS样式控制。

  • 创建标签并指定位置,方式类似于创建矩形,只是多了一条.text(d => `${d.Age}yrs\n${Math.abs(d.People).toLocaleString()}`),它的作用是指定标签内容
 groups.selectAll('text')
                .data(d => d[1])
              .enter().append('text')
                .attr('x', d => x1(d.Age) + x1.bandwidth() / 2)
                .attr('y', d => d.Sex === 1 ? y(d.People) - 5 : yBottom(d.People) + 15) // Adjust text position above or below the bar
                .attr('class', 'label')
                .text(d => `${d.Age} yrs\n${Math.abs(d.People).toLocaleString()}`);

这段代码创建了一个简单的图例,其中包含了两个条目:“Male”和“Female”,每个条目都有一个颜色标记和一个描述性标签。

  • 添加坐标轴相关信息
     // 添加x轴
                svg.append('g')
                    .attr('class', 'x-axis')
                    .attr('transform', `translate(0,${height / 2})`) // Center x-axis
                    .call(d3.axisBottom(x0).tickSize(0))
                  .selectAll('text')
                    .attr('transform', 'rotate(-45)')
                    .style('text-anchor', 'end');
    
                // 添加y轴
                svg.append('g')
                    .attr('class', 'y-axis')
                    .call(d3.axisLeft(y).ticks(5));
    
                svg.append('g')
                    .attr('class', 'y-axis-bottom')
                    .attr('transform', `translate(0,${0})`)
                    .call(d3.axisLeft(yBottom).ticks(5));
    

  • 创建图例组:

    const legend = svg.append('g')
        .attr('class', 'legend')
        .attr('transform', `translate(${width - 150},0)`);

    这段代码在SVG容器中添加了一个新的<g>元素,用于容纳图例。通过设置transform属性,将图例定位到SVG容器的右侧边缘,距离右边150个单位的位置。这里假设width变量代表整个SVG的宽度。

  • 添加矩形标记:

    legend.selectAll('rect')
        .data([1, 2])
      .enter().append('rect')
        .attr('x', 0)
        .attr('y', (d, i) => i * 20)
        .attr('width', 18)
        .attr('height', 18)
        .attr('fill', d => color(d));

    这里使用了D3的数据绑定方法.selectAll('rect').data([1, 2]),将数据[1, 2]与图例中的矩形元素关联起来。然后使用.enter().append('rect')为每个数据点创建一个矩形。这些矩形将作为图例中的颜色标记。它们的位置、大小以及填充颜色都是根据特定的规则设定的:

    • 每个矩形的左上角的x坐标固定为0。
    • y坐标基于索引i,每个矩形向下偏移20个单位,因此第一个矩形位于顶部,第二个矩形紧随其下。
    • 宽度和高度均为18个单位。
    • 填充颜色由color(d)函数决定,该函数应该是一个颜色比例尺,根据输入值返回相应的颜色。
  • 为图例添加文本标签

    legend.selectAll('text')
        .data([1, 2])
      .enter().append('text')
        .attr('x', 30)
        .attr('y', (d, i) => i * 20 + 9)
        .attr('dy', '0.32em')
        .text(d => d === 1 ? 'Male' : 'Female');

    这段代码创建了两个文本元素,分别对应于之前创建的两个矩形标记。每个文本元素的文本内容取决于其关联的数据值:当数据值为1时,显示“Male”;当数据值为2时,显示“Female”。这些文本标签的位置也进行了适当的调整,以确保它们位于对应的矩形标记旁边,且垂直居中对齐。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值