Bar3DView.js 10.4 KB
import * as echarts from 'echarts/lib/echarts';
import graphicGL from '../../util/graphicGL';
import retrieve from '../../util/retrieve';
import format from '../../util/format';
import BarsGeometry from '../../util/geometry/Bars3DGeometry';
import LabelsBuilder from '../../component/common/LabelsBuilder';
import glmatrix from 'claygl/src/dep/glmatrix';
import {getItemVisualColor, getItemVisualOpacity} from '../../util/visual';

var vec3 = glmatrix.vec3;

export default echarts.ChartView.extend({

    type: 'bar3D',

    __ecgl__: true,

    init: function (ecModel, api) {

        this.groupGL = new graphicGL.Node();

        this._api = api;

        this._labelsBuilder = new LabelsBuilder(256, 256, api);
        var self = this;
        this._labelsBuilder.getLabelPosition = function (dataIndex, position, distance) {
            if (self._data) {
                var layout = self._data.getItemLayout(dataIndex);
                var start = layout[0];
                var dir = layout[1];
                var height = layout[2][1];
                return vec3.scaleAndAdd([], start, dir, distance + height);
            }
            else {
                return [0, 0];
            }
        };

        // Give a large render order.
        this._labelsBuilder.getMesh().renderOrder = 100;
    },

    render: function (seriesModel, ecModel, api) {

        // Swap barMesh
        var tmp = this._prevBarMesh;
        this._prevBarMesh = this._barMesh;
        this._barMesh = tmp;

        if (!this._barMesh) {
            this._barMesh = new graphicGL.Mesh({
                geometry: new BarsGeometry(),
                shadowDepthMaterial: new graphicGL.Material({
                    shader: new graphicGL.Shader(
                        graphicGL.Shader.source('ecgl.sm.depth.vertex'),
                        graphicGL.Shader.source('ecgl.sm.depth.fragment')
                    )
                }),
                // Only cartesian3D enable culling
                // FIXME Performance
                culling: seriesModel.coordinateSystem.type === 'cartesian3D',
                // Render after axes
                renderOrder: 10,
                // Render normal in normal pass
                renderNormal: true
            });
        }

        this.groupGL.remove(this._prevBarMesh);
        this.groupGL.add(this._barMesh);
        this.groupGL.add(this._labelsBuilder.getMesh());

        var coordSys = seriesModel.coordinateSystem;
        this._doRender(seriesModel, api);
        if (coordSys && coordSys.viewGL) {
            coordSys.viewGL.add(this.groupGL);

            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);
    },

    _updateAnimation: function (seriesModel) {
        graphicGL.updateVertexAnimation(
            [['prevPosition', 'position'],
            ['prevNormal', 'normal']],
            this._prevBarMesh,
            this._barMesh,
            seriesModel
        );
    },

    _doRender: function (seriesModel, api) {
        var data = seriesModel.getData();
        var shading = seriesModel.get('shading');
        var enableNormal = shading !== 'color';
        var self = this;
        var barMesh = this._barMesh;

        var shadingPrefix = 'ecgl.' + shading;
        if (!barMesh.material || barMesh.material.shader.name !== shadingPrefix) {
            barMesh.material = graphicGL.createMaterial(shadingPrefix, ['VERTEX_COLOR']);
        }

        graphicGL.setMaterialFromModel(
            shading, barMesh.material, seriesModel, api
        );

        barMesh.geometry.enableNormal = enableNormal;

        barMesh.geometry.resetOffset();

        // Bevel settings
        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);
            if (opacity == null) {
                opacity = 1;
            }

            graphicGL.parseColor(color, colorArr);
            colorArr[3] *= opacity;
            vertexColors[colorOffset++] = colorArr[0];
            vertexColors[colorOffset++] = colorArr[1];
            vertexColors[colorOffset++] = colorArr[2];
            vertexColors[colorOffset++] = colorArr[3];

            if (colorArr[3] > 0) {
                barCount++;
                if (colorArr[3] < 0.99) {
                    hasTransparent = true;
                }
            }
        });

        barMesh.geometry.setBarCount(barCount);

        var orient = data.getLayout('orient');

        // Map of dataIndex and barIndex.
        var barIndexOfData = this._barIndexOfData = new Int32Array(data.count());
        var barCount = 0;
        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];

            var idx4 = idx * 4;
            colorArr[0] = vertexColors[idx4++];
            colorArr[1] = vertexColors[idx4++];
            colorArr[2] = vertexColors[idx4++];
            colorArr[3] = vertexColors[idx4++];
            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);
    },

    _initHandler: function (seriesModel, api) {
        var data = seriesModel.getData();
        var barMesh = this._barMesh;
        var isCartesian3D = seriesModel.coordinateSystem.type === 'cartesian3D';

        barMesh.seriesIndex = seriesModel.seriesIndex;

        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);
    },

    _highlight: function (dataIndex) {
        var data = this._data;
        if (!data) {
            return;
        }
        var barIndex = this._barIndexOfData[dataIndex];
        if (barIndex < 0) {
            return;
        }

        var itemModel = data.getItemModel(dataIndex);
        var emphasisItemStyleModel = itemModel.getModel('emphasis.itemStyle');
        var emphasisColor = emphasisItemStyleModel.get('color');
        var emphasisOpacity = emphasisItemStyleModel.get('opacity');
        if (emphasisColor == null) {
            var color = getItemVisualColor(data, dataIndex);
            emphasisColor = echarts.color.lift(color, -0.4);
        }
        if (emphasisOpacity == null) {
            emphasisOpacity = getItemVisualOpacity(data, dataIndex);
        }
        var colorArr = graphicGL.parseColor(emphasisColor);
        colorArr[3] *= emphasisOpacity;

        this._barMesh.geometry.setColor(barIndex, colorArr);

        this._api.getZr().refresh();
    },

    _downplay: function (dataIndex) {
        var data = this._data;
        if (!data) {
            return;
        }
        var barIndex = this._barIndexOfData[dataIndex];
        if (barIndex < 0) {
            return;
        }

        var color = getItemVisualColor(data, dataIndex);
        var opacity = getItemVisualOpacity(data, dataIndex);

        var colorArr = graphicGL.parseColor(color);
        colorArr[3] *= opacity;

        this._barMesh.geometry.setColor(barIndex, colorArr);

        this._api.getZr().refresh();
    },

    highlight: function (seriesModel, ecModel, api, payload) {
        this._toggleStatus('highlight', seriesModel, ecModel, api, payload);
    },

    downplay: function (seriesModel, ecModel, api, payload) {
        this._toggleStatus('downplay', seriesModel, ecModel, api, payload);
    },

    _toggleStatus: function (status, seriesModel, ecModel, api, payload) {
        var data = seriesModel.getData();
        var dataIndex = retrieve.queryDataIndex(data, payload);

        var self = this;
        if (dataIndex != null) {
            echarts.util.each(format.normalizeToArray(dataIndex), function (dataIdx) {
                status === 'highlight' ? this._highlight(dataIdx) : this._downplay(dataIdx);
            }, this);
        }
        else {
            data.each(function (dataIdx) {
                status === 'highlight' ? self._highlight(dataIdx) : self._downplay(dataIdx);
            });
        }
    },

    remove: function () {
        this.groupGL.removeAll();
    },

    dispose: function () {
        this._labelsBuilder.dispose();
        this.groupGL.removeAll();
    }
});