在本教程的第二篇文章中,我们将获取来自无服务器函数的数据,并使用 Vue 和 Vuex 来分发数据、更新表格以及修改数据以在我们的 WebGL 地球仪中使用。本文假设您具备 Vue 的基础知识。到目前为止,我们在本文中将要讨论的最酷/最有用的内容是使用 Vue.js 中的计算属性来创建高性能的表格过滤。继续阅读!
文章系列
- 使用无服务器函数自动更新 GitHub 文件
- 过滤和使用数据(您当前所在位置!)

您可以 在此处查看实时演示,或在 GitHub 上 浏览代码。
首先,我们将使用名为 Nuxt 的工具启动一个完整的 Vue 应用程序,该应用程序具有服务器端渲染、路由和代码分割。(这类似于 Zeit 的 Next.js 用于 React)。如果您尚未安装 Vue CLI 工具,请运行
npm install -g vue-cli
# or
yarn global add vue-cli
这将在全局安装 Vue CLI,以便我们可以在需要时使用它。然后我们将运行
vue init nuxt/starter my-project
cd my-project
yarn
这将创建此特定应用程序。现在,我们可以使用以下命令启动本地开发服务器:
npm run dev
如果您还不熟悉 Vuex,它类似于 React 的 Redux。有关其作用和功能的更多详细信息,请参阅本文。
import Vuex from 'vuex';
import speakerData from './../assets/cda-data.json';
const createStore = () => {
return new Vuex.Store({
state: {
speakingColumns: ['Name', 'Conference', 'From', 'To', 'Location'],
speakerData
}
});
};
export default createStore;
在这里,我们从 `cda.json` 文件中提取演讲者数据,该文件现在已使用来自无服务器函数的经度和纬度进行了更新。在导入时,我们将将其存储在我们的状态中,以便我们可以在应用程序范围内访问它。您可能还会注意到,在我们使用无服务器函数更新 JSON 后,列不再对应于我们希望在表中使用的列。没关系!我们还将仅存储我们需要用于创建表格的列。
现在,在我们的应用程序的 pages 目录中,我们将有一个 `Index.vue` 文件。如果我们想要更多页面,我们只需要将它们添加到此目录中即可。我们现在将使用此索引页面并在我们的模板中使用几个组件。
<template>
<section>
<h1>Cloud Developer Advocate Speaking</h1>
<h3>Microsoft Azure</h3>
<div class="tablecontain">
...
<speaking-table></speaking-table>
</div>
<more-info></more-info>
<speaking-globe></speaking-globe>
</section>
</template>
我们将从 Vuex 存储中获取所有数据,并将为此使用计算属性。我们还将在此处创建一个方法,以便在计算属性中过滤这些数据。最终,我们将把过滤后的属性传递给演讲者表格和演讲者地球仪。
computed: {
speakerData() {
return this.$store.state.speakerData;
},
columns() {
return this.$store.state.speakingColumns;
},
filteredData() {
const x = this.selectedFilter,
filter = new RegExp(this.filteredText, 'i')
return this.speakerData.filter(el => {
if (el[x] !== undefined) { return el[x].match(filter) }
else return true;
})
}
}
}</script>
您会注意到,我们使用了计算属性的名称,即使在其他计算属性中也是如此,就像我们使用数据一样,即 `speakerData()` 在过滤器中变为 `this.speakerData`。它也可以在我们的模板中作为 `{{ speakerData }}` 使用,依此类推。这就是它们的使用方式。根据用户输入快速排序和过滤表格中的大量数据,绝对是计算属性的用途。在此过滤器中,我们还将检查并确保我们不会因大小写敏感而丢弃内容,或者尝试匹配未定义的行,因为我们的数据有时会存在空洞。
这里有一点很重要,因为 Vue 中的计算属性非常有用。它们是基于其依赖项进行缓存的计算,并且仅在需要时更新。这意味着它们在使用得当的情况下性能极佳。虽然计算属性在最初看起来可能与方法类似,但它们的使用方式与方法不同。我们可能以相同的方式注册它们,通常会伴随一些逻辑,但实际上它们更像是数据。您可以将它们视为数据的另一种视图。
计算值对于操作已存在的数据非常有价值。无论何时构建需要筛选大量数据的内容,并且不希望在每次按键时都重新运行这些计算,请考虑使用计算值。另一个好的选择是当您从 Vuex 存储中获取信息时。您可以收集这些数据并将其缓存。
创建输入
现在,我们希望允许用户选择要过滤的数据类型。为了使用该计算属性根据用户输入进行过滤,我们可以在数据中创建一个空字符串值,并使用 `v-model` 建立输入框中键入的内容与我们之前在 `filteredData` 函数中过滤的数据之间的关系。我们还希望他们能够选择一个类别来缩小搜索范围。在我们的例子中,我们已经可以访问这些类别,它们与我们用于表格的列相同。因此,我们可以创建一个带相应标签的选择框
<label for="filterLabel">Filter By</label>
<select id="filterLabel" name="select" v-model="selectedFilter">
<option v-for="column in columns" key="column" :value="column">
{{ column }}
</option>
</select>
我们还将把额外的过滤器输入包装在一个 `v-if` 指令中,因为它应该仅在用户已选择列时才可用。
<span v-if="selectedFilter">
<label for="filterText" class="hidden">{{ selectedFilter }}</label>
<input id="filteredText" type="text" name="textfield" v-model="filteredText"></input>
</span>
创建表格
现在,我们将过滤后的数据传递给演讲者表格和演讲者地球仪
<speaking-globe :filteredData="filteredData"></speaking-globe>
这使得我们能够非常快速地更新我们的表格。我们还可以充分利用指令来保持表格的简洁、声明性和可读性。
<table class="scroll">
<thead>
<tr>
<th v-for="key in columns">
{{ key }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(post, i) in filteredData">
<td v-for="entry in columns">
<a :href="post.Link" target="_blank">
{{ post[entry] }}
</a>
</td>
</tr>
</tbody>
</table>
由于我们使用了从输入更新的传递下来的计算属性,它将使用数据的另一个视图,并且仅在数据发生更改时才会更新,这种情况非常少见。
现在,我们有了一种高性能的方法来使用 Vue 扫描表格上的大量数据。指令和计算属性是这里的英雄,使声明性地编写代码变得非常容易。

我喜欢它在几乎不费吹灰之力的情况下过滤信息的速度。计算属性充分利用了 Vue 的缓存能力。
创建地球仪可视化
如前所述,我正在使用来自 Google dataarts 的库来创建地球仪,该库位于 此存储库 中。
地球仪本身非常漂亮,但我们需要两件事才能使用它:我们需要修改我们的数据以创建地球仪期望的 JSON,并且我们需要了解足够的 three.js 知识来更新其外观并使其在 Vue 中工作。
这是一个较旧的存储库,因此无法作为 npm 模块安装,这在我们的案例中实际上没问题,因为我们将稍微修改其外观,因为我是一个控制狂 咳咳我的意思是,我们想修改它以使其成为我们自己的。
将此存储库的所有内容都转储到一个方法中并不是那么干净,因此我将使用一个混合。混合允许我们做两件事:它使我们的代码模块化,这样我们就不需要浏览一个巨大的文件,并且如果我们想在应用程序的其他页面上使用此地球仪,它允许我们重用它。
我这样注册地球仪
import * as THREE from 'three';
import { createGlobe } from './../mixins/createGlobe';
export default {
mixins: [createGlobe],
…
}
并在名为 mixins(如果我想创建更多混合)的目录中创建一个名为 `createGlobe.js` 的单独文件。有关混合以及它们的工作原理和功能的更多信息,请参阅我撰写的这篇关于如何使用它们的另一篇文章。
修改数据
如果您还记得第一篇文章,为了创建地球仪,我们需要向其提供如下所示的值
var data = [
[
'seriesA', [ latitude, longitude, magnitude, latitude, longitude, magnitude, ... ]
],
[
'seriesB', [ latitude, longitude, magnitude, latitude, longitude, magnitude, ... ]
]
];
到目前为止,我们从存储中返回的 `filteredData` 计算值将为我们提供每个条目的经度和纬度,因为我们从计算属性中获取了这些信息。目前,我们只希望查看该数据集的一个视图,即我的团队的数据,但将来我们可能还想收集其他团队的信息,因此我们应该将其构建为能够相当轻松地添加新值。
让我们再创建一个计算值,以我们所需的方式返回数据。我们首先将其作为对象创建,因为在构建过程中这样做效率更高,然后我们将创建一个数组。
teamArr() {
//create it as an object first because that's more efficient than an array
var endUnit = {};
//our logic to build the data will go here
//we'll turn it into an array here
let x = Object.entries(endUnit);
let area = [],
places,
all;
for (let i = 0; i < x.length; i++) {
[all, places] = x[i];
area.push([all, [].concat(...Object.values(places))]);
}
return area;
}
在我们刚刚创建的对象中,我们将检查我们的值是否已经存在,如果不存在,我们将创建一个新的。我们还需要根据纬度和经度组合创建一个键,以便我们可以检查重复的实例。这特别有用,因为我不知道我的队友是否会将位置仅输入为城市或城市和州。Google 地图 API 在这方面非常宽容——他们将能够找到任一字符串一致的位置。
我们还将确定放大倍数的最小值和增量值。我们对放大倍数的决定主要来自反复试验,调整此值并查看哪些值以对查看者有意义的方式适合。我在这里的第一次尝试是长长的、摇晃的细长杆,看起来像一只秃头断裂的豪猪,花了一分钟左右才找到一个可用的值。
this.speakerData.forEach(function(index) {
let lat = index.Latitude,
long = index.Longitude,
key = lat + ", " + long,
magBase = 0.1,
val = 'Microsoft CDAs';
//if we either the latitude or longitude are missing, skip it
if (lat === undefined || long === undefined) return;
//because the pins are grouped together by magnitude, as we build out the data, we need to check if one exists or increment the value
if (val in endUnit) {
//if we already have this location (stored together as key) let's increment it
if (key in endUnit[val]) {
//we'll increase the maginifation here
}
} else {
//we'll create the new values here
}
})
现在,我们将检查位置是否已存在,如果存在,我们将对其进行递增。如果不存在,我们将为它们创建新的值。
this.speakerData.forEach(function(index) {
...
if (val in endUnit) {
//if we already have this location (stored together as key) let's increment it
if (key in endUnit[val]) {
endUnit[val][key][2] += magBase;
} else {
endUnit[val][key] = [lat, long, magBase];
}
} else {
let y = {};
y[key] = [lat, long, magBase];
endUnit[val] = y;
}
})
使其更有趣
我之前提到过,我们将基本 dataarts JavaScript 存储在 mixin 中的部分原因是我们希望对其外观进行一些修改。让我们也花点时间谈谈这一点,因为它是任何有趣的数据可视化的一个方面。
如果您不太了解如何使用 three.js,它是一个库,其文档编制得非常好,并且有许多示例可以借鉴。不过,我对它是什么以及如何使用它的真正突破并非来自这些来源。我从Rachel Smith 在 codepen 上的系列文章和 Chris Gammon(不要与 Chris Gannon 混淆)优秀的YouTube 系列文章中获得了很大的启发。如果您不太了解 three.js 并想将其用于 3D 数据可视化,我的建议是从那里开始。
我们要做的第一件事是调整地球仪上图钉的颜色。开箱即用的颜色很漂亮,但它们与我们页面的样式或我们对此数据所需的放大倍数不符。更新代码位于我们 mixin 的第 11 行
const colorFn = opts.colorFn || function(x) {
let c = new THREE.Color();
c.setHSL(0.1 - x * 0.19, 1.0, 0.6);
return c;
};
如果您不熟悉它,HSL 是一种非常人性化的可读颜色格式,这使得可以轻松更新我们图钉的颜色范围。
- H 代表色相,它以圆形给出。这对像这样的生成项目来说非常棒,因为与许多其他颜色格式不同,它永远不会失败。20 度将给我们与 380 度相同的值,依此类推。我们在这里传递的 x 与我们的放大倍数有关,因此我们需要弄清楚该范围从哪里开始以及将增加多少。
- 第二个值将是饱和度,我们将在此处将其提高到最大,以便它能够脱颖而出——在 0 到 1 的范围内,1.0 是最高值。
- 第三个值是亮度。与饱和度一样,我们将获得 0 到 1 的值,我们将在 0.5 处使用它的一半。
您可以看到,如果我仅对该代码行进行了一点修改,改为c.setHSL(0.6 - x * 0.7, 1.0, 0.4);
,它将极大地改变颜色范围。

我们还将进行一些其他微调:地球仪将是一个圆圈,但它将使用图像作为纹理。如果我们想将该形状更改为二十面体甚至环面纽结,我们可以这样做,我们只需要更改此处的一行代码即可
//from
const geometry = new THREE.SphereGeometry(200, 40, 30);
//to
const geometry = new THREE.IcosahedronGeometry(200, 0);
我们将得到类似这样的结果,您可以看到纹理仍然会映射到这个新形状上

奇怪而酷炫,也许在这种情况下没有用,但使用 three.js 更新三维形状非常容易,这确实很棒。不过,自定义形状会变得更复杂。
我们在 Vue 中加载该纹理的方式与库中的方式不同——我们需要在组件挂载时获取它并加载它,在实例化地球仪时也将其作为参数传递进去。您会注意到我们不必创建到 assets 文件夹的相对路径,因为 Nuxt 和 Webpack 会在后台为我们执行此操作。我们可以轻松地以这种方式使用静态图像文件。
mounted() {
let earthmap = THREE.ImageUtils.loadTexture('/world4.jpg');
this.initGlobe(earthmap);
}
然后,在创建材质时,我们将应用此处传递的纹理
uniforms = THREE.UniformsUtils.clone(shader.uniforms);
uniforms['texture'].value = imageLoad;
material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
});
我们可以用很多方法处理这些数据并更改其输出方式——我们可以调整地球仪周围的白色条带,我们可以用一行代码更改地球仪的形状,我们可以用粒子将其包围。天空才是极限!
就是这样!我们正在使用无服务器函数与 Google 地图 API 交互,我们正在使用 Nuxt 使用服务器端渲染创建应用程序,我们正在使用 Vue 中的计算属性使该表格流畅、声明式和高性能。使用所有这些技术可以产生真正有趣且探索性的数据查看方式。
文章系列
- 使用无服务器函数自动更新 GitHub 文件
- 过滤和使用数据(您当前所在位置!)
您好,您决定为此项目使用服务器端渲染的原因是什么?
嗨,Thibaud!
是的,我在关于 Nuxt 的另一篇文章中提供了更多关于为什么要选择使用服务器端渲染的信息:https://css-tricks.org.cn/simple-server-side-rendering-routing-page-transitions-nuxt-js/
谢谢!
Sarah
感谢您的回答,Sarah,我不完全相信这种单页项目能从 SSR 中获益很多(我想我必须自己测试一下)
顺便说一句,这与我使用 Vue 完成的一个(3 年前)项目非常相似
http://globe.cop21horizon.com/
太棒了。惊讶于可以用这么少的代码完成这么多事情。引入 THREE.js 的绝佳示例
嘿,Sarah,感谢您撰写了这篇精彩的文章!我似乎收到了一条关于 filteredText 的 Vue 警告……“实例上未定义属性或方法“filteredText”,但在渲染期间引用了它”。您知道为什么会这样吗?
很棒的文章!
在阅读时,我试图想象数据如何在
teamArr
计算属性中进行转换。在每个步骤中包含数据在各个阶段的样子的小示例可能会更好。我想我也可以自己提取项目来运行它。:P