使用JavaScript整理数据的简单实例

本文是前段时间做的一个疫情数据可视化案例的数据整理部分的教程。个人水平有限,欢迎批评指正。

🐖:本教程针对的是几乎没有编程基础的艺术类专业同学,但又不可避免的会遇到一些难以理解的概念,编程的学习本身就不是一个线性渐进的过程,这种现象被称为“过早引用”(关于这点推荐这篇文章),遇到“过早引用”的知识点我的建议是:不懂也要硬着头皮跟着做完,体会整个过程,并且善于使用搜索引擎。当然你也可以在博客首页找到我的联系方式,我很高兴能够跟大家交流学习。

⚡原案例使用了vue.js,AntV等框架,本教程只针对数据处理部分,所以单独把数据从整个项目里抽离,更加简介直观地讲数据处理的部分。

完整项目文件,你可以在Github下载

概述

我们要把每个市的冠状病毒确诊人数绘制到地图上该市对应的地理坐标上,需要数据是:

  • 每个市的确诊人数
  • 每个市的经纬度信息

通过每个市的经纬度信息我们就能在地图上精确的找到每个城市的位置,再把确诊人数绘制到每个点就能制作关于地理位置的数据可视化。

而常常当我们去获取这些数据时,拿到数据并不是我们想要的样子,大多数情况这些数据会非常乱,会包括很多乱七八糟的无用数据,而这个教程就是通过一个简单的案例,来告诉大家整理数据的过程。

这个案例中我实际拿到的数据错综复杂,经过整理每个城市的数据是这样的:

1
2
3
4
5
6
7
8
9
10
{
cityName: "杭州",
currentConfirmedCount: 75,
confirmedCount: 168,
suspectedCount: 0,
curedCount: 93,
deadCount: 0,
locationId: 330100,
cityEnglishName: "Hangzhou"
}

其中我们需要的数据有城市的名称cityName,城市当前确证的人数currentConfirmedCount,所以目前数据情况是这样的:

  • 每个市的确诊人数 ✔
  • 每个市的经纬度信息 ❌

我们需要把这两项有用的数据拿出来,并且还要另外找每个市的经纬度信息。因为这个教程是整理数据,所以我直接把每个市的经纬度数据提供给大家了,大概长下面这样。

1
2
3
4
杭州: {
x: "120.155070",
y: "30.274084"
}

最后把这些数据合并起来才是我们需要的数据。

1
2
3
4
5
6
{
cityName: "杭州",
currentConfirmedCount: 75,
lon: "120.155070",
lat: "30.274084"
}

最终整理完的数据是这样的,当然这只是一个城市的数据,我们需要把每个城市的数据都封装成这样,就可以为后续的图像绘制提供理想的数据了。

📦 要做的事:

💾 需要准备的软件:

📁 需要准备的文件:

必须的基础知识

在开始操作之前,我们先简单了解一下所需的基本知识。

对象(object)

对象概述🔨

javascript 中的对象(object),和其它编程语言中的对象一样,可以比照现实生活中的对象(物体)来理解它。

我们拿它和一个杯子做下类比。一个杯子是一个对象,杯子有颜色,图案,重量和材质等属性。同样,javascript对象也有属性来定义它的特征。

在这个案例中我把每个城市当成了一个对象,我们最后封装好的数据就是一个对象。

1
2
3
4
5
6
{
cityName: "杭州",
currentConfirmedCount: 75,
lon: "120.155070",
lat: "30.274084" ,
}

如你所见,这个由两个花括号包裹起来的东西就是一个对象,其中有4个属性分别是cityNamecurrentConfirmedCountlonlat

冒号前面的部分被称为键(key),冒号后面的部分被称为值(value)。

感受对象🔨

上面讲了一大堆,理解不了没关系,重点是下面跟着我做的部分,我们打两行代码感受下什么是对象。(以下内容我会插入大量GIF给大家参考)

通常我们测试javascript最方便的地方就是chrome浏览器的控制台(console)。

首先我们打开chrome,随便进入一个网页,按下F12。你就会看到控制台。

现在我们尝试定义一个新的对象orange ,输入以下代码,回车。

输入:

1
let orange = {color:"red",size:"10cm",weight:"100g"};

​ 输出:

1
undefined

GIF中我直接粘贴了上面一段代码,但是不熟悉的同学建议手动敲一遍。

1
2
3
4
5
let orange = {
color:"red",
size:"10cm",
weight:"100g"
};

我们把上面一段代码格式化来看,首先我们定义一个对象用到了 let关键字,整个定义的语法可以写为let [对象名] = {...},然后对象中的属性的格式是[属性名]:[属性值] ,这里要注意的是连接属性和属性值的符号是冒号:,并且两个属性定义之间用逗号,分割。

把上面的代码输进去并回车之后我们看到的是一个灰色的undefined,我们先不用管这个什么意思,正常情况下我们已经定义好了一个orange对象,并且它有三个属性,颜色(color)是红色(red),尺寸(size)是10cm,重量(weight)是100g。

这里插一句,Javascript里面的变量名是可以用中文的,所以理论上你可以这么写:

1
let 橘子 = {颜色:"红色",尺寸:"10厘米",重量:"100克"};

但是习惯上,我们一般都会用英文来做变量名。

回到主题上,如果成功定义了orange,我们在控制台中输入orange并且回车,会看到orange对象的输出,如果你看到了跟GIF里一样的输出,那么你的第一个对象定义成功了。🙈

接下来我们来获取这个orange对象里的特定属性。

输入:

1
orange.size

输出:

1
"10cm"

如你所见,通常情况下我们用点.来获取对象中的属性。

获取对象中的属性还有另外一种方法,在对象后面加方括号里面写属性名。

输入:

1
orange["color"]

输出:

1
"red"

总结🔨

对象是javascript中非常常用的数据结构,使用对象来组织我们数据的好处是数据结构理非常清楚,并且调用非常方便。

数组(Array)

通过上面的介绍对象,我们能把每个城市的数据组织起来,放在一对花括号(对象)中,但是我们有千千万万个城市的数据,不可能把每个城市的数据都储存在单独的变量中,这样调用数据的时候就需要输入每个城市的名字,太麻烦。

所以就需要用到数组,来放置所有对象。

数组概述🔨

数组通常被描述为“像列表一样的对象”; 简单来说,数组是一个包含了多个值的对象。数组对象可以存储在变量中,并且能用和其他任何类型的值完全相同的方式处理,区别在于我们可以单独访问列表中的每个值,并使用列表执行一些高效的操作,如循环,对数组中的每个元素都执行相同的操作。

感受数组🔨

同样,我们通过实际操作来感受一下数组,在终端中定义一个数组:

输入

1
let fruit = ["apple","orange","banana"];

定义完了我们调用一下这个数组

输入:

1
fruit

输出:

1
2
3
4
5
6
(3) ["apple", "orange", "banana"]
0: "apple"
1: "orange"
2: "banana"
length: 3
__proto__: Array(0)

这是一个包含了三个元素的数组,有趣的是,我们可以注意到他的序号是从0开始的,实际上在几乎所有编程语言中序号都是从零开始的。

我们还可以指定获取数组的第几个元素:

输入:

1
fruit[0]

输出:

1
"apple"

这种访问方式很像上面介绍的第二种对象调用方式,为了方便理解,我们可以简单的认为,数组就是把对象中的键换成了有序的序号。

在这个简单的案例中,我们定义的数组中的每个元素都是字符串,实际上数组中的元素可以是任何类型,包括对象。下面我们来定义一个包含对象的复杂数组。

输入:

1
2
3
4
5
6
7
8
9
10
let cities = [
{
citeName: "杭州",
citeindex: 1
},
{
citeName: "上海",
citeindex: 2
}
]

🐖:这里为了结构清楚,换行展示了这段代码,在控制台里换行需要按Shift+Enter,可以注意GIF中左下角的按键。你也可以直接复制进控制台。

现在我们定义了一个cites数组里面包含了两个对象,我尝试把现在的数据结构画出来。

用文字来描述就是,cities数组包含了两个对象,每个对象中有两个属性,cityNamecityIndex,第一个对象的cityName是杭州,cityIndex是1。

这里数组中的元素是对象,同样的对象中属性的值也可以是数组,用这种数组和对象互相嵌套的方式,可以构造复杂的数据结构。

定义了数组,我们再来访问一下,如果需要获取“杭州”这个值,我们可以这么写

输入:

1
cities[0].cityName

输出:

1
"杭州"

这样就可以方便的对数中的所有元素进行访问,那么聪明的你现在就尝试一下如何访问其他剩余的元素吧。

数组的方法

在上面的定义中,你或许会有疑问,看起来使用了数组并没有感觉到定义数据变得方便了。那是因为数组方便的地方是他提供了很多内置的方法,通过调用这些方法,我们就能极大地提高效率。下我就介绍一下两个非常核心的方法push()forEach()

🚀🚀 push()

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

用人话来说就是在一个数组后面加上一个数组,理解不了的话跟着下面操作一遍肯定能理解。

我们接下来的操作是先定义出来我们想加进数组中的元素,然后通过push()方法把这个新定义的元素加入数组中。

输入:

1
2
3
4
let tmp = {
cityName:"北京"
cityIndex:3
}

然后把定义好的这个tmp,通过push(),加入到数组中。

输入:

1
cities.push(tmp);

此时我们看到,通过push()我们已经把北京的数据加入到数组中了。

🚀🚀 forEach()

forEach() 方法对数组的每个元素执行一次提供的函数。

用人话来说就是,把数组中的每个元素拿出来做一次相同的操作。我们通过这个方法可以非常方便的获取数组里所有的值。

下面我会用forEach()来把所有的cityName打印出来。代码你可能会看不懂但重要的是跟着做一遍和大致知道代码每个部分的作用。

输入:

1
2
3
cities.forEach(item => {
console.log(item.cityName);
});

输出:

1
2
3
杭州
上海
北京

这里简要讲一讲这段代码的意思,item指的就是数组的每个单个元素。console.log()则是在控制台输出括号中的内容。

总结🔨

通过数组和对象的结合,我就可以构造出复杂的数据结构,并且可以非常方便的调用。

JSON

JSON 是一种语法,用来序列化对象、数组、数值、字符串、布尔值和 null 。它基于 JavaScript 语法,如果你大概了解了上面讲的对象和数组,那么你也大概了解了JSON。这个案例中的数据就使用JSON存储。

JSON的出现是为了存储结构化数据,通常情况下计算能够读取的数据我们人类看不懂,而我们人类能读懂的数据计算机又读不懂。为了能够让人和计算机同时能够读懂数据,就需要这种结构化的数据。

准备工作

了解了上面讲述的基础知识我们就可以开始进行实际操作了,在这之前我们还需要一些准备工作。

查看原始输数据

首先下载【📁 需要准备的文件】中的数据集,在合适的位置新建一个名为dataFormat的文件夹,并解压出我们的数据集,放在一个名为data的文件夹下。此时我们的目录结构为

1
2
3
4
dataFormat
└── data
├── location.json
└── ncovdata.json

使用VScode打开dataFormat文件夹。

在左侧文件目录下打开location.jsonncovdata.json两个文件。

location.json储存的是中国部分城市的经纬度坐标。

ncovdata.json储存的是全国疫情的统计数据。

本教程的最终目的就是提取出ncovdata.json中的有用数据,并把location.json中每个城市的经纬度坐标合并进去,生成最终的数据。

最终数据

进入这个网址 https://drafff.art/js-dataFormat-tutorial/ ,我把整理好的数据通过控制台打印了出来,我们先看看最后整理好的数据长什么样。

这个页面,按下F12,你会在控制台看到有一个输出的数组,里面包含了397个对象,这就是我们最终处理完的数据。

并且每个对象都包含了cityNamecurrentConfirmedCountlonlat 四个属性。

最后我们只需要对这个数组使用一次forEach()就能方便的调用每个对象的属性。

准备工作

开始处理数据之前,我们需要配置一下工作环境。

1.安装Live Server插件

2.新建需要的文件

dataFormat文件夹中新建index.htmlscript.js两个文件。

此时我们的目录结构为

1
2
3
4
5
6
dataFormat
├── data
| ├── location.json
| └── ncovdata.json
├── index.html
└── script.js

index.html:网页文件,我们整个项目为了方便环境配置,使用了浏览器环境,所以需要一个html文件作为载体。

script.js:JavaScript脚本文件,这是我们用来控制数据的文件,也是我们后面操作的重点。

3.初始化html,导入JavaScript文件

开始之前,我们还需要对网页进行初始化,跟着下面的按键顺序来。

  1. 打开index.html
  2. 输入!,要注意的是这边的感叹号是半角字符,也就是在英文模式下输入的感叹号。
  3. 这时会跳出一个候选框,选中第一个,按下Tab键,一切顺利的话会出现一大段html代码,我们也成功了一半。
  4. </head>的上一行插入一句<script src="./script.js"></script>导入脚本文件。

此时index.html文件的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./script.js"></script>
</head>
<body>

</body>
</html>

4.使用Live Server打开网页文件并检查script.js是否导入成功

  1. 为了看得更清楚,我们在index.html中再加入一些文字

    1
    2
    3
    4
    <!-- 保留前面代码 -->
    <body>
    <h1>hello data!!!</h1>
    </body>

  2. 右键,点击Open with Live Server,会弹出一个网页窗口,里面显示的内容就是我们加入的内容

  3. 编辑script.js,加入下面的代码,此时回到浏览器,按F12打开控制台,如果出现了hello data!! ,那么说明脚本文件被正确读取了。

    1
    console.log("hello data !!")

到这里所有的准备工作完成了,我们开始吧。后面所有操作都是在script.js中。

数据处理

  1. 引入数据集。

    1
    2
    3
    4
    5
    fetch("./data/ncovdata.json")         
    .then(res => res.json())
    .then(data => {
    console.log(data.results)
    })


    逐句分析一下上面代码的意思,

    1
    fetch("./data/ncovdata.json")

    这句代码是获取data目录中ncovdata.json的数据。

    1
    .then(res => res.json())

    把由fetch获取到的JSON数据解析,这边的.then()用来接收上一行代码获取的数据。

    1
    2
    3
    .then(data => {
    console.log(data.results)
    })

    这里的data指的就是解析出来的数据,但是结果是储存在dataresults属性中的,所以我们输出在控制台的数据就是data.results,可以发现results属性实际上是一个数组。

  2. 分离出results中的每个元素。

    1
    2
    3
    4
    5
    6
      //保留之前代码
    .then(data => {
    data.results.forEach(item =>{
    console.log(item);
    })
    })

    这里通过数组的forEach()方法,把数组中的每个元素单独提取出来,这里的item指的就是数组的每个元素。

  3. 获取需要的数据

    通过上面在浏览器的数据我们发现,获取到的对象还包括国外的地区,我们需要筛选每个对象的countryName属性来获取中国的地区。

    而每个对象实际是一个省,每个城市的数据包含在itemcities属性中,我们还需要对cities属性再使用一次forEach()方法,来获取每个城市的数据。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //保留之前代码
    .then(data => {
    data.results.forEach(item => {
    if (item.countryName === "中国") {
    item.cities.forEach(city => {
    console.log(city)
    })
    }
    });
    })

  4. 取出所需的数据,并保存在一个临时变量中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //保留之前的代码
    .then(data => {
    let cityData = [];
    data.results.forEach(item => {
    if (item.countryName === "中国") {
    item.cities.forEach(city => {
    let singleCity = {};
    singleCity.cityName = city.cityName;
    singleCity.currentConfirmedCount = city.currentConfirmedCount;
    cityData.push(singleCity);
    })
    }
    });
    console.log(cityData)
    return cityData
    })

    这里我们新建了两个临时变量cityDatasingleCity,我们把每个城市的cityNamecurrentConfirmedCount数据单独取出,放在singleCity中,再把singleCity通过push()方法加入到cityData数组中。

    打印出cityData

    img

    可以看到这个数组中的每个元素都是只包含了城市名字和,当前确认人数的对象。现在我们已经获取并且分离了所需的疫情数据,还需每个城市的经纬度信息。

  5. 导入经纬度数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let locationData = {};
    fetch("./data/location.json").then(res => res.json()).then(data => {
    locationData = data;
    })
    fetch("./data/ncovdata.json")
    .then(res => res.json())
    .then(data => {
    //保留内部代码
    })

    我们定义了一个locationData来保存经纬度的数据,和之前不同的是,locationData是一个对象,而不是数组,使用对象来储存每个城市的经纬度数据,可以更方便的调用。

  6. 合并数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //保留之前代码
    fetch("./data/ncovdata.json")
    .then(res => res.json())
    .then(data => {
    //保留内部代码
    })
    .then(cityData =>{
    let combinedData=[]
    cityData.forEach(city=>{
    let key = locationData[city.cityName];
    if(key){
    let item = {
    cityName:'',
    currentConfirmedCount:0,
    lon:0,
    lat:0
    };
    item.cityName = city.cityName;
    item.currentConfirmedCount = city.currentConfirmedCount;
    item.lon = key.x;
    item.lat = key.y;
    combinedData.push(item);
    }
    })
    console.log(combinedData)
    })

    这里我们进行的操作和上面大致相同,新建一个临时变量用来储存数据。通过forEach()遍历数组,取出数据,合并,最后放入combinedData并打印出来。

    值得注意的是,这里有一段:

    1
    2
    3
    4
    let key = locationData[city.cityName];
    if(key){
    ...
    }

    这边的if(key)是用来判断经纬度数据中有没有不存在的城市,因为经纬度数据包括的城市不是很完整,如碰到数据中不包含的数据就会出现undefine,导致程序出现错误。

    最后我们打印出了combineData

    此时的数据就服务我们的要求了。

如果你在过程中出现了问题,可以下载上面给出的项目文件,参照着寻找错误。

script.js完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
let locationData = {};
fetch("./data/location.json").then(res => res.json()).then(data => {
locationData = data
})
fetch("./data/ncovdata.json").then(res => res.json()).then(data => {
let cityData = [];
data.results.forEach(item => {
if (item.countryName === "中国") {
item.cities.forEach(city => {
// console.log(city)
let singleCity = {};
singleCity.cityName = city.cityName;
singleCity.currentConfirmedCount = city.currentConfirmedCount;
cityData.push(singleCity);
})
}
});
return cityData
}).then(cityData =>{
let combinedData=[]
cityData.forEach(city=>{
let key = locationData[city.cityName];
if(key){
let item = {
cityName:'',
currentConfirmedCount:0,
lon:0,
lat:0
}
item.cityName = city.cityName;
item.currentConfirmedCount = city.currentConfirmedCount;
item.lon = key.x;
item.lat = key.y;
combinedData.push(item);
}
})
console.log(combinedData)
})

总·总结

这个教程包含了使用JavaScript处理数据最基本的一些知识和操作,希望能给大家一些帮助。

(●’◡’●)