Skip to content

Conversation

@BQXBQX
Copy link
Contributor

@BQXBQX BQXBQX commented Mar 26, 2025

问题背景

G2 在 5.0 版本中增加 spec 的使用方式,未来也会作为主流的使用方式,但是当前 spec 存在一个问题:为了让 spec 更灵活的配置图形样式,支持了大量的 function callback 的方式去支持用户自定义,这样带来问题是 spec 的 function 配置不可序列化,比如对于用户 ssr 场景,期望用户输入的 spec 配置可以持久化存储起来,所以需要用一种 字符串表达式 来描述一个 function。

{
  style: {
    fill:  d => d.value > 100 ? 'red' : 'green',  // 👈🏻 使用回调函数灵活自定义,但是无法持久化
  }
}

以此,设计和开源了 expr

接入到 G2

接入到 G2、G6、L7、S2 等,思路应该是一致的,就是对于所有具备 function callback 的地方支持 expr,并在渲染前解析城渲染器能理解的 function 函数,具体建 expr 的文档。

这里就会遇到一个问题,不同的 function 有不同的参数,且参数的语义不一致,那怎么批量支持 expr 表达式了?

expr 设计时,要求模版语法的参数和 context 中的 key 是严格对应的,但是在 G2 中使用 expr template 语法时,参数名无法统一,回调函数的参数非常多样化,如果简单设置为 datum,i,data,options,语义上会非常不合适,需要考虑多种情况。

方案一 ✅

像 echarts 那样通过 a, b, c, d 无语意化的变量来实现变量的赋值,变量只表示顺序。

{
  style: {
    fill: "{ a.value > 10 ? 'red' : 'green' }",
  }
}

方案二

expr 增加 variables() 能力,如下

image

通过获取模版 expr 中使用到的所有变量,将变量和函数入参进行一一映射,但是这层映射关系怎么实现呢?在 js 动态语法中这层映射是依靠顺序实现的,但我们中获得变量的顺序是无序的。觉得可以通过修改 expr 的模版语法规则或者增加 options 传入参数实现用户自定义顺序,但是感觉大大增加开发者使用成本。(整体思路就是模仿 js 实现一个 值传递

方案三

定义一套 80% 普适的变量名。例如:datum、i、data、context。

{
  style: {
    fill: "{ datum.value > 10 ? 'red' : 'green' }",
  }
}

结论

最终选择方案一,使用无意义参数,弊端是无意义,优势在于:

  1. 代码能批量一次接入,不用耦合多出代码
  2. 维护更方便,避免参数名称的变化,带来 break change
  3. 用户理解心智只有一次门槛,理解 a, b, c, d 无语义变量仅代表参数顺序即可

具体实施

在 runtime 统一入口,对 options 进行递归处理,当前仅仅处理 style, encode, attr, label, children,后续可以追加,具体代码建 src/utils/expr.ts

另外,额外注意避免:

  1. 递归 data 配置,可能存在大数据量带来性能降低。
  2. 使用 expr 的 compile 提升性能,特别对于 style 是会大批量调用的情况。
  3. 使用缓存 compile 结果,但是也需要控制内存占用,所以使用 lru 的缓存机制,保留最新的 128 个缓存。

@coveralls
Copy link

coveralls commented Mar 26, 2025

Pull Request Test Coverage Report for Build 14200117306

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 32 of 32 (100.0%) changed or added relevant lines in 2 files are covered.
  • 36 unchanged lines in 4 files lost coverage.
  • Overall coverage increased (+0.06%) to 86.768%

Files with Coverage Reduction New Missed Lines %
src/animation/morphing.ts 1 98.19%
src/interaction/utils.ts 5 89.21%
src/interaction/elementSelect.ts 8 89.19%
src/interaction/tooltip.ts 22 91.41%
Totals Coverage Status
Change from base Build 14143232555: 0.06%
Covered Lines: 10733
Relevant Lines: 11989

💛 - Coveralls

@antvis antvis deleted a comment from BQXBQX Mar 26, 2025
@antvis antvis deleted a comment from BQXBQX Mar 26, 2025
@lvisei
Copy link
Member

lvisei commented Mar 26, 2025

可以参考 vega 的看看 https://vega.github.io/vega/docs/expressions/

@hustcc
Copy link
Member

hustcc commented Mar 27, 2025

我的观点:

方案三,不能代表所有情况,除非我们给不同的 callback 定义不同语义的名字,就需要额外对不同的 key 做不同的逻辑。即使现在可行,未来扩展的时候也会头疼。

方案二,技术上应该是不可行的。

目前只有方案一,是可行,对开发者来说理解上成本也不算高。

@hustcc
Copy link
Member

hustcc commented Mar 27, 2025

todo list:

  • expr 接入进来,目前有一些 cr 问题
  • 文档,我想法是单独一篇文档,类似于 vega,然后 相关的章节的文档,能提供一些文档案例
  • 增加 demo,放哪里比较合适?
  • 单测
    • 来一个完整的图表,相关配置均使用 expr 的方式
    • 对 expr 的一些纯函数增加 unit 测试
  • 发布一个中版本 5.3.0
  • 准备运营稿 @BQXBQX 这个也可以你来帮忙写写

@interstellarmt
Copy link
Member

使用方案一的话,后续需要在模版字符串的相关文档里做介绍:
看一下是分别放到配置项里介绍还是用一篇文章汇总。
image

@moayuisuda
Copy link
Collaborator

方案一也可以提供一个 vscode 插件来自动提示当前配置下每个模板变量的信息,插件里的规则写好后也可以解析生成为文档的内容。
之后这个插件也可以扩展其他 antv 相关的开发体验优化功能。

@lvisei
Copy link
Member

lvisei commented Mar 27, 2025

方案一也可以提供一个 vscode 插件来自动提示当前配置下每个模板变量的信息,插件里的规则写好后也可以解析生成为文档的内容。 之后这个插件也可以扩展其他 antv 相关的开发体验优化功能。

插件对开发者和使用者来说成本还是有的😂

@hustcc
Copy link
Member

hustcc commented Mar 27, 2025

方案一也可以提供一个 vscode 插件来自动提示当前配置下每个模板变量的信息,插件里的规则写好后也可以解析生成为文档的内容。 之后这个插件也可以扩展其他 antv 相关的开发体验优化功能。

我也觉得针对这个能力提供插件,有点过度了,最好是开发者不用插件,就能理解我们的设计,否则有种我们把 A 做复杂之后,再给开发者提供 B 去降低复杂。

@moayuisuda
Copy link
Collaborator

我看着方案1基本是必须依赖文档的,切来切去这个成本,和本身开发的成本可以权衡下。另外我意思不是只针对这个功能专门设计一个插件,只是这个功能刚好不好理解,可以用插件来解决下,这个插件也能集成其他能力,比如意图生成snippets之类的

@moayuisuda
Copy link
Collaborator

插件这东西我只是提一下,本身不阻塞这个pr

@hustcc
Copy link
Member

hustcc commented Mar 27, 2025

@moayuisuda 嗯嗯,了解了~

@BQXBQX
Copy link
Contributor Author

BQXBQX commented Mar 28, 2025

  • expr 接入, cr 问题解决
  • 对 expr 的一些纯函数增加 unit 测试

@BQXBQX
Copy link
Contributor Author

BQXBQX commented Mar 29, 2025

添加了一个 static 单测,渲染正常

import { register } from '@antv/expr';

export function expr() {
  /**
   * 计算每个圆的半径
   * @param datum 当前数据
   * @param i 当前数据索引
   * @param data 所有数据
   * @param global 所有变量的集合
   * @returns 圆的半径
   */
  const getRadius = (datum, i, data, global) => {
    const total = data.reduce((a, b) => a + b.value, 0);
    return 1 - (datum.value / total) * 5;
  };

  const getTooltip = (datum, i, data, global) => {
    return {
      name: '*' + datum.name + ' population: ',
      value: datum.value,
    };
  };

  register('getRadius', getRadius);
  register('getTooltip', getTooltip);

  return {
    type: 'interval',
    height: 640,
    data: {
      type: 'fetch',
      value: 'data/population2015.csv',
    },
    transform: [{ type: 'stackY' }],
    coordinate: { type: 'theta' },
    scale: {
      color: { palette: 'spectral', offset: '{a * 0.8 + 0.1}' },
    },
    encode: { y: 'value', color: 'name' },
    style: { stroke: 'white' },
    tooltip: '{@getTooltip(a, b, c, global)}',
    labels: [
      {
        text: '{"*" + a.name}',
        radius: '{@getRadius(a, b, c, global)}',
        style: {
          fontSize: '{a.value>15000000 ? a.value>20000000 ? 20 : 15 : 9}',
          fontWeight: 'bold',
        },
      },
      {
        text: '{b < c.length - 3 ? a.value : ""}',
        radius: '{@getRadius(a, b, c, global)}',
        style: { fontSize: 9, dy: 12 },
      },
    ],
    animate: { enter: { type: 'waveIn', duration: 1000 } },
  };
}
image

@hustcc
Copy link
Member

hustcc commented Mar 30, 2025

@BQXBQX WhiteList 我来定一下吧, 先做高优先的,避免带来预见不了的问题。从 ssr 来看,组件、交互、动画都不需要。

所以只保留: style、encode、attr、labels,顺序代表优先级,其他都先去掉。后续有需求再加比出问题移除容易的多。

@hustcc hustcc marked this pull request as ready for review March 31, 2025 11:05
@hustcc hustcc changed the title feat: support expr parse feat: support expression string for function config Apr 2, 2025
@hustcc hustcc merged commit e97f996 into antvis:v5 Apr 2, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

G2 Spec 的序列化,便于 SSR 的时候纯 JSON

6 participants