使用 Serverless 和 Vue 探索数据:过滤和使用数据

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

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

文章系列

  1. 使用无服务器函数自动更新 GitHub 文件
  2. 过滤和使用数据(您当前所在位置!)
showing how the demo works

您可以 在此处查看实时演示,或在 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 扫描表格上的大量数据。指令和计算属性是这里的英雄,使声明性地编写代码变得非常容易。

filtering the data in the table

我喜欢它在几乎不费吹灰之力的情况下过滤信息的速度。计算属性充分利用了 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);,它将极大地改变颜色范围。

two different color patterns for the pins on the globe

我们还将进行一些其他微调:地球仪将是一个圆圈,但它将使用图像作为纹理。如果我们想将该形状更改为二十面体甚至环面纽结,我们可以这样做,我们只需要更改此处的一行代码即可

//from
const geometry = new THREE.SphereGeometry(200, 40, 30);
//to 
const geometry = new THREE.IcosahedronGeometry(200, 0);

我们将得到类似这样的结果,您可以看到纹理仍然会映射到这个新形状上

showing how we can change the shape of the globe to an icosahedron

奇怪而酷炫,也许在这种情况下没有用,但使用 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 中的计算属性使该表格流畅、声明式和高性能。使用所有这些技术可以产生真正有趣且探索性的数据查看方式。

文章系列

  1. 使用无服务器函数自动更新 GitHub 文件
  2. 过滤和使用数据(您当前所在位置!)