使用可视化组件构建自定义可视化图表

本教程面向经验丰富的 JavaScript 开发者,并且对函数式编程技术有一定的了解。

在此示例中,我们将从条形图开始,这是一种原生 Looker 图表,其中会显示虚构的每周销售信息:

然后,我们将使用可视化组件来构建自定义可视化图表,显示每个品牌的产品在过去一个季度内的发展趋势。最终形成一种新的可视化效果,由一系列嵌套在表中的火花谱线图组成,如以下示例所示:

此示例不仅展示了如何创建自定义可视化图表,还演示了在 React 应用内使用 Looker API 的一些最佳做法。

如需使用 Looker 组件构建自定义的可视化图表,请确保您的设置符合要求,然后执行以下步骤:

  1. 在“探索”中构建查询并复制 qid
  2. 将数据传递给自定义可视化组件
  3. 构建 CustomVis 组件
  4. 转换归一化的数据
  5. 将转换后的数据插入 CustomVis
  6. 生成自定义可视化图表

如果自定义可视化图表适用于嵌入式应用或扩展程序,则适合使用可视化组件构建自定义可视化图表。如果您希望在整个 Looker 实例中向 Looker 用户提供自定义的可视化内容,请按照 visualization 文档页面上的说明进行操作。如果您要开发自定义的可视化图表并将其上传到 Looker Marketplace,请按照为 Looker Marketplace 开发自定义可视化图表文档页面中的说明操作。

使用要求

开始前,您需要准备以下几个元素:

  • 您必须有权访问 Looker 实例。
  • 无论您是在扩展框架中构建,还是在您自己的独立 React 应用中构建,都必须使用 Looker 的 API 进行身份验证并有权访问 Looker SDK 对象。如需了解详情,请参阅 Looker API 身份验证或我们的扩展程序框架
  • 确保您已安装 Looker 可视化组件 NPM 软件包@looker/components-data NPM 软件包。如需了解如何安装和使用可视化组件包,请参阅自述文档(可在 GitHubNPM 中找到)。

第 1 步:在“探索”中构建查询并复制查询 ID

在此示例中,我们针对假设一个季度我们会跟踪的品牌使用每周销售信息。

在“探索”中,我们可以使用 Looker 的原生可视化类型之一运行查询,并创建数据图表。该图表提供了大量信息,但很难一目了然地分析每个品牌的商品趋势:

呈现简单可视化图表的示例一样,下一步是从“探索”的网址栏中复制 qid 值。在此示例中,qid 将为 4tQKzCBOwBGNWTskQyEpG8

第 2 步:将数据传递给自定义可视化组件

首先,将来自“探索”网址的 qid 值传递到 Query 组件,并将经过身份验证的 SDK 对象传递给 DataProvider

import React, { useContext } from 'react'
import { ExtensionContext } from '@looker/extension-sdk-react'
import { DataProvider } from '@looker/components-data'
import { Query } from '@looker/visualizations'

export const MyReactApp = () => {
  const { core40SDK } = useContext(ExtensionContext)

  return (
    <DataProvider sdk={core40SDK}>
      <Query query='4tQKzCBOwBGNWTskQyEpG8'></Query>
    </DataProvider>
  )
}

接下来,我们要构建一个名为 CustomVis 的自定义组件,而不是通过 Visualization 组件呈现原生 Looker 可视化图表。

Query 组件可以接受任何 React 元素作为子元素,并且仅会传递 configdatafieldstotals 值作为属性,以呈现您自己的可视化组件。我们会将 CustomVis 呈现为 Query 的子元素,因此它可以接收所有相关数据作为属性。

import React, { useContext } from 'react'
import { ExtensionContext } from '@looker/extension-sdk-react'
import { DataProvider } from '@looker/components-data'
import { Query } from '@looker/visualizations'
import { CustomVis } from '../path/to/MyCustomVis'

export const MyReactApp = () => {
  const { core40SDK } = useContext(ExtensionContext)

  return (
    <DataProvider sdk={core40SDK}>
      <Query query='4tQKzCBOwBGNWTskQyEpG8'>
        <CustomVis />
      </Query>
    </DataProvider>
  )
}

第 3 步:构建 CustomVis 组件

接下来,我们构建 CustomVis 组件。从 Query 组件继承的属性是 configfieldsdatatotals

  • config 描述应在图表中呈现数据的所有方式,例如火花谱线图中的线条粗细或散点图的大小和形状。
  • fields 会存储与查询返回的测量值和维度值有关的其他元数据,例如值的格式化方式或每条轴的标签。
  • data 是从查询返回的键值对。
  • totals 引用 Looker 的行总计值以用于基于表格的可视化。

我们可以通过插入 Table 组件,将这些未经修改的属性传递给表的可视化图表。

import React from 'react'
import { Table } from '@looker/visualizations'

export const CustomVis = ({ config, fields, data }) => {
  return <Table config={config} data={data} fields={fields} />
}

这样,我们就可以了解直接从 SDK 返回的数据。在呈现的响应中,每个品牌每周都占据一行:

第 4 步:转换归一化数据

要从这些数据中获取时间序列趋势信息,我们需要按品牌名称对行进行分组。我们不希望每个品牌和每周都各占一行,而是希望每个品牌名称单独占据一行,并且嵌套在其中是一个日期和订单计数列表。

我们将创建一个自定义转换,将如下所示的值分组。以下是特定于这种情况的示例;您需要相应地解析自己的数据。

import React from 'react'
import { Table } from '@looker/visualizations'
import { filter, pick } from 'lodash'

const transformData = data => {
  const brandKey = 'products.brand_name'

  // create a unique set of brand names
  const uniqueBrands = new Set(data.map(d => d[brandKey]))

  // convert the Set back to an array and nest values below each brand name
  // sample output:
  // [{
  //     products.brand_name: "Looker",
  //     orders.count: { orders.created_week: '2019-09-30', orders.count: 17 }
  //  }, ...]
  return Array.from(uniqueBrands).map(brand => {
    const values = filter(data, { [brandKey]: brand }).map(d =>
      pick(d, ['orders.created_week', 'orders.count'])
    )

    return { [brandKey]: brand, 'orders.count': values }
  })
}

export const CustomVis = ({ config, fields, data }) => {
  return <Table config={config} data={data} fields={fields} />
}

以下步骤会创建此函数:

  1. 首先,将引用 products.brand_name 分配给名为 brandKey 的变量。这就是数据响应中的系列标签,也是对信息进行分组的方式。
  2. 接下来,通过在响应上映射并仅返回分配给 products.brand_name 的值,构建唯一的品牌名称列表。然后,这些对象会传入 Set 构造函数。在 JavaScript 中,Set 是一个值出现一次的列表,会自动构建唯一值的列表。
  3. 然后,将唯一品牌的 Set 转换回数组,以使用列表转换实用程序,例如 map
  4. 对于每个唯一品牌,请过滤原始数据响应,仅过滤出与该品牌名称相关的时序行。
  5. 最后,对于每个品牌名称,请将这些值嵌套在 orders.count 对象键下。在这里,您还可以看到一个示例,说明如何将品牌名称设置为字符串值,并将 orders.count 设置为该品牌的所有相关值的列表。

第 5 步:将转换后的数据插入 CustomVis

现在,使用我们的新函数转换数据,并将输出分配给名为 nestedData 的新变量。

import React from 'react'
import { Table } from '@looker/visualizations'
import { filter, pick } from 'lodash'

const transformData = data => {
  const brandKey = 'products.brand_name'

  // create a unique set of brand names
  const uniqueBrands = new Set(data.map(d => d[brandKey]))

  // convert the Set back to an array and nest values below each brand name
  // sample output:
  // [{
  //     products.brand_name: "Looker",
  //     orders.count: { orders.created_week: '2019-09-30', orders.count: 17 }
  //  }, ...]
  return Array.from(uniqueBrands).map(brand => {
    const values = filter(data, { [brandKey]: brand }).map(d =>
      pick(d, ['orders.created_week', 'orders.count'])
    )

    return { [brandKey]: brand, 'orders.count': values }
  })
}

export const CustomVis = ({ config, fields, data }) => {
  const nestedData = transformData(data)

  return <Table config={config} data={data} fields={fields} />
}

接下来,遍历 nestedData 数组,并将嵌套值传递到由 Looker 的可视化组件库提供的标准火花谱线图中。

import React from 'react'
import { Table, Sparkline } from '@looker/visualizations'
import { filter, pick } from 'lodash'

const transformData = data => {
  const brandKey = 'products.brand_name'

  // create a unique set of brand names
  const uniqueBrands = new Set(data.map(d => d[brandKey]))

  // convert the Set back to an array and nest values below each brand name
  // sample output:
  // [{
  //     products.brand_name: "Looker",
  //     orders.count: { orders.created_week: '2019-09-30', orders.count: 17 }
  //  }, ...]
  return Array.from(uniqueBrands).map(brand => {
    const values = filter(data, { [brandKey]: brand }).map(d =>
      pick(d, ['orders.created_week', 'orders.count'])
    )

    return { [brandKey]: brand, 'orders.count': values }
  })
}

export const CustomVis = ({ config, fields, data }) => {
  const nestedData = transformData(data)

  const nestedSparklines = nestedData.map(d => {
    return {
      ...d,
      'orders.count': () => (
        <Sparkline
          data={d['orders.count']}
          config={config}
          fields={fields}
          height={75}
        />
      ),
    }
  })

  return <Table config={config} data={nestedSparklines} fields={fields} />
}

上述代码发生以下情况:

  • 系统会再进行一次数据映射,为每一行创建一个新的火花谱线图。
  • 嵌套在 orders.count 键下的值(属于相应品牌的日期系列和计数)会传递到 Sparklinedata 属性中。
  • 系统还会传递所有图表所需的 configfields 对象。高度也设置为 75 像素,以便为表格中的每一行提供舒适的布局。
  • 最后,嵌套图表对象会传入 Table 图表中。

第 6 步:生成自定义可视化图表

插入转换后的数据并配置图表后,图表将如下面的表格所示,每一行对应一个火花谱线图:

呈现上述可视化所需的完整代码如下:

import React, { useContext } from 'react'
import { ExtensionContext } from '@looker/extension-sdk-react'
import { DataProvider } from '@looker/components-data'
import { Query, Table, Sparkline } from '@looker/visualizations'
import { filter, pick } from 'lodash'

const transformData = data => {
  const brandKey = 'products.brand_name'

  // create a unique set of brand names
  const uniqueBrands = new Set(data.map(d => d[brandKey]))

  // convert the Set back to an array and nest values below each brand name
  // sample output:
  // [{
  //     products.brand_name: "Looker",
  //     orders.count: { orders.created_week: '2019-09-30', orders.count: 17 }
  //  }, ...]
  return Array.from(uniqueBrands).map(brand => {
    const values = filter(data, { [brandKey]: brand }).map(d =>
      pick(d, ['orders.created_week', 'orders.count'])
    )

    return { [brandKey]: brand, 'orders.count': values }
  })
}

export const CustomVis = ({ config, fields, data }) => {
  const nestedData = transformData(data)

  const nestedSparklines = nestedData.map(d => {
    return {
      ...d,
      'orders.count': () => (
        <Sparkline
          data={d['orders.count']}
          config={config}
          fields={fields}
          height={75}
        />
      ),
    }
  })

  return <Table config={config} data={nestedSparklines} fields={fields} />
}

export const MyReactApp = () => {
  const { core40SDK } = useContext(ExtensionContext)

  return (
    <DataProvider sdk={core40SDK}>
      <Query query='4tQKzCBOwBGNWTskQyEpG8'>
        <CustomVis />
      </Query>
    </DataProvider>
  )
}

后续步骤