전체 아키텍처 분석
라이브러리 구조 분석
코어 모듈 관계트리
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 뷰 관리 및claygl와echarts사이에서 브릿지 역할
// 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-gl의registerPostInit()등록
// 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);
};