3d图表的利弊、以及3d散点图、surface图的绘制

您所在的位置:网站首页 怎么做3d图表 3d图表的利弊、以及3d散点图、surface图的绘制

3d图表的利弊、以及3d散点图、surface图的绘制

2023-10-31 08:38| 来源: 网络整理| 查看: 265

背景

逛 echarts,plotly , highcharts 等业界知名可视化库时,经常会遇到酷炫的3d图表:

echarts echarts

plotly plotly

但是像vega、ggplot、 g2这样的可视化库却没有对应的3d图表实现,为什么呢?

先说结论

三维图表很受欢迎,但在大多数情况下,人们解读图形的准确性和速度会受到负面影响。一般来说,最好是避免它们。然而,也存在3d散点图,曲面图这样的例外。同时也可以通过添加交互,例如利用VR/AR技术提升其对数据的表达效果,。

3d图表可能会造成对图表数据的错误解读、隐藏图表想传达的信息、

以一张barchart为例:

image.png 请思考如下问题:

你能告诉我每个数据的实际大小吗? 最右边Karan的数据高度是140吗? Karan 的三月的数据高度好像比一月的数据高一点,实际也是如此吗?(其实一样)如果把视角调整,会有另外的结论吗? 有的绿色数据条好像被遮住了?

更糟糕的是3d饼图:

image.png

以一个有两个比例数据的简单饼图为例,其中一个代表25%的数据,另一个代表75%的数据,在空间中旋转这个饼图。当我们改变看饼的角度时,饼的大小似乎也会改变。特别是25%的那一块,当我们从平面角度看饼图时,它看起来要比25%大得多(上图a)。

image.png

再比如这张销售数据图,占比30%的数据块貌似是最大的,很明显不正确。(35%才是最大的)

将3D对象投影到二维空间,用于显示在显示器上,会使数据失真,尽管人眼会做一些纠正,但是效果有限。事实上,由于3d空间存在透视投影、视锥裁剪以及物体遮挡,很多时候会造成一些错觉,隐藏图表的关键信息。

争议:3d散点图

尽管3d饼图、3d柱状图已经广为诟病,关于3d散点图能否可以有效传达数据信息依旧存在着争议。 3d 散点图最大的缺点在于,人眼实际上无法辨识3d空间(投影在显示屏上的)中点的位置(见下图,数据点的sepal length根本无法辨识),因此该图表缺少表达准确性。

image.png Claus O. Wilke在其 Fundamentals of Data Visualization 一书中举了32辆汽车的燃油效率与排量和功率的3D散点图的例子。32辆汽车(1973-74车型)的燃油效率与排量和功率。每个圆点代表一辆车,圆点的颜色代表这辆车的气缸数。四个图表(a) - (d)显示了完全相同的数据,但使用了不同的透视图, image.png 你能说出(a)部分中的哪些点对应(b)部分中的哪些点吗?显然很困难。

Claus O. Wilke也提出了替代3d点图的数据可视化方案:

用多张2d点图展示数据,如果我们主要关心的是燃油效率受排量、功率等因素的影响,我们可以绘制两次,一次针对排量,一次针对功率。

image.png

2 如果我们更感兴趣的是位移和功率之间的关系,而燃料效率是次要的兴趣变量,我们可以绘制功率与位移的关系,并将燃料效率映射到圆点的大小上,如果你了解可视化的图形语法的概念,实际上就是添加一个数据字段到视觉通道的映射。

image.png

然而Holtz Yan认为,如果3d图表是可交互的,使用者可以自由缩放旋转,调整视角,尽管依旧很难知晓点的具体位置,但是至少可以探究数据点组的结构,因此3d散点图还是有一定的意义的。(我认为点的具体位置可以通过annotation、legend等交互手段弥补),Claus O. Wilke认为,如果可视化是交互式的,并且可以被观看者旋转,或者如果它可以在VR或增强现实环境中显示,可以从多个角度进行检查,那么上面中描述的问题就不那么重要了。其次,即使可视化不是交互式的,缓慢旋转3d相机,而不是从一个角度显示静态图像,这将允许观看者辨别3D空间中不同图形元素的位置。人类的大脑非常擅长从不同角度拍摄一系列图像来重建3D场景,而缓慢旋转的图像恰恰提供了这些图像。

可交互的3d散点图

例外:surface plot(曲面图)

如果在一系列的网格坐标中每个位置都有一个数值变量,则可以使用曲面图表示数据。这种表示方式是有意义的,特别是当数值表示高度等具有实际空间意义的信息时。

newplot (3).png

实战1:绘制三维曲面图

那么,surface plot是如何绘制出来的呢? 我写了一个示例:rainy-25Ghz/three-surface-plot: Created with StackBlitz ⚡️ (github.com)

以threejs作为渲染器,渲染一组数据,其位置满足以下关系:

y=20−x2−z2y=20-x^2 - z^2y=20−x2−z2

本质上,就是把曲面分成一个一个的网格(空间4边形),每一个网格都由两个三角形面片组成,通过threejs的buffergeometry自定义几何体,渲染出来。绘制一个网格的函数如下所示

const drawCell = (points: number[][]) => { const p0 = points[0]; //左下角 const p1 = points[1]; //右下角 const p2 = points[2]; //左上角 const p3 = points[3]; //右上角 const positions = [...p0, ...p1, ...p2, ...p3]; const uvs = [...[0, 0], ...[1, 0], ...[0, 1], ...[1, 1]];//纹理坐标 const colors = points.flatMap((pt, index) => { const hsl = `hsl(${pt[1] * 18}, 100%, 50%)`; const color = new THREE.Color(hsl); return [color.r, color.g, color.b]; });//vertex color与高度(y)有关,建立颜色与数据点高度的映射 //配置几何 const geometry = new THREE.BufferGeometry(); const positionNumComponents = 3; const uvNumComponents = 2; geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array(positions), positionNumComponents ) ); geometry.setAttribute( 'uv', new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents) ); geometry.computeVertexNormals(); geometry.setAttribute( 'color', new THREE.BufferAttribute(new Float32Array(colors), 3) ); geometry.setIndex([0, 1, 2, 2, 1, 3]); const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors }) ); mesh.material.side=THREE.DoubleSide; scene.add(mesh); //渲染wireframe来观察网格的几何结构 const wireframe = new THREE.WireframeGeometry(geometry); const line = new THREE.LineSegments(wireframe); line.material.depthTest = false; line.material.opacity = 0.25; line.material.transparent = true; scene.add(line); };

渲染出来的网格效果: image.png

然后计算每个网格四个点的坐标,并绘制:

onst computeY = (x: number, z: number) => { return 20 - (x ** 2 + z ** 2) / 10; }; const step_x = 0.5; const step_z = 0.5; for (let i = -10; i < 10; i += step_x) { for (let j = -10; j < 10; j += step_z) { const p0 = [i, computeY(i, j), j]; const p2 = [i + step_x, computeY(i + step_x, j), j]; const p1 = [i, computeY(i, j + step_z), j + step_z]; const p3 = [i + step_x, computeY(i + step_x, j + step_z), j + step_z]; drawCell([p0, p1, p2, p3]); } }

最终结果是一个抛物曲面(红色方块为y=0这个平面): image.png

实战2 绘制一个3d散点图

散点图很简单,直接绘制SphereGeometry(可以设置半径大小与位置)即可 具体代码见 Typescript (forked) - StackBlitz

image.png 注意:使用正交投影代替透视投影,可以避免物体由于透视导致的近大远小造成误判。

参考文献

www.data-to-viz.com/caveat/3d.h… clauswilke.com/dataviz/no-…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3