antvis/G2

Add Rect #4176

pearmini posted onGitHub

新增 Rect Mark

主要用于绘制直方图、聚合热力图和矩阵树图。

image

// 绘制一个直方图
G2.render({
  type: 'rect',
  transform: [{ type: 'binX', y: 'count' }],
  encode: { x: 'Volume' },
});

设计思考

在 4.0 里面的直方图是用 Interval 绘制的,存在的问题有至少有两点:

  • 理解成本:Interval 的 x 理论上应该是离散的,但是直方图的 x 是连续的。用一个 Mark 绘制这个地方是有点混淆的。
  • 坐标轴 tick 的生成:正是上面离散和连续的区别,导致了绘制出来的坐标轴应该是不一样的。离散的应该是每一段一个 tick,连续的应该是每一段的两端都有 tick。

绘制直方图的图的另一个思路是用 Polygon Mark,但是这样就需要提供 4 个 x 和 4 个 y 用来生成控制点。这样最大的问题就是不能直接使用 StackY 等 transform,因为它们都是只处理两个 y 通道。

所以这里独立一个 Mark 出来:Rect,它的 x 和 y 通道都是离散的,并且只有 2 个 x 通道和 2 个 y 通道。

实现细节

  • shape 直接复用 interval 下面的 rect 和 hollow。
  • 给 rect 和 hollow 增加笛卡尔坐标系下的 inset 配置。
  • props 如下:
Rect.props = {
  defaultShape: 'rect',
  defaultLabelShape: 'label',
  channels: [
    ...baseGeometryChannels(),
    { name: 'x', required: true },
    { name: 'y', required: true },
  ],
  preInference: [...basePreInference(),  { type: 'maybeZeroY1' },],
  postInference: [
    ...basePostInference(),
    { type: 'maybeTitleX' },
    { type: 'maybeTooltipY' },
  ],
  shapes: ['rect', 'hollow'],
};

案例验证

目前还没有添加 binX 和 binY transform,所以暂时用矩阵树图验证就好。替换这里的案例如下:

image

(() => {
  const width = 640;
  const height = 480;
  const padding = 3;
  const layout = (data) => {
    const root = d3.hierarchy(data);
    root.count();
    d3.treemap().size([width, height]).padding(padding)(root);
    return root
      .descendants()
      .map((d) =>
        Object.assign(d, {
          x: [d.x0, d.x1],
          y: [d.y0, d.y0],
        }),
      )
      .filter((d) => d.height === 0);
  };
  const name = (d) => {
    const { name } = d.data;
    return name.length > 5 ? name.slice(0, 4) + '...' : name;
  };
  const chart = new G2.Chart({
    width,
    height,
    paddingLeft: padding,
    paddingBottom: padding,
    paddingRight: padding,
  });

  chart.data({
    type: 'fetch',
    value: 'https://gw.alipayobjects.com/os/bmw-prod/5155ef81-db23-49f3-b72b-d436a219d289.json',
    transform: [{ type: 'custom', callback: layout }],
  });

  chart
    .rect()
    .encode('x', 'x')
    .encode('y', 'y')
    .encode('size', 'r')
    .encode('color', (d) => d.parent.data.name)
    .encode('tooltip', (d) => d.parent.data.name)
    .encode('title', '')
    .scale('x', { domain: [0, width], guide: null })
    .scale('y', { domain: [0, height], guide: null, range: [0, 1] })
    .scale('color', {
      field: '学派',
      guide: { size: 72, autoWrap: true, maxRows: 3, cols: 6 },
    })
    .scale('size', { type: 'identity' })
    .scale('tooltip', { field: '流派' });

  chart
    .text()
    .data({
      transform: [
        { type: 'filterBy', fields: ['height'], callback: (d) => d === 0 },
      ],
    })
    .encode('x', (d) => d.x[0])
    .encode('y', (d) => d.y[0])
    .encode('text', name)
    .style('dy', '15px')
    .style('dx', '5px')
    .style('fill', 'black')
    .style('fontSize', 12)
    .style('textAnchor', 'start');

  return chart.render().node();
})();

待办

下面两件事情分成两个 PR:

  • 更名 - Rect -> Square:API、Spec、Mark、测试以及案例,分面
  • 新增 - Rect

Fund this Issue

$0.00
Funded

Pull requests