posts

전체 아키텍처 분석

Oct 1, 2025 updated Oct 1, 2025 3darchitectureawslegacysecurity

라이브러리 구조 분석

코어 모듈 관계트리

ECharts
|-- echarts.init() - 차트 초기화
| |-- ZRender - 2D 렌더링 엔진
|-- SeriesModel - 시리즈 데이터 및 옵션 관리
| |-- List - 데이터 구조
|-- CoordinateSystem - 좌표계 관리
|-- View - 시각화 요소 렌더링


ECharts-GL (확장)
|-- echarts-gl.js - GL 확장 모듈 초기화
| |-- registerPostInit() - ECharts 초기화 후 GL 컴포넌트 설정
|-- ViewGL - 3D 뷰 관리
| |-- Scene - claygl 씬 관리
| |-- Camera - 카메라 관리
| |-- EffectCompositor - 후처리 효과
|-- 3D 시리즈
| |-- Bar3DSeries - 3D 바 차트 모델
| |-- Bar3DView - 3D 바 차트 뷰
| |-- 기타 3D 시리즈...
|-- 3D 좌표계
|-- Grid3DModel - 3D 그리드 모델
|-- Grid3DView - 3D 그리드 뷰
|-- 기타 3D 좌표계...

이차트 코어 아키텍처

1. 초기화 및 렌더링 엔진

  • echarts.init(el): 차트 인스턴스 생성 및 내부 zrender 인스턴스 초기화
  • zrender: 2D 그래픽 렌더링 엔진, canvas 또는 svg 기반으로 작동하며 기본적인 그래픽 요소 드로잉
// HTML에 요소를 생성 
// <div id="chart-container" style="width: 600px; height: 400px;"></div>

// echarts 인스턴스 초기화
var chart = echarts.init(document.getElementById('chart-container'));

// zrender 인스턴스는 내부적으로 생성되며 직접 접근할 필요는 없음
// chart._zr은 내부적으로 생성된 zrender 인스턴스를 참조합니다

2. 모델-뷰 관계

  • globalModel: 전체 차트의 데이터와 옵션을 관리
  // 내부적으로 globalModel은 다음과 같은 역할 수행
  // 모든 컴포넌트와 시리즈의 옵션을 저장하고 관리
  // 테마 적용, 옵션 병합 등을 처리
  • seriesModel: 각 시리즈에 대한 데이터와 옵션을 관리
  // 시리즈 모델 예시 (내부 구현)
  var LineSeries = echarts.SeriesModel.extend({
      type: 'series.line',
      getInitialData: function() {
          // 시리즈의 데이터를 초기화하는 로직
          return createListFromArray(this.getSource(), this);
      }
  });
  • componentModel: 축, 범례 등 컴포넌트의 옵션을 관리
    • 축(Axis): 차트에서 X축, Y축과 같은 데이터의 척도를 표시하는 컴포넌트, 예를 들어, 막대 차트에서 X축은 카테고리를, Y축은 값의 범위를 나타냄
    • 범례(Legend): 차트에 사용된 다양한 시리즈(데이터 세트)를 식별하는 데 도움을 주는 색상 코드와 레이블을 제공하는 컴포넌트
    • 툴팁(Tooltip): 마우스를 데이터 포인트 위에 올렸을 때 자세한 정보를 표시하는 팝업 상자
    • 데이터줌(DataZoom): 사용자가 차트의 일부분을 확대/축소하여 볼 수 있게 해주는 컴포넌트
    • 그리드(Grid): 차트의 레이아웃을 정의하고 여러 차트를 정렬하는데 사용
  // 컴포넌트 모델 예시 (내부 구현)
  var AxisModel = echarts.ComponentModel.extend({
      type: 'xAxis',
      // 축의 기본 옵션 및 상태 관리
  });
  • view: 모델 데이터를 시각적 요소로 변환 및 렌더링 담당
  // 뷰 예시 (내부 구현)
  var LineView = echarts.ChartView.extend({
      type: 'line',
      render: function(seriesModel, ecModel, api) {
          // 시리즈 모델의 데이터를 기반으로 선을 그리는 로직
          var data = seriesModel.getData();
          // zrender 그래픽 요소 생성 및 렌더링
      }
  });

3. 좌표계 시스템

  • coordinateSystem: 데이터 좌표를 화면 좌표로 변환하는 시스템
  chart.setOption({
      // 직교 좌표계(cartesian) 정의
      grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
      },
      xAxis: {
          type: 'category',
          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
          type: 'value'
      },
      series: [{
          type: 'line',
          data: [120, 132, 101, 134, 90, 230, 210]
      }]
  });
  • 주요 좌표계: cartesian2d, polar, geo, calendar
  • cartesian2D: 일반적인 직교 좌표계 (X,Y)
    // 직교 좌표계 예시
    chart.setOption({
        xAxis: { type: 'category', data: ['A', 'B', 'C'] },
        yAxis: { type: 'value' },
        series: [{ type: 'bar', data: [10, 20, 30] }]
    });
  • polar: 극좌표계 (각도, 반지름)
    // 극좌표계 예시
    chart.setOption({
        polar: {},
        angleAxis: { type: 'category', data: ['A', 'B', 'C'] },
        radiusAxis: {},
        series: [{ type: 'bar', coordinateSystem: 'polar', data: [10, 20, 30] }]
    });
  • geo: 지리 좌표계 (위도, 경도)
    // 지리 좌표계 예시
    chart.setOption({
        geo: {
            map: 'china'
        },
        series: [{
            type: 'scatter',
            coordinateSystem: 'geo',
            data: 120.13, 30.26, 100 // [경도, 위도, 값]
        }]
    });
  • calendar: 달력 좌표계 (날짜, 값)
    // 달력 좌표계 예시
    chart.setOption({
        calendar: {
            range: '2022'
        },
        series: [{
            type: 'heatmap',
            coordinateSystem: 'calendar',
            data: [['2022-01-01', 100], ['2022-01-02', 200]]
        }]
    });

4. 데이터 처리 및 시각화 매핑

  • list: 차트 데이터 자료구조
  // 내부적으로 List 객체는 다음과 같이 생성됨
  var list = new echarts.List(['value']);
  list.initData([10, 20, 30]);

  // 데이터 접근 방법
  var value = list.get('value', 1); // 20 반환
  • visualMapping: 데이터 값을 색상, 크기 등 시각적 요소로 매핑
  // visualMap을 사용한 시각화 매핑 예시
  chart.setOption({
      visualMap: {
          min: 0,
          max: 100,
          inRange: {
              // 데이터 값에 따라 색상을 매핑
              color: ['#blue', '#red']
          }
      },
      series: [{
          type: 'scatter',
          data: [[10, 20, 30], [40, 50, 60], [70, 80, 90]],
          symbolSize: function(val) {
              // 데이터 값에 따라 심볼 크기 매핑
              return val[2] / 3;
          }
      }]
  });

5. 이벤트 처리

  // 이벤트 리스너 등록
  chart.on('click', function(params) {
      console.log('차트 요소 클릭:', params.name, params.value);
  });

  // 액션 디스패치
  chart.dispatchAction({
      type: 'highlight',
      seriesIndex: 0,
      dataIndex: 1
  });

이차트 지엘 아키텍처

1. 3D 렌더링 엔진

  • claygl: 3D 그래픽 렌더링을 하기위한 webgl 기반 라이브러리
  // claygl은 일반적으로 직접 사용하지 않고 echarts-gl 내부에서 사용됨
  // 내부 구현 예시
  var scene = new claygl.Scene();
  var camera = new claygl.PerspectiveCamera();
  var renderer = new claygl.Renderer();
  • viewgl: 3D 뷰 관리 및 clayglecharts 사이에서 브릿지 역할
  // ViewGL은 ECharts-GL 내부적으로 사용됨
  // 생성 예시 (내부 구현)
  var viewGL = new ViewGL('perspective');

  // 카메라 설정
  viewGL.setProjection('perspective');
  viewGL.setViewport(0, 0, width, height);

  // 후처리 효과 설정
  viewGL.setPostEffect({
      enable: true,
      bloom: { enable: true }
  });

2. 3D 시리즈 타입

  • bar3D, line3D, surface 등등이 있으며 추가적으로 필요한 모양에 따라 커스텀 할 수 있음
  // 3D 바 차트 예시
  chart.setOption({
      xAxis3D: { type: 'category', data: ['A', 'B', 'C'] },
      yAxis3D: { type: 'category', data: ['X', 'Y', 'Z'] },
      zAxis3D: { type: 'value' },
      grid3D: {},
      series: [{
          type: 'bar3D',
          data: [
              [0, 0, 10], // [x, y, z] 형식
              [1, 1, 20],
              [2, 2, 30]
          ],
          shading: 'realistic'
      }]
  });

  // 3D 표면 차트 예시
  chart.setOption({
      grid3D: {},
      xAxis3D: { type: 'value' },
      yAxis3D: { type: 'value' },
      zAxis3D: { type: 'value' },
      series: [{
          type: 'surface',
          equation: {
              x: { min: -2, max: 2, step: 0.1 },
              y: { min: -2, max: 2, step: 0.1 },
              z: function(x, y) {
                  return Math.sin(x * x + y * y) * x;
              }
          }
      }]
  });

3. 3D 좌표계

  • grid3D: 3D 직교 좌표계
    • 직교 좌표계는 3개의 축이 서로 수직으로 교차
    • X, Y, Z 3개의 축을 사용하여 3차원 공간의 모든 점을 표현
    • 일반적인 3D 바 차트, 산점도 등에 사용
  // grid3D 좌표계 예시
  chart.setOption({
      grid3D: {
          boxWidth: 200,
          boxHeight: 100,
          boxDepth: 80,
          viewControl: {
              // 카메라 위치, 회전 등 설정
              autoRotate: true
          }
      },
      xAxis3D: { type: 'value' },
      yAxis3D: { type: 'value' },
      zAxis3D: { type: 'value' },
      series: [/* 시리즈 데이터 */]
  });
  • globe: 구면 좌표계
    • 면 좌표계는 지구와 같은 구체 표면에 데이터 매핑
    • 위도(latitude), 경도(longitude), 고도(altitude) 값으로 좌표를 표현
    • 지구본 위에 데이터를 표시하는 차트에 사용
  // globe 좌표계 예시
  chart.setOption({
      globe: {
          baseTexture: 'world.topo.bathy.200401.jpg', // 지구 지형 텍스처
          heightTexture: 'bathymetry_bw_composite_4k.jpg', // 고도 텍스처
          displacementScale: 0.1, // 지형 고도 스케일
          environment: 'starfield.jpg', // 배경 환경 맵
          shading: 'realistic', // 셰이딩 모드
          viewControl: {
              autoRotate: true // 자동 회전
          }
      },
      series: [{
          type: 'bar3D',
          coordinateSystem: 'globe', // globe 좌표계 사용
          data: [
              // [경도, 위도, 값] 형식
              [120, 30, 100],
              [90, 40, 80]
          ]
      }]
  });
  • geo3D: 3D 지리 좌표계
    • 3D 지리 좌표계는 지도 데이터를 3D로 표현
    • 일반적으로 GeoJSON 형식의 지리 데이터를 사용하여 지역, 국가 등을 3D로 표현
    • 지역별 데이터 시각화에 유용
  // geo3D 좌표계 예시
  chart.setOption({
      geo3D: {
          map: 'china', // 중국 지도
          itemStyle: {
              // 지역별 스타일 설정
              areaColor: '#003366',
              opacity: 0.8
          },
          regions: [
              { name: 'Beijing', itemStyle: { areaColor: 'red' } }
          ],
          shading: 'lambert',
          light: {
              main: { intensity: 1.2 }
          }
      },
      series: [{
          type: 'bar3D',
          coordinateSystem: 'geo3D', // geo3D 좌표계 사용
          data: [
              { name: 'Beijing', value: 100 },
              { name: 'Shanghai', value: 120 }
          ]
      }]
  });

4. 3D 특수 효과

  • postEffect: 후처리 효과 (블룸, DOF, SSAO)
    • 블룸(Bloom): 밝은 부분에서 빛이 번지는 효과
    • 피사계 심도(DOF, Depth of Field): 카메라 초점과 가까운 물체는 선명하게, 멀리 있는 물체는 흐릿하게 표현
    • SSAO(Screen Space Ambient Occlusion): 주변 환경광 차폐 효과로, 오목한 부분에 그림자를 만들어 입체감 표현
    • SSR(Screen Space Reflection): 화면 공간 반사로, 물체의 표면에 주변 환경이 반사되는 효과
  // 후처리 효과 설정 예시
  chart.setOption({
      grid3D: {},
      xAxis3D: { type: 'value' },
      yAxis3D: { type: 'value' },
      zAxis3D: { type: 'value' },
      postEffect: {
          enable: true, // 후처리 효과 활성화
          bloom: {
              enable: true, // 블룸 효과 활성화
              intensity: 0.3 // 블룸 강도
          },
          DOF: {
              enable: true, // 피사계 심도 활성화
              focalDistance: 50, // 초점 거리
              focalRange: 20 // 초점 범위
          },
          SSAO: {
              enable: true, // SSAO 활성화
              radius: 2, // 반경
              intensity: 1.5 // 강도
          }
      },
      series: [/* 시리즈 데이터 */]
  });
  • temporalSuperSampling: 시간적 슈퍼샘플링, 안티앨리어싱
    • 시간적 슈퍼샘플링은 여러 프레임에 걸쳐 약간씩 다른 위치에서 샘플링하고 결과를 누적하여 안티앨리어싱 효과를 생성하는 기법
    • 이 기법은 계단현상(jaggies)을 줄이고 더 부드러운 이미지를 제공
    • 특히 고해상도 렌더링이 필요하거나 작은 디테일이 많은 3D 장면에서 유용
  // 시간적 슈퍼샘플링 설정 예시
  chart.setOption({
      grid3D: {},
      temporalSuperSampling: {
          enable: true, // 시간적 슈퍼샘플링 활성화
          frameCount: 8 // 누적할 프레임 수
      },
      series: [/* 시리즈 데이터 */]
  });

    // ViewGL.js에서 시간적 슈퍼샘플링 처리 예시
    if (this.needsTemporalSS()) {
      // 프로젝션 행렬에 지터링 적용
      this._temporalSS.jitterProjection(renderer, camera);

      // 프레임 렌더링
      var frameBuffer = this._temporalSS.getSourceFrameBuffer();
      frameBuffer.bind(renderer);
      // 렌더링 로직
      frameBuffer.unbind(renderer);

      // 누적된 프레임 합성
      renderer.setViewport(this.viewport);
      this._temporalSS.render(renderer);
    }

5. 3D 쉐이더 개념

  • vertex shader: 3D 모델의 각 정점의 위치, 색상, 텍스쳐 좌표 등을 표현
// 버텍스 세이더 예시
attribute vec3 position;
attribute vec2 texcoord;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
varying vec2 v_texcoord;

void main() {
v_texcoord = texcoord;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
  • fragment shader: 화면의 각 픽셀의 최종 색상을 결정
// 프래그먼트 세이더 예시
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D texture;
uniform vec3 ambientLight;

void main() {
    vec4 color = texture2D(texture, v_texcoord);
    gl_FragColor = vec4(color.rgb * ambientLight, color.a);
}

6. 텍스쳐와 버퍼 바인딩

  • 텍스쳐 바인딩: 이미지 데이터를 GPU에 로드하고 세이더에서 사용할 수 있도록 함
// 텍스처 바인딩 예시
gl.bindTexture(gl.TEXTURE_2D, textureObject);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  • 버퍼 바인딩: 정점 데이터, 인덱스 등을 GPU 메모리에 로드
// 버텍스 버퍼 바인딩 예시
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  • WebGL Draw Calls: GPU에게 실제로 드로잉 작업 지시 명령어
// 드로우 콜 예시
gl.drawArrays(gl.TRIANGLES, 0, vertexCount); // 모든 정점 그리기
// 또는
gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0); // 인덱스 기반 그리기
  • 배치 처리(Batching): 유사한 객체를 하나의 드로우 콜로 그룹화
  • 인스턴싱(Instancing): 동일한 지오메트리를 여러 번 그릴 때 사용
// 인스턴싱 예시
gl.drawArraysInstanced(gl.TRIANGLES, 0, vertexCount, instanceCount);

7. 메시 및 지오메트리

  • 지오메트리: 3D 객체의 형태를 정의하는 정점, 면, 법선 등의 데이터
// 지오메트리 생성 예시
var geometry = new BarsGeometry();  // 3D 바 차트용 특수 지오메트리
geometry.addBar(
    [0, 0, 0],    // 시작 위치
    [0, 1, 0],    // 방향 벡터
    'y',          // 방향 지정자
    [1, 5, 1],    // 크기
    [1, 0, 0, 1]  // 색상 (RGBA)
);

// 정점 데이터 업데이트 표시
geometry.dirty();
geometry.updateBoundingBox();
  • 메쉬: 지오메트리와 재질을 결합하여 렌더링 가능한 객체를 생성
// 메시 생성
var barMesh = new graphicGL.Mesh({
  geometry: geometry,               // 지오메트리
  material: material,               // 재질
  culling: true,                    // 후면 제거
  renderOrder: 10,                  // 렌더링 순서
  renderNormal: true                // 일반 렌더링 패스에 포함
});

// 메시 그룹에 추가
groupGL.add(barMesh);

8. 프레임버퍼(Framebuffer)

  • 프레임버퍼는 렌더링 결과를 저장하는 메모리 공간으로, 후처리 효과에 사용
// 프레임버퍼 생성 및 바인딩 예시
var frameBuffer = new claygl.FrameBuffer();
frameBuffer.attach(new claygl.Texture2D({
    width: width,
    height: height
}));
frameBuffer.bind(renderer);
// 렌더링 로직
frameBuffer.unbind(renderer);

코어 클래스 및 인터페이스

1. [echarts] 클래스

  • echarts: 차트 인스턴스, 렌더링 및 상호작용 관리
  • seriesModel: 시리즈 데이터 및 옵션 관리
  • componentModel: 컴포넌트 옵션 관리
  • chartView: 차트 렌더링 담당
  • optionManager: 사용자 옵션 및 테마 관리

2. [echarts-gl] 확장 클래스

  • viewGL: 3D 뷰 관리
    • 간단한 샘플 코드라도 필요함, 뷰를 관리한다는게 무엇인지 설명 필요함
  • Scene: 3D 씬 그래프 관리
// 씬 그래프 구성 예시
var scene = new claygl.Scene();      // 씬(장면) 노드
var rootNode = new claygl.Node();    // 루트 노드
scene.add(rootNode);                 // 루트 노드를 씬에 추가

// 자식 노드들 추가
var childNode1 = new claygl.Node();
var childNode2 = new claygl.Node();
rootNode.add(childNode1);
rootNode.add(childNode2);

// 메시 추가
var mesh = new claygl.Mesh({
    geometry: geometry,
    material: material
});
childNode1.add(mesh);
  • perspectiveCamera: 원근감이 있는 일반적인 뷰 / OrthographicCamera: 원근감 없는 2D 같은 뷰(CAD, 등축 뷰에 유용) -> 3D 카메라 관리
// 투영 카메라 생성 및 설정
var camera = new claygl.PerspectiveCamera();
camera.aspect = width / height;  // 종횡비
camera.fov = 60;                 // 시야각(Field of View)
camera.near = 0.1;                // 가까운 클리핑 평면
camera.far = 1000;               // 먼 클리핑 평면

// 카메라 위치 설정
camera.position.set(0, 0, 10);   // 카메라 위치
camera.lookAt(0, 0, 0);          // 카메라가 바라보는 방향

// 직교 카메라
var orthoCamera = new claygl.OrthographicCamera();
orthoCamera.left = -width / 2;
orthoCamera.right = width / 2;
orthoCamera.top = height / 2;
orthoCamera.bottom = -height / 2;
  • effectCompositor: 후처리 효과 관리 :: 최종 렌더링 장면에 적용됨
// EffectCompositor 설정
var compositor = new EffectCompositor();
compositor.resize(width, height);

// 블룸 효과 활성화 및 설정
compositor.enableBloom();
compositor.setBloomIntensity(0.5);

// DOF(피사계 심도) 효과 활성화 및 설정
compositor.enableDOF();
compositor.setDOFParameter('focalDistance', 50);
compositor.setDOFParameter('focalRange', 30);
compositor.setDOFParameter('blurRadius', 10);

// SSAO 효과 활성화 및 설정
compositor.enableSSAO();
compositor.setSSAOParameter('radius', 2);
compositor.setSSAOParameter('intensity', 1.5);

// 렌더링 후 후처리 효과 적용
var sourceFrameBuffer = compositor.getSourceFrameBuffer();
sourceFrameBuffer.bind(renderer);
renderer.render(scene, camera);
sourceFrameBuffer.unbind(renderer);
compositor.composite(renderer, null, 0);
  • 3DSreies: 차트 데이터 및 옵션 관리
    • 간단한 샘플 코드라도 필요함

렌더링 파이프라인 분석

프로세스

1. 초기화

   var chart = echarts.init(document.getElementById('main'));
  • 돔 요소를 기반으로 차트 인스턴스 생성
   // echarts.js 내부 구현
   function init(dom, theme, opts) {
       var chart = new ECharts(dom);
       chart._theme = theme;
       chart._opts = opts || {};
       return chart;
   }
  • zrender 인스턴스 초기화
   // echarts.js 내부 구현
   ECharts.prototype._initZr = function() {
       this._zr = zrender.init(this.dom, {
           renderer: this._opts.renderer || 'canvas',
           devicePixelRatio: this._opts.devicePixelRatio,
           width: this._width,
           height: this._height
       });
   };
  • echarts-glregisterPostInit() 등록
   // echarts-gl.js 내부 구현
   var glUtil = {};
   // echarts 초기화 후 GL 컴포넌트 설정
   echarts.registerPostInit(function(chart) {
       var zr = chart.getZr();
       glUtil.attachToChart(zr);
   });
  • ViewGl, LayerGL, ZRTextureAtlasSurface 생성
   // 내부 구현
   function attachToChart(zr) {
       var painter = zr.painter;
       if (painter.getViewGL) {
           return;
       }

       // 레이어 생성
       var viewGL = new ViewGL();
       var layerGL = new LayerGL();

       // zrender에 GL 레이어 추가
       painter.getViewGL = function() {
           return viewGL;
       };

       // 텍스처 아틀라스 생성
       var textureAtlas = new ZRTextureAtlasSurface();
       painter.getTextureAtlas = function() {
           return textureAtlas;
       };
   }
  • OrbitControl 설정
    // OrbitControl 생성 및 설정
    var control = new OrbitControl({
      zr: componentModel.ecModel.getZr(),
      viewGL: viewGL
    });

    // 뷰 제어 옵션에서 설정 불러오기
    control.setFromViewControlModel(viewControlModel);

    // 카메라 자동 회전, 줌 제한 등 설정
    control.autoRotate = viewControlModel.get('autoRotate');
    control.minDistance = viewControlModel.get('minDistance');
    control.maxDistance = viewControlModel.get('maxDistance');

    return control;

2. 옵션 설정

   chart.setOption({...})
  • OptionManager.setOption(): 사용자 옵션과 기본 옵션 병합
   // 내부 구현
   OptionManager.prototype.setOption = function(option, optionPreprocessorFuncs) {
       // 옵션 전처리
       option = this._preprocessOption(option, optionPreprocessorFuncs);

       // 테마 적용
       this._applyTheme(option);

       // 기존 옵션과 병합
       if (this._optionBackup) {
           mergeOption(this._optionBackup, option);
       }
       else {
           this._optionBackup = clone(option);
       }

       return this._optionBackup;
   };
  • GlobalModal 생성 및 업데이트
   // 내부 구현
   function updateModel(ecModel, newOption) {
       // 컴포넌트 모델 생성
       ecModel.mergeOption(newOption);

       // 좌표계 생성 및 업데이트
       ecModel.updateCoordinateSystems();

       // 시리즈 모델 생성
       ecModel.initSeriesModels();
   }
  • 컴포넌트 및 시리즈 모델 생성 후 인스턴스 생성
   // 3D 시리즈 모델 생성 예시
   var Bar3DSeries = echarts.SeriesModel.extend({
       type: 'series.bar3D',
       defaultOption: {
           // 기본 옵션
       },
       getInitialData: function() {
           // 데이터 초기화 로직
           var data = this.getData();
           // ... 데이터 처리 로직
           return data;
       }
   });

   // 모델 등록
   echarts.registerSeriesModel(Bar3DSeries);

   // 뷰 등록
   echarts.registerChartView(Bar3DView);
  • 시리즈 데이터 초기화
   // 데이터 초기화 로직 (내부 구현)
   SeriesModel.prototype.getData = function() {
       // 원시 데이터를 List 인스턴스로 변환
       var data = this._data;
       if (!data) {
           data = this._data = this.getInitialData();
           // 데이터 관련 이벤트 등록
           data.wrapMethod('getItemModel', function(model, idx) {
               // ...
           });
       }
       return data;
   };

3. 좌표계 초기화

  • 옵션에 따라 좌표계 초기화
  • 3D 좌표계 설정
  • 카메라 위치, 투영 방식 설정
// 'grid3D' 좌표계 파일: Grid3DModel.js
var Grid3DModel = ComponentModel.extend({
    type: 'grid3D',
    defaultOption: {
        // 기본 옵션 설정
    },

    // 좌표계 초기화 메서드
    init: function() {
        ComponentModel.prototype.init.apply(this, arguments);

        // 좌표계 객체 생성
        this._grid3DModel = new Cartesian3D();
    },

    // 좌표계 인스턴스 반환 메서드
    getCoordinateSystem: function() {
        return this._grid3DModel;
    }
});

// 카메라 및 시점 설정: Grid3DView.js
Grid3DView.prototype.render = function(grid3DModel, ecModel, api) {
    // grid3D 모델에서 ViewGL 인스턴스 가져오기
    var viewGL = grid3DModel.viewGL;

    // 카메라 설정
    if (grid3DModel.get('viewControl.projection') === 'orthographic') {
        viewGL.setProjection('orthographic');
    } else {
        viewGL.setProjection('perspective');
    }

    // 축 및 그리드라인 설정
    // ...

    // 카메라 위치 및 시점 업데이트
    var viewControl = viewGL.viewControl;
    viewControl.setDistance(grid3DModel.get('viewControl.distance'));
    viewControl.setCenter(grid3DModel.get('viewControl.center'));
    viewControl.setAlpha(grid3DModel.get('viewControl.alpha'));
    viewControl.setBeta(grid3DModel.get('viewControl.beta'));
};

4. 뷰 초기화

  • 모델에 대응하는 뷰 객체 생성
  • groupGL, LabelsBuilder 설정
  • 메시 및 지오메트리 생성 준비
  • 각 차트 타입 별 View 클래스
// Bar3DView.js
Bar3DView.prototype.init = function(ecModel, api) {
    // GL 노드 그룹 생성
    this.groupGL = new graphicGL.Node();

    // API 참조 저장
    this._api = api;

    // 라벨 빌더 초기화
    this._labelsBuilder = new LabelsBuilder(256, 256, api);

    // 라벨 위치 계산 함수 설정
    this._labelsBuilder.getLabelPosition = function(dataIndex, position, distance) {
        // 라벨 위치 계산 로직
    };

    // 라벨 메시 렌더링 순서 설정
    this._labelsBuilder.getMesh().renderOrder = 100;
};

// 메시 및 지오메트리 생성
var geometry = new BarsGeometry();
this._barMesh = new graphicGL.Mesh({
    geometry: geometry,
    material: material,
    // 기타 설정
});

// GL 그룹에 메시 추가
this.groupGL.add(this._barMesh);

5. 렌더링

  • view.render() 호출
    • 이전 메시와 교체 또는 새 메시 생성
    • GeoMetry, graphicGL.mesh 생성 및 추가
    • geometry.add(): 도형 생성
    • geometry.dirty(): GPU 업로드 표시
// Bar3DView.js
Bar3DView.prototype.render = function(seriesModel, ecModel, api) {
    // 이전 메시와 현재 메시 교체
    var tmp = this._prevBarMesh;
    this._prevBarMesh = this._barMesh;
    this._barMesh = tmp;

    // 메시가 없으면 새로 생성
    if (!this._barMesh) {
        // 메시 생성 로직
    }

    // GL 그룹에서 이전 메시 제거 및 현재 메시 추가
    this.groupGL.remove(this._prevBarMesh);
    this.groupGL.add(this._barMesh);
    this.groupGL.add(this._labelsBuilder.getMesh());

    // 실제 렌더링 처리
    this._doRender(seriesModel, api);

    // 좌표계에 그룹 추가 및 SRGB 디코딩 설정
    if (coordSys && coordSys.viewGL) {
        coordSys.viewGL.add(this.groupGL);

        // 선형 공간에 따라 SRGB 디코딩 활성화 여부 설정
        var methodName = coordSys.viewGL.isLinearSpace() ? 'define' : 'undefine';
        this._barMesh.material[methodName]('fragment', 'SRGB_DECODE');
    }

    // 데이터 저장 및 라벨 업데이트
    this._data = seriesModel.getData();
    this._labelsBuilder.updateData(this._data);
    this._labelsBuilder.updateLabels();

    // 애니메이션 업데이트
    this._updateAnimation(seriesModel);
};

// 실제 바 렌더링 로직
Bar3DView.prototype._doRender = function(seriesModel, api) {
    var data = seriesModel.getData();
    var shading = seriesModel.get('shading');
    var barMesh = this._barMesh;

    // 재질 설정
    if (!barMesh.material || barMesh.material.shader.name !== shadingPrefix) {
        barMesh.material = graphicGL.createMaterial(shadingPrefix, ['VERTEX_COLOR']);
    }

    // 메시 지오메트리 설정
    barMesh.geometry.enableNormal = enableNormal;
    barMesh.geometry.resetOffset();

    // 베벨(모서리 둥글기) 설정
    var bevelSize = seriesModel.get('bevelSize');
    var bevelSegments = seriesModel.get('bevelSmoothness');
    barMesh.geometry.bevelSegments = bevelSegments;
    barMesh.geometry.bevelSize = bevelSize;

    // 색상 버퍼 준비
    var colorArr = [];
    var vertexColors = new Float32Array(data.count() * 4);
    var colorOffset = 0;
    var barCount = 0;
    var hasTransparent = false;

    // 데이터 순회하여 색상 정보 설정
    data.each(function(idx) {
        if (!data.hasValue(idx)) {
            return;
        }
        var color = getItemVisualColor(data, idx);
        var opacity = getItemVisualOpacity(data, idx);
        // ... 색상 처리 로직
    });

    // 바 개수 설정 및 바 추가
    barMesh.geometry.setBarCount(barCount);

    // 데이터 순회하여 바 추가
    data.each(function(idx) {
        if (!data.hasValue(idx)) {
            barIndexOfData[idx] = -1;
            return;
        }
        var layout = data.getItemLayout(idx);
        var start = layout[0];
        var dir = layout[1];
        var size = layout[2];

        // 바 추가
        if (colorArr[3] > 0) {
            self._barMesh.geometry.addBar(start, dir, orient, size, colorArr, idx);
            barIndexOfData[idx] = barCount++;
        }
    });

    // 지오메트리 갱신
    barMesh.geometry.dirty();
    barMesh.geometry.updateBoundingBox();

    // 투명도 처리
    var material = barMesh.material;
    material.transparent = hasTransparent;
    material.depthMask = !hasTransparent;
    barMesh.geometry.sortTriangles = hasTransparent;

    // 이벤트 핸들러 초기화
    this._initHandler(seriesModel, api);
};
  • viewGL.render() 호출
    • claygl의 렌더링 파이프라인
    • 세이더 프로그램, 텍스처, 버퍼 바인딩
    • webgl 드로우콜 실행
// ViewGL.js
ViewGL.prototype.render = function(renderer, accumulating) {
    this._doRender(renderer, accumulating, this._frame);
    this._frame++;
};

ViewGL.prototype._doRender = function(renderer, accumulating, accumFrame) {
    var scene = this.scene;
    var camera = this.camera;

    // 투명 객체 업데이트
    this._updateTransparent(renderer, scene, camera, accumFrame);

    // 그림자 맵 렌더링
    if (!accumulating) {
        this._shadowMapPass.render(renderer, scene, camera, true);
    }

    // 그림자 PCF 커널 업데이트
    this._updateShadowPCFKernel(accumFrame);

    // 배경색 설정
    var bgColor = renderer.clearColor;
    renderer.gl.clearColor(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);

    // 후처리 효과 활성화 시 처리
    if (this._enablePostEffect) {
        // 노멀 맵 및 SSAO 업데이트
        this._compositor.updateNormal(renderer, scene, camera, this._temporalSS.getFrame());
        this._updateSSAO(renderer, scene, camera, this._temporalSS.getFrame());

        // 소스 프레임버퍼에 렌더링
        var frameBuffer = this._compositor.getSourceFrameBuffer();
        frameBuffer.bind(renderer);
        renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT);
        renderer.render(scene, camera, true, true);
        frameBuffer.unbind(renderer);

        // 시간적 슈퍼샘플링 처리
        if (this.needsTemporalSS() && accumulating) {
            this._compositor.composite(renderer, scene, camera, this._temporalSS.getSourceFrameBuffer(), this._temporalSS.getFrame());
            renderer.setViewport(this.viewport);
            this._temporalSS.render(renderer);
        }
        else {
            renderer.setViewport(this.viewport);
            this._compositor.composite(renderer, scene, camera, null, 0);
        }
    }
    // 후처리 없이 직접 렌더링
    else {
        if (this.needsTemporalSS() && accumulating) {
            // 시간적 슈퍼샘플링 처리
            var frameBuffer = this._temporalSS.getSourceFrameBuffer();
            frameBuffer.bind(renderer);
            renderer.saveClear();
            renderer.clearBit = renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT;
            renderer.render(scene, camera, true, true);
            renderer.restoreClear();
            frameBuffer.unbind(renderer);

            renderer.setViewport(this.viewport);
            this._temporalSS.render(renderer);
        }
        else {
            renderer.setViewport(this.viewport);
            renderer.render(scene, camera, true, true);
        }
    }
};
  • ZRender.render() 호출
    • GL 레이어 이후 2D 캔버스 레이어 렌더링

6. 후처리 및 특수 효과

  • 활성화된 경우 후처리 효과 적용
  • 안티앨리어싱, 블룸, DOF
// EffectCompositor.js
EffectCompositor.prototype.composite = function(renderer, scene, camera, sourceTexture, frame) {
    var blurRadius = this._blurRadius;
    var frameBuffer = this._framebuffer;

    // 소스 텍스처가 있으면 사용, 없으면 기본 소스 텍스처 사용
    sourceTexture = sourceTexture || this._sourceTexture;

    // 법선 텍스처 복원(Deferred Shading)
    if (this._enableSSR) {
        this._normalPass.render(renderer, frameBuffer);
    }

    // SSAO 계산
    if (this._enableSSAO) {
        this._ssaoPass.render(renderer, frameBuffer, sourceTexture, frame);
    }

    // 블룸 효과 적용
    if (this._enableBloom) {
        // 밝은 부분 추출
        this._bloomPass.render(renderer, sourceTexture);
    }

    // 최종 합성
    var compositePass = this._compositePass;
    compositePass.material.define('fragment', 'BLOOM_ENABLED', this._enableBloom);
    compositePass.material.define('fragment', 'SSAO_ENABLED', this._enableSSAO);
    compositePass.material.define('fragment', 'SSR_ENABLED', this._enableSSR);
    compositePass.material.define('fragment', 'DOF_ENABLED', this._enableDOF);
    compositePass.material.define('fragment', 'FXAA_ENABLED', this._enableFXAA);

    // 각 효과에 필요한 텍스처 설정
    if (this._enableBloom) {
        compositePass.material.setUniform('bloomTexture', this._bloomPass.getBloomTexture());
    }
    if (this._enableSSAO) {
        compositePass.material.setUniform('ssaoTexture', this._ssaoPass.getSSAOTexture());
    }
    if (this._enableDOF) {
        // DOF 텍스처 설정
    }

    // 최종 화면에 렌더링
    compositePass.render(renderer);
};

7. 이벤트 처리

  • 사용자 상호작용 감지 및 처리
  • 레이캐스팅 3D 객체 선택
Bar3DView.prototype._initHandler = function(seriesModel, api) {
    var data = seriesModel.getData();
    var barMesh = this._barMesh;
    var isCartesian3D = seriesModel.coordinateSystem.type === 'cartesian3D';

    var lastDataIndex = -1;
    // 이벤트 핸들러 초기화
    barMesh.off('mousemove');
    barMesh.off('mouseout');

    // 마우스 이동 이벤트 처리
    barMesh.on('mousemove', function(e) {
        // 레이캐스팅으로 선택된 객체의 데이터 인덱스 가져오기
        var dataIndex = barMesh.geometry.getDataIndexOfVertex(e.triangle[0]);
        if (dataIndex !== lastDataIndex) {
            // 이전 항목 비활성화, 새 항목 활성화
            this._downplay(lastDataIndex);
            this._highlight(dataIndex);
            this._labelsBuilder.updateLabels([dataIndex]);

            // 축 포인터 표시
            if (isCartesian3D) {
                api.dispatchAction({
                    type: 'grid3DShowAxisPointer',
                    value: [
                        data.get('x', dataIndex),
                        data.get('y', dataIndex),
                        data.get('z', dataIndex, true)
                    ]
                });
            }
        }
        lastDataIndex = dataIndex;
        barMesh.dataIndex = dataIndex;
    }, this);

    // 마우스 아웃 이벤트 처리
    barMesh.on('mouseout', function(e) {
        this._downplay(lastDataIndex);
        this._labelsBuilder.updateLabels();
        lastDataIndex = -1;
        barMesh.dataIndex = -1;

        // 축 포인터 숨기기
        if (isCartesian3D) {
            api.dispatchAction({
                type: 'grid3DHideAxisPointer'
            });
        }
    }, this);
};