diff --git a/__tests__/plots/api/chart-render-3d-scatter-plot-perspective.ts b/__tests__/plots/api/chart-render-3d-scatter-plot-perspective.ts index ba43d4506a..0cfc969a6f 100644 --- a/__tests__/plots/api/chart-render-3d-scatter-plot-perspective.ts +++ b/__tests__/plots/api/chart-render-3d-scatter-plot-perspective.ts @@ -1,8 +1,9 @@ -import { CameraType, Canvas } from '@antv/g'; +import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '../../../src/api'; +import { Runtime, extend } from '../../../src/api'; +import { corelib, threedlib } from '../../../src/lib'; export function chartRender3dScatterPlotPerspective(context) { const { container } = context; @@ -12,61 +13,51 @@ export function chartRender3dScatterPlotPerspective(context) { renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); - const canvas = new Canvas({ + const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); + const chart = new Chart({ container, - width: 640, - height: 480, + theme: 'classic', renderer, + depth: 400, }); - const camera = canvas.getCamera(); - camera.setPerspective(0.1, 5000, 45, 500 / 500); - camera.setType(CameraType.ORBITING); - - // Add a directional light into scene. - const light = new DirectionalLight({ - style: { - intensity: 3, - fill: 'white', - direction: [-1, 0, 1], - }, - }); - canvas.appendChild(light); - - const chart = new Chart({ theme: 'classic', container, canvas }); - chart.options({ - width: 500, - height: 500, - depth: 400, - type: 'point3D', - padding: 'auto', - data: { + chart + .point3D() + .data({ type: 'fetch', value: 'data/cars2.csv', - }, - encode: { - x: 'Horsepower', - y: 'Miles_per_Gallon', - z: 'Weight_in_lbs', - size: 'Origin', - color: 'Cylinders', - shape: 'cube', - }, - scale: { - x: { nice: true }, - y: { nice: true }, - z: { nice: true }, - }, - coordinate: { type: 'cartesian3D' }, - axis: { - x: { gridLineWidth: 3 }, - y: { gridLineWidth: 3, titleBillboardRotation: -Math.PI / 2 }, - z: { gridLineWidth: 3 }, - }, - legend: false, - }); + }) + .encode('x', 'Horsepower') + .encode('y', 'Miles_per_Gallon') + .encode('z', 'Weight_in_lbs') + .encode('size', 'Origin') + .encode('color', 'Cylinders') + .encode('shape', 'cube') + .coordinate({ type: 'cartesian3D' }) + .scale('x', { nice: true }) + .scale('y', { nice: true }) + .scale('z', { nice: true }) + .legend(false) + .axis('x', { gridLineWidth: 2 }) + .axis('y', { gridLineWidth: 2, titleBillboardRotation: -Math.PI / 2 }) + .axis('z', { gridLineWidth: 2 }); - const finished = chart.render(); + const finished = chart.render().then(() => { + const { canvas } = chart.getContext(); + const camera = canvas!.getCamera(); + camera.setPerspective(0.1, 5000, 45, 500 / 500); + camera.setType(CameraType.ORBITING); + + // Add a directional light into scene. + const light = new DirectionalLight({ + style: { + intensity: 3, + fill: 'white', + direction: [-1, 0, 1], + }, + }); + canvas!.appendChild(light); + }); return { finished }; } diff --git a/__tests__/plots/api/chart-render-3d-scatter-plot.ts b/__tests__/plots/api/chart-render-3d-scatter-plot.ts index 1957ef36fe..c18e81d6f7 100644 --- a/__tests__/plots/api/chart-render-3d-scatter-plot.ts +++ b/__tests__/plots/api/chart-render-3d-scatter-plot.ts @@ -2,7 +2,8 @@ import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '../../../src/api'; +import { Runtime, extend } from '../../../src/api'; +import { corelib, threedlib } from '../../../src/lib'; export function chartRender3dScatterPlot(context) { const { container } = context; @@ -12,6 +13,7 @@ export function chartRender3dScatterPlot(context) { renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); + const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container, theme: 'classic', diff --git a/site/.dumirc.ts b/site/.dumirc.ts index 2b06c20e15..e696183678 100644 --- a/site/.dumirc.ts +++ b/site/.dumirc.ts @@ -215,6 +215,14 @@ export default defineConfig({ }, order: 14, }, + { + slug: 'spec/3d', + title: { + zh: '3D 图表 - 3D Charts', + en: '3D', + }, + order: 15, + }, { slug: 'spec/theme', title: { diff --git a/site/docs/api/chart.zh.md b/site/docs/api/chart.zh.md index 042c3cfcda..9e6daffec5 100644 --- a/site/docs/api/chart.zh.md +++ b/site/docs/api/chart.zh.md @@ -37,6 +37,7 @@ chart.render(); | container | 指定 chart 绘制的 DOM,可以传入 DOM id,也可以直接传入 dom 实例 | `string \| HTMLElement` | | | width | 图表宽度 | `number` | 640 | | height | 图表高度 | `number` | 480 | +| depth | 图表深度,在 3D 图表中使用 | `number` | 0 | | renderer | 指定渲染引擎,默认使用 canvas。 | | | | plugins | 指定渲染时使用的插件 ,具体见 [plugin](/api/plugin/rough) | `any[]` | | | autoFit | 图表是否自适应容器宽高,默认为 `false`,用户需要手动设置 `width` 和 `height`。
当 `autoFit: true` 时,会自动取图表容器的宽高,如果用户设置了 `height`,那么会以用户设置的 `height` 为准。 | `boolean` | false | @@ -196,7 +197,7 @@ chart.render(); ### `chart.point3D` -添加 point3D 图形,具体见 [mark](/spec/mark/point3D)。 +添加 point3D 图形,具体见 [3d](/spec/3d/point3-d)。 ## 设置属性 diff --git a/site/docs/manual/extra-topics/3d-charts.en.md b/site/docs/manual/extra-topics/3d-charts.en.md index f040cd421c..195d7d6f86 100644 --- a/site/docs/manual/extra-topics/3d-charts.en.md +++ b/site/docs/manual/extra-topics/3d-charts.en.md @@ -1,5 +1,5 @@ --- -title: 3D Charts +title: Use 3D Charts order: 11 --- diff --git a/site/docs/manual/extra-topics/3d-charts.zh.md b/site/docs/manual/extra-topics/3d-charts.zh.md index 8bb2981ba6..a3374f20f2 100644 --- a/site/docs/manual/extra-topics/3d-charts.zh.md +++ b/site/docs/manual/extra-topics/3d-charts.zh.md @@ -1,11 +1,12 @@ --- -title: 3D 图表 +title: 绘制 3D 图表 order: 11 --- 以 3D 散点图为例,创建图表需要以下步骤: - 创建 WebGL 渲染器和插件 +- 扩展 threedlib - 设置 z 通道、比例尺和坐标轴 - 在场景中设置相机 - 添加光源 @@ -18,7 +19,7 @@ order: 11 首先安装依赖: ```bash -$ npm install @antv/g-webgl @antv/g-plugin-3d @antv/g-plugin-control --save; +$ npm install @antv/g-webgl @antv/g-plugin-3d @antv/g-plugin-control --save ``` 然后使用 [@antv/g-webgl](https://g.antv.antgroup.com/api/renderer/webgl) 作为渲染器并注册以下两个插件: @@ -36,6 +37,16 @@ renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); ``` +## 扩展 threedlib + +由于 3D 相关的功能代码体积巨大,我们将其分离到 `threedlib` 中,在运行时扩展它并自定义 Chart 对象: + +```ts +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; + +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); +``` + ## 设置 z 通道、比例尺和坐标轴 在创建 Chart 时通过 `depth` 指定深度: @@ -49,7 +60,7 @@ const chart = new Chart({ }); ``` -我们使用 [point3D](/spec/mark/point3-d) Mark 并选择 cube 作为 shape 进行绘制。 +我们使用 [point3D](/spec/3d/point3-d) Mark 并选择 cube 作为 shape 进行绘制。 随后设置 z 通道、比例尺和坐标轴。 ```ts @@ -92,7 +103,62 @@ chart.render().then(() => { 效果如下: -perspective +```js | ob { pin: false } +(() => { + const renderer = new gWebgl.Renderer(); + renderer.registerPlugin(new gPluginControl.Plugin()); + renderer.registerPlugin(new gPlugin3d.Plugin()); + + const Chart = G2.extend(G2.Runtime, { ...G2.corelib(), ...G2.threedlib() }); + + // 初始化图表实例 + const chart = new Chart({ + theme: 'classic', + renderer, + depth: 400, + }); + + chart + .point3D() + .data({ + type: 'fetch', + value: + 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', + }) + .encode('x', 'Horsepower') + .encode('y', 'Miles_per_Gallon') + .encode('z', 'Weight_in_lbs') + .encode('color', 'Cylinders') + .encode('shape', 'cube') + .coordinate({ type: 'cartesian3D' }) + .scale('x', { nice: true }) + .scale('y', { nice: true }) + .scale('z', { nice: true }) + .legend(false) + .axis('x', { gridLineWidth: 2 }) + .axis('y', { gridLineWidth: 2, titleBillboardRotation: -Math.PI / 2 }) + .axis('z', { gridLineWidth: 2 }); + + chart.render().then(() => { + const { canvas } = chart.getContext(); + const camera = canvas.getCamera(); + camera.setPerspective(0.1, 5000, 45, 500 / 500); + camera.setType(g.CameraType.ORBITING); + + // Add a directional light into scene. + const light = new gPlugin3d.DirectionalLight({ + style: { + intensity: 3, + fill: 'white', + direction: [-1, 0, 1], + }, + }); + canvas.appendChild(light); + }); + + return chart.getContainer(); +})(); +``` 我们还可以让相机固定视点进行一定角度的旋转,这里使用了 [rotate](https://g.antv.antgroup.com/api/camera/action#rotate): @@ -100,7 +166,62 @@ chart.render().then(() => { camera.rotate(-20, -20, 0); ``` -orthographic +```js | ob { pin: false } +(() => { + const renderer = new gWebgl.Renderer(); + renderer.registerPlugin(new gPluginControl.Plugin()); + renderer.registerPlugin(new gPlugin3d.Plugin()); + + const Chart = G2.extend(G2.Runtime, { ...G2.corelib(), ...G2.threedlib() }); + + // 初始化图表实例 + const chart = new Chart({ + theme: 'classic', + renderer, + depth: 400, + }); + + chart + .point3D() + .data({ + type: 'fetch', + value: + 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', + }) + .encode('x', 'Horsepower') + .encode('y', 'Miles_per_Gallon') + .encode('z', 'Weight_in_lbs') + .encode('color', 'Cylinders') + .encode('shape', 'cube') + .coordinate({ type: 'cartesian3D' }) + .scale('x', { nice: true }) + .scale('y', { nice: true }) + .scale('z', { nice: true }) + .legend(false) + .axis('x', { gridLineWidth: 2 }) + .axis('y', { gridLineWidth: 2, titleBillboardRotation: -Math.PI / 2 }) + .axis('z', { gridLineWidth: 2 }); + + chart.render().then(() => { + const { canvas } = chart.getContext(); + const camera = canvas.getCamera(); + camera.setType(g.CameraType.ORBITING); + camera.rotate(-20, -20, 0); + + // Add a directional light into scene. + const light = new gPlugin3d.DirectionalLight({ + style: { + intensity: 3, + fill: 'white', + direction: [-1, 0, 1], + }, + }); + canvas.appendChild(light); + }); + + return chart.getContainer(); +})(); +``` ## 添加光源 @@ -121,10 +242,6 @@ canvas.appendChild(light); ## 使用相机交互 -3D 场景下的交互和 2D 场景有很大的不同,[g-plugin-control](https://g.antv.antgroup.com/plugins/control) 提供了 3D 场景下基于相机的交互。当我们拖拽画布时,会控制相机绕视点进行旋转操作,而鼠标滚轮的缩放会让相机进行 dolly 操作: - -perspective control - -需要注意的是缩放操作在正交投影下是没有效果的,但旋转操作依然有效: +3D 场景下的交互和 2D 场景有很大的不同,[g-plugin-control](https://g.antv.antgroup.com/plugins/control) 提供了 3D 场景下基于相机的交互。当我们拖拽画布时,会控制相机绕视点进行旋转操作,而鼠标滚轮的缩放会让相机进行 dolly 操作。 -orthographic control +需要注意的是缩放操作在正交投影下是没有效果的,但旋转操作依然有效。 diff --git a/site/docs/manual/introduction/why-g2.zh.md b/site/docs/manual/introduction/why-g2.zh.md index 56b99d08bd..ed80f9cc78 100644 --- a/site/docs/manual/introduction/why-g2.zh.md +++ b/site/docs/manual/introduction/why-g2.zh.md @@ -686,7 +686,7 @@ import { Runtime, corelib, extend } from '@antv/g2'; // 基于 corelib 对 Runtime 进行扩展 // 1. 增加类型(如果使用的 TypeScript) // 2. 增加 Mark -const Chart = extend(Runtime, corelib); +const Chart = extend(Runtime, { ...corelib() }); const chart = new Chart({ container: 'container' }); diff --git a/site/docs/spec/3d/point3D.en.md b/site/docs/spec/3d/point3D.en.md new file mode 100644 index 0000000000..896c822015 --- /dev/null +++ b/site/docs/spec/3d/point3D.en.md @@ -0,0 +1,6 @@ +--- +title: point3D +order: 1 +--- + + diff --git a/site/docs/spec/mark/point3D.zh.md b/site/docs/spec/3d/point3D.zh.md similarity index 95% rename from site/docs/spec/mark/point3D.zh.md rename to site/docs/spec/3d/point3D.zh.md index 9a212a9c6f..3656c475df 100644 --- a/site/docs/spec/mark/point3D.zh.md +++ b/site/docs/spec/3d/point3D.zh.md @@ -21,12 +21,13 @@ import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '@antv/g2'; +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; const renderer = new WebGLRenderer(); renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container: 'container', theme: 'classic', @@ -61,9 +62,6 @@ chart.render().then(() => { const camera = canvas.getCamera(); camera.setType(CameraType.ORBITING); - // TODO: infer by depth in layout process. - canvas.document.documentElement.translate(0, 0, -200); - // Add a directional light into scene. const light = new DirectionalLight({ style: { diff --git a/site/docs/spec/coordinate/cartesian3D.en.md b/site/docs/spec/coordinate/cartesian3D.en.md new file mode 100644 index 0000000000..4865c88f91 --- /dev/null +++ b/site/docs/spec/coordinate/cartesian3D.en.md @@ -0,0 +1,6 @@ +--- +title: cartesian3D +order: 5 +--- + + diff --git a/site/docs/spec/coordinate/cartesian3D.zh.md b/site/docs/spec/coordinate/cartesian3D.zh.md new file mode 100644 index 0000000000..6062942265 --- /dev/null +++ b/site/docs/spec/coordinate/cartesian3D.zh.md @@ -0,0 +1,50 @@ +--- +title: cartesian3D +order: 5 +--- + +在 2D 笛卡尔坐标系基础上,通过增加 Z 轴扩展而来。[示例](/manual/extra-topics/3d-charts) + +## 开始使用 + +cartesian3D + +```js +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; + +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); + +const chart = new Chart({ + container: 'container', + theme: 'classic', + renderer, + depth: 400, +}); + +chart.coordinate({ + type: 'cartesian3D', +}); + +chart + .point3D() + .data({ + type: 'fetch', + value: + 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', + }) + .encode('x', 'Horsepower') + .encode('y', 'Miles_per_Gallon') + .encode('z', 'Weight_in_lbs') + .encode('size', 'Origin') + .encode('color', 'Cylinders') + .encode('shape', 'cube') + .scale('x', { nice: true }) + .scale('y', { nice: true }) + .scale('z', { nice: true }) + .legend(false) + .axis('x', { gridLineWidth: 2 }) + .axis('y', { gridLineWidth: 2, titleBillboardRotation: -Math.PI / 2 }) + .axis('z', { gridLineWidth: 2 }); + +chart.render(); +``` diff --git a/site/docs/spec/mark/point3D.en.md b/site/docs/spec/mark/point3D.en.md deleted file mode 100644 index 7a82fe06b5..0000000000 --- a/site/docs/spec/mark/point3D.en.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: point -order: 1 ---- - - diff --git a/site/examples/3d/scatter/demo/orthographic-projection.ts b/site/examples/3d/scatter/demo/orthographic-projection.ts index 19fef5a054..2583d096e4 100644 --- a/site/examples/3d/scatter/demo/orthographic-projection.ts +++ b/site/examples/3d/scatter/demo/orthographic-projection.ts @@ -2,13 +2,15 @@ import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '@antv/g2'; +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; // Create a WebGL renderer. const renderer = new WebGLRenderer(); renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); +// Customize our own Chart with threedlib. +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container: 'container', theme: 'classic', diff --git a/site/examples/3d/scatter/demo/perspective-projection.ts b/site/examples/3d/scatter/demo/perspective-projection.ts index 291d7ec5d6..b858addcaa 100644 --- a/site/examples/3d/scatter/demo/perspective-projection.ts +++ b/site/examples/3d/scatter/demo/perspective-projection.ts @@ -2,13 +2,15 @@ import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '@antv/g2'; +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; // Create a WebGL renderer. const renderer = new WebGLRenderer(); renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); +// Customize our own Chart with threedlib. +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container: 'container', theme: 'classic', @@ -40,6 +42,7 @@ chart chart.render().then(() => { const { canvas } = chart.getContext(); const camera = canvas.getCamera(); + // Use perspective projection mode. camera.setPerspective(0.1, 5000, 45, 640 / 480); camera.setType(CameraType.ORBITING); diff --git a/site/examples/3d/scatter/demo/sphere-shape.ts b/site/examples/3d/scatter/demo/sphere-shape.ts index e1a51aa55d..f7d68853ef 100644 --- a/site/examples/3d/scatter/demo/sphere-shape.ts +++ b/site/examples/3d/scatter/demo/sphere-shape.ts @@ -2,13 +2,15 @@ import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; -import { Chart } from '@antv/g2'; +import { Runtime, corelib, threedlib, extend } from '@antv/g2'; // Create a WebGL renderer. const renderer = new WebGLRenderer(); renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); +// Customize our own Chart with threedlib. +const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container: 'container', theme: 'classic', diff --git a/src/api/utils.ts b/src/api/utils.ts index 193e1aa8ba..262c02ef39 100644 --- a/src/api/utils.ts +++ b/src/api/utils.ts @@ -64,7 +64,7 @@ export function valueOf(node: Node): Record { export function sizeOf(options, container) { const { autoFit } = options; if (autoFit) return getContainerSize(container); - const { width = 640, height = 480, depth = 640 } = options; + const { width = 640, height = 480, depth = 0 } = options; return { width, height, depth }; } diff --git a/src/index.ts b/src/index.ts index fe46b86196..33eae8c927 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ export { AREA_CLASS_NAME, } from './runtime'; -export { corelib, stdlib, litelib } from './lib'; +export { corelib, stdlib, litelib, threedlib } from './lib'; export * from './mark'; diff --git a/src/lib/std.ts b/src/lib/std.ts index b62a19d3f1..a1138b557a 100644 --- a/src/lib/std.ts +++ b/src/lib/std.ts @@ -2,14 +2,12 @@ import { corelib } from './core'; import { geolib } from './geo'; import { graphlib } from './graph'; import { plotlib } from './plot'; -import { threedlib } from './threed'; export function stdlib() { return { ...geolib(), ...graphlib(), ...plotlib(), - ...threedlib(), ...corelib(), } as const; }