From ba421a8a89fb54568adb3ab0fd8e6e4ae178933c Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 22:56:48 -0500 Subject: [PATCH 01/38] mv topojsons to src/assets/topojson --- src/{geo/geo-assets.js => assets/geo_assets.js} | 0 src/{geo => assets}/topojson/africa_110m.json | 0 src/{geo => assets}/topojson/africa_50m.json | 0 src/{geo => assets}/topojson/asia_110m.json | 0 src/{geo => assets}/topojson/asia_50m.json | 0 src/{geo => assets}/topojson/europe_110m.json | 0 src/{geo => assets}/topojson/europe_50m.json | 0 src/{geo => assets}/topojson/north-america_110m.json | 0 src/{geo => assets}/topojson/north-america_50m.json | 0 src/{geo => assets}/topojson/south-america_110m.json | 0 src/{geo => assets}/topojson/south-america_50m.json | 0 src/{geo => assets}/topojson/usa_110m.json | 0 src/{geo => assets}/topojson/usa_50m.json | 0 src/{geo => assets}/topojson/world_110m.json | 0 src/{geo => assets}/topojson/world_50m.json | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename src/{geo/geo-assets.js => assets/geo_assets.js} (100%) rename src/{geo => assets}/topojson/africa_110m.json (100%) rename src/{geo => assets}/topojson/africa_50m.json (100%) rename src/{geo => assets}/topojson/asia_110m.json (100%) rename src/{geo => assets}/topojson/asia_50m.json (100%) rename src/{geo => assets}/topojson/europe_110m.json (100%) rename src/{geo => assets}/topojson/europe_50m.json (100%) rename src/{geo => assets}/topojson/north-america_110m.json (100%) rename src/{geo => assets}/topojson/north-america_50m.json (100%) rename src/{geo => assets}/topojson/south-america_110m.json (100%) rename src/{geo => assets}/topojson/south-america_50m.json (100%) rename src/{geo => assets}/topojson/usa_110m.json (100%) rename src/{geo => assets}/topojson/usa_50m.json (100%) rename src/{geo => assets}/topojson/world_110m.json (100%) rename src/{geo => assets}/topojson/world_50m.json (100%) diff --git a/src/geo/geo-assets.js b/src/assets/geo_assets.js similarity index 100% rename from src/geo/geo-assets.js rename to src/assets/geo_assets.js diff --git a/src/geo/topojson/africa_110m.json b/src/assets/topojson/africa_110m.json similarity index 100% rename from src/geo/topojson/africa_110m.json rename to src/assets/topojson/africa_110m.json diff --git a/src/geo/topojson/africa_50m.json b/src/assets/topojson/africa_50m.json similarity index 100% rename from src/geo/topojson/africa_50m.json rename to src/assets/topojson/africa_50m.json diff --git a/src/geo/topojson/asia_110m.json b/src/assets/topojson/asia_110m.json similarity index 100% rename from src/geo/topojson/asia_110m.json rename to src/assets/topojson/asia_110m.json diff --git a/src/geo/topojson/asia_50m.json b/src/assets/topojson/asia_50m.json similarity index 100% rename from src/geo/topojson/asia_50m.json rename to src/assets/topojson/asia_50m.json diff --git a/src/geo/topojson/europe_110m.json b/src/assets/topojson/europe_110m.json similarity index 100% rename from src/geo/topojson/europe_110m.json rename to src/assets/topojson/europe_110m.json diff --git a/src/geo/topojson/europe_50m.json b/src/assets/topojson/europe_50m.json similarity index 100% rename from src/geo/topojson/europe_50m.json rename to src/assets/topojson/europe_50m.json diff --git a/src/geo/topojson/north-america_110m.json b/src/assets/topojson/north-america_110m.json similarity index 100% rename from src/geo/topojson/north-america_110m.json rename to src/assets/topojson/north-america_110m.json diff --git a/src/geo/topojson/north-america_50m.json b/src/assets/topojson/north-america_50m.json similarity index 100% rename from src/geo/topojson/north-america_50m.json rename to src/assets/topojson/north-america_50m.json diff --git a/src/geo/topojson/south-america_110m.json b/src/assets/topojson/south-america_110m.json similarity index 100% rename from src/geo/topojson/south-america_110m.json rename to src/assets/topojson/south-america_110m.json diff --git a/src/geo/topojson/south-america_50m.json b/src/assets/topojson/south-america_50m.json similarity index 100% rename from src/geo/topojson/south-america_50m.json rename to src/assets/topojson/south-america_50m.json diff --git a/src/geo/topojson/usa_110m.json b/src/assets/topojson/usa_110m.json similarity index 100% rename from src/geo/topojson/usa_110m.json rename to src/assets/topojson/usa_110m.json diff --git a/src/geo/topojson/usa_50m.json b/src/assets/topojson/usa_50m.json similarity index 100% rename from src/geo/topojson/usa_50m.json rename to src/assets/topojson/usa_50m.json diff --git a/src/geo/topojson/world_110m.json b/src/assets/topojson/world_110m.json similarity index 100% rename from src/geo/topojson/world_110m.json rename to src/assets/topojson/world_110m.json diff --git a/src/geo/topojson/world_50m.json b/src/assets/topojson/world_50m.json similarity index 100% rename from src/geo/topojson/world_50m.json rename to src/assets/topojson/world_50m.json From edb54d355f95930e6fc93161c6d2fdfa8eafe34b Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 23:01:37 -0500 Subject: [PATCH 02/38] put all trace modules into src/traces/*/ --- src/geo/defaults/choropleth.js | 61 --- src/gl3d/defaults/mesh3d.js | 94 ---- src/traces/bars/attributes.js | 69 +++ src/{ => traces/bars}/bars.js | 120 +---- src/traces/bars/layout_attributes.js | 50 ++ src/traces/boxes/attributes.js | 166 +++++++ src/{ => traces/boxes}/boxes.js | 205 +-------- src/traces/boxes/layout_attributes.js | 38 ++ .../choropleth/attributes.js} | 0 src/traces/choropleth/choropleth.js | 26 ++ src/traces/choropleth/defaults.js | 40 ++ .../choropleth/plot.js} | 14 +- src/traces/contour/attributes.js | 88 ++++ src/{ => traces/contour}/contour.js | 90 +--- src/traces/heatmap/attributes.js | 84 ++++ src/{ => traces/heatmap}/heatmap.js | 84 +--- src/traces/histogram/attributes.js | 140 ++++++ src/{ => traces/histogram}/histogram.js | 145 +----- .../mesh3d.js => traces/mesh3d/attributes.js} | 0 .../mesh.js => traces/mesh3d/convert.js} | 13 +- src/traces/mesh3d/defaults.js | 81 ++++ src/traces/mesh3d/mesh3d.js | 17 + src/traces/pie/attributes.js | 250 ++++++++++ src/{ => traces/pie}/pie.js | 250 +--------- src/traces/scatter/attributes.js | 427 +++++++++++++++++ src/{ => traces/scatter}/scatter.js | 428 +----------------- .../scatter3d/attributes.js} | 2 +- .../scatter3d/calc_errors.js} | 0 .../scatter3d/convert.js} | 27 +- .../scatter3d/defaults.js} | 66 +-- src/traces/scatter3d/scatter3d.js | 38 ++ .../scattergeo/attributes.js} | 0 .../scattergeo/defaults.js} | 33 +- .../scattergeo/plot.js} | 11 +- src/traces/scattergeo/scattergeo.js | 27 ++ src/{gl2d => traces}/scattergl/attributes.js | 11 +- src/{gl2d => traces}/scattergl/convert.js | 10 +- src/{gl2d => traces}/scattergl/defaults.js | 10 +- src/{gl2d => traces}/scattergl/scattergl.js | 0 .../surface/attributes.js} | 0 .../surface.js => traces/surface/convert.js} | 15 +- .../surface.js => traces/surface/defaults.js} | 32 +- src/traces/surface/surface.js | 31 ++ 43 files changed, 1686 insertions(+), 1607 deletions(-) delete mode 100644 src/geo/defaults/choropleth.js delete mode 100644 src/gl3d/defaults/mesh3d.js create mode 100644 src/traces/bars/attributes.js rename src/{ => traces/bars}/bars.js (84%) create mode 100644 src/traces/bars/layout_attributes.js create mode 100644 src/traces/boxes/attributes.js rename src/{ => traces/boxes}/boxes.js (77%) create mode 100644 src/traces/boxes/layout_attributes.js rename src/{geo/attributes/choropleth.js => traces/choropleth/attributes.js} (100%) create mode 100644 src/traces/choropleth/choropleth.js create mode 100644 src/traces/choropleth/defaults.js rename src/{geo/plot/choropleth.js => traces/choropleth/plot.js} (94%) create mode 100644 src/traces/contour/attributes.js rename src/{ => traces/contour}/contour.js (91%) create mode 100644 src/traces/heatmap/attributes.js rename src/{ => traces/heatmap}/heatmap.js (92%) create mode 100644 src/traces/histogram/attributes.js rename src/{ => traces/histogram}/histogram.js (76%) rename src/{gl3d/attributes/mesh3d.js => traces/mesh3d/attributes.js} (100%) rename src/{gl3d/convert/mesh.js => traces/mesh3d/convert.js} (93%) create mode 100644 src/traces/mesh3d/defaults.js create mode 100644 src/traces/mesh3d/mesh3d.js create mode 100644 src/traces/pie/attributes.js rename src/{ => traces/pie}/pie.js (82%) create mode 100644 src/traces/scatter/attributes.js rename src/{ => traces/scatter}/scatter.js (69%) rename src/{gl3d/attributes/scatter3d.js => traces/scatter3d/attributes.js} (98%) rename src/{gl3d/lib/calc-errors.js => traces/scatter3d/calc_errors.js} (100%) rename src/{gl3d/convert/scatter.js => traces/scatter3d/convert.js} (95%) rename src/{gl3d/defaults/scatter3d.js => traces/scatter3d/defaults.js} (53%) create mode 100644 src/traces/scatter3d/scatter3d.js rename src/{geo/attributes/scattergeo.js => traces/scattergeo/attributes.js} (100%) rename src/{geo/defaults/scattergeo.js => traces/scattergeo/defaults.js} (52%) rename src/{geo/plot/scattergeo.js => traces/scattergeo/plot.js} (96%) create mode 100644 src/traces/scattergeo/scattergeo.js rename src/{gl2d => traces}/scattergl/attributes.js (91%) rename src/{gl2d => traces}/scattergl/convert.js (98%) rename src/{gl2d => traces}/scattergl/defaults.js (81%) rename src/{gl2d => traces}/scattergl/scattergl.js (100%) rename src/{gl3d/attributes/surface.js => traces/surface/attributes.js} (100%) rename src/{gl3d/convert/surface.js => traces/surface/convert.js} (97%) rename src/{gl3d/defaults/surface.js => traces/surface/defaults.js} (63%) create mode 100644 src/traces/surface/surface.js diff --git a/src/geo/defaults/choropleth.js b/src/geo/defaults/choropleth.js deleted file mode 100644 index fc33e060793..00000000000 --- a/src/geo/defaults/choropleth.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var Choropleth = module.exports = {}; - -Plotly.Plots.register(Choropleth, 'choropleth', ['geo', 'noOpacity'], { - description: [ - 'The data that describes the choropleth value-to-color mapping', - 'is set in `z`.', - 'The geographic locations corresponding to each value in `z`', - 'are set in `locations`.' - ].join(' ') -}); - -Choropleth.attributes = require('../attributes/choropleth'); - -Choropleth.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { - var locations, len, z; - - function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, - Choropleth.attributes, attr, dflt); - } - - locations = coerce('locations'); - if(locations) len = locations.length; - if(!locations || !len) { - traceOut.visible = false; - return; - } - - z = coerce('z'); - if(!Array.isArray(z)) { - traceOut.visible = false; - return; - } - - if(z.length > len) traceOut.z = z.slice(0, len); - - coerce('locationmode'); - - coerce('text'); - - coerce('marker.line.color'); - coerce('marker.line.width'); - - Plotly.Colorscale.handleDefaults( - traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} - ); - - coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined); -}; - -Choropleth.colorbar = Plotly.Colorbar.traceColorbar; - -Choropleth.calc = function(gd, trace) { - - Plotly.Colorscale.calc(trace, trace.z, '', 'z'); - -}; diff --git a/src/gl3d/defaults/mesh3d.js b/src/gl3d/defaults/mesh3d.js deleted file mode 100644 index 200633fc1f9..00000000000 --- a/src/gl3d/defaults/mesh3d.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var Mesh3D = module.exports = {}; - -Plotly.Plots.register(Mesh3D, 'mesh3d', ['gl3d'], { - description: [ - '' - ].join(' ') -}); - -Mesh3D.attributes = require('../attributes/mesh3d'); - -Mesh3D.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { - var self = this; - function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, self.attributes, attr, dflt); - } - - //Read in face/vertex properties - function readComponents(array) { - var ret = array.map(function(attr) { - var result = coerce(attr); - if(result && Array.isArray(result)) { - return result; - } - return null; - }); - return ret.every(function(x) { - return x && x.length === ret[0].length; - }) && ret; - } - - var coords = readComponents(['x', 'y', 'z']); - var indices = readComponents(['i', 'j', 'k']); - - if(!coords) { - traceOut.visible = false; - return; - } - - if(indices) { - //Otherwise, convert all face indices to ints - indices.forEach(function(index) { - for(var i=0; i len) traceOut.z = z.slice(0, len); + + coerce('locationmode'); + + coerce('text'); + + coerce('marker.line.color'); + coerce('marker.line.width'); + + Plotly.Colorscale.handleDefaults( + traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} + ); + + coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined); +}; diff --git a/src/geo/plot/choropleth.js b/src/traces/choropleth/plot.js similarity index 94% rename from src/geo/plot/choropleth.js rename to src/traces/choropleth/plot.js index 25ab0384f6f..a2447b5d4e0 100644 --- a/src/geo/plot/choropleth.js +++ b/src/traces/choropleth/plot.js @@ -1,11 +1,13 @@ 'use strict'; -var Plotly = require('../../plotly'), - d3 = require('d3'), - params = require('../lib/params'), - getTopojsonFeatures = require('../lib/topojson-utils').getTopojsonFeatures, - locationToFeature = require('../lib/location-utils').locationToFeature, - arrayToCalcItem = require('../lib/array-to-calc-item'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); + +var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; +var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; +var arrayToCalcItem = require('../../lib/array_to_calc_item'); + +var constants = require('../../constants/geo_constants'); var plotChoropleth = module.exports = {}; diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js new file mode 100644 index 00000000000..52e77eb4956 --- /dev/null +++ b/src/traces/contour/attributes.js @@ -0,0 +1,88 @@ +var Plotly = require('../../plotly'); + +var extendFlat = Plotly.Lib.extendFlat; +var scatterLineAttrs = Plotly.Scatter.attributes.line; + +module.exports = { + _composedModules: { // composed module coupling + 'contour': 'Heatmap', + 'histogram2dcontour': 'Heatmap' + }, + autocontour: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether of not the contour level attributes at', + 'picked by an algorithm.', + 'If *true*, the number of contour levels can be set in `ncontours`.', + 'If *false*, set the contour level attributes in `contours`.' + ].join(' ') + }, + ncontours: { + valType: 'integer', + dflt: 0, + role: 'style', + description: 'Sets the number of contour levels.' + }, + contours: { + start: { + valType: 'number', + dflt: null, + role: 'style', + description: 'Sets the starting contour level value.' + }, + end: { + valType: 'number', + dflt: null, + role: 'style', + description: 'Sets the end contour level value.' + }, + size: { + valType: 'number', + dflt: null, + role: 'style', + description: 'Sets the step between each contour level.' + }, + coloring: { + valType: 'enumerated', + values: ['fill', 'heatmap', 'lines', 'none'], + dflt: 'fill', + role: 'style', + description: [ + 'Determines the coloring method showing the contour values.', + 'If *fill*, coloring is done evenly between each contour level', + 'If *heatmap*, a heatmap gradient is coloring is applied', + 'between each contour level.', + 'If *lines*, coloring is done on the contour lines.', + 'If *none*, no coloring is applied on this trace.' + ].join(' ') + }, + showlines: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the contour lines are drawn.', + 'Has only an effect if `contours.coloring` is set to *fill*.' + ].join(' ') + } + }, + line: { + color: extendFlat({}, scatterLineAttrs.color, { + description: [ + 'Sets the color of the contour level.', + 'Has no if `contours.coloring` is set to *lines*.' + ].join(' ') + }), + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + smoothing: extendFlat({}, scatterLineAttrs.smoothing, { + description: [ + 'Sets the amount of smoothing for the contour lines,', + 'where *0* corresponds to no smoothing.' + ].join(' ') + }) + } +}; + diff --git a/src/contour.js b/src/traces/contour/contour.js similarity index 91% rename from src/contour.js rename to src/traces/contour/contour.js index ec132684f24..d5681aecf6c 100644 --- a/src/contour.js +++ b/src/traces/contour/contour.js @@ -1,7 +1,7 @@ 'use strict'; -var Plotly = require('./plotly'), - d3 = require('d3'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); var contour = module.exports = {}; @@ -33,91 +33,7 @@ Plotly.Plots.register(contour, 'histogram2dcontour', ].join(' ') }); -var scatterLineAttrs = Plotly.Scatter.attributes.line, - extendFlat = Plotly.Lib.extendFlat; - -contour.attributes = { - _composedModules: { // composed module coupling - 'contour': 'Heatmap', - 'histogram2dcontour': 'Heatmap' - }, - autocontour: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether of not the contour level attributes at', - 'picked by an algorithm.', - 'If *true*, the number of contour levels can be set in `ncontours`.', - 'If *false*, set the contour level attributes in `contours`.' - ].join(' ') - }, - ncontours: { - valType: 'integer', - dflt: 0, - role: 'style', - description: 'Sets the number of contour levels.' - }, - contours: { - start: { - valType: 'number', - dflt: null, - role: 'style', - description: 'Sets the starting contour level value.' - }, - end: { - valType: 'number', - dflt: null, - role: 'style', - description: 'Sets the end contour level value.' - }, - size: { - valType: 'number', - dflt: null, - role: 'style', - description: 'Sets the step between each contour level.' - }, - coloring: { - valType: 'enumerated', - values: ['fill', 'heatmap', 'lines', 'none'], - dflt: 'fill', - role: 'style', - description: [ - 'Determines the coloring method showing the contour values.', - 'If *fill*, coloring is done evenly between each contour level', - 'If *heatmap*, a heatmap gradient is coloring is applied', - 'between each contour level.', - 'If *lines*, coloring is done on the contour lines.', - 'If *none*, no coloring is applied on this trace.' - ].join(' ') - }, - showlines: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the contour lines are drawn.', - 'Has only an effect if `contours.coloring` is set to *fill*.' - ].join(' ') - } - }, - line: { - color: extendFlat({}, scatterLineAttrs.color, { - description: [ - 'Sets the color of the contour level.', - 'Has no if `contours.coloring` is set to *lines*.' - ].join(' ') - }), - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - smoothing: extendFlat({}, scatterLineAttrs.smoothing, { - description: [ - 'Sets the amount of smoothing for the contour lines,', - 'where *0* corresponds to no smoothing.' - ].join(' ') - }) - } -}; +contour.attributes = require('./attributes'); contour.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js new file mode 100644 index 00000000000..13ed9719d1d --- /dev/null +++ b/src/traces/heatmap/attributes.js @@ -0,0 +1,84 @@ +var Plotly = require('../../plotly'); + +var scatterAttrs = Plotly.Scatter.attributes; +var traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; + +module.exports = { + z: { + valType: 'data_array', + description: 'Sets the z data.' + }, + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, + text: { + valType: 'data_array', + description: 'Sets the text elements associated with each z value.' + }, + transpose: { + valType: 'boolean', + dflt: false, + role: 'info', + description: 'Transposes the z data.' + }, + xtype: { + valType: 'enumerated', + values: ['array', 'scaled'], + role: 'info', + description: [ + 'If *array*, the heatmap\'s x coordinates are given by *x*', + '(the default behavior when `x` is provided).', + 'If *scaled*, the heatmap\'s x coordinates are given by *x0* and *dx*', + '(the default behavior when `x` is not provided).' + ].join(' ') + }, + ytype: { + valType: 'enumerated', + values: ['array', 'scaled'], + role: 'info', + description: [ + 'If *array*, the heatmap\'s y coordinates are given by *y*', + '(the default behavior when `y` is provided)', + 'If *scaled*, the heatmap\'s y coordinates are given by *y0* and *dy*', + '(the default behavior when `y` is not provided)' + ].join(' ') + }, + zauto: traceColorbarAttrs.zauto, + zmin: traceColorbarAttrs.zmin, + zmax: traceColorbarAttrs.zmax, + colorscale: traceColorbarAttrs.colorscale, + autocolorscale: Plotly.Lib.extendFlat({}, traceColorbarAttrs.autocolorscale, + {dflt: false}), + reversescale: traceColorbarAttrs.reversescale, + showscale: traceColorbarAttrs.showscale, + zsmooth: { + valType: 'enumerated', + values: ['fast', 'best', false], + dflt: false, + role: 'style', + description: [ + 'Picks a smoothing algorithm use to smooth `z` data.' + ].join(' ') + }, + connectgaps: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.' + ].join(' ') + }, + _nestedModules: { // nested module coupling + 'colorbar': 'Colorbar' + }, + _composedModules: { // composed module coupling + 'histogram2d': 'Histogram', + 'histogram2dcontour': 'Histogram' + } +}; + diff --git a/src/heatmap.js b/src/traces/heatmap/heatmap.js similarity index 92% rename from src/heatmap.js rename to src/traces/heatmap/heatmap.js index fe70cfd216d..01645956c68 100644 --- a/src/heatmap.js +++ b/src/traces/heatmap/heatmap.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var tinycolor = require('tinycolor2'); var isNumeric = require('fast-isnumeric'); @@ -34,87 +34,7 @@ Plotly.Plots.register(heatmap, 'heatmap', ['cartesian', '2dMap'], { ].join(' ') }); -var scatterAttrs = Plotly.Scatter.attributes, - traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; - -heatmap.attributes = { - z: { - valType: 'data_array', - description: 'Sets the z data.' - }, - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, - text: { - valType: 'data_array', - description: 'Sets the text elements associated with each z value.' - }, - transpose: { - valType: 'boolean', - dflt: false, - role: 'info', - description: 'Transposes the z data.' - }, - xtype: { - valType: 'enumerated', - values: ['array', 'scaled'], - role: 'info', - description: [ - 'If *array*, the heatmap\'s x coordinates are given by *x*', - '(the default behavior when `x` is provided).', - 'If *scaled*, the heatmap\'s x coordinates are given by *x0* and *dx*', - '(the default behavior when `x` is not provided).' - ].join(' ') - }, - ytype: { - valType: 'enumerated', - values: ['array', 'scaled'], - role: 'info', - description: [ - 'If *array*, the heatmap\'s y coordinates are given by *y*', - '(the default behavior when `y` is provided)', - 'If *scaled*, the heatmap\'s y coordinates are given by *y0* and *dy*', - '(the default behavior when `y` is not provided)' - ].join(' ') - }, - zauto: traceColorbarAttrs.zauto, - zmin: traceColorbarAttrs.zmin, - zmax: traceColorbarAttrs.zmax, - colorscale: traceColorbarAttrs.colorscale, - autocolorscale: Plotly.Lib.extendFlat({}, traceColorbarAttrs.autocolorscale, - {dflt: false}), - reversescale: traceColorbarAttrs.reversescale, - showscale: traceColorbarAttrs.showscale, - zsmooth: { - valType: 'enumerated', - values: ['fast', 'best', false], - dflt: false, - role: 'style', - description: [ - 'Picks a smoothing algorithm use to smooth `z` data.' - ].join(' ') - }, - connectgaps: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data are filled in.' - ].join(' ') - }, - _nestedModules: { // nested module coupling - 'colorbar': 'Colorbar' - }, - _composedModules: { // composed module coupling - 'histogram2d': 'Histogram', - 'histogram2dcontour': 'Histogram' - } -}; +heatmap.attributes = require('./attributes'); heatmap.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { var isContour = Plotly.Plots.traceIs(traceOut, 'contour'); diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js new file mode 100644 index 00000000000..0369b8d68d9 --- /dev/null +++ b/src/traces/histogram/attributes.js @@ -0,0 +1,140 @@ +'use strict'; + +var Plotly = require('../../plotly'); + +var barAttrs = Plotly.Bars.attributes; + + +module.exports = { + x: { + valType: 'data_array', + description: [ + 'Sets the sample data to be binned on the x axis.' + ].join(' ') + }, + y: { + valType: 'data_array', + description: [ + 'Sets the sample data to be binned on the y axis.' + ].join(' ') + }, + z: { + valType: 'data_array', + description: 'Sets the aggregation data.' + }, + marker: { + color: { // FIXME this overrides 'bar' + valType: 'data_array', + arrayOk: undefined + } + }, + orientation: barAttrs.orientation, + histfunc: { + valType: 'enumerated', + values: ['count', 'sum', 'avg', 'min', 'max'], + role: 'style', + dflt: 'count', + description: [ + 'Specifies the binning function used for this histogram trace.', + + 'If *count*, the histogram values are computed by counting the', + 'number of values lying inside each bin.', + + 'If *sum*, *avg*, *min*, *max*,', + 'the histogram values are computed using', + 'the sum, the average, the minimum or the maximum', + 'of the values lying inside each bin respectively.' + ].join(' ') + }, + histnorm: { + valType: 'enumerated', + values: ['', 'percent', 'probability', 'density', 'probability density'], + dflt: '', + role: 'style', + description: [ + 'Specifies the type of normalization used for this histogram trace.', + + 'If **, the span of each bar corresponds to the number of', + 'occurrences (i.e. the number of data points lying inside the bins).', + + 'If *percent*, the span of each bar corresponds to the percentage', + 'of occurrences with respect to the total number of sample points', + '(here, the sum of all bin area equals 100%).', + + 'If *density*, the span of each bar corresponds to the number of', + 'occurrences in a bin divided by the size of the bin interval', + '(here, the sum of all bin area equals the', + 'total number of sample points).', + + 'If *probability density*, the span of each bar corresponds to the', + 'probability that an event will fall into the corresponding bin', + '(here, the sum of all bin area equals 1).' + ].join(' ') + }, + autobinx: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the x axis bin attributes are picked', + 'by an algorithm.' + ].join(' ') + }, + nbinsx: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: 'Sets the number of x axis bins.' + }, + xbins: makeBinsAttr('x'), + autobiny: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the y axis bin attributes are picked', + 'by an algorithm.' + ].join(' ') + }, + nbinsy: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: 'Sets the number of y axis bins.' + }, + ybins: makeBinsAttr('y') +}; + +function makeBinsAttr(axLetter) { + return { + start: { + valType: 'number', + dflt: null, + role: 'style', + description: [ + 'Sets the starting value for the', axLetter, + 'axis bins.' + ].join(' ') + }, + end: { + valType: 'number', + dflt: null, + role: 'style', + description: [ + 'Sets the end value for the', axLetter, + 'axis bins.' + ].join(' ') + }, + size: { + valType: 'any', // for date axes + dflt: 1, + role: 'style', + description: [ + 'Sets the step in-between value each', axLetter, + 'axis bin.' + ].join(' ') + } + }; +} diff --git a/src/histogram.js b/src/traces/histogram/histogram.js similarity index 76% rename from src/histogram.js rename to src/traces/histogram/histogram.js index d24d687388e..7dfcf1c4a72 100644 --- a/src/histogram.js +++ b/src/traces/histogram/histogram.js @@ -1,9 +1,13 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var isNumeric = require('fast-isnumeric'); -var barAttrs = Plotly.Bars.attributes; +/** + * Histogram has its own calc function, + * but uses Bars.plot to display + * and Bars.setPositions for stacking and grouping + */ var histogram = module.exports = {}; /** @@ -37,142 +41,7 @@ Plotly.Plots.register(Plotly.Heatmap, 'histogram2d', ].join(' ') }); -// histogram has its own calc function, -// but uses Bars.plot to display -// and Bars.setPositions for stacking and grouping -function makeBinsAttr(axLetter) { - return { - start: { - valType: 'number', - dflt: null, - role: 'style', - description: [ - 'Sets the starting value for the', axLetter, - 'axis bins.' - ].join(' ') - }, - end: { - valType: 'number', - dflt: null, - role: 'style', - description: [ - 'Sets the end value for the', axLetter, - 'axis bins.' - ].join(' ') - }, - size: { - valType: 'any', // for date axes - dflt: 1, - role: 'style', - description: [ - 'Sets the step in-between value each', axLetter, - 'axis bin.' - ].join(' ') - } - }; -} - -histogram.attributes = { - x: { - valType: 'data_array', - description: [ - 'Sets the sample data to be binned on the x axis.' - ].join(' ') - }, - y: { - valType: 'data_array', - description: [ - 'Sets the sample data to be binned on the y axis.' - ].join(' ') - }, - z: { - valType: 'data_array', - description: 'Sets the aggregation data.' - }, - marker: { - color: { // FIXME this overrides 'bar' - valType: 'data_array', - arrayOk: undefined - } - }, - orientation: barAttrs.orientation, - histfunc: { - valType: 'enumerated', - values: ['count', 'sum', 'avg', 'min', 'max'], - role: 'style', - dflt: 'count', - description: [ - 'Specifies the binning function used for this histogram trace.', - - 'If *count*, the histogram values are computed by counting the', - 'number of values lying inside each bin.', - - 'If *sum*, *avg*, *min*, *max*,', - 'the histogram values are computed using', - 'the sum, the average, the minimum or the maximum', - 'of the values lying inside each bin respectively.' - ].join(' ') - }, - histnorm: { - valType: 'enumerated', - values: ['', 'percent', 'probability', 'density', 'probability density'], - dflt: '', - role: 'style', - description: [ - 'Specifies the type of normalization used for this histogram trace.', - - 'If **, the span of each bar corresponds to the number of', - 'occurrences (i.e. the number of data points lying inside the bins).', - - 'If *percent*, the span of each bar corresponds to the percentage', - 'of occurrences with respect to the total number of sample points', - '(here, the sum of all bin area equals 100%).', - - 'If *density*, the span of each bar corresponds to the number of', - 'occurrences in a bin divided by the size of the bin interval', - '(here, the sum of all bin area equals the', - 'total number of sample points).', - - 'If *probability density*, the span of each bar corresponds to the', - 'probability that an event will fall into the corresponding bin', - '(here, the sum of all bin area equals 1).' - ].join(' ') - }, - autobinx: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the x axis bin attributes are picked', - 'by an algorithm.' - ].join(' ') - }, - nbinsx: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: 'Sets the number of x axis bins.' - }, - xbins: makeBinsAttr('x'), - autobiny: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the y axis bin attributes are picked', - 'by an algorithm.' - ].join(' ') - }, - nbinsy: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: 'Sets the number of y axis bins.' - }, - ybins: makeBinsAttr('y') -}; +histogram.attributes = require('./attributes'); histogram.supplyDefaults = function(traceIn, traceOut) { function coerce(attr, dflt) { diff --git a/src/gl3d/attributes/mesh3d.js b/src/traces/mesh3d/attributes.js similarity index 100% rename from src/gl3d/attributes/mesh3d.js rename to src/traces/mesh3d/attributes.js diff --git a/src/gl3d/convert/mesh.js b/src/traces/mesh3d/convert.js similarity index 93% rename from src/gl3d/convert/mesh.js rename to src/traces/mesh3d/convert.js index 9335a303d8f..9e5ec078782 100644 --- a/src/gl3d/convert/mesh.js +++ b/src/traces/mesh3d/convert.js @@ -1,11 +1,12 @@ 'use strict'; -var createMesh = require('gl-mesh3d'), - str2RgbaArray = require('../lib/str2rgbarray'), - tinycolor = require('tinycolor2'), - triangulate = require('delaunay-triangulate'), - alphaShape = require('alpha-shape'), - convexHull = require('convex-hull'); +var createMesh = require('gl-mesh3d'); +var tinycolor = require('tinycolor2'); +var triangulate = require('delaunay-triangulate'); +var alphaShape = require('alpha-shape'); +var convexHull = require('convex-hull'); + +var str2RgbaArray = require('../../lib/str2rgbarray'); function Mesh3DTrace(scene, mesh, uid) { diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js new file mode 100644 index 00000000000..eee73b80618 --- /dev/null +++ b/src/traces/mesh3d/defaults.js @@ -0,0 +1,81 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var Mesh3D = require('./mesh3d'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Plotly.Lib.coerce(traceIn, traceOut, Mesh3D.attributes, attr, dflt); + } + + // read in face/vertex properties + function readComponents(array) { + var ret = array.map(function(attr) { + var result = coerce(attr); + + if(result && Array.isArray(result)) return result; + return null; + }); + + return ret.every(function(x) { + return x && x.length === ret[0].length; + }) && ret; + } + + var coords = readComponents(['x', 'y', 'z']); + var indices = readComponents(['i', 'j', 'k']); + + if(!coords) { + traceOut.visible = false; + return; + } + + if(indices) { + // otherwise, convert all face indices to ints + indices.forEach(function(index) { + for(var i=0; i just get lines scatter.PTS_LINESONLY = 20; -scatter.attributes = { - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' - }, - x0: { - valType: 'any', - dflt: 0, - role: 'info', - description: [ - 'Alternate to `x`.', - 'Builds a linear space of x coordinates.', - 'Use with `dx`', - 'where `x0` is the starting coordinate and `dx` the step.' - ].join(' ') - }, - dx: { - valType: 'number', - dflt: 1, - role: 'info', - description: [ - 'Sets the x coordinate step.', - 'See `x0` for more info.' - ].join(' ') - }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' - }, - y0: { - valType: 'any', - dflt: 0, - role: 'info', - description: [ - 'Alternate to `y`.', - 'Builds a linear space of y coordinates.', - 'Use with `dy`', - 'where `y0` is the starting coordinate and `dy` the step.' - ].join(' ') - }, - dy: { - valType: 'number', - dflt: 1, - role: 'info', - description: [ - 'Sets the y coordinate step.', - 'See `y0` for more info.' - ].join(' ') - }, - text: { - valType: 'string', - role: 'info', - dflt: '', - arrayOk: true, - description: [ - 'Sets text elements associated with each (x,y) pair.', - '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) coordinates.' - ].join(' ') - }, - mode: { - valType: 'flaglist', - flags: ['lines','markers','text'], - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.', - 'If the provided `mode` includes *text* then the `text` elements', - 'appear at the coordinates. Otherwise, the `text` elements', - 'appear on hover.', - 'If there are less than ' + scatter.PTS_LINESONLY + ' points,', - 'then the default is *lines+markers*. Otherwise, *lines*.' - ].join(' ') - }, - line: { - color: { - valType: 'color', - role: 'style', - description: 'Sets the line color.' - }, - width: { - valType: 'number', - min: 0, - dflt: 2, - role: 'style', - description: 'Sets the line width (in px).' - }, - shape: { - valType: 'enumerated', - values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'], - dflt: 'linear', - role: 'style', - description: [ - 'Determines the line shape.', - 'With *spline* the lines are drawn using spline interpolation.', - 'The other available values correspond to step-wise line shapes.' - ].join(' ') - }, - smoothing: { - valType: 'number', - min: 0, - max: 1.3, - dflt: 1, - role: 'style', - description: [ - 'Has only an effect if `shape` is set to *spline*', - 'Sets the amount of smoothing.', - '*0* corresponds to no smoothing (equivalent to a *linear* shape).' - ].join(' ') - }, - dash: { - valType: 'string', - // string type usually doesn't take values... this one should really be - // a special type or at least a special coercion function, from the GUI - // you only get these values but elsewhere the user can supply a list of - // dash lengths in px, and it will be honored - values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'], - dflt: 'solid', - role: 'style', - description: [ - 'Sets the style of the lines. Set to a dash string type', - 'or a dash length in px.' - ].join(' ') - } - }, - connectgaps: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the provided data arrays are connected.' - ].join(' ') - }, - fill: { - valType: 'enumerated', - values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx'], - dflt: 'none', - role: 'style', - description: [ - 'Sets the area to fill with a solid color.', - 'Use with `fillcolor`.' - ].join(' ') - }, - fillcolor: { - valType: 'color', - role: 'style', - description: 'Sets the fill color.' - }, - marker: { - symbol: { - valType: 'enumerated', - values: Plotly.Drawing.symbolList, - dflt: 'circle', - arrayOk: true, - role: 'style', - description: [ - 'Sets the marker symbol type.', - 'Adding 100 is equivalent to appending *-open* to a symbol name.', - 'Adding 200 is equivalent to appending *-dot* to a symbol name.', - 'Adding 300 is equivalent to appending *-open-dot*', - 'or *dot-open* to a symbol name.' - ].join(' ') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - arrayOk: true, - role: 'style', - description: 'Sets the marker opacity.' - }, - size: { - valType: 'number', - min: 0, - dflt: 6, - arrayOk: true, - role: 'style', - description: 'Sets the marker size (in px).' - }, - color: { - valType: 'color', - arrayOk: true, - role: 'style', - description: 'Sets the marker color.' - }, - maxdisplayed: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Sets a maximum number of points to be drawn on the graph.', - '*0* corresponds to no limit.' - ].join(' ') - }, - sizeref: { - valType: 'number', - dflt: 1, - role: 'style', - description: [ - 'Has only an effect if `marker.size` is set to a numerical array.', - 'Sets the scale factor used to determine the rendered size of', - 'marker points. Use with `sizemin` and `sizemode`.' - ].join(' ') - }, - sizemin: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Has only an effect if `marker.size` is set to a numerical array.', - 'Sets the minimum size (in px) of the rendered marker points.' - ].join(' ') - }, - sizemode: { - valType: 'enumerated', - values: ['diameter', 'area'], - dflt: 'diameter', - role: 'info', - description: [ - 'Has only an effect if `marker.size` is set to a numerical array.', - 'Sets the rule for which the data in `size` is converted', - 'to pixels.' - ].join(' ') - }, - colorscale: { - valType: 'colorscale', - role: 'style', - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Sets the colorscale.' - ].join(' ') - }, - cauto: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Determines the whether or not the color domain is computed', - 'automatically.' - ].join(' ') - }, - cmax: { - valType: 'number', - dflt: null, - role: 'info', - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Sets the upper bound of the color domain.' - ].join(' ') - }, - cmin: { - valType: 'number', - dflt: null, - role: 'info', - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Sets the lower bound of the color domain.' - ].join(' ') - }, - autocolorscale: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Determines whether or not the colorscale is picked using', - 'values inside `marker.color`.' - ].join(' ') - }, - reversescale: { - valType: 'boolean', - role: 'style', - dflt: false, - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Reverses the colorscale.' - ].join(' ') - }, - showscale: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Has only an effect if `marker.color` is set to a numerical array.', - 'Determines whether or not a colorbar is displayed.' - ].join(' ') - }, - line: { - color: { - valType: 'color', - arrayOk: true, - role: 'style', - description: 'Sets the color of the lines bounding the marker points.' - }, - width: { - valType: 'number', - min: 0, - arrayOk: true, - role: 'style', - description: 'Sets the width (in px) of the lines bounding the marker points.' - }, - colorscale: { - valType: 'colorscale', - role: 'style', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Sets the colorscale.' - ].join(' ') - }, - cauto: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Determines the whether or not the color domain is computed', - 'with respect to the input data.' - ].join(' ') - }, - cmax: { - valType: 'number', - dflt: null, - role: 'info', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Sets the upper bound of the color domain.' - ].join(' ') - }, - cmin: { - valType: 'number', - dflt: null, - role: 'info', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Sets the lower bound of the color domain.' - ].join(' ') - }, - autocolorscale: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Determines whether or not the colorscale is picked using', - 'the sign of values inside `marker.line.color`.' - ].join(' ') - }, - reversescale: { - valType: 'boolean', - dflt: false, - role: 'style', - description: [ - 'Has only an effect if `marker.line.color` is set to a numerical array.', - 'Reverses the colorscale.' - ].join(' ') - } - } - }, - textposition: { - valType: 'enumerated', - values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right' - ], - dflt: 'middle center', - arrayOk: true, - role: 'style', - description: [ - 'Sets the positions of the `text` elements', - 'with respects to the (x,y) coordinates.' - ].join(' ') - }, - textfont: { - family: { - valType: 'string', - role: 'style', - noBlank: true, - strict: true, - arrayOk: true - }, - size: { - valType: 'number', - role: 'style', - min: 1, - arrayOk: true - }, - color: { - valType: 'color', - role: 'style', - arrayOk: true - }, - description: 'Sets the text font.' - }, - r: { - valType: 'data_array', - description: [ - 'For polar chart only.', - 'Sets the radial coordinates.' - ].join('') - }, - t: { - valType: 'data_array', - description: [ - 'For polar chart only.', - 'Sets the angular coordinates.' - ].join('') - }, - _nestedModules: { // nested module coupling - 'error_y': 'ErrorBars', - 'error_x': 'ErrorBars', - 'marker.colorbar': 'Colorbar' - } -}; +scatter.attributes = require('./attributes'); scatter.handleXYDefaults = function(traceIn, traceOut, coerce) { var len, @@ -1280,7 +858,7 @@ scatter.getTraceColor = function(trace, di) { lc : trace.fillcolor; } } -} +}; scatter.hoverPoints = function(pointData, xval, yval, hovermode) { var cd = pointData.cd, diff --git a/src/gl3d/attributes/scatter3d.js b/src/traces/scatter3d/attributes.js similarity index 98% rename from src/gl3d/attributes/scatter3d.js rename to src/traces/scatter3d/attributes.js index 9f22ce111b7..fc8c042f78a 100644 --- a/src/gl3d/attributes/scatter3d.js +++ b/src/traces/scatter3d/attributes.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var MARKER_SYMBOLS = require('../lib/markers.json'); +var MARKER_SYMBOLS = require('../../constants/gl_markers.json'); var scatterAttrs = Plotly.Scatter.attributes, scatterLineAttrs = scatterAttrs.line, diff --git a/src/gl3d/lib/calc-errors.js b/src/traces/scatter3d/calc_errors.js similarity index 100% rename from src/gl3d/lib/calc-errors.js rename to src/traces/scatter3d/calc_errors.js diff --git a/src/gl3d/convert/scatter.js b/src/traces/scatter3d/convert.js similarity index 95% rename from src/gl3d/convert/scatter.js rename to src/traces/scatter3d/convert.js index c65ebf4bfb4..8d98e54e573 100644 --- a/src/gl3d/convert/scatter.js +++ b/src/traces/scatter3d/convert.js @@ -1,16 +1,21 @@ 'use strict'; -var createLinePlot = require('gl-line3d'), - createScatterPlot = require('gl-scatter3d'), - createErrorBars = require('gl-error3d'), - createMesh = require('gl-mesh3d'), - triangulate = require('delaunay-triangulate'), - str2RgbaArray = require('../lib/str2rgbarray'), - formatColor = require('../lib/format-color'), - calculateError = require('../lib/calc-errors'), - DASH_PATTERNS = require('../lib/dashes.json'), - MARKER_SYMBOLS = require('../lib/markers.json'), - Plotly = require('../../plotly'); +var Plotly = require('../../plotly'); + +var createLinePlot = require('gl-line3d'); +var createScatterPlot = require('gl-scatter3d'); +var createErrorBars = require('gl-error3d'); +var createMesh = require('gl-mesh3d'); +var triangulate = require('delaunay-triangulate'); + +var str2RgbaArray = require('../../lib/str2rgbarray'); +var formatColor = require('../../lib/gl_format_color'); + +var DASH_PATTERNS = require('../../constants/gl3d_dashes.json'); +var MARKER_SYMBOLS = require('../../constants/gl_markers.json'); + +// TODO use ErrorBars.calcFromTrace instead +var calculateError = require('./calc_errors'); function LineWithMarkers(scene, uid) { this.scene = scene; diff --git a/src/gl3d/defaults/scatter3d.js b/src/traces/scatter3d/defaults.js similarity index 53% rename from src/gl3d/defaults/scatter3d.js rename to src/traces/scatter3d/defaults.js index 4060d7b2273..d50e371734c 100644 --- a/src/gl3d/defaults/scatter3d.js +++ b/src/traces/scatter3d/defaults.js @@ -1,51 +1,17 @@ 'use strict'; var Plotly = require('../../plotly'); -var MARKER_SYMBOLS = require('../lib/markers.json'); +var Scatter3D = require('./scatter3d'); -var Scatter3D = module.exports = {}; -Plotly.Plots.register(Scatter3D, - 'scatter3d', ['gl3d', 'symbols', 'markerColorscale', 'showLegend'], { - hrName: 'scatter_3d', - description: [ - 'The data visualized as scatter point or lines in 3D dimension', - 'is set in `x`, `y`, `z`.', - 'Text (appearing either on the chart or on hover only) is via `text`.', - 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', - 'Projections are achieved via `projection`.', - 'Surface fills are achieved via `surfaceaxis`.' - ].join(' ') -}); - -Scatter3D.attributes = require('../attributes/scatter3d'); -Scatter3D.markerSymbols = MARKER_SYMBOLS; - -Scatter3D.handleXYZDefaults = function(traceIn, 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; -}; - -Scatter3D.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) { +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var Scatter = Plotly.Scatter; function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, - Scatter3D.attributes, attr, dflt); + return Plotly.Lib.coerce(traceIn, traceOut, Scatter3D.attributes, attr, dflt); } - var len = Scatter3D.handleXYZDefaults(traceIn, traceOut, coerce); + var len = handleXYZDefaults(traceIn, traceOut, coerce); if(!len) { traceOut.visible = false; return; @@ -86,16 +52,18 @@ Scatter3D.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) { } }; -Scatter3D.colorbar = Plotly.Scatter.colorbar; - -Scatter3D.calc = function(gd, trace) { - // 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. - var cd = [{x: false, y: false, trace: trace, t: {}}]; - Plotly.Scatter.arraysToCalcdata(cd); +function handleXYZDefaults(traceIn, traceOut, coerce) { + var len = 0, + x = coerce('x'), + y = coerce('y'), + z = coerce('z'); - Plotly.Scatter.calcMarkerColorscales(trace); + 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 cd; -}; + return len; +} diff --git a/src/traces/scatter3d/scatter3d.js b/src/traces/scatter3d/scatter3d.js new file mode 100644 index 00000000000..b48e88fccb9 --- /dev/null +++ b/src/traces/scatter3d/scatter3d.js @@ -0,0 +1,38 @@ +'use strict'; + +var Plotly = require('../../plotly'); + +var Scatter3D = module.exports = {}; + +Plotly.Plots.register(Scatter3D, + 'scatter3d', ['gl3d', 'symbols', 'markerColorscale', 'showLegend'], { + hrName: 'scatter_3d', + description: [ + 'The data visualized as scatter point or lines in 3D dimension', + 'is set in `x`, `y`, `z`.', + 'Text (appearing either on the chart or on hover only) is via `text`.', + 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', + 'Projections are achieved via `projection`.', + 'Surface fills are achieved via `surfaceaxis`.' + ].join(' ') +}); + +Scatter3D.attributes = require('./attributes'); + +Scatter3D.markerSymbols = require('../../constants/gl_markers.json'); + +Scatter3D.supplyDefaults = require('./defaults'); + +Scatter3D.colorbar = Plotly.Scatter.colorbar; + +Scatter3D.calc = function(gd, trace) { + // 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. + var cd = [{x: false, y: false, trace: trace, t: {}}]; + Plotly.Scatter.arraysToCalcdata(cd); + + Plotly.Scatter.calcMarkerColorscales(trace); + + return cd; +}; diff --git a/src/geo/attributes/scattergeo.js b/src/traces/scattergeo/attributes.js similarity index 100% rename from src/geo/attributes/scattergeo.js rename to src/traces/scattergeo/attributes.js diff --git a/src/geo/defaults/scattergeo.js b/src/traces/scattergeo/defaults.js similarity index 52% rename from src/geo/defaults/scattergeo.js rename to src/traces/scattergeo/defaults.js index e2516842a67..63a87719067 100644 --- a/src/geo/defaults/scattergeo.js +++ b/src/traces/scattergeo/defaults.js @@ -1,30 +1,17 @@ 'use strict'; var Plotly = require('../../plotly'); +var ScatterGeo = require('./scattergeo'); -var ScatterGeo = module.exports = {}; -Plotly.Plots.register(ScatterGeo, 'scattergeo', - ['geo', 'symbols', 'markerColorscale', 'showLegend'], { - hrName: 'scatter_geo', - description: [ - 'The data visualized as scatter point or lines on a geographic map', - 'is provided either by longitude/latitude pairs in `lon` and `lat`', - 'respectively or by geographic location IDs or names in `locations`.' - ].join(' ') -}); - -ScatterGeo.attributes = require('../attributes/scattergeo'); - -ScatterGeo.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var Scatter = Plotly.Scatter; function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, - ScatterGeo.attributes, attr, dflt); + return Plotly.Lib.coerce(traceIn, traceOut, ScatterGeo.attributes, attr, dflt); } - var len = ScatterGeo.handleLonLatLocDefaults(traceIn, traceOut, coerce); + var len = handleLonLatLocDefaults(traceIn, traceOut, coerce); if(!len) { traceOut.visible = false; return; @@ -48,7 +35,7 @@ ScatterGeo.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+location+text' : undefined); }; -ScatterGeo.handleLonLatLocDefaults = function(traceIn, traceOut, coerce) { +function handleLonLatLocDefaults(traceIn, traceOut, coerce) { var len = 0, locations = coerce('locations'); @@ -68,12 +55,4 @@ ScatterGeo.handleLonLatLocDefaults = function(traceIn, traceOut, coerce) { if(len < lat.length) traceOut.lat = lat.slice(0, len); return len; -}; - -ScatterGeo.colorbar = Plotly.Scatter.colorbar; - -ScatterGeo.calc = function(gd, trace) { - - Plotly.Scatter.calcMarkerColorscales(trace); - -}; +} diff --git a/src/geo/plot/scattergeo.js b/src/traces/scattergeo/plot.js similarity index 96% rename from src/geo/plot/scattergeo.js rename to src/traces/scattergeo/plot.js index 6c0c3dc9a8a..0c192770024 100644 --- a/src/geo/plot/scattergeo.js +++ b/src/traces/scattergeo/plot.js @@ -1,10 +1,11 @@ 'use strict'; -var Plotly = require('../../plotly'), - d3 = require('d3'), - getTopojsonFeatures = require('../lib/topojson-utils').getTopojsonFeatures, - locationToFeature = require('../lib/location-utils').locationToFeature, - arrayToCalcItem = require('../lib/array-to-calc-item'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); + +var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; +var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; +var arrayToCalcItem = require('../../lib/array_to_calc_item'); var plotScatterGeo = module.exports = {}; diff --git a/src/traces/scattergeo/scattergeo.js b/src/traces/scattergeo/scattergeo.js new file mode 100644 index 00000000000..001a49937c8 --- /dev/null +++ b/src/traces/scattergeo/scattergeo.js @@ -0,0 +1,27 @@ +'use strict'; + +var Plotly = require('../../plotly'); + +var ScatterGeo = module.exports = {}; + +Plotly.Plots.register(ScatterGeo, 'scattergeo', + ['geo', 'symbols', 'markerColorscale', 'showLegend'], { + hrName: 'scatter_geo', + description: [ + 'The data visualized as scatter point or lines on a geographic map', + 'is provided either by longitude/latitude pairs in `lon` and `lat`', + 'respectively or by geographic location IDs or names in `locations`.' + ].join(' ') +}); + +ScatterGeo.attributes = require('./attributes'); + +ScatterGeo.supplyDefaults = require('./defaults'); + +ScatterGeo.colorbar = Plotly.Scatter.colorbar; + +ScatterGeo.calc = function(gd, trace) { + + Plotly.Scatter.calcMarkerColorscales(trace); + +}; diff --git a/src/gl2d/scattergl/attributes.js b/src/traces/scattergl/attributes.js similarity index 91% rename from src/gl2d/scattergl/attributes.js rename to src/traces/scattergl/attributes.js index 78482f76dbc..1529d3c3ea6 100644 --- a/src/gl2d/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -1,7 +1,10 @@ 'use strict'; -var Plotly = require('../../plotly'), - extendFlat = Plotly.Lib.extendFlat; +var Plotly = require('../../plotly'); +var extendFlat = Plotly.Lib.extendFlat; + +var DASHES = require('../../constants/gl2d_dashes.json'); +var MARKERS = require('../../constants/gl_markers.json'); var scatterAttrs = Plotly.Scatter.attributes, scatterLineAttrs = scatterAttrs.line, @@ -38,7 +41,7 @@ module.exports = { width: scatterLineAttrs.width, dash: { valType: 'enumerated', - values: Object.keys(require('../lib/dashes.json')), + values: Object.keys(DASHES), dflt: 'solid', role: 'style', description: 'Sets the style of the lines.' @@ -48,7 +51,7 @@ module.exports = { color: scatterMarkerAttrs.color, symbol: { valType: 'enumerated', - values: Object.keys(require('../../gl3d/lib/markers.json')), + values: Object.keys(MARKERS), dflt: 'circle', arrayOk: true, role: 'style', diff --git a/src/gl2d/scattergl/convert.js b/src/traces/scattergl/convert.js similarity index 98% rename from src/gl2d/scattergl/convert.js rename to src/traces/scattergl/convert.js index 203fa363d96..d4144892d7c 100644 --- a/src/gl2d/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -6,13 +6,13 @@ var createScatter = require('gl-scatter2d'); var createFancyScatter = require('gl-scatter2d-fancy'); var createLine = require('gl-line2d'); var createError = require('gl-error2d'); - -var str2RGBArray = require('../../gl3d/lib/str2rgbarray'); -var formatColor = require('../../gl3d/lib/format-color'); var isNumeric = require('fast-isnumeric'); -var MARKER_SYMBOLS = require('../../gl3d/lib/markers.json'); -var DASHES = require('../lib/dashes.json'); +var str2RGBArray = require('../../lib/str2rgbarray'); +var formatColor = require('../../lib/gl_format_color'); + +var MARKER_SYMBOLS = require('../../constants/gl_markers.json'); +var DASHES = require('../../constants/gl2d_dashes.json'); var AXES = ['xaxis', 'yaxis']; diff --git a/src/gl2d/scattergl/defaults.js b/src/traces/scattergl/defaults.js similarity index 81% rename from src/gl2d/scattergl/defaults.js rename to src/traces/scattergl/defaults.js index 194ac2bd8d6..52f46ab2e59 100644 --- a/src/gl2d/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -1,20 +1,20 @@ 'use strict'; -var Plotly = require('../../plotly'), - ScatterGl = require('./scattergl'); +var Plotly = require('../../plotly'); +var ScatterGl = require('./scattergl'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var Scatter = Plotly.Scatter; function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, ScatterGl.attributes, attr, dflt); + return Plotly.Lib.coerce(traceIn, traceOut, ScatterGl.attributes, attr, dflt); } var len = Scatter.handleXYDefaults(traceIn, traceOut, coerce); if(!len) { - traceOut.visible = false; - return; + traceOut.visible = false; + return; } coerce('text'); diff --git a/src/gl2d/scattergl/scattergl.js b/src/traces/scattergl/scattergl.js similarity index 100% rename from src/gl2d/scattergl/scattergl.js rename to src/traces/scattergl/scattergl.js diff --git a/src/gl3d/attributes/surface.js b/src/traces/surface/attributes.js similarity index 100% rename from src/gl3d/attributes/surface.js rename to src/traces/surface/attributes.js diff --git a/src/gl3d/convert/surface.js b/src/traces/surface/convert.js similarity index 97% rename from src/gl3d/convert/surface.js rename to src/traces/surface/convert.js index 3ade2ff8a2c..8e23fbe011d 100644 --- a/src/gl3d/convert/surface.js +++ b/src/traces/surface/convert.js @@ -1,12 +1,13 @@ 'use strict'; -var createSurface = require('gl-surface3d'), - ndarray = require('ndarray'), - homography = require('ndarray-homography'), - fill = require('ndarray-fill'), - ops = require('ndarray-ops'), - str2RgbaArray = require('../lib/str2rgbarray'), - tinycolor = require('tinycolor2'); +var createSurface = require('gl-surface3d'); +var ndarray = require('ndarray'); +var homography = require('ndarray-homography'); +var fill = require('ndarray-fill'); +var ops = require('ndarray-ops'); +var tinycolor = require('tinycolor2'); + +var str2RgbaArray = require('../../lib/str2rgbarray'); var MIN_RESOLUTION = 128; diff --git a/src/gl3d/defaults/surface.js b/src/traces/surface/defaults.js similarity index 63% rename from src/gl3d/defaults/surface.js rename to src/traces/surface/defaults.js index c8894b3eda7..9fd121e7c03 100644 --- a/src/gl3d/defaults/surface.js +++ b/src/traces/surface/defaults.js @@ -1,29 +1,14 @@ 'use strict'; var Plotly = require('../../plotly'); +var Surface = require('./surface'); -var Surface = module.exports = {}; -Plotly.Plots.register(Surface, 'surface', ['gl3d', 'noOpacity'], { - description: [ - 'The data the describes the coordinates of the surface is set in `z`.', - 'Data in `z` should be a {2D array}.', - - 'Coordinates in `x` and `y` can either be 1D {arrays}', - 'or {2D arrays} (e.g. to graph parametric surfaces).', - - 'If not provided in `x` and `y`, the x and y coordinates are assumed', - 'to be linear starting at 0 with a unit step.' - ].join(' ') -}); - -Surface.attributes = require('../attributes/surface'); - -Surface.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) { - var i, j, _this = this; +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + var i, j; function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt); + return Plotly.Lib.coerce(traceIn, traceOut, Surface.attributes, attr, dflt); } var z = coerce('z'); @@ -93,12 +78,3 @@ Surface.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) { traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} ); }; - -Surface.colorbar = Plotly.Colorbar.traceColorbar; - -Surface.calc = function(gd, trace) { - - // auto-z and autocolorscale if applicable - Plotly.Colorscale.calc(trace, trace.z, '', 'z'); - -}; diff --git a/src/traces/surface/surface.js b/src/traces/surface/surface.js new file mode 100644 index 00000000000..32d3fe2676b --- /dev/null +++ b/src/traces/surface/surface.js @@ -0,0 +1,31 @@ +'use strict'; + +var Plotly = require('../../plotly'); + +var Surface = module.exports = {}; + +Plotly.Plots.register(Surface, 'surface', ['gl3d', 'noOpacity'], { + description: [ + 'The data the describes the coordinates of the surface is set in `z`.', + 'Data in `z` should be a {2D array}.', + + 'Coordinates in `x` and `y` can either be 1D {arrays}', + 'or {2D arrays} (e.g. to graph parametric surfaces).', + + 'If not provided in `x` and `y`, the x and y coordinates are assumed', + 'to be linear starting at 0 with a unit step.' + ].join(' ') +}); + +Surface.attributes = require('./attributes'); + +Surface.supplyDefaults = require('./defaults'); + +Surface.colorbar = Plotly.Colorbar.traceColorbar; + +Surface.calc = function(gd, trace) { + + // auto-z and autocolorscale if applicable + Plotly.Colorscale.calc(trace, trace.z, '', 'z'); + +}; From 03fd86552ab65a930c944f1af80a7905fd8a5017 Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 23:02:51 -0500 Subject: [PATCH 03/38] put constants files into src/constants/ --- src/{geo/raw => constants}/country-name_to_iso3.json | 0 src/{geo/lib/params.js => constants/geo_constants.js} | 0 src/{gl2d/lib/dashes.json => constants/gl2d_dashes.json} | 0 src/{gl3d/lib/dashes.json => constants/gl3d_dashes.json} | 0 src/{gl3d/lib/markers.json => constants/gl_markers.json} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/{geo/raw => constants}/country-name_to_iso3.json (100%) rename src/{geo/lib/params.js => constants/geo_constants.js} (100%) rename src/{gl2d/lib/dashes.json => constants/gl2d_dashes.json} (100%) rename src/{gl3d/lib/dashes.json => constants/gl3d_dashes.json} (100%) rename src/{gl3d/lib/markers.json => constants/gl_markers.json} (100%) diff --git a/src/geo/raw/country-name_to_iso3.json b/src/constants/country-name_to_iso3.json similarity index 100% rename from src/geo/raw/country-name_to_iso3.json rename to src/constants/country-name_to_iso3.json diff --git a/src/geo/lib/params.js b/src/constants/geo_constants.js similarity index 100% rename from src/geo/lib/params.js rename to src/constants/geo_constants.js diff --git a/src/gl2d/lib/dashes.json b/src/constants/gl2d_dashes.json similarity index 100% rename from src/gl2d/lib/dashes.json rename to src/constants/gl2d_dashes.json diff --git a/src/gl3d/lib/dashes.json b/src/constants/gl3d_dashes.json similarity index 100% rename from src/gl3d/lib/dashes.json rename to src/constants/gl3d_dashes.json diff --git a/src/gl3d/lib/markers.json b/src/constants/gl_markers.json similarity index 100% rename from src/gl3d/lib/markers.json rename to src/constants/gl_markers.json From db76f0261751fd5e6e69d482a7cb68990d27f83e Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 23:04:25 -0500 Subject: [PATCH 04/38] put events.js & queue.js in lib/ rename plotly_util --> svg_text_utils --- src/{ => lib}/events.js | 0 src/{ => lib}/queue.js | 2 +- src/lib/{plotly_util.js => svg_text_utils.js} | 11 +++-------- 3 files changed, 4 insertions(+), 9 deletions(-) rename src/{ => lib}/events.js (100%) rename src/{ => lib}/queue.js (99%) rename src/lib/{plotly_util.js => svg_text_utils.js} (98%) diff --git a/src/events.js b/src/lib/events.js similarity index 100% rename from src/events.js rename to src/lib/events.js diff --git a/src/queue.js b/src/lib/queue.js similarity index 99% rename from src/queue.js rename to src/lib/queue.js index 240b3a6cf4d..f8852f41ff9 100644 --- a/src/queue.js +++ b/src/lib/queue.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../plotly'); /** * Copy arg array *without* removing `undefined` values from objects. diff --git a/src/lib/plotly_util.js b/src/lib/svg_text_utils.js similarity index 98% rename from src/lib/plotly_util.js rename to src/lib/svg_text_utils.js index a299113d549..40ca14d3a93 100644 --- a/src/lib/plotly_util.js +++ b/src/lib/svg_text_utils.js @@ -1,14 +1,13 @@ 'use strict'; -/* global MathJax:false, Promise:false */ +/* global MathJax:false */ -var Plotly = require('../plotly'), - d3 = require('d3'); +var Plotly = require('../plotly'); +var d3 = require('d3'); var util = module.exports = {}; // Append SVG -///////////////////////////// d3.selection.prototype.appendSVG = function(_svgString) { var skeleton = ' Date: Tue, 10 Nov 2015 23:05:48 -0500 Subject: [PATCH 05/38] put plot components into src/components/ --- src/axes.js | 3154 ----------------- .../annotations}/annotations.js | 291 +- src/components/annotations/arrow_paths.js | 49 + src/components/annotations/attributes.js | 242 ++ src/{ => components/color}/color.js | 0 src/components/colorbar/attributes.js | 185 + src/{ => components/colorbar}/colorbar.js | 250 +- src/components/colorbar/trace_attributes.js | 64 + src/{ => components/colorscale}/colorscale.js | 86 +- src/components/colorscale/scales.js | 82 + src/{ => components/drawing}/drawing.js | 463 +-- src/components/drawing/symbol_defs.js | 465 +++ src/components/errorbars/attributes.js | 129 + src/{ => components/errorbars}/errorbars.js | 134 +- src/components/legend/attributes.js | 97 + src/{ => components/legend}/legend.js | 98 +- src/{ => components/modebar}/modebar.js | 4 +- src/components/shapes/attributes.js | 133 + src/{ => components/shapes}/shapes.js | 133 +- 19 files changed, 1469 insertions(+), 4590 deletions(-) delete mode 100644 src/axes.js rename src/{ => components/annotations}/annotations.js (79%) create mode 100644 src/components/annotations/arrow_paths.js create mode 100644 src/components/annotations/attributes.js rename src/{ => components/color}/color.js (100%) create mode 100644 src/components/colorbar/attributes.js rename src/{ => components/colorbar}/colorbar.js (76%) create mode 100644 src/components/colorbar/trace_attributes.js rename src/{ => components/colorscale}/colorscale.js (62%) create mode 100644 src/components/colorscale/scales.js rename src/{ => components/drawing}/drawing.js (57%) create mode 100644 src/components/drawing/symbol_defs.js create mode 100644 src/components/errorbars/attributes.js rename src/{ => components/errorbars}/errorbars.js (74%) create mode 100644 src/components/legend/attributes.js rename src/{ => components/legend}/legend.js (88%) rename src/{ => components/modebar}/modebar.js (99%) create mode 100644 src/components/shapes/attributes.js rename src/{ => components/shapes}/shapes.js (77%) diff --git a/src/axes.js b/src/axes.js deleted file mode 100644 index 345e3ce0f80..00000000000 --- a/src/axes.js +++ /dev/null @@ -1,3154 +0,0 @@ -'use strict'; - -var Plotly = require('./plotly'); -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); - -var axes = module.exports = {}; - -axes.traceAttributes = { - xaxis: { - valType: 'axisid', - role: 'info', - dflt: 'x', - description: [ - 'Sets a reference between this trace\'s x coordinates and', - 'a 2D cartesian x axis.', - 'If *x* (the default value), the x coordinates refer to', - '`layout.xaxis`.', - 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - }, - yaxis: { - valType: 'axisid', - role: 'info', - dflt: 'y', - description: [ - 'Sets a reference between this trace\'s y coordinates and', - 'a 2D cartesian y axis.', - 'If *y* (the default value), the y coordinates refer to', - '`layout.yaxis`.', - 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - } -}; - -Plotly.Plots.registerSubplot('cartesian', ['xaxis', 'yaxis'], ['x', 'y'], - axes.traceAttributes); - -var extendFlat = Plotly.Lib.extendFlat; - -axes.layoutAttributes = { - title: { - valType: 'string', - role: 'info', - description: 'Sets the title of this axis.' - }, - titlefont: extendFlat({}, Plotly.Plots.fontAttrs, { - description: [ - 'Sets this axis\' title font.' - ].join(' ') - }), - type: { - valType: 'enumerated', - // '-' means we haven't yet run autotype or couldn't find any data - // it gets turned into linear in td._fullLayout but not copied back - // to td.data like the others are. - values: ['-', 'linear', 'log', 'date', 'category'], - dflt: '-', - role: 'info', - description: [ - 'Sets the axis type.', - 'By default, plotly attempts to determined the axis type', - 'by looking into the data of the traces that referenced', - 'the axis in question.' - ].join(' ') - }, - autorange: { - valType: 'enumerated', - values: [true, false, 'reversed'], - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the range of this axis is', - 'computed in relation to the input data.', - 'See `rangemode` for more info.', - 'If `range` is provided, then `autorange` is set to *false*.' - ].join(' ') - }, - rangemode: { - valType: 'enumerated', - values: ['normal', 'tozero', 'nonnegative'], - dflt: 'normal', - role: 'style', - description: [ - 'If *normal*, the range is computed in relation to the extrema', - 'of the input data.', - 'If *tozero*`, the range extends to 0,', - 'regardless of the input data', - 'If *nonnegative*, the range is non-negative,', - 'regardless of the input data.' - ].join(' ') - }, - range: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number'}, - {valType: 'number'} - ], - description: [ - 'Sets the range of this axis.', - 'If the axis `type` is *log*, then you must take the log of your desired range', - '(e.g. to set the range from 1 to 100, set the range from 0 to 2).', - 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', - '(the number of milliseconds since January 1st, 1970). For example, to set the date range from', - 'January 1st 1970 to November 4th, 2013, set the range from 0 to 1380844800000.0' - ].join(' ') - }, - fixedrange: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not this axis is zoom-able.', - 'If true, then zoom is disabled.' - ].join(' ') - }, - // ticks - tickmode: { - valType: 'enumerated', - values: ['auto', 'linear', 'array'], - role: 'info', - description: [ - 'Sets the tick mode for this axis.', - 'If *auto*, the number of ticks is set via `nticks`.', - 'If *linear*, the placement of the ticks is determined by', - 'a starting position `tick0` and a tick step `dtick`', - '(*linear* is the default value if `tick0` and `dtick` are provided).', - 'If *array*, the placement of the ticks is set via `tickvals`', - 'and the tick text is `ticktext`.', - '(*array* is the default value if `tickvals` is provided).' - ].join(' ') - }, - nticks: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Sets the number of ticks.', - 'Has an effect only if `tickmode` is set to *auto*.' - ].join(' ') - }, - tick0: { - valType: 'number', - dflt: 0, - role: 'style', - description: [ - 'Sets the placement of the first tick on this axis.', - 'Use with `dtick`.', - 'If the axis `type` is *log*, then you must take the log of your starting tick', - '(e.g. to set the starting tick to 100, set the `tick0` to 2).', - 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', - '(the number of milliseconds since January 1st, 1970).', - 'For example, to set the starting tick to', - 'November 4th, 2013, set the range to 1380844800000.0.' - ].join(' ') - }, - dtick: { - valType: 'any', - dflt: 1, - role: 'style', - description: [ - 'Sets the step in-between ticks on this axis', - 'Use with `tick0`.', - 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', - 'is the tick number. For example,', - 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', - 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', - 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', - 'If the axis `type` is *date*, then you must convert the time to milliseconds.', - 'For example, to set the interval between ticks to one day,', - 'set `dtick` to 86400000.0.' - ].join(' ') - }, - tickvals: { - valType: 'data_array', - description: [ - 'Sets the values at which ticks on this axis appear.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `ticktext`.' - ].join(' ') - }, - ticktext: { - valType: 'data_array', - description: [ - 'Sets the text displayed at the ticks position via `tickvals`.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `ticktext`.' - ].join(' ') - }, - ticks: { - valType: 'enumerated', - values: ['outside', 'inside', ''], - role: 'style', - description: [ - 'Determines whether ticks are drawn or not.', - 'If **, this axis\' ticks are not drawn.', - 'If *outside* (*inside*), this axis\' are drawn outside (inside)', - 'the axis lines.' - ].join(' ') - }, - mirror: { - valType: 'enumerated', - values: [true, 'ticks', false, 'all', 'allticks'], - dflt: false, - role: 'style', - description: [ - 'Determines if the axis lines or/and ticks are mirrored to', - 'the opposite side of the plotting area.', - 'If *true*, the axis lines are mirrored.', - 'If *ticks*, the axis lines and ticks are mirrored.', - 'If *false*, mirroring is disable.', - 'If *all*, axis lines are mirrored on all shared-axes subplots.', - 'If *allticks*, axis lines and ticks are mirrored', - 'on all shared-axes subplots.' - ].join(' ') - }, - ticklen: { - valType: 'number', - min: 0, - dflt: 5, - role: 'style', - description: 'Sets the tick length (in px).' - }, - tickwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the tick width (in px).' - }, - tickcolor: { - valType: 'color', - dflt: Plotly.Color.defaultLine, - role: 'style', - description: 'Sets the tick color.' - }, - showticklabels: { - valType: 'boolean', - dflt: true, - role: 'style', - description: 'Determines whether or not the tick labels are drawn.' - }, - tickfont: extendFlat({}, Plotly.Plots.fontAttrs, { - description: 'Sets the tick font.' - }), - tickangle: { - valType: 'angle', - dflt: 'auto', - role: 'style', - description: [ - 'Sets the angle of the tick labels with respect to the horizontal.', - 'For example, a `tickangle` of -90 draws the tick labels', - 'vertically.' - ].join(' ') - }, - tickprefix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label prefix.' - }, - showtickprefix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all tick labels are displayed with a prefix.', - 'If *first*, only the first tick is displayed with a prefix.', - 'If *last*, only the last tick is displayed with a suffix.', - 'If *none*, tick prefixes are hidden.' - ].join(' ') - }, - ticksuffix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label suffix.' - }, - showticksuffix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: 'Same as `showtickprefix` but for tick suffixes.' - }, - showexponent: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all exponents are shown besides their significands.', - 'If *first*, only the exponent of the first tick is shown.', - 'If *last*, only the exponent of the last tick is shown.', - 'If *none*, no exponents appear.' - ].join(' ') - }, - exponentformat: { - valType: 'enumerated', - values: ['none', 'e', 'E', 'power', 'SI', 'B'], - dflt: 'B', - role: 'style', - description: [ - 'Determines a formatting rule for the tick exponents.', - 'For example, consider the number 1,000,000,000.', - 'If *none*, it appears as 1,000,000,000.', - 'If *e*, 1e+9.', - 'If *E*, 1E+9.', - 'If *power*, 1x10^9 (with 9 in a super script).', - 'If *SI*, 1G.', - 'If *B*, 1B.' - ].join(' ') - }, - tickformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the tick label formatting rule using the', - 'python/d3 number formatting language.', - 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', - 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', - 'for more info.' - ].join(' ') - }, - hoverformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the hover text formatting rule for data values on this axis,', - 'using the python/d3 number formatting language.', - 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', - 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', - 'for more info.' - ].join(' ') - }, - // lines and grids - showline: { - valType: 'boolean', - dflt: false, - role: 'style', - description: [ - 'Determines whether or not a line bounding this axis is drawn.' - ].join(' ') - }, - linecolor: { - valType: 'color', - dflt: Plotly.Color.defaultLine, - role: 'style', - description: 'Sets the axis line color.' - }, - linewidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the axis line.' - }, - showgrid: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not grid lines are drawn.', - 'If *true*, the grid lines are drawn at every tick mark.' - ].join(' ') - }, - gridcolor: { - valType: 'color', - dflt: Plotly.Color.lightLine, - role: 'style', - description: 'Sets the color of the grid lines.' - }, - gridwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the grid lines.' - }, - zeroline: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not a line is drawn at along the 0 value', - 'of this axis.', - 'If *true*, the zero line is drawn on top of the grid lines.' - ].join(' ') - }, - zerolinecolor: { - valType: 'color', - dflt: Plotly.Color.defaultLine, - role: 'style', - description: 'Sets the line color of the zero line.' - }, - zerolinewidth: { - valType: 'number', - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the zero line.' - }, - // positioning attributes - // anchor: not used directly, just put here for reference - // values are any opposite-letter axis id - anchor: { - valType: 'enumerated', - values: [ - 'free', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), - Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', - 'the corresponding opposite-letter axis.', - 'If set to *free*, this axis\' position is determined by `position`.' - ].join(' ') - }, - // side: not used directly, as values depend on direction - // values are top, bottom for x axes, and left, right for y - side: { - valType: 'enumerated', - values: ['top', 'bottom', 'left', 'right'], - role: 'info', - description: [ - 'Determines whether a x (y) axis is positioned', - 'at the *bottom* (*left*) or *top* (*right*)', - 'of the plotting area.' - ].join(' ') - }, - // overlaying: not used directly, just put here for reference - // values are false and any other same-letter axis id that's not - // itself overlaying anything - overlaying: { - valType: 'enumerated', - values: [ - 'free', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), - Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set a same-letter axis id, this axis is overlaid on top of', - 'the corresponding same-letter axis.', - 'If *false*, this axis does not overlay any same-letter axes.' - ].join(' ') - }, - domain: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the domain of this axis (in plot fraction).' - ].join(' ') - }, - position: { - valType: 'number', - min: 0, - max: 1, - dflt: 0, - role: 'style', - description: [ - 'Sets the position of this axis in the plotting space', - '(in normalized coordinates).', - 'Only has an effect if `anchor` is set to *free*.' - ].join(' ') - }, - - _deprecated: { - autotick: { - valType: 'boolean', - role: 'info', - description: [ - 'Obsolete.', - 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', - 'Set `tickmode` to *linear* for `autotick` *false*.' - ].join(' ') - } - } -}; - -var xAxisMatch = /^xaxis[0-9]*$/, - yAxisMatch = /^yaxis[0-9]*$/; - -axes.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { - // get the full list of axes already defined - var layoutKeys = Object.keys(layoutIn), - xaList = [], - yaList = [], - outerTicks = {}, - noGrids = {}, - i; - - for(i = 0; i < layoutKeys.length; i++) { - var key = layoutKeys[i]; - if(xAxisMatch.test(key)) xaList.push(key); - else if(yAxisMatch.test(key)) yaList.push(key); - } - - for(i = 0; i < fullData.length; i++) { - var trace = fullData[i], - xaName = axes.id2name(trace.xaxis), - yaName = axes.id2name(trace.yaxis); - - // add axes implied by traces - if(xaName && xaList.indexOf(xaName)===-1) xaList.push(xaName); - if(yaName && yaList.indexOf(yaName)===-1) yaList.push(yaName); - - // check for default formatting tweaks - if(Plotly.Plots.traceIs(trace, '2dMap')) { - outerTicks[xaName] = true; - outerTicks[yaName] = true; - } - - if(Plotly.Plots.traceIs(trace, 'oriented')) { - var positionAxis = trace.orientation==='h' ? yaName : xaName; - noGrids[positionAxis] = true; - } - } - - function axSort(a,b) { - var aNum = Number(a.substr(5)||1), - bNum = Number(b.substr(5)||1); - return aNum - bNum; - } - - if(layoutOut._hasCartesian || layoutOut._hasGL2D || !fullData.length) { - // make sure there's at least one of each and lists are sorted - if(!xaList.length) xaList = ['xaxis']; - else xaList.sort(axSort); - - if(!yaList.length) yaList = ['yaxis']; - else yaList.sort(axSort); - } - - xaList.concat(yaList).forEach(function(axName){ - var axLetter = axName.charAt(0), - axLayoutIn = layoutIn[axName] || {}, - axLayoutOut = {}, - defaultOptions = { - letter: axLetter, - font: layoutOut.font, - outerTicks: outerTicks[axName], - showGrid: !noGrids[axName], - name: axName, - data: fullData - }, - positioningOptions = { - letter: axLetter, - counterAxes: {x: yaList, y: xaList}[axLetter].map(axes.name2id), - overlayableAxes: {x: xaList, y: yaList}[axLetter].filter(function(axName2){ - return axName2!==axName && !(layoutIn[axName2]||{}).overlaying; - }).map(axes.name2id) - }; - - function coerce(attr, dflt) { - return Plotly.Lib.coerce(axLayoutIn, axLayoutOut, - axes.layoutAttributes, - attr, dflt); - } - - axes.handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); - axes.handleAxisPositioningDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions); - layoutOut[axName] = axLayoutOut; - - // so we don't have to repeat autotype unnecessarily, - // copy an autotype back to layoutIn - if(!layoutIn[axName] && axLayoutIn.type!=='-') { - layoutIn[axName] = {type: axLayoutIn.type}; - } - - }); - - // plot_bgcolor only makes sense if there's a (2D) plot! - // TODO: bgcolor for each subplot, to inherit from the main one - if(xaList.length && yaList.length) { - Plotly.Lib.coerce(layoutIn, layoutOut, - Plotly.Plots.layoutAttributes, 'plot_bgcolor'); - } -}; - -/** - * options: object containing: - * letter: 'x' or 'y' - * title: name of the axis (ie 'Colorbar') to go in default title - * name: axis object name (ie 'xaxis') if one should be stored - * font: the default font to inherit - * outerTicks: boolean, should ticks default to outside? - * showGrid: boolean, should gridlines be shown by default? - * noHover: boolean, this axis doesn't support hover effects? - * data: the plot data to use in choosing auto type - */ -axes.handleAxisDefaults = function(containerIn, containerOut, coerce, options) { - var letter = options.letter, - font = options.font || {}, - defaultTitle = 'Click to enter ' + - (options.title || (letter.toUpperCase() + ' axis')) + - ' title'; - - // set up some private properties - if(options.name) { - containerOut._name = options.name; - containerOut._id = axes.name2id(options.name); - } - - // now figure out type and do some more initialization - var axType = coerce('type'); - if(axType==='-') { - setAutoType(containerOut, options.data); - - if(containerOut.type==='-') { - containerOut.type = 'linear'; - } - else { - // copy autoType back to input axis - // note that if this object didn't exist - // in the input layout, we have to put it in - // this happens in the main supplyDefaults function - axType = containerIn.type = containerOut.type; - } - } - axes.setConvert(containerOut); - - coerce('title', defaultTitle); - Plotly.Lib.coerceFont(coerce, 'titlefont', { - family: font.family, - size: Math.round(font.size * 1.2), - color: font.color - }); - - var validRange = (containerIn.range||[]).length===2 && - isNumeric(containerIn.range[0]) && - isNumeric(containerIn.range[1]), - autoRange = coerce('autorange', !validRange); - - if(autoRange) coerce('rangemode'); - var range = coerce('range', [-1, letter==='x' ? 6 : 4]); - if(range[0] === range[1]) { - containerOut.range = [range[0] - 1, range[0] + 1]; - } - Plotly.Lib.noneOrAll(containerIn.range, containerOut.range, [0, 1]); - - coerce('fixedrange'); - - axes.handleTickValueDefaults(containerIn, containerOut, coerce, axType); - - axes.handleTickDefaults(containerIn, containerOut, coerce, axType, options); - - - - var lineColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'linecolor'), - lineWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'linewidth'), - showLine = coerce('showline', !!lineColor || !!lineWidth); - - if(!showLine) { - delete containerOut.linecolor; - delete containerOut.linewidth; - } - - if(showLine || containerOut.ticks) coerce('mirror'); - - var gridColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'gridcolor'), - gridWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'gridwidth'), - showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); - - if(!showGridLines) { - delete containerOut.gridcolor; - delete containerOut.gridwidth; - } - - var zeroLineColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'zerolinecolor'), - zeroLineWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'zerolinewidth'), - showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth); - - if(!showZeroLine) { - delete containerOut.zerolinecolor; - delete containerOut.zerolinewidth; - } - - return containerOut; -}; - -/** - * options: inherits font, outerTicks, noHover from axes.handleAxisDefaults - */ -axes.handleTickDefaults = function(containerIn, containerOut, coerce, axType, options) { - var tickLen = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'ticklen'), - tickWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'tickwidth'), - tickColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'tickcolor'), - showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); - if(!showTicks) { - delete containerOut.ticklen; - delete containerOut.tickwidth; - delete containerOut.tickcolor; - } - - var showTickLabels = coerce('showticklabels'); - if(showTickLabels) { - Plotly.Lib.coerceFont(coerce, 'tickfont', options.font || {}); - coerce('tickangle'); - - var showAttrDflt = axes.getShowAttrDflt(containerIn); - - if(axType !== 'category') { - var tickFormat = coerce('tickformat'); - if(!options.noHover) coerce('hoverformat'); - - if(!tickFormat && axType !== 'date') { - coerce('showexponent', showAttrDflt); - coerce('exponentformat'); - } - } - - var tickPrefix = coerce('tickprefix'); - if(tickPrefix) coerce('showtickprefix', showAttrDflt); - - var tickSuffix = coerce('ticksuffix'); - if(tickSuffix) coerce('showticksuffix', showAttrDflt); - } -}; - -axes.handleTickValueDefaults = function(containerIn, containerOut, coerce, axType) { - var tickmodeDefault = 'auto'; - - if(containerIn.tickmode === 'array' && - (axType === 'log' || axType === 'date')) { - containerIn.tickmode = 'auto'; - } - - if(Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array'; - else if(containerIn.dtick && isNumeric(containerIn.dtick)) { - tickmodeDefault = 'linear'; - } - var tickmode = coerce('tickmode', tickmodeDefault); - - if(tickmode === 'auto') coerce('nticks'); - else if(tickmode === 'linear') { - coerce('tick0'); - coerce('dtick'); - } - else { - var tickvals = coerce('tickvals'); - if(tickvals === undefined) containerOut.tickmode = 'auto'; - else coerce('ticktext'); - } -}; - -axes.handleAxisPositioningDefaults = function(containerIn, containerOut, coerce, options) { - var counterAxes = options.counterAxes || [], - overlayableAxes = options.overlayableAxes || [], - letter = options.letter; - - var anchor = Plotly.Lib.coerce(containerIn, containerOut, - { - anchor: { - valType:'enumerated', - values: ['free'].concat(counterAxes), - dflt: isNumeric(containerIn.position) ? 'free' : - (counterAxes[0] || 'free') - } - }, - 'anchor'); - - if(anchor==='free') coerce('position'); - - Plotly.Lib.coerce(containerIn, containerOut, - { - side: { - valType: 'enumerated', - values: letter==='x' ? ['bottom', 'top'] : ['left', 'right'], - dflt: letter==='x' ? 'bottom' : 'left' - } - }, - 'side'); - - var overlaying = false; - if(overlayableAxes.length) { - overlaying = Plotly.Lib.coerce(containerIn, containerOut, - { - overlaying: { - valType: 'enumerated', - values: [false].concat(overlayableAxes), - dflt: false - } - }, - 'overlaying'); - } - - if(!overlaying) { - // TODO: right now I'm copying this domain over to overlaying axes - // in ax.setscale()... but this means we still need (imperfect) logic - // in the axes popover to hide domain for the overlaying axis. - // perhaps I should make a private version _domain that all axes get??? - var domain = coerce('domain'); - if(domain[0] > domain[1] - 0.01) containerOut.domain = [0,1]; - Plotly.Lib.noneOrAll(containerIn.domain, containerOut.domain, [0, 1]); - } - - return containerOut; -}; - -// find the list of possible axes to reference with an xref or yref attribute -// and coerce it to that list -axes.coerceRef = function(containerIn, containerOut, td, axLetter) { - var axlist = td._fullLayout._hasGL2D ? [] : axes.listIds(td, axLetter), - refAttr = axLetter + 'ref', - attrDef = {}; - - // data-ref annotations are not supported in gl2d yet - - attrDef[refAttr] = { - valType: 'enumerated', - values: axlist.concat(['paper']), - dflt: axlist[0] || 'paper' - }; - - // xref, yref - return Plotly.Lib.coerce(containerIn, containerOut, attrDef, refAttr); -}; - -// empty out types for all axes containing these traces -// so we auto-set them again -axes.clearTypes = function(gd, traces) { - if(!Array.isArray(traces) || !traces.length) { - traces = (gd._fullData).map(function(d,i) { return i; }); - } - traces.forEach(function(tracenum) { - var trace = gd.data[tracenum]; - delete (axes.getFromId(gd, trace.xaxis)||{}).type; - delete (axes.getFromId(gd, trace.yaxis)||{}).type; - }); -}; - -// convert between axis names (xaxis, xaxis2, etc, elements of td.layout) -// and axis id's (x, x2, etc). Would probably have ditched 'xaxis' -// completely in favor of just 'x' if it weren't ingrained in the API etc. -var AX_ID_PATTERN = /^[xyz][0-9]*$/, - AX_NAME_PATTERN = /^[xyz]axis[0-9]*$/; -axes.id2name = function(id) { - if(typeof id !== 'string' || !id.match(AX_ID_PATTERN)) return; - var axNum = id.substr(1); - if(axNum==='1') axNum = ''; - return id.charAt(0) + 'axis' + axNum; -}; - -axes.name2id = function(name) { - if(!name.match(AX_NAME_PATTERN)) return; - var axNum = name.substr(5); - if(axNum==='1') axNum = ''; - return name.charAt(0)+axNum; -}; - -axes.cleanId = function(id, axLetter) { - if(!id.match(AX_ID_PATTERN)) return; - if(axLetter && id.charAt(0)!==axLetter) return; - - var axNum = id.substr(1).replace(/^0+/,''); - if(axNum==='1') axNum = ''; - return id.charAt(0) + axNum; -}; - -axes.cleanName = function(name, axLetter) { - if(!name.match(AX_ID_PATTERN)) return; - if(axLetter && name.charAt(0)!==axLetter) return; - - var axNum = name.substr(5).replace(/^0+/,''); - if(axNum==='1') axNum = ''; - return name.charAt(0) + 'axis' + axNum; -}; - -// get counteraxis letter for this axis (name or id) -// this can also be used as the id for default counter axis -axes.counterLetter = function(id) { - return {x:'y',y:'x'}[id.charAt(0)]; -}; - -function setAutoType(ax, data){ - // new logic: let people specify any type they want, - // only autotype if type is '-' - if(ax.type!=='-') return; - - var id = ax._id, - axLetter = id.charAt(0); - - // support 3d - if(id.indexOf('scene') !== -1) id = axLetter; - - var d0 = getFirstNonEmptyTrace(data, id, axLetter); - if(!d0) return; - - // first check for histograms, as the count direction - // should always default to a linear axis - if(d0.type==='histogram' && - axLetter==={v:'y', h:'x'}[d0.orientation || 'v']) { - ax.type='linear'; - return; - } - - // check all boxes on this x axis to see - // if they're dates, numbers, or categories - if(isBoxWithoutPositionCoords(d0, axLetter)) { - var posLetter = getBoxPosLetter(d0), - boxPositions = [], - trace; - - for(var i = 0; i < data.length; i++) { - trace = data[i]; - if(!Plotly.Plots.traceIs(trace, 'box') || - (trace[axLetter + 'axis'] || axLetter) !== id) continue; - - if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]); - else if(trace.name !== undefined) boxPositions.push(trace.name); - else boxPositions.push('text'); - } - - ax.type = axes.autoType(boxPositions); - } - else { - ax.type = axes.autoType(d0[axLetter] || [d0[axLetter+'0']]); - } -} - -function getBoxPosLetter(trace) { - return {v:'x', h:'y'}[trace.orientation || 'v']; -} - -function isBoxWithoutPositionCoords(trace, axLetter) { - var posLetter = getBoxPosLetter(trace); - return Plotly.Plots.traceIs(trace, 'box') && axLetter===posLetter && - trace[posLetter]===undefined && trace[posLetter + '0']===undefined; -} - -function getFirstNonEmptyTrace(data, id, axLetter) { - var trace; - - for(var i = 0; i < data.length; i++) { - trace = data[i]; - - if((trace[axLetter + 'axis'] || axLetter) === id) { - if(isBoxWithoutPositionCoords(trace, axLetter)) { - return trace; - } - else if((trace[axLetter] || []).length || trace[axLetter + '0']) { - return trace; - } - } - } -} - -axes.autoType = function(array) { - if(axes.moreDates(array)) return 'date'; - if(axes.category(array)) return 'category'; - if(linearOK(array)) return 'linear'; - else return '-'; -}; - -/* - * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix' - * share values. - * - * If only 1 attribute is set, - * the remaining attributes inherit that value. - * - * If 2 attributes are set to the same value, - * the remaining attribute inherits that value. - * - * If 2 attributes are set to different values, - * the remaining is set to its dflt value. - * - */ -axes.getShowAttrDflt = function getShowAttrDflt(containerIn) { - var showAttrsAll = ['showexponent', - 'showtickprefix', - 'showticksuffix'], - showAttrs = showAttrsAll.filter(function(a){ - return containerIn[a]!==undefined; - }), - sameVal = function(a){ - return containerIn[a]===containerIn[showAttrs[0]]; - }; - if (showAttrs.every(sameVal) || showAttrs.length===1) { - return containerIn[showAttrs[0]]; - } -}; - -// is there at least one number in array? If not, we should leave -// ax.type empty so it can be autoset later -function linearOK(array) { - if(!array) return false; - for(var i = 0; i < array.length; i++) { - if(isNumeric(array[i])) return true; - } - return false; -} - -// does the array a have mostly dates rather than numbers? -// note: some values can be neither (such as blanks, text) -// 2- or 4-digit integers can be both, so require twice as many -// dates as non-dates, to exclude cases with mostly 2 & 4 digit -// numbers and a few dates -axes.moreDates = function(a) { - var dcnt=0, ncnt=0, - // test at most 1000 points, evenly spaced - inc = Math.max(1,(a.length-1)/1000), - ai; - for(var i=0; incnt*2); -}; - -// are the (x,y)-values in td.data mostly text? -// require twice as many categories as numbers -axes.category = function(a) { - // test at most 1000 points - var inc = Math.max(1, (a.length - 1) / 1000), - curvenums = 0, - curvecats = 0, - ai; - - for(var i = 0; i < a.length; i += inc) { - ai = axes.cleanDatum(a[Math.round(i)]); - if(isNumeric(ai)) curvenums++; - else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++; - } - return curvecats > curvenums * 2; -}; - -// cleanDatum: removes characters -// same replace criteria used in the grid.js:scrapeCol -// but also handling dates, numbers, and NaN, null, Infinity etc -axes.cleanDatum = function(c){ - try{ - if(typeof c==='object' && c!==null && c.getTime) { - return Plotly.Lib.ms2DateTime(c); - } - if(typeof c!=='string' && !isNumeric(c)) { - return ''; - } - c = c.toString().replace(/['"%,$# ]/g,''); - }catch(e){ - console.log(e,c); - } - return c; -}; - -/** - * standardize all missing data in calcdata to use undefined - * never null or NaN. - * that way we can use !==undefined, or !==axes.BADNUM, - * to test for real data - */ -axes.BADNUM = undefined; - -// setConvert: define the conversion functions for an axis -// data is used in 4 ways: -// d: data, in whatever form it's provided -// c: calcdata: turned into numbers, but not linearized -// l: linearized - same as c except for log axes (and other -// mappings later?) this is used by ranges, and when we -// need to know if it's *possible* to show some data on -// this axis, without caring about the current range -// p: pixel value - mapped to the screen with current size and zoom -// setAxConvert creates/updates these conversion functions -// also clears the autorange bounds ._min and ._max -// and the autotick constraints ._minDtick, ._forceTick0, -// and looks for date ranges that aren't yet in numeric format -axes.setConvert = function(ax) { - // clipMult: how many axis lengths past the edge do we render? - // for panning, 1-2 would suffice, but for zooming more is nice. - // also, clipping can affect the direction of lines off the edge... - var clipMult = 10; - - function toLog(v, clip){ - if(v>0) return Math.log(v)/Math.LN10; - - else if(v<=0 && clip && ax.range && ax.range.length===2) { - // clip NaN (ie past negative infinity) to clipMult axis - // length past the negative edge - var r0 = ax.range[0], - r1 = ax.range[1]; - return 0.5*(r0 + r1 - 3 * clipMult * Math.abs(r0 - r1)); - } - - else return axes.BADNUM; - } - function fromLog(v){ return Math.pow(10,v); } - function num(v){ return isNumeric(v) ? Number(v) : axes.BADNUM; } - - ax.c2l = (ax.type==='log') ? toLog : num; - ax.l2c = (ax.type==='log') ? fromLog : num; - ax.l2d = function(v) { return ax.c2d(ax.l2c(v)); }; - - // set scaling to pixels - ax.setScale = function(){ - var gs = ax._td._fullLayout._size, - i; - - // TODO cleaner way to handle this case - if (!ax._categories) ax._categories = []; - - // make sure we have a domain (pull it in from the axis - // this one is overlaying if necessary) - if(ax.overlaying) { - var ax2 = axes.getFromId(ax._td, ax.overlaying); - ax.domain = ax2.domain; - } - - // make sure we have a range (linearized data values) - // and that it stays away from the limits of javascript numbers - if(!ax.range || ax.range.length!==2 || ax.range[0]===ax.range[1]) { - ax.range = [-1,1]; - } - for(i=0; i<2; i++) { - if(!isNumeric(ax.range[i])) { - ax.range[i] = isNumeric(ax.range[1-i]) ? - (ax.range[1-i] * (i ? 10 : 0.1)) : - (i ? 1 : -1); - } - - if(ax.range[i]<-(Number.MAX_VALUE/2)) { - ax.range[i] = -(Number.MAX_VALUE/2); - } - else if(ax.range[i]>Number.MAX_VALUE/2) { - ax.range[i] = Number.MAX_VALUE/2; - } - - } - - if(ax._id.charAt(0)==='y') { - ax._offset = gs.t+(1-ax.domain[1])*gs.h; - ax._length = gs.h*(ax.domain[1]-ax.domain[0]); - ax._m = ax._length/(ax.range[0]-ax.range[1]); - ax._b = -ax._m*ax.range[1]; - } - else { - ax._offset = gs.l+ax.domain[0]*gs.w; - ax._length = gs.w*(ax.domain[1]-ax.domain[0]); - ax._m = ax._length/(ax.range[1]-ax.range[0]); - ax._b = -ax._m*ax.range[0]; - } - - if (!isFinite(ax._m) || !isFinite(ax._b)) { - Plotly.Lib.notifier( - 'Something went wrong with axis scaling', - 'long'); - ax._td._replotting = false; - throw new Error('axis scaling'); - } - }; - - ax.l2p = function(v) { - if(!isNumeric(v)) return axes.BADNUM; - // include 2 fractional digits on pixel, for PDF zooming etc - return d3.round(Plotly.Lib.constrain(ax._b + ax._m*v, - -clipMult*ax._length, (1+clipMult)*ax._length), 2); - }; - - ax.p2l = function(px) { return (px-ax._b)/ax._m; }; - - ax.c2p = function(v, clip) { return ax.l2p(ax.c2l(v, clip)); }; - ax.p2c = function(px){ return ax.l2c(ax.p2l(px)); }; - - if(['linear','log','-'].indexOf(ax.type)!==-1) { - ax.c2d = num; - ax.d2c = function(v){ - v = axes.cleanDatum(v); - return isNumeric(v) ? Number(v) : axes.BADNUM; - }; - ax.d2l = function (v, clip) { - if (ax.type === 'log') return ax.c2l(ax.d2c(v), clip); - else return ax.d2c(v); - }; - } - else if(ax.type==='date') { - ax.c2d = function(v) { - return isNumeric(v) ? Plotly.Lib.ms2DateTime(v) : axes.BADNUM; - }; - - ax.d2c = function(v){ - return (isNumeric(v)) ? Number(v) : Plotly.Lib.dateTime2ms(v); - }; - - ax.d2l = ax.d2c; - - // check if date strings or js date objects are provided for range - // and convert to ms - if(ax.range && ax.range.length>1) { - try { - var ar1 = ax.range.map(Plotly.Lib.dateTime2ms); - if(!isNumeric(ax.range[0]) && isNumeric(ar1[0])) { - ax.range[0] = ar1[0]; - } - if(!isNumeric(ax.range[1]) && isNumeric(ar1[1])) { - ax.range[1] = ar1[1]; - } - } - catch(e) { console.log(e, ax.range); } - } - } - else if(ax.type==='category') { - - ax.c2d = function(v) { - return ax._categories[Math.round(v)]; - }; - - ax.d2c = function(v) { - // create the category list - // this will enter the categories in the order it - // encounters them, ie all the categories from the - // first data set, then all the ones from the second - // that aren't in the first etc. - // TODO: sorting options - do the sorting - // progressively here as we insert? - if(ax._categories.indexOf(v)===-1) ax._categories.push(v); - - var c = ax._categories.indexOf(v); - return c===-1 ? axes.BADNUM : c; - }; - - ax.d2l = ax.d2c; - } - - // makeCalcdata: takes an x or y array and converts it - // to a position on the axis object "ax" - // inputs: - // tdc - a data object from td.data - // axletter - a string, either 'x' or 'y', for which item - // to convert (TODO: is this now always the same as - // the first letter of ax._id?) - // in case the expected data isn't there, make a list of - // integers based on the opposite data - ax.makeCalcdata = function(tdc, axletter) { - var arrayIn, arrayOut, i; - - if(axletter in tdc) { - arrayIn = tdc[axletter]; - arrayOut = new Array(arrayIn.length); - - for(i = 0; i < arrayIn.length; i++) arrayOut[i] = ax.d2c(arrayIn[i]); - } - else { - var v0 = ((axletter+'0') in tdc) ? - ax.d2c(tdc[axletter+'0']) : 0, - dv = (tdc['d'+axletter]) ? - Number(tdc['d'+axletter]) : 1; - - // the opposing data, for size if we have x and dx etc - arrayIn = tdc[{x: 'y',y: 'x'}[axletter]]; - arrayOut = new Array(arrayIn.length); - - for(i = 0; i < arrayIn.length; i++) arrayOut[i] = v0+i*dv; - } - return arrayOut; - }; - - // for autoranging: arrays of objects: - // {val: axis value, pad: pixel padding} - // on the low and high sides - ax._min = []; - ax._max = []; - - // and for bar charts and box plots: reset forced minimum tick spacing - ax._minDtick = null; - ax._forceTick0 = null; -}; - -// incorporate a new minimum difference and first tick into -// forced -axes.minDtick = function(ax,newDiff,newFirst,allow) { - // doesn't make sense to do forced min dTick on log or category axes, - // and the plot itself may decide to cancel (ie non-grouped bars) - if(['log','category'].indexOf(ax.type)!==-1 || !allow) { - ax._minDtick = 0; - } - // null means there's nothing there yet - else if(ax._minDtick===null) { - ax._minDtick = newDiff; - ax._forceTick0 = newFirst; - } - else if(ax._minDtick) { - // existing minDtick is an integer multiple of newDiff - // (within rounding err) - // and forceTick0 can be shifted to newFirst - if((ax._minDtick/newDiff+1e-6)%1 < 2e-6 && - (((newFirst-ax._forceTick0)/newDiff%1) + - 1.000001) % 1 < 2e-6) { - ax._minDtick = newDiff; - ax._forceTick0 = newFirst; - } - // if the converse is true (newDiff is a multiple of minDtick and - // newFirst can be shifted to forceTick0) then do nothing - same - // forcing stands. Otherwise, cancel forced minimum - else if((newDiff/ax._minDtick+1e-6)%1 > 2e-6 || - (((newFirst-ax._forceTick0)/ax._minDtick%1) + - 1.000001) % 1 > 2e-6) { - ax._minDtick = 0; - } - } -}; - -axes.doAutoRange = function(ax) { - if(!ax._length) ax.setScale(); - - if(ax.autorange && ax._min && ax._max && - ax._min.length && ax._max.length) { - var minmin = ax._min[0].val, - maxmax = ax._max[0].val, - i; - - for(i = 1; i < ax._min.length; i++) { - if(minmin !== maxmax) break; - minmin = Math.min(minmin, ax._min[i].val); - } - for(i = 1; i < ax._max.length; i++) { - if(minmin !== maxmax) break; - maxmax = Math.max(maxmax, ax._max[i].val); - } - - var j,minpt,maxpt,minbest,maxbest,dp,dv, - mbest = 0, - axReverse = (ax.range && ax.range[1]0 && dp>0 && dv/dp > mbest) { - minbest = minpt; - maxbest = maxpt; - mbest = dv/dp; - } - } - } - if(minmin===maxmax) { - ax.range = axReverse ? - [minmin+1, ax.rangemode!=='normal' ? 0 : minmin-1] : - [ax.rangemode!=='normal' ? 0 : minmin-1, minmin+1]; - } - else if(mbest) { - if(ax.type==='linear' || ax.type==='-') { - if(ax.rangemode==='tozero' && minbest.val>=0) { - minbest = {val:0, pad:0}; - } - else if(ax.rangemode==='nonnegative') { - if(minbest.val - mbest*minbest.pad<0) { - minbest = {val:0, pad:0}; - } - if(maxbest.val<0) { - maxbest = {val:1, pad:0}; - } - } - - // in case it changed again... - mbest = (maxbest.val-minbest.val) / - (ax._length-minbest.pad-maxbest.pad); - } - - ax.range = [ - minbest.val - mbest*minbest.pad, - maxbest.val + mbest*maxbest.pad - ]; - - // don't let axis have zero size - if(ax.range[0]===ax.range[1]) { - ax.range = [ax.range[0]-1, ax.range[0]+1]; - } - - // maintain reversal - if(axReverse) { - ax.range.reverse(); - } - } - - // doAutoRange will get called on fullLayout, - // but we want to report its results back to layout - var axIn = ax._td.layout[ax._name]; - if(!axIn) ax._td.layout[ax._name] = axIn = {}; - if(axIn!==ax) { - axIn.range = ax.range.slice(); - axIn.autorange = ax.autorange; - } - } -}; - -// save a copy of the initial axis ranges in fullLayout -// use them in modebar and dblclick events -axes.saveRangeInitial = function(gd, overwrite) { - var axList = axes.list(gd, '', true), - hasOneAxisChanged = false; - - var ax, isNew, hasChanged; - - for(var i = 0; i < axList.length; i++) { - ax = axList[i]; - - isNew = ax._rangeInitial===undefined; - hasChanged = isNew || - !(ax.range[0]===ax._rangeInitial[0] && ax.range[1]===ax._rangeInitial[1]); - - if((isNew && ax.autorange===false) || (overwrite && hasChanged)) { - ax._rangeInitial = ax.range.slice(); - hasOneAxisChanged = true; - } - } - - return hasOneAxisChanged; -}; - -// axes.expand: if autoranging, include new data in the outer limits -// for this axis -// data is an array of numbers (ie already run through ax.d2c) -// available options: -// vpad: (number or number array) pad values (data value +-vpad) -// ppad: (number or number array) pad pixels (pixel location +-ppad) -// ppadplus, ppadminus, vpadplus, vpadminus: -// separate padding for each side, overrides symmetric -// padded: (boolean) add 5% padding to both ends -// (unless one end is overridden by tozero) -// tozero: (boolean) make sure to include zero if axis is linear, -// and make it a tight bound if possible -var FP_SAFE = Number.MAX_VALUE/2; -axes.expand = function(ax, data, options) { - if(!ax.autorange || !data) return; - if(!ax._min) ax._min = []; - if(!ax._max) ax._max = []; - if(!options) options = {}; - if(!ax._m) ax.setScale(); - - var len = data.length, - extrappad = options.padded ? ax._length*0.05 : 0, - tozero = options.tozero && (ax.type==='linear' || ax.type==='-'), - i, j, v, di, dmin, dmax, - ppadiplus, ppadiminus, includeThis, vmin, vmax; - - function getPad(item) { - if(Array.isArray(item)) { - return function(i) { return Math.max(Number(item[i]||0),0); }; - } - else { - var v = Math.max(Number(item||0),0); - return function(){ return v; }; - } - } - var ppadplus = getPad((ax._m>0 ? - options.ppadplus : options.ppadminus) || options.ppad || 0), - ppadminus = getPad((ax._m>0 ? - options.ppadminus : options.ppadplus) || options.ppad || 0), - vpadplus = getPad(options.vpadplus||options.vpad), - vpadminus = getPad(options.vpadminus||options.vpad); - - function addItem(i) { - di = data[i]; - if(!isNumeric(di)) return; - ppadiplus = ppadplus(i) + extrappad; - ppadiminus = ppadminus(i) + extrappad; - vmin = di-vpadminus(i); - vmax = di+vpadplus(i); - // special case for log axes: if vpad makes this object span - // more than an order of mag, clip it to one order. This is so - // we don't have non-positive errors or absurdly large lower - // range due to rounding errors - if(ax.type==='log' && vmin=ppadiminus) { - includeThis = false; - } - else if(v.val>=dmin && v.pad<=ppadiminus) { - ax._min.splice(j,1); - j--; - } - } - if(includeThis) { - ax._min.push({ - val:dmin, - pad:(tozero && dmin===0) ? 0 : ppadiminus - }); - } - } - - if(goodNumber(dmax)) { - includeThis = true; - for(j=0; j=dmax && v.pad>=ppadiplus) { - includeThis = false; - } - else if(v.val<=dmax && v.pad<=ppadiplus) { - ax._max.splice(j,1); - j--; - } - } - if(includeThis) { - ax._max.push({ - val:dmax, - pad:(tozero && dmax===0) ? 0 : ppadiplus - }); - } - } - } - - // For efficiency covering monotonic or near-monotonic data, - // check a few points at both ends first and then sweep - // through the middle - for(i=0; i<6; i++) addItem(i); - for(i=len-1; i>5; i--) addItem(i); - -}; - -axes.autoBin = function(data,ax,nbins,is2d) { - var datamin = Plotly.Lib.aggNums(Math.min, null, data), - datamax = Plotly.Lib.aggNums(Math.max, null, data); - if(ax.type==='category') { - return { - start: datamin-0.5, - end: datamax+0.5, - size: 1 - }; - } - - var size0; - if(nbins) size0 = ((datamax-datamin)/nbins); - else { - // totally auto: scale off std deviation so the highest bin is - // somewhat taller than the total number of bins, but don't let - // the size get smaller than the 'nice' rounded down minimum - // difference between values - var distinctData = Plotly.Lib.distinctVals(data), - msexp = Math.pow(10, Math.floor( - Math.log(distinctData.minDiff) / Math.LN10)), - // TODO: there are some date cases where this will fail... - minSize = msexp*Plotly.Lib.roundUp( - distinctData.minDiff/msexp, [0.9, 1.9, 4.9, 9.9], true); - size0 = Math.max(minSize, 2*Plotly.Lib.stdev(data) / - Math.pow(data.length, is2d ? 0.25 : 0.4)); - } - - // piggyback off autotick code to make "nice" bin sizes - var dummyax = { - type: ax.type==='log' ? 'linear' : ax.type, - range:[datamin, datamax] - }; - axes.autoTicks(dummyax, size0); - var binstart = axes.tickIncrement( - axes.tickFirst(dummyax), dummyax.dtick, 'reverse'), - binend; - - function nearEdge(v) { - // is a value within 1% of a bin edge? - return (1 + (v-binstart)*100/dummyax.dtick)%100 < 2; - } - - // check for too many data points right at the edges of bins - // (>50% within 1% of bin edges) or all data points integral - // and offset the bins accordingly - if(typeof dummyax.dtick === 'number') { - var edgecount = 0, - midcount = 0, - intcount = 0, - blankcount = 0; - for(var i=0; i datacount * 0.3 || - nearEdge(datamin) || nearEdge(datamax)) { - // lots of points at the edge, not many in the middle - // shift half a bin - var binshift = dummyax.dtick / 2; - binstart += (binstart+binshift0 && ax.dtick=endtick):(x<=endtick); - x = axes.tickIncrement(x,ax.dtick,axrev)) { - vals.push(x); - - // prevent infinite loops - if(vals.length>1000) break; - } - - // save the last tick as well as first, so we can - // show the exponent only on the last one - ax._tmax = vals[vals.length - 1]; - - var ticksOut = new Array(vals.length); - for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]); - - return ticksOut; -}; - -function arrayTicks(ax) { - var vals = ax.tickvals, - text = ax.ticktext, - ticksOut = new Array(vals.length), - r0expanded = ax.range[0] * 1.0001 - ax.range[1] * 0.0001, - r1expanded = ax.range[1] * 1.0001 - ax.range[0] * 0.0001, - tickMin = Math.min(r0expanded, r1expanded), - tickMax = Math.max(r0expanded, r1expanded), - vali, - i, - j = 0; - - - // without a text array, just format the given values as any other ticks - // except with more precision to the numbers - if(!Array.isArray(text)) text = []; - - for(i = 0; i < vals.length; i++) { - vali = ax.d2l(vals[i]); - if(vali > tickMin && vali < tickMax) { - if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali); - else ticksOut[j] = tickTextObj(ax, vali, String(text[i])); - j++; - } - } - - if(j < vals.length) ticksOut.splice(j, vals.length - j); - - return ticksOut; -} - -var roundBase10 = [2, 5, 10], - roundBase24 = [1, 2, 3, 6, 12], - roundBase60 = [1, 2, 5, 10, 15, 30], - // 2&3 day ticks are weird, but need something btwn 1&7 - roundDays = [1, 2, 3, 7, 14], - // approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2) - // these don't have to be exact, just close enough to round to the right value - roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1], - roundLog2 = [-0.301, 0, 0.301, 0.699, 1]; - -function roundDTick(roughDTick, base, roundingSet) { - return base * Plotly.Lib.roundUp(roughDTick / base, roundingSet); -} - -// autoTicks: calculate best guess at pleasant ticks for this axis -// inputs: -// ax - an axis object -// roughDTick - rough tick spacing (to be turned into a nice round number) -// outputs (into ax): -// tick0: starting point for ticks (not necessarily on the graph) -// usually 0 for numeric (=10^0=1 for log) or jan 1, 2000 for dates -// dtick: the actual, nice round tick spacing, somewhat larger than roughDTick -// if the ticks are spaced linearly (linear scale, categories, -// log with only full powers, date ticks < month), -// this will just be a number -// months: M# -// years: M# where # is 12*number of years -// log with linear ticks: L# where # is the linear tick spacing -// log showing powers plus some intermediates: -// D1 shows all digits, D2 shows 2 and 5 -axes.autoTicks = function(ax, roughDTick){ - var base; - - if(ax.type === 'date'){ - ax.tick0 = new Date(2000, 0, 1).getTime(); - - if(roughDTick > 15778800000){ - // years if roughDTick > 6mo - roughDTick /= 31557600000; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)); - } - else if(roughDTick > 1209600000){ - // months if roughDTick > 2wk - roughDTick /= 2629800000; - ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24); - } - else if(roughDTick > 43200000){ - // days if roughDTick > 12h - ax.dtick = roundDTick(roughDTick, 86400000, roundDays); - // get week ticks on sunday - ax.tick0 = new Date(2000, 0, 2).getTime(); - } - else if(roughDTick > 1800000){ - // hours if roughDTick > 30m - ax.dtick = roundDTick(roughDTick, 3600000, roundBase24); - } - else if(roughDTick > 30000){ - // minutes if roughDTick > 30sec - ax.dtick = roundDTick(roughDTick, 60000, roundBase60); - } - else if(roughDTick > 500){ - // seconds if roughDTick > 0.5sec - ax.dtick = roundDTick(roughDTick, 1000, roundBase60); - } - else { - //milliseconds - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = roundDTick(roughDTick, base, roundBase10); - } - } - else if(ax.type === 'log'){ - ax.tick0 = 0; - - //only show powers of 10 - if(roughDTick > 0.7) ax.dtick = Math.ceil(roughDTick); - else if(Math.abs(ax.range[1] - ax.range[0]) < 1){ - // span is less than one power of 10 - var nt = 1.5 * Math.abs((ax.range[1] - ax.range[0]) / roughDTick); - - // ticks on a linear scale, labeled fully - roughDTick = Math.abs(Math.pow(10, ax.range[1]) - - Math.pow(10, ax.range[0])) / nt; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10); - } - else { - // include intermediates between powers of 10, - // labeled with small digits - // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits) - ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1'; - } - } - else if(ax.type==='category') { - ax.tick0 = 0; - ax.dtick = Math.ceil(Math.max(roughDTick, 1)); - } - else{ - // auto ticks always start at 0 - ax.tick0 = 0; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = roundDTick(roughDTick, base, roundBase10); - } - - // prevent infinite loops - if(ax.dtick === 0) ax.dtick = 1; - - // TODO: this is from log axis histograms with autorange off - if(!isNumeric(ax.dtick) && typeof ax.dtick !=='string') { - var olddtick = ax.dtick; - ax.dtick = 1; - throw 'ax.dtick error: ' + String(olddtick); - } -}; - -// after dtick is already known, find tickround = precision -// to display in tick labels -// for numeric ticks, integer # digits after . to round to -// for date ticks, the last date part to show (y,m,d,H,M,S) -// or an integer # digits past seconds -function autoTickRound(ax) { - var dtick = ax.dtick, - maxend; - - ax._tickexponent = 0; - if(!isNumeric(dtick) && typeof dtick !== 'string') dtick = 1; - - if(ax.type === 'category') ax._tickround = null; - else if(isNumeric(dtick) || dtick.charAt(0) === 'L') { - if(ax.type === 'date') { - if(dtick >= 86400000) ax._tickround = 'd'; - else if(dtick >= 3600000) ax._tickround = 'H'; - else if(dtick >= 60000) ax._tickround = 'M'; - else if(dtick >= 1000) ax._tickround = 'S'; - else ax._tickround = 3 - Math.round(Math.log(dtick / 2) / Math.LN10); - } - else { - if(!isNumeric(dtick)) dtick = Number(dtick.substr(1)); - // 2 digits past largest digit of dtick - ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01); - - if(ax.type === 'log') { - maxend = Math.pow(10, Math.max(ax.range[0], ax.range[1])); - } - else maxend = Math.max(Math.abs(ax.range[0]), Math.abs(ax.range[1])); - - var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01); - if(Math.abs(rangeexp) > 3) { - if(ax.exponentformat === 'SI' || ax.exponentformat === 'B') { - ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3); - } - else ax._tickexponent = rangeexp; - } - } - } - else if(dtick.charAt(0) === 'M') ax._tickround = (dtick.length===2) ? 'm' : 'y'; - else ax._tickround = null; -} - -// months and years don't have constant millisecond values -// (but a year is always 12 months so we only need months) -// log-scale ticks are also not consistently spaced, except -// for pure powers of 10 -// numeric ticks always have constant differences, other datetime ticks -// can all be calculated as constant number of milliseconds -axes.tickIncrement = function(x, dtick, axrev){ - var axSign = axrev ? -1 : 1; - - // includes all dates smaller than month, and pure 10^n in log - if(isNumeric(dtick)) return x + axSign * dtick; - - var tType = dtick.charAt(0), - dtSigned = axSign * Number(dtick.substr(1)); - - // Dates: months (or years) - if(tType === 'M'){ - var y = new Date(x); - // is this browser consistent? setMonth edits a date but - // returns that date's milliseconds - return y.setMonth(y.getMonth() + dtSigned); - } - - // Log scales: Linear, Digits - else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10; - - // log10 of 2,5,10, or all digits (logs just have to be - // close enough to round) - else if(tType === 'D') { - var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, - x2 = x + axSign * 0.01, - frac = Plotly.Lib.roundUp(mod(x2, 1), tickset, axrev); - - return Math.floor(x2) + - Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; - } - else throw 'unrecognized dtick ' + String(dtick); -}; - -// calculate the first tick on an axis -axes.tickFirst = function(ax){ - var axrev = ax.range[1] < ax.range[0], - sRound = axrev ? Math.floor : Math.ceil, - // add a tiny extra bit to make sure we get ticks - // that may have been rounded out - r0 = ax.range[0] * 1.0001 - ax.range[1] * 0.0001, - dtick = ax.dtick, - tick0 = ax.tick0; - if(isNumeric(dtick)) { - var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0; - - // make sure no ticks outside the category list - if(ax.type === 'category') { - tmin = Plotly.Lib.constrain(tmin, 0, ax._categories.length - 1); - } - return tmin; - } - - var tType = dtick.charAt(0), - dtNum = Number(dtick.substr(1)), - t0, - mdif, - t1; - - // Dates: months (or years) - if(tType === 'M'){ - t0 = new Date(tick0); - r0 = new Date(r0); - mdif = (r0.getFullYear() - t0.getFullYear()) * 12 + - r0.getMonth() - t0.getMonth(); - t1 = t0.setMonth(t0.getMonth() + - (Math.round(mdif / dtNum) + (axrev ? 1 : -1)) * dtNum); - - while(axrev ? t1 > r0 : t1 < r0) { - t1 = axes.tickIncrement(t1, dtick, axrev); - } - return t1; - } - - // Log scales: Linear, Digits - else if(tType === 'L') { - return Math.log(sRound( - (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10; - } - else if(tType === 'D') { - var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, - frac = Plotly.Lib.roundUp(mod(r0, 1), tickset, axrev); - - return Math.floor(r0) + - Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; - } - else throw 'unrecognized dtick ' + String(dtick); -}; - -var yearFormat = d3.time.format('%Y'), - monthFormat = d3.time.format('%b %Y'), - dayFormat = d3.time.format('%b %-d'), - hourFormat = d3.time.format('%b %-d %Hh'), - minuteFormat = d3.time.format('%H:%M'), - secondFormat = d3.time.format(':%S'); - -// add one item to d3's vocabulary: -// %{n}f where n is the max number of digits -// of fractional seconds -var fracMatch = /%(\d?)f/g; -function modDateFormat(fmt,x) { - var fm = fmt.match(fracMatch), - d = new Date(x); - if(fm) { - var digits = Math.min(+fm[1]||6,6), - fracSecs = String((x/1000 % 1) + 2.0000005) - .substr(2,digits).replace(/0+$/,'')||'0'; - return d3.time.format(fmt.replace(fracMatch,fracSecs))(d); - } - else { - return d3.time.format(fmt)(d); - } -} - -// draw the text for one tick. -// px,py are the location on td.paper -// prefix is there so the x axis ticks can be dropped a line -// ax is the axis layout, x is the tick value -// hover is a (truthy) flag for whether to show numbers with a bit -// more precision for hovertext -axes.tickText = function(ax, x, hover){ - var out = tickTextObj(ax, x), - hideexp, - arrayMode = ax.tickmode === 'array', - extraPrecision = hover || arrayMode; - - if(arrayMode && Array.isArray(ax.ticktext)) { - var minDiff = Math.abs(ax.range[1] - ax.range[0]) / 10000; - for(var i = 0; i < ax.ticktext.length; i++) { - if(Math.abs(x - ax.d2l(ax.tickvals[i])) < minDiff) break; - } - if(i < ax.ticktext.length) { - out.text = String(ax.ticktext[i]); - return out; - } - } - - function isHidden(showAttr) { - var first_or_last; - - if (showAttr===undefined) return true; - if (hover) return showAttr==='none'; - - first_or_last = { - first: ax._tmin, - last: ax._tmax - }[showAttr]; - - return showAttr!=='all' && x!==first_or_last; - } - - hideexp = ax.exponentformat!=='none' && isHidden(ax.showexponent) ? 'hide' : ''; - - if(ax.type==='date') formatDate(ax, out, hover, extraPrecision); - else if(ax.type==='log') formatLog(ax, out, hover, extraPrecision, hideexp); - else if(ax.type==='category') formatCategory(ax, out); - else formatLinear(ax, out, hover, extraPrecision, hideexp); - - // add prefix and suffix - if (ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text; - if (ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix; - - return out; -}; - -function tickTextObj(ax, x, text) { - var tf = ax.tickfont || ax._td._fullLayout.font; - - return { - x: x, - dx: 0, - dy: 0, - text: text || '', - fontSize: tf.size, - font: tf.family, - fontColor: tf.color - }; -} - -function formatDate(ax, out, hover, extraPrecision) { - var x = out.x, - tr = ax._tickround, - d = new Date(x), - // suffix completes the full date info, to be included - // with only the first tick - suffix = '', - tt; - if(hover && ax.hoverformat) { - tt = modDateFormat(ax.hoverformat,x); - } - else if(ax.tickformat) { - tt = modDateFormat(ax.tickformat,x); - // TODO: potentially hunt for ways to automatically add more - // precision to the hover text? - } - else { - if(extraPrecision) { - if(isNumeric(tr)) tr+=2; - else tr = {y:'m', m:'d', d:'H', H:'M', M:'S', S:2}[tr]; - } - if(tr==='y') tt = yearFormat(d); - else if(tr==='m') tt = monthFormat(d); - else { - if(x===ax._tmin && !hover) { - suffix = '
'+yearFormat(d); - } - - if(tr==='d') tt = dayFormat(d); - else if(tr==='H') tt = hourFormat(d); - else { - if(x===ax._tmin && !hover) { - suffix = '
'+dayFormat(d)+', '+yearFormat(d); - } - - tt = minuteFormat(d); - if(tr!=='M'){ - tt += secondFormat(d); - if(tr!=='S') { - tt += numFormat(mod(x/1000,1),ax,'none',hover) - .substr(1); - } - } - } - } - } - out.text = tt + suffix; -} - -function formatLog(ax, out, hover, extraPrecision, hideexp) { - var dtick = ax.dtick, - x = out.x; - if(extraPrecision && ((typeof dtick !== 'string') || dtick.charAt(0)!=='L')) dtick = 'L3'; - - if(ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) { - out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision); - } - else if(isNumeric(dtick)||((dtick.charAt(0)==='D')&&(mod(x+0.01,1)<0.1))) { - if(['e','E','power'].indexOf(ax.exponentformat)!==-1) { - var p = Math.round(x); - if(p === 0) out.text = 1; - else if(p === 1) out.text = '10'; - else if(p > 1) out.text = '10' + p + ''; - else out.text = '10\u2212' + -p + ''; - - out.fontSize *= 1.25; - } - else { - out.text = numFormat(Math.pow(10,x), ax,'','fakehover'); - if(dtick==='D1' && ax._id.charAt(0)==='y') { - out.dy -= out.fontSize/6; - } - } - } - else if(dtick.charAt(0) === 'D') { - out.text = String(Math.round(Math.pow(10, mod(x, 1)))); - out.fontSize *= 0.75; - } - else throw 'unrecognized dtick ' + String(dtick); - - // if 9's are printed on log scale, move the 10's away a bit - if(ax.dtick==='D1') { - var firstChar = String(out.text).charAt(0); - if(firstChar === '0' || firstChar === '1') { - if(ax._id.charAt(0) === 'y') { - out.dx -= out.fontSize / 4; - } - else { - out.dy += out.fontSize / 2; - out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) * - out.fontSize * (x < 0 ? 0.5 : 0.25); - } - } - } -} - -function formatCategory(ax, out) { - var tt = ax._categories[Math.round(out.x)]; - if(tt === undefined) tt = ''; - out.text = String(tt); -} - -function formatLinear(ax, out, hover, extraPrecision, hideexp) { - // don't add an exponent to zero if we're showing all exponents - // so the only reason you'd show an exponent on zero is if it's the - // ONLY tick to get an exponent (first or last) - if(ax.showexponent==='all' && Math.abs(out.x/ax.dtick)<1e-6) { - hideexp = 'hide'; - } - out.text = numFormat(out.x, ax, hideexp, extraPrecision); -} - -// format a number (tick value) according to the axis settings -// new, more reliable procedure than d3.round or similar: -// add half the rounding increment, then stringify and truncate -// also automatically switch to sci. notation -var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T']; -function numFormat(v, ax, fmtoverride, hover) { - // negative? - var isNeg = v < 0, - // max number of digits past decimal point to show - tickRound = ax._tickround, - exponentFormat = fmtoverride || ax.exponentformat || 'B', - exponent = ax._tickexponent, - tickformat = ax.tickformat; - - // special case for hover: set exponent just for this value, and - // add a couple more digits of precision over tick labels - if(hover) { - // make a dummy axis obj to get the auto rounding and exponent - var ah = { - exponentformat:ax.exponentformat, - dtick: ax.showexponent==='none' ? ax.dtick : - (isNumeric(v) ? Math.abs(v) || 1 : 1), - // if not showing any exponents, don't change the exponent - // from what we calculate - range: ax.showexponent === 'none' ? ax.range : [0, v || 1] - }; - autoTickRound(ah); - tickRound = (Number(ah._tickround) || 0) + 4; - exponent = ah._tickexponent; - if(ax.hoverformat) tickformat = ax.hoverformat; - } - - if(tickformat) return d3.format(tickformat)(v).replace(/-/g,'\u2212'); - - // 'epsilon' - rounding increment - var e = Math.pow(10, -tickRound) / 2; - - // exponentFormat codes: - // 'e' (1.2e+6, default) - // 'E' (1.2E+6) - // 'SI' (1.2M) - // 'B' (same as SI except 10^9=B not G) - // 'none' (1200000) - // 'power' (1.2x10^6) - // 'hide' (1.2, use 3rd argument=='hide' to eg - // only show exponent on last tick) - if(exponentFormat === 'none') exponent = 0; - - // take the sign out, put it back manually at the end - // - makes cases easier - v = Math.abs(v); - if(v < e) { - // 0 is just 0, but may get exponent if it's the last tick - v = '0'; - isNeg = false; - } - else { - v += e; - // take out a common exponent, if any - if(exponent) { - v *= Math.pow(10, -exponent); - tickRound += exponent; - } - // round the mantissa - if(tickRound === 0) v = String(Math.floor(v)); - else if(tickRound < 0) { - v = String(Math.round(v)); - v = v.substr(0, v.length + tickRound); - for(var i = tickRound; i < 0; i++) v += '0'; - } - else { - v = String(v); - var dp = v.indexOf('.') + 1; - if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, ''); - } - // insert appropriate decimal point and thousands separator - v = numSeparate(v, ax._td._fullLayout.separators); - } - - // add exponent - if(exponent && exponentFormat !== 'hide') { - var signedExponent; - if(exponent < 0) signedExponent = '\u2212' + -exponent; - else if(exponentFormat !== 'power') signedExponent = '+' + exponent; - else signedExponent = String(exponent); - - if(exponentFormat === 'e' || - ((exponentFormat === 'SI' || exponentFormat === 'B') && - (exponent > 12 || exponent < -15))) { - v += 'e' + signedExponent; - } - else if(exponentFormat === 'E') { - v += 'E' + signedExponent; - } - else if(exponentFormat === 'power') { - v += '×10' + signedExponent + ''; - } - else if(exponentFormat === 'B' && exponent === 9) { - v += 'B'; - } - else if(exponentFormat === 'SI' || exponentFormat === 'B') { - v += SIPREFIXES[exponent / 3 + 5]; - } - } - - // put sign back in and return - // replace standard minus character (which is technically a hyphen) - // with a true minus sign - if(isNeg) return '\u2212' + v; - return v; -} - -// add arbitrary decimal point and thousands separator -var findThousands = /(\d+)(\d{3})/; -function numSeparate(nStr, separators) { - // separators - first char is decimal point, - // next char is thousands separator if there is one - - var dp = separators.charAt(0), - thou = separators.charAt(1), - x = nStr.split('.'), - x1 = x[0], - x2 = x.length > 1 ? dp + x[1] : ''; - // even if there is a thousands separator, don't use it on - // 4-digit integers (like years) - if(thou && (x.length > 1 || x1.length>4)) { - while (findThousands.test(x1)) { - x1 = x1.replace(findThousands, '$1' + thou + '$2'); - } - } - return x1 + x2; -} - -// get all axis object names -// optionally restricted to only x or y or z by string axLetter -// and optionally 2D axes only, not those inside 3D scenes -function listNames(td, axLetter, only2d) { - var fullLayout = td._fullLayout; - if(!fullLayout) return []; - - function filterAxis(obj, extra) { - var keys = Object.keys(obj), - axMatch = /^[xyz]axis[0-9]*/, - out = []; - - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; - if(axLetter && k.charAt(0) !== axLetter) continue; - if(axMatch.test(k)) out.push(extra + k); - } - - return out.sort(); - } - - var names = filterAxis(fullLayout, ''); - if(only2d) return names; - - var sceneIds3D = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d') || []; - for(var i = 0; i < sceneIds3D.length; i++) { - var sceneId = sceneIds3D[i]; - names = names.concat( - filterAxis(fullLayout[sceneId], sceneId + '.') - ); - } - - return names; -} - -// get all axis objects, as restricted in listNames -axes.list = function(td, axletter, only2d) { - return listNames(td, axletter, only2d) - .map(function(axName) { - return Plotly.Lib.nestedProperty(td._fullLayout, axName).get(); - }); -}; - -// get all axis ids, optionally restricted by letter -// this only makes sense for 2d axes -axes.listIds = function(td, axletter) { - return listNames(td, axletter, true).map(axes.name2id); -}; - -// get an axis object from its id 'x','x2' etc -// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it -axes.getFromId = function(td, id, type) { - var fullLayout = td._fullLayout; - - if(type==='x') id = id.replace(/y[0-9]*/,''); - else if(type==='y') id = id.replace(/x[0-9]*/,''); - - return fullLayout[axes.id2name(id)]; -}; - -// get an axis object of specified type from the containing trace -axes.getFromTrace = function (td, fullTrace, type) { - var fullLayout = td._fullLayout; - var ax = null; - if (Plotly.Plots.traceIs(fullTrace, 'gl3d')) { - var scene = fullTrace.scene; - if (scene.substr(0,5)==='scene') { - ax = fullLayout[scene][type + 'axis']; - } - } else { - ax = axes.getFromId(td, fullTrace[type + 'axis'] || type); - } - - return ax; -}; - -axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/; - -// getSubplots - extract all combinations of axes we need to make plots for -// as an array of items like 'xy', 'x2y', 'x2y2'... -// sorted by x (x,x2,x3...) then y -// optionally restrict to only subplots containing axis object ax -// looks both for combinations of x and y found in the data -// and at axes and their anchors -axes.getSubplots = function(gd, ax) { - var subplots = []; - var i, j, sp; - - // look for subplots in the data - var data = gd.data || []; - - for(i = 0; i < data.length; i++) { - var trace = data[i]; - - if(trace.visible === false || trace.visible === 'legendonly' || - !(Plotly.Plots.traceIs(trace, 'cartesian') || - Plotly.Plots.traceIs(trace, 'gl2d')) - ) continue; - - var xId = trace.xaxis || 'x', - yId = trace.yaxis || 'y'; - sp = xId + yId; - - if(subplots.indexOf(sp) === -1) subplots.push(sp); - } - - // look for subplots in the axes/anchors, so that we at least draw all axes - var axesList = axes.list(gd, '', true); - - function hasAx2(sp, ax2) { - return sp.indexOf(ax2._id) !== -1; - } - - for(i = 0; i < axesList.length; i++) { - var ax2 = axesList[i], - ax2Letter = ax2._id.charAt(0), - ax3Id = (ax2.anchor === 'free') ? - ((ax2Letter === 'x') ? 'y' : 'x') : - ax2.anchor, - ax3 = axes.getFromId(gd, ax3Id); - - // if a free axis is already represented in the data, ignore it - var foundAx2 = false; - for(j = 0; j < subplots.length; j++) { - if(hasAx2(subplots[j], ax2)) { - foundAx2 = true; - break; - } - } - if(ax2.anchor === 'free' && foundAx2) continue; - - if(!ax3) { - console.log([ - 'Warning: couldnt find anchor', ax3Id, - 'for axis', ax2._id - ].join(' ')); - return; - } - - sp = (ax2Letter === 'x') ? - ax2._id + ax3._id : - ax3._id + ax2._id; - - if(subplots.indexOf(sp) === -1) subplots.push(sp); - } - - // filter invalid subplots - var spMatch = axes.subplotMatch, - allSubplots = []; - - for(i = 0; i < subplots.length; i++) { - sp = subplots[i]; - if(spMatch.test(sp)) allSubplots.push(sp); - } - - // sort the subplot ids - allSubplots.sort(function(a, b) { - var aMatch = a.match(spMatch), - bMatch = b.match(spMatch); - - if(aMatch[1] === bMatch[1]) { - return +(aMatch[2]||1) - (bMatch[2]||1); - } - - return +(aMatch[1]||0) - (bMatch[1]||0); - }); - - if(ax) return axes.findSubplotsWithAxis(allSubplots, ax); - return allSubplots; -}; - -// find all subplots with axis 'ax' -axes.findSubplotsWithAxis = function(subplots, ax) { - var axMatch = new RegExp( - (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') - ); - var subplotsWithAxis = []; - - for(var i = 0; i < subplots.length; i++) { - var sp = subplots[i]; - if(axMatch.test(sp)) subplotsWithAxis.push(sp); - } - - return subplotsWithAxis; -}; - -// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings -axes.makeClipPaths = function(td) { - var layout = td._fullLayout, - defs = layout._defs, - fullWidth = {_offset: 0, _length: layout.width, _id: ''}, - fullHeight = {_offset: 0, _length: layout.height, _id: ''}, - xaList = axes.list(td, 'x', true), - yaList = axes.list(td, 'y', true), - clipList = [], - i, - j; - - for(i = 0; i < xaList.length; i++) { - clipList.push({x: xaList[i], y: fullHeight}); - for(j = 0; j < yaList.length; j++) { - if(i===0) clipList.push({x: fullWidth, y: yaList[j]}); - clipList.push({x: xaList[i], y: yaList[j]}); - } - } - - var defGroup = defs.selectAll('g.clips') - .data([0]); - defGroup.enter().append('g') - .classed('clips', true); - - // selectors don't work right with camelCase tags, - // have to use class instead - // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I - var axClips = defGroup.selectAll('.axesclip') - .data(clipList, function(d) { return d.x._id + d.y._id; }); - axClips.enter().append('clipPath') - .classed('axesclip', true) - .attr('id', function(d) { return 'clip' + layout._uid + d.x._id + d.y._id; } ) - .append('rect'); - axClips.exit().remove(); - axClips.each(function(d) { - d3.select(this).select('rect').attr({ - x: d.x._offset || 0, - y: d.y._offset || 0, - width: d.x._length || 1, - height: d.y._length || 1 - }); - }); -}; - - -// doTicks: draw ticks, grids, and tick labels -// axid: 'x', 'y', 'x2' etc, -// blank to do all, -// 'redraw' to force full redraw, and reset ax._r -// (stored range for use by zoom/pan) -// or can pass in an axis object directly -axes.doTicks = function(td, axid, skipTitle) { - var fullLayout = td._fullLayout, - ax, - independent = false; - - // allow passing an independent axis object instead of id - if(typeof axid === 'object') { - ax = axid; - axid = ax._id; - independent = true; - } - else { - ax = axes.getFromId(td,axid); - - if(axid==='redraw') { - fullLayout._paper.selectAll('g.subplot').each(function(subplot) { - var plotinfo = fullLayout._plots[subplot], - xa = plotinfo.x(), - ya = plotinfo.y(); - plotinfo.plot.attr('viewBox', - '0 0 '+xa._length+' '+ya._length); - plotinfo.xaxislayer - .selectAll('.'+xa._id+'tick').remove(); - plotinfo.yaxislayer - .selectAll('.'+ya._id+'tick').remove(); - plotinfo.gridlayer - .selectAll('path').remove(); - plotinfo.zerolinelayer - .selectAll('path').remove(); - }); - } - - if(!axid || axid==='redraw') { - return Plotly.Lib.syncOrAsync(axes.list(td, '', true).map(function(ax) { - return function(){ - if(!ax._id) return; - var axDone = axes.doTicks(td,ax._id); - if(axid==='redraw') ax._r = ax.range.slice(); - return axDone; - }; - })); - } - } - - // make sure we only have allowed options for exponents - // (others can make confusing errors) - if(!ax.tickformat) { - if(['none','e','E','power','SI','B'].indexOf(ax.exponentformat)===-1) { - ax.exponentformat = 'e'; - } - if(['all','first','last','none'].indexOf(ax.showexponent)===-1) { - ax.showexponent = 'all'; - } - } - - // in case a val turns into string somehow - ax.range = [+ax.range[0], +ax.range[1]]; - - // set scaling to pixels - ax.setScale(); - - var axletter = axid.charAt(0), - counterLetter = axes.counterLetter(axid), - vals = axes.calcTicks(ax), - datafn = function(d){ return d.text + d.x + ax.mirror; }, - tcls = axid+'tick', - gcls = axid+'grid', - zcls = axid+'zl', - pad = (ax.linewidth||1) / 2, - labelStandoff = - (ax.ticks==='outside' ? ax.ticklen : 1) + (ax.linewidth||0), - gridWidth = Plotly.Drawing.crispRound(td, ax.gridwidth, 1), - zeroLineWidth = Plotly.Drawing.crispRound(td, ax.zerolinewidth, gridWidth), - tickWidth = Plotly.Drawing.crispRound(td, ax.tickwidth, 1), - sides, transfn, tickprefix, tickmid, - i; - - // positioning arguments for x vs y axes - if(axletter==='x') { - sides = ['bottom', 'top']; - transfn = function(d){ - return 'translate('+ax.l2p(d.x)+',0)'; - }; - // dumb templating with string concat - // would be better to use an actual template - tickprefix = 'M0,'; - tickmid = 'v'; - } - else if(axletter==='y') { - sides = ['left', 'right']; - transfn = function(d){ - return 'translate(0,'+ax.l2p(d.x)+')'; - }; - tickprefix = 'M'; - tickmid = ',0h'; - } - else { - console.log('unrecognized doTicks axis', axid); - return; - } - var axside = ax.side||sides[0], - // which direction do the side[0], side[1], and free ticks go? - // then we flip if outside XOR y axis - ticksign = [-1, 1, axside===sides[1] ? 1 : -1]; - if((ax.ticks!=='inside') === (axletter==='x')) { - ticksign = ticksign.map(function(v){ return -v; }); - } - - // remove zero lines, grid lines, and inside ticks if they're within - // 1 pixel of the end - // The key case here is removing zero lines when the axis bound is zero. - function clipEnds(d) { - var p = ax.l2p(d.x); - return (p>1 && p1) { - for(j = 1; j < groupsi.length; j++) { - groupj = groups[groupsi[j]]; - mergeAxisGroups(group0.x, groupj.x); - mergeAxisGroups(group0.y, groupj.y); - } - } - mergeAxisGroups(group0.x, [xi]); - mergeAxisGroups(group0.y, [yi]); - } - - return groups; -} - -function mergeAxisGroups(intoSet, fromSet) { - for(var i = 0; i < fromSet.length; i++) { - if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]); - } -} - -function swapAxisGroup(gd, xIds, yIds) { - var i, - j, - xFullAxes = [], - yFullAxes = [], - layout = gd.layout; - - for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i])); - for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i])); - - var allAxKeys = Object.keys(xFullAxes[0]), - noSwapAttrs = [ - 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle' - ], - numericTypes = ['linear', 'log']; - - for(i = 0; i < allAxKeys.length; i++) { - var keyi = allAxKeys[i], - xVal = xFullAxes[0][keyi], - yVal = yFullAxes[0][keyi], - allEqual = true, - coerceLinearX = false, - coerceLinearY = false; - if(keyi.charAt(0) === '_' || typeof xVal === 'function' || - noSwapAttrs.indexOf(keyi) !== -1) { - continue; - } - for(j = 1; j < xFullAxes.length && allEqual; j++) { - var xVali = xFullAxes[j][keyi]; - if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 && - numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) { - // type is special - if we find a mixture of linear and log, - // coerce them all to linear on flipping - coerceLinearX = true; - } - else if(xVali !== xVal) allEqual = false; - } - for(j = 1; j < yFullAxes.length && allEqual; j++) { - var yVali = yFullAxes[j][keyi]; - if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 && - numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) { - // type is special - if we find a mixture of linear and log, - // coerce them all to linear on flipping - coerceLinearY = true; - } - else if(yFullAxes[j][keyi] !== yVal) allEqual = false; - } - if(allEqual) { - if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear'; - if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear'; - swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes); - } - } - - // now swap x&y for any annotations anchored to these x & y - for(i = 0; i < gd._fullLayout.annotations.length; i++) { - var ann = gd._fullLayout.annotations[i]; - if(xIds.indexOf(ann.xref) !== -1 && - yIds.indexOf(ann.yref) !== -1) { - Plotly.Lib.swapAttrs(layout.annotations[i],['?']); - } - } -} - -function swapAxisAttrs(layout, key, xFullAxes, yFullAxes) { - // in case the value is the default for either axis, - // look at the first axis in each list and see if - // this key's value is undefined - var np = Plotly.Lib.nestedProperty, - xVal = np(layout[xFullAxes[0]._name], key).get(), - yVal = np(layout[yFullAxes[0]._name], key).get(), - i; - if(key === 'title') { - // special handling of placeholder titles - if(xVal === 'Click to enter X axis title') { - xVal = 'Click to enter Y axis title'; - } - if(yVal === 'Click to enter Y axis title') { - yVal = 'Click to enter X axis title'; - } - } - - for(i = 0; i < xFullAxes.length; i++) { - np(layout, xFullAxes[i]._name + '.' + key).set(yVal); - } - for(i = 0; i < yFullAxes.length; i++) { - np(layout, yFullAxes[i]._name + '.' + key).set(xVal); - } -} - -// mod - version of modulus that always restricts to [0,divisor) -// rather than built-in % which gives a negative value for negative v -function mod(v,d){ return ((v%d) + d) % d; } diff --git a/src/annotations.js b/src/components/annotations/annotations.js similarity index 79% rename from src/annotations.js rename to src/components/annotations/annotations.js index e58f7a3c0b9..c829af7c3de 100644 --- a/src/annotations.js +++ b/src/components/annotations/annotations.js @@ -1,293 +1,14 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); var annotations = module.exports = {}; -// centerx is a center of scaling tuned for maximum scalability of -// the arrowhead ie throughout mag=0.3..3 the head is joined smoothly -// to the line, but the endpoint moves. -// backoff is the distance to move the arrowhead, and the end of the -// line, in order to end at the right place -// TODO: option to have the pointed-to point a little in front of the -// end of the line, as people tend to want a bit of a gap there... -annotations.ARROWPATHS = [ - // no arrow - '', - // wide with flat back - { - path: 'M-2.4,-3V3L0.6,0Z', - backoff: 0.6 - }, - // narrower with flat back - { - path: 'M-3.7,-2.5V2.5L1.3,0Z', - backoff: 1.3 - }, - // barbed - { - path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z', - backoff: 1.55 - }, - // wide line-drawn - { - path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z', - backoff: 1.6 - }, - // narrower line-drawn - { - path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z', - backoff: 2 - }, - // circle - { - path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z', - backoff: 0 - }, - // square - { - path: 'M2,2V-2H-2V2Z', - backoff: 0 - } -]; - -annotations.layoutAttributes = { - _isLinkedToArray: true, - - text: { - valType: 'string', - role: 'info', - description: [ - 'Sets the text associated with this annotation.', - 'Plotly uses a subset of HTML tags to do things like', - 'newline (
), bold (), italics (),', - 'hyperlinks (). Tags , , ', - ' are also supported.' - ].join(' ') - }, - textangle: { - valType: 'angle', - dflt: 0, - role: 'style', - description: [ - 'Sets the angle at which the `text` is drawn', - 'with respect to the horizontal.' - ].join(' ') - }, - font: Plotly.Lib.extendFlat({}, Plotly.Plots.fontAttrs, { - description: 'Sets the annotation text font.' - }), - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - role: 'style', - description: 'Sets the opacity of the annotation (text + arrow).' - }, - align: { - valType: 'enumerated', - values: ['left', 'center', 'right'], - dflt: 'center', - role: 'style', - description: [ - 'Sets the vertical alignment of the `text` with', - 'respect to the set `x` and `y` position.', - 'Has only an effect if `text` spans more two or more lines', - '(i.e. `text` contains one or more
HTML tags).' - ].join(' ') - }, - bgcolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'style', - description: 'Sets the background color of the annotation.' - }, - bordercolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'style', - description: [ - 'Sets the color of the border enclosing the annotation `text`.' - ].join(' ') - }, - borderpad: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the padding (in px) between the `text`', - 'and the enclosing border.' - ].join(' ') - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the width (in px) of the border enclosing', - 'the annotation `text`.' - ].join(' ') - }, - // arrow - showarrow: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the annotation is drawn with an arrow.', - 'If *true*, `text` is placed near the arrow\'s tail.', - 'If *false*, `text` lines up with the `x` and `y` provided.' - ].join(' ') - }, - arrowcolor: { - valType: 'color', - role: 'style', - description: 'Sets the color of the annotation arrow.' - }, - arrowhead: { - valType: 'integer', - min: 0, - max: annotations.ARROWPATHS.length, - dflt: 1, - role: 'style', - description: 'Sets the annotation arrow head style.' - }, - arrowsize: { - valType: 'number', - min: 0.3, - dflt: 1, - role: 'style', - description: 'Sets the size (in px) of annotation arrow head.' - }, - arrowwidth: { - valType: 'number', - min: 0.1, - role: 'style', - description: 'Sets the width (in px) of annotation arrow.' - }, - ax: { - valType: 'number', - dflt: -10, - role: 'info', - description: [ - 'Sets the x component of the arrow tail about the arrow head.', - 'A positive (negative) component corresponds to an arrow pointing', - 'from right to left (left to right)' - ].join(' ') - }, - ay: { - valType: 'number', - dflt: -30, - role: 'info', - description: [ - 'Sets the y component of the arrow tail about the arrow head.', - 'A positive (negative) component corresponds to an arrow pointing', - 'from bottom to top (top to bottom)' - ].join(' ') - }, - // positioning - xref: { - valType: 'enumerated', - values: [ - 'paper', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString() - ], - role: 'info', - description: [ - 'Sets the annotation\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the left (right) side.' - ].join(' ') - }, - x: { - valType: 'number', - role: 'info', - description: [ - 'Sets the annotation\'s x position.', - 'Note that dates and categories are converted to numbers.' - ].join(' ') - }, - xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'auto', - role: 'info', - description: [ - 'Sets the annotation\'s horizontal position anchor', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the annotation.', - 'For example, if `x` is set to 1, `xref` to *paper* and', - '`xanchor` to *right* then the right-most portion of the', - 'annotation lines up with the right-most edge of the', - 'plotting area.', - 'If *auto*, the anchor is equivalent to *center* for', - 'data-referenced annotations', - 'whereas for paper-referenced, the anchor picked corresponds', - 'to the closest side.' - ].join(' ') - }, - yref: { - valType: 'enumerated', - values: [ - 'paper', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() - ], - role: 'info', - description: [ - 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the bottom (top).' - ].join(' ') - }, - y: { - valType: 'number', - role: 'info', - description: [ - 'Sets the annotation\'s y position.', - 'Note that dates and categories are converted to numbers.' - ].join(' ') - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'auto', - role: 'info', - description: [ - 'Sets the annotation\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the annotation.', - 'For example, if `y` is set to 1, `yref` to *paper* and', - '`yanchor` to *top* then the top-most portion of the', - 'annotation lines up with the top-most edge of the', - 'plotting area.', - 'If *auto*, the anchor is equivalent to *middle* for', - 'data-referenced annotations', - 'whereas for paper-referenced, the anchor picked corresponds', - 'to the closest side.' - ].join(' ') - }, - - _deprecated: { - ref: { - valType: 'string', - role: 'info', - description: [ - 'Obsolete. Set `xref` and `yref` separately instead.' - ].join(' ') - } - } -}; +annotations.ARROWPATHS = require('./arrow_paths'); + +annotations.layoutAttributes = require('./attributes'); annotations.supplyLayoutDefaults = function(layoutIn, layoutOut) { var containerIn = layoutIn.annotations || [], @@ -302,9 +23,7 @@ function handleAnnotationDefaults(annIn, fullLayout) { var annOut = {}; function coerce(attr, dflt) { - return Plotly.Lib.coerce(annIn, annOut, - annotations.layoutAttributes, - attr, dflt); + return Plotly.Lib.coerce(annIn, annOut, annotations.layoutAttributes, attr, dflt); } coerce('opacity'); diff --git a/src/components/annotations/arrow_paths.js b/src/components/annotations/arrow_paths.js new file mode 100644 index 00000000000..64c662f6ae0 --- /dev/null +++ b/src/components/annotations/arrow_paths.js @@ -0,0 +1,49 @@ +/** + * centerx is a center of scaling tuned for maximum scalability of + * the arrowhead ie throughout mag=0.3..3 the head is joined smoothly + * to the line, but the endpoint moves. + * backoff is the distance to move the arrowhead, and the end of the + * line, in order to end at the right place + * + * TODO: option to have the pointed-to point a little in front of the + * end of the line, as people tend to want a bit of a gap there... + */ +module.exports = [ + // no arrow + '', + // wide with flat back + { + path: 'M-2.4,-3V3L0.6,0Z', + backoff: 0.6 + }, + // narrower with flat back + { + path: 'M-3.7,-2.5V2.5L1.3,0Z', + backoff: 1.3 + }, + // barbed + { + path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z', + backoff: 1.55 + }, + // wide line-drawn + { + path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z', + backoff: 1.6 + }, + // narrower line-drawn + { + path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z', + backoff: 2 + }, + // circle + { + path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z', + backoff: 0 + }, + // square + { + path: 'M2,2V-2H-2V2Z', + backoff: 0 + } +]; diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js new file mode 100644 index 00000000000..333c9053f3c --- /dev/null +++ b/src/components/annotations/attributes.js @@ -0,0 +1,242 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var ARROWPATHS = require('./arrow_paths'); + +var extendFlat = Plotly.Lib.extendFlat; + +module.exports = { + _isLinkedToArray: true, + + text: { + valType: 'string', + role: 'info', + description: [ + 'Sets the text associated with this annotation.', + 'Plotly uses a subset of HTML tags to do things like', + 'newline (
), bold (), italics (),', + 'hyperlinks (). Tags , , ', + ' are also supported.' + ].join(' ') + }, + textangle: { + valType: 'angle', + dflt: 0, + role: 'style', + description: [ + 'Sets the angle at which the `text` is drawn', + 'with respect to the horizontal.' + ].join(' ') + }, + font: extendFlat({}, Plotly.Plots.fontAttrs, { + description: 'Sets the annotation text font.' + }), + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + role: 'style', + description: 'Sets the opacity of the annotation (text + arrow).' + }, + align: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'center', + role: 'style', + description: [ + 'Sets the vertical alignment of the `text` with', + 'respect to the set `x` and `y` position.', + 'Has only an effect if `text` spans more two or more lines', + '(i.e. `text` contains one or more
HTML tags).' + ].join(' ') + }, + bgcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'style', + description: 'Sets the background color of the annotation.' + }, + bordercolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'style', + description: [ + 'Sets the color of the border enclosing the annotation `text`.' + ].join(' ') + }, + borderpad: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the padding (in px) between the `text`', + 'and the enclosing border.' + ].join(' ') + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the width (in px) of the border enclosing', + 'the annotation `text`.' + ].join(' ') + }, + // arrow + showarrow: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the annotation is drawn with an arrow.', + 'If *true*, `text` is placed near the arrow\'s tail.', + 'If *false*, `text` lines up with the `x` and `y` provided.' + ].join(' ') + }, + arrowcolor: { + valType: 'color', + role: 'style', + description: 'Sets the color of the annotation arrow.' + }, + arrowhead: { + valType: 'integer', + min: 0, + max: ARROWPATHS.length, + dflt: 1, + role: 'style', + description: 'Sets the annotation arrow head style.' + }, + arrowsize: { + valType: 'number', + min: 0.3, + dflt: 1, + role: 'style', + description: 'Sets the size (in px) of annotation arrow head.' + }, + arrowwidth: { + valType: 'number', + min: 0.1, + role: 'style', + description: 'Sets the width (in px) of annotation arrow.' + }, + ax: { + valType: 'number', + dflt: -10, + role: 'info', + description: [ + 'Sets the x component of the arrow tail about the arrow head.', + 'A positive (negative) component corresponds to an arrow pointing', + 'from right to left (left to right)' + ].join(' ') + }, + ay: { + valType: 'number', + dflt: -30, + role: 'info', + description: [ + 'Sets the y component of the arrow tail about the arrow head.', + 'A positive (negative) component corresponds to an arrow pointing', + 'from bottom to top (top to bottom)' + ].join(' ') + }, + // positioning + xref: { + valType: 'enumerated', + values: [ + 'paper', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString() + ], + role: 'info', + description: [ + 'Sets the annotation\'s x coordinate axis.', + 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left side of the plotting area in normalized coordinates', + 'where 0 (1) corresponds to the left (right) side.' + ].join(' ') + }, + x: { + valType: 'number', + role: 'info', + description: [ + 'Sets the annotation\'s x position.', + 'Note that dates and categories are converted to numbers.' + ].join(' ') + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the annotation\'s horizontal position anchor', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the annotation.', + 'For example, if `x` is set to 1, `xref` to *paper* and', + '`xanchor` to *right* then the right-most portion of the', + 'annotation lines up with the right-most edge of the', + 'plotting area.', + 'If *auto*, the anchor is equivalent to *center* for', + 'data-referenced annotations', + 'whereas for paper-referenced, the anchor picked corresponds', + 'to the closest side.' + ].join(' ') + }, + yref: { + valType: 'enumerated', + values: [ + 'paper', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() + ], + role: 'info', + description: [ + 'Sets the annotation\'s y coordinate axis.', + 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to an y coordinate', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plotting area in normalized coordinates', + 'where 0 (1) corresponds to the bottom (top).' + ].join(' ') + }, + y: { + valType: 'number', + role: 'info', + description: [ + 'Sets the annotation\'s y position.', + 'Note that dates and categories are converted to numbers.' + ].join(' ') + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the annotation\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the annotation.', + 'For example, if `y` is set to 1, `yref` to *paper* and', + '`yanchor` to *top* then the top-most portion of the', + 'annotation lines up with the top-most edge of the', + 'plotting area.', + 'If *auto*, the anchor is equivalent to *middle* for', + 'data-referenced annotations', + 'whereas for paper-referenced, the anchor picked corresponds', + 'to the closest side.' + ].join(' ') + }, + + _deprecated: { + ref: { + valType: 'string', + role: 'info', + description: [ + 'Obsolete. Set `xref` and `yref` separately instead.' + ].join(' ') + } + } +}; diff --git a/src/color.js b/src/components/color/color.js similarity index 100% rename from src/color.js rename to src/components/color/color.js diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js new file mode 100644 index 00000000000..e0841c357ea --- /dev/null +++ b/src/components/colorbar/attributes.js @@ -0,0 +1,185 @@ +'use strict'; + +var Plotly = require('../../plotly'); + +var axesAttrs = Plotly.Axes.layoutAttributes; +var extendFlat = Plotly.Lib.extendFlat; + +module.exports = { +// TODO: only right is supported currently +// orient: { +// valType: 'enumerated', +// role: 'info', +// values: ['left', 'right', 'top', 'bottom'], +// dflt: 'right', +// description: [ +// 'Determines which side are the labels on', +// '(so left and right make vertical bars, etc.)' +// ].join(' ') +// }, + thicknessmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + role: 'style', + dflt: 'pixels', + description: [ + 'Determines whether this color bar\'s thickness', + '(i.e. the measure in the constant color direction)', + 'is set in units of plot *fraction* or in *pixels*.', + 'Use `thickness` to set the value.' + ].join(' ') + }, + thickness: { + valType: 'number', + role: 'style', + min: 0, + dflt: 30, + description: [ + 'Sets the thickness of the color bar', + 'This measure excludes the size of the padding, ticks and labels.' + ].join(' ') + }, + lenmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + role: 'info', + dflt: 'fraction', + description: [ + 'Determines whether this color bar\'s length', + '(i.e. the measure in the color variation direction)', + 'is set in units of plot *fraction* or in *pixels.', + 'Use `len` to set the value.' + ].join(' ') + }, + len: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the length of the color bar', + 'This measure excludes the padding of both ends.', + 'That is, the color bar length is this length minus the', + 'padding on both ends.' + ].join(' ') + }, + x: { + valType: 'number', + dflt: 1.02, + min: -2, + max: 3, + role: 'style', + description: [ + 'Sets the x position of the color bar (in plot fraction).' + ].join(' ') + }, + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + role: 'style', + description: [ + 'Sets this color bar\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the color bar.' + ].join(' ') + }, + xpad: { + valType: 'number', + role: 'style', + min: 0, + dflt: 10, + description: 'Sets the amount of padding (in px) along the x direction.' + }, + y: { + valType: 'number', + role: 'style', + dflt: 0.5, + min: -2, + max: 3, + description: [ + 'Sets the y position of the color bar (in plot fraction).' + ].join(' ') + }, + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + role: 'style', + dflt: 'middle', + description: [ + 'Sets this color bar\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the color bar.' + ].join(' ') + }, + ypad: { + valType: 'number', + role: 'style', + min: 0, + dflt: 10, + description: 'Sets the amount of padding (in px) along the y direction.' + }, + // a possible line around the bar itself + outlinecolor: axesAttrs.linecolor, + outlinewidth: axesAttrs.linewidth, + // Should outlinewidth have {dflt: 0} ? + // another possible line outside the padding and tick labels + bordercolor: axesAttrs.linecolor, + borderwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 0, + description: [ + 'Sets the width (in px) or the border enclosing this color bar.' + ].join(' ') + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: 'rgba(0,0,0,0)', + description: 'Sets the color of padded area.' + }, + // tick and title properties named and function exactly as in axes + tickmode: axesAttrs.tickmode, + nticks: axesAttrs.nticks, + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + tickprefix: axesAttrs.tickprefix, + showtickprefix: axesAttrs.showtickprefix, + ticksuffix: axesAttrs.ticksuffix, + showticksuffix: axesAttrs.showticksuffix, + exponentformat: axesAttrs.exponentformat, + showexponent: axesAttrs.showexponent, + title: { + valType: 'string', + role: 'info', + dflt: 'Click to enter colorscale title', + description: 'Sets the title of the color bar.' + }, + titlefont: extendFlat({}, Plotly.Plots.fontAttrs, { + description: [ + 'Sets this color bar\'s title font.' + ].join(' ') + }), + titleside: { + valType: 'enumerated', + values: ['right', 'top', 'bottom'], + role: 'style', + dflt: 'top', + description: [ + 'Determines the location of the colorbar title', + 'with respect to the color bar.' + ].join(' ') + } +}; diff --git a/src/colorbar.js b/src/components/colorbar/colorbar.js similarity index 76% rename from src/colorbar.js rename to src/components/colorbar/colorbar.js index 74d14df5557..769dc148cdc 100644 --- a/src/colorbar.js +++ b/src/components/colorbar/colorbar.js @@ -1,9 +1,10 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); + var colorbar = module.exports = function(td, id) { // opts: options object, containing everything from attributes // plus a few others that are the equivalent of the colorbar "data" @@ -519,187 +520,7 @@ var colorbar = module.exports = function(td, id) { return component; }; -var axesAttrs = Plotly.Axes.layoutAttributes, - extendFlat = Plotly.Lib.extendFlat; - -colorbar.attributes = { -// TODO: only right is supported currently -// orient: { -// valType: 'enumerated', -// role: 'info', -// values: ['left', 'right', 'top', 'bottom'], -// dflt: 'right', -// description: [ -// 'Determines which side are the labels on', -// '(so left and right make vertical bars, etc.)' -// ].join(' ') -// }, - thicknessmode: { - valType: 'enumerated', - values: ['fraction', 'pixels'], - role: 'style', - dflt: 'pixels', - description: [ - 'Determines whether this color bar\'s thickness', - '(i.e. the measure in the constant color direction)', - 'is set in units of plot *fraction* or in *pixels*.', - 'Use `thickness` to set the value.' - ].join(' ') - }, - thickness: { - valType: 'number', - role: 'style', - min: 0, - dflt: 30, - description: [ - 'Sets the thickness of the color bar', - 'This measure excludes the size of the padding, ticks and labels.' - ].join(' ') - }, - lenmode: { - valType: 'enumerated', - values: ['fraction', 'pixels'], - role: 'info', - dflt: 'fraction', - description: [ - 'Determines whether this color bar\'s length', - '(i.e. the measure in the color variation direction)', - 'is set in units of plot *fraction* or in *pixels.', - 'Use `len` to set the value.' - ].join(' ') - }, - len: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the length of the color bar', - 'This measure excludes the padding of both ends.', - 'That is, the color bar length is this length minus the', - 'padding on both ends.' - ].join(' ') - }, - x: { - valType: 'number', - dflt: 1.02, - min: -2, - max: 3, - role: 'style', - description: [ - 'Sets the x position of the color bar (in plot fraction).' - ].join(' ') - }, - xanchor: { - valType: 'enumerated', - values: ['left', 'center', 'right'], - dflt: 'left', - role: 'style', - description: [ - 'Sets this color bar\'s horizontal position anchor.', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the color bar.' - ].join(' ') - }, - xpad: { - valType: 'number', - role: 'style', - min: 0, - dflt: 10, - description: 'Sets the amount of padding (in px) along the x direction.' - }, - y: { - valType: 'number', - role: 'style', - dflt: 0.5, - min: -2, - max: 3, - description: [ - 'Sets the y position of the color bar (in plot fraction).' - ].join(' ') - }, - yanchor: { - valType: 'enumerated', - values: ['top', 'middle', 'bottom'], - role: 'style', - dflt: 'middle', - description: [ - 'Sets this color bar\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the color bar.' - ].join(' ') - }, - ypad: { - valType: 'number', - role: 'style', - min: 0, - dflt: 10, - description: 'Sets the amount of padding (in px) along the y direction.' - }, - // a possible line around the bar itself - outlinecolor: axesAttrs.linecolor, - outlinewidth: axesAttrs.linewidth, - // Should outlinewidth have {dflt: 0} ? - // another possible line outside the padding and tick labels - bordercolor: axesAttrs.linecolor, - borderwidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 0, - description: [ - 'Sets the width (in px) or the border enclosing this color bar.' - ].join(' ') - }, - bgcolor: { - valType: 'color', - role: 'style', - dflt: 'rgba(0,0,0,0)', - description: 'Sets the color of padded area.' - }, - // tick and title properties named and function exactly as in axes - tickmode: axesAttrs.tickmode, - nticks: axesAttrs.nticks, - tick0: axesAttrs.tick0, - dtick: axesAttrs.dtick, - tickvals: axesAttrs.tickvals, - ticktext: axesAttrs.ticktext, - ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), - ticklen: axesAttrs.ticklen, - tickwidth: axesAttrs.tickwidth, - tickcolor: axesAttrs.tickcolor, - showticklabels: axesAttrs.showticklabels, - tickfont: axesAttrs.tickfont, - tickangle: axesAttrs.tickangle, - tickformat: axesAttrs.tickformat, - tickprefix: axesAttrs.tickprefix, - showtickprefix: axesAttrs.showtickprefix, - ticksuffix: axesAttrs.ticksuffix, - showticksuffix: axesAttrs.showticksuffix, - exponentformat: axesAttrs.exponentformat, - showexponent: axesAttrs.showexponent, - title: { - valType: 'string', - role: 'info', - dflt: 'Click to enter colorscale title', - description: 'Sets the title of the color bar.' - }, - titlefont: extendFlat({}, Plotly.Plots.fontAttrs, { - description: [ - 'Sets this color bar\'s title font.' - ].join(' ') - }), - titleside: { - valType: 'enumerated', - values: ['right', 'top', 'bottom'], - role: 'style', - dflt: 'top', - description: [ - 'Determines the location of the colorbar title', - 'with respect to the color bar.' - ].join(' ') - } -}; +colorbar.attributes = require('./attributes'); colorbar.supplyDefaults = function(containerIn, containerOut, layout) { var colorbarOut = containerOut.colorbar = {}, @@ -772,67 +593,4 @@ colorbar.traceColorbar = function(gd, cd) { Plotly.Lib.markTime('done colorbar'); }; -colorbar.traceColorbarAttributes = { - zauto: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines the whether or not the color domain is computed', - 'with respect to the input data.' - ].join(' ') - }, - zmin: { - valType: 'number', - role: 'info', - dflt: null, - description: 'Sets the lower bound of color domain.' - }, - zmax: { - valType: 'number', - role: 'info', - dflt: null, - description: 'Sets the upper bound of color domain.' - }, - colorscale: { - valType: 'colorscale', - role: 'style', - description: 'Sets the colorscale.' - }, - autocolorscale: { - valType: 'boolean', - role: 'style', - dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp. - description: [ - 'Determines whether or not the colorscale is picked using the sign of', - 'the input z values.' - ].join(' ') - }, - reversescale: { - valType: 'boolean', - role: 'style', - dflt: false, - description: 'Reverses the colorscale.' - }, - showscale: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not a colorbar is displayed for this trace.' - ].join(' ') - }, - - _deprecated: { - scl: { - valType: 'colorscale', - role: 'style', - description: 'Renamed to `colorscale`.' - }, - reversescl: { - valType: 'boolean', - role: 'style', - description: 'Renamed to `reversescale`.' - } - } -}; +colorbar.traceColorbarAttributes = require('./trace_attributes'); diff --git a/src/components/colorbar/trace_attributes.js b/src/components/colorbar/trace_attributes.js new file mode 100644 index 00000000000..61def16d1bc --- /dev/null +++ b/src/components/colorbar/trace_attributes.js @@ -0,0 +1,64 @@ +module.exports = { + zauto: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines the whether or not the color domain is computed', + 'with respect to the input data.' + ].join(' ') + }, + zmin: { + valType: 'number', + role: 'info', + dflt: null, + description: 'Sets the lower bound of color domain.' + }, + zmax: { + valType: 'number', + role: 'info', + dflt: null, + description: 'Sets the upper bound of color domain.' + }, + colorscale: { + valType: 'colorscale', + role: 'style', + description: 'Sets the colorscale.' + }, + autocolorscale: { + valType: 'boolean', + role: 'style', + dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp. + description: [ + 'Determines whether or not the colorscale is picked using the sign of', + 'the input z values.' + ].join(' ') + }, + reversescale: { + valType: 'boolean', + role: 'style', + dflt: false, + description: 'Reverses the colorscale.' + }, + showscale: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not a colorbar is displayed for this trace.' + ].join(' ') + }, + + _deprecated: { + scl: { + valType: 'colorscale', + role: 'style', + description: 'Renamed to `colorscale`.' + }, + reversescl: { + valType: 'boolean', + role: 'style', + description: 'Renamed to `reversescale`.' + } + } +}; diff --git a/src/colorscale.js b/src/components/colorscale/colorscale.js similarity index 62% rename from src/colorscale.js rename to src/components/colorscale/colorscale.js index 4f0aa7c06ea..2054766cc4d 100644 --- a/src/colorscale.js +++ b/src/components/colorscale/colorscale.js @@ -1,95 +1,13 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var tinycolor = require('tinycolor2'); var isNumeric = require('fast-isnumeric'); var colorscale = module.exports = {}; -colorscale.scales = { - 'Greys':[[0,'rgb(0,0,0)'],[1,'rgb(255,255,255)']], - - 'YIGnBu':[[0,'rgb(8, 29, 88)'],[0.125,'rgb(37, 52, 148)'], - [0.25,'rgb(34, 94, 168)'],[0.375,'rgb(29, 145, 192)'], - [0.5,'rgb(65, 182, 196)'],[0.625,'rgb(127, 205, 187)'], - [0.75,'rgb(199, 233, 180)'],[0.875,'rgb(237, 248, 217)'], - [1,'rgb(255, 255, 217)']], - - 'Greens':[[0,'rgb(0, 68, 27)'],[0.125,'rgb(0, 109, 44)'], - [0.25,'rgb(35, 139, 69)'],[0.375,'rgb(65, 171, 93)'], - [0.5,'rgb(116, 196, 118)'],[0.625,'rgb(161, 217, 155)'], - [0.75,'rgb(199, 233, 192)'],[0.875,'rgb(229, 245, 224)'], - [1,'rgb(247, 252, 245)']], - - 'YIOrRd':[[0,'rgb(128, 0, 38)'],[0.125,'rgb(189, 0, 38)'], - [0.25,'rgb(227, 26, 28)'],[0.375,'rgb(252, 78, 42)'], - [0.5,'rgb(253, 141, 60)'],[0.625,'rgb(254, 178, 76)'], - [0.75,'rgb(254, 217, 118)'],[0.875,'rgb(255, 237, 160)'], - [1,'rgb(255, 255, 204)']], - - 'Bluered':[[0,'rgb(0,0,255)'],[1,'rgb(255,0,0)']], - - // modified RdBu based on - // www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf - 'RdBu':[[0,'rgb(5, 10, 172)'],[0.35,'rgb(106, 137, 247)'], - [0.5,'rgb(190,190,190)'],[0.6,'rgb(220, 170, 132)'], - [0.7,'rgb(230, 145, 90)'],[1,'rgb(178, 10, 28)']], - // Scale for non-negative numeric values - 'Reds': [[0, 'rgb(220, 220, 220)'], [0.2,'rgb(245, 195, 157)'], - [0.4,'rgb(245, 160, 105)'], [1, 'rgb(178, 10, 28)']], - // Scale for non-positive numeric values - 'Blues': [[0, 'rgb(5, 10, 172)'], [0.35, 'rgb(40, 60, 190)'], - [0.5, 'rgb(70, 100, 245)'], [0.6, 'rgb(90, 120, 245)'], - [0.7, 'rgb(106, 137, 247)'], [1, 'rgb(220, 220, 220)']], - - 'Picnic':[[0,'rgb(0,0,255)'],[0.1,'rgb(51,153,255)'], - [0.2,'rgb(102,204,255)'],[0.3,'rgb(153,204,255)'], - [0.4,'rgb(204,204,255)'],[0.5,'rgb(255,255,255)'], - [0.6,'rgb(255,204,255)'],[0.7,'rgb(255,153,255)'], - [0.8,'rgb(255,102,204)'],[0.9,'rgb(255,102,102)'], - [1,'rgb(255,0,0)']], - - 'Rainbow':[[0,'rgb(150,0,90)'],[0.125,'rgb(0, 0, 200)'], - [0.25,'rgb(0, 25, 255)'],[0.375,'rgb(0, 152, 255)'], - [0.5,'rgb(44, 255, 150)'],[0.625,'rgb(151, 255, 0)'], - [0.75,'rgb(255, 234, 0)'],[0.875,'rgb(255, 111, 0)'], - [1,'rgb(255, 0, 0)']], - - 'Portland':[[0,'rgb(12,51,131)'],[0.25,'rgb(10,136,186)'], - [0.5,'rgb(242,211,56)'],[0.75,'rgb(242,143,56)'], - [1,'rgb(217,30,30)']], - - 'Jet':[[0,'rgb(0,0,131)'],[0.125,'rgb(0,60,170)'], - [0.375,'rgb(5,255,255)'],[0.625,'rgb(255,255,0)'], - [0.875,'rgb(250,0,0)'],[1,'rgb(128,0,0)']], - - 'Hot':[[0,'rgb(0,0,0)'],[0.3,'rgb(230,0,0)'], - [0.6,'rgb(255,210,0)'],[1,'rgb(255,255,255)']], - - 'Blackbody':[[0,'rgb(0,0,0)'],[0.2,'rgb(230,0,0)'], - [0.4,'rgb(230,210,0)'],[0.7,'rgb(255,255,255)'], - [1,'rgb(160,200,255)']], - - 'Earth':[[0,'rgb(0,0,130)'],[0.1,'rgb(0,180,180)'], - [0.2,'rgb(40,210,40)'],[0.4,'rgb(230,230,50)'], - [0.6,'rgb(120,70,20)'],[1,'rgb(255,255,255)']], - - 'Electric':[[0,'rgb(0,0,0)'],[0.15,'rgb(30,0,100)'], - [0.4,'rgb(120,0,100)'],[0.6,'rgb(160,90,0)'], - [0.8,'rgb(230,200,0)'],[1,'rgb(255,250,220)']], - - 'Viridis': [[0,'#440154'],[0.06274509803921569,'#48186a'], - [0.12549019607843137,'#472d7b'],[0.18823529411764706,'#424086'], - [0.25098039215686274,'#3b528b'],[0.3137254901960784,'#33638d'], - [0.3764705882352941,'#2c728e'],[0.4392156862745098,'#26828e'], - [0.5019607843137255,'#21918c'],[0.5647058823529412,'#1fa088'], - [0.6274509803921569,'#28ae80'],[0.6901960784313725,'#3fbc73'], - [0.7529411764705882,'#5ec962'],[0.8156862745098039,'#84d44b'], - [0.8784313725490196,'#addc30'],[0.9411764705882353,'#d8e219'], - [1,'#fde725']] -}; - +colorscale.scales = require('./scales'); colorscale.defaultScale = colorscale.scales.RdBu; function isValidScaleArray(scl) { diff --git a/src/components/colorscale/scales.js b/src/components/colorscale/scales.js new file mode 100644 index 00000000000..c5163c91a62 --- /dev/null +++ b/src/components/colorscale/scales.js @@ -0,0 +1,82 @@ +module.exports = { + 'Greys':[[0,'rgb(0,0,0)'],[1,'rgb(255,255,255)']], + + 'YIGnBu':[[0,'rgb(8, 29, 88)'],[0.125,'rgb(37, 52, 148)'], + [0.25,'rgb(34, 94, 168)'],[0.375,'rgb(29, 145, 192)'], + [0.5,'rgb(65, 182, 196)'],[0.625,'rgb(127, 205, 187)'], + [0.75,'rgb(199, 233, 180)'],[0.875,'rgb(237, 248, 217)'], + [1,'rgb(255, 255, 217)']], + + 'Greens':[[0,'rgb(0, 68, 27)'],[0.125,'rgb(0, 109, 44)'], + [0.25,'rgb(35, 139, 69)'],[0.375,'rgb(65, 171, 93)'], + [0.5,'rgb(116, 196, 118)'],[0.625,'rgb(161, 217, 155)'], + [0.75,'rgb(199, 233, 192)'],[0.875,'rgb(229, 245, 224)'], + [1,'rgb(247, 252, 245)']], + + 'YIOrRd':[[0,'rgb(128, 0, 38)'],[0.125,'rgb(189, 0, 38)'], + [0.25,'rgb(227, 26, 28)'],[0.375,'rgb(252, 78, 42)'], + [0.5,'rgb(253, 141, 60)'],[0.625,'rgb(254, 178, 76)'], + [0.75,'rgb(254, 217, 118)'],[0.875,'rgb(255, 237, 160)'], + [1,'rgb(255, 255, 204)']], + + 'Bluered':[[0,'rgb(0,0,255)'],[1,'rgb(255,0,0)']], + + // modified RdBu based on + // www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf + 'RdBu':[[0,'rgb(5, 10, 172)'],[0.35,'rgb(106, 137, 247)'], + [0.5,'rgb(190,190,190)'],[0.6,'rgb(220, 170, 132)'], + [0.7,'rgb(230, 145, 90)'],[1,'rgb(178, 10, 28)']], + // Scale for non-negative numeric values + 'Reds': [[0, 'rgb(220, 220, 220)'], [0.2,'rgb(245, 195, 157)'], + [0.4,'rgb(245, 160, 105)'], [1, 'rgb(178, 10, 28)']], + // Scale for non-positive numeric values + 'Blues': [[0, 'rgb(5, 10, 172)'], [0.35, 'rgb(40, 60, 190)'], + [0.5, 'rgb(70, 100, 245)'], [0.6, 'rgb(90, 120, 245)'], + [0.7, 'rgb(106, 137, 247)'], [1, 'rgb(220, 220, 220)']], + + 'Picnic':[[0,'rgb(0,0,255)'],[0.1,'rgb(51,153,255)'], + [0.2,'rgb(102,204,255)'],[0.3,'rgb(153,204,255)'], + [0.4,'rgb(204,204,255)'],[0.5,'rgb(255,255,255)'], + [0.6,'rgb(255,204,255)'],[0.7,'rgb(255,153,255)'], + [0.8,'rgb(255,102,204)'],[0.9,'rgb(255,102,102)'], + [1,'rgb(255,0,0)']], + + 'Rainbow':[[0,'rgb(150,0,90)'],[0.125,'rgb(0, 0, 200)'], + [0.25,'rgb(0, 25, 255)'],[0.375,'rgb(0, 152, 255)'], + [0.5,'rgb(44, 255, 150)'],[0.625,'rgb(151, 255, 0)'], + [0.75,'rgb(255, 234, 0)'],[0.875,'rgb(255, 111, 0)'], + [1,'rgb(255, 0, 0)']], + + 'Portland':[[0,'rgb(12,51,131)'],[0.25,'rgb(10,136,186)'], + [0.5,'rgb(242,211,56)'],[0.75,'rgb(242,143,56)'], + [1,'rgb(217,30,30)']], + + 'Jet':[[0,'rgb(0,0,131)'],[0.125,'rgb(0,60,170)'], + [0.375,'rgb(5,255,255)'],[0.625,'rgb(255,255,0)'], + [0.875,'rgb(250,0,0)'],[1,'rgb(128,0,0)']], + + 'Hot':[[0,'rgb(0,0,0)'],[0.3,'rgb(230,0,0)'], + [0.6,'rgb(255,210,0)'],[1,'rgb(255,255,255)']], + + 'Blackbody':[[0,'rgb(0,0,0)'],[0.2,'rgb(230,0,0)'], + [0.4,'rgb(230,210,0)'],[0.7,'rgb(255,255,255)'], + [1,'rgb(160,200,255)']], + + 'Earth':[[0,'rgb(0,0,130)'],[0.1,'rgb(0,180,180)'], + [0.2,'rgb(40,210,40)'],[0.4,'rgb(230,230,50)'], + [0.6,'rgb(120,70,20)'],[1,'rgb(255,255,255)']], + + 'Electric':[[0,'rgb(0,0,0)'],[0.15,'rgb(30,0,100)'], + [0.4,'rgb(120,0,100)'],[0.6,'rgb(160,90,0)'], + [0.8,'rgb(230,200,0)'],[1,'rgb(255,250,220)']], + + 'Viridis': [[0,'#440154'],[0.06274509803921569,'#48186a'], + [0.12549019607843137,'#472d7b'],[0.18823529411764706,'#424086'], + [0.25098039215686274,'#3b528b'],[0.3137254901960784,'#33638d'], + [0.3764705882352941,'#2c728e'],[0.4392156862745098,'#26828e'], + [0.5019607843137255,'#21918c'],[0.5647058823529412,'#1fa088'], + [0.6274509803921569,'#28ae80'],[0.6901960784313725,'#3fbc73'], + [0.7529411764705882,'#5ec962'],[0.8156862745098039,'#84d44b'], + [0.8784313725490196,'#addc30'],[0.9411764705882353,'#d8e219'], + [1,'#fde725']] +}; diff --git a/src/drawing.js b/src/components/drawing/drawing.js similarity index 57% rename from src/drawing.js rename to src/components/drawing/drawing.js index 1cd1526be04..1b17ca6486c 100644 --- a/src/drawing.js +++ b/src/components/drawing/drawing.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -110,471 +110,14 @@ drawing.fillGroupStyle = function(s) { }); }; -// marker symbol definitions -// users can specify markers either by number or name -// add 100 (or '-open') and you get an open marker -// open markers have no fill and use line color as the stroke color -// add 200 (or '-dot') and you get a dot in the middle -// add both and you get both -var SYMBOLDEFS = { - circle: { - n: 0, - f: function(r) { - var rs = d3.round(r,2); - return 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ - 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; - } - }, - square: { - n: 1, - f: function(r) { - var rs = d3.round(r,2); - return 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; - } - }, - diamond: { - n: 2, - f: function(r) { - var rd = d3.round(r*1.3,2); - return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'; - } - }, - cross: { - n: 3, - f: function(r) { - var rc = d3.round(r*0.4,2), - rc2 = d3.round(r*1.2,2); - return 'M'+rc2+','+rc+'H'+rc+'V'+rc2+'H-'+rc+ - 'V'+rc+'H-'+rc2+'V-'+rc+'H-'+rc+'V-'+rc2+ - 'H'+rc+'V-'+rc+'H'+rc2+'Z'; - } - }, - x: { - n: 4, - f: function(r) { - var rx = d3.round(r*0.8/Math.sqrt(2),2), - ne = 'l'+rx+','+rx, - se = 'l'+rx+',-'+rx, - sw = 'l-'+rx+',-'+rx, - nw = 'l-'+rx+','+rx; - return 'M0,'+rx+ne+se+sw+se+sw+nw+sw+nw+ne+nw+ne+'Z'; - } - }, - 'triangle-up': { - n: 5, - f: function(r) { - var rt = d3.round(r*2/Math.sqrt(3),2), - r2 = d3.round(r/2,2), - rs = d3.round(r,2); - return 'M-'+rt+','+r2+'H'+rt+'L0,-'+rs+'Z'; - } - }, - 'triangle-down': { - n: 6, - f: function(r) { - var rt = d3.round(r*2/Math.sqrt(3),2), - r2 = d3.round(r/2,2), - rs = d3.round(r,2); - return 'M-'+rt+',-'+r2+'H'+rt+'L0,'+rs+'Z'; - } - }, - 'triangle-left': { - n: 7, - f: function(r) { - var rt = d3.round(r*2/Math.sqrt(3),2), - r2 = d3.round(r/2,2), - rs = d3.round(r,2); - return 'M'+r2+',-'+rt+'V'+rt+'L-'+rs+',0Z'; - } - }, - 'triangle-right': { - n: 8, - f: function(r) { - var rt = d3.round(r*2/Math.sqrt(3),2), - r2 = d3.round(r/2,2), - rs = d3.round(r,2); - return 'M-'+r2+',-'+rt+'V'+rt+'L'+rs+',0Z'; - } - }, - 'triangle-ne': { - n: 9, - f: function(r) { - var r1 = d3.round(r*0.6,2), - r2 = d3.round(r*1.2,2); - return 'M-'+r2+',-'+r1+'H'+r1+'V'+r2+'Z'; - } - }, - 'triangle-se': { - n: 10, - f: function(r) { - var r1 = d3.round(r*0.6,2), - r2 = d3.round(r*1.2,2); - return 'M'+r1+',-'+r2+'V'+r1+'H-'+r2+'Z'; - } - }, - 'triangle-sw': { - n: 11, - f: function(r) { - var r1 = d3.round(r*0.6,2), - r2 = d3.round(r*1.2,2); - return 'M'+r2+','+r1+'H-'+r1+'V-'+r2+'Z'; - } - }, - 'triangle-nw': { - n: 12, - f: function(r) { - var r1 = d3.round(r*0.6,2), - r2 = d3.round(r*1.2,2); - return 'M-'+r1+','+r2+'V-'+r1+'H'+r2+'Z'; - } - }, - pentagon: { - n: 13, - f: function(r) { - var x1 = d3.round(r*0.951,2), - x2 = d3.round(r*0.588,2), - y0 = d3.round(-r,2), - y1 = d3.round(r*-0.309,2), - y2 = d3.round(r*0.809,2); - return 'M'+x1+','+y1+'L'+x2+','+y2+'H-'+x2+ - 'L-'+x1+','+y1+'L0,'+y0+'Z'; - } - }, - hexagon: { - n: 14, - f: function(r) { - var y0 = d3.round(r,2), - y1 = d3.round(r/2,2), - x = d3.round(r*Math.sqrt(3)/2,2); - return 'M'+x+',-'+y1+'V'+y1+'L0,'+y0+ - 'L-'+x+','+y1+'V-'+y1+'L0,-'+y0+'Z'; - } - }, - hexagon2: { - n: 15, - f: function(r) { - var x0 = d3.round(r,2), - x1 = d3.round(r/2,2), - y = d3.round(r*Math.sqrt(3)/2,2); - return 'M-'+x1+','+y+'H'+x1+'L'+x0+ - ',0L'+x1+',-'+y+'H-'+x1+'L-'+x0+',0Z'; - } - }, - octagon: { - n: 16, - f: function(r) { - var a = d3.round(r*0.924,2), - b = d3.round(r*0.383,2); - return 'M-'+b+',-'+a+'H'+b+'L'+a+',-'+b+'V'+b+ - 'L'+b+','+a+'H-'+b+'L-'+a+','+b+'V-'+b+'Z'; - } - }, - star: { - n: 17, - f: function(r) { - var rs = r*1.4, - x1 = d3.round(rs*0.225,2), - x2 = d3.round(rs*0.951,2), - x3 = d3.round(rs*0.363,2), - x4 = d3.round(rs*0.588,2), - y0 = d3.round(-rs,2), - y1 = d3.round(rs*-0.309,2), - y3 = d3.round(rs*0.118,2), - y4 = d3.round(rs*0.809,2), - y5 = d3.round(rs*0.382,2); - return 'M'+x1+','+y1+'H'+x2+'L'+x3+','+y3+ - 'L'+x4+','+y4+'L0,'+y5+'L-'+x4+','+y4+ - 'L-'+x3+','+y3+'L-'+x2+','+y1+'H-'+x1+ - 'L0,'+y0+'Z'; - } - }, - hexagram: { - n: 18, - f: function(r) { - var y = d3.round(r*0.66,2), - x1 = d3.round(r*0.38,2), - x2 = d3.round(r*0.76,2); - return 'M-'+x2+',0l-'+x1+',-'+y+'h'+x2+ - 'l'+x1+',-'+y+'l'+x1+','+y+'h'+x2+ - 'l-'+x1+','+y+'l'+x1+','+y+'h-'+x2+ - 'l-'+x1+','+y+'l-'+x1+',-'+y+'h-'+x2+'Z'; - } - }, - 'star-triangle-up': { - n: 19, - f: function(r) { - var x = d3.round(r*Math.sqrt(3)*0.8,2), - y1 = d3.round(r*0.8,2), - y2 = d3.round(r*1.6,2), - rc = d3.round(r*4,2), - aPart = 'A '+rc+','+rc+' 0 0 1 '; - return 'M-'+x+','+y1+aPart+x+','+y1+ - aPart+'0,-'+y2+aPart+'-'+x+','+y1+'Z'; - } - }, - 'star-triangle-down': { - n: 20, - f: function(r) { - var x = d3.round(r*Math.sqrt(3)*0.8,2), - y1 = d3.round(r*0.8,2), - y2 = d3.round(r*1.6,2), - rc = d3.round(r*4,2), - aPart = 'A '+rc+','+rc+' 0 0 1 '; - return 'M'+x+',-'+y1+aPart+'-'+x+',-'+y1+ - aPart+'0,'+y2+aPart+x+',-'+y1+'Z'; - } - }, - 'star-square': { - n: 21, - f: function(r) { - var rp = d3.round(r*1.1,2), - rc = d3.round(r*2,2), - aPart = 'A '+rc+','+rc+' 0 0 1 '; - return 'M-'+rp+',-'+rp+aPart+'-'+rp+','+rp+ - aPart+rp+','+rp+aPart+rp+',-'+rp+ - aPart+'-'+rp+',-'+rp+'Z'; - } - }, - 'star-diamond': { - n: 22, - f: function(r) { - var rp = d3.round(r*1.4,2), - rc = d3.round(r*1.9,2), - aPart = 'A '+rc+','+rc+' 0 0 1 '; - return 'M-'+rp+',0'+aPart+'0,'+rp+ - aPart+rp+',0'+aPart+'0,-'+rp+ - aPart+'-'+rp+',0'+'Z'; - } - }, - 'diamond-tall': { - n: 23, - f: function(r) { - var x = d3.round(r*0.7,2), - y = d3.round(r*1.4,2); - return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z'; - } - }, - 'diamond-wide': { - n: 24, - f: function(r) { - var x = d3.round(r*1.4,2), - y = d3.round(r*0.7,2); - return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z'; - } - }, - hourglass: { - n: 25, - f: function(r) { - var rs = d3.round(r,2); - return 'M'+rs+','+rs+'H-'+rs+'L'+rs+',-'+rs+'H-'+rs+'Z'; - }, - noDot: true - }, - bowtie: { - n: 26, - f: function(r) { - var rs = d3.round(r,2); - return 'M'+rs+','+rs+'V-'+rs+'L-'+rs+','+rs+'V-'+rs+'Z'; - }, - noDot: true - }, - 'circle-cross': { - n: 27, - f: function(r) { - var rs = d3.round(r,2); - return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+ - 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ - 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; - }, - needLine: true, - noDot: true - }, - 'circle-x': { - n: 28, - f: function(r) { - var rs = d3.round(r,2), - rc = d3.round(r/Math.sqrt(2),2); - return 'M'+rc+','+rc+'L-'+rc+',-'+rc+ - 'M'+rc+',-'+rc+'L-'+rc+','+rc+ - 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ - 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; - }, - needLine: true, - noDot: true - }, - 'square-cross': { - n: 29, - f: function(r) { - var rs = d3.round(r,2); - return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+ - 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; - }, - needLine: true, - noDot: true - }, - 'square-x': { - n: 30, - f: function(r) { - var rs = d3.round(r,2); - return 'M'+rs+','+rs+'L-'+rs+',-'+rs+ - 'M'+rs+',-'+rs+'L-'+rs+','+rs+ - 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; - }, - needLine: true, - noDot: true - }, - 'diamond-cross': { - n: 31, - f: function(r) { - var rd = d3.round(r*1.3,2); - return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+ - 'M0,-'+rd+'V'+rd+'M-'+rd+',0H'+rd; - }, - needLine: true, - noDot: true - }, - 'diamond-x': { - n: 32, - f: function(r) { - var rd = d3.round(r*1.3,2), - r2 = d3.round(r*0.65,2); - return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+ - 'M-'+r2+',-'+r2+'L'+r2+','+r2+ - 'M-'+r2+','+r2+'L'+r2+',-'+r2; - }, - needLine: true, - noDot: true - }, - 'cross-thin': { - n: 33, - f: function(r) { - var rc = d3.round(r*1.4,2); - return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc; - }, - needLine: true, - noDot: true - }, - 'x-thin': { - n: 34, - f: function(r) { - var rx = d3.round(r,2); - return 'M'+rx+','+rx+'L-'+rx+',-'+rx+ - 'M'+rx+',-'+rx+'L-'+rx+','+rx; - }, - needLine: true, - noDot: true - }, - asterisk: { - n: 35, - f: function(r) { - var rc = d3.round(r*1.2,2); - var rs = d3.round(r*0.85,2); - return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc+ - 'M'+rs+','+rs+'L-'+rs+',-'+rs+ - 'M'+rs+',-'+rs+'L-'+rs+','+rs; - }, - needLine: true, - noDot: true - }, - hash: { - n: 36, - f: function(r) { - var r1 = d3.round(r/2,2), - r2 = d3.round(r,2); - return 'M'+r1+','+r2+'V-'+r2+ - 'm-'+r2+',0V'+r2+ - 'M'+r2+','+r1+'H-'+r2+ - 'm0,-'+r2+'H'+r2; - }, - needLine: true - }, - 'y-up': { - n: 37, - f: function(r) { - var x = d3.round(r*1.2,2), - y0 = d3.round(r*1.6,2), - y1 = d3.round(r*0.8,2); - return 'M-'+x+','+y1+'L0,0M'+x+','+y1+'L0,0M0,-'+y0+'L0,0'; - }, - needLine: true, - noDot: true - }, - 'y-down': { - n: 38, - f: function(r) { - var x = d3.round(r*1.2,2), - y0 = d3.round(r*1.6,2), - y1 = d3.round(r*0.8,2); - return 'M-'+x+',-'+y1+'L0,0M'+x+',-'+y1+'L0,0M0,'+y0+'L0,0'; - }, - needLine: true, - noDot: true - }, - 'y-left': { - n: 39, - f: function(r) { - var y = d3.round(r*1.2,2), - x0 = d3.round(r*1.6,2), - x1 = d3.round(r*0.8,2); - return 'M'+x1+','+y+'L0,0M'+x1+',-'+y+'L0,0M-'+x0+',0L0,0'; - }, - needLine: true, - noDot: true - }, - 'y-right': { - n: 40, - f: function(r) { - var y = d3.round(r*1.2,2), - x0 = d3.round(r*1.6,2), - x1 = d3.round(r*0.8,2); - return 'M-'+x1+','+y+'L0,0M-'+x1+',-'+y+'L0,0M'+x0+',0L0,0'; - }, - needLine: true, - noDot: true - }, - 'line-ew': { - n: 41, - f: function(r) { - var rc = d3.round(r*1.4,2); - return 'M'+rc+',0H-'+rc; - }, - needLine: true, - noDot: true - }, - 'line-ns': { - n: 42, - f: function(r) { - var rc = d3.round(r*1.4,2); - return 'M0,'+rc+'V-'+rc; - }, - needLine: true, - noDot: true - }, - 'line-ne': { - n: 43, - f: function(r) { - var rx = d3.round(r,2); - return 'M'+rx+',-'+rx+'L-'+rx+','+rx; - }, - needLine: true, - noDot: true - }, - 'line-nw': { - n: 44, - f: function(r) { - var rx = d3.round(r,2); - return 'M'+rx+','+rx+'L-'+rx+',-'+rx; - }, - needLine: true, - noDot: true - } -}; +var SYMBOLDEFS = require('./symbol_defs'); drawing.symbolNames = []; drawing.symbolFuncs = []; drawing.symbolNeedLines = {}; drawing.symbolNoDot = {}; drawing.symbolList = []; + Object.keys(SYMBOLDEFS).forEach(function(k) { var symDef = SYMBOLDEFS[k]; drawing.symbolList = drawing.symbolList.concat( diff --git a/src/components/drawing/symbol_defs.js b/src/components/drawing/symbol_defs.js new file mode 100644 index 00000000000..2c461f2f003 --- /dev/null +++ b/src/components/drawing/symbol_defs.js @@ -0,0 +1,465 @@ +'use strict'; + +var d3 = require('d3'); + +/** Marker symbol definitions + * users can specify markers either by number or name + * add 100 (or '-open') and you get an open marker + * open markers have no fill and use line color as the stroke color + * add 200 (or '-dot') and you get a dot in the middle + * add both and you get both + */ + +module.exports = { + circle: { + n: 0, + f: function(r) { + var rs = d3.round(r,2); + return 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ + 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; + } + }, + square: { + n: 1, + f: function(r) { + var rs = d3.round(r,2); + return 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; + } + }, + diamond: { + n: 2, + f: function(r) { + var rd = d3.round(r*1.3,2); + return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'; + } + }, + cross: { + n: 3, + f: function(r) { + var rc = d3.round(r*0.4,2), + rc2 = d3.round(r*1.2,2); + return 'M'+rc2+','+rc+'H'+rc+'V'+rc2+'H-'+rc+ + 'V'+rc+'H-'+rc2+'V-'+rc+'H-'+rc+'V-'+rc2+ + 'H'+rc+'V-'+rc+'H'+rc2+'Z'; + } + }, + x: { + n: 4, + f: function(r) { + var rx = d3.round(r*0.8/Math.sqrt(2),2), + ne = 'l'+rx+','+rx, + se = 'l'+rx+',-'+rx, + sw = 'l-'+rx+',-'+rx, + nw = 'l-'+rx+','+rx; + return 'M0,'+rx+ne+se+sw+se+sw+nw+sw+nw+ne+nw+ne+'Z'; + } + }, + 'triangle-up': { + n: 5, + f: function(r) { + var rt = d3.round(r*2/Math.sqrt(3),2), + r2 = d3.round(r/2,2), + rs = d3.round(r,2); + return 'M-'+rt+','+r2+'H'+rt+'L0,-'+rs+'Z'; + } + }, + 'triangle-down': { + n: 6, + f: function(r) { + var rt = d3.round(r*2/Math.sqrt(3),2), + r2 = d3.round(r/2,2), + rs = d3.round(r,2); + return 'M-'+rt+',-'+r2+'H'+rt+'L0,'+rs+'Z'; + } + }, + 'triangle-left': { + n: 7, + f: function(r) { + var rt = d3.round(r*2/Math.sqrt(3),2), + r2 = d3.round(r/2,2), + rs = d3.round(r,2); + return 'M'+r2+',-'+rt+'V'+rt+'L-'+rs+',0Z'; + } + }, + 'triangle-right': { + n: 8, + f: function(r) { + var rt = d3.round(r*2/Math.sqrt(3),2), + r2 = d3.round(r/2,2), + rs = d3.round(r,2); + return 'M-'+r2+',-'+rt+'V'+rt+'L'+rs+',0Z'; + } + }, + 'triangle-ne': { + n: 9, + f: function(r) { + var r1 = d3.round(r*0.6,2), + r2 = d3.round(r*1.2,2); + return 'M-'+r2+',-'+r1+'H'+r1+'V'+r2+'Z'; + } + }, + 'triangle-se': { + n: 10, + f: function(r) { + var r1 = d3.round(r*0.6,2), + r2 = d3.round(r*1.2,2); + return 'M'+r1+',-'+r2+'V'+r1+'H-'+r2+'Z'; + } + }, + 'triangle-sw': { + n: 11, + f: function(r) { + var r1 = d3.round(r*0.6,2), + r2 = d3.round(r*1.2,2); + return 'M'+r2+','+r1+'H-'+r1+'V-'+r2+'Z'; + } + }, + 'triangle-nw': { + n: 12, + f: function(r) { + var r1 = d3.round(r*0.6,2), + r2 = d3.round(r*1.2,2); + return 'M-'+r1+','+r2+'V-'+r1+'H'+r2+'Z'; + } + }, + pentagon: { + n: 13, + f: function(r) { + var x1 = d3.round(r*0.951,2), + x2 = d3.round(r*0.588,2), + y0 = d3.round(-r,2), + y1 = d3.round(r*-0.309,2), + y2 = d3.round(r*0.809,2); + return 'M'+x1+','+y1+'L'+x2+','+y2+'H-'+x2+ + 'L-'+x1+','+y1+'L0,'+y0+'Z'; + } + }, + hexagon: { + n: 14, + f: function(r) { + var y0 = d3.round(r,2), + y1 = d3.round(r/2,2), + x = d3.round(r*Math.sqrt(3)/2,2); + return 'M'+x+',-'+y1+'V'+y1+'L0,'+y0+ + 'L-'+x+','+y1+'V-'+y1+'L0,-'+y0+'Z'; + } + }, + hexagon2: { + n: 15, + f: function(r) { + var x0 = d3.round(r,2), + x1 = d3.round(r/2,2), + y = d3.round(r*Math.sqrt(3)/2,2); + return 'M-'+x1+','+y+'H'+x1+'L'+x0+ + ',0L'+x1+',-'+y+'H-'+x1+'L-'+x0+',0Z'; + } + }, + octagon: { + n: 16, + f: function(r) { + var a = d3.round(r*0.924,2), + b = d3.round(r*0.383,2); + return 'M-'+b+',-'+a+'H'+b+'L'+a+',-'+b+'V'+b+ + 'L'+b+','+a+'H-'+b+'L-'+a+','+b+'V-'+b+'Z'; + } + }, + star: { + n: 17, + f: function(r) { + var rs = r*1.4, + x1 = d3.round(rs*0.225,2), + x2 = d3.round(rs*0.951,2), + x3 = d3.round(rs*0.363,2), + x4 = d3.round(rs*0.588,2), + y0 = d3.round(-rs,2), + y1 = d3.round(rs*-0.309,2), + y3 = d3.round(rs*0.118,2), + y4 = d3.round(rs*0.809,2), + y5 = d3.round(rs*0.382,2); + return 'M'+x1+','+y1+'H'+x2+'L'+x3+','+y3+ + 'L'+x4+','+y4+'L0,'+y5+'L-'+x4+','+y4+ + 'L-'+x3+','+y3+'L-'+x2+','+y1+'H-'+x1+ + 'L0,'+y0+'Z'; + } + }, + hexagram: { + n: 18, + f: function(r) { + var y = d3.round(r*0.66,2), + x1 = d3.round(r*0.38,2), + x2 = d3.round(r*0.76,2); + return 'M-'+x2+',0l-'+x1+',-'+y+'h'+x2+ + 'l'+x1+',-'+y+'l'+x1+','+y+'h'+x2+ + 'l-'+x1+','+y+'l'+x1+','+y+'h-'+x2+ + 'l-'+x1+','+y+'l-'+x1+',-'+y+'h-'+x2+'Z'; + } + }, + 'star-triangle-up': { + n: 19, + f: function(r) { + var x = d3.round(r*Math.sqrt(3)*0.8,2), + y1 = d3.round(r*0.8,2), + y2 = d3.round(r*1.6,2), + rc = d3.round(r*4,2), + aPart = 'A '+rc+','+rc+' 0 0 1 '; + return 'M-'+x+','+y1+aPart+x+','+y1+ + aPart+'0,-'+y2+aPart+'-'+x+','+y1+'Z'; + } + }, + 'star-triangle-down': { + n: 20, + f: function(r) { + var x = d3.round(r*Math.sqrt(3)*0.8,2), + y1 = d3.round(r*0.8,2), + y2 = d3.round(r*1.6,2), + rc = d3.round(r*4,2), + aPart = 'A '+rc+','+rc+' 0 0 1 '; + return 'M'+x+',-'+y1+aPart+'-'+x+',-'+y1+ + aPart+'0,'+y2+aPart+x+',-'+y1+'Z'; + } + }, + 'star-square': { + n: 21, + f: function(r) { + var rp = d3.round(r*1.1,2), + rc = d3.round(r*2,2), + aPart = 'A '+rc+','+rc+' 0 0 1 '; + return 'M-'+rp+',-'+rp+aPart+'-'+rp+','+rp+ + aPart+rp+','+rp+aPart+rp+',-'+rp+ + aPart+'-'+rp+',-'+rp+'Z'; + } + }, + 'star-diamond': { + n: 22, + f: function(r) { + var rp = d3.round(r*1.4,2), + rc = d3.round(r*1.9,2), + aPart = 'A '+rc+','+rc+' 0 0 1 '; + return 'M-'+rp+',0'+aPart+'0,'+rp+ + aPart+rp+',0'+aPart+'0,-'+rp+ + aPart+'-'+rp+',0'+'Z'; + } + }, + 'diamond-tall': { + n: 23, + f: function(r) { + var x = d3.round(r*0.7,2), + y = d3.round(r*1.4,2); + return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z'; + } + }, + 'diamond-wide': { + n: 24, + f: function(r) { + var x = d3.round(r*1.4,2), + y = d3.round(r*0.7,2); + return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z'; + } + }, + hourglass: { + n: 25, + f: function(r) { + var rs = d3.round(r,2); + return 'M'+rs+','+rs+'H-'+rs+'L'+rs+',-'+rs+'H-'+rs+'Z'; + }, + noDot: true + }, + bowtie: { + n: 26, + f: function(r) { + var rs = d3.round(r,2); + return 'M'+rs+','+rs+'V-'+rs+'L-'+rs+','+rs+'V-'+rs+'Z'; + }, + noDot: true + }, + 'circle-cross': { + n: 27, + f: function(r) { + var rs = d3.round(r,2); + return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+ + 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ + 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; + }, + needLine: true, + noDot: true + }, + 'circle-x': { + n: 28, + f: function(r) { + var rs = d3.round(r,2), + rc = d3.round(r/Math.sqrt(2),2); + return 'M'+rc+','+rc+'L-'+rc+',-'+rc+ + 'M'+rc+',-'+rc+'L-'+rc+','+rc+ + 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+ + 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z'; + }, + needLine: true, + noDot: true + }, + 'square-cross': { + n: 29, + f: function(r) { + var rs = d3.round(r,2); + return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+ + 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; + }, + needLine: true, + noDot: true + }, + 'square-x': { + n: 30, + f: function(r) { + var rs = d3.round(r,2); + return 'M'+rs+','+rs+'L-'+rs+',-'+rs+ + 'M'+rs+',-'+rs+'L-'+rs+','+rs+ + 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z'; + }, + needLine: true, + noDot: true + }, + 'diamond-cross': { + n: 31, + f: function(r) { + var rd = d3.round(r*1.3,2); + return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+ + 'M0,-'+rd+'V'+rd+'M-'+rd+',0H'+rd; + }, + needLine: true, + noDot: true + }, + 'diamond-x': { + n: 32, + f: function(r) { + var rd = d3.round(r*1.3,2), + r2 = d3.round(r*0.65,2); + return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+ + 'M-'+r2+',-'+r2+'L'+r2+','+r2+ + 'M-'+r2+','+r2+'L'+r2+',-'+r2; + }, + needLine: true, + noDot: true + }, + 'cross-thin': { + n: 33, + f: function(r) { + var rc = d3.round(r*1.4,2); + return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc; + }, + needLine: true, + noDot: true + }, + 'x-thin': { + n: 34, + f: function(r) { + var rx = d3.round(r,2); + return 'M'+rx+','+rx+'L-'+rx+',-'+rx+ + 'M'+rx+',-'+rx+'L-'+rx+','+rx; + }, + needLine: true, + noDot: true + }, + asterisk: { + n: 35, + f: function(r) { + var rc = d3.round(r*1.2,2); + var rs = d3.round(r*0.85,2); + return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc+ + 'M'+rs+','+rs+'L-'+rs+',-'+rs+ + 'M'+rs+',-'+rs+'L-'+rs+','+rs; + }, + needLine: true, + noDot: true + }, + hash: { + n: 36, + f: function(r) { + var r1 = d3.round(r/2,2), + r2 = d3.round(r,2); + return 'M'+r1+','+r2+'V-'+r2+ + 'm-'+r2+',0V'+r2+ + 'M'+r2+','+r1+'H-'+r2+ + 'm0,-'+r2+'H'+r2; + }, + needLine: true + }, + 'y-up': { + n: 37, + f: function(r) { + var x = d3.round(r*1.2,2), + y0 = d3.round(r*1.6,2), + y1 = d3.round(r*0.8,2); + return 'M-'+x+','+y1+'L0,0M'+x+','+y1+'L0,0M0,-'+y0+'L0,0'; + }, + needLine: true, + noDot: true + }, + 'y-down': { + n: 38, + f: function(r) { + var x = d3.round(r*1.2,2), + y0 = d3.round(r*1.6,2), + y1 = d3.round(r*0.8,2); + return 'M-'+x+',-'+y1+'L0,0M'+x+',-'+y1+'L0,0M0,'+y0+'L0,0'; + }, + needLine: true, + noDot: true + }, + 'y-left': { + n: 39, + f: function(r) { + var y = d3.round(r*1.2,2), + x0 = d3.round(r*1.6,2), + x1 = d3.round(r*0.8,2); + return 'M'+x1+','+y+'L0,0M'+x1+',-'+y+'L0,0M-'+x0+',0L0,0'; + }, + needLine: true, + noDot: true + }, + 'y-right': { + n: 40, + f: function(r) { + var y = d3.round(r*1.2,2), + x0 = d3.round(r*1.6,2), + x1 = d3.round(r*0.8,2); + return 'M-'+x1+','+y+'L0,0M-'+x1+',-'+y+'L0,0M'+x0+',0L0,0'; + }, + needLine: true, + noDot: true + }, + 'line-ew': { + n: 41, + f: function(r) { + var rc = d3.round(r*1.4,2); + return 'M'+rc+',0H-'+rc; + }, + needLine: true, + noDot: true + }, + 'line-ns': { + n: 42, + f: function(r) { + var rc = d3.round(r*1.4,2); + return 'M0,'+rc+'V-'+rc; + }, + needLine: true, + noDot: true + }, + 'line-ne': { + n: 43, + f: function(r) { + var rx = d3.round(r,2); + return 'M'+rx+',-'+rx+'L-'+rx+','+rx; + }, + needLine: true, + noDot: true + }, + 'line-nw': { + n: 44, + f: function(r) { + var rx = d3.round(r,2); + return 'M'+rx+','+rx+'L-'+rx+',-'+rx; + }, + needLine: true, + noDot: true + } +}; diff --git a/src/components/errorbars/attributes.js b/src/components/errorbars/attributes.js new file mode 100644 index 00000000000..9d20961e876 --- /dev/null +++ b/src/components/errorbars/attributes.js @@ -0,0 +1,129 @@ +module.exports = { + visible: { + valType: 'boolean', + role: 'info', + description: [ + 'Determines whether or not this set of error bars is visible.' + ].join(' ') + }, + type: { + valType: 'enumerated', + values: ['percent', 'constant', 'sqrt', 'data'], + role: 'info', + description: [ + 'Determines the rule used to generate the error bars.', + + 'If *constant`, the bar lengths are of a constant value.', + 'Set this constant in `value`.', + + 'If *percent*, the bar lengths correspond to a percentage of', + 'underlying data. Set this percentage in `value`.', + + 'If *sqrt*, the bar lengths correspond to the sqaure of the', + 'underlying data.', + + 'If *array*, the bar lengths are set with data set `array`.' + ].join(' ') + }, + symmetric: { + valType: 'boolean', + role: 'info', + description: [ + 'Determines whether or not the error bars have the same length', + 'in both direction', + '(top/bottom for vertical bars, left/right for horizontal bars.' + ].join(' ') + }, + array: { + valType: 'data_array', + description: [ + 'Sets the data corresponding the length of each error bar.', + 'Values are plotted relative to the underlying data.' + ].join(' ') + }, + arrayminus: { + valType: 'data_array', + description: [ + 'Sets the data corresponding the length of each error bar in the', + 'bottom (left) direction for vertical (horizontal) bars', + 'Values are plotted relative to the underlying data.' + ].join(' ') + }, + value: { + valType: 'number', + min: 0, + dflt: 10, + role: 'info', + description: [ + 'Sets the value of either the percentage', + '(if `type` is set to *percent*) or the constant', + '(if `type` is set to *constant*) corresponding to the lengths of', + 'the error bars.' + ].join(' ') + }, + valueminus: { + valType: 'number', + min: 0, + dflt: 10, + role: 'info', + description: [ + 'Sets the value of either the percentage', + '(if `type` is set to *percent*) or the constant', + '(if `type` is set to *constant*) corresponding to the lengths of', + 'the error bars in the', + 'bottom (left) direction for vertical (horizontal) bars' + ].join(' ') + }, + traceref: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'info' + }, + tracerefminus: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'info' + }, + copy_ystyle: { + valType: 'boolean', + role: 'style' + }, + copy_zstyle: { + valType: 'boolean', + role: 'style' + }, + color: { + valType: 'color', + role: 'style', + description: 'Sets the stoke color of the error bars.' + }, + thickness: { + valType: 'number', + min: 0, + dflt: 2, + role: 'style', + description: 'Sets the thickness (in px) of the error bars.' + }, + width: { + valType: 'number', + min: 0, + role: 'style', + description: [ + 'Sets the width (in px) of the cross-bar at both ends', + 'of the error bars.' + ].join(' ') + }, + + _deprecated: { + opacity: { + valType: 'number', + role: 'style', + description: [ + 'Obsolete.', + 'Use the alpha channel in error bar `color` to set the opacity.' + ].join(' ') + } + } +}; diff --git a/src/errorbars.js b/src/components/errorbars/errorbars.js similarity index 74% rename from src/errorbars.js rename to src/components/errorbars/errorbars.js index 05e2aff4c17..39537a73030 100644 --- a/src/errorbars.js +++ b/src/components/errorbars/errorbars.js @@ -1,142 +1,12 @@ 'use strict'; -/* jshint camelcase: false */ - -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); var errorBars = module.exports = {}; -errorBars.attributes = { - visible: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not this set of error bars is visible.' - ].join(' ') - }, - type: { - valType: 'enumerated', - values: ['percent', 'constant', 'sqrt', 'data'], - role: 'info', - description: [ - 'Determines the rule used to generate the error bars.', - - 'If *constant`, the bar lengths are of a constant value.', - 'Set this constant in `value`.', - - 'If *percent*, the bar lengths correspond to a percentage of', - 'underlying data. Set this percentage in `value`.', - - 'If *sqrt*, the bar lengths correspond to the sqaure of the', - 'underlying data.', - - 'If *array*, the bar lengths are set with data set `array`.' - ].join(' ') - }, - symmetric: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not the error bars have the same length', - 'in both direction', - '(top/bottom for vertical bars, left/right for horizontal bars.' - ].join(' ') - }, - array: { - valType: 'data_array', - description: [ - 'Sets the data corresponding the length of each error bar.', - 'Values are plotted relative to the underlying data.' - ].join(' ') - }, - arrayminus: { - valType: 'data_array', - description: [ - 'Sets the data corresponding the length of each error bar in the', - 'bottom (left) direction for vertical (horizontal) bars', - 'Values are plotted relative to the underlying data.' - ].join(' ') - }, - value: { - valType: 'number', - min: 0, - dflt: 10, - role: 'info', - description: [ - 'Sets the value of either the percentage', - '(if `type` is set to *percent*) or the constant', - '(if `type` is set to *constant*) corresponding to the lengths of', - 'the error bars.' - ].join(' ') - }, - valueminus: { - valType: 'number', - min: 0, - dflt: 10, - role: 'info', - description: [ - 'Sets the value of either the percentage', - '(if `type` is set to *percent*) or the constant', - '(if `type` is set to *constant*) corresponding to the lengths of', - 'the error bars in the', - 'bottom (left) direction for vertical (horizontal) bars' - ].join(' ') - }, - traceref: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'info' - }, - tracerefminus: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'info' - }, - copy_ystyle: { - valType: 'boolean', - role: 'style' - }, - copy_zstyle: { - valType: 'boolean', - role: 'style' - }, - color: { - valType: 'color', - role: 'style', - description: 'Sets the stoke color of the error bars.' - }, - thickness: { - valType: 'number', - min: 0, - dflt: 2, - role: 'style', - description: 'Sets the thickness (in px) of the error bars.' - }, - width: { - valType: 'number', - min: 0, - role: 'style', - description: [ - 'Sets the width (in px) of the cross-bar at both ends', - 'of the error bars.' - ].join(' ') - }, - - _deprecated: { - opacity: { - valType: 'number', - role: 'style', - description: [ - 'Obsolete.', - 'Use the alpha channel in error bar `color` to set the opacity.' - ].join(' ') - } - } -}; +errorBars.attributes = require('./attributes'); errorBars.supplyDefaults = function(traceIn, traceOut, defaultColor, opts) { var objName = 'error_' + opts.axis, diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js new file mode 100644 index 00000000000..e5ac2d36d04 --- /dev/null +++ b/src/components/legend/attributes.js @@ -0,0 +1,97 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var extendFlat = Plotly.Lib.extendFlat; + + +module.exports = { + bgcolor: { + valType: 'color', + role: 'style', + description: 'Sets the legend background color.' + }, + bordercolor: { + valType: 'color', + dflt: Plotly.Color.defaultLine, + role: 'style', + description: 'Sets the color of the border enclosing the legend.' + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: 'Sets the width (in px) of the border enclosing the legend.' + }, + font: extendFlat({}, Plotly.Plots.fontAttrs, { + description: 'Sets the font used to text the legend items.' + }), + traceorder: { + valType: 'flaglist', + flags: ['reversed', 'grouped'], + extras: ['normal'], + role: 'style', + description: [ + 'Determines the order at which the legend items are displayed.', + + 'If *normal*, the items are displayed top-to-bottom in the same', + 'order as the input data.', + + 'If *reversed*, the items are displayed in the opposite order', + 'as *normal*.', + + 'If *grouped*, the items are displayed in groups', + '(when a trace `legendgroup` is provided).', + + 'if *grouped+reversed*, the items are displayed in the opposite order', + 'as *grouped*.' + ].join(' ') + }, + tracegroupgap: { + valType: 'number', + min: 0, + dflt: 10, + role: 'style', + description: [ + 'Sets the amount of vertical space (in px) between legend groups.' + ].join(' ') + }, + x: { + valType: 'number', + min: -2, + max: 3, + dflt: 1.02, + role: 'style', + description: 'Sets the x position (in normalized coordinates) of the legend.' + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: [ + 'Sets the legend\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the legend.' + ].join(' ') + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 1, + role: 'style', + description: 'Sets the y position (in normalized coordinates) of the legend.' + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the legend\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the legend.' + ].join(' ') + } +}; diff --git a/src/legend.js b/src/components/legend/legend.js similarity index 88% rename from src/legend.js rename to src/components/legend/legend.js index 42fb366d050..9ee54e11ee0 100644 --- a/src/legend.js +++ b/src/components/legend/legend.js @@ -1,103 +1,11 @@ 'use strict'; -/* jshint camelcase: false */ - -var Plotly = require('./plotly'), - d3 = require('d3'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); var legend = module.exports = {}; -legend.layoutAttributes = { - bgcolor: { - valType: 'color', - role: 'style', - description: 'Sets the legend background color.' - }, - bordercolor: { - valType: 'color', - dflt: Plotly.Color.defaultLine, - role: 'style', - description: 'Sets the color of the border enclosing the legend.' - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: 'Sets the width (in px) of the border enclosing the legend.' - }, - font: Plotly.Lib.extendFlat({}, Plotly.Plots.fontAttrs, { - description: 'Sets the font used to text the legend items.' - }), - traceorder: { - valType: 'flaglist', - flags: ['reversed', 'grouped'], - extras: ['normal'], - role: 'style', - description: [ - 'Determines the order at which the legend items are displayed.', - - 'If *normal*, the items are displayed top-to-bottom in the same', - 'order as the input data.', - - 'If *reversed*, the items are displayed in the opposite order', - 'as *normal*.', - - 'If *grouped*, the items are displayed in groups', - '(when a trace `legendgroup` is provided).', - - 'if *grouped+reversed*, the items are displayed in the opposite order', - 'as *grouped*.' - ].join(' ') - }, - tracegroupgap: { - valType: 'number', - min: 0, - dflt: 10, - role: 'style', - description: [ - 'Sets the amount of vertical space (in px) between legend groups.' - ].join(' ') - }, - x: { - valType: 'number', - min: -2, - max: 3, - dflt: 1.02, - role: 'style', - description: 'Sets the x position (in normalized coordinates) of the legend.' - }, - xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'left', - role: 'info', - description: [ - 'Sets the legend\'s horizontal position anchor.', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the legend.' - ].join(' ') - }, - y: { - valType: 'number', - min: -2, - max: 3, - dflt: 1, - role: 'style', - description: 'Sets the y position (in normalized coordinates) of the legend.' - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'auto', - role: 'info', - description: [ - 'Sets the legend\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the legend.' - ].join(' ') - } -}; +legend.layoutAttributes = require('./attributes'); legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { var containerIn = layoutIn.legend || {}, diff --git a/src/modebar.js b/src/components/modebar/modebar.js similarity index 99% rename from src/modebar.js rename to src/components/modebar/modebar.js index c47fbbddb61..0396a1928af 100644 --- a/src/modebar.js +++ b/src/components/modebar/modebar.js @@ -1,7 +1,7 @@ 'use strict'; -var Plotly = require('./plotly'), - d3 = require('d3'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); /** * UI controller for interactive plots diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js new file mode 100644 index 00000000000..c064e229b14 --- /dev/null +++ b/src/components/shapes/attributes.js @@ -0,0 +1,133 @@ +var Plotly = require('../../plotly'); + +var scatterLineAttrs = Plotly.Scatter.attributes.line; +var extendFlat = Plotly.Lib.extendFlat; + +module.exports = { + _isLinkedToArray: true, + + type: { + valType: 'enumerated', + values: ['circle', 'rect', 'path', 'line'], + role: 'info', + description: [ + 'Specifies the shape type to be drawn.', + + 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)', + + 'If *circle*, a circle is drawn from', + '((`x0`+`x1`)/2, (`y0`+`y1`)/2))', + 'with radius', + '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)', + + 'If *rect*, a rectangle is drawn linking', + '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)', + + 'If *path*, draw a custom SVG path using `path`.' + ].join(' ') + }, + + xref: extendFlat({}, Plotly.Annotations.layoutAttributes.xref, { + description: [ + 'Sets the shape\'s x coordinate axis.', + 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left side of the plotting area in normalized coordinates', + 'where *0* (*1*) corresponds to the left (right) side.' + ].join(' ') + }), + x0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s starting x position.', + 'See `type` for more info.' + ].join(' ') + }, + x1: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s end x position.', + 'See `type` for more info.' + ].join(' ') + }, + + yref: extendFlat({}, Plotly.Annotations.layoutAttributes.yref, { + description: [ + 'Sets the annotation\'s y coordinate axis.', + 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to an y coordinate', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plotting area in normalized coordinates', + 'where *0* (*1*) corresponds to the bottom (top).' + ].join(' ') + }), + y0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s starting y position.', + 'See `type` for more info.' + ].join(' ') + }, + y1: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s end y position.', + 'See `type` for more info.' + ].join(' ') + }, + + path: { + valType: 'string', + role: 'info', + description: [ + 'For `type` *path* - a valid SVG path but with the pixel values', + 'replaced by data values. There are a few restrictions / quirks', + 'only absolute instructions, not relative. So the allowed segments', + 'are: M, L, H, V, Q, C, T, S, and Z', + 'arcs (A) are not allowed because radius rx and ry are relative.', + + 'In the future we could consider supporting relative commands,', + 'but we would have to decide on how to handle date and log axes.', + 'Note that even as is, Q and C Bezier paths that are smooth on', + 'linear axes may not be smooth on log, and vice versa.', + 'no chained "polybezier" commands - specify the segment type for', + 'each one.', + + 'On category axes, values are numbers scaled to the serial numbers', + 'of categories because using the categories themselves there would', + 'be no way to describe fractional positions', + 'On data axes: because space and T are both normal components of path', + 'strings, we can\'t use either to separate date from time parts.', + 'Therefore we\'ll use underscore for this purpose:', + '2015-02-21_13:45:56.789' + ].join(' ') + }, + + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + role: 'info', + description: 'Sets the opacity of the shape.' + }, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + role: 'info' + }, + fillcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'info', + description: [ + 'Sets the color filling the shape\'s interior.' + ].join(' ') + } +}; diff --git a/src/shapes.js b/src/components/shapes/shapes.js similarity index 77% rename from src/shapes.js rename to src/components/shapes/shapes.js index 1459d2d2ea3..6ed2dd97c31 100644 --- a/src/shapes.js +++ b/src/components/shapes/shapes.js @@ -1,140 +1,11 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var isNumeric = require('fast-isnumeric'); var shapes = module.exports = {}; -var extendFlat = Plotly.Lib.extendFlat, - scatterLineAttrs = Plotly.Scatter.attributes.line; - -shapes.layoutAttributes = { - _isLinkedToArray: true, - type: { - valType: 'enumerated', - values: ['circle', 'rect', 'path', 'line'], - role: 'info', - description: [ - 'Specifies the shape type to be drawn.', - - 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)', - - 'If *circle*, a circle is drawn from', - '((`x0`+`x1`)/2, (`y0`+`y1`)/2))', - 'with radius', - '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)', - - 'If *rect*, a rectangle is drawn linking', - '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)', - - 'If *path*, draw a custom SVG path using `path`.' - ].join(' ') - }, - - xref: extendFlat({}, Plotly.Annotations.layoutAttributes.xref, { - description: [ - 'Sets the shape\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the left (right) side.' - ].join(' ') - }), - x0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s starting x position.', - 'See `type` for more info.' - ].join(' ') - }, - x1: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s end x position.', - 'See `type` for more info.' - ].join(' ') - }, - - yref: extendFlat({}, Plotly.Annotations.layoutAttributes.yref, { - description: [ - 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).' - ].join(' ') - }), - y0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s starting y position.', - 'See `type` for more info.' - ].join(' ') - }, - y1: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s end y position.', - 'See `type` for more info.' - ].join(' ') - }, - - path: { - valType: 'string', - role: 'info', - description: [ - 'For `type` *path* - a valid SVG path but with the pixel values', - 'replaced by data values. There are a few restrictions / quirks', - 'only absolute instructions, not relative. So the allowed segments', - 'are: M, L, H, V, Q, C, T, S, and Z', - 'arcs (A) are not allowed because radius rx and ry are relative.', - - 'In the future we could consider supporting relative commands,', - 'but we would have to decide on how to handle date and log axes.', - 'Note that even as is, Q and C Bezier paths that are smooth on', - 'linear axes may not be smooth on log, and vice versa.', - 'no chained "polybezier" commands - specify the segment type for', - 'each one.', - - 'On category axes, values are numbers scaled to the serial numbers', - 'of categories because using the categories themselves there would', - 'be no way to describe fractional positions', - 'On data axes: because space and T are both normal components of path', - 'strings, we can\'t use either to separate date from time parts.', - 'Therefore we\'ll use underscore for this purpose:', - '2015-02-21_13:45:56.789' - ].join(' ') - }, - - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - role: 'info', - description: 'Sets the opacity of the shape.' - }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - role: 'info' - }, - fillcolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'info', - description: [ - 'Sets the color filling the shape\'s interior.' - ].join(' ') - } -}; +shapes.layoutAttributes = require('./attributes'); shapes.supplyLayoutDefaults = function(layoutIn, layoutOut) { var containerIn = layoutIn.shapes || [], From 97cb7bba5d7d472179b5354661d9f1922c42a146 Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 23:08:11 -0500 Subject: [PATCH 06/38] break up graph_obj.js: - Plotly methods go in src/plot_api/plot_api.js - Plotly.Plots module goes in src/plots/plots/plots.js - break up attribute object in Plotly.Plots into seperate files - factor Plotly.Plots.titles into components/titles/ --- src/components/titles/titles.js | 304 ++++ src/{graph_obj.js => plot_api/plot_api.js} | 1733 +------------------- src/plots/plots/attributes.js | 94 ++ src/plots/plots/font_attributes.js | 29 + src/plots/plots/layout_attributes.js | 191 +++ src/plots/plots/plots.js | 959 +++++++++++ 6 files changed, 1663 insertions(+), 1647 deletions(-) create mode 100644 src/components/titles/titles.js rename src/{graph_obj.js => plot_api/plot_api.js} (67%) create mode 100644 src/plots/plots/attributes.js create mode 100644 src/plots/plots/font_attributes.js create mode 100644 src/plots/plots/layout_attributes.js create mode 100644 src/plots/plots/plots.js diff --git a/src/components/titles/titles.js b/src/components/titles/titles.js new file mode 100644 index 00000000000..e390d09107c --- /dev/null +++ b/src/components/titles/titles.js @@ -0,0 +1,304 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var d3 = require('d3'); +var isNumeric = require('fast-isnumeric'); + +var plots = Plotly.Plots; + +var Titles = module.exports = {}; + +// titles - (re)draw titles on the axes and plot +// title can be 'xtitle', 'ytitle', 'gtitle', +// or empty to draw all +Titles.draw = function(gd, title) { + if(!title) { + Plotly.Axes.listIds(gd).forEach(function(axId) { + Titles.draw(gd, axId + 'title'); + }); + Titles.draw(gd, 'gtitle'); + return; + } + + var fullLayout = gd._fullLayout, + gs = fullLayout._size, + axletter = title.charAt(0), + colorbar = title.substr(1,2)==='cb'; + + var cbnum, cont, options; + + if(colorbar) { + var uid = title.substr(3).replace('title',''); + gd._fullData.some(function(trace, i) { + if(trace.uid===uid) { + cbnum = i; + cont = gd.calcdata[i][0].t.cb.axis; + return true; + } + }); + } + else cont = fullLayout[Plotly.Axes.id2name(title.replace('title',''))] || fullLayout; + + var prop = cont===fullLayout ? 'title' : cont._name+'.title', + name = colorbar ? 'colorscale' : + ((cont._id||axletter).toUpperCase()+' axis'), + font = cont.titlefont.family, + fontSize = cont.titlefont.size, + fontColor = cont.titlefont.color, + x, + y, + transform='', + attr = {}, + xa, + ya, + avoid = { + selection:d3.select(gd).selectAll('g.'+cont._id+'tick'), + side:cont.side + }, + // multiples of fontsize to offset label from axis + offsetBase = colorbar ? 0 : 1.5, + avoidTransform; + + // find the transform applied to the parents of the avoid selection + // which doesn't get picked up by Plotly.Drawing.bBox + if(colorbar) { + avoid.offsetLeft = gs.l; + avoid.offsetTop = gs.t; + } + else if(avoid.selection.size()) { + avoidTransform = d3.select(avoid.selection.node().parentNode) + .attr('transform') + .match(/translate\(([-\.\d]+),([-\.\d]+)\)/); + if(avoidTransform) { + avoid.offsetLeft = +avoidTransform[1]; + avoid.offsetTop = +avoidTransform[2]; + } + } + + if(colorbar && cont.titleside) { + // argh, we only make it here if the title is on top or bottom, + // not right + x = gs.l+cont.titlex*gs.w; + y = gs.t+(1-cont.titley)*gs.h + ((cont.titleside==='top') ? + 3+fontSize*0.75 : - 3-fontSize*0.25); + options = {x: x, y: y, 'text-anchor':'start'}; + avoid = {}; + + // convertToTspans rotates any 'y...' by 90 degrees... + // TODO: need a better solution than this hack + title = 'h'+title; + } + else if(axletter==='x'){ + xa = cont; + ya = (xa.anchor==='free') ? + {_offset:gs.t+(1-(xa.position||0))*gs.h, _length:0} : + Plotly.Axes.getFromId(gd, xa.anchor); + x = xa._offset+xa._length/2; + y = ya._offset + ((xa.side==='top') ? + -10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) : + ya._length + 10 + + fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5))); + options = {x: x, y: y, 'text-anchor': 'middle'}; + if(!avoid.side) { avoid.side = 'bottom'; } + } + else if(axletter==='y'){ + ya = cont; + xa = (ya.anchor==='free') ? + {_offset:gs.l+(ya.position||0)*gs.w, _length:0} : + Plotly.Axes.getFromId(gd, ya.anchor); + y = ya._offset+ya._length/2; + x = xa._offset + ((ya.side==='right') ? + xa._length + 10 + + fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) : + -10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0))); + attr = {center: 0}; + options = {x: x, y: y, 'text-anchor': 'middle'}; + transform = {rotate: '-90', offset: 0}; + if(!avoid.side) { avoid.side = 'left'; } + } + else{ + // plot title + name = 'Plot'; + fontSize = fullLayout.titlefont.size; + x = fullLayout.width/2; + y = fullLayout._size.t/2; + options = {x: x, y: y, 'text-anchor': 'middle'}; + avoid = {}; + } + + var opacity = 1, + isplaceholder = false, + txt = cont.title.trim(); + if(txt === '') { opacity = 0; } + if(txt.match(/Click to enter .+ title/)) { + opacity = 0.2; + isplaceholder = true; + } + + var group; + if(colorbar) { + group = d3.select(gd) + .selectAll('.'+cont._id.substr(1)+' .cbtitle'); + // this class-to-rotate thing with convertToTspans is + // getting hackier and hackier... delete groups with the + // wrong class + var otherClass = title.charAt(0)==='h' ? + title.substr(1) : ('h'+title); + group.selectAll('.'+otherClass+',.'+otherClass+'-math-group') + .remove(); + } + else { + group = fullLayout._infolayer.selectAll('.g-'+title) + .data([0]); + group.enter().append('g') + .classed('g-'+title, true); + } + + var el = group.selectAll('text') + .data([0]); + el.enter().append('text'); + el.text(txt) + // this is hacky, but convertToTspans uses the class + // to determine whether to rotate mathJax... + // so we need to clear out any old class and put the + // correct one (only relevant for colorbars, at least + // for now) - ie don't use .classed + .attr('class', title); + + function titleLayout(titleEl){ + Plotly.Lib.syncOrAsync([drawTitle,scootTitle], titleEl); + } + + function drawTitle(titleEl) { + titleEl.attr('transform', transform ? + 'rotate(' + [transform.rotate, options.x, options.y] + + ') translate(0, '+transform.offset+')' : + null); + titleEl.style({ + 'font-family': font, + 'font-size': d3.round(fontSize,2)+'px', + fill: Plotly.Color.rgb(fontColor), + opacity: opacity*Plotly.Color.opacity(fontColor), + 'font-weight': plots.fontWeight + }) + .attr(options) + .call(Plotly.util.convertToTspans) + .attr(options); + titleEl.selectAll('tspan.line') + .attr(options); + return plots.previousPromises(gd); + } + + function scootTitle(titleElIn) { + var titleGroup = d3.select(titleElIn.node().parentNode); + + if(avoid && avoid.selection && avoid.side && txt){ + titleGroup.attr('transform',null); + + // move toward avoid.side (= left, right, top, bottom) if needed + // can include pad (pixels, default 2) + var shift = 0, + backside = { + left: 'right', + right: 'left', + top: 'bottom', + bottom: 'top' + }[avoid.side], + shiftSign = (['left','top'].indexOf(avoid.side)!==-1) ? + -1 : 1, + pad = isNumeric(avoid.pad) ? avoid.pad : 2, + titlebb = Plotly.Drawing.bBox(titleGroup.node()), + paperbb = { + left: 0, + top: 0, + right: fullLayout.width, + bottom: fullLayout.height + }, + maxshift = colorbar ? fullLayout.width: + (paperbb[avoid.side]-titlebb[avoid.side]) * + ((avoid.side==='left' || avoid.side==='top') ? -1 : 1); + // Prevent the title going off the paper + if(maxshift<0) shift = maxshift; + else { + // so we don't have to offset each avoided element, + // give the title the opposite offset + titlebb.left -= avoid.offsetLeft; + titlebb.right -= avoid.offsetLeft; + titlebb.top -= avoid.offsetTop; + titlebb.bottom -= avoid.offsetTop; + + // iterate over a set of elements (avoid.selection) + // to avoid collisions with + avoid.selection.each(function(){ + var avoidbb = Plotly.Drawing.bBox(this); + + if(Plotly.Lib.bBoxIntersect(titlebb,avoidbb,pad)) { + shift = Math.max(shift, shiftSign * ( + avoidbb[avoid.side] - titlebb[backside]) + pad); + } + }); + shift = Math.min(maxshift, shift); + } + if(shift>0 || maxshift<0) { + var shiftTemplate = { + left: [-shift, 0], + right: [shift, 0], + top: [0, -shift], + bottom: [0, shift] + }[avoid.side]; + titleGroup.attr('transform', + 'translate(' + shiftTemplate + ')'); + } + } + } + + el.attr({'data-unformatted': txt}) + .call(titleLayout); + + var placeholderText = 'Click to enter '+name.replace(/\d+/,'')+' title'; + + function setPlaceholder(){ + opacity = 0; + isplaceholder = true; + txt = placeholderText; + fullLayout._infolayer.select('.'+title) + .attr({'data-unformatted': txt}) + .text(txt) + .on('mouseover.opacity',function(){ + d3.select(this).transition() + .duration(100).style('opacity',1); + }) + .on('mouseout.opacity',function(){ + d3.select(this).transition() + .duration(1000).style('opacity',0); + }); + } + + if(gd._context.editable){ + if(!txt) setPlaceholder(); + + el.call(Plotly.util.makeEditable) + .on('edit', function(text){ + if(colorbar) { + var trace = gd._fullData[cbnum]; + if(plots.traceIs(trace, 'markerColorscale')) { + Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum); + } else Plotly.restyle(gd, 'colorbar.title', text, cbnum); + } + else Plotly.relayout(gd,prop,text); + }) + .on('cancel', function(){ + this.text(this.attr('data-unformatted')) + .call(titleLayout); + }) + .on('input', function(d){ + this.text(d || ' ').attr(options) + .selectAll('tspan.line') + .attr(options); + }); + } + else if(!txt || txt.match(/Click to enter .+ title/)) { + el.remove(); + } + el.classed('js-placeholder',isplaceholder); +}; diff --git a/src/graph_obj.js b/src/plot_api/plot_api.js similarity index 67% rename from src/graph_obj.js rename to src/plot_api/plot_api.js index 7050edaa173..225e2a5d64d 100644 --- a/src/graph_obj.js +++ b/src/plot_api/plot_api.js @@ -1,414 +1,30 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../plotly'); +var Events = require('../lib/events'); + var d3 = require('d3'); var m4FromQuat = require('gl-mat4/fromQuat'); var isNumeric = require('fast-isnumeric'); -var Events = require('./events'); - -var plots = module.exports = {}; -// Most of the generic plotting functions get put into Plotly.Plots, -// but some - the ones we want 3rd-party developers to use - go directly -// into Plotly. These are: -// plot -// restyle -// relayout - -// allTypes and getModule are used for the graph_reference app as well as plotting -var modules = plots.modules = {}, - allTypes = plots.allTypes = [], - allCategories = plots.allCategories = {}, - subplotsRegistry = plots.subplotsRegistry = {}; - -/** - * plots.register: register a module as the handler for a trace type - * - * _module: (object) the module that will handle plotting this trace type - * thisType: (string) - * categoriesIn: (array of strings) all the categories this type is in, - * tested by calls: Plotly.Plots.traceIs(trace, oneCategory) - * meta: (object) add meta information about the trace type - */ -plots.register = function(_module, thisType, categoriesIn, meta) { - if(modules[thisType]) { - throw new Error('type ' + thisType + ' already registered'); - } - - var categoryObj = {}; - for(var i = 0; i < categoriesIn.length; i++) { - categoryObj[categoriesIn[i]] = true; - allCategories[categoriesIn[i]] = true; - } - - modules[thisType] = { - module: _module, - categories: categoryObj - }; - - if(meta && Object.keys(meta).length) { - modules[thisType].meta = meta; - } - - allTypes.push(thisType); -}; - -function getTraceType(traceType) { - if(typeof traceType === 'object') traceType = traceType.type; - return traceType; -} - -plots.getModule = function(trace) { - if(trace.r !== undefined) { - console.log('Oops, tried to put a polar trace ' + - 'on an incompatible graph of cartesian ' + - 'data. Ignoring this dataset.', trace - ); - return false; - } - - var _module = modules[getTraceType(trace)]; - if(!_module) return false; - return _module.module; -}; +var plots = Plotly.Plots; /** - * plots.traceIs: is this trace type in this category? + * Main plot-creation function * - * traceType: a trace (object) or trace type (string) - * category: a category (string) - */ -plots.traceIs = function traceIs(traceType, category) { - traceType = getTraceType(traceType); - - if(traceType === 'various') return false; // FIXME - - var _module = modules[traceType]; - - if(!_module) { - if(traceType !== undefined) { - console.warn('unrecognized trace type ' + traceType); - } - _module = modules[plots.attributes.type.dflt]; - } - - return !!_module.categories[category]; -}; - - -/** - * plots.registerSubplot: register a subplot type - * - * subplotType: (string) subplot type - * (these must also be categories defined with plots.register) - * attr: (string or array of strings) attribute name in traces and layout - * idRoot: (string or array of strings) root of id - * (setting the possible value for attrName) - * attributes (object) attribute for traces of this subplot type - * - * In trace objects `attr` is the object key taking a valid `id` as value - * (the set of all valid ids is generated below and stored in idRegex). + * Note: will call makePlotFramework if necessary to create the framework * - * In the layout object, a or several valid `attr` name(s) can be keys linked - * to a nested attribute objects - * (the set of all valid attr names is generated below and stored in attrRegex). - * - * TODO use these in Lib.coerce - */ -plots.registerSubplot = function(subplotType, attr, idRoot, attributes) { - if(subplotsRegistry[subplotType]) { - throw new Error('subplot ' + subplotType + ' already registered'); - } - - var regexStart = '^', - regexEnd = '([2-9]|[1-9][0-9]+)?$', - hasXY = (subplotType === 'cartesian' || subplotsRegistry === 'gl2d'); - - function makeRegex(mid) { - return new RegExp(regexStart + mid + regexEnd); - } - - // not sure what's best for the 'cartesian' type at this point - subplotsRegistry[subplotType] = { - attr: attr, - idRoot: idRoot, - attributes: attributes, - // register the regex representing the set of all valid attribute names - attrRegex: hasXY ? - { x: makeRegex(attr[0]), y: makeRegex(attr[1]) } : - makeRegex(attr), - // register the regex representing the set of all valid attribute ids - idRegex: hasXY ? - { x: makeRegex(idRoot[0]), y: makeRegex(idRoot[1]) } : - makeRegex(idRoot) - }; -}; - -// TODO separate the 'find subplot' step (which looks in layout) -// from the 'get subplot ids' step (which looks in fullLayout._plots) -plots.getSubplotIds = function getSubplotIds(layout, type) { - if(plots.subplotsRegistry[type] === undefined) return []; - - // layout must be 'fullLayout' here - if(type === 'cartesian' && !layout._hasCartesian) return []; - if(type === 'gl2d' && !layout._hasGL2D) return []; - if(type === 'cartesian' || type === 'gl2d') { - return Object.keys(layout._plots); - } - - var idRegex = plots.subplotsRegistry[type].idRegex, - layoutKeys = Object.keys(layout), - subplotIds = [], - layoutKey; - - for(var i = 0; i < layoutKeys.length; i++) { - layoutKey = layoutKeys[i]; - if(idRegex.test(layoutKey)) subplotIds.push(layoutKey); - } - - return subplotIds; -}; - -plots.getSubplotIdsInData = function getSubplotsInData(data, type) { - if(plots.subplotsRegistry[type] === undefined) return []; - - var attr = plots.subplotsRegistry[type].attr, - subplotIds = [], - trace; - - for (var i = 0; i < data.length; i++) { - trace = data[i]; - if(Plotly.Plots.traceIs(trace, type) && subplotIds.indexOf(trace[attr])===-1) { - subplotIds.push(trace[attr]); - } - } - - return subplotIds; -}; - -plots.getSubplotData = function getSubplotData(data, type, subplotId) { - if(plots.subplotsRegistry[type] === undefined) return []; - - var attr = plots.subplotsRegistry[type].attr, - subplotData = [], - trace; - - for(var i = 0; i < data.length; i++) { - trace = data[i]; - - if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { - var spmatch = Plotly.Axes.subplotMatch, - subplotX = 'x' + subplotId.match(spmatch)[1], - subplotY = 'y' + subplotId.match(spmatch)[2]; - - if(trace[attr[0]]===subplotX && trace[attr[1]]===subplotY) { - subplotData.push(trace); - } - } - else { - if(trace[attr] === subplotId) subplotData.push(trace); - } - } - - return subplotData; -}; - -// in some cases the browser doesn't seem to know how big -// the text is at first, so it needs to draw it, -// then wait a little, then draw it again -plots.redrawText = function(gd) { - gd = getGraphDiv(gd); - - // doesn't work presently (and not needed) for polar or 3d - if(gd._fullLayout._hasGL3D || (gd.data && gd.data[0] && gd.data[0].r)) { - return; - } - - return new Promise(function(resolve) { - setTimeout(function(){ - Plotly.Annotations.drawAll(gd); - Plotly.Legend.draw(gd); - (gd.calcdata||[]).forEach(function(d){ - if(d[0]&&d[0].t&&d[0].t.cb) d[0].t.cb(); - }); - resolve(plots.previousPromises(gd)); - },300); - }); -}; - -function opaqueSetBackground(gd, bgColor) { - gd._fullLayout._paperdiv.style('background', 'white'); - Plotly.defaultConfig.setBackground(gd, bgColor); -} - -function setPlotContext(gd, config) { - if(!gd._context) gd._context = Plotly.Lib.extendFlat({}, Plotly.defaultConfig); - var context = gd._context; - - if(config) { - Object.keys(config).forEach(function(key) { - if(key in context) { - if(key === 'setBackground' && config[key] === 'opaque') { - context[key] = opaqueSetBackground; - } - else context[key] = config[key]; - } - }); - - // cause a remake of the modebar any time we change context - if(gd._fullLayout && gd._fullLayout._modebar) { - delete gd._fullLayout._modebar; - } - - // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility - if(config.plot3dPixelRatio && !context.plotGlPixelRatio) { - context.plotGlPixelRatio = context.plot3dPixelRatio; - } - } - - //staticPlot forces a bunch of others: - if(context.staticPlot) { - context.editable = false; - context.autosizable = false; - context.scrollZoom = false; - context.doubleClick = false; - context.showTips = false; - context.showLink = false; - context.displayModeBar = false; - } -} - -/** - * Adds the 'Edit chart' link. - * Note that now Plotly.plot() calls this so it can regenerate whenever it replots + * @param {string id or DOM element} gd + * the id or DOM element of the graph container div + * @param {array of objects} data + * array of traces, containing the data and display information for each trace + * @param {object} layout + * object describing the overall display of the plot, + * all the stuff that doesn't pertain to any individual trace + * @param {object} config + * configuration options * - * Add source links to your graph inside the 'showSources' config argument. */ -plots.addLinks = function(gd) { - var fullLayout = gd._fullLayout; - - var linkContainer = fullLayout._paper - .selectAll('text.js-plot-link-container').data([0]); - - linkContainer.enter().append('text') - .classed('js-plot-link-container', true) - .style({ - 'font-family':'"Open Sans",Arial,sans-serif', - 'font-size':'12px', - 'fill': Plotly.Color.defaultLine, - 'pointer-events': 'all' - }) - .each(function(){ - var links = d3.select(this); - links.append('tspan').classed('js-link-to-tool', true); - links.append('tspan').classed('js-link-spacer', true); - links.append('tspan').classed('js-sourcelinks', true); - }); - - // The text node inside svg - var text = linkContainer.node(), - attrs = { - y: fullLayout._paper.attr('height') - 9 - }; - - // If text's width is bigger than the layout - // IE doesn't like getComputedTextLength if an element - // isn't visible, which it (sometimes?) isn't - // apparently offsetParent is null for invisibles. - // http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom - if (text && text.offsetParent && - text.getComputedTextLength() >= (fullLayout.width - 20)) { - // Align the text at the left - attrs['text-anchor'] = 'start'; - attrs.x = 5; - } - else { - // Align the text at the right - attrs['text-anchor'] = 'end'; - attrs.x = fullLayout._paper.attr('width') - 7; - } - - linkContainer.attr(attrs); - - var toolspan = linkContainer.select('.js-link-to-tool'), - spacespan = linkContainer.select('.js-link-spacer'), - sourcespan = linkContainer.select('.js-sourcelinks'); - - if(gd._context.showSources) gd._context.showSources(gd); - - // 'view in plotly' link for embedded plots - if(gd._context.showLink) positionPlayWithData(gd, toolspan); - - // separator if we have both sources and tool link - spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : ''); -}; - -// note that now this function is only adding the brand in -// iframes and 3rd-party apps -function positionPlayWithData(gd, container){ - container.text(''); - var link = container.append('a') - .attr({ - 'xlink:xlink:href': '#', - 'class': 'link--impt link--embedview', - 'font-weight':'bold' - }) - .text(gd._context.linkText + ' ' + String.fromCharCode(187)); - - if(gd._context.sendData) { - link.on('click',function(){ - gd.emit('plotly_beforeexport'); - - var baseUrl = (window.PLOTLYENV && window.PLOTLYENV.BASE_URL) || 'https://plot.ly'; - - var hiddenformDiv = d3.select(gd) - .append('div') - .attr('id', 'hiddenform') - .style('display', 'none'); - - var hiddenform = hiddenformDiv - .append('form') - .attr({ - action: baseUrl + '/external', - method: 'post', - target: '_blank' - }); - - var hiddenformInput = hiddenform - .append('input') - .attr({ - type: 'text', - name: 'data' - }); - - hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata'); - hiddenform.node().submit(); - hiddenformDiv.remove(); - - gd.emit('plotly_afterexport'); - return false; - }); - } - else { - var path = window.location.pathname.split('/'); - var query = window.location.search; - link.attr({ - 'xlink:xlink:show': 'new', - 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query - }); - } -} - -// ---------------------------------------------------- -// Main plot-creation function. Note: will call makePlotFramework -// if necessary to create the framework -// ---------------------------------------------------- -// inputs: -// gd - the id or DOM element of the graph container div -// data - array of traces, containing the data and display -// information for each trace -// layout - object describing the overall display of the plot, -// all the stuff that doesn't pertain to any individual trace Plotly.plot = function(gd, data, layout, config) { Plotly.Lib.markTime('in plot'); @@ -548,7 +164,7 @@ Plotly.plot = function(gd, data, layout, config) { else trace._module.colorbar(gd, cd); } - doAutoMargin(gd); + plots.doAutoMargin(gd); return plots.previousPromises(gd); } @@ -735,6 +351,70 @@ Plotly.plot = function(gd, data, layout, config) { donePlotting : Promise.resolve(); }; +// Get the container div: we store all variables for this plot as +// properties of this div +// some callers send this in by DOM element, others by id (string) +function getGraphDiv(gd) { + var gdElement; + + if(typeof gd === 'string') { + gdElement = document.getElementById(gd); + + if(gdElement === null) { + throw new Error('No DOM element with id \'' + gd + '\' exits on the page.'); + } + + return gdElement; + } + else if(gd===null || gd===undefined) { + throw new Error('DOM element provided is null or undefined'); + } + + return gd; // otherwise assume that gd is a DOM element +} + +function opaqueSetBackground(gd, bgColor) { + gd._fullLayout._paperdiv.style('background', 'white'); + Plotly.defaultConfig.setBackground(gd, bgColor); +} + +function setPlotContext(gd, config) { + if(!gd._context) gd._context = Plotly.Lib.extendFlat({}, Plotly.defaultConfig); + var context = gd._context; + + if(config) { + Object.keys(config).forEach(function(key) { + if(key in context) { + if(key === 'setBackground' && config[key] === 'opaque') { + context[key] = opaqueSetBackground; + } + else context[key] = config[key]; + } + }); + + // cause a remake of the modebar any time we change context + if(gd._fullLayout && gd._fullLayout._modebar) { + delete gd._fullLayout._modebar; + } + + // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility + if(config.plot3dPixelRatio && !context.plotGlPixelRatio) { + context.plotGlPixelRatio = context.plot3dPixelRatio; + } + } + + //staticPlot forces a bunch of others: + if(context.staticPlot) { + context.editable = false; + context.autosizable = false; + context.scrollZoom = false; + context.doubleClick = false; + context.showTips = false; + context.showLink = false; + context.displayModeBar = false; + } +} + function plotGl3d(gd) { var fullLayout = gd._fullLayout, fullData = gd._fullData, @@ -1230,43 +910,6 @@ function emptyContainer(outer, innerStr) { (Object.keys(outer[innerStr]).length === 0); } -function sanitizeMargins(fullLayout) { - // polar doesn't do margins... - if(!fullLayout || !fullLayout.margin) return; - - var width = fullLayout.width, - height = fullLayout.height, - margin = fullLayout.margin, - plotWidth = width - (margin.l + margin.r), - plotHeight = height - (margin.t + margin.b), - correction; - - // if margin.l + margin.r = 0 then plotWidth > 0 - // as width >= 10 by supplyDefaults - // similarly for margin.t + margin.b - - if (plotWidth < 0) { - correction = (width - 1) / (margin.l + margin.r); - margin.l = Math.floor(correction * margin.l); - margin.r = Math.floor(correction * margin.r); - } - - if (plotHeight < 0) { - correction = (height - 1) / (margin.t + margin.b); - margin.t = Math.floor(correction * margin.t); - margin.b = Math.floor(correction * margin.b); - } -} - -// for use in Plotly.Lib.syncOrAsync, check if there are any -// pending promises in this plot and wait for them -plots.previousPromises = function(gd){ - if((gd._promises||[]).length) { - return Promise.all(gd._promises) - .then(function(){ gd._promises=[]; }); - } -}; - // convenience function to force a full redraw, mostly for use by plotly.js Plotly.redraw = function(gd) { gd = getGraphDiv(gd); @@ -1275,6 +918,7 @@ Plotly.redraw = function(gd) { console.log('This element is not a Plotly Plot', gd); return; } + gd.calcdata = undefined; return Plotly.plot(gd).then(function () { gd.emit('plotly_redraw'); @@ -1291,639 +935,10 @@ Plotly.redraw = function(gd) { */ Plotly.newPlot = function (gd, data, layout, config) { gd = getGraphDiv(gd); - Plotly.Plots.purge(gd); + plots.purge(gd); Plotly.plot(gd, data, layout, config); }; -plots.attributes = { - type: { - valType: 'enumerated', - role: 'info', - values: allTypes, - dflt: 'scatter' - }, - visible: { - valType: 'enumerated', - values: [true, false, 'legendonly'], - role: 'info', - dflt: true, - description: [ - 'Determines whether or not this trace is visible.', - 'If *legendonly*, the trace is not drawn,', - 'but can appear as a legend item', - '(provided that the legend itself is visible).' - ].join(' ') - }, - showlegend: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not an item corresponding to this', - 'trace is shown in the legend.' - ].join(' ') - }, - legendgroup: { - valType: 'string', - role: 'info', - dflt: '', - description: [ - 'Sets the legend group for this trace.', - 'Traces part of the same legend group hide/show at the same time', - 'when toggling legend items.' - ].join(' ') - }, - opacity: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the opacity of the trace.' - }, - name: { - valType: 'string', - role: 'info', - description: [ - 'Sets the trace name.', - 'The trace name appear as the legend item and on hover.' - ].join(' ') - }, - uid: { - valType: 'string', - role: 'info', - dflt: '' - }, - hoverinfo: { - valType: 'flaglist', - role: 'info', - flags: ['x', 'y', 'z', 'text', 'name'], - extras: ['all', 'none'], - dflt: 'all', - description: 'Determines which trace information appear on hover.' - }, - stream: { - token: { - valType: 'string', - noBlank: true, - strict: true, - role: 'info', - description: [ - 'The stream id number links a data trace on a plot with a stream.', - 'See https://plot.ly/settings for more details.' - ].join(' ') - }, - maxpoints: { - valType: 'number', - min: 0, - role: 'info', - description: [ - 'Sets the maximum number of points to keep on the plots from an', - 'incoming stream.', - 'If `maxpoints` is set to *50*, only the newest 50 points will', - 'be displayed on the plot.' - ].join(' ') - } - } -}; - -plots.supplyDefaults = function(gd) { - // fill in default values: - // gd.data, gd.layout: - // are precisely what the user specified - // gd._fullData, gd._fullLayout: - // are complete descriptions of how to draw the plot - var oldFullLayout = gd._fullLayout || {}, - newFullLayout = gd._fullLayout = {}, - newLayout = gd.layout || {}, - oldFullData = gd._fullData || [], - newFullData = gd._fullData = [], - newData = gd.data || [], - modules = gd._modules = []; - - var i, trace, fullTrace, module, axList, ax; - - - // first fill in what we can of layout without looking at data - // because fullData needs a few things from layout - plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); - - // keep track of how many traces are inputted - newFullLayout._dataLength = newData.length; - - // then do the data - for (i = 0; i < newData.length; i++) { - trace = newData[i]; - - fullTrace = plots.supplyDataDefaults(trace, i, newFullLayout); - newFullData.push(fullTrace); - - // DETECT 3D, Cartesian, and Polar - if(plots.traceIs(fullTrace, 'cartesian')) newFullLayout._hasCartesian = true; - else if(plots.traceIs(fullTrace, 'gl3d')) newFullLayout._hasGL3D = true; - else if(plots.traceIs(fullTrace, 'geo')) newFullLayout._hasGeo = true; - else if(plots.traceIs(fullTrace, 'pie')) newFullLayout._hasPie = true; - else if(plots.traceIs(fullTrace, 'gl2d')) newFullLayout._hasGL2D = true; - else if('r' in fullTrace) newFullLayout._hasPolar = true; - - module = fullTrace._module; - if (module && modules.indexOf(module)===-1) modules.push(module); - } - - // special cases that introduce interactions between traces - for (i = 0; i < modules.length; i++) { - module = modules[i]; - if (module.cleanData) module.cleanData(newFullData); - } - - if (oldFullData.length === newData.length) { - for (i = 0; i < newFullData.length; i++) { - relinkPrivateKeys(newFullData[i], oldFullData[i]); - } - } - - // finally, fill in the pieces of layout that may need to look at data - plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData); - - cleanScenes(newFullLayout, oldFullLayout); - - /* - * Relink functions and underscore attributes to promote consistency between - * plots. - */ - relinkPrivateKeys(newFullLayout, oldFullLayout); - - doAutoMargin(gd); - - // can't quite figure out how to get rid of this... each axis needs - // a reference back to the DOM object for just a few purposes - axList = Plotly.Axes.list(gd); - for (i = 0; i < axList.length; i++) { - ax = axList[i]; - ax._td = gd; - ax.setScale(); - } - - // update object references in calcdata - if ((gd.calcdata || []).length === newFullData.length) { - for (i = 0; i < newFullData.length; i++) { - trace = newFullData[i]; - (gd.calcdata[i][0] || {}).trace = trace; - } - } -}; - -function cleanScenes(newFullLayout, oldFullLayout) { - var oldSceneKey, - oldSceneKeys = plots.getSubplotIds(oldFullLayout, 'gl3d'); - - for (var i = 0; i < oldSceneKeys.length; i++) { - oldSceneKey = oldSceneKeys[i]; - if(!newFullLayout[oldSceneKey] && !!oldFullLayout[oldSceneKey]._scene) { - oldFullLayout[oldSceneKey]._scene.destroy(); - } - } - -} - -// relink private _keys and keys with a function value from one layout -// (usually cached) to the new fullLayout. -// relink means copying if object is pass-by-value and adding a reference -// if object is pass-by-ref. This prevents deepCopying massive structures like -// a webgl context. -function relinkPrivateKeys(toLayout, fromLayout) { - - var keys = Object.keys(fromLayout), - j; - - for (var i = 0; i < keys.length; ++i) { - var k = keys[i]; - if(k.charAt(0)==='_' || typeof fromLayout[k]==='function') { - // if it already exists at this point, it's something - // that we recreate each time around, so ignore it - if(k in toLayout) continue; - - toLayout[k] = fromLayout[k]; - } - else if (Array.isArray(fromLayout[k]) && - Array.isArray(toLayout[k]) && - fromLayout[k].length && - Plotly.Lib.isPlainObject(fromLayout[k][0])) { - if(fromLayout[k].length !== toLayout[k].length) { - // this should be handled elsewhere, it causes - // ambiguity if we try to deal with it here. - throw new Error('relinkPrivateKeys needs equal ' + - 'length arrays'); - } - - for(j = 0; j < fromLayout[k].length; j++) { - relinkPrivateKeys(toLayout[k][j], fromLayout[k][j]); - } - } - else if (Plotly.Lib.isPlainObject(fromLayout[k]) && - Plotly.Lib.isPlainObject(toLayout[k])) { - // recurse into objects, but only if they still exist - relinkPrivateKeys(toLayout[k], fromLayout[k]); - if (!Object.keys(toLayout[k]).length) delete toLayout[k]; - } - } -} - -plots.supplyDataDefaults = function(traceIn, i, layout) { - var traceOut = {}, - defaultColor = Plotly.Color.defaults[i % Plotly.Color.defaults.length]; - - function coerce(attr, dflt) { - return Plotly.Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); - } - - function coerceSubplotAttr(subplotType, subplotAttr) { - if(!plots.traceIs(traceOut, subplotType)) return; - return Plotly.Lib.coerce(traceIn, traceOut, - plots.subplotsRegistry[subplotType].attributes, subplotAttr); - } - - // module-independent attributes - traceOut.index = i; - var visible = coerce('visible'), - scene, - module; - - coerce('type'); - coerce('uid'); - - // this is necessary otherwise we lose references to scene objects when - // the traces of a scene are invisible. Also we handle visible/unvisible - // differently for 3D cases. - coerceSubplotAttr('gl3d', 'scene'); - coerceSubplotAttr('geo', 'geo'); - - // module-specific attributes --- note: we need to send a trace into - // the 3D modules to have it removed from the webgl context. - if(visible || scene) { - module = plots.getModule(traceOut); - traceOut._module = module; - } - - // gets overwritten in pie and geo - if(visible) coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); - - if(module && visible) module.supplyDefaults(traceIn, traceOut, defaultColor, layout); - - if(visible) { - coerce('name', 'trace ' + i); - - if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity'); - - coerceSubplotAttr('cartesian', 'xaxis'); - coerceSubplotAttr('cartesian', 'yaxis'); - - coerceSubplotAttr('gl2d', 'xaxis'); - coerceSubplotAttr('gl2d', 'yaxis'); - - if(plots.traceIs(traceOut, 'showLegend')) { - coerce('showlegend'); - coerce('legendgroup'); - } - } - - // NOTE: I didn't include fit info at all... for now I think it can stay - // just in gd.data, as this info isn't involved in creating plots at all, - // only in pulling back up the fit popover - - // reference back to the input object for convenience - traceOut._input = traceIn; - - return traceOut; -}; - -plots.fontAttrs = { - family: { - valType: 'string', - role: 'style', - noBlank: true, - strict: true, - description: [ - 'HTML font family - the typeface that will be applied by the web browser.', - 'The web browser will only be able to apply a font if it is available on the system', - 'which it operates. Provide multiple font families, separated by commas, to indicate', - 'the preference in which to apply fonts if they aren\'t available on the system.', - 'The plotly service (at https://plot.ly or on-premise) generates images on a server,', - 'where only a select number of', - 'fonts are installed and supported.', - 'These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*,', - '*Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*,', - '*PT Sans Narrow*, *Raleway*, *Times New Roman*.' - ].join(' ') - }, - size: { - valType: 'number', - role: 'style', - min: 1 - }, - color: { - valType: 'color', - role: 'style' - } -}; - -// TODO make this a plot attribute? -plots.fontWeight = 'normal'; - -var extendFlat = Plotly.Lib.extendFlat; - -plots.layoutAttributes = { - font: { - family: extendFlat({}, plots.fontAttrs.family, { - dflt: '"Open sans", verdana, arial, sans-serif' - }), - size: extendFlat({}, plots.fontAttrs.size, { - dflt: 12 - }), - color: extendFlat({}, plots.fontAttrs.color, { - dflt: Plotly.Color.defaultLine - }), - description: [ - 'Sets the global font.', - 'Note that fonts used in traces and other', - 'layout components inherit from the global font.' - ].join(' ') - }, - title: { - valType: 'string', - role: 'info', - dflt: 'Click to enter Plot title', - description: [ - 'Sets the plot\'s title.' - ].join(' ') - }, - titlefont: extendFlat({}, plots.fontAttrs, { - description: 'Sets the title font.' - }), - autosize: { - valType: 'enumerated', - role: 'info', - // TODO: better handling of 'initial' - values: [true, false, 'initial'], - description: [ - 'Determines whether or not the dimensions of the figure are', - 'computed as a function of the display size.' - ].join(' ') - }, - width: { - valType: 'number', - role: 'info', - min: 10, - dflt: 700, - description: [ - 'Sets the plot\'s width (in px).' - ].join(' ') - }, - height: { - valType: 'number', - role: 'info', - min: 10, - dflt: 450, - description: [ - 'Sets the plot\'s height (in px).' - ].join(' ') - }, - margin: { - l: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the left margin (in px).' - }, - r: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the right margin (in px).' - }, - t: { - valType: 'number', - role: 'info', - min: 0, - dflt: 100, - description: 'Sets the top margin (in px).' - }, - b: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the bottom margin (in px).' - }, - pad: { - valType: 'number', - role: 'info', - min: 0, - dflt: 0, - description: [ - 'Sets the amount of padding (in px)', - 'between the plotting area and the axis lines' - ].join(' ') - }, - autoexpand: { - valType: 'boolean', - role: 'info', - dflt: true - } - }, - paper_bgcolor: { - valType: 'color', - role: 'style', - dflt: Plotly.Color.background, - description: 'Sets the color of paper where the graph is drawn.' - }, - plot_bgcolor: { - // defined here, but set in Axes.supplyLayoutDefaults - // because it needs to know if there are (2D) axes or not - valType: 'color', - role: 'style', - dflt: Plotly.Color.background, - description: [ - 'Sets the color of plotting area in-between x and y axes.' - ].join(' ') - }, - separators: { - valType: 'string', - role: 'style', - dflt: '.,', - description: [ - 'Sets the decimal and thousand separators.', - 'For example, *. * puts a \'.\' before decimals and', - 'a space between thousands.' - ].join(' ') - }, - hidesources: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not a text link citing the data source is', - 'placed at the bottom-right cored of the figure.', - 'Has only an effect only on graphs that have been generated via', - 'forked graphs from the plotly service (at https://plot.ly or on-premise).' - ].join(' ') - }, - smith: { - // will become a boolean if/when we implement this - valType: 'enumerated', - role: 'info', - values: [false], - dflt: false - }, - showlegend: { - // handled in legend.supplyLayoutDefaults - // but included here because it's not in the legend object - valType: 'boolean', - role: 'info', - description: 'Determines whether or not a legend is drawn.' - }, - _hasCartesian: { - valType: 'boolean', - dflt: false - }, - _hasGL3D: { - valType: 'boolean', - dflt: false - }, - _hasGeo: { - valType: 'boolean', - dflt: false - }, - _hasPie: { - valType: 'boolean', - dflt: false - }, - _hasGL2D: { - valType: 'boolean', - dflt: false - }, - _composedModules: { - '*': 'Fx' - }, - _nestedModules: { - 'xaxis': 'Axes', - 'yaxis': 'Axes', - 'scene': 'Gl3dLayout', - 'geo': 'GeoLayout', - 'legend': 'Legend', - 'annotations': 'Annotations', - 'shapes': 'Shapes' - } -}; - -plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { - function coerce(attr, dflt) { - return Plotly.Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt); - } - - var globalFont = Plotly.Lib.coerceFont(coerce, 'font'); - - coerce('title'); - - Plotly.Lib.coerceFont(coerce, 'titlefont', { - family: globalFont.family, - size: Math.round(globalFont.size * 1.4), - color: globalFont.color - }); - - var autosize = coerce('autosize', - (layoutIn.width && layoutIn.height) ? false : 'initial'); - coerce('width'); - coerce('height'); - - coerce('margin.l'); - coerce('margin.r'); - coerce('margin.t'); - coerce('margin.b'); - coerce('margin.pad'); - coerce('margin.autoexpand'); - - // called in plotAutoSize otherwise - if (autosize!=='initial') sanitizeMargins(layoutOut); - - coerce('paper_bgcolor'); - - coerce('separators'); - coerce('hidesources'); - coerce('smith'); - coerce('_hasCartesian'); - coerce('_hasGL3D'); - coerce('_hasGeo'); - coerce('_hasPie'); - coerce('_hasGL2D'); -}; - -plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) { - var moduleLayoutDefaults = [ - 'Axes', 'Annotations', 'Shapes', 'Fx', - 'Bars', 'Boxes', 'Gl3dLayout', 'GeoLayout', 'Pie', 'Legend' - ]; - - var i, module; - - // don't add a check for 'function in module' as it is better to error out and - // secure the module API then not apply the default function. - for(i = 0; i < moduleLayoutDefaults.length; i++) { - module = moduleLayoutDefaults[i]; - if(Plotly[module]) { - Plotly[module].supplyLayoutDefaults(layoutIn, layoutOut, fullData); - } - } -}; - -plots.purge = function(gd) { - // remove all plotly attributes from a div so it can be replotted fresh - // TODO: these really need to be encapsulated into a much smaller set... - - // note: we DO NOT remove _context because it doesn't change when we insert - // a new plot, and may have been set outside of our scope. - - // clean up the gl and geo containers - // TODO unify subplot creation/update with d3.selection.order - // and/or subplot ids - var fullLayout = gd._fullLayout || {}; - if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove(); - if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove(); - - // data and layout - delete gd.data; - delete gd.layout; - delete gd._fullData; - delete gd._fullLayout; - delete gd.calcdata; - delete gd.framework; - delete gd.empty; - - delete gd.fid; - - delete gd.undoqueue; // action queue - delete gd.undonum; - delete gd.autoplay; // are we doing an action that doesn't go in undo queue? - delete gd.changed; - - // these get recreated on Plotly.plot anyway, but just to be safe - // (and to have a record of them...) - delete gd._modules; - delete gd._tester; - delete gd._testref; - delete gd._promises; - delete gd._redrawTimer; - delete gd._replotting; - delete gd.firstscatter; - delete gd.hmlumcount; - delete gd.hmpixcount; - delete gd.numboxes; - delete gd._hoverTimer; - delete gd._lastHoverTime; -}; - function doCalcdata(gd) { var axList = Plotly.Axes.list(gd), fullData = gd._fullData, @@ -1979,18 +994,6 @@ function doCalcdata(gd) { } } -plots.style = function(gd) { - var modulesWithErrorBars = Plotly.ErrorBars ? - gd._modules.concat(Plotly.ErrorBars) : gd._modules, - i, - module; - - for (i = 0; i < modulesWithErrorBars.length; i++) { - module = modulesWithErrorBars[i]; - if (module.style) module.style(gd); - } -}; - /** * Wrap negative indicies to their positive counterparts. * @@ -3453,7 +2456,7 @@ Plotly.relayout = function relayout(gd, astr, val) { if(doticks) { seq.push(function(){ Plotly.Axes.doTicks(gd,'redraw'); - plots.titles(gd,'gtitle'); + Plotly.Titles.draw(gd, 'gtitle'); return plots.previousPromises(gd); }); } @@ -3559,53 +2562,11 @@ function plotAutoSize(gd, aobj) { fullLayout.autosize = gd.layout.autosize = true; } - sanitizeMargins(fullLayout); + plots.sanitizeMargins(fullLayout); return aobj; } -// check whether to resize a tab (if it's a plot) to the container -plots.resize = function(gd) { - if(!gd || d3.select(gd).style('display') === 'none') return; - - if(gd._redrawTimer) clearTimeout(gd._redrawTimer); - - gd._redrawTimer = setTimeout(function() { - if((gd._fullLayout || {}).autosize) { - // autosizing doesn't count as a change that needs saving - var oldchanged = gd.changed; - - // nor should it be included in the undo queue - gd.autoplay = true; - - Plotly.relayout(gd, {autosize: true}); - gd.changed = oldchanged; - } - }, 100); -}; - -// Get the container div: we store all variables for this plot as -// properties of this div -// some callers send this in by DOM element, others by id (string) -function getGraphDiv(gd) { - var gdElement; - - if(typeof gd === 'string') { - gdElement = document.getElementById(gd); - - if(gdElement === null) { - throw new Error('No DOM element with id \'' + gd + '\' exits on the page.'); - } - - return gdElement; - } - else if(gd===null || gd===undefined) { - throw new Error('DOM element provided is null or undefined'); - } - - return gd; // otherwise assume that gd is a DOM element -} - // ------------------------------------------------------- // makePlotFramework: Create the plot container and axes // ------------------------------------------------------- @@ -3870,119 +2831,9 @@ function makeCartesianPlotFramwork(gd, subplots) { }); } -// called by legend and colorbar routines to see if we need to -// expand the margins to show them -// o is {x,l,r,y,t,b} where x and y are plot fractions, -// the rest are pixels in each direction -// or leave o out to delete this entry (like if it's hidden) -plots.autoMargin = function(gd,id,o) { - var fullLayout = gd._fullLayout; - if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; - if(fullLayout.margin.autoexpand!==false) { - if(!o) delete fullLayout._pushmargin[id]; - else { - var pad = o.pad||12; - - // if the item is too big, just give it enough automargin to - // make sure you can still grab it and bring it back - if(o.l+o.r > fullLayout.width*0.5) o.l = o.r = 0; - if(o.b+o.t > fullLayout.height*0.5) o.b = o.t = 0; - - fullLayout._pushmargin[id] = { - l: {val:o.x, size: o.l+pad}, - r: {val:o.x, size: o.r+pad}, - b: {val:o.y, size: o.b+pad}, - t: {val:o.y, size: o.t+pad} - }; - } - - if(!gd._replotting) doAutoMargin(gd); - } -}; - -function doAutoMargin(gd) { - var fullLayout = gd._fullLayout; - if(!fullLayout._size) fullLayout._size = {}; - if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; - var gs = fullLayout._size, - oldmargins = JSON.stringify(gs); - - // adjust margins for outside legends and colorbars - // fullLayout.margin is the requested margin, - // fullLayout._size has margins and plotsize after adjustment - var ml = Math.max(fullLayout.margin.l||0,0), - mr = Math.max(fullLayout.margin.r||0,0), - mt = Math.max(fullLayout.margin.t||0,0), - mb = Math.max(fullLayout.margin.b||0,0), - pm = fullLayout._pushmargin; - if(fullLayout.margin.autoexpand!==false) { - // fill in the requested margins - pm.base = { - l:{val:0, size:ml}, - r:{val:1, size:mr}, - t:{val:1, size:mt}, - b:{val:0, size:mb} - }; - // now cycle through all the combinations of l and r - // (and t and b) to find the required margins - Object.keys(pm).forEach(function(k1) { - var pushleft = pm[k1].l||{}, - pushbottom = pm[k1].b||{}, - fl = pushleft.val, - pl = pushleft.size, - fb = pushbottom.val, - pb = pushbottom.size; - Object.keys(pm).forEach(function(k2) { - if(isNumeric(pl) && pm[k2].r) { - var fr = pm[k2].r.val, - pr = pm[k2].r.size; - if(fr>fl) { - var newl = (pl*fr + - (pr-fullLayout.width)*fl) / (fr-fl), - newr = (pr*(1-fl) + - (pl-fullLayout.width)*(1-fr)) / (fr-fl); - if(newl>=0 && newr>=0 && newl+newr>ml+mr) { - ml = newl; - mr = newr; - } - } - } - if(isNumeric(pb) && pm[k2].t) { - var ft = pm[k2].t.val, - pt = pm[k2].t.size; - if(ft>fb) { - var newb = (pb*ft + - (pt-fullLayout.height)*fb) / (ft-fb), - newt = (pt*(1-fb) + - (pb-fullLayout.height)*(1-ft)) / (ft-fb); - if(newb>=0 && newt>=0 && newb+newt>mb+mt) { - mb = newb; - mt = newt; - } - } - } - }); - }); - } - - gs.l = Math.round(ml); - gs.r = Math.round(mr); - gs.t = Math.round(mt); - gs.b = Math.round(mb); - gs.p = Math.round(fullLayout.margin.pad); - gs.w = Math.round(fullLayout.width)-gs.l-gs.r; - gs.h = Math.round(fullLayout.height)-gs.t-gs.b; - - // if things changed and we're not already redrawing, trigger a redraw - if(!gd._replotting && oldmargins!=='{}' && - oldmargins!==JSON.stringify(fullLayout._size)) { - return Plotly.plot(gd); - } -} - // layoutStyles: styling for plot layout elements function layoutStyles(gd) { - return Plotly.Lib.syncOrAsync([doAutoMargin, lsInner], gd); + return Plotly.Lib.syncOrAsync([plots.doAutoMargin, lsInner], gd); } function lsInner(gd) { @@ -4110,7 +2961,6 @@ function lsInner(gd) { rightpos += xa._offset - gs.l; } - plotinfo.xlines .attr('transform', originx) .attr('d',( @@ -4146,420 +2996,9 @@ function lsInner(gd) { Plotly.Axes.makeClipPaths(gd); - plots.titles(gd,'gtitle'); + Plotly.Titles.draw(gd, 'gtitle'); Plotly.Fx.modeBar(gd); return gd._promises.length && Promise.all(gd._promises); } - -// titles - (re)draw titles on the axes and plot -// title can be 'xtitle', 'ytitle', 'gtitle', -// or empty to draw all -plots.titles = function(gd, title) { - gd = getGraphDiv(gd); - - if(!title) { - Plotly.Axes.listIds(gd).forEach(function(axId) { - plots.titles(gd, axId+'title'); - }); - plots.titles(gd,'gtitle'); - return; - } - - var fullLayout = gd._fullLayout, - gs = fullLayout._size, - axletter = title.charAt(0), - colorbar = title.substr(1,2)==='cb'; - - var cbnum, cont, options; - - if(colorbar) { - var uid = title.substr(3).replace('title',''); - gd._fullData.some(function(trace, i) { - if(trace.uid===uid) { - cbnum = i; - cont = gd.calcdata[i][0].t.cb.axis; - return true; - } - }); - } - else cont = fullLayout[Plotly.Axes.id2name(title.replace('title',''))] || fullLayout; - - var prop = cont===fullLayout ? 'title' : cont._name+'.title', - name = colorbar ? 'colorscale' : - ((cont._id||axletter).toUpperCase()+' axis'), - font = cont.titlefont.family, - fontSize = cont.titlefont.size, - fontColor = cont.titlefont.color, - x, - y, - transform='', - attr = {}, - xa, - ya, - avoid = { - selection:d3.select(gd).selectAll('g.'+cont._id+'tick'), - side:cont.side - }, - // multiples of fontsize to offset label from axis - offsetBase = colorbar ? 0 : 1.5, - avoidTransform; - - // find the transform applied to the parents of the avoid selection - // which doesn't get picked up by Plotly.Drawing.bBox - if(colorbar) { - avoid.offsetLeft = gs.l; - avoid.offsetTop = gs.t; - } - else if(avoid.selection.size()) { - avoidTransform = d3.select(avoid.selection.node().parentNode) - .attr('transform') - .match(/translate\(([-\.\d]+),([-\.\d]+)\)/); - if(avoidTransform) { - avoid.offsetLeft = +avoidTransform[1]; - avoid.offsetTop = +avoidTransform[2]; - } - } - - if(colorbar && cont.titleside) { - // argh, we only make it here if the title is on top or bottom, - // not right - x = gs.l+cont.titlex*gs.w; - y = gs.t+(1-cont.titley)*gs.h + ((cont.titleside==='top') ? - 3+fontSize*0.75 : - 3-fontSize*0.25); - options = {x: x, y: y, 'text-anchor':'start'}; - avoid = {}; - - // convertToTspans rotates any 'y...' by 90 degrees... - // TODO: need a better solution than this hack - title = 'h'+title; - } - else if(axletter==='x'){ - xa = cont; - ya = (xa.anchor==='free') ? - {_offset:gs.t+(1-(xa.position||0))*gs.h, _length:0} : - Plotly.Axes.getFromId(gd, xa.anchor); - x = xa._offset+xa._length/2; - y = ya._offset + ((xa.side==='top') ? - -10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) : - ya._length + 10 + - fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5))); - options = {x: x, y: y, 'text-anchor': 'middle'}; - if(!avoid.side) { avoid.side = 'bottom'; } - } - else if(axletter==='y'){ - ya = cont; - xa = (ya.anchor==='free') ? - {_offset:gs.l+(ya.position||0)*gs.w, _length:0} : - Plotly.Axes.getFromId(gd, ya.anchor); - y = ya._offset+ya._length/2; - x = xa._offset + ((ya.side==='right') ? - xa._length + 10 + - fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) : - -10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0))); - attr = {center: 0}; - options = {x: x, y: y, 'text-anchor': 'middle'}; - transform = {rotate: '-90', offset: 0}; - if(!avoid.side) { avoid.side = 'left'; } - } - else{ - // plot title - name = 'Plot'; - fontSize = fullLayout.titlefont.size; - x = fullLayout.width/2; - y = fullLayout._size.t/2; - options = {x: x, y: y, 'text-anchor': 'middle'}; - avoid = {}; - } - - var opacity = 1, - isplaceholder = false, - txt = cont.title.trim(); - if(txt === '') { opacity = 0; } - if(txt.match(/Click to enter .+ title/)) { - opacity = 0.2; - isplaceholder = true; - } - - var group; - if(colorbar) { - group = d3.select(gd) - .selectAll('.'+cont._id.substr(1)+' .cbtitle'); - // this class-to-rotate thing with convertToTspans is - // getting hackier and hackier... delete groups with the - // wrong class - var otherClass = title.charAt(0)==='h' ? - title.substr(1) : ('h'+title); - group.selectAll('.'+otherClass+',.'+otherClass+'-math-group') - .remove(); - } - else { - group = fullLayout._infolayer.selectAll('.g-'+title) - .data([0]); - group.enter().append('g') - .classed('g-'+title, true); - } - - var el = group.selectAll('text') - .data([0]); - el.enter().append('text'); - el.text(txt) - // this is hacky, but convertToTspans uses the class - // to determine whether to rotate mathJax... - // so we need to clear out any old class and put the - // correct one (only relevant for colorbars, at least - // for now) - ie don't use .classed - .attr('class', title); - - function titleLayout(titleEl){ - Plotly.Lib.syncOrAsync([drawTitle,scootTitle], titleEl); - } - - function drawTitle(titleEl) { - titleEl.attr('transform', transform ? - 'rotate(' + [transform.rotate, options.x, options.y] + - ') translate(0, '+transform.offset+')' : - null); - titleEl.style({ - 'font-family': font, - 'font-size': d3.round(fontSize,2)+'px', - fill: Plotly.Color.rgb(fontColor), - opacity: opacity*Plotly.Color.opacity(fontColor), - 'font-weight': plots.fontWeight - }) - .attr(options) - .call(Plotly.util.convertToTspans) - .attr(options); - titleEl.selectAll('tspan.line') - .attr(options); - return plots.previousPromises(gd); - } - - function scootTitle(titleElIn) { - var titleGroup = d3.select(titleElIn.node().parentNode); - - if(avoid && avoid.selection && avoid.side && txt){ - titleGroup.attr('transform',null); - - // move toward avoid.side (= left, right, top, bottom) if needed - // can include pad (pixels, default 2) - var shift = 0, - backside = { - left: 'right', - right: 'left', - top: 'bottom', - bottom: 'top' - }[avoid.side], - shiftSign = (['left','top'].indexOf(avoid.side)!==-1) ? - -1 : 1, - pad = isNumeric(avoid.pad) ? avoid.pad : 2, - titlebb = Plotly.Drawing.bBox(titleGroup.node()), - paperbb = { - left: 0, - top: 0, - right: fullLayout.width, - bottom: fullLayout.height - }, - maxshift = colorbar ? fullLayout.width: - (paperbb[avoid.side]-titlebb[avoid.side]) * - ((avoid.side==='left' || avoid.side==='top') ? -1 : 1); - // Prevent the title going off the paper - if(maxshift<0) shift = maxshift; - else { - // so we don't have to offset each avoided element, - // give the title the opposite offset - titlebb.left -= avoid.offsetLeft; - titlebb.right -= avoid.offsetLeft; - titlebb.top -= avoid.offsetTop; - titlebb.bottom -= avoid.offsetTop; - - // iterate over a set of elements (avoid.selection) - // to avoid collisions with - avoid.selection.each(function(){ - var avoidbb = Plotly.Drawing.bBox(this); - - if(Plotly.Lib.bBoxIntersect(titlebb,avoidbb,pad)) { - shift = Math.max(shift, shiftSign * ( - avoidbb[avoid.side] - titlebb[backside]) + pad); - } - }); - shift = Math.min(maxshift, shift); - } - if(shift>0 || maxshift<0) { - var shiftTemplate = { - left: [-shift, 0], - right: [shift, 0], - top: [0, -shift], - bottom: [0, shift] - }[avoid.side]; - titleGroup.attr('transform', - 'translate(' + shiftTemplate + ')'); - } - } - } - - el.attr({'data-unformatted': txt}) - .call(titleLayout); - - var placeholderText = 'Click to enter '+name.replace(/\d+/,'')+' title'; - - function setPlaceholder(){ - opacity = 0; - isplaceholder = true; - txt = placeholderText; - fullLayout._infolayer.select('.'+title) - .attr({'data-unformatted': txt}) - .text(txt) - .on('mouseover.opacity',function(){ - d3.select(this).transition() - .duration(100).style('opacity',1); - }) - .on('mouseout.opacity',function(){ - d3.select(this).transition() - .duration(1000).style('opacity',0); - }); - } - - if(gd._context.editable){ - if(!txt) setPlaceholder(); - - el.call(Plotly.util.makeEditable) - .on('edit', function(text){ - if(colorbar) { - var trace = gd._fullData[cbnum]; - if(plots.traceIs(trace, 'markerColorscale')) { - Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum); - } else Plotly.restyle(gd, 'colorbar.title', text, cbnum); - } - else Plotly.relayout(gd,prop,text); - }) - .on('cancel', function(){ - this.text(this.attr('data-unformatted')) - .call(titleLayout); - }) - .on('input', function(d){ - this.text(d || ' ').attr(options) - .selectAll('tspan.line') - .attr(options); - }); - } - else if(!txt || txt.match(/Click to enter .+ title/)) { - el.remove(); - } - el.classed('js-placeholder',isplaceholder); -}; - -// ---------------------------------------------------- -// Utility functions -// ---------------------------------------------------- - -/** - * JSONify the graph data and layout - * - * This function needs to recurse because some src can be inside - * sub-objects. - * - * It also strips out functions and private (starts with _) elements. - * Therefore, we can add temporary things to data and layout that don't - * get saved. - * - * @param gd The graphDiv - * @param {Boolean} dataonly If true, don't return layout. - * @param {'keepref'|'keepdata'|'keepall'} [mode='keepref'] Filter what's kept - * keepref: remove data for which there's a src present - * eg if there's xsrc present (and xsrc is well-formed, - * ie has : and some chars before it), strip out x - * keepdata: remove all src tags, don't remove the data itself - * keepall: keep data and src - * @param {String} output If you specify 'object', the result will not be stringified - * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData - * @returns {Object|String} - */ -plots.graphJson = function(gd, dataonly, mode, output, useDefaults){ - gd = getGraphDiv(gd); - - // if the defaults aren't supplied yet, we need to do that... - if ((useDefaults && dataonly && !gd._fullData) || - (useDefaults && !dataonly && !gd._fullLayout)) { - plots.supplyDefaults(gd); - } - - var data = (useDefaults) ? gd._fullData : gd.data, - layout = (useDefaults) ? gd._fullLayout : gd.layout; - - function stripObj(d) { - if(typeof d === 'function') { - return null; - } - if(Plotly.Lib.isPlainObject(d)) { - var o={}, v, src; - for(v in d) { - // remove private elements and functions - // _ is for private, [ is a mistake ie [object Object] - if(typeof d[v]==='function' || - ['_','['].indexOf(v.charAt(0))!==-1) { - continue; - } - - // look for src/data matches and remove the appropriate one - if(mode==='keepdata') { - // keepdata: remove all ...src tags - if(v.substr(v.length-3)==='src') { - continue; - } - } - else if(mode==='keepstream') { - // keep sourced data if it's being streamed. - // similar to keepref, but if the 'stream' object exists - // in a trace, we will keep the data array. - src = d[v+'src']; - if(typeof src==='string' && src.indexOf(':')>0) { - if(!Plotly.Lib.isPlainObject(d.stream)) { - continue; - } - } - } - else if(mode!=='keepall') { - // keepref: remove sourced data but only - // if the source tag is well-formed - src = d[v+'src']; - if(typeof src==='string' && src.indexOf(':')>0) { - continue; - } - } - - // OK, we're including this... recurse into it - o[v] = stripObj(d[v]); - } - return o; - } - - if(Array.isArray(d)) { - return d.map(stripObj); - } - - // convert native dates to date strings... - // mostly for external users exporting to plotly - if(d && d.getTime) { - return Plotly.Lib.ms2DateTime(d); - } - - return d; - } - - var obj = { - data:(data||[]).map(function(v){ - var d = stripObj(v); - // fit has some little arrays in it that don't contain data, - // just fit params and meta - if(dataonly) { delete d.fit; } - return d; - }) - }; - if(!dataonly) { obj.layout = stripObj(layout); } - - if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig(); - - return (output==='object') ? obj : JSON.stringify(obj); -}; diff --git a/src/plots/plots/attributes.js b/src/plots/plots/attributes.js new file mode 100644 index 00000000000..a2401832ab7 --- /dev/null +++ b/src/plots/plots/attributes.js @@ -0,0 +1,94 @@ +var Plotly = require('../../plotly'); +var plots = require('./plots'); + +module.exports = { + type: { + valType: 'enumerated', + role: 'info', + values: plots.allTypes, + dflt: 'scatter' + }, + visible: { + valType: 'enumerated', + values: [true, false, 'legendonly'], + role: 'info', + dflt: true, + description: [ + 'Determines whether or not this trace is visible.', + 'If *legendonly*, the trace is not drawn,', + 'but can appear as a legend item', + '(provided that the legend itself is visible).' + ].join(' ') + }, + showlegend: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not an item corresponding to this', + 'trace is shown in the legend.' + ].join(' ') + }, + legendgroup: { + valType: 'string', + role: 'info', + dflt: '', + description: [ + 'Sets the legend group for this trace.', + 'Traces part of the same legend group hide/show at the same time', + 'when toggling legend items.' + ].join(' ') + }, + opacity: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of the trace.' + }, + name: { + valType: 'string', + role: 'info', + description: [ + 'Sets the trace name.', + 'The trace name appear as the legend item and on hover.' + ].join(' ') + }, + uid: { + valType: 'string', + role: 'info', + dflt: '' + }, + hoverinfo: { + valType: 'flaglist', + role: 'info', + flags: ['x', 'y', 'z', 'text', 'name'], + extras: ['all', 'none'], + dflt: 'all', + description: 'Determines which trace information appear on hover.' + }, + stream: { + token: { + valType: 'string', + noBlank: true, + strict: true, + role: 'info', + description: [ + 'The stream id number links a data trace on a plot with a stream.', + 'See https://plot.ly/settings for more details.' + ].join(' ') + }, + maxpoints: { + valType: 'number', + min: 0, + role: 'info', + description: [ + 'Sets the maximum number of points to keep on the plots from an', + 'incoming stream.', + 'If `maxpoints` is set to *50*, only the newest 50 points will', + 'be displayed on the plot.' + ].join(' ') + } + } +}; diff --git a/src/plots/plots/font_attributes.js b/src/plots/plots/font_attributes.js new file mode 100644 index 00000000000..742bc509c81 --- /dev/null +++ b/src/plots/plots/font_attributes.js @@ -0,0 +1,29 @@ +module.exports = { + family: { + valType: 'string', + role: 'style', + noBlank: true, + strict: true, + description: [ + 'HTML font family - the typeface that will be applied by the web browser.', + 'The web browser will only be able to apply a font if it is available on the system', + 'which it operates. Provide multiple font families, separated by commas, to indicate', + 'the preference in which to apply fonts if they aren\'t available on the system.', + 'The plotly service (at https://plot.ly or on-premise) generates images on a server,', + 'where only a select number of', + 'fonts are installed and supported.', + 'These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*,', + '*Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*,', + '*PT Sans Narrow*, *Raleway*, *Times New Roman*.' + ].join(' ') + }, + size: { + valType: 'number', + role: 'style', + min: 1 + }, + color: { + valType: 'color', + role: 'style' + } +}; diff --git a/src/plots/plots/layout_attributes.js b/src/plots/plots/layout_attributes.js new file mode 100644 index 00000000000..b3bfeebcc64 --- /dev/null +++ b/src/plots/plots/layout_attributes.js @@ -0,0 +1,191 @@ +var Plotly = require('../../plotly'); +var plots = require('./plots'); + +var extendFlat = Plotly.Lib.extendFlat; + + +module.exports = { + font: { + family: extendFlat({}, plots.fontAttrs.family, { + dflt: '"Open sans", verdana, arial, sans-serif' + }), + size: extendFlat({}, plots.fontAttrs.size, { + dflt: 12 + }), + color: extendFlat({}, plots.fontAttrs.color, { + dflt: Plotly.Color.defaultLine + }), + description: [ + 'Sets the global font.', + 'Note that fonts used in traces and other', + 'layout components inherit from the global font.' + ].join(' ') + }, + title: { + valType: 'string', + role: 'info', + dflt: 'Click to enter Plot title', + description: [ + 'Sets the plot\'s title.' + ].join(' ') + }, + titlefont: extendFlat({}, plots.fontAttrs, { + description: 'Sets the title font.' + }), + autosize: { + valType: 'enumerated', + role: 'info', + // TODO: better handling of 'initial' + values: [true, false, 'initial'], + description: [ + 'Determines whether or not the dimensions of the figure are', + 'computed as a function of the display size.' + ].join(' ') + }, + width: { + valType: 'number', + role: 'info', + min: 10, + dflt: 700, + description: [ + 'Sets the plot\'s width (in px).' + ].join(' ') + }, + height: { + valType: 'number', + role: 'info', + min: 10, + dflt: 450, + description: [ + 'Sets the plot\'s height (in px).' + ].join(' ') + }, + margin: { + l: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the left margin (in px).' + }, + r: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the right margin (in px).' + }, + t: { + valType: 'number', + role: 'info', + min: 0, + dflt: 100, + description: 'Sets the top margin (in px).' + }, + b: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the bottom margin (in px).' + }, + pad: { + valType: 'number', + role: 'info', + min: 0, + dflt: 0, + description: [ + 'Sets the amount of padding (in px)', + 'between the plotting area and the axis lines' + ].join(' ') + }, + autoexpand: { + valType: 'boolean', + role: 'info', + dflt: true + } + }, + paper_bgcolor: { + valType: 'color', + role: 'style', + dflt: Plotly.Color.background, + description: 'Sets the color of paper where the graph is drawn.' + }, + plot_bgcolor: { + // defined here, but set in Axes.supplyLayoutDefaults + // because it needs to know if there are (2D) axes or not + valType: 'color', + role: 'style', + dflt: Plotly.Color.background, + description: [ + 'Sets the color of plotting area in-between x and y axes.' + ].join(' ') + }, + separators: { + valType: 'string', + role: 'style', + dflt: '.,', + description: [ + 'Sets the decimal and thousand separators.', + 'For example, *. * puts a \'.\' before decimals and', + 'a space between thousands.' + ].join(' ') + }, + hidesources: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not a text link citing the data source is', + 'placed at the bottom-right cored of the figure.', + 'Has only an effect only on graphs that have been generated via', + 'forked graphs from the plotly service (at https://plot.ly or on-premise).' + ].join(' ') + }, + smith: { + // will become a boolean if/when we implement this + valType: 'enumerated', + role: 'info', + values: [false], + dflt: false + }, + showlegend: { + // handled in legend.supplyLayoutDefaults + // but included here because it's not in the legend object + valType: 'boolean', + role: 'info', + description: 'Determines whether or not a legend is drawn.' + }, + _hasCartesian: { + valType: 'boolean', + dflt: false + }, + _hasGL3D: { + valType: 'boolean', + dflt: false + }, + _hasGeo: { + valType: 'boolean', + dflt: false + }, + _hasPie: { + valType: 'boolean', + dflt: false + }, + _hasGL2D: { + valType: 'boolean', + dflt: false + }, + _composedModules: { + '*': 'Fx' + }, + _nestedModules: { + 'xaxis': 'Axes', + 'yaxis': 'Axes', + 'scene': 'Gl3dLayout', // TODO should be Scene + 'geo': 'GeoLayout', // TODO should be Geo + 'legend': 'Legend', + 'annotations': 'Annotations', + 'shapes': 'Shapes' + } +}; diff --git a/src/plots/plots/plots.js b/src/plots/plots/plots.js new file mode 100644 index 00000000000..710ce5dc1cb --- /dev/null +++ b/src/plots/plots/plots.js @@ -0,0 +1,959 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var d3 = require('d3'); +var isNumeric = require('fast-isnumeric'); + +var plots = module.exports = {}; + +var modules = plots.modules = {}, + allTypes = plots.allTypes = [], + allCategories = plots.allCategories = {}, + subplotsRegistry = plots.subplotsRegistry = {}; + +plots.attributes = require('./attributes'); +plots.fontAttrs = require('./font_attributes'); +plots.layoutAttributes = require('./layout_attributes'); + +// TODO make this a plot attribute? +plots.fontWeight = 'normal'; + +/** + * plots.register: register a module as the handler for a trace type + * + * @param {object} module the module that will handle plotting this trace type + * @param {string} thisType + * @param {array of strings} categoriesIn all the categories this type is in, + * tested by calls: Plotly.Plots.traceIs(trace, oneCategory) + * @param {object} meta meta information about the trace type + */ +plots.register = function(_module, thisType, categoriesIn, meta) { + if(modules[thisType]) { + throw new Error('type ' + thisType + ' already registered'); + } + + var categoryObj = {}; + for(var i = 0; i < categoriesIn.length; i++) { + categoryObj[categoriesIn[i]] = true; + allCategories[categoriesIn[i]] = true; + } + + modules[thisType] = { + module: _module, + categories: categoryObj + }; + + if(meta && Object.keys(meta).length) { + modules[thisType].meta = meta; + } + + allTypes.push(thisType); +}; + +function getTraceType(traceType) { + if(typeof traceType === 'object') traceType = traceType.type; + return traceType; +} + +plots.getModule = function(trace) { + if(trace.r !== undefined) { + console.log('Oops, tried to put a polar trace ' + + 'on an incompatible graph of cartesian ' + + 'data. Ignoring this dataset.', trace + ); + return false; + } + + var _module = modules[getTraceType(trace)]; + if(!_module) return false; + return _module.module; +}; + + +/** + * plots.traceIs: is this trace type in this category? + * + * traceType: a trace (object) or trace type (string) + * category: a category (string) + */ +plots.traceIs = function traceIs(traceType, category) { + traceType = getTraceType(traceType); + + if(traceType === 'various') return false; // FIXME + + var _module = modules[traceType]; + + if(!_module) { + if(traceType !== undefined) { + console.warn('unrecognized trace type ' + traceType); + } + _module = modules[plots.attributes.type.dflt]; + } + + return !!_module.categories[category]; +}; + + +/** + * plots.registerSubplot: register a subplot type + * + * @param {string} subplotType subplot type + * (these must also be categories defined with plots.register) + * @param {string or array of strings} attr attribute name in traces and layout + * @param {string or array of strings} idRoot root of id + * (setting the possible value for attrName) + * @param {object} attributes attribute(s) for traces of this subplot type + * + * In trace objects `attr` is the object key taking a valid `id` as value + * (the set of all valid ids is generated below and stored in idRegex). + * + * In the layout object, a or several valid `attr` name(s) can be keys linked + * to a nested attribute objects + * (the set of all valid attr names is generated below and stored in attrRegex). + * + * TODO use these in Lib.coerce + */ +plots.registerSubplot = function(subplotType, attr, idRoot, attributes) { + if(subplotsRegistry[subplotType]) { + throw new Error('subplot ' + subplotType + ' already registered'); + } + + var regexStart = '^', + regexEnd = '([2-9]|[1-9][0-9]+)?$', + hasXY = (subplotType === 'cartesian' || subplotsRegistry === 'gl2d'); + + function makeRegex(mid) { + return new RegExp(regexStart + mid + regexEnd); + } + + // not sure what's best for the 'cartesian' type at this point + subplotsRegistry[subplotType] = { + attr: attr, + idRoot: idRoot, + attributes: attributes, + // register the regex representing the set of all valid attribute names + attrRegex: hasXY ? + { x: makeRegex(attr[0]), y: makeRegex(attr[1]) } : + makeRegex(attr), + // register the regex representing the set of all valid attribute ids + idRegex: hasXY ? + { x: makeRegex(idRoot[0]), y: makeRegex(idRoot[1]) } : + makeRegex(idRoot) + }; +}; + +// TODO separate the 'find subplot' step (which looks in layout) +// from the 'get subplot ids' step (which looks in fullLayout._plots) +plots.getSubplotIds = function getSubplotIds(layout, type) { + if(plots.subplotsRegistry[type] === undefined) return []; + + // layout must be 'fullLayout' here + if(type === 'cartesian' && !layout._hasCartesian) return []; + if(type === 'gl2d' && !layout._hasGL2D) return []; + if(type === 'cartesian' || type === 'gl2d') { + return Object.keys(layout._plots); + } + + var idRegex = plots.subplotsRegistry[type].idRegex, + layoutKeys = Object.keys(layout), + subplotIds = [], + layoutKey; + + for(var i = 0; i < layoutKeys.length; i++) { + layoutKey = layoutKeys[i]; + if(idRegex.test(layoutKey)) subplotIds.push(layoutKey); + } + + return subplotIds; +}; + +plots.getSubplotIdsInData = function getSubplotsInData(data, type) { + if(plots.subplotsRegistry[type] === undefined) return []; + + var attr = plots.subplotsRegistry[type].attr, + subplotIds = [], + trace; + + for (var i = 0; i < data.length; i++) { + trace = data[i]; + if(Plotly.Plots.traceIs(trace, type) && subplotIds.indexOf(trace[attr])===-1) { + subplotIds.push(trace[attr]); + } + } + + return subplotIds; +}; + +plots.getSubplotData = function getSubplotData(data, type, subplotId) { + if(plots.subplotsRegistry[type] === undefined) return []; + + var attr = plots.subplotsRegistry[type].attr, + subplotData = [], + trace; + + for(var i = 0; i < data.length; i++) { + trace = data[i]; + + if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { + var spmatch = Plotly.Axes.subplotMatch, + subplotX = 'x' + subplotId.match(spmatch)[1], + subplotY = 'y' + subplotId.match(spmatch)[2]; + + if(trace[attr[0]]===subplotX && trace[attr[1]]===subplotY) { + subplotData.push(trace); + } + } + else { + if(trace[attr] === subplotId) subplotData.push(trace); + } + } + + return subplotData; +}; + +// in some cases the browser doesn't seem to know how big +// the text is at first, so it needs to draw it, +// then wait a little, then draw it again +plots.redrawText = function(gd) { + + // doesn't work presently (and not needed) for polar or 3d + if(gd._fullLayout._hasGL3D || (gd.data && gd.data[0] && gd.data[0].r)) { + return; + } + + return new Promise(function(resolve) { + setTimeout(function(){ + Plotly.Annotations.drawAll(gd); + Plotly.Legend.draw(gd); + (gd.calcdata||[]).forEach(function(d){ + if(d[0]&&d[0].t&&d[0].t.cb) d[0].t.cb(); + }); + resolve(plots.previousPromises(gd)); + },300); + }); +}; + +// resize plot about the container size +plots.resize = function(gd) { + if(!gd || d3.select(gd).style('display') === 'none') return; + + if(gd._redrawTimer) clearTimeout(gd._redrawTimer); + + gd._redrawTimer = setTimeout(function() { + if((gd._fullLayout || {}).autosize) { + // autosizing doesn't count as a change that needs saving + var oldchanged = gd.changed; + + // nor should it be included in the undo queue + gd.autoplay = true; + + Plotly.relayout(gd, {autosize: true}); + gd.changed = oldchanged; + } + }, 100); +}; + + +// for use in Plotly.Lib.syncOrAsync, check if there are any +// pending promises in this plot and wait for them +plots.previousPromises = function(gd) { + if((gd._promises || []).length) { + return Promise.all(gd._promises) + .then(function(){ gd._promises=[]; }); + } +}; + +/** + * Adds the 'Edit chart' link. + * Note that now Plotly.plot() calls this so it can regenerate whenever it replots + * + * Add source links to your graph inside the 'showSources' config argument. + */ +plots.addLinks = function(gd) { + var fullLayout = gd._fullLayout; + + var linkContainer = fullLayout._paper + .selectAll('text.js-plot-link-container').data([0]); + + linkContainer.enter().append('text') + .classed('js-plot-link-container', true) + .style({ + 'font-family':'"Open Sans",Arial,sans-serif', + 'font-size':'12px', + 'fill': Plotly.Color.defaultLine, + 'pointer-events': 'all' + }) + .each(function(){ + var links = d3.select(this); + links.append('tspan').classed('js-link-to-tool', true); + links.append('tspan').classed('js-link-spacer', true); + links.append('tspan').classed('js-sourcelinks', true); + }); + + // The text node inside svg + var text = linkContainer.node(), + attrs = { + y: fullLayout._paper.attr('height') - 9 + }; + + // If text's width is bigger than the layout + // IE doesn't like getComputedTextLength if an element + // isn't visible, which it (sometimes?) isn't + // apparently offsetParent is null for invisibles. + // http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom + if (text && text.offsetParent && + text.getComputedTextLength() >= (fullLayout.width - 20)) { + // Align the text at the left + attrs['text-anchor'] = 'start'; + attrs.x = 5; + } + else { + // Align the text at the right + attrs['text-anchor'] = 'end'; + attrs.x = fullLayout._paper.attr('width') - 7; + } + + linkContainer.attr(attrs); + + var toolspan = linkContainer.select('.js-link-to-tool'), + spacespan = linkContainer.select('.js-link-spacer'), + sourcespan = linkContainer.select('.js-sourcelinks'); + + if(gd._context.showSources) gd._context.showSources(gd); + + // 'view in plotly' link for embedded plots + if(gd._context.showLink) positionPlayWithData(gd, toolspan); + + // separator if we have both sources and tool link + spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : ''); +}; + +// note that now this function is only adding the brand in +// iframes and 3rd-party apps +function positionPlayWithData(gd, container){ + container.text(''); + var link = container.append('a') + .attr({ + 'xlink:xlink:href': '#', + 'class': 'link--impt link--embedview', + 'font-weight':'bold' + }) + .text(gd._context.linkText + ' ' + String.fromCharCode(187)); + + if(gd._context.sendData) { + link.on('click',function(){ + gd.emit('plotly_beforeexport'); + + var baseUrl = (window.PLOTLYENV && window.PLOTLYENV.BASE_URL) || 'https://plot.ly'; + + var hiddenformDiv = d3.select(gd) + .append('div') + .attr('id', 'hiddenform') + .style('display', 'none'); + + var hiddenform = hiddenformDiv + .append('form') + .attr({ + action: baseUrl + '/external', + method: 'post', + target: '_blank' + }); + + var hiddenformInput = hiddenform + .append('input') + .attr({ + type: 'text', + name: 'data' + }); + + hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata'); + hiddenform.node().submit(); + hiddenformDiv.remove(); + + gd.emit('plotly_afterexport'); + return false; + }); + } + else { + var path = window.location.pathname.split('/'); + var query = window.location.search; + link.attr({ + 'xlink:xlink:show': 'new', + 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query + }); + } +} + +plots.supplyDefaults = function(gd) { + // fill in default values: + // gd.data, gd.layout: + // are precisely what the user specified + // gd._fullData, gd._fullLayout: + // are complete descriptions of how to draw the plot + var oldFullLayout = gd._fullLayout || {}, + newFullLayout = gd._fullLayout = {}, + newLayout = gd.layout || {}, + oldFullData = gd._fullData || [], + newFullData = gd._fullData = [], + newData = gd.data || [], + modules = gd._modules = []; + + var i, trace, fullTrace, module, axList, ax; + + + // first fill in what we can of layout without looking at data + // because fullData needs a few things from layout + plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); + + // keep track of how many traces are inputted + newFullLayout._dataLength = newData.length; + + // then do the data + for (i = 0; i < newData.length; i++) { + trace = newData[i]; + + fullTrace = plots.supplyDataDefaults(trace, i, newFullLayout); + newFullData.push(fullTrace); + + // DETECT 3D, Cartesian, and Polar + if(plots.traceIs(fullTrace, 'cartesian')) newFullLayout._hasCartesian = true; + else if(plots.traceIs(fullTrace, 'gl3d')) newFullLayout._hasGL3D = true; + else if(plots.traceIs(fullTrace, 'geo')) newFullLayout._hasGeo = true; + else if(plots.traceIs(fullTrace, 'pie')) newFullLayout._hasPie = true; + else if(plots.traceIs(fullTrace, 'gl2d')) newFullLayout._hasGL2D = true; + else if('r' in fullTrace) newFullLayout._hasPolar = true; + + module = fullTrace._module; + if (module && modules.indexOf(module)===-1) modules.push(module); + } + + // special cases that introduce interactions between traces + for (i = 0; i < modules.length; i++) { + module = modules[i]; + if (module.cleanData) module.cleanData(newFullData); + } + + if (oldFullData.length === newData.length) { + for (i = 0; i < newFullData.length; i++) { + relinkPrivateKeys(newFullData[i], oldFullData[i]); + } + } + + // finally, fill in the pieces of layout that may need to look at data + plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData); + + cleanScenes(newFullLayout, oldFullLayout); + + /* + * Relink functions and underscore attributes to promote consistency between + * plots. + */ + relinkPrivateKeys(newFullLayout, oldFullLayout); + + plots.doAutoMargin(gd); + + // can't quite figure out how to get rid of this... each axis needs + // a reference back to the DOM object for just a few purposes + axList = Plotly.Axes.list(gd); + for (i = 0; i < axList.length; i++) { + ax = axList[i]; + ax._td = gd; + ax.setScale(); + } + + // update object references in calcdata + if ((gd.calcdata || []).length === newFullData.length) { + for (i = 0; i < newFullData.length; i++) { + trace = newFullData[i]; + (gd.calcdata[i][0] || {}).trace = trace; + } + } +}; + +function cleanScenes(newFullLayout, oldFullLayout) { + var oldSceneKey, + oldSceneKeys = plots.getSubplotIds(oldFullLayout, 'gl3d'); + + for (var i = 0; i < oldSceneKeys.length; i++) { + oldSceneKey = oldSceneKeys[i]; + if(!newFullLayout[oldSceneKey] && !!oldFullLayout[oldSceneKey]._scene) { + oldFullLayout[oldSceneKey]._scene.destroy(); + } + } + +} + +/** + * Relink private _keys and keys with a function value from one layout + * (usually cached) to the new fullLayout. + * relink means copying if object is pass-by-value and adding a reference + * if object is pass-by-ref. This prevents deepCopying massive structures like + * a webgl context. + */ +function relinkPrivateKeys(toLayout, fromLayout) { + var keys = Object.keys(fromLayout); + var j; + + for (var i = 0; i < keys.length; ++i) { + var k = keys[i]; + if(k.charAt(0)==='_' || typeof fromLayout[k]==='function') { + // if it already exists at this point, it's something + // that we recreate each time around, so ignore it + if(k in toLayout) continue; + + toLayout[k] = fromLayout[k]; + } + else if (Array.isArray(fromLayout[k]) && + Array.isArray(toLayout[k]) && + fromLayout[k].length && + Plotly.Lib.isPlainObject(fromLayout[k][0])) { + if(fromLayout[k].length !== toLayout[k].length) { + // this should be handled elsewhere, it causes + // ambiguity if we try to deal with it here. + throw new Error('relinkPrivateKeys needs equal ' + + 'length arrays'); + } + + for(j = 0; j < fromLayout[k].length; j++) { + relinkPrivateKeys(toLayout[k][j], fromLayout[k][j]); + } + } + else if (Plotly.Lib.isPlainObject(fromLayout[k]) && + Plotly.Lib.isPlainObject(toLayout[k])) { + // recurse into objects, but only if they still exist + relinkPrivateKeys(toLayout[k], fromLayout[k]); + if (!Object.keys(toLayout[k]).length) delete toLayout[k]; + } + } +} + +plots.supplyDataDefaults = function(traceIn, i, layout) { + var traceOut = {}, + defaultColor = Plotly.Color.defaults[i % Plotly.Color.defaults.length]; + + function coerce(attr, dflt) { + return Plotly.Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); + } + + function coerceSubplotAttr(subplotType, subplotAttr) { + if(!plots.traceIs(traceOut, subplotType)) return; + return Plotly.Lib.coerce(traceIn, traceOut, + plots.subplotsRegistry[subplotType].attributes, subplotAttr); + } + + // module-independent attributes + traceOut.index = i; + var visible = coerce('visible'), + scene, + module; + + coerce('type'); + coerce('uid'); + + // this is necessary otherwise we lose references to scene objects when + // the traces of a scene are invisible. Also we handle visible/unvisible + // differently for 3D cases. + coerceSubplotAttr('gl3d', 'scene'); + coerceSubplotAttr('geo', 'geo'); + + // module-specific attributes --- note: we need to send a trace into + // the 3D modules to have it removed from the webgl context. + if(visible || scene) { + module = plots.getModule(traceOut); + traceOut._module = module; + } + + // gets overwritten in pie and geo + if(visible) coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); + + if(module && visible) module.supplyDefaults(traceIn, traceOut, defaultColor, layout); + + if(visible) { + coerce('name', 'trace ' + i); + + if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity'); + + coerceSubplotAttr('cartesian', 'xaxis'); + coerceSubplotAttr('cartesian', 'yaxis'); + + coerceSubplotAttr('gl2d', 'xaxis'); + coerceSubplotAttr('gl2d', 'yaxis'); + + if(plots.traceIs(traceOut, 'showLegend')) { + coerce('showlegend'); + coerce('legendgroup'); + } + } + + // NOTE: I didn't include fit info at all... for now I think it can stay + // just in gd.data, as this info isn't involved in creating plots at all, + // only in pulling back up the fit popover + + // reference back to the input object for convenience + traceOut._input = traceIn; + + return traceOut; +}; + +plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Plotly.Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt); + } + + var globalFont = Plotly.Lib.coerceFont(coerce, 'font'); + + coerce('title'); + + Plotly.Lib.coerceFont(coerce, 'titlefont', { + family: globalFont.family, + size: Math.round(globalFont.size * 1.4), + color: globalFont.color + }); + + var autosize = coerce('autosize', + (layoutIn.width && layoutIn.height) ? false : 'initial'); + coerce('width'); + coerce('height'); + + coerce('margin.l'); + coerce('margin.r'); + coerce('margin.t'); + coerce('margin.b'); + coerce('margin.pad'); + coerce('margin.autoexpand'); + + // called in plotAutoSize otherwise + if(autosize!=='initial') plots.sanitizeMargins(layoutOut); + + coerce('paper_bgcolor'); + + coerce('separators'); + coerce('hidesources'); + coerce('smith'); + coerce('_hasCartesian'); + coerce('_hasGL3D'); + coerce('_hasGeo'); + coerce('_hasPie'); + coerce('_hasGL2D'); +}; + +plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) { + var moduleLayoutDefaults = [ + 'Axes', 'Annotations', 'Shapes', 'Fx', + 'Bars', 'Boxes', 'Gl3dLayout', 'GeoLayout', 'Pie', 'Legend' + ]; + + var i, module; + + // don't add a check for 'function in module' as it is better to error out and + // secure the module API then not apply the default function. + for(i = 0; i < moduleLayoutDefaults.length; i++) { + module = moduleLayoutDefaults[i]; + if(Plotly[module]) { + Plotly[module].supplyLayoutDefaults(layoutIn, layoutOut, fullData); + } + } +}; + +plots.purge = function(gd) { + // remove all plotly attributes from a div so it can be replotted fresh + // TODO: these really need to be encapsulated into a much smaller set... + + // note: we DO NOT remove _context because it doesn't change when we insert + // a new plot, and may have been set outside of our scope. + + // clean up the gl and geo containers + // TODO unify subplot creation/update with d3.selection.order + // and/or subplot ids + var fullLayout = gd._fullLayout || {}; + if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove(); + if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove(); + + // data and layout + delete gd.data; + delete gd.layout; + delete gd._fullData; + delete gd._fullLayout; + delete gd.calcdata; + delete gd.framework; + delete gd.empty; + + delete gd.fid; + + delete gd.undoqueue; // action queue + delete gd.undonum; + delete gd.autoplay; // are we doing an action that doesn't go in undo queue? + delete gd.changed; + + // these get recreated on Plotly.plot anyway, but just to be safe + // (and to have a record of them...) + delete gd._modules; + delete gd._tester; + delete gd._testref; + delete gd._promises; + delete gd._redrawTimer; + delete gd._replotting; + delete gd.firstscatter; + delete gd.hmlumcount; + delete gd.hmpixcount; + delete gd.numboxes; + delete gd._hoverTimer; + delete gd._lastHoverTime; +}; + +plots.style = function(gd) { + var modulesWithErrorBars = Plotly.ErrorBars ? + gd._modules.concat(Plotly.ErrorBars) : gd._modules, + i, + module; + + for (i = 0; i < modulesWithErrorBars.length; i++) { + module = modulesWithErrorBars[i]; + if (module.style) module.style(gd); + } +}; + +plots.sanitizeMargins = function(fullLayout) { + // polar doesn't do margins... + if(!fullLayout || !fullLayout.margin) return; + + var width = fullLayout.width, + height = fullLayout.height, + margin = fullLayout.margin, + plotWidth = width - (margin.l + margin.r), + plotHeight = height - (margin.t + margin.b), + correction; + + // if margin.l + margin.r = 0 then plotWidth > 0 + // as width >= 10 by supplyDefaults + // similarly for margin.t + margin.b + + if (plotWidth < 0) { + correction = (width - 1) / (margin.l + margin.r); + margin.l = Math.floor(correction * margin.l); + margin.r = Math.floor(correction * margin.r); + } + + if (plotHeight < 0) { + correction = (height - 1) / (margin.t + margin.b); + margin.t = Math.floor(correction * margin.t); + margin.b = Math.floor(correction * margin.b); + } +}; +// called by legend and colorbar routines to see if we need to +// expand the margins to show them +// o is {x,l,r,y,t,b} where x and y are plot fractions, +// the rest are pixels in each direction +// or leave o out to delete this entry (like if it's hidden) +plots.autoMargin = function(gd,id,o) { + var fullLayout = gd._fullLayout; + if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; + if(fullLayout.margin.autoexpand!==false) { + if(!o) delete fullLayout._pushmargin[id]; + else { + var pad = o.pad||12; + + // if the item is too big, just give it enough automargin to + // make sure you can still grab it and bring it back + if(o.l+o.r > fullLayout.width*0.5) o.l = o.r = 0; + if(o.b+o.t > fullLayout.height*0.5) o.b = o.t = 0; + + fullLayout._pushmargin[id] = { + l: {val:o.x, size: o.l+pad}, + r: {val:o.x, size: o.r+pad}, + b: {val:o.y, size: o.b+pad}, + t: {val:o.y, size: o.t+pad} + }; + } + + if(!gd._replotting) plots.doAutoMargin(gd); + } +}; + +plots.doAutoMargin = function(gd) { + var fullLayout = gd._fullLayout; + if(!fullLayout._size) fullLayout._size = {}; + if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; + var gs = fullLayout._size, + oldmargins = JSON.stringify(gs); + + // adjust margins for outside legends and colorbars + // fullLayout.margin is the requested margin, + // fullLayout._size has margins and plotsize after adjustment + var ml = Math.max(fullLayout.margin.l||0,0), + mr = Math.max(fullLayout.margin.r||0,0), + mt = Math.max(fullLayout.margin.t||0,0), + mb = Math.max(fullLayout.margin.b||0,0), + pm = fullLayout._pushmargin; + if(fullLayout.margin.autoexpand!==false) { + // fill in the requested margins + pm.base = { + l:{val:0, size:ml}, + r:{val:1, size:mr}, + t:{val:1, size:mt}, + b:{val:0, size:mb} + }; + // now cycle through all the combinations of l and r + // (and t and b) to find the required margins + Object.keys(pm).forEach(function(k1) { + var pushleft = pm[k1].l||{}, + pushbottom = pm[k1].b||{}, + fl = pushleft.val, + pl = pushleft.size, + fb = pushbottom.val, + pb = pushbottom.size; + Object.keys(pm).forEach(function(k2) { + if(isNumeric(pl) && pm[k2].r) { + var fr = pm[k2].r.val, + pr = pm[k2].r.size; + if(fr>fl) { + var newl = (pl*fr + + (pr-fullLayout.width)*fl) / (fr-fl), + newr = (pr*(1-fl) + + (pl-fullLayout.width)*(1-fr)) / (fr-fl); + if(newl>=0 && newr>=0 && newl+newr>ml+mr) { + ml = newl; + mr = newr; + } + } + } + if(isNumeric(pb) && pm[k2].t) { + var ft = pm[k2].t.val, + pt = pm[k2].t.size; + if(ft>fb) { + var newb = (pb*ft + + (pt-fullLayout.height)*fb) / (ft-fb), + newt = (pt*(1-fb) + + (pb-fullLayout.height)*(1-ft)) / (ft-fb); + if(newb>=0 && newt>=0 && newb+newt>mb+mt) { + mb = newb; + mt = newt; + } + } + } + }); + }); + } + + gs.l = Math.round(ml); + gs.r = Math.round(mr); + gs.t = Math.round(mt); + gs.b = Math.round(mb); + gs.p = Math.round(fullLayout.margin.pad); + gs.w = Math.round(fullLayout.width)-gs.l-gs.r; + gs.h = Math.round(fullLayout.height)-gs.t-gs.b; + + // if things changed and we're not already redrawing, trigger a redraw + if(!gd._replotting && oldmargins!=='{}' && + oldmargins!==JSON.stringify(fullLayout._size)) { + return Plotly.plot(gd); + } +}; + +/** + * JSONify the graph data and layout + * + * This function needs to recurse because some src can be inside + * sub-objects. + * + * It also strips out functions and private (starts with _) elements. + * Therefore, we can add temporary things to data and layout that don't + * get saved. + * + * @param gd The graphDiv + * @param {Boolean} dataonly If true, don't return layout. + * @param {'keepref'|'keepdata'|'keepall'} [mode='keepref'] Filter what's kept + * keepref: remove data for which there's a src present + * eg if there's xsrc present (and xsrc is well-formed, + * ie has : and some chars before it), strip out x + * keepdata: remove all src tags, don't remove the data itself + * keepall: keep data and src + * @param {String} output If you specify 'object', the result will not be stringified + * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData + * @returns {Object|String} + */ +plots.graphJson = function(gd, dataonly, mode, output, useDefaults){ + // if the defaults aren't supplied yet, we need to do that... + if ((useDefaults && dataonly && !gd._fullData) || + (useDefaults && !dataonly && !gd._fullLayout)) { + plots.supplyDefaults(gd); + } + + var data = (useDefaults) ? gd._fullData : gd.data, + layout = (useDefaults) ? gd._fullLayout : gd.layout; + + function stripObj(d) { + if(typeof d === 'function') { + return null; + } + if(Plotly.Lib.isPlainObject(d)) { + var o={}, v, src; + for(v in d) { + // remove private elements and functions + // _ is for private, [ is a mistake ie [object Object] + if(typeof d[v]==='function' || + ['_','['].indexOf(v.charAt(0))!==-1) { + continue; + } + + // look for src/data matches and remove the appropriate one + if(mode==='keepdata') { + // keepdata: remove all ...src tags + if(v.substr(v.length-3)==='src') { + continue; + } + } + else if(mode==='keepstream') { + // keep sourced data if it's being streamed. + // similar to keepref, but if the 'stream' object exists + // in a trace, we will keep the data array. + src = d[v+'src']; + if(typeof src==='string' && src.indexOf(':')>0) { + if(!Plotly.Lib.isPlainObject(d.stream)) { + continue; + } + } + } + else if(mode!=='keepall') { + // keepref: remove sourced data but only + // if the source tag is well-formed + src = d[v+'src']; + if(typeof src==='string' && src.indexOf(':')>0) { + continue; + } + } + + // OK, we're including this... recurse into it + o[v] = stripObj(d[v]); + } + return o; + } + + if(Array.isArray(d)) { + return d.map(stripObj); + } + + // convert native dates to date strings... + // mostly for external users exporting to plotly + if(d && d.getTime) { + return Plotly.Lib.ms2DateTime(d); + } + + return d; + } + + var obj = { + data:(data||[]).map(function(v){ + var d = stripObj(v); + // fit has some little arrays in it that don't contain data, + // just fit params and meta + if(dataonly) { delete d.fit; } + return d; + }) + }; + if(!dataonly) { obj.layout = stripObj(layout); } + + if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig(); + + return (output==='object') ? obj : JSON.stringify(obj); +}; From 942e49e16ed856522e0f5ca1560f50766162e6cc Mon Sep 17 00:00:00 2001 From: etpinard Date: Tue, 10 Nov 2015 23:10:04 -0500 Subject: [PATCH 07/38] put plot-type-specific lib functions in lib/ --- .../array-to-calc-item.js => lib/array_to_calc_item.js} | 0 .../lib/location-utils.js => lib/geo_location_utils.js} | 7 ++++--- src/{gl3d/lib/format-color.js => lib/gl_format_color.js} | 2 +- src/{gl3d => }/lib/html2unicode.js | 0 src/lib/lib.js | 2 +- src/{gl3d => }/lib/show_no_webgl_msg.js | 2 +- src/{gl3d => }/lib/str2rgbarray.js | 4 ++-- src/{geo/lib/topojson-utils.js => lib/topojson_utils.js} | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) rename src/{geo/lib/array-to-calc-item.js => lib/array_to_calc_item.js} (100%) rename src/{geo/lib/location-utils.js => lib/geo_location_utils.js} (89%) rename src/{gl3d/lib/format-color.js => lib/gl_format_color.js} (97%) rename src/{gl3d => }/lib/html2unicode.js (100%) rename src/{gl3d => }/lib/show_no_webgl_msg.js (96%) rename src/{gl3d => }/lib/str2rgbarray.js (68%) rename src/{geo/lib/topojson-utils.js => lib/topojson_utils.js} (89%) diff --git a/src/geo/lib/array-to-calc-item.js b/src/lib/array_to_calc_item.js similarity index 100% rename from src/geo/lib/array-to-calc-item.js rename to src/lib/array_to_calc_item.js diff --git a/src/geo/lib/location-utils.js b/src/lib/geo_location_utils.js similarity index 89% rename from src/geo/lib/location-utils.js rename to src/lib/geo_location_utils.js index 0ae35fd6661..6bb51cc3aa8 100644 --- a/src/geo/lib/location-utils.js +++ b/src/lib/geo_location_utils.js @@ -2,9 +2,10 @@ var locationUtils = module.exports = {}; -var Plotly = require('../../plotly'), - // an hash object iso3 to regex string - countryNameData = require('../raw/country-name_to_iso3.json'); +var Plotly = require('../plotly'); + +// an hash object iso3 to regex string +var countryNameData = require('../constants/country-name_to_iso3.json'); // make list of all country iso3 ids from at runtime var countryIds = Object.keys(countryNameData); diff --git a/src/gl3d/lib/format-color.js b/src/lib/gl_format_color.js similarity index 97% rename from src/gl3d/lib/format-color.js rename to src/lib/gl_format_color.js index 08a7bc7b15c..bd2b5271810 100644 --- a/src/gl3d/lib/format-color.js +++ b/src/lib/gl_format_color.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('../../plotly'); +var Plotly = require('../plotly'); var tinycolor = require('tinycolor2'); var isNumeric = require('fast-isnumeric'); var str2RgbaArray = require('./str2rgbarray'); diff --git a/src/gl3d/lib/html2unicode.js b/src/lib/html2unicode.js similarity index 100% rename from src/gl3d/lib/html2unicode.js rename to src/lib/html2unicode.js diff --git a/src/lib/lib.js b/src/lib/lib.js index df380b74758..09b7fd66b33 100644 --- a/src/lib/lib.js +++ b/src/lib/lib.js @@ -1156,7 +1156,7 @@ lib.stripTrailingSlash = function (str) { return str; }; -var colorscaleNames = Object.keys(require('../colorscale').scales); +var colorscaleNames = Object.keys(require('../components/colorscale/scales')); lib.valObjects = { data_array: { diff --git a/src/gl3d/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js similarity index 96% rename from src/gl3d/lib/show_no_webgl_msg.js rename to src/lib/show_no_webgl_msg.js index 5e382318ea2..0118e1e0d9b 100644 --- a/src/gl3d/lib/show_no_webgl_msg.js +++ b/src/lib/show_no_webgl_msg.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('../../plotly'); +var Plotly = require('../plotly'); /** * Prints a no webgl error message into the scene container diff --git a/src/gl3d/lib/str2rgbarray.js b/src/lib/str2rgbarray.js similarity index 68% rename from src/gl3d/lib/str2rgbarray.js rename to src/lib/str2rgbarray.js index 5593ab75e5a..2dfcd0bb3f3 100644 --- a/src/gl3d/lib/str2rgbarray.js +++ b/src/lib/str2rgbarray.js @@ -1,7 +1,7 @@ 'use strict'; -var tinycolor = require('tinycolor2'), - arrtools = require('arraytools'); +var tinycolor = require('tinycolor2'); +var arrtools = require('arraytools'); function str2RgbaArray(color) { color = tinycolor(color); diff --git a/src/geo/lib/topojson-utils.js b/src/lib/topojson_utils.js similarity index 89% rename from src/geo/lib/topojson-utils.js rename to src/lib/topojson_utils.js index 42df3cf4177..a5aa8f44e98 100644 --- a/src/geo/lib/topojson-utils.js +++ b/src/lib/topojson_utils.js @@ -2,7 +2,7 @@ var topojsonUtils = module.exports = {}; -var locationmodeToLayer = require('./params').locationmodeToLayer, +var locationmodeToLayer = require('../constants/geo_constants').locationmodeToLayer, topojsonFeature = require('topojson').feature; From dd7ccc544f12ff2776376c30ab7a64d497a9f313 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 09:11:36 -0500 Subject: [PATCH 08/38] change path to geo assets index file --- tasks/util/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/util/constants.js b/tasks/util/constants.js index ed3a1b54aac..00984b4c28c 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -15,7 +15,7 @@ module.exports = { pathToPlotlyDistMin: path.join(pathToDist, 'plotly.min.js'), pathToPlotlyDistWithMeta: path.join(pathToDist, 'plotly-with-meta.js'), - pathToPlotlyGeoAssetsSrc: path.join(pathToSrc, 'geo/geo-assets.js'), + pathToPlotlyGeoAssetsSrc: path.join(pathToSrc, 'assets/geo_assets.js'), pathToPlotlyGeoAssetsDist: path.join(pathToDist, 'plotly-geo-assets.js'), pathToFontSVG: path.join(pathToSrc, 'fonts/ploticon/ploticon.svg'), From edae22498bcc94692eb88209cdda0b272a56a06e Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 10:14:08 -0500 Subject: [PATCH 09/38] put plot_schema in plot_api/ --- src/{plotschema.js => plot_api/plot_schema.js} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/{plotschema.js => plot_api/plot_schema.js} (98%) diff --git a/src/plotschema.js b/src/plot_api/plot_schema.js similarity index 98% rename from src/plotschema.js rename to src/plot_api/plot_schema.js index aa655e795b2..f95923e9ba7 100644 --- a/src/plotschema.js +++ b/src/plot_api/plot_schema.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../plotly'); var extendFlat = Plotly.Lib.extendFlat; var extendDeep = Plotly.Lib.extendDeep; @@ -22,8 +22,8 @@ var plotSchema = { }; // FIXME polar attribute are not part of Plotly yet -var polarAreaAttrs = require('./polar/attributes/area'), - polarAxisAttrs = require('./polar/attributes/polaraxes'); +var polarAreaAttrs = require('../plots/polar/attributes/area'), + polarAxisAttrs = require('../plots/polar/attributes/polaraxes'); var PlotSchema = module.exports = {}; From df06911e0db8f0ce9240006e54277207b3c69b50 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 11:09:38 -0500 Subject: [PATCH 10/38] break lib.js into smaller lib modules: - coerce.js, dates.js, matrix.js nested_property.js, notifier.js, search.js, stats.js --- src/lib/coerce.js | 334 ++++++++++ src/lib/dates.js | 325 +++++++++ src/lib/lib.js | 1266 ++---------------------------------- src/lib/matrix.js | 99 +++ src/lib/nested_property.js | 245 +++++++ src/lib/notifier.js | 66 ++ src/lib/search.js | 98 +++ src/lib/stats.js | 85 +++ 8 files changed, 1294 insertions(+), 1224 deletions(-) create mode 100644 src/lib/coerce.js create mode 100644 src/lib/dates.js create mode 100644 src/lib/matrix.js create mode 100644 src/lib/nested_property.js create mode 100644 src/lib/notifier.js create mode 100644 src/lib/search.js create mode 100644 src/lib/stats.js diff --git a/src/lib/coerce.js b/src/lib/coerce.js new file mode 100644 index 00000000000..e62f560cb29 --- /dev/null +++ b/src/lib/coerce.js @@ -0,0 +1,334 @@ +'use strict'; + +var Plotly = require('../plotly'); +var isNumeric = require('fast-isnumeric'); +var tinycolor = require('tinycolor2'); +var nestedProperty = require('./nested_property'); + +var colorscaleNames = Object.keys(require('../components/colorscale/scales')); + + +exports.valObjects = { + data_array: { + // You can use *dflt=[] to force said array to exist though. + description: [ + 'An {array} of data.', + 'The value MUST be an {array}, or we ignore it.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(Array.isArray(v)) propOut.set(v); + else if(dflt!==undefined) propOut.set(dflt); + } + }, + enumerated: { + description: [ + 'Enumerated value type. The available values are listed', + 'in `values`.' + ].join(' '), + requiredOpts: ['values'], + otherOpts: ['dflt', 'coerceNumber', 'arrayOk'], + coerceFunction: function(v, propOut, dflt, opts) { + if(opts.coerceNumber) v = +v; + if(opts.values.indexOf(v)===-1) propOut.set(dflt); + else propOut.set(v); + } + }, + 'boolean': { + description: 'A boolean (true/false) value.', + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(v===true || v===false) propOut.set(v); + else propOut.set(dflt); + } + }, + number: { + description: [ + 'A number or a numeric value', + '(e.g. a number inside a string).', + 'When applicable, values greater (less) than `max` (`min`)', + 'are coerced to the `dflt`.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'min', 'max', 'arrayOk'], + coerceFunction: function(v, propOut, dflt, opts) { + if(!isNumeric(v) || + (opts.min!==undefined && vopts.max)) { + propOut.set(dflt); + } + else propOut.set(+v); + } + }, + integer: { + description: [ + 'An integer or an integer inside a string.', + 'When applicable, values greater (less) than `max` (`min`)', + 'are coerced to the `dflt`.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'min', 'max'], + coerceFunction: function(v, propOut, dflt, opts) { + if(v%1 || !isNumeric(v) || + (opts.min!==undefined && vopts.max)) { + propOut.set(dflt); + } + else propOut.set(+v); + } + }, + string: { + description: [ + 'A string value.', + 'Numbers are converted to strings except for attributes with', + '`strict` set to true.' + ].join(' '), + requiredOpts: [], + // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter) + otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'], + coerceFunction: function(v, propOut, dflt, opts) { + if(opts.strict===true && typeof v !== 'string') { + propOut.set(dflt); + return; + } + + var s = String(v); + if(v===undefined || (opts.noBlank===true && !s)) { + propOut.set(dflt); + } + else propOut.set(s); + } + }, + color: { + description: [ + 'A string describing color.', + 'Supported formats:', + '- hex (e.g. \'#d3d3d3\')', + '- rgb (e.g. \'rgb(255, 0, 0)\')', + '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')', + '- hsl (e.g. \'hsl(0, 100%, 50%)\')', + '- hsv (e.g. \'hsv(0, 100%, 100%)\')', + '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'arrayOk'], + coerceFunction: function(v, propOut, dflt) { + if(tinycolor(v).isValid()) propOut.set(v); + else propOut.set(dflt); + } + }, + colorscale: { + description: [ + 'A Plotly colorscale either picked by a name:', + '(any of', colorscaleNames.join(', '), ')', + 'customized as an {array} of 2-element {arrays} where', + 'the first element is the normalized color level value', + '(starting at *0* and ending at *1*),', + 'and the second item is a valid color string.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + propOut.set(Plotly.Colorscale.getScale(v, dflt)); + } + }, + angle: { + description: [ + 'A number (in degree) between -180 and 180.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(v==='auto') propOut.set('auto'); + else if(!isNumeric(v)) propOut.set(dflt); + else { + if(Math.abs(v)>180) v -= Math.round(v/360)*360; + propOut.set(+v); + } + } + }, + axisid: { + description: [ + 'An axis id string (e.g. \'x\', \'x2\', \'x3\', ...).' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(typeof v === 'string' && v.charAt(0)===dflt) { + var axnum = Number(v.substr(1)); + if(axnum%1 === 0 && axnum>1) { + propOut.set(v); + return; + } + } + propOut.set(dflt); + } + }, + sceneid: { + description: [ + 'A scene id string (e.g. \'scene\', \'scene2\', \'scene3\', ...).' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(typeof v === 'string' && v.substr(0,5)===dflt) { + var scenenum = Number(v.substr(5)); + if(scenenum%1 === 0 && scenenum>1) { + propOut.set(v); + return; + } + } + propOut.set(dflt); + } + }, + geoid: { + description: [ + 'A geo id string (e.g. \'geo\', \'geo2\', \'geo3\', ...).' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function(v, propOut, dflt) { + if(typeof v === 'string' && v.substr(0,3)===dflt) { + var geonum = Number(v.substr(3)); + if(geonum%1 === 0 && geonum>1) { + propOut.set(v); + return; + } + } + propOut.set(dflt); + } + }, + flaglist: { + description: [ + 'A string representing a combination of flags', + '(order does not matter here).', + 'Combine any of the available `flags` with *+*.', + '(e.g. (\'lines+markers\')).', + 'Values in `extras` cannot be combined.' + ].join(' '), + requiredOpts: ['flags'], + otherOpts: ['dflt', 'extras'], + coerceFunction: function(v, propOut, dflt, opts) { + if(typeof v !== 'string') { + propOut.set(dflt); + return; + } + if(opts.extras.indexOf(v)!==-1) { + propOut.set(v); + return; + } + var vParts = v.split('+'), + i = 0; + while(i3 digits, though javascript dates truncate to milliseconds + * returns false if it doesn't find a date + * + * 2-digit to 4-digit year conversion, where to cut off? + * from http://support.microsoft.com/kb/244664: + * 1930-2029 (the most retro of all...) + * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')): + * 1950-2049 + * by Java, from http://stackoverflow.com/questions/2024273/: + * now-80 - now+20 + * or FileMaker Pro, from + * http://www.filemaker.com/12help/html/add_view_data.4.21.html: + * now-70 - now+30 + * but python strptime etc, via + * http://docs.python.org/py3k/library/time.html: + * 1969-2068 (super forward-looking, but static, not sliding!) + * + * lets go with now-70 to now+30, and if anyone runs into this problem + * they can learn the hard way not to use 2-digit years, as no choice we + * make now will cover all possibilities. mostly this will all be taken + * care of in initial parsing, should only be an issue for hand-entered data + * currently (2012) this range is: + * 1942-2041 + */ + +exports.dateTime2ms = function(s) { + // first check if s is a date object + try { + if (s.getTime) return +s; + } + catch(e) { + return false; + } + + var y, m, d, h; + // split date and time parts + var datetime = String(s).split(' '); + if (datetime.length > 2) return false; + + var p = datetime[0].split('-'); // date part + if (p.length > 3 || (p.length !== 3 && datetime[1])) return false; + + // year + if (p[0].length === 4) y = Number(p[0]); + else if (p[0].length === 2) { + var yNow = new Date().getFullYear(); + y = ((Number(p[0]) - yNow + 70)%100 + 200)%100 + yNow - 70; + } + else return false; + if (!isNumeric(y)) return false; + if (p.length === 1) return new Date(y,0,1).getTime(); // year only + + // month + m = Number(p[1]) - 1; // new Date() uses zero-based months + if (p[1].length > 2 || !(m >= 0 && m <= 11)) return false; + if (p.length === 2) return new Date(y, m, 1).getTime(); // year-month + + // day + d = Number(p[2]); + if (p[2].length > 2 || !(d >= 1 && d <= 31)) return false; + + // now save the date part + d = new Date(y, m, d).getTime(); + if (!datetime[1]) return d; // year-month-day + p = datetime[1].split(':'); + if (p.length > 3) return false; + + // hour + h = Number(p[0]); + if (p[0].length > 2 || !(h >= 0 && h <= 23)) return false; + d += 3600000*h; + if (p.length === 1) return d; + + // minute + m = Number(p[1]); + if (p[1].length > 2 || !(m >= 0 && m <= 59)) return false; + d += 60000*m; + if (p.length === 2) return d; + + // second + s = Number(p[2]); + if (!(s >= 0 && s < 60)) return false; + return d+s*1000; +}; + +// is string s a date? (see above) +exports.isDateTime = function(s) { + return (exports.dateTime2ms(s) !== false); +}; + +// pad a number with zeroes, to given # of digits before the decimal point +function lpad(val, digits){ + return String(val + Math.pow(10, digits)).substr(1); +} + +/** + * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss + * Crop any trailing zeros in time, but always leave full date + * (we could choose to crop '-01' from date too)... + * Optional range r is the data range that applies, also in ms. + * If rng is big, the later parts of time will be omitted + */ +exports.ms2DateTime = function(ms, r) { + if(typeof(d3)==='undefined'){ + console.log('d3 is not defined'); + return; + } + + if(!r) r=0; + var d = new Date(ms), + s = d3.time.format('%Y-%m-%d')(d); + if(r<7776000000) { + // <90 days: add hours + s+=' '+lpad(d.getHours(),2); + if(r<432000000) { + // <5 days: add minutes + s+=':'+lpad(d.getMinutes(),2); + if(r<10800000) { + // <3 hours: add seconds + s+=':'+lpad(d.getSeconds(),2); + if(r<300000) { + // <5 minutes: add ms + s+='.'+lpad(d.getMilliseconds(),3); + } + } + } + // strip trailing zeros + return s.replace(/([:\s]00)*\.?[0]*$/,''); + } + return s; +}; + +/** + * parseDate: forgiving attempt to turn any date string + * into a javascript date object + * + * first collate all the date formats we want to support, precompiled + * to d3 format objects see below for the string cleaning that happens + * before this separate out 2-digit (y) and 4-digit-year (Y) formats, + * formats with month names (b), and formats with am/pm (I) or no time (D) + * (also includes hour only, as the test is really for a colon) so we can + * cut down the number of tests we need to run for any given string + * (right now all are between 15 and 32 tests) + */ + +// TODO: this is way out of date vs. the server-side version +var timeFormats = { + // 24 hour + H:['%H:%M:%S~%L', '%H:%M:%S', '%H:%M'], + // with am/pm + I:['%I:%M:%S~%L%p', '%I:%M:%S%p', '%I:%M%p'], + // no colon, ie only date or date with hour (could also support eg 12h34m?) + D:['%H', '%I%p', '%Hh'] +}; + +var dateFormats = { + Y:[ + '%Y~%m~%d', + '%Y%m%d', + '%y%m%d', // YYMMDD, has 6 digits together so will match Y, not y + '%m~%d~%Y', // MM/DD/YYYY has first precedence + '%d~%m~%Y' // then DD/MM/YYYY + ], + Yb:[ + '%b~%d~%Y', // eg nov 21 2013 + '%d~%b~%Y', // eg 21 nov 2013 + '%Y~%d~%b', // eg 2013 21 nov (or 2013 q3, after replacement) + '%Y~%b~%d' // eg 2013 nov 21 + ], + /** + * the two-digit year cases have so many potential ambiguities + * it's not even funny, but we'll try them anyway. + */ + y:[ + '%m~%d~%y', + '%d~%m~%y', + '%y~%m~%d' + ], + yb:[ + '%b~%d~%y', + '%d~%b~%y', + '%y~%d~%b', + '%y~%b~%d' + ] +}; + +// use utc formatter since we're ignoring timezone info +var formatter = d3.time.format.utc; + +/** + * ISO8601 and YYYYMMDDHHMMSS are the only ones where date and time + * are not separated by a space, so they get inserted specially here. + * Also a couple formats with no day (so time makes no sense) + */ +var dateTimeFormats = { + Y: { + H: ['%Y~%m~%dT%H:%M:%S', '%Y~%m~%dT%H:%M:%S~%L'].map(formatter), + I: [], + D: ['%Y%m%d%H%M%S', '%Y~%m', '%m~%Y'].map(formatter) + }, + Yb: {H: [], I: [], D: ['%Y~%b', '%b~%Y'].map(formatter)}, + y: {H: [], I: [], D: []}, + yb: {H: [], I: [], D: []} +}; +// all others get inserted in all possible combinations from dateFormats and timeFormats +['Y', 'Yb', 'y', 'yb'].forEach(function(dateType) { + dateFormats[dateType].forEach(function(dateFormat) { + // just a date (don't do just a time) + dateTimeFormats[dateType].D.push(formatter(dateFormat)); + ['H', 'I', 'D'].forEach(function(timeType) { + timeFormats[timeType].forEach(function(timeFormat) { + var a = dateTimeFormats[dateType][timeType]; + // 'date time', then 'time date' + a.push(formatter(dateFormat+'~'+timeFormat)); + a.push(formatter(timeFormat+'~'+dateFormat)); + }); + }); + }); +}); + +// precompiled regexps for performance +var matchword = /[a-z]*/g, + shortenword = function(m) { return m.substr(0,3); }, + weekdaymatch = /(mon|tue|wed|thu|fri|sat|sun|the|of|st|nd|rd|th)/g, + separatormatch = /[\s,\/\-\.\(\)]+/g, + ampmmatch = /~?([ap])~?m(~|$)/, + replaceampm = function(m,ap) { return ap+'m '; }, + match4Y = /\d\d\d\d/, + matchMonthName = /(^|~)[a-z]{3}/, + matchAMPM = /[ap]m/, + matchcolon = /:/, + matchquarter = /q([1-4])/, + quarters = ['31~mar','30~jun','30~sep','31~dec'], + replacequarter = function(m,n) { return quarters[n-1]; }, + matchTZ = / ?([+\-]\d\d:?\d\d|Z)$/; + +function getDateType(v) { + var dateType; + dateType = (match4Y.test(v) ? 'Y' : 'y'); + dateType = dateType + (matchMonthName.test(v) ? 'b' : ''); + return dateType; +} + +function getTimeType(v) { + var timeType; + timeType = matchcolon.test(v) ? (matchAMPM.test(v) ? 'I' : 'H') : 'D'; + return timeType; +} + +exports.parseDate = function(v) { + // is it already a date? just return it + if (v.getTime) return v; + /** + * otherwise, if it's not a string, return nothing + * the case of numbers that just have years will get + * dealt with elsewhere. + */ + if (typeof v !== 'string') return false; + + // first clean up the string a bit to reduce the number of formats we have to test + v = v.toLowerCase() + /** + * cut all words down to 3 characters - this will result in + * some spurious matches, ie whenever the first three characters + * of a word match a month or weekday but that seems more likely + * to fix typos than to make dates where they shouldn't be... + * and then we can omit the long form of months from our testing + */ + .replace(matchword, shortenword) + /** + * remove weekday names, as they get overridden anyway if they're + * inconsistent also removes a few more words + * (ie "tuesday the 26th of november") + * TODO: language support? + * for months too, but these seem to be built into d3 + */ + .replace(weekdaymatch, '') + /** + * collapse all separators one ~ at a time, except : which seems + * pretty consistent for the time part use ~ instead of space or + * something since d3 can eat a space as padding on 1-digit numbers + */ + .replace(separatormatch, '~') + // in case of a.m. or p.m. (also take off any space before am/pm) + .replace(ampmmatch, replaceampm) + // turn quarters Q1-4 into dates (quarter ends) + .replace(matchquarter, replacequarter) + .trim() + // also try to ignore timezone info, at least for now + .replace(matchTZ, ''); + + // now test against the various formats that might match + var out = null, + dateType = getDateType(v), + timeType = getTimeType(v), + formatList, + len; + + formatList = dateTimeFormats[dateType][timeType]; + len = formatList.length; + + for (var i = 0; i < len; i++) { + out = formatList[i].parse(v); + if (out) break; + } + + // If not an instance of Date at this point, just return it. + if (!(out instanceof Date)) return false; + // parse() method interprets arguments with local time zone. + var tzoff = out.getTimezoneOffset(); + // In general (default) this is not what we want, so force into UTC: + out.setTime(out.getTime() + tzoff * 60 * 1000); + return out; +}; diff --git a/src/lib/lib.js b/src/lib/lib.js index 09b7fd66b33..44ed10f12c7 100644 --- a/src/lib/lib.js +++ b/src/lib/lib.js @@ -1,661 +1,54 @@ 'use strict'; -var Plotly = require('../plotly'); var d3 = require('d3'); -var tinycolor = require('tinycolor2'); -var isNumeric = require('fast-isnumeric'); var lib = module.exports = {}; -/** - * dateTime2ms - turn a date object or string s of the form - * YYYY-mm-dd HH:MM:SS.sss into milliseconds (relative to 1970-01-01, - * per javascript standard) - * may truncate after any full field, and sss can be any length - * even >3 digits, though javascript dates truncate to milliseconds - * returns false if it doesn't find a date - * - * 2-digit to 4-digit year conversion, where to cut off? - * from http://support.microsoft.com/kb/244664: - * 1930-2029 (the most retro of all...) - * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')): - * 1950-2049 - * by Java, from http://stackoverflow.com/questions/2024273/: - * now-80 - now+20 - * or FileMaker Pro, from - * http://www.filemaker.com/12help/html/add_view_data.4.21.html: - * now-70 - now+30 - * but python strptime etc, via - * http://docs.python.org/py3k/library/time.html: - * 1969-2068 (super forward-looking, but static, not sliding!) - * - * lets go with now-70 to now+30, and if anyone runs into this problem - * they can learn the hard way not to use 2-digit years, as no choice we - * make now will cover all possibilities. mostly this will all be taken - * care of in initial parsing, should only be an issue for hand-entered data - * currently (2012) this range is: - * 1942-2041 - */ - -lib.dateTime2ms = function(s) { - // first check if s is a date object - try { - if (s.getTime) return +s; - } - catch(e) { - return false; - } - - var y, m, d, h; - // split date and time parts - var datetime = String(s).split(' '); - if (datetime.length > 2) return false; - - var p = datetime[0].split('-'); // date part - if (p.length > 3 || (p.length !== 3 && datetime[1])) return false; - - // year - if (p[0].length === 4) y = Number(p[0]); - else if (p[0].length === 2) { - var yNow = new Date().getFullYear(); - y = ((Number(p[0]) - yNow + 70)%100 + 200)%100 + yNow - 70; - } - else return false; - if (!isNumeric(y)) return false; - if (p.length === 1) return new Date(y,0,1).getTime(); // year only - - // month - m = Number(p[1]) - 1; // new Date() uses zero-based months - if (p[1].length > 2 || !(m >= 0 && m <= 11)) return false; - if (p.length === 2) return new Date(y, m, 1).getTime(); // year-month - - // day - d = Number(p[2]); - if (p[2].length > 2 || !(d >= 1 && d <= 31)) return false; - - // now save the date part - d = new Date(y, m, d).getTime(); - if (!datetime[1]) return d; // year-month-day - p = datetime[1].split(':'); - if (p.length > 3) return false; - - // hour - h = Number(p[0]); - if (p[0].length > 2 || !(h >= 0 && h <= 23)) return false; - d += 3600000*h; - if (p.length === 1) return d; - - // minute - m = Number(p[1]); - if (p[1].length > 2 || !(m >= 0 && m <= 59)) return false; - d += 60000*m; - if (p.length === 2) return d; - - // second - s = Number(p[2]); - if (!(s >= 0 && s < 60)) return false; - return d+s*1000; -}; - -// is string s a date? (see above) -lib.isDateTime = function(s) { - return (lib.dateTime2ms(s) !== false); -}; - -/** - * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss - * Crop any trailing zeros in time, but always leave full date - * (we could choose to crop '-01' from date too)... - * Optional range r is the data range that applies, also in ms. - * If rng is big, the later parts of time will be omitted - */ -lib.ms2DateTime = function(ms,r) { - if(typeof(d3)==='undefined'){ - console.log('d3 is not defined'); - return; - } - - if(!r) r=0; - var d = new Date(ms), - s = d3.time.format('%Y-%m-%d')(d); - if(r<7776000000) { - // <90 days: add hours - s+=' '+lib.lpad(d.getHours(),2); - if(r<432000000) { - // <5 days: add minutes - s+=':'+lib.lpad(d.getMinutes(),2); - if(r<10800000) { - // <3 hours: add seconds - s+=':'+lib.lpad(d.getSeconds(),2); - if(r<300000) { - // <5 minutes: add ms - s+='.'+lib.lpad(d.getMilliseconds(),3); - } - } - } - // strip trailing zeros - return s.replace(/([:\s]00)*\.?[0]*$/,''); - } - return s; -}; - -/** - * Plotly.Lib.parseDate: forgiving attempt to turn any date string - * into a javascript date object - * - * first collate all the date formats we want to support, precompiled - * to d3 format objects see below for the string cleaning that happens - * before this separate out 2-digit (y) and 4-digit-year (Y) formats, - * formats with month names (b), and formats with am/pm (I) or no time (D) - * (also includes hour only, as the test is really for a colon) so we can - * cut down the number of tests we need to run for any given string - * (right now all are between 15 and 32 tests) - */ - -// TODO: this is way out of date vs. the server-side version -var timeFormats = { - // 24 hour - H:['%H:%M:%S~%L', '%H:%M:%S', '%H:%M'], - // with am/pm - I:['%I:%M:%S~%L%p', '%I:%M:%S%p', '%I:%M%p'], - // no colon, ie only date or date with hour (could also support eg 12h34m?) - D:['%H', '%I%p', '%Hh'] -}; -var dateFormats = { - Y:[ - '%Y~%m~%d', - '%Y%m%d', - '%y%m%d', // YYMMDD, has 6 digits together so will match Y, not y - '%m~%d~%Y', // MM/DD/YYYY has first precedence - '%d~%m~%Y' // then DD/MM/YYYY - ], - Yb:[ - '%b~%d~%Y', // eg nov 21 2013 - '%d~%b~%Y', // eg 21 nov 2013 - '%Y~%d~%b', // eg 2013 21 nov (or 2013 q3, after replacement) - '%Y~%b~%d' // eg 2013 nov 21 - ], - /** - * the two-digit year cases have so many potential ambiguities - * it's not even funny, but we'll try them anyway. - */ - y:[ - '%m~%d~%y', - '%d~%m~%y', - '%y~%m~%d' - ], - yb:[ - '%b~%d~%y', - '%d~%b~%y', - '%y~%d~%b', - '%y~%b~%d' - ] -}; - -// use utc formatter since we're ignoring timezone info -var formatter = d3.time.format.utc; - -/** - * ISO8601 and YYYYMMDDHHMMSS are the only ones where date and time - * are not separated by a space, so they get inserted specially here. - * Also a couple formats with no day (so time makes no sense) - */ -var dateTimeFormats = { - Y: { - H: ['%Y~%m~%dT%H:%M:%S', '%Y~%m~%dT%H:%M:%S~%L'].map(formatter), - I: [], - D: ['%Y%m%d%H%M%S', '%Y~%m', '%m~%Y'].map(formatter) - }, - Yb: {H: [], I: [], D: ['%Y~%b', '%b~%Y'].map(formatter)}, - y: {H: [], I: [], D: []}, - yb: {H: [], I: [], D: []} -}; -// all others get inserted in all possible combinations from dateFormats and timeFormats -['Y', 'Yb', 'y', 'yb'].forEach(function(dateType) { - dateFormats[dateType].forEach(function(dateFormat) { - // just a date (don't do just a time) - dateTimeFormats[dateType].D.push(formatter(dateFormat)); - ['H', 'I', 'D'].forEach(function(timeType) { - timeFormats[timeType].forEach(function(timeFormat) { - var a = dateTimeFormats[dateType][timeType]; - // 'date time', then 'time date' - a.push(formatter(dateFormat+'~'+timeFormat)); - a.push(formatter(timeFormat+'~'+dateFormat)); - }); - }); - }); -}); - -// precompiled regexps for performance -var matchword = /[a-z]*/g, - shortenword = function(m) { return m.substr(0,3); }, - weekdaymatch = /(mon|tue|wed|thu|fri|sat|sun|the|of|st|nd|rd|th)/g, - separatormatch = /[\s,\/\-\.\(\)]+/g, - ampmmatch = /~?([ap])~?m(~|$)/, - replaceampm = function(m,ap) { return ap+'m '; }, - match4Y = /\d\d\d\d/, - matchMonthName = /(^|~)[a-z]{3}/, - matchAMPM = /[ap]m/, - matchcolon = /:/, - matchquarter = /q([1-4])/, - quarters = ['31~mar','30~jun','30~sep','31~dec'], - replacequarter = function(m,n) { return quarters[n-1]; }, - matchTZ = / ?([+\-]\d\d:?\d\d|Z)$/; - -function getDateType(v) { - var dateType; - dateType = (match4Y.test(v) ? 'Y' : 'y'); - dateType = dateType + (matchMonthName.test(v) ? 'b' : ''); - return dateType; -} - -function getTimeType(v) { - var timeType; - timeType = matchcolon.test(v) ? (matchAMPM.test(v) ? 'I' : 'H') : 'D'; - return timeType; -} - -lib.parseDate = function(v) { - // is it already a date? just return it - if (v.getTime) return v; - /** - * otherwise, if it's not a string, return nothing - * the case of numbers that just have years will get - * dealt with elsewhere. - */ - if (typeof v !== 'string') return false; - - // first clean up the string a bit to reduce the number of formats we have to test - v = v.toLowerCase() - /** - * cut all words down to 3 characters - this will result in - * some spurious matches, ie whenever the first three characters - * of a word match a month or weekday but that seems more likely - * to fix typos than to make dates where they shouldn't be... - * and then we can omit the long form of months from our testing - */ - .replace(matchword, shortenword) - /** - * remove weekday names, as they get overridden anyway if they're - * inconsistent also removes a few more words - * (ie "tuesday the 26th of november") - * TODO: language support? - * for months too, but these seem to be built into d3 - */ - .replace(weekdaymatch, '') - /** - * collapse all separators one ~ at a time, except : which seems - * pretty consistent for the time part use ~ instead of space or - * something since d3 can eat a space as padding on 1-digit numbers - */ - .replace(separatormatch, '~') - // in case of a.m. or p.m. (also take off any space before am/pm) - .replace(ampmmatch, replaceampm) - // turn quarters Q1-4 into dates (quarter ends) - .replace(matchquarter, replacequarter) - .trim() - // also try to ignore timezone info, at least for now - .replace(matchTZ, ''); - - // now test against the various formats that might match - var out = null, - dateType = getDateType(v), - timeType = getTimeType(v), - formatList, - len; - - formatList = dateTimeFormats[dateType][timeType]; - len = formatList.length; - - for (var i = 0; i < len; i++) { - out = formatList[i].parse(v); - if (out) break; - } - - // If not an instance of Date at this point, just return it. - if (!(out instanceof Date)) return false; - // parse() method interprets arguments with local time zone. - var tzoff = out.getTimezoneOffset(); - // In general (default) this is not what we want, so force into UTC: - out.setTime(out.getTime() + tzoff * 60 * 1000); - return out; -}; - -/** - * findBin - find the bin for val - note that it can return outside the - * bin range any pos. or neg. integer for linear bins, or -1 or - * bins.length-1 for explicit. - * bins is either an object {start,size,end} or an array length #bins+1 - * bins can be either increasing or decreasing but must be monotonic - * for linear bins, we can just calculate. For listed bins, run a binary - * search linelow (truthy) says the bin boundary should be attributed to - * the lower bin rather than the default upper bin - */ -lib.findBin = function(val, bins, linelow) { - if(isNumeric(bins.start)) { - return linelow ? - Math.ceil((val - bins.start) / bins.size) - 1 : - Math.floor((val - bins.start) / bins.size); - } - else { - var n1 = 0, - n2 = bins.length, - c = 0, - n, - test; - if(bins[bins.length - 1] > bins[0]) { - test = linelow ? lessThan : lessOrEqual; - } else { - test = linelow ? greaterOrEqual : greaterThan; - } - // c is just to avoid infinite loops if there's an error - while(n1 < n2 && c++ < 100){ - n = Math.floor((n1 + n2) / 2); - if(test(bins[n], val)) n1 = n + 1; - else n2 = n; - } - if(c > 90) console.log('Long binary search...'); - return n1 - 1; - } -}; - -function lessThan(a, b) { return a < b; } -function lessOrEqual(a, b) { return a <= b; } -function greaterThan(a, b) { return a > b; } -function greaterOrEqual(a, b) { return a >= b; } - -lib.sorterAsc = function(a, b) { return a - b; }; - -/** - * find distinct values in an array, lumping together ones that appear to - * just be off by a rounding error - * return the distinct values and the minimum difference between any two - */ -lib.distinctVals = function(valsIn) { - var vals = valsIn.slice(); // otherwise we sort the original array... - vals.sort(lib.sorterAsc); - - var l = vals.length - 1, - minDiff = (vals[l] - vals[0]) || 1, - errDiff = minDiff / (l || 1) / 10000, - v2 = [vals[0]]; - - for(var i = 0; i < l; i++) { - // make sure values aren't just off by a rounding error - if(vals[i + 1] > vals[i] + errDiff) { - minDiff = Math.min(minDiff, vals[i + 1] - vals[i]); - v2.push(vals[i + 1]); - } - } - - return {vals: v2, minDiff: minDiff}; -}; - -/** - * return the smallest element from (sorted) array arrayIn that's bigger than val, - * or (reverse) the largest element smaller than val - * used to find the best tick given the minimum (non-rounded) tick - * particularly useful for date/time where things are not powers of 10 - * binary search is probably overkill here... - */ -lib.roundUp = function(val, arrayIn, reverse){ - var low = 0, - high = arrayIn.length - 1, - mid, - c = 0, - dlow = reverse ? 0 : 1, - dhigh = reverse ? 1 : 0, - rounded = reverse ? Math.ceil : Math.floor; - // c is just to avoid infinite loops if there's an error - while(low < high && c++ < 100){ - mid = rounded((low + high) / 2); - if(arrayIn[mid] <= val) low = mid + dlow; - else high = mid - dhigh; - } - return arrayIn[low]; -}; - -/** - * convert a string s (such as 'xaxis.range[0]') - * representing a property of nested object into set and get methods - * also return the string and object so we don't have to keep track of them - * allows [-1] for an array index, to set a property inside all elements - * of an array - * eg if obj = {arr: [{a: 1}, {a: 2}]} - * you can do p = nestedProperty(obj, 'arr[-1].a') - * but you cannot set the array itself this way, to do that - * just set the whole array. - * eg if obj = {arr: [1, 2, 3]} - * you can't do nestedProperty(obj, 'arr[-1]').set(5) - * but you can do nestedProperty(obj, 'arr').set([5, 5, 5]) - */ -lib.nestedProperty = function(container, propStr) { - if(isNumeric(propStr)) propStr = String(propStr); - else if(typeof propStr !== 'string' || - propStr.substr(propStr.length - 4) === '[-1]') { - throw 'bad property string'; - } - - var j = 0, - propParts = propStr.split('.'), - indexed, - indices, - i; - - // check for parts of the nesting hierarchy that are numbers (ie array elements) - while(j < propParts.length) { - // look for non-bracket chars, then any number of [##] blocks - indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/); - if(indexed) { - if(indexed[1]) propParts[j] = indexed[1]; - // allow propStr to start with bracketed array indices - else if(j === 0) propParts.splice(0,1); - else throw 'bad property string'; - - indices = indexed[2] - .substr(1,indexed[2].length-2) - .split(']['); - - for(i=0; i= 0; i--) { - curCont = containerLevels[i]; - remainingKeys = false; - if(Array.isArray(curCont)) { - for(j = curCont.length - 1; j >= 0; j--) { - if(emptyObj(curCont[j])) { - if(remainingKeys) curCont[j] = undefined; - else curCont.pop(); - } - else remainingKeys = true; - } - } - else if(typeof curCont === 'object' && curCont !== null) { - keys = Object.keys(curCont); - remainingKeys = false; - for(j = keys.length - 1; j >= 0; j--) { - if(emptyObj(curCont[keys[j]]) && !isDataArray(curCont[keys[j]], keys[j])) delete curCont[keys[j]]; - else remainingKeys = true; - } - } - if(remainingKeys) return; - } -} +var coerceModule = require('./coerce'); +lib.valObjects = coerceModule.valObjects; +lib.coerce = coerceModule.coerce; +lib.coerce2 = coerceModule.coerce2; +lib.coerceFont = coerceModule.coerceFont; + +var datesModule = require('./dates'); +lib.dateTime2ms = datesModule.dateTime2ms; +lib.isDateTime = datesModule.isDateTime; +lib.ms2DateTime = datesModule.ms2DateTime; +lib.parseDate = datesModule.parseDate; + +var searchModule = require('./search'); +lib.findBin = searchModule.findBin; +lib.sorterAsc = searchModule.sorterAsc; +lib.distinctVals = searchModule.distinctVals; +lib.roundUp = searchModule.roundUp; + +var statsModule = require('./stats'); +lib.aggNums = statsModule.aggNums; +lib.len = statsModule.len; +lib.mean = statsModule.mean; +lib.variance = statsModule.variance; +lib.stdev = statsModule.stdev; +lib.interp = statsModule.interp; + +var matrixModule = require('./matrix'); +lib.init2dArray = matrixModule.init2dArray; +lib.transposeRagged = matrixModule.transposeRagged; +lib.dot = matrixModule.dot; +lib.translationMatrix = matrixModule.translationMatrix; +lib.rotationMatrix = matrixModule.rotationMatrix; +lib.rotationXYMatrix = matrixModule.rotationXYMatrix; +lib.apply2DTransform = matrixModule.apply2DTransform; +lib.apply2DTransform2 = matrixModule.apply2DTransform2; -function emptyObj(obj) { - if(obj===undefined || obj===null) return true; - if(typeof obj !== 'object') return false; // any plain value - if(Array.isArray(obj)) return !obj.length; // [] - return !Object.keys(obj).length; // {} -} +var extendModule = require('./extend'); +lib.extendFlat = extendModule.extendFlat; +lib.extendDeep = extendModule.extendDeep; +lib.extendDeepAll = extendModule.extendDeepAll; -function badContainer(container, propStr, propParts) { - return { - set: function() { throw 'bad container'; }, - get: function() {}, - astr: propStr, - parts: propParts, - obj: container - }; -} +lib.notifier = require('./notifier'); /** * swap x and y of the same attribute in container cont @@ -689,94 +82,6 @@ lib.pauseEvent = function(e){ return false; }; -// pad a number with zeroes, to given # of digits before the decimal point -lib.lpad = function(val, digits){ - return String(val + Math.pow(10, digits)).substr(1); -}; - -// STATISTICS FUNCTIONS - -/** - * aggNums() returns the result of an aggregate function applied to an array of - * values, where non-numerical values have been tossed out. - * - * @param {function} f - aggregation function (e.g., Math.min) - * @param {Number} v - initial value (continuing from previous calls) - * if there's no continuing value, use null for selector-type - * functions (max,min), or 0 for summations - * @param {Array} a - array to aggregate (may be nested, we will recurse, - * but all elements must have the same dimension) - * @param {Number} len - maximum length of a to aggregate - * @return {Number} - result of f applied to a starting from v - */ -lib.aggNums = function(f, v, a, len) { - var i, - b; - if (!len) len = a.length; - if (!isNumeric(v)) v = false; - if (Array.isArray(a[0])) { - b = new Array(len); - for(i = 0; i < len; i++) b[i] = lib.aggNums(f, v, a[i]); - a = b; - } - - for (i = 0; i < len; i++) { - if (!isNumeric(v)) v = a[i]; - else if (isNumeric(a[i])) v = f(+v, +a[i]); - } - return v; -}; - -/** - * mean & std dev functions using aggNums, so it handles non-numerics nicely - * even need to use aggNums instead of .length, to toss out non-numerics - */ -lib.len = function(data) { - return lib.aggNums(function(a){ return a + 1; }, 0, data); -}; - -lib.mean = function(data, len) { - if(!len) len = lib.len(data); - return lib.aggNums(function(a, b){ return a + b; }, 0, data) / len; -}; - -lib.variance = function(data, len, mean) { - if (!len) len = lib.len(data); - if (!isNumeric(mean)) mean = lib.mean(data, len); - - return lib.aggNums(function(a, b) { - return a + Math.pow(b - mean, 2); - }, 0, data)/len; -}; - -lib.stdev = function(data, len, mean) { - return Math.sqrt(lib.variance(data, len, mean)); -}; - -/** - * interp() computes a percentile (quantile) for a given distribution. - * We interpolate the distribution (to compute quantiles, we follow method #10 here: - * http://www.amstat.org/publications/jse/v14n3/langford.html). - * Typically the index or rank (n * arr.length) may be non-integer. - * For reference: ends are clipped to the extreme values in the array; - * For box plots: index you get is half a point too high (see - * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition - * indexes from 1 rather than 0, so we subtract 1/2 (instead of add). - * - * @param {Array} arr - This array contains the values that make up the distribution. - * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile. - * For example, the 50th percentile (or median) corresponds to n = 0.5 - * @return {Number} - percentile - */ -lib.interp = function(arr, n) { - if (!isNumeric(n)) throw 'n should be a finite number'; - n = n * arr.length - 0.5; - if (n < 0) return arr[0]; - if (n > arr.length - 1) return arr[arr.length - 1]; - var frac = n % 1; - return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)]; -}; - /** * ------------------------------------------ * debugging tools @@ -812,68 +117,6 @@ lib.constrain = function(v, v0, v1) { return Math.max(v0, Math.min(v1, v)); }; -/** - * notifier - * @param {String} text The person's user name - * @param {Number} [delay=1000] The delay time in milliseconds - * or 'long' which provides 2000 ms delay time. - * @return {undefined} this function does not return a value - */ -var NOTEDATA = []; -lib.notifier = function(text, displayLength) { - - if(NOTEDATA.indexOf(text) !== -1) return; - - NOTEDATA.push(text); - - var ts = 1000; - if (isNumeric(displayLength)) ts = displayLength; - else if (displayLength === 'long') ts = 3000; - - var notifierContainer = d3.select('body') - .selectAll('.plotly-notifier') - .data([0]); - notifierContainer.enter() - .append('div') - .classed('plotly-notifier', true); - - var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA); - - function killNote(transition) { - transition - .duration(700) - .style('opacity', 0) - .each('end', function(thisText) { - var thisIndex = NOTEDATA.indexOf(thisText); - if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1); - d3.select(this).remove(); - }); - } - - notes.enter().append('div') - .classed('notifier-note', true) - .style('opacity', 0) - .each(function(thisText) { - var note = d3.select(this); - - note.append('button') - .classed('notifier-close', true) - .html('×') - .on('click', function() { - note.transition().call(killNote); - }); - - note.append('p').html(thisText); - - note.transition() - .duration(700) - .style('opacity', 1) - .transition() - .delay(ts) - .call(killNote); - }); -}; - /** * do two bounding boxes from getBoundingClientRect, * ie {left,right,top,bottom,width,height}, overlap? @@ -931,7 +174,6 @@ lib.randstr = function randstr(existing, bits, base) { else return res; }; - lib.OptionControl = function(opt, optname) { /* * An environment to contain all option setters and @@ -1047,105 +289,6 @@ lib.syncOrAsync = function(sequence, arg, finalStep) { return finalStep && finalStep(arg); }; -lib.init2dArray = function(rowLength, colLength) { - var array = new Array(rowLength); - for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength); - return array; -}; - -/** - * transpose a (possibly ragged) 2d array z. inspired by - * http://stackoverflow.com/questions/17428587/ - * transposing-a-2d-array-in-javascript - */ -lib.transposeRagged = function(z) { - var maxlen = 0, - zlen = z.length, - i, - j; - // Maximum row length: - for (i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length); - - var t = new Array(maxlen); - for (i = 0; i < maxlen; i++) { - t[i] = new Array(zlen); - for (j = 0; j < zlen; j++) t[i][j] = z[j][i]; - } - - return t; -}; - -// our own dot function so that we don't need to include numeric -lib.dot = function(x, y) { - if (!(x.length && y.length) || x.length !== y.length) return null; - - var len = x.length, - out, - i; - - if(x[0].length) { - // mat-vec or mat-mat - out = new Array(len); - for(i = 0; i < len; i++) out[i] = lib.dot(x[i], y); - } - else if(y[0].length) { - // vec-mat - var yTranspose = lib.transposeRagged(y); - out = new Array(yTranspose.length); - for(i = 0; i < yTranspose.length; i++) out[i] = lib.dot(x, yTranspose[i]); - } - else { - // vec-vec - out = 0; - for(i = 0; i < len; i++) out += x[i] * y[i]; - } - - return out; -}; - - -// Functions to manipulate 2D transformation matrices - -// translate by (x,y) -lib.translationMatrix = function (x, y) { - return [[1, 0, x], [0, 1, y], [0, 0, 1]]; -}; - -// rotate by alpha around (0,0) -lib.rotationMatrix = function (alpha) { - var a = alpha*Math.PI/180; - return [[Math.cos(a), -Math.sin(a), 0], - [Math.sin(a), Math.cos(a), 0], - [0, 0, 1]]; -}; - -// rotate by alpha around (x,y) -lib.rotationXYMatrix = function(a, x, y) { - return lib.dot( - lib.dot(lib.translationMatrix(x, y), - lib.rotationMatrix(a)), - lib.translationMatrix(-x, -y)); -}; - -// applies a 2D transformation matrix to either x and y params or an [x,y] array -lib.apply2DTransform = function(transform) { - return function() { - var args = arguments; - if (args.length === 3) { - args = args[0]; - }//from map - var xy = arguments.length === 1 ? args[0] : [args[0], args[1]]; - return lib.dot(transform, [xy[0], xy[1], 1]).slice(0, 2); - }; -}; - -// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment) -lib.apply2DTransform2 = function(transform) { - var at = lib.apply2DTransform(transform); - return function(xys) { - return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); - }; -}; /** * Helper to strip trailing slash, from @@ -1156,324 +299,6 @@ lib.stripTrailingSlash = function (str) { return str; }; -var colorscaleNames = Object.keys(require('../components/colorscale/scales')); - -lib.valObjects = { - data_array: { - // You can use *dflt=[] to force said array to exist though. - description: [ - 'An {array} of data.', - 'The value MUST be an {array}, or we ignore it.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(Array.isArray(v)) propOut.set(v); - else if(dflt!==undefined) propOut.set(dflt); - } - }, - enumerated: { - description: [ - 'Enumerated value type. The available values are listed', - 'in `values`.' - ].join(' '), - requiredOpts: ['values'], - otherOpts: ['dflt', 'coerceNumber', 'arrayOk'], - coerceFunction: function(v, propOut, dflt, opts) { - if(opts.coerceNumber) v = +v; - if(opts.values.indexOf(v)===-1) propOut.set(dflt); - else propOut.set(v); - } - }, - 'boolean': { - description: 'A boolean (true/false) value.', - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(v===true || v===false) propOut.set(v); - else propOut.set(dflt); - } - }, - number: { - description: [ - 'A number or a numeric value', - '(e.g. a number inside a string).', - 'When applicable, values greater (less) than `max` (`min`)', - 'are coerced to the `dflt`.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt', 'min', 'max', 'arrayOk'], - coerceFunction: function(v, propOut, dflt, opts) { - if(!isNumeric(v) || - (opts.min!==undefined && vopts.max)) { - propOut.set(dflt); - } - else propOut.set(+v); - } - }, - integer: { - description: [ - 'An integer or an integer inside a string.', - 'When applicable, values greater (less) than `max` (`min`)', - 'are coerced to the `dflt`.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt', 'min', 'max'], - coerceFunction: function(v, propOut, dflt, opts) { - if(v%1 || !isNumeric(v) || - (opts.min!==undefined && vopts.max)) { - propOut.set(dflt); - } - else propOut.set(+v); - } - }, - string: { - description: [ - 'A string value.', - 'Numbers are converted to strings except for attributes with', - '`strict` set to true.' - ].join(' '), - requiredOpts: [], - // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter) - otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'], - coerceFunction: function(v, propOut, dflt, opts) { - if(opts.strict===true && typeof v !== 'string') { - propOut.set(dflt); - return; - } - - var s = String(v); - if(v===undefined || (opts.noBlank===true && !s)) { - propOut.set(dflt); - } - else propOut.set(s); - } - }, - color: { - description: [ - 'A string describing color.', - 'Supported formats:', - '- hex (e.g. \'#d3d3d3\')', - '- rgb (e.g. \'rgb(255, 0, 0)\')', - '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')', - '- hsl (e.g. \'hsl(0, 100%, 50%)\')', - '- hsv (e.g. \'hsv(0, 100%, 100%)\')', - '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt', 'arrayOk'], - coerceFunction: function(v, propOut, dflt) { - if(tinycolor(v).isValid()) propOut.set(v); - else propOut.set(dflt); - } - }, - colorscale: { - description: [ - 'A Plotly colorscale either picked by a name:', - '(any of', colorscaleNames.join(', '), ')', - 'customized as an {array} of 2-element {arrays} where', - 'the first element is the normalized color level value', - '(starting at *0* and ending at *1*),', - 'and the second item is a valid color string.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - propOut.set(Plotly.Colorscale.getScale(v, dflt)); - } - }, - angle: { - description: [ - 'A number (in degree) between -180 and 180.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(v==='auto') propOut.set('auto'); - else if(!isNumeric(v)) propOut.set(dflt); - else { - if(Math.abs(v)>180) v -= Math.round(v/360)*360; - propOut.set(+v); - } - } - }, - axisid: { - description: [ - 'An axis id string (e.g. \'x\', \'x2\', \'x3\', ...).' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.charAt(0)===dflt) { - var axnum = Number(v.substr(1)); - if(axnum%1 === 0 && axnum>1) { - propOut.set(v); - return; - } - } - propOut.set(dflt); - } - }, - sceneid: { - description: [ - 'A scene id string (e.g. \'scene\', \'scene2\', \'scene3\', ...).' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.substr(0,5)===dflt) { - var scenenum = Number(v.substr(5)); - if(scenenum%1 === 0 && scenenum>1) { - propOut.set(v); - return; - } - } - propOut.set(dflt); - } - }, - geoid: { - description: [ - 'A geo id string (e.g. \'geo\', \'geo2\', \'geo3\', ...).' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.substr(0,3)===dflt) { - var geonum = Number(v.substr(3)); - if(geonum%1 === 0 && geonum>1) { - propOut.set(v); - return; - } - } - propOut.set(dflt); - } - }, - flaglist: { - description: [ - 'A string representing a combination of flags', - '(order does not matter here).', - 'Combine any of the available `flags` with *+*.', - '(e.g. (\'lines+markers\')).', - 'Values in `extras` cannot be combined.' - ].join(' '), - requiredOpts: ['flags'], - otherOpts: ['dflt', 'extras'], - coerceFunction: function(v, propOut, dflt, opts) { - if(typeof v !== 'string') { - propOut.set(dflt); - return; - } - if(opts.extras.indexOf(v)!==-1) { - propOut.set(v); - return; - } - var vParts = v.split('+'), - i = 0; - while(i= 0; i--) { + curCont = containerLevels[i]; + remainingKeys = false; + if(Array.isArray(curCont)) { + for(j = curCont.length - 1; j >= 0; j--) { + if(emptyObj(curCont[j])) { + if(remainingKeys) curCont[j] = undefined; + else curCont.pop(); + } + else remainingKeys = true; + } + } + else if(typeof curCont === 'object' && curCont !== null) { + keys = Object.keys(curCont); + remainingKeys = false; + for(j = keys.length - 1; j >= 0; j--) { + if(emptyObj(curCont[keys[j]]) && !isDataArray(curCont[keys[j]], keys[j])) delete curCont[keys[j]]; + else remainingKeys = true; + } + } + if(remainingKeys) return; + } +} + +function emptyObj(obj) { + if(obj===undefined || obj===null) return true; + if(typeof obj !== 'object') return false; // any plain value + if(Array.isArray(obj)) return !obj.length; // [] + return !Object.keys(obj).length; // {} +} + +function badContainer(container, propStr, propParts) { + return { + set: function() { throw 'bad container'; }, + get: function() {}, + astr: propStr, + parts: propParts, + obj: container + }; +} diff --git a/src/lib/notifier.js b/src/lib/notifier.js new file mode 100644 index 00000000000..a1742717f80 --- /dev/null +++ b/src/lib/notifier.js @@ -0,0 +1,66 @@ +'use strict'; + +var d3 = require('d3'); +var isNumeric = require('fast-isnumeric'); + +var NOTEDATA = []; + +/** + * notifier + * @param {String} text The person's user name + * @param {Number} [delay=1000] The delay time in milliseconds + * or 'long' which provides 2000 ms delay time. + * @return {undefined} this function does not return a value + */ +module.exports = function(text, displayLength) { + if(NOTEDATA.indexOf(text) !== -1) return; + + NOTEDATA.push(text); + + var ts = 1000; + if (isNumeric(displayLength)) ts = displayLength; + else if (displayLength === 'long') ts = 3000; + + var notifierContainer = d3.select('body') + .selectAll('.plotly-notifier') + .data([0]); + notifierContainer.enter() + .append('div') + .classed('plotly-notifier', true); + + var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA); + + function killNote(transition) { + transition + .duration(700) + .style('opacity', 0) + .each('end', function(thisText) { + var thisIndex = NOTEDATA.indexOf(thisText); + if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1); + d3.select(this).remove(); + }); + } + + notes.enter().append('div') + .classed('notifier-note', true) + .style('opacity', 0) + .each(function(thisText) { + var note = d3.select(this); + + note.append('button') + .classed('notifier-close', true) + .html('×') + .on('click', function() { + note.transition().call(killNote); + }); + + note.append('p').html(thisText); + + note.transition() + .duration(700) + .style('opacity', 1) + .transition() + .delay(ts) + .call(killNote); + }); +}; diff --git a/src/lib/search.js b/src/lib/search.js new file mode 100644 index 00000000000..45297c5d394 --- /dev/null +++ b/src/lib/search.js @@ -0,0 +1,98 @@ +'use strict'; + +var isNumeric = require('fast-isnumeric'); + + +/** + * findBin - find the bin for val - note that it can return outside the + * bin range any pos. or neg. integer for linear bins, or -1 or + * bins.length-1 for explicit. + * bins is either an object {start,size,end} or an array length #bins+1 + * bins can be either increasing or decreasing but must be monotonic + * for linear bins, we can just calculate. For listed bins, run a binary + * search linelow (truthy) says the bin boundary should be attributed to + * the lower bin rather than the default upper bin + */ +exports.findBin = function(val, bins, linelow) { + if(isNumeric(bins.start)) { + return linelow ? + Math.ceil((val - bins.start) / bins.size) - 1 : + Math.floor((val - bins.start) / bins.size); + } + else { + var n1 = 0, + n2 = bins.length, + c = 0, + n, + test; + if(bins[bins.length - 1] > bins[0]) { + test = linelow ? lessThan : lessOrEqual; + } else { + test = linelow ? greaterOrEqual : greaterThan; + } + // c is just to avoid infinite loops if there's an error + while(n1 < n2 && c++ < 100){ + n = Math.floor((n1 + n2) / 2); + if(test(bins[n], val)) n1 = n + 1; + else n2 = n; + } + if(c > 90) console.log('Long binary search...'); + return n1 - 1; + } +}; + +function lessThan(a, b) { return a < b; } +function lessOrEqual(a, b) { return a <= b; } +function greaterThan(a, b) { return a > b; } +function greaterOrEqual(a, b) { return a >= b; } + +exports.sorterAsc = function(a, b) { return a - b; }; + +/** + * find distinct values in an array, lumping together ones that appear to + * just be off by a rounding error + * return the distinct values and the minimum difference between any two + */ +exports.distinctVals = function(valsIn) { + var vals = valsIn.slice(); // otherwise we sort the original array... + vals.sort(exports.sorterAsc); + + var l = vals.length - 1, + minDiff = (vals[l] - vals[0]) || 1, + errDiff = minDiff / (l || 1) / 10000, + v2 = [vals[0]]; + + for(var i = 0; i < l; i++) { + // make sure values aren't just off by a rounding error + if(vals[i + 1] > vals[i] + errDiff) { + minDiff = Math.min(minDiff, vals[i + 1] - vals[i]); + v2.push(vals[i + 1]); + } + } + + return {vals: v2, minDiff: minDiff}; +}; + +/** + * return the smallest element from (sorted) array arrayIn that's bigger than val, + * or (reverse) the largest element smaller than val + * used to find the best tick given the minimum (non-rounded) tick + * particularly useful for date/time where things are not powers of 10 + * binary search is probably overkill here... + */ +exports.roundUp = function(val, arrayIn, reverse){ + var low = 0, + high = arrayIn.length - 1, + mid, + c = 0, + dlow = reverse ? 0 : 1, + dhigh = reverse ? 1 : 0, + rounded = reverse ? Math.ceil : Math.floor; + // c is just to avoid infinite loops if there's an error + while(low < high && c++ < 100){ + mid = rounded((low + high) / 2); + if(arrayIn[mid] <= val) low = mid + dlow; + else high = mid - dhigh; + } + return arrayIn[low]; +}; diff --git a/src/lib/stats.js b/src/lib/stats.js new file mode 100644 index 00000000000..962f9d8c569 --- /dev/null +++ b/src/lib/stats.js @@ -0,0 +1,85 @@ +'use strict'; + +var isNumeric = require('fast-isnumeric'); + + +/** + * aggNums() returns the result of an aggregate function applied to an array of + * values, where non-numerical values have been tossed out. + * + * @param {function} f - aggregation function (e.g., Math.min) + * @param {Number} v - initial value (continuing from previous calls) + * if there's no continuing value, use null for selector-type + * functions (max,min), or 0 for summations + * @param {Array} a - array to aggregate (may be nested, we will recurse, + * but all elements must have the same dimension) + * @param {Number} len - maximum length of a to aggregate + * @return {Number} - result of f applied to a starting from v + */ +exports.aggNums = function(f, v, a, len) { + var i, + b; + if (!len) len = a.length; + if (!isNumeric(v)) v = false; + if (Array.isArray(a[0])) { + b = new Array(len); + for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]); + a = b; + } + + for (i = 0; i < len; i++) { + if (!isNumeric(v)) v = a[i]; + else if (isNumeric(a[i])) v = f(+v, +a[i]); + } + return v; +}; + +/** + * mean & std dev functions using aggNums, so it handles non-numerics nicely + * even need to use aggNums instead of .length, to toss out non-numerics + */ +exports.len = function(data) { + return exports.aggNums(function(a){ return a + 1; }, 0, data); +}; + +exports.mean = function(data, len) { + if(!len) len = exports.len(data); + return exports.aggNums(function(a, b){ return a + b; }, 0, data) / len; +}; + +exports.variance = function(data, len, mean) { + if (!len) len = exports.len(data); + if (!isNumeric(mean)) mean = exports.mean(data, len); + + return exports.aggNums(function(a, b) { + return a + Math.pow(b - mean, 2); + }, 0, data)/len; +}; + +exports.stdev = function(data, len, mean) { + return Math.sqrt(exports.variance(data, len, mean)); +}; + +/** + * interp() computes a percentile (quantile) for a given distribution. + * We interpolate the distribution (to compute quantiles, we follow method #10 here: + * http://www.amstat.org/publications/jse/v14n3/langford.html). + * Typically the index or rank (n * arr.length) may be non-integer. + * For reference: ends are clipped to the extreme values in the array; + * For box plots: index you get is half a point too high (see + * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition + * indexes from 1 rather than 0, so we subtract 1/2 (instead of add). + * + * @param {Array} arr - This array contains the values that make up the distribution. + * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile. + * For example, the 50th percentile (or median) corresponds to n = 0.5 + * @return {Number} - percentile + */ +exports.interp = function(arr, n) { + if (!isNumeric(n)) throw 'n should be a finite number'; + n = n * arr.length - 0.5; + if (n < 0) return arr[0]; + if (n > arr.length - 1) return arr[arr.length - 1]; + var frac = n % 1; + return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)]; +}; From 4181e9788a346c22b3f8ed00df3b51353c68801a Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:30:54 -0500 Subject: [PATCH 11/38] split color attributes into attribute.js file --- src/components/color/attributes.js | 19 +++++++++++++++++++ src/components/color/color.js | 22 +++++----------------- 2 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 src/components/color/attributes.js diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js new file mode 100644 index 00000000000..dac67382220 --- /dev/null +++ b/src/components/color/attributes.js @@ -0,0 +1,19 @@ +// IMPORTANT - default colors should be in hex for compatibility +exports.defaults = [ + '#1f77b4', // muted blue + '#ff7f0e', // safety orange + '#2ca02c', // cooked asparagus green + '#d62728', // brick red + '#9467bd', // muted purple + '#8c564b', // chestnut brown + '#e377c2', // raspberry yogurt pink + '#7f7f7f', // middle gray + '#bcbd22', // curry yellow-green + '#17becf' // blue-teal +]; + +exports.defaultLine = '#444'; + +exports.lightLine = '#eee'; + +exports.background = '#fff'; diff --git a/src/components/color/color.js b/src/components/color/color.js index 4e7780b6629..2f4dfac805d 100644 --- a/src/components/color/color.js +++ b/src/components/color/color.js @@ -5,23 +5,11 @@ var isNumeric = require('fast-isnumeric'); var color = module.exports = {}; -// IMPORTANT - default colors should be in hex for grid.js -color.defaults = [ - '#1f77b4', // muted blue - '#ff7f0e', // safety orange - '#2ca02c', // cooked asparagus green - '#d62728', // brick red - '#9467bd', // muted purple - '#8c564b', // chestnut brown - '#e377c2', // raspberry yogurt pink - '#7f7f7f', // middle gray - '#bcbd22', // curry yellow-green - '#17becf' // blue-teal -]; - -color.defaultLine = '#444'; -color.lightLine = '#eee'; -color.background = '#fff'; +var colorAttrs = require('./attributes'); +color.defaults = colorAttrs.defaults; +color.defaultLine = colorAttrs.defaultLine; +color.lightLine = colorAttrs.lightLine; +color.background = colorAttrs.background; color.tinyRGB = function(tc) { var c = tc.toRgb(); From f842118bdcff4e7cc8597a6b2d4ee717e0c38a9f Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:33:40 -0500 Subject: [PATCH 12/38] make attributes files require in attributes file directly: - do not require in Plotly when not necessary --- src/components/annotations/attributes.js | 8 ++-- src/components/colorbar/attributes.js | 11 ++--- src/components/legend/attributes.js | 11 +++-- src/components/shapes/attributes.js | 11 ++--- src/lib/gl_format_color.js | 4 +- src/plots/plots/attributes.js | 5 +-- src/plots/plots/layout_attributes.js | 20 +++++---- src/plots/plots/plots.js | 1 + src/polar/utils/undo_manager.js | 56 ------------------------ src/traces/bars/attributes.js | 6 +-- src/traces/boxes/attributes.js | 10 ++--- src/traces/choropleth/attributes.js | 11 ++--- src/traces/contour/attributes.js | 6 +-- src/traces/heatmap/attributes.js | 10 ++--- src/traces/histogram/attributes.js | 6 +-- src/traces/mesh3d/attributes.js | 4 +- src/traces/pie/attributes.js | 17 ++++--- src/traces/scatter/attributes.js | 6 +-- src/traces/scatter3d/attributes.js | 9 ++-- src/traces/scattergeo/attributes.js | 10 ++--- src/traces/scattergl/attributes.js | 10 ++--- src/traces/surface/attributes.js | 6 +-- 22 files changed, 82 insertions(+), 156 deletions(-) delete mode 100644 src/polar/utils/undo_manager.js diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 333c9053f3c..f3eb81d802d 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -1,9 +1,7 @@ -'use strict'; - var Plotly = require('../../plotly'); var ARROWPATHS = require('./arrow_paths'); - -var extendFlat = Plotly.Lib.extendFlat; +var fontAttrs = require('../../plots/plots/font_attributes'); +var extendFlat = require('../../lib/extend').extendFlat; module.exports = { _isLinkedToArray: true, @@ -28,7 +26,7 @@ module.exports = { 'with respect to the horizontal.' ].join(' ') }, - font: extendFlat({}, Plotly.Plots.fontAttrs, { + font: extendFlat({}, fontAttrs, { description: 'Sets the annotation text font.' }), opacity: { diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index e0841c357ea..c2d2e38593b 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -1,9 +1,6 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var axesAttrs = Plotly.Axes.layoutAttributes; -var extendFlat = Plotly.Lib.extendFlat; +var axesAttrs = require('../../plots/cartesian/attributes'); +var fontAttrs = require('../../plots/plots/font_attributes'); +var extendFlat = require('../../lib/extend').extendFlat; module.exports = { // TODO: only right is supported currently @@ -167,7 +164,7 @@ module.exports = { dflt: 'Click to enter colorscale title', description: 'Sets the title of the color bar.' }, - titlefont: extendFlat({}, Plotly.Plots.fontAttrs, { + titlefont: extendFlat({}, fontAttrs, { description: [ 'Sets this color bar\'s title font.' ].join(' ') diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index e5ac2d36d04..68ab8de8b8f 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -1,7 +1,6 @@ -'use strict'; - -var Plotly = require('../../plotly'); -var extendFlat = Plotly.Lib.extendFlat; +var fontAttrs = require('../../plots/plots/font_attributes'); +var colorAttrs = require('../color/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; module.exports = { @@ -12,7 +11,7 @@ module.exports = { }, bordercolor: { valType: 'color', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, role: 'style', description: 'Sets the color of the border enclosing the legend.' }, @@ -23,7 +22,7 @@ module.exports = { role: 'style', description: 'Sets the width (in px) of the border enclosing the legend.' }, - font: extendFlat({}, Plotly.Plots.fontAttrs, { + font: extendFlat({}, fontAttrs, { description: 'Sets the font used to text the legend items.' }), traceorder: { diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index c064e229b14..f19c3384d17 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -1,7 +1,8 @@ -var Plotly = require('../../plotly'); +var annAttrs = require('../annotations/attributes'); +var scatterAttrs = require('../../traces/scatter/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var scatterLineAttrs = Plotly.Scatter.attributes.line; -var extendFlat = Plotly.Lib.extendFlat; +var scatterLineAttrs = scatterAttrs.line; module.exports = { _isLinkedToArray: true, @@ -27,7 +28,7 @@ module.exports = { ].join(' ') }, - xref: extendFlat({}, Plotly.Annotations.layoutAttributes.xref, { + xref: extendFlat({}, annAttrs.xref, { description: [ 'Sets the shape\'s x coordinate axis.', 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', @@ -54,7 +55,7 @@ module.exports = { ].join(' ') }, - yref: extendFlat({}, Plotly.Annotations.layoutAttributes.yref, { + yref: extendFlat({}, annAttrs.yref, { description: [ 'Sets the annotation\'s y coordinate axis.', 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js index bd2b5271810..18ff6897292 100644 --- a/src/lib/gl_format_color.js +++ b/src/lib/gl_format_color.js @@ -5,8 +5,8 @@ var tinycolor = require('tinycolor2'); var isNumeric = require('fast-isnumeric'); var str2RgbaArray = require('./str2rgbarray'); -var colorDflt = Plotly.Color.defaultLine, - opacityDflt = 1; +var colorDflt = require('../components/color/attributes').defaultLine; +var opacityDflt = 1; function calculateColor(colorIn, opacityIn) { var colorOut = str2RgbaArray(colorIn); diff --git a/src/plots/plots/attributes.js b/src/plots/plots/attributes.js index a2401832ab7..cc17f271eb0 100644 --- a/src/plots/plots/attributes.js +++ b/src/plots/plots/attributes.js @@ -1,11 +1,8 @@ -var Plotly = require('../../plotly'); -var plots = require('./plots'); - module.exports = { type: { valType: 'enumerated', role: 'info', - values: plots.allTypes, + values: [], // listed dynamically dflt: 'scatter' }, visible: { diff --git a/src/plots/plots/layout_attributes.js b/src/plots/plots/layout_attributes.js index b3bfeebcc64..85831fe8f3f 100644 --- a/src/plots/plots/layout_attributes.js +++ b/src/plots/plots/layout_attributes.js @@ -1,19 +1,21 @@ var Plotly = require('../../plotly'); -var plots = require('./plots'); + +var fontAttrs = require('./font_attributes'); +var colorAttrs = require('../../components/color/attributes'); var extendFlat = Plotly.Lib.extendFlat; module.exports = { font: { - family: extendFlat({}, plots.fontAttrs.family, { + family: extendFlat({}, fontAttrs.family, { dflt: '"Open sans", verdana, arial, sans-serif' }), - size: extendFlat({}, plots.fontAttrs.size, { + size: extendFlat({}, fontAttrs.size, { dflt: 12 }), - color: extendFlat({}, plots.fontAttrs.color, { - dflt: Plotly.Color.defaultLine + color: extendFlat({}, fontAttrs.color, { + dflt: colorAttrs.defaultLine }), description: [ 'Sets the global font.', @@ -29,7 +31,7 @@ module.exports = { 'Sets the plot\'s title.' ].join(' ') }, - titlefont: extendFlat({}, plots.fontAttrs, { + titlefont: extendFlat({}, fontAttrs, { description: 'Sets the title font.' }), autosize: { @@ -108,7 +110,7 @@ module.exports = { paper_bgcolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.background, + dflt: colorAttrs.background, description: 'Sets the color of paper where the graph is drawn.' }, plot_bgcolor: { @@ -116,7 +118,7 @@ module.exports = { // because it needs to know if there are (2D) axes or not valType: 'color', role: 'style', - dflt: Plotly.Color.background, + dflt: colorAttrs.background, description: [ 'Sets the color of plotting area in-between x and y axes.' ].join(' ') @@ -183,7 +185,7 @@ module.exports = { 'xaxis': 'Axes', 'yaxis': 'Axes', 'scene': 'Gl3dLayout', // TODO should be Scene - 'geo': 'GeoLayout', // TODO should be Geo + 'geo': 'Geo', 'legend': 'Legend', 'annotations': 'Annotations', 'shapes': 'Shapes' diff --git a/src/plots/plots/plots.js b/src/plots/plots/plots.js index 710ce5dc1cb..e855b24e83f 100644 --- a/src/plots/plots/plots.js +++ b/src/plots/plots/plots.js @@ -12,6 +12,7 @@ var modules = plots.modules = {}, subplotsRegistry = plots.subplotsRegistry = {}; plots.attributes = require('./attributes'); +plots.attributes.type.values = allTypes; plots.fontAttrs = require('./font_attributes'); plots.layoutAttributes = require('./layout_attributes'); diff --git a/src/polar/utils/undo_manager.js b/src/polar/utils/undo_manager.js deleted file mode 100644 index 99db9aedb83..00000000000 --- a/src/polar/utils/undo_manager.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -//Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager -//Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com -module.exports = function UndoManager() { - var undoCommands = [], - index = -1, - isExecuting = false, - callback; - - function execute(command, action){ - if(!command) return this; - - isExecuting = true; - command[action](); - isExecuting = false; - - return this; - } - - return { - add: function(command){ - if(isExecuting) return this; - undoCommands.splice(index + 1, undoCommands.length - index); - undoCommands.push(command); - index = undoCommands.length - 1; - return this; - }, - setCallback: function(callbackFunc){ callback = callbackFunc; }, - undo: function(){ - var command = undoCommands[index]; - if(!command) return this; - execute(command, 'undo'); - index -= 1; - if(callback) callback(command.undo); - return this; - }, - redo: function(){ - var command = undoCommands[index + 1]; - if(!command) return this; - execute(command, 'redo'); - index += 1; - if(callback) callback(command.redo); - return this; - }, - clear: function(){ - undoCommands = []; - index = -1; - }, - hasUndo: function(){ return index !== -1; }, - hasRedo: function(){ return index < (undoCommands.length - 1); }, - getCommands: function(){ return undoCommands; }, - getPreviousCommand: function(){ return undoCommands[index-1]; }, - getIndex: function(){ return index; } - }; -}; diff --git a/src/traces/bars/attributes.js b/src/traces/bars/attributes.js index ed9669f2b0e..1ed2800c5ac 100644 --- a/src/traces/bars/attributes.js +++ b/src/traces/bars/attributes.js @@ -1,8 +1,4 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var scatterAttrs = Plotly.Scatter.attributes, +var scatterAttrs = require('../scatter/attributes'), scatterMarkerAttrs = scatterAttrs.marker, scatterMarkerLineAttrs = scatterMarkerAttrs.line; diff --git a/src/traces/boxes/attributes.js b/src/traces/boxes/attributes.js index 6d35e04d291..4a8c9419b59 100644 --- a/src/traces/boxes/attributes.js +++ b/src/traces/boxes/attributes.js @@ -1,8 +1,8 @@ -var Plotly = require('../../plotly'); -var extendFlat = Plotly.Lib.extendFlat; +var scatterAttrs = require('../scatter/attributes'); +var colorAttrs = require('../../components/color/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var scatterAttrs = Plotly.Scatter.attributes, - scatterMarkerAttrs = scatterAttrs.marker, +var scatterMarkerAttrs = scatterAttrs.marker, scatterMarkerLineAttrs = scatterMarkerAttrs.line; @@ -126,7 +126,7 @@ module.exports = { {arrayOk: false}), line: { color: extendFlat({}, scatterMarkerLineAttrs.color, - {arrayOk: false, dflt: Plotly.Color.defaultLine}), + {arrayOk: false, dflt: colorAttrs.defaultLine}), width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false, dflt: 0}), outliercolor: { diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 3e2b134516c..4da99e04f86 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -1,8 +1,9 @@ -var Plotly = require('../../plotly'); +var ScatterGeoAttrs = require('../scattergeo/attributes'); +var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var plotAttrs = require('../../plots/plots/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var ScatterGeoAttrs = Plotly.ScatterGeo.attributes, - ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line, - traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; +var ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line; module.exports = { locations: { @@ -34,7 +35,7 @@ module.exports = { autocolorscale: traceColorbarAttrs.autocolorscale, reversescale: traceColorbarAttrs.reversescale, showscale: traceColorbarAttrs.showscale, - hoverinfo: Plotly.Lib.extendFlat({}, Plotly.Plots.attributes.hoverinfo, { + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['location', 'z', 'text', 'name'] }), _nestedModules: { diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 52e77eb4956..3e064c13f5b 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -1,7 +1,7 @@ -var Plotly = require('../../plotly'); +var scatterAttrs = require('../scatter/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var extendFlat = Plotly.Lib.extendFlat; -var scatterLineAttrs = Plotly.Scatter.attributes.line; +var scatterLineAttrs = scatterAttrs.line; module.exports = { _composedModules: { // composed module coupling diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 13ed9719d1d..c9b9714b5e6 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -1,7 +1,8 @@ -var Plotly = require('../../plotly'); +var scatterAttrs = require('../scatter/attributes'); +var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); + +var extendFlat = require('../../lib/extend').extendFlat; -var scatterAttrs = Plotly.Scatter.attributes; -var traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; module.exports = { z: { @@ -50,7 +51,7 @@ module.exports = { zmin: traceColorbarAttrs.zmin, zmax: traceColorbarAttrs.zmax, colorscale: traceColorbarAttrs.colorscale, - autocolorscale: Plotly.Lib.extendFlat({}, traceColorbarAttrs.autocolorscale, + autocolorscale: extendFlat({}, traceColorbarAttrs.autocolorscale, {dflt: false}), reversescale: traceColorbarAttrs.reversescale, showscale: traceColorbarAttrs.showscale, @@ -81,4 +82,3 @@ module.exports = { 'histogram2dcontour': 'Histogram' } }; - diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 0369b8d68d9..5a17956cb83 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -1,8 +1,4 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var barAttrs = Plotly.Bars.attributes; +var barAttrs = require('../bars/attributes'); module.exports = { diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index 83852ed6fd1..7b17b44c760 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -1,6 +1,4 @@ -var Plotly = require('../../plotly'); - -var traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; +var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); module.exports = { x: {valType: 'data_array'}, diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index bc61bd7facb..613d70bf867 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -1,6 +1,9 @@ -var Plotly = require('../../plotly'); +var colorAttrs = require('../../components/color/attributes'); +var fontAttrs = require('../../plots/plots/font_attributes'); +var plotAttrs = require('../../plots/plots/attributes'); + +var extendFlat = require('../../lib/extend').extendFlat; -var extendFlat = Plotly.Lib.extendFlat; module.exports = { labels: { @@ -45,7 +48,7 @@ module.exports = { color: { valType: 'color', role: 'style', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, arrayOk: true, description: [ 'Sets the color of the line enclosing each sector.' @@ -94,7 +97,7 @@ module.exports = { 'Determines which trace information appear on the graph.' ].join(' ') }, - hoverinfo: extendFlat({}, Plotly.Plots.attributes.hoverinfo, { + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), textposition: { @@ -108,13 +111,13 @@ module.exports = { ].join(' ') }, // TODO make those arrayOk? - textfont: extendFlat({}, Plotly.Plots.fontAttrs, { + textfont: extendFlat({}, fontAttrs, { description: 'Sets the font used for `textinfo`.' }), - insidetextfont: extendFlat({}, Plotly.Plots.fontAttrs, { + insidetextfont: extendFlat({}, fontAttrs, { description: 'Sets the font used for `textinfo` lying inside the pie.' }), - outsidetextfont: extendFlat({}, Plotly.Plots.fontAttrs, { + outsidetextfont: extendFlat({}, fontAttrs, { description: 'Sets the font used for `textinfo` lying outside the pie.' }), diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index dcb730d27d2..ed3682938a1 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -1,8 +1,6 @@ -'use strict'; - var Plotly = require('../../plotly'); -var scatter = require('./scatter'); +var PTS_LINESONLY = 20; // TODO put in constants/ module.exports = { x: { @@ -76,7 +74,7 @@ module.exports = { 'If the provided `mode` includes *text* then the `text` elements', 'appear at the coordinates. Otherwise, the `text` elements', 'appear on hover.', - 'If there are less than ' + scatter.PTS_LINESONLY + ' points,', + 'If there are less than ' + PTS_LINESONLY + ' points,', 'then the default is *lines+markers*. Otherwise, *lines*.' ].join(' ') }, diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index fc8c042f78a..469cfecae43 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -1,13 +1,12 @@ 'use strict'; -var Plotly = require('../../plotly'); +var scatterAttrs = require('../scatter/attributes'); var MARKER_SYMBOLS = require('../../constants/gl_markers.json'); +var extendFlat = require('../../lib/extend').extendFlat; -var scatterAttrs = Plotly.Scatter.attributes, - scatterLineAttrs = scatterAttrs.line, +var scatterLineAttrs = scatterAttrs.line, scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line, - extendFlat = Plotly.Lib.extendFlat; + scatterMarkerLineAttrs = scatterMarkerAttrs.line; function makeProjectionAttr(axLetter) { return { diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 6c80e7f6e50..2427c70e3c9 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -1,11 +1,11 @@ -var Plotly = require('../../plotly'); +var scatterAttrs = require('../scatter/attributes'); +var plotAttrs = require('../../plots/plots/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var scatterAttrs = Plotly.Scatter.attributes, - scatterMarkerAttrs = scatterAttrs.marker, +var scatterMarkerAttrs = scatterAttrs.marker, scatterLineAttrs = scatterAttrs.line, scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var extendFlat = Plotly.Lib.extendFlat; module.exports = { lon: { @@ -78,7 +78,7 @@ module.exports = { }, textfont: scatterAttrs.textfont, textposition: scatterAttrs.textposition, - hoverinfo: Plotly.Lib.extendFlat({}, Plotly.Plots.attributes.hoverinfo, { + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['lon', 'lat', 'location', 'text', 'name'] }), _nestedModules: { diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 1529d3c3ea6..eecd58ad9df 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -1,13 +1,9 @@ -'use strict'; - -var Plotly = require('../../plotly'); -var extendFlat = Plotly.Lib.extendFlat; - +var scatterAttrs = require('../scatter/attributes'); var DASHES = require('../../constants/gl2d_dashes.json'); var MARKERS = require('../../constants/gl_markers.json'); +var extendFlat = require('../../lib/extend').extendFlat; -var scatterAttrs = Plotly.Scatter.attributes, - scatterLineAttrs = scatterAttrs.line, +var scatterLineAttrs = scatterAttrs.line, scatterMarkerAttrs = scatterAttrs.marker, scatterMarkerLineAttrs = scatterMarkerAttrs.line; diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index aa21f652bb8..ead98a774f1 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -1,8 +1,8 @@ 'use strict'; -var Plotly = require('../../plotly'); +var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var traceColorbarAttrs = Plotly.Colorbar.traceColorbarAttributes; function makeContourProjAttr(axLetter) { return { @@ -90,7 +90,7 @@ module.exports = { zmin: traceColorbarAttrs.zmin, zmax: traceColorbarAttrs.zmax, colorscale: traceColorbarAttrs.colorscale, - autocolorscale: Plotly.Lib.extendFlat({}, traceColorbarAttrs.autocolorscale, + autocolorscale: extendFlat({}, traceColorbarAttrs.autocolorscale, {dflt: false}), reversescale: traceColorbarAttrs.reversescale, showscale: traceColorbarAttrs.showscale, From 01ee2542a68c98c61325036c6215d3193f9e3f53 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:34:22 -0500 Subject: [PATCH 13/38] update colorbar Plotly.Plots.titles --> Plotly.Title.draw --- src/components/colorbar/colorbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/colorbar/colorbar.js b/src/components/colorbar/colorbar.js index 769dc148cdc..48abadf2e5b 100644 --- a/src/components/colorbar/colorbar.js +++ b/src/components/colorbar/colorbar.js @@ -239,7 +239,7 @@ var colorbar = module.exports = function(td, id) { if(['top','bottom'].indexOf(opts.titleside)!==-1) { // draw the title so we know how much room it needs // when we squish the axis - Plotly.Plots.titles(td, cbAxisOut._id + 'title'); + Plotly.Titles.draw(td, cbAxisOut._id + 'title'); } function drawAxis(){ From 2d40132a5b004a8e559df41a480ff63271faed87 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:34:48 -0500 Subject: [PATCH 14/38] fix choropleth plot params --> constants --- src/traces/choropleth/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index a2447b5d4e0..b69a78590f8 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -48,7 +48,7 @@ plotChoropleth.plot = function(geo, choroplethData, geoLayout) { gChoropleth = framework.select('g.choroplethlayer'), gBaseLayer = framework.select('g.baselayer'), gBaseLayerOverChoropleth = framework.select('g.baselayeroverchoropleth'), - baseLayersOverChoropleth = params.baseLayersOverChoropleth, + baseLayersOverChoropleth = constants.baseLayersOverChoropleth, layerName; // TODO move to more d3-idiomatic pattern (that's work on replot) From 865476a50d4e0c705c3dc2291720e0a8753eec38 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:35:54 -0500 Subject: [PATCH 15/38] mv geo module to plots/geo : - keep GeoLayout module (for now) - merge GeoAxes into GeoLayout --- src/{ => plots}/geo/geo.js | 52 +++++++------- .../geo/layout/attributes.js} | 34 ++++----- .../geo/layout/axis_attributes.js} | 4 +- .../geo/layout/axis_defaults.js} | 33 ++++----- .../geo/layout/defaults.js} | 70 +++++++------------ src/plots/geo/layout/layout.js | 9 +++ src/plots/geo/layout/trace_attributes.js | 15 ++++ src/{geo/lib => plots/geo}/projections.js | 0 .../set-scale.js => plots/geo/set_scale.js} | 2 +- src/{geo/lib => plots/geo}/zoom.js | 8 +-- .../zoom-reset.js => plots/geo/zoom_reset.js} | 0 11 files changed, 112 insertions(+), 115 deletions(-) rename src/{ => plots}/geo/geo.js (90%) rename src/{geo/attributes/geolayout.js => plots/geo/layout/attributes.js} (90%) rename src/{geo/attributes/geoaxes.js => plots/geo/layout/axis_attributes.js} (91%) rename src/{geo/defaults/geoaxes.js => plots/geo/layout/axis_defaults.js} (58%) rename src/{geo/defaults/geolayout.js => plots/geo/layout/defaults.js} (54%) create mode 100644 src/plots/geo/layout/layout.js create mode 100644 src/plots/geo/layout/trace_attributes.js rename src/{geo/lib => plots/geo}/projections.js (100%) rename src/{geo/lib/set-scale.js => plots/geo/set_scale.js} (98%) rename src/{geo/lib => plots/geo}/zoom.js (99%) rename src/{geo/lib/zoom-reset.js => plots/geo/zoom_reset.js} (100%) diff --git a/src/geo/geo.js b/src/plots/geo/geo.js similarity index 90% rename from src/geo/geo.js rename to src/plots/geo/geo.js index 6574b00da22..0408074eb95 100644 --- a/src/geo/geo.js +++ b/src/plots/geo/geo.js @@ -2,17 +2,21 @@ /* global PlotlyGeoAssets:false */ -var Plotly = require('../plotly'), - d3 = require('d3'), - params = require('./lib/params'), - addProjectionsToD3 = require('./lib/projections'), - createGeoScale = require('./lib/set-scale'), - createGeoZoom = require('./lib/zoom'), - createGeoZoomReset = require('./lib/zoom-reset'), - plotScatterGeo = require('./plot/scattergeo'), - plotChoropleth = require('./plot/choropleth'), - topojsonUtils = require('./lib/topojson-utils'), - topojsonFeature = require('topojson').feature; +var Plotly = require('../../plotly'); +var d3 = require('d3'); + +var addProjectionsToD3 = require('./projections'); +var createGeoScale = require('./set_scale'); +var createGeoZoom = require('./zoom'); +var createGeoZoomReset = require('./zoom_reset'); + +var plotScatterGeo = require('../../traces/scattergeo/plot'); +var plotChoropleth = require('../../traces/choropleth/plot'); + +var constants = require('../../constants/geo_constants'); +var topojsonUtils = require('../../lib/topojson_utils'); +var topojsonFeature = require('topojson').feature; + function Geo(options, fullLayout) { @@ -129,13 +133,13 @@ proto.makeProjection = function(geoLayout) { if(isNew) { this.projectionType = projType; - projection = this.projection = d3.geo[params.projNames[projType]](); + projection = this.projection = d3.geo[constants.projNames[projType]](); } else projection = this.projection; projection .translate(projLayout._translate0) - .precision(params.precision); + .precision(constants.precision); if(!geoLayout._isAlbersUsa) { projection @@ -146,7 +150,7 @@ proto.makeProjection = function(geoLayout) { if(geoLayout._clipAngle) { this.clipAngle = geoLayout._clipAngle; // needed in proto.render projection - .clipAngle(geoLayout._clipAngle - params.clipPad); + .clipAngle(geoLayout._clipAngle - constants.clipPad); } else this.clipAngle = null; // for graph edits @@ -249,7 +253,7 @@ proto.drawTopo = function(selection, layerName, geoLayout) { var topojson = this.topojson, datum = layerName==='frame' ? - params.sphereSVG : + constants.sphereSVG : topojsonFeature(topojson, topojson.objects[layerName]); selection.append('g') @@ -273,7 +277,7 @@ proto.drawGraticule = function(selection, axisName, geoLayout) { if(axisLayout.showgrid !== true) return; - var scopeDefaults = params.scopeDefaults[geoLayout.scope], + var scopeDefaults = constants.scopeDefaults[geoLayout.scope], lonaxisRange = scopeDefaults.lonaxisRange, lataxisRange = scopeDefaults.lataxisRange, step = axisName==='lonaxis' ? @@ -290,8 +294,8 @@ proto.drawGraticule = function(selection, axisName, geoLayout) { proto.drawLayout = function(geoLayout) { var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = params.baseLayers, - axesNames = params.axesNames, + baseLayers = constants.baseLayers, + axesNames = constants.axesNames, layerName; // TODO move to more d3-idiomatic pattern (that's work on replot) @@ -311,7 +315,7 @@ proto.drawLayout = function(geoLayout) { }; function styleFillLayer(selection, layerName, geoLayout) { - var layerAdj = params.layerNameToAdjective[layerName]; + var layerAdj = constants.layerNameToAdjective[layerName]; selection.select('.' + layerName) .selectAll('path') @@ -320,7 +324,7 @@ function styleFillLayer(selection, layerName, geoLayout) { } function styleLineLayer(selection, layerName, geoLayout) { - var layerAdj = params.layerNameToAdjective[layerName]; + var layerAdj = constants.layerNameToAdjective[layerName]; selection.select('.' + layerName) .selectAll('path') @@ -338,8 +342,8 @@ function styleGraticule(selection, axisName, geoLayout) { } proto.styleLayer = function(selection, layerName, geoLayout) { - var fillLayers = params.fillLayers, - lineLayers = params.lineLayers; + var fillLayers = constants.fillLayers, + lineLayers = constants.lineLayers; if(fillLayers.indexOf(layerName)!==-1) { styleFillLayer(selection, layerName, geoLayout); @@ -351,8 +355,8 @@ proto.styleLayer = function(selection, layerName, geoLayout) { proto.styleLayout = function(geoLayout) { var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = params.baseLayers, - axesNames = params.axesNames, + baseLayers = constants.baseLayers, + axesNames = constants.axesNames, layerName; for(var i = 0; i < baseLayers.length; i++) { diff --git a/src/geo/attributes/geolayout.js b/src/plots/geo/layout/attributes.js similarity index 90% rename from src/geo/attributes/geolayout.js rename to src/plots/geo/layout/attributes.js index be775750f96..24e87e052fd 100644 --- a/src/geo/attributes/geolayout.js +++ b/src/plots/geo/layout/attributes.js @@ -1,5 +1,7 @@ -var Plotly = require('../../plotly'), - params = require('../lib/params'); +var colorAttrs = require('../../../components/color/attributes'); +var constants = require('../../../constants/geo_constants'); +var geoAxesAttrs = require('./axis_attributes'); + module.exports = { domain: { @@ -45,7 +47,7 @@ module.exports = { scope: { valType: 'enumerated', role: 'info', - values: Object.keys(params.scopeDefaults), + values: Object.keys(constants.scopeDefaults), dflt: 'world', description: 'Set the scope of the map.' }, @@ -53,7 +55,7 @@ module.exports = { type: { valType: 'enumerated', role: 'info', - values: Object.keys(params.projNames), + values: Object.keys(constants.projNames), description: 'Sets the projection type.' }, rotation: { @@ -112,7 +114,7 @@ module.exports = { coastlinecolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, description: 'Sets the coastline color.' }, coastlinewidth: { @@ -131,7 +133,7 @@ module.exports = { landcolor: { valType: 'color', role: 'style', - dflt: params.landColor, + dflt: constants.landColor, description: 'Sets the land mass color.' }, showocean: { @@ -143,7 +145,7 @@ module.exports = { oceancolor: { valType: 'color', role: 'style', - dflt: params.waterColor, + dflt: constants.waterColor, description: 'Sets the ocean color' }, showlakes: { @@ -155,7 +157,7 @@ module.exports = { lakecolor: { valType: 'color', role: 'style', - dflt: params.waterColor, + dflt: constants.waterColor, description: 'Sets the color of the lakes.' }, showrivers: { @@ -167,7 +169,7 @@ module.exports = { rivercolor: { valType: 'color', role: 'style', - dflt: params.waterColor, + dflt: constants.waterColor, description: 'Sets color of the rivers.' }, riverwidth: { @@ -185,7 +187,7 @@ module.exports = { countrycolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, description: 'Sets line color of the country boundaries.' }, countrywidth: { @@ -206,7 +208,7 @@ module.exports = { subunitcolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, description: 'Sets the color of the subunits boundaries.' }, subunitwidth: { @@ -224,7 +226,7 @@ module.exports = { framecolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.defaultLine, + dflt: colorAttrs.defaultLine, description: 'Sets the color the frame.' }, framewidth: { @@ -237,11 +239,9 @@ module.exports = { bgcolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.background, + dflt: colorAttrs.background, description: 'Set the background color of the map' }, - _nestedModules: { - 'lonaxis': 'GeoAxes', - 'lataxis': 'GeoAxes' - } + lonaxis: geoAxesAttrs, + lataxis: geoAxesAttrs }; diff --git a/src/geo/attributes/geoaxes.js b/src/plots/geo/layout/axis_attributes.js similarity index 91% rename from src/geo/attributes/geoaxes.js rename to src/plots/geo/layout/axis_attributes.js index 0018f4b198c..99cb42dfa9a 100644 --- a/src/geo/attributes/geoaxes.js +++ b/src/plots/geo/layout/axis_attributes.js @@ -1,4 +1,4 @@ -var Plotly = require('../../plotly'); +var colorAttrs = require('../../../components/color/attributes'); module.exports = { range: { @@ -33,7 +33,7 @@ module.exports = { gridcolor: { valType: 'color', role: 'style', - dflt: Plotly.Color.lightLine, + dflt: colorAttrs.lightLine, description: [ 'Sets the graticule\'s stroke color.' ].join(' ') diff --git a/src/geo/defaults/geoaxes.js b/src/plots/geo/layout/axis_defaults.js similarity index 58% rename from src/geo/defaults/geoaxes.js rename to src/plots/geo/layout/axis_defaults.js index 1a028b241d7..7261c9d185c 100644 --- a/src/geo/defaults/geoaxes.js +++ b/src/plots/geo/layout/axis_defaults.js @@ -1,20 +1,15 @@ 'use strict'; -var Plotly = require('../../plotly'), - params = require('../lib/params'); +var Plotly = require('../../../plotly'); +var constants = require('../../../constants/geo_constants'); +var axisAttributes = require('./axis_attributes'); -var GeoAxes = module.exports = {}; -GeoAxes.layoutAttributes = require('../attributes/geoaxes'); - -GeoAxes.supplyLayoutDefaults = function(geoLayoutIn, geoLayoutOut) { - var axesNames = params.axesNames; - - var axisIn, axisOut, axisName, rangeDflt, range, show; +module.exports = function supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut) { + var axesNames = constants.axesNames; function coerce(attr, dflt) { - return Plotly.Lib.coerce(axisIn, axisOut, - GeoAxes.layoutAttributes, attr, dflt); + return Plotly.Lib.coerce(axisIn, axisOut, axisAttributes, attr, dflt); } function getRangeDflt(axisName) { @@ -26,7 +21,7 @@ GeoAxes.supplyLayoutDefaults = function(geoLayoutIn, geoLayoutOut) { projLayout = geoLayoutOut.projection; projType = projLayout.type; projRotation = projLayout.rotation; - dfltSpans = params[axisName + 'Span']; + dfltSpans = constants[axisName + 'Span']; halfSpan = dfltSpans[projType]!==undefined ? dfltSpans[projType] / 2 : @@ -37,24 +32,24 @@ GeoAxes.supplyLayoutDefaults = function(geoLayoutIn, geoLayoutOut) { return [rotateAngle - halfSpan, rotateAngle + halfSpan]; } - else return params.scopeDefaults[scope][axisName + 'Range']; + else return constants.scopeDefaults[scope][axisName + 'Range']; } for(var i = 0; i < axesNames.length; i++) { - axisName = axesNames[i]; - axisIn = geoLayoutIn[axisName] || {}; - axisOut = {}; + var axisName = axesNames[i]; + var axisIn = geoLayoutIn[axisName] || {}; + var axisOut = {}; - rangeDflt = getRangeDflt(axisName); + var rangeDflt = getRangeDflt(axisName); - range = coerce('range', rangeDflt); + var range = coerce('range', rangeDflt); Plotly.Lib.noneOrAll(axisIn.range, axisOut.range, [0, 1]); coerce('tick0', range[0]); coerce('dtick', axisName==='lonaxis' ? 30 : 10); - show = coerce('showgrid'); + var show = coerce('showgrid'); if(show) { coerce('gridcolor'); coerce('gridwidth'); diff --git a/src/geo/defaults/geolayout.js b/src/plots/geo/layout/defaults.js similarity index 54% rename from src/geo/defaults/geolayout.js rename to src/plots/geo/layout/defaults.js index 1d40136d402..2d6f1281ef5 100644 --- a/src/geo/defaults/geolayout.js +++ b/src/plots/geo/layout/defaults.js @@ -1,74 +1,52 @@ 'use strict'; -var Plotly = require('../../plotly'), - params = require('../lib/params'); - -var GeoLayout = module.exports = {}; - -Plotly.Plots.registerSubplot('geo', 'geo', 'geo', { - geo: { - valType: 'geoid', - role: 'info', - dflt: 'geo', - description: [ - 'Sets a reference between this trace\'s geospatial coordinates and', - 'a geographic map.', - 'If *geo* (the default value), the geospatial coordinates refer to', - '`layout.geo`.', - 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', - 'and so on.' - ].join(' ') - } -}); +var Plotly = require('../../../plotly'); +var constants = require('../../../constants/geo_constants'); +var layoutAttributes = require('./attributes'); +var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); -GeoLayout.layoutAttributes = require('../attributes/geolayout'); -GeoLayout.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var geos = Plotly.Plots.getSubplotIdsInData(fullData, 'geo'), geosLength = geos.length; - var geo, geoLayoutIn, geoLayoutOut; - function coerce(attr, dflt) { - return Plotly.Lib.coerce(geoLayoutIn, geoLayoutOut, - GeoLayout.layoutAttributes, attr, dflt); + return Plotly.Lib.coerce(geoLayoutIn, geoLayoutOut, layoutAttributes, attr, dflt); } for(var i = 0; i < geosLength; i++) { - geo = geos[i]; - geoLayoutIn = layoutIn[geo] || {}; - geoLayoutOut = {}; + var geo = geos[i]; + var geoLayoutIn = layoutIn[geo] || {}; + var geoLayoutOut = {}; coerce('domain.x'); coerce('domain.y', [i / geosLength, (i + 1) / geosLength]); - GeoLayout.handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce); + handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce); layoutOut[geo] = geoLayoutOut; } }; -GeoLayout.handleGeoDefaults = function(geoLayoutIn, geoLayoutOut, coerce) { - var scope, resolution, projType, - scopeParams, dfltProjRotate, dfltProjParallels, - isScoped, isAlbersUsa, isConic, show; +function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { + var show; - scope = coerce('scope'); - isScoped = scope!=='world'; - scopeParams = params.scopeDefaults[scope]; + var scope = coerce('scope'); + var isScoped = (scope !== 'world'); + var scopeParams = constants.scopeDefaults[scope]; - resolution = coerce('resolution'); + var resolution = coerce('resolution'); - projType = coerce('projection.type', scopeParams.projType); - isAlbersUsa = projType==='albers usa'; - isConic = projType.indexOf('conic')!==-1; + var projType = coerce('projection.type', scopeParams.projType); + var isAlbersUsa = projType==='albers usa'; + var isConic = projType.indexOf('conic')!==-1; if(isConic) { - dfltProjParallels = scopeParams.projParallels || [0, 60]; + var dfltProjParallels = scopeParams.projParallels || [0, 60]; coerce('projection.parallels', dfltProjParallels); } if(!isAlbersUsa) { - dfltProjRotate = scopeParams.projRotate || [0, 0, 0]; + var dfltProjRotate = scopeParams.projRotate || [0, 0, 0]; coerce('projection.rotation.lon', dfltProjRotate[0]); coerce('projection.rotation.lat', dfltProjRotate[1]); coerce('projection.rotation.roll', dfltProjRotate[2]); @@ -124,11 +102,11 @@ GeoLayout.handleGeoDefaults = function(geoLayoutIn, geoLayoutOut, coerce) { coerce('bgcolor'); - Plotly.GeoAxes.supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); + supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut); // bind a few helper variables geoLayoutOut._isHighRes = resolution===50; - geoLayoutOut._clipAngle = params.lonaxisSpan[projType] / 2; + geoLayoutOut._clipAngle = constants.lonaxisSpan[projType] / 2; geoLayoutOut._isAlbersUsa = isAlbersUsa; geoLayoutOut._isConic = isConic; geoLayoutOut._isScoped = isScoped; @@ -139,4 +117,4 @@ GeoLayout.handleGeoDefaults = function(geoLayoutIn, geoLayoutOut, coerce) { -rotation.lat || 0, rotation.roll || 0 ]; -}; +} diff --git a/src/plots/geo/layout/layout.js b/src/plots/geo/layout/layout.js new file mode 100644 index 00000000000..197524e6f09 --- /dev/null +++ b/src/plots/geo/layout/layout.js @@ -0,0 +1,9 @@ +'use strict'; + +var Plotly = require('../../../plotly'); +var traceAttributes = require('./trace_attributes'); + +Plotly.Plots.registerSubplot('geo', 'geo', 'geo', traceAttributes); + +exports.layoutAttributes = require('./attributes'); +exports.supplyLayoutDefaults = require('./defaults'); diff --git a/src/plots/geo/layout/trace_attributes.js b/src/plots/geo/layout/trace_attributes.js new file mode 100644 index 00000000000..9ad8341f0f9 --- /dev/null +++ b/src/plots/geo/layout/trace_attributes.js @@ -0,0 +1,15 @@ +module.exports = { + geo: { + valType: 'geoid', + role: 'info', + dflt: 'geo', + description: [ + 'Sets a reference between this trace\'s geospatial coordinates and', + 'a geographic map.', + 'If *geo* (the default value), the geospatial coordinates refer to', + '`layout.geo`.', + 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', + 'and so on.' + ].join(' ') + } +}; diff --git a/src/geo/lib/projections.js b/src/plots/geo/projections.js similarity index 100% rename from src/geo/lib/projections.js rename to src/plots/geo/projections.js diff --git a/src/geo/lib/set-scale.js b/src/plots/geo/set_scale.js similarity index 98% rename from src/geo/lib/set-scale.js rename to src/plots/geo/set_scale.js index 8169e00b329..0ba1703b7dd 100644 --- a/src/geo/lib/set-scale.js +++ b/src/plots/geo/set_scale.js @@ -2,7 +2,7 @@ var d3 = require('d3'); -var clipPad = require('./params').clipPad; +var clipPad = require('../../constants/geo_constants').clipPad; function createGeoScale(geoLayout, graphSize) { var projLayout = geoLayout.projection, diff --git a/src/geo/lib/zoom.js b/src/plots/geo/zoom.js similarity index 99% rename from src/geo/lib/zoom.js rename to src/plots/geo/zoom.js index c4cad650f2c..527122f5965 100644 --- a/src/geo/lib/zoom.js +++ b/src/plots/geo/zoom.js @@ -4,12 +4,8 @@ var d3 = require('d3'); var radians = Math.PI / 180, degrees = 180 / Math.PI, - zoomstartStyle = { - cursor: 'pointer' - }, - zoomendStyle = { - cursor: 'auto' - }; + zoomstartStyle = { cursor: 'pointer' }, + zoomendStyle = { cursor: 'auto' }; function createGeoZoom(geo, geoLayout) { diff --git a/src/geo/lib/zoom-reset.js b/src/plots/geo/zoom_reset.js similarity index 100% rename from src/geo/lib/zoom-reset.js rename to src/plots/geo/zoom_reset.js From 968257e3d3051dd94c77f07774c35edca143b04f Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:36:41 -0500 Subject: [PATCH 16/38] put gl2d modules in src/plots/gl2d/ --- src/{gl2d/lib => plots/gl2d}/camera.js | 0 .../lib/gl2daxes.js => plots/gl2d/convert.js} | 5 +- src/{ => plots}/gl2d/scene2d.js | 16 +- src/plots/gl3d/camera.js | 237 ++++++++++++++++++ 4 files changed, 249 insertions(+), 9 deletions(-) rename src/{gl2d/lib => plots/gl2d}/camera.js (100%) rename src/{gl2d/lib/gl2daxes.js => plots/gl2d/convert.js} (98%) rename src/{ => plots}/gl2d/scene2d.js (97%) create mode 100644 src/plots/gl3d/camera.js diff --git a/src/gl2d/lib/camera.js b/src/plots/gl2d/camera.js similarity index 100% rename from src/gl2d/lib/camera.js rename to src/plots/gl2d/camera.js diff --git a/src/gl2d/lib/gl2daxes.js b/src/plots/gl2d/convert.js similarity index 98% rename from src/gl2d/lib/gl2daxes.js rename to src/plots/gl2d/convert.js index 33cb8089afd..4b0701751e8 100644 --- a/src/gl2d/lib/gl2daxes.js +++ b/src/plots/gl2d/convert.js @@ -1,8 +1,9 @@ 'use strict'; var Plotly = require('../../plotly'); -var htmlToUnicode = require('../../gl3d/lib/html2unicode'); -var str2RGBArray = require('../../gl3d/lib/str2rgbarray'); + +var htmlToUnicode = require('../../lib/html2unicode'); +var str2RGBArray = require('../../lib/str2rgbarray'); function Axes2DOptions(scene) { this.scene = scene; diff --git a/src/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js similarity index 97% rename from src/gl2d/scene2d.js rename to src/plots/gl2d/scene2d.js index b9ab2f3cad8..d87eceb2b78 100644 --- a/src/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -1,19 +1,21 @@ 'use strict'; -var Plotly = require('../plotly'); +var Plotly = require('../../plotly'); var createPlot2D = require('gl-plot2d'); var createSpikes = require('gl-spikes2d'); var createSelectBox = require('gl-select-box'); -var createLineWithMarkers = require('./scattergl/convert/'); -var createOptions = require('./lib/gl2daxes'); -var createCamera = require('./lib/camera'); -var htmlToUnicode = require('../gl3d/lib/html2unicode'); -var showNoWebGlMsg = require('../gl3d/lib/show_no_webgl_msg'); + +var createOptions = require('./convert'); +var createCamera = require('./camera'); + +var createLineWithMarkers = require('../../traces/scattergl/convert'); + +var htmlToUnicode = require('../../lib/html2unicode'); +var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); var AXES = ['xaxis', 'yaxis']; var STATIC_CANVAS, STATIC_CONTEXT; - Plotly.Plots.registerSubplot('gl2d', ['xaxis', 'yaxis'], ['x', 'y'], Plotly.Axes.traceAttributes); diff --git a/src/plots/gl3d/camera.js b/src/plots/gl3d/camera.js new file mode 100644 index 00000000000..53c44f3c0bb --- /dev/null +++ b/src/plots/gl3d/camera.js @@ -0,0 +1,237 @@ +/* jshint shadow: true */ + +'use strict'; + +module.exports = createCamera; + +var now = require('right-now'); +var createView = require('3d-view'); +var mouseChange = require('mouse-change'); +var mouseWheel = require('mouse-wheel'); + +function createCamera(element, options) { + element = element || document.body; + options = options || {}; + + var limits = [ 0.01, Infinity ]; + if('distanceLimits' in options) { + limits[0] = options.distanceLimits[0]; + limits[1] = options.distanceLimits[1]; + } + if('zoomMin' in options) { + limits[0] = options.zoomMin; + } + if('zoomMax' in options) { + limits[1] = options.zoomMax; + } + + var view = createView({ + center: options.center || [0, 0, 0], + up: options.up || [0, 1, 0], + eye: options.eye || [0, 0, 10], + mode: options.mode || 'orbit', + distanceLimits: limits + }); + + var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + var distance = 0.0; + var width = element.clientWidth; + var height = element.clientHeight; + + var camera = { + keyBindingMode: 'rotate', + view: view, + element: element, + delay: options.delay || 16, + rotateSpeed: options.rotateSpeed || 1, + zoomSpeed: options.zoomSpeed || 1, + translateSpeed: options.translateSpeed || 1, + flipX: !!options.flipX, + flipY: !!options.flipY, + modes: view.modes, + tick: function() { + var t = now(); + var delay = this.delay; + var ctime = t - 2 * delay; + view.idle(t-delay); + view.recalcMatrix(ctime); + view.flush(t - (100+delay * 2)); + var allEqual = true; + var matrix = view.computedMatrix; + for(var i = 0; i < 16; ++i) { + allEqual = allEqual && (pmatrix[i] === matrix[i]); + pmatrix[i] = matrix[i]; + } + var sizeChanged = + element.clientWidth === width && + element.clientHeight === height; + width = element.clientWidth; + height = element.clientHeight; + if(allEqual) { + return !sizeChanged; + } + distance = Math.exp(view.computedRadius[0]); + return true; + }, + lookAt: function(center, eye, up) { + view.lookAt(view.lastT(), center, eye, up); + }, + rotate: function(pitch, yaw, roll) { + view.rotate(view.lastT(), pitch, yaw, roll); + }, + pan: function(dx, dy, dz) { + view.pan(view.lastT(), dx, dy, dz); + }, + translate: function(dx, dy, dz) { + view.translate(view.lastT(), dx, dy, dz); + } + }; + + Object.defineProperties(camera, { + matrix: { + get: function() { + return view.computedMatrix; + }, + set: function(mat) { + view.setMatrix(view.lastT(), mat); + return view.computedMatrix; + }, + enumerable: true + }, + mode: { + get: function() { + return view.getMode(); + }, + set: function(mode) { + var curUp = view.computedUp.slice(); + var curEye = view.computedEye.slice(); + var curCenter = view.computedCenter.slice(); + view.setMode(mode); + if(mode === 'turntable') { + //Hacky time warping stuff to generate smooth animation + var t0 = now(); + view._active.lookAt(t0, curEye, curCenter, curUp); + view._active.lookAt(t0 + 500, curEye, curCenter, [0,0,1]); + view._active.flush(t0); + } + return view.getMode(); + }, + enumerable: true + }, + center: { + get: function() { + return view.computedCenter; + }, + set: function(ncenter) { + view.lookAt(view.lastT(), null, ncenter); + return view.computedCenter; + }, + enumerable: true + }, + eye: { + get: function() { + return view.computedEye; + }, + set: function(neye) { + view.lookAt(view.lastT(), neye); + return view.computedEye; + }, + enumerable: true + }, + up: { + get: function() { + return view.computedUp; + }, + set: function(nup) { + view.lookAt(view.lastT(), null, null, nup); + return view.computedUp; + }, + enumerable: true + }, + distance: { + get: function() { + return distance; + }, + set: function(d) { + view.setDistance(view.lastT(), d); + return d; + }, + enumerable: true + }, + distanceLimits: { + get: function() { + return view.getDistanceLimits(limits); + }, + set: function(v) { + view.setDistanceLimits(v); + return v; + }, + enumerable: true + } + }); + + element.addEventListener('contextmenu', function(ev) { + ev.preventDefault(); + return false; + }); + + var lastX = 0, lastY = 0; + mouseChange(element, function(buttons, x, y, mods) { + var rotate = camera.keyBindingMode === 'rotate'; + var pan = camera.keyBindingMode === 'pan'; + var zoom = camera.keyBindingMode === 'zoom'; + + var ctrl = !!mods.control; + var alt = !!mods.alt; + var shift = !!mods.shift; + var left = !!(buttons&1); + var right = !!(buttons&2); + var middle = !!(buttons&4); + + var scale = 1.0 / element.clientHeight; + var dx = scale * (x - lastX); + var dy = scale * (y - lastY); + + var flipX = camera.flipX ? 1 : -1; + var flipY = camera.flipY ? 1 : -1; + + var t = now(); + + var drot = Math.PI * camera.rotateSpeed; + + if( (rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { + //Rotate + view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0); + } + + if( (pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { + //Pan + view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0); + } + + if( (zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { + //Zoom + var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100; + view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); + } + + lastX = x; + lastY = y; + + return true; + }); + + mouseWheel(element, function(dx, dy, dz) { + var flipX = camera.flipX ? 1 : -1; + var flipY = camera.flipY ? 1 : -1; + var t = now(); + if(Math.abs(dx) > Math.abs(dy)) { + view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth); + } else { + var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 100.0; + view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); + } + }, true); + + return camera; +} From ff7c9942260b0509e5d3eafcd32b2d78b8a3dd2a Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:38:47 -0500 Subject: [PATCH 17/38] put gl3d module in plots/gl3d/ : - keep Gl3dLayout (for now) - merge Gl3dAxes into Gl3dLayout --- src/gl3d/defaults/gl3daxes.js | 82 ------ src/gl3d/lib/camera.js | 237 ------------------ .../gl3d/layout/attributes.js} | 13 +- .../gl3d/layout/axis_attributes.js} | 6 +- src/plots/gl3d/layout/axis_defaults.js | 52 ++++ .../axes.js => plots/gl3d/layout/convert.js} | 9 +- .../gl3d/layout/defaults.js} | 44 +--- src/plots/gl3d/layout/layout.js | 48 ++++ .../convert => plots/gl3d/layout}/spikes.js | 2 +- .../gl3d/layout/tick_marks.js} | 4 +- src/plots/gl3d/layout/trace_attributes.js | 15 ++ src/{gl3d/lib => plots/gl3d}/project.js | 0 src/{ => plots}/gl3d/scene.js | 31 ++- 13 files changed, 155 insertions(+), 388 deletions(-) delete mode 100644 src/gl3d/defaults/gl3daxes.js delete mode 100644 src/gl3d/lib/camera.js rename src/{gl3d/attributes/gl3dlayout.js => plots/gl3d/layout/attributes.js} (95%) rename src/{gl3d/attributes/gl3daxes.js => plots/gl3d/layout/axis_attributes.js} (94%) create mode 100644 src/plots/gl3d/layout/axis_defaults.js rename src/{gl3d/convert/axes.js => plots/gl3d/layout/convert.js} (96%) rename src/{gl3d/defaults/gl3dlayout.js => plots/gl3d/layout/defaults.js} (68%) create mode 100644 src/plots/gl3d/layout/layout.js rename src/{gl3d/convert => plots/gl3d/layout}/spikes.js (93%) rename src/{gl3d/lib/tick-marks.js => plots/gl3d/layout/tick_marks.js} (96%) create mode 100644 src/plots/gl3d/layout/trace_attributes.js rename src/{gl3d/lib => plots/gl3d}/project.js (100%) rename src/{ => plots}/gl3d/scene.js (96%) diff --git a/src/gl3d/defaults/gl3daxes.js b/src/gl3d/defaults/gl3daxes.js deleted file mode 100644 index e4f26fc4932..00000000000 --- a/src/gl3d/defaults/gl3daxes.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -var Plotly = require('../../plotly'); - -var Gl3dAxes = module.exports = {}; - -Gl3dAxes.axesNames = ['xaxis', 'yaxis', 'zaxis']; - -Gl3dAxes.layoutAttributes = require('../attributes/gl3daxes'); - -var noop = function () {}; - -Gl3dAxes.supplyLayoutDefaults = function(layoutIn, layoutOut, options) { - - var Axes = Plotly.Axes; - var containerIn, containerOut; - - function coerce(attr, dflt) { - return Plotly.Lib.coerce(containerIn, containerOut, - Gl3dAxes.layoutAttributes, attr, dflt); - } - - for (var j = 0; j < Gl3dAxes.axesNames.length; j++) { - var axName = Gl3dAxes.axesNames[j]; - containerIn = layoutIn[axName] || {}; - - containerOut = { - _id: axName[0] + options.scene, - _name: axName - }; - - layoutOut[axName] = containerOut = Axes.handleAxisDefaults( - containerIn, - containerOut, - coerce, - { - font: options.font, - letter: axName[0], - data: options.data, - showGrid: true - }); - - coerce('gridcolor'); - coerce('title', axName[0]); // shouldn't this be on-par with 2D? - - containerOut.setScale = noop; - - if (coerce('showspikes')) { - coerce('spikesides'); - coerce('spikethickness'); - coerce('spikecolor'); - } - if (coerce('showbackground')) coerce('backgroundcolor'); - - coerce('showaxeslabels'); - } -}; - -Gl3dAxes.setConvert = function (containerOut) { - Plotly.Axes.setConvert(containerOut); - containerOut.setScale = noop; -}; - -Gl3dAxes.initAxes = function (td) { - var fullLayout = td._fullLayout; - - // until they play better together - delete fullLayout.xaxis; - delete fullLayout.yaxis; - - var sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'); - - for (var i = 0; i < sceneIds.length; ++i) { - var sceneId = sceneIds[i]; - var sceneLayout = fullLayout[sceneId]; - for (var j = 0; j < 3; ++j) { - var axisName = Gl3dAxes.axesNames[j]; - var ax = sceneLayout[axisName]; - ax._td = td; - } - } -}; diff --git a/src/gl3d/lib/camera.js b/src/gl3d/lib/camera.js deleted file mode 100644 index 53c44f3c0bb..00000000000 --- a/src/gl3d/lib/camera.js +++ /dev/null @@ -1,237 +0,0 @@ -/* jshint shadow: true */ - -'use strict'; - -module.exports = createCamera; - -var now = require('right-now'); -var createView = require('3d-view'); -var mouseChange = require('mouse-change'); -var mouseWheel = require('mouse-wheel'); - -function createCamera(element, options) { - element = element || document.body; - options = options || {}; - - var limits = [ 0.01, Infinity ]; - if('distanceLimits' in options) { - limits[0] = options.distanceLimits[0]; - limits[1] = options.distanceLimits[1]; - } - if('zoomMin' in options) { - limits[0] = options.zoomMin; - } - if('zoomMax' in options) { - limits[1] = options.zoomMax; - } - - var view = createView({ - center: options.center || [0, 0, 0], - up: options.up || [0, 1, 0], - eye: options.eye || [0, 0, 10], - mode: options.mode || 'orbit', - distanceLimits: limits - }); - - var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - var distance = 0.0; - var width = element.clientWidth; - var height = element.clientHeight; - - var camera = { - keyBindingMode: 'rotate', - view: view, - element: element, - delay: options.delay || 16, - rotateSpeed: options.rotateSpeed || 1, - zoomSpeed: options.zoomSpeed || 1, - translateSpeed: options.translateSpeed || 1, - flipX: !!options.flipX, - flipY: !!options.flipY, - modes: view.modes, - tick: function() { - var t = now(); - var delay = this.delay; - var ctime = t - 2 * delay; - view.idle(t-delay); - view.recalcMatrix(ctime); - view.flush(t - (100+delay * 2)); - var allEqual = true; - var matrix = view.computedMatrix; - for(var i = 0; i < 16; ++i) { - allEqual = allEqual && (pmatrix[i] === matrix[i]); - pmatrix[i] = matrix[i]; - } - var sizeChanged = - element.clientWidth === width && - element.clientHeight === height; - width = element.clientWidth; - height = element.clientHeight; - if(allEqual) { - return !sizeChanged; - } - distance = Math.exp(view.computedRadius[0]); - return true; - }, - lookAt: function(center, eye, up) { - view.lookAt(view.lastT(), center, eye, up); - }, - rotate: function(pitch, yaw, roll) { - view.rotate(view.lastT(), pitch, yaw, roll); - }, - pan: function(dx, dy, dz) { - view.pan(view.lastT(), dx, dy, dz); - }, - translate: function(dx, dy, dz) { - view.translate(view.lastT(), dx, dy, dz); - } - }; - - Object.defineProperties(camera, { - matrix: { - get: function() { - return view.computedMatrix; - }, - set: function(mat) { - view.setMatrix(view.lastT(), mat); - return view.computedMatrix; - }, - enumerable: true - }, - mode: { - get: function() { - return view.getMode(); - }, - set: function(mode) { - var curUp = view.computedUp.slice(); - var curEye = view.computedEye.slice(); - var curCenter = view.computedCenter.slice(); - view.setMode(mode); - if(mode === 'turntable') { - //Hacky time warping stuff to generate smooth animation - var t0 = now(); - view._active.lookAt(t0, curEye, curCenter, curUp); - view._active.lookAt(t0 + 500, curEye, curCenter, [0,0,1]); - view._active.flush(t0); - } - return view.getMode(); - }, - enumerable: true - }, - center: { - get: function() { - return view.computedCenter; - }, - set: function(ncenter) { - view.lookAt(view.lastT(), null, ncenter); - return view.computedCenter; - }, - enumerable: true - }, - eye: { - get: function() { - return view.computedEye; - }, - set: function(neye) { - view.lookAt(view.lastT(), neye); - return view.computedEye; - }, - enumerable: true - }, - up: { - get: function() { - return view.computedUp; - }, - set: function(nup) { - view.lookAt(view.lastT(), null, null, nup); - return view.computedUp; - }, - enumerable: true - }, - distance: { - get: function() { - return distance; - }, - set: function(d) { - view.setDistance(view.lastT(), d); - return d; - }, - enumerable: true - }, - distanceLimits: { - get: function() { - return view.getDistanceLimits(limits); - }, - set: function(v) { - view.setDistanceLimits(v); - return v; - }, - enumerable: true - } - }); - - element.addEventListener('contextmenu', function(ev) { - ev.preventDefault(); - return false; - }); - - var lastX = 0, lastY = 0; - mouseChange(element, function(buttons, x, y, mods) { - var rotate = camera.keyBindingMode === 'rotate'; - var pan = camera.keyBindingMode === 'pan'; - var zoom = camera.keyBindingMode === 'zoom'; - - var ctrl = !!mods.control; - var alt = !!mods.alt; - var shift = !!mods.shift; - var left = !!(buttons&1); - var right = !!(buttons&2); - var middle = !!(buttons&4); - - var scale = 1.0 / element.clientHeight; - var dx = scale * (x - lastX); - var dy = scale * (y - lastY); - - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; - - var t = now(); - - var drot = Math.PI * camera.rotateSpeed; - - if( (rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { - //Rotate - view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0); - } - - if( (pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { - //Pan - view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0); - } - - if( (zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { - //Zoom - var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } - - lastX = x; - lastY = y; - - return true; - }); - - mouseWheel(element, function(dx, dy, dz) { - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; - var t = now(); - if(Math.abs(dx) > Math.abs(dy)) { - view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth); - } else { - var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 100.0; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } - }, true); - - return camera; -} diff --git a/src/gl3d/attributes/gl3dlayout.js b/src/plots/gl3d/layout/attributes.js similarity index 95% rename from src/gl3d/attributes/gl3dlayout.js rename to src/plots/gl3d/layout/attributes.js index a73ff04fa01..7b0234bbf5d 100644 --- a/src/gl3d/attributes/gl3dlayout.js +++ b/src/plots/gl3d/layout/attributes.js @@ -1,8 +1,7 @@ 'use strict'; -var Plotly = require('../../plotly'); - -var extendFlat = Plotly.Lib.extendFlat; +var gl3dAxisAttrs = require('./axis_attributes'); +var extendFlat = require('../../../lib/extend').extendFlat; function makeVector(x, y, z) { return { @@ -127,11 +126,9 @@ module.exports = { ].join(' ') }, - _nestedModules: { - 'xaxis': 'Gl3dAxes', - 'yaxis': 'Gl3dAxes', - 'zaxis': 'Gl3dAxes' - }, + xaxis: gl3dAxisAttrs, + yaxis: gl3dAxisAttrs, + zaxis: gl3dAxisAttrs, _deprecated: { cameraposition: { diff --git a/src/gl3d/attributes/gl3daxes.js b/src/plots/gl3d/layout/axis_attributes.js similarity index 94% rename from src/gl3d/attributes/gl3daxes.js rename to src/plots/gl3d/layout/axis_attributes.js index dad9635c28c..eb71968348b 100644 --- a/src/gl3d/attributes/gl3daxes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -1,7 +1,6 @@ -var Plotly = require('../../plotly'); +var axesAttrs = require('../../cartesian/attributes'); +var extendFlat = require('../../../lib/extend').extendFlat; -var axesAttrs = Plotly.Axes.layoutAttributes; -var extendFlat = Plotly.Lib.extendFlat; module.exports = { showspikes: { @@ -54,7 +53,6 @@ module.exports = { showaxeslabels: { valType: 'boolean', role: 'info', - dflt: 'rgba(204, 204, 204, 0.5)', dflt: true, description: 'Sets whether or not this axis is labeled' }, diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js new file mode 100644 index 00000000000..fdb547b27be --- /dev/null +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -0,0 +1,52 @@ +'use strict'; + +var Plotly = require('../../../plotly'); +var layoutAttributes = require('./axis_attributes'); + +var axesNames = ['xaxis', 'yaxis', 'zaxis']; +var noop = function() {}; + + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { + var Axes = Plotly.Axes; + var containerIn, containerOut; + + function coerce(attr, dflt) { + return Plotly.Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt); + } + + for (var j = 0; j < axesNames.length; j++) { + var axName = axesNames[j]; + containerIn = layoutIn[axName] || {}; + + containerOut = { + _id: axName[0] + options.scene, + _name: axName + }; + + layoutOut[axName] = containerOut = Axes.handleAxisDefaults( + containerIn, + containerOut, + coerce, + { + font: options.font, + letter: axName[0], + data: options.data, + showGrid: true + }); + + coerce('gridcolor'); + coerce('title', axName[0]); // shouldn't this be on-par with 2D? + + containerOut.setScale = noop; + + if (coerce('showspikes')) { + coerce('spikesides'); + coerce('spikethickness'); + coerce('spikecolor'); + } + if (coerce('showbackground')) coerce('backgroundcolor'); + + coerce('showaxeslabels'); + } +}; diff --git a/src/gl3d/convert/axes.js b/src/plots/gl3d/layout/convert.js similarity index 96% rename from src/gl3d/convert/axes.js rename to src/plots/gl3d/layout/convert.js index 3ba79fb3fbf..c1553f49aa2 100644 --- a/src/gl3d/convert/axes.js +++ b/src/plots/gl3d/layout/convert.js @@ -1,9 +1,10 @@ 'use strict'; -var arrtools = require('arraytools'), - convertHTML = require('../lib/html2unicode'), - arrayCopy1D = arrtools.copy1D, - str2RgbaArray = require('../lib/str2rgbarray'); +var arrtools = require('arraytools'); +var convertHTML = require('../../../lib/html2unicode'); +var str2RgbaArray = require('../../../lib/str2rgbarray'); + +var arrayCopy1D = arrtools.copy1D; var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; diff --git a/src/gl3d/defaults/gl3dlayout.js b/src/plots/gl3d/layout/defaults.js similarity index 68% rename from src/gl3d/defaults/gl3dlayout.js rename to src/plots/gl3d/layout/defaults.js index 70ca6eba11d..c6937f73c4f 100644 --- a/src/gl3d/defaults/gl3dlayout.js +++ b/src/plots/gl3d/layout/defaults.js @@ -1,33 +1,14 @@ 'use strict'; -var Plotly = require('../../plotly'); - -var Gl3dLayout = module.exports = {}; - -Plotly.Plots.registerSubplot('gl3d', 'scene', 'scene', { - scene: { - valType: 'sceneid', - role: 'info', - dflt: 'scene', - description: [ - 'Sets a reference between this trace\'s 3D coordinate system and', - 'a 3D scene.', - 'If *scene* (the default value), the (x,y,z) coordinates refer to', - '`layout.scene`.', - 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', - 'and so on.' - ].join(' ') - } -}); - -Gl3dLayout.layoutAttributes = require('../attributes/gl3dlayout'); +var Plotly = require('../../../plotly'); +var layoutAttributes = require('./attributes'); +var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); -Gl3dLayout.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { if (!layoutOut._hasGL3D) return; var scenes = Plotly.Plots.getSubplotIdsInData(fullData, 'gl3d'); - var attributes = Gl3dLayout.layoutAttributes; var i; // until they play better together @@ -38,8 +19,7 @@ Gl3dLayout.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { var scenesLength = scenes.length; function coerce(attr, dflt) { - return Plotly.Lib.coerce(sceneLayoutIn, sceneLayoutOut, - attributes, attr, dflt); + return Plotly.Lib.coerce(sceneLayoutIn, sceneLayoutOut, layoutAttributes, attr, dflt); } for (i = 0; i < scenesLength; ++i) { @@ -63,7 +43,7 @@ Gl3dLayout.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { coerce('bgcolor'); - var cameraKeys = Object.keys(attributes.camera); + var cameraKeys = Object.keys(layoutAttributes.camera); for(var j = 0; j < cameraKeys.length; j++) { coerce('camera.' + cameraKeys[j] + '.x'); @@ -104,7 +84,7 @@ Gl3dLayout.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { * x:[0,1] -> x:[0,0.5], x:[0.5,1] -> * x:[0, 0.333] x:[0.333,0.666] x:[0.666, 1] */ - Plotly.Gl3dAxes.supplyLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { + supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { font: layoutOut.font, scene: scene, data: fullData @@ -113,13 +93,3 @@ Gl3dLayout.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { layoutOut[scene] = sceneLayoutOut; } }; - -// Clean scene ids, 'scene1' -> 'scene' -Gl3dLayout.cleanId = function cleanId(id) { - if (!id.match(/^scene[0-9]*$/)) return; - - var sceneNum = id.substr(5); - if (sceneNum === '1') sceneNum = ''; - - return 'scene' + sceneNum; -}; diff --git a/src/plots/gl3d/layout/layout.js b/src/plots/gl3d/layout/layout.js new file mode 100644 index 00000000000..b9fc14440ce --- /dev/null +++ b/src/plots/gl3d/layout/layout.js @@ -0,0 +1,48 @@ +'use strict'; + +var Plotly = require('../../../plotly'); +var traceAttributes = require('./trace_attributes'); + +var axesNames = ['xaxis', 'yaxis', 'zaxis']; +var noop = function () {}; + +Plotly.Plots.registerSubplot('gl3d', 'scene', 'scene', traceAttributes); + +exports.layoutAttributes = require('./attributes'); + +exports.supplyLayoutDefaults = require('./defaults'); + +// clean scene ids, 'scene1' -> 'scene' +exports.cleanId = function cleanId(id) { + if (!id.match(/^scene[0-9]*$/)) return; + + var sceneNum = id.substr(5); + if (sceneNum === '1') sceneNum = ''; + + return 'scene' + sceneNum; +}; + +exports.setConvert = function(containerOut) { + Plotly.Axes.setConvert(containerOut); + containerOut.setScale = noop; +}; + +exports.initAxes = function (td) { + var fullLayout = td._fullLayout; + + // until they play better together + delete fullLayout.xaxis; + delete fullLayout.yaxis; + + var sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'); + + for (var i = 0; i < sceneIds.length; ++i) { + var sceneId = sceneIds[i]; + var sceneLayout = fullLayout[sceneId]; + for (var j = 0; j < 3; ++j) { + var axisName = axesNames[j]; + var ax = sceneLayout[axisName]; + ax._td = td; + } + } +}; diff --git a/src/gl3d/convert/spikes.js b/src/plots/gl3d/layout/spikes.js similarity index 93% rename from src/gl3d/convert/spikes.js rename to src/plots/gl3d/layout/spikes.js index 98da3b78f93..f1b4cf5bbbb 100644 --- a/src/gl3d/convert/spikes.js +++ b/src/plots/gl3d/layout/spikes.js @@ -1,6 +1,6 @@ 'use strict'; -var str2RGBArray = require('../lib/str2rgbarray'); +var str2RGBArray = require('../../../lib/str2rgbarray'); var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; diff --git a/src/gl3d/lib/tick-marks.js b/src/plots/gl3d/layout/tick_marks.js similarity index 96% rename from src/gl3d/lib/tick-marks.js rename to src/plots/gl3d/layout/tick_marks.js index 7d067007b67..42f3212b3b4 100644 --- a/src/gl3d/lib/tick-marks.js +++ b/src/plots/gl3d/layout/tick_marks.js @@ -4,8 +4,8 @@ module.exports = computeTickMarks; -var Plotly = require('../../plotly'); -var convertHTML = require('./html2unicode'); +var Plotly = require('../../../plotly'); +var convertHTML = require('../../../lib/html2unicode'); var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; diff --git a/src/plots/gl3d/layout/trace_attributes.js b/src/plots/gl3d/layout/trace_attributes.js new file mode 100644 index 00000000000..13f119f6646 --- /dev/null +++ b/src/plots/gl3d/layout/trace_attributes.js @@ -0,0 +1,15 @@ +module.exports = { + scene: { + valType: 'sceneid', + role: 'info', + dflt: 'scene', + description: [ + 'Sets a reference between this trace\'s 3D coordinate system and', + 'a 3D scene.', + 'If *scene* (the default value), the (x,y,z) coordinates refer to', + '`layout.scene`.', + 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', + 'and so on.' + ].join(' ') + } +}; diff --git a/src/gl3d/lib/project.js b/src/plots/gl3d/project.js similarity index 100% rename from src/gl3d/lib/project.js rename to src/plots/gl3d/project.js diff --git a/src/gl3d/scene.js b/src/plots/gl3d/scene.js similarity index 96% rename from src/gl3d/scene.js rename to src/plots/gl3d/scene.js index acbdb2f5343..4825bec0ec3 100644 --- a/src/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -1,21 +1,26 @@ /* jshint shadow: true */ 'use strict'; -var createPlot = require('gl-plot3d'), - createAxesOptions = require('./convert/axes'), - createSpikeOptions = require('./convert/spikes'), - createScatterTrace = require('./convert/scatter'), - createSurfaceTrace = require('./convert/surface'), - createMeshTrace = require('./convert/mesh'), - computeTickMarks = require('./lib/tick-marks'), - createCamera = require('./lib/camera'), - str2RGBAarray = require('./lib/str2rgbarray'), - project = require('./lib/project'), - showNoWebGlMsg = require('./lib/show_no_webgl_msg'), - Plotly = require('../plotly'); +var Plotly = require('../../plotly'); +var createPlot = require('gl-plot3d'); + +var createAxesOptions = require('./layout/convert'); +var createSpikeOptions = require('./layout/spikes'); +var computeTickMarks = require('./layout/tick_marks'); + +var createScatterTrace = require('../../traces/scatter3d/convert'); +var createSurfaceTrace = require('../../traces/surface/convert'); +var createMeshTrace = require('../../traces/mesh3d/convert'); + +var createCamera = require('./camera'); +var project = require('./project'); + +var str2RGBAarray = require('../../lib/str2rgbarray'); +var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); var STATIC_CANVAS, STATIC_CONTEXT; + function render(scene) { //Update size of svg container @@ -297,7 +302,7 @@ proto.plot = function(sceneData, fullLayout, layout) { // Update axes functions BEFORE updating traces for (i = 0; i < 3; ++i) { var axis = fullSceneLayout[axisProperties[i]]; - Plotly.Gl3dAxes.setConvert(axis); + Plotly.Gl3dLayout.setConvert(axis); } //Convert scene data From 9edbce3f26ffd43c1d7d9d56d0d58f7757636bb6 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:40:05 -0500 Subject: [PATCH 18/38] break attributes from axes.js --- src/plots/cartesian/attributes.js | 453 ++++ src/plots/cartesian/axes.js | 2681 +++++++++++++++++++++++ src/plots/cartesian/trace_attributes.js | 26 + 3 files changed, 3160 insertions(+) create mode 100644 src/plots/cartesian/attributes.js create mode 100644 src/plots/cartesian/axes.js create mode 100644 src/plots/cartesian/trace_attributes.js diff --git a/src/plots/cartesian/attributes.js b/src/plots/cartesian/attributes.js new file mode 100644 index 00000000000..2bdf3f57ee1 --- /dev/null +++ b/src/plots/cartesian/attributes.js @@ -0,0 +1,453 @@ +var Plotly = require('../../plotly'); +var fontAttrs = require('../plots/font_attributes'); +var colorAttrs = require('../../components/color/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; + + +module.exports = { + title: { + valType: 'string', + role: 'info', + description: 'Sets the title of this axis.' + }, + titlefont: extendFlat({}, fontAttrs, { + description: [ + 'Sets this axis\' title font.' + ].join(' ') + }), + type: { + valType: 'enumerated', + // '-' means we haven't yet run autotype or couldn't find any data + // it gets turned into linear in td._fullLayout but not copied back + // to td.data like the others are. + values: ['-', 'linear', 'log', 'date', 'category'], + dflt: '-', + role: 'info', + description: [ + 'Sets the axis type.', + 'By default, plotly attempts to determined the axis type', + 'by looking into the data of the traces that referenced', + 'the axis in question.' + ].join(' ') + }, + autorange: { + valType: 'enumerated', + values: [true, false, 'reversed'], + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the range of this axis is', + 'computed in relation to the input data.', + 'See `rangemode` for more info.', + 'If `range` is provided, then `autorange` is set to *false*.' + ].join(' ') + }, + rangemode: { + valType: 'enumerated', + values: ['normal', 'tozero', 'nonnegative'], + dflt: 'normal', + role: 'style', + description: [ + 'If *normal*, the range is computed in relation to the extrema', + 'of the input data.', + 'If *tozero*`, the range extends to 0,', + 'regardless of the input data', + 'If *nonnegative*, the range is non-negative,', + 'regardless of the input data.' + ].join(' ') + }, + range: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number'}, + {valType: 'number'} + ], + description: [ + 'Sets the range of this axis.', + 'If the axis `type` is *log*, then you must take the log of your desired range', + '(e.g. to set the range from 1 to 100, set the range from 0 to 2).', + 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', + '(the number of milliseconds since January 1st, 1970). For example, to set the date range from', + 'January 1st 1970 to November 4th, 2013, set the range from 0 to 1380844800000.0' + ].join(' ') + }, + fixedrange: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not this axis is zoom-able.', + 'If true, then zoom is disabled.' + ].join(' ') + }, + // ticks + tickmode: { + valType: 'enumerated', + values: ['auto', 'linear', 'array'], + role: 'info', + description: [ + 'Sets the tick mode for this axis.', + 'If *auto*, the number of ticks is set via `nticks`.', + 'If *linear*, the placement of the ticks is determined by', + 'a starting position `tick0` and a tick step `dtick`', + '(*linear* is the default value if `tick0` and `dtick` are provided).', + 'If *array*, the placement of the ticks is set via `tickvals`', + 'and the tick text is `ticktext`.', + '(*array* is the default value if `tickvals` is provided).' + ].join(' ') + }, + nticks: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Sets the number of ticks.', + 'Has an effect only if `tickmode` is set to *auto*.' + ].join(' ') + }, + tick0: { + valType: 'number', + dflt: 0, + role: 'style', + description: [ + 'Sets the placement of the first tick on this axis.', + 'Use with `dtick`.', + 'If the axis `type` is *log*, then you must take the log of your starting tick', + '(e.g. to set the starting tick to 100, set the `tick0` to 2).', + 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', + '(the number of milliseconds since January 1st, 1970).', + 'For example, to set the starting tick to', + 'November 4th, 2013, set the range to 1380844800000.0.' + ].join(' ') + }, + dtick: { + valType: 'any', + dflt: 1, + role: 'style', + description: [ + 'Sets the step in-between ticks on this axis', + 'Use with `tick0`.', + 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', + 'is the tick number. For example,', + 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', + 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', + 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', + 'If the axis `type` is *date*, then you must convert the time to milliseconds.', + 'For example, to set the interval between ticks to one day,', + 'set `dtick` to 86400000.0.' + ].join(' ') + }, + tickvals: { + valType: 'data_array', + description: [ + 'Sets the values at which ticks on this axis appear.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `ticktext`.' + ].join(' ') + }, + ticktext: { + valType: 'data_array', + description: [ + 'Sets the text displayed at the ticks position via `tickvals`.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `ticktext`.' + ].join(' ') + }, + ticks: { + valType: 'enumerated', + values: ['outside', 'inside', ''], + role: 'style', + description: [ + 'Determines whether ticks are drawn or not.', + 'If **, this axis\' ticks are not drawn.', + 'If *outside* (*inside*), this axis\' are drawn outside (inside)', + 'the axis lines.' + ].join(' ') + }, + mirror: { + valType: 'enumerated', + values: [true, 'ticks', false, 'all', 'allticks'], + dflt: false, + role: 'style', + description: [ + 'Determines if the axis lines or/and ticks are mirrored to', + 'the opposite side of the plotting area.', + 'If *true*, the axis lines are mirrored.', + 'If *ticks*, the axis lines and ticks are mirrored.', + 'If *false*, mirroring is disable.', + 'If *all*, axis lines are mirrored on all shared-axes subplots.', + 'If *allticks*, axis lines and ticks are mirrored', + 'on all shared-axes subplots.' + ].join(' ') + }, + ticklen: { + valType: 'number', + min: 0, + dflt: 5, + role: 'style', + description: 'Sets the tick length (in px).' + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the tick width (in px).' + }, + tickcolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the tick color.' + }, + showticklabels: { + valType: 'boolean', + dflt: true, + role: 'style', + description: 'Determines whether or not the tick labels are drawn.' + }, + tickfont: extendFlat({}, fontAttrs, { + description: 'Sets the tick font.' + }), + tickangle: { + valType: 'angle', + dflt: 'auto', + role: 'style', + description: [ + 'Sets the angle of the tick labels with respect to the horizontal.', + 'For example, a `tickangle` of -90 draws the tick labels', + 'vertically.' + ].join(' ') + }, + tickprefix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label prefix.' + }, + showtickprefix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all tick labels are displayed with a prefix.', + 'If *first*, only the first tick is displayed with a prefix.', + 'If *last*, only the last tick is displayed with a suffix.', + 'If *none*, tick prefixes are hidden.' + ].join(' ') + }, + ticksuffix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label suffix.' + }, + showticksuffix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: 'Same as `showtickprefix` but for tick suffixes.' + }, + showexponent: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all exponents are shown besides their significands.', + 'If *first*, only the exponent of the first tick is shown.', + 'If *last*, only the exponent of the last tick is shown.', + 'If *none*, no exponents appear.' + ].join(' ') + }, + exponentformat: { + valType: 'enumerated', + values: ['none', 'e', 'E', 'power', 'SI', 'B'], + dflt: 'B', + role: 'style', + description: [ + 'Determines a formatting rule for the tick exponents.', + 'For example, consider the number 1,000,000,000.', + 'If *none*, it appears as 1,000,000,000.', + 'If *e*, 1e+9.', + 'If *E*, 1E+9.', + 'If *power*, 1x10^9 (with 9 in a super script).', + 'If *SI*, 1G.', + 'If *B*, 1B.' + ].join(' ') + }, + tickformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the tick label formatting rule using the', + 'python/d3 number formatting language.', + 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', + 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', + 'for more info.' + ].join(' ') + }, + hoverformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the hover text formatting rule for data values on this axis,', + 'using the python/d3 number formatting language.', + 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', + 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', + 'for more info.' + ].join(' ') + }, + // lines and grids + showline: { + valType: 'boolean', + dflt: false, + role: 'style', + description: [ + 'Determines whether or not a line bounding this axis is drawn.' + ].join(' ') + }, + linecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the axis line color.' + }, + linewidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the axis line.' + }, + showgrid: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not grid lines are drawn.', + 'If *true*, the grid lines are drawn at every tick mark.' + ].join(' ') + }, + gridcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + role: 'style', + description: 'Sets the color of the grid lines.' + }, + gridwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the grid lines.' + }, + zeroline: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not a line is drawn at along the 0 value', + 'of this axis.', + 'If *true*, the zero line is drawn on top of the grid lines.' + ].join(' ') + }, + zerolinecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the line color of the zero line.' + }, + zerolinewidth: { + valType: 'number', + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the zero line.' + }, + // positioning attributes + // anchor: not used directly, just put here for reference + // values are any opposite-letter axis id + anchor: { + valType: 'enumerated', + values: [ + 'free', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), + Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', + 'the corresponding opposite-letter axis.', + 'If set to *free*, this axis\' position is determined by `position`.' + ].join(' ') + }, + // side: not used directly, as values depend on direction + // values are top, bottom for x axes, and left, right for y + side: { + valType: 'enumerated', + values: ['top', 'bottom', 'left', 'right'], + role: 'info', + description: [ + 'Determines whether a x (y) axis is positioned', + 'at the *bottom* (*left*) or *top* (*right*)', + 'of the plotting area.' + ].join(' ') + }, + // overlaying: not used directly, just put here for reference + // values are false and any other same-letter axis id that's not + // itself overlaying anything + overlaying: { + valType: 'enumerated', + values: [ + 'free', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), + Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set a same-letter axis id, this axis is overlaid on top of', + 'the corresponding same-letter axis.', + 'If *false*, this axis does not overlay any same-letter axes.' + ].join(' ') + }, + domain: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the domain of this axis (in plot fraction).' + ].join(' ') + }, + position: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + role: 'style', + description: [ + 'Sets the position of this axis in the plotting space', + '(in normalized coordinates).', + 'Only has an effect if `anchor` is set to *free*.' + ].join(' ') + }, + + _deprecated: { + autotick: { + valType: 'boolean', + role: 'info', + description: [ + 'Obsolete.', + 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', + 'Set `tickmode` to *linear* for `autotick` *false*.' + ].join(' ') + } + } +}; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js new file mode 100644 index 00000000000..00a9e962031 --- /dev/null +++ b/src/plots/cartesian/axes.js @@ -0,0 +1,2681 @@ +'use strict'; + +var Plotly = require('../../plotly'); +var d3 = require('d3'); +var isNumeric = require('fast-isnumeric'); + +var axes = module.exports = {}; + +axes.traceAttributes = require('./trace_attributes'); + +Plotly.Plots.registerSubplot('cartesian', ['xaxis', 'yaxis'], ['x', 'y'], + axes.traceAttributes); + +axes.layoutAttributes = require('./attributes'); + +var xAxisMatch = /^xaxis[0-9]*$/, + yAxisMatch = /^yaxis[0-9]*$/; + +axes.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { + // get the full list of axes already defined + var layoutKeys = Object.keys(layoutIn), + xaList = [], + yaList = [], + outerTicks = {}, + noGrids = {}, + i; + + for(i = 0; i < layoutKeys.length; i++) { + var key = layoutKeys[i]; + if(xAxisMatch.test(key)) xaList.push(key); + else if(yAxisMatch.test(key)) yaList.push(key); + } + + for(i = 0; i < fullData.length; i++) { + var trace = fullData[i], + xaName = axes.id2name(trace.xaxis), + yaName = axes.id2name(trace.yaxis); + + // add axes implied by traces + if(xaName && xaList.indexOf(xaName)===-1) xaList.push(xaName); + if(yaName && yaList.indexOf(yaName)===-1) yaList.push(yaName); + + // check for default formatting tweaks + if(Plotly.Plots.traceIs(trace, '2dMap')) { + outerTicks[xaName] = true; + outerTicks[yaName] = true; + } + + if(Plotly.Plots.traceIs(trace, 'oriented')) { + var positionAxis = trace.orientation==='h' ? yaName : xaName; + noGrids[positionAxis] = true; + } + } + + function axSort(a,b) { + var aNum = Number(a.substr(5)||1), + bNum = Number(b.substr(5)||1); + return aNum - bNum; + } + + if(layoutOut._hasCartesian || layoutOut._hasGL2D || !fullData.length) { + // make sure there's at least one of each and lists are sorted + if(!xaList.length) xaList = ['xaxis']; + else xaList.sort(axSort); + + if(!yaList.length) yaList = ['yaxis']; + else yaList.sort(axSort); + } + + xaList.concat(yaList).forEach(function(axName){ + var axLetter = axName.charAt(0), + axLayoutIn = layoutIn[axName] || {}, + axLayoutOut = {}, + defaultOptions = { + letter: axLetter, + font: layoutOut.font, + outerTicks: outerTicks[axName], + showGrid: !noGrids[axName], + name: axName, + data: fullData + }, + positioningOptions = { + letter: axLetter, + counterAxes: {x: yaList, y: xaList}[axLetter].map(axes.name2id), + overlayableAxes: {x: xaList, y: yaList}[axLetter].filter(function(axName2){ + return axName2!==axName && !(layoutIn[axName2]||{}).overlaying; + }).map(axes.name2id) + }; + + function coerce(attr, dflt) { + return Plotly.Lib.coerce(axLayoutIn, axLayoutOut, + axes.layoutAttributes, + attr, dflt); + } + + axes.handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); + axes.handleAxisPositioningDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions); + layoutOut[axName] = axLayoutOut; + + // so we don't have to repeat autotype unnecessarily, + // copy an autotype back to layoutIn + if(!layoutIn[axName] && axLayoutIn.type!=='-') { + layoutIn[axName] = {type: axLayoutIn.type}; + } + + }); + + // plot_bgcolor only makes sense if there's a (2D) plot! + // TODO: bgcolor for each subplot, to inherit from the main one + if(xaList.length && yaList.length) { + Plotly.Lib.coerce(layoutIn, layoutOut, + Plotly.Plots.layoutAttributes, 'plot_bgcolor'); + } +}; + +/** + * options: object containing: + * letter: 'x' or 'y' + * title: name of the axis (ie 'Colorbar') to go in default title + * name: axis object name (ie 'xaxis') if one should be stored + * font: the default font to inherit + * outerTicks: boolean, should ticks default to outside? + * showGrid: boolean, should gridlines be shown by default? + * noHover: boolean, this axis doesn't support hover effects? + * data: the plot data to use in choosing auto type + */ +axes.handleAxisDefaults = function(containerIn, containerOut, coerce, options) { + var letter = options.letter, + font = options.font || {}, + defaultTitle = 'Click to enter ' + + (options.title || (letter.toUpperCase() + ' axis')) + + ' title'; + + // set up some private properties + if(options.name) { + containerOut._name = options.name; + containerOut._id = axes.name2id(options.name); + } + + // now figure out type and do some more initialization + var axType = coerce('type'); + if(axType==='-') { + setAutoType(containerOut, options.data); + + if(containerOut.type==='-') { + containerOut.type = 'linear'; + } + else { + // copy autoType back to input axis + // note that if this object didn't exist + // in the input layout, we have to put it in + // this happens in the main supplyDefaults function + axType = containerIn.type = containerOut.type; + } + } + axes.setConvert(containerOut); + + coerce('title', defaultTitle); + Plotly.Lib.coerceFont(coerce, 'titlefont', { + family: font.family, + size: Math.round(font.size * 1.2), + color: font.color + }); + + var validRange = (containerIn.range||[]).length===2 && + isNumeric(containerIn.range[0]) && + isNumeric(containerIn.range[1]), + autoRange = coerce('autorange', !validRange); + + if(autoRange) coerce('rangemode'); + var range = coerce('range', [-1, letter==='x' ? 6 : 4]); + if(range[0] === range[1]) { + containerOut.range = [range[0] - 1, range[0] + 1]; + } + Plotly.Lib.noneOrAll(containerIn.range, containerOut.range, [0, 1]); + + coerce('fixedrange'); + + axes.handleTickValueDefaults(containerIn, containerOut, coerce, axType); + + axes.handleTickDefaults(containerIn, containerOut, coerce, axType, options); + + + + var lineColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'linecolor'), + lineWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'linewidth'), + showLine = coerce('showline', !!lineColor || !!lineWidth); + + if(!showLine) { + delete containerOut.linecolor; + delete containerOut.linewidth; + } + + if(showLine || containerOut.ticks) coerce('mirror'); + + var gridColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'gridcolor'), + gridWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'gridwidth'), + showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); + + if(!showGridLines) { + delete containerOut.gridcolor; + delete containerOut.gridwidth; + } + + var zeroLineColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'zerolinecolor'), + zeroLineWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'zerolinewidth'), + showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth); + + if(!showZeroLine) { + delete containerOut.zerolinecolor; + delete containerOut.zerolinewidth; + } + + return containerOut; +}; + +/** + * options: inherits font, outerTicks, noHover from axes.handleAxisDefaults + */ +axes.handleTickDefaults = function(containerIn, containerOut, coerce, axType, options) { + var tickLen = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'ticklen'), + tickWidth = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'tickwidth'), + tickColor = Plotly.Lib.coerce2(containerIn, containerOut, axes.layoutAttributes, 'tickcolor'), + showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); + if(!showTicks) { + delete containerOut.ticklen; + delete containerOut.tickwidth; + delete containerOut.tickcolor; + } + + var showTickLabels = coerce('showticklabels'); + if(showTickLabels) { + Plotly.Lib.coerceFont(coerce, 'tickfont', options.font || {}); + coerce('tickangle'); + + var showAttrDflt = axes.getShowAttrDflt(containerIn); + + if(axType !== 'category') { + var tickFormat = coerce('tickformat'); + if(!options.noHover) coerce('hoverformat'); + + if(!tickFormat && axType !== 'date') { + coerce('showexponent', showAttrDflt); + coerce('exponentformat'); + } + } + + var tickPrefix = coerce('tickprefix'); + if(tickPrefix) coerce('showtickprefix', showAttrDflt); + + var tickSuffix = coerce('ticksuffix'); + if(tickSuffix) coerce('showticksuffix', showAttrDflt); + } +}; + +axes.handleTickValueDefaults = function(containerIn, containerOut, coerce, axType) { + var tickmodeDefault = 'auto'; + + if(containerIn.tickmode === 'array' && + (axType === 'log' || axType === 'date')) { + containerIn.tickmode = 'auto'; + } + + if(Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array'; + else if(containerIn.dtick && isNumeric(containerIn.dtick)) { + tickmodeDefault = 'linear'; + } + var tickmode = coerce('tickmode', tickmodeDefault); + + if(tickmode === 'auto') coerce('nticks'); + else if(tickmode === 'linear') { + coerce('tick0'); + coerce('dtick'); + } + else { + var tickvals = coerce('tickvals'); + if(tickvals === undefined) containerOut.tickmode = 'auto'; + else coerce('ticktext'); + } +}; + +axes.handleAxisPositioningDefaults = function(containerIn, containerOut, coerce, options) { + var counterAxes = options.counterAxes || [], + overlayableAxes = options.overlayableAxes || [], + letter = options.letter; + + var anchor = Plotly.Lib.coerce(containerIn, containerOut, + { + anchor: { + valType:'enumerated', + values: ['free'].concat(counterAxes), + dflt: isNumeric(containerIn.position) ? 'free' : + (counterAxes[0] || 'free') + } + }, + 'anchor'); + + if(anchor==='free') coerce('position'); + + Plotly.Lib.coerce(containerIn, containerOut, + { + side: { + valType: 'enumerated', + values: letter==='x' ? ['bottom', 'top'] : ['left', 'right'], + dflt: letter==='x' ? 'bottom' : 'left' + } + }, + 'side'); + + var overlaying = false; + if(overlayableAxes.length) { + overlaying = Plotly.Lib.coerce(containerIn, containerOut, + { + overlaying: { + valType: 'enumerated', + values: [false].concat(overlayableAxes), + dflt: false + } + }, + 'overlaying'); + } + + if(!overlaying) { + // TODO: right now I'm copying this domain over to overlaying axes + // in ax.setscale()... but this means we still need (imperfect) logic + // in the axes popover to hide domain for the overlaying axis. + // perhaps I should make a private version _domain that all axes get??? + var domain = coerce('domain'); + if(domain[0] > domain[1] - 0.01) containerOut.domain = [0,1]; + Plotly.Lib.noneOrAll(containerIn.domain, containerOut.domain, [0, 1]); + } + + return containerOut; +}; + +// find the list of possible axes to reference with an xref or yref attribute +// and coerce it to that list +axes.coerceRef = function(containerIn, containerOut, td, axLetter) { + var axlist = td._fullLayout._hasGL2D ? [] : axes.listIds(td, axLetter), + refAttr = axLetter + 'ref', + attrDef = {}; + + // data-ref annotations are not supported in gl2d yet + + attrDef[refAttr] = { + valType: 'enumerated', + values: axlist.concat(['paper']), + dflt: axlist[0] || 'paper' + }; + + // xref, yref + return Plotly.Lib.coerce(containerIn, containerOut, attrDef, refAttr); +}; + +// empty out types for all axes containing these traces +// so we auto-set them again +axes.clearTypes = function(gd, traces) { + if(!Array.isArray(traces) || !traces.length) { + traces = (gd._fullData).map(function(d,i) { return i; }); + } + traces.forEach(function(tracenum) { + var trace = gd.data[tracenum]; + delete (axes.getFromId(gd, trace.xaxis)||{}).type; + delete (axes.getFromId(gd, trace.yaxis)||{}).type; + }); +}; + +// convert between axis names (xaxis, xaxis2, etc, elements of td.layout) +// and axis id's (x, x2, etc). Would probably have ditched 'xaxis' +// completely in favor of just 'x' if it weren't ingrained in the API etc. +var AX_ID_PATTERN = /^[xyz][0-9]*$/, + AX_NAME_PATTERN = /^[xyz]axis[0-9]*$/; +axes.id2name = function(id) { + if(typeof id !== 'string' || !id.match(AX_ID_PATTERN)) return; + var axNum = id.substr(1); + if(axNum==='1') axNum = ''; + return id.charAt(0) + 'axis' + axNum; +}; + +axes.name2id = function(name) { + if(!name.match(AX_NAME_PATTERN)) return; + var axNum = name.substr(5); + if(axNum==='1') axNum = ''; + return name.charAt(0)+axNum; +}; + +axes.cleanId = function(id, axLetter) { + if(!id.match(AX_ID_PATTERN)) return; + if(axLetter && id.charAt(0)!==axLetter) return; + + var axNum = id.substr(1).replace(/^0+/,''); + if(axNum==='1') axNum = ''; + return id.charAt(0) + axNum; +}; + +axes.cleanName = function(name, axLetter) { + if(!name.match(AX_ID_PATTERN)) return; + if(axLetter && name.charAt(0)!==axLetter) return; + + var axNum = name.substr(5).replace(/^0+/,''); + if(axNum==='1') axNum = ''; + return name.charAt(0) + 'axis' + axNum; +}; + +// get counteraxis letter for this axis (name or id) +// this can also be used as the id for default counter axis +axes.counterLetter = function(id) { + return {x:'y',y:'x'}[id.charAt(0)]; +}; + +function setAutoType(ax, data){ + // new logic: let people specify any type they want, + // only autotype if type is '-' + if(ax.type!=='-') return; + + var id = ax._id, + axLetter = id.charAt(0); + + // support 3d + if(id.indexOf('scene') !== -1) id = axLetter; + + var d0 = getFirstNonEmptyTrace(data, id, axLetter); + if(!d0) return; + + // first check for histograms, as the count direction + // should always default to a linear axis + if(d0.type==='histogram' && + axLetter==={v:'y', h:'x'}[d0.orientation || 'v']) { + ax.type='linear'; + return; + } + + // check all boxes on this x axis to see + // if they're dates, numbers, or categories + if(isBoxWithoutPositionCoords(d0, axLetter)) { + var posLetter = getBoxPosLetter(d0), + boxPositions = [], + trace; + + for(var i = 0; i < data.length; i++) { + trace = data[i]; + if(!Plotly.Plots.traceIs(trace, 'box') || + (trace[axLetter + 'axis'] || axLetter) !== id) continue; + + if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]); + else if(trace.name !== undefined) boxPositions.push(trace.name); + else boxPositions.push('text'); + } + + ax.type = axes.autoType(boxPositions); + } + else { + ax.type = axes.autoType(d0[axLetter] || [d0[axLetter+'0']]); + } +} + +function getBoxPosLetter(trace) { + return {v:'x', h:'y'}[trace.orientation || 'v']; +} + +function isBoxWithoutPositionCoords(trace, axLetter) { + var posLetter = getBoxPosLetter(trace); + return Plotly.Plots.traceIs(trace, 'box') && axLetter===posLetter && + trace[posLetter]===undefined && trace[posLetter + '0']===undefined; +} + +function getFirstNonEmptyTrace(data, id, axLetter) { + var trace; + + for(var i = 0; i < data.length; i++) { + trace = data[i]; + + if((trace[axLetter + 'axis'] || axLetter) === id) { + if(isBoxWithoutPositionCoords(trace, axLetter)) { + return trace; + } + else if((trace[axLetter] || []).length || trace[axLetter + '0']) { + return trace; + } + } + } +} + +axes.autoType = function(array) { + if(axes.moreDates(array)) return 'date'; + if(axes.category(array)) return 'category'; + if(linearOK(array)) return 'linear'; + else return '-'; +}; + +/* + * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix' + * share values. + * + * If only 1 attribute is set, + * the remaining attributes inherit that value. + * + * If 2 attributes are set to the same value, + * the remaining attribute inherits that value. + * + * If 2 attributes are set to different values, + * the remaining is set to its dflt value. + * + */ +axes.getShowAttrDflt = function getShowAttrDflt(containerIn) { + var showAttrsAll = ['showexponent', + 'showtickprefix', + 'showticksuffix'], + showAttrs = showAttrsAll.filter(function(a){ + return containerIn[a]!==undefined; + }), + sameVal = function(a){ + return containerIn[a]===containerIn[showAttrs[0]]; + }; + if (showAttrs.every(sameVal) || showAttrs.length===1) { + return containerIn[showAttrs[0]]; + } +}; + +// is there at least one number in array? If not, we should leave +// ax.type empty so it can be autoset later +function linearOK(array) { + if(!array) return false; + for(var i = 0; i < array.length; i++) { + if(isNumeric(array[i])) return true; + } + return false; +} + +// does the array a have mostly dates rather than numbers? +// note: some values can be neither (such as blanks, text) +// 2- or 4-digit integers can be both, so require twice as many +// dates as non-dates, to exclude cases with mostly 2 & 4 digit +// numbers and a few dates +axes.moreDates = function(a) { + var dcnt=0, ncnt=0, + // test at most 1000 points, evenly spaced + inc = Math.max(1,(a.length-1)/1000), + ai; + for(var i=0; incnt*2); +}; + +// are the (x,y)-values in td.data mostly text? +// require twice as many categories as numbers +axes.category = function(a) { + // test at most 1000 points + var inc = Math.max(1, (a.length - 1) / 1000), + curvenums = 0, + curvecats = 0, + ai; + + for(var i = 0; i < a.length; i += inc) { + ai = axes.cleanDatum(a[Math.round(i)]); + if(isNumeric(ai)) curvenums++; + else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++; + } + return curvecats > curvenums * 2; +}; + +// cleanDatum: removes characters +// same replace criteria used in the grid.js:scrapeCol +// but also handling dates, numbers, and NaN, null, Infinity etc +axes.cleanDatum = function(c){ + try{ + if(typeof c==='object' && c!==null && c.getTime) { + return Plotly.Lib.ms2DateTime(c); + } + if(typeof c!=='string' && !isNumeric(c)) { + return ''; + } + c = c.toString().replace(/['"%,$# ]/g,''); + }catch(e){ + console.log(e,c); + } + return c; +}; + +/** + * standardize all missing data in calcdata to use undefined + * never null or NaN. + * that way we can use !==undefined, or !==axes.BADNUM, + * to test for real data + */ +axes.BADNUM = undefined; + +// setConvert: define the conversion functions for an axis +// data is used in 4 ways: +// d: data, in whatever form it's provided +// c: calcdata: turned into numbers, but not linearized +// l: linearized - same as c except for log axes (and other +// mappings later?) this is used by ranges, and when we +// need to know if it's *possible* to show some data on +// this axis, without caring about the current range +// p: pixel value - mapped to the screen with current size and zoom +// setAxConvert creates/updates these conversion functions +// also clears the autorange bounds ._min and ._max +// and the autotick constraints ._minDtick, ._forceTick0, +// and looks for date ranges that aren't yet in numeric format +axes.setConvert = function(ax) { + // clipMult: how many axis lengths past the edge do we render? + // for panning, 1-2 would suffice, but for zooming more is nice. + // also, clipping can affect the direction of lines off the edge... + var clipMult = 10; + + function toLog(v, clip){ + if(v>0) return Math.log(v)/Math.LN10; + + else if(v<=0 && clip && ax.range && ax.range.length===2) { + // clip NaN (ie past negative infinity) to clipMult axis + // length past the negative edge + var r0 = ax.range[0], + r1 = ax.range[1]; + return 0.5*(r0 + r1 - 3 * clipMult * Math.abs(r0 - r1)); + } + + else return axes.BADNUM; + } + function fromLog(v){ return Math.pow(10,v); } + function num(v){ return isNumeric(v) ? Number(v) : axes.BADNUM; } + + ax.c2l = (ax.type==='log') ? toLog : num; + ax.l2c = (ax.type==='log') ? fromLog : num; + ax.l2d = function(v) { return ax.c2d(ax.l2c(v)); }; + + // set scaling to pixels + ax.setScale = function(){ + var gs = ax._td._fullLayout._size, + i; + + // TODO cleaner way to handle this case + if (!ax._categories) ax._categories = []; + + // make sure we have a domain (pull it in from the axis + // this one is overlaying if necessary) + if(ax.overlaying) { + var ax2 = axes.getFromId(ax._td, ax.overlaying); + ax.domain = ax2.domain; + } + + // make sure we have a range (linearized data values) + // and that it stays away from the limits of javascript numbers + if(!ax.range || ax.range.length!==2 || ax.range[0]===ax.range[1]) { + ax.range = [-1,1]; + } + for(i=0; i<2; i++) { + if(!isNumeric(ax.range[i])) { + ax.range[i] = isNumeric(ax.range[1-i]) ? + (ax.range[1-i] * (i ? 10 : 0.1)) : + (i ? 1 : -1); + } + + if(ax.range[i]<-(Number.MAX_VALUE/2)) { + ax.range[i] = -(Number.MAX_VALUE/2); + } + else if(ax.range[i]>Number.MAX_VALUE/2) { + ax.range[i] = Number.MAX_VALUE/2; + } + + } + + if(ax._id.charAt(0)==='y') { + ax._offset = gs.t+(1-ax.domain[1])*gs.h; + ax._length = gs.h*(ax.domain[1]-ax.domain[0]); + ax._m = ax._length/(ax.range[0]-ax.range[1]); + ax._b = -ax._m*ax.range[1]; + } + else { + ax._offset = gs.l+ax.domain[0]*gs.w; + ax._length = gs.w*(ax.domain[1]-ax.domain[0]); + ax._m = ax._length/(ax.range[1]-ax.range[0]); + ax._b = -ax._m*ax.range[0]; + } + + if (!isFinite(ax._m) || !isFinite(ax._b)) { + Plotly.Lib.notifier( + 'Something went wrong with axis scaling', + 'long'); + ax._td._replotting = false; + throw new Error('axis scaling'); + } + }; + + ax.l2p = function(v) { + if(!isNumeric(v)) return axes.BADNUM; + // include 2 fractional digits on pixel, for PDF zooming etc + return d3.round(Plotly.Lib.constrain(ax._b + ax._m*v, + -clipMult*ax._length, (1+clipMult)*ax._length), 2); + }; + + ax.p2l = function(px) { return (px-ax._b)/ax._m; }; + + ax.c2p = function(v, clip) { return ax.l2p(ax.c2l(v, clip)); }; + ax.p2c = function(px){ return ax.l2c(ax.p2l(px)); }; + + if(['linear','log','-'].indexOf(ax.type)!==-1) { + ax.c2d = num; + ax.d2c = function(v){ + v = axes.cleanDatum(v); + return isNumeric(v) ? Number(v) : axes.BADNUM; + }; + ax.d2l = function (v, clip) { + if (ax.type === 'log') return ax.c2l(ax.d2c(v), clip); + else return ax.d2c(v); + }; + } + else if(ax.type==='date') { + ax.c2d = function(v) { + return isNumeric(v) ? Plotly.Lib.ms2DateTime(v) : axes.BADNUM; + }; + + ax.d2c = function(v){ + return (isNumeric(v)) ? Number(v) : Plotly.Lib.dateTime2ms(v); + }; + + ax.d2l = ax.d2c; + + // check if date strings or js date objects are provided for range + // and convert to ms + if(ax.range && ax.range.length>1) { + try { + var ar1 = ax.range.map(Plotly.Lib.dateTime2ms); + if(!isNumeric(ax.range[0]) && isNumeric(ar1[0])) { + ax.range[0] = ar1[0]; + } + if(!isNumeric(ax.range[1]) && isNumeric(ar1[1])) { + ax.range[1] = ar1[1]; + } + } + catch(e) { console.log(e, ax.range); } + } + } + else if(ax.type==='category') { + + ax.c2d = function(v) { + return ax._categories[Math.round(v)]; + }; + + ax.d2c = function(v) { + // create the category list + // this will enter the categories in the order it + // encounters them, ie all the categories from the + // first data set, then all the ones from the second + // that aren't in the first etc. + // TODO: sorting options - do the sorting + // progressively here as we insert? + if(ax._categories.indexOf(v)===-1) ax._categories.push(v); + + var c = ax._categories.indexOf(v); + return c===-1 ? axes.BADNUM : c; + }; + + ax.d2l = ax.d2c; + } + + // makeCalcdata: takes an x or y array and converts it + // to a position on the axis object "ax" + // inputs: + // tdc - a data object from td.data + // axletter - a string, either 'x' or 'y', for which item + // to convert (TODO: is this now always the same as + // the first letter of ax._id?) + // in case the expected data isn't there, make a list of + // integers based on the opposite data + ax.makeCalcdata = function(tdc, axletter) { + var arrayIn, arrayOut, i; + + if(axletter in tdc) { + arrayIn = tdc[axletter]; + arrayOut = new Array(arrayIn.length); + + for(i = 0; i < arrayIn.length; i++) arrayOut[i] = ax.d2c(arrayIn[i]); + } + else { + var v0 = ((axletter+'0') in tdc) ? + ax.d2c(tdc[axletter+'0']) : 0, + dv = (tdc['d'+axletter]) ? + Number(tdc['d'+axletter]) : 1; + + // the opposing data, for size if we have x and dx etc + arrayIn = tdc[{x: 'y',y: 'x'}[axletter]]; + arrayOut = new Array(arrayIn.length); + + for(i = 0; i < arrayIn.length; i++) arrayOut[i] = v0+i*dv; + } + return arrayOut; + }; + + // for autoranging: arrays of objects: + // {val: axis value, pad: pixel padding} + // on the low and high sides + ax._min = []; + ax._max = []; + + // and for bar charts and box plots: reset forced minimum tick spacing + ax._minDtick = null; + ax._forceTick0 = null; +}; + +// incorporate a new minimum difference and first tick into +// forced +axes.minDtick = function(ax,newDiff,newFirst,allow) { + // doesn't make sense to do forced min dTick on log or category axes, + // and the plot itself may decide to cancel (ie non-grouped bars) + if(['log','category'].indexOf(ax.type)!==-1 || !allow) { + ax._minDtick = 0; + } + // null means there's nothing there yet + else if(ax._minDtick===null) { + ax._minDtick = newDiff; + ax._forceTick0 = newFirst; + } + else if(ax._minDtick) { + // existing minDtick is an integer multiple of newDiff + // (within rounding err) + // and forceTick0 can be shifted to newFirst + if((ax._minDtick/newDiff+1e-6)%1 < 2e-6 && + (((newFirst-ax._forceTick0)/newDiff%1) + + 1.000001) % 1 < 2e-6) { + ax._minDtick = newDiff; + ax._forceTick0 = newFirst; + } + // if the converse is true (newDiff is a multiple of minDtick and + // newFirst can be shifted to forceTick0) then do nothing - same + // forcing stands. Otherwise, cancel forced minimum + else if((newDiff/ax._minDtick+1e-6)%1 > 2e-6 || + (((newFirst-ax._forceTick0)/ax._minDtick%1) + + 1.000001) % 1 > 2e-6) { + ax._minDtick = 0; + } + } +}; + +axes.doAutoRange = function(ax) { + if(!ax._length) ax.setScale(); + + if(ax.autorange && ax._min && ax._max && + ax._min.length && ax._max.length) { + var minmin = ax._min[0].val, + maxmax = ax._max[0].val, + i; + + for(i = 1; i < ax._min.length; i++) { + if(minmin !== maxmax) break; + minmin = Math.min(minmin, ax._min[i].val); + } + for(i = 1; i < ax._max.length; i++) { + if(minmin !== maxmax) break; + maxmax = Math.max(maxmax, ax._max[i].val); + } + + var j,minpt,maxpt,minbest,maxbest,dp,dv, + mbest = 0, + axReverse = (ax.range && ax.range[1]0 && dp>0 && dv/dp > mbest) { + minbest = minpt; + maxbest = maxpt; + mbest = dv/dp; + } + } + } + if(minmin===maxmax) { + ax.range = axReverse ? + [minmin+1, ax.rangemode!=='normal' ? 0 : minmin-1] : + [ax.rangemode!=='normal' ? 0 : minmin-1, minmin+1]; + } + else if(mbest) { + if(ax.type==='linear' || ax.type==='-') { + if(ax.rangemode==='tozero' && minbest.val>=0) { + minbest = {val:0, pad:0}; + } + else if(ax.rangemode==='nonnegative') { + if(minbest.val - mbest*minbest.pad<0) { + minbest = {val:0, pad:0}; + } + if(maxbest.val<0) { + maxbest = {val:1, pad:0}; + } + } + + // in case it changed again... + mbest = (maxbest.val-minbest.val) / + (ax._length-minbest.pad-maxbest.pad); + } + + ax.range = [ + minbest.val - mbest*minbest.pad, + maxbest.val + mbest*maxbest.pad + ]; + + // don't let axis have zero size + if(ax.range[0]===ax.range[1]) { + ax.range = [ax.range[0]-1, ax.range[0]+1]; + } + + // maintain reversal + if(axReverse) { + ax.range.reverse(); + } + } + + // doAutoRange will get called on fullLayout, + // but we want to report its results back to layout + var axIn = ax._td.layout[ax._name]; + if(!axIn) ax._td.layout[ax._name] = axIn = {}; + if(axIn!==ax) { + axIn.range = ax.range.slice(); + axIn.autorange = ax.autorange; + } + } +}; + +// save a copy of the initial axis ranges in fullLayout +// use them in modebar and dblclick events +axes.saveRangeInitial = function(gd, overwrite) { + var axList = axes.list(gd, '', true), + hasOneAxisChanged = false; + + var ax, isNew, hasChanged; + + for(var i = 0; i < axList.length; i++) { + ax = axList[i]; + + isNew = ax._rangeInitial===undefined; + hasChanged = isNew || + !(ax.range[0]===ax._rangeInitial[0] && ax.range[1]===ax._rangeInitial[1]); + + if((isNew && ax.autorange===false) || (overwrite && hasChanged)) { + ax._rangeInitial = ax.range.slice(); + hasOneAxisChanged = true; + } + } + + return hasOneAxisChanged; +}; + +// axes.expand: if autoranging, include new data in the outer limits +// for this axis +// data is an array of numbers (ie already run through ax.d2c) +// available options: +// vpad: (number or number array) pad values (data value +-vpad) +// ppad: (number or number array) pad pixels (pixel location +-ppad) +// ppadplus, ppadminus, vpadplus, vpadminus: +// separate padding for each side, overrides symmetric +// padded: (boolean) add 5% padding to both ends +// (unless one end is overridden by tozero) +// tozero: (boolean) make sure to include zero if axis is linear, +// and make it a tight bound if possible +var FP_SAFE = Number.MAX_VALUE/2; +axes.expand = function(ax, data, options) { + if(!ax.autorange || !data) return; + if(!ax._min) ax._min = []; + if(!ax._max) ax._max = []; + if(!options) options = {}; + if(!ax._m) ax.setScale(); + + var len = data.length, + extrappad = options.padded ? ax._length*0.05 : 0, + tozero = options.tozero && (ax.type==='linear' || ax.type==='-'), + i, j, v, di, dmin, dmax, + ppadiplus, ppadiminus, includeThis, vmin, vmax; + + function getPad(item) { + if(Array.isArray(item)) { + return function(i) { return Math.max(Number(item[i]||0),0); }; + } + else { + var v = Math.max(Number(item||0),0); + return function(){ return v; }; + } + } + var ppadplus = getPad((ax._m>0 ? + options.ppadplus : options.ppadminus) || options.ppad || 0), + ppadminus = getPad((ax._m>0 ? + options.ppadminus : options.ppadplus) || options.ppad || 0), + vpadplus = getPad(options.vpadplus||options.vpad), + vpadminus = getPad(options.vpadminus||options.vpad); + + function addItem(i) { + di = data[i]; + if(!isNumeric(di)) return; + ppadiplus = ppadplus(i) + extrappad; + ppadiminus = ppadminus(i) + extrappad; + vmin = di-vpadminus(i); + vmax = di+vpadplus(i); + // special case for log axes: if vpad makes this object span + // more than an order of mag, clip it to one order. This is so + // we don't have non-positive errors or absurdly large lower + // range due to rounding errors + if(ax.type==='log' && vmin=ppadiminus) { + includeThis = false; + } + else if(v.val>=dmin && v.pad<=ppadiminus) { + ax._min.splice(j,1); + j--; + } + } + if(includeThis) { + ax._min.push({ + val:dmin, + pad:(tozero && dmin===0) ? 0 : ppadiminus + }); + } + } + + if(goodNumber(dmax)) { + includeThis = true; + for(j=0; j=dmax && v.pad>=ppadiplus) { + includeThis = false; + } + else if(v.val<=dmax && v.pad<=ppadiplus) { + ax._max.splice(j,1); + j--; + } + } + if(includeThis) { + ax._max.push({ + val:dmax, + pad:(tozero && dmax===0) ? 0 : ppadiplus + }); + } + } + } + + // For efficiency covering monotonic or near-monotonic data, + // check a few points at both ends first and then sweep + // through the middle + for(i=0; i<6; i++) addItem(i); + for(i=len-1; i>5; i--) addItem(i); + +}; + +axes.autoBin = function(data,ax,nbins,is2d) { + var datamin = Plotly.Lib.aggNums(Math.min, null, data), + datamax = Plotly.Lib.aggNums(Math.max, null, data); + if(ax.type==='category') { + return { + start: datamin-0.5, + end: datamax+0.5, + size: 1 + }; + } + + var size0; + if(nbins) size0 = ((datamax-datamin)/nbins); + else { + // totally auto: scale off std deviation so the highest bin is + // somewhat taller than the total number of bins, but don't let + // the size get smaller than the 'nice' rounded down minimum + // difference between values + var distinctData = Plotly.Lib.distinctVals(data), + msexp = Math.pow(10, Math.floor( + Math.log(distinctData.minDiff) / Math.LN10)), + // TODO: there are some date cases where this will fail... + minSize = msexp*Plotly.Lib.roundUp( + distinctData.minDiff/msexp, [0.9, 1.9, 4.9, 9.9], true); + size0 = Math.max(minSize, 2*Plotly.Lib.stdev(data) / + Math.pow(data.length, is2d ? 0.25 : 0.4)); + } + + // piggyback off autotick code to make "nice" bin sizes + var dummyax = { + type: ax.type==='log' ? 'linear' : ax.type, + range:[datamin, datamax] + }; + axes.autoTicks(dummyax, size0); + var binstart = axes.tickIncrement( + axes.tickFirst(dummyax), dummyax.dtick, 'reverse'), + binend; + + function nearEdge(v) { + // is a value within 1% of a bin edge? + return (1 + (v-binstart)*100/dummyax.dtick)%100 < 2; + } + + // check for too many data points right at the edges of bins + // (>50% within 1% of bin edges) or all data points integral + // and offset the bins accordingly + if(typeof dummyax.dtick === 'number') { + var edgecount = 0, + midcount = 0, + intcount = 0, + blankcount = 0; + for(var i=0; i datacount * 0.3 || + nearEdge(datamin) || nearEdge(datamax)) { + // lots of points at the edge, not many in the middle + // shift half a bin + var binshift = dummyax.dtick / 2; + binstart += (binstart+binshift0 && ax.dtick=endtick):(x<=endtick); + x = axes.tickIncrement(x,ax.dtick,axrev)) { + vals.push(x); + + // prevent infinite loops + if(vals.length>1000) break; + } + + // save the last tick as well as first, so we can + // show the exponent only on the last one + ax._tmax = vals[vals.length - 1]; + + var ticksOut = new Array(vals.length); + for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]); + + return ticksOut; +}; + +function arrayTicks(ax) { + var vals = ax.tickvals, + text = ax.ticktext, + ticksOut = new Array(vals.length), + r0expanded = ax.range[0] * 1.0001 - ax.range[1] * 0.0001, + r1expanded = ax.range[1] * 1.0001 - ax.range[0] * 0.0001, + tickMin = Math.min(r0expanded, r1expanded), + tickMax = Math.max(r0expanded, r1expanded), + vali, + i, + j = 0; + + + // without a text array, just format the given values as any other ticks + // except with more precision to the numbers + if(!Array.isArray(text)) text = []; + + for(i = 0; i < vals.length; i++) { + vali = ax.d2l(vals[i]); + if(vali > tickMin && vali < tickMax) { + if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali); + else ticksOut[j] = tickTextObj(ax, vali, String(text[i])); + j++; + } + } + + if(j < vals.length) ticksOut.splice(j, vals.length - j); + + return ticksOut; +} + +var roundBase10 = [2, 5, 10], + roundBase24 = [1, 2, 3, 6, 12], + roundBase60 = [1, 2, 5, 10, 15, 30], + // 2&3 day ticks are weird, but need something btwn 1&7 + roundDays = [1, 2, 3, 7, 14], + // approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2) + // these don't have to be exact, just close enough to round to the right value + roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1], + roundLog2 = [-0.301, 0, 0.301, 0.699, 1]; + +function roundDTick(roughDTick, base, roundingSet) { + return base * Plotly.Lib.roundUp(roughDTick / base, roundingSet); +} + +// autoTicks: calculate best guess at pleasant ticks for this axis +// inputs: +// ax - an axis object +// roughDTick - rough tick spacing (to be turned into a nice round number) +// outputs (into ax): +// tick0: starting point for ticks (not necessarily on the graph) +// usually 0 for numeric (=10^0=1 for log) or jan 1, 2000 for dates +// dtick: the actual, nice round tick spacing, somewhat larger than roughDTick +// if the ticks are spaced linearly (linear scale, categories, +// log with only full powers, date ticks < month), +// this will just be a number +// months: M# +// years: M# where # is 12*number of years +// log with linear ticks: L# where # is the linear tick spacing +// log showing powers plus some intermediates: +// D1 shows all digits, D2 shows 2 and 5 +axes.autoTicks = function(ax, roughDTick){ + var base; + + if(ax.type === 'date'){ + ax.tick0 = new Date(2000, 0, 1).getTime(); + + if(roughDTick > 15778800000){ + // years if roughDTick > 6mo + roughDTick /= 31557600000; + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); + ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)); + } + else if(roughDTick > 1209600000){ + // months if roughDTick > 2wk + roughDTick /= 2629800000; + ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24); + } + else if(roughDTick > 43200000){ + // days if roughDTick > 12h + ax.dtick = roundDTick(roughDTick, 86400000, roundDays); + // get week ticks on sunday + ax.tick0 = new Date(2000, 0, 2).getTime(); + } + else if(roughDTick > 1800000){ + // hours if roughDTick > 30m + ax.dtick = roundDTick(roughDTick, 3600000, roundBase24); + } + else if(roughDTick > 30000){ + // minutes if roughDTick > 30sec + ax.dtick = roundDTick(roughDTick, 60000, roundBase60); + } + else if(roughDTick > 500){ + // seconds if roughDTick > 0.5sec + ax.dtick = roundDTick(roughDTick, 1000, roundBase60); + } + else { + //milliseconds + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); + ax.dtick = roundDTick(roughDTick, base, roundBase10); + } + } + else if(ax.type === 'log'){ + ax.tick0 = 0; + + //only show powers of 10 + if(roughDTick > 0.7) ax.dtick = Math.ceil(roughDTick); + else if(Math.abs(ax.range[1] - ax.range[0]) < 1){ + // span is less than one power of 10 + var nt = 1.5 * Math.abs((ax.range[1] - ax.range[0]) / roughDTick); + + // ticks on a linear scale, labeled fully + roughDTick = Math.abs(Math.pow(10, ax.range[1]) - + Math.pow(10, ax.range[0])) / nt; + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); + ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10); + } + else { + // include intermediates between powers of 10, + // labeled with small digits + // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits) + ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1'; + } + } + else if(ax.type==='category') { + ax.tick0 = 0; + ax.dtick = Math.ceil(Math.max(roughDTick, 1)); + } + else{ + // auto ticks always start at 0 + ax.tick0 = 0; + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); + ax.dtick = roundDTick(roughDTick, base, roundBase10); + } + + // prevent infinite loops + if(ax.dtick === 0) ax.dtick = 1; + + // TODO: this is from log axis histograms with autorange off + if(!isNumeric(ax.dtick) && typeof ax.dtick !=='string') { + var olddtick = ax.dtick; + ax.dtick = 1; + throw 'ax.dtick error: ' + String(olddtick); + } +}; + +// after dtick is already known, find tickround = precision +// to display in tick labels +// for numeric ticks, integer # digits after . to round to +// for date ticks, the last date part to show (y,m,d,H,M,S) +// or an integer # digits past seconds +function autoTickRound(ax) { + var dtick = ax.dtick, + maxend; + + ax._tickexponent = 0; + if(!isNumeric(dtick) && typeof dtick !== 'string') dtick = 1; + + if(ax.type === 'category') ax._tickround = null; + else if(isNumeric(dtick) || dtick.charAt(0) === 'L') { + if(ax.type === 'date') { + if(dtick >= 86400000) ax._tickround = 'd'; + else if(dtick >= 3600000) ax._tickround = 'H'; + else if(dtick >= 60000) ax._tickround = 'M'; + else if(dtick >= 1000) ax._tickround = 'S'; + else ax._tickround = 3 - Math.round(Math.log(dtick / 2) / Math.LN10); + } + else { + if(!isNumeric(dtick)) dtick = Number(dtick.substr(1)); + // 2 digits past largest digit of dtick + ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01); + + if(ax.type === 'log') { + maxend = Math.pow(10, Math.max(ax.range[0], ax.range[1])); + } + else maxend = Math.max(Math.abs(ax.range[0]), Math.abs(ax.range[1])); + + var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01); + if(Math.abs(rangeexp) > 3) { + if(ax.exponentformat === 'SI' || ax.exponentformat === 'B') { + ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3); + } + else ax._tickexponent = rangeexp; + } + } + } + else if(dtick.charAt(0) === 'M') ax._tickround = (dtick.length===2) ? 'm' : 'y'; + else ax._tickround = null; +} + +// months and years don't have constant millisecond values +// (but a year is always 12 months so we only need months) +// log-scale ticks are also not consistently spaced, except +// for pure powers of 10 +// numeric ticks always have constant differences, other datetime ticks +// can all be calculated as constant number of milliseconds +axes.tickIncrement = function(x, dtick, axrev){ + var axSign = axrev ? -1 : 1; + + // includes all dates smaller than month, and pure 10^n in log + if(isNumeric(dtick)) return x + axSign * dtick; + + var tType = dtick.charAt(0), + dtSigned = axSign * Number(dtick.substr(1)); + + // Dates: months (or years) + if(tType === 'M'){ + var y = new Date(x); + // is this browser consistent? setMonth edits a date but + // returns that date's milliseconds + return y.setMonth(y.getMonth() + dtSigned); + } + + // Log scales: Linear, Digits + else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10; + + // log10 of 2,5,10, or all digits (logs just have to be + // close enough to round) + else if(tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, + x2 = x + axSign * 0.01, + frac = Plotly.Lib.roundUp(mod(x2, 1), tickset, axrev); + + return Math.floor(x2) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; + } + else throw 'unrecognized dtick ' + String(dtick); +}; + +// calculate the first tick on an axis +axes.tickFirst = function(ax){ + var axrev = ax.range[1] < ax.range[0], + sRound = axrev ? Math.floor : Math.ceil, + // add a tiny extra bit to make sure we get ticks + // that may have been rounded out + r0 = ax.range[0] * 1.0001 - ax.range[1] * 0.0001, + dtick = ax.dtick, + tick0 = ax.tick0; + if(isNumeric(dtick)) { + var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0; + + // make sure no ticks outside the category list + if(ax.type === 'category') { + tmin = Plotly.Lib.constrain(tmin, 0, ax._categories.length - 1); + } + return tmin; + } + + var tType = dtick.charAt(0), + dtNum = Number(dtick.substr(1)), + t0, + mdif, + t1; + + // Dates: months (or years) + if(tType === 'M'){ + t0 = new Date(tick0); + r0 = new Date(r0); + mdif = (r0.getFullYear() - t0.getFullYear()) * 12 + + r0.getMonth() - t0.getMonth(); + t1 = t0.setMonth(t0.getMonth() + + (Math.round(mdif / dtNum) + (axrev ? 1 : -1)) * dtNum); + + while(axrev ? t1 > r0 : t1 < r0) { + t1 = axes.tickIncrement(t1, dtick, axrev); + } + return t1; + } + + // Log scales: Linear, Digits + else if(tType === 'L') { + return Math.log(sRound( + (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10; + } + else if(tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, + frac = Plotly.Lib.roundUp(mod(r0, 1), tickset, axrev); + + return Math.floor(r0) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; + } + else throw 'unrecognized dtick ' + String(dtick); +}; + +var yearFormat = d3.time.format('%Y'), + monthFormat = d3.time.format('%b %Y'), + dayFormat = d3.time.format('%b %-d'), + hourFormat = d3.time.format('%b %-d %Hh'), + minuteFormat = d3.time.format('%H:%M'), + secondFormat = d3.time.format(':%S'); + +// add one item to d3's vocabulary: +// %{n}f where n is the max number of digits +// of fractional seconds +var fracMatch = /%(\d?)f/g; +function modDateFormat(fmt,x) { + var fm = fmt.match(fracMatch), + d = new Date(x); + if(fm) { + var digits = Math.min(+fm[1]||6,6), + fracSecs = String((x/1000 % 1) + 2.0000005) + .substr(2,digits).replace(/0+$/,'')||'0'; + return d3.time.format(fmt.replace(fracMatch,fracSecs))(d); + } + else { + return d3.time.format(fmt)(d); + } +} + +// draw the text for one tick. +// px,py are the location on td.paper +// prefix is there so the x axis ticks can be dropped a line +// ax is the axis layout, x is the tick value +// hover is a (truthy) flag for whether to show numbers with a bit +// more precision for hovertext +axes.tickText = function(ax, x, hover){ + var out = tickTextObj(ax, x), + hideexp, + arrayMode = ax.tickmode === 'array', + extraPrecision = hover || arrayMode; + + if(arrayMode && Array.isArray(ax.ticktext)) { + var minDiff = Math.abs(ax.range[1] - ax.range[0]) / 10000; + for(var i = 0; i < ax.ticktext.length; i++) { + if(Math.abs(x - ax.d2l(ax.tickvals[i])) < minDiff) break; + } + if(i < ax.ticktext.length) { + out.text = String(ax.ticktext[i]); + return out; + } + } + + function isHidden(showAttr) { + var first_or_last; + + if (showAttr===undefined) return true; + if (hover) return showAttr==='none'; + + first_or_last = { + first: ax._tmin, + last: ax._tmax + }[showAttr]; + + return showAttr!=='all' && x!==first_or_last; + } + + hideexp = ax.exponentformat!=='none' && isHidden(ax.showexponent) ? 'hide' : ''; + + if(ax.type==='date') formatDate(ax, out, hover, extraPrecision); + else if(ax.type==='log') formatLog(ax, out, hover, extraPrecision, hideexp); + else if(ax.type==='category') formatCategory(ax, out); + else formatLinear(ax, out, hover, extraPrecision, hideexp); + + // add prefix and suffix + if (ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text; + if (ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix; + + return out; +}; + +function tickTextObj(ax, x, text) { + var tf = ax.tickfont || ax._td._fullLayout.font; + + return { + x: x, + dx: 0, + dy: 0, + text: text || '', + fontSize: tf.size, + font: tf.family, + fontColor: tf.color + }; +} + +function formatDate(ax, out, hover, extraPrecision) { + var x = out.x, + tr = ax._tickround, + d = new Date(x), + // suffix completes the full date info, to be included + // with only the first tick + suffix = '', + tt; + if(hover && ax.hoverformat) { + tt = modDateFormat(ax.hoverformat,x); + } + else if(ax.tickformat) { + tt = modDateFormat(ax.tickformat,x); + // TODO: potentially hunt for ways to automatically add more + // precision to the hover text? + } + else { + if(extraPrecision) { + if(isNumeric(tr)) tr+=2; + else tr = {y:'m', m:'d', d:'H', H:'M', M:'S', S:2}[tr]; + } + if(tr==='y') tt = yearFormat(d); + else if(tr==='m') tt = monthFormat(d); + else { + if(x===ax._tmin && !hover) { + suffix = '
'+yearFormat(d); + } + + if(tr==='d') tt = dayFormat(d); + else if(tr==='H') tt = hourFormat(d); + else { + if(x===ax._tmin && !hover) { + suffix = '
'+dayFormat(d)+', '+yearFormat(d); + } + + tt = minuteFormat(d); + if(tr!=='M'){ + tt += secondFormat(d); + if(tr!=='S') { + tt += numFormat(mod(x/1000,1),ax,'none',hover) + .substr(1); + } + } + } + } + } + out.text = tt + suffix; +} + +function formatLog(ax, out, hover, extraPrecision, hideexp) { + var dtick = ax.dtick, + x = out.x; + if(extraPrecision && ((typeof dtick !== 'string') || dtick.charAt(0)!=='L')) dtick = 'L3'; + + if(ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) { + out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision); + } + else if(isNumeric(dtick)||((dtick.charAt(0)==='D')&&(mod(x+0.01,1)<0.1))) { + if(['e','E','power'].indexOf(ax.exponentformat)!==-1) { + var p = Math.round(x); + if(p === 0) out.text = 1; + else if(p === 1) out.text = '10'; + else if(p > 1) out.text = '10' + p + ''; + else out.text = '10\u2212' + -p + ''; + + out.fontSize *= 1.25; + } + else { + out.text = numFormat(Math.pow(10,x), ax,'','fakehover'); + if(dtick==='D1' && ax._id.charAt(0)==='y') { + out.dy -= out.fontSize/6; + } + } + } + else if(dtick.charAt(0) === 'D') { + out.text = String(Math.round(Math.pow(10, mod(x, 1)))); + out.fontSize *= 0.75; + } + else throw 'unrecognized dtick ' + String(dtick); + + // if 9's are printed on log scale, move the 10's away a bit + if(ax.dtick==='D1') { + var firstChar = String(out.text).charAt(0); + if(firstChar === '0' || firstChar === '1') { + if(ax._id.charAt(0) === 'y') { + out.dx -= out.fontSize / 4; + } + else { + out.dy += out.fontSize / 2; + out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) * + out.fontSize * (x < 0 ? 0.5 : 0.25); + } + } + } +} + +function formatCategory(ax, out) { + var tt = ax._categories[Math.round(out.x)]; + if(tt === undefined) tt = ''; + out.text = String(tt); +} + +function formatLinear(ax, out, hover, extraPrecision, hideexp) { + // don't add an exponent to zero if we're showing all exponents + // so the only reason you'd show an exponent on zero is if it's the + // ONLY tick to get an exponent (first or last) + if(ax.showexponent==='all' && Math.abs(out.x/ax.dtick)<1e-6) { + hideexp = 'hide'; + } + out.text = numFormat(out.x, ax, hideexp, extraPrecision); +} + +// format a number (tick value) according to the axis settings +// new, more reliable procedure than d3.round or similar: +// add half the rounding increment, then stringify and truncate +// also automatically switch to sci. notation +var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T']; +function numFormat(v, ax, fmtoverride, hover) { + // negative? + var isNeg = v < 0, + // max number of digits past decimal point to show + tickRound = ax._tickround, + exponentFormat = fmtoverride || ax.exponentformat || 'B', + exponent = ax._tickexponent, + tickformat = ax.tickformat; + + // special case for hover: set exponent just for this value, and + // add a couple more digits of precision over tick labels + if(hover) { + // make a dummy axis obj to get the auto rounding and exponent + var ah = { + exponentformat:ax.exponentformat, + dtick: ax.showexponent==='none' ? ax.dtick : + (isNumeric(v) ? Math.abs(v) || 1 : 1), + // if not showing any exponents, don't change the exponent + // from what we calculate + range: ax.showexponent === 'none' ? ax.range : [0, v || 1] + }; + autoTickRound(ah); + tickRound = (Number(ah._tickround) || 0) + 4; + exponent = ah._tickexponent; + if(ax.hoverformat) tickformat = ax.hoverformat; + } + + if(tickformat) return d3.format(tickformat)(v).replace(/-/g,'\u2212'); + + // 'epsilon' - rounding increment + var e = Math.pow(10, -tickRound) / 2; + + // exponentFormat codes: + // 'e' (1.2e+6, default) + // 'E' (1.2E+6) + // 'SI' (1.2M) + // 'B' (same as SI except 10^9=B not G) + // 'none' (1200000) + // 'power' (1.2x10^6) + // 'hide' (1.2, use 3rd argument=='hide' to eg + // only show exponent on last tick) + if(exponentFormat === 'none') exponent = 0; + + // take the sign out, put it back manually at the end + // - makes cases easier + v = Math.abs(v); + if(v < e) { + // 0 is just 0, but may get exponent if it's the last tick + v = '0'; + isNeg = false; + } + else { + v += e; + // take out a common exponent, if any + if(exponent) { + v *= Math.pow(10, -exponent); + tickRound += exponent; + } + // round the mantissa + if(tickRound === 0) v = String(Math.floor(v)); + else if(tickRound < 0) { + v = String(Math.round(v)); + v = v.substr(0, v.length + tickRound); + for(var i = tickRound; i < 0; i++) v += '0'; + } + else { + v = String(v); + var dp = v.indexOf('.') + 1; + if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, ''); + } + // insert appropriate decimal point and thousands separator + v = numSeparate(v, ax._td._fullLayout.separators); + } + + // add exponent + if(exponent && exponentFormat !== 'hide') { + var signedExponent; + if(exponent < 0) signedExponent = '\u2212' + -exponent; + else if(exponentFormat !== 'power') signedExponent = '+' + exponent; + else signedExponent = String(exponent); + + if(exponentFormat === 'e' || + ((exponentFormat === 'SI' || exponentFormat === 'B') && + (exponent > 12 || exponent < -15))) { + v += 'e' + signedExponent; + } + else if(exponentFormat === 'E') { + v += 'E' + signedExponent; + } + else if(exponentFormat === 'power') { + v += '×10' + signedExponent + ''; + } + else if(exponentFormat === 'B' && exponent === 9) { + v += 'B'; + } + else if(exponentFormat === 'SI' || exponentFormat === 'B') { + v += SIPREFIXES[exponent / 3 + 5]; + } + } + + // put sign back in and return + // replace standard minus character (which is technically a hyphen) + // with a true minus sign + if(isNeg) return '\u2212' + v; + return v; +} + +// add arbitrary decimal point and thousands separator +var findThousands = /(\d+)(\d{3})/; +function numSeparate(nStr, separators) { + // separators - first char is decimal point, + // next char is thousands separator if there is one + + var dp = separators.charAt(0), + thou = separators.charAt(1), + x = nStr.split('.'), + x1 = x[0], + x2 = x.length > 1 ? dp + x[1] : ''; + // even if there is a thousands separator, don't use it on + // 4-digit integers (like years) + if(thou && (x.length > 1 || x1.length>4)) { + while (findThousands.test(x1)) { + x1 = x1.replace(findThousands, '$1' + thou + '$2'); + } + } + return x1 + x2; +} + +// get all axis object names +// optionally restricted to only x or y or z by string axLetter +// and optionally 2D axes only, not those inside 3D scenes +function listNames(td, axLetter, only2d) { + var fullLayout = td._fullLayout; + if(!fullLayout) return []; + + function filterAxis(obj, extra) { + var keys = Object.keys(obj), + axMatch = /^[xyz]axis[0-9]*/, + out = []; + + for(var i = 0; i < keys.length; i++) { + var k = keys[i]; + if(axLetter && k.charAt(0) !== axLetter) continue; + if(axMatch.test(k)) out.push(extra + k); + } + + return out.sort(); + } + + var names = filterAxis(fullLayout, ''); + if(only2d) return names; + + var sceneIds3D = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d') || []; + for(var i = 0; i < sceneIds3D.length; i++) { + var sceneId = sceneIds3D[i]; + names = names.concat( + filterAxis(fullLayout[sceneId], sceneId + '.') + ); + } + + return names; +} + +// get all axis objects, as restricted in listNames +axes.list = function(td, axletter, only2d) { + return listNames(td, axletter, only2d) + .map(function(axName) { + return Plotly.Lib.nestedProperty(td._fullLayout, axName).get(); + }); +}; + +// get all axis ids, optionally restricted by letter +// this only makes sense for 2d axes +axes.listIds = function(td, axletter) { + return listNames(td, axletter, true).map(axes.name2id); +}; + +// get an axis object from its id 'x','x2' etc +// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it +axes.getFromId = function(td, id, type) { + var fullLayout = td._fullLayout; + + if(type==='x') id = id.replace(/y[0-9]*/,''); + else if(type==='y') id = id.replace(/x[0-9]*/,''); + + return fullLayout[axes.id2name(id)]; +}; + +// get an axis object of specified type from the containing trace +axes.getFromTrace = function (td, fullTrace, type) { + var fullLayout = td._fullLayout; + var ax = null; + if (Plotly.Plots.traceIs(fullTrace, 'gl3d')) { + var scene = fullTrace.scene; + if (scene.substr(0,5)==='scene') { + ax = fullLayout[scene][type + 'axis']; + } + } else { + ax = axes.getFromId(td, fullTrace[type + 'axis'] || type); + } + + return ax; +}; + +axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/; + +// getSubplots - extract all combinations of axes we need to make plots for +// as an array of items like 'xy', 'x2y', 'x2y2'... +// sorted by x (x,x2,x3...) then y +// optionally restrict to only subplots containing axis object ax +// looks both for combinations of x and y found in the data +// and at axes and their anchors +axes.getSubplots = function(gd, ax) { + var subplots = []; + var i, j, sp; + + // look for subplots in the data + var data = gd.data || []; + + for(i = 0; i < data.length; i++) { + var trace = data[i]; + + if(trace.visible === false || trace.visible === 'legendonly' || + !(Plotly.Plots.traceIs(trace, 'cartesian') || + Plotly.Plots.traceIs(trace, 'gl2d')) + ) continue; + + var xId = trace.xaxis || 'x', + yId = trace.yaxis || 'y'; + sp = xId + yId; + + if(subplots.indexOf(sp) === -1) subplots.push(sp); + } + + // look for subplots in the axes/anchors, so that we at least draw all axes + var axesList = axes.list(gd, '', true); + + function hasAx2(sp, ax2) { + return sp.indexOf(ax2._id) !== -1; + } + + for(i = 0; i < axesList.length; i++) { + var ax2 = axesList[i], + ax2Letter = ax2._id.charAt(0), + ax3Id = (ax2.anchor === 'free') ? + ((ax2Letter === 'x') ? 'y' : 'x') : + ax2.anchor, + ax3 = axes.getFromId(gd, ax3Id); + + // if a free axis is already represented in the data, ignore it + var foundAx2 = false; + for(j = 0; j < subplots.length; j++) { + if(hasAx2(subplots[j], ax2)) { + foundAx2 = true; + break; + } + } + if(ax2.anchor === 'free' && foundAx2) continue; + + if(!ax3) { + console.log([ + 'Warning: couldnt find anchor', ax3Id, + 'for axis', ax2._id + ].join(' ')); + return; + } + + sp = (ax2Letter === 'x') ? + ax2._id + ax3._id : + ax3._id + ax2._id; + + if(subplots.indexOf(sp) === -1) subplots.push(sp); + } + + // filter invalid subplots + var spMatch = axes.subplotMatch, + allSubplots = []; + + for(i = 0; i < subplots.length; i++) { + sp = subplots[i]; + if(spMatch.test(sp)) allSubplots.push(sp); + } + + // sort the subplot ids + allSubplots.sort(function(a, b) { + var aMatch = a.match(spMatch), + bMatch = b.match(spMatch); + + if(aMatch[1] === bMatch[1]) { + return +(aMatch[2]||1) - (bMatch[2]||1); + } + + return +(aMatch[1]||0) - (bMatch[1]||0); + }); + + if(ax) return axes.findSubplotsWithAxis(allSubplots, ax); + return allSubplots; +}; + +// find all subplots with axis 'ax' +axes.findSubplotsWithAxis = function(subplots, ax) { + var axMatch = new RegExp( + (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') + ); + var subplotsWithAxis = []; + + for(var i = 0; i < subplots.length; i++) { + var sp = subplots[i]; + if(axMatch.test(sp)) subplotsWithAxis.push(sp); + } + + return subplotsWithAxis; +}; + +// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings +axes.makeClipPaths = function(td) { + var layout = td._fullLayout, + defs = layout._defs, + fullWidth = {_offset: 0, _length: layout.width, _id: ''}, + fullHeight = {_offset: 0, _length: layout.height, _id: ''}, + xaList = axes.list(td, 'x', true), + yaList = axes.list(td, 'y', true), + clipList = [], + i, + j; + + for(i = 0; i < xaList.length; i++) { + clipList.push({x: xaList[i], y: fullHeight}); + for(j = 0; j < yaList.length; j++) { + if(i===0) clipList.push({x: fullWidth, y: yaList[j]}); + clipList.push({x: xaList[i], y: yaList[j]}); + } + } + + var defGroup = defs.selectAll('g.clips') + .data([0]); + defGroup.enter().append('g') + .classed('clips', true); + + // selectors don't work right with camelCase tags, + // have to use class instead + // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I + var axClips = defGroup.selectAll('.axesclip') + .data(clipList, function(d) { return d.x._id + d.y._id; }); + axClips.enter().append('clipPath') + .classed('axesclip', true) + .attr('id', function(d) { return 'clip' + layout._uid + d.x._id + d.y._id; } ) + .append('rect'); + axClips.exit().remove(); + axClips.each(function(d) { + d3.select(this).select('rect').attr({ + x: d.x._offset || 0, + y: d.y._offset || 0, + width: d.x._length || 1, + height: d.y._length || 1 + }); + }); +}; + + +// doTicks: draw ticks, grids, and tick labels +// axid: 'x', 'y', 'x2' etc, +// blank to do all, +// 'redraw' to force full redraw, and reset ax._r +// (stored range for use by zoom/pan) +// or can pass in an axis object directly +axes.doTicks = function(td, axid, skipTitle) { + var fullLayout = td._fullLayout, + ax, + independent = false; + + // allow passing an independent axis object instead of id + if(typeof axid === 'object') { + ax = axid; + axid = ax._id; + independent = true; + } + else { + ax = axes.getFromId(td,axid); + + if(axid==='redraw') { + fullLayout._paper.selectAll('g.subplot').each(function(subplot) { + var plotinfo = fullLayout._plots[subplot], + xa = plotinfo.x(), + ya = plotinfo.y(); + plotinfo.plot.attr('viewBox', + '0 0 '+xa._length+' '+ya._length); + plotinfo.xaxislayer + .selectAll('.'+xa._id+'tick').remove(); + plotinfo.yaxislayer + .selectAll('.'+ya._id+'tick').remove(); + plotinfo.gridlayer + .selectAll('path').remove(); + plotinfo.zerolinelayer + .selectAll('path').remove(); + }); + } + + if(!axid || axid==='redraw') { + return Plotly.Lib.syncOrAsync(axes.list(td, '', true).map(function(ax) { + return function(){ + if(!ax._id) return; + var axDone = axes.doTicks(td,ax._id); + if(axid==='redraw') ax._r = ax.range.slice(); + return axDone; + }; + })); + } + } + + // make sure we only have allowed options for exponents + // (others can make confusing errors) + if(!ax.tickformat) { + if(['none','e','E','power','SI','B'].indexOf(ax.exponentformat)===-1) { + ax.exponentformat = 'e'; + } + if(['all','first','last','none'].indexOf(ax.showexponent)===-1) { + ax.showexponent = 'all'; + } + } + + // in case a val turns into string somehow + ax.range = [+ax.range[0], +ax.range[1]]; + + // set scaling to pixels + ax.setScale(); + + var axletter = axid.charAt(0), + counterLetter = axes.counterLetter(axid), + vals = axes.calcTicks(ax), + datafn = function(d){ return d.text + d.x + ax.mirror; }, + tcls = axid+'tick', + gcls = axid+'grid', + zcls = axid+'zl', + pad = (ax.linewidth||1) / 2, + labelStandoff = + (ax.ticks==='outside' ? ax.ticklen : 1) + (ax.linewidth||0), + gridWidth = Plotly.Drawing.crispRound(td, ax.gridwidth, 1), + zeroLineWidth = Plotly.Drawing.crispRound(td, ax.zerolinewidth, gridWidth), + tickWidth = Plotly.Drawing.crispRound(td, ax.tickwidth, 1), + sides, transfn, tickprefix, tickmid, + i; + + // positioning arguments for x vs y axes + if(axletter==='x') { + sides = ['bottom', 'top']; + transfn = function(d){ + return 'translate('+ax.l2p(d.x)+',0)'; + }; + // dumb templating with string concat + // would be better to use an actual template + tickprefix = 'M0,'; + tickmid = 'v'; + } + else if(axletter==='y') { + sides = ['left', 'right']; + transfn = function(d){ + return 'translate(0,'+ax.l2p(d.x)+')'; + }; + tickprefix = 'M'; + tickmid = ',0h'; + } + else { + console.log('unrecognized doTicks axis', axid); + return; + } + var axside = ax.side||sides[0], + // which direction do the side[0], side[1], and free ticks go? + // then we flip if outside XOR y axis + ticksign = [-1, 1, axside===sides[1] ? 1 : -1]; + if((ax.ticks!=='inside') === (axletter==='x')) { + ticksign = ticksign.map(function(v){ return -v; }); + } + + // remove zero lines, grid lines, and inside ticks if they're within + // 1 pixel of the end + // The key case here is removing zero lines when the axis bound is zero. + function clipEnds(d) { + var p = ax.l2p(d.x); + return (p>1 && p1) { + for(j = 1; j < groupsi.length; j++) { + groupj = groups[groupsi[j]]; + mergeAxisGroups(group0.x, groupj.x); + mergeAxisGroups(group0.y, groupj.y); + } + } + mergeAxisGroups(group0.x, [xi]); + mergeAxisGroups(group0.y, [yi]); + } + + return groups; +} + +function mergeAxisGroups(intoSet, fromSet) { + for(var i = 0; i < fromSet.length; i++) { + if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]); + } +} + +function swapAxisGroup(gd, xIds, yIds) { + var i, + j, + xFullAxes = [], + yFullAxes = [], + layout = gd.layout; + + for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i])); + for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i])); + + var allAxKeys = Object.keys(xFullAxes[0]), + noSwapAttrs = [ + 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle' + ], + numericTypes = ['linear', 'log']; + + for(i = 0; i < allAxKeys.length; i++) { + var keyi = allAxKeys[i], + xVal = xFullAxes[0][keyi], + yVal = yFullAxes[0][keyi], + allEqual = true, + coerceLinearX = false, + coerceLinearY = false; + if(keyi.charAt(0) === '_' || typeof xVal === 'function' || + noSwapAttrs.indexOf(keyi) !== -1) { + continue; + } + for(j = 1; j < xFullAxes.length && allEqual; j++) { + var xVali = xFullAxes[j][keyi]; + if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 && + numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) { + // type is special - if we find a mixture of linear and log, + // coerce them all to linear on flipping + coerceLinearX = true; + } + else if(xVali !== xVal) allEqual = false; + } + for(j = 1; j < yFullAxes.length && allEqual; j++) { + var yVali = yFullAxes[j][keyi]; + if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 && + numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) { + // type is special - if we find a mixture of linear and log, + // coerce them all to linear on flipping + coerceLinearY = true; + } + else if(yFullAxes[j][keyi] !== yVal) allEqual = false; + } + if(allEqual) { + if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear'; + if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear'; + swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes); + } + } + + // now swap x&y for any annotations anchored to these x & y + for(i = 0; i < gd._fullLayout.annotations.length; i++) { + var ann = gd._fullLayout.annotations[i]; + if(xIds.indexOf(ann.xref) !== -1 && + yIds.indexOf(ann.yref) !== -1) { + Plotly.Lib.swapAttrs(layout.annotations[i],['?']); + } + } +} + +function swapAxisAttrs(layout, key, xFullAxes, yFullAxes) { + // in case the value is the default for either axis, + // look at the first axis in each list and see if + // this key's value is undefined + var np = Plotly.Lib.nestedProperty, + xVal = np(layout[xFullAxes[0]._name], key).get(), + yVal = np(layout[yFullAxes[0]._name], key).get(), + i; + if(key === 'title') { + // special handling of placeholder titles + if(xVal === 'Click to enter X axis title') { + xVal = 'Click to enter Y axis title'; + } + if(yVal === 'Click to enter Y axis title') { + yVal = 'Click to enter X axis title'; + } + } + + for(i = 0; i < xFullAxes.length; i++) { + np(layout, xFullAxes[i]._name + '.' + key).set(yVal); + } + for(i = 0; i < yFullAxes.length; i++) { + np(layout, yFullAxes[i]._name + '.' + key).set(xVal); + } +} + +// mod - version of modulus that always restricts to [0,divisor) +// rather than built-in % which gives a negative value for negative v +function mod(v,d){ return ((v%d) + d) % d; } diff --git a/src/plots/cartesian/trace_attributes.js b/src/plots/cartesian/trace_attributes.js new file mode 100644 index 00000000000..a7c0127c1b8 --- /dev/null +++ b/src/plots/cartesian/trace_attributes.js @@ -0,0 +1,26 @@ +module.exports = { + xaxis: { + valType: 'axisid', + role: 'info', + dflt: 'x', + description: [ + 'Sets a reference between this trace\'s x coordinates and', + 'a 2D cartesian x axis.', + 'If *x* (the default value), the x coordinates refer to', + '`layout.xaxis`.', + 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' + ].join(' ') + }, + yaxis: { + valType: 'axisid', + role: 'info', + dflt: 'y', + description: [ + 'Sets a reference between this trace\'s y coordinates and', + 'a 2D cartesian y axis.', + 'If *y* (the default value), the y coordinates refer to', + '`layout.yaxis`.', + 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' + ].join(' ') + } +}; From 769f6dd5d6d99c9fc52d3d14487a0e625f9390f4 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:40:22 -0500 Subject: [PATCH 19/38] put graph_interact in plots/cartesian --- src/{ => plots/cartesian}/graph_interact.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/{ => plots/cartesian}/graph_interact.js (99%) diff --git a/src/graph_interact.js b/src/plots/cartesian/graph_interact.js similarity index 99% rename from src/graph_interact.js rename to src/plots/cartesian/graph_interact.js index deeb6f37a15..ce068313722 100644 --- a/src/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -1,10 +1,10 @@ 'use strict'; -var Plotly = require('./plotly'); +var Plotly = require('../../plotly'); var d3 = require('d3'); var tinycolor = require('tinycolor2'); var isNumeric = require('fast-isnumeric'); -var Events = require('./events'); +var Events = require('../../lib/events'); var fx = module.exports = {}; From 914dcbf9bee5dfba2d181e7c9ab534081059186b Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:40:47 -0500 Subject: [PATCH 20/38] put polar modules in plots/polar --- .../polar/area_attributes.js} | 6 +- .../polar/axis_attributes.js} | 7 +-- src/{ => plots}/polar/micropolar.js | 8 +-- src/{ => plots}/polar/micropolar_manager.js | 6 +- src/plots/polar/undo_manager.js | 56 +++++++++++++++++++ 5 files changed, 68 insertions(+), 15 deletions(-) rename src/{polar/attributes/area.js => plots/polar/area_attributes.js} (66%) rename src/{polar/attributes/polaraxes.js => plots/polar/axis_attributes.js} (96%) rename src/{ => plots}/polar/micropolar.js (99%) rename src/{ => plots}/polar/micropolar_manager.js (95%) create mode 100644 src/plots/polar/undo_manager.js diff --git a/src/polar/attributes/area.js b/src/plots/polar/area_attributes.js similarity index 66% rename from src/polar/attributes/area.js rename to src/plots/polar/area_attributes.js index a9b6963ef06..47cc4307e8d 100644 --- a/src/polar/attributes/area.js +++ b/src/plots/polar/area_attributes.js @@ -1,7 +1,5 @@ -var Plotly = require('../../plotly'); - -var scatterAttrs = Plotly.Scatter.attributes, - scatterMarkerAttrs = scatterAttrs.marker; +var scatterAttrs = require('../../traces/scatter/attributes'); +var scatterMarkerAttrs = scatterAttrs.marker; module.exports = { r: scatterAttrs.r, diff --git a/src/polar/attributes/polaraxes.js b/src/plots/polar/axis_attributes.js similarity index 96% rename from src/polar/attributes/polaraxes.js rename to src/plots/polar/axis_attributes.js index 3b63271a493..eb2b937e1b2 100644 --- a/src/polar/attributes/polaraxes.js +++ b/src/plots/polar/axis_attributes.js @@ -1,10 +1,9 @@ 'use strict'; -var Plotly = require('../../plotly'); +var axesAttrs = require('../cartesian/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; -var extendFlat = Plotly.Lib.extendFlat; - -var domainAttr = extendFlat({}, Plotly.Axes.layoutAttributes.domain, { +var domainAttr = extendFlat({}, axesAttrs.domain, { description: [ 'Polar chart subplots are not supported yet.', 'This key has currently no effect.' diff --git a/src/polar/micropolar.js b/src/plots/polar/micropolar.js similarity index 99% rename from src/polar/micropolar.js rename to src/plots/polar/micropolar.js index 2f3871be934..97e5f850d32 100644 --- a/src/polar/micropolar.js +++ b/src/plots/polar/micropolar.js @@ -1,13 +1,13 @@ -var Plotly = require('../plotly'), - d3 = require('d3'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); var µ = module.exports = { - version: '0.2.2' + version: '0.2.2', + manager: require('./micropolar_manager') }; var extendDeepAll = Plotly.Lib.extendDeepAll; - µ.Axis = function module() { var config = { data: [], diff --git a/src/polar/micropolar_manager.js b/src/plots/polar/micropolar_manager.js similarity index 95% rename from src/polar/micropolar_manager.js rename to src/plots/polar/micropolar_manager.js index e622c9acbad..2b7da576136 100644 --- a/src/polar/micropolar_manager.js +++ b/src/plots/polar/micropolar_manager.js @@ -1,8 +1,8 @@ 'use strict'; -var Plotly = require('../plotly'), - d3 = require('d3'), - UndoManager = require('./utils/undo_manager'); +var Plotly = require('../../plotly'); +var d3 = require('d3'); +var UndoManager = require('./undo_manager'); var manager = module.exports = {}; diff --git a/src/plots/polar/undo_manager.js b/src/plots/polar/undo_manager.js new file mode 100644 index 00000000000..99db9aedb83 --- /dev/null +++ b/src/plots/polar/undo_manager.js @@ -0,0 +1,56 @@ +'use strict'; + +//Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager +//Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com +module.exports = function UndoManager() { + var undoCommands = [], + index = -1, + isExecuting = false, + callback; + + function execute(command, action){ + if(!command) return this; + + isExecuting = true; + command[action](); + isExecuting = false; + + return this; + } + + return { + add: function(command){ + if(isExecuting) return this; + undoCommands.splice(index + 1, undoCommands.length - index); + undoCommands.push(command); + index = undoCommands.length - 1; + return this; + }, + setCallback: function(callbackFunc){ callback = callbackFunc; }, + undo: function(){ + var command = undoCommands[index]; + if(!command) return this; + execute(command, 'undo'); + index -= 1; + if(callback) callback(command.undo); + return this; + }, + redo: function(){ + var command = undoCommands[index + 1]; + if(!command) return this; + execute(command, 'redo'); + index += 1; + if(callback) callback(command.redo); + return this; + }, + clear: function(){ + undoCommands = []; + index = -1; + }, + hasUndo: function(){ return index !== -1; }, + hasRedo: function(){ return index < (undoCommands.length - 1); }, + getCommands: function(){ return undoCommands; }, + getPreviousCommand: function(){ return undoCommands[index-1]; }, + getIndex: function(){ return index; } + }; +}; From c83319805cb6ebf446cc28fe724ec27475c33835 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:42:01 -0500 Subject: [PATCH 21/38] re-org plotly.js --- src/plotly.js | 107 +++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 58 deletions(-) diff --git a/src/plotly.js b/src/plotly.js index 01aab2b1290..5980903821c 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -1,72 +1,63 @@ require('es6-promise').polyfill(); -// order of requires should matter only for interdependencies -// in attributes definitions. put the common modules first - exports.Lib = require('./lib/lib'); -exports.util = require('./lib/plotly_util'); +exports.util = require('./lib/svg_text_utils'); -// icons, css and configuration +// plot icons svg and css exports.Icons = require('../build/ploticon'); require('../build/plotcss'); +// configuration exports.MathJaxConfig = require('./fonts/mathjax_config'); exports.defaultConfig = require('./plot_config'); -exports.Color = require('./color'); -exports.Colorscale = require('./colorscale'); -exports.Drawing = require('./drawing'); -// then the plot structure -exports.Plots = require('./graph_obj'); -exports.Axes = require('./axes'); -exports.Colorbar = require('./colorbar'); -exports.Fx = require('./graph_interact'); -// then trace modules - scatter has to come first -exports.Scatter = require('./scatter'); -exports.Bars = require('./bars'); -exports.Boxes = require('./boxes'); -exports.ErrorBars = require('./errorbars'); -exports.Heatmap = require('./heatmap'); -exports.Histogram = require('./histogram'); -exports.Pie = require('./pie'); -exports.Contour = require('./contour'); -// and extra plot components -exports.Annotations = require('./annotations'); -exports.Shapes = require('./shapes'); -exports.Legend = require('./legend'); -exports.ModeBar = require('./modebar'); - -// polar -exports.micropolar = require('./polar/micropolar'); -exports.micropolar.manager = require('./polar/micropolar_manager'); - -// GL3D -exports.Gl3dLayout = require('./gl3d/defaults/gl3dlayout'); -exports.Gl3dAxes = require('./gl3d/defaults/gl3daxes'); -exports.Scatter3D = require('./gl3d/defaults/scatter3d'); -exports.Surface = require('./gl3d/defaults/surface'); -exports.Mesh3D = require('./gl3d/defaults/mesh3d'); -exports.Scene = require('./gl3d/scene'); - -// Geo -exports.GeoLayout = require('./geo/defaults/geolayout'); -exports.GeoAxes = require('./geo/defaults/geoaxes'); -exports.ScatterGeo = require('./geo/defaults/scattergeo'); -exports.Choropleth = require('./geo/defaults/choropleth'); -exports.Geo = require('./geo/geo'); - -// GL2D -exports.ScatterGl = require('./gl2d/scattergl/scattergl'); -exports.Scene2D = require('./gl2d/scene2d'); - -// plot schema -exports.PlotSchema = require('./plotschema'); - -// imaging Routines +// plots +exports.Plots = require('./plots/plots/plots'); +exports.Axes = require('./plots/cartesian/axes'); +exports.Fx = require('./plots/cartesian/graph_interact'); +exports.Scene = require('./plots/gl3d/scene'); +exports.Gl3dLayout = require('./plots/gl3d/layout/layout'); +exports.Geo = require('./plots/geo/geo'); +exports.GeoLayout = require('./plots/geo/layout/layout'); +exports.Scene2D = require('./plots/gl2d/scene2d'); +exports.micropolar = require('./plots/polar/micropolar'); + +// components +exports.Color = require('./components/color/color'); +exports.Drawing = require('./components/drawing/drawing'); +exports.Colorscale = require('./components/colorscale/colorscale'); +exports.Colorbar = require('./components/colorbar/colorbar'); +exports.ErrorBars = require('./components/errorbars/errorbars'); +exports.Annotations = require('./components/annotations/annotations'); +exports.Shapes = require('./components/shapes/shapes'); +exports.Titles = require('./components/titles/titles'); +exports.Legend = require('./components/legend/legend'); +exports.ModeBar = require('./components/modebar/modebar'); + +// traces +exports.Scatter = require('./traces/scatter/scatter'); +exports.Bars = require('./traces/bars/bars'); +exports.Boxes = require('./traces/boxes/boxes'); +exports.Heatmap = require('./traces/heatmap/heatmap'); +exports.Histogram = require('./traces/histogram/histogram'); +exports.Pie = require('./traces/pie/pie'); +exports.Contour = require('./traces/contour/contour'); +exports.Scatter3D = require('./traces/scatter3d/scatter3d'); +exports.Surface = require('./traces/surface/surface'); +exports.Mesh3D = require('./traces/mesh3d/mesh3d'); +exports.ScatterGeo = require('./traces/scattergeo/scattergeo'); +exports.Choropleth = require('./traces/choropleth/choropleth'); +exports.ScatterGl = require('./traces/scattergl/scattergl'); + +// plot api +require('./plot_api/plot_api'); +exports.PlotSchema = require('./plot_api/plot_schema'); + +// imaging routines exports.Snapshot = require('./snapshot/snapshot'); -// Queue for undo/redo -exports.Queue = require('./queue'); +// queue for undo/redo +exports.Queue = require('./lib/queue'); -// exports d3 used in the bundle +// export d3 used in the bundle exports.d3 = require('d3'); From 4f57bffe6af31243d52ff649c7045d146d42d457 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 15:42:44 -0500 Subject: [PATCH 22/38] fix paths + lint --- devtools/test_dashboard/test-geo.js | 2 +- src/plot_api/plot_api.js | 2 +- src/plot_api/plot_schema.js | 4 ++-- src/plot_config.js | 2 +- src/plots/plots/layout_attributes.js | 6 ++++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/devtools/test_dashboard/test-geo.js b/devtools/test_dashboard/test-geo.js index d6c307b1442..cf27d98e4e3 100644 --- a/devtools/test_dashboard/test-geo.js +++ b/devtools/test_dashboard/test-geo.js @@ -1,6 +1,6 @@ var plotButtons = require('./buttons'); -var figDir = '../../test/image/baseline/geo_'; +var figDir = '../../test/image/baselines/geo_'; var plots = {}; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 225e2a5d64d..3cc56c97448 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2577,7 +2577,7 @@ function makePlotFramework(gd) { /* * TODO - find a better place for 3D to initialize axes */ - if(fullLayout._hasGL3D) Plotly.Gl3dAxes.initAxes(gd); + if(fullLayout._hasGL3D) Plotly.Gl3dLayout.initAxes(gd); // Plot container fullLayout._container = gd3.selectAll('.plot-container').data([0]); diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index f95923e9ba7..b6606d7bb40 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -22,8 +22,8 @@ var plotSchema = { }; // FIXME polar attribute are not part of Plotly yet -var polarAreaAttrs = require('../plots/polar/attributes/area'), - polarAxisAttrs = require('../plots/polar/attributes/polaraxes'); +var polarAreaAttrs = require('../plots/polar/area_attributes'), + polarAxisAttrs = require('../plots/polar/axis_attributes'); var PlotSchema = module.exports = {}; diff --git a/src/plot_config.js b/src/plot_config.js index ffdc41f8727..a5820ec0a78 100644 --- a/src/plot_config.js +++ b/src/plot_config.js @@ -1,6 +1,6 @@ 'use strict'; -/* +/** * This will be transfered over to gd and overridden by * config args to Plotly.plot. * diff --git a/src/plots/plots/layout_attributes.js b/src/plots/plots/layout_attributes.js index 85831fe8f3f..a33fe44921c 100644 --- a/src/plots/plots/layout_attributes.js +++ b/src/plots/plots/layout_attributes.js @@ -181,11 +181,13 @@ module.exports = { _composedModules: { '*': 'Fx' }, + + // TODO merge with moduleLayoutDefaults in plots.js _nestedModules: { 'xaxis': 'Axes', 'yaxis': 'Axes', - 'scene': 'Gl3dLayout', // TODO should be Scene - 'geo': 'Geo', + 'scene': 'Gl3dLayout', + 'geo': 'GeoLayout', 'legend': 'Legend', 'annotations': 'Annotations', 'shapes': 'Shapes' From 3349e7b1cf9ddb2f50112c573759dafdf9c276c7 Mon Sep 17 00:00:00 2001 From: etpinard Date: Wed, 11 Nov 2015 17:09:21 -0500 Subject: [PATCH 23/38] fix paths in jamsine tests --- test/jasmine/tests/events_test.js | 2 +- test/jasmine/tests/geoaxes_test.js | 12 +++-- test/jasmine/tests/gl3daxes_test.js | 70 ++++++++++++++--------------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/test/jasmine/tests/events_test.js b/test/jasmine/tests/events_test.js index 93b5a9b6fe7..35c76b6dcdd 100644 --- a/test/jasmine/tests/events_test.js +++ b/test/jasmine/tests/events_test.js @@ -5,7 +5,7 @@ */ -var Events = require('@src/events'); +var Events = require('@src/lib/events'); describe('Events', function () { 'use strict'; diff --git a/test/jasmine/tests/geoaxes_test.js b/test/jasmine/tests/geoaxes_test.js index e0472ae2d88..c043bc764b0 100644 --- a/test/jasmine/tests/geoaxes_test.js +++ b/test/jasmine/tests/geoaxes_test.js @@ -1,11 +1,9 @@ -var Plotly = require('@src/plotly'), - params = require('@src/geo/lib/params'); +var params = require('@src/constants/geo_constants'); +var supplyLayoutDefaults = require('@src/plots/geo/layout/axis_defaults'); describe('Test geoaxes', function () { 'use strict'; - var GeoAxes = Plotly.GeoAxes; - describe('supplyLayoutDefaults', function() { var geoLayoutIn, geoLayoutOut; @@ -31,7 +29,7 @@ describe('Test geoaxes', function () { geoLayoutIn = {}; geoLayoutOut = {scope: scope}; - GeoAxes.supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); + supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); expect(geoLayoutOut.lonaxis.range).toEqual(dfltLonaxisRange); expect(geoLayoutOut.lataxis.range).toEqual(dfltLataxisRange); expect(geoLayoutOut.lonaxis.tick0).toEqual(dfltLonaxisRange[0]); @@ -43,7 +41,7 @@ describe('Test geoaxes', function () { }; geoLayoutOut = {scope: scope}; - GeoAxes.supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); + supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); expect(geoLayoutOut.lonaxis.range).toEqual(customLonaxisRange); expect(geoLayoutOut.lataxis.range).toEqual(customLataxisRange); expect(geoLayoutOut.lonaxis.tick0).toEqual(customLonaxisRange[0]); @@ -55,7 +53,7 @@ describe('Test geoaxes', function () { var expectedLonaxisRange, expectedLataxisRange; function testOne() { - GeoAxes.supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); + supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); expect(geoLayoutOut.lonaxis.range).toEqual(expectedLonaxisRange); expect(geoLayoutOut.lataxis.range).toEqual(expectedLataxisRange); } diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js index 8489b84347a..acfec12c285 100644 --- a/test/jasmine/tests/gl3daxes_test.js +++ b/test/jasmine/tests/gl3daxes_test.js @@ -1,4 +1,5 @@ -var Plotly = require('@src/plotly'); +var supplyLayoutDefaults = require('@src/plots/gl3d/layout/axis_defaults'); + describe('Test Gl3dAxes', function () { 'use strict'; @@ -7,7 +8,6 @@ describe('Test Gl3dAxes', function () { var layoutIn, layoutOut; - var supplyLayoutDefaults = Plotly.Gl3dAxes.supplyLayoutDefaults; var options = { font: 'Open Sans', scene: {id: 'scene'}, @@ -22,41 +22,41 @@ describe('Test Gl3dAxes', function () { layoutIn = {}; var expected = { - "xaxis": { - "showline": false, - "showgrid": true, - "gridcolor": "rgb(204, 204, 204)", - "gridwidth": 1, - "showspikes": true, - "spikesides": true, - "spikethickness": 2, - "spikecolor": "rgb(0,0,0)", - "showbackground": false, - "showaxeslabels": true + 'xaxis': { + 'showline': false, + 'showgrid': true, + 'gridcolor': 'rgb(204, 204, 204)', + 'gridwidth': 1, + 'showspikes': true, + 'spikesides': true, + 'spikethickness': 2, + 'spikecolor': 'rgb(0,0,0)', + 'showbackground': false, + 'showaxeslabels': true }, - "yaxis": { - "showline": false, - "showgrid": true, - "gridcolor": "rgb(204, 204, 204)", - "gridwidth": 1, - "showspikes": true, - "spikesides": true, - "spikethickness": 2, - "spikecolor": "rgb(0,0,0)", - "showbackground": false, - "showaxeslabels": true + 'yaxis': { + 'showline': false, + 'showgrid': true, + 'gridcolor': 'rgb(204, 204, 204)', + 'gridwidth': 1, + 'showspikes': true, + 'spikesides': true, + 'spikethickness': 2, + 'spikecolor': 'rgb(0,0,0)', + 'showbackground': false, + 'showaxeslabels': true }, - "zaxis": { - "showline": false, - "showgrid": true, - "gridcolor": "rgb(204, 204, 204)", - "gridwidth": 1, - "showspikes": true, - "spikesides": true, - "spikethickness": 2, - "spikecolor": "rgb(0,0,0)", - "showbackground": false, - "showaxeslabels": true + 'zaxis': { + 'showline': false, + 'showgrid': true, + 'gridcolor': 'rgb(204, 204, 204)', + 'gridwidth': 1, + 'showspikes': true, + 'spikesides': true, + 'spikethickness': 2, + 'spikecolor': 'rgb(0,0,0)', + 'showbackground': false, + 'showaxeslabels': true } }; From d3087b104d3b959bccf6ba75efbe22bddbbafa1c Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:09:21 -0500 Subject: [PATCH 24/38] add index file exporting only API Plotly methods: - browserify that file so that window.Plotly doesn't contain internal methods --- package.json | 2 +- src/index.js | 29 +++++++++++++++++++++++++++++ src/plotly.js | 3 --- tasks/util/constants.js | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/index.js diff --git a/package.json b/package.json index e44a8776301..2f2209de508 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "The premier javascript graphing library", "license": "MIT", - "main": "./src/plotly.js", + "main": "./src/index.js", "repository": { "type": "git", "url": "https://github.com/plotly/plotly.js.git" diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000000..bcb3ee1057f --- /dev/null +++ b/src/index.js @@ -0,0 +1,29 @@ +/* + * Export the plotly.js API methods. + * + * This file is browserify'ed into a standalone 'Plotly' object. + * + */ + +var Plotly = require('./plotly'); + +// plot api +exports.plot = Plotly.plot; +exports.newPlot = Plotly.newPlot; +exports.restyle = Plotly.restyle; +exports.relayout = Plotly.relayout; +exports.redraw = Plotly.redraw; +exports.extendTraces = Plotly.extendTraces; +exports.prependTraces = Plotly.prependTraces; +exports.addTraces = Plotly.addTraces; +exports.deleteTraces = Plotly.deleteTraces; +exports.moveTraces = Plotly.moveTraces; + +// unofficial plot methods, use at your own risk +exports.Plots = Plotly.Plots; +exports.Fx = Plotly.Fx; + +// TODO expose snapshot and plot_schema + +// export d3 used in the bundle +exports.d3 = require('d3'); diff --git a/src/plotly.js b/src/plotly.js index 5980903821c..5ff6bfa35fa 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -58,6 +58,3 @@ exports.Snapshot = require('./snapshot/snapshot'); // queue for undo/redo exports.Queue = require('./lib/queue'); - -// export d3 used in the bundle -exports.d3 = require('d3'); diff --git a/tasks/util/constants.js b/tasks/util/constants.js index 00984b4c28c..349c5b20480 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -10,7 +10,7 @@ module.exports = { pathToSrc: pathToSrc, pathToMocks: path.join(pathToRoot, 'test/image/mocks'), - pathToPlotlySrc: path.join(pathToSrc, 'plotly.js'), + pathToPlotlySrc: path.join(pathToSrc, 'index.js'), pathToPlotlyDist: path.join(pathToDist, 'plotly.js'), pathToPlotlyDistMin: path.join(pathToDist, 'plotly.min.js'), pathToPlotlyDistWithMeta: path.join(pathToDist, 'plotly-with-meta.js'), From 55e203a6f2a97c1673cebd2c06e7d47dd7cc093c Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:11:08 -0500 Subject: [PATCH 25/38] mv plots/plots/* to plots/* : - folders in plots/ are reserved for plot types --- src/plotly.js | 2 +- src/plots/{plots => }/attributes.js | 0 src/plots/{plots => }/font_attributes.js | 0 src/plots/{plots => }/layout_attributes.js | 4 ++-- src/plots/{plots => }/plots.js | 13 +++++-------- src/plots/polar/axis_attributes.js | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) rename src/plots/{plots => }/attributes.js (100%) rename src/plots/{plots => }/font_attributes.js (100%) rename src/plots/{plots => }/layout_attributes.js (98%) rename src/plots/{plots => }/plots.js (99%) diff --git a/src/plotly.js b/src/plotly.js index 5ff6bfa35fa..483be137d91 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -12,7 +12,7 @@ exports.MathJaxConfig = require('./fonts/mathjax_config'); exports.defaultConfig = require('./plot_config'); // plots -exports.Plots = require('./plots/plots/plots'); +exports.Plots = require('./plots/plots'); exports.Axes = require('./plots/cartesian/axes'); exports.Fx = require('./plots/cartesian/graph_interact'); exports.Scene = require('./plots/gl3d/scene'); diff --git a/src/plots/plots/attributes.js b/src/plots/attributes.js similarity index 100% rename from src/plots/plots/attributes.js rename to src/plots/attributes.js diff --git a/src/plots/plots/font_attributes.js b/src/plots/font_attributes.js similarity index 100% rename from src/plots/plots/font_attributes.js rename to src/plots/font_attributes.js diff --git a/src/plots/plots/layout_attributes.js b/src/plots/layout_attributes.js similarity index 98% rename from src/plots/plots/layout_attributes.js rename to src/plots/layout_attributes.js index a33fe44921c..fb4a13af9f2 100644 --- a/src/plots/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -1,7 +1,7 @@ -var Plotly = require('../../plotly'); +var Plotly = require('../plotly'); var fontAttrs = require('./font_attributes'); -var colorAttrs = require('../../components/color/attributes'); +var colorAttrs = require('../components/color/attributes'); var extendFlat = Plotly.Lib.extendFlat; diff --git a/src/plots/plots/plots.js b/src/plots/plots.js similarity index 99% rename from src/plots/plots/plots.js rename to src/plots/plots.js index e855b24e83f..e61d1778436 100644 --- a/src/plots/plots/plots.js +++ b/src/plots/plots.js @@ -1,6 +1,6 @@ 'use strict'; -var Plotly = require('../../plotly'); +var Plotly = require('../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -703,14 +703,11 @@ plots.purge = function(gd) { }; plots.style = function(gd) { - var modulesWithErrorBars = Plotly.ErrorBars ? - gd._modules.concat(Plotly.ErrorBars) : gd._modules, - i, - module; + var modulesWithErrorBars = gd._modules.concat(Plotly.ErrorBars); - for (i = 0; i < modulesWithErrorBars.length; i++) { - module = modulesWithErrorBars[i]; - if (module.style) module.style(gd); + for(var i = 0; i < modulesWithErrorBars.length; i++) { + var _module = modulesWithErrorBars[i]; + if(_module.style) _module.style(gd); } }; diff --git a/src/plots/polar/axis_attributes.js b/src/plots/polar/axis_attributes.js index eb2b937e1b2..07a50f04e5f 100644 --- a/src/plots/polar/axis_attributes.js +++ b/src/plots/polar/axis_attributes.js @@ -1,6 +1,6 @@ 'use strict'; -var axesAttrs = require('../cartesian/attributes'); +var axesAttrs = require('../cartesian/layout_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var domainAttr = extendFlat({}, axesAttrs.domain, { From 4fd274a7b93e27792740c72191d957ee02afeba3 Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:13:39 -0500 Subject: [PATCH 26/38] mv colorscale attributes out of colorbar/ into colorscale/ --- src/components/colorbar/colorbar.js | 2 -- .../attributes.js} | 0 src/components/colorscale/colorscale.js | 2 ++ src/traces/choropleth/attributes.js | 16 ++++++++-------- src/traces/heatmap/attributes.js | 16 ++++++++-------- src/traces/mesh3d/attributes.js | 8 ++++---- src/traces/surface/attributes.js | 16 ++++++++-------- 7 files changed, 30 insertions(+), 30 deletions(-) rename src/components/{colorbar/trace_attributes.js => colorscale/attributes.js} (100%) diff --git a/src/components/colorbar/colorbar.js b/src/components/colorbar/colorbar.js index 48abadf2e5b..0f6073ead96 100644 --- a/src/components/colorbar/colorbar.js +++ b/src/components/colorbar/colorbar.js @@ -592,5 +592,3 @@ colorbar.traceColorbar = function(gd, cd) { Plotly.Lib.markTime('done colorbar'); }; - -colorbar.traceColorbarAttributes = require('./trace_attributes'); diff --git a/src/components/colorbar/trace_attributes.js b/src/components/colorscale/attributes.js similarity index 100% rename from src/components/colorbar/trace_attributes.js rename to src/components/colorscale/attributes.js diff --git a/src/components/colorscale/colorscale.js b/src/components/colorscale/colorscale.js index 2054766cc4d..48f213c5e1a 100644 --- a/src/components/colorscale/colorscale.js +++ b/src/components/colorscale/colorscale.js @@ -10,6 +10,8 @@ var colorscale = module.exports = {}; colorscale.scales = require('./scales'); colorscale.defaultScale = colorscale.scales.RdBu; +colorscale.attributes = require('./attributes'); + function isValidScaleArray(scl) { var isValid = true, highestVal = 0, diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 4da99e04f86..08187ebb398 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -1,5 +1,5 @@ var ScatterGeoAttrs = require('../scattergeo/attributes'); -var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var colorscaleAttrs = require('../../components/colorscale/attributes'); var plotAttrs = require('../../plots/plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -28,13 +28,13 @@ module.exports = { width: ScatterGeoMarkerLineAttrs.width } }, - zauto: traceColorbarAttrs.zauto, - zmin: traceColorbarAttrs.zmin, - zmax: traceColorbarAttrs.zmax, - colorscale: traceColorbarAttrs.colorscale, - autocolorscale: traceColorbarAttrs.autocolorscale, - reversescale: traceColorbarAttrs.reversescale, - showscale: traceColorbarAttrs.showscale, + zauto: colorscaleAttrs.zauto, + zmin: colorscaleAttrs.zmin, + zmax: colorscaleAttrs.zmax, + colorscale: colorscaleAttrs.colorscale, + autocolorscale: colorscaleAttrs.autocolorscale, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['location', 'z', 'text', 'name'] }), diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index c9b9714b5e6..f95004bd596 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -1,5 +1,5 @@ var scatterAttrs = require('../scatter/attributes'); -var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var colorscaleAttrs = require('../../components/colorscale/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -47,14 +47,14 @@ module.exports = { '(the default behavior when `y` is not provided)' ].join(' ') }, - zauto: traceColorbarAttrs.zauto, - zmin: traceColorbarAttrs.zmin, - zmax: traceColorbarAttrs.zmax, - colorscale: traceColorbarAttrs.colorscale, - autocolorscale: extendFlat({}, traceColorbarAttrs.autocolorscale, + zauto: colorscaleAttrs.zauto, + zmin: colorscaleAttrs.zmin, + zmax: colorscaleAttrs.zmax, + colorscale: colorscaleAttrs.colorscale, + autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), - reversescale: traceColorbarAttrs.reversescale, - showscale: traceColorbarAttrs.showscale, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, zsmooth: { valType: 'enumerated', values: ['fast', 'best', false], diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index 7b17b44c760..31d3e153691 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -1,4 +1,4 @@ -var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var colorscaleAttrs = require('../../components/colorscale/attributes'); module.exports = { x: {valType: 'data_array'}, @@ -74,9 +74,9 @@ module.exports = { } }, - colorscale: traceColorbarAttrs.colorscale, - reversescale: traceColorbarAttrs.reversescale, - showscale: traceColorbarAttrs.showscale, + colorscale: colorscaleAttrs.colorscale, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, lighting: { ambient: { diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index ead98a774f1..eb8256c0047 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -1,6 +1,6 @@ 'use strict'; -var traceColorbarAttrs = require('../../components/colorbar/trace_attributes'); +var colorscaleAttrs = require('../../components/colorscale/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -86,14 +86,14 @@ module.exports = { valType: 'data_array', description: 'Sets the text elements associated with each z value.' }, - zauto: traceColorbarAttrs.zauto, - zmin: traceColorbarAttrs.zmin, - zmax: traceColorbarAttrs.zmax, - colorscale: traceColorbarAttrs.colorscale, - autocolorscale: extendFlat({}, traceColorbarAttrs.autocolorscale, + zauto: colorscaleAttrs.zauto, + zmin: colorscaleAttrs.zmin, + zmax: colorscaleAttrs.zmax, + colorscale: colorscaleAttrs.colorscale, + autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), - reversescale: traceColorbarAttrs.reversescale, - showscale: traceColorbarAttrs.showscale, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, contours: { x: makeContourAttr('x'), y: makeContourAttr('y'), From ad49e57c6c9447f63ac3cf34a6c63f94b9a05744 Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:25:16 -0500 Subject: [PATCH 27/38] be more consistent about attribute file names: - attributes.js ALWAYS refers to trace attributes - layout_attributes.js ALWAYS referes to layout attributes --- src/components/annotations/attributes.js | 2 +- src/components/colorbar/attributes.js | 4 +- src/components/legend/attributes.js | 2 +- src/plots/cartesian/attributes.js | 473 +-------------------- src/plots/cartesian/axes.js | 7 +- src/plots/cartesian/layout_attributes.js | 453 ++++++++++++++++++++ src/plots/cartesian/trace_attributes.js | 26 -- src/plots/geo/layout/attributes.js | 252 +---------- src/plots/geo/layout/defaults.js | 2 +- src/plots/geo/layout/layout.js | 6 +- src/plots/geo/layout/layout_attributes.js | 247 +++++++++++ src/plots/geo/layout/trace_attributes.js | 15 - src/plots/gl2d/scene2d.js | 2 +- src/plots/gl3d/layout/attributes.js | 143 +------ src/plots/gl3d/layout/axis_attributes.js | 2 +- src/plots/gl3d/layout/defaults.js | 2 +- src/plots/gl3d/layout/layout.js | 6 +- src/plots/gl3d/layout/layout_attributes.js | 140 ++++++ src/plots/gl3d/layout/trace_attributes.js | 15 - src/traces/choropleth/attributes.js | 2 +- src/traces/pie/attributes.js | 4 +- src/traces/scattergeo/attributes.js | 2 +- 22 files changed, 903 insertions(+), 904 deletions(-) create mode 100644 src/plots/cartesian/layout_attributes.js delete mode 100644 src/plots/cartesian/trace_attributes.js create mode 100644 src/plots/geo/layout/layout_attributes.js delete mode 100644 src/plots/geo/layout/trace_attributes.js create mode 100644 src/plots/gl3d/layout/layout_attributes.js delete mode 100644 src/plots/gl3d/layout/trace_attributes.js diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index f3eb81d802d..2338a81707c 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -1,6 +1,6 @@ var Plotly = require('../../plotly'); var ARROWPATHS = require('./arrow_paths'); -var fontAttrs = require('../../plots/plots/font_attributes'); +var fontAttrs = require('../../plots/font_attributes'); var extendFlat = require('../../lib/extend').extendFlat; module.exports = { diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index c2d2e38593b..0dd0b65f01d 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -1,5 +1,5 @@ -var axesAttrs = require('../../plots/cartesian/attributes'); -var fontAttrs = require('../../plots/plots/font_attributes'); +var axesAttrs = require('../../plots/cartesian/layout_attributes'); +var fontAttrs = require('../../plots/font_attributes'); var extendFlat = require('../../lib/extend').extendFlat; module.exports = { diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 68ab8de8b8f..94c3033fc55 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -1,4 +1,4 @@ -var fontAttrs = require('../../plots/plots/font_attributes'); +var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../color/attributes'); var extendFlat = require('../../lib/extend').extendFlat; diff --git a/src/plots/cartesian/attributes.js b/src/plots/cartesian/attributes.js index 2bdf3f57ee1..a7c0127c1b8 100644 --- a/src/plots/cartesian/attributes.js +++ b/src/plots/cartesian/attributes.js @@ -1,453 +1,26 @@ -var Plotly = require('../../plotly'); -var fontAttrs = require('../plots/font_attributes'); -var colorAttrs = require('../../components/color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; - - module.exports = { - title: { - valType: 'string', - role: 'info', - description: 'Sets the title of this axis.' - }, - titlefont: extendFlat({}, fontAttrs, { - description: [ - 'Sets this axis\' title font.' - ].join(' ') - }), - type: { - valType: 'enumerated', - // '-' means we haven't yet run autotype or couldn't find any data - // it gets turned into linear in td._fullLayout but not copied back - // to td.data like the others are. - values: ['-', 'linear', 'log', 'date', 'category'], - dflt: '-', - role: 'info', - description: [ - 'Sets the axis type.', - 'By default, plotly attempts to determined the axis type', - 'by looking into the data of the traces that referenced', - 'the axis in question.' - ].join(' ') - }, - autorange: { - valType: 'enumerated', - values: [true, false, 'reversed'], - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the range of this axis is', - 'computed in relation to the input data.', - 'See `rangemode` for more info.', - 'If `range` is provided, then `autorange` is set to *false*.' - ].join(' ') - }, - rangemode: { - valType: 'enumerated', - values: ['normal', 'tozero', 'nonnegative'], - dflt: 'normal', - role: 'style', - description: [ - 'If *normal*, the range is computed in relation to the extrema', - 'of the input data.', - 'If *tozero*`, the range extends to 0,', - 'regardless of the input data', - 'If *nonnegative*, the range is non-negative,', - 'regardless of the input data.' - ].join(' ') - }, - range: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number'}, - {valType: 'number'} - ], - description: [ - 'Sets the range of this axis.', - 'If the axis `type` is *log*, then you must take the log of your desired range', - '(e.g. to set the range from 1 to 100, set the range from 0 to 2).', - 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', - '(the number of milliseconds since January 1st, 1970). For example, to set the date range from', - 'January 1st 1970 to November 4th, 2013, set the range from 0 to 1380844800000.0' - ].join(' ') - }, - fixedrange: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not this axis is zoom-able.', - 'If true, then zoom is disabled.' - ].join(' ') - }, - // ticks - tickmode: { - valType: 'enumerated', - values: ['auto', 'linear', 'array'], - role: 'info', - description: [ - 'Sets the tick mode for this axis.', - 'If *auto*, the number of ticks is set via `nticks`.', - 'If *linear*, the placement of the ticks is determined by', - 'a starting position `tick0` and a tick step `dtick`', - '(*linear* is the default value if `tick0` and `dtick` are provided).', - 'If *array*, the placement of the ticks is set via `tickvals`', - 'and the tick text is `ticktext`.', - '(*array* is the default value if `tickvals` is provided).' - ].join(' ') - }, - nticks: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Sets the number of ticks.', - 'Has an effect only if `tickmode` is set to *auto*.' - ].join(' ') - }, - tick0: { - valType: 'number', - dflt: 0, - role: 'style', - description: [ - 'Sets the placement of the first tick on this axis.', - 'Use with `dtick`.', - 'If the axis `type` is *log*, then you must take the log of your starting tick', - '(e.g. to set the starting tick to 100, set the `tick0` to 2).', - 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', - '(the number of milliseconds since January 1st, 1970).', - 'For example, to set the starting tick to', - 'November 4th, 2013, set the range to 1380844800000.0.' + xaxis: { + valType: 'axisid', + role: 'info', + dflt: 'x', + description: [ + 'Sets a reference between this trace\'s x coordinates and', + 'a 2D cartesian x axis.', + 'If *x* (the default value), the x coordinates refer to', + '`layout.xaxis`.', + 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' ].join(' ') - }, - dtick: { - valType: 'any', - dflt: 1, - role: 'style', - description: [ - 'Sets the step in-between ticks on this axis', - 'Use with `tick0`.', - 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', - 'is the tick number. For example,', - 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', - 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', - 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', - 'If the axis `type` is *date*, then you must convert the time to milliseconds.', - 'For example, to set the interval between ticks to one day,', - 'set `dtick` to 86400000.0.' - ].join(' ') - }, - tickvals: { - valType: 'data_array', - description: [ - 'Sets the values at which ticks on this axis appear.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `ticktext`.' - ].join(' ') - }, - ticktext: { - valType: 'data_array', - description: [ - 'Sets the text displayed at the ticks position via `tickvals`.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `ticktext`.' - ].join(' ') - }, - ticks: { - valType: 'enumerated', - values: ['outside', 'inside', ''], - role: 'style', - description: [ - 'Determines whether ticks are drawn or not.', - 'If **, this axis\' ticks are not drawn.', - 'If *outside* (*inside*), this axis\' are drawn outside (inside)', - 'the axis lines.' - ].join(' ') - }, - mirror: { - valType: 'enumerated', - values: [true, 'ticks', false, 'all', 'allticks'], - dflt: false, - role: 'style', - description: [ - 'Determines if the axis lines or/and ticks are mirrored to', - 'the opposite side of the plotting area.', - 'If *true*, the axis lines are mirrored.', - 'If *ticks*, the axis lines and ticks are mirrored.', - 'If *false*, mirroring is disable.', - 'If *all*, axis lines are mirrored on all shared-axes subplots.', - 'If *allticks*, axis lines and ticks are mirrored', - 'on all shared-axes subplots.' - ].join(' ') - }, - ticklen: { - valType: 'number', - min: 0, - dflt: 5, - role: 'style', - description: 'Sets the tick length (in px).' - }, - tickwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the tick width (in px).' - }, - tickcolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the tick color.' - }, - showticklabels: { - valType: 'boolean', - dflt: true, - role: 'style', - description: 'Determines whether or not the tick labels are drawn.' - }, - tickfont: extendFlat({}, fontAttrs, { - description: 'Sets the tick font.' - }), - tickangle: { - valType: 'angle', - dflt: 'auto', - role: 'style', - description: [ - 'Sets the angle of the tick labels with respect to the horizontal.', - 'For example, a `tickangle` of -90 draws the tick labels', - 'vertically.' - ].join(' ') - }, - tickprefix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label prefix.' - }, - showtickprefix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all tick labels are displayed with a prefix.', - 'If *first*, only the first tick is displayed with a prefix.', - 'If *last*, only the last tick is displayed with a suffix.', - 'If *none*, tick prefixes are hidden.' - ].join(' ') - }, - ticksuffix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label suffix.' - }, - showticksuffix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: 'Same as `showtickprefix` but for tick suffixes.' - }, - showexponent: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all exponents are shown besides their significands.', - 'If *first*, only the exponent of the first tick is shown.', - 'If *last*, only the exponent of the last tick is shown.', - 'If *none*, no exponents appear.' - ].join(' ') - }, - exponentformat: { - valType: 'enumerated', - values: ['none', 'e', 'E', 'power', 'SI', 'B'], - dflt: 'B', - role: 'style', - description: [ - 'Determines a formatting rule for the tick exponents.', - 'For example, consider the number 1,000,000,000.', - 'If *none*, it appears as 1,000,000,000.', - 'If *e*, 1e+9.', - 'If *E*, 1E+9.', - 'If *power*, 1x10^9 (with 9 in a super script).', - 'If *SI*, 1G.', - 'If *B*, 1B.' - ].join(' ') - }, - tickformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the tick label formatting rule using the', - 'python/d3 number formatting language.', - 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', - 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', - 'for more info.' - ].join(' ') - }, - hoverformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the hover text formatting rule for data values on this axis,', - 'using the python/d3 number formatting language.', - 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', - 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', - 'for more info.' - ].join(' ') - }, - // lines and grids - showline: { - valType: 'boolean', - dflt: false, - role: 'style', - description: [ - 'Determines whether or not a line bounding this axis is drawn.' - ].join(' ') - }, - linecolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the axis line color.' - }, - linewidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the axis line.' - }, - showgrid: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not grid lines are drawn.', - 'If *true*, the grid lines are drawn at every tick mark.' - ].join(' ') - }, - gridcolor: { - valType: 'color', - dflt: colorAttrs.lightLine, - role: 'style', - description: 'Sets the color of the grid lines.' - }, - gridwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the grid lines.' - }, - zeroline: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not a line is drawn at along the 0 value', - 'of this axis.', - 'If *true*, the zero line is drawn on top of the grid lines.' - ].join(' ') - }, - zerolinecolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the line color of the zero line.' - }, - zerolinewidth: { - valType: 'number', - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the zero line.' - }, - // positioning attributes - // anchor: not used directly, just put here for reference - // values are any opposite-letter axis id - anchor: { - valType: 'enumerated', - values: [ - 'free', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), - Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', - 'the corresponding opposite-letter axis.', - 'If set to *free*, this axis\' position is determined by `position`.' - ].join(' ') - }, - // side: not used directly, as values depend on direction - // values are top, bottom for x axes, and left, right for y - side: { - valType: 'enumerated', - values: ['top', 'bottom', 'left', 'right'], - role: 'info', - description: [ - 'Determines whether a x (y) axis is positioned', - 'at the *bottom* (*left*) or *top* (*right*)', - 'of the plotting area.' - ].join(' ') - }, - // overlaying: not used directly, just put here for reference - // values are false and any other same-letter axis id that's not - // itself overlaying anything - overlaying: { - valType: 'enumerated', - values: [ - 'free', - Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), - Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set a same-letter axis id, this axis is overlaid on top of', - 'the corresponding same-letter axis.', - 'If *false*, this axis does not overlay any same-letter axes.' - ].join(' ') - }, - domain: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the domain of this axis (in plot fraction).' - ].join(' ') - }, - position: { - valType: 'number', - min: 0, - max: 1, - dflt: 0, - role: 'style', - description: [ - 'Sets the position of this axis in the plotting space', - '(in normalized coordinates).', - 'Only has an effect if `anchor` is set to *free*.' - ].join(' ') - }, - - _deprecated: { - autotick: { - valType: 'boolean', - role: 'info', - description: [ - 'Obsolete.', - 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', - 'Set `tickmode` to *linear* for `autotick` *false*.' - ].join(' ') - } - } + }, + yaxis: { + valType: 'axisid', + role: 'info', + dflt: 'y', + description: [ + 'Sets a reference between this trace\'s y coordinates and', + 'a 2D cartesian y axis.', + 'If *y* (the default value), the y coordinates refer to', + '`layout.yaxis`.', + 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' + ].join(' ') + } }; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 00a9e962031..fae0224ddc0 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -6,12 +6,11 @@ var isNumeric = require('fast-isnumeric'); var axes = module.exports = {}; -axes.traceAttributes = require('./trace_attributes'); +axes.attributes = require('./attributes'); -Plotly.Plots.registerSubplot('cartesian', ['xaxis', 'yaxis'], ['x', 'y'], - axes.traceAttributes); +Plotly.Plots.registerSubplot('cartesian', ['xaxis', 'yaxis'], ['x', 'y'], axes.attributes); -axes.layoutAttributes = require('./attributes'); +axes.layoutAttributes = require('./layout_attributes'); var xAxisMatch = /^xaxis[0-9]*$/, yAxisMatch = /^yaxis[0-9]*$/; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js new file mode 100644 index 00000000000..00ce79c2cde --- /dev/null +++ b/src/plots/cartesian/layout_attributes.js @@ -0,0 +1,453 @@ +var Plotly = require('../../plotly'); +var fontAttrs = require('../font_attributes'); +var colorAttrs = require('../../components/color/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; + + +module.exports = { + title: { + valType: 'string', + role: 'info', + description: 'Sets the title of this axis.' + }, + titlefont: extendFlat({}, fontAttrs, { + description: [ + 'Sets this axis\' title font.' + ].join(' ') + }), + type: { + valType: 'enumerated', + // '-' means we haven't yet run autotype or couldn't find any data + // it gets turned into linear in td._fullLayout but not copied back + // to td.data like the others are. + values: ['-', 'linear', 'log', 'date', 'category'], + dflt: '-', + role: 'info', + description: [ + 'Sets the axis type.', + 'By default, plotly attempts to determined the axis type', + 'by looking into the data of the traces that referenced', + 'the axis in question.' + ].join(' ') + }, + autorange: { + valType: 'enumerated', + values: [true, false, 'reversed'], + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the range of this axis is', + 'computed in relation to the input data.', + 'See `rangemode` for more info.', + 'If `range` is provided, then `autorange` is set to *false*.' + ].join(' ') + }, + rangemode: { + valType: 'enumerated', + values: ['normal', 'tozero', 'nonnegative'], + dflt: 'normal', + role: 'style', + description: [ + 'If *normal*, the range is computed in relation to the extrema', + 'of the input data.', + 'If *tozero*`, the range extends to 0,', + 'regardless of the input data', + 'If *nonnegative*, the range is non-negative,', + 'regardless of the input data.' + ].join(' ') + }, + range: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number'}, + {valType: 'number'} + ], + description: [ + 'Sets the range of this axis.', + 'If the axis `type` is *log*, then you must take the log of your desired range', + '(e.g. to set the range from 1 to 100, set the range from 0 to 2).', + 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', + '(the number of milliseconds since January 1st, 1970). For example, to set the date range from', + 'January 1st 1970 to November 4th, 2013, set the range from 0 to 1380844800000.0' + ].join(' ') + }, + fixedrange: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not this axis is zoom-able.', + 'If true, then zoom is disabled.' + ].join(' ') + }, + // ticks + tickmode: { + valType: 'enumerated', + values: ['auto', 'linear', 'array'], + role: 'info', + description: [ + 'Sets the tick mode for this axis.', + 'If *auto*, the number of ticks is set via `nticks`.', + 'If *linear*, the placement of the ticks is determined by', + 'a starting position `tick0` and a tick step `dtick`', + '(*linear* is the default value if `tick0` and `dtick` are provided).', + 'If *array*, the placement of the ticks is set via `tickvals`', + 'and the tick text is `ticktext`.', + '(*array* is the default value if `tickvals` is provided).' + ].join(' ') + }, + nticks: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Sets the number of ticks.', + 'Has an effect only if `tickmode` is set to *auto*.' + ].join(' ') + }, + tick0: { + valType: 'number', + dflt: 0, + role: 'style', + description: [ + 'Sets the placement of the first tick on this axis.', + 'Use with `dtick`.', + 'If the axis `type` is *log*, then you must take the log of your starting tick', + '(e.g. to set the starting tick to 100, set the `tick0` to 2).', + 'If the axis `type` is *date*, then you must convert the date to unix time in milliseconds', + '(the number of milliseconds since January 1st, 1970).', + 'For example, to set the starting tick to', + 'November 4th, 2013, set the range to 1380844800000.0.' + ].join(' ') + }, + dtick: { + valType: 'any', + dflt: 1, + role: 'style', + description: [ + 'Sets the step in-between ticks on this axis', + 'Use with `tick0`.', + 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', + 'is the tick number. For example,', + 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', + 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', + 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', + 'If the axis `type` is *date*, then you must convert the time to milliseconds.', + 'For example, to set the interval between ticks to one day,', + 'set `dtick` to 86400000.0.' + ].join(' ') + }, + tickvals: { + valType: 'data_array', + description: [ + 'Sets the values at which ticks on this axis appear.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `ticktext`.' + ].join(' ') + }, + ticktext: { + valType: 'data_array', + description: [ + 'Sets the text displayed at the ticks position via `tickvals`.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `ticktext`.' + ].join(' ') + }, + ticks: { + valType: 'enumerated', + values: ['outside', 'inside', ''], + role: 'style', + description: [ + 'Determines whether ticks are drawn or not.', + 'If **, this axis\' ticks are not drawn.', + 'If *outside* (*inside*), this axis\' are drawn outside (inside)', + 'the axis lines.' + ].join(' ') + }, + mirror: { + valType: 'enumerated', + values: [true, 'ticks', false, 'all', 'allticks'], + dflt: false, + role: 'style', + description: [ + 'Determines if the axis lines or/and ticks are mirrored to', + 'the opposite side of the plotting area.', + 'If *true*, the axis lines are mirrored.', + 'If *ticks*, the axis lines and ticks are mirrored.', + 'If *false*, mirroring is disable.', + 'If *all*, axis lines are mirrored on all shared-axes subplots.', + 'If *allticks*, axis lines and ticks are mirrored', + 'on all shared-axes subplots.' + ].join(' ') + }, + ticklen: { + valType: 'number', + min: 0, + dflt: 5, + role: 'style', + description: 'Sets the tick length (in px).' + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the tick width (in px).' + }, + tickcolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the tick color.' + }, + showticklabels: { + valType: 'boolean', + dflt: true, + role: 'style', + description: 'Determines whether or not the tick labels are drawn.' + }, + tickfont: extendFlat({}, fontAttrs, { + description: 'Sets the tick font.' + }), + tickangle: { + valType: 'angle', + dflt: 'auto', + role: 'style', + description: [ + 'Sets the angle of the tick labels with respect to the horizontal.', + 'For example, a `tickangle` of -90 draws the tick labels', + 'vertically.' + ].join(' ') + }, + tickprefix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label prefix.' + }, + showtickprefix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all tick labels are displayed with a prefix.', + 'If *first*, only the first tick is displayed with a prefix.', + 'If *last*, only the last tick is displayed with a suffix.', + 'If *none*, tick prefixes are hidden.' + ].join(' ') + }, + ticksuffix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label suffix.' + }, + showticksuffix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: 'Same as `showtickprefix` but for tick suffixes.' + }, + showexponent: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all exponents are shown besides their significands.', + 'If *first*, only the exponent of the first tick is shown.', + 'If *last*, only the exponent of the last tick is shown.', + 'If *none*, no exponents appear.' + ].join(' ') + }, + exponentformat: { + valType: 'enumerated', + values: ['none', 'e', 'E', 'power', 'SI', 'B'], + dflt: 'B', + role: 'style', + description: [ + 'Determines a formatting rule for the tick exponents.', + 'For example, consider the number 1,000,000,000.', + 'If *none*, it appears as 1,000,000,000.', + 'If *e*, 1e+9.', + 'If *E*, 1E+9.', + 'If *power*, 1x10^9 (with 9 in a super script).', + 'If *SI*, 1G.', + 'If *B*, 1B.' + ].join(' ') + }, + tickformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the tick label formatting rule using the', + 'python/d3 number formatting language.', + 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', + 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', + 'for more info.' + ].join(' ') + }, + hoverformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the hover text formatting rule for data values on this axis,', + 'using the python/d3 number formatting language.', + 'See https://github.com/mbostock/d3/wiki/Formatting#numbers', + 'or https://docs.python.org/release/3.1.3/library/string.html#formatspec', + 'for more info.' + ].join(' ') + }, + // lines and grids + showline: { + valType: 'boolean', + dflt: false, + role: 'style', + description: [ + 'Determines whether or not a line bounding this axis is drawn.' + ].join(' ') + }, + linecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the axis line color.' + }, + linewidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the axis line.' + }, + showgrid: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not grid lines are drawn.', + 'If *true*, the grid lines are drawn at every tick mark.' + ].join(' ') + }, + gridcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + role: 'style', + description: 'Sets the color of the grid lines.' + }, + gridwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the grid lines.' + }, + zeroline: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not a line is drawn at along the 0 value', + 'of this axis.', + 'If *true*, the zero line is drawn on top of the grid lines.' + ].join(' ') + }, + zerolinecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the line color of the zero line.' + }, + zerolinewidth: { + valType: 'number', + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the zero line.' + }, + // positioning attributes + // anchor: not used directly, just put here for reference + // values are any opposite-letter axis id + anchor: { + valType: 'enumerated', + values: [ + 'free', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), + Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', + 'the corresponding opposite-letter axis.', + 'If set to *free*, this axis\' position is determined by `position`.' + ].join(' ') + }, + // side: not used directly, as values depend on direction + // values are top, bottom for x axes, and left, right for y + side: { + valType: 'enumerated', + values: ['top', 'bottom', 'left', 'right'], + role: 'info', + description: [ + 'Determines whether a x (y) axis is positioned', + 'at the *bottom* (*left*) or *top* (*right*)', + 'of the plotting area.' + ].join(' ') + }, + // overlaying: not used directly, just put here for reference + // values are false and any other same-letter axis id that's not + // itself overlaying anything + overlaying: { + valType: 'enumerated', + values: [ + 'free', + Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString(), + Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set a same-letter axis id, this axis is overlaid on top of', + 'the corresponding same-letter axis.', + 'If *false*, this axis does not overlay any same-letter axes.' + ].join(' ') + }, + domain: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the domain of this axis (in plot fraction).' + ].join(' ') + }, + position: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + role: 'style', + description: [ + 'Sets the position of this axis in the plotting space', + '(in normalized coordinates).', + 'Only has an effect if `anchor` is set to *free*.' + ].join(' ') + }, + + _deprecated: { + autotick: { + valType: 'boolean', + role: 'info', + description: [ + 'Obsolete.', + 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', + 'Set `tickmode` to *linear* for `autotick` *false*.' + ].join(' ') + } + } +}; diff --git a/src/plots/cartesian/trace_attributes.js b/src/plots/cartesian/trace_attributes.js deleted file mode 100644 index a7c0127c1b8..00000000000 --- a/src/plots/cartesian/trace_attributes.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - xaxis: { - valType: 'axisid', - role: 'info', - dflt: 'x', - description: [ - 'Sets a reference between this trace\'s x coordinates and', - 'a 2D cartesian x axis.', - 'If *x* (the default value), the x coordinates refer to', - '`layout.xaxis`.', - 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - }, - yaxis: { - valType: 'axisid', - role: 'info', - dflt: 'y', - description: [ - 'Sets a reference between this trace\'s y coordinates and', - 'a 2D cartesian y axis.', - 'If *y* (the default value), the y coordinates refer to', - '`layout.yaxis`.', - 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - } -}; diff --git a/src/plots/geo/layout/attributes.js b/src/plots/geo/layout/attributes.js index 24e87e052fd..9ad8341f0f9 100644 --- a/src/plots/geo/layout/attributes.js +++ b/src/plots/geo/layout/attributes.js @@ -1,247 +1,15 @@ -var colorAttrs = require('../../../components/color/attributes'); -var constants = require('../../../constants/geo_constants'); -var geoAxesAttrs = require('./axis_attributes'); - - module.exports = { - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this map', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this map', - '(in plot fraction).' - ].join(' ') - } - }, - resolution: { - valType: 'enumerated', - values: [110, 50], + geo: { + valType: 'geoid', role: 'info', - dflt: 110, - coerceNumber: true, + dflt: 'geo', description: [ - 'Sets the resolution of the base layers.', - 'The values have units of km/mm', - 'e.g. 110 corresponds to a scale ratio of 1:110,000,000.' + 'Sets a reference between this trace\'s geospatial coordinates and', + 'a geographic map.', + 'If *geo* (the default value), the geospatial coordinates refer to', + '`layout.geo`.', + 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', + 'and so on.' ].join(' ') - }, - scope: { - valType: 'enumerated', - role: 'info', - values: Object.keys(constants.scopeDefaults), - dflt: 'world', - description: 'Set the scope of the map.' - }, - projection: { - type: { - valType: 'enumerated', - role: 'info', - values: Object.keys(constants.projNames), - description: 'Sets the projection type.' - }, - rotation: { - lon: { - valType: 'number', - role: 'info', - description: [ - 'Rotates the map along parallels', - '(in degrees East).' - ].join(' ') - }, - lat: { - valType: 'number', - role: 'info', - description: [ - 'Rotates the map along meridians', - '(in degrees North).' - ].join(' ') - }, - roll: { - valType: 'number', - role: 'info', - description: [ - 'Roll the map (in degrees)', - 'For example, a roll of *180* makes the map appear upside down.' - ].join(' ') - } - }, - parallels: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number'}, - {valType: 'number'} - ], - description: [ - 'For conic projection types only.', - 'Sets the parallels (tangent, secant)', - 'where the cone intersects the sphere.' - ].join(' ') - }, - scale: { - valType: 'number', - role: 'info', - min: 0, - max: 10, - dflt: 1, - description: 'Zooms in or out on the map view.' - } - }, - showcoastlines: { - valType: 'boolean', - role: 'info', - description: 'Sets whether or not the coastlines are drawn.' - }, - coastlinecolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the coastline color.' - }, - coastlinewidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the coastline stroke width (in px).' - }, - showland: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not land masses are filled in color.' - }, - landcolor: { - valType: 'color', - role: 'style', - dflt: constants.landColor, - description: 'Sets the land mass color.' - }, - showocean: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not oceans are filled in color.' - }, - oceancolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets the ocean color' - }, - showlakes: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not lakes are drawn.' - }, - lakecolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets the color of the lakes.' - }, - showrivers: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not rivers are drawn.' - }, - rivercolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets color of the rivers.' - }, - riverwidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the rivers.' - }, - showcountries: { - valType: 'boolean', - role: 'info', - description: 'Sets whether or not country boundaries are drawn.' - }, - countrycolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets line color of the country boundaries.' - }, - countrywidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets line width (in px) of the country boundaries.' - }, - showsubunits: { - valType: 'boolean', - role: 'info', - description: [ - 'Sets whether or not boundaries of subunits within countries', - '(e.g. states, provinces) are drawn.' - ].join(' ') - }, - subunitcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the color of the subunits boundaries.' - }, - subunitwidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the subunits boundaries.' - }, - showframe: { - valType: 'boolean', - role: 'info', - description: 'Sets whether or not a frame is drawn around the map.' - }, - framecolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the color the frame.' - }, - framewidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the frame.' - }, - bgcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.background, - description: 'Set the background color of the map' - }, - lonaxis: geoAxesAttrs, - lataxis: geoAxesAttrs + } }; diff --git a/src/plots/geo/layout/defaults.js b/src/plots/geo/layout/defaults.js index 2d6f1281ef5..bf8bd1a8b32 100644 --- a/src/plots/geo/layout/defaults.js +++ b/src/plots/geo/layout/defaults.js @@ -2,7 +2,7 @@ var Plotly = require('../../../plotly'); var constants = require('../../../constants/geo_constants'); -var layoutAttributes = require('./attributes'); +var layoutAttributes = require('./layout_attributes'); var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); diff --git a/src/plots/geo/layout/layout.js b/src/plots/geo/layout/layout.js index 197524e6f09..91616991e53 100644 --- a/src/plots/geo/layout/layout.js +++ b/src/plots/geo/layout/layout.js @@ -1,9 +1,9 @@ 'use strict'; var Plotly = require('../../../plotly'); -var traceAttributes = require('./trace_attributes'); +var attributes = require('./attributes'); -Plotly.Plots.registerSubplot('geo', 'geo', 'geo', traceAttributes); +Plotly.Plots.registerSubplot('geo', 'geo', 'geo', attributes); -exports.layoutAttributes = require('./attributes'); +exports.layoutAttributes = require('./layout_attributes'); exports.supplyLayoutDefaults = require('./defaults'); diff --git a/src/plots/geo/layout/layout_attributes.js b/src/plots/geo/layout/layout_attributes.js new file mode 100644 index 00000000000..24e87e052fd --- /dev/null +++ b/src/plots/geo/layout/layout_attributes.js @@ -0,0 +1,247 @@ +var colorAttrs = require('../../../components/color/attributes'); +var constants = require('../../../constants/geo_constants'); +var geoAxesAttrs = require('./axis_attributes'); + + +module.exports = { + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this map', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this map', + '(in plot fraction).' + ].join(' ') + } + }, + resolution: { + valType: 'enumerated', + values: [110, 50], + role: 'info', + dflt: 110, + coerceNumber: true, + description: [ + 'Sets the resolution of the base layers.', + 'The values have units of km/mm', + 'e.g. 110 corresponds to a scale ratio of 1:110,000,000.' + ].join(' ') + }, + scope: { + valType: 'enumerated', + role: 'info', + values: Object.keys(constants.scopeDefaults), + dflt: 'world', + description: 'Set the scope of the map.' + }, + projection: { + type: { + valType: 'enumerated', + role: 'info', + values: Object.keys(constants.projNames), + description: 'Sets the projection type.' + }, + rotation: { + lon: { + valType: 'number', + role: 'info', + description: [ + 'Rotates the map along parallels', + '(in degrees East).' + ].join(' ') + }, + lat: { + valType: 'number', + role: 'info', + description: [ + 'Rotates the map along meridians', + '(in degrees North).' + ].join(' ') + }, + roll: { + valType: 'number', + role: 'info', + description: [ + 'Roll the map (in degrees)', + 'For example, a roll of *180* makes the map appear upside down.' + ].join(' ') + } + }, + parallels: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number'}, + {valType: 'number'} + ], + description: [ + 'For conic projection types only.', + 'Sets the parallels (tangent, secant)', + 'where the cone intersects the sphere.' + ].join(' ') + }, + scale: { + valType: 'number', + role: 'info', + min: 0, + max: 10, + dflt: 1, + description: 'Zooms in or out on the map view.' + } + }, + showcoastlines: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not the coastlines are drawn.' + }, + coastlinecolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the coastline color.' + }, + coastlinewidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the coastline stroke width (in px).' + }, + showland: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not land masses are filled in color.' + }, + landcolor: { + valType: 'color', + role: 'style', + dflt: constants.landColor, + description: 'Sets the land mass color.' + }, + showocean: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not oceans are filled in color.' + }, + oceancolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets the ocean color' + }, + showlakes: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not lakes are drawn.' + }, + lakecolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets the color of the lakes.' + }, + showrivers: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not rivers are drawn.' + }, + rivercolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets color of the rivers.' + }, + riverwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the rivers.' + }, + showcountries: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not country boundaries are drawn.' + }, + countrycolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets line color of the country boundaries.' + }, + countrywidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets line width (in px) of the country boundaries.' + }, + showsubunits: { + valType: 'boolean', + role: 'info', + description: [ + 'Sets whether or not boundaries of subunits within countries', + '(e.g. states, provinces) are drawn.' + ].join(' ') + }, + subunitcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the color of the subunits boundaries.' + }, + subunitwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the subunits boundaries.' + }, + showframe: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not a frame is drawn around the map.' + }, + framecolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the color the frame.' + }, + framewidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the frame.' + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: 'Set the background color of the map' + }, + lonaxis: geoAxesAttrs, + lataxis: geoAxesAttrs +}; diff --git a/src/plots/geo/layout/trace_attributes.js b/src/plots/geo/layout/trace_attributes.js deleted file mode 100644 index 9ad8341f0f9..00000000000 --- a/src/plots/geo/layout/trace_attributes.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - geo: { - valType: 'geoid', - role: 'info', - dflt: 'geo', - description: [ - 'Sets a reference between this trace\'s geospatial coordinates and', - 'a geographic map.', - 'If *geo* (the default value), the geospatial coordinates refer to', - '`layout.geo`.', - 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', - 'and so on.' - ].join(' ') - } -}; diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index d87eceb2b78..a63683471db 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -17,7 +17,7 @@ var AXES = ['xaxis', 'yaxis']; var STATIC_CANVAS, STATIC_CONTEXT; Plotly.Plots.registerSubplot('gl2d', ['xaxis', 'yaxis'], ['x', 'y'], - Plotly.Axes.traceAttributes); + Plotly.Axes.attributes); function Scene2D(options, fullLayout) { this.container = options.container; diff --git a/src/plots/gl3d/layout/attributes.js b/src/plots/gl3d/layout/attributes.js index 7b0234bbf5d..13f119f6646 100644 --- a/src/plots/gl3d/layout/attributes.js +++ b/src/plots/gl3d/layout/attributes.js @@ -1,140 +1,15 @@ -'use strict'; - -var gl3dAxisAttrs = require('./axis_attributes'); -var extendFlat = require('../../../lib/extend').extendFlat; - -function makeVector(x, y, z) { - return { - x: { - valType: 'number', - role: 'info', - dflt: x - }, - y: { - valType: 'number', - role: 'info', - dflt: y - }, - z: { - valType: 'number', - role: 'info', - dflt: z - } - }; -} - module.exports = { - bgcolor: { - valType: 'color', - role: 'style', - dflt: 'rgba(0,0,0,0)' - }, - camera: { - up: extendFlat(makeVector(0, 0, 1), { - description: [ - 'Sets the (x,y,z) components of the \'up\' camera vector.', - 'This vector determines the up direction of this scene', - 'with respect to the page.', - 'The default is *{x: 0, y: 0, z: 1}* which means that', - 'the z axis points up.' - ].join(' ') - }), - center: extendFlat(makeVector(0, 0, 0), { - description: [ - 'Sets the (x,y,z) components of the \'center\' camera vector', - 'This vector determines the translation (x,y,z) space', - 'about the center of this scene.', - 'By default, there is no such translation.' - ].join(' ') - }), - eye: extendFlat(makeVector(1.25, 1.25, 1.25), { - description: [ - 'Sets the (x,y,z) components of the \'eye\' camera vector.', - 'This vector determines the view point about the origin', - 'of this scene.' - ].join(' ') - }) - }, - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this scene', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this scene', - '(in plot fraction).' - ].join(' ') - } - }, - aspectmode: { - valType: 'enumerated', + scene: { + valType: 'sceneid', role: 'info', - values: ['auto', 'cube', 'data', 'manual'], - dflt: 'auto', + dflt: 'scene', description: [ - 'If *cube*, this scene\'s axes are drawn as a cube,', - 'regardless of the axes\' ranges.', - - 'If *data*, this scene\'s axes are drawn', - 'in proportion with the axes\' ranges.', - - 'If *manual*, this scene\'s axes are drawn', - 'in proportion with the input of *aspectratio*', - '(the default behavior if *aspectratio* is provided).', - - 'If *auto*, this scene\'s axes are drawn', - 'using the results of *data* except when one axis', - 'is more than four times the size of the two others,', - 'where in that case the results of *cube* are used.' + 'Sets a reference between this trace\'s 3D coordinate system and', + 'a 3D scene.', + 'If *scene* (the default value), the (x,y,z) coordinates refer to', + '`layout.scene`.', + 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', + 'and so on.' ].join(' ') - }, - aspectratio: { // must be positive (0's are coerced to 1) - x: { - valType: 'number', - role: 'info', - min: 0 - }, - y: { - valType: 'number', - role: 'info', - min: 0 - }, - z: { - valType: 'number', - role: 'info', - min: 0 - }, - description: [ - 'Sets this scene\'s axis aspectratio.' - ].join(' ') - }, - - xaxis: gl3dAxisAttrs, - yaxis: gl3dAxisAttrs, - zaxis: gl3dAxisAttrs, - - _deprecated: { - cameraposition: { - valType: 'info_array', - role: 'info', - description: 'Obsolete. Use `camera` instead.' - } } }; diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index eb71968348b..31bcb9a0ece 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -1,4 +1,4 @@ -var axesAttrs = require('../../cartesian/attributes'); +var axesAttrs = require('../../cartesian/layout_attributes'); var extendFlat = require('../../../lib/extend').extendFlat; diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index c6937f73c4f..c5826c27b0a 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../../plotly'); -var layoutAttributes = require('./attributes'); +var layoutAttributes = require('./layout_attributes'); var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); diff --git a/src/plots/gl3d/layout/layout.js b/src/plots/gl3d/layout/layout.js index b9fc14440ce..42a5cb4be7b 100644 --- a/src/plots/gl3d/layout/layout.js +++ b/src/plots/gl3d/layout/layout.js @@ -1,14 +1,14 @@ 'use strict'; var Plotly = require('../../../plotly'); -var traceAttributes = require('./trace_attributes'); +var attributes = require('./attributes'); var axesNames = ['xaxis', 'yaxis', 'zaxis']; var noop = function () {}; -Plotly.Plots.registerSubplot('gl3d', 'scene', 'scene', traceAttributes); +Plotly.Plots.registerSubplot('gl3d', 'scene', 'scene', attributes); -exports.layoutAttributes = require('./attributes'); +exports.layoutAttributes = require('./layout_attributes'); exports.supplyLayoutDefaults = require('./defaults'); diff --git a/src/plots/gl3d/layout/layout_attributes.js b/src/plots/gl3d/layout/layout_attributes.js new file mode 100644 index 00000000000..7b0234bbf5d --- /dev/null +++ b/src/plots/gl3d/layout/layout_attributes.js @@ -0,0 +1,140 @@ +'use strict'; + +var gl3dAxisAttrs = require('./axis_attributes'); +var extendFlat = require('../../../lib/extend').extendFlat; + +function makeVector(x, y, z) { + return { + x: { + valType: 'number', + role: 'info', + dflt: x + }, + y: { + valType: 'number', + role: 'info', + dflt: y + }, + z: { + valType: 'number', + role: 'info', + dflt: z + } + }; +} + +module.exports = { + bgcolor: { + valType: 'color', + role: 'style', + dflt: 'rgba(0,0,0,0)' + }, + camera: { + up: extendFlat(makeVector(0, 0, 1), { + description: [ + 'Sets the (x,y,z) components of the \'up\' camera vector.', + 'This vector determines the up direction of this scene', + 'with respect to the page.', + 'The default is *{x: 0, y: 0, z: 1}* which means that', + 'the z axis points up.' + ].join(' ') + }), + center: extendFlat(makeVector(0, 0, 0), { + description: [ + 'Sets the (x,y,z) components of the \'center\' camera vector', + 'This vector determines the translation (x,y,z) space', + 'about the center of this scene.', + 'By default, there is no such translation.' + ].join(' ') + }), + eye: extendFlat(makeVector(1.25, 1.25, 1.25), { + description: [ + 'Sets the (x,y,z) components of the \'eye\' camera vector.', + 'This vector determines the view point about the origin', + 'of this scene.' + ].join(' ') + }) + }, + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this scene', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this scene', + '(in plot fraction).' + ].join(' ') + } + }, + aspectmode: { + valType: 'enumerated', + role: 'info', + values: ['auto', 'cube', 'data', 'manual'], + dflt: 'auto', + description: [ + 'If *cube*, this scene\'s axes are drawn as a cube,', + 'regardless of the axes\' ranges.', + + 'If *data*, this scene\'s axes are drawn', + 'in proportion with the axes\' ranges.', + + 'If *manual*, this scene\'s axes are drawn', + 'in proportion with the input of *aspectratio*', + '(the default behavior if *aspectratio* is provided).', + + 'If *auto*, this scene\'s axes are drawn', + 'using the results of *data* except when one axis', + 'is more than four times the size of the two others,', + 'where in that case the results of *cube* are used.' + ].join(' ') + }, + aspectratio: { // must be positive (0's are coerced to 1) + x: { + valType: 'number', + role: 'info', + min: 0 + }, + y: { + valType: 'number', + role: 'info', + min: 0 + }, + z: { + valType: 'number', + role: 'info', + min: 0 + }, + description: [ + 'Sets this scene\'s axis aspectratio.' + ].join(' ') + }, + + xaxis: gl3dAxisAttrs, + yaxis: gl3dAxisAttrs, + zaxis: gl3dAxisAttrs, + + _deprecated: { + cameraposition: { + valType: 'info_array', + role: 'info', + description: 'Obsolete. Use `camera` instead.' + } + } +}; diff --git a/src/plots/gl3d/layout/trace_attributes.js b/src/plots/gl3d/layout/trace_attributes.js deleted file mode 100644 index 13f119f6646..00000000000 --- a/src/plots/gl3d/layout/trace_attributes.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - scene: { - valType: 'sceneid', - role: 'info', - dflt: 'scene', - description: [ - 'Sets a reference between this trace\'s 3D coordinate system and', - 'a 3D scene.', - 'If *scene* (the default value), the (x,y,z) coordinates refer to', - '`layout.scene`.', - 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', - 'and so on.' - ].join(' ') - } -}; diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 08187ebb398..d71d293f263 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -1,6 +1,6 @@ var ScatterGeoAttrs = require('../scattergeo/attributes'); var colorscaleAttrs = require('../../components/colorscale/attributes'); -var plotAttrs = require('../../plots/plots/attributes'); +var plotAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; var ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line; diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index 613d70bf867..adcf6200f5e 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -1,6 +1,6 @@ var colorAttrs = require('../../components/color/attributes'); -var fontAttrs = require('../../plots/plots/font_attributes'); -var plotAttrs = require('../../plots/plots/attributes'); +var fontAttrs = require('../../plots/font_attributes'); +var plotAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 2427c70e3c9..9a9f18bedbb 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -1,5 +1,5 @@ var scatterAttrs = require('../scatter/attributes'); -var plotAttrs = require('../../plots/plots/attributes'); +var plotAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; var scatterMarkerAttrs = scatterAttrs.marker, From 9d2568deaf309afece8103ee3ff7ed838015427b Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:26:22 -0500 Subject: [PATCH 28/38] rm check for Plotly.ErrorBars (rm sub-bundles for now) --- src/plot_api/plot_api.js | 8 +++----- src/traces/bars/bars.js | 8 +++----- src/traces/scatter/scatter.js | 19 +++++++------------ src/traces/scatter3d/defaults.js | 8 +++----- src/traces/scattergl/defaults.js | 6 ++---- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3cc56c97448..eff5bdfb3e3 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -199,10 +199,8 @@ Plotly.plot = function(gd, data, layout, config) { Plotly.Lib.markTime('done with bar/box adjustments'); // calc and autorange for errorbars - if(Plotly.ErrorBars) { - Plotly.ErrorBars.calc(gd); - Plotly.Lib.markTime('done Plotly.ErrorBars.calc'); - } + Plotly.ErrorBars.calc(gd); + Plotly.Lib.markTime('done Plotly.ErrorBars.calc'); // TODO: autosize extra for text markers return Plotly.Lib.syncOrAsync([ @@ -302,7 +300,7 @@ Plotly.plot = function(gd, data, layout, config) { } // finally do all error bars at once - if(gd._fullLayout._hasCartesian && Plotly.ErrorBars) { + if(gd._fullLayout._hasCartesian) { Plotly.ErrorBars.plot(gd, subplotInfo, cdError); Plotly.Lib.markTime('done ErrorBars'); } diff --git a/src/traces/bars/bars.js b/src/traces/bars/bars.js index 49b43756345..65dd651541a 100644 --- a/src/traces/bars/bars.js +++ b/src/traces/bars/bars.js @@ -61,10 +61,8 @@ bars.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { coerce('text'); // override defaultColor for error bars with defaultLine - if(Plotly.ErrorBars) { - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, Plotly.Color.defaultLine, {axis: 'y'}); - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, Plotly.Color.defaultLine, {axis: 'x', inherit: 'y'}); - } + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, Plotly.Color.defaultLine, {axis: 'y'}); + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, Plotly.Color.defaultLine, {axis: 'x', inherit: 'y'}); }; bars.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { @@ -558,7 +556,7 @@ bars.hoverPoints = function(pointData, xval, yval, hovermode) { if(di.tx) pointData.text = di.tx; - if(Plotly.ErrorBars) Plotly.ErrorBars.hoverInfo(di, trace, pointData); + Plotly.ErrorBars.hoverInfo(di, trace, pointData); return [pointData]; }; diff --git a/src/traces/scatter/scatter.js b/src/traces/scatter/scatter.js index e11ec585763..b2f3ff54e1d 100644 --- a/src/traces/scatter/scatter.js +++ b/src/traces/scatter/scatter.js @@ -95,10 +95,8 @@ scatter.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) { if(!scatter.hasLines(traceOut)) lineShapeDefaults(traceIn, traceOut, coerce); } - if(Plotly.ErrorBars) { - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); - } + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); }; // common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' @@ -367,13 +365,10 @@ scatter.calc = function(gd, trace) { } // if no error bars, markers or text, or fill to y=0 remove x padding - else if( - (Plotly.ErrorBars===undefined || !trace.error_y.visible) && - ( - ['tonexty', 'tozeroy'].indexOf(trace.fill)!==-1 || - (!scatter.hasMarkers(trace) && !scatter.hasText(trace)) - ) - ) { + else if(!trace.error_y.visible && ( + ['tonexty', 'tozeroy'].indexOf(trace.fill)!==-1 || + (!scatter.hasMarkers(trace) && !scatter.hasText(trace)) + )) { xOptions.padded = false; xOptions.ppad = 0; } @@ -909,7 +904,7 @@ scatter.hoverPoints = function(pointData, xval, yval, hovermode) { if(di.tx) pointData.text = di.tx; else if(trace.text) pointData.text = trace.text; - if(Plotly.ErrorBars) Plotly.ErrorBars.hoverInfo(di, trace, pointData); + Plotly.ErrorBars.hoverInfo(di, trace, pointData); return [pointData]; }; diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index d50e371734c..a815a75d238 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -45,11 +45,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } } - if(Plotly.ErrorBars) { - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'}); - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'}); - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'}); - } + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'}); + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'}); + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'}); }; function handleXYZDefaults(traceIn, traceOut, coerce) { diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 52f46ab2e59..11a329f7e2b 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -33,8 +33,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout Scatter.fillColorDefaults(traceIn, traceOut, defaultColor, coerce); } - if(Plotly.ErrorBars) { - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); - } + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); + Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); }; From 94a450c52bfbf1d470341c7fcdaf38b758dbd235 Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:27:01 -0500 Subject: [PATCH 29/38] rm check for Plotly.Queue: - Queue is now part of plotly.js and always there --- src/plot_api/plot_api.js | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index eff5bdfb3e3..87eb312c5f1 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1358,9 +1358,7 @@ Plotly.extendTraces = function extendTraces (gd, update, indices, maxPoints) { Plotly.redraw(gd); var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - if (Plotly.Queue) { - Plotly.Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); - } + Plotly.Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); }; Plotly.prependTraces = function prependTraces (gd, update, indices, maxPoints) { @@ -1385,9 +1383,7 @@ Plotly.prependTraces = function prependTraces (gd, update, indices, maxPoints) Plotly.redraw(gd); var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - if (Plotly.Queue) { - Plotly.Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); - } + Plotly.Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); }; /** @@ -1431,7 +1427,7 @@ Plotly.addTraces = function addTraces (gd, traces, newIndices) { // i.e., we can simply redraw and be done if (typeof newIndices === 'undefined') { Plotly.redraw(gd); - if (Plotly.Queue) Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return; } @@ -1454,10 +1450,10 @@ Plotly.addTraces = function addTraces (gd, traces, newIndices) { // if we're here, the user has defined specific places to place the new traces // this requires some extra work that moveTraces will do - if (Plotly.Queue) Plotly.Queue.startSequence(gd); - if (Plotly.Queue) Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Plotly.Queue.startSequence(gd); + Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); Plotly.moveTraces(gd, currentIndices, newIndices); - if (Plotly.Queue) Plotly.Queue.stopSequence(gd); + Plotly.Queue.stopSequence(gd); }; /** @@ -1497,8 +1493,7 @@ Plotly.deleteTraces = function deleteTraces (gd, indices) { } Plotly.redraw(gd); - - if (Plotly.Queue) Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); }; /** @@ -1592,10 +1587,8 @@ Plotly.moveTraces = function moveTraces (gd, currentIndices, newIndices) { } gd.data = newData; - Plotly.redraw(gd); - - if (Plotly.Queue) Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Plotly.Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); }; // ----------------------------------------------------- @@ -2032,9 +2025,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) { // now all attribute mods are done, as are redo and undo // so we can save them - if(Plotly.Queue) { - Plotly.Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]); - } + Plotly.Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]); // do we need to force a recalc? var autorangeOn = false; @@ -2412,9 +2403,7 @@ Plotly.relayout = function relayout(gd, astr, val) { } // now all attribute mods are done, as are // redo and undo so we can save them - if(Plotly.Queue) { - Plotly.Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]); - } + Plotly.Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]); // calculate autosizing - if size hasn't changed, // will remove h&w so we don't need to redraw From f8ce24a76d0dc68dfed6d99848b1f5c40855327a Mon Sep 17 00:00:00 2001 From: etpinard Date: Thu, 12 Nov 2015 17:27:13 -0500 Subject: [PATCH 30/38] lint --- src/plotly.js | 13 ++++++++++++- test/jasmine/tests/axes_test.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/plotly.js b/src/plotly.js index 483be137d91..c161c9bbcec 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -1,9 +1,20 @@ +/* + * Pack internal modules unto an object. + * + * This object is require'ed in as 'Plotly' in numerous src and test files. + * Require'ing 'Plotly' bypasses circular dependencies. + * + * Future development should move away from this pattern. + * + */ + +// promise polyfill require('es6-promise').polyfill(); exports.Lib = require('./lib/lib'); exports.util = require('./lib/svg_text_utils'); -// plot icons svg and css +// plot icons svg and plot css exports.Icons = require('../build/ploticon'); require('../build/plotcss'); diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 336618e2a31..bd336907956 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -27,7 +27,7 @@ describe('Test axes', function () { } } }; - var expectedYaxis = Plotly.Lib.extendDeep({} ,gd.layout.xaxis), + var expectedYaxis = Plotly.Lib.extendDeep({}, gd.layout.xaxis), expectedXaxis = { title: 'Click to enter X axis title', type: 'date' From 7150055a01e8541cbf8ab1d8f0642bca01b09826 Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 14:18:42 -0500 Subject: [PATCH 31/38] add postinstall hook to preprocess plot css and icons on install --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2f2209de508..fdcae2c438d 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ ], "scripts": { "preprocess": "node tasks/preprocess.js", + "postinstall": "npm run preprocess", "bundle": "node tasks/bundle.js", "build": "npm run preprocess && npm run bundle", "watch": "node tasks/watch_plotly.js", From ce393fb0e9f4f86dc6db20dc61b2addb86ba554c Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 14:20:00 -0500 Subject: [PATCH 32/38] make Snapshot and PlotSchema to 'beta' exposed methods --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index bcb3ee1057f..17356b2130d 100644 --- a/src/index.js +++ b/src/index.js @@ -19,11 +19,11 @@ exports.addTraces = Plotly.addTraces; exports.deleteTraces = Plotly.deleteTraces; exports.moveTraces = Plotly.moveTraces; -// unofficial plot methods, use at your own risk +// unofficial 'beta' plot methods, use at your own risk exports.Plots = Plotly.Plots; exports.Fx = Plotly.Fx; - -// TODO expose snapshot and plot_schema +exports.Snapshot = Plotly.Snapshot; +exports.PlotSchema = Plotly.PlotSchema; // export d3 used in the bundle exports.d3 = require('d3'); From dd8b3d3ca2d52893f284b47049e41b04830f34b9 Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 14:20:44 -0500 Subject: [PATCH 33/38] mv plot_config to plot_api/ --- src/{ => plot_api}/plot_config.js | 0 src/plotly.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => plot_api}/plot_config.js (100%) diff --git a/src/plot_config.js b/src/plot_api/plot_config.js similarity index 100% rename from src/plot_config.js rename to src/plot_api/plot_config.js diff --git a/src/plotly.js b/src/plotly.js index c161c9bbcec..8c6c5565973 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -20,7 +20,7 @@ require('../build/plotcss'); // configuration exports.MathJaxConfig = require('./fonts/mathjax_config'); -exports.defaultConfig = require('./plot_config'); +exports.defaultConfig = require('./plot_api/plot_config'); // plots exports.Plots = require('./plots/plots'); From 6d0e221c797a5951a7822fc80da2ddf4527df8d3 Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 14:37:32 -0500 Subject: [PATCH 34/38] add Plotly.setPlotConfig to API: - to extend the default config with the API. --- src/index.js | 1 + src/plot_api/set_plot_config.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/plot_api/set_plot_config.js diff --git a/src/index.js b/src/index.js index 17356b2130d..e0a8c258a10 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ exports.prependTraces = Plotly.prependTraces; exports.addTraces = Plotly.addTraces; exports.deleteTraces = Plotly.deleteTraces; exports.moveTraces = Plotly.moveTraces; +exports.setPlotConfig = require('./plot_api/set_plot_config'); // unofficial 'beta' plot methods, use at your own risk exports.Plots = Plotly.Plots; diff --git a/src/plot_api/set_plot_config.js b/src/plot_api/set_plot_config.js new file mode 100644 index 00000000000..d3a4b8c739f --- /dev/null +++ b/src/plot_api/set_plot_config.js @@ -0,0 +1,14 @@ +'use strict'; + +var Plotly = require('../plotly'); + +/** + * Extends the plot config + * + * @param {object} configObj partial plot configuration object + * to extend the current plot configuration. + * + */ +module.exports = function setPlotConfig(configObj) { + return Plotly.Lib.extendFlat(Plotly.defaultConfig, configObj); +}; From a2c72ed2d0c39035978cc8afa624b9f78560c45b Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 14:39:51 -0500 Subject: [PATCH 35/38] change inner module pattern: - rename src/$type/$module_name/$module_name.js -> src/$type/$module_name/index.js so that require('../$type/$module_name/$module_name') becomes require('../$type?$module_name') --- .../annotations/{annotations.js => index.js} | 0 src/components/color/{color.js => index.js} | 0 .../colorbar/{colorbar.js => index.js} | 0 .../colorscale/{colorscale.js => index.js} | 0 .../drawing/{drawing.js => index.js} | 0 .../errorbars/{errorbars.js => index.js} | 0 src/components/legend/{legend.js => index.js} | 0 .../modebar/{modebar.js => index.js} | 0 src/components/shapes/{shapes.js => index.js} | 0 src/components/titles/{titles.js => index.js} | 0 src/lib/{lib.js => index.js} | 0 src/plotly.js | 59 +++++++++---------- src/plots/geo/layout/{layout.js => index.js} | 0 src/plots/gl3d/layout/{layout.js => index.js} | 0 src/snapshot/{snapshot.js => index.js} | 0 src/traces/bars/{bars.js => index.js} | 0 src/traces/boxes/{boxes.js => index.js} | 0 src/traces/choropleth/defaults.js | 2 +- .../choropleth/{choropleth.js => index.js} | 0 src/traces/contour/{contour.js => index.js} | 0 src/traces/heatmap/{heatmap.js => index.js} | 0 .../histogram/{histogram.js => index.js} | 0 src/traces/mesh3d/defaults.js | 2 +- src/traces/mesh3d/{mesh3d.js => index.js} | 0 src/traces/pie/{pie.js => index.js} | 0 src/traces/scatter/{scatter.js => index.js} | 0 src/traces/scatter3d/defaults.js | 2 +- .../scatter3d/{scatter3d.js => index.js} | 0 src/traces/scattergeo/defaults.js | 2 +- .../scattergeo/{scattergeo.js => index.js} | 0 src/traces/scattergl/defaults.js | 2 +- .../scattergl/{scattergl.js => index.js} | 0 src/traces/surface/defaults.js | 2 +- src/traces/surface/{surface.js => index.js} | 0 34 files changed, 35 insertions(+), 36 deletions(-) rename src/components/annotations/{annotations.js => index.js} (100%) rename src/components/color/{color.js => index.js} (100%) rename src/components/colorbar/{colorbar.js => index.js} (100%) rename src/components/colorscale/{colorscale.js => index.js} (100%) rename src/components/drawing/{drawing.js => index.js} (100%) rename src/components/errorbars/{errorbars.js => index.js} (100%) rename src/components/legend/{legend.js => index.js} (100%) rename src/components/modebar/{modebar.js => index.js} (100%) rename src/components/shapes/{shapes.js => index.js} (100%) rename src/components/titles/{titles.js => index.js} (100%) rename src/lib/{lib.js => index.js} (100%) rename src/plots/geo/layout/{layout.js => index.js} (100%) rename src/plots/gl3d/layout/{layout.js => index.js} (100%) rename src/snapshot/{snapshot.js => index.js} (100%) rename src/traces/bars/{bars.js => index.js} (100%) rename src/traces/boxes/{boxes.js => index.js} (100%) rename src/traces/choropleth/{choropleth.js => index.js} (100%) rename src/traces/contour/{contour.js => index.js} (100%) rename src/traces/heatmap/{heatmap.js => index.js} (100%) rename src/traces/histogram/{histogram.js => index.js} (100%) rename src/traces/mesh3d/{mesh3d.js => index.js} (100%) rename src/traces/pie/{pie.js => index.js} (100%) rename src/traces/scatter/{scatter.js => index.js} (100%) rename src/traces/scatter3d/{scatter3d.js => index.js} (100%) rename src/traces/scattergeo/{scattergeo.js => index.js} (100%) rename src/traces/scattergl/{scattergl.js => index.js} (100%) rename src/traces/surface/{surface.js => index.js} (100%) diff --git a/src/components/annotations/annotations.js b/src/components/annotations/index.js similarity index 100% rename from src/components/annotations/annotations.js rename to src/components/annotations/index.js diff --git a/src/components/color/color.js b/src/components/color/index.js similarity index 100% rename from src/components/color/color.js rename to src/components/color/index.js diff --git a/src/components/colorbar/colorbar.js b/src/components/colorbar/index.js similarity index 100% rename from src/components/colorbar/colorbar.js rename to src/components/colorbar/index.js diff --git a/src/components/colorscale/colorscale.js b/src/components/colorscale/index.js similarity index 100% rename from src/components/colorscale/colorscale.js rename to src/components/colorscale/index.js diff --git a/src/components/drawing/drawing.js b/src/components/drawing/index.js similarity index 100% rename from src/components/drawing/drawing.js rename to src/components/drawing/index.js diff --git a/src/components/errorbars/errorbars.js b/src/components/errorbars/index.js similarity index 100% rename from src/components/errorbars/errorbars.js rename to src/components/errorbars/index.js diff --git a/src/components/legend/legend.js b/src/components/legend/index.js similarity index 100% rename from src/components/legend/legend.js rename to src/components/legend/index.js diff --git a/src/components/modebar/modebar.js b/src/components/modebar/index.js similarity index 100% rename from src/components/modebar/modebar.js rename to src/components/modebar/index.js diff --git a/src/components/shapes/shapes.js b/src/components/shapes/index.js similarity index 100% rename from src/components/shapes/shapes.js rename to src/components/shapes/index.js diff --git a/src/components/titles/titles.js b/src/components/titles/index.js similarity index 100% rename from src/components/titles/titles.js rename to src/components/titles/index.js diff --git a/src/lib/lib.js b/src/lib/index.js similarity index 100% rename from src/lib/lib.js rename to src/lib/index.js diff --git a/src/plotly.js b/src/plotly.js index 8c6c5565973..99e5756838c 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -11,8 +11,10 @@ // promise polyfill require('es6-promise').polyfill(); -exports.Lib = require('./lib/lib'); +// lib functions +exports.Lib = require('./lib'); exports.util = require('./lib/svg_text_utils'); +exports.Queue = require('./lib/queue'); // plot icons svg and plot css exports.Icons = require('../build/ploticon'); @@ -27,45 +29,42 @@ exports.Plots = require('./plots/plots'); exports.Axes = require('./plots/cartesian/axes'); exports.Fx = require('./plots/cartesian/graph_interact'); exports.Scene = require('./plots/gl3d/scene'); -exports.Gl3dLayout = require('./plots/gl3d/layout/layout'); +exports.Gl3dLayout = require('./plots/gl3d/layout'); exports.Geo = require('./plots/geo/geo'); -exports.GeoLayout = require('./plots/geo/layout/layout'); +exports.GeoLayout = require('./plots/geo/layout'); exports.Scene2D = require('./plots/gl2d/scene2d'); exports.micropolar = require('./plots/polar/micropolar'); // components -exports.Color = require('./components/color/color'); -exports.Drawing = require('./components/drawing/drawing'); -exports.Colorscale = require('./components/colorscale/colorscale'); -exports.Colorbar = require('./components/colorbar/colorbar'); -exports.ErrorBars = require('./components/errorbars/errorbars'); -exports.Annotations = require('./components/annotations/annotations'); -exports.Shapes = require('./components/shapes/shapes'); -exports.Titles = require('./components/titles/titles'); -exports.Legend = require('./components/legend/legend'); -exports.ModeBar = require('./components/modebar/modebar'); +exports.Color = require('./components/color'); +exports.Drawing = require('./components/drawing'); +exports.Colorscale = require('./components/colorscale'); +exports.Colorbar = require('./components/colorbar'); +exports.ErrorBars = require('./components/errorbars'); +exports.Annotations = require('./components/annotations'); +exports.Shapes = require('./components/shapes'); +exports.Titles = require('./components/titles'); +exports.Legend = require('./components/legend'); +exports.ModeBar = require('./components/modebar'); // traces -exports.Scatter = require('./traces/scatter/scatter'); -exports.Bars = require('./traces/bars/bars'); -exports.Boxes = require('./traces/boxes/boxes'); -exports.Heatmap = require('./traces/heatmap/heatmap'); -exports.Histogram = require('./traces/histogram/histogram'); -exports.Pie = require('./traces/pie/pie'); -exports.Contour = require('./traces/contour/contour'); -exports.Scatter3D = require('./traces/scatter3d/scatter3d'); -exports.Surface = require('./traces/surface/surface'); -exports.Mesh3D = require('./traces/mesh3d/mesh3d'); -exports.ScatterGeo = require('./traces/scattergeo/scattergeo'); -exports.Choropleth = require('./traces/choropleth/choropleth'); -exports.ScatterGl = require('./traces/scattergl/scattergl'); +exports.Scatter = require('./traces/scatter'); +exports.Bars = require('./traces/bars'); +exports.Boxes = require('./traces/boxes'); +exports.Heatmap = require('./traces/heatmap'); +exports.Histogram = require('./traces/histogram'); +exports.Pie = require('./traces/pie'); +exports.Contour = require('./traces/contour'); +exports.Scatter3D = require('./traces/scatter3d'); +exports.Surface = require('./traces/surface'); +exports.Mesh3D = require('./traces/mesh3d'); +exports.ScatterGeo = require('./traces/scattergeo'); +exports.Choropleth = require('./traces/choropleth'); +exports.ScatterGl = require('./traces/scattergl'); // plot api require('./plot_api/plot_api'); exports.PlotSchema = require('./plot_api/plot_schema'); // imaging routines -exports.Snapshot = require('./snapshot/snapshot'); - -// queue for undo/redo -exports.Queue = require('./lib/queue'); +exports.Snapshot = require('./snapshot'); diff --git a/src/plots/geo/layout/layout.js b/src/plots/geo/layout/index.js similarity index 100% rename from src/plots/geo/layout/layout.js rename to src/plots/geo/layout/index.js diff --git a/src/plots/gl3d/layout/layout.js b/src/plots/gl3d/layout/index.js similarity index 100% rename from src/plots/gl3d/layout/layout.js rename to src/plots/gl3d/layout/index.js diff --git a/src/snapshot/snapshot.js b/src/snapshot/index.js similarity index 100% rename from src/snapshot/snapshot.js rename to src/snapshot/index.js diff --git a/src/traces/bars/bars.js b/src/traces/bars/index.js similarity index 100% rename from src/traces/bars/bars.js rename to src/traces/bars/index.js diff --git a/src/traces/boxes/boxes.js b/src/traces/boxes/index.js similarity index 100% rename from src/traces/boxes/boxes.js rename to src/traces/boxes/index.js diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index c1a2e059e41..a731d93dfa6 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var Choropleth = require('./choropleth'); +var Choropleth = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var locations, len, z; diff --git a/src/traces/choropleth/choropleth.js b/src/traces/choropleth/index.js similarity index 100% rename from src/traces/choropleth/choropleth.js rename to src/traces/choropleth/index.js diff --git a/src/traces/contour/contour.js b/src/traces/contour/index.js similarity index 100% rename from src/traces/contour/contour.js rename to src/traces/contour/index.js diff --git a/src/traces/heatmap/heatmap.js b/src/traces/heatmap/index.js similarity index 100% rename from src/traces/heatmap/heatmap.js rename to src/traces/heatmap/index.js diff --git a/src/traces/histogram/histogram.js b/src/traces/histogram/index.js similarity index 100% rename from src/traces/histogram/histogram.js rename to src/traces/histogram/index.js diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index eee73b80618..d667a55485c 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var Mesh3D = require('./mesh3d'); +var Mesh3D = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { diff --git a/src/traces/mesh3d/mesh3d.js b/src/traces/mesh3d/index.js similarity index 100% rename from src/traces/mesh3d/mesh3d.js rename to src/traces/mesh3d/index.js diff --git a/src/traces/pie/pie.js b/src/traces/pie/index.js similarity index 100% rename from src/traces/pie/pie.js rename to src/traces/pie/index.js diff --git a/src/traces/scatter/scatter.js b/src/traces/scatter/index.js similarity index 100% rename from src/traces/scatter/scatter.js rename to src/traces/scatter/index.js diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index a815a75d238..44373a6e238 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var Scatter3D = require('./scatter3d'); +var Scatter3D = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { diff --git a/src/traces/scatter3d/scatter3d.js b/src/traces/scatter3d/index.js similarity index 100% rename from src/traces/scatter3d/scatter3d.js rename to src/traces/scatter3d/index.js diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index 63a87719067..81ed36996df 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var ScatterGeo = require('./scattergeo'); +var ScatterGeo = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { diff --git a/src/traces/scattergeo/scattergeo.js b/src/traces/scattergeo/index.js similarity index 100% rename from src/traces/scattergeo/scattergeo.js rename to src/traces/scattergeo/index.js diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 11a329f7e2b..be7b51a03a7 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var ScatterGl = require('./scattergl'); +var ScatterGl = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { diff --git a/src/traces/scattergl/scattergl.js b/src/traces/scattergl/index.js similarity index 100% rename from src/traces/scattergl/scattergl.js rename to src/traces/scattergl/index.js diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 9fd121e7c03..c92afde9e81 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -1,7 +1,7 @@ 'use strict'; var Plotly = require('../../plotly'); -var Surface = require('./surface'); +var Surface = require('./'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { diff --git a/src/traces/surface/surface.js b/src/traces/surface/index.js similarity index 100% rename from src/traces/surface/surface.js rename to src/traces/surface/index.js From 2de1313694f7b4afc429b9a98b14a925163297e1 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 15 Nov 2015 23:58:38 +0100 Subject: [PATCH 36/38] jshintrc copied from streambed mainly so sublime doesn't complain about node --- .jshintrc | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000000..8d148c611b7 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,86 @@ +{ + // environments + "browser": true, // Define globals exposed by modern browsers. + "jquery": false, // Define globals exposed by jQuery. + "node": true, // Define globals exposed by Node.js. + + // enforcing (true means bug us about it) + "camelcase": false, // Force all variable names to use either camelCase style or UPPER_CASE with underscores. + "curly": false, // This option requires you to always put curly braces around blocks in loops and conditionals. + "eqeqeq": true, // Prohibit use of == and != in favor of === and !==. + "es3": true, // This option tells JSHint that your code needs to adhere to ECMAScript 3 spec (old browsers) + "forin": false, // This option requires all for in loops to filter object's items. + "freeze": true, // This options prohibits overwriting prototypes of native objects such as Array, Date and so on. + "immed": true, // This option prohibits the use of immediate function invocations without wrapping them in parentheses. + "indent": 4, // Enforce tab width of 4 spaces. + "latedef": "nofunc", // Prohibit use of a variable before it is defined. + "maxcomplexity": false, // This option lets you control cyclomatic complexity throughout your code. + "maxdepth": 5, // This option lets you control how nested do you want your blocks to be + "maxlen": 120, // Enforce line length to 120 characters + "maxparams": 5, // This option lets you set the max number of formal parameters allowed per function + "maxstatements": 60, // This option lets you set the max number of statements allowed per function + "newcap": true, // Require capitalized names for constructor functions. + "noarg": true, // This option prohibits the use of arguments.caller and arguments.callee. + "noempty": true, // This option warns when you have an empty block in your code. + "nonbsp": true, // This option warns about "non-breaking whitespace" characters. + "nonew": true, // This option prohibits the use of constructor functions for side-effects. + "plusplus": false, // This option prohibits the use of unary increment and decrement operators. + "quotmark": "single", // Enforce use of single quotation marks for strings. + "strict": true, // Enforce placing 'use strict' at the top function scope + "trailing": true, // Prohibit trailing whitespace. + "undef": true, // Prohibit use of explicitly undeclared variables. + "unused": true, // Warn when variables are defined but never used. + + + // relaxing (true means DON'T bug us about it) + "asi": false, // This option suppresses warnings about missing semicolons. + "boss": false, // This option suppresses warnings about the use of assignments in cases where comparisons are expected. + "debug": false, // This option suppresses warnings about the debugger statements in your code. + "eqnull": true, // Suppress warnings about == null comparisons. + "esnext": true, // This option tells JSHint that your code uses ECMAScript 6 specific syntax. + "evil": false, // This option suppresses warnings about the use of eval. + "expr": false, // This option suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls. + "funcscope": false, // This option suppresses warnings about declaring variables inside of control structures while accessing them later from the outside. + "globalstrict": false, // This option suppresses warnings about the use of global strict mode. + "iterator": false, // This option suppresses warnings about the __iterator__ property. + "lastsemic": false, // This option suppresses warnings about missing semicolons + "laxbreak": false, // This option suppresses most of the warnings about possibly unsafe line breakings in your code. + "laxcomma": false, // This option suppresses warnings about comma-first coding style + "loopfunc": false, // This option suppresses warnings about functions inside of loops. + "maxerr": 500, // This options allows you to set the maximum amount of warnings JSHint will produce before giving up. + "moz": false, // This options tells JSHint that your code uses Mozilla JavaScript extensions. + "multistr": false, // This option suppresses warnings about multi-line strings. + "notypeof": false, // This option suppresses warnings about invalid typeof operator values. + "proto": false, // This option suppresses warnings about the __proto__ property. + "scripturl": false, // This option suppresses warnings about the use of script-targeted URLs—such as javascript:... + "shadow": false, // This option suppresses warnings about variable shadowing + "sub": false, // This option suppresses warnings about using [] notation when it can be expressed in dot notation + "supernew": false, // This option suppresses warnings about "weird" constructions like new function () { ... } and new Object; + "validthis": false, // This option suppresses warnings about possible strict violations when the code is running in strict mode + "noyield": false, // This option suppresses warnings about generator functions with no yield statement in them. + + // global pre defined variables + "predef": [ + "angular", + "afterEach", + "beforeEach", + "describe", + "encodeURIComponent", + "expect", + "inject", + "it", + "spyOn", + "JSON", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "Int8Array", + "Int16Array", + "Int32Array", + "Float32Array", + "Float64Array", + "require", + "module", + "document" + ] +} From 6c279ee0d304378deb325c8ae3485c083b71b988 Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 21:50:49 -0500 Subject: [PATCH 37/38] update jshintrc: - rm maxparms - rm maxstatements - bump maxdepth to 6 - add jasmine env --- .jshintrc | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/.jshintrc b/.jshintrc index 8d148c611b7..2190b0e1c7c 100644 --- a/.jshintrc +++ b/.jshintrc @@ -2,7 +2,8 @@ // environments "browser": true, // Define globals exposed by modern browsers. "jquery": false, // Define globals exposed by jQuery. - "node": true, // Define globals exposed by Node.js. + "node": true, // Define globals exposed by Node.js. + "jasmine": true, // Define globals exposed by jasmine // enforcing (true means bug us about it) "camelcase": false, // Force all variable names to use either camelCase style or UPPER_CASE with underscores. @@ -15,10 +16,8 @@ "indent": 4, // Enforce tab width of 4 spaces. "latedef": "nofunc", // Prohibit use of a variable before it is defined. "maxcomplexity": false, // This option lets you control cyclomatic complexity throughout your code. - "maxdepth": 5, // This option lets you control how nested do you want your blocks to be + "maxdepth": 6, // This option lets you control how nested do you want your blocks to be "maxlen": 120, // Enforce line length to 120 characters - "maxparams": 5, // This option lets you set the max number of formal parameters allowed per function - "maxstatements": 60, // This option lets you set the max number of statements allowed per function "newcap": true, // Require capitalized names for constructor functions. "noarg": true, // This option prohibits the use of arguments.caller and arguments.callee. "noempty": true, // This option warns when you have an empty block in your code. @@ -31,7 +30,6 @@ "undef": true, // Prohibit use of explicitly undeclared variables. "unused": true, // Warn when variables are defined but never used. - // relaxing (true means DON'T bug us about it) "asi": false, // This option suppresses warnings about missing semicolons. "boss": false, // This option suppresses warnings about the use of assignments in cases where comparisons are expected. @@ -61,15 +59,6 @@ // global pre defined variables "predef": [ - "angular", - "afterEach", - "beforeEach", - "describe", - "encodeURIComponent", - "expect", - "inject", - "it", - "spyOn", "JSON", "Uint8Array", "Uint16Array", @@ -78,9 +67,6 @@ "Int16Array", "Int32Array", "Float32Array", - "Float64Array", - "require", - "module", - "document" + "Float64Array" ] } From 4491321a8bb515cf1aec20b771691cb37f63b2d7 Mon Sep 17 00:00:00 2001 From: etpinard Date: Sun, 15 Nov 2015 22:04:12 -0500 Subject: [PATCH 38/38] add jshint devDep and 'run lint' script --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index fdcae2c438d..4f52ce7cdda 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "bundle": "node tasks/bundle.js", "build": "npm run preprocess && npm run bundle", "watch": "node tasks/watch_plotly.js", + "lint": "cd src && jshint . || true", "test-jasmine": "karma start test/jasmine/karma.conf.js", "test-image": "echo TODO", "test": "npm run test-jasmine && npm test-image", @@ -98,6 +99,7 @@ "browserify-transform-tools": "^1.5.0", "ecstatic": "^1.2.0", "jasmine-core": "^2.3.4", + "jshint": "^2.8.0", "karma": "^0.13.15", "karma-browserify": "^4.4.0", "karma-chrome-launcher": "^0.2.1",