数据可视化大屏设计器开发-关联参数

数据可视化大屏设计器开发-关联参数

开头

本文是数据可视化开始的开发细节第六章。关于大屏中组件的关联参数设置的逻辑。

关联参数所指的是组件的数据变更或者配置变更依赖于某一些地方带来的变量。

比如

  • 一个柱形图的数据请求的url需要一个用户id
  • 一个标题的内容是根据当前的学校,来显示其名称。
  • 一个雷达图需要根据数据的不同显示不一样的维度。
  • 一个视频需要根据数据的不同来控制是否显示。

下面就关联参数相关来进行详细的逻辑讲解。

开始

关联参数来源

针对不同的场景,可能组件依赖了来自于不同地方的数据。
就此大屏设计器来说,主要来源的参数分为3种(urlparamsconstants)。

url

顾名思义,来源于大屏的链接地址。
比如https://www.baidu.com/?userId=xxx&address=yyyuserIdaddress就会被认为是两个关联参数被保存到配置当中。

constants

大屏当中存在一些特殊的情况,需要用到一些不变值,所以在配置中新增了一个全局常量的概念。

主要是名称常量两个地方的定义,即对象的keyvalue

params

关联参数的主要来源都是在params中,代指的是各个组件中的交互配置。

根据组件的一条特殊事件,触发对应的逻辑,对设置的值进行变动,进而通知关联了该组件的参数的组件进行相应的更新。

比如上图的"基础柱形图"的交互配置,点击柱形图的柱条时,该柱条对应的数据:横轴(x)、纵轴(y)以及系列(s)的值,就会更新到配置中,同时关联了这些参数的组件也会接收到值的变动的通知,并进行对应的操作。

关联参数设置

上面介绍了关联参数的来源,这里继续介绍每一种参数的设置。

url

关于url上的参数的获取,其实在平时的业务当中并不少见,这里就简单的使用querystring来获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { parse } from 'querystring';
const { search } = new URL(window.location.href)
// object版本
const objectUrlParams = parse(search.replace('?', ''))
// object[]版本
const objectArrayUrlParams = Object.entries(value).reduce((acc, cur) => {
const [key, value] = cur;
acc.push({
key,
value,
id: key,
description: `来源于url地址:${key}`,
});
return acc;
},[])

上面的object[]版本为统一处理的版本。

constants

和上面的urlobject[]格式一致,keyvalue即为前面介绍的名称和常量值,description为可自定义的描述文字。

params

而对于params,比上面两种参数都较为复杂,因为其涉及到组件和组件之间的关联,所以他本身存在于组件本身全局两个地方。

组件本身

组件本身需要存储它的一些特性,比如他的数据原始字段名称、映射字段名称、默认值以及描述。

  • 原始字段名称
    原始字段表示该组件本身的字段的名称,因为每一个组件的数据格式都是不一样的,它的数据字段名称也不一样,比如柱形图的数据格式是一个数组对象,而标题的数据格式是一个对象。
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
// 柱形图的数据格式
const barData = [
{
x: '01-01',
y: 100,
s: 's1'
},
{
x: '01-01',
y: 200,
s: 's2'
},
{
x: '01-02',
y: 70,
s: 's1'
},
{
x: '01-02',
y: 50,
s: 's2'
}
]
// 标题的数据格式
const titleData = {
value: '我是一个标题'
}

而在需要真正使用时,其他组件不可能直接关联组件的原始字段,因为这样子的重复率会很高,从而导致参数值来回覆盖,显示错误的问题。

所以组件当中的字段配置格式是下面这样的

1
2
3
4
5
6
7
8
const fieldData = {
// 不可更改
key: '原始的字段名称',
variable: '映射的字段名称',
description: '描述',
mapId: '全局的params id'
// TODO
}

mapId为与之同步的全局的参数信息的id,用于两边的数据同步。
当组件的数据发生变化时,根据变动的字段名称找到上面的fieldData,并从中找到mapId,进而将数据同步到全局(之后根据逻辑通知关联的组件,这里后面会讲到)。

全局

全局参数则是用于接收同步组件的关联参数,并下发到关联组件。
其数据格式如下

1
2
3
4
5
6
7
const paramsData = {
id: '唯一id',
value: '实际的值',
key: '原始的字段名称',
variable: '映射的字段名称'
// TODO
}

当组件配置的variable和组件的数据发生变化时,variablevalue就会更新。

关联参数使用

讲了这么多的概念,还没有具体介绍到其使用场景,大屏当中有非常多的地方值得使用关联参数的功能。
具体包括三个地方。

  • 数据请求
  • 数据过滤
  • 条件

数据请求

数据请求中有三个地方可以用到这个功能。

  • url
    请求的url上可以携带查询参数或者路径。
    比如/api/{{userType}}/?userId={{userId}}
  • data
    url可以携带,那请求参数同样可以携带。
1
2
3
{
"userId": "{{userId}}"
}
  • headers
    请求头上也可以携带,效果也同上。

具体效果可以查看下图。



上面看到的那个特殊语法{{xxx}}得益于mustache,其实和vue的语法是一样的。

1
2
import Mustache from 'mustache';
Mustache.render('/api/{{data}}', { data: '100' });

数据过滤

数据过滤的功能常用于,当原始的数据格式不能满足组件的需要,或者需要对数据进行处理时。
本质上就是一个个函数,分别接收上一个函数的返回值,第一个函数则是接收原始数据,最终返回的数据则为组件需要的数据。
比如

1
2
3
4
5
6
7
8
9
// 把数据的name和value映射到组件需要的x和y字段上
function filter(data) {
return data.map(item => {
return {
x: item.name,
y: item.value
}
})
}

此时,可以将关联参数作为过滤器的第二参数传入,可以非常灵活的利用js代码来做各种各样的处理。
比如下面的简单示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 假设有这样的一条关联参数数据
/*
{
userId: 'xxx'
}
*/

// 过滤函数
// 只抽取userId和关联参数的userId一样的数据
function filter(data, params) {
const { userId } = params
return data.filter(item => item.userId === userId)
}

数据过滤函数也是存在于全局的,组件与组件可以共用同一个过滤函数。

而具体的设置逻辑呢?
根据全局的参数,我们可以封装一个下拉选择组件,用于选择需要关联的参数。

其实即使不显式设置关联参数,同样可以在过滤器的第二参数当中访问到,区别就在于是否为响应式,此逻辑其实有很大作用,这个会在后续的文章中详细展开。

条件

条件功能则是用于控制组件的显示隐藏。
从上面的话中也能看出,关联参数在其中的重要性。

用过datav的应该都见过这个东西。
选择一个或多个关联参数,设置一个或多个条件等等。
判断参数是等于、大于、小于、不等于。
满足条件就响应最终的行为逻辑。

比如userId等于xxx时,隐藏组件。

并且笔者还在此基础上增加了一个自定义控制的逻辑。
即使用函数的形式,根据返回的布尔值来判断条件是否成立。

此逻辑其实类似于上面所述的"数据过滤"。

参数变更处理

最后就是关于参数变更的处理。
当参数的值发生变化时,我们需要如何去处理它的变化。

使用的技术栈为React

所有依赖关联参数的组件都依赖了全局的关联参数源。
但是我们不可能把整个全局的源全部传入到渲染组件中,因为这会导致不必要的渲染。
所以我们可以定义一个空的组件,在其中处理相关的一些逻辑,并传入诸如:重新请求、重新过滤等等的业务逻辑。

1
2
3
4
5
6
// 直接使用useEffect去做比较 "params" 并更新数据
useEffect(() => {
compare(params)
}, [params])
// 监听 "url" 变化触发 compare
// 常量的话是属于不可变的类型,所以不必管

上面的compare实质上是一个比较并赋值的过程,比较前后两个值如果发生了变化,就收集对应的更新函数。
比如reRequestreFilterreCondition等等。
并在比较完成后进行统一的执行。

而比较的过程不是对params等的比较,在初始化时,对所有的关联参数做了统一化的处理,形成了一个对象的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const mapParams = {
variable: {
// 执行的操作
action: () => {},
index: {
// 参数类型
type: 'href' // params href constants,
// 参数的映射名称
value: 'variable',
// 获取最新的变更的参数值
getValue: (params) => {},
// TODO
},
value: '当前参数的值'
}
}

比较则是遍历上面的mapParams

结束

以上逻辑均为本人自己的想法,如有问题或错误可指正🙏🏻 。

结束🔚。

顺便在下面附上相关的链接。

试用地址
试用账号
静态版试用地址
操作文档
代码地址