diff --git a/lib/heatmapgl.js b/lib/heatmapgl.js new file mode 100644 index 00000000000..f00dcd51551 --- /dev/null +++ b/lib/heatmapgl.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/heatmapgl'); diff --git a/package.json b/package.json index 3d50e8afbf5..c692aa632cf 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "fs-extra": "^0.26.2", "gl-error2d": "^1.0.0", "gl-error3d": "^1.0.0", + "gl-heatmap2d": "^1.0.2", "gl-line2d": "^1.2.1", "gl-line3d": "^1.1.0", "gl-mat4": "^1.1.2", diff --git a/src/plots/gl2d/index.js b/src/plots/gl2d/index.js index d8bb69580b9..d446972fe7f 100644 --- a/src/plots/gl2d/index.js +++ b/src/plots/gl2d/index.js @@ -62,7 +62,7 @@ exports.plot = function plotGl2d(gd) { subplotObj._scene2d = scene; } - scene.plot(fullSubplotData, fullLayout, gd.layout); + scene.plot(fullSubplotData, gd.calcdata, fullLayout, gd.layout); } }; diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index fb16dffb638..cc0ed3df658 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -9,7 +9,6 @@ 'use strict'; -var Plots = require('../../plots/plots'); var Axes = require('../../plots/cartesian/axes'); var Fx = require('../../plots/cartesian/graph_interact'); @@ -19,7 +18,6 @@ var createSelectBox = require('gl-select-box'); var createOptions = require('./convert'); var createCamera = require('./camera'); - var htmlToUnicode = require('../../lib/html2unicode'); var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); @@ -301,11 +299,11 @@ proto.destroy = function() { this.stopped = true; }; -proto.plot = function(fullData, fullLayout) { +proto.plot = function(fullData, calcData, fullLayout) { var glplot = this.glplot, pixelRatio = this.pixelRatio; - var i, j; + var i, j, trace; this.fullLayout = fullLayout; this.updateAxes(fullLayout); @@ -322,21 +320,18 @@ proto.plot = function(fullData, fullLayout) { canvas.height = pixelHeight; } - if(!fullData) fullData = []; - else if(!Array.isArray(fullData)) fullData = [fullData]; - // update traces - var traceData, trace; for(i = 0; i < fullData.length; ++i) { - traceData = fullData[i]; - trace = this.traces[traceData.uid]; + var fullTrace = fullData[i], + calcTrace = calcData[i]; + trace = this.traces[fullTrace.uid]; - if(trace) trace.update(traceData); + if(trace) trace.update(fullTrace, calcTrace); else { - var traceModule = Plots.getModule(traceData.type); - trace = traceModule.plot(this, traceData); + trace = fullTrace._module.plot(this, fullTrace, calcTrace); } - this.traces[traceData.uid] = trace; + + this.traces[fullTrace.uid] = trace; } // remove empty traces @@ -344,8 +339,8 @@ proto.plot = function(fullData, fullLayout) { trace_id_loop: for(i = 0; i < traceIds.length; ++i) { - for(j = 0; j < fullData.length; ++j) { - if(fullData[j].uid === traceIds[i]) continue trace_id_loop; + for(j = 0; j < calcData.length; ++j) { + if(calcData[j][0].trace.uid === traceIds[i]) continue trace_id_loop; } trace = this.traces[traceIds[i]]; @@ -480,6 +475,7 @@ proto.draw = function() { var parts = hoverinfo.split('+'); if(parts.indexOf('x') === -1) selection.traceCoord[0] = undefined; if(parts.indexOf('y') === -1) selection.traceCoord[1] = undefined; + if(parts.indexOf('z') === -1) selection.traceCoord[2] = undefined; if(parts.indexOf('text') === -1) selection.textLabel = undefined; if(parts.indexOf('name') === -1) selection.name = undefined; } @@ -489,6 +485,7 @@ proto.draw = function() { y: selection.screenCoord[1], xLabel: this.hoverFormatter('xaxis', selection.traceCoord[0]), yLabel: this.hoverFormatter('yaxis', selection.traceCoord[1]), + zLabel: selection.traceCoord[2], text: selection.textLabel, name: selection.name, color: selection.color diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index 57ee556cd0b..a48af753fe6 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -172,9 +172,11 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { var arrayOut = [], isContour = Plots.traceIs(trace, 'contour'), isHist = Plots.traceIs(trace, 'histogram'), + isGL2D = Plots.traceIs(trace, 'gl2d'), v0, dv, i; + if(Array.isArray(arrayIn) && !isHist && (ax.type!=='category')) { arrayIn = arrayIn.map(ax.d2c); var len = arrayIn.length; @@ -184,7 +186,7 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { // and extend it linearly based on the last two points if(len <= numbricks) { // contour plots only want the centers - if(isContour) arrayOut = arrayIn.slice(0, numbricks); + if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks); else if(numbricks === 1) arrayOut = [arrayIn[0]-0.5,arrayIn[0]+0.5]; else { arrayOut = [1.5*arrayIn[0]-0.5*arrayIn[1]]; @@ -217,7 +219,9 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { else if(isHist || ax.type==='category') v0 = v0In; else v0 = ax.d2c(v0In); - for(i = isContour ? 0 : -0.5; i < numbricks; i++) arrayOut.push(v0 + dv * i); + for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) { + arrayOut.push(v0 + dv * i); + } } return arrayOut; } diff --git a/src/traces/heatmapgl/attributes.js b/src/traces/heatmapgl/attributes.js new file mode 100644 index 00000000000..e00fd49a1ab --- /dev/null +++ b/src/traces/heatmapgl/attributes.js @@ -0,0 +1,14 @@ +/** +* 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 heatmapAttrs = require('../scatter/attributes'); + +module.exports = heatmapAttrs; diff --git a/src/traces/heatmapgl/convert.js b/src/traces/heatmapgl/convert.js new file mode 100644 index 00000000000..1509f916d51 --- /dev/null +++ b/src/traces/heatmapgl/convert.js @@ -0,0 +1,128 @@ +/** +* 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 createHeatmap2D = require('gl-heatmap2d'); + +var str2RGBArray = require('../../lib/str2rgbarray'); + + +function Heatmap(scene, uid) { + this.scene = scene; + this.uid = uid; + + this.name = ''; + this.hoverinfo = 'all'; + + this.xData = []; + this.yData = []; + this.zData = []; + this.textLabels = []; + + this.idToIndex = []; + this.bounds = [0, 0, 0, 0]; + + this.options = { + z: [], + x: [], + y: [], + shape: [0, 0], + colorLevels: [0], + colorValues: [0, 0, 0, 1] + }; + + this.heatmap = createHeatmap2D(scene.glplot, this.options); + this.heatmap._trace = this; +} + +var proto = Heatmap.prototype; + +proto.handlePick = function(pickResult) { + var index = pickResult.pointId, + shape = this.options.shape; + + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: [ + this.options.x[index % shape[0]], + this.options.y[Math.floor(index / shape[0])], + this.options.z[index] + ], + textLabel: this.textLabels[index], + name: this.name, + hoverinfo: this.hoverinfo + }; +}; + +proto.update = function(fullTrace, calcTrace) { + var calcPt = calcTrace[0]; + + this.name = fullTrace.name; + this.hoverinfo = fullTrace.hoverinfo; + + // convert z from 2D -> 1D + var z = calcPt.z; + this.options.z = [].concat.apply([], z); + + var rowLen = z[0].length, + colLen = z.length; + this.options.shape = [rowLen, colLen]; + + this.options.x = calcPt.x; + this.options.y = calcPt.y; + + var colorOptions = convertColorscale(fullTrace); + this.options.colorLevels = colorOptions.colorLevels; + this.options.colorValues = colorOptions.colorValues; + + // convert text from 2D -> 1D + this.textLabels = [].concat.apply([], fullTrace.text); + + this.heatmap.update(this.options); +}; + +proto.dispose = function() { + this.heatmap.dispose(); +}; + +function convertColorscale(fullTrace) { + var scl = fullTrace.colorscale, + zmin = fullTrace.zmin, + zmax = fullTrace.zmax; + + var N = scl.length, + domain = new Array(N), + range = new Array(4 * N); + + for(var i = 0; i < N; i++) { + var si = scl[i]; + var color = str2RGBArray(si[1]); + + domain[i] = zmin + si[0] * (zmax - zmin); + + for(var j = 0; j < 4; j++) { + range[(4 * i) + j] = color[j]; + } + } + + return { + colorLevels: domain, + colorValues: range + }; +} + +function createHeatmap(scene, fullTrace, calcTrace) { + var plot = new Heatmap(scene, fullTrace.uid); + plot.update(fullTrace, calcTrace); + return plot; +} + +module.exports = createHeatmap; diff --git a/src/traces/heatmapgl/index.js b/src/traces/heatmapgl/index.js new file mode 100644 index 00000000000..f8789310196 --- /dev/null +++ b/src/traces/heatmapgl/index.js @@ -0,0 +1,31 @@ +/** +* 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 HeatmapGl = {}; + +HeatmapGl.attributes = require('./attributes'); +HeatmapGl.supplyDefaults = require('../heatmap/defaults'); +HeatmapGl.colorbar = require('../heatmap/colorbar'); + +HeatmapGl.calc = require('../heatmap/calc'); +HeatmapGl.plot = require('./convert'); + +HeatmapGl.moduleType = 'trace'; +HeatmapGl.name = 'heatmapgl'; +HeatmapGl.basePlotModule = require('../../plots/gl2d'); +HeatmapGl.categories = ['gl2d', '2dMap']; +HeatmapGl.meta = { + description: [ + 'WebGL heatmap (beta)' + ].join(' ') +}; + +module.exports = HeatmapGl;