All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.traces.scatter3d.convert.js Maven / Gradle / Ivy

The newest version!
'use strict';

var createLinePlot = require('../../../stackgl_modules').gl_line3d;
var createScatterPlot = require('../../../stackgl_modules').gl_scatter3d;
var createErrorBars = require('../../../stackgl_modules').gl_error3d;
var createMesh = require('../../../stackgl_modules').gl_mesh3d;
var triangulate = require('../../../stackgl_modules').delaunay_triangulate;

var Lib = require('../../lib');
var str2RgbaArray = require('../../lib/str2rgbarray');
var formatColor = require('../../lib/gl_format_color').formatColor;
var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
var DASH_PATTERNS = require('../../constants/gl3d_dashes');
var MARKER_SYMBOLS = require('../../constants/gl3d_markers');

var Axes = require('../../plots/cartesian/axes');
var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;

var calculateError = require('./calc_errors');

function LineWithMarkers(scene, uid) {
    this.scene = scene;
    this.uid = uid;
    this.linePlot = null;
    this.scatterPlot = null;
    this.errorBars = null;
    this.textMarkers = null;
    this.delaunayMesh = null;
    this.color = null;
    this.mode = '';
    this.dataPoints = [];
    this.axesBounds = [
        [-Infinity, -Infinity, -Infinity],
        [Infinity, Infinity, Infinity]
    ];
    this.textLabels = null;
    this.data = null;
}

var proto = LineWithMarkers.prototype;

proto.handlePick = function(selection) {
    if(selection.object &&
        (selection.object === this.linePlot ||
         selection.object === this.delaunayMesh ||
         selection.object === this.textMarkers ||
         selection.object === this.scatterPlot)
    ) {
        var ind = selection.index = selection.data.index;

        if(selection.object.highlight) {
            selection.object.highlight(null);
        }
        if(this.scatterPlot) {
            selection.object = this.scatterPlot;
            this.scatterPlot.highlight(selection.data);
        }

        selection.textLabel = '';
        if(this.textLabels) {
            if(Lib.isArrayOrTypedArray(this.textLabels)) {
                if(this.textLabels[ind] || this.textLabels[ind] === 0) {
                    selection.textLabel = this.textLabels[ind];
                }
            } else {
                selection.textLabel = this.textLabels;
            }
        }

        selection.traceCoordinate = [
            this.data.x[ind],
            this.data.y[ind],
            this.data.z[ind]
        ];

        return true;
    }
};

function constructDelaunay(points, color, axis) {
    var u = (axis + 1) % 3;
    var v = (axis + 2) % 3;
    var filteredPoints = [];
    var filteredIds = [];
    var i;

    for(i = 0; i < points.length; ++i) {
        var p = points[i];
        if(isNaN(p[u]) || !isFinite(p[u]) ||
           isNaN(p[v]) || !isFinite(p[v])) {
            continue;
        }
        filteredPoints.push([p[u], p[v]]);
        filteredIds.push(i);
    }
    var cells = triangulate(filteredPoints);
    for(i = 0; i < cells.length; ++i) {
        var c = cells[i];
        for(var j = 0; j < c.length; ++j) {
            c[j] = filteredIds[c[j]];
        }
    }
    return {
        positions: points,
        cells: cells,
        meshColor: color
    };
}

function calculateErrorParams(errors) {
    var capSize = [0.0, 0.0, 0.0];
    var color = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
    var lineWidth = [1.0, 1.0, 1.0];

    for(var i = 0; i < 3; i++) {
        var e = errors[i];

        if(e && e.copy_zstyle !== false && errors[2].visible !== false) e = errors[2];
        if(!e || !e.visible) continue;

        capSize[i] = e.width / 2;  // ballpark rescaling
        color[i] = str2RgbaArray(e.color);
        lineWidth[i] = e.thickness;
    }

    return {capSize: capSize, color: color, lineWidth: lineWidth};
}

function parseAlignmentX(a) {
    if(a === null || a === undefined) return 0;

    return (a.indexOf('left') > -1) ? -1 :
           (a.indexOf('right') > -1) ? 1 : 0;
}

function parseAlignmentY(a) {
    if(a === null || a === undefined) return 0;

    return (a.indexOf('top') > -1) ? -1 :
           (a.indexOf('bottom') > -1) ? 1 : 0;
}

function calculateTextOffset(tp) {
    // Read out text properties

    var defaultAlignmentX = 0;
    var defaultAlignmentY = 0;

    var textOffset = [
        defaultAlignmentX,
        defaultAlignmentY
    ];

    if(Array.isArray(tp)) {
        for(var i = 0; i < tp.length; i++) {
            textOffset[i] = [
                defaultAlignmentX,
                defaultAlignmentY
            ];
            if(tp[i]) {
                textOffset[i][0] = parseAlignmentX(tp[i]);
                textOffset[i][1] = parseAlignmentY(tp[i]);
            }
        }
    } else {
        textOffset[0] = parseAlignmentX(tp);
        textOffset[1] = parseAlignmentY(tp);
    }

    return textOffset;
}


function calculateSize(sizeIn, sizeFn) {
    // rough parity with Plotly 2D markers
    return sizeFn(sizeIn * 4);
}

function calculateSymbol(symbolIn) {
    return MARKER_SYMBOLS[symbolIn];
}

function formatParam(paramIn, len, calculate, dflt, extraFn) {
    var paramOut = null;

    if(Lib.isArrayOrTypedArray(paramIn)) {
        paramOut = [];

        for(var i = 0; i < len; i++) {
            if(paramIn[i] === undefined) paramOut[i] = dflt;
            else paramOut[i] = calculate(paramIn[i], extraFn);
        }
    } else paramOut = calculate(paramIn, Lib.identity);

    return paramOut;
}


function convertPlotlyOptions(scene, data) {
    var points = [];
    var sceneLayout = scene.fullSceneLayout;
    var scaleFactor = scene.dataScale;
    var xaxis = sceneLayout.xaxis;
    var yaxis = sceneLayout.yaxis;
    var zaxis = sceneLayout.zaxis;
    var marker = data.marker;
    var line = data.line;
    var x = data.x || [];
    var y = data.y || [];
    var z = data.z || [];
    var len = x.length;
    var xcalendar = data.xcalendar;
    var ycalendar = data.ycalendar;
    var zcalendar = data.zcalendar;
    var xc, yc, zc;
    var params, i;
    var text;

    // Convert points
    for(i = 0; i < len; i++) {
        // sanitize numbers and apply transforms based on axes.type
        xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0];
        yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1];
        zc = zaxis.d2l(z[i], 0, zcalendar) * scaleFactor[2];

        points[i] = [xc, yc, zc];
    }

    // convert text
    if(Array.isArray(data.text)) {
        text = data.text;
    } else if(Lib.isTypedArray(data.text)) {
        text = Array.from(data.text);
    } else if(data.text !== undefined) {
        text = new Array(len);
        for(i = 0; i < len; i++) text[i] = data.text;
    }

    function formatter(axName, val) {
        var ax = sceneLayout[axName];
        return Axes.tickText(ax, ax.d2l(val), true).text;
    }

    // check texttemplate
    var texttemplate = data.texttemplate;
    if(texttemplate) {
        var fullLayout = scene.fullLayout;
        var d3locale = fullLayout._d3locale;
        var isArray = Array.isArray(texttemplate);
        var N = isArray ? Math.min(texttemplate.length, len) : len;
        var txt = isArray ?
            function(i) { return texttemplate[i]; } :
            function() { return texttemplate; };

        text = new Array(N);

        for(i = 0; i < N; i++) {
            var d = {x: x[i], y: y[i], z: z[i]};
            var labels = {
                xLabel: formatter('xaxis', x[i]),
                yLabel: formatter('yaxis', y[i]),
                zLabel: formatter('zaxis', z[i])
            };
            var pointValues = {};
            appendArrayPointValue(pointValues, data, i);
            var meta = data._meta || {};
            text[i] = Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta);
        }
    }

    // Build object parameters
    params = {
        position: points,
        mode: data.mode,
        text: text
    };

    if('line' in data) {
        params.lineColor = formatColor(line, 1, len);
        params.lineWidth = line.width;
        params.lineDashes = line.dash;
    }

    if('marker' in data) {
        var sizeFn = makeBubbleSizeFn(data);

        params.scatterColor = formatColor(marker, 1, len);
        params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn);
        params.scatterMarker = formatParam(marker.symbol, len, calculateSymbol, '●');
        params.scatterLineWidth = marker.line.width;  // arrayOk === false
        params.scatterLineColor = formatColor(marker.line, 1, len);
        params.scatterAngle = 0;
    }

    if('textposition' in data) {
        params.textOffset = calculateTextOffset(data.textposition);
        params.textColor = formatColor(data.textfont, 1, len);
        params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12);
        params.textFontFamily = data.textfont.family;
        params.textFontWeight = data.textfont.weight;
        params.textFontStyle = data.textfont.style;
        params.textFontVariant = data.textfont.variant;
        params.textAngle = 0;
    }

    var dims = ['x', 'y', 'z'];
    params.project = [false, false, false];
    params.projectScale = [1, 1, 1];
    params.projectOpacity = [1, 1, 1];
    for(i = 0; i < 3; ++i) {
        var projection = data.projection[dims[i]];
        if((params.project[i] = projection.show)) {
            params.projectOpacity[i] = projection.opacity;
            params.projectScale[i] = projection.scale;
        }
    }

    params.errorBounds = calculateError(data, scaleFactor, sceneLayout);

    var errorParams = calculateErrorParams([data.error_x, data.error_y, data.error_z]);
    params.errorColor = errorParams.color;
    params.errorLineWidth = errorParams.lineWidth;
    params.errorCapSize = errorParams.capSize;

    params.delaunayAxis = data.surfaceaxis;
    params.delaunayColor = str2RgbaArray(data.surfacecolor);

    return params;
}

function _arrayToColor(color) {
    if(Lib.isArrayOrTypedArray(color)) {
        var c = color[0];

        if(Lib.isArrayOrTypedArray(c)) color = c;

        return 'rgb(' + color.slice(0, 3).map(function(x) {
            return Math.round(x * 255);
        }) + ')';
    }

    return null;
}

function arrayToColor(colors) {
    if(!Lib.isArrayOrTypedArray(colors)) {
        return null;
    }

    if((colors.length === 4) && (typeof colors[0] === 'number')) {
        return _arrayToColor(colors);
    }

    return colors.map(_arrayToColor);
}

proto.update = function(data) {
    var gl = this.scene.glplot.gl;
    var lineOptions;
    var scatterOptions;
    var errorOptions;
    var textOptions;
    var dashPattern = DASH_PATTERNS.solid;

    // Save data
    this.data = data;

    // Run data conversion
    var options = convertPlotlyOptions(this.scene, data);

    if('mode' in options) {
        this.mode = options.mode;
    }
    if('lineDashes' in options) {
        if(options.lineDashes in DASH_PATTERNS) {
            dashPattern = DASH_PATTERNS[options.lineDashes];
        }
    }

    this.color = arrayToColor(options.scatterColor) ||
                 arrayToColor(options.lineColor);

    // Save data points
    this.dataPoints = options.position;

    lineOptions = {
        gl: this.scene.glplot.gl,
        position: options.position,
        color: options.lineColor,
        lineWidth: options.lineWidth || 1,
        dashes: dashPattern[0],
        dashScale: dashPattern[1],
        opacity: data.opacity,
        connectGaps: data.connectgaps
    };

    if(this.mode.indexOf('lines') !== -1) {
        if(this.linePlot) this.linePlot.update(lineOptions);
        else {
            this.linePlot = createLinePlot(lineOptions);
            this.linePlot._trace = this;
            this.scene.glplot.add(this.linePlot);
        }
    } else if(this.linePlot) {
        this.scene.glplot.remove(this.linePlot);
        this.linePlot.dispose();
        this.linePlot = null;
    }

    // N.B. marker.opacity must be a scalar for performance
    var scatterOpacity = data.opacity;
    if(data.marker && data.marker.opacity !== undefined) scatterOpacity *= data.marker.opacity;

    scatterOptions = {
        gl: this.scene.glplot.gl,
        position: options.position,
        color: options.scatterColor,
        size: options.scatterSize,
        glyph: options.scatterMarker,
        opacity: scatterOpacity,
        orthographic: true,
        lineWidth: options.scatterLineWidth,
        lineColor: options.scatterLineColor,
        project: options.project,
        projectScale: options.projectScale,
        projectOpacity: options.projectOpacity
    };

    if(this.mode.indexOf('markers') !== -1) {
        if(this.scatterPlot) this.scatterPlot.update(scatterOptions);
        else {
            this.scatterPlot = createScatterPlot(scatterOptions);
            this.scatterPlot._trace = this;
            this.scatterPlot.highlightScale = 1;
            this.scene.glplot.add(this.scatterPlot);
        }
    } else if(this.scatterPlot) {
        this.scene.glplot.remove(this.scatterPlot);
        this.scatterPlot.dispose();
        this.scatterPlot = null;
    }

    textOptions = {
        gl: this.scene.glplot.gl,
        position: options.position,
        glyph: options.text,
        color: options.textColor,
        size: options.textSize,
        angle: options.textAngle,
        alignment: options.textOffset,
        font: options.textFontFamily,
        fontWeight: options.textFontWeight,
        fontStyle: options.textFontStyle,
        fontVariant: options.textFontVariant,
        orthographic: true,
        lineWidth: 0,
        project: false,
        opacity: data.opacity
    };

    this.textLabels = data.hovertext || data.text;

    if(this.mode.indexOf('text') !== -1) {
        if(this.textMarkers) this.textMarkers.update(textOptions);
        else {
            this.textMarkers = createScatterPlot(textOptions);
            this.textMarkers._trace = this;
            this.textMarkers.highlightScale = 1;
            this.scene.glplot.add(this.textMarkers);
        }
    } else if(this.textMarkers) {
        this.scene.glplot.remove(this.textMarkers);
        this.textMarkers.dispose();
        this.textMarkers = null;
    }

    errorOptions = {
        gl: this.scene.glplot.gl,
        position: options.position,
        color: options.errorColor,
        error: options.errorBounds,
        lineWidth: options.errorLineWidth,
        capSize: options.errorCapSize,
        opacity: data.opacity
    };
    if(this.errorBars) {
        if(options.errorBounds) {
            this.errorBars.update(errorOptions);
        } else {
            this.scene.glplot.remove(this.errorBars);
            this.errorBars.dispose();
            this.errorBars = null;
        }
    } else if(options.errorBounds) {
        this.errorBars = createErrorBars(errorOptions);
        this.errorBars._trace = this;
        this.scene.glplot.add(this.errorBars);
    }

    if(options.delaunayAxis >= 0) {
        var delaunayOptions = constructDelaunay(
            options.position,
            options.delaunayColor,
            options.delaunayAxis
        );
        delaunayOptions.opacity = data.opacity;

        if(this.delaunayMesh) {
            this.delaunayMesh.update(delaunayOptions);
        } else {
            delaunayOptions.gl = gl;
            this.delaunayMesh = createMesh(delaunayOptions);
            this.delaunayMesh._trace = this;
            this.scene.glplot.add(this.delaunayMesh);
        }
    } else if(this.delaunayMesh) {
        this.scene.glplot.remove(this.delaunayMesh);
        this.delaunayMesh.dispose();
        this.delaunayMesh = null;
    }
};

proto.dispose = function() {
    if(this.linePlot) {
        this.scene.glplot.remove(this.linePlot);
        this.linePlot.dispose();
    }
    if(this.scatterPlot) {
        this.scene.glplot.remove(this.scatterPlot);
        this.scatterPlot.dispose();
    }
    if(this.errorBars) {
        this.scene.glplot.remove(this.errorBars);
        this.errorBars.dispose();
    }
    if(this.textMarkers) {
        this.scene.glplot.remove(this.textMarkers);
        this.textMarkers.dispose();
    }
    if(this.delaunayMesh) {
        this.scene.glplot.remove(this.delaunayMesh);
        this.delaunayMesh.dispose();
    }
};

function createLineWithMarkers(scene, data) {
    var plot = new LineWithMarkers(scene, data.uid);
    plot.update(data);
    return plot;
}

module.exports = createLineWithMarkers;




© 2015 - 2024 Weber Informatics LLC | Privacy Policy