diff --git a/lib/index.js b/lib/index.js index 631fed56656..8e4a80b5a52 100644 --- a/lib/index.js +++ b/lib/index.js @@ -25,6 +25,7 @@ Core.register([ require('./pie'), require('./contour'), require('./scatter3d'), + require('./streamtube'), require('./surface'), require('./mesh3d'), require('./scattergeo'), diff --git a/lib/streamtube.js b/lib/streamtube.js new file mode 100644 index 00000000000..2fceca2e7e8 --- /dev/null +++ b/lib/streamtube.js @@ -0,0 +1,9 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +module.exports = require('../src/traces/streamtube'); diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index f799c40543b..69337d81265 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -527,7 +527,7 @@ proto.plot = function(sceneData, fullLayout, layout) { } /* - * Write aspect Ratio back to user data and fullLayout so that it is modifies as user + * Write aspect Ratio back to user data and fullLayout so that it is modified as user * manipulates the aspectmode settings and the fullLayout is up-to-date. */ fullSceneLayout.aspectratio.x = sceneLayout.aspectratio.x = aspectRatio[0]; diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index f4b0e2b29c1..207811d784a 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -17,7 +17,7 @@ Mesh3D.colorbar = require('../heatmap/colorbar'); Mesh3D.plot = require('./convert'); Mesh3D.moduleType = 'trace'; -Mesh3D.name = 'mesh3d', +Mesh3D.name = 'mesh3d'; Mesh3D.basePlotModule = require('../../plots/gl3d'); Mesh3D.categories = ['gl3d']; Mesh3D.meta = { diff --git a/src/traces/scatter/basic_line_defaults.js b/src/traces/scatter/basic_line_defaults.js new file mode 100644 index 00000000000..dc26c512cc1 --- /dev/null +++ b/src/traces/scatter/basic_line_defaults.js @@ -0,0 +1,30 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var hasColorscale = require('../../components/colorscale/has_colorscale'); +var colorscaleDefaults = require('../../components/colorscale/defaults'); + + +// common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' +module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { + + var markerColor = (traceIn.marker || {}).color; + + coerce('line.color', defaultColor); + if(hasColorscale(traceIn, 'line')) { + colorscaleDefaults( + traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'} + ); + } else { + coerce('line.color', (Array.isArray(markerColor) ? false : markerColor) || + defaultColor); + } +}; diff --git a/src/traces/scatter/line_defaults.js b/src/traces/scatter/line_defaults.js index 92aae25b149..408bba405fa 100644 --- a/src/traces/scatter/line_defaults.js +++ b/src/traces/scatter/line_defaults.js @@ -9,25 +9,13 @@ 'use strict'; -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); +var basicLineDefaults = require('./basic_line_defaults'); // common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - var markerColor = (traceIn.marker || {}).color; - - coerce('line.color', defaultColor); - if(hasColorscale(traceIn, 'line')) { - colorscaleDefaults( - traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'} - ); - } else { - coerce('line.color', (Array.isArray(markerColor) ? false : markerColor) || - defaultColor); - } - + basicLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('line.width'); coerce('line.dash'); diff --git a/src/traces/scatter/marker_basic_defaults.js b/src/traces/scatter/marker_basic_defaults.js new file mode 100644 index 00000000000..475ee9d7aa8 --- /dev/null +++ b/src/traces/scatter/marker_basic_defaults.js @@ -0,0 +1,42 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var hasColorscale = require('../../components/colorscale/has_colorscale'); +var colorscaleDefaults = require('../../components/colorscale/defaults'); + +var subTypes = require('./subtypes'); + + +// common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' +module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce) { + var isBubble = subTypes.isBubble(traceIn), + lineColor = !Array.isArray(traceIn.line) ? (traceIn.line || {}).color : undefined; + + if(lineColor) defaultColor = lineColor; + + coerce('marker.opacity', isBubble ? 0.7 : 1); + coerce('marker.size'); + + coerce('marker.color', defaultColor); + if(hasColorscale(traceIn, 'marker')) { + colorscaleDefaults( + traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'} + ); + } + + if(isBubble) { + coerce('marker.sizeref'); + coerce('marker.sizemin'); + coerce('marker.sizemode'); + } + + return lineColor; +}; diff --git a/src/traces/scatter/marker_defaults.js b/src/traces/scatter/marker_defaults.js index eaee453d6c0..e6f79c4ee3f 100644 --- a/src/traces/scatter/marker_defaults.js +++ b/src/traces/scatter/marker_defaults.js @@ -9,31 +9,22 @@ 'use strict'; +var markerBasicDefaults = require('./marker_basic_defaults'); var Color = require('../../components/color'); var hasColorscale = require('../../components/colorscale/has_colorscale'); var colorscaleDefaults = require('../../components/colorscale/defaults'); var subTypes = require('./subtypes'); - // common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - var isBubble = subTypes.isBubble(traceIn), - lineColor = !Array.isArray(traceIn.line) ? (traceIn.line || {}).color : undefined, - defaultMLC; - if(lineColor) defaultColor = lineColor; + var defaultMLC; - coerce('marker.symbol'); - coerce('marker.opacity', isBubble ? 0.7 : 1); - coerce('marker.size'); + var isBubble = subTypes.isBubble(traceIn); + var lineColor = markerBasicDefaults(traceIn, traceOut, defaultColor, layout, coerce); - coerce('marker.color', defaultColor); - if(hasColorscale(traceIn, 'marker')) { - colorscaleDefaults( - traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'} - ); - } + coerce('marker.symbol'); // if there's a line with a different color than the marker, use // that line color as the default marker line color @@ -45,6 +36,7 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout else defaultMLC = Color.defaultLine; coerce('marker.line.color', defaultMLC); + if(hasColorscale(traceIn, 'marker.line')) { colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'} @@ -53,9 +45,5 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout coerce('marker.line.width', isBubble ? 1 : 0); - if(isBubble) { - coerce('marker.sizeref'); - coerce('marker.sizemin'); - coerce('marker.sizemode'); - } + }; diff --git a/src/traces/streamtube/attributes.js b/src/traces/streamtube/attributes.js new file mode 100644 index 00000000000..1735be9616b --- /dev/null +++ b/src/traces/streamtube/attributes.js @@ -0,0 +1,141 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var scatterAttrs = require('../scatter/attributes'); +var colorAttributes = require('../../components/colorscale/color_attributes'); + +var extendFlat = require('../../lib/extend').extendFlat; + +var scatterMarkerAttrs = scatterAttrs.marker; + +function makeProjectionAttr(axLetter) { + return { + show: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Sets whether or not projections are shown along the', + axLetter, 'axis.' + ].join(' ') + }, + opacity: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the projection color.' + }, + scale: { + valType: 'number', + role: 'style', + min: 0, + max: 10, + dflt: 2 / 3, + description: [ + 'Sets the scale factor determining the size of the', + 'projection marker points.' + ].join(' ') + } + }; +} + +module.exports = { + x: { + valType: 'data_array', + description: 'Sets the x coordinates.' + }, + y: { + valType: 'data_array', + description: 'Sets the y coordinates.' + }, + z: { + valType: 'data_array', + description: 'Sets the z coordinates.' + }, + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y,z) triplet.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y,z) coordinates.' + ].join(' ') + }), + mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? + {dflt: 'lines+markers'}), + projection: { + x: makeProjectionAttr('x'), + y: makeProjectionAttr('y'), + z: makeProjectionAttr('z') + }, + sizingaxis: { + valType: 'enumerated', + role: 'info', + values: [0, 1, 2], + dflt: 0, + description: [ + 'Specifies the axis index in relation to which the `line.width` and `marker.size` values are determined. The', + 'default value is `0`, which specifies the `x` axis, i.e. sizes will be determined as a multiple of one unit', + 'on the `x` axis. `0`, `1`, `2` refer to `x`, `y`, `z`, respectively.' + ].join(' ') + }, + connectgaps: scatterAttrs.connectgaps, + line: extendFlat({}, { + connectiondiameter: extendFlat({}, scatterMarkerAttrs.size, { + dflt: 1, + description: 'Sets the radius of the line connection. Either a number, or an array with as many elements as the number of points.' + }), + showscale: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Has an effect only if `line.color` is set to a numerical array.', + 'Determines whether or not a colorbar is displayed.' + ].join(' ') + } + }, + colorAttributes('line') + ), + marker: extendFlat({}, { + size: { + valType: 'number', + min: 0, + dflt: 1, + arrayOk: true, + role: 'style', + description: 'Sets the marker radius, in units of `sizingaxis`. May be a number or an array of numbers.' + }, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: extendFlat({}, scatterMarkerAttrs.opacity, { + arrayOk: false, + description: [ + 'Sets the marker opacity.', + 'Note that the marker opacity for scatter3d traces', + 'must be a scalar value for performance reasons.', + 'To set a blending opacity value', + '(i.e. which is not transparent), set *marker.color*', + 'to an rgba color and use its alpha channel.' + ].join(' ') + }), + showscale: scatterMarkerAttrs.showscale + }, + colorAttributes('marker') + ), + textposition: extendFlat({}, scatterAttrs.textposition, {dflt: 'top center'}), + textfont: scatterAttrs.textfont, + _nestedModules: { + 'marker.colorbar': 'Colorbar' + } +}; diff --git a/src/traces/streamtube/calc.js b/src/traces/streamtube/calc.js new file mode 100644 index 00000000000..2fee965f035 --- /dev/null +++ b/src/traces/streamtube/calc.js @@ -0,0 +1,27 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); +var calcColorscales = require('../scatter/colorscale_calc'); + + +/** + * This is a kludge to put the array attributes into + * calcdata the way Scatter.plot does, so that legends and + * popovers know what to do with them. + */ +module.exports = function calc(gd, trace) { + var cd = [{x: false, y: false, trace: trace, t: {}}]; + + arraysToCalcdata(cd); + calcColorscales(trace); + + return cd; +}; diff --git a/src/traces/streamtube/convert.js b/src/traces/streamtube/convert.js new file mode 100644 index 00000000000..e29acd6f7d1 --- /dev/null +++ b/src/traces/streamtube/convert.js @@ -0,0 +1,896 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var createLinePlot = require('gl-line3d'); +var createScatterPlot = require('gl-scatter3d'); +var createMesh = require('gl-mesh3d'); + +var Lib = require('../../lib'); +var formatColor = require('../../lib/gl_format_color'); +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); + +function LineWithMarkers(scene, uid) { + this.scene = scene; + this.uid = uid; + this.linePlot = null; + this.scatterPlot = null; + this.textMarkers = null; + this.color = null; + this.mode = ''; + this.textLabels = null; + this.data = null; +} + +var proto = LineWithMarkers.prototype; + +proto.handlePick = function(selection) { + if(selection.object && + (selection.object === this.linePlot || + selection.object === this.textMarkers || + selection.object === this.scatterPlot)) { + if(selection.object.highlight) { + selection.object.highlight(null); + } + if(this.scatterPlot) { + selection.object = this.scatterPlot; + this.scatterPlot.highlight(selection.data); + } + if(this.textLabels && this.textLabels[selection.data.index] !== undefined) { + selection.textLabel = this.textLabels[selection.data.index]; + } + else selection.textLabel = ''; + + var selectIndex = selection.data.index; + selection.traceCoordinate = [ + this.data.x[selectIndex], + this.data.y[selectIndex], + this.data.z[selectIndex] + ]; + + return true; + } +}; + +function calculateTextOffset(tp, offsetValue) { + //Read out text properties + var textOffset = [0, 0]; + if(Array.isArray(tp)) return [0, -1]; + if(tp.indexOf('bottom') >= 0) textOffset[1] += offsetValue; + if(tp.indexOf('top') >= 0) textOffset[1] -= offsetValue; + if(tp.indexOf('left') >= 0) textOffset[0] -= offsetValue; + if(tp.indexOf('right') >= 0) textOffset[0] += offsetValue; + return textOffset; +} + + +function calculateSize(sizeIn, sizeFn) { + // parity with scatter3d markers + return sizeFn(sizeIn); +} + +function formatParam(paramIn, len, calculate, dflt, extraFn) { + var paramOut = null; + + if(Array.isArray(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 params, i, + points = [], + sceneLayout = scene.fullSceneLayout, + scaleFactor = scene.dataScale, + xaxis = sceneLayout.xaxis, + yaxis = sceneLayout.yaxis, + zaxis = sceneLayout.zaxis, + marker = data.marker, + line = data.line, + xc, x = data.x || [], + yc, y = data.y || [], + zc, z = data.z || [], + len = x.length, + text; + + //Convert points + for(i = 0; i < len; i++) { + // sanitize numbers and apply transforms based on axes.type + xc = xaxis.d2l(x[i]) * scaleFactor[0]; + yc = yaxis.d2l(y[i]) * scaleFactor[1]; + zc = zaxis.d2l(z[i]) * scaleFactor[2]; + + points[i] = [xc, yc, zc]; + } + + // convert text + if(Array.isArray(data.text)) text = data.text; + else if(data.text !== undefined) { + text = new Array(len); + for(i = 0; i < len; i++) text[i] = data.text; + } + + //Build object parameters + params = { + position: points, + mode: data.mode, + text: text + }; + + if('line' in data) { + params.lineColor = formatColor(line, 1, len); + params.connectiondiameter = line.connectiondiameter; + } + + if('marker' in data) { + var sizeFn = makeBubbleSizeFn(data); + + params.scatterColor = formatColor(marker, 1, len); + params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn); + if(!Array.isArray(marker.size)) { + params.scatterSize *= 0.5; // for some reason, above formatParam divides by two only if it's an array + } + params.scatterAngle = 0; + } + + if('textposition' in data) { + params.textOffset = calculateTextOffset(data.textposition, 2 * (Array.isArray(data.marker.size) ? Math.max.apply(Math, data.marker.size) : data.marker.size)); + params.textColor = formatColor(data.textfont, 1, len); + params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12); + params.textFont = data.textfont.family; // arrayOk === false + 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; + } + } + + return params; +} + +function arrayToColor(color) { + if(Array.isArray(color)) { + var c = color[0]; + + if(Array.isArray(c)) color = c; + + return 'rgb(' + color.slice(0, 3).map(function(x) { + return Math.round(x * 255); + }) + ')'; + } + + return null; +} + +proto.update = function(data) { + var gl = this.scene.glplot.gl, + lineOptions, + scatterOptions, + textOptions; + + //Save data + this.data = data; + + //Run data conversion + var options = convertPlotlyOptions(this.scene, data); + + if('mode' in options) { + this.mode = options.mode; + } + + this.color = arrayToColor(options.scatterColor) || + arrayToColor(options.lineColor); + + lineOptions = { + gl: gl, + position: options.position, + color: options.lineColor, + lineWidth: options.lineWidth || 1, + opacity: data.opacity, + connectGaps: data.connectgaps + }; + + if(this.mode.indexOf('lines-FIXME') !== -1) { + if(this.linePlot) this.linePlot.update(lineOptions); + else { + this.linePlot = createLinePlot(lineOptions); + 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) scatterOpacity *= data.marker.opacity; + + scatterOptions = { + gl: gl, + position: options.position, + color: options.scatterColor, + size: options.scatterSize, + glyph: options.scatterMarker, + opacity: scatterOpacity, + orthographic: true, + 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.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: gl, + position: options.position, + glyph: options.text, + color: options.textColor, + size: options.textSize, + angle: options.textAngle, + alignment: options.textOffset, + font: options.textFont, + orthographic: true, + lineWidth: 0, + project: false, + opacity: data.opacity + }; + + this.textLabels = options.text; + + if(this.mode.indexOf('text') !== -1) { + if(this.textMarkers) this.textMarkers.update(textOptions); + else { + this.textMarkers = createScatterPlot(textOptions); + 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; + } + + var meshOptions = calculateMesh(this.data.x, this.data.y, this.data.z, options.connectiondiameter, options.lineColor, options.scatterSize, options.scatterColor, this.data.sizingaxis, this.scene.dataScale, this.scene.glplotLayout.aspectratio); + if(this.streamTubeMesh) { + this.streamTubeMesh.update(meshOptions); + } else { + meshOptions.gl = gl; + this.streamTubeMesh = createMesh(meshOptions); + this.scene.glplot.add(this.streamTubeMesh); + } + +}; + +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.textMarkers) { + this.scene.glplot.remove(this.textMarkers); + this.textMarkers.dispose(); + } +}; + +function createLineWithMarkers(scene, data) { + var plot = new LineWithMarkers(scene, data.uid); + plot.update(data); + return plot; +} + +function calculateMesh(inputX, inputY, inputZ, inputW, inputC, inputMW, inputMC, sizingaxis, scalingFactor, aspect) { + + var sx = scalingFactor[0]; + var sy = scalingFactor[1]; + var sz = scalingFactor[2]; + + function addVertex(X, Y, Z, x, y, z) { + X.push(x); + Y.push(y); + Z.push(z); + } + + function addFace(I, J, K, F, i, j, k, f) { + I.push(i); + J.push(j); + K.push(k); + F.push(f); + } + + function catmullRom(x, y, z, r, R, G, B, Tratio) { + + var t0 = 0; + var d, c1, c2; + var alpha = 0.5; + var pow = alpha / 2; + var t1 = Math.pow(Math.pow(x[1] - x[0], 2) + Math.pow(y[1] - y[0], 2) + Math.pow(z[1] - z[0], 2), pow) + t0; + var t2 = Math.pow(Math.pow(x[2] - x[1], 2) + Math.pow(y[2] - y[1], 2) + Math.pow(z[2] - z[1], 2), pow) + t1; + var t3 = Math.pow(Math.pow(x[3] - x[2], 2) + Math.pow(y[3] - y[2], 2) + Math.pow(z[3] - z[2], 2), pow) + t2; + + var T = t1 + Tratio * (t2 - t1); + + d = t1 - t0; + c1 = (t1 - T) / d; + c2 = (T - t0) / d; + var A1x = c1 * x[0] + c2 * x[1]; + var A1y = c1 * y[0] + c2 * y[1]; + var A1z = c1 * z[0] + c2 * z[1]; + var A1r = c1 * r[0] + c2 * r[1]; + var A1R = c1 * R[0] + c2 * R[1]; + var A1G = c1 * G[0] + c2 * G[1]; + var A1B = c1 * B[0] + c2 * B[1]; + + d = t2 - t1; + c1 = (t2 - T) / d; + c2 = (T - t1) / d; + var A2x = c1 * x[1] + c2 * x[2]; + var A2y = c1 * y[1] + c2 * y[2]; + var A2z = c1 * z[1] + c2 * z[2]; + var A2r = c1 * r[1] + c2 * r[2]; + var A2R = c1 * R[1] + c2 * R[2]; + var A2G = c1 * G[1] + c2 * G[2]; + var A2B = c1 * B[1] + c2 * B[2]; + + d = t3 - t2; + c1 = (t3 - T) / d; + c2 = (T - t2) / d; + var A3x = c1 * x[2] + c2 * x[3]; + var A3y = c1 * y[2] + c2 * y[3]; + var A3z = c1 * z[2] + c2 * z[3]; + var A3r = c1 * r[2] + c2 * r[3]; + var A3R = c1 * R[2] + c2 * R[3]; + var A3G = c1 * G[2] + c2 * G[3]; + var A3B = c1 * B[2] + c2 * B[3]; + + d = t2 - t0; + c1 = (t2 - T) / d; + c2 = (T - t0) / d; + var B1x = c1 * A1x + c2 * A2x; + var B1y = c1 * A1y + c2 * A2y; + var B1z = c1 * A1z + c2 * A2z; + var B1r = c1 * A1r + c2 * A2r; + var B1R = c1 * A1R + c2 * A2R; + var B1G = c1 * A1G + c2 * A2G; + var B1B = c1 * A1B + c2 * A2B; + + d = t3 - t1; + c1 = (t3 - T) / d; + c2 = (T - t1) / d; + var B2x = c1 * A2x + c2 * A3x; + var B2y = c1 * A2y + c2 * A3y; + var B2z = c1 * A2z + c2 * A3z; + var B2r = c1 * A2r + c2 * A3r; + var B2R = c1 * A2R + c2 * A3R; + var B2G = c1 * A2G + c2 * A3G; + var B2B = c1 * A2B + c2 * A3B; + + d = t2 - t1; + c1 = (t2 - T) / d; + c2 = (T - t1) / d; + var Cx = c1 * B1x + c2 * B2x; + var Cy = c1 * B1y + c2 * B2y; + var Cz = c1 * B1z + c2 * B2z; + var Cr = c1 * B1r + c2 * B2r; + var CR = c1 * B1R + c2 * B2R; + var CG = c1 * B1G + c2 * B2G; + var CB = c1 * B1B + c2 * B2B; + + return [Cx, Cy, Cz, Cr, CR, CG, CB]; + } + + var quadCount = 36; + + var sinVector = []; + var cosVector = []; + for(var q = 0; q < quadCount; q++) { + var a = q * Math.PI * 2 / quadCount; + sinVector.push(Math.sin(a)); + cosVector.push(Math.cos(a)); + } + + function cylinderMaker(r1, r2, x1, x2, y1, y2, z1, z2, f1, f2, continuable) { + + x1 *= sx; + x2 *= sx; + y1 *= sy; + y2 *= sy; + z1 *= sz; + z2 *= sz; + + var uu = x2 - x1; + var vv = y2 - y1; + var ww = z2 - z1; + + var X = []; + var Y = []; + var Z = []; + + var I = []; + var J = []; + var K = []; + var F = []; + + var av = addVertex.bind(null, X, Y, Z); + var af = addFace.bind(null, I, J, K, F); + + var q, vert, sa, ca; + + var x, y, z, xx, yy, zz; + + var length = Math.sqrt(uu * uu + vv * vv + ww * ww); + + var u = uu / length; + var v = vv / length; + var w = ww / length; + + x = - w; y = - w; z = u + v; + + var xxb, yyb, zzb, xxc, yyc, zzc, xxs, yys, zzs; + + // While we could easily avoid variable capture and turn this into a pure function, + // it would involve the creation of intermediary data (lots of small array allocations) + // so this function, which pushes things in a large array, is kept this way. + function ncompute(r1, uu, vv, ww) { + + length = Math.sqrt(x * x + y * y + z * z) / r1; + x /= length; + y /= length; + z /= length; + + xxb = u * (u * x + v * y + w * z); + yyb = v * (u * x + v * y + w * z); + zzb = w * (u * x + v * y + w * z); + + xxc = x * (v * v + w * w) - u * (v * y + w * z); + yyc = y * (u * u + w * w) - v * (u * x + w * z); + zzc = z * (u * u + v * v) - w * (u * x + v * y); + + xxs = v * z - w * y; + yys = w * x - u * z; + zzs = u * y - v * x; + + for(q = 0; q < quadCount; q++) { + + sa = sinVector[q]; + ca = cosVector[q]; + + xx = xxb + xxc * ca + xxs * sa; + yy = yyb + yyc * ca + yys * sa; + zz = zzb + zzc * ca + zzs * sa; + + av((xx + uu + x1) / sx, (yy + vv + y1) / sy, (zz + ww + z1) / sz); // with translation + } + + } + + if(!continuable) { + ncompute(r1, 0, 0, 0); + } + + ncompute(r2, uu, vv, ww); + + var o = continuable ? -quadCount : 0; // offset for possible welding (continue == true) + + for(q = 0; q < quadCount; q++) { + + vert = q; + + af(vert + o, vert + quadCount + o, (vert + 1) % quadCount + o, f1); + af((vert + 1) % quadCount + o, vert + quadCount + o, (vert + 1) % quadCount + quadCount + o, f2); + } + + var model = { + x: X, + y: Y, + z: Z, + i: I, + j: J, + k: K, + f: F + }; + + return model; + } + + function unitIcosahedron() { + + var X = []; + var Y = []; + var Z = []; + + var I = []; + var J = []; + var K = []; + var F = []; + + var s = Math.sqrt((5 - Math.sqrt(5)) / 10); + var t = Math.sqrt((5 + Math.sqrt(5)) / 10); + + var av = addVertex.bind(null, X, Y, Z); + var af = addFace.bind(null, I, J, K, F); + + av(-s, t, 0); + av(s, t, 0); + av(-s, -t, 0); + av(s, -t, 0); + + av(0, -s, t); + av(0, s, t); + av(0, -s, -t); + av(0, s, -t); + + av(t, 0, -s); + av(t, 0, s); + av(-t, 0, -s); + av(-t, 0, s); + + af(0, 5, 11); + af(0, 1, 5); + af(0, 7, 1); + af(0, 10, 7); + af(0, 11, 10); + + af(1, 9, 5); + af(5, 4, 11); + af(11, 2, 10); + af(10, 6, 7); + af(7, 8, 1); + + af(3, 4, 9); + af(3, 2, 4); + af(3, 6, 2); + af(3, 8, 6); + af(3, 9, 8); + + af(4, 5, 9); + af(2, 11, 4); + af(6, 10, 2); + af(8, 7, 6); + af(9, 1, 8); + + var model = { + x: X, + y: Y, + z: Z, + i: I, + j: J, + k: K, + f: F + }; + + return model; + } + + function increaseLoD(m) { + + var I = []; + var J = []; + var K = []; + var F = []; + + var p; + + var mx = m.x.slice(); + var my = m.y.slice(); + var mz = m.z.slice(); + var mi = m.i; + var mj = m.j; + var mk = m.k; + var mf = m.f; + + var midx1, midy1, midz1, midx2, midy2, midz2, midx3, midy3, midz3, v1, v2, v3, midi1, midi2, midi3, k, length; + + var vCache = {}; + + for(p = 0; p < mi.length; p++) { + + v1 = mi[p]; + v2 = mj[p]; + v3 = mk[p]; + + k = [v1, v2]; + if(vCache[k.join()]) { + midi1 = vCache[k.join()]; + } else { + midx1 = (mx[v1] + mx[v2]) / 2; + midy1 = (my[v1] + my[v2]) / 2; + midz1 = (mz[v1] + mz[v2]) / 2; + length = Math.sqrt(midx1 * midx1 + midy1 * midy1 + midz1 * midz1); + mx.push(midx1 / length); + my.push(midy1 / length); + mz.push(midz1 / length); + midi1 = mx.length - 1; // vertex index to the newly created midpoint + vCache[k.join()] = midi1; + } + + k = [v2, v3]; + if(vCache[k.join()]) { + midi2 = vCache[k.join()]; + } else { + midx2 = (mx[v2] + mx[v3]) / 2; + midy2 = (my[v2] + my[v3]) / 2; + midz2 = (mz[v2] + mz[v3]) / 2; + length = Math.sqrt(midx2 * midx2 + midy2 * midy2 + midz2 * midz2); + mx.push(midx2 / length); + my.push(midy2 / length); + mz.push(midz2 / length); + midi2 = mx.length - 1; // vertex index to the newly created midpoint + vCache[k.join()] = midi2; + } + + k = [v3, v1]; + if(vCache[k.join()]) { + midi2 = vCache[k.join()]; + } else { + midx3 = (mx[v3] + mx[v1]) / 2; + midy3 = (my[v3] + my[v1]) / 2; + midz3 = (mz[v3] + mz[v1]) / 2; + length = Math.sqrt(midx3 * midx3 + midy3 * midy3 + midz3 * midz3); + mx.push(midx3 / length); + my.push(midy3 / length); + mz.push(midz3 / length); + midi3 = mx.length - 1; // vertex index to the newly created midpoint + vCache[k.join()] = midi3; + } + + I.push(mi[p]); + J.push(midi1); + K.push(midi3); + F.push(mf[p]); + + I.push(mj[p]); + J.push(midi2); + K.push(midi1); + F.push(mf[p]); + + I.push(mk[p]); + J.push(midi3); + K.push(midi2); + F.push(mf[p]); + + I.push(midi1); + J.push(midi2); + K.push(midi3); + F.push(mf[p]); + } + + var model = { + x: mx, + y: my, + z: mz, + i: I, + j: J, + k: K, + f: F + }; + + return model; + } + + var unitSphere = increaseLoD(increaseLoD(increaseLoD(increaseLoD(unitIcosahedron())))); + + function addPointMarker(geom, x, y, z, f, r, vOffset, X, Y, Z, I, J, K, F) { + + var v, p; + + var mx = geom.x; + var my = geom.y; + var mz = geom.z; + var mi = geom.i; + var mj = geom.j; + var mk = geom.k; + + for(v = 0; v < mx.length; v++) { + X.push(x + mx[v] * r / sx * aspect.x); + Y.push(y + my[v] * r / sy * aspect.y); + Z.push(z + mz[v] * r / sz * aspect.z); + } + + for(p = 0; p < mi.length; p++) { + I.push(vOffset + mi[p]); + J.push(vOffset + mj[p]); + K.push(vOffset + mk[p]); + F.push(f); + } + + return mx.length; + } + + function addLine(geom, vOffset, X, Y, Z, I, J, K, F) { + + var v, p; + + var mx = geom.x; + var my = geom.y; + var mz = geom.z; + var mi = geom.i; + var mj = geom.j; + var mk = geom.k; + var mf = geom.f; + + for(v = 0; v < mx.length; v++) { + + X.push(mx[v] * aspect.x); + Y.push(my[v] * aspect.y); + Z.push(mz[v] * aspect.z); + } + + for(p = 0; p < mi.length; p++) { + I.push(vOffset + mi[p]); + J.push(vOffset + mj[p]); + K.push(vOffset + mk[p]); + F.push(mf[p]); + } + + return mx.length; + } + + var x, y, z; + + var m, n, r, r2, c, c1, c2; + + var scaler = [sx, sy, sz][sizingaxis]; + + var rArr = Array.isArray(inputW); + var cArr = Array.isArray(inputC[0]); + + var mrArr = Array.isArray(inputMW); + var mcArr = Array.isArray(inputMC); + + var p = { + x: inputX, + y: inputY, + z: inputZ, + r: rArr ? inputW : null, + c: cArr ? inputC : null + }; + + var rp = { + x: [], + y: [], + z: [], + r: [], + c: [] + }; + + var upsamplingFactor = 100; // convert every original point to as many upsampled points + var n0, n1, n2, n3, xyzrf; + for(n = 0; n < p.x.length - 1; n++) { + + for(m = 0; m < upsamplingFactor; m++) { + + c1 = m / upsamplingFactor; + c2 = (upsamplingFactor - m) / upsamplingFactor; + + n0 = (n - 1 + p.x.length) % p.x.length; + n1 = n; + n2 = (n + 1) % p.x.length; + n3 = (n + 2) % p.x.length; + + xyzrf = catmullRom( + [p.x[n0], p.x[n1], p.x[n2], p.x[n3]], + [p.y[n0], p.y[n1], p.y[n2], p.y[n3]], + [p.z[n0], p.z[n1], p.z[n2], p.z[n3]], + rArr ? [p.r[n0], p.r[n1], p.r[n2], p.r[n3]] : [inputW, inputW, inputW, inputW], + cArr ? [p.c[n0][0], p.c[n1][0], p.c[n2][0], p.c[n3][0]] : [inputC[0], inputC[0], inputC[0], inputC[0]], + cArr ? [p.c[n0][1], p.c[n1][1], p.c[n2][1], p.c[n3][1]] : [inputC[1], inputC[1], inputC[1], inputC[1]], + cArr ? [p.c[n0][2], p.c[n1][2], p.c[n2][2], p.c[n3][2]] : [inputC[2], inputC[2], inputC[2], inputC[2]], + c1); + + rp.x.push(xyzrf[0]); + rp.y.push(xyzrf[1]); + rp.z.push(xyzrf[2]); + rp.r.push(xyzrf[3]); + rp.c.push([xyzrf[4], xyzrf[5], xyzrf[6], 1]); // fixme transfer opacity too + } + } + + var cylinderModels = []; + + for(n = 0; n < rp.x.length - 1; n++) { + + var point1 = n; + var point2 = n + 1; + + x = rp.x[point1]; + y = rp.y[point1]; + z = rp.z[point1]; + r = rp.r[point1]; + c = rp.c[point1]; + + var x2 = rp.x[point2]; + var y2 = rp.y[point2]; + var z2 = rp.z[point2]; + r2 = rp.r[point2]; + c2 = rp.c[point2]; + + cylinderModels.push(cylinderMaker(scaler / 2 * r, scaler / 2 * r2, x, x2, y, y2, z, z2, c, c2, n > 0)); + } + + var X = []; + var Y = []; + var Z = []; + var I = []; + var J = []; + var K = []; + var F = []; + + // weird, index *is* used at two places... + var index = 0; // eslint-disable-line no-unused-vars + + for(n = 0; n < rp.x.length - 1; n++) { + index += addLine(cylinderModels[n], index, X, Y, Z, I, J, K, F); + } + + if(inputMC && inputMW) { + for(n = 0; n < p.x.length; n++) { + index += addPointMarker(unitSphere, p.x[n], p.y[n], p.z[n], mcArr ? inputMC[n] : inputMC, scaler * (mrArr ? inputMW[n] : inputMW), index, X, Y, Z, I, J, K, F); + } + } + + return { + positions: X.map(function(d, i) {return [ + X[i] * sx, + Y[i] * sy, + Z[i] * sz + ];}), + cells: I.map(function(d, i) {return [I[i], J[i], K[i]];}), + cellColors: F, + opacity: 1, + lightPosition: [1e6 * scalingFactor[0], 1e6 * scalingFactor[1], 1e6 * scalingFactor[2]], + ambient: 0.2, + diffuse: 1, + specular: 0.3, + roughness: 0.2, + fresnel: 0, + vertexNormalsEpsilon: 0, + contourEnable: true, // fixme check what it is; doesn't seem to matter + contourCount: 100, // fixme check what it is; doesn't seem to matter + contourLineWidth: 10, // fixme check what it is; doesn't seem to matter + contourColor: [1, 0, 0] // fixme check what it is; doesn't seem to matter + }; +} + +module.exports = createLineWithMarkers; diff --git a/src/traces/streamtube/defaults.js b/src/traces/streamtube/defaults.js new file mode 100644 index 00000000000..af5868ba53a --- /dev/null +++ b/src/traces/streamtube/defaults.js @@ -0,0 +1,76 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var Lib = require('../../lib'); + +var subTypes = require('../scatter/subtypes'); +var handleMarkerDefaults = require('../scatter/marker_basic_defaults'); +var handleLineDefaults = require('../scatter/basic_line_defaults'); +var handleTextDefaults = require('../scatter/text_defaults'); + +var attributes = require('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len = handleXYZDefaults(traceOut, coerce); + if(!len) { + traceOut.visible = false; + return; + } + + coerce('text'); + coerce('mode'); + coerce('sizingaxis'); + + if(subTypes.hasLines(traceOut)) { + coerce('connectgaps'); + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); + coerce('line.connectiondiameter', traceIn.connectiondiameter); + } + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); + } + + if(subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + + var dims = ['x', 'y', 'z']; + for(var i = 0; i < 3; ++i) { + var projection = 'projection.' + dims[i]; + if(coerce(projection + '.show')) { + coerce(projection + '.opacity'); + coerce(projection + '.scale'); + } + } +}; + +function handleXYZDefaults(traceOut, coerce) { + var len = 0, + x = coerce('x'), + y = coerce('y'), + z = coerce('z'); + + if(x && y && z) { + len = Math.min(x.length, y.length, z.length); + if(len < x.length) traceOut.x = x.slice(0, len); + if(len < y.length) traceOut.y = y.slice(0, len); + if(len < z.length) traceOut.z = z.slice(0, len); + } + + return len; +} diff --git a/src/traces/streamtube/index.js b/src/traces/streamtube/index.js new file mode 100644 index 00000000000..78ca15f78d6 --- /dev/null +++ b/src/traces/streamtube/index.js @@ -0,0 +1,33 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Streamtube = {}; + +Streamtube.plot = require('./convert'); +Streamtube.attributes = require('./attributes'); +Streamtube.supplyDefaults = require('./defaults'); +Streamtube.colorbar = require('../scatter/colorbar'); +Streamtube.calc = require('./calc'); + +Streamtube.moduleType = 'trace'; +Streamtube.name = 'streamtube'; +Streamtube.basePlotModule = require('../../plots/gl3d'); +Streamtube.categories = ['gl3d', 'markerColorscale', 'showLegend']; +Streamtube.meta = { + hrName: 'streamtube', + description: [ + 'The data visualized as a streamtube and/or its markers in 3D dimension', + 'is set in `x`, `y`, `z`.', + 'Text (appearing either on the chart or on hover only) is via `text`.', + 'Projections are achieved via `projection`.' + ].join(' ') +}; + +module.exports = Streamtube; diff --git a/test/image/baselines/gl3d_streamtubes_aspectmode_cube.png b/test/image/baselines/gl3d_streamtubes_aspectmode_cube.png new file mode 100644 index 00000000000..a0c6b519a3b Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_aspectmode_cube.png differ diff --git a/test/image/baselines/gl3d_streamtubes_aspectmode_manual.png b/test/image/baselines/gl3d_streamtubes_aspectmode_manual.png new file mode 100644 index 00000000000..2538b645606 Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_aspectmode_manual.png differ diff --git a/test/image/baselines/gl3d_streamtubes_basic.png b/test/image/baselines/gl3d_streamtubes_basic.png new file mode 100644 index 00000000000..75e81721052 Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_basic.png differ diff --git a/test/image/baselines/gl3d_streamtubes_closed.png b/test/image/baselines/gl3d_streamtubes_closed.png new file mode 100644 index 00000000000..6f18c31c7f7 Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_closed.png differ diff --git a/test/image/baselines/gl3d_streamtubes_flat.png b/test/image/baselines/gl3d_streamtubes_flat.png new file mode 100644 index 00000000000..610029306dd Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_flat.png differ diff --git a/test/image/baselines/gl3d_streamtubes_globule.png b/test/image/baselines/gl3d_streamtubes_globule.png new file mode 100644 index 00000000000..2aeb19a3858 Binary files /dev/null and b/test/image/baselines/gl3d_streamtubes_globule.png differ diff --git a/test/image/mocks/gl3d_streamtubes_aspectmode_cube.json b/test/image/mocks/gl3d_streamtubes_aspectmode_cube.json new file mode 100644 index 00000000000..b6acf398eba --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_aspectmode_cube.json @@ -0,0 +1,63 @@ +{ + "data": [ + { + "x":[0,0.5,1,1.5,1.5,2], + "y":[0,0.5,1,1,1.5,2], + "z":[0,0.5,1,1,0.5,0], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines", + "line": { + "connectiondiameter": 1, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10] + }, + "marker": { + "size": 0, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310, + "scene": { + "aspectmode": "cube", + "aspectratio": {"x": 1, "y": 2, "z": 1} + } + } +} diff --git a/test/image/mocks/gl3d_streamtubes_aspectmode_flatjson b/test/image/mocks/gl3d_streamtubes_aspectmode_flatjson new file mode 100644 index 00000000000..b6acf398eba --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_aspectmode_flatjson @@ -0,0 +1,63 @@ +{ + "data": [ + { + "x":[0,0.5,1,1.5,1.5,2], + "y":[0,0.5,1,1,1.5,2], + "z":[0,0.5,1,1,0.5,0], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines", + "line": { + "connectiondiameter": 1, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10] + }, + "marker": { + "size": 0, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310, + "scene": { + "aspectmode": "cube", + "aspectratio": {"x": 1, "y": 2, "z": 1} + } + } +} diff --git a/test/image/mocks/gl3d_streamtubes_aspectmode_manual.json b/test/image/mocks/gl3d_streamtubes_aspectmode_manual.json new file mode 100644 index 00000000000..49f13d3a174 --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_aspectmode_manual.json @@ -0,0 +1,63 @@ +{ + "data": [ + { + "x":[0,0.5,1,1.5,1.5,2], + "y":[0,0.5,1,1,1.5,2], + "z":[0,0.5,1,1,0.5,0], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines+markers", + "line": { + "connectiondiameter": 0.15, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5] + }, + "marker": { + "size": [0.2,0,0,0,0,0.2], + "colorscale": "Viridis", + "color": [0,1,2,3,4,5], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310, + "scene": { + "aspectmode": "manual", + "aspectratio": {"x": 1, "y": 1, "z": 0.5} + } + } +} diff --git a/test/image/mocks/gl3d_streamtubes_basic.json b/test/image/mocks/gl3d_streamtubes_basic.json new file mode 100644 index 00000000000..7193e63326a --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_basic.json @@ -0,0 +1,60 @@ +{ + "data": [ + { + "x":[-4,-3,-2,-1,0,1,2,3,4,5,7], + "y":[7,5,6,4,2,3,1,3,-1,-4,-2], + "z":[2,3,5,7,4,3,2,1,0,-2,-4], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines+markers+text", + "line": { + "width": [10,16,12,18,20,28,22,24,14,26,30], + "connectionradius": [0.3, 0.3, 0.3, 0.3, 0.3, 0.1, 0.3, 0.3, 0.3, 0.3, 0.3], + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10] + }, + "marker": { + "size": [1,0.032,0.032,0.032,0.032,1.2,0.032,0.032,0.032,0.032,1], + "colorscale": "Viridis", + "color": [10,9,8,7,6,5,4,3,2,1,0], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310 + } +} diff --git a/test/image/mocks/gl3d_streamtubes_closed.json b/test/image/mocks/gl3d_streamtubes_closed.json new file mode 100644 index 00000000000..a08daaf0caf --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_closed.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "x":[1,0,0,1,1,0], + "y":[0,0,1,1,0,0], + "z":[1,1,0,0,1,1], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines+markers+text", + "line": { + "connectiondiameter": 1, + "colorscale": "Viridis", + "color": [0,0,1,1,0,0] + }, + "marker": { + "size": 0, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310 + } +} diff --git a/test/image/mocks/gl3d_streamtubes_flat.json b/test/image/mocks/gl3d_streamtubes_flat.json new file mode 100644 index 00000000000..a48bd218ab9 --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_flat.json @@ -0,0 +1,63 @@ +{ + "data": [ + { + "x":[0,0.5,1,1.5,1.5,2], + "y":[0,0.5,1,1,1.5,2], + "z":[0,0.5,1,1,0.5,0], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines", + "line": { + "connectiondiameter": 0.1, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10] + }, + "marker": { + "size": 0, + "colorscale": "Viridis", + "color": [0,1,2,3,4,5,6,7,8,9,10], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310, + "scene": { + "aspectmode": "manual", + "aspectratio": {"x": 1, "y": 1, "z": 0.3} + } + } +} diff --git a/test/image/mocks/gl3d_streamtubes_globule.json b/test/image/mocks/gl3d_streamtubes_globule.json new file mode 100644 index 00000000000..3d0dfbf79c3 --- /dev/null +++ b/test/image/mocks/gl3d_streamtubes_globule.json @@ -0,0 +1,63 @@ +{ + "data": [ + { + "x":[5, 8, 10, 2, 0, 1, 4, 3, 3, 1, 3, 2, 0, 4, 9, 8, 3, 8, 6, 8, 8, 6, 6, 2, 5, 4, 7, 8, 1, 9, 1, 8, 1, 6, 6, 1, 7, 1, 9, 9, 9, 5, 2, 0, 1, 4, 2, 9, 9, 10], + "y":[2, 0, 2, 6, 5, 1, 9, 1, 0, 1, 9, 8, 5, 7, 3, 0, 10, 10, 6, 8, 0, 5, 3, 2, 8, 5, 5, 0, 9, 5, 6, 10, 1, 5, 2, 6, 9, 1, 4, 8, 4, 3, 9, 4, 6, 4, 7, 8, 9, 0], + "z":[5, 9, 6, 5, 3, 7, 4, 1, 10, 4, 9, 0, 0, 4, 7, 3, 4, 0, 8, 1, 1, 8, 8, 7, 9, 8, 1, 6, 6, 6, 6, 10, 9, 1, 8, 6, 6, 4, 9, 6, 3, 1, 4, 5, 8, 10, 5, 4, 0, 0], + "connectgaps": false, + "text": ["A","B","C","D","E","F","G","H","I","J","K"], + "mode": "lines+markers", + "line": { + "connectiondiameter": [0.3, 0.1, 0.1, 0.2, 0.1, 0.4, 0.4, 0.4, 0.4, 0.3, 0.2, 0.3, 0.3, 0.3, 0.3, 0.4, 0.1, 0.3, 0.2, 0.3, 0.4, 0.3, 0.4, 0.1, 0.2, 0.3, 0.3, 0.1, 0.3, 0.2, 0.3, 0.2, 0.3, 0.1, 0.2, 0.3, 0.2, 0.2, 0.4, 0.2, 0.1, 0.2, 0.2, 0.3, 0.4, 0.2, 0.4, 0.3, 0.1, 0.4], + "colorscale": "Rainbow", + "color": [8, 8, 0, 7, 3, 4, 8, 5, 6, 4, 10, 4, 9, 2, 6, 8, 0, 1, 10, 10, 9, 1, 3, 9, 1, 6, 6, 6, 3, 5, 1, 5, 9, 6, 7, 5, 0, 2, 5, 6, 7, 1, 6, 1, 8, 9, 9, 8, 9, 4] + }, + "marker": { + "size": [0.4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.4], + "colorscale": "Viridis", + "color": [49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + "showscale": true + }, + "type":"streamtube", + "projection": { + "x": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "y": { + "show": true, + "opacity": 1, + "scale": 1 + }, + "z": { + "show": true, + "opacity": 1, + "scale": 1 + } + }, + "flatshading": false, + "lightposition": { + "x": 1000, + "y": 1000, + "z": 1000 + }, + "lighting": { + "ambient": 0.4, + "diffuse": 2, + "specular": 0.5, + "fresnel": 0, + "roughness": 0.4 + } + } + ], + "layout": { + "title": "Basic test for streamtube", + "height":758, + "width":1310, + "scene": { + "aspectmode": "manual", + "aspectratio": {"x": 1, "y": 1, "z": 1} + } + } +}