如何在three.js三维场景里添加echarts图表组件

前端 潘老师 2天前 15 ℃ (0) 扫码查看

如果你开发过可视化大屏项目,比如三维数字孪生、智慧城市看板等项目中,经常会碰到一个棘手的问题:怎么在three.js构建的三维场景里,动态展示echarts图表呢?今天,就来和大家详细讲讲,如何利用three.js和ECharts技术的融合,通过自定义拖拽的方式,在three.js三维场景中加载不同的echarts图表组件。

一、借助CSS3DRenderer和CSS3DObject实现融合

three.js提供了一个很实用的API——CSS3DRenderer,它能把DOM元素渲染到3D场景里。不过,使用的时候有些地方得注意:

  • CSS3DRenderer渲染的内容,没办法像3D模型材质那样进行导入导出操作。
  • 它只支持基础的3D变换,像位移、旋转、缩放这些,像复杂光照、阴影、自定义材质、粒子系统这些高级效果就实现不了。
  • 原生支持DOM元素,也就是说,可以直接把HTML、CSS元素,像div、svg,还有ECharts画布当作3D对象来渲染。

二、代码实现过程

(一)封装相关代码

为了让代码结构更清晰,我们把渲染和创建echarts模块的代码,用class类函数封装成css3DRendererModules 。具体代码如下:

export default class css3DRendererModules {
  css3DRenderer: CSS3DRenderer | null;
  css3DControls: OrbitControls | null;
  raycaster = new THREE.Raycaster();
  mouse = new THREE.Vector2();
  scene: THREE.Scene | null;
  camera: THREE.PerspectiveCamera | null;
  renderer: THREE.WebGLRenderer | null;
  container: HTMLElement | null;
  constructor() {
    this.css3DRenderer = null;
    this.css3DControls = null;
    this.raycaster = new THREE.Raycaster();
    this.mouse = new THREE.Vector2();
    this.scene = null;
    this.camera = null
    this.renderer = null
    this.container = document.querySelector('#echarts');
  }
}

在这段代码里,定义了一些属性,用于存放渲染器、控制器、场景、相机等对象,constructor构造函数里对这些属性进行了初始化,并获取了页面上id为echarts的DOM元素。

(二)初始化场景和渲染器

接下来,在init方法里创建场景、相机、渲染器,并且让CSS3DRenderer渲染器和WebGLRenderer渲染器的位置重叠,同时给相关元素添加pointerEvents属性,避免影响WebGLRenderer渲染器中场景的交互功能。代码如下:

init() {
  // 创建场景
  this.scene = new THREE.Scene();
  const rgbeLoader = new RGBELoader();
  const texture = await rgbeLoader.loadAsync('hdr/view-hdr-11.hdr');
  texture.mapping = THREE.EquirectangularReflectionMapping;
  // 创建相机
  const { offsetWidth, offsetHeight } = this.container;
  const aspectRatio = offsetWidth / offsetHeight;
  this.camera = new THREE.PerspectiveCamera(45, aspectRatio, 1, 20000);
  this.camera.position.set(0, 2, 6);
  this.camera.name = 'Camera';
  this.camera.updateProjectionMatrix();
  // 创建渲染器
  this.renderer = new THREE.WebGLRenderer({
    antialias: true, // 开启硬件抗锯齿
    alpha: true,
    preserveDrawingBuffer: true,
    powerPreference: 'high-performance', // 优先使用高性能GPU
  });
  this.renderer.setClearColor(0xcccccc);
  this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制最大像素比为2
  // 创建css3d渲染器
  this.css3DRenderer = new CSS3DRenderer();
  this.css3DRenderer.setSize(offsetWidth, offsetHeight);
  this.css3DRenderer.domElement.style.position = 'absolute';
  this.css3DRenderer.domElement.style.pointerEvents = 'none';
  this.css3DRenderer.domElement.style.top = '0';
  this.css3DRenderer.domElement.style.zIndex = '0';
  this.css3DControls = new OrbitControls(
    this.camera,
    this.css3DRenderer.domElement
  );
}

这段代码依次完成了场景、相机、WebGLRenderer渲染器和CSS3DRenderer渲染器的创建,还设置了相机的位置、渲染器的一些属性,并且初始化了用于控制相机视角的OrbitControls

(三)创建echarts图表

下面的createEcharts方法,用来动态创建DOM元素内容,通过Raycaster射线检测和THREE.Vector2()方法获取鼠标在三维场景中的相对位置,根据传入的echarts图表参数信息设置图表数据和类型,再把元素节点转换为three.js可渲染的内容,添加到场景中。代码如下:

/**
 * 创建echarts
 * @param options - 选项
 */
createEcharts(options: unknown) {
  const { modelData, clientY, clientX } = options as EchartsType;
    
  if (!this?.container || !this?.camera || !this.scene?.children)
    return;

  const rect = this?.container.getBoundingClientRect();
  this.mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
  this.mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;
  this.raycaster.setFromCamera(this.mouse, this?.camera);

  const intersects = this.raycaster
  .intersectObjects(this.scene?.children, true)
  .slice(0, 1);

  if (intersects.length == 0) return;

  const element = document.createElement('div');

  const tagsMode = createApp({
    mounted() {
      const chartDom = this.$refs.chart as HTMLElement;
      const myChart = echarts.init(chartDom);
      myChart.setOption(modelData?.options);
    },
    render() {
      return (
        <div
          ref="chart"
          id="echarts"
          style={{
            width: `${modelData.width}px`,
            height: `${modelData.height}px`,
            backgroundColor: 'rgba(0,0,0,0.6)',
            borderRadius: '4px',
            pointerEvents: 'auto',
          }}
        ></div>
      );
    },
  });

  const vNode = tagsMode.mount(document.createElement('div'));
  element.appendChild(vNode.$el);
  const cssObject = new CSS3DObject(element);
  cssObject.position.set(0, 1.5, 0);
  cssObject.scale.set(0.004, 0.004, 0.004);
  const boxGeometry = new THREE.BoxGeometry(3, 0.5, 0.5);
  const boxMaterial = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: true,
    visible: false,
  });
  const helperBox = new THREE.Mesh(boxGeometry, boxMaterial);
  helperBox.add(cssObject);
  helperBox.userData = {
    isTransformControls: true,
    ...options,
  };
  const { x, y, z } = intersects[0].point;
  helperBox.position.set(x, y, z);
  helperBox.name = modelData.name;
  this.scene?.add(helperBox);
}

这里使用了jsx语法,要是想正常使用,得安装@vitejs/plugin-vue-jsx插件,并且在vite.config.ts里添加jsx相关配置vueJsx() 。具体配置代码如下:

import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig((mode) => {
  return {
    plugins: [vue(),  vueJsx()],
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src'),
        vue: 'vue/dist/vue.esm-bundler.js',
      },
    },
  };
});

(四)实现效果:创建饼图

下面这段代码展示了如何创建一个饼图:

const css3DRendererModules = new css3DRendererModules();
css3DRendererModules.init()
const config =  {
  options: {
    title: {
      text: '今日访客',
      left: 'center',
      textStyle: {
        color: '#fff',
      },
    },
    tooltip: {
      trigger: 'item',
    },
    legend: {
      orient: 'vertical',
      left: 'left',
      textStyle: {
        color: '#fff',
      },
    },
    series: [
      {
        name: '今日访客',
        type: 'pie',
        radius: '50%',
        data: [
          { value: 1048, name: '北京' },
          { value: 735, name: '上海' },
          { value: 580, name: '广州' },
          { value: 484, name: '深圳' },
          { value: 300, name: '成都' },
        ],
        emphasis: {
          itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: 'rgba(0, 0, 0, 0.5)',
            color: '#fff',
          },
        },
      },
    ],
  },
  height: 500,
  width: 850,
  type: 'pie',
  name: '饼图',
}
  
css3DRendererModules.createEcharts(config);

通过上述步骤,一个在three.js三维场景中动态创建echarts图表的功能就实现啦。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/17916.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】