diff --git a/lib/bar.js b/lib/bar.js
index 64283813cb1..37a62cfa50e 100644
--- a/lib/bar.js
+++ b/lib/bar.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/bar');
+"use strict";
+module.exports = require("../src/traces/bar");
diff --git a/lib/box.js b/lib/box.js
index f3fde994b63..6ef50340d61 100644
--- a/lib/box.js
+++ b/lib/box.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/box');
+"use strict";
+module.exports = require("../src/traces/box");
diff --git a/lib/calendars.js b/lib/calendars.js
index 8ad06f1d6da..86eb1b1fb0d 100644
--- a/lib/calendars.js
+++ b/lib/calendars.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/components/calendars');
+"use strict";
+module.exports = require("../src/components/calendars");
diff --git a/lib/candlestick.js b/lib/candlestick.js
index c608d5b6755..a4c16de27f6 100644
--- a/lib/candlestick.js
+++ b/lib/candlestick.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/candlestick');
+"use strict";
+module.exports = require("../src/traces/candlestick");
diff --git a/lib/choropleth.js b/lib/choropleth.js
index 4e1f61186c5..37045b2e42c 100644
--- a/lib/choropleth.js
+++ b/lib/choropleth.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/choropleth');
+"use strict";
+module.exports = require("../src/traces/choropleth");
diff --git a/lib/contour.js b/lib/contour.js
index 703eb8d311b..25cd26592d2 100644
--- a/lib/contour.js
+++ b/lib/contour.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/contour');
+"use strict";
+module.exports = require("../src/traces/contour");
diff --git a/lib/contourgl.js b/lib/contourgl.js
index 74901df14b4..47f7f4d8b32 100644
--- a/lib/contourgl.js
+++ b/lib/contourgl.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/contourgl');
+"use strict";
+module.exports = require("../src/traces/contourgl");
diff --git a/lib/core.js b/lib/core.js
index afec96d280d..46b80c73a1f 100644
--- a/lib/core.js
+++ b/lib/core.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/core');
+"use strict";
+module.exports = require("../src/core");
diff --git a/lib/filter.js b/lib/filter.js
index 14ffd87f7e1..4112f572a32 100644
--- a/lib/filter.js
+++ b/lib/filter.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/transforms/filter');
+"use strict";
+module.exports = require("../src/transforms/filter");
diff --git a/lib/groupby.js b/lib/groupby.js
index 8ef1dd1f6c2..f0cc35899f4 100644
--- a/lib/groupby.js
+++ b/lib/groupby.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/transforms/groupby');
+"use strict";
+module.exports = require("../src/transforms/groupby");
diff --git a/lib/heatmap.js b/lib/heatmap.js
index 0560a47d95a..9aded18216d 100644
--- a/lib/heatmap.js
+++ b/lib/heatmap.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/heatmap');
+"use strict";
+module.exports = require("../src/traces/heatmap");
diff --git a/lib/heatmapgl.js b/lib/heatmapgl.js
index 26352a58216..d36c77033c3 100644
--- a/lib/heatmapgl.js
+++ b/lib/heatmapgl.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/heatmapgl');
+"use strict";
+module.exports = require("../src/traces/heatmapgl");
diff --git a/lib/histogram.js b/lib/histogram.js
index f704db7fa70..dcb27108677 100644
--- a/lib/histogram.js
+++ b/lib/histogram.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/histogram');
+"use strict";
+module.exports = require("../src/traces/histogram");
diff --git a/lib/histogram2d.js b/lib/histogram2d.js
index 69d2f691c03..872ab1eb287 100644
--- a/lib/histogram2d.js
+++ b/lib/histogram2d.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/histogram2d');
+"use strict";
+module.exports = require("../src/traces/histogram2d");
diff --git a/lib/histogram2dcontour.js b/lib/histogram2dcontour.js
index 133617a9961..f3094de2f0c 100644
--- a/lib/histogram2dcontour.js
+++ b/lib/histogram2dcontour.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/histogram2dcontour');
+"use strict";
+module.exports = require("../src/traces/histogram2dcontour");
diff --git a/lib/index-basic.js b/lib/index-basic.js
index 4c837e413ad..80f5838f296 100644
--- a/lib/index-basic.js
+++ b/lib/index-basic.js
@@ -5,14 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Plotly = require("./core");
-'use strict';
-
-var Plotly = require('./core');
-
-Plotly.register([
- require('./bar'),
- require('./pie')
-]);
+Plotly.register([require("./bar"), require("./pie")]);
module.exports = Plotly;
diff --git a/lib/index-cartesian.js b/lib/index-cartesian.js
index 4d07f5f5f09..c0243480edd 100644
--- a/lib/index-cartesian.js
+++ b/lib/index-cartesian.js
@@ -5,21 +5,19 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Plotly = require('./core');
+"use strict";
+var Plotly = require("./core");
Plotly.register([
- require('./bar'),
- require('./box'),
- require('./heatmap'),
- require('./histogram'),
- require('./histogram2d'),
- require('./histogram2dcontour'),
- require('./pie'),
- require('./contour'),
- require('./scatterternary')
+ require("./bar"),
+ require("./box"),
+ require("./heatmap"),
+ require("./histogram"),
+ require("./histogram2d"),
+ require("./histogram2dcontour"),
+ require("./pie"),
+ require("./contour"),
+ require("./scatterternary")
]);
module.exports = Plotly;
diff --git a/lib/index-finance.js b/lib/index-finance.js
index 4759344b760..486e3ec999e 100644
--- a/lib/index-finance.js
+++ b/lib/index-finance.js
@@ -5,17 +5,15 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Plotly = require('./core');
+"use strict";
+var Plotly = require("./core");
Plotly.register([
- require('./bar'),
- require('./histogram'),
- require('./pie'),
- require('./ohlc'),
- require('./candlestick')
+ require("./bar"),
+ require("./histogram"),
+ require("./pie"),
+ require("./ohlc"),
+ require("./candlestick")
]);
module.exports = Plotly;
diff --git a/lib/index-geo.js b/lib/index-geo.js
index 2283f1f0489..aa7aea199f9 100644
--- a/lib/index-geo.js
+++ b/lib/index-geo.js
@@ -5,14 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Plotly = require("./core");
-'use strict';
-
-var Plotly = require('./core');
-
-Plotly.register([
- require('./scattergeo'),
- require('./choropleth')
-]);
+Plotly.register([require("./scattergeo"), require("./choropleth")]);
module.exports = Plotly;
diff --git a/lib/index-gl2d.js b/lib/index-gl2d.js
index a7738a91744..cfceda4cbf5 100644
--- a/lib/index-gl2d.js
+++ b/lib/index-gl2d.js
@@ -5,16 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Plotly = require('./core');
+"use strict";
+var Plotly = require("./core");
Plotly.register([
- require('./scattergl'),
- require('./pointcloud'),
- require('./heatmapgl'),
- require('./contourgl')
+ require("./scattergl"),
+ require("./pointcloud"),
+ require("./heatmapgl"),
+ require("./contourgl")
]);
module.exports = Plotly;
diff --git a/lib/index-gl3d.js b/lib/index-gl3d.js
index 7134846cb7d..69464017f61 100644
--- a/lib/index-gl3d.js
+++ b/lib/index-gl3d.js
@@ -5,15 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Plotly = require('./core');
+"use strict";
+var Plotly = require("./core");
Plotly.register([
- require('./scatter3d'),
- require('./surface'),
- require('./mesh3d')
+ require("./scatter3d"),
+ require("./surface"),
+ require("./mesh3d")
]);
module.exports = Plotly;
diff --git a/lib/index-mapbox.js b/lib/index-mapbox.js
index 17b075b5f49..234182976ad 100644
--- a/lib/index-mapbox.js
+++ b/lib/index-mapbox.js
@@ -5,13 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Plotly = require("./core");
-'use strict';
-
-var Plotly = require('./core');
-
-Plotly.register([
- require('./scattermapbox')
-]);
+Plotly.register([require("./scattermapbox")]);
module.exports = Plotly;
diff --git a/lib/index.js b/lib/index.js
index 1cbbb8bba08..79f107f9b22 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,38 +5,31 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Plotly = require('./core');
+"use strict";
+var Plotly = require("./core");
// traces
Plotly.register([
- require('./bar'),
- require('./box'),
- require('./heatmap'),
- require('./histogram'),
- require('./histogram2d'),
- require('./histogram2dcontour'),
- require('./pie'),
- require('./contour'),
- require('./scatterternary'),
-
- require('./scatter3d'),
- require('./surface'),
- require('./mesh3d'),
-
- require('./scattergeo'),
- require('./choropleth'),
-
- require('./scattergl'),
- require('./pointcloud'),
- require('./heatmapgl'),
-
- require('./scattermapbox'),
-
- require('./ohlc'),
- require('./candlestick')
+ require("./bar"),
+ require("./box"),
+ require("./heatmap"),
+ require("./histogram"),
+ require("./histogram2d"),
+ require("./histogram2dcontour"),
+ require("./pie"),
+ require("./contour"),
+ require("./scatterternary"),
+ require("./scatter3d"),
+ require("./surface"),
+ require("./mesh3d"),
+ require("./scattergeo"),
+ require("./choropleth"),
+ require("./scattergl"),
+ require("./pointcloud"),
+ require("./heatmapgl"),
+ require("./scattermapbox"),
+ require("./ohlc"),
+ require("./candlestick")
]);
// transforms
@@ -49,14 +42,9 @@ Plotly.register([
// For more info, see:
// https://github.com/plotly/plotly.js/pull/978#pullrequestreview-2403353
//
-Plotly.register([
- require('./filter'),
- require('./groupby')
-]);
+Plotly.register([require("./filter"), require("./groupby")]);
// components
-Plotly.register([
- require('./calendars')
-]);
+Plotly.register([require("./calendars")]);
module.exports = Plotly;
diff --git a/lib/mesh3d.js b/lib/mesh3d.js
index 13453614a45..e6c99f2f39d 100644
--- a/lib/mesh3d.js
+++ b/lib/mesh3d.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/mesh3d');
+"use strict";
+module.exports = require("../src/traces/mesh3d");
diff --git a/lib/ohlc.js b/lib/ohlc.js
index 40537cfd011..086c85e64de 100644
--- a/lib/ohlc.js
+++ b/lib/ohlc.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/ohlc');
+"use strict";
+module.exports = require("../src/traces/ohlc");
diff --git a/lib/pie.js b/lib/pie.js
index 08f1d09ab76..099234fc945 100644
--- a/lib/pie.js
+++ b/lib/pie.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/pie');
+"use strict";
+module.exports = require("../src/traces/pie");
diff --git a/lib/pointcloud.js b/lib/pointcloud.js
index 4d6c7d286e0..edbaa5cebb6 100644
--- a/lib/pointcloud.js
+++ b/lib/pointcloud.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/pointcloud');
+"use strict";
+module.exports = require("../src/traces/pointcloud");
diff --git a/lib/scatter.js b/lib/scatter.js
index 589c7bd8b0c..876e9d0045e 100644
--- a/lib/scatter.js
+++ b/lib/scatter.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scatter');
+"use strict";
+module.exports = require("../src/traces/scatter");
diff --git a/lib/scatter3d.js b/lib/scatter3d.js
index c4ee1a63465..cbb8d1ca233 100644
--- a/lib/scatter3d.js
+++ b/lib/scatter3d.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scatter3d');
+"use strict";
+module.exports = require("../src/traces/scatter3d");
diff --git a/lib/scattergeo.js b/lib/scattergeo.js
index 90f892115bb..02421046954 100644
--- a/lib/scattergeo.js
+++ b/lib/scattergeo.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scattergeo');
+"use strict";
+module.exports = require("../src/traces/scattergeo");
diff --git a/lib/scattergl.js b/lib/scattergl.js
index 7c5b6f2ba79..72006b1cc34 100644
--- a/lib/scattergl.js
+++ b/lib/scattergl.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scattergl');
+"use strict";
+module.exports = require("../src/traces/scattergl");
diff --git a/lib/scattermapbox.js b/lib/scattermapbox.js
index 5391bc32630..11933be948c 100644
--- a/lib/scattermapbox.js
+++ b/lib/scattermapbox.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scattermapbox');
+"use strict";
+module.exports = require("../src/traces/scattermapbox");
diff --git a/lib/scatterternary.js b/lib/scatterternary.js
index 2196d3c41ae..0fd0aee69bc 100644
--- a/lib/scatterternary.js
+++ b/lib/scatterternary.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/scatterternary');
+"use strict";
+module.exports = require("../src/traces/scatterternary");
diff --git a/lib/surface.js b/lib/surface.js
index d4a3197a2e8..2fc1f1c0bac 100644
--- a/lib/surface.js
+++ b/lib/surface.js
@@ -5,7 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-module.exports = require('../src/traces/surface');
+"use strict";
+module.exports = require("../src/traces/surface");
diff --git a/package.json b/package.json
index f4fbc318a68..e587ce21ec3 100644
--- a/package.json
+++ b/package.json
@@ -28,8 +28,8 @@
"build": "npm run preprocess && npm run bundle && npm run header && npm run stats",
"cibuild": "npm run preprocess && node tasks/cibundle.js",
"watch": "node tasks/watch.js",
- "lint": "eslint --version && eslint . || true",
- "lint-fix": "eslint . --fix",
+ "lint": "prettier --write \"src/**/*.js\" \"test/**/*.js\" \"lib/**/*.js\"",
+ "lint-fix": "prettier --write \"src/**/*.js\" \"test/**/*.js\" \"lib/**/*.js\"",
"docker": "node tasks/docker.js",
"pretest": "node tasks/pretest.js",
"test-jasmine": "karma start test/jasmine/karma.conf.js",
@@ -117,6 +117,7 @@
"npm-link-check": "^1.2.0",
"open": "0.0.5",
"prepend-file": "^1.3.1",
+ "prettier": "^0.16.0",
"prettysize": "0.0.3",
"requirejs": "^2.3.1",
"through2": "^2.0.3",
diff --git a/src/assets/geo_assets.js b/src/assets/geo_assets.js
index 5222e524f44..8b137891791 100644
--- a/src/assets/geo_assets.js
+++ b/src/assets/geo_assets.js
@@ -1,17 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var saneTopojson = require('sane-topojson');
-
-
-// package version injected by `npm run preprocess`
-exports.version = '1.23.1';
-
-exports.topojson = saneTopojson;
diff --git a/src/components/annotations/annotation_defaults.js b/src/components/annotations/annotation_defaults.js
index 7df91978c59..8b137891791 100644
--- a/src/components/annotations/annotation_defaults.js
+++ b/src/components/annotations/annotation_defaults.js
@@ -1,107 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-var Color = require('../color');
-var Axes = require('../../plots/cartesian/axes');
-
-var attributes = require('./attributes');
-
-
-module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, opts, itemOpts) {
- opts = opts || {};
- itemOpts = itemOpts || {};
-
- function coerce(attr, dflt) {
- return Lib.coerce(annIn, annOut, attributes, attr, dflt);
- }
-
- var visible = coerce('visible', !itemOpts.itemIsNotPlainObject);
- var clickToShow = coerce('clicktoshow');
-
- if(!(visible || clickToShow)) return annOut;
-
- coerce('opacity');
- coerce('align');
- coerce('bgcolor');
-
- var borderColor = coerce('bordercolor'),
- borderOpacity = Color.opacity(borderColor);
-
- coerce('borderpad');
-
- var borderWidth = coerce('borderwidth');
- var showArrow = coerce('showarrow');
-
- coerce('text', showArrow ? ' ' : 'new text');
- coerce('textangle');
- Lib.coerceFont(coerce, 'font', fullLayout.font);
-
- // positioning
- var axLetters = ['x', 'y'],
- arrowPosDflt = [-10, -30],
- gdMock = {_fullLayout: fullLayout};
- for(var i = 0; i < 2; i++) {
- var axLetter = axLetters[i];
-
- // xref, yref
- var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper');
-
- // x, y
- Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);
-
- if(showArrow) {
- var arrowPosAttr = 'a' + axLetter,
- // axref, ayref
- aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel');
-
- // for now the arrow can only be on the same axis or specified as pixels
- // TODO: sometime it might be interesting to allow it to be on *any* axis
- // but that would require updates to drawing & autorange code and maybe more
- if(aaxRef !== 'pixel' && aaxRef !== axRef) {
- aaxRef = annOut[arrowPosAttr] = 'pixel';
- }
-
- // ax, ay
- var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4;
- Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt);
- }
-
- // xanchor, yanchor
- coerce(axLetter + 'anchor');
- }
-
- // if you have one coordinate you should have both
- Lib.noneOrAll(annIn, annOut, ['x', 'y']);
-
- if(showArrow) {
- coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
- coerce('arrowhead');
- coerce('arrowsize');
- coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
- coerce('standoff');
-
- // if you have one part of arrow length you should have both
- Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
- }
-
- if(clickToShow) {
- var xClick = coerce('xclick');
- var yClick = coerce('yclick');
-
- // put the actual click data to bind to into private attributes
- // so we don't have to do this little bit of logic on every hover event
- annOut._xclick = (xClick === undefined) ? annOut.x : xClick;
- annOut._yclick = (yClick === undefined) ? annOut.y : yClick;
- }
-
- return annOut;
-};
diff --git a/src/components/annotations/arrow_paths.js b/src/components/annotations/arrow_paths.js
index 3f27bbaf83a..8b137891791 100644
--- a/src/components/annotations/arrow_paths.js
+++ b/src/components/annotations/arrow_paths.js
@@ -1,63 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-/**
- * 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
- {
- path: '',
- backoff: 0
- },
- // 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
index cdaccd8bc73..8b137891791 100644
--- a/src/components/annotations/attributes.js
+++ b/src/components/annotations/attributes.js
@@ -1,359 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var ARROWPATHS = require('./arrow_paths');
-var fontAttrs = require('../../plots/font_attributes');
-var cartesianConstants = require('../../plots/cartesian/constants');
-var extendFlat = require('../../lib/extend').extendFlat;
-
-
-module.exports = {
- _isLinkedToArray: 'annotation',
-
- visible: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines whether or not this annotation is visible.'
- ].join(' ')
- },
-
- 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({}, 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.'
- },
- standoff: {
- valType: 'number',
- min: 0,
- dflt: 0,
- role: 'style',
- description: [
- 'Sets a distance, in pixels, to move the arrowhead away from the',
- 'position it is pointing at, for example to point at the edge of',
- 'a marker independent of zoom.'
- ].join(' ')
- },
- ax: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the x component of the arrow tail about the arrow head.',
- 'If `axref` is `pixel`, a positive (negative) ',
- 'component corresponds to an arrow pointing',
- 'from right to left (left to right).',
- 'If `axref` is an axis, this is an absolute value on that axis,',
- 'like `x`, NOT a relative value.'
- ].join(' ')
- },
- ay: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the y component of the arrow tail about the arrow head.',
- 'If `ayref` is `pixel`, a positive (negative) ',
- 'component corresponds to an arrow pointing',
- 'from bottom to top (top to bottom).',
- 'If `ayref` is an axis, this is an absolute value on that axis,',
- 'like `y`, NOT a relative value.'
- ].join(' ')
- },
- axref: {
- valType: 'enumerated',
- dflt: 'pixel',
- values: [
- 'pixel',
- cartesianConstants.idRegex.x.toString()
- ],
- role: 'info',
- description: [
- 'Indicates in what terms the tail of the annotation (ax,ay) ',
- 'is specified. If `pixel`, `ax` is a relative offset in pixels ',
- 'from `x`. If set to an x axis id (e.g. *x* or *x2*), `ax` is ',
- 'specified in the same terms as that axis. This is useful ',
- 'for trendline annotations which should continue to indicate ',
- 'the correct trend when zoomed.'
- ].join(' ')
- },
- ayref: {
- valType: 'enumerated',
- dflt: 'pixel',
- values: [
- 'pixel',
- cartesianConstants.idRegex.y.toString()
- ],
- role: 'info',
- description: [
- 'Indicates in what terms the tail of the annotation (ax,ay) ',
- 'is specified. If `pixel`, `ay` is a relative offset in pixels ',
- 'from `y`. If set to a y axis id (e.g. *y* or *y2*), `ay` is ',
- 'specified in the same terms as that axis. This is useful ',
- 'for trendline annotations which should continue to indicate ',
- 'the correct trend when zoomed.'
- ].join(' ')
- },
- // positioning
- xref: {
- valType: 'enumerated',
- values: [
- 'paper',
- cartesianConstants.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: 'any',
- role: 'info',
- description: [
- 'Sets the annotation\'s x position.',
- 'If the axis `type` is *log*, then you must take the',
- 'log of your desired range.',
- 'If the axis `type` is *date*, it should be date strings,',
- 'like date data, though Date objects and unix milliseconds',
- 'will be accepted and converted to strings.',
- 'If the axis `type` is *category*, it should be numbers,',
- 'using the scale where each category is assigned a serial',
- 'number from zero in the order it appears.'
- ].join(' ')
- },
- xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'auto',
- role: 'info',
- description: [
- 'Sets the text box\'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 or if there is an arrow,',
- 'whereas for paper-referenced with no arrow, the anchor picked',
- 'corresponds to the closest side.'
- ].join(' ')
- },
- yref: {
- valType: 'enumerated',
- values: [
- 'paper',
- cartesianConstants.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: 'any',
- role: 'info',
- description: [
- 'Sets the annotation\'s y position.',
- 'If the axis `type` is *log*, then you must take the',
- 'log of your desired range.',
- 'If the axis `type` is *date*, it should be date strings,',
- 'like date data, though Date objects and unix milliseconds',
- 'will be accepted and converted to strings.',
- 'If the axis `type` is *category*, it should be numbers,',
- 'using the scale where each category is assigned a serial',
- 'number from zero in the order it appears.'
- ].join(' ')
- },
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'auto',
- role: 'info',
- description: [
- 'Sets the text box\'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 or if there is an arrow,',
- 'whereas for paper-referenced with no arrow, the anchor picked',
- 'corresponds to the closest side.'
- ].join(' ')
- },
- clicktoshow: {
- valType: 'enumerated',
- values: [false, 'onoff', 'onout'],
- dflt: false,
- role: 'style',
- description: [
- 'Makes this annotation respond to clicks on the plot.',
- 'If you click a data point that exactly matches the `x` and `y`',
- 'values of this annotation, and it is hidden (visible: false),',
- 'it will appear. In *onoff* mode, you must click the same point',
- 'again to make it disappear, so if you click multiple points,',
- 'you can show multiple annotations. In *onout* mode, a click',
- 'anywhere else in the plot (on another data point or not) will',
- 'hide this annotation.',
- 'If you need to show/hide this annotation in response to different',
- '`x` or `y` values, you can set `xclick` and/or `yclick`. This is',
- 'useful for example to label the side of a bar. To label markers',
- 'though, `standoff` is preferred over `xclick` and `yclick`.'
- ].join(' ')
- },
- xclick: {
- valType: 'any',
- role: 'info',
- description: [
- 'Toggle this annotation when clicking a data point whose `x` value',
- 'is `xclick` rather than the annotation\'s `x` value.'
- ].join(' ')
- },
- yclick: {
- valType: 'any',
- role: 'info',
- description: [
- 'Toggle this annotation when clicking a data point whose `y` value',
- 'is `yclick` rather than the annotation\'s `y` value.'
- ].join(' ')
- },
-
- _deprecated: {
- ref: {
- valType: 'string',
- role: 'info',
- description: [
- 'Obsolete. Set `xref` and `yref` separately instead.'
- ].join(' ')
- }
- }
-};
diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js
index f68ea537c63..8b137891791 100644
--- a/src/components/annotations/calc_autorange.js
+++ b/src/components/annotations/calc_autorange.js
@@ -1,93 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-
-var draw = require('./draw').draw;
-
-
-module.exports = function calcAutorange(gd) {
- var fullLayout = gd._fullLayout,
- annotationList = Lib.filterVisible(fullLayout.annotations);
-
- if(!annotationList.length || !gd._fullData.length) return;
-
- var annotationAxes = {};
- annotationList.forEach(function(ann) {
- annotationAxes[ann.xref] = true;
- annotationAxes[ann.yref] = true;
- });
-
- var autorangedAnnos = Axes.list(gd).filter(function(ax) {
- return ax.autorange && annotationAxes[ax._id];
- });
- if(!autorangedAnnos.length) return;
-
- return Lib.syncOrAsync([
- draw,
- annAutorange
- ], gd);
-};
-
-function annAutorange(gd) {
- var fullLayout = gd._fullLayout;
-
- // find the bounding boxes for each of these annotations'
- // relative to their anchor points
- // use the arrow and the text bg rectangle,
- // as the whole anno may include hidden text in its bbox
- Lib.filterVisible(fullLayout.annotations).forEach(function(ann) {
- var xa = Axes.getFromId(gd, ann.xref),
- ya = Axes.getFromId(gd, ann.yref),
- headSize = 3 * ann.arrowsize * ann.arrowwidth || 0;
-
- if(xa && xa.autorange) {
- if(ann.axref === ann.xref) {
- // expand for the arrowhead (padded by arrowhead)
- Axes.expand(xa, [xa.r2c(ann.x)], {
- ppadplus: headSize,
- ppadminus: headSize
- });
- // again for the textbox (padded by textbox)
- Axes.expand(xa, [xa.r2c(ann.ax)], {
- ppadplus: ann._xpadplus,
- ppadminus: ann._xpadminus
- });
- }
- else {
- Axes.expand(xa, [xa.r2c(ann.x)], {
- ppadplus: Math.max(ann._xpadplus, headSize),
- ppadminus: Math.max(ann._xpadminus, headSize)
- });
- }
- }
-
- if(ya && ya.autorange) {
- if(ann.ayref === ann.yref) {
- Axes.expand(ya, [ya.r2c(ann.y)], {
- ppadplus: headSize,
- ppadminus: headSize
- });
- Axes.expand(ya, [ya.r2c(ann.ay)], {
- ppadplus: ann._ypadplus,
- ppadminus: ann._ypadminus
- });
- }
- else {
- Axes.expand(ya, [ya.r2c(ann.y)], {
- ppadplus: Math.max(ann._ypadplus, headSize),
- ppadminus: Math.max(ann._ypadminus, headSize)
- });
- }
- }
- });
-}
diff --git a/src/components/annotations/click.js b/src/components/annotations/click.js
index 8fe77ce8286..8b137891791 100644
--- a/src/components/annotations/click.js
+++ b/src/components/annotations/click.js
@@ -1,121 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Plotly = require('../../plotly');
-
-
-module.exports = {
- hasClickToShow: hasClickToShow,
- onClick: onClick
-};
-
-/*
- * hasClickToShow: does the given hoverData have ANY annotations which will
- * turn ON if we click here? (used by hover events to set cursor)
- *
- * gd: graphDiv
- * hoverData: a hoverData array, as included with the *plotly_hover* or
- * *plotly_click* events in the `points` attribute
- *
- * returns: boolean
- */
-function hasClickToShow(gd, hoverData) {
- var sets = getToggleSets(gd, hoverData);
- return sets.on.length > 0 || sets.explicitOff.length > 0;
-}
-
-/*
- * onClick: perform the toggling (via Plotly.update) implied by clicking
- * at this hoverData
- *
- * gd: graphDiv
- * hoverData: a hoverData array, as included with the *plotly_hover* or
- * *plotly_click* events in the `points` attribute
- *
- * returns: Promise that the update is complete
- */
-function onClick(gd, hoverData) {
- var toggleSets = getToggleSets(gd, hoverData),
- onSet = toggleSets.on,
- offSet = toggleSets.off.concat(toggleSets.explicitOff),
- update = {},
- i;
-
- if(!(onSet.length || offSet.length)) return;
-
- for(i = 0; i < onSet.length; i++) {
- update['annotations[' + onSet[i] + '].visible'] = true;
- }
-
- for(i = 0; i < offSet.length; i++) {
- update['annotations[' + offSet[i] + '].visible'] = false;
- }
-
- return Plotly.update(gd, {}, update);
-}
-
-/*
- * getToggleSets: find the annotations which will turn on or off at this
- * hoverData
- *
- * gd: graphDiv
- * hoverData: a hoverData array, as included with the *plotly_hover* or
- * *plotly_click* events in the `points` attribute
- *
- * returns: {
- * on: Array (indices of annotations to turn on),
- * off: Array (indices to turn off because you're not hovering on them),
- * explicitOff: Array (indices to turn off because you *are* hovering on them)
- * }
- */
-function getToggleSets(gd, hoverData) {
- var annotations = gd._fullLayout.annotations,
- onSet = [],
- offSet = [],
- explicitOffSet = [],
- hoverLen = (hoverData || []).length;
-
- var i, j, anni, showMode, pointj, toggleType;
-
- for(i = 0; i < annotations.length; i++) {
- anni = annotations[i];
- showMode = anni.clicktoshow;
- if(showMode) {
- for(j = 0; j < hoverLen; j++) {
- pointj = hoverData[j];
- if(pointj.x === anni._xclick && pointj.y === anni._yclick &&
- pointj.xaxis._id === anni.xref &&
- pointj.yaxis._id === anni.yref) {
- // match! toggle this annotation
- // regardless of its clicktoshow mode
- // but if it's onout mode, off is implicit
- if(anni.visible) {
- if(showMode === 'onout') toggleType = offSet;
- else toggleType = explicitOffSet;
- }
- else {
- toggleType = onSet;
- }
- toggleType.push(i);
- break;
- }
- }
-
- if(j === hoverLen) {
- // no match - only turn this annotation OFF, and only if
- // showmode is 'onout'
- if(anni.visible && showMode === 'onout') offSet.push(i);
- }
- }
- }
-
- return {on: onSet, off: offSet, explicitOff: explicitOffSet};
-}
diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js
index a4e9b9b45df..8b137891791 100644
--- a/src/components/annotations/defaults.js
+++ b/src/components/annotations/defaults.js
@@ -1,23 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
-var handleAnnotationDefaults = require('./annotation_defaults');
-
-
-module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
- var opts = {
- name: 'annotations',
- handleItemDefaults: handleAnnotationDefaults
- };
-
- handleArrayContainerDefaults(layoutIn, layoutOut, opts);
-};
diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js
index f6cb37b8515..8b137891791 100644
--- a/src/components/annotations/draw.js
+++ b/src/components/annotations/draw.js
@@ -1,775 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-var Color = require('../color');
-var Drawing = require('../drawing');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var setCursor = require('../../lib/setcursor');
-var dragElement = require('../dragelement');
-
-var handleAnnotationDefaults = require('./annotation_defaults');
-var supplyLayoutDefaults = require('./defaults');
-var drawArrowHead = require('./draw_arrow_head');
-
-
-// Annotations are stored in gd.layout.annotations, an array of objects
-// index can point to one item in this array,
-// or non-numeric to simply add a new one
-// or -1 to modify all existing
-// opt can be the full options object, or one key (to be set to value)
-// or undefined to simply redraw
-// if opt is blank, val can be 'add' or a full options object to add a new
-// annotation at that point in the array, or 'remove' to delete this one
-
-module.exports = {
- draw: draw,
- drawOne: drawOne
-};
-
-function draw(gd) {
- var fullLayout = gd._fullLayout;
-
- fullLayout._infolayer.selectAll('.annotation').remove();
-
- for(var i = 0; i < fullLayout.annotations.length; i++) {
- if(fullLayout.annotations[i].visible) {
- drawOne(gd, i);
- }
- }
-
- return Plots.previousPromises(gd);
-}
-
-function drawOne(gd, index, opt, value) {
- var layout = gd.layout,
- fullLayout = gd._fullLayout,
- i;
-
- if(!isNumeric(index) || index === -1) {
-
- // no index provided - we're operating on ALL annotations
- if(!index && Array.isArray(value)) {
- // a whole annotation array is passed in
- // (as in, redo of delete all)
- layout.annotations = value;
- supplyLayoutDefaults(layout, fullLayout);
- draw(gd);
- return;
- }
- else if(value === 'remove') {
- // delete all
- delete layout.annotations;
- fullLayout.annotations = [];
- draw(gd);
- return;
- }
- else if(opt && value !== 'add') {
- // make the same change to all annotations
- for(i = 0; i < fullLayout.annotations.length; i++) {
- drawOne(gd, i, opt, value);
- }
- return;
- }
- else {
- // add a new empty annotation
- index = fullLayout.annotations.length;
- fullLayout.annotations.push({});
- }
- }
-
- if(!opt && value) {
- if(value === 'remove') {
- fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]')
- .remove();
- fullLayout.annotations.splice(index, 1);
- layout.annotations.splice(index, 1);
- for(i = index; i < fullLayout.annotations.length; i++) {
- fullLayout._infolayer
- .selectAll('.annotation[data-index="' + (i + 1) + '"]')
- .attr('data-index', String(i));
-
- // redraw all annotations past the removed one,
- // so they bind to the right events
- drawOne(gd, i);
- }
- return;
- }
- else if(value === 'add' || Lib.isPlainObject(value)) {
- fullLayout.annotations.splice(index, 0, {});
-
- var rule = Lib.isPlainObject(value) ?
- Lib.extendFlat({}, value) :
- {text: 'New text'};
-
- if(layout.annotations) {
- layout.annotations.splice(index, 0, rule);
- } else {
- layout.annotations = [rule];
- }
-
- for(i = fullLayout.annotations.length - 1; i > index; i--) {
- fullLayout._infolayer
- .selectAll('.annotation[data-index="' + (i - 1) + '"]')
- .attr('data-index', String(i));
- drawOne(gd, i);
- }
- }
- }
-
- // remove the existing annotation if there is one
- fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]').remove();
-
- // remember a few things about what was already there,
- var optionsIn = layout.annotations[index],
- oldPrivate = fullLayout.annotations[index];
-
- // not sure how we're getting here... but C12 is seeing a bug
- // where we fail here when they add/remove annotations
- if(!optionsIn) return;
-
- // alter the input annotation as requested
- var optionsEdit = {};
- if(typeof opt === 'string' && opt) optionsEdit[opt] = value;
- else if(Lib.isPlainObject(opt)) optionsEdit = opt;
-
- var optionKeys = Object.keys(optionsEdit);
- for(i = 0; i < optionKeys.length; i++) {
- var k = optionKeys[i];
- Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]);
- }
-
- // return early in visible: false updates
- if(optionsIn.visible === false) return;
-
- var gs = fullLayout._size;
- var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref};
-
- var axLetters = ['x', 'y'];
- for(i = 0; i < 2; i++) {
- var axLetter = axLetters[i];
- // if we don't have an explicit position already,
- // don't set one just because we're changing references
- // or axis type.
- // the defaults will be consistent most of the time anyway,
- // except in log/linear changes
- if(optionsEdit[axLetter] !== undefined ||
- optionsIn[axLetter] === undefined) {
- continue;
- }
-
- var axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')),
- axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')),
- position = optionsIn[axLetter],
- axTypeOld = oldPrivate['_' + axLetter + 'type'];
-
- if(optionsEdit[axLetter + 'ref'] !== undefined) {
-
- // TODO: include ax / ay / axref / ayref here if not 'pixel'
- // or even better, move all of this machinery out of here and into
- // streambed as extra attributes to a regular relayout call
- // we should do this after v2.0 when it can work equivalently for
- // annotations, shapes, and images.
-
- var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto',
- plotSize = (axLetter === 'x' ? gs.w : gs.h),
- halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) /
- (2 * plotSize);
- if(axOld && axNew) { // data -> different data
- // go to the same fraction of the axis length
- // whether or not these axes share a domain
-
- position = axNew.fraction2r(axOld.r2fraction(position));
- }
- else if(axOld) { // data -> paper
- // first convert to fraction of the axis
- position = axOld.r2fraction(position);
-
- // next scale the axis to the whole plot
- position = axOld.domain[0] +
- position * (axOld.domain[1] - axOld.domain[0]);
-
- // finally see if we need to adjust auto alignment
- // because auto always means middle / center alignment for data,
- // but it changes for page alignment based on the closest side
- if(autoAnchor) {
- var posPlus = position + halfSizeFrac,
- posMinus = position - halfSizeFrac;
- if(position + posMinus < 2 / 3) position = posMinus;
- else if(position + posPlus > 4 / 3) position = posPlus;
- }
- }
- else if(axNew) { // paper -> data
- // first see if we need to adjust auto alignment
- if(autoAnchor) {
- if(position < 1 / 3) position += halfSizeFrac;
- else if(position > 2 / 3) position -= halfSizeFrac;
- }
-
- // next convert to fraction of the axis
- position = (position - axNew.domain[0]) /
- (axNew.domain[1] - axNew.domain[0]);
-
- // finally convert to data coordinates
- position = axNew.fraction2r(position);
- }
- }
-
- if(axNew && axNew === axOld && axTypeOld) {
- if(axTypeOld === 'log' && axNew.type !== 'log') {
- position = Math.pow(10, position);
- }
- else if(axTypeOld !== 'log' && axNew.type === 'log') {
- position = (position > 0) ?
- Math.log(position) / Math.LN10 : undefined;
- }
- }
-
- optionsIn[axLetter] = position;
- }
-
- var options = {};
- handleAnnotationDefaults(optionsIn, options, fullLayout);
- fullLayout.annotations[index] = options;
-
- var xa = Axes.getFromId(gd, options.xref),
- ya = Axes.getFromId(gd, options.yref),
-
- // calculated pixel positions
- // x & y each will get text, head, and tail as appropriate
- annPosPx = {x: {}, y: {}},
- textangle = +options.textangle || 0;
-
- // create the components
- // made a single group to contain all, so opacity can work right
- // with border/arrow together this could handle a whole bunch of
- // cleanup at this point, but works for now
- var annGroup = fullLayout._infolayer.append('g')
- .classed('annotation', true)
- .attr('data-index', String(index))
- .style('opacity', options.opacity)
- .on('click', function() {
- gd._dragging = false;
- gd.emit('plotly_clickannotation', {
- index: index,
- annotation: optionsIn,
- fullAnnotation: options
- });
- });
-
- // another group for text+background so that they can rotate together
- var annTextGroup = annGroup.append('g')
- .classed('annotation-text-g', true)
- .attr('data-index', String(index));
-
- var annTextGroupInner = annTextGroup.append('g');
-
- var borderwidth = options.borderwidth,
- borderpad = options.borderpad,
- borderfull = borderwidth + borderpad;
-
- var annTextBG = annTextGroupInner.append('rect')
- .attr('class', 'bg')
- .style('stroke-width', borderwidth + 'px')
- .call(Color.stroke, options.bordercolor)
- .call(Color.fill, options.bgcolor);
-
- var font = options.font;
-
- var annText = annTextGroupInner.append('text')
- .classed('annotation', true)
- .attr('data-unformatted', options.text)
- .text(options.text);
-
- function textLayout(s) {
- s.call(Drawing.font, font)
- .attr({
- 'text-anchor': {
- left: 'start',
- right: 'end'
- }[options.align] || 'middle'
- });
-
- svgTextUtils.convertToTspans(s, drawGraphicalElements);
- return s;
- }
-
- function drawGraphicalElements() {
-
- // make sure lines are aligned the way they will be
- // at the end, even if their position changes
- annText.selectAll('tspan.line').attr({y: 0, x: 0});
-
- var mathjaxGroup = annTextGroupInner.select('.annotation-math-group'),
- hasMathjax = !mathjaxGroup.empty(),
- anntextBB = Drawing.bBox(
- (hasMathjax ? mathjaxGroup : annText).node()),
- annwidth = anntextBB.width,
- annheight = anntextBB.height,
- outerwidth = Math.round(annwidth + 2 * borderfull),
- outerheight = Math.round(annheight + 2 * borderfull);
-
-
- // save size in the annotation object for use by autoscale
- options._w = annwidth;
- options._h = annheight;
-
- function shiftFraction(v, anchor) {
- if(anchor === 'auto') {
- if(v < 1 / 3) anchor = 'left';
- else if(v > 2 / 3) anchor = 'right';
- else anchor = 'center';
- }
- return {
- center: 0,
- middle: 0,
- left: 0.5,
- bottom: -0.5,
- right: -0.5,
- top: 0.5
- }[anchor];
- }
-
- var annotationIsOffscreen = false;
- ['x', 'y'].forEach(function(axLetter) {
- var axRef = options[axLetter + 'ref'] || axLetter,
- tailRef = options['a' + axLetter + 'ref'],
- ax = Axes.getFromId(gd, axRef),
- dimAngle = (textangle + (axLetter === 'x' ? 0 : -90)) * Math.PI / 180,
- // note that these two can be either positive or negative
- annSizeFromWidth = outerwidth * Math.cos(dimAngle),
- annSizeFromHeight = outerheight * Math.sin(dimAngle),
- // but this one is the positive total size
- annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight),
- anchor = options[axLetter + 'anchor'],
- posPx = annPosPx[axLetter],
- basePx,
- textPadShift,
- alignPosition,
- autoAlignFraction,
- textShift;
-
- /*
- * calculate the *primary* pixel position
- * which is the arrowhead if there is one,
- * otherwise the text anchor point
- */
- if(ax) {
- /*
- * hide the annotation if it's pointing outside the visible plot
- * as long as the axis isn't autoranged - then we need to draw it
- * anyway to get its bounding box. When we're dragging, an axis can
- * still look autoranged even though it won't be when the drag finishes.
- */
- var posFraction = ax.r2fraction(options[axLetter]);
- if((gd._dragging || !ax.autorange) && (posFraction < 0 || posFraction > 1)) {
- if(tailRef === axRef) {
- posFraction = ax.r2fraction(options['a' + axLetter]);
- if(posFraction < 0 || posFraction > 1) {
- annotationIsOffscreen = true;
- }
- }
- else {
- annotationIsOffscreen = true;
- }
-
- if(annotationIsOffscreen) return;
- }
- basePx = ax._offset + ax.r2p(options[axLetter]);
- autoAlignFraction = 0.5;
- }
- else {
- if(axLetter === 'x') {
- alignPosition = options[axLetter];
- basePx = gs.l + gs.w * alignPosition;
- }
- else {
- alignPosition = 1 - options[axLetter];
- basePx = gs.t + gs.h * alignPosition;
- }
- autoAlignFraction = options.showarrow ? 0.5 : alignPosition;
- }
-
- // now translate this into pixel positions of head, tail, and text
- // as well as paddings for autorange
- if(options.showarrow) {
- posPx.head = basePx;
-
- var arrowLength = options['a' + axLetter];
-
- // with an arrow, the text rotates around the anchor point
- textShift = annSizeFromWidth * shiftFraction(0.5, options.xanchor) -
- annSizeFromHeight * shiftFraction(0.5, options.yanchor);
-
- if(tailRef === axRef) {
- posPx.tail = ax._offset + ax.r2p(arrowLength);
- // tail is data-referenced: autorange pads the text in px from the tail
- textPadShift = textShift;
- }
- else {
- posPx.tail = basePx + arrowLength;
- // tail is specified in px from head, so autorange also pads vs head
- textPadShift = textShift + arrowLength;
- }
-
- posPx.text = posPx.tail + textShift;
-
- // constrain pixel/paper referenced so the draggers are at least
- // partially visible
- var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height'];
- if(axRef === 'paper') {
- posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1);
- }
- if(tailRef === 'pixel') {
- var shiftPlus = -Math.max(posPx.tail - 3, posPx.text),
- shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx;
- if(shiftPlus > 0) {
- posPx.tail += shiftPlus;
- posPx.text += shiftPlus;
- }
- else if(shiftMinus > 0) {
- posPx.tail -= shiftMinus;
- posPx.text -= shiftMinus;
- }
- }
- }
- else {
- // with no arrow, the text rotates and *then* we put the anchor
- // relative to the new bounding box
- textShift = annSize * shiftFraction(autoAlignFraction, anchor);
- textPadShift = textShift;
- posPx.text = basePx + textShift;
- }
-
- options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift;
- options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift;
-
- // save the current axis type for later log/linear changes
- options['_' + axLetter + 'type'] = ax && ax.type;
- });
-
- if(annotationIsOffscreen) {
- annTextGroupInner.remove();
- return;
- }
-
- if(hasMathjax) {
- mathjaxGroup.select('svg').attr({x: borderfull - 1, y: borderfull});
- }
- else {
- var texty = borderfull - anntextBB.top,
- textx = borderfull - anntextBB.left;
- annText.attr({x: textx, y: texty});
- annText.selectAll('tspan.line').attr({y: texty, x: textx});
- }
-
- annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2,
- outerwidth - borderwidth, outerheight - borderwidth);
-
- annTextGroupInner.call(Drawing.setTranslate,
- Math.round(annPosPx.x.text - outerwidth / 2),
- Math.round(annPosPx.y.text - outerheight / 2));
-
- /*
- * rotate text and background
- * we already calculated the text center position *as rotated*
- * because we needed that for autoranging anyway, so now whether
- * we have an arrow or not, we rotate about the text center.
- */
- annTextGroup.attr({transform: 'rotate(' + textangle + ',' +
- annPosPx.x.text + ',' + annPosPx.y.text + ')'});
-
- var annbase = 'annotations[' + index + ']';
-
- /*
- * add the arrow
- * uses options[arrowwidth,arrowcolor,arrowhead] for styling
- * dx and dy are normally zero, but when you are dragging the textbox
- * while the head stays put, dx and dy are the pixel offsets
- */
- var drawArrow = function(dx, dy) {
- d3.select(gd)
- .selectAll('.annotation-arrow-g[data-index="' + index + '"]')
- .remove();
-
- var headX = annPosPx.x.head,
- headY = annPosPx.y.head,
- tailX = annPosPx.x.tail + dx,
- tailY = annPosPx.y.tail + dy,
- textX = annPosPx.x.text + dx,
- textY = annPosPx.y.text + dy,
-
- // find the edge of the text box, where we'll start the arrow:
- // create transform matrix to rotate the text box corners
- transform = Lib.rotationXYMatrix(textangle, textX, textY),
- applyTransform = Lib.apply2DTransform(transform),
- applyTransform2 = Lib.apply2DTransform2(transform),
-
- // calculate and transform bounding box
- width = +annTextBG.attr('width'),
- height = +annTextBG.attr('height'),
- xLeft = textX - 0.5 * width,
- xRight = xLeft + width,
- yTop = textY - 0.5 * height,
- yBottom = yTop + height,
- edges = [
- [xLeft, yTop, xLeft, yBottom],
- [xLeft, yBottom, xRight, yBottom],
- [xRight, yBottom, xRight, yTop],
- [xRight, yTop, xLeft, yTop]
- ].map(applyTransform2);
-
- // Remove the line if it ends inside the box. Use ray
- // casting for rotated boxes: see which edges intersect a
- // line from the arrowhead to far away and reduce with xor
- // to get the parity of the number of intersections.
- if(edges.reduce(function(a, x) {
- return a ^
- !!lineIntersect(headX, headY, headX + 1e6, headY + 1e6,
- x[0], x[1], x[2], x[3]);
- }, false)) {
- // no line or arrow - so quit drawArrow now
- return;
- }
-
- edges.forEach(function(x) {
- var p = lineIntersect(tailX, tailY, headX, headY,
- x[0], x[1], x[2], x[3]);
- if(p) {
- tailX = p.x;
- tailY = p.y;
- }
- });
-
- var strokewidth = options.arrowwidth,
- arrowColor = options.arrowcolor;
-
- var arrowGroup = annGroup.append('g')
- .style({opacity: Color.opacity(arrowColor)})
- .classed('annotation-arrow-g', true)
- .attr('data-index', String(index));
-
- var arrow = arrowGroup.append('path')
- .attr('d', 'M' + tailX + ',' + tailY + 'L' + headX + ',' + headY)
- .style('stroke-width', strokewidth + 'px')
- .call(Color.stroke, Color.rgb(arrowColor));
-
- drawArrowHead(arrow, options.arrowhead, 'end', options.arrowsize, options.standoff);
-
- // the arrow dragger is a small square right at the head, then a line to the tail,
- // all expanded by a stroke width of 6px plus the arrow line width
- if(gd._context.editable && arrow.node().parentNode) {
- var arrowDragHeadX = headX;
- var arrowDragHeadY = headY;
- if(options.standoff) {
- var arrowLength = Math.sqrt(Math.pow(headX - tailX, 2) + Math.pow(headY - tailY, 2));
- arrowDragHeadX += options.standoff * (tailX - headX) / arrowLength;
- arrowDragHeadY += options.standoff * (tailY - headY) / arrowLength;
- }
- var arrowDrag = arrowGroup.append('path')
- .classed('annotation', true)
- .classed('anndrag', true)
- .attr({
- 'data-index': String(index),
- d: 'M3,3H-3V-3H3ZM0,0L' + (tailX - arrowDragHeadX) + ',' + (tailY - arrowDragHeadY),
- transform: 'translate(' + arrowDragHeadX + ',' + arrowDragHeadY + ')'
- })
- .style('stroke-width', (strokewidth + 6) + 'px')
- .call(Color.stroke, 'rgba(0,0,0,0)')
- .call(Color.fill, 'rgba(0,0,0,0)');
-
- var update,
- annx0,
- anny0;
-
- // dragger for the arrow & head: translates the whole thing
- // (head/tail/text) all together
- dragElement.init({
- element: arrowDrag.node(),
- prepFn: function() {
- var pos = Drawing.getTranslate(annTextGroupInner);
-
- annx0 = pos.x;
- anny0 = pos.y;
- update = {};
- if(xa && xa.autorange) {
- update[xa._name + '.autorange'] = true;
- }
- if(ya && ya.autorange) {
- update[ya._name + '.autorange'] = true;
- }
- },
- moveFn: function(dx, dy) {
- var annxy0 = applyTransform(annx0, anny0),
- xcenter = annxy0[0] + dx,
- ycenter = annxy0[1] + dy;
- annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter);
-
- update[annbase + '.x'] = xa ?
- xa.p2r(xa.r2p(options.x) + dx) :
- ((headX + dx - gs.l) / gs.w);
- update[annbase + '.y'] = ya ?
- ya.p2r(ya.r2p(options.y) + dy) :
- (1 - ((headY + dy - gs.t) / gs.h));
-
- if(options.axref === options.xref) {
- update[annbase + '.ax'] = xa ?
- xa.p2r(xa.r2p(options.ax) + dx) :
- ((headX + dx - gs.l) / gs.w);
- }
-
- if(options.ayref === options.yref) {
- update[annbase + '.ay'] = ya ?
- ya.p2r(ya.r2p(options.ay) + dy) :
- (1 - ((headY + dy - gs.t) / gs.h));
- }
-
- arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
- annTextGroup.attr({
- transform: 'rotate(' + textangle + ',' +
- xcenter + ',' + ycenter + ')'
- });
- },
- doneFn: function(dragged) {
- if(dragged) {
- Plotly.relayout(gd, update);
- var notesBox = document.querySelector('.js-notes-box-panel');
- if(notesBox) notesBox.redraw(notesBox.selectedObj);
- }
- }
- });
- }
- };
-
- if(options.showarrow) drawArrow(0, 0);
-
- // user dragging the annotation (text, not arrow)
- if(gd._context.editable) {
- var update,
- baseTextTransform;
-
- // dragger for the textbox: if there's an arrow, just drag the
- // textbox and tail, leave the head untouched
- dragElement.init({
- element: annTextGroupInner.node(),
- prepFn: function() {
- baseTextTransform = annTextGroup.attr('transform');
- update = {};
- },
- moveFn: function(dx, dy) {
- var csr = 'pointer';
- if(options.showarrow) {
- if(options.axref === options.xref) {
- update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx);
- } else {
- update[annbase + '.ax'] = options.ax + dx;
- }
-
- if(options.ayref === options.yref) {
- update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy);
- } else {
- update[annbase + '.ay'] = options.ay + dy;
- }
-
- drawArrow(dx, dy);
- }
- else {
- if(xa) update[annbase + '.x'] = options.x + dx / xa._m;
- else {
- var widthFraction = options._xsize / gs.w,
- xLeft = options.x + options._xshift / gs.w - widthFraction / 2;
-
- update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w,
- widthFraction, 0, 1, options.xanchor);
- }
-
- if(ya) update[annbase + '.y'] = options.y + dy / ya._m;
- else {
- var heightFraction = options._ysize / gs.h,
- yBottom = options.y - options._yshift / gs.h - heightFraction / 2;
-
- update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h,
- heightFraction, 0, 1, options.yanchor);
- }
- if(!xa || !ya) {
- csr = dragElement.getCursor(
- xa ? 0.5 : update[annbase + '.x'],
- ya ? 0.5 : update[annbase + '.y'],
- options.xanchor, options.yanchor
- );
- }
- }
-
- annTextGroup.attr({
- transform: 'translate(' + dx + ',' + dy + ')' + baseTextTransform
- });
-
- setCursor(annTextGroupInner, csr);
- },
- doneFn: function(dragged) {
- setCursor(annTextGroupInner);
- if(dragged) {
- Plotly.relayout(gd, update);
- var notesBox = document.querySelector('.js-notes-box-panel');
- if(notesBox) notesBox.redraw(notesBox.selectedObj);
- }
- }
- });
- }
- }
-
- if(gd._context.editable) {
- annText.call(svgTextUtils.makeEditable, annTextGroupInner)
- .call(textLayout)
- .on('edit', function(_text) {
- options.text = _text;
- this.attr({'data-unformatted': options.text});
- this.call(textLayout);
- var update = {};
- update['annotations[' + index + '].text'] = options.text;
- if(xa && xa.autorange) {
- update[xa._name + '.autorange'] = true;
- }
- if(ya && ya.autorange) {
- update[ya._name + '.autorange'] = true;
- }
- Plotly.relayout(gd, update);
- });
- }
- else annText.call(textLayout);
-}
-
-// look for intersection of two line segments
-// (1->2 and 3->4) - returns array [x,y] if they do, null if not
-function lineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
- var a = x2 - x1,
- b = x3 - x1,
- c = x4 - x3,
- d = y2 - y1,
- e = y3 - y1,
- f = y4 - y3,
- det = a * f - c * d;
- // parallel lines? intersection is undefined
- // ignore the case where they are colinear
- if(det === 0) return null;
- var t = (b * f - c * e) / det,
- u = (b * d - a * e) / det;
- // segments do not intersect?
- if(u < 0 || u > 1 || t < 0 || t > 1) return null;
-
- return {x: x1 + a * t, y: y1 + d * t};
-}
diff --git a/src/components/annotations/draw_arrow_head.js b/src/components/annotations/draw_arrow_head.js
index 69e5181914c..8b137891791 100644
--- a/src/components/annotations/draw_arrow_head.js
+++ b/src/components/annotations/draw_arrow_head.js
@@ -1,132 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Color = require('../color');
-var Drawing = require('../drawing');
-
-var ARROWPATHS = require('./arrow_paths');
-
-// add arrowhead(s) to a path or line d3 element el3
-// style: 1-6, first 5 are pointers, 6 is circle, 7 is square, 8 is none
-// ends is 'start', 'end' (default), 'start+end'
-// mag is magnification vs. default (default 1)
-
-module.exports = function drawArrowHead(el3, style, ends, mag, standoff) {
- if(!isNumeric(mag)) mag = 1;
- var el = el3.node(),
- headStyle = ARROWPATHS[style||0];
-
- if(typeof ends !== 'string' || !ends) ends = 'end';
-
- var scale = (Drawing.getPx(el3, 'stroke-width') || 1) * mag,
- stroke = el3.style('stroke') || Color.defaultLine,
- opacity = el3.style('stroke-opacity') || 1,
- doStart = ends.indexOf('start') >= 0,
- doEnd = ends.indexOf('end') >= 0,
- backOff = headStyle.backoff * scale + standoff,
- start,
- end,
- startRot,
- endRot;
-
- if(el.nodeName === 'line') {
- start = {x: +el3.attr('x1'), y: +el3.attr('y1')};
- end = {x: +el3.attr('x2'), y: +el3.attr('y2')};
-
- var dx = start.x - end.x,
- dy = start.y - end.y;
-
- startRot = Math.atan2(dy, dx);
- endRot = startRot + Math.PI;
- if(backOff) {
- if(backOff * backOff > dx * dx + dy * dy) {
- hideLine();
- return;
- }
- var backOffX = backOff * Math.cos(startRot),
- backOffY = backOff * Math.sin(startRot);
-
- if(doStart) {
- start.x -= backOffX;
- start.y -= backOffY;
- el3.attr({x1: start.x, y1: start.y});
- }
- if(doEnd) {
- end.x += backOffX;
- end.y += backOffY;
- el3.attr({x2: end.x, y2: end.y});
- }
- }
- }
- else if(el.nodeName === 'path') {
- var pathlen = el.getTotalLength(),
- // using dash to hide the backOff region of the path.
- // if we ever allow dash for the arrow we'll have to
- // do better than this hack... maybe just manually
- // combine the two
- dashArray = '';
-
- if(pathlen < backOff) {
- hideLine();
- return;
- }
-
- if(doStart) {
- var start0 = el.getPointAtLength(0),
- dstart = el.getPointAtLength(0.1);
- startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x);
- start = el.getPointAtLength(Math.min(backOff, pathlen));
- if(backOff) dashArray = '0px,' + backOff + 'px,';
- }
-
- if(doEnd) {
- var end0 = el.getPointAtLength(pathlen),
- dend = el.getPointAtLength(pathlen - 0.1);
- endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x);
- end = el.getPointAtLength(Math.max(0, pathlen - backOff));
-
- if(backOff) {
- var shortening = dashArray ? 2 * backOff : backOff;
- dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px';
- }
- }
- else if(dashArray) dashArray += pathlen + 'px';
-
- if(dashArray) el3.style('stroke-dasharray', dashArray);
- }
-
- function hideLine() { el3.style('stroke-dasharray', '0px,100px'); }
-
- function drawhead(p, rot) {
- if(!headStyle.path) return;
- if(style > 5) rot = 0; // don't rotate square or circle
- d3.select(el.parentElement).append('path')
- .attr({
- 'class': el3.attr('class'),
- d: headStyle.path,
- transform:
- 'translate(' + p.x + ',' + p.y + ')' +
- 'rotate(' + (rot * 180 / Math.PI) + ')' +
- 'scale(' + scale + ')'
- })
- .style({
- fill: stroke,
- opacity: opacity,
- 'stroke-width': 0
- });
- }
-
- if(doStart) drawhead(start, startRot);
- if(doEnd) drawhead(end, endRot);
-};
diff --git a/src/components/annotations/index.js b/src/components/annotations/index.js
index bb32b6b69df..8b137891791 100644
--- a/src/components/annotations/index.js
+++ b/src/components/annotations/index.js
@@ -1,28 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var drawModule = require('./draw');
-var clickModule = require('./click');
-
-module.exports = {
- moduleType: 'component',
- name: 'annotations',
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- calcAutorange: require('./calc_autorange'),
- draw: drawModule.draw,
- drawOne: drawModule.drawOne,
-
- hasClickToShow: clickModule.hasClickToShow,
- onClick: clickModule.onClick
-};
diff --git a/src/components/calendars/calendars.js b/src/components/calendars/calendars.js
index 2f2a1dec9d5..8b137891791 100644
--- a/src/components/calendars/calendars.js
+++ b/src/components/calendars/calendars.js
@@ -1,31 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-// a trimmed down version of:
-// https://github.com/alexcjohnson/world-calendars/blob/master/dist/index.js
-
-module.exports = require('world-calendars/dist/main');
-
-require('world-calendars/dist/plus');
-
-require('world-calendars/dist/calendars/chinese');
-require('world-calendars/dist/calendars/coptic');
-require('world-calendars/dist/calendars/discworld');
-require('world-calendars/dist/calendars/ethiopian');
-require('world-calendars/dist/calendars/hebrew');
-require('world-calendars/dist/calendars/islamic');
-require('world-calendars/dist/calendars/julian');
-require('world-calendars/dist/calendars/mayan');
-require('world-calendars/dist/calendars/nanakshahi');
-require('world-calendars/dist/calendars/nepali');
-require('world-calendars/dist/calendars/persian');
-require('world-calendars/dist/calendars/taiwan');
-require('world-calendars/dist/calendars/thai');
-require('world-calendars/dist/calendars/ummalqura');
diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js
index eca51c1ac8a..8b137891791 100644
--- a/src/components/calendars/index.js
+++ b/src/components/calendars/index.js
@@ -1,257 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var calendars = require('./calendars');
-
-var Lib = require('../../lib');
-var constants = require('../../constants/numerical');
-
-var EPOCHJD = constants.EPOCHJD;
-var ONEDAY = constants.ONEDAY;
-
-var attributes = {
- valType: 'enumerated',
- values: Object.keys(calendars.calendars),
- role: 'info',
- dflt: 'gregorian'
-};
-
-var handleDefaults = function(contIn, contOut, attr, dflt) {
- var attrs = {};
- attrs[attr] = attributes;
-
- return Lib.coerce(contIn, contOut, attrs, attr, dflt);
-};
-
-var handleTraceDefaults = function(traceIn, traceOut, coords, layout) {
- for(var i = 0; i < coords.length; i++) {
- handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar);
- }
-};
-
-// each calendar needs its own default canonical tick. I would love to use
-// 2000-01-01 (or even 0000-01-01) for them all but they don't necessarily
-// all support either of those dates. Instead I'll use the most significant
-// number they *do* support, biased toward the present day.
-var CANONICAL_TICK = {
- chinese: '2000-01-01',
- coptic: '2000-01-01',
- discworld: '2000-01-01',
- ethiopian: '2000-01-01',
- hebrew: '5000-01-01',
- islamic: '1000-01-01',
- julian: '2000-01-01',
- mayan: '5000-01-01',
- nanakshahi: '1000-01-01',
- nepali: '2000-01-01',
- persian: '1000-01-01',
- jalali: '1000-01-01',
- taiwan: '1000-01-01',
- thai: '2000-01-01',
- ummalqura: '1400-01-01'
-};
-
-// Start on a Sunday - for week ticks
-// Discworld and Mayan calendars don't have 7-day weeks but we're going to give them
-// 7-day week ticks so start on our Sundays.
-// If anyone really cares we can customize the auto tick spacings for these calendars.
-var CANONICAL_SUNDAY = {
- chinese: '2000-01-02',
- coptic: '2000-01-03',
- discworld: '2000-01-03',
- ethiopian: '2000-01-05',
- hebrew: '5000-01-01',
- islamic: '1000-01-02',
- julian: '2000-01-03',
- mayan: '5000-01-01',
- nanakshahi: '1000-01-05',
- nepali: '2000-01-05',
- persian: '1000-01-01',
- jalali: '1000-01-01',
- taiwan: '1000-01-04',
- thai: '2000-01-04',
- ummalqura: '1400-01-06'
-};
-
-var DFLTRANGE = {
- chinese: ['2000-01-01', '2001-01-01'],
- coptic: ['1700-01-01', '1701-01-01'],
- discworld: ['1800-01-01', '1801-01-01'],
- ethiopian: ['2000-01-01', '2001-01-01'],
- hebrew: ['5700-01-01', '5701-01-01'],
- islamic: ['1400-01-01', '1401-01-01'],
- julian: ['2000-01-01', '2001-01-01'],
- mayan: ['5200-01-01', '5201-01-01'],
- nanakshahi: ['0500-01-01', '0501-01-01'],
- nepali: ['2000-01-01', '2001-01-01'],
- persian: ['1400-01-01', '1401-01-01'],
- jalali: ['1400-01-01', '1401-01-01'],
- taiwan: ['0100-01-01', '0101-01-01'],
- thai: ['2500-01-01', '2501-01-01'],
- ummalqura: ['1400-01-01', '1401-01-01']
-};
-
-/*
- * convert d3 templates to world-calendars templates, so our users only need
- * to know d3's specifiers. Map space padding to no padding, and unknown fields
- * to an ugly placeholder
- */
-var UNKNOWN = '##';
-var d3ToWorldCalendars = {
- 'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month
- 'e': {'0': 'd', '-': 'd'}, // alternate, always unpadded day of month
- 'a': {'0': 'D', '-': 'D'}, // short weekday name
- 'A': {'0': 'DD', '-': 'DD'}, // full weekday name
- 'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year
- 'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first)
- 'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number
- 'b': {'0': 'M', '-': 'M'}, // short month name
- 'B': {'0': 'MM', '-': 'MM'}, // full month name
- 'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded)
- 'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded)
- 'U': UNKNOWN, // Sunday-first week of the year
- 'w': UNKNOWN, // day of the week [0(sunday),6]
- // combined format, we replace the date part with the world-calendar version
- // and the %X stays there for d3 to handle with time parts
- 'c': {'0': 'D M d %X yyyy', '-': 'D M d %X yyyy'},
- 'x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'}
-};
-
-function worldCalFmt(fmt, x, calendar) {
- var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
- cDate = getCal(calendar).fromJD(dateJD),
- i = 0,
- modifier, directive, directiveLen, directiveObj, replacementPart;
- while((i = fmt.indexOf('%', i)) !== -1) {
- modifier = fmt.charAt(i + 1);
- if(modifier === '0' || modifier === '-' || modifier === '_') {
- directiveLen = 3;
- directive = fmt.charAt(i + 2);
- if(modifier === '_') modifier = '-';
- }
- else {
- directive = modifier;
- modifier = '0';
- directiveLen = 2;
- }
- directiveObj = d3ToWorldCalendars[directive];
- if(!directiveObj) {
- i += directiveLen;
- }
- else {
- // code is recognized as a date part but world-calendars doesn't support it
- if(directiveObj === UNKNOWN) replacementPart = UNKNOWN;
-
- // format the cDate according to the translated directive
- else replacementPart = cDate.formatDate(directiveObj[modifier]);
-
- fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen);
- i += replacementPart.length;
- }
- }
- return fmt;
-}
-
-// cache world calendars, so we don't have to reinstantiate
-// during each date-time conversion
-var allCals = {};
-function getCal(calendar) {
- var calendarObj = allCals[calendar];
- if(calendarObj) return calendarObj;
-
- calendarObj = allCals[calendar] = calendars.instance(calendar);
- return calendarObj;
-}
-
-function makeAttrs(description) {
- return Lib.extendFlat({}, attributes, { description: description });
-}
-
-function makeTraceAttrsDescription(coord) {
- return 'Sets the calendar system to use with `' + coord + '` date data.';
-}
-
-var xAttrs = {
- xcalendar: makeAttrs(makeTraceAttrsDescription('x'))
-};
-
-var xyAttrs = Lib.extendFlat({}, xAttrs, {
- ycalendar: makeAttrs(makeTraceAttrsDescription('y'))
-});
-
-var xyzAttrs = Lib.extendFlat({}, xyAttrs, {
- zcalendar: makeAttrs(makeTraceAttrsDescription('z'))
-});
-
-var axisAttrs = makeAttrs([
- 'Sets the calendar system to use for `range` and `tick0`',
- 'if this is a date axis. This does not set the calendar for',
- 'interpreting data on this axis, that\'s specified in the trace',
- 'or via the global `layout.calendar`'
-].join(' '));
-
-module.exports = {
- moduleType: 'component',
- name: 'calendars',
-
- schema: {
- traces: {
- scatter: xyAttrs,
- bar: xyAttrs,
- heatmap: xyAttrs,
- contour: xyAttrs,
- histogram: xyAttrs,
- histogram2d: xyAttrs,
- histogram2dcontour: xyAttrs,
- scatter3d: xyzAttrs,
- surface: xyzAttrs,
- mesh3d: xyzAttrs,
- scattergl: xyAttrs,
- ohlc: xAttrs,
- candlestick: xAttrs
- },
- layout: {
- calendar: makeAttrs([
- 'Sets the default calendar system to use for interpreting and',
- 'displaying dates throughout the plot.'
- ].join(' ')),
- 'xaxis.calendar': axisAttrs,
- 'yaxis.calendar': axisAttrs,
- 'scene.xaxis.calendar': axisAttrs,
- 'scene.yaxis.calendar': axisAttrs,
- 'scene.zaxis.calendar': axisAttrs
- },
- transforms: {
- filter: {
- valuecalendar: makeAttrs([
- 'Sets the calendar system to use for `value`, if it is a date.'
- ].join(' ')),
- targetcalendar: makeAttrs([
- 'Sets the calendar system to use for `target`, if it is an',
- 'array of dates. If `target` is a string (eg *x*) we use the',
- 'corresponding trace attribute (eg `xcalendar`) if it exists,',
- 'even if `targetcalendar` is provided.'
- ].join(' '))
- }
- }
- },
-
- layoutAttributes: attributes,
-
- handleDefaults: handleDefaults,
- handleTraceDefaults: handleTraceDefaults,
-
- CANONICAL_SUNDAY: CANONICAL_SUNDAY,
- CANONICAL_TICK: CANONICAL_TICK,
- DFLTRANGE: DFLTRANGE,
-
- getCal: getCal,
- worldCalFmt: worldCalFmt
-};
diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js
index e4a5c6d2c35..8b137891791 100644
--- a/src/components/color/attributes.js
+++ b/src/components/color/attributes.js
@@ -1,38 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-
-// 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';
-
-exports.borderLine = '#BEC8D9';
-
-// with axis.color and Color.interp we aren't using lightLine
-// itself anymore, instead interpolating between axis.color
-// and the background color using tinycolor.mix. lightFraction
-// gives back exactly lightLine if the other colors are defaults.
-exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4);
diff --git a/src/components/color/index.js b/src/components/color/index.js
index 714d5a05cad..8b137891791 100644
--- a/src/components/color/index.js
+++ b/src/components/color/index.js
@@ -1,155 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var tinycolor = require('tinycolor2');
-var isNumeric = require('fast-isnumeric');
-
-var color = module.exports = {};
-
-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();
- return 'rgb(' + Math.round(c.r) + ', ' +
- Math.round(c.g) + ', ' + Math.round(c.b) + ')';
-};
-
-color.rgb = function(cstr) { return color.tinyRGB(tinycolor(cstr)); };
-
-color.opacity = function(cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0; };
-
-color.addOpacity = function(cstr, op) {
- var c = tinycolor(cstr).toRgb();
- return 'rgba(' + Math.round(c.r) + ', ' +
- Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')';
-};
-
-// combine two colors into one apparent color
-// if back has transparency or is missing,
-// color.background is assumed behind it
-color.combine = function(front, back) {
- var fc = tinycolor(front).toRgb();
- if(fc.a === 1) return tinycolor(front).toRgbString();
-
- var bc = tinycolor(back || color.background).toRgb(),
- bcflat = bc.a === 1 ? bc : {
- r: 255 * (1 - bc.a) + bc.r * bc.a,
- g: 255 * (1 - bc.a) + bc.g * bc.a,
- b: 255 * (1 - bc.a) + bc.b * bc.a
- },
- fcflat = {
- r: bcflat.r * (1 - fc.a) + fc.r * fc.a,
- g: bcflat.g * (1 - fc.a) + fc.g * fc.a,
- b: bcflat.b * (1 - fc.a) + fc.b * fc.a
- };
- return tinycolor(fcflat).toRgbString();
-};
-
-color.contrast = function(cstr, lightAmount, darkAmount) {
- var tc = tinycolor(cstr);
-
- var newColor = tc.isLight() ?
- tc.darken(darkAmount) :
- tc.lighten(lightAmount);
-
- return newColor.toString();
-};
-
-color.stroke = function(s, c) {
- var tc = tinycolor(c);
- s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()});
-};
-
-color.fill = function(s, c) {
- var tc = tinycolor(c);
- s.style({
- 'fill': color.tinyRGB(tc),
- 'fill-opacity': tc.getAlpha()
- });
-};
-
-// search container for colors with the deprecated rgb(fractions) format
-// and convert them to rgb(0-255 values)
-color.clean = function(container) {
- if(!container || typeof container !== 'object') return;
-
- var keys = Object.keys(container),
- i,
- j,
- key,
- val;
-
- for(i = 0; i < keys.length; i++) {
- key = keys[i];
- val = container[key];
-
- // only sanitize keys that end in "color" or "colorscale"
- if(key.substr(key.length - 5) === 'color') {
- if(Array.isArray(val)) {
- for(j = 0; j < val.length; j++) val[j] = cleanOne(val[j]);
- }
- else container[key] = cleanOne(val);
- }
- else if(key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) {
- // colorscales have the format [[0, color1], [frac, color2], ... [1, colorN]]
- for(j = 0; j < val.length; j++) {
- if(Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]);
- }
- }
- // recurse into arrays of objects, and plain objects
- else if(Array.isArray(val)) {
- var el0 = val[0];
- if(!Array.isArray(el0) && el0 && typeof el0 === 'object') {
- for(j = 0; j < val.length; j++) color.clean(val[j]);
- }
- }
- else if(val && typeof val === 'object') color.clean(val);
- }
-};
-
-function cleanOne(val) {
- if(isNumeric(val) || typeof val !== 'string') return val;
-
- var valTrim = val.trim();
- if(valTrim.substr(0, 3) !== 'rgb') return val;
-
- var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/);
- if(!match) return val;
-
- var parts = match[1].trim().split(/\s*[\s,]\s*/),
- rgba = valTrim.charAt(3) === 'a' && parts.length === 4;
- if(!rgba && parts.length !== 3) return val;
-
- for(var i = 0; i < parts.length; i++) {
- if(!parts[i].length) return val;
- parts[i] = Number(parts[i]);
-
- // all parts must be non-negative numbers
- if(!(parts[i] >= 0)) return val;
- // alpha>1 gets clipped to 1
- if(i === 3) {
- if(parts[i] > 1) parts[i] = 1;
- }
- // r, g, b must be < 1 (ie 1 itself is not allowed)
- else if(parts[i] >= 1) return val;
- }
-
- var rgbStr = Math.round(parts[0] * 255) + ', ' +
- Math.round(parts[1] * 255) + ', ' +
- Math.round(parts[2] * 255);
-
- if(rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')';
- return 'rgb(' + rgbStr + ')';
-}
diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js
index 62f1e031ff8..8b137891791 100644
--- a/src/components/colorbar/attributes.js
+++ b/src/components/colorbar/attributes.js
@@ -1,194 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var axesAttrs = require('../../plots/cartesian/layout_attributes');
-var fontAttrs = require('../../plots/font_attributes');
-var extendFlat = require('../../lib/extend').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,
- separatethousands: axesAttrs.separatethousands,
- 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({}, 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/components/colorbar/defaults.js b/src/components/colorbar/defaults.js
index be767c67524..8b137891791 100644
--- a/src/components/colorbar/defaults.js
+++ b/src/components/colorbar/defaults.js
@@ -1,65 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-var handleTickValueDefaults = require('../../plots/cartesian/tick_value_defaults');
-var handleTickMarkDefaults = require('../../plots/cartesian/tick_mark_defaults');
-var handleTickLabelDefaults = require('../../plots/cartesian/tick_label_defaults');
-
-var attributes = require('./attributes');
-
-
-module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
- var colorbarOut = containerOut.colorbar = {},
- colorbarIn = containerIn.colorbar || {};
-
- function coerce(attr, dflt) {
- return Lib.coerce(colorbarIn, colorbarOut, attributes, attr, dflt);
- }
-
- var thicknessmode = coerce('thicknessmode');
- coerce('thickness', (thicknessmode === 'fraction') ?
- 30 / (layout.width - layout.margin.l - layout.margin.r) :
- 30
- );
-
- var lenmode = coerce('lenmode');
- coerce('len', (lenmode === 'fraction') ?
- 1 :
- layout.height - layout.margin.t - layout.margin.b
- );
-
- coerce('x');
- coerce('xanchor');
- coerce('xpad');
- coerce('y');
- coerce('yanchor');
- coerce('ypad');
- Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']);
-
- coerce('outlinecolor');
- coerce('outlinewidth');
- coerce('bordercolor');
- coerce('borderwidth');
- coerce('bgcolor');
-
- handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear');
-
- handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear',
- {outerTicks: false, font: layout.font, noHover: true});
-
- handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear',
- {outerTicks: false, font: layout.font, noHover: true});
-
- coerce('title');
- Lib.coerceFont(coerce, 'titlefont', layout.font);
- coerce('titleside');
-};
diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js
index b7eef65140c..8b137891791 100644
--- a/src/components/colorbar/draw.js
+++ b/src/components/colorbar/draw.js
@@ -1,631 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var d3 = require('d3');
-var tinycolor = require('tinycolor2');
-
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
-var Registry = require('../../registry');
-var Axes = require('../../plots/cartesian/axes');
-var dragElement = require('../dragelement');
-var Lib = require('../../lib');
-var extendFlat = require('../../lib/extend').extendFlat;
-var setCursor = require('../../lib/setcursor');
-var Drawing = require('../drawing');
-var Color = require('../color');
-var Titles = require('../titles');
-
-var handleAxisDefaults = require('../../plots/cartesian/axis_defaults');
-var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults');
-var axisLayoutAttrs = require('../../plots/cartesian/layout_attributes');
-
-var attributes = require('./attributes');
-
-
-module.exports = function draw(gd, id) {
- // opts: options object, containing everything from attributes
- // plus a few others that are the equivalent of the colorbar "data"
- var opts = {};
- Object.keys(attributes).forEach(function(k) {
- opts[k] = null;
- });
- // fillcolor can be a d3 scale, domain is z values, range is colors
- // or leave it out for no fill,
- // or set to a string constant for single-color fill
- opts.fillcolor = null;
- // line.color has the same options as fillcolor
- opts.line = {color: null, width: null, dash: null};
- // levels of lines to draw.
- // note that this DOES NOT determine the extent of the bar
- // that's given by the domain of fillcolor
- // (or line.color if no fillcolor domain)
- opts.levels = {start: null, end: null, size: null};
- // separate fill levels (for example, heatmap coloring of a
- // contour map) if this is omitted, fillcolors will be
- // evaluated halfway between levels
- opts.filllevels = null;
-
- function component() {
- var fullLayout = gd._fullLayout,
- gs = fullLayout._size;
- if((typeof opts.fillcolor !== 'function') &&
- (typeof opts.line.color !== 'function')) {
- fullLayout._infolayer.selectAll('g.' + id).remove();
- return;
- }
- var zrange = d3.extent(((typeof opts.fillcolor === 'function') ?
- opts.fillcolor : opts.line.color).domain()),
- linelevels = [],
- filllevels = [],
- l,
- linecolormap = typeof opts.line.color === 'function' ?
- opts.line.color : function() { return opts.line.color; },
- fillcolormap = typeof opts.fillcolor === 'function' ?
- opts.fillcolor : function() { return opts.fillcolor; };
-
- var l0 = opts.levels.end + opts.levels.size / 100,
- ls = opts.levels.size,
- zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]),
- zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]);
- for(l = opts.levels.start; (l - l0) * ls < 0; l += ls) {
- if(l > zr0 && l < zr1) linelevels.push(l);
- }
-
- if(typeof opts.fillcolor === 'function') {
- if(opts.filllevels) {
- l0 = opts.filllevels.end + opts.filllevels.size / 100;
- ls = opts.filllevels.size;
- for(l = opts.filllevels.start; (l - l0) * ls < 0; l += ls) {
- if(l > zrange[0] && l < zrange[1]) filllevels.push(l);
- }
- }
- else {
- filllevels = linelevels.map(function(v) {
- return v - opts.levels.size / 2;
- });
- filllevels.push(filllevels[filllevels.length - 1] +
- opts.levels.size);
- }
- }
- else if(opts.fillcolor && typeof opts.fillcolor === 'string') {
- // doesn't matter what this value is, with a single value
- // we'll make a single fill rect covering the whole bar
- filllevels = [0];
- }
-
- if(opts.levels.size < 0) {
- linelevels.reverse();
- filllevels.reverse();
- }
-
- // now make a Plotly Axes object to scale with and draw ticks
- // TODO: does not support orientation other than right
-
- // we calculate pixel sizes based on the specified graph size,
- // not the actual (in case something pushed the margins around)
- // which is a little odd but avoids an odd iterative effect
- // when the colorbar itself is pushing the margins.
- // but then the fractional size is calculated based on the
- // actual graph size, so that the axes will size correctly.
- var originalPlotHeight = fullLayout.height - fullLayout.margin.t - fullLayout.margin.b,
- originalPlotWidth = fullLayout.width - fullLayout.margin.l - fullLayout.margin.r,
- thickPx = Math.round(opts.thickness *
- (opts.thicknessmode === 'fraction' ? originalPlotWidth : 1)),
- thickFrac = thickPx / gs.w,
- lenPx = Math.round(opts.len *
- (opts.lenmode === 'fraction' ? originalPlotHeight : 1)),
- lenFrac = lenPx / gs.h,
- xpadFrac = opts.xpad / gs.w,
- yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2,
- ypadFrac = opts.ypad / gs.h,
-
- // x positioning: do it initially just for left anchor,
- // then fix at the end (since we don't know the width yet)
- xLeft = Math.round(opts.x * gs.w + opts.xpad),
- // for dragging... this is getting a little muddled...
- xLeftFrac = opts.x - thickFrac *
- ({middle: 0.5, right: 1}[opts.xanchor]||0),
-
- // y positioning we can do correctly from the start
- yBottomFrac = opts.y + lenFrac *
- (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5),
- yBottomPx = Math.round(gs.h * (1 - yBottomFrac)),
- yTopPx = yBottomPx - lenPx,
- titleEl,
- cbAxisIn = {
- type: 'linear',
- range: zrange,
- tickmode: opts.tickmode,
- nticks: opts.nticks,
- tick0: opts.tick0,
- dtick: opts.dtick,
- tickvals: opts.tickvals,
- ticktext: opts.ticktext,
- ticks: opts.ticks,
- ticklen: opts.ticklen,
- tickwidth: opts.tickwidth,
- tickcolor: opts.tickcolor,
- showticklabels: opts.showticklabels,
- tickfont: opts.tickfont,
- tickangle: opts.tickangle,
- tickformat: opts.tickformat,
- exponentformat: opts.exponentformat,
- separatethousands: opts.separatethousands,
- showexponent: opts.showexponent,
- showtickprefix: opts.showtickprefix,
- tickprefix: opts.tickprefix,
- showticksuffix: opts.showticksuffix,
- ticksuffix: opts.ticksuffix,
- title: opts.title,
- titlefont: opts.titlefont,
- anchor: 'free',
- position: 1
- },
- cbAxisOut = {},
- axisOptions = {
- letter: 'y',
- font: fullLayout.font,
- noHover: true,
- calendar: fullLayout.calendar // not really necessary (yet?)
- };
-
- // Coerce w.r.t. Axes layoutAttributes:
- // re-use axes.js logic without updating _fullData
- function coerce(attr, dflt) {
- return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt);
- }
-
- // Prepare the Plotly axis object
- handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions);
- handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions);
-
- cbAxisOut._id = 'y' + id;
- cbAxisOut._gd = gd;
-
- // position can't go in through supplyDefaults
- // because that restricts it to [0,1]
- cbAxisOut.position = opts.x + xpadFrac + thickFrac;
-
- // save for other callers to access this axis
- component.axis = cbAxisOut;
-
- if(['top', 'bottom'].indexOf(opts.titleside) !== -1) {
- cbAxisOut.titleside = opts.titleside;
- cbAxisOut.titlex = opts.x + xpadFrac;
- cbAxisOut.titley = yBottomFrac +
- (opts.titleside === 'top' ? lenFrac - ypadFrac : ypadFrac);
- }
-
- if(opts.line.color && opts.tickmode === 'auto') {
- cbAxisOut.tickmode = 'linear';
- cbAxisOut.tick0 = opts.levels.start;
- var dtick = opts.levels.size;
- // expand if too many contours, so we don't get too many ticks
- var autoNtick = Lib.constrain(
- (yBottomPx - yTopPx) / 50, 4, 15) + 1,
- dtFactor = (zrange[1] - zrange[0]) /
- ((opts.nticks || autoNtick) * dtick);
- if(dtFactor > 1) {
- var dtexp = Math.pow(10, Math.floor(
- Math.log(dtFactor) / Math.LN10));
- dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]);
- // if the contours are at round multiples, reset tick0
- // so they're still at round multiples. Otherwise,
- // keep the first label on the first contour level
- if((Math.abs(opts.levels.start) /
- opts.levels.size + 1e-6) % 1 < 2e-6) {
- cbAxisOut.tick0 = 0;
- }
- }
- cbAxisOut.dtick = dtick;
- }
-
- // set domain after init, because we may want to
- // allow it outside [0,1]
- cbAxisOut.domain = [
- yBottomFrac + ypadFrac,
- yBottomFrac + lenFrac - ypadFrac
- ];
- cbAxisOut.setScale();
-
- // now draw the elements
- var container = fullLayout._infolayer.selectAll('g.' + id).data([0]);
- container.enter().append('g').classed(id, true)
- .each(function() {
- var s = d3.select(this);
- s.append('rect').classed('cbbg', true);
- s.append('g').classed('cbfills', true);
- s.append('g').classed('cblines', true);
- s.append('g').classed('cbaxis', true).classed('crisp', true);
- s.append('g').classed('cbtitleunshift', true)
- .append('g').classed('cbtitle', true);
- s.append('rect').classed('cboutline', true);
- s.select('.cbtitle').datum(0);
- });
- container.attr('transform', 'translate(' + Math.round(gs.l) +
- ',' + Math.round(gs.t) + ')');
- // TODO: this opposite transform is a hack until we make it
- // more rational which items get this offset
- var titleCont = container.select('.cbtitleunshift')
- .attr('transform', 'translate(-' +
- Math.round(gs.l) + ',-' +
- Math.round(gs.t) + ')');
-
- cbAxisOut._axislayer = container.select('.cbaxis');
- var titleHeight = 0;
- if(['top', 'bottom'].indexOf(opts.titleside) !== -1) {
- // draw the title so we know how much room it needs
- // when we squish the axis. This one only applies to
- // top or bottom titles, not right side.
- var x = gs.l + (opts.x + xpadFrac) * gs.w,
- fontSize = cbAxisOut.titlefont.size,
- y;
-
- if(opts.titleside === 'top') {
- y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h +
- gs.t + 3 + fontSize * 0.75;
- }
- else {
- y = (1 - (yBottomFrac + ypadFrac)) * gs.h +
- gs.t - 3 - fontSize * 0.25;
- }
- drawTitle(cbAxisOut._id + 'title', {
- attributes: {x: x, y: y, 'text-anchor': 'start'}
- });
- }
-
- function drawAxis() {
- if(['top', 'bottom'].indexOf(opts.titleside) !== -1) {
- // squish the axis top to make room for the title
- var titleGroup = container.select('.cbtitle'),
- titleText = titleGroup.select('text'),
- titleTrans =
- [-opts.outlinewidth / 2, opts.outlinewidth / 2],
- mathJaxNode = titleGroup
- .select('.h' + cbAxisOut._id + 'title-math-group')
- .node(),
- lineSize = 15.6;
- if(titleText.node()) {
- lineSize =
- parseInt(titleText.style('font-size'), 10) * 1.3;
- }
- if(mathJaxNode) {
- titleHeight = Drawing.bBox(mathJaxNode).height;
- if(titleHeight > lineSize) {
- // not entirely sure how mathjax is doing
- // vertical alignment, but this seems to work.
- titleTrans[1] -= (titleHeight - lineSize) / 2;
- }
- }
- else if(titleText.node() &&
- !titleText.classed('js-placeholder')) {
- titleHeight = Drawing.bBox(
- titleGroup.node()).height;
- }
- if(titleHeight) {
- // buffer btwn colorbar and title
- // TODO: configurable
- titleHeight += 5;
-
- if(opts.titleside === 'top') {
- cbAxisOut.domain[1] -= titleHeight / gs.h;
- titleTrans[1] *= -1;
- }
- else {
- cbAxisOut.domain[0] += titleHeight / gs.h;
- var nlines = Math.max(1,
- titleText.selectAll('tspan.line').size());
- titleTrans[1] += (1 - nlines) * lineSize;
- }
-
- titleGroup.attr('transform',
- 'translate(' + titleTrans + ')');
-
- cbAxisOut.setScale();
- }
- }
-
- container.selectAll('.cbfills,.cblines,.cbaxis')
- .attr('transform', 'translate(0,' +
- Math.round(gs.h * (1 - cbAxisOut.domain[1])) + ')');
-
- var fills = container.select('.cbfills')
- .selectAll('rect.cbfill')
- .data(filllevels);
- fills.enter().append('rect')
- .classed('cbfill', true)
- .style('stroke', 'none');
- fills.exit().remove();
- fills.each(function(d, i) {
- var z = [
- (i === 0) ? zrange[0] :
- (filllevels[i] + filllevels[i - 1]) / 2,
- (i === filllevels.length - 1) ? zrange[1] :
- (filllevels[i] + filllevels[i + 1]) / 2
- ]
- .map(cbAxisOut.c2p)
- .map(Math.round);
-
- // offset the side adjoining the next rectangle so they
- // overlap, to prevent antialiasing gaps
- if(i !== filllevels.length - 1) {
- z[1] += (z[1] > z[0]) ? 1 : -1;
- }
-
-
- // Tinycolor can't handle exponents and
- // at this scale, removing it makes no difference.
- var colorString = fillcolormap(d).replace('e-', ''),
- opaqueColor = tinycolor(colorString).toHexString();
-
- // Colorbar cannot currently support opacities so we
- // use an opaque fill even when alpha channels present
- d3.select(this).attr({
- x: xLeft,
- width: Math.max(thickPx, 2),
- y: d3.min(z),
- height: Math.max(d3.max(z) - d3.min(z), 2),
- fill: opaqueColor
- });
- });
-
- var lines = container.select('.cblines')
- .selectAll('path.cbline')
- .data(opts.line.color && opts.line.width ?
- linelevels : []);
- lines.enter().append('path')
- .classed('cbline', true);
- lines.exit().remove();
- lines.each(function(d) {
- d3.select(this)
- .attr('d', 'M' + xLeft + ',' +
- (Math.round(cbAxisOut.c2p(d)) + (opts.line.width / 2) % 1) +
- 'h' + thickPx)
- .call(Drawing.lineGroupStyle,
- opts.line.width, linecolormap(d), opts.line.dash);
- });
-
- // force full redraw of labels and ticks
- cbAxisOut._axislayer.selectAll('g.' + cbAxisOut._id + 'tick,path')
- .remove();
-
- cbAxisOut._pos = xLeft + thickPx +
- (opts.outlinewidth||0) / 2 - (opts.ticks === 'outside' ? 1 : 0);
- cbAxisOut.side = 'right';
-
- // separate out axis and title drawing,
- // so we don't need such complicated logic in Titles.draw
- // if title is on the top or bottom, we've already drawn it
- // this title call only handles side=right
- return Lib.syncOrAsync([
- function() {
- return Axes.doTicks(gd, cbAxisOut, true);
- },
- function() {
- if(['top', 'bottom'].indexOf(opts.titleside) === -1) {
- var fontSize = cbAxisOut.titlefont.size,
- y = cbAxisOut._offset + cbAxisOut._length / 2,
- x = gs.l + (cbAxisOut.position || 0) * gs.w + ((cbAxisOut.side === 'right') ?
- 10 + fontSize * ((cbAxisOut.showticklabels ? 1 : 0.5)) :
- -10 - fontSize * ((cbAxisOut.showticklabels ? 0.5 : 0)));
-
- // the 'h' + is a hack to get around the fact that
- // convertToTspans rotates any 'y...' class by 90 degrees.
- // TODO: find a better way to control this.
- drawTitle('h' + cbAxisOut._id + 'title', {
- avoid: {
- selection: d3.select(gd).selectAll('g.' + cbAxisOut._id + 'tick'),
- side: opts.titleside,
- offsetLeft: gs.l,
- offsetTop: gs.t,
- maxShift: fullLayout.width
- },
- attributes: {x: x, y: y, 'text-anchor': 'middle'},
- transform: {rotate: '-90', offset: 0}
- });
- }
- }]);
- }
-
- function drawTitle(titleClass, titleOpts) {
- var trace = getTrace(),
- propName;
- if(Registry.traceIs(trace, 'markerColorscale')) {
- propName = 'marker.colorbar.title';
- }
- else propName = 'colorbar.title';
-
- var dfltTitleOpts = {
- propContainer: cbAxisOut,
- propName: propName,
- traceIndex: trace.index,
- dfltName: 'colorscale',
- containerGroup: container.select('.cbtitle')
- };
-
- // this class-to-rotate thing with convertToTspans is
- // getting hackier and hackier... delete groups with the
- // wrong class (in case earlier the colorbar was drawn on
- // a different side, I think?)
- var otherClass = titleClass.charAt(0) === 'h' ?
- titleClass.substr(1) : ('h' + titleClass);
- container.selectAll('.' + otherClass + ',.' + otherClass + '-math-group')
- .remove();
-
- Titles.draw(gd, titleClass,
- extendFlat(dfltTitleOpts, titleOpts || {}));
- }
-
- function positionCB() {
- // wait for the axis & title to finish rendering before
- // continuing positioning
- // TODO: why are we redrawing multiple times now with this?
- // I guess autoMargin doesn't like being post-promise?
- var innerWidth = thickPx + opts.outlinewidth / 2 +
- Drawing.bBox(cbAxisOut._axislayer.node()).width;
- titleEl = titleCont.select('text');
- if(titleEl.node() && !titleEl.classed('js-placeholder')) {
- var mathJaxNode = titleCont
- .select('.h' + cbAxisOut._id + 'title-math-group')
- .node(),
- titleWidth;
- if(mathJaxNode &&
- ['top', 'bottom'].indexOf(opts.titleside) !== -1) {
- titleWidth = Drawing.bBox(mathJaxNode).width;
- }
- else {
- // note: the formula below works for all titlesides,
- // (except for top/bottom mathjax, above)
- // but the weird gs.l is because the titleunshift
- // transform gets removed by Drawing.bBox
- titleWidth =
- Drawing.bBox(titleCont.node()).right -
- xLeft - gs.l;
- }
- innerWidth = Math.max(innerWidth, titleWidth);
- }
-
- var outerwidth = 2 * opts.xpad + innerWidth +
- opts.borderwidth + opts.outlinewidth / 2,
- outerheight = yBottomPx - yTopPx;
-
- container.select('.cbbg').attr({
- x: xLeft - opts.xpad -
- (opts.borderwidth + opts.outlinewidth) / 2,
- y: yTopPx - yExtraPx,
- width: Math.max(outerwidth, 2),
- height: Math.max(outerheight + 2 * yExtraPx, 2)
- })
- .call(Color.fill, opts.bgcolor)
- .call(Color.stroke, opts.bordercolor)
- .style({'stroke-width': opts.borderwidth});
-
- container.selectAll('.cboutline').attr({
- x: xLeft,
- y: yTopPx + opts.ypad +
- (opts.titleside === 'top' ? titleHeight : 0),
- width: Math.max(thickPx, 2),
- height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2)
- })
- .call(Color.stroke, opts.outlinecolor)
- .style({
- fill: 'None',
- 'stroke-width': opts.outlinewidth
- });
-
- // fix positioning for xanchor!='left'
- var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) *
- outerwidth;
- container.attr('transform',
- 'translate(' + (gs.l - xoffset) + ',' + gs.t + ')');
-
- // auto margin adjustment
- Plots.autoMargin(gd, id, {
- x: opts.x,
- y: opts.y,
- l: outerwidth * ({right: 1, center: 0.5}[opts.xanchor] || 0),
- r: outerwidth * ({left: 1, center: 0.5}[opts.xanchor] || 0),
- t: outerheight * ({bottom: 1, middle: 0.5}[opts.yanchor] || 0),
- b: outerheight * ({top: 1, middle: 0.5}[opts.yanchor] || 0)
- });
- }
-
- var cbDone = Lib.syncOrAsync([
- Plots.previousPromises,
- drawAxis,
- Plots.previousPromises,
- positionCB
- ], gd);
-
- if(cbDone && cbDone.then) (gd._promises || []).push(cbDone);
-
- // dragging...
- if(gd._context.editable) {
- var t0,
- xf,
- yf;
-
- dragElement.init({
- element: container.node(),
- prepFn: function() {
- t0 = container.attr('transform');
- setCursor(container);
- },
- moveFn: function(dx, dy) {
- container.attr('transform',
- t0 + ' ' + 'translate(' + dx + ',' + dy + ')');
-
- xf = dragElement.align(xLeftFrac + (dx / gs.w), thickFrac,
- 0, 1, opts.xanchor);
- yf = dragElement.align(yBottomFrac - (dy / gs.h), lenFrac,
- 0, 1, opts.yanchor);
-
- var csr = dragElement.getCursor(xf, yf,
- opts.xanchor, opts.yanchor);
- setCursor(container, csr);
- },
- doneFn: function(dragged) {
- setCursor(container);
-
- if(dragged && xf !== undefined && yf !== undefined) {
- Plotly.restyle(gd,
- {'colorbar.x': xf, 'colorbar.y': yf},
- getTrace().index);
- }
- }
- });
- }
- return cbDone;
- }
-
- function getTrace() {
- var idNum = id.substr(2),
- i,
- trace;
- for(i = 0; i < gd._fullData.length; i++) {
- trace = gd._fullData[i];
- if(trace.uid === idNum) return trace;
- }
- }
-
- // setter/getters for every item defined in opts
- Object.keys(opts).forEach(function(name) {
- component[name] = function(v) {
- // getter
- if(!arguments.length) return opts[name];
-
- // setter - for multi-part properties,
- // set only the parts that are provided
- opts[name] = Lib.isPlainObject(opts[name]) ?
- Lib.extendFlat(opts[name], v) :
- v;
-
- return component;
- };
- });
-
- // or use .options to set multiple options at once via a dictionary
- component.options = function(o) {
- Object.keys(o).forEach(function(name) {
- // in case something random comes through
- // that's not an option, ignore it
- if(typeof component[name] === 'function') {
- component[name](o[name]);
- }
- });
- return component;
- };
-
- component._opts = opts;
-
- return component;
-};
diff --git a/src/components/colorbar/has_colorbar.js b/src/components/colorbar/has_colorbar.js
index fb32bc8b6cc..8b137891791 100644
--- a/src/components/colorbar/has_colorbar.js
+++ b/src/components/colorbar/has_colorbar.js
@@ -1,17 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-
-
-module.exports = function hasColorbar(container) {
- return Lib.isPlainObject(container.colorbar);
-};
diff --git a/src/components/colorbar/index.js b/src/components/colorbar/index.js
index c0960b78f7c..8b137891791 100644
--- a/src/components/colorbar/index.js
+++ b/src/components/colorbar/index.js
@@ -1,19 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-
-exports.attributes = require('./attributes');
-
-exports.supplyDefaults = require('./defaults');
-
-exports.draw = require('./draw');
-
-exports.hasColorbar = require('./has_colorbar');
diff --git a/src/components/colorscale/attributes.js b/src/components/colorscale/attributes.js
index bbbfc60e9ff..8b137891791 100644
--- a/src/components/colorscale/attributes.js
+++ b/src/components/colorscale/attributes.js
@@ -1,71 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-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.',
- 'The colorscale must be an array containing',
- 'arrays mapping a normalized value to an',
- 'rgb, rgba, hex, hsl, hsv, or named color string.',
- 'At minimum, a mapping for the lowest (0) and highest (1)',
- 'values are required. For example,',
- '`[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.',
- 'To control the bounds of the colorscale in z space,',
- 'use zmin and zmax'
- ].join(' ')
- },
- 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(' ')
- }
-};
diff --git a/src/components/colorscale/calc.js b/src/components/colorscale/calc.js
index 8d095e8642d..8b137891791 100644
--- a/src/components/colorscale/calc.js
+++ b/src/components/colorscale/calc.js
@@ -1,77 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-
-var scales = require('./scales');
-var flipScale = require('./flip_scale');
-
-
-module.exports = function calc(trace, vals, containerStr, cLetter) {
- var container, inputContainer;
-
- if(containerStr) {
- container = Lib.nestedProperty(trace, containerStr).get();
- inputContainer = Lib.nestedProperty(trace._input, containerStr).get();
- }
- else {
- container = trace;
- inputContainer = trace._input;
- }
-
- var autoAttr = cLetter + 'auto',
- minAttr = cLetter + 'min',
- maxAttr = cLetter + 'max',
- auto = container[autoAttr],
- min = container[minAttr],
- max = container[maxAttr],
- scl = container.colorscale;
-
- if(auto !== false || min === undefined) {
- min = Lib.aggNums(Math.min, null, vals);
- }
-
- if(auto !== false || max === undefined) {
- max = Lib.aggNums(Math.max, null, vals);
- }
-
- if(min === max) {
- min -= 0.5;
- max += 0.5;
- }
-
- container[minAttr] = min;
- container[maxAttr] = max;
-
- inputContainer[minAttr] = min;
- inputContainer[maxAttr] = max;
-
- /*
- * If auto was explicitly false but min or max was missing,
- * we filled in the missing piece here but later the trace does
- * not look auto.
- * Otherwise make sure the trace still looks auto as far as later
- * changes are concerned.
- */
- inputContainer[autoAttr] = (auto !== false ||
- (min === undefined && max === undefined));
-
- if(container.autocolorscale) {
- if(min * max < 0) scl = scales.RdBu;
- else if(min >= 0) scl = scales.Reds;
- else scl = scales.Blues;
-
- // reversescale is handled at the containerOut level
- inputContainer.colorscale = scl;
- if(container.reversescale) scl = flipScale(scl);
- container.colorscale = scl;
- }
-};
diff --git a/src/components/colorscale/color_attributes.js b/src/components/colorscale/color_attributes.js
index 9c8f6cdb065..8b137891791 100644
--- a/src/components/colorscale/color_attributes.js
+++ b/src/components/colorscale/color_attributes.js
@@ -1,88 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var colorScaleAttributes = require('./attributes');
-var extendDeep = require('../../lib/extend').extendDeep;
-var palettes = require('./scales.js');
-
-module.exports = function makeColorScaleAttributes(context) {
- return {
- color: {
- valType: 'color',
- arrayOk: true,
- role: 'style',
- description: [
- 'Sets the ', context, ' color. It accepts either a specific color',
- ' or an array of numbers that are mapped to the colorscale',
- ' relative to the max and min values of the array or relative to',
- ' `cmin` and `cmax` if set.'
- ].join('')
- },
- colorscale: extendDeep({}, colorScaleAttributes.colorscale, {
- description: [
- 'Sets the colorscale and only has an effect',
- ' if `', context, '.color` is set to a numerical array.',
- ' The colorscale must be an array containing',
- ' arrays mapping a normalized value to an',
- ' rgb, rgba, hex, hsl, hsv, or named color string.',
- ' At minimum, a mapping for the lowest (0) and highest (1)',
- ' values are required. For example,',
- ' `[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.',
- ' To control the bounds of the colorscale in color space,',
- ' use `', context, '.cmin` and `', context, '.cmax`.',
- ' Alternatively, `colorscale` may be a palette name string',
- ' of the following list: '
- ].join('').concat(Object.keys(palettes).join(', '))
- }),
- cauto: extendDeep({}, colorScaleAttributes.zauto, {
- description: [
- 'Has an effect only if `', context, '.color` is set to a numerical array',
- ' and `cmin`, `cmax` are set by the user. In this case,',
- ' it controls whether the range of colors in `colorscale` is mapped to',
- ' the range of values in the `color` array (`cauto: true`), or the `cmin`/`cmax`',
- ' values (`cauto: false`).',
- ' Defaults to `false` when `cmin`, `cmax` are set by the user.'
- ].join('')
- }),
- cmax: extendDeep({}, colorScaleAttributes.zmax, {
- description: [
- 'Has an effect only if `', context, '.color` is set to a numerical array.',
- ' Sets the upper bound of the color domain.',
- ' Value should be associated to the `', context, '.color` array index,',
- ' and if set, `', context, '.cmin` must be set as well.'
- ].join('')
- }),
- cmin: extendDeep({}, colorScaleAttributes.zmin, {
- description: [
- 'Has an effect only if `', context, '.color` is set to a numerical array.',
- ' Sets the lower bound of the color domain.',
- ' Value should be associated to the `', context, '.color` array index,',
- ' and if set, `', context, '.cmax` must be set as well.'
- ].join('')
- }),
- autocolorscale: extendDeep({}, colorScaleAttributes.autocolorscale, {
- description: [
- 'Has an effect only if `', context, '.color` is set to a numerical array.',
- ' Determines whether the colorscale is a default palette (`autocolorscale: true`)',
- ' or the palette determined by `', context, '.colorscale`.',
- ' In case `colorscale` is unspecified or `autocolorscale` is true, the default ',
- ' palette will be chosen according to whether numbers in the `color` array are',
- ' all positive, all negative or mixed.'
- ].join('')
- }),
- reversescale: extendDeep({}, colorScaleAttributes.reversescale, {
- description: [
- 'Has an effect only if `', context, '.color` is set to a numerical array.',
- ' Reverses the color mapping if true (`cmin` will correspond to the last color',
- ' in the array and `cmax` will correspond to the first color).'
- ].join('')
- })
- };
-};
diff --git a/src/components/colorscale/default_scale.js b/src/components/colorscale/default_scale.js
index 286663dac37..8b137891791 100644
--- a/src/components/colorscale/default_scale.js
+++ b/src/components/colorscale/default_scale.js
@@ -1,14 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-var scales = require('./scales');
-
-
-module.exports = scales.RdBu;
diff --git a/src/components/colorscale/defaults.js b/src/components/colorscale/defaults.js
index 55444bb4094..8b137891791 100644
--- a/src/components/colorscale/defaults.js
+++ b/src/components/colorscale/defaults.js
@@ -1,62 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-
-var hasColorbar = require('../colorbar/has_colorbar');
-var colorbarDefaults = require('../colorbar/defaults');
-var isValidScale = require('./is_valid_scale');
-var flipScale = require('./flip_scale');
-
-
-module.exports = function colorScaleDefaults(traceIn, traceOut, layout, coerce, opts) {
- var prefix = opts.prefix,
- cLetter = opts.cLetter,
- containerStr = prefix.slice(0, prefix.length - 1),
- containerIn = prefix ?
- Lib.nestedProperty(traceIn, containerStr).get() || {} :
- traceIn,
- containerOut = prefix ?
- Lib.nestedProperty(traceOut, containerStr).get() || {} :
- traceOut,
- minIn = containerIn[cLetter + 'min'],
- maxIn = containerIn[cLetter + 'max'],
- sclIn = containerIn.colorscale;
-
- var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn);
- coerce(prefix + cLetter + 'auto', !validMinMax);
- coerce(prefix + cLetter + 'min');
- coerce(prefix + cLetter + 'max');
-
- // handles both the trace case (autocolorscale is false by default) and
- // the marker and marker.line case (autocolorscale is true by default)
- var autoColorscaleDftl;
- if(sclIn !== undefined) autoColorscaleDftl = !isValidScale(sclIn);
- coerce(prefix + 'autocolorscale', autoColorscaleDftl);
- var sclOut = coerce(prefix + 'colorscale');
-
- // reversescale is handled at the containerOut level
- var reverseScale = coerce(prefix + 'reversescale');
- if(reverseScale) containerOut.colorscale = flipScale(sclOut);
-
- // ... until Scatter.colorbar can handle marker line colorbars
- if(prefix === 'marker.line.') return;
-
- // handle both the trace case where the dflt is listed in attributes and
- // the marker case where the dflt is determined by hasColorbar
- var showScaleDftl;
- if(prefix) showScaleDftl = hasColorbar(containerIn);
- var showScale = coerce(prefix + 'showscale', showScaleDftl);
-
- if(showScale) colorbarDefaults(containerIn, containerOut, layout);
-};
diff --git a/src/components/colorscale/extract_scale.js b/src/components/colorscale/extract_scale.js
index d1e3c83d4ff..8b137891791 100644
--- a/src/components/colorscale/extract_scale.js
+++ b/src/components/colorscale/extract_scale.js
@@ -1,35 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-/**
- * Extract colorscale into numeric domain and color range.
- *
- * @param {array} scl colorscale array of arrays
- * @param {number} cmin minimum color value (used to clamp scale)
- * @param {number} cmax maximum color value (used to clamp scale)
- */
-module.exports = function extractScale(scl, cmin, cmax) {
- var N = scl.length,
- domain = new Array(N),
- range = new Array(N);
-
- for(var i = 0; i < N; i++) {
- var si = scl[i];
-
- domain[i] = cmin + si[0] * (cmax - cmin);
- range[i] = si[1];
- }
-
- return {
- domain: domain,
- range: range
- };
-};
diff --git a/src/components/colorscale/flip_scale.js b/src/components/colorscale/flip_scale.js
index 5e974846ea6..8b137891791 100644
--- a/src/components/colorscale/flip_scale.js
+++ b/src/components/colorscale/flip_scale.js
@@ -1,23 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-module.exports = function flipScale(scl) {
- var N = scl.length,
- sclNew = new Array(N),
- si;
-
- for(var i = N - 1, j = 0; i >= 0; i--, j++) {
- si = scl[i];
- sclNew[j] = [1 - si[0], si[1]];
- }
-
- return sclNew;
-};
diff --git a/src/components/colorscale/get_scale.js b/src/components/colorscale/get_scale.js
index 1f88c328a42..8b137891791 100644
--- a/src/components/colorscale/get_scale.js
+++ b/src/components/colorscale/get_scale.js
@@ -1,38 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var scales = require('./scales');
-var defaultScale = require('./default_scale');
-var isValidScaleArray = require('./is_valid_scale_array');
-
-
-module.exports = function getScale(scl, dflt) {
- if(!dflt) dflt = defaultScale;
- if(!scl) return dflt;
-
- function parseScale() {
- try {
- scl = scales[scl] || JSON.parse(scl);
- }
- catch(e) {
- scl = dflt;
- }
- }
-
- if(typeof scl === 'string') {
- parseScale();
- // occasionally scl is double-JSON encoded...
- if(typeof scl === 'string') parseScale();
- }
-
- if(!isValidScaleArray(scl)) return dflt;
- return scl;
-};
diff --git a/src/components/colorscale/has_colorscale.js b/src/components/colorscale/has_colorscale.js
index 2744e956442..8b137891791 100644
--- a/src/components/colorscale/has_colorscale.js
+++ b/src/components/colorscale/has_colorscale.js
@@ -1,44 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-
-var isValidScale = require('./is_valid_scale');
-
-
-module.exports = function hasColorscale(trace, containerStr) {
- var container = containerStr ?
- Lib.nestedProperty(trace, containerStr).get() || {} :
- trace,
- color = container.color,
- isArrayWithOneNumber = false;
-
- if(Array.isArray(color)) {
- for(var i = 0; i < color.length; i++) {
- if(isNumeric(color[i])) {
- isArrayWithOneNumber = true;
- break;
- }
- }
- }
-
- return (
- Lib.isPlainObject(container) && (
- isArrayWithOneNumber ||
- container.showscale === true ||
- (isNumeric(container.cmin) && isNumeric(container.cmax)) ||
- isValidScale(container.colorscale) ||
- Lib.isPlainObject(container.colorbar)
- )
- );
-};
diff --git a/src/components/colorscale/index.js b/src/components/colorscale/index.js
index 0e07e23c32c..8b137891791 100644
--- a/src/components/colorscale/index.js
+++ b/src/components/colorscale/index.js
@@ -1,32 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-exports.scales = require('./scales');
-
-exports.defaultScale = require('./default_scale');
-
-exports.attributes = require('./attributes');
-
-exports.handleDefaults = require('./defaults');
-
-exports.calc = require('./calc');
-
-exports.hasColorscale = require('./has_colorscale');
-
-exports.isValidScale = require('./is_valid_scale');
-
-exports.getScale = require('./get_scale');
-
-exports.flipScale = require('./flip_scale');
-
-exports.extractScale = require('./extract_scale');
-
-exports.makeColorScaleFunc = require('./make_color_scale_func');
diff --git a/src/components/colorscale/is_valid_scale.js b/src/components/colorscale/is_valid_scale.js
index f3137486694..8b137891791 100644
--- a/src/components/colorscale/is_valid_scale.js
+++ b/src/components/colorscale/is_valid_scale.js
@@ -1,19 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var scales = require('./scales');
-var isValidScaleArray = require('./is_valid_scale_array');
-
-
-module.exports = function isValidScale(scl) {
- if(scales[scl] !== undefined) return true;
- else return isValidScaleArray(scl);
-};
diff --git a/src/components/colorscale/is_valid_scale_array.js b/src/components/colorscale/is_valid_scale_array.js
index 324b576b50f..8b137891791 100644
--- a/src/components/colorscale/is_valid_scale_array.js
+++ b/src/components/colorscale/is_valid_scale_array.js
@@ -1,35 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var tinycolor = require('tinycolor2');
-
-
-module.exports = function isValidScaleArray(scl) {
- var highestVal = 0;
-
- if(!Array.isArray(scl) || scl.length < 2) return false;
-
- if(!scl[0] || !scl[scl.length - 1]) return false;
-
- if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false;
-
- for(var i = 0; i < scl.length; i++) {
- var si = scl[i];
-
- if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) {
- return false;
- }
-
- highestVal = +si[0];
- }
-
- return true;
-};
diff --git a/src/components/colorscale/make_color_scale_func.js b/src/components/colorscale/make_color_scale_func.js
index 562e104b00a..8b137891791 100644
--- a/src/components/colorscale/make_color_scale_func.js
+++ b/src/components/colorscale/make_color_scale_func.js
@@ -1,94 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var d3 = require('d3');
-var tinycolor = require('tinycolor2');
-var isNumeric = require('fast-isnumeric');
-
-var Color = require('../color');
-
-/**
- * General colorscale function generator.
- *
- * @param {object} specs output of Colorscale.extractScale or precomputed domain, range.
- * - domain {array}
- * - range {array}
- *
- * @param {object} opts
- * - noNumericCheck {boolean} if true, scale func bypasses numeric checks
- * - returnArray {boolean} if true, scale func return 4-item array instead of color strings
- *
- * @return {function}
- */
-module.exports = function makeColorScaleFunc(specs, opts) {
- opts = opts || {};
-
- var domain = specs.domain,
- range = specs.range,
- N = range.length,
- _range = new Array(N);
-
- for(var i = 0; i < N; i++) {
- var rgba = tinycolor(range[i]).toRgb();
- _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a];
- }
-
- var _sclFunc = d3.scale.linear()
- .domain(domain)
- .range(_range)
- .clamp(true);
-
- var noNumericCheck = opts.noNumericCheck,
- returnArray = opts.returnArray,
- sclFunc;
-
- if(noNumericCheck && returnArray) {
- sclFunc = _sclFunc;
- }
- else if(noNumericCheck) {
- sclFunc = function(v) {
- return colorArray2rbga(_sclFunc(v));
- };
- }
- else if(returnArray) {
- sclFunc = function(v) {
- if(isNumeric(v)) return _sclFunc(v);
- else if(tinycolor(v).isValid()) return v;
- else return Color.defaultLine;
- };
- }
- else {
- sclFunc = function(v) {
- if(isNumeric(v)) return colorArray2rbga(_sclFunc(v));
- else if(tinycolor(v).isValid()) return v;
- else return Color.defaultLine;
- };
- }
-
- // colorbar draw looks into the d3 scale closure for domain and range
-
- sclFunc.domain = _sclFunc.domain;
-
- sclFunc.range = function() { return range; };
-
- return sclFunc;
-};
-
-function colorArray2rbga(colorArray) {
- var colorObj = {
- r: colorArray[0],
- g: colorArray[1],
- b: colorArray[2],
- a: colorArray[3]
- };
-
- return tinycolor(colorObj).toRgbString();
-}
diff --git a/src/components/colorscale/scales.js b/src/components/colorscale/scales.js
index 1993b937264..8b137891791 100644
--- a/src/components/colorscale/scales.js
+++ b/src/components/colorscale/scales.js
@@ -1,129 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-'use strict';
-
-
-module.exports = {
- 'Greys': [
- [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)']
- ],
-
- 'YlGnBu': [
- [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)']
- ],
-
- 'YlOrRd': [
- [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/components/dragelement/align.js b/src/components/dragelement/align.js
index 9503473ed6c..8b137891791 100644
--- a/src/components/dragelement/align.js
+++ b/src/components/dragelement/align.js
@@ -1,31 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-
-// for automatic alignment on dragging, <1/3 means left align,
-// >2/3 means right, and between is center. Pick the right fraction
-// based on where you are, and return the fraction corresponding to
-// that position on the object
-module.exports = function align(v, dv, v0, v1, anchor) {
- var vmin = (v - v0) / (v1 - v0),
- vmax = vmin + dv / (v1 - v0),
- vc = (vmin + vmax) / 2;
-
- // explicitly specified anchor
- if(anchor === 'left' || anchor === 'bottom') return vmin;
- if(anchor === 'center' || anchor === 'middle') return vc;
- if(anchor === 'right' || anchor === 'top') return vmax;
-
- // automatic based on position
- if(vmin < (2 / 3) - vc) return vmin;
- if(vmax > (4 / 3) - vc) return vmax;
- return vc;
-};
diff --git a/src/components/dragelement/cursor.js b/src/components/dragelement/cursor.js
index 5601c8aaa30..8b137891791 100644
--- a/src/components/dragelement/cursor.js
+++ b/src/components/dragelement/cursor.js
@@ -1,36 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Lib = require('../../lib');
-
-
-// set cursors pointing toward the closest corner/side,
-// to indicate alignment
-// x and y are 0-1, fractions of the plot area
-var cursorset = [
- ['sw-resize', 's-resize', 'se-resize'],
- ['w-resize', 'move', 'e-resize'],
- ['nw-resize', 'n-resize', 'ne-resize']
-];
-
-module.exports = function getCursor(x, y, xanchor, yanchor) {
- if(xanchor === 'left') x = 0;
- else if(xanchor === 'center') x = 1;
- else if(xanchor === 'right') x = 2;
- else x = Lib.constrain(Math.floor(x * 3), 0, 2);
-
- if(yanchor === 'bottom') y = 0;
- else if(yanchor === 'middle') y = 1;
- else if(yanchor === 'top') y = 2;
- else y = Lib.constrain(Math.floor(y * 3), 0, 2);
-
- return cursorset[y][x];
-};
diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js
index 8b531a1b5a8..8b137891791 100644
--- a/src/components/dragelement/index.js
+++ b/src/components/dragelement/index.js
@@ -1,185 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var Plotly = require('../../plotly');
-var Lib = require('../../lib');
-
-var constants = require('../../plots/cartesian/constants');
-
-
-var dragElement = module.exports = {};
-
-dragElement.align = require('./align');
-dragElement.getCursor = require('./cursor');
-
-var unhover = require('./unhover');
-dragElement.unhover = unhover.wrapped;
-dragElement.unhoverRaw = unhover.raw;
-
-/**
- * Abstracts click & drag interactions
- * @param {object} options with keys:
- * element (required) the DOM element to drag
- * prepFn (optional) function(event, startX, startY)
- * executed on mousedown
- * startX and startY are the clientX and clientY pixel position
- * of the mousedown event
- * moveFn (optional) function(dx, dy, dragged)
- * executed on move
- * dx and dy are the net pixel offset of the drag,
- * dragged is true/false, has the mouse moved enough to
- * constitute a drag
- * doneFn (optional) function(dragged, numClicks)
- * executed on mouseup, or mouseout of window since
- * we don't get events after that
- * dragged is as in moveFn
- * numClicks is how many clicks we've registered within
- * a doubleclick time
- * setCursor (optional) function(event)
- * executed on mousemove before mousedown
- * the purpose of this callback is to update the mouse cursor before
- * the click & drag interaction has been initiated
- */
-dragElement.init = function init(options) {
- var gd = Lib.getPlotDiv(options.element) || {},
- numClicks = 1,
- DBLCLICKDELAY = constants.DBLCLICKDELAY,
- startX,
- startY,
- newMouseDownTime,
- dragCover,
- initialTarget,
- initialOnMouseMove;
-
- if(!gd._mouseDownTime) gd._mouseDownTime = 0;
-
- function onStart(e) {
- // disable call to options.setCursor(evt)
- options.element.onmousemove = initialOnMouseMove;
-
- // make dragging and dragged into properties of gd
- // so that others can look at and modify them
- gd._dragged = false;
- gd._dragging = true;
- startX = e.clientX;
- startY = e.clientY;
- initialTarget = e.target;
-
- newMouseDownTime = (new Date()).getTime();
- if(newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) {
- // in a click train
- numClicks += 1;
- }
- else {
- // new click train
- numClicks = 1;
- gd._mouseDownTime = newMouseDownTime;
- }
-
- if(options.prepFn) options.prepFn(e, startX, startY);
-
- dragCover = coverSlip();
-
- dragCover.onmousemove = onMove;
- dragCover.onmouseup = onDone;
- dragCover.onmouseout = onDone;
-
- dragCover.style.cursor = window.getComputedStyle(options.element).cursor;
-
- return Lib.pauseEvent(e);
- }
-
- function onMove(e) {
- var dx = e.clientX - startX,
- dy = e.clientY - startY,
- minDrag = options.minDrag || constants.MINDRAG;
-
- if(Math.abs(dx) < minDrag) dx = 0;
- if(Math.abs(dy) < minDrag) dy = 0;
- if(dx || dy) {
- gd._dragged = true;
- dragElement.unhover(gd);
- }
-
- if(options.moveFn) options.moveFn(dx, dy, gd._dragged);
-
- return Lib.pauseEvent(e);
- }
-
- function onDone(e) {
- // re-enable call to options.setCursor(evt)
- initialOnMouseMove = options.element.onmousemove;
- if(options.setCursor) options.element.onmousemove = options.setCursor;
-
- dragCover.onmousemove = null;
- dragCover.onmouseup = null;
- dragCover.onmouseout = null;
- Lib.removeElement(dragCover);
-
- if(!gd._dragging) {
- gd._dragged = false;
- return;
- }
- gd._dragging = false;
-
- // don't count as a dblClick unless the mouseUp is also within
- // the dblclick delay
- if((new Date()).getTime() - gd._mouseDownTime > DBLCLICKDELAY) {
- numClicks = Math.max(numClicks - 1, 1);
- }
-
- if(options.doneFn) options.doneFn(gd._dragged, numClicks);
-
- if(!gd._dragged) {
- var e2 = document.createEvent('MouseEvents');
- e2.initEvent('click', true, true);
- initialTarget.dispatchEvent(e2);
- }
-
- finishDrag(gd);
-
- gd._dragged = false;
-
- return Lib.pauseEvent(e);
- }
-
- // enable call to options.setCursor(evt)
- initialOnMouseMove = options.element.onmousemove;
- if(options.setCursor) options.element.onmousemove = options.setCursor;
-
- options.element.onmousedown = onStart;
- options.element.style.pointerEvents = 'all';
-};
-
-function coverSlip() {
- var cover = document.createElement('div');
-
- cover.className = 'dragcover';
- var cStyle = cover.style;
- cStyle.position = 'fixed';
- cStyle.left = 0;
- cStyle.right = 0;
- cStyle.top = 0;
- cStyle.bottom = 0;
- cStyle.zIndex = 999999999;
- cStyle.background = 'none';
-
- document.body.appendChild(cover);
-
- return cover;
-}
-
-dragElement.coverSlip = coverSlip;
-
-function finishDrag(gd) {
- gd._dragging = false;
- if(gd._replotPending) Plotly.plot(gd);
-}
diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js
index 61b602ac6e6..8b137891791 100644
--- a/src/components/dragelement/unhover.js
+++ b/src/components/dragelement/unhover.js
@@ -1,49 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-
-var Events = require('../../lib/events');
-
-
-var unhover = module.exports = {};
-
-
-unhover.wrapped = function(gd, evt, subplot) {
- if(typeof gd === 'string') gd = document.getElementById(gd);
-
- // Important, clear any queued hovers
- if(gd._hoverTimer) {
- clearTimeout(gd._hoverTimer);
- gd._hoverTimer = undefined;
- }
-
- unhover.raw(gd, evt, subplot);
-};
-
-
-// remove hover effects on mouse out, and emit unhover event
-unhover.raw = function unhoverRaw(gd, evt) {
- var fullLayout = gd._fullLayout;
-
- if(!evt) evt = {};
- if(evt.target &&
- Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) {
- return;
- }
-
- fullLayout._hoverlayer.selectAll('g').remove();
-
- if(evt.target && gd._hoverdata) {
- gd.emit('plotly_unhover', {points: gd._hoverdata});
- }
-
- gd._hoverdata = undefined;
-};
diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js
index fa995b0641e..8b137891791 100644
--- a/src/components/drawing/index.js
+++ b/src/components/drawing/index.js
@@ -1,680 +1 @@
-/**
-* Copyright 2012-2017, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Registry = require('../../registry');
-var Color = require('../color');
-var Colorscale = require('../colorscale');
-var Lib = require('../../lib');
-var svgTextUtils = require('../../lib/svg_text_utils');
-
-var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
-var subTypes = require('../../traces/scatter/subtypes');
-var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func');
-
-var drawing = module.exports = {};
-
-// -----------------------------------------------------
-// styling functions for plot elements
-// -----------------------------------------------------
-
-drawing.font = function(s, family, size, color) {
- // also allow the form font(s, {family, size, color})
- if(family && family.family) {
- color = family.color;
- size = family.size;
- family = family.family;
- }
- if(family) s.style('font-family', family);
- if(size + 1) s.style('font-size', size + 'px');
- if(color) s.call(Color.fill, color);
-};
-
-drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); };
-drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); };
-drawing.setRect = function(s, x, y, w, h) {
- s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h);
-};
-
-drawing.translatePoint = function(d, sel, xa, ya) {
- // put xp and yp into d if pixel scaling is already done
- var x = d.xp || xa.c2p(d.x),
- y = d.yp || ya.c2p(d.y);
-
- if(isNumeric(x) && isNumeric(y) && sel.node()) {
- // for multiline text this works better
- if(sel.node().nodeName === 'text') {
- sel.attr('x', x).attr('y', y);
- } else {
- sel.attr('transform', 'translate(' + x + ',' + y + ')');
- }
- }
- else sel.remove();
-};
-
-drawing.translatePoints = function(s, xa, ya, trace) {
- s.each(function(d) {
- var sel = d3.select(this);
- drawing.translatePoint(d, sel, xa, ya, trace);
- });
-};
-
-drawing.getPx = function(s, styleAttr) {
- // helper to pull out a px value from a style that may contain px units
- // s is a d3 selection (will pull from the first one)
- return Number(s.style(styleAttr).replace(/px$/, ''));
-};
-
-drawing.crispRound = function(gd, lineWidth, dflt) {
- // for lines that disable antialiasing we want to
- // make sure the width is an integer, and at least 1 if it's nonzero
-
- if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0;
-
- // but not for static plots - these don't get antialiased anyway.
- if(gd._context.staticPlot) return lineWidth;
-
- if(lineWidth < 1) return 1;
- return Math.round(lineWidth);
-};
-
-drawing.singleLineStyle = function(d, s, lw, lc, ld) {
- s.style('fill', 'none');
- var line = (((d || [])[0] || {}).trace || {}).line || {},
- lw1 = lw || line.width||0,
- dash = ld || line.dash || '';
-
- Color.stroke(s, lc || line.color);
- drawing.dashLine(s, dash, lw1);
-};
-
-drawing.lineGroupStyle = function(s, lw, lc, ld) {
- s.style('fill', 'none')
- .each(function(d) {
- var line = (((d || [])[0] || {}).trace || {}).line || {},
- lw1 = lw || line.width||0,
- dash = ld || line.dash || '';
-
- d3.select(this)
- .call(Color.stroke, lc || line.color)
- .call(drawing.dashLine, dash, lw1);
- });
-};
-
-drawing.dashLine = function(s, dash, lineWidth) {
- lineWidth = +lineWidth || 0;
- var dlw = Math.max(lineWidth, 3);
-
- if(dash === 'solid') dash = '';
- else if(dash === 'dot') dash = dlw + 'px,' + dlw + 'px';
- else if(dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px';
- else if(dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px';
- else if(dash === 'dashdot') {
- dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px';
- }
- else if(dash === 'longdashdot') {
- dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px';
- }
- // otherwise user wrote the dasharray themselves - leave it be
-
- s.style({
- 'stroke-dasharray': dash,
- 'stroke-width': lineWidth + 'px'
- });
-};
-
-drawing.fillGroupStyle = function(s) {
- s.style('stroke-width', 0)
- .each(function(d) {
- var shape = d3.select(this);
- try {
- shape.call(Color.fill, d[0].trace.fillcolor);
- }
- catch(e) {
- Lib.error(e, s);
- shape.remove();
- }
- });
-};
-
-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(
- [symDef.n, k, symDef.n + 100, k + '-open']);
- drawing.symbolNames[symDef.n] = k;
- drawing.symbolFuncs[symDef.n] = symDef.f;
- if(symDef.needLine) {
- drawing.symbolNeedLines[symDef.n] = true;
- }
- if(symDef.noDot) {
- drawing.symbolNoDot[symDef.n] = true;
- }
- else {
- drawing.symbolList = drawing.symbolList.concat(
- [symDef.n + 200, k + '-dot', symDef.n + 300, k + '-open-dot']);
- }
-});
-var MAXSYMBOL = drawing.symbolNames.length,
- // add a dot in the middle of the symbol
- DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z';
-
-drawing.symbolNumber = function(v) {
- if(typeof v === 'string') {
- var vbase = 0;
- if(v.indexOf('-open') > 0) {
- vbase = 100;
- v = v.replace('-open', '');
- }
- if(v.indexOf('-dot') > 0) {
- vbase += 200;
- v = v.replace('-dot', '');
- }
- v = drawing.symbolNames.indexOf(v);
- if(v >= 0) { v += vbase; }
- }
- if((v % 100 >= MAXSYMBOL) || v >= 400) { return 0; }
- return Math.floor(Math.max(v, 0));
-};
-
-function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) {
- // only scatter & box plots get marker path and opacity
- // bars, histograms don't
- if(Registry.traceIs(trace, 'symbols')) {
- var sizeFn = makeBubbleSizeFn(trace);
-
- sel.attr('d', function(d) {
- var r;
-
- // handle multi-trace graph edit case
- if(d.ms === 'various' || marker.size === 'various') r = 3;
- else {
- r = subTypes.isBubble(trace) ?
- sizeFn(d.ms) : (marker.size || 6) / 2;
- }
-
- // store the calculated size so hover can use it
- d.mrc = r;
-
- // turn the symbol into a sanitized number
- var x = drawing.symbolNumber(d.mx || marker.symbol) || 0,
- xBase = x % 100;
-
- // save if this marker is open
- // because that impacts how to handle colors
- d.om = x % 200 >= 100;
-
- return drawing.symbolFuncs[xBase](r) +
- (x >= 200 ? DOTPATH : '');
- })
- .style('opacity', function(d) {
- return (d.mo + 1 || marker.opacity + 1) - 1;
- });
- }
-
- // 'so' is suspected outliers, for box plots
- var fillColor,
- lineColor,
- lineWidth;
- if(d.so) {
- lineWidth = markerLine.outlierwidth;
- lineColor = markerLine.outliercolor;
- fillColor = marker.outliercolor;
- }
- else {
- lineWidth = (d.mlw + 1 || markerLine.width + 1 ||
- // TODO: we need the latter for legends... can we get rid of it?
- (d.trace ? d.trace.marker.line.width : 0) + 1) - 1;
-
- if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc);
- // weird case: array wasn't long enough to apply to every point
- else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine;
- else lineColor = markerLine.color;
-
- if('mc' in d) fillColor = d.mcc = markerScale(d.mc);
- else if(Array.isArray(marker.color)) fillColor = Color.defaultLine;
- else fillColor = marker.color || 'rgba(0,0,0,0)';
- }
-
- if(d.om) {
- // open markers can't have zero linewidth, default to 1px,
- // and use fill color as stroke color
- sel.call(Color.stroke, fillColor)
- .style({
- 'stroke-width': (lineWidth || 1) + 'px',
- fill: 'none'
- });
- }
- else {
- sel.style('stroke-width', lineWidth + 'px')
- .call(Color.fill, fillColor);
- if(lineWidth) {
- sel.call(Color.stroke, lineColor);
- }
- }
-}
-
-drawing.singlePointStyle = function(d, sel, trace) {
- var marker = trace.marker,
- markerLine = marker.line;
-
- // allow array marker and marker line colors to be
- // scaled by given max and min to colorscales
- var markerScale = drawing.tryColorscale(marker, ''),
- lineScale = drawing.tryColorscale(marker, 'line');
-
- singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine);
-
-};
-
-drawing.pointStyle = function(s, trace) {
- if(!s.size()) return;
-
- // allow array marker and marker line colors to be
- // scaled by given max and min to colorscales
- var marker = trace.marker;
- var markerScale = drawing.tryColorscale(marker, ''),
- lineScale = drawing.tryColorscale(marker, 'line');
-
- s.each(function(d) {
- drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale);
- });
-};
-
-drawing.tryColorscale = function(marker, prefix) {
- var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker,
- scl = cont.colorscale,
- colorArray = cont.color;
-
- if(scl && Array.isArray(colorArray)) {
- return Colorscale.makeColorScaleFunc(
- Colorscale.extractScale(scl, cont.cmin, cont.cmax)
- );
- }
- else return Lib.identity;
-};
-
-// draw text at points
-var TEXTOFFSETSIGN = {start: 1, end: -1, middle: 0, bottom: 1, top: -1},
- LINEEXPAND = 1.3;
-drawing.textPointStyle = function(s, trace) {
- s.each(function(d) {
- var p = d3.select(this),
- text = d.tx || trace.text;
-
- if(!text || Array.isArray(text)) {
- // isArray test handles the case of (intentionally) missing
- // or empty text within a text array
- p.remove();
- return;
- }
-
- var pos = d.tp || trace.textposition,
- v = pos.indexOf('top') !== -1 ? 'top' :
- pos.indexOf('bottom') !== -1 ? 'bottom' : 'middle',
- h = pos.indexOf('left') !== -1 ? 'end' :
- pos.indexOf('right') !== -1 ? 'start' : 'middle',
- fontSize = d.ts || trace.textfont.size,
- // if markers are shown, offset a little more than
- // the nominal marker size
- // ie 2/1.6 * nominal, bcs some markers are a bit bigger
- r = d.mrc ? (d.mrc / 0.8 + 1) : 0;
-
- fontSize = (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0;
-
- p.call(drawing.font,
- d.tf || trace.textfont.family,
- fontSize,
- d.tc || trace.textfont.color)
- .attr('text-anchor', h)
- .text(text)
- .call(svgTextUtils.convertToTspans);
- var pgroup = d3.select(this.parentNode),
- tspans = p.selectAll('tspan.line'),
- numLines = ((tspans[0].length || 1) - 1) * LINEEXPAND + 1,
- dx = TEXTOFFSETSIGN[h] * r,
- dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r +
- (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2;
-
- // fix the overall text group position
- pgroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
-
- // then fix multiline text
- if(numLines > 1) {
- tspans.attr({ x: p.attr('x'), y: p.attr('y') });
- }
- });
-};
-
-// generalized Catmull-Rom splines, per
-// http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
-var CatmullRomExp = 0.5;
-drawing.smoothopen = function(pts, smoothness) {
- if(pts.length < 3) { return 'M' + pts.join('L');}
- var path = 'M' + pts[0],
- tangents = [], i;
- for(i = 1; i < pts.length - 1; i++) {
- tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness));
- }
- path += 'Q' + tangents[0][0] + ' ' + pts[1];
- for(i = 2; i < pts.length - 1; i++) {
- path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i];
- }
- path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1];
- return path;
-};
-
-drawing.smoothclosed = function(pts, smoothness) {
- if(pts.length < 3) { return 'M' + pts.join('L') + 'Z'; }
- var path = 'M' + pts[0],
- pLast = pts.length - 1,
- tangents = [makeTangent(pts[pLast],
- pts[0], pts[1], smoothness)],
- i;
- for(i = 1; i < pLast; i++) {
- tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness));
- }
- tangents.push(
- makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness)
- );
-
- for(i = 1; i <= pLast; i++) {
- path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i];
- }
- path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z';
- return path;
-};
-
-function makeTangent(prevpt, thispt, nextpt, smoothness) {
- var d1x = prevpt[0] - thispt[0],
- d1y = prevpt[1] - thispt[1],
- d2x = nextpt[0] - thispt[0],
- d2y = nextpt[1] - thispt[1],
- d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2),
- d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2),
- numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness,
- numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness,
- denom1 = 3 * d2a * (d1a + d2a),
- denom2 = 3 * d1a * (d1a + d2a);
- return [
- [
- d3.round(thispt[0] + (denom1 && numx / denom1), 2),
- d3.round(thispt[1] + (denom1 && numy / denom1), 2)
- ], [
- d3.round(thispt[0] - (denom2 && numx / denom2), 2),
- d3.round(thispt[1] - (denom2 && numy / denom2), 2)
- ]
- ];
-}
-
-// step paths - returns a generator function for paths
-// with the given step shape
-var STEPPATH = {
- hv: function(p0, p1) {
- return 'H' + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2);
- },
- vh: function(p0, p1) {
- return 'V' + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2);
- },
- hvh: function(p0, p1) {
- return 'H' + d3.round((p0[0] + p1[0]) / 2, 2) + 'V' +
- d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2);
- },
- vhv: function(p0, p1) {
- return 'V' + d3.round((p0[1] + p1[1]) / 2, 2) + 'H' +
- d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2);
- }
-};
-var STEPLINEAR = function(p0, p1) {
- return 'L' + d3.round(p1[0], 2) + ',' + d3.round(p1[1], 2);
-};
-drawing.steps = function(shape) {
- var onestep = STEPPATH[shape] || STEPLINEAR;
- return function(pts) {
- var path = 'M' + d3.round(pts[0][0], 2) + ',' + d3.round(pts[0][1], 2);
- for(var i = 1; i < pts.length; i++) {
- path += onestep(pts[i - 1], pts[i]);
- }
- return path;
- };
-};
-
-// off-screen svg render testing element, shared by the whole page
-// uses the id 'js-plotly-tester' and stores it in gd._tester
-// makes a hash of cached text items in tester.node()._cache
-// so we can add references to rendered text (including all info
-// needed to fully determine its bounding rect)
-drawing.makeTester = function(gd) {
- var tester = d3.select('body')
- .selectAll('#js-plotly-tester')
- .data([0]);
-
- tester.enter().append('svg')
- .attr('id', 'js-plotly-tester')
- .attr(xmlnsNamespaces.svgAttrs)
- .style({
- position: 'absolute',
- left: '-10000px',
- top: '-10000px',
- width: '9000px',
- height: '9000px',
- 'z-index': '1'
- });
-
- // browsers differ on how they describe the bounding rect of
- // the svg if its contents spill over... so make a 1x1px
- // reference point we can measure off of.
- var testref = tester.selectAll('.js-reference-point').data([0]);
- testref.enter().append('path')
- .classed('js-reference-point', true)
- .attr('d', 'M0,0H1V1H0Z')
- .style({
- 'stroke-width': 0,
- fill: 'black'
- });
-
- if(!tester.node()._cache) {
- tester.node()._cache = {};
- }
-
- gd._tester = tester;
- gd._testref = testref;
-};
-
-// use our offscreen tester to get a clientRect for an element,
-// in a reference frame where it isn't translated and its anchor
-// point is at (0,0)
-// always returns a copy of the bbox, so the caller can modify it safely
-var savedBBoxes = [],
- maxSavedBBoxes = 10000;
-drawing.bBox = function(node) {
- // cache elements we've already measured so we don't have to
- // remeasure the same thing many times
- var saveNum = node.attributes['data-bb'];
- if(saveNum && saveNum.value) {
- return Lib.extendFlat({}, savedBBoxes[saveNum.value]);
- }
-
- var test3 = d3.select('#js-plotly-tester'),
- tester = test3.node();
-
- // copy the node to test into the tester
- var testNode = node.cloneNode(true);
- tester.appendChild(testNode);
- // standardize its position... do we really want to do this?
- d3.select(testNode).attr({
- x: 0,
- y: 0,
- transform: ''
- });
-
- var testRect = testNode.getBoundingClientRect(),
- refRect = test3.select('.js-reference-point')
- .node().getBoundingClientRect();
-
- tester.removeChild(testNode);
-
- var bb = {
- height: testRect.height,
- width: testRect.width,
- left: testRect.left - refRect.left,
- top: testRect.top - refRect.top,
- right: testRect.right - refRect.left,
- bottom: testRect.bottom - refRect.top
- };
-
- // make sure we don't have too many saved boxes,
- // or a long session could overload on memory
- // by saving boxes for long-gone elements
- if(savedBBoxes.length >= maxSavedBBoxes) {
- d3.selectAll('[data-bb]').attr('data-bb', null);
- savedBBoxes = [];
- }
-
- // cache this bbox
- node.setAttribute('data-bb', savedBBoxes.length);
- savedBBoxes.push(bb);
-
- return Lib.extendFlat({}, bb);
-};
-
-/*
- * make a robust clipPath url from a local id
- * note! We'd better not be exporting from a page
- * with a or the svg will not be portable!
- */
-drawing.setClipUrl = function(s, localId) {
- if(!localId) {
- s.attr('clip-path', null);
- return;
- }
-
- var url = '#' + localId,
- base = d3.select('base');
-
- // add id to location href w/o hashes if any)
- if(base.size() && base.attr('href')) {
- url = window.location.href.split('#')[0] + url;
- }
-
- s.attr('clip-path', 'url(' + url + ')');
-};
-
-drawing.getTranslate = function(element) {
- // Note the separator [^\d] between x and y in this regex
- // We generally use ',' but IE will convert it to ' '
- var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/,
- getter = element.attr ? 'attr' : 'getAttribute',
- transform = element[getter]('transform') || '';
-
- var translate = transform.replace(re, function(match, p1, p2) {
- return [p1, p2].join(' ');
- })
- .split(' ');
-
- return {
- x: +translate[0] || 0,
- y: +translate[1] || 0
- };
-};
-
-drawing.setTranslate = function(element, x, y) {
-
- var re = /(\btranslate\(.*?\);?)/,
- getter = element.attr ? 'attr' : 'getAttribute',
- setter = element.attr ? 'attr' : 'setAttribute',
- transform = element[getter]('transform') || '';
-
- x = x || 0;
- y = y || 0;
-
- transform = transform.replace(re, '').trim();
- transform += ' translate(' + x + ', ' + y + ')';
- transform = transform.trim();
-
- element[setter]('transform', transform);
-
- return transform;
-};
-
-drawing.getScale = function(element) {
-
- var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/,
- getter = element.attr ? 'attr' : 'getAttribute',
- transform = element[getter]('transform') || '';
-
- var translate = transform.replace(re, function(match, p1, p2) {
- return [p1, p2].join(' ');
- })
- .split(' ');
-
- return {
- x: +translate[0] || 1,
- y: +translate[1] || 1
- };
-};
-
-drawing.setScale = function(element, x, y) {
-
- var re = /(\bscale\(.*?\);?)/,
- getter = element.attr ? 'attr' : 'getAttribute',
- setter = element.attr ? 'attr' : 'setAttribute',
- transform = element[getter]('transform') || '';
-
- x = x || 1;
- y = y || 1;
-
- transform = transform.replace(re, '').trim();
- transform += ' scale(' + x + ', ' + y + ')';
- transform = transform.trim();
-
- element[setter]('transform', transform);
-
- return transform;
-};
-
-drawing.setPointGroupScale = function(selection, x, y) {
- var t, scale, re;
-
- x = x || 1;
- y = y || 1;
-
- if(x === 1 && y === 1) {
- scale = '';
- } else {
- // The same scale transform for every point:
- scale = ' scale(' + x + ',' + y + ')';
- }
-
- // A regex to strip any existing scale:
- re = /\s*sc.*/;
-
- selection.each(function() {
- // Get the transform:
- t = (this.getAttribute('transform') || '').replace(re, '');
- t += scale;
- t = t.trim();
-
- // Append the scale transform
- this.setAttribute('transform', t);
- });
-
- return scale;
-};
diff --git a/src/components/drawing/symbol_defs.js b/src/components/drawing/symbol_defs.js
index 548a8c5c307..46e44801d72 100644
--- a/src/components/drawing/symbol_defs.js
+++ b/src/components/drawing/symbol_defs.js
@@ -5,11 +5,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
+"use strict";
+var d3 = require("d3");
/** Marker symbol definitions
* users can specify markers either by number or name
@@ -18,457 +15,931 @@ var d3 = require('d3');
* 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
+ 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
index be441d7b364..58a5e6896d8 100644
--- a/src/components/errorbars/attributes.js
+++ b/src/components/errorbars/attributes.js
@@ -5,136 +5,112 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
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(' ')
- }
+ 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/components/errorbars/calc.js b/src/components/errorbars/calc.js
index d631758fcb6..cb1295aeea7 100644
--- a/src/components/errorbars/calc.js
+++ b/src/components/errorbars/calc.js
@@ -5,57 +5,51 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var Registry = require("../../registry");
+var Axes = require("../../plots/cartesian/axes");
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Registry = require('../../registry');
-var Axes = require('../../plots/cartesian/axes');
-
-var makeComputeError = require('./compute_error');
-
+var makeComputeError = require("./compute_error");
module.exports = function calc(gd) {
- var calcdata = gd.calcdata;
+ var calcdata = gd.calcdata;
- for(var i = 0; i < calcdata.length; i++) {
- var calcTrace = calcdata[i],
- trace = calcTrace[0].trace;
+ for (var i = 0; i < calcdata.length; i++) {
+ var calcTrace = calcdata[i], trace = calcTrace[0].trace;
- if(!Registry.traceIs(trace, 'errorBarsOK')) continue;
+ if (!Registry.traceIs(trace, "errorBarsOK")) continue;
- var xa = Axes.getFromId(gd, trace.xaxis),
- ya = Axes.getFromId(gd, trace.yaxis);
+ var xa = Axes.getFromId(gd, trace.xaxis),
+ ya = Axes.getFromId(gd, trace.yaxis);
- calcOneAxis(calcTrace, trace, xa, 'x');
- calcOneAxis(calcTrace, trace, ya, 'y');
- }
+ calcOneAxis(calcTrace, trace, xa, "x");
+ calcOneAxis(calcTrace, trace, ya, "y");
+ }
};
function calcOneAxis(calcTrace, trace, axis, coord) {
- var opts = trace['error_' + coord] || {},
- isVisible = (opts.visible && ['linear', 'log'].indexOf(axis.type) !== -1),
- vals = [];
+ var opts = trace["error_" + coord] || {},
+ isVisible = opts.visible && ["linear", "log"].indexOf(axis.type) !== -1,
+ vals = [];
- if(!isVisible) return;
+ if (!isVisible) return;
- var computeError = makeComputeError(opts);
+ var computeError = makeComputeError(opts);
- for(var i = 0; i < calcTrace.length; i++) {
- var calcPt = calcTrace[i],
- calcCoord = calcPt[coord];
+ for (var i = 0; i < calcTrace.length; i++) {
+ var calcPt = calcTrace[i], calcCoord = calcPt[coord];
- if(!isNumeric(axis.c2l(calcCoord))) continue;
+ if (!isNumeric(axis.c2l(calcCoord))) continue;
- var errors = computeError(calcCoord, i);
- if(isNumeric(errors[0]) && isNumeric(errors[1])) {
- var shoe = calcPt[coord + 's'] = calcCoord - errors[0],
- hat = calcPt[coord + 'h'] = calcCoord + errors[1];
- vals.push(shoe, hat);
- }
+ var errors = computeError(calcCoord, i);
+ if (isNumeric(errors[0]) && isNumeric(errors[1])) {
+ var shoe = calcPt[coord + "s"] = calcCoord - errors[0],
+ hat = calcPt[coord + "h"] = calcCoord + errors[1];
+ vals.push(shoe, hat);
}
+ }
- Axes.expand(axis, vals, {padded: true});
+ Axes.expand(axis, vals, { padded: true });
}
diff --git a/src/components/errorbars/compute_error.js b/src/components/errorbars/compute_error.js
index dd0b189662d..74cd965e790 100644
--- a/src/components/errorbars/compute_error.js
+++ b/src/components/errorbars/compute_error.js
@@ -5,11 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
/**
* Error bar computing function generator
*
@@ -26,44 +22,36 @@
* - error[1] : " " " " positive "
*/
module.exports = function makeComputeError(opts) {
- var type = opts.type,
- symmetric = opts.symmetric;
+ var type = opts.type, symmetric = opts.symmetric;
- if(type === 'data') {
- var array = opts.array,
- arrayminus = opts.arrayminus;
+ if (type === "data") {
+ var array = opts.array, arrayminus = opts.arrayminus;
- if(symmetric || arrayminus === undefined) {
- return function computeError(dataPt, index) {
- var val = +(array[index]);
- return [val, val];
- };
- }
- else {
- return function computeError(dataPt, index) {
- return [+arrayminus[index], +array[index]];
- };
- }
+ if (symmetric || arrayminus === undefined) {
+ return function computeError(dataPt, index) {
+ var val = +array[index];
+ return [val, val];
+ };
+ } else {
+ return function computeError(dataPt, index) {
+ return [+arrayminus[index], +array[index]];
+ };
}
- else {
- var computeErrorValue = makeComputeErrorValue(type, opts.value),
- computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus);
+ } else {
+ var computeErrorValue = makeComputeErrorValue(type, opts.value),
+ computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus);
- if(symmetric || opts.valueminus === undefined) {
- return function computeError(dataPt) {
- var val = computeErrorValue(dataPt);
- return [val, val];
- };
- }
- else {
- return function computeError(dataPt) {
- return [
- computeErrorValueMinus(dataPt),
- computeErrorValue(dataPt)
- ];
- };
- }
+ if (symmetric || opts.valueminus === undefined) {
+ return function computeError(dataPt) {
+ var val = computeErrorValue(dataPt);
+ return [val, val];
+ };
+ } else {
+ return function computeError(dataPt) {
+ return [computeErrorValueMinus(dataPt), computeErrorValue(dataPt)];
+ };
}
+ }
};
/**
@@ -76,19 +64,19 @@ module.exports = function makeComputeError(opts) {
* @param {numeric} dataPt
*/
function makeComputeErrorValue(type, value) {
- if(type === 'percent') {
- return function(dataPt) {
- return Math.abs(dataPt * value / 100);
- };
- }
- if(type === 'constant') {
- return function() {
- return Math.abs(value);
- };
- }
- if(type === 'sqrt') {
- return function(dataPt) {
- return Math.sqrt(Math.abs(dataPt));
- };
- }
+ if (type === "percent") {
+ return function(dataPt) {
+ return Math.abs(dataPt * value / 100);
+ };
+ }
+ if (type === "constant") {
+ return function() {
+ return Math.abs(value);
+ };
+ }
+ if (type === "sqrt") {
+ return function(dataPt) {
+ return Math.sqrt(Math.abs(dataPt));
+ };
+ }
}
diff --git a/src/components/errorbars/defaults.js b/src/components/errorbars/defaults.js
index 433f585352b..4957cf43278 100644
--- a/src/components/errorbars/defaults.js
+++ b/src/components/errorbars/defaults.js
@@ -5,71 +5,70 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-
-var attributes = require('./attributes');
+var Registry = require("../../registry");
+var Lib = require("../../lib");
+var attributes = require("./attributes");
module.exports = function(traceIn, traceOut, defaultColor, opts) {
- var objName = 'error_' + opts.axis,
- containerOut = traceOut[objName] = {},
- containerIn = traceIn[objName] || {};
+ var objName = "error_" + opts.axis,
+ containerOut = traceOut[objName] = {},
+ containerIn = traceIn[objName] || {};
- function coerce(attr, dflt) {
- return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
- }
+ function coerce(attr, dflt) {
+ return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
+ }
- var hasErrorBars = (
- containerIn.array !== undefined ||
- containerIn.value !== undefined ||
- containerIn.type === 'sqrt'
- );
+ var hasErrorBars = containerIn.array !== undefined ||
+ containerIn.value !== undefined ||
+ containerIn.type === "sqrt";
- var visible = coerce('visible', hasErrorBars);
+ var visible = coerce("visible", hasErrorBars);
- if(visible === false) return;
+ if (visible === false) return;
- var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'),
- symmetric = true;
+ var type = coerce("type", "array" in containerIn ? "data" : "percent"),
+ symmetric = true;
- if(type !== 'sqrt') {
- symmetric = coerce('symmetric',
- !((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn));
- }
+ if (type !== "sqrt") {
+ symmetric = coerce(
+ "symmetric",
+ !((type === "data" ? "arrayminus" : "valueminus") in containerIn)
+ );
+ }
- if(type === 'data') {
- var array = coerce('array');
- if(!array) containerOut.array = [];
- coerce('traceref');
- if(!symmetric) {
- var arrayminus = coerce('arrayminus');
- if(!arrayminus) containerOut.arrayminus = [];
- coerce('tracerefminus');
- }
- }
- else if(type === 'percent' || type === 'constant') {
- coerce('value');
- if(!symmetric) coerce('valueminus');
+ if (type === "data") {
+ var array = coerce("array");
+ if (!array) containerOut.array = [];
+ coerce("traceref");
+ if (!symmetric) {
+ var arrayminus = coerce("arrayminus");
+ if (!arrayminus) containerOut.arrayminus = [];
+ coerce("tracerefminus");
}
+ } else if (type === "percent" || type === "constant") {
+ coerce("value");
+ if (!symmetric) coerce("valueminus");
+ }
- var copyAttr = 'copy_' + opts.inherit + 'style';
- if(opts.inherit) {
- var inheritObj = traceOut['error_' + opts.inherit];
- if((inheritObj || {}).visible) {
- coerce(copyAttr, !(containerIn.color ||
- isNumeric(containerIn.thickness) ||
- isNumeric(containerIn.width)));
- }
- }
- if(!opts.inherit || !containerOut[copyAttr]) {
- coerce('color', defaultColor);
- coerce('thickness');
- coerce('width', Registry.traceIs(traceOut, 'gl3d') ? 0 : 4);
+ var copyAttr = "copy_" + opts.inherit + "style";
+ if (opts.inherit) {
+ var inheritObj = traceOut["error_" + opts.inherit];
+ if ((inheritObj || {}).visible) {
+ coerce(
+ copyAttr,
+ !(containerIn.color ||
+ isNumeric(containerIn.thickness) ||
+ isNumeric(containerIn.width))
+ );
}
+ }
+ if (!opts.inherit || !containerOut[copyAttr]) {
+ coerce("color", defaultColor);
+ coerce("thickness");
+ coerce("width", Registry.traceIs(traceOut, "gl3d") ? 0 : 4);
+ }
};
diff --git a/src/components/errorbars/index.js b/src/components/errorbars/index.js
index a27378ad6e7..4ab3e45d7d6 100644
--- a/src/components/errorbars/index.js
+++ b/src/components/errorbars/index.js
@@ -5,53 +5,46 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
var errorBars = module.exports = {};
-errorBars.attributes = require('./attributes');
+errorBars.attributes = require("./attributes");
-errorBars.supplyDefaults = require('./defaults');
+errorBars.supplyDefaults = require("./defaults");
-errorBars.calc = require('./calc');
+errorBars.calc = require("./calc");
errorBars.calcFromTrace = function(trace, layout) {
- var x = trace.x || [],
- y = trace.y,
- len = x.length || y.length;
+ var x = trace.x || [], y = trace.y, len = x.length || y.length;
- var calcdataMock = new Array(len);
+ var calcdataMock = new Array(len);
- for(var i = 0; i < len; i++) {
- calcdataMock[i] = {
- x: x[i],
- y: y[i]
- };
- }
+ for (var i = 0; i < len; i++) {
+ calcdataMock[i] = { x: x[i], y: y[i] };
+ }
- calcdataMock[0].trace = trace;
+ calcdataMock[0].trace = trace;
- errorBars.calc({
- calcdata: [calcdataMock],
- _fullLayout: layout
- });
+ errorBars.calc({ calcdata: [calcdataMock], _fullLayout: layout });
- return calcdataMock;
+ return calcdataMock;
};
-errorBars.plot = require('./plot');
+errorBars.plot = require("./plot");
-errorBars.style = require('./style');
+errorBars.style = require("./style");
errorBars.hoverInfo = function(calcPoint, trace, hoverPoint) {
- if((trace.error_y || {}).visible) {
- hoverPoint.yerr = calcPoint.yh - calcPoint.y;
- if(!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys;
+ if ((trace.error_y || {}).visible) {
+ hoverPoint.yerr = calcPoint.yh - calcPoint.y;
+ if (!trace.error_y.symmetric) {
+ hoverPoint.yerrneg = calcPoint.y - calcPoint.ys;
}
- if((trace.error_x || {}).visible) {
- hoverPoint.xerr = calcPoint.xh - calcPoint.x;
- if(!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs;
+ }
+ if ((trace.error_x || {}).visible) {
+ hoverPoint.xerr = calcPoint.xh - calcPoint.x;
+ if (!trace.error_x.symmetric) {
+ hoverPoint.xerrneg = calcPoint.x - calcPoint.xs;
}
+ }
};
diff --git a/src/components/errorbars/plot.js b/src/components/errorbars/plot.js
index 84bc05504bf..57e8fb7309e 100644
--- a/src/components/errorbars/plot.js
+++ b/src/components/errorbars/plot.js
@@ -5,158 +5,171 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var subTypes = require('../../traces/scatter/subtypes');
+var subTypes = require("../../traces/scatter/subtypes");
module.exports = function plot(traces, plotinfo, transitionOpts) {
- var isNew;
-
- var xa = plotinfo.xaxis,
- ya = plotinfo.yaxis;
-
- var hasAnimation = transitionOpts && transitionOpts.duration > 0;
-
- traces.each(function(d) {
- var trace = d[0].trace,
- // || {} is in case the trace (specifically scatterternary)
- // doesn't support error bars at all, but does go through
- // the scatter.plot mechanics, which calls ErrorBars.plot
- // internally
- xObj = trace.error_x || {},
- yObj = trace.error_y || {};
-
- var keyFunc;
-
- if(trace.ids) {
- keyFunc = function(d) {return d.id;};
- }
-
- var sparse = (
- subTypes.hasMarkers(trace) &&
- trace.marker.maxdisplayed > 0
- );
-
- if(!yObj.visible && !xObj.visible) return;
-
- var errorbars = d3.select(this).selectAll('g.errorbar')
- .data(d, keyFunc);
-
- errorbars.exit().remove();
+ var isNew;
- errorbars.style('opacity', 1);
+ var xa = plotinfo.xaxis, ya = plotinfo.yaxis;
- var enter = errorbars.enter().append('g')
- .classed('errorbar', true);
-
- if(hasAnimation) {
- enter.style('opacity', 0).transition()
- .duration(transitionOpts.duration)
- .style('opacity', 1);
- }
-
- errorbars.each(function(d) {
- var errorbar = d3.select(this);
- var coords = errorCoords(d, xa, ya);
-
- if(sparse && !d.vis) return;
-
- var path;
-
- if(yObj.visible && isNumeric(coords.x) &&
- isNumeric(coords.yh) &&
- isNumeric(coords.ys)) {
- var yw = yObj.width;
-
- path = 'M' + (coords.x - yw) + ',' +
- coords.yh + 'h' + (2 * yw) + // hat
- 'm-' + yw + ',0V' + coords.ys; // bar
+ var hasAnimation = transitionOpts && transitionOpts.duration > 0;
+ traces.each(function(d) {
+ var trace = d[0].trace,
+ // || {} is in case the trace (specifically scatterternary)
+ // doesn't support error bars at all, but does go through
+ // the scatter.plot mechanics, which calls ErrorBars.plot
+ // internally
+ xObj = trace.error_x || {},
+ yObj = trace.error_y || {};
- if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe
+ var keyFunc;
- var yerror = errorbar.select('path.yerror');
+ if (trace.ids) {
+ keyFunc = function(d) {
+ return d.id;
+ };
+ }
- isNew = !yerror.size();
+ var sparse = subTypes.hasMarkers(trace) && trace.marker.maxdisplayed > 0;
- if(isNew) {
- yerror = errorbar.append('path')
- .classed('yerror', true);
- } else if(hasAnimation) {
- yerror = yerror
- .transition()
- .duration(transitionOpts.duration)
- .ease(transitionOpts.easing);
- }
+ if (!yObj.visible && !xObj.visible) return;
- yerror.attr('d', path);
- }
+ var errorbars = d3.select(this).selectAll("g.errorbar").data(d, keyFunc);
- if(xObj.visible && isNumeric(coords.y) &&
- isNumeric(coords.xh) &&
- isNumeric(coords.xs)) {
- var xw = (xObj.copy_ystyle ? yObj : xObj).width;
+ errorbars.exit().remove();
- path = 'M' + coords.xh + ',' +
- (coords.y - xw) + 'v' + (2 * xw) + // hat
- 'm0,-' + xw + 'H' + coords.xs; // bar
+ errorbars.style("opacity", 1);
- if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe
+ var enter = errorbars.enter().append("g").classed("errorbar", true);
- var xerror = errorbar.select('path.xerror');
+ if (hasAnimation) {
+ enter
+ .style("opacity", 0)
+ .transition()
+ .duration(transitionOpts.duration)
+ .style("opacity", 1);
+ }
- isNew = !xerror.size();
+ errorbars.each(function(d) {
+ var errorbar = d3.select(this);
+ var coords = errorCoords(d, xa, ya);
+
+ if (sparse && !d.vis) return;
+
+ var path;
+
+ if (
+ yObj.visible &&
+ isNumeric(coords.x) &&
+ isNumeric(coords.yh) &&
+ isNumeric(coords.ys)
+ ) {
+ var yw = yObj.width;
+
+ path = "M" +
+ (coords.x - yw) +
+ "," +
+ coords.yh +
+ "h" +
+ 2 * yw + // hat
+ "m-" +
+ yw +
+ ",0V" +
+ coords.ys;
+
+ // bar
+ if (!coords.noYS) path += "m-" + yw + ",0h" + 2 * yw;
+
+ // shoe
+ var yerror = errorbar.select("path.yerror");
+
+ isNew = !yerror.size();
+
+ if (isNew) {
+ yerror = errorbar.append("path").classed("yerror", true);
+ } else if (hasAnimation) {
+ yerror = yerror
+ .transition()
+ .duration(transitionOpts.duration)
+ .ease(transitionOpts.easing);
+ }
- if(isNew) {
- xerror = errorbar.append('path')
- .classed('xerror', true);
- } else if(hasAnimation) {
- xerror = xerror
- .transition()
- .duration(transitionOpts.duration)
- .ease(transitionOpts.easing);
- }
+ yerror.attr("d", path);
+ }
+
+ if (
+ xObj.visible &&
+ isNumeric(coords.y) &&
+ isNumeric(coords.xh) &&
+ isNumeric(coords.xs)
+ ) {
+ var xw = (xObj.copy_ystyle ? yObj : xObj).width;
+
+ path = "M" +
+ coords.xh +
+ "," +
+ (coords.y - xw) +
+ "v" +
+ 2 * xw + // hat
+ "m0,-" +
+ xw +
+ "H" +
+ coords.xs;
+
+ // bar
+ if (!coords.noXS) path += "m0,-" + xw + "v" + 2 * xw;
+
+ // shoe
+ var xerror = errorbar.select("path.xerror");
+
+ isNew = !xerror.size();
+
+ if (isNew) {
+ xerror = errorbar.append("path").classed("xerror", true);
+ } else if (hasAnimation) {
+ xerror = xerror
+ .transition()
+ .duration(transitionOpts.duration)
+ .ease(transitionOpts.easing);
+ }
- xerror.attr('d', path);
- }
- });
+ xerror.attr("d", path);
+ }
});
+ });
};
// compute the coordinates of the error-bar objects
function errorCoords(d, xa, ya) {
- var out = {
- x: xa.c2p(d.x),
- y: ya.c2p(d.y)
- };
-
- // calculate the error bar size and hat and shoe locations
- if(d.yh !== undefined) {
- out.yh = ya.c2p(d.yh);
- out.ys = ya.c2p(d.ys);
-
- // if the shoes go off-scale (ie log scale, error bars past zero)
- // clip the bar and hide the shoes
- if(!isNumeric(out.ys)) {
- out.noYS = true;
- out.ys = ya.c2p(d.ys, true);
- }
+ var out = { x: xa.c2p(d.x), y: ya.c2p(d.y) };
+
+ // calculate the error bar size and hat and shoe locations
+ if (d.yh !== undefined) {
+ out.yh = ya.c2p(d.yh);
+ out.ys = ya.c2p(d.ys);
+
+ // if the shoes go off-scale (ie log scale, error bars past zero)
+ // clip the bar and hide the shoes
+ if (!isNumeric(out.ys)) {
+ out.noYS = true;
+ out.ys = ya.c2p(d.ys, true);
}
+ }
- if(d.xh !== undefined) {
- out.xh = xa.c2p(d.xh);
- out.xs = xa.c2p(d.xs);
+ if (d.xh !== undefined) {
+ out.xh = xa.c2p(d.xh);
+ out.xs = xa.c2p(d.xs);
- if(!isNumeric(out.xs)) {
- out.noXS = true;
- out.xs = xa.c2p(d.xs, true);
- }
+ if (!isNumeric(out.xs)) {
+ out.noXS = true;
+ out.xs = xa.c2p(d.xs, true);
}
+ }
- return out;
+ return out;
}
diff --git a/src/components/errorbars/style.js b/src/components/errorbars/style.js
index b6c81feb662..fa6b3fe0239 100644
--- a/src/components/errorbars/style.js
+++ b/src/components/errorbars/style.js
@@ -5,31 +5,29 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
-
-'use strict';
-
-var d3 = require('d3');
-
-var Color = require('../color');
-
+var Color = require("../color");
module.exports = function style(traces) {
- traces.each(function(d) {
- var trace = d[0].trace,
- yObj = trace.error_y || {},
- xObj = trace.error_x || {};
+ traces.each(function(d) {
+ var trace = d[0].trace,
+ yObj = trace.error_y || {},
+ xObj = trace.error_x || {};
- var s = d3.select(this);
+ var s = d3.select(this);
- s.selectAll('path.yerror')
- .style('stroke-width', yObj.thickness + 'px')
- .call(Color.stroke, yObj.color);
+ s
+ .selectAll("path.yerror")
+ .style("stroke-width", yObj.thickness + "px")
+ .call(Color.stroke, yObj.color);
- if(xObj.copy_ystyle) xObj = yObj;
+ if (xObj.copy_ystyle) xObj = yObj;
- s.selectAll('path.xerror')
- .style('stroke-width', xObj.thickness + 'px')
- .call(Color.stroke, xObj.color);
- });
+ s
+ .selectAll("path.xerror")
+ .style("stroke-width", xObj.thickness + "px")
+ .call(Color.stroke, xObj.color);
+ });
};
diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js
index 2f15c72f908..4eaa3795d93 100644
--- a/src/components/images/attributes.js
+++ b/src/components/images/attributes.js
@@ -5,163 +5,138 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var cartesianConstants = require('../../plots/cartesian/constants');
-
+"use strict";
+var cartesianConstants = require("../../plots/cartesian/constants");
module.exports = {
- _isLinkedToArray: 'image',
-
- visible: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines whether or not this image is visible.'
- ].join(' ')
- },
-
- source: {
- valType: 'string',
- role: 'info',
- description: [
- 'Specifies the URL of the image to be used.',
- 'The URL must be accessible from the domain where the',
- 'plot code is run, and can be either relative or absolute.'
-
- ].join(' ')
- },
-
- layer: {
- valType: 'enumerated',
- values: ['below', 'above'],
- dflt: 'above',
- role: 'info',
- description: [
- 'Specifies whether images are drawn below or above traces.',
- 'When `xref` and `yref` are both set to `paper`,',
- 'image is drawn below the entire plot area.'
- ].join(' ')
- },
-
- sizex: {
- valType: 'number',
- role: 'info',
- dflt: 0,
- description: [
- 'Sets the image container size horizontally.',
- 'The image will be sized based on the `position` value.',
- 'When `xref` is set to `paper`, units are sized relative',
- 'to the plot width.'
- ].join(' ')
- },
-
- sizey: {
- valType: 'number',
- role: 'info',
- dflt: 0,
- description: [
- 'Sets the image container size vertically.',
- 'The image will be sized based on the `position` value.',
- 'When `yref` is set to `paper`, units are sized relative',
- 'to the plot height.'
- ].join(' ')
- },
-
- sizing: {
- valType: 'enumerated',
- values: ['fill', 'contain', 'stretch'],
- dflt: 'contain',
- role: 'info',
- description: [
- 'Specifies which dimension of the image to constrain.'
- ].join(' ')
- },
-
- opacity: {
- valType: 'number',
- role: 'info',
- min: 0,
- max: 1,
- dflt: 1,
- description: 'Sets the opacity of the image.'
- },
-
- x: {
- valType: 'any',
- role: 'info',
- dflt: 0,
- description: [
- 'Sets the image\'s x position.',
- 'When `xref` is set to `paper`, units are sized relative',
- 'to the plot height.',
- 'See `xref` for more info'
- ].join(' ')
- },
-
- y: {
- valType: 'any',
- role: 'info',
- dflt: 0,
- description: [
- 'Sets the image\'s y position.',
- 'When `yref` is set to `paper`, units are sized relative',
- 'to the plot height.',
- 'See `yref` for more info'
- ].join(' ')
- },
-
- xanchor: {
- valType: 'enumerated',
- values: ['left', 'center', 'right'],
- dflt: 'left',
- role: 'info',
- description: 'Sets the anchor for the x position'
- },
-
- yanchor: {
- valType: 'enumerated',
- values: ['top', 'middle', 'bottom'],
- dflt: 'top',
- role: 'info',
- description: 'Sets the anchor for the y position.'
- },
-
- xref: {
- valType: 'enumerated',
- values: [
- 'paper',
- cartesianConstants.idRegex.x.toString()
- ],
- dflt: 'paper',
- role: 'info',
- description: [
- 'Sets the images\'s x coordinate axis.',
- 'If set to a x axis id (e.g. *x* or *x2*), the `x` position',
- 'refers to an x data coordinate',
- 'If set to *paper*, the `x` position refers to the distance from',
- 'the left of plot in normalized coordinates',
- 'where *0* (*1*) corresponds to the left (right).'
- ].join(' ')
- },
-
- yref: {
- valType: 'enumerated',
- values: [
- 'paper',
- cartesianConstants.idRegex.y.toString()
- ],
- dflt: 'paper',
- role: 'info',
- description: [
- 'Sets the images\'s y coordinate axis.',
- 'If set to a y axis id (e.g. *y* or *y2*), the `y` position',
- 'refers to a y data coordinate.',
- 'If set to *paper*, the `y` position refers to the distance from',
- 'the bottom of the plot in normalized coordinates',
- 'where *0* (*1*) corresponds to the bottom (top).'
- ].join(' ')
- }
+ _isLinkedToArray: "image",
+ visible: {
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: ["Determines whether or not this image is visible."].join(" ")
+ },
+ source: {
+ valType: "string",
+ role: "info",
+ description: [
+ "Specifies the URL of the image to be used.",
+ "The URL must be accessible from the domain where the",
+ "plot code is run, and can be either relative or absolute."
+ ].join(" ")
+ },
+ layer: {
+ valType: "enumerated",
+ values: ["below", "above"],
+ dflt: "above",
+ role: "info",
+ description: [
+ "Specifies whether images are drawn below or above traces.",
+ "When `xref` and `yref` are both set to `paper`,",
+ "image is drawn below the entire plot area."
+ ].join(" ")
+ },
+ sizex: {
+ valType: "number",
+ role: "info",
+ dflt: 0,
+ description: [
+ "Sets the image container size horizontally.",
+ "The image will be sized based on the `position` value.",
+ "When `xref` is set to `paper`, units are sized relative",
+ "to the plot width."
+ ].join(" ")
+ },
+ sizey: {
+ valType: "number",
+ role: "info",
+ dflt: 0,
+ description: [
+ "Sets the image container size vertically.",
+ "The image will be sized based on the `position` value.",
+ "When `yref` is set to `paper`, units are sized relative",
+ "to the plot height."
+ ].join(" ")
+ },
+ sizing: {
+ valType: "enumerated",
+ values: ["fill", "contain", "stretch"],
+ dflt: "contain",
+ role: "info",
+ description: ["Specifies which dimension of the image to constrain."].join(
+ " "
+ )
+ },
+ opacity: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ max: 1,
+ dflt: 1,
+ description: "Sets the opacity of the image."
+ },
+ x: {
+ valType: "any",
+ role: "info",
+ dflt: 0,
+ description: [
+ "Sets the image's x position.",
+ "When `xref` is set to `paper`, units are sized relative",
+ "to the plot height.",
+ "See `xref` for more info"
+ ].join(" ")
+ },
+ y: {
+ valType: "any",
+ role: "info",
+ dflt: 0,
+ description: [
+ "Sets the image's y position.",
+ "When `yref` is set to `paper`, units are sized relative",
+ "to the plot height.",
+ "See `yref` for more info"
+ ].join(" ")
+ },
+ xanchor: {
+ valType: "enumerated",
+ values: ["left", "center", "right"],
+ dflt: "left",
+ role: "info",
+ description: "Sets the anchor for the x position"
+ },
+ yanchor: {
+ valType: "enumerated",
+ values: ["top", "middle", "bottom"],
+ dflt: "top",
+ role: "info",
+ description: "Sets the anchor for the y position."
+ },
+ xref: {
+ valType: "enumerated",
+ values: ["paper", cartesianConstants.idRegex.x.toString()],
+ dflt: "paper",
+ role: "info",
+ description: [
+ "Sets the images's x coordinate axis.",
+ "If set to a x axis id (e.g. *x* or *x2*), the `x` position",
+ "refers to an x data coordinate",
+ "If set to *paper*, the `x` position refers to the distance from",
+ "the left of plot in normalized coordinates",
+ "where *0* (*1*) corresponds to the left (right)."
+ ].join(" ")
+ },
+ yref: {
+ valType: "enumerated",
+ values: ["paper", cartesianConstants.idRegex.y.toString()],
+ dflt: "paper",
+ role: "info",
+ description: [
+ "Sets the images's y coordinate axis.",
+ "If set to a y axis id (e.g. *y* or *y2*), the `y` position",
+ "refers to a y data coordinate.",
+ "If set to *paper*, the `y` position refers to the distance from",
+ "the bottom of the plot in normalized coordinates",
+ "where *0* (*1*) corresponds to the bottom (top)."
+ ].join(" ")
+ }
};
diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js
index 0c6c5b32c93..7705519adde 100644
--- a/src/components/images/defaults.js
+++ b/src/components/images/defaults.js
@@ -5,54 +5,48 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Lib = require("../../lib");
+var Axes = require("../../plots/cartesian/axes");
+var handleArrayContainerDefaults = require(
+ "../../plots/array_container_defaults"
+);
-'use strict';
-
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
-
-var attributes = require('./attributes');
-var name = 'images';
+var attributes = require("./attributes");
+var name = "images";
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
- var opts = {
- name: name,
- handleItemDefaults: imageDefaults
- };
+ var opts = { name: name, handleItemDefaults: imageDefaults };
- handleArrayContainerDefaults(layoutIn, layoutOut, opts);
+ handleArrayContainerDefaults(layoutIn, layoutOut, opts);
};
-
function imageDefaults(imageIn, imageOut, fullLayout) {
+ function coerce(attr, dflt) {
+ return Lib.coerce(imageIn, imageOut, attributes, attr, dflt);
+ }
- function coerce(attr, dflt) {
- return Lib.coerce(imageIn, imageOut, attributes, attr, dflt);
- }
-
- var source = coerce('source');
- var visible = coerce('visible', !!source);
+ var source = coerce("source");
+ var visible = coerce("visible", !!source);
- if(!visible) return imageOut;
+ if (!visible) return imageOut;
- coerce('layer');
- coerce('x');
- coerce('y');
- coerce('xanchor');
- coerce('yanchor');
- coerce('sizex');
- coerce('sizey');
- coerce('sizing');
- coerce('opacity');
+ coerce("layer");
+ coerce("x");
+ coerce("y");
+ coerce("xanchor");
+ coerce("yanchor");
+ coerce("sizex");
+ coerce("sizey");
+ coerce("sizing");
+ coerce("opacity");
- var gdMock = { _fullLayout: fullLayout },
- axLetters = ['x', 'y'];
+ var gdMock = { _fullLayout: fullLayout }, axLetters = ["x", "y"];
- for(var i = 0; i < 2; i++) {
- // 'paper' is the fallback axref
- Axes.coerceRef(imageIn, imageOut, gdMock, axLetters[i], 'paper');
- }
+ for (var i = 0; i < 2; i++) {
+ // 'paper' is the fallback axref
+ Axes.coerceRef(imageIn, imageOut, gdMock, axLetters[i], "paper");
+ }
- return imageOut;
+ return imageOut;
}
diff --git a/src/components/images/draw.js b/src/components/images/draw.js
index 228916d3de4..7680e3b5836 100644
--- a/src/components/images/draw.js
+++ b/src/components/images/draw.js
@@ -5,174 +5,172 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var d3 = require('d3');
-var Drawing = require('../drawing');
-var Axes = require('../../plots/cartesian/axes');
-var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
+"use strict";
+var d3 = require("d3");
+var Drawing = require("../drawing");
+var Axes = require("../../plots/cartesian/axes");
+var xmlnsNamespaces = require("../../constants/xmlns_namespaces");
module.exports = function draw(gd) {
- var fullLayout = gd._fullLayout,
- imageDataAbove = [],
- imageDataSubplot = [],
- imageDataBelow = [];
-
- // Sort into top, subplot, and bottom layers
- for(var i = 0; i < fullLayout.images.length; i++) {
- var img = fullLayout.images[i];
-
- if(img.visible) {
- if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') {
- imageDataSubplot.push(img);
- } else if(img.layer === 'above') {
- imageDataAbove.push(img);
- } else {
- imageDataBelow.push(img);
- }
- }
+ var fullLayout = gd._fullLayout,
+ imageDataAbove = [],
+ imageDataSubplot = [],
+ imageDataBelow = [];
+
+ // Sort into top, subplot, and bottom layers
+ for (var i = 0; i < fullLayout.images.length; i++) {
+ var img = fullLayout.images[i];
+
+ if (img.visible) {
+ if (
+ img.layer === "below" && img.xref !== "paper" && img.yref !== "paper"
+ ) {
+ imageDataSubplot.push(img);
+ } else if (img.layer === "above") {
+ imageDataAbove.push(img);
+ } else {
+ imageDataBelow.push(img);
+ }
}
+ }
+
+ var anchors = {
+ x: {
+ left: { sizing: "xMin", offset: 0 },
+ center: { sizing: "xMid", offset: (-1) / 2 },
+ right: { sizing: "xMax", offset: -1 }
+ },
+ y: {
+ top: { sizing: "YMin", offset: 0 },
+ middle: { sizing: "YMid", offset: (-1) / 2 },
+ bottom: { sizing: "YMax", offset: -1 }
+ }
+ };
+ // Images must be converted to dataURL's for exporting.
+ function setImage(d) {
+ var thisImage = d3.select(this);
- var anchors = {
- x: {
- left: { sizing: 'xMin', offset: 0 },
- center: { sizing: 'xMid', offset: -1 / 2 },
- right: { sizing: 'xMax', offset: -1 }
- },
- y: {
- top: { sizing: 'YMin', offset: 0 },
- middle: { sizing: 'YMid', offset: -1 / 2 },
- bottom: { sizing: 'YMax', offset: -1 }
- }
- };
-
-
- // Images must be converted to dataURL's for exporting.
- function setImage(d) {
- var thisImage = d3.select(this);
-
- if(this.img && this.img.src === d.source) {
- return;
- }
-
- thisImage.attr('xmlns', xmlnsNamespaces.svg);
-
- var imagePromise = new Promise(function(resolve) {
-
- var img = new Image();
- this.img = img;
-
- // If not set, a `tainted canvas` error is thrown
- img.setAttribute('crossOrigin', 'anonymous');
- img.onerror = errorHandler;
- img.onload = function() {
- var canvas = document.createElement('canvas');
- canvas.width = this.width;
- canvas.height = this.height;
-
- var ctx = canvas.getContext('2d');
- ctx.drawImage(this, 0, 0);
-
- var dataURL = canvas.toDataURL('image/png');
-
- thisImage.attr('xlink:href', dataURL);
- };
+ if (this.img && this.img.src === d.source) {
+ return;
+ }
+ thisImage.attr("xmlns", xmlnsNamespaces.svg);
- thisImage.on('error', errorHandler);
- thisImage.on('load', resolve);
+ var imagePromise = new Promise(
+ (function(resolve) {
+ var img = new Image();
+ this.img = img;
- img.src = d.source;
+ // If not set, a `tainted canvas` error is thrown
+ img.setAttribute("crossOrigin", "anonymous");
+ img.onerror = errorHandler;
+ img.onload = function() {
+ var canvas = document.createElement("canvas");
+ canvas.width = this.width;
+ canvas.height = this.height;
- function errorHandler() {
- thisImage.remove();
- resolve();
- }
- }.bind(this));
+ var ctx = canvas.getContext("2d");
+ ctx.drawImage(this, 0, 0);
- gd._promises.push(imagePromise);
- }
+ var dataURL = canvas.toDataURL("image/png");
- function applyAttributes(d) {
- var thisImage = d3.select(this);
+ thisImage.attr("xlink:href", dataURL);
+ };
- // Axes if specified
- var xa = Axes.getFromId(gd, d.xref),
- ya = Axes.getFromId(gd, d.yref);
+ thisImage.on("error", errorHandler);
+ thisImage.on("load", resolve);
- var size = fullLayout._size,
- width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w,
- height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h;
+ img.src = d.source;
- // Offsets for anchor positioning
- var xOffset = width * anchors.x[d.xanchor].offset,
- yOffset = height * anchors.y[d.yanchor].offset;
+ function errorHandler() {
+ thisImage.remove();
+ resolve();
+ }
+ }).bind(this)
+ );
- var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing;
+ gd._promises.push(imagePromise);
+ }
- // Final positions
- var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset,
- yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset;
+ function applyAttributes(d) {
+ var thisImage = d3.select(this);
+ // Axes if specified
+ var xa = Axes.getFromId(gd, d.xref), ya = Axes.getFromId(gd, d.yref);
- // Construct the proper aspectRatio attribute
- switch(d.sizing) {
- case 'fill':
- sizing += ' slice';
- break;
+ var size = fullLayout._size,
+ width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w,
+ height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h;
- case 'stretch':
- sizing = 'none';
- break;
- }
+ // Offsets for anchor positioning
+ var xOffset = width * anchors.x[d.xanchor].offset,
+ yOffset = height * anchors.y[d.yanchor].offset;
- thisImage.attr({
- x: xPos,
- y: yPos,
- width: width,
- height: height,
- preserveAspectRatio: sizing,
- opacity: d.opacity
- });
+ var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing;
+ // Final positions
+ var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) +
+ xOffset,
+ yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) +
+ yOffset;
- // Set proper clipping on images
- var xId = xa ? xa._id : '',
- yId = ya ? ya._id : '',
- clipAxes = xId + yId;
+ // Construct the proper aspectRatio attribute
+ switch (d.sizing) {
+ case "fill":
+ sizing += " slice";
+ break;
- if(clipAxes) {
- thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes);
- }
+ case "stretch":
+ sizing = "none";
+ break;
}
- var imagesBelow = fullLayout._imageLowerLayer.selectAll('image')
- .data(imageDataBelow),
- imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image')
- .data(imageDataSubplot),
- imagesAbove = fullLayout._imageUpperLayer.selectAll('image')
- .data(imageDataAbove);
-
- imagesBelow.enter().append('image');
- imagesSubplot.enter().append('image');
- imagesAbove.enter().append('image');
+ thisImage.attr({
+ x: xPos,
+ y: yPos,
+ width: width,
+ height: height,
+ preserveAspectRatio: sizing,
+ opacity: d.opacity
+ });
- imagesBelow.exit().remove();
- imagesSubplot.exit().remove();
- imagesAbove.exit().remove();
+ // Set proper clipping on images
+ var xId = xa ? xa._id : "", yId = ya ? ya._id : "", clipAxes = xId + yId;
- imagesBelow.each(function(d) {
- setImage.bind(this)(d);
- applyAttributes.bind(this)(d);
- });
- imagesSubplot.each(function(d) {
- setImage.bind(this)(d);
- applyAttributes.bind(this)(d);
- });
- imagesAbove.each(function(d) {
- setImage.bind(this)(d);
- applyAttributes.bind(this)(d);
- });
+ if (clipAxes) {
+ thisImage.call(Drawing.setClipUrl, "clip" + fullLayout._uid + clipAxes);
+ }
+ }
+
+ var imagesBelow = fullLayout._imageLowerLayer
+ .selectAll("image")
+ .data(imageDataBelow),
+ imagesSubplot = fullLayout._imageSubplotLayer
+ .selectAll("image")
+ .data(imageDataSubplot),
+ imagesAbove = fullLayout._imageUpperLayer
+ .selectAll("image")
+ .data(imageDataAbove);
+
+ imagesBelow.enter().append("image");
+ imagesSubplot.enter().append("image");
+ imagesAbove.enter().append("image");
+
+ imagesBelow.exit().remove();
+ imagesSubplot.exit().remove();
+ imagesAbove.exit().remove();
+
+ imagesBelow.each(function(d) {
+ setImage.bind(this)(d);
+ applyAttributes.bind(this)(d);
+ });
+ imagesSubplot.each(function(d) {
+ setImage.bind(this)(d);
+ applyAttributes.bind(this)(d);
+ });
+ imagesAbove.each(function(d) {
+ setImage.bind(this)(d);
+ applyAttributes.bind(this)(d);
+ });
};
diff --git a/src/components/images/index.js b/src/components/images/index.js
index d7ce308ae28..61363f699a7 100644
--- a/src/components/images/index.js
+++ b/src/components/images/index.js
@@ -5,15 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
- moduleType: 'component',
- name: 'images',
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- draw: require('./draw')
+ moduleType: "component",
+ name: "images",
+ layoutAttributes: require("./attributes"),
+ supplyLayoutDefaults: require("./defaults"),
+ draw: require("./draw")
};
diff --git a/src/components/legend/anchor_utils.js b/src/components/legend/anchor_utils.js
index 2dcc0161538..5975e6d623b 100644
--- a/src/components/legend/anchor_utils.js
+++ b/src/components/legend/anchor_utils.js
@@ -5,11 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
/**
* Determine the position anchor property of x/y xanchor/yanchor components.
*
@@ -19,29 +15,20 @@
*/
exports.isRightAnchor = function isRightAnchor(opts) {
- return (
- opts.xanchor === 'right' ||
- (opts.xanchor === 'auto' && opts.x >= 2 / 3)
- );
+ return opts.xanchor === "right" || opts.xanchor === "auto" && opts.x >= 2 / 3;
};
exports.isCenterAnchor = function isCenterAnchor(opts) {
- return (
- opts.xanchor === 'center' ||
- (opts.xanchor === 'auto' && opts.x > 1 / 3 && opts.x < 2 / 3)
- );
+ return opts.xanchor === "center" ||
+ opts.xanchor === "auto" && opts.x > 1 / 3 && opts.x < 2 / 3;
};
exports.isBottomAnchor = function isBottomAnchor(opts) {
- return (
- opts.yanchor === 'bottom' ||
- (opts.yanchor === 'auto' && opts.y <= 1 / 3)
- );
+ return opts.yanchor === "bottom" ||
+ opts.yanchor === "auto" && opts.y <= 1 / 3;
};
exports.isMiddleAnchor = function isMiddleAnchor(opts) {
- return (
- opts.yanchor === 'middle' ||
- (opts.yanchor === 'auto' && opts.y > 1 / 3 && opts.y < 2 / 3)
- );
+ return opts.yanchor === "middle" ||
+ opts.yanchor === "auto" && opts.y > 1 / 3 && opts.y < 2 / 3;
};
diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js
index 8ae61ac29be..77a1825dbb1 100644
--- a/src/components/legend/attributes.js
+++ b/src/components/legend/attributes.js
@@ -5,109 +5,102 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var fontAttrs = require('../../plots/font_attributes');
-var colorAttrs = require('../color/attributes');
-var extendFlat = require('../../lib/extend').extendFlat;
-
+"use strict";
+var fontAttrs = require("../../plots/font_attributes");
+var colorAttrs = require("../color/attributes");
+var extendFlat = require("../../lib/extend").extendFlat;
module.exports = {
- bgcolor: {
- valType: 'color',
- role: 'style',
- description: 'Sets the legend background color.'
- },
- bordercolor: {
- valType: 'color',
- dflt: colorAttrs.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({}, fontAttrs, {
- description: 'Sets the font used to text the legend items.'
- }),
- orientation: {
- valType: 'enumerated',
- values: ['v', 'h'],
- dflt: 'v',
- role: 'info',
- description: 'Sets the orientation of the legend.'
- },
- 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(' ')
- }
+ bgcolor: {
+ valType: "color",
+ role: "style",
+ description: "Sets the legend background color."
+ },
+ bordercolor: {
+ valType: "color",
+ dflt: colorAttrs.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({}, fontAttrs, {
+ description: "Sets the font used to text the legend items."
+ }),
+ orientation: {
+ valType: "enumerated",
+ values: ["v", "h"],
+ dflt: "v",
+ role: "info",
+ description: "Sets the orientation of the legend."
+ },
+ 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/components/legend/constants.js b/src/components/legend/constants.js
index 527fa7ba190..344c828f6bf 100644
--- a/src/components/legend/constants.js
+++ b/src/components/legend/constants.js
@@ -5,12 +5,10 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
- scrollBarWidth: 4,
- scrollBarHeight: 20,
- scrollBarColor: '#808BA4',
- scrollBarMargin: 4
+ scrollBarWidth: 4,
+ scrollBarHeight: 20,
+ scrollBarColor: "#808BA4",
+ scrollBarMargin: 4
};
diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js
index a094d5799ba..b430f38e6ea 100644
--- a/src/components/legend/defaults.js
+++ b/src/components/legend/defaults.js
@@ -5,87 +5,90 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Registry = require("../../registry");
+var Lib = require("../../lib");
-
-'use strict';
-
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-
-var attributes = require('./attributes');
-var basePlotLayoutAttributes = require('../../plots/layout_attributes');
-var helpers = require('./helpers');
-
+var attributes = require("./attributes");
+var basePlotLayoutAttributes = require("../../plots/layout_attributes");
+var helpers = require("./helpers");
module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
- var containerIn = layoutIn.legend || {},
- containerOut = layoutOut.legend = {};
-
- var visibleTraces = 0,
- defaultOrder = 'normal',
- defaultX,
- defaultY,
- defaultXAnchor,
- defaultYAnchor;
-
- for(var i = 0; i < fullData.length; i++) {
- var trace = fullData[i];
-
- if(helpers.legendGetsTrace(trace)) {
- visibleTraces++;
- // always show the legend by default if there's a pie
- if(Registry.traceIs(trace, 'pie')) visibleTraces++;
- }
-
- if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') ||
- ['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) {
- defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ?
- 'grouped+reversed' : 'reversed';
- }
-
- if(trace.legendgroup !== undefined && trace.legendgroup !== '') {
- defaultOrder = helpers.isReversed({traceorder: defaultOrder}) ?
- 'reversed+grouped' : 'grouped';
- }
+ var containerIn = layoutIn.legend || {}, containerOut = layoutOut.legend = {};
+
+ var visibleTraces = 0,
+ defaultOrder = "normal",
+ defaultX,
+ defaultY,
+ defaultXAnchor,
+ defaultYAnchor;
+
+ for (var i = 0; i < fullData.length; i++) {
+ var trace = fullData[i];
+
+ if (helpers.legendGetsTrace(trace)) {
+ visibleTraces++;
+ // always show the legend by default if there's a pie
+ if (Registry.traceIs(trace, "pie")) visibleTraces++;
}
- function coerce(attr, dflt) {
- return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
+ if (
+ Registry.traceIs(trace, "bar") && layoutOut.barmode === "stack" ||
+ ["tonextx", "tonexty"].indexOf(trace.fill) !== -1
+ ) {
+ defaultOrder = helpers.isGrouped({ traceorder: defaultOrder })
+ ? "grouped+reversed"
+ : "reversed";
}
- var showLegend = Lib.coerce(layoutIn, layoutOut,
- basePlotLayoutAttributes, 'showlegend', visibleTraces > 1);
-
- if(showLegend === false) return;
-
- coerce('bgcolor', layoutOut.paper_bgcolor);
- coerce('bordercolor');
- coerce('borderwidth');
- Lib.coerceFont(coerce, 'font', layoutOut.font);
-
- coerce('orientation');
- if(containerOut.orientation === 'h') {
- var xaxis = layoutIn.xaxis;
- if(xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) {
- defaultX = 0;
- defaultXAnchor = 'left';
- defaultY = 1.1;
- defaultYAnchor = 'bottom';
- }
- else {
- defaultX = 0;
- defaultXAnchor = 'left';
- defaultY = -0.1;
- defaultYAnchor = 'top';
- }
+ if (trace.legendgroup !== undefined && trace.legendgroup !== "") {
+ defaultOrder = helpers.isReversed({ traceorder: defaultOrder })
+ ? "reversed+grouped"
+ : "grouped";
+ }
+ }
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
+ }
+
+ var showLegend = Lib.coerce(
+ layoutIn,
+ layoutOut,
+ basePlotLayoutAttributes,
+ "showlegend",
+ visibleTraces > 1
+ );
+
+ if (showLegend === false) return;
+
+ coerce("bgcolor", layoutOut.paper_bgcolor);
+ coerce("bordercolor");
+ coerce("borderwidth");
+ Lib.coerceFont(coerce, "font", layoutOut.font);
+
+ coerce("orientation");
+ if (containerOut.orientation === "h") {
+ var xaxis = layoutIn.xaxis;
+ if (xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) {
+ defaultX = 0;
+ defaultXAnchor = "left";
+ defaultY = 1.1;
+ defaultYAnchor = "bottom";
+ } else {
+ defaultX = 0;
+ defaultXAnchor = "left";
+ defaultY = -0.1;
+ defaultYAnchor = "top";
}
+ }
- coerce('traceorder', defaultOrder);
- if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap');
+ coerce("traceorder", defaultOrder);
+ if (helpers.isGrouped(layoutOut.legend)) coerce("tracegroupgap");
- coerce('x', defaultX);
- coerce('xanchor', defaultXAnchor);
- coerce('y', defaultY);
- coerce('yanchor', defaultYAnchor);
- Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);
+ coerce("x", defaultX);
+ coerce("xanchor", defaultXAnchor);
+ coerce("y", defaultY);
+ coerce("yanchor", defaultYAnchor);
+ Lib.noneOrAll(containerIn, containerOut, ["x", "y"]);
};
diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js
index d6c836c14e1..d6cf15ea24a 100644
--- a/src/components/legend/draw.js
+++ b/src/components/legend/draw.js
@@ -5,710 +5,677 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+
+var Plotly = require("../../plotly");
+var Lib = require("../../lib");
+var Plots = require("../../plots/plots");
+var Registry = require("../../registry");
+var dragElement = require("../dragelement");
+var Drawing = require("../drawing");
+var Color = require("../color");
+var svgTextUtils = require("../../lib/svg_text_utils");
+
+var constants = require("./constants");
+var getLegendData = require("./get_legend_data");
+var style = require("./style");
+var helpers = require("./helpers");
+var anchorUtils = require("./anchor_utils");
+module.exports = function draw(gd) {
+ var fullLayout = gd._fullLayout;
+ var clipId = "legend" + fullLayout._uid;
-'use strict';
-
-var d3 = require('d3');
-
-var Plotly = require('../../plotly');
-var Lib = require('../../lib');
-var Plots = require('../../plots/plots');
-var Registry = require('../../registry');
-var dragElement = require('../dragelement');
-var Drawing = require('../drawing');
-var Color = require('../color');
-var svgTextUtils = require('../../lib/svg_text_utils');
+ if (!fullLayout._infolayer || !gd.calcdata) return;
-var constants = require('./constants');
-var getLegendData = require('./get_legend_data');
-var style = require('./style');
-var helpers = require('./helpers');
-var anchorUtils = require('./anchor_utils');
+ var opts = fullLayout.legend,
+ legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts),
+ hiddenSlices = fullLayout.hiddenlabels || [];
+ if (!fullLayout.showlegend || !legendData.length) {
+ fullLayout._infolayer.selectAll(".legend").remove();
+ fullLayout._topdefs.select("#" + clipId).remove();
-module.exports = function draw(gd) {
- var fullLayout = gd._fullLayout;
- var clipId = 'legend' + fullLayout._uid;
+ Plots.autoMargin(gd, "legend");
+ return;
+ }
- if(!fullLayout._infolayer || !gd.calcdata) return;
+ var legend = fullLayout._infolayer.selectAll("g.legend").data([0]);
- var opts = fullLayout.legend,
- legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts),
- hiddenSlices = fullLayout.hiddenlabels || [];
+ legend.enter().append("g").attr({ class: "legend", "pointer-events": "all" });
- if(!fullLayout.showlegend || !legendData.length) {
- fullLayout._infolayer.selectAll('.legend').remove();
- fullLayout._topdefs.select('#' + clipId).remove();
+ var clipPath = fullLayout._topdefs.selectAll("#" + clipId).data([0]);
- Plots.autoMargin(gd, 'legend');
- return;
- }
+ clipPath.enter().append("clipPath").attr("id", clipId).append("rect");
- var legend = fullLayout._infolayer.selectAll('g.legend')
- .data([0]);
+ var bg = legend.selectAll("rect.bg").data([0]);
- legend.enter().append('g')
- .attr({
- 'class': 'legend',
- 'pointer-events': 'all'
- });
+ bg
+ .enter()
+ .append("rect")
+ .attr({ class: "bg", "shape-rendering": "crispEdges" });
- var clipPath = fullLayout._topdefs.selectAll('#' + clipId)
- .data([0]);
+ bg.call(Color.stroke, opts.bordercolor);
+ bg.call(Color.fill, opts.bgcolor);
+ bg.style("stroke-width", opts.borderwidth + "px");
- clipPath.enter().append('clipPath')
- .attr('id', clipId)
- .append('rect');
+ var scrollBox = legend.selectAll("g.scrollbox").data([0]);
- var bg = legend.selectAll('rect.bg')
- .data([0]);
+ scrollBox.enter().append("g").attr("class", "scrollbox");
- bg.enter().append('rect').attr({
- 'class': 'bg',
- 'shape-rendering': 'crispEdges'
- });
+ var scrollBar = legend.selectAll("rect.scrollbar").data([0]);
- bg.call(Color.stroke, opts.bordercolor);
- bg.call(Color.fill, opts.bgcolor);
- bg.style('stroke-width', opts.borderwidth + 'px');
-
- var scrollBox = legend.selectAll('g.scrollbox')
- .data([0]);
-
- scrollBox.enter().append('g')
- .attr('class', 'scrollbox');
-
- var scrollBar = legend.selectAll('rect.scrollbar')
- .data([0]);
-
- scrollBar.enter().append('rect')
- .attr({
- 'class': 'scrollbar',
- 'rx': 20,
- 'ry': 2,
- 'width': 0,
- 'height': 0
- })
- .call(Color.fill, '#808BA4');
-
- var groups = scrollBox.selectAll('g.groups')
- .data(legendData);
-
- groups.enter().append('g')
- .attr('class', 'groups');
-
- groups.exit().remove();
-
- var traces = groups.selectAll('g.traces')
- .data(Lib.identity);
-
- traces.enter().append('g').attr('class', 'traces');
- traces.exit().remove();
-
- traces.call(style)
- .style('opacity', function(d) {
- var trace = d[0].trace;
- if(Registry.traceIs(trace, 'pie')) {
- return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1;
- } else {
- return trace.visible === 'legendonly' ? 0.5 : 1;
- }
- })
- .each(function() {
- d3.select(this)
- .call(drawTexts, gd)
- .call(setupTraceToggle, gd);
- });
-
- var firstRender = legend.enter().size() !== 0;
- if(firstRender) {
- computeLegendDimensions(gd, groups, traces);
- expandMargin(gd);
- }
+ scrollBar
+ .enter()
+ .append("rect")
+ .attr({ class: "scrollbar", rx: 20, ry: 2, width: 0, height: 0 })
+ .call(Color.fill, "#808BA4");
- // Position and size the legend
- var lxMin = 0,
- lxMax = fullLayout.width,
- lyMin = 0,
- lyMax = fullLayout.height;
+ var groups = scrollBox.selectAll("g.groups").data(legendData);
- computeLegendDimensions(gd, groups, traces);
+ groups.enter().append("g").attr("class", "groups");
- if(opts.height > lyMax) {
- // If the legend doesn't fit in the plot area,
- // do not expand the vertical margins.
- expandHorizontalMargin(gd);
- } else {
- expandMargin(gd);
- }
+ groups.exit().remove();
- // Scroll section must be executed after repositionLegend.
- // It requires the legend width, height, x and y to position the scrollbox
- // and these values are mutated in repositionLegend.
- var gs = fullLayout._size,
- lx = gs.l + gs.w * opts.x,
- ly = gs.t + gs.h * (1 - opts.y);
+ var traces = groups.selectAll("g.traces").data(Lib.identity);
- if(anchorUtils.isRightAnchor(opts)) {
- lx -= opts.width;
- }
- else if(anchorUtils.isCenterAnchor(opts)) {
- lx -= opts.width / 2;
- }
+ traces.enter().append("g").attr("class", "traces");
+ traces.exit().remove();
- if(anchorUtils.isBottomAnchor(opts)) {
- ly -= opts.height;
- }
- else if(anchorUtils.isMiddleAnchor(opts)) {
- ly -= opts.height / 2;
- }
-
- // Make sure the legend left and right sides are visible
- var legendWidth = opts.width,
- legendWidthMax = gs.w;
+ traces
+ .call(style)
+ .style("opacity", function(d) {
+ var trace = d[0].trace;
+ if (Registry.traceIs(trace, "pie")) {
+ return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1;
+ } else {
+ return trace.visible === "legendonly" ? 0.5 : 1;
+ }
+ })
+ .each(function() {
+ d3.select(this).call(drawTexts, gd).call(setupTraceToggle, gd);
+ });
- if(legendWidth > legendWidthMax) {
- lx = gs.l;
- legendWidth = legendWidthMax;
- }
- else {
- if(lx + legendWidth > lxMax) lx = lxMax - legendWidth;
- if(lx < lxMin) lx = lxMin;
- legendWidth = Math.min(lxMax - lx, opts.width);
- }
+ var firstRender = legend.enter().size() !== 0;
+ if (firstRender) {
+ computeLegendDimensions(gd, groups, traces);
+ expandMargin(gd);
+ }
+
+ // Position and size the legend
+ var lxMin = 0, lxMax = fullLayout.width, lyMin = 0, lyMax = fullLayout.height;
+
+ computeLegendDimensions(gd, groups, traces);
+
+ if (opts.height > lyMax) {
+ // If the legend doesn't fit in the plot area,
+ // do not expand the vertical margins.
+ expandHorizontalMargin(gd);
+ } else {
+ expandMargin(gd);
+ }
+
+ // Scroll section must be executed after repositionLegend.
+ // It requires the legend width, height, x and y to position the scrollbox
+ // and these values are mutated in repositionLegend.
+ var gs = fullLayout._size,
+ lx = gs.l + gs.w * opts.x,
+ ly = gs.t + gs.h * (1 - opts.y);
+
+ if (anchorUtils.isRightAnchor(opts)) {
+ lx -= opts.width;
+ } else if (anchorUtils.isCenterAnchor(opts)) {
+ lx -= opts.width / 2;
+ }
+
+ if (anchorUtils.isBottomAnchor(opts)) {
+ ly -= opts.height;
+ } else if (anchorUtils.isMiddleAnchor(opts)) {
+ ly -= opts.height / 2;
+ }
+
+ // Make sure the legend left and right sides are visible
+ var legendWidth = opts.width, legendWidthMax = gs.w;
+
+ if (legendWidth > legendWidthMax) {
+ lx = gs.l;
+ legendWidth = legendWidthMax;
+ } else {
+ if (lx + legendWidth > lxMax) lx = lxMax - legendWidth;
+ if (lx < lxMin) lx = lxMin;
+ legendWidth = Math.min(lxMax - lx, opts.width);
+ }
+
+ // Make sure the legend top and bottom are visible
+ // (legends with a scroll bar are not allowed to stretch beyond the extended
+ // margins)
+ var legendHeight = opts.height, legendHeightMax = gs.h;
+
+ if (legendHeight > legendHeightMax) {
+ ly = gs.t;
+ legendHeight = legendHeightMax;
+ } else {
+ if (ly + legendHeight > lyMax) ly = lyMax - legendHeight;
+ if (ly < lyMin) ly = lyMin;
+ legendHeight = Math.min(lyMax - ly, opts.height);
+ }
+
+ // Set size and position of all the elements that make up a legend:
+ // legend, background and border, scroll box and scroll bar
+ Drawing.setTranslate(legend, lx, ly);
+
+ var scrollBarYMax = legendHeight -
+ constants.scrollBarHeight -
+ 2 * constants.scrollBarMargin,
+ scrollBoxYMax = opts.height - legendHeight,
+ scrollBarY,
+ scrollBoxY;
+
+ if (opts.height <= legendHeight || gd._context.staticPlot) {
+ // if scrollbar should not be shown.
+ bg.attr({
+ width: legendWidth - opts.borderwidth,
+ height: legendHeight - opts.borderwidth,
+ x: opts.borderwidth / 2,
+ y: opts.borderwidth / 2
+ });
- // Make sure the legend top and bottom are visible
- // (legends with a scroll bar are not allowed to stretch beyond the extended
- // margins)
- var legendHeight = opts.height,
- legendHeightMax = gs.h;
+ Drawing.setTranslate(scrollBox, 0, 0);
- if(legendHeight > legendHeightMax) {
- ly = gs.t;
- legendHeight = legendHeightMax;
- }
- else {
- if(ly + legendHeight > lyMax) ly = lyMax - legendHeight;
- if(ly < lyMin) ly = lyMin;
- legendHeight = Math.min(lyMax - ly, opts.height);
- }
+ clipPath.select("rect").attr({
+ width: legendWidth - 2 * opts.borderwidth,
+ height: legendHeight - 2 * opts.borderwidth,
+ x: opts.borderwidth,
+ y: opts.borderwidth
+ });
- // Set size and position of all the elements that make up a legend:
- // legend, background and border, scroll box and scroll bar
- Drawing.setTranslate(legend, lx, ly);
-
- var scrollBarYMax = legendHeight -
- constants.scrollBarHeight -
- 2 * constants.scrollBarMargin,
- scrollBoxYMax = opts.height - legendHeight,
- scrollBarY,
- scrollBoxY;
-
- if(opts.height <= legendHeight || gd._context.staticPlot) {
- // if scrollbar should not be shown.
- bg.attr({
- width: legendWidth - opts.borderwidth,
- height: legendHeight - opts.borderwidth,
- x: opts.borderwidth / 2,
- y: opts.borderwidth / 2
- });
-
- Drawing.setTranslate(scrollBox, 0, 0);
-
- clipPath.select('rect').attr({
- width: legendWidth - 2 * opts.borderwidth,
- height: legendHeight - 2 * opts.borderwidth,
- x: opts.borderwidth,
- y: opts.borderwidth
- });
-
- scrollBox.call(Drawing.setClipUrl, clipId);
- }
- else {
- scrollBarY = constants.scrollBarMargin,
- scrollBoxY = scrollBox.attr('data-scroll') || 0;
-
- // increase the background and clip-path width
- // by the scrollbar width and margin
- bg.attr({
- width: legendWidth -
- 2 * opts.borderwidth +
- constants.scrollBarWidth +
- constants.scrollBarMargin,
- height: legendHeight - opts.borderwidth,
- x: opts.borderwidth / 2,
- y: opts.borderwidth / 2
- });
-
- clipPath.select('rect').attr({
- width: legendWidth -
- 2 * opts.borderwidth +
- constants.scrollBarWidth +
- constants.scrollBarMargin,
- height: legendHeight - 2 * opts.borderwidth,
- x: opts.borderwidth,
- y: opts.borderwidth - scrollBoxY
- });
-
- scrollBox.call(Drawing.setClipUrl, clipId);
-
- if(firstRender) scrollHandler(scrollBarY, scrollBoxY);
-
- legend.on('wheel', null); // to be safe, remove previous listeners
- legend.on('wheel', function() {
- scrollBoxY = Lib.constrain(
- scrollBox.attr('data-scroll') -
- d3.event.deltaY / scrollBarYMax * scrollBoxYMax,
- -scrollBoxYMax, 0);
- scrollBarY = constants.scrollBarMargin -
- scrollBoxY / scrollBoxYMax * scrollBarYMax;
- scrollHandler(scrollBarY, scrollBoxY);
- d3.event.preventDefault();
- });
-
- // to be safe, remove previous listeners
- scrollBar.on('.drag', null);
- scrollBox.on('.drag', null);
-
- var drag = d3.behavior.drag().on('drag', function() {
- scrollBarY = Lib.constrain(
- d3.event.y - constants.scrollBarHeight / 2,
- constants.scrollBarMargin,
- constants.scrollBarMargin + scrollBarYMax);
- scrollBoxY = - (scrollBarY - constants.scrollBarMargin) /
- scrollBarYMax * scrollBoxYMax;
- scrollHandler(scrollBarY, scrollBoxY);
- });
-
- scrollBar.call(drag);
- scrollBox.call(drag);
- }
+ scrollBox.call(Drawing.setClipUrl, clipId);
+ } else {
+ scrollBarY = constants.scrollBarMargin, scrollBoxY = scrollBox.attr(
+ "data-scroll"
+ ) ||
+ 0;
+
+ // increase the background and clip-path width
+ // by the scrollbar width and margin
+ bg.attr({
+ width: (
+ legendWidth -
+ 2 * opts.borderwidth +
+ constants.scrollBarWidth +
+ constants.scrollBarMargin
+ ),
+ height: legendHeight - opts.borderwidth,
+ x: opts.borderwidth / 2,
+ y: opts.borderwidth / 2
+ });
+ clipPath.select("rect").attr({
+ width: (
+ legendWidth -
+ 2 * opts.borderwidth +
+ constants.scrollBarWidth +
+ constants.scrollBarMargin
+ ),
+ height: legendHeight - 2 * opts.borderwidth,
+ x: opts.borderwidth,
+ y: opts.borderwidth - scrollBoxY
+ });
- function scrollHandler(scrollBarY, scrollBoxY) {
- scrollBox
- .attr('data-scroll', scrollBoxY)
- .call(Drawing.setTranslate, 0, scrollBoxY);
+ scrollBox.call(Drawing.setClipUrl, clipId);
+
+ if (firstRender) scrollHandler(scrollBarY, scrollBoxY);
+
+ legend.on("wheel", null);
+ // to be safe, remove previous listeners
+ legend.on("wheel", function() {
+ scrollBoxY = Lib.constrain(
+ scrollBox.attr("data-scroll") -
+ d3.event.deltaY / scrollBarYMax * scrollBoxYMax,
+ -scrollBoxYMax,
+ 0
+ );
+ scrollBarY = constants.scrollBarMargin -
+ scrollBoxY / scrollBoxYMax * scrollBarYMax;
+ scrollHandler(scrollBarY, scrollBoxY);
+ d3.event.preventDefault();
+ });
- scrollBar.call(
- Drawing.setRect,
- legendWidth,
- scrollBarY,
- constants.scrollBarWidth,
- constants.scrollBarHeight
- );
- clipPath.select('rect').attr({
- y: opts.borderwidth - scrollBoxY
- });
- }
+ // to be safe, remove previous listeners
+ scrollBar.on(".drag", null);
+ scrollBox.on(".drag", null);
+
+ var drag = d3.behavior.drag().on("drag", function() {
+ scrollBarY = Lib.constrain(
+ d3.event.y - constants.scrollBarHeight / 2,
+ constants.scrollBarMargin,
+ constants.scrollBarMargin + scrollBarYMax
+ );
+ scrollBoxY = (-(scrollBarY - constants.scrollBarMargin)) /
+ scrollBarYMax *
+ scrollBoxYMax;
+ scrollHandler(scrollBarY, scrollBoxY);
+ });
- if(gd._context.editable) {
- var xf, yf, x0, y0;
-
- legend.classed('cursor-move', true);
-
- dragElement.init({
- element: legend.node(),
- prepFn: function() {
- var transform = Drawing.getTranslate(legend);
-
- x0 = transform.x;
- y0 = transform.y;
- },
- moveFn: function(dx, dy) {
- var newX = x0 + dx,
- newY = y0 + dy;
-
- Drawing.setTranslate(legend, newX, newY);
-
- xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor);
- yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor);
- },
- doneFn: function(dragged) {
- if(dragged && xf !== undefined && yf !== undefined) {
- Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf});
- }
- }
- });
- }
+ scrollBar.call(drag);
+ scrollBox.call(drag);
+ }
+
+ function scrollHandler(scrollBarY, scrollBoxY) {
+ scrollBox
+ .attr("data-scroll", scrollBoxY)
+ .call(Drawing.setTranslate, 0, scrollBoxY);
+
+ scrollBar.call(
+ Drawing.setRect,
+ legendWidth,
+ scrollBarY,
+ constants.scrollBarWidth,
+ constants.scrollBarHeight
+ );
+ clipPath.select("rect").attr({ y: opts.borderwidth - scrollBoxY });
+ }
+
+ if (gd._context.editable) {
+ var xf, yf, x0, y0;
+
+ legend.classed("cursor-move", true);
+
+ dragElement.init({
+ element: legend.node(),
+ prepFn: function() {
+ var transform = Drawing.getTranslate(legend);
+
+ x0 = transform.x;
+ y0 = transform.y;
+ },
+ moveFn: function(dx, dy) {
+ var newX = x0 + dx, newY = y0 + dy;
+
+ Drawing.setTranslate(legend, newX, newY);
+
+ xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor);
+ yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor);
+ },
+ doneFn: function(dragged) {
+ if (dragged && xf !== undefined && yf !== undefined) {
+ Plotly.relayout(gd, { "legend.x": xf, "legend.y": yf });
+ }
+ }
+ });
+ }
};
function drawTexts(g, gd) {
- var legendItem = g.data()[0][0],
- fullLayout = gd._fullLayout,
- trace = legendItem.trace,
- isPie = Registry.traceIs(trace, 'pie'),
- traceIndex = trace.index,
- name = isPie ? legendItem.label : trace.name;
-
- var text = g.selectAll('text.legendtext')
- .data([0]);
- text.enter().append('text').classed('legendtext', true);
- text.attr({
- x: 40,
- y: 0,
- 'data-unformatted': name
- })
- .style('text-anchor', 'start')
- .classed('user-select-none', true)
+ var legendItem = g.data()[0][0],
+ fullLayout = gd._fullLayout,
+ trace = legendItem.trace,
+ isPie = Registry.traceIs(trace, "pie"),
+ traceIndex = trace.index,
+ name = isPie ? legendItem.label : trace.name;
+
+ var text = g.selectAll("text.legendtext").data([0]);
+ text.enter().append("text").classed("legendtext", true);
+ text
+ .attr({ x: 40, y: 0, "data-unformatted": name })
+ .style("text-anchor", "start")
+ .classed("user-select-none", true)
.call(Drawing.font, fullLayout.legend.font)
.text(name);
- function textLayout(s) {
- svgTextUtils.convertToTspans(s, function() {
- s.selectAll('tspan.line').attr({x: s.attr('x')});
- g.call(computeTextDimensions, gd);
- });
- }
+ function textLayout(s) {
+ svgTextUtils.convertToTspans(s, function() {
+ s.selectAll("tspan.line").attr({ x: s.attr("x") });
+ g.call(computeTextDimensions, gd);
+ });
+ }
- if(gd._context.editable && !isPie) {
- text.call(svgTextUtils.makeEditable)
- .call(textLayout)
- .on('edit', function(text) {
- this.attr({'data-unformatted': text});
+ if (gd._context.editable && !isPie) {
+ text
+ .call(svgTextUtils.makeEditable)
+ .call(textLayout)
+ .on("edit", function(text) {
+ this.attr({ "data-unformatted": text });
- this.text(text)
- .call(textLayout);
+ this.text(text).call(textLayout);
- if(!this.text()) text = ' \u0020\u0020 ';
+ if (!this.text()) text = " ";
- var fullInput = legendItem.trace._fullInput || {},
- astr;
+ var fullInput = legendItem.trace._fullInput || {}, astr;
- // N.B. this block isn't super clean,
- // is unfortunately untested at the moment,
- // and only works for for 'ohlc' and 'candlestick',
- // but should be generalized for other one-to-many transforms
- if(['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) {
- var transforms = legendItem.trace.transforms,
- direction = transforms[transforms.length - 1].direction;
+ // N.B. this block isn't super clean,
+ // is unfortunately untested at the moment,
+ // and only works for for 'ohlc' and 'candlestick',
+ // but should be generalized for other one-to-many transforms
+ if (["ohlc", "candlestick"].indexOf(fullInput.type) !== -1) {
+ var transforms = legendItem.trace.transforms,
+ direction = transforms[transforms.length - 1].direction;
- astr = direction + '.name';
- }
- else astr = 'name';
+ astr = direction + ".name";
+ } else {
+ astr = "name";
+ }
- Plotly.restyle(gd, astr, text, traceIndex);
- });
- }
- else text.call(textLayout);
+ Plotly.restyle(gd, astr, text, traceIndex);
+ });
+ } else {
+ text.call(textLayout);
+ }
}
function setupTraceToggle(g, gd) {
- var hiddenSlices = gd._fullLayout.hiddenlabels ?
- gd._fullLayout.hiddenlabels.slice() :
- [];
-
- var traceToggle = g.selectAll('rect')
- .data([0]);
-
- traceToggle.enter().append('rect')
- .classed('legendtoggle', true)
- .style('cursor', 'pointer')
- .attr('pointer-events', 'all')
- .call(Color.fill, 'rgba(0,0,0,0)');
-
- traceToggle.on('click', function() {
- if(gd._dragged) return;
-
- var legendItem = g.data()[0][0],
- fullData = gd._fullData,
- trace = legendItem.trace,
- legendgroup = trace.legendgroup,
- traceIndicesInGroup = [],
- tracei,
- newVisible;
-
- if(Registry.traceIs(trace, 'pie')) {
- var thisLabel = legendItem.label,
- thisLabelIndex = hiddenSlices.indexOf(thisLabel);
-
- if(thisLabelIndex === -1) hiddenSlices.push(thisLabel);
- else hiddenSlices.splice(thisLabelIndex, 1);
-
- Plotly.relayout(gd, 'hiddenlabels', hiddenSlices);
- } else {
- if(legendgroup === '') {
- traceIndicesInGroup = [trace.index];
- } else {
- for(var i = 0; i < fullData.length; i++) {
- tracei = fullData[i];
- if(tracei.legendgroup === legendgroup) {
- traceIndicesInGroup.push(tracei.index);
- }
- }
- }
-
- newVisible = trace.visible === true ? 'legendonly' : true;
- Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup);
- }
- });
-}
+ var hiddenSlices = gd._fullLayout.hiddenlabels
+ ? gd._fullLayout.hiddenlabels.slice()
+ : [];
+
+ var traceToggle = g.selectAll("rect").data([0]);
+
+ traceToggle
+ .enter()
+ .append("rect")
+ .classed("legendtoggle", true)
+ .style("cursor", "pointer")
+ .attr("pointer-events", "all")
+ .call(Color.fill, "rgba(0,0,0,0)");
+
+ traceToggle.on("click", function() {
+ if (gd._dragged) return;
-function computeTextDimensions(g, gd) {
var legendItem = g.data()[0][0],
- mathjaxGroup = g.select('g[class*=math-group]'),
- opts = gd._fullLayout.legend,
- lineHeight = opts.font.size * 1.3,
- height,
- width;
-
- if(!legendItem.trace.showlegend) {
- g.remove();
- return;
- }
+ fullData = gd._fullData,
+ trace = legendItem.trace,
+ legendgroup = trace.legendgroup,
+ traceIndicesInGroup = [],
+ tracei,
+ newVisible;
- if(mathjaxGroup.node()) {
- var mathjaxBB = Drawing.bBox(mathjaxGroup.node());
+ if (Registry.traceIs(trace, "pie")) {
+ var thisLabel = legendItem.label,
+ thisLabelIndex = hiddenSlices.indexOf(thisLabel);
- height = mathjaxBB.height;
- width = mathjaxBB.width;
+ if (thisLabelIndex === -1) hiddenSlices.push(thisLabel);
+ else hiddenSlices.splice(thisLabelIndex, 1);
- Drawing.setTranslate(mathjaxGroup, 0, (height / 4));
- }
- else {
- var text = g.selectAll('.legendtext'),
- textSpans = g.selectAll('.legendtext>tspan'),
- textLines = textSpans[0].length || 1;
-
- height = lineHeight * textLines;
- width = text.node() && Drawing.bBox(text.node()).width;
-
- // approximation to height offset to center the font
- // to avoid getBoundingClientRect
- var textY = lineHeight * (0.3 + (1 - textLines) / 2);
- text.attr('y', textY);
- textSpans.attr('y', textY);
- }
+ Plotly.relayout(gd, "hiddenlabels", hiddenSlices);
+ } else {
+ if (legendgroup === "") {
+ traceIndicesInGroup = [trace.index];
+ } else {
+ for (var i = 0; i < fullData.length; i++) {
+ tracei = fullData[i];
+ if (tracei.legendgroup === legendgroup) {
+ traceIndicesInGroup.push(tracei.index);
+ }
+ }
+ }
- height = Math.max(height, 16) + 3;
+ newVisible = trace.visible === true ? "legendonly" : true;
+ Plotly.restyle(gd, "visible", newVisible, traceIndicesInGroup);
+ }
+ });
+}
- legendItem.height = height;
- legendItem.width = width;
+function computeTextDimensions(g, gd) {
+ var legendItem = g.data()[0][0],
+ mathjaxGroup = g.select("g[class*=math-group]"),
+ opts = gd._fullLayout.legend,
+ lineHeight = opts.font.size * 1.3,
+ height,
+ width;
+
+ if (!legendItem.trace.showlegend) {
+ g.remove();
+ return;
+ }
+
+ if (mathjaxGroup.node()) {
+ var mathjaxBB = Drawing.bBox(mathjaxGroup.node());
+
+ height = mathjaxBB.height;
+ width = mathjaxBB.width;
+
+ Drawing.setTranslate(mathjaxGroup, 0, height / 4);
+ } else {
+ var text = g.selectAll(".legendtext"),
+ textSpans = g.selectAll(".legendtext>tspan"),
+ textLines = textSpans[0].length || 1;
+
+ height = lineHeight * textLines;
+ width = text.node() && Drawing.bBox(text.node()).width;
+
+ // approximation to height offset to center the font
+ // to avoid getBoundingClientRect
+ var textY = lineHeight * (0.3 + (1 - textLines) / 2);
+ text.attr("y", textY);
+ textSpans.attr("y", textY);
+ }
+
+ height = Math.max(height, 16) + 3;
+
+ legendItem.height = height;
+ legendItem.width = width;
}
function computeLegendDimensions(gd, groups, traces) {
- var fullLayout = gd._fullLayout,
- opts = fullLayout.legend,
- borderwidth = opts.borderwidth,
- isGrouped = helpers.isGrouped(opts);
-
- if(helpers.isVertical(opts)) {
- if(isGrouped) {
- groups.each(function(d, i) {
- Drawing.setTranslate(this, 0, i * opts.tracegroupgap);
- });
- }
-
- opts.width = 0;
- opts.height = 0;
+ var fullLayout = gd._fullLayout,
+ opts = fullLayout.legend,
+ borderwidth = opts.borderwidth,
+ isGrouped = helpers.isGrouped(opts);
+
+ if (helpers.isVertical(opts)) {
+ if (isGrouped) {
+ groups.each(function(d, i) {
+ Drawing.setTranslate(this, 0, i * opts.tracegroupgap);
+ });
+ }
- traces.each(function(d) {
- var legendItem = d[0],
- textHeight = legendItem.height,
- textWidth = legendItem.width;
+ opts.width = 0;
+ opts.height = 0;
- Drawing.setTranslate(this,
- borderwidth,
- (5 + borderwidth + opts.height + textHeight / 2));
+ traces.each(function(d) {
+ var legendItem = d[0],
+ textHeight = legendItem.height,
+ textWidth = legendItem.width;
- opts.height += textHeight;
- opts.width = Math.max(opts.width, textWidth);
- });
+ Drawing.setTranslate(
+ this,
+ borderwidth,
+ 5 + borderwidth + opts.height + textHeight / 2
+ );
- opts.width += 45 + borderwidth * 2;
- opts.height += 10 + borderwidth * 2;
+ opts.height += textHeight;
+ opts.width = Math.max(opts.width, textWidth);
+ });
- if(isGrouped) {
- opts.height += (opts._lgroupsLength - 1) * opts.tracegroupgap;
- }
+ opts.width += 45 + borderwidth * 2;
+ opts.height += 10 + borderwidth * 2;
- // make sure we're only getting full pixels
- opts.width = Math.ceil(opts.width);
- opts.height = Math.ceil(opts.height);
-
- traces.each(function(d) {
- var legendItem = d[0],
- bg = d3.select(this).select('.legendtoggle');
-
- bg.call(Drawing.setRect,
- 0,
- -legendItem.height / 2,
- (gd._context.editable ? 0 : opts.width) + 40,
- legendItem.height
- );
- });
+ if (isGrouped) {
+ opts.height += (opts._lgroupsLength - 1) * opts.tracegroupgap;
}
- else if(isGrouped) {
- opts.width = 0;
- opts.height = 0;
- var groupXOffsets = [opts.width],
- groupData = groups.data();
+ // make sure we're only getting full pixels
+ opts.width = Math.ceil(opts.width);
+ opts.height = Math.ceil(opts.height);
- for(var i = 0, n = groupData.length; i < n; i++) {
- var textWidths = groupData[i].map(function(legendItemArray) {
- return legendItemArray[0].width;
- });
+ traces.each(function(d) {
+ var legendItem = d[0], bg = d3.select(this).select(".legendtoggle");
- var groupWidth = 40 + Math.max.apply(null, textWidths);
+ bg.call(
+ Drawing.setRect,
+ 0,
+ (-legendItem.height) / 2,
+ (gd._context.editable ? 0 : opts.width) + 40,
+ legendItem.height
+ );
+ });
+ } else if (isGrouped) {
+ opts.width = 0;
+ opts.height = 0;
- opts.width += opts.tracegroupgap + groupWidth;
+ var groupXOffsets = [opts.width], groupData = groups.data();
- groupXOffsets.push(opts.width);
- }
+ for (var i = 0, n = groupData.length; i < n; i++) {
+ var textWidths = groupData[i].map(function(legendItemArray) {
+ return legendItemArray[0].width;
+ });
- groups.each(function(d, i) {
- Drawing.setTranslate(this, groupXOffsets[i], 0);
- });
+ var groupWidth = 40 + Math.max.apply(null, textWidths);
- groups.each(function() {
- var group = d3.select(this),
- groupTraces = group.selectAll('g.traces'),
- groupHeight = 0;
+ opts.width += opts.tracegroupgap + groupWidth;
- groupTraces.each(function(d) {
- var legendItem = d[0],
- textHeight = legendItem.height;
+ groupXOffsets.push(opts.width);
+ }
- Drawing.setTranslate(this,
- 0,
- (5 + borderwidth + groupHeight + textHeight / 2));
+ groups.each(function(d, i) {
+ Drawing.setTranslate(this, groupXOffsets[i], 0);
+ });
- groupHeight += textHeight;
- });
+ groups.each(function() {
+ var group = d3.select(this),
+ groupTraces = group.selectAll("g.traces"),
+ groupHeight = 0;
- opts.height = Math.max(opts.height, groupHeight);
- });
+ groupTraces.each(function(d) {
+ var legendItem = d[0], textHeight = legendItem.height;
- opts.height += 10 + borderwidth * 2;
- opts.width += borderwidth * 2;
+ Drawing.setTranslate(
+ this,
+ 0,
+ 5 + borderwidth + groupHeight + textHeight / 2
+ );
- // make sure we're only getting full pixels
- opts.width = Math.ceil(opts.width);
- opts.height = Math.ceil(opts.height);
+ groupHeight += textHeight;
+ });
- traces.each(function(d) {
- var legendItem = d[0],
- bg = d3.select(this).select('.legendtoggle');
+ opts.height = Math.max(opts.height, groupHeight);
+ });
- bg.call(Drawing.setRect,
- 0,
- -legendItem.height / 2,
- (gd._context.editable ? 0 : opts.width),
- legendItem.height
- );
- });
- }
- else {
- opts.width = 0;
- opts.height = 0;
- var rowHeight = 0,
- maxTraceHeight = 0,
- maxTraceWidth = 0,
- offsetX = 0;
-
- // calculate largest width for traces and use for width of all legend items
- traces.each(function(d) {
- maxTraceWidth = Math.max(40 + d[0].width, maxTraceWidth);
- });
-
- traces.each(function(d) {
- var legendItem = d[0],
- traceWidth = maxTraceWidth,
- traceGap = opts.tracegroupgap || 5;
-
- if((borderwidth + offsetX + traceGap + traceWidth) > (fullLayout.width - (fullLayout.margin.r + fullLayout.margin.l))) {
- offsetX = 0;
- rowHeight = rowHeight + maxTraceHeight;
- opts.height = opts.height + maxTraceHeight;
- // reset for next row
- maxTraceHeight = 0;
- }
-
- Drawing.setTranslate(this,
- (borderwidth + offsetX),
- (5 + borderwidth + legendItem.height / 2) + rowHeight);
-
- opts.width += traceGap + traceWidth;
- opts.height = Math.max(opts.height, legendItem.height);
-
- // keep track of tallest trace in group
- offsetX += traceGap + traceWidth;
- maxTraceHeight = Math.max(legendItem.height, maxTraceHeight);
- });
-
- opts.width += borderwidth * 2;
- opts.height += 10 + borderwidth * 2;
-
- // make sure we're only getting full pixels
- opts.width = Math.ceil(opts.width);
- opts.height = Math.ceil(opts.height);
-
- traces.each(function(d) {
- var legendItem = d[0],
- bg = d3.select(this).select('.legendtoggle');
-
- bg.call(Drawing.setRect,
- 0,
- -legendItem.height / 2,
- (gd._context.editable ? 0 : opts.width),
- legendItem.height
- );
- });
- }
-}
+ opts.height += 10 + borderwidth * 2;
+ opts.width += borderwidth * 2;
-function expandMargin(gd) {
- var fullLayout = gd._fullLayout,
- opts = fullLayout.legend;
+ // make sure we're only getting full pixels
+ opts.width = Math.ceil(opts.width);
+ opts.height = Math.ceil(opts.height);
- var xanchor = 'left';
- if(anchorUtils.isRightAnchor(opts)) {
- xanchor = 'right';
- }
- else if(anchorUtils.isCenterAnchor(opts)) {
- xanchor = 'center';
- }
+ traces.each(function(d) {
+ var legendItem = d[0], bg = d3.select(this).select(".legendtoggle");
- var yanchor = 'top';
- if(anchorUtils.isBottomAnchor(opts)) {
- yanchor = 'bottom';
- }
- else if(anchorUtils.isMiddleAnchor(opts)) {
- yanchor = 'middle';
- }
+ bg.call(
+ Drawing.setRect,
+ 0,
+ (-legendItem.height) / 2,
+ gd._context.editable ? 0 : opts.width,
+ legendItem.height
+ );
+ });
+ } else {
+ opts.width = 0;
+ opts.height = 0;
+ var rowHeight = 0, maxTraceHeight = 0, maxTraceWidth = 0, offsetX = 0;
+
+ // calculate largest width for traces and use for width of all legend items
+ traces.each(function(d) {
+ maxTraceWidth = Math.max(40 + d[0].width, maxTraceWidth);
+ });
- // lastly check if the margin auto-expand has changed
- Plots.autoMargin(gd, 'legend', {
- x: opts.x,
- y: opts.y,
- l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
- r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0),
- b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
- t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
+ traces.each(function(d) {
+ var legendItem = d[0],
+ traceWidth = maxTraceWidth,
+ traceGap = opts.tracegroupgap || 5;
+
+ if (
+ borderwidth + offsetX + traceGap + traceWidth >
+ fullLayout.width - (fullLayout.margin.r + fullLayout.margin.l)
+ ) {
+ offsetX = 0;
+ rowHeight = rowHeight + maxTraceHeight;
+ opts.height = opts.height + maxTraceHeight;
+ // reset for next row
+ maxTraceHeight = 0;
+ }
+
+ Drawing.setTranslate(
+ this,
+ borderwidth + offsetX,
+ 5 + borderwidth + legendItem.height / 2 + rowHeight
+ );
+
+ opts.width += traceGap + traceWidth;
+ opts.height = Math.max(opts.height, legendItem.height);
+
+ // keep track of tallest trace in group
+ offsetX += traceGap + traceWidth;
+ maxTraceHeight = Math.max(legendItem.height, maxTraceHeight);
});
-}
-function expandHorizontalMargin(gd) {
- var fullLayout = gd._fullLayout,
- opts = fullLayout.legend;
+ opts.width += borderwidth * 2;
+ opts.height += 10 + borderwidth * 2;
- var xanchor = 'left';
- if(anchorUtils.isRightAnchor(opts)) {
- xanchor = 'right';
- }
- else if(anchorUtils.isCenterAnchor(opts)) {
- xanchor = 'center';
- }
+ // make sure we're only getting full pixels
+ opts.width = Math.ceil(opts.width);
+ opts.height = Math.ceil(opts.height);
+
+ traces.each(function(d) {
+ var legendItem = d[0], bg = d3.select(this).select(".legendtoggle");
- // lastly check if the margin auto-expand has changed
- Plots.autoMargin(gd, 'legend', {
- x: opts.x,
- y: 0.5,
- l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
- r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0),
- b: 0,
- t: 0
+ bg.call(
+ Drawing.setRect,
+ 0,
+ (-legendItem.height) / 2,
+ gd._context.editable ? 0 : opts.width,
+ legendItem.height
+ );
});
+ }
+}
+
+function expandMargin(gd) {
+ var fullLayout = gd._fullLayout, opts = fullLayout.legend;
+
+ var xanchor = "left";
+ if (anchorUtils.isRightAnchor(opts)) {
+ xanchor = "right";
+ } else if (anchorUtils.isCenterAnchor(opts)) {
+ xanchor = "center";
+ }
+
+ var yanchor = "top";
+ if (anchorUtils.isBottomAnchor(opts)) {
+ yanchor = "bottom";
+ } else if (anchorUtils.isMiddleAnchor(opts)) {
+ yanchor = "middle";
+ }
+
+ // lastly check if the margin auto-expand has changed
+ Plots.autoMargin(gd, "legend", {
+ x: opts.x,
+ y: opts.y,
+ l: opts.width * (({ right: 1, center: 0.5 })[xanchor] || 0),
+ r: opts.width * (({ left: 1, center: 0.5 })[xanchor] || 0),
+ b: opts.height * (({ top: 1, middle: 0.5 })[yanchor] || 0),
+ t: opts.height * (({ bottom: 1, middle: 0.5 })[yanchor] || 0)
+ });
+}
+
+function expandHorizontalMargin(gd) {
+ var fullLayout = gd._fullLayout, opts = fullLayout.legend;
+
+ var xanchor = "left";
+ if (anchorUtils.isRightAnchor(opts)) {
+ xanchor = "right";
+ } else if (anchorUtils.isCenterAnchor(opts)) {
+ xanchor = "center";
+ }
+
+ // lastly check if the margin auto-expand has changed
+ Plots.autoMargin(gd, "legend", {
+ x: opts.x,
+ y: 0.5,
+ l: opts.width * (({ right: 1, center: 0.5 })[xanchor] || 0),
+ r: opts.width * (({ left: 1, center: 0.5 })[xanchor] || 0),
+ b: 0,
+ t: 0
+ });
}
diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js
index 4ad4636d442..2522570ee03 100644
--- a/src/components/legend/get_legend_data.js
+++ b/src/components/legend/get_legend_data.js
@@ -5,99 +5,95 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Registry = require('../../registry');
-var helpers = require('./helpers');
-
+"use strict";
+var Registry = require("../../registry");
+var helpers = require("./helpers");
module.exports = function getLegendData(calcdata, opts) {
- var lgroupToTraces = {},
- lgroups = [],
- hasOneNonBlankGroup = false,
- slicesShown = {},
- lgroupi = 0;
-
- var i, j;
-
- function addOneItem(legendGroup, legendItem) {
- // each '' legend group is treated as a separate group
- if(legendGroup === '' || !helpers.isGrouped(opts)) {
- var uniqueGroup = '~~i' + lgroupi; // TODO: check this against fullData legendgroups?
-
- lgroups.push(uniqueGroup);
- lgroupToTraces[uniqueGroup] = [[legendItem]];
- lgroupi++;
- }
- else if(lgroups.indexOf(legendGroup) === -1) {
- lgroups.push(legendGroup);
- hasOneNonBlankGroup = true;
- lgroupToTraces[legendGroup] = [[legendItem]];
- }
- else lgroupToTraces[legendGroup].push([legendItem]);
+ var lgroupToTraces = {},
+ lgroups = [],
+ hasOneNonBlankGroup = false,
+ slicesShown = {},
+ lgroupi = 0;
+
+ var i, j;
+
+ function addOneItem(legendGroup, legendItem) {
+ // each '' legend group is treated as a separate group
+ if (legendGroup === "" || !helpers.isGrouped(opts)) {
+ var uniqueGroup = "~~i" + lgroupi;
+
+ // TODO: check this against fullData legendgroups?
+ lgroups.push(uniqueGroup);
+ lgroupToTraces[uniqueGroup] = [[legendItem]];
+ lgroupi++;
+ } else if (lgroups.indexOf(legendGroup) === -1) {
+ lgroups.push(legendGroup);
+ hasOneNonBlankGroup = true;
+ lgroupToTraces[legendGroup] = [[legendItem]];
+ } else {
+ lgroupToTraces[legendGroup].push([legendItem]);
}
+ }
- // build an { legendgroup: [cd0, cd0], ... } object
- for(i = 0; i < calcdata.length; i++) {
- var cd = calcdata[i],
- cd0 = cd[0],
- trace = cd0.trace,
- lgroup = trace.legendgroup;
+ // build an { legendgroup: [cd0, cd0], ... } object
+ for (i = 0; i < calcdata.length; i++) {
+ var cd = calcdata[i],
+ cd0 = cd[0],
+ trace = cd0.trace,
+ lgroup = trace.legendgroup;
- if(!helpers.legendGetsTrace(trace) || !trace.showlegend) continue;
+ if (!helpers.legendGetsTrace(trace) || !trace.showlegend) continue;
- if(Registry.traceIs(trace, 'pie')) {
- if(!slicesShown[lgroup]) slicesShown[lgroup] = {};
+ if (Registry.traceIs(trace, "pie")) {
+ if (!slicesShown[lgroup]) slicesShown[lgroup] = {};
- for(j = 0; j < cd.length; j++) {
- var labelj = cd[j].label;
+ for (j = 0; j < cd.length; j++) {
+ var labelj = cd[j].label;
- if(!slicesShown[lgroup][labelj]) {
- addOneItem(lgroup, {
- label: labelj,
- color: cd[j].color,
- i: cd[j].i,
- trace: trace
- });
+ if (!slicesShown[lgroup][labelj]) {
+ addOneItem(lgroup, {
+ label: labelj,
+ color: cd[j].color,
+ i: cd[j].i,
+ trace: trace
+ });
- slicesShown[lgroup][labelj] = true;
- }
- }
+ slicesShown[lgroup][labelj] = true;
}
-
- else addOneItem(lgroup, cd0);
+ }
+ } else {
+ addOneItem(lgroup, cd0);
}
+ }
- // won't draw a legend in this case
- if(!lgroups.length) return [];
+ // won't draw a legend in this case
+ if (!lgroups.length) return [];
- // rearrange lgroupToTraces into a d3-friendly array of arrays
- var lgroupsLength = lgroups.length,
- ltraces,
- legendData;
+ // rearrange lgroupToTraces into a d3-friendly array of arrays
+ var lgroupsLength = lgroups.length, ltraces, legendData;
- if(hasOneNonBlankGroup && helpers.isGrouped(opts)) {
- legendData = new Array(lgroupsLength);
+ if (hasOneNonBlankGroup && helpers.isGrouped(opts)) {
+ legendData = new Array(lgroupsLength);
- for(i = 0; i < lgroupsLength; i++) {
- ltraces = lgroupToTraces[lgroups[i]];
- legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces;
- }
+ for (i = 0; i < lgroupsLength; i++) {
+ ltraces = lgroupToTraces[lgroups[i]];
+ legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces;
}
- else {
- // collapse all groups into one if all groups are blank
- legendData = [new Array(lgroupsLength)];
-
- for(i = 0; i < lgroupsLength; i++) {
- ltraces = lgroupToTraces[lgroups[i]][0];
- legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces;
- }
- lgroupsLength = 1;
+ } else {
+ // collapse all groups into one if all groups are blank
+ legendData = [new Array(lgroupsLength)];
+
+ for (i = 0; i < lgroupsLength; i++) {
+ ltraces = lgroupToTraces[lgroups[i]][0];
+ legendData[0][
+ helpers.isReversed(opts) ? lgroupsLength - i - 1 : i
+ ] = ltraces;
}
+ lgroupsLength = 1;
+ }
- // needed in repositionLegend
- opts._lgroupsLength = lgroupsLength;
- return legendData;
+ // needed in repositionLegend
+ opts._lgroupsLength = lgroupsLength;
+ return legendData;
};
diff --git a/src/components/legend/helpers.js b/src/components/legend/helpers.js
index 140fa6d7f5f..4117b548124 100644
--- a/src/components/legend/helpers.js
+++ b/src/components/legend/helpers.js
@@ -5,25 +5,21 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Registry = require('../../registry');
-
+"use strict";
+var Registry = require("../../registry");
exports.legendGetsTrace = function legendGetsTrace(trace) {
- return trace.visible && Registry.traceIs(trace, 'showLegend');
+ return trace.visible && Registry.traceIs(trace, "showLegend");
};
exports.isGrouped = function isGrouped(legendLayout) {
- return (legendLayout.traceorder || '').indexOf('grouped') !== -1;
+ return (legendLayout.traceorder || "").indexOf("grouped") !== -1;
};
exports.isVertical = function isVertical(legendLayout) {
- return legendLayout.orientation !== 'h';
+ return legendLayout.orientation !== "h";
};
exports.isReversed = function isReversed(legendLayout) {
- return (legendLayout.traceorder || '').indexOf('reversed') !== -1;
+ return (legendLayout.traceorder || "").indexOf("reversed") !== -1;
};
diff --git a/src/components/legend/index.js b/src/components/legend/index.js
index 71e45a0b723..600db0132b0 100644
--- a/src/components/legend/index.js
+++ b/src/components/legend/index.js
@@ -5,18 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
module.exports = {
- moduleType: 'component',
- name: 'legend',
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- draw: require('./draw'),
- style: require('./style')
+ moduleType: "component",
+ name: "legend",
+ layoutAttributes: require("./attributes"),
+ supplyLayoutDefaults: require("./defaults"),
+ draw: require("./draw"),
+ style: require("./style")
};
diff --git a/src/components/legend/style.js b/src/components/legend/style.js
index c3c2101e62e..1ebc5b9e271 100644
--- a/src/components/legend/style.js
+++ b/src/components/legend/style.js
@@ -5,53 +5,41 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var Registry = require("../../registry");
+var Lib = require("../../lib");
+var Drawing = require("../drawing");
+var Color = require("../color");
-'use strict';
+var subTypes = require("../../traces/scatter/subtypes");
+var stylePie = require("../../traces/pie/style_one");
-var d3 = require('d3');
+module.exports = function style(s) {
+ s
+ .each(function(d) {
+ var traceGroup = d3.select(this);
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-var Drawing = require('../drawing');
-var Color = require('../color');
+ var layers = traceGroup.selectAll("g.layers").data([0]);
+ layers.enter().append("g").classed("layers", true);
+ layers.style("opacity", d[0].trace.opacity);
-var subTypes = require('../../traces/scatter/subtypes');
-var stylePie = require('../../traces/pie/style_one');
+ var fill = layers.selectAll("g.legendfill").data([d]);
+ fill.enter().append("g").classed("legendfill", true);
+ var line = layers.selectAll("g.legendlines").data([d]);
+ line.enter().append("g").classed("legendlines", true);
-module.exports = function style(s) {
- s.each(function(d) {
- var traceGroup = d3.select(this);
-
- var layers = traceGroup.selectAll('g.layers')
- .data([0]);
- layers.enter().append('g')
- .classed('layers', true);
- layers.style('opacity', d[0].trace.opacity);
-
- var fill = layers
- .selectAll('g.legendfill')
- .data([d]);
- fill.enter().append('g')
- .classed('legendfill', true);
-
- var line = layers
- .selectAll('g.legendlines')
- .data([d]);
- line.enter().append('g')
- .classed('legendlines', true);
-
- var symbol = layers
- .selectAll('g.legendsymbols')
- .data([d]);
- symbol.enter().append('g')
- .classed('legendsymbols', true);
-
- symbol.selectAll('g.legendpoints')
- .data([d])
- .enter().append('g')
- .classed('legendpoints', true);
+ var symbol = layers.selectAll("g.legendsymbols").data([d]);
+ symbol.enter().append("g").classed("legendsymbols", true);
+
+ symbol
+ .selectAll("g.legendpoints")
+ .data([d])
+ .enter()
+ .append("g")
+ .classed("legendpoints", true);
})
.each(styleBars)
.each(styleBoxes)
@@ -61,166 +49,180 @@ module.exports = function style(s) {
};
function styleLines(d) {
- var trace = d[0].trace,
- showFill = trace.visible && trace.fill && trace.fill !== 'none',
- showLine = subTypes.hasLines(trace);
-
- var fill = d3.select(this).select('.legendfill').selectAll('path')
- .data(showFill ? [d] : []);
- fill.enter().append('path').classed('js-fill', true);
- fill.exit().remove();
- fill.attr('d', 'M5,0h30v6h-30z')
- .call(Drawing.fillGroupStyle);
-
- var line = d3.select(this).select('.legendlines').selectAll('path')
- .data(showLine ? [d] : []);
- line.enter().append('path').classed('js-line', true)
- .attr('d', 'M5,0h30');
- line.exit().remove();
- line.call(Drawing.lineGroupStyle);
+ var trace = d[0].trace,
+ showFill = trace.visible && trace.fill && trace.fill !== "none",
+ showLine = subTypes.hasLines(trace);
+
+ var fill = d3
+ .select(this)
+ .select(".legendfill")
+ .selectAll("path")
+ .data(showFill ? [d] : []);
+ fill.enter().append("path").classed("js-fill", true);
+ fill.exit().remove();
+ fill.attr("d", "M5,0h30v6h-30z").call(Drawing.fillGroupStyle);
+
+ var line = d3
+ .select(this)
+ .select(".legendlines")
+ .selectAll("path")
+ .data(showLine ? [d] : []);
+ line.enter().append("path").classed("js-line", true).attr("d", "M5,0h30");
+ line.exit().remove();
+ line.call(Drawing.lineGroupStyle);
}
function stylePoints(d) {
- var d0 = d[0],
- trace = d0.trace,
- showMarkers = subTypes.hasMarkers(trace),
- showText = subTypes.hasText(trace),
- showLines = subTypes.hasLines(trace);
-
- var dMod, tMod;
-
- // 'scatter3d' and 'scattergeo' don't use gd.calcdata yet;
- // use d0.trace to infer arrayOk attributes
-
- function boundVal(attrIn, arrayToValFn, bounds) {
- var valIn = Lib.nestedProperty(trace, attrIn).get(),
- valToBound = (Array.isArray(valIn) && arrayToValFn) ?
- arrayToValFn(valIn) : valIn;
-
- if(bounds) {
- if(valToBound < bounds[0]) return bounds[0];
- else if(valToBound > bounds[1]) return bounds[1];
- }
- return valToBound;
+ var d0 = d[0],
+ trace = d0.trace,
+ showMarkers = subTypes.hasMarkers(trace),
+ showText = subTypes.hasText(trace),
+ showLines = subTypes.hasLines(trace);
+
+ var dMod, tMod;
+
+ // 'scatter3d' and 'scattergeo' don't use gd.calcdata yet;
+ // use d0.trace to infer arrayOk attributes
+ function boundVal(attrIn, arrayToValFn, bounds) {
+ var valIn = Lib.nestedProperty(trace, attrIn).get(),
+ valToBound = Array.isArray(valIn) && arrayToValFn
+ ? arrayToValFn(valIn)
+ : valIn;
+
+ if (bounds) {
+ if (valToBound < bounds[0]) return bounds[0];
+ else if (valToBound > bounds[1]) return bounds[1];
+ }
+ return valToBound;
+ }
+
+ function pickFirst(array) {
+ return array[0];
+ }
+
+ // constrain text, markers, etc so they'll fit on the legend
+ if (showMarkers || showText || showLines) {
+ var dEdit = {}, tEdit = {};
+
+ if (showMarkers) {
+ dEdit.mc = boundVal("marker.color", pickFirst);
+ dEdit.mo = boundVal("marker.opacity", Lib.mean, [0.2, 1]);
+ dEdit.ms = boundVal("marker.size", Lib.mean, [2, 16]);
+ dEdit.mlc = boundVal("marker.line.color", pickFirst);
+ dEdit.mlw = boundVal("marker.line.width", Lib.mean, [0, 5]);
+ tEdit.marker = { sizeref: 1, sizemin: 1, sizemode: "diameter" };
}
- function pickFirst(array) { return array[0]; }
-
- // constrain text, markers, etc so they'll fit on the legend
- if(showMarkers || showText || showLines) {
- var dEdit = {},
- tEdit = {};
-
- if(showMarkers) {
- dEdit.mc = boundVal('marker.color', pickFirst);
- dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]);
- dEdit.ms = boundVal('marker.size', Lib.mean, [2, 16]);
- dEdit.mlc = boundVal('marker.line.color', pickFirst);
- dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]);
- tEdit.marker = {
- sizeref: 1,
- sizemin: 1,
- sizemode: 'diameter'
- };
- }
-
- if(showLines) {
- tEdit.line = {
- width: boundVal('line.width', pickFirst, [0, 10])
- };
- }
-
- if(showText) {
- dEdit.tx = 'Aa';
- dEdit.tp = boundVal('textposition', pickFirst);
- dEdit.ts = 10;
- dEdit.tc = boundVal('textfont.color', pickFirst);
- dEdit.tf = boundVal('textfont.family', pickFirst);
- }
-
- dMod = [Lib.minExtend(d0, dEdit)];
- tMod = Lib.minExtend(trace, tEdit);
+ if (showLines) {
+ tEdit.line = { width: boundVal("line.width", pickFirst, [0, 10]) };
}
- var ptgroup = d3.select(this).select('g.legendpoints');
-
- var pts = ptgroup.selectAll('path.scatterpts')
- .data(showMarkers ? dMod : []);
- pts.enter().append('path').classed('scatterpts', true)
- .attr('transform', 'translate(20,0)');
- pts.exit().remove();
- pts.call(Drawing.pointStyle, tMod);
-
- // 'mrc' is set in pointStyle and used in textPointStyle:
- // constrain it here
- if(showMarkers) dMod[0].mrc = 3;
-
- var txt = ptgroup.selectAll('g.pointtext')
- .data(showText ? dMod : []);
- txt.enter()
- .append('g').classed('pointtext', true)
- .append('text').attr('transform', 'translate(20,0)');
- txt.exit().remove();
- txt.selectAll('text').call(Drawing.textPointStyle, tMod);
+ if (showText) {
+ dEdit.tx = "Aa";
+ dEdit.tp = boundVal("textposition", pickFirst);
+ dEdit.ts = 10;
+ dEdit.tc = boundVal("textfont.color", pickFirst);
+ dEdit.tf = boundVal("textfont.family", pickFirst);
+ }
+
+ dMod = [Lib.minExtend(d0, dEdit)];
+ tMod = Lib.minExtend(trace, tEdit);
+ }
+
+ var ptgroup = d3.select(this).select("g.legendpoints");
+
+ var pts = ptgroup.selectAll("path.scatterpts").data(showMarkers ? dMod : []);
+ pts
+ .enter()
+ .append("path")
+ .classed("scatterpts", true)
+ .attr("transform", "translate(20,0)");
+ pts.exit().remove();
+ pts.call(Drawing.pointStyle, tMod);
+
+ // 'mrc' is set in pointStyle and used in textPointStyle:
+ // constrain it here
+ if (showMarkers) dMod[0].mrc = 3;
+
+ var txt = ptgroup.selectAll("g.pointtext").data(showText ? dMod : []);
+ txt
+ .enter()
+ .append("g")
+ .classed("pointtext", true)
+ .append("text")
+ .attr("transform", "translate(20,0)");
+ txt.exit().remove();
+ txt.selectAll("text").call(Drawing.textPointStyle, tMod);
}
function styleBars(d) {
- var trace = d[0].trace,
- marker = trace.marker || {},
- markerLine = marker.line || {},
- barpath = d3.select(this).select('g.legendpoints')
- .selectAll('path.legendbar')
- .data(Registry.traceIs(trace, 'bar') ? [d] : []);
- barpath.enter().append('path').classed('legendbar', true)
- .attr('d', 'M6,6H-6V-6H6Z')
- .attr('transform', 'translate(20,0)');
- barpath.exit().remove();
- barpath.each(function(d) {
- var p = d3.select(this),
- d0 = d[0],
- w = (d0.mlw + 1 || markerLine.width + 1) - 1;
-
- p.style('stroke-width', w + 'px')
- .call(Color.fill, d0.mc || marker.color);
-
- if(w) {
- p.call(Color.stroke, d0.mlc || markerLine.color);
- }
- });
+ var trace = d[0].trace,
+ marker = trace.marker || {},
+ markerLine = marker.line || {},
+ barpath = d3
+ .select(this)
+ .select("g.legendpoints")
+ .selectAll("path.legendbar")
+ .data(Registry.traceIs(trace, "bar") ? [d] : []);
+ barpath
+ .enter()
+ .append("path")
+ .classed("legendbar", true)
+ .attr("d", "M6,6H-6V-6H6Z")
+ .attr("transform", "translate(20,0)");
+ barpath.exit().remove();
+ barpath.each(function(d) {
+ var p = d3.select(this),
+ d0 = d[0],
+ w = (d0.mlw + 1 || markerLine.width + 1) - 1;
+
+ p.style("stroke-width", w + "px").call(Color.fill, d0.mc || marker.color);
+
+ if (w) {
+ p.call(Color.stroke, d0.mlc || markerLine.color);
+ }
+ });
}
function styleBoxes(d) {
- var trace = d[0].trace,
- pts = d3.select(this).select('g.legendpoints')
- .selectAll('path.legendbox')
- .data(Registry.traceIs(trace, 'box') && trace.visible ? [d] : []);
- pts.enter().append('path').classed('legendbox', true)
- // if we want the median bar, prepend M6,0H-6
- .attr('d', 'M6,6H-6V-6H6Z')
- .attr('transform', 'translate(20,0)');
- pts.exit().remove();
- pts.each(function() {
- var w = trace.line.width,
- p = d3.select(this);
-
- p.style('stroke-width', w + 'px')
- .call(Color.fill, trace.fillcolor);
-
- if(w) {
- p.call(Color.stroke, trace.line.color);
- }
- });
+ var trace = d[0].trace,
+ pts = d3
+ .select(this)
+ .select("g.legendpoints")
+ .selectAll("path.legendbox")
+ .data(Registry.traceIs(trace, "box") && trace.visible ? [d] : []);
+ pts
+ .enter()
+ .append("path")
+ .classed("legendbox", true)
+ .attr("d", "M6,6H-6V-6H6Z")
+ .attr("transform", "translate(20,0)");
+ pts.exit().remove();
+ pts.each(function() {
+ var w = trace.line.width, p = d3.select(this);
+
+ p.style("stroke-width", w + "px").call(Color.fill, trace.fillcolor);
+
+ if (w) {
+ p.call(Color.stroke, trace.line.color);
+ }
+ });
}
function stylePies(d) {
- var trace = d[0].trace,
- pts = d3.select(this).select('g.legendpoints')
- .selectAll('path.legendpie')
- .data(Registry.traceIs(trace, 'pie') && trace.visible ? [d] : []);
- pts.enter().append('path').classed('legendpie', true)
- .attr('d', 'M6,6H-6V-6H6Z')
- .attr('transform', 'translate(20,0)');
- pts.exit().remove();
-
- if(pts.size()) pts.call(stylePie, d[0], trace);
+ var trace = d[0].trace,
+ pts = d3
+ .select(this)
+ .select("g.legendpoints")
+ .selectAll("path.legendpie")
+ .data(Registry.traceIs(trace, "pie") && trace.visible ? [d] : []);
+ pts
+ .enter()
+ .append("path")
+ .classed("legendpie", true)
+ .attr("d", "M6,6H-6V-6H6Z")
+ .attr("transform", "translate(20,0)");
+ pts.exit().remove();
+
+ if (pts.size()) pts.call(stylePie, d[0], trace);
}
diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js
index aae0503e368..694e4b98b56 100644
--- a/src/components/modebar/buttons.js
+++ b/src/components/modebar/buttons.js
@@ -5,17 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
-var Axes = require('../../plots/cartesian/axes');
-var Lib = require('../../lib');
-var downloadImage = require('../../snapshot/download');
-var Icons = require('../../../build/ploticon');
-
+"use strict";
+var Plotly = require("../../plotly");
+var Plots = require("../../plots/plots");
+var Axes = require("../../plots/cartesian/axes");
+var Lib = require("../../lib");
+var downloadImage = require("../../snapshot/download");
+var Icons = require("../../../build/ploticon");
var modeBarButtons = module.exports = {};
@@ -44,477 +40,466 @@ var modeBarButtons = module.exports = {};
* @param {boolean} [toggle]
* is the button a toggle button?
*/
-
modeBarButtons.toImage = {
- name: 'toImage',
- title: 'Download plot as a png',
- icon: Icons.camera,
- click: function(gd) {
- var format = 'png';
-
- Lib.notifier('Taking snapshot - this may take a few seconds', 'long');
+ name: "toImage",
+ title: "Download plot as a png",
+ icon: Icons.camera,
+ click: function(gd) {
+ var format = "png";
- if(Lib.isIE()) {
- Lib.notifier('IE only supports svg. Changing format to svg.', 'long');
- format = 'svg';
- }
+ Lib.notifier("Taking snapshot - this may take a few seconds", "long");
- downloadImage(gd, {'format': format})
- .then(function(filename) {
- Lib.notifier('Snapshot succeeded - ' + filename, 'long');
- })
- .catch(function() {
- Lib.notifier('Sorry there was a problem downloading your snapshot!', 'long');
- });
+ if (Lib.isIE()) {
+ Lib.notifier("IE only supports svg. Changing format to svg.", "long");
+ format = "svg";
}
+
+ downloadImage(gd, { format: format })
+ .then(function(filename) {
+ Lib.notifier("Snapshot succeeded - " + filename, "long");
+ })
+ .catch(function() {
+ Lib.notifier(
+ "Sorry there was a problem downloading your snapshot!",
+ "long"
+ );
+ });
+ }
};
modeBarButtons.sendDataToCloud = {
- name: 'sendDataToCloud',
- title: 'Save and edit plot in cloud',
- icon: Icons.disk,
- click: function(gd) {
- Plots.sendDataToCloud(gd);
- }
+ name: "sendDataToCloud",
+ title: "Save and edit plot in cloud",
+ icon: Icons.disk,
+ click: function(gd) {
+ Plots.sendDataToCloud(gd);
+ }
};
modeBarButtons.zoom2d = {
- name: 'zoom2d',
- title: 'Zoom',
- attr: 'dragmode',
- val: 'zoom',
- icon: Icons.zoombox,
- click: handleCartesian
+ name: "zoom2d",
+ title: "Zoom",
+ attr: "dragmode",
+ val: "zoom",
+ icon: Icons.zoombox,
+ click: handleCartesian
};
modeBarButtons.pan2d = {
- name: 'pan2d',
- title: 'Pan',
- attr: 'dragmode',
- val: 'pan',
- icon: Icons.pan,
- click: handleCartesian
+ name: "pan2d",
+ title: "Pan",
+ attr: "dragmode",
+ val: "pan",
+ icon: Icons.pan,
+ click: handleCartesian
};
modeBarButtons.select2d = {
- name: 'select2d',
- title: 'Box Select',
- attr: 'dragmode',
- val: 'select',
- icon: Icons.selectbox,
- click: handleCartesian
+ name: "select2d",
+ title: "Box Select",
+ attr: "dragmode",
+ val: "select",
+ icon: Icons.selectbox,
+ click: handleCartesian
};
modeBarButtons.lasso2d = {
- name: 'lasso2d',
- title: 'Lasso Select',
- attr: 'dragmode',
- val: 'lasso',
- icon: Icons.lasso,
- click: handleCartesian
+ name: "lasso2d",
+ title: "Lasso Select",
+ attr: "dragmode",
+ val: "lasso",
+ icon: Icons.lasso,
+ click: handleCartesian
};
modeBarButtons.zoomIn2d = {
- name: 'zoomIn2d',
- title: 'Zoom in',
- attr: 'zoom',
- val: 'in',
- icon: Icons.zoom_plus,
- click: handleCartesian
+ name: "zoomIn2d",
+ title: "Zoom in",
+ attr: "zoom",
+ val: "in",
+ icon: Icons.zoom_plus,
+ click: handleCartesian
};
modeBarButtons.zoomOut2d = {
- name: 'zoomOut2d',
- title: 'Zoom out',
- attr: 'zoom',
- val: 'out',
- icon: Icons.zoom_minus,
- click: handleCartesian
+ name: "zoomOut2d",
+ title: "Zoom out",
+ attr: "zoom",
+ val: "out",
+ icon: Icons.zoom_minus,
+ click: handleCartesian
};
modeBarButtons.autoScale2d = {
- name: 'autoScale2d',
- title: 'Autoscale',
- attr: 'zoom',
- val: 'auto',
- icon: Icons.autoscale,
- click: handleCartesian
+ name: "autoScale2d",
+ title: "Autoscale",
+ attr: "zoom",
+ val: "auto",
+ icon: Icons.autoscale,
+ click: handleCartesian
};
modeBarButtons.resetScale2d = {
- name: 'resetScale2d',
- title: 'Reset axes',
- attr: 'zoom',
- val: 'reset',
- icon: Icons.home,
- click: handleCartesian
+ name: "resetScale2d",
+ title: "Reset axes",
+ attr: "zoom",
+ val: "reset",
+ icon: Icons.home,
+ click: handleCartesian
};
modeBarButtons.hoverClosestCartesian = {
- name: 'hoverClosestCartesian',
- title: 'Show closest data on hover',
- attr: 'hovermode',
- val: 'closest',
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: handleCartesian
+ name: "hoverClosestCartesian",
+ title: "Show closest data on hover",
+ attr: "hovermode",
+ val: "closest",
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: handleCartesian
};
modeBarButtons.hoverCompareCartesian = {
- name: 'hoverCompareCartesian',
- title: 'Compare data on hover',
- attr: 'hovermode',
- val: function(gd) {
- return gd._fullLayout._isHoriz ? 'y' : 'x';
- },
- icon: Icons.tooltip_compare,
- gravity: 'ne',
- click: handleCartesian
+ name: "hoverCompareCartesian",
+ title: "Compare data on hover",
+ attr: "hovermode",
+ val: function(gd) {
+ return gd._fullLayout._isHoriz ? "y" : "x";
+ },
+ icon: Icons.tooltip_compare,
+ gravity: "ne",
+ click: handleCartesian
};
function handleCartesian(gd, ev) {
- var button = ev.currentTarget,
- astr = button.getAttribute('data-attr'),
- val = button.getAttribute('data-val') || true,
- fullLayout = gd._fullLayout,
- aobj = {};
-
- if(astr === 'zoom') {
- var mag = (val === 'in') ? 0.5 : 2,
- r0 = (1 + mag) / 2,
- r1 = (1 - mag) / 2,
- axList = Axes.list(gd, null, true);
-
- var ax, axName;
-
- for(var i = 0; i < axList.length; i++) {
- ax = axList[i];
-
- if(!ax.fixedrange) {
- axName = ax._name;
- if(val === 'auto') aobj[axName + '.autorange'] = true;
- else if(val === 'reset') {
- if(ax._rangeInitial === undefined) {
- aobj[axName + '.autorange'] = true;
- }
- else {
- var rangeInitial = ax._rangeInitial.slice();
- aobj[axName + '.range[0]'] = rangeInitial[0];
- aobj[axName + '.range[1]'] = rangeInitial[1];
- }
- }
- else {
- var rangeNow = [
- ax.r2l(ax.range[0]),
- ax.r2l(ax.range[1]),
- ];
-
- var rangeNew = [
- r0 * rangeNow[0] + r1 * rangeNow[1],
- r0 * rangeNow[1] + r1 * rangeNow[0]
- ];
-
- aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]);
- aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]);
- }
- }
+ var button = ev.currentTarget,
+ astr = button.getAttribute("data-attr"),
+ val = button.getAttribute("data-val") || true,
+ fullLayout = gd._fullLayout,
+ aobj = {};
+
+ if (astr === "zoom") {
+ var mag = val === "in" ? 0.5 : 2,
+ r0 = (1 + mag) / 2,
+ r1 = (1 - mag) / 2,
+ axList = Axes.list(gd, null, true);
+
+ var ax, axName;
+
+ for (var i = 0; i < axList.length; i++) {
+ ax = axList[i];
+
+ if (!ax.fixedrange) {
+ axName = ax._name;
+ if (val === "auto") {
+ aobj[axName + ".autorange"] = true;
+ } else if (val === "reset") {
+ if (ax._rangeInitial === undefined) {
+ aobj[axName + ".autorange"] = true;
+ } else {
+ var rangeInitial = ax._rangeInitial.slice();
+ aobj[axName + ".range[0]"] = rangeInitial[0];
+ aobj[axName + ".range[1]"] = rangeInitial[1];
+ }
+ } else {
+ var rangeNow = [ax.r2l(ax.range[0]), ax.r2l(ax.range[1])];
+
+ var rangeNew = [
+ r0 * rangeNow[0] + r1 * rangeNow[1],
+ r0 * rangeNow[1] + r1 * rangeNow[0]
+ ];
+
+ aobj[axName + ".range[0]"] = ax.l2r(rangeNew[0]);
+ aobj[axName + ".range[1]"] = ax.l2r(rangeNew[1]);
}
+ }
}
- else {
- // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
- if(astr === 'hovermode' && (val === 'x' || val === 'y')) {
- val = fullLayout._isHoriz ? 'y' : 'x';
- button.setAttribute('data-val', val);
- }
-
- aobj[astr] = val;
+ } else {
+ // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
+ if (astr === "hovermode" && (val === "x" || val === "y")) {
+ val = fullLayout._isHoriz ? "y" : "x";
+ button.setAttribute("data-val", val);
}
- Plotly.relayout(gd, aobj);
+ aobj[astr] = val;
+ }
+
+ Plotly.relayout(gd, aobj);
}
modeBarButtons.zoom3d = {
- name: 'zoom3d',
- title: 'Zoom',
- attr: 'scene.dragmode',
- val: 'zoom',
- icon: Icons.zoombox,
- click: handleDrag3d
+ name: "zoom3d",
+ title: "Zoom",
+ attr: "scene.dragmode",
+ val: "zoom",
+ icon: Icons.zoombox,
+ click: handleDrag3d
};
modeBarButtons.pan3d = {
- name: 'pan3d',
- title: 'Pan',
- attr: 'scene.dragmode',
- val: 'pan',
- icon: Icons.pan,
- click: handleDrag3d
+ name: "pan3d",
+ title: "Pan",
+ attr: "scene.dragmode",
+ val: "pan",
+ icon: Icons.pan,
+ click: handleDrag3d
};
modeBarButtons.orbitRotation = {
- name: 'orbitRotation',
- title: 'orbital rotation',
- attr: 'scene.dragmode',
- val: 'orbit',
- icon: Icons['3d_rotate'],
- click: handleDrag3d
+ name: "orbitRotation",
+ title: "orbital rotation",
+ attr: "scene.dragmode",
+ val: "orbit",
+ icon: Icons["3d_rotate"],
+ click: handleDrag3d
};
modeBarButtons.tableRotation = {
- name: 'tableRotation',
- title: 'turntable rotation',
- attr: 'scene.dragmode',
- val: 'turntable',
- icon: Icons['z-axis'],
- click: handleDrag3d
+ name: "tableRotation",
+ title: "turntable rotation",
+ attr: "scene.dragmode",
+ val: "turntable",
+ icon: Icons["z-axis"],
+ click: handleDrag3d
};
function handleDrag3d(gd, ev) {
- var button = ev.currentTarget,
- attr = button.getAttribute('data-attr'),
- val = button.getAttribute('data-val') || true,
- fullLayout = gd._fullLayout,
- sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
- layoutUpdate = {};
+ var button = ev.currentTarget,
+ attr = button.getAttribute("data-attr"),
+ val = button.getAttribute("data-val") || true,
+ fullLayout = gd._fullLayout,
+ sceneIds = Plots.getSubplotIds(fullLayout, "gl3d"),
+ layoutUpdate = {};
- var parts = attr.split('.');
+ var parts = attr.split(".");
- for(var i = 0; i < sceneIds.length; i++) {
- layoutUpdate[sceneIds[i] + '.' + parts[1]] = val;
- }
+ for (var i = 0; i < sceneIds.length; i++) {
+ layoutUpdate[sceneIds[i] + "." + parts[1]] = val;
+ }
- Plotly.relayout(gd, layoutUpdate);
+ Plotly.relayout(gd, layoutUpdate);
}
modeBarButtons.resetCameraDefault3d = {
- name: 'resetCameraDefault3d',
- title: 'Reset camera to default',
- attr: 'resetDefault',
- icon: Icons.home,
- click: handleCamera3d
+ name: "resetCameraDefault3d",
+ title: "Reset camera to default",
+ attr: "resetDefault",
+ icon: Icons.home,
+ click: handleCamera3d
};
modeBarButtons.resetCameraLastSave3d = {
- name: 'resetCameraLastSave3d',
- title: 'Reset camera to last save',
- attr: 'resetLastSave',
- icon: Icons.movie,
- click: handleCamera3d
+ name: "resetCameraLastSave3d",
+ title: "Reset camera to last save",
+ attr: "resetLastSave",
+ icon: Icons.movie,
+ click: handleCamera3d
};
function handleCamera3d(gd, ev) {
- var button = ev.currentTarget,
- attr = button.getAttribute('data-attr'),
- fullLayout = gd._fullLayout,
- sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'),
- aobj = {};
-
- for(var i = 0; i < sceneIds.length; i++) {
- var sceneId = sceneIds[i],
- key = sceneId + '.camera',
- scene = fullLayout[sceneId]._scene;
-
- if(attr === 'resetDefault') {
- aobj[key] = null;
- }
- else if(attr === 'resetLastSave') {
- aobj[key] = Lib.extendDeep({}, scene.cameraInitial);
- }
+ var button = ev.currentTarget,
+ attr = button.getAttribute("data-attr"),
+ fullLayout = gd._fullLayout,
+ sceneIds = Plots.getSubplotIds(fullLayout, "gl3d"),
+ aobj = {};
+
+ for (var i = 0; i < sceneIds.length; i++) {
+ var sceneId = sceneIds[i],
+ key = sceneId + ".camera",
+ scene = fullLayout[sceneId]._scene;
+
+ if (attr === "resetDefault") {
+ aobj[key] = null;
+ } else if (attr === "resetLastSave") {
+ aobj[key] = Lib.extendDeep({}, scene.cameraInitial);
}
+ }
- Plotly.relayout(gd, aobj);
+ Plotly.relayout(gd, aobj);
}
modeBarButtons.hoverClosest3d = {
- name: 'hoverClosest3d',
- title: 'Toggle show closest data on hover',
- attr: 'hovermode',
- val: null,
- toggle: true,
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: handleHover3d
+ name: "hoverClosest3d",
+ title: "Toggle show closest data on hover",
+ attr: "hovermode",
+ val: null,
+ toggle: true,
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: handleHover3d
};
function handleHover3d(gd, ev) {
- var button = ev.currentTarget,
- val = button._previousVal || false,
- layout = gd.layout,
- fullLayout = gd._fullLayout,
- sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d');
-
- var axes = ['xaxis', 'yaxis', 'zaxis'],
- spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];
-
- // initialize 'current spike' object to be stored in the DOM
- var currentSpikes = {},
- axisSpikes = {},
- layoutUpdate = {};
-
- if(val) {
- layoutUpdate = Lib.extendDeep(layout, val);
- button._previousVal = null;
- }
- else {
- layoutUpdate = {
- 'allaxes.showspikes': false
- };
-
- for(var i = 0; i < sceneIds.length; i++) {
- var sceneId = sceneIds[i],
- sceneLayout = fullLayout[sceneId],
- sceneSpikes = currentSpikes[sceneId] = {};
-
- sceneSpikes.hovermode = sceneLayout.hovermode;
- layoutUpdate[sceneId + '.hovermode'] = false;
-
- // copy all the current spike attrs
- for(var j = 0; j < 3; j++) {
- var axis = axes[j];
- axisSpikes = sceneSpikes[axis] = {};
-
- for(var k = 0; k < spikeAttrs.length; k++) {
- var spikeAttr = spikeAttrs[k];
- axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
- }
- }
+ var button = ev.currentTarget,
+ val = button._previousVal || false,
+ layout = gd.layout,
+ fullLayout = gd._fullLayout,
+ sceneIds = Plots.getSubplotIds(fullLayout, "gl3d");
+
+ var axes = ["xaxis", "yaxis", "zaxis"],
+ spikeAttrs = ["showspikes", "spikesides", "spikethickness", "spikecolor"];
+
+ // initialize 'current spike' object to be stored in the DOM
+ var currentSpikes = {}, axisSpikes = {}, layoutUpdate = {};
+
+ if (val) {
+ layoutUpdate = Lib.extendDeep(layout, val);
+ button._previousVal = null;
+ } else {
+ layoutUpdate = { "allaxes.showspikes": false };
+
+ for (var i = 0; i < sceneIds.length; i++) {
+ var sceneId = sceneIds[i],
+ sceneLayout = fullLayout[sceneId],
+ sceneSpikes = currentSpikes[sceneId] = {};
+
+ sceneSpikes.hovermode = sceneLayout.hovermode;
+ layoutUpdate[sceneId + ".hovermode"] = false;
+
+ // copy all the current spike attrs
+ for (var j = 0; j < 3; j++) {
+ var axis = axes[j];
+ axisSpikes = sceneSpikes[axis] = {};
+
+ for (var k = 0; k < spikeAttrs.length; k++) {
+ var spikeAttr = spikeAttrs[k];
+ axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
}
-
- button._previousVal = Lib.extendDeep({}, currentSpikes);
+ }
}
- Plotly.relayout(gd, layoutUpdate);
+ button._previousVal = Lib.extendDeep({}, currentSpikes);
+ }
+
+ Plotly.relayout(gd, layoutUpdate);
}
modeBarButtons.zoomInGeo = {
- name: 'zoomInGeo',
- title: 'Zoom in',
- attr: 'zoom',
- val: 'in',
- icon: Icons.zoom_plus,
- click: handleGeo
+ name: "zoomInGeo",
+ title: "Zoom in",
+ attr: "zoom",
+ val: "in",
+ icon: Icons.zoom_plus,
+ click: handleGeo
};
modeBarButtons.zoomOutGeo = {
- name: 'zoomOutGeo',
- title: 'Zoom out',
- attr: 'zoom',
- val: 'out',
- icon: Icons.zoom_minus,
- click: handleGeo
+ name: "zoomOutGeo",
+ title: "Zoom out",
+ attr: "zoom",
+ val: "out",
+ icon: Icons.zoom_minus,
+ click: handleGeo
};
modeBarButtons.resetGeo = {
- name: 'resetGeo',
- title: 'Reset',
- attr: 'reset',
- val: null,
- icon: Icons.autoscale,
- click: handleGeo
+ name: "resetGeo",
+ title: "Reset",
+ attr: "reset",
+ val: null,
+ icon: Icons.autoscale,
+ click: handleGeo
};
modeBarButtons.hoverClosestGeo = {
- name: 'hoverClosestGeo',
- title: 'Toggle show closest data on hover',
- attr: 'hovermode',
- val: null,
- toggle: true,
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: toggleHover
+ name: "hoverClosestGeo",
+ title: "Toggle show closest data on hover",
+ attr: "hovermode",
+ val: null,
+ toggle: true,
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: toggleHover
};
function handleGeo(gd, ev) {
- var button = ev.currentTarget,
- attr = button.getAttribute('data-attr'),
- val = button.getAttribute('data-val') || true,
- fullLayout = gd._fullLayout,
- geoIds = Plots.getSubplotIds(fullLayout, 'geo');
-
- for(var i = 0; i < geoIds.length; i++) {
- var geo = fullLayout[geoIds[i]]._subplot;
-
- if(attr === 'zoom') {
- var scale = geo.projection.scale();
- var newScale = (val === 'in') ? 2 * scale : 0.5 * scale;
- geo.projection.scale(newScale);
- geo.zoom.scale(newScale);
- geo.render();
- }
- else if(attr === 'reset') geo.zoomReset();
- }
+ var button = ev.currentTarget,
+ attr = button.getAttribute("data-attr"),
+ val = button.getAttribute("data-val") || true,
+ fullLayout = gd._fullLayout,
+ geoIds = Plots.getSubplotIds(fullLayout, "geo");
+
+ for (var i = 0; i < geoIds.length; i++) {
+ var geo = fullLayout[geoIds[i]]._subplot;
+
+ if (attr === "zoom") {
+ var scale = geo.projection.scale();
+ var newScale = val === "in" ? 2 * scale : 0.5 * scale;
+ geo.projection.scale(newScale);
+ geo.zoom.scale(newScale);
+ geo.render();
+ } else if (attr === "reset") geo.zoomReset();
+ }
}
modeBarButtons.hoverClosestGl2d = {
- name: 'hoverClosestGl2d',
- title: 'Toggle show closest data on hover',
- attr: 'hovermode',
- val: null,
- toggle: true,
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: toggleHover
+ name: "hoverClosestGl2d",
+ title: "Toggle show closest data on hover",
+ attr: "hovermode",
+ val: null,
+ toggle: true,
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: toggleHover
};
modeBarButtons.hoverClosestPie = {
- name: 'hoverClosestPie',
- title: 'Toggle show closest data on hover',
- attr: 'hovermode',
- val: 'closest',
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: toggleHover
+ name: "hoverClosestPie",
+ title: "Toggle show closest data on hover",
+ attr: "hovermode",
+ val: "closest",
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: toggleHover
};
function toggleHover(gd) {
- var fullLayout = gd._fullLayout;
+ var fullLayout = gd._fullLayout;
- var onHoverVal;
- if(fullLayout._has('cartesian')) {
- onHoverVal = fullLayout._isHoriz ? 'y' : 'x';
- }
- else onHoverVal = 'closest';
+ var onHoverVal;
+ if (fullLayout._has("cartesian")) {
+ onHoverVal = fullLayout._isHoriz ? "y" : "x";
+ } else {
+ onHoverVal = "closest";
+ }
- var newHover = gd._fullLayout.hovermode ? false : onHoverVal;
+ var newHover = gd._fullLayout.hovermode ? false : onHoverVal;
- Plotly.relayout(gd, 'hovermode', newHover);
+ Plotly.relayout(gd, "hovermode", newHover);
}
// buttons when more then one plot types are present
-
modeBarButtons.toggleHover = {
- name: 'toggleHover',
- title: 'Toggle show closest data on hover',
- attr: 'hovermode',
- val: null,
- toggle: true,
- icon: Icons.tooltip_basic,
- gravity: 'ne',
- click: function(gd, ev) {
- toggleHover(gd);
-
- // the 3d hovermode update must come
- // last so that layout.hovermode update does not
- // override scene?.hovermode?.layout.
- handleHover3d(gd, ev);
- }
+ name: "toggleHover",
+ title: "Toggle show closest data on hover",
+ attr: "hovermode",
+ val: null,
+ toggle: true,
+ icon: Icons.tooltip_basic,
+ gravity: "ne",
+ click: function(gd, ev) {
+ toggleHover(gd);
+
+ // the 3d hovermode update must come
+ // last so that layout.hovermode update does not
+ // override scene?.hovermode?.layout.
+ handleHover3d(gd, ev);
+ }
};
modeBarButtons.resetViews = {
- name: 'resetViews',
- title: 'Reset views',
- icon: Icons.home,
- click: function(gd, ev) {
- var button = ev.currentTarget;
-
- button.setAttribute('data-attr', 'zoom');
- button.setAttribute('data-val', 'reset');
- handleCartesian(gd, ev);
-
- button.setAttribute('data-attr', 'resetLastSave');
- handleCamera3d(gd, ev);
-
- // N.B handleCamera3d also triggers a replot for
- // geo subplots.
- }
+ name: "resetViews",
+ title: "Reset views",
+ icon: Icons.home,
+ click: function(gd, ev) {
+ var button = ev.currentTarget;
+
+ button.setAttribute("data-attr", "zoom");
+ button.setAttribute("data-val", "reset");
+ handleCartesian(gd, ev);
+
+ button.setAttribute("data-attr", "resetLastSave");
+ handleCamera3d(gd, ev);
+ // N.B handleCamera3d also triggers a replot for
+ // geo subplots.
+ }
};
diff --git a/src/components/modebar/index.js b/src/components/modebar/index.js
index 787fa706d4b..bf310fa1aeb 100644
--- a/src/components/modebar/index.js
+++ b/src/components/modebar/index.js
@@ -5,8 +5,5 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-exports.manage = require('./manage');
+"use strict";
+exports.manage = require("./manage");
diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js
index 57e5e2d17b3..e1d7473c8dc 100644
--- a/src/components/modebar/manage.js
+++ b/src/components/modebar/manage.js
@@ -5,15 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Axes = require("../../plots/cartesian/axes");
+var scatterSubTypes = require("../../traces/scatter/subtypes");
-
-'use strict';
-
-var Axes = require('../../plots/cartesian/axes');
-var scatterSubTypes = require('../../traces/scatter/subtypes');
-
-var createModeBar = require('./modebar');
-var modeBarButtons = require('./buttons');
+var createModeBar = require("./modebar");
+var modeBarButtons = require("./buttons");
/**
* ModeBar wrapper around 'create' and 'update',
@@ -24,203 +21,205 @@ var modeBarButtons = require('./buttons');
*
*/
module.exports = function manageModeBar(gd) {
- var fullLayout = gd._fullLayout,
- context = gd._context,
- modeBar = fullLayout._modeBar;
-
- if(!context.displayModeBar) {
- if(modeBar) {
- modeBar.destroy();
- delete fullLayout._modeBar;
- }
- return;
- }
-
- if(!Array.isArray(context.modeBarButtonsToRemove)) {
- throw new Error([
- '*modeBarButtonsToRemove* configuration options',
- 'must be an array.'
- ].join(' '));
- }
-
- if(!Array.isArray(context.modeBarButtonsToAdd)) {
- throw new Error([
- '*modeBarButtonsToAdd* configuration options',
- 'must be an array.'
- ].join(' '));
- }
-
- var customButtons = context.modeBarButtons;
- var buttonGroups;
-
- if(Array.isArray(customButtons) && customButtons.length) {
- buttonGroups = fillCustomButton(customButtons);
- }
- else {
- buttonGroups = getButtonGroups(
- gd,
- context.modeBarButtonsToRemove,
- context.modeBarButtonsToAdd
- );
- }
-
- if(modeBar) modeBar.update(gd, buttonGroups);
- else fullLayout._modeBar = createModeBar(gd, buttonGroups);
+ var fullLayout = gd._fullLayout,
+ context = gd._context,
+ modeBar = fullLayout._modeBar;
+
+ if (!context.displayModeBar) {
+ if (modeBar) {
+ modeBar.destroy();
+ delete fullLayout._modeBar;
+ }
+ return;
+ }
+
+ if (!Array.isArray(context.modeBarButtonsToRemove)) {
+ throw new Error(
+ [
+ "*modeBarButtonsToRemove* configuration options",
+ "must be an array."
+ ].join(" ")
+ );
+ }
+
+ if (!Array.isArray(context.modeBarButtonsToAdd)) {
+ throw new Error(
+ ["*modeBarButtonsToAdd* configuration options", "must be an array."].join(
+ " "
+ )
+ );
+ }
+
+ var customButtons = context.modeBarButtons;
+ var buttonGroups;
+
+ if (Array.isArray(customButtons) && customButtons.length) {
+ buttonGroups = fillCustomButton(customButtons);
+ } else {
+ buttonGroups = getButtonGroups(
+ gd,
+ context.modeBarButtonsToRemove,
+ context.modeBarButtonsToAdd
+ );
+ }
+
+ if (modeBar) modeBar.update(gd, buttonGroups);
+ else fullLayout._modeBar = createModeBar(gd, buttonGroups);
};
// logic behind which buttons are displayed by default
function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
- var fullLayout = gd._fullLayout,
- fullData = gd._fullData;
+ var fullLayout = gd._fullLayout, fullData = gd._fullData;
- var hasCartesian = fullLayout._has('cartesian'),
- hasGL3D = fullLayout._has('gl3d'),
- hasGeo = fullLayout._has('geo'),
- hasPie = fullLayout._has('pie'),
- hasGL2D = fullLayout._has('gl2d'),
- hasTernary = fullLayout._has('ternary');
+ var hasCartesian = fullLayout._has("cartesian"),
+ hasGL3D = fullLayout._has("gl3d"),
+ hasGeo = fullLayout._has("geo"),
+ hasPie = fullLayout._has("pie"),
+ hasGL2D = fullLayout._has("gl2d"),
+ hasTernary = fullLayout._has("ternary");
- var groups = [];
+ var groups = [];
- function addGroup(newGroup) {
- var out = [];
+ function addGroup(newGroup) {
+ var out = [];
- for(var i = 0; i < newGroup.length; i++) {
- var button = newGroup[i];
- if(buttonsToRemove.indexOf(button) !== -1) continue;
- out.push(modeBarButtons[button]);
- }
-
- groups.push(out);
+ for (var i = 0; i < newGroup.length; i++) {
+ var button = newGroup[i];
+ if (buttonsToRemove.indexOf(button) !== -1) continue;
+ out.push(modeBarButtons[button]);
}
- // buttons common to all plot types
- addGroup(['toImage', 'sendDataToCloud']);
+ groups.push(out);
+ }
- // graphs with more than one plot types get 'union buttons'
- // which reset the view or toggle hover labels across all subplots.
- if((hasCartesian || hasGL2D || hasPie || hasTernary) + hasGeo + hasGL3D > 1) {
- addGroup(['resetViews', 'toggleHover']);
- return appendButtonsToGroups(groups, buttonsToAdd);
- }
-
- if(hasGL3D) {
- addGroup(['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']);
- addGroup(['resetCameraDefault3d', 'resetCameraLastSave3d']);
- addGroup(['hoverClosest3d']);
- }
-
- if(hasGeo) {
- addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']);
- addGroup(['hoverClosestGeo']);
- }
-
- var allAxesFixed = areAllAxesFixed(fullLayout),
- dragModeGroup = [];
-
- if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) {
- dragModeGroup = ['zoom2d', 'pan2d'];
- }
- if((hasCartesian || hasTernary) && isSelectable(fullData)) {
- dragModeGroup.push('select2d');
- dragModeGroup.push('lasso2d');
- }
- if(dragModeGroup.length) addGroup(dragModeGroup);
-
- if((hasCartesian || hasGL2D) && !allAxesFixed && !hasTernary) {
- addGroup(['zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']);
- }
-
- if(hasCartesian && hasPie) {
- addGroup(['toggleHover']);
- }
- else if(hasGL2D) {
- addGroup(['hoverClosestGl2d']);
- }
- else if(hasCartesian) {
- addGroup(['hoverClosestCartesian', 'hoverCompareCartesian']);
- }
- else if(hasPie) {
- addGroup(['hoverClosestPie']);
- }
+ // buttons common to all plot types
+ addGroup(["toImage", "sendDataToCloud"]);
+ // graphs with more than one plot types get 'union buttons'
+ // which reset the view or toggle hover labels across all subplots.
+ if (
+ (hasCartesian || hasGL2D || hasPie || hasTernary) + hasGeo + hasGL3D > 1
+ ) {
+ addGroup(["resetViews", "toggleHover"]);
return appendButtonsToGroups(groups, buttonsToAdd);
+ }
+
+ if (hasGL3D) {
+ addGroup(["zoom3d", "pan3d", "orbitRotation", "tableRotation"]);
+ addGroup(["resetCameraDefault3d", "resetCameraLastSave3d"]);
+ addGroup(["hoverClosest3d"]);
+ }
+
+ if (hasGeo) {
+ addGroup(["zoomInGeo", "zoomOutGeo", "resetGeo"]);
+ addGroup(["hoverClosestGeo"]);
+ }
+
+ var allAxesFixed = areAllAxesFixed(fullLayout), dragModeGroup = [];
+
+ if ((hasCartesian || hasGL2D) && !allAxesFixed || hasTernary) {
+ dragModeGroup = ["zoom2d", "pan2d"];
+ }
+ if ((hasCartesian || hasTernary) && isSelectable(fullData)) {
+ dragModeGroup.push("select2d");
+ dragModeGroup.push("lasso2d");
+ }
+ if (dragModeGroup.length) addGroup(dragModeGroup);
+
+ if ((hasCartesian || hasGL2D) && !allAxesFixed && !hasTernary) {
+ addGroup(["zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d"]);
+ }
+
+ if (hasCartesian && hasPie) {
+ addGroup(["toggleHover"]);
+ } else if (hasGL2D) {
+ addGroup(["hoverClosestGl2d"]);
+ } else if (hasCartesian) {
+ addGroup(["hoverClosestCartesian", "hoverCompareCartesian"]);
+ } else if (hasPie) {
+ addGroup(["hoverClosestPie"]);
+ }
+
+ return appendButtonsToGroups(groups, buttonsToAdd);
}
function areAllAxesFixed(fullLayout) {
- var axList = Axes.list({_fullLayout: fullLayout}, null, true);
- var allFixed = true;
+ var axList = Axes.list({ _fullLayout: fullLayout }, null, true);
+ var allFixed = true;
- for(var i = 0; i < axList.length; i++) {
- if(!axList[i].fixedrange) {
- allFixed = false;
- break;
- }
+ for (var i = 0; i < axList.length; i++) {
+ if (!axList[i].fixedrange) {
+ allFixed = false;
+ break;
}
+ }
- return allFixed;
+ return allFixed;
}
// look for traces that support selection
// to be updated as we add more selectPoints handlers
function isSelectable(fullData) {
- var selectable = false;
+ var selectable = false;
- for(var i = 0; i < fullData.length; i++) {
- if(selectable) break;
+ for (var i = 0; i < fullData.length; i++) {
+ if (selectable) break;
- var trace = fullData[i];
+ var trace = fullData[i];
- if(!trace._module || !trace._module.selectPoints) continue;
+ if (!trace._module || !trace._module.selectPoints) continue;
- if(trace.type === 'scatter' || trace.type === 'scatterternary') {
- if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
- selectable = true;
- }
- }
- // assume that in general if the trace module has selectPoints,
- // then it's selectable. Scatter is an exception to this because it must
- // have markers or text, not just be a scatter type.
- else selectable = true;
+ if (trace.type === "scatter" || trace.type === "scatterternary") {
+ if (scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
+ selectable = true;
+ }
+ } else {
+ // assume that in general if the trace module has selectPoints,
+ // then it's selectable. Scatter is an exception to this because it must
+ // have markers or text, not just be a scatter type.
+ selectable = true;
}
+ }
- return selectable;
+ return selectable;
}
function appendButtonsToGroups(groups, buttons) {
- if(buttons.length) {
- if(Array.isArray(buttons[0])) {
- for(var i = 0; i < buttons.length; i++) {
- groups.push(buttons[i]);
- }
- }
- else groups.push(buttons);
+ if (buttons.length) {
+ if (Array.isArray(buttons[0])) {
+ for (var i = 0; i < buttons.length; i++) {
+ groups.push(buttons[i]);
+ }
+ } else {
+ groups.push(buttons);
}
+ }
- return groups;
+ return groups;
}
// fill in custom buttons referring to default mode bar buttons
function fillCustomButton(customButtons) {
- for(var i = 0; i < customButtons.length; i++) {
- var buttonGroup = customButtons[i];
-
- for(var j = 0; j < buttonGroup.length; j++) {
- var button = buttonGroup[j];
-
- if(typeof button === 'string') {
- if(modeBarButtons[button] !== undefined) {
- customButtons[i][j] = modeBarButtons[button];
- }
- else {
- throw new Error([
- '*modeBarButtons* configuration options',
- 'invalid button name'
- ].join(' '));
- }
- }
+ for (var i = 0; i < customButtons.length; i++) {
+ var buttonGroup = customButtons[i];
+
+ for (var j = 0; j < buttonGroup.length; j++) {
+ var button = buttonGroup[j];
+
+ if (typeof button === "string") {
+ if (modeBarButtons[button] !== undefined) {
+ customButtons[i][j] = modeBarButtons[button];
+ } else {
+ throw new Error(
+ [
+ "*modeBarButtons* configuration options",
+ "invalid button name"
+ ].join(" ")
+ );
}
+ }
}
+ }
- return customButtons;
+ return customButtons;
}
diff --git a/src/components/modebar/modebar.js b/src/components/modebar/modebar.js
index 054a8a91928..25dfcf8448c 100644
--- a/src/components/modebar/modebar.js
+++ b/src/components/modebar/modebar.js
@@ -5,15 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
-
-'use strict';
-
-var d3 = require('d3');
-
-var Lib = require('../../lib');
-var Icons = require('../../../build/ploticon');
-
+var Lib = require("../../lib");
+var Icons = require("../../../build/ploticon");
/**
* UI controller for interactive plots
@@ -24,12 +20,12 @@ var Icons = require('../../../build/ploticon');
* @Param {object} opts.graphInfo primary plot object containing data and layout
*/
function ModeBar(opts) {
- this.container = opts.container;
- this.element = document.createElement('div');
+ this.container = opts.container;
+ this.element = document.createElement("div");
- this.update(opts.graphInfo, opts.buttons);
+ this.update(opts.graphInfo, opts.buttons);
- this.container.appendChild(this.element);
+ this.container.appendChild(this.element);
}
var proto = ModeBar.prototype;
@@ -42,60 +38,61 @@ var proto = ModeBar.prototype;
*
*/
proto.update = function(graphInfo, buttons) {
- this.graphInfo = graphInfo;
+ this.graphInfo = graphInfo;
- var context = this.graphInfo._context;
+ var context = this.graphInfo._context;
- if(context.displayModeBar === 'hover') {
- this.element.className = 'modebar modebar--hover';
- }
- else this.element.className = 'modebar';
+ if (context.displayModeBar === "hover") {
+ this.element.className = "modebar modebar--hover";
+ } else {
+ this.element.className = "modebar";
+ }
- // if buttons or logo have changed, redraw modebar interior
- var needsNewButtons = !this.hasButtons(buttons),
- needsNewLogo = (this.hasLogo !== context.displaylogo);
+ // if buttons or logo have changed, redraw modebar interior
+ var needsNewButtons = !this.hasButtons(buttons),
+ needsNewLogo = this.hasLogo !== context.displaylogo;
- if(needsNewButtons || needsNewLogo) {
- this.removeAllButtons();
+ if (needsNewButtons || needsNewLogo) {
+ this.removeAllButtons();
- this.updateButtons(buttons);
+ this.updateButtons(buttons);
- if(context.displaylogo) {
- this.element.appendChild(this.getLogo());
- this.hasLogo = true;
- }
+ if (context.displaylogo) {
+ this.element.appendChild(this.getLogo());
+ this.hasLogo = true;
}
+ }
- this.updateActiveButton();
+ this.updateActiveButton();
};
proto.updateButtons = function(buttons) {
- var _this = this;
-
- this.buttons = buttons;
- this.buttonElements = [];
- this.buttonsNames = [];
-
- this.buttons.forEach(function(buttonGroup) {
- var group = _this.createGroup();
-
- buttonGroup.forEach(function(buttonConfig) {
- var buttonName = buttonConfig.name;
- if(!buttonName) {
- throw new Error('must provide button \'name\' in button config');
- }
- if(_this.buttonsNames.indexOf(buttonName) !== -1) {
- throw new Error('button name \'' + buttonName + '\' is taken');
- }
- _this.buttonsNames.push(buttonName);
-
- var button = _this.createButton(buttonConfig);
- _this.buttonElements.push(button);
- group.appendChild(button);
- });
-
- _this.element.appendChild(group);
+ var _this = this;
+
+ this.buttons = buttons;
+ this.buttonElements = [];
+ this.buttonsNames = [];
+
+ this.buttons.forEach(function(buttonGroup) {
+ var group = _this.createGroup();
+
+ buttonGroup.forEach(function(buttonConfig) {
+ var buttonName = buttonConfig.name;
+ if (!buttonName) {
+ throw new Error("must provide button 'name' in button config");
+ }
+ if (_this.buttonsNames.indexOf(buttonName) !== -1) {
+ throw new Error("button name '" + buttonName + "' is taken");
+ }
+ _this.buttonsNames.push(buttonName);
+
+ var button = _this.createButton(buttonConfig);
+ _this.buttonElements.push(button);
+ group.appendChild(button);
});
+
+ _this.element.appendChild(group);
+ });
};
/**
@@ -103,10 +100,10 @@ proto.updateButtons = function(buttons) {
* @Return {HTMLelement}
*/
proto.createGroup = function() {
- var group = document.createElement('div');
- group.className = 'modebar-group';
+ var group = document.createElement("div");
+ group.className = "modebar-group";
- return group;
+ return group;
};
/**
@@ -115,44 +112,42 @@ proto.createGroup = function() {
* @Return {HTMLelement}
*/
proto.createButton = function(config) {
- var _this = this,
- button = document.createElement('a');
+ var _this = this, button = document.createElement("a");
- button.setAttribute('rel', 'tooltip');
- button.className = 'modebar-btn';
+ button.setAttribute("rel", "tooltip");
+ button.className = "modebar-btn";
- var title = config.title;
- if(title === undefined) title = config.name;
- if(title || title === 0) button.setAttribute('data-title', title);
+ var title = config.title;
+ if (title === undefined) title = config.name;
+ if (title || title === 0) button.setAttribute("data-title", title);
- if(config.attr !== undefined) button.setAttribute('data-attr', config.attr);
+ if (config.attr !== undefined) button.setAttribute("data-attr", config.attr);
- var val = config.val;
- if(val !== undefined) {
- if(typeof val === 'function') val = val(this.graphInfo);
- button.setAttribute('data-val', val);
- }
+ var val = config.val;
+ if (val !== undefined) {
+ if (typeof val === "function") val = val(this.graphInfo);
+ button.setAttribute("data-val", val);
+ }
- var click = config.click;
- if(typeof click !== 'function') {
- throw new Error('must provide button \'click\' function in button config');
- }
- else {
- button.addEventListener('click', function(ev) {
- config.click(_this.graphInfo, ev);
+ var click = config.click;
+ if (typeof click !== "function") {
+ throw new Error("must provide button 'click' function in button config");
+ } else {
+ button.addEventListener("click", function(ev) {
+ config.click(_this.graphInfo, ev);
- // only needed for 'hoverClosestGeo' which does not call relayout
- _this.updateActiveButton(ev.currentTarget);
- });
- }
+ // only needed for 'hoverClosestGeo' which does not call relayout
+ _this.updateActiveButton(ev.currentTarget);
+ });
+ }
- button.setAttribute('data-toggle', config.toggle || false);
- if(config.toggle) d3.select(button).classed('active', true);
+ button.setAttribute("data-toggle", config.toggle || false);
+ if (config.toggle) d3.select(button).classed("active", true);
- button.appendChild(this.createIcon(config.icon || Icons.question));
- button.setAttribute('data-gravity', config.gravity || 'n');
+ button.appendChild(this.createIcon(config.icon || Icons.question));
+ button.setAttribute("data-gravity", config.gravity || "n");
- return button;
+ return button;
};
/**
@@ -163,20 +158,20 @@ proto.createButton = function(config) {
* @Return {HTMLelement}
*/
proto.createIcon = function(thisIcon) {
- var iconHeight = thisIcon.ascent - thisIcon.descent,
- svgNS = 'http://www.w3.org/2000/svg',
- icon = document.createElementNS(svgNS, 'svg'),
- path = document.createElementNS(svgNS, 'path');
+ var iconHeight = thisIcon.ascent - thisIcon.descent,
+ svgNS = "http://www.w3.org/2000/svg",
+ icon = document.createElementNS(svgNS, "svg"),
+ path = document.createElementNS(svgNS, "path");
- icon.setAttribute('height', '1em');
- icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em');
- icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));
+ icon.setAttribute("height", "1em");
+ icon.setAttribute("width", thisIcon.width / iconHeight + "em");
+ icon.setAttribute("viewBox", [0, 0, thisIcon.width, iconHeight].join(" "));
- path.setAttribute('d', thisIcon.path);
- path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')');
- icon.appendChild(path);
+ path.setAttribute("d", thisIcon.path);
+ path.setAttribute("transform", "matrix(1 0 0 -1 0 " + thisIcon.ascent + ")");
+ icon.appendChild(path);
- return icon;
+ return icon;
};
/**
@@ -185,33 +180,31 @@ proto.createIcon = function(thisIcon) {
* @Return {HTMLelement}
*/
proto.updateActiveButton = function(buttonClicked) {
- var fullLayout = this.graphInfo._fullLayout,
- dataAttrClicked = (buttonClicked !== undefined) ?
- buttonClicked.getAttribute('data-attr') :
- null;
-
- this.buttonElements.forEach(function(button) {
- var thisval = button.getAttribute('data-val') || true,
- dataAttr = button.getAttribute('data-attr'),
- isToggleButton = (button.getAttribute('data-toggle') === 'true'),
- button3 = d3.select(button);
-
- // Use 'data-toggle' and 'buttonClicked' to toggle buttons
- // that have no one-to-one equivalent in fullLayout
- if(isToggleButton) {
- if(dataAttr === dataAttrClicked) {
- button3.classed('active', !button3.classed('active'));
- }
- }
- else {
- var val = (dataAttr === null) ?
- dataAttr :
- Lib.nestedProperty(fullLayout, dataAttr).get();
-
- button3.classed('active', val === thisval);
- }
-
- });
+ var fullLayout = this.graphInfo._fullLayout,
+ dataAttrClicked = buttonClicked !== undefined
+ ? buttonClicked.getAttribute("data-attr")
+ : null;
+
+ this.buttonElements.forEach(function(button) {
+ var thisval = button.getAttribute("data-val") || true,
+ dataAttr = button.getAttribute("data-attr"),
+ isToggleButton = button.getAttribute("data-toggle") === "true",
+ button3 = d3.select(button);
+
+ // Use 'data-toggle' and 'buttonClicked' to toggle buttons
+ // that have no one-to-one equivalent in fullLayout
+ if (isToggleButton) {
+ if (dataAttr === dataAttrClicked) {
+ button3.classed("active", !button3.classed("active"));
+ }
+ } else {
+ var val = dataAttr === null
+ ? dataAttr
+ : Lib.nestedProperty(fullLayout, dataAttr).get();
+
+ button3.classed("active", val === thisval);
+ }
+ });
};
/**
@@ -221,68 +214,69 @@ proto.updateActiveButton = function(buttonClicked) {
* @Return {boolean}
*/
proto.hasButtons = function(buttons) {
- var currentButtons = this.buttons;
+ var currentButtons = this.buttons;
- if(!currentButtons) return false;
+ if (!currentButtons) return false;
- if(buttons.length !== currentButtons.length) return false;
+ if (buttons.length !== currentButtons.length) return false;
- for(var i = 0; i < buttons.length; ++i) {
- if(buttons[i].length !== currentButtons[i].length) return false;
- for(var j = 0; j < buttons[i].length; j++) {
- if(buttons[i][j].name !== currentButtons[i][j].name) return false;
- }
+ for (var i = 0; i < buttons.length; ++i) {
+ if (buttons[i].length !== currentButtons[i].length) return false;
+ for (var j = 0; j < buttons[i].length; j++) {
+ if (buttons[i][j].name !== currentButtons[i][j].name) return false;
}
+ }
- return true;
+ return true;
};
/**
* @return {HTMLDivElement} The logo image wrapped in a group
*/
proto.getLogo = function() {
- var group = this.createGroup(),
- a = document.createElement('a');
+ var group = this.createGroup(), a = document.createElement("a");
- a.href = 'https://plot.ly/';
- a.target = '_blank';
- a.setAttribute('data-title', 'Produced with Plotly');
- a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';
+ a.href = "https://plot.ly/";
+ a.target = "_blank";
+ a.setAttribute("data-title", "Produced with Plotly");
+ a.className = "modebar-btn plotlyjsicon modebar-btn--logo";
- a.appendChild(this.createIcon(Icons.plotlylogo));
+ a.appendChild(this.createIcon(Icons.plotlylogo));
- group.appendChild(a);
- return group;
+ group.appendChild(a);
+ return group;
};
proto.removeAllButtons = function() {
- while(this.element.firstChild) {
- this.element.removeChild(this.element.firstChild);
- }
+ while (this.element.firstChild) {
+ this.element.removeChild(this.element.firstChild);
+ }
- this.hasLogo = false;
+ this.hasLogo = false;
};
proto.destroy = function() {
- Lib.removeElement(this.container.querySelector('.modebar'));
+ Lib.removeElement(this.container.querySelector(".modebar"));
};
function createModeBar(gd, buttons) {
- var fullLayout = gd._fullLayout;
-
- var modeBar = new ModeBar({
- graphInfo: gd,
- container: fullLayout._paperdiv.node(),
- buttons: buttons
- });
-
- if(fullLayout._privateplot) {
- d3.select(modeBar.element).append('span')
- .classed('badge-private float--left', true)
- .text('PRIVATE');
- }
-
- return modeBar;
+ var fullLayout = gd._fullLayout;
+
+ var modeBar = new ModeBar({
+ graphInfo: gd,
+ container: fullLayout._paperdiv.node(),
+ buttons: buttons
+ });
+
+ if (fullLayout._privateplot) {
+ d3
+ .select(modeBar.element)
+ .append("span")
+ .classed("badge-private float--left", true)
+ .text("PRIVATE");
+ }
+
+ return modeBar;
}
module.exports = createModeBar;
diff --git a/src/components/rangeselector/attributes.js b/src/components/rangeselector/attributes.js
index 390afe3e200..449c6b3cec3 100644
--- a/src/components/rangeselector/attributes.js
+++ b/src/components/rangeselector/attributes.js
@@ -5,99 +5,92 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var fontAttrs = require('../../plots/font_attributes');
-var colorAttrs = require('../color/attributes');
-var extendFlat = require('../../lib/extend').extendFlat;
-var buttonAttrs = require('./button_attributes');
+"use strict";
+var fontAttrs = require("../../plots/font_attributes");
+var colorAttrs = require("../color/attributes");
+var extendFlat = require("../../lib/extend").extendFlat;
+var buttonAttrs = require("./button_attributes");
buttonAttrs = extendFlat(buttonAttrs, {
- _isLinkedToArray: 'button',
-
- description: [
- 'Sets the specifications for each buttons.',
- 'By default, a range selector comes with no buttons.'
- ].join(' ')
+ _isLinkedToArray: "button",
+ description: [
+ "Sets the specifications for each buttons.",
+ "By default, a range selector comes with no buttons."
+ ].join(" ")
});
module.exports = {
- visible: {
- valType: 'boolean',
- role: 'info',
- description: [
- 'Determines whether or not this range selector is visible.',
- 'Note that range selectors are only available for x axes of',
- '`type` set to or auto-typed to *date*.'
- ].join(' ')
- },
-
- buttons: buttonAttrs,
-
- x: {
- valType: 'number',
- min: -2,
- max: 3,
- role: 'style',
- description: 'Sets the x position (in normalized coordinates) of the range selector.'
- },
- xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'left',
- role: 'info',
- description: [
- 'Sets the range selector\'s horizontal position anchor.',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the range selector.'
- ].join(' ')
- },
- y: {
- valType: 'number',
- min: -2,
- max: 3,
- role: 'style',
- description: 'Sets the y position (in normalized coordinates) of the range selector.'
- },
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'bottom',
- role: 'info',
- description: [
- 'Sets the range selector\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the range selector.'
- ].join(' ')
- },
-
- font: extendFlat({}, fontAttrs, {
- description: 'Sets the font of the range selector button text.'
- }),
-
- bgcolor: {
- valType: 'color',
- dflt: colorAttrs.lightLine,
- role: 'style',
- description: 'Sets the background color of the range selector buttons.'
- },
- activecolor: {
- valType: 'color',
- role: 'style',
- description: 'Sets the background color of the active range selector button.'
- },
- bordercolor: {
- valType: 'color',
- dflt: colorAttrs.defaultLine,
- role: 'style',
- description: 'Sets the color of the border enclosing the range selector.'
- },
- borderwidth: {
- valType: 'number',
- min: 0,
- dflt: 0,
- role: 'style',
- description: 'Sets the width (in px) of the border enclosing the range selector.'
- }
+ visible: {
+ valType: "boolean",
+ role: "info",
+ description: [
+ "Determines whether or not this range selector is visible.",
+ "Note that range selectors are only available for x axes of",
+ "`type` set to or auto-typed to *date*."
+ ].join(" ")
+ },
+ buttons: buttonAttrs,
+ x: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ role: "style",
+ description: "Sets the x position (in normalized coordinates) of the range selector."
+ },
+ xanchor: {
+ valType: "enumerated",
+ values: ["auto", "left", "center", "right"],
+ dflt: "left",
+ role: "info",
+ description: [
+ "Sets the range selector's horizontal position anchor.",
+ "This anchor binds the `x` position to the *left*, *center*",
+ "or *right* of the range selector."
+ ].join(" ")
+ },
+ y: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ role: "style",
+ description: "Sets the y position (in normalized coordinates) of the range selector."
+ },
+ yanchor: {
+ valType: "enumerated",
+ values: ["auto", "top", "middle", "bottom"],
+ dflt: "bottom",
+ role: "info",
+ description: [
+ "Sets the range selector's vertical position anchor",
+ "This anchor binds the `y` position to the *top*, *middle*",
+ "or *bottom* of the range selector."
+ ].join(" ")
+ },
+ font: extendFlat({}, fontAttrs, {
+ description: "Sets the font of the range selector button text."
+ }),
+ bgcolor: {
+ valType: "color",
+ dflt: colorAttrs.lightLine,
+ role: "style",
+ description: "Sets the background color of the range selector buttons."
+ },
+ activecolor: {
+ valType: "color",
+ role: "style",
+ description: "Sets the background color of the active range selector button."
+ },
+ bordercolor: {
+ valType: "color",
+ dflt: colorAttrs.defaultLine,
+ role: "style",
+ description: "Sets the color of the border enclosing the range selector."
+ },
+ borderwidth: {
+ valType: "number",
+ min: 0,
+ dflt: 0,
+ role: "style",
+ description: "Sets the width (in px) of the border enclosing the range selector."
+ }
};
diff --git a/src/components/rangeselector/button_attributes.js b/src/components/rangeselector/button_attributes.js
index 14fd193a0d4..7dc0e5b538a 100644
--- a/src/components/rangeselector/button_attributes.js
+++ b/src/components/rangeselector/button_attributes.js
@@ -5,52 +5,49 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
- step: {
- valType: 'enumerated',
- role: 'info',
- values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'],
- dflt: 'month',
- description: [
- 'The unit of measurement that the `count` value will set the range by.'
- ].join(' ')
- },
- stepmode: {
- valType: 'enumerated',
- role: 'info',
- values: ['backward', 'todate'],
- dflt: 'backward',
- description: [
- 'Sets the range update mode.',
- 'If *backward*, the range update shifts the start of range',
- 'back *count* times *step* milliseconds.',
- 'If *todate*, the range update shifts the start of range',
- 'back to the first timestamp from *count* times',
- '*step* milliseconds back.',
- 'For example, with `step` set to *year* and `count` set to *1*',
- 'the range update shifts the start of the range back to',
- 'January 01 of the current year.',
- 'Month and year *todate* are currently available only',
- 'for the built-in (Gregorian) calendar.'
- ].join(' ')
- },
- count: {
- valType: 'number',
- role: 'info',
- min: 0,
- dflt: 1,
- description: [
- 'Sets the number of steps to take to update the range.',
- 'Use with `step` to specify the update interval.'
- ].join(' ')
- },
- label: {
- valType: 'string',
- role: 'info',
- description: 'Sets the text label to appear on the button.'
- }
+ step: {
+ valType: "enumerated",
+ role: "info",
+ values: ["month", "year", "day", "hour", "minute", "second", "all"],
+ dflt: "month",
+ description: [
+ "The unit of measurement that the `count` value will set the range by."
+ ].join(" ")
+ },
+ stepmode: {
+ valType: "enumerated",
+ role: "info",
+ values: ["backward", "todate"],
+ dflt: "backward",
+ description: [
+ "Sets the range update mode.",
+ "If *backward*, the range update shifts the start of range",
+ "back *count* times *step* milliseconds.",
+ "If *todate*, the range update shifts the start of range",
+ "back to the first timestamp from *count* times",
+ "*step* milliseconds back.",
+ "For example, with `step` set to *year* and `count` set to *1*",
+ "the range update shifts the start of the range back to",
+ "January 01 of the current year.",
+ "Month and year *todate* are currently available only",
+ "for the built-in (Gregorian) calendar."
+ ].join(" ")
+ },
+ count: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ dflt: 1,
+ description: [
+ "Sets the number of steps to take to update the range.",
+ "Use with `step` to specify the update interval."
+ ].join(" ")
+ },
+ label: {
+ valType: "string",
+ role: "info",
+ description: "Sets the text label to appear on the button."
+ }
};
diff --git a/src/components/rangeselector/constants.js b/src/components/rangeselector/constants.js
index 202e73a1cc9..973f61c41db 100644
--- a/src/components/rangeselector/constants.js
+++ b/src/components/rangeselector/constants.js
@@ -5,23 +5,16 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
-
- // 'y' position pad above counter axis domain
- yPad: 0.02,
-
- // minimum button width (regardless of text size)
- minButtonWidth: 30,
-
- // buttons rect radii
- rx: 3,
- ry: 3,
-
- // light fraction used to compute the 'activecolor' default
- lightAmount: 25,
- darkAmount: 10
+ // 'y' position pad above counter axis domain
+ yPad: 0.02,
+ // minimum button width (regardless of text size)
+ minButtonWidth: 30,
+ // buttons rect radii
+ rx: 3,
+ ry: 3,
+ // light fraction used to compute the 'activecolor' default
+ lightAmount: 25,
+ darkAmount: 10
};
diff --git a/src/components/rangeselector/defaults.js b/src/components/rangeselector/defaults.js
index a2523d69621..0a350dedd88 100644
--- a/src/components/rangeselector/defaults.js
+++ b/src/components/rangeselector/defaults.js
@@ -5,93 +5,102 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Lib = require('../../lib');
-var Color = require('../color');
-
-var attributes = require('./attributes');
-var buttonAttrs = require('./button_attributes');
-var constants = require('./constants');
-
-
-module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) {
- var selectorIn = containerIn.rangeselector || {},
- selectorOut = containerOut.rangeselector = {};
-
- function coerce(attr, dflt) {
- return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
- }
-
- var buttons = buttonsDefaults(selectorIn, selectorOut, calendar);
-
- var visible = coerce('visible', buttons.length > 0);
- if(!visible) return;
-
- var posDflt = getPosDflt(containerOut, layout, counterAxes);
- coerce('x', posDflt[0]);
- coerce('y', posDflt[1]);
- Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);
-
- coerce('xanchor');
- coerce('yanchor');
-
- Lib.coerceFont(coerce, 'font', layout.font);
-
- var bgColor = coerce('bgcolor');
- coerce('activecolor', Color.contrast(bgColor, constants.lightAmount, constants.darkAmount));
- coerce('bordercolor');
- coerce('borderwidth');
+"use strict";
+var Lib = require("../../lib");
+var Color = require("../color");
+
+var attributes = require("./attributes");
+var buttonAttrs = require("./button_attributes");
+var constants = require("./constants");
+
+module.exports = function handleDefaults(
+ containerIn,
+ containerOut,
+ layout,
+ counterAxes,
+ calendar
+) {
+ var selectorIn = containerIn.rangeselector || {},
+ selectorOut = containerOut.rangeselector = {};
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
+ }
+
+ var buttons = buttonsDefaults(selectorIn, selectorOut, calendar);
+
+ var visible = coerce("visible", buttons.length > 0);
+ if (!visible) return;
+
+ var posDflt = getPosDflt(containerOut, layout, counterAxes);
+ coerce("x", posDflt[0]);
+ coerce("y", posDflt[1]);
+ Lib.noneOrAll(containerIn, containerOut, ["x", "y"]);
+
+ coerce("xanchor");
+ coerce("yanchor");
+
+ Lib.coerceFont(coerce, "font", layout.font);
+
+ var bgColor = coerce("bgcolor");
+ coerce(
+ "activecolor",
+ Color.contrast(bgColor, constants.lightAmount, constants.darkAmount)
+ );
+ coerce("bordercolor");
+ coerce("borderwidth");
};
function buttonsDefaults(containerIn, containerOut, calendar) {
- var buttonsIn = containerIn.buttons || [],
- buttonsOut = containerOut.buttons = [];
-
- var buttonIn, buttonOut;
-
- function coerce(attr, dflt) {
- return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
+ var buttonsIn = containerIn.buttons || [],
+ buttonsOut = containerOut.buttons = [];
+
+ var buttonIn, buttonOut;
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
+ }
+
+ for (var i = 0; i < buttonsIn.length; i++) {
+ buttonIn = buttonsIn[i];
+ buttonOut = {};
+
+ if (!Lib.isPlainObject(buttonIn)) continue;
+
+ var step = coerce("step");
+ if (step !== "all") {
+ if (
+ calendar &&
+ calendar !== "gregorian" &&
+ (step === "month" || step === "year")
+ ) {
+ buttonOut.stepmode = "backward";
+ } else {
+ coerce("stepmode");
+ }
+
+ coerce("count");
}
- for(var i = 0; i < buttonsIn.length; i++) {
- buttonIn = buttonsIn[i];
- buttonOut = {};
-
- if(!Lib.isPlainObject(buttonIn)) continue;
-
- var step = coerce('step');
- if(step !== 'all') {
- if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) {
- buttonOut.stepmode = 'backward';
- }
- else {
- coerce('stepmode');
- }
+ coerce("label");
- coerce('count');
- }
+ buttonOut._index = i;
+ buttonsOut.push(buttonOut);
+ }
- coerce('label');
-
- buttonOut._index = i;
- buttonsOut.push(buttonOut);
- }
-
- return buttonsOut;
+ return buttonsOut;
}
function getPosDflt(containerOut, layout, counterAxes) {
- var anchoredList = counterAxes.filter(function(ax) {
- return layout[ax].anchor === containerOut._id;
- });
-
- var posY = 0;
- for(var i = 0; i < anchoredList.length; i++) {
- var domain = layout[anchoredList[i]].domain;
- if(domain) posY = Math.max(domain[1], posY);
- }
+ var anchoredList = counterAxes.filter(function(ax) {
+ return layout[ax].anchor === containerOut._id;
+ });
+
+ var posY = 0;
+ for (var i = 0; i < anchoredList.length; i++) {
+ var domain = layout[anchoredList[i]].domain;
+ if (domain) posY = Math.max(domain[1], posY);
+ }
- return [containerOut.domain[0], posY + constants.yPad];
+ return [containerOut.domain[0], posY + constants.yPad];
}
diff --git a/src/components/rangeselector/draw.js b/src/components/rangeselector/draw.js
index 8dbc8ff4774..1879420a5bc 100644
--- a/src/components/rangeselector/draw.js
+++ b/src/components/rangeselector/draw.js
@@ -5,269 +5,248 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var Plotly = require("../../plotly");
+var Plots = require("../../plots/plots");
+var Color = require("../color");
+var Drawing = require("../drawing");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var axisIds = require("../../plots/cartesian/axis_ids");
+var anchorUtils = require("../legend/anchor_utils");
-'use strict';
-
-var d3 = require('d3');
-
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
-var Color = require('../color');
-var Drawing = require('../drawing');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var axisIds = require('../../plots/cartesian/axis_ids');
-var anchorUtils = require('../legend/anchor_utils');
-
-var constants = require('./constants');
-var getUpdateObject = require('./get_update_object');
-
+var constants = require("./constants");
+var getUpdateObject = require("./get_update_object");
module.exports = function draw(gd) {
- var fullLayout = gd._fullLayout;
-
- var selectors = fullLayout._infolayer.selectAll('.rangeselector')
- .data(makeSelectorData(gd), selectorKeyFunc);
+ var fullLayout = gd._fullLayout;
- selectors.enter().append('g')
- .classed('rangeselector', true);
+ var selectors = fullLayout._infolayer
+ .selectAll(".rangeselector")
+ .data(makeSelectorData(gd), selectorKeyFunc);
- selectors.exit().remove();
+ selectors.enter().append("g").classed("rangeselector", true);
- selectors.style({
- cursor: 'pointer',
- 'pointer-events': 'all'
- });
-
- selectors.each(function(d) {
- var selector = d3.select(this),
- axisLayout = d,
- selectorLayout = axisLayout.rangeselector;
+ selectors.exit().remove();
- var buttons = selector.selectAll('g.button')
- .data(selectorLayout.buttons);
+ selectors.style({ cursor: "pointer", "pointer-events": "all" });
- buttons.enter().append('g')
- .classed('button', true);
+ selectors.each(function(d) {
+ var selector = d3.select(this),
+ axisLayout = d,
+ selectorLayout = axisLayout.rangeselector;
- buttons.exit().remove();
+ var buttons = selector.selectAll("g.button").data(selectorLayout.buttons);
- buttons.each(function(d) {
- var button = d3.select(this);
- var update = getUpdateObject(axisLayout, d);
+ buttons.enter().append("g").classed("button", true);
- d.isActive = isActive(axisLayout, d, update);
+ buttons.exit().remove();
- button.call(drawButtonRect, selectorLayout, d);
- button.call(drawButtonText, selectorLayout, d);
+ buttons.each(function(d) {
+ var button = d3.select(this);
+ var update = getUpdateObject(axisLayout, d);
- button.on('click', function() {
- if(gd._dragged) return;
+ d.isActive = isActive(axisLayout, d, update);
- Plotly.relayout(gd, update);
- });
+ button.call(drawButtonRect, selectorLayout, d);
+ button.call(drawButtonText, selectorLayout, d);
- button.on('mouseover', function() {
- d.isHovered = true;
- button.call(drawButtonRect, selectorLayout, d);
- });
+ button.on("click", function() {
+ if (gd._dragged) return;
- button.on('mouseout', function() {
- d.isHovered = false;
- button.call(drawButtonRect, selectorLayout, d);
- });
- });
+ Plotly.relayout(gd, update);
+ });
- // N.B. this mutates selectorLayout
- reposition(gd, buttons, selectorLayout, axisLayout._name);
+ button.on("mouseover", function() {
+ d.isHovered = true;
+ button.call(drawButtonRect, selectorLayout, d);
+ });
- selector.attr('transform', 'translate(' +
- selectorLayout.lx + ',' + selectorLayout.ly +
- ')');
+ button.on("mouseout", function() {
+ d.isHovered = false;
+ button.call(drawButtonRect, selectorLayout, d);
+ });
});
+ // N.B. this mutates selectorLayout
+ reposition(gd, buttons, selectorLayout, axisLayout._name);
+
+ selector.attr(
+ "transform",
+ "translate(" + selectorLayout.lx + "," + selectorLayout.ly + ")"
+ );
+ });
};
function makeSelectorData(gd) {
- var axes = axisIds.list(gd, 'x', true);
- var data = [];
+ var axes = axisIds.list(gd, "x", true);
+ var data = [];
- for(var i = 0; i < axes.length; i++) {
- var axis = axes[i];
+ for (var i = 0; i < axes.length; i++) {
+ var axis = axes[i];
- if(axis.rangeselector && axis.rangeselector.visible) {
- data.push(axis);
- }
+ if (axis.rangeselector && axis.rangeselector.visible) {
+ data.push(axis);
}
+ }
- return data;
+ return data;
}
function selectorKeyFunc(d) {
- return d._id;
+ return d._id;
}
function isActive(axisLayout, opts, update) {
- if(opts.step === 'all') {
- return axisLayout.autorange === true;
- }
- else {
- var keys = Object.keys(update);
-
- return (
- axisLayout.range[0] === update[keys[0]] &&
- axisLayout.range[1] === update[keys[1]]
- );
- }
+ if (opts.step === "all") {
+ return axisLayout.autorange === true;
+ } else {
+ var keys = Object.keys(update);
+
+ return axisLayout.range[0] === update[keys[0]] &&
+ axisLayout.range[1] === update[keys[1]];
+ }
}
function drawButtonRect(button, selectorLayout, d) {
- var rect = button.selectAll('rect')
- .data([0]);
+ var rect = button.selectAll("rect").data([0]);
- rect.enter().append('rect')
- .classed('selector-rect', true);
+ rect.enter().append("rect").classed("selector-rect", true);
- rect.attr('shape-rendering', 'crispEdges');
+ rect.attr("shape-rendering", "crispEdges");
- rect.attr({
- 'rx': constants.rx,
- 'ry': constants.ry
- });
+ rect.attr({ rx: constants.rx, ry: constants.ry });
- rect.call(Color.stroke, selectorLayout.bordercolor)
- .call(Color.fill, getFillColor(selectorLayout, d))
- .style('stroke-width', selectorLayout.borderwidth + 'px');
+ rect
+ .call(Color.stroke, selectorLayout.bordercolor)
+ .call(Color.fill, getFillColor(selectorLayout, d))
+ .style("stroke-width", selectorLayout.borderwidth + "px");
}
function getFillColor(selectorLayout, d) {
- return (d.isActive || d.isHovered) ?
- selectorLayout.activecolor :
- selectorLayout.bgcolor;
+ return d.isActive || d.isHovered
+ ? selectorLayout.activecolor
+ : selectorLayout.bgcolor;
}
function drawButtonText(button, selectorLayout, d) {
- function textLayout(s) {
- svgTextUtils.convertToTspans(s);
+ function textLayout(s) {
+ svgTextUtils.convertToTspans(s);
+ // TODO do we need anything else here?
+ }
- // TODO do we need anything else here?
- }
+ var text = button.selectAll("text").data([0]);
- var text = button.selectAll('text')
- .data([0]);
+ text
+ .enter()
+ .append("text")
+ .classed("selector-text", true)
+ .classed("user-select-none", true);
- text.enter().append('text')
- .classed('selector-text', true)
- .classed('user-select-none', true);
+ text.attr("text-anchor", "middle");
- text.attr('text-anchor', 'middle');
-
- text.call(Drawing.font, selectorLayout.font)
- .text(getLabel(d))
- .call(textLayout);
+ text
+ .call(Drawing.font, selectorLayout.font)
+ .text(getLabel(d))
+ .call(textLayout);
}
function getLabel(opts) {
- if(opts.label) return opts.label;
+ if (opts.label) return opts.label;
- if(opts.step === 'all') return 'all';
+ if (opts.step === "all") return "all";
- return opts.count + opts.step.charAt(0);
+ return opts.count + opts.step.charAt(0);
}
function reposition(gd, buttons, opts, axName) {
- opts.width = 0;
- opts.height = 0;
-
- var borderWidth = opts.borderwidth;
-
- buttons.each(function() {
- var button = d3.select(this),
- text = button.select('.selector-text'),
- tspans = text.selectAll('tspan');
-
- var tHeight = opts.font.size * 1.3,
- tLines = tspans[0].length || 1,
- hEff = Math.max(tHeight * tLines, 16) + 3;
-
- opts.height = Math.max(opts.height, hEff);
- });
-
- buttons.each(function() {
- var button = d3.select(this),
- rect = button.select('.selector-rect'),
- text = button.select('.selector-text'),
- tspans = text.selectAll('tspan');
-
- var tWidth = text.node() && Drawing.bBox(text.node()).width,
- tHeight = opts.font.size * 1.3,
- tLines = tspans[0].length || 1;
-
- var wEff = Math.max(tWidth + 10, constants.minButtonWidth);
-
- // TODO add MathJax support
-
- // TODO add buttongap attribute
-
- button.attr('transform', 'translate(' +
- (borderWidth + opts.width) + ',' + borderWidth +
- ')');
-
- rect.attr({
- x: 0,
- y: 0,
- width: wEff,
- height: opts.height
- });
-
- var textAttrs = {
- x: wEff / 2,
- y: opts.height / 2 - ((tLines - 1) * tHeight / 2) + 3
- };
-
- text.attr(textAttrs);
- tspans.attr(textAttrs);
-
- opts.width += wEff + 5;
- });
-
- buttons.selectAll('rect').attr('height', opts.height);
-
- var graphSize = gd._fullLayout._size;
- opts.lx = graphSize.l + graphSize.w * opts.x;
- opts.ly = graphSize.t + graphSize.h * (1 - opts.y);
-
- var xanchor = 'left';
- if(anchorUtils.isRightAnchor(opts)) {
- opts.lx -= opts.width;
- xanchor = 'right';
- }
- if(anchorUtils.isCenterAnchor(opts)) {
- opts.lx -= opts.width / 2;
- xanchor = 'center';
- }
-
- var yanchor = 'top';
- if(anchorUtils.isBottomAnchor(opts)) {
- opts.ly -= opts.height;
- yanchor = 'bottom';
- }
- if(anchorUtils.isMiddleAnchor(opts)) {
- opts.ly -= opts.height / 2;
- yanchor = 'middle';
- }
-
- opts.width = Math.ceil(opts.width);
- opts.height = Math.ceil(opts.height);
- opts.lx = Math.round(opts.lx);
- opts.ly = Math.round(opts.ly);
-
- Plots.autoMargin(gd, axName + '-range-selector', {
- x: opts.x,
- y: opts.y,
- l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
- r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0),
- b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
- t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
- });
+ opts.width = 0;
+ opts.height = 0;
+
+ var borderWidth = opts.borderwidth;
+
+ buttons.each(function() {
+ var button = d3.select(this),
+ text = button.select(".selector-text"),
+ tspans = text.selectAll("tspan");
+
+ var tHeight = opts.font.size * 1.3,
+ tLines = tspans[0].length || 1,
+ hEff = Math.max(tHeight * tLines, 16) + 3;
+
+ opts.height = Math.max(opts.height, hEff);
+ });
+
+ buttons.each(function() {
+ var button = d3.select(this),
+ rect = button.select(".selector-rect"),
+ text = button.select(".selector-text"),
+ tspans = text.selectAll("tspan");
+
+ var tWidth = text.node() && Drawing.bBox(text.node()).width,
+ tHeight = opts.font.size * 1.3,
+ tLines = tspans[0].length || 1;
+
+ var wEff = Math.max(tWidth + 10, constants.minButtonWidth);
+
+ // TODO add MathJax support
+ // TODO add buttongap attribute
+ button.attr(
+ "transform",
+ "translate(" + (borderWidth + opts.width) + "," + borderWidth + ")"
+ );
+
+ rect.attr({ x: 0, y: 0, width: wEff, height: opts.height });
+
+ var textAttrs = {
+ x: wEff / 2,
+ y: opts.height / 2 - (tLines - 1) * tHeight / 2 + 3
+ };
+
+ text.attr(textAttrs);
+ tspans.attr(textAttrs);
+
+ opts.width += wEff + 5;
+ });
+
+ buttons.selectAll("rect").attr("height", opts.height);
+
+ var graphSize = gd._fullLayout._size;
+ opts.lx = graphSize.l + graphSize.w * opts.x;
+ opts.ly = graphSize.t + graphSize.h * (1 - opts.y);
+
+ var xanchor = "left";
+ if (anchorUtils.isRightAnchor(opts)) {
+ opts.lx -= opts.width;
+ xanchor = "right";
+ }
+ if (anchorUtils.isCenterAnchor(opts)) {
+ opts.lx -= opts.width / 2;
+ xanchor = "center";
+ }
+
+ var yanchor = "top";
+ if (anchorUtils.isBottomAnchor(opts)) {
+ opts.ly -= opts.height;
+ yanchor = "bottom";
+ }
+ if (anchorUtils.isMiddleAnchor(opts)) {
+ opts.ly -= opts.height / 2;
+ yanchor = "middle";
+ }
+
+ opts.width = Math.ceil(opts.width);
+ opts.height = Math.ceil(opts.height);
+ opts.lx = Math.round(opts.lx);
+ opts.ly = Math.round(opts.ly);
+
+ Plots.autoMargin(gd, axName + "-range-selector", {
+ x: opts.x,
+ y: opts.y,
+ l: opts.width * (({ right: 1, center: 0.5 })[xanchor] || 0),
+ r: opts.width * (({ left: 1, center: 0.5 })[xanchor] || 0),
+ b: opts.height * (({ top: 1, middle: 0.5 })[yanchor] || 0),
+ t: opts.height * (({ bottom: 1, middle: 0.5 })[yanchor] || 0)
+ });
}
diff --git a/src/components/rangeselector/get_update_object.js b/src/components/rangeselector/get_update_object.js
index e8fa28971c7..eafc64b456c 100644
--- a/src/components/rangeselector/get_update_object.js
+++ b/src/components/rangeselector/get_update_object.js
@@ -5,51 +5,46 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
+"use strict";
+var d3 = require("d3");
module.exports = function getUpdateObject(axisLayout, buttonLayout) {
- var axName = axisLayout._name;
- var update = {};
+ var axName = axisLayout._name;
+ var update = {};
- if(buttonLayout.step === 'all') {
- update[axName + '.autorange'] = true;
- }
- else {
- var xrange = getXRange(axisLayout, buttonLayout);
+ if (buttonLayout.step === "all") {
+ update[axName + ".autorange"] = true;
+ } else {
+ var xrange = getXRange(axisLayout, buttonLayout);
- update[axName + '.range[0]'] = xrange[0];
- update[axName + '.range[1]'] = xrange[1];
- }
+ update[axName + ".range[0]"] = xrange[0];
+ update[axName + ".range[1]"] = xrange[1];
+ }
- return update;
+ return update;
};
function getXRange(axisLayout, buttonLayout) {
- var currentRange = axisLayout.range;
- var base = new Date(axisLayout.r2l(currentRange[1]));
+ var currentRange = axisLayout.range;
+ var base = new Date(axisLayout.r2l(currentRange[1]));
- var step = buttonLayout.step,
- count = buttonLayout.count;
+ var step = buttonLayout.step, count = buttonLayout.count;
- var range0;
+ var range0;
- switch(buttonLayout.stepmode) {
- case 'backward':
- range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count));
- break;
+ switch (buttonLayout.stepmode) {
+ case "backward":
+ range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count));
+ break;
- case 'todate':
- var base2 = d3.time[step].utc.offset(base, -count);
+ case "todate":
+ var base2 = d3.time[step].utc.offset(base, -count);
- range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2));
- break;
- }
+ range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2));
+ break;
+ }
- var range1 = currentRange[1];
+ var range1 = currentRange[1];
- return [range0, range1];
+ return [range0, range1];
}
diff --git a/src/components/rangeselector/index.js b/src/components/rangeselector/index.js
index a4e3e7cfd58..945b46bad07 100644
--- a/src/components/rangeselector/index.js
+++ b/src/components/rangeselector/index.js
@@ -5,21 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
- moduleType: 'component',
- name: 'rangeselector',
-
- schema: {
- layout: {
- 'xaxis.rangeselector': require('./attributes')
- }
- },
-
- layoutAttributes: require('./attributes'),
- handleDefaults: require('./defaults'),
-
- draw: require('./draw')
+ moduleType: "component",
+ name: "rangeselector",
+ schema: { layout: { "xaxis.rangeselector": require("./attributes") } },
+ layoutAttributes: require("./attributes"),
+ handleDefaults: require("./defaults"),
+ draw: require("./draw")
};
diff --git a/src/components/rangeslider/attributes.js b/src/components/rangeslider/attributes.js
index d81dfbfaecf..f02f1bcb745 100644
--- a/src/components/rangeslider/attributes.js
+++ b/src/components/rangeslider/attributes.js
@@ -5,69 +5,64 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var colorAttributes = require('../color/attributes');
+"use strict";
+var colorAttributes = require("../color/attributes");
module.exports = {
- bgcolor: {
- valType: 'color',
- dflt: colorAttributes.background,
- role: 'style',
- description: 'Sets the background color of the range slider.'
- },
- bordercolor: {
- valType: 'color',
- dflt: colorAttributes.defaultLine,
- role: 'style',
- description: 'Sets the border color of the range slider.'
- },
- borderwidth: {
- valType: 'integer',
- dflt: 0,
- min: 0,
- role: 'style',
- description: 'Sets the border color of the range slider.'
- },
- range: {
- valType: 'info_array',
- role: 'info',
- items: [
- {valType: 'any'},
- {valType: 'any'}
- ],
- description: [
- 'Sets the range of the range slider.',
- 'If not set, defaults to the full xaxis range.',
- 'If the axis `type` is *log*, then you must take the',
- 'log of your desired range.',
- 'If the axis `type` is *date*, it should be date strings,',
- 'like date data, though Date objects and unix milliseconds',
- 'will be accepted and converted to strings.',
- 'If the axis `type` is *category*, it should be numbers,',
- 'using the scale where each category is assigned a serial',
- 'number from zero in the order it appears.'
- ].join(' ')
- },
- thickness: {
- valType: 'number',
- dflt: 0.15,
- min: 0,
- max: 1,
- role: 'style',
- description: [
- 'The height of the range slider as a fraction of the',
- 'total plot area height.'
- ].join(' ')
- },
- visible: {
- valType: 'boolean',
- dflt: true,
- role: 'info',
- description: [
- 'Determines whether or not the range slider will be visible.',
- 'If visible, perpendicular axes will be set to `fixedrange`'
- ].join(' ')
- }
+ bgcolor: {
+ valType: "color",
+ dflt: colorAttributes.background,
+ role: "style",
+ description: "Sets the background color of the range slider."
+ },
+ bordercolor: {
+ valType: "color",
+ dflt: colorAttributes.defaultLine,
+ role: "style",
+ description: "Sets the border color of the range slider."
+ },
+ borderwidth: {
+ valType: "integer",
+ dflt: 0,
+ min: 0,
+ role: "style",
+ description: "Sets the border color of the range slider."
+ },
+ range: {
+ valType: "info_array",
+ role: "info",
+ items: [{ valType: "any" }, { valType: "any" }],
+ description: [
+ "Sets the range of the range slider.",
+ "If not set, defaults to the full xaxis range.",
+ "If the axis `type` is *log*, then you must take the",
+ "log of your desired range.",
+ "If the axis `type` is *date*, it should be date strings,",
+ "like date data, though Date objects and unix milliseconds",
+ "will be accepted and converted to strings.",
+ "If the axis `type` is *category*, it should be numbers,",
+ "using the scale where each category is assigned a serial",
+ "number from zero in the order it appears."
+ ].join(" ")
+ },
+ thickness: {
+ valType: "number",
+ dflt: 0.15,
+ min: 0,
+ max: 1,
+ role: "style",
+ description: [
+ "The height of the range slider as a fraction of the",
+ "total plot area height."
+ ].join(" ")
+ },
+ visible: {
+ valType: "boolean",
+ dflt: true,
+ role: "info",
+ description: [
+ "Determines whether or not the range slider will be visible.",
+ "If visible, perpendicular axes will be set to `fixedrange`"
+ ].join(" ")
+ }
};
diff --git a/src/components/rangeslider/constants.js b/src/components/rangeslider/constants.js
index 9adc42e6407..ce761a356fa 100644
--- a/src/components/rangeslider/constants.js
+++ b/src/components/rangeslider/constants.js
@@ -5,47 +5,34 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
-
- // attribute container name
- name: 'rangeslider',
-
- // class names
-
- containerClassName: 'rangeslider-container',
- bgClassName: 'rangeslider-bg',
- rangePlotClassName: 'rangeslider-rangeplot',
-
- maskMinClassName: 'rangeslider-mask-min',
- maskMaxClassName: 'rangeslider-mask-max',
- slideBoxClassName: 'rangeslider-slidebox',
-
- grabberMinClassName: 'rangeslider-grabber-min',
- grabAreaMinClassName: 'rangeslider-grabarea-min',
- handleMinClassName: 'rangeslider-handle-min',
-
- grabberMaxClassName: 'rangeslider-grabber-max',
- grabAreaMaxClassName: 'rangeslider-grabarea-max',
- handleMaxClassName: 'rangeslider-handle-max',
-
- // style constants
-
- maskColor: 'rgba(0,0,0,0.4)',
-
- slideBoxFill: 'transparent',
- slideBoxCursor: 'ew-resize',
-
- grabAreaFill: 'transparent',
- grabAreaCursor: 'col-resize',
- grabAreaWidth: 10,
- grabAreaMinOffset: -6,
- grabAreaMaxOffset: -2,
-
- handleWidth: 2,
- handleRadius: 1,
- handleFill: '#fff',
- handleStroke: '#666',
+ // attribute container name
+ name: "rangeslider",
+ // class names
+ containerClassName: "rangeslider-container",
+ bgClassName: "rangeslider-bg",
+ rangePlotClassName: "rangeslider-rangeplot",
+ maskMinClassName: "rangeslider-mask-min",
+ maskMaxClassName: "rangeslider-mask-max",
+ slideBoxClassName: "rangeslider-slidebox",
+ grabberMinClassName: "rangeslider-grabber-min",
+ grabAreaMinClassName: "rangeslider-grabarea-min",
+ handleMinClassName: "rangeslider-handle-min",
+ grabberMaxClassName: "rangeslider-grabber-max",
+ grabAreaMaxClassName: "rangeslider-grabarea-max",
+ handleMaxClassName: "rangeslider-handle-max",
+ // style constants
+ maskColor: "rgba(0,0,0,0.4)",
+ slideBoxFill: "transparent",
+ slideBoxCursor: "ew-resize",
+ grabAreaFill: "transparent",
+ grabAreaCursor: "col-resize",
+ grabAreaWidth: 10,
+ grabAreaMinOffset: -6,
+ grabAreaMaxOffset: -2,
+ handleWidth: 2,
+ handleRadius: 1,
+ handleFill: "#fff",
+ handleStroke: "#666"
};
diff --git a/src/components/rangeslider/defaults.js b/src/components/rangeslider/defaults.js
index 379a4596b84..21fec917766 100644
--- a/src/components/rangeslider/defaults.js
+++ b/src/components/rangeslider/defaults.js
@@ -5,48 +5,48 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Lib = require('../../lib');
-var attributes = require('./attributes');
-
+"use strict";
+var Lib = require("../../lib");
+var attributes = require("./attributes");
module.exports = function handleDefaults(layoutIn, layoutOut, axName) {
- if(!layoutIn[axName].rangeslider) return;
-
- // not super proud of this (maybe store _ in axis object instead
- if(!Lib.isPlainObject(layoutIn[axName].rangeslider)) {
- layoutIn[axName].rangeslider = {};
- }
-
- var containerIn = layoutIn[axName].rangeslider,
- axOut = layoutOut[axName],
- containerOut = axOut.rangeslider = {};
-
- function coerce(attr, dflt) {
- return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
- }
-
- coerce('bgcolor', layoutOut.plot_bgcolor);
- coerce('bordercolor');
- coerce('borderwidth');
- coerce('thickness');
- coerce('visible');
- coerce('range');
-
- // Expand slider range to the axis range
- if(containerOut.range && !axOut.autorange) {
- // TODO: what if the ranges are reversed?
- var outRange = containerOut.range,
- axRange = axOut.range;
-
- outRange[0] = axOut.l2r(Math.min(axOut.r2l(outRange[0]), axOut.r2l(axRange[0])));
- outRange[1] = axOut.l2r(Math.max(axOut.r2l(outRange[1]), axOut.r2l(axRange[1])));
- } else {
- axOut._needsExpand = true;
- }
-
- // to map back range slider (auto) range
- containerOut._input = containerIn;
+ if (!layoutIn[axName].rangeslider) return;
+
+ // not super proud of this (maybe store _ in axis object instead
+ if (!Lib.isPlainObject(layoutIn[axName].rangeslider)) {
+ layoutIn[axName].rangeslider = {};
+ }
+
+ var containerIn = layoutIn[axName].rangeslider,
+ axOut = layoutOut[axName],
+ containerOut = axOut.rangeslider = {};
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
+ }
+
+ coerce("bgcolor", layoutOut.plot_bgcolor);
+ coerce("bordercolor");
+ coerce("borderwidth");
+ coerce("thickness");
+ coerce("visible");
+ coerce("range");
+
+ // Expand slider range to the axis range
+ if (containerOut.range && !axOut.autorange) {
+ // TODO: what if the ranges are reversed?
+ var outRange = containerOut.range, axRange = axOut.range;
+
+ outRange[0] = axOut.l2r(
+ Math.min(axOut.r2l(outRange[0]), axOut.r2l(axRange[0]))
+ );
+ outRange[1] = axOut.l2r(
+ Math.max(axOut.r2l(outRange[1]), axOut.r2l(axRange[1]))
+ );
+ } else {
+ axOut._needsExpand = true;
+ }
+
+ // to map back range slider (auto) range
+ containerOut._input = containerIn;
};
diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js
index ded3198d316..5c7acf9b850 100644
--- a/src/components/rangeslider/draw.js
+++ b/src/components/rangeslider/draw.js
@@ -5,32 +5,29 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
-'use strict';
+var Plotly = require("../../plotly");
+var Plots = require("../../plots/plots");
-var d3 = require('d3');
+var Lib = require("../../lib");
+var Drawing = require("../drawing");
+var Color = require("../color");
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
+var Cartesian = require("../../plots/cartesian");
+var Axes = require("../../plots/cartesian/axes");
-var Lib = require('../../lib');
-var Drawing = require('../drawing');
-var Color = require('../color');
-
-var Cartesian = require('../../plots/cartesian');
-var Axes = require('../../plots/cartesian/axes');
-
-var dragElement = require('../dragelement');
-var setCursor = require('../../lib/setcursor');
-
-var constants = require('./constants');
+var dragElement = require("../dragelement");
+var setCursor = require("../../lib/setcursor");
+var constants = require("./constants");
module.exports = function(gd) {
- var fullLayout = gd._fullLayout,
- rangeSliderData = makeRangeSliderData(fullLayout);
+ var fullLayout = gd._fullLayout,
+ rangeSliderData = makeRangeSliderData(fullLayout);
- /*
+ /*
*
*
* < .... range plot />
@@ -46,484 +43,489 @@ module.exports = function(gd) {
*
* ...
*/
-
- function keyFunction(axisOpts) {
- return axisOpts._name;
+ function keyFunction(axisOpts) {
+ return axisOpts._name;
+ }
+
+ var rangeSliders = fullLayout._infolayer
+ .selectAll("g." + constants.containerClassName)
+ .data(rangeSliderData, keyFunction);
+
+ rangeSliders
+ .enter()
+ .append("g")
+ .classed(constants.containerClassName, true)
+ .attr("pointer-events", "all");
+
+ // remove exiting sliders and their corresponding clip paths
+ rangeSliders.exit().each(function(axisOpts) {
+ var rangeSlider = d3.select(this), opts = axisOpts[constants.name];
+
+ rangeSlider.remove();
+ fullLayout._topdefs.select("#" + opts._clipId).remove();
+ });
+
+ // remove push margin object(s)
+ if (rangeSliders.exit().size()) clearPushMargins(gd);
+
+ // return early if no range slider is visible
+ if (rangeSliderData.length === 0) return;
+
+ // for all present range sliders
+ rangeSliders.each(function(axisOpts) {
+ var rangeSlider = d3.select(this), opts = axisOpts[constants.name];
+
+ // compute new slider range using axis autorange if necessary
+ // copy back range to input range slider container to skip
+ // this step in subsequent draw calls
+ if (!opts.range) {
+ opts._input.range = opts.range = Axes.getAutoRange(axisOpts);
}
- var rangeSliders = fullLayout._infolayer
- .selectAll('g.' + constants.containerClassName)
- .data(rangeSliderData, keyFunction);
-
- rangeSliders.enter().append('g')
- .classed(constants.containerClassName, true)
- .attr('pointer-events', 'all');
-
- // remove exiting sliders and their corresponding clip paths
- rangeSliders.exit().each(function(axisOpts) {
- var rangeSlider = d3.select(this),
- opts = axisOpts[constants.name];
-
- rangeSlider.remove();
- fullLayout._topdefs.select('#' + opts._clipId).remove();
- });
-
- // remove push margin object(s)
- if(rangeSliders.exit().size()) clearPushMargins(gd);
-
- // return early if no range slider is visible
- if(rangeSliderData.length === 0) return;
-
- // for all present range sliders
- rangeSliders.each(function(axisOpts) {
- var rangeSlider = d3.select(this),
- opts = axisOpts[constants.name];
-
- // compute new slider range using axis autorange if necessary
- // copy back range to input range slider container to skip
- // this step in subsequent draw calls
- if(!opts.range) {
- opts._input.range = opts.range = Axes.getAutoRange(axisOpts);
- }
-
- // update range slider dimensions
-
- var margin = fullLayout.margin,
- graphSize = fullLayout._size,
- domain = axisOpts.domain;
-
- opts._id = constants.name + axisOpts._id;
- opts._clipId = opts._id + '-' + fullLayout._uid;
-
- opts._width = graphSize.w * (domain[1] - domain[0]);
- opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
- opts._offsetShift = Math.floor(opts.borderwidth / 2);
-
- var x = margin.l + (graphSize.w * domain[0]),
- y = fullLayout.height - opts._height - margin.b;
-
- rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')');
-
- // update data <--> pixel coordinate conversion methods
+ // update range slider dimensions
+ var margin = fullLayout.margin,
+ graphSize = fullLayout._size,
+ domain = axisOpts.domain;
- var range0 = axisOpts.r2l(opts.range[0]),
- range1 = axisOpts.r2l(opts.range[1]),
- dist = range1 - range0;
+ opts._id = constants.name + axisOpts._id;
+ opts._clipId = opts._id + "-" + fullLayout._uid;
- opts.p2d = function(v) {
- return (v / opts._width) * dist + range0;
- };
+ opts._width = graphSize.w * (domain[1] - domain[0]);
+ opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
+ opts._offsetShift = Math.floor(opts.borderwidth / 2);
- opts.d2p = function(v) {
- return (v - range0) / dist * opts._width;
- };
+ var x = margin.l + graphSize.w * domain[0],
+ y = fullLayout.height - opts._height - margin.b;
- opts._rl = [range0, range1];
+ rangeSlider.attr("transform", "translate(" + x + "," + y + ")");
- // update inner nodes
+ // update data <--> pixel coordinate conversion methods
+ var range0 = axisOpts.r2l(opts.range[0]),
+ range1 = axisOpts.r2l(opts.range[1]),
+ dist = range1 - range0;
- rangeSlider
- .call(drawBg, gd, axisOpts, opts)
- .call(addClipPath, gd, axisOpts, opts)
- .call(drawRangePlot, gd, axisOpts, opts)
- .call(drawMasks, gd, axisOpts, opts)
- .call(drawSlideBox, gd, axisOpts, opts)
- .call(drawGrabbers, gd, axisOpts, opts);
-
- // setup drag element
- setupDragElement(rangeSlider, gd, axisOpts, opts);
-
- // update current range
- setPixelRange(rangeSlider, gd, axisOpts, opts);
-
- // update margins
+ opts.p2d = function(v) {
+ return v / opts._width * dist + range0;
+ };
- var bb = axisOpts._boundingBox ? axisOpts._boundingBox.height : 0;
+ opts.d2p = function(v) {
+ return (v - range0) / dist * opts._width;
+ };
- Plots.autoMargin(gd, opts._id, {
- x: 0, y: 0, l: 0, r: 0, t: 0,
- b: opts._height + fullLayout.margin.b + bb,
- pad: 15 + opts._offsetShift * 2
- });
+ opts._rl = [range0, range1];
+
+ // update inner nodes
+ rangeSlider
+ .call(drawBg, gd, axisOpts, opts)
+ .call(addClipPath, gd, axisOpts, opts)
+ .call(drawRangePlot, gd, axisOpts, opts)
+ .call(drawMasks, gd, axisOpts, opts)
+ .call(drawSlideBox, gd, axisOpts, opts)
+ .call(drawGrabbers, gd, axisOpts, opts);
+
+ // setup drag element
+ setupDragElement(rangeSlider, gd, axisOpts, opts);
+
+ // update current range
+ setPixelRange(rangeSlider, gd, axisOpts, opts);
+
+ // update margins
+ var bb = axisOpts._boundingBox ? axisOpts._boundingBox.height : 0;
+
+ Plots.autoMargin(gd, opts._id, {
+ x: 0,
+ y: 0,
+ l: 0,
+ r: 0,
+ t: 0,
+ b: opts._height + fullLayout.margin.b + bb,
+ pad: 15 + opts._offsetShift * 2
});
+ });
};
function makeRangeSliderData(fullLayout) {
- if(!fullLayout.xaxis) return [];
- if(!fullLayout.xaxis[constants.name]) return [];
- if(!fullLayout.xaxis[constants.name].visible) return [];
- if(fullLayout._has('gl2d')) return [];
+ if (!fullLayout.xaxis) return [];
+ if (!fullLayout.xaxis[constants.name]) return [];
+ if (!fullLayout.xaxis[constants.name].visible) return [];
+ if (fullLayout._has("gl2d")) return [];
- return [fullLayout.xaxis];
+ return [fullLayout.xaxis];
}
function setupDragElement(rangeSlider, gd, axisOpts, opts) {
- var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(),
- grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(),
- grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node();
-
- rangeSlider.on('mousedown', function() {
- var event = d3.event,
- target = event.target,
- startX = event.clientX,
- offsetX = startX - rangeSlider.node().getBoundingClientRect().left,
- minVal = opts.d2p(axisOpts._rl[0]),
- maxVal = opts.d2p(axisOpts._rl[1]);
-
- var dragCover = dragElement.coverSlip();
-
- dragCover.addEventListener('mousemove', mouseMove);
- dragCover.addEventListener('mouseup', mouseUp);
-
- function mouseMove(e) {
- var delta = +e.clientX - startX;
- var pixelMin, pixelMax, cursor;
-
- switch(target) {
- case slideBox:
- cursor = 'ew-resize';
- pixelMin = minVal + delta;
- pixelMax = maxVal + delta;
- break;
-
- case grabAreaMin:
- cursor = 'col-resize';
- pixelMin = minVal + delta;
- pixelMax = maxVal;
- break;
-
- case grabAreaMax:
- cursor = 'col-resize';
- pixelMin = minVal;
- pixelMax = maxVal + delta;
- break;
-
- default:
- cursor = 'ew-resize';
- pixelMin = offsetX;
- pixelMax = offsetX + delta;
- break;
- }
-
- if(pixelMax < pixelMin) {
- var tmp = pixelMax;
- pixelMax = pixelMin;
- pixelMin = tmp;
- }
-
- opts._pixelMin = pixelMin;
- opts._pixelMax = pixelMax;
-
- setCursor(d3.select(dragCover), cursor);
- setDataRange(rangeSlider, gd, axisOpts, opts);
- }
-
- function mouseUp() {
- dragCover.removeEventListener('mousemove', mouseMove);
- dragCover.removeEventListener('mouseup', mouseUp);
- Lib.removeElement(dragCover);
- }
- });
+ var slideBox = rangeSlider
+ .select("rect." + constants.slideBoxClassName)
+ .node(),
+ grabAreaMin = rangeSlider
+ .select("rect." + constants.grabAreaMinClassName)
+ .node(),
+ grabAreaMax = rangeSlider
+ .select("rect." + constants.grabAreaMaxClassName)
+ .node();
+
+ rangeSlider.on("mousedown", function() {
+ var event = d3.event,
+ target = event.target,
+ startX = event.clientX,
+ offsetX = startX - rangeSlider.node().getBoundingClientRect().left,
+ minVal = opts.d2p(axisOpts._rl[0]),
+ maxVal = opts.d2p(axisOpts._rl[1]);
+
+ var dragCover = dragElement.coverSlip();
+
+ dragCover.addEventListener("mousemove", mouseMove);
+ dragCover.addEventListener("mouseup", mouseUp);
+
+ function mouseMove(e) {
+ var delta = +e.clientX - startX;
+ var pixelMin, pixelMax, cursor;
+
+ switch (target) {
+ case slideBox:
+ cursor = "ew-resize";
+ pixelMin = minVal + delta;
+ pixelMax = maxVal + delta;
+ break;
+
+ case grabAreaMin:
+ cursor = "col-resize";
+ pixelMin = minVal + delta;
+ pixelMax = maxVal;
+ break;
+
+ case grabAreaMax:
+ cursor = "col-resize";
+ pixelMin = minVal;
+ pixelMax = maxVal + delta;
+ break;
+
+ default:
+ cursor = "ew-resize";
+ pixelMin = offsetX;
+ pixelMax = offsetX + delta;
+ break;
+ }
+
+ if (pixelMax < pixelMin) {
+ var tmp = pixelMax;
+ pixelMax = pixelMin;
+ pixelMin = tmp;
+ }
+
+ opts._pixelMin = pixelMin;
+ opts._pixelMax = pixelMax;
+
+ setCursor(d3.select(dragCover), cursor);
+ setDataRange(rangeSlider, gd, axisOpts, opts);
+ }
+
+ function mouseUp() {
+ dragCover.removeEventListener("mousemove", mouseMove);
+ dragCover.removeEventListener("mouseup", mouseUp);
+ Lib.removeElement(dragCover);
+ }
+ });
}
function setDataRange(rangeSlider, gd, axisOpts, opts) {
+ function clamp(v) {
+ return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1]));
+ }
- function clamp(v) {
- return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1]));
- }
+ var dataMin = clamp(opts.p2d(opts._pixelMin)),
+ dataMax = clamp(opts.p2d(opts._pixelMax));
- var dataMin = clamp(opts.p2d(opts._pixelMin)),
- dataMax = clamp(opts.p2d(opts._pixelMax));
-
- window.requestAnimationFrame(function() {
- Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);
- });
+ window.requestAnimationFrame(function() {
+ Plotly.relayout(gd, "xaxis.range", [dataMin, dataMax]);
+ });
}
function setPixelRange(rangeSlider, gd, axisOpts, opts) {
-
- function clamp(v) {
- return Lib.constrain(v, 0, opts._width);
- }
-
- var pixelMin = clamp(opts.d2p(axisOpts._rl[0])),
- pixelMax = clamp(opts.d2p(axisOpts._rl[1]));
-
- rangeSlider.select('rect.' + constants.slideBoxClassName)
- .attr('x', pixelMin)
- .attr('width', pixelMax - pixelMin);
-
- rangeSlider.select('rect.' + constants.maskMinClassName)
- .attr('width', pixelMin);
-
- rangeSlider.select('rect.' + constants.maskMaxClassName)
- .attr('x', pixelMax)
- .attr('width', opts._width - pixelMax);
-
- rangeSlider.select('g.' + constants.grabberMinClassName)
- .attr('transform', 'translate(' + (pixelMin - constants.handleWidth - 1) + ',0)');
-
- rangeSlider.select('g.' + constants.grabberMaxClassName)
- .attr('transform', 'translate(' + pixelMax + ',0)');
+ function clamp(v) {
+ return Lib.constrain(v, 0, opts._width);
+ }
+
+ var pixelMin = clamp(opts.d2p(axisOpts._rl[0])),
+ pixelMax = clamp(opts.d2p(axisOpts._rl[1]));
+
+ rangeSlider
+ .select("rect." + constants.slideBoxClassName)
+ .attr("x", pixelMin)
+ .attr("width", pixelMax - pixelMin);
+
+ rangeSlider
+ .select("rect." + constants.maskMinClassName)
+ .attr("width", pixelMin);
+
+ rangeSlider
+ .select("rect." + constants.maskMaxClassName)
+ .attr("x", pixelMax)
+ .attr("width", opts._width - pixelMax);
+
+ rangeSlider
+ .select("g." + constants.grabberMinClassName)
+ .attr(
+ "transform",
+ "translate(" + (pixelMin - constants.handleWidth - 1) + ",0)"
+ );
+
+ rangeSlider
+ .select("g." + constants.grabberMaxClassName)
+ .attr("transform", "translate(" + pixelMax + ",0)");
}
function drawBg(rangeSlider, gd, axisOpts, opts) {
- var bg = rangeSlider.selectAll('rect.' + constants.bgClassName)
- .data([0]);
-
- bg.enter().append('rect')
- .classed(constants.bgClassName, true)
- .attr({
- x: 0,
- y: 0,
- 'shape-rendering': 'crispEdges'
- });
-
- var borderCorrect = (opts.borderwidth % 2) === 0 ?
- opts.borderwidth :
- opts.borderwidth - 1;
-
- var offsetShift = -opts._offsetShift;
-
- bg.attr({
- width: opts._width + borderCorrect,
- height: opts._height + borderCorrect,
- transform: 'translate(' + offsetShift + ',' + offsetShift + ')',
- fill: opts.bgcolor,
- stroke: opts.bordercolor,
- 'stroke-width': opts.borderwidth,
- });
+ var bg = rangeSlider.selectAll("rect." + constants.bgClassName).data([0]);
+
+ bg
+ .enter()
+ .append("rect")
+ .classed(constants.bgClassName, true)
+ .attr({ x: 0, y: 0, "shape-rendering": "crispEdges" });
+
+ var borderCorrect = opts.borderwidth % 2 === 0
+ ? opts.borderwidth
+ : opts.borderwidth - 1;
+
+ var offsetShift = -opts._offsetShift;
+
+ bg.attr({
+ width: opts._width + borderCorrect,
+ height: opts._height + borderCorrect,
+ transform: "translate(" + offsetShift + "," + offsetShift + ")",
+ fill: opts.bgcolor,
+ stroke: opts.bordercolor,
+ "stroke-width": opts.borderwidth
+ });
}
function addClipPath(rangeSlider, gd, axisOpts, opts) {
- var fullLayout = gd._fullLayout;
+ var fullLayout = gd._fullLayout;
- var clipPath = fullLayout._topdefs.selectAll('#' + opts._clipId)
- .data([0]);
+ var clipPath = fullLayout._topdefs.selectAll("#" + opts._clipId).data([0]);
- clipPath.enter().append('clipPath')
- .attr('id', opts._clipId)
- .append('rect')
- .attr({ x: 0, y: 0 });
+ clipPath
+ .enter()
+ .append("clipPath")
+ .attr("id", opts._clipId)
+ .append("rect")
+ .attr({ x: 0, y: 0 });
- clipPath.select('rect').attr({
- width: opts._width,
- height: opts._height
- });
+ clipPath.select("rect").attr({ width: opts._width, height: opts._height });
}
function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
- var subplotData = Axes.getSubplots(gd, axisOpts),
- calcData = gd.calcdata;
-
- var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
- .data(subplotData, Lib.identity);
-
- rangePlots.enter().append('g')
- .attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; })
- .call(Drawing.setClipUrl, opts._clipId);
-
- rangePlots.order();
-
- rangePlots.exit().remove();
-
- var mainplotinfo;
-
- rangePlots.each(function(id, i) {
- var plotgroup = d3.select(this),
- isMainPlot = (i === 0);
-
- var oppAxisOpts = Axes.getFromId(gd, id, 'y'),
- oppAxisName = oppAxisOpts._name;
-
- var mockFigure = {
- data: [],
- layout: {
- xaxis: {
- type: axisOpts.type,
- domain: [0, 1],
- range: opts.range.slice(),
- calendar: axisOpts.calendar
- },
- width: opts._width,
- height: opts._height,
- margin: { t: 0, b: 0, l: 0, r: 0 }
- }
- };
-
- mockFigure.layout[oppAxisName] = {
- domain: [0, 1],
- range: oppAxisOpts.range.slice(),
- calendar: oppAxisOpts.calendar
- };
-
- Plots.supplyDefaults(mockFigure);
-
- var xa = mockFigure._fullLayout.xaxis,
- ya = mockFigure._fullLayout[oppAxisName];
-
- var plotinfo = {
- id: id,
- plotgroup: plotgroup,
- xaxis: xa,
- yaxis: ya
- };
-
- if(isMainPlot) mainplotinfo = plotinfo;
- else {
- plotinfo.mainplot = 'xy';
- plotinfo.mainplotinfo = mainplotinfo;
- }
-
- Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id));
-
- // no need for the bg layer,
- // drawBg handles coloring the background
- if(isMainPlot) plotinfo.bg.remove();
- });
-}
+ var subplotData = Axes.getSubplots(gd, axisOpts), calcData = gd.calcdata;
-function filterRangePlotCalcData(calcData, subplotId) {
- var out = [];
+ var rangePlots = rangeSlider
+ .selectAll("g." + constants.rangePlotClassName)
+ .data(subplotData, Lib.identity);
- for(var i = 0; i < calcData.length; i++) {
- var calcTrace = calcData[i],
- trace = calcTrace[0].trace;
+ rangePlots
+ .enter()
+ .append("g")
+ .attr("class", function(id) {
+ return constants.rangePlotClassName + " " + id;
+ })
+ .call(Drawing.setClipUrl, opts._clipId);
- if(trace.xaxis + trace.yaxis === subplotId) {
- out.push(calcTrace);
- }
- }
+ rangePlots.order();
- return out;
-}
+ rangePlots.exit().remove();
-function drawMasks(rangeSlider, gd, axisOpts, opts) {
- var maskMin = rangeSlider.selectAll('rect.' + constants.maskMinClassName)
- .data([0]);
+ var mainplotinfo;
- maskMin.enter().append('rect')
- .classed(constants.maskMinClassName, true)
- .attr({ x: 0, y: 0 });
+ rangePlots.each(function(id, i) {
+ var plotgroup = d3.select(this), isMainPlot = i === 0;
- maskMin
- .attr('height', opts._height)
- .call(Color.fill, constants.maskColor);
+ var oppAxisOpts = Axes.getFromId(gd, id, "y"),
+ oppAxisName = oppAxisOpts._name;
- var maskMax = rangeSlider.selectAll('rect.' + constants.maskMaxClassName)
- .data([0]);
+ var mockFigure = {
+ data: [],
+ layout: {
+ xaxis: {
+ type: axisOpts.type,
+ domain: [0, 1],
+ range: opts.range.slice(),
+ calendar: axisOpts.calendar
+ },
+ width: opts._width,
+ height: opts._height,
+ margin: { t: 0, b: 0, l: 0, r: 0 }
+ }
+ };
+
+ mockFigure.layout[oppAxisName] = {
+ domain: [0, 1],
+ range: oppAxisOpts.range.slice(),
+ calendar: oppAxisOpts.calendar
+ };
- maskMax.enter().append('rect')
- .classed(constants.maskMaxClassName, true)
- .attr('y', 0);
+ Plots.supplyDefaults(mockFigure);
+
+ var xa = mockFigure._fullLayout.xaxis,
+ ya = mockFigure._fullLayout[oppAxisName];
+
+ var plotinfo = { id: id, plotgroup: plotgroup, xaxis: xa, yaxis: ya };
+
+ if (isMainPlot) {
+ mainplotinfo = plotinfo;
+ } else {
+ plotinfo.mainplot = "xy";
+ plotinfo.mainplotinfo = mainplotinfo;
+ }
- maskMax
- .attr('height', opts._height)
- .call(Color.fill, constants.maskColor);
+ Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id));
+
+ // no need for the bg layer,
+ // drawBg handles coloring the background
+ if (isMainPlot) plotinfo.bg.remove();
+ });
}
-function drawSlideBox(rangeSlider, gd, axisOpts, opts) {
- if(gd._context.staticPlot) return;
+function filterRangePlotCalcData(calcData, subplotId) {
+ var out = [];
- var slideBox = rangeSlider.selectAll('rect.' + constants.slideBoxClassName)
- .data([0]);
+ for (var i = 0; i < calcData.length; i++) {
+ var calcTrace = calcData[i], trace = calcTrace[0].trace;
- slideBox.enter().append('rect')
- .classed(constants.slideBoxClassName, true)
- .attr('y', 0)
- .attr('cursor', constants.slideBoxCursor);
+ if (trace.xaxis + trace.yaxis === subplotId) {
+ out.push(calcTrace);
+ }
+ }
- slideBox.attr({
- height: opts._height,
- fill: constants.slideBoxFill
- });
+ return out;
}
-function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
+function drawMasks(rangeSlider, gd, axisOpts, opts) {
+ var maskMin = rangeSlider
+ .selectAll("rect." + constants.maskMinClassName)
+ .data([0]);
- //
+ maskMin
+ .enter()
+ .append("rect")
+ .classed(constants.maskMinClassName, true)
+ .attr({ x: 0, y: 0 });
- var grabberMin = rangeSlider.selectAll('g.' + constants.grabberMinClassName)
- .data([0]);
- grabberMin.enter().append('g')
- .classed(constants.grabberMinClassName, true);
+ maskMin.attr("height", opts._height).call(Color.fill, constants.maskColor);
- var grabberMax = rangeSlider.selectAll('g.' + constants.grabberMaxClassName)
- .data([0]);
- grabberMax.enter().append('g')
- .classed(constants.grabberMaxClassName, true);
+ var maskMax = rangeSlider
+ .selectAll("rect." + constants.maskMaxClassName)
+ .data([0]);
- //
+ maskMax
+ .enter()
+ .append("rect")
+ .classed(constants.maskMaxClassName, true)
+ .attr("y", 0);
- var handleFixAttrs = {
- x: 0,
- width: constants.handleWidth,
- rx: constants.handleRadius,
- fill: constants.handleFill,
- stroke: constants.handleStroke,
- 'shape-rendering': 'crispEdges'
- };
+ maskMax.attr("height", opts._height).call(Color.fill, constants.maskColor);
+}
- var handleDynamicAttrs = {
- y: opts._height / 4,
- height: opts._height / 2,
- };
+function drawSlideBox(rangeSlider, gd, axisOpts, opts) {
+ if (gd._context.staticPlot) return;
- var handleMin = grabberMin.selectAll('rect.' + constants.handleMinClassName)
- .data([0]);
- handleMin.enter().append('rect')
- .classed(constants.handleMinClassName, true)
- .attr(handleFixAttrs);
- handleMin.attr(handleDynamicAttrs);
-
- var handleMax = grabberMax.selectAll('rect.' + constants.handleMaxClassName)
- .data([0]);
- handleMax.enter().append('rect')
- .classed(constants.handleMaxClassName, true)
- .attr(handleFixAttrs);
- handleMax.attr(handleDynamicAttrs);
-
- //
-
- if(gd._context.staticPlot) return;
-
- var grabAreaFixAttrs = {
- width: constants.grabAreaWidth,
- y: 0,
- fill: constants.grabAreaFill,
- cursor: constants.grabAreaCursor
- };
+ var slideBox = rangeSlider
+ .selectAll("rect." + constants.slideBoxClassName)
+ .data([0]);
- var grabAreaMin = grabberMin.selectAll('rect.' + constants.grabAreaMinClassName)
- .data([0]);
- grabAreaMin.enter().append('rect')
- .classed(constants.grabAreaMinClassName, true)
- .attr(grabAreaFixAttrs);
- grabAreaMin.attr({
- x: constants.grabAreaMinOffset,
- height: opts._height
- });
+ slideBox
+ .enter()
+ .append("rect")
+ .classed(constants.slideBoxClassName, true)
+ .attr("y", 0)
+ .attr("cursor", constants.slideBoxCursor);
- var grabAreaMax = grabberMax.selectAll('rect.' + constants.grabAreaMaxClassName)
- .data([0]);
- grabAreaMax.enter().append('rect')
- .classed(constants.grabAreaMaxClassName, true)
- .attr(grabAreaFixAttrs);
- grabAreaMax.attr({
- x: constants.grabAreaMaxOffset,
- height: opts._height
- });
+ slideBox.attr({ height: opts._height, fill: constants.slideBoxFill });
+}
+
+function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
+ //
+ var grabberMin = rangeSlider
+ .selectAll("g." + constants.grabberMinClassName)
+ .data([0]);
+ grabberMin.enter().append("g").classed(constants.grabberMinClassName, true);
+
+ var grabberMax = rangeSlider
+ .selectAll("g." + constants.grabberMaxClassName)
+ .data([0]);
+ grabberMax.enter().append("g").classed(constants.grabberMaxClassName, true);
+
+ //
+ var handleFixAttrs = {
+ x: 0,
+ width: constants.handleWidth,
+ rx: constants.handleRadius,
+ fill: constants.handleFill,
+ stroke: constants.handleStroke,
+ "shape-rendering": "crispEdges"
+ };
+
+ var handleDynamicAttrs = { y: opts._height / 4, height: opts._height / 2 };
+
+ var handleMin = grabberMin
+ .selectAll("rect." + constants.handleMinClassName)
+ .data([0]);
+ handleMin
+ .enter()
+ .append("rect")
+ .classed(constants.handleMinClassName, true)
+ .attr(handleFixAttrs);
+ handleMin.attr(handleDynamicAttrs);
+
+ var handleMax = grabberMax
+ .selectAll("rect." + constants.handleMaxClassName)
+ .data([0]);
+ handleMax
+ .enter()
+ .append("rect")
+ .classed(constants.handleMaxClassName, true)
+ .attr(handleFixAttrs);
+ handleMax.attr(handleDynamicAttrs);
+
+ //
+ if (gd._context.staticPlot) return;
+
+ var grabAreaFixAttrs = {
+ width: constants.grabAreaWidth,
+ y: 0,
+ fill: constants.grabAreaFill,
+ cursor: constants.grabAreaCursor
+ };
+
+ var grabAreaMin = grabberMin
+ .selectAll("rect." + constants.grabAreaMinClassName)
+ .data([0]);
+ grabAreaMin
+ .enter()
+ .append("rect")
+ .classed(constants.grabAreaMinClassName, true)
+ .attr(grabAreaFixAttrs);
+ grabAreaMin.attr({ x: constants.grabAreaMinOffset, height: opts._height });
+
+ var grabAreaMax = grabberMax
+ .selectAll("rect." + constants.grabAreaMaxClassName)
+ .data([0]);
+ grabAreaMax
+ .enter()
+ .append("rect")
+ .classed(constants.grabAreaMaxClassName, true)
+ .attr(grabAreaFixAttrs);
+ grabAreaMax.attr({ x: constants.grabAreaMaxOffset, height: opts._height });
}
function clearPushMargins(gd) {
- var pushMargins = gd._fullLayout._pushmargin || {},
- keys = Object.keys(pushMargins);
+ var pushMargins = gd._fullLayout._pushmargin || {},
+ keys = Object.keys(pushMargins);
- for(var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
- if(k.indexOf(constants.name) !== -1) {
- Plots.autoMargin(gd, k);
- }
+ if (k.indexOf(constants.name) !== -1) {
+ Plots.autoMargin(gd, k);
}
+ }
}
diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js
index 2d29e3b16fd..2181ff19749 100644
--- a/src/components/rangeslider/index.js
+++ b/src/components/rangeslider/index.js
@@ -5,21 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
- moduleType: 'component',
- name: 'rangeslider',
-
- schema: {
- layout: {
- 'xaxis.rangeslider': require('./attributes')
- }
- },
-
- layoutAttributes: require('./attributes'),
- handleDefaults: require('./defaults'),
-
- draw: require('./draw')
+ moduleType: "component",
+ name: "rangeslider",
+ schema: { layout: { "xaxis.rangeslider": require("./attributes") } },
+ layoutAttributes: require("./attributes"),
+ handleDefaults: require("./defaults"),
+ draw: require("./draw")
};
diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js
index a8974f2825e..e2a2dcccaca 100644
--- a/src/components/shapes/attributes.js
+++ b/src/components/shapes/attributes.js
@@ -5,161 +5,142 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var annAttrs = require('../annotations/attributes');
-var scatterAttrs = require('../../traces/scatter/attributes');
-var extendFlat = require('../../lib/extend').extendFlat;
+"use strict";
+var annAttrs = require("../annotations/attributes");
+var scatterAttrs = require("../../traces/scatter/attributes");
+var extendFlat = require("../../lib/extend").extendFlat;
var scatterLineAttrs = scatterAttrs.line;
module.exports = {
- _isLinkedToArray: 'shape',
-
- visible: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines whether or not this shape is visible.'
- ].join(' ')
- },
-
- 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(' ')
- },
-
- layer: {
- valType: 'enumerated',
- values: ['below', 'above'],
- dflt: 'above',
- role: 'info',
- description: 'Specifies whether shapes are drawn below or above traces.'
- },
-
- 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',
- '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.',
- 'If the axis `type` is *log*, then you must take the',
- 'log of your desired range.',
- 'If the axis `type` is *date*, then you must convert',
- 'the date to unix time in milliseconds.'
- ].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({}, 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',
- '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(' ')
- }
+ _isLinkedToArray: "shape",
+ visible: {
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: ["Determines whether or not this shape is visible."].join(" ")
+ },
+ 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(" ")
+ },
+ layer: {
+ valType: "enumerated",
+ values: ["below", "above"],
+ dflt: "above",
+ role: "info",
+ description: "Specifies whether shapes are drawn below or above traces."
+ },
+ 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",
+ "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.",
+ "If the axis `type` is *log*, then you must take the",
+ "log of your desired range.",
+ "If the axis `type` is *date*, then you must convert",
+ "the date to unix time in milliseconds."
+ ].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({}, 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",
+ "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/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js
index 6f88b4aad96..a115725b2cb 100644
--- a/src/components/shapes/calc_autorange.js
+++ b/src/components/shapes/calc_autorange.js
@@ -5,71 +5,78 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Lib = require("../../lib");
+var Axes = require("../../plots/cartesian/axes");
-
-'use strict';
-
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-
-var constants = require('./constants');
-var helpers = require('./helpers');
-
+var constants = require("./constants");
+var helpers = require("./helpers");
module.exports = function calcAutorange(gd) {
- var fullLayout = gd._fullLayout,
- shapeList = Lib.filterVisible(fullLayout.shapes);
-
- if(!shapeList.length || !gd._fullData.length) return;
-
- for(var i = 0; i < shapeList.length; i++) {
- var shape = shapeList[i],
- ppad = shape.line.width / 2;
-
- var ax, bounds;
-
- if(shape.xref !== 'paper') {
- ax = Axes.getFromId(gd, shape.xref);
- bounds = shapeBounds(ax, shape.x0, shape.x1, shape.path, constants.paramIsX);
- if(bounds) Axes.expand(ax, bounds, {ppad: ppad});
- }
+ var fullLayout = gd._fullLayout,
+ shapeList = Lib.filterVisible(fullLayout.shapes);
+
+ if (!shapeList.length || !gd._fullData.length) return;
+
+ for (var i = 0; i < shapeList.length; i++) {
+ var shape = shapeList[i], ppad = shape.line.width / 2;
+
+ var ax, bounds;
+
+ if (shape.xref !== "paper") {
+ ax = Axes.getFromId(gd, shape.xref);
+ bounds = shapeBounds(
+ ax,
+ shape.x0,
+ shape.x1,
+ shape.path,
+ constants.paramIsX
+ );
+ if (bounds) Axes.expand(ax, bounds, { ppad: ppad });
+ }
- if(shape.yref !== 'paper') {
- ax = Axes.getFromId(gd, shape.yref);
- bounds = shapeBounds(ax, shape.y0, shape.y1, shape.path, constants.paramIsY);
- if(bounds) Axes.expand(ax, bounds, {ppad: ppad});
- }
+ if (shape.yref !== "paper") {
+ ax = Axes.getFromId(gd, shape.yref);
+ bounds = shapeBounds(
+ ax,
+ shape.y0,
+ shape.y1,
+ shape.path,
+ constants.paramIsY
+ );
+ if (bounds) Axes.expand(ax, bounds, { ppad: ppad });
}
+ }
};
function shapeBounds(ax, v0, v1, path, paramsToUse) {
- var convertVal = (ax.type === 'category') ? Number : ax.d2c;
-
- if(v0 !== undefined) return [convertVal(v0), convertVal(v1)];
- if(!path) return;
-
- var min = Infinity,
- max = -Infinity,
- segments = path.match(constants.segmentRE),
- i,
- segment,
- drawnParam,
- params,
- val;
-
- if(ax.type === 'date') convertVal = helpers.decodeDate(convertVal);
-
- for(i = 0; i < segments.length; i++) {
- segment = segments[i];
- drawnParam = paramsToUse[segment.charAt(0)].drawn;
- if(drawnParam === undefined) continue;
-
- params = segments[i].substr(1).match(constants.paramRE);
- if(!params || params.length < drawnParam) continue;
-
- val = convertVal(params[drawnParam]);
- if(val < min) min = val;
- if(val > max) max = val;
- }
- if(max >= min) return [min, max];
+ var convertVal = ax.type === "category" ? Number : ax.d2c;
+
+ if (v0 !== undefined) return [convertVal(v0), convertVal(v1)];
+ if (!path) return;
+
+ var min = Infinity,
+ max = -Infinity,
+ segments = path.match(constants.segmentRE),
+ i,
+ segment,
+ drawnParam,
+ params,
+ val;
+
+ if (ax.type === "date") convertVal = helpers.decodeDate(convertVal);
+
+ for (i = 0; i < segments.length; i++) {
+ segment = segments[i];
+ drawnParam = paramsToUse[segment.charAt(0)].drawn;
+ if (drawnParam === undefined) continue;
+
+ params = segments[i].substr(1).match(constants.paramRE);
+ if (!params || params.length < drawnParam) continue;
+
+ val = convertVal(params[drawnParam]);
+ if (val < min) min = val;
+ if (val > max) max = val;
+ }
+ if (max >= min) return [min, max];
}
diff --git a/src/components/shapes/constants.js b/src/components/shapes/constants.js
index e0c009ca84e..c83fe912421 100644
--- a/src/components/shapes/constants.js
+++ b/src/components/shapes/constants.js
@@ -5,58 +5,51 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
module.exports = {
- segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g,
- paramRE: /[^\s,]+/g,
-
- // which numbers in each path segment are x (or y) values
- // drawn is which param is a drawn point, as opposed to a
- // control point (which doesn't count toward autorange.
- // TODO: this means curved paths could extend beyond the
- // autorange bounds. This is a bit tricky to get right
- // unless we revert to bounding boxes, but perhaps there's
- // a calculation we could do...)
- paramIsX: {
- M: {0: true, drawn: 0},
- L: {0: true, drawn: 0},
- H: {0: true, drawn: 0},
- V: {},
- Q: {0: true, 2: true, drawn: 2},
- C: {0: true, 2: true, 4: true, drawn: 4},
- T: {0: true, drawn: 0},
- S: {0: true, 2: true, drawn: 2},
- // A: {0: true, 5: true},
- Z: {}
- },
-
- paramIsY: {
- M: {1: true, drawn: 1},
- L: {1: true, drawn: 1},
- H: {},
- V: {0: true, drawn: 0},
- Q: {1: true, 3: true, drawn: 3},
- C: {1: true, 3: true, 5: true, drawn: 5},
- T: {1: true, drawn: 1},
- S: {1: true, 3: true, drawn: 5},
- // A: {1: true, 6: true},
- Z: {}
- },
-
- numParams: {
- M: 2,
- L: 2,
- H: 1,
- V: 1,
- Q: 4,
- C: 6,
- T: 2,
- S: 4,
- // A: 7,
- Z: 0
- }
+ segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g,
+ paramRE: /[^\s,]+/g,
+ // which numbers in each path segment are x (or y) values
+ // drawn is which param is a drawn point, as opposed to a
+ // control point (which doesn't count toward autorange.
+ // TODO: this means curved paths could extend beyond the
+ // autorange bounds. This is a bit tricky to get right
+ // unless we revert to bounding boxes, but perhaps there's
+ // a calculation we could do...)
+ paramIsX: {
+ M: { 0: true, drawn: 0 },
+ L: { 0: true, drawn: 0 },
+ H: { 0: true, drawn: 0 },
+ V: {},
+ Q: { 0: true, 2: true, drawn: 2 },
+ C: { 0: true, 2: true, 4: true, drawn: 4 },
+ T: { 0: true, drawn: 0 },
+ S: { 0: true, 2: true, drawn: 2 },
+ // A: {0: true, 5: true},
+ Z: {}
+ },
+ paramIsY: {
+ M: { 1: true, drawn: 1 },
+ L: { 1: true, drawn: 1 },
+ H: {},
+ V: { 0: true, drawn: 0 },
+ Q: { 1: true, 3: true, drawn: 3 },
+ C: { 1: true, 3: true, 5: true, drawn: 5 },
+ T: { 1: true, drawn: 1 },
+ S: { 1: true, 3: true, drawn: 5 },
+ // A: {1: true, 6: true},
+ Z: {}
+ },
+ numParams: {
+ M: 2,
+ L: 2,
+ H: 1,
+ V: 1,
+ Q: 4,
+ C: 6,
+ T: 2,
+ S: 4,
+ // A: 7,
+ Z: 0
+ }
};
diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js
index ceb3b4c0a1a..dc0848dfac2 100644
--- a/src/components/shapes/defaults.js
+++ b/src/components/shapes/defaults.js
@@ -5,19 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
-var handleShapeDefaults = require('./shape_defaults');
-
+"use strict";
+var handleArrayContainerDefaults = require(
+ "../../plots/array_container_defaults"
+);
+var handleShapeDefaults = require("./shape_defaults");
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
- var opts = {
- name: 'shapes',
- handleItemDefaults: handleShapeDefaults
- };
+ var opts = { name: "shapes", handleItemDefaults: handleShapeDefaults };
- handleArrayContainerDefaults(layoutIn, layoutOut, opts);
+ handleArrayContainerDefaults(layoutIn, layoutOut, opts);
};
diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js
index 314cd339f76..4437642731c 100644
--- a/src/components/shapes/draw.js
+++ b/src/components/shapes/draw.js
@@ -5,26 +5,22 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var Plotly = require("../../plotly");
+var Lib = require("../../lib");
+var Axes = require("../../plots/cartesian/axes");
+var Color = require("../color");
+var Drawing = require("../drawing");
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Plotly = require('../../plotly');
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-var Color = require('../color');
-var Drawing = require('../drawing');
-
-var dragElement = require('../dragelement');
-var setCursor = require('../../lib/setcursor');
-
-var constants = require('./constants');
-var helpers = require('./helpers');
-var handleShapeDefaults = require('./shape_defaults');
-var supplyLayoutDefaults = require('./defaults');
+var dragElement = require("../dragelement");
+var setCursor = require("../../lib/setcursor");
+var constants = require("./constants");
+var helpers = require("./helpers");
+var handleShapeDefaults = require("./shape_defaults");
+var supplyLayoutDefaults = require("./defaults");
// Shapes are stored in gd.layout.shapes, an array of objects
// index can point to one item in this array,
@@ -34,532 +30,554 @@ var supplyLayoutDefaults = require('./defaults');
// or undefined to simply redraw
// if opt is blank, val can be 'add' or a full options object to add a new
// annotation at that point in the array, or 'remove' to delete this one
-
-module.exports = {
- draw: draw,
- drawOne: drawOne
-};
+module.exports = { draw: draw, drawOne: drawOne };
function draw(gd) {
- var fullLayout = gd._fullLayout;
+ var fullLayout = gd._fullLayout;
- // Remove previous shapes before drawing new in shapes in fullLayout.shapes
- fullLayout._shapeUpperLayer.selectAll('path').remove();
- fullLayout._shapeLowerLayer.selectAll('path').remove();
- fullLayout._shapeSubplotLayer.selectAll('path').remove();
+ // Remove previous shapes before drawing new in shapes in fullLayout.shapes
+ fullLayout._shapeUpperLayer.selectAll("path").remove();
+ fullLayout._shapeLowerLayer.selectAll("path").remove();
+ fullLayout._shapeSubplotLayer.selectAll("path").remove();
- for(var i = 0; i < fullLayout.shapes.length; i++) {
- if(fullLayout.shapes[i].visible) {
- drawOne(gd, i);
- }
+ for (var i = 0; i < fullLayout.shapes.length; i++) {
+ if (fullLayout.shapes[i].visible) {
+ drawOne(gd, i);
}
-
- // may need to resurrect this if we put text (LaTeX) in shapes
- // return Plots.previousPromises(gd);
+ }
+ // may need to resurrect this if we put text (LaTeX) in shapes
+ // return Plots.previousPromises(gd);
}
function drawOne(gd, index, opt, value) {
- if(!isNumeric(index) || index === -1) {
-
- // no index provided - we're operating on ALL shapes
- if(!index && Array.isArray(value)) {
- replaceAllShapes(gd, value);
- return;
- }
- else if(value === 'remove') {
- deleteAllShapes(gd);
- return;
- }
- else if(opt && value !== 'add') {
- updateAllShapes(gd, opt, value);
- return;
- }
- else {
- // add a new empty annotation
- index = gd._fullLayout.shapes.length;
- gd._fullLayout.shapes.push({});
- }
+ if (!isNumeric(index) || index === -1) {
+ // no index provided - we're operating on ALL shapes
+ if (!index && Array.isArray(value)) {
+ replaceAllShapes(gd, value);
+ return;
+ } else if (value === "remove") {
+ deleteAllShapes(gd);
+ return;
+ } else if (opt && value !== "add") {
+ updateAllShapes(gd, opt, value);
+ return;
+ } else {
+ // add a new empty annotation
+ index = gd._fullLayout.shapes.length;
+ gd._fullLayout.shapes.push({});
}
-
- if(!opt && value) {
- if(value === 'remove') {
- deleteShape(gd, index);
- return;
- }
- else if(value === 'add' || Lib.isPlainObject(value)) {
- insertShape(gd, index, value);
- }
+ }
+
+ if (!opt && value) {
+ if (value === "remove") {
+ deleteShape(gd, index);
+ return;
+ } else if (value === "add" || Lib.isPlainObject(value)) {
+ insertShape(gd, index, value);
}
+ }
- updateShape(gd, index, opt, value);
+ updateShape(gd, index, opt, value);
}
function replaceAllShapes(gd, newShapes) {
- gd.layout.shapes = newShapes;
- supplyLayoutDefaults(gd.layout, gd._fullLayout);
- draw(gd);
+ gd.layout.shapes = newShapes;
+ supplyLayoutDefaults(gd.layout, gd._fullLayout);
+ draw(gd);
}
function deleteAllShapes(gd) {
- delete gd.layout.shapes;
- gd._fullLayout.shapes = [];
- draw(gd);
+ delete gd.layout.shapes;
+ gd._fullLayout.shapes = [];
+ draw(gd);
}
function updateAllShapes(gd, opt, value) {
- for(var i = 0; i < gd._fullLayout.shapes.length; i++) {
- drawOne(gd, i, opt, value);
- }
+ for (var i = 0; i < gd._fullLayout.shapes.length; i++) {
+ drawOne(gd, i, opt, value);
+ }
}
function deleteShape(gd, index) {
- getShapeLayer(gd, index)
- .selectAll('[data-index="' + index + '"]')
- .remove();
+ getShapeLayer(gd, index).selectAll('[data-index="' + index + '"]').remove();
- gd._fullLayout.shapes.splice(index, 1);
+ gd._fullLayout.shapes.splice(index, 1);
- gd.layout.shapes.splice(index, 1);
+ gd.layout.shapes.splice(index, 1);
- for(var i = index; i < gd._fullLayout.shapes.length; i++) {
- // redraw all shapes past the removed one,
- // so they bind to the right events
- getShapeLayer(gd, i)
- .selectAll('[data-index="' + (i + 1) + '"]')
- .attr('data-index', i);
- drawOne(gd, i);
- }
+ for (var i = index; i < gd._fullLayout.shapes.length; i++) {
+ // redraw all shapes past the removed one,
+ // so they bind to the right events
+ getShapeLayer(gd, i)
+ .selectAll('[data-index="' + (i + 1) + '"]')
+ .attr("data-index", i);
+ drawOne(gd, i);
+ }
}
function insertShape(gd, index, newShape) {
- gd._fullLayout.shapes.splice(index, 0, {});
-
- var rule = Lib.isPlainObject(newShape) ?
- Lib.extendFlat({}, newShape) :
- {text: 'New text'};
-
- if(gd.layout.shapes) {
- gd.layout.shapes.splice(index, 0, rule);
- } else {
- gd.layout.shapes = [rule];
- }
-
- // there is no need to call shapes.draw(gd, index),
- // because updateShape() is called from within shapes.draw()
-
- for(var i = gd._fullLayout.shapes.length - 1; i > index; i--) {
- getShapeLayer(gd, i)
- .selectAll('[data-index="' + (i - 1) + '"]')
- .attr('data-index', i);
- drawOne(gd, i);
- }
+ gd._fullLayout.shapes.splice(index, 0, {});
+
+ var rule = Lib.isPlainObject(newShape)
+ ? Lib.extendFlat({}, newShape)
+ : { text: "New text" };
+
+ if (gd.layout.shapes) {
+ gd.layout.shapes.splice(index, 0, rule);
+ } else {
+ gd.layout.shapes = [rule];
+ }
+
+ // there is no need to call shapes.draw(gd, index),
+ // because updateShape() is called from within shapes.draw()
+ for (var i = gd._fullLayout.shapes.length - 1; i > index; i--) {
+ getShapeLayer(gd, i)
+ .selectAll('[data-index="' + (i - 1) + '"]')
+ .attr("data-index", i);
+ drawOne(gd, i);
+ }
}
function updateShape(gd, index, opt, value) {
- var i, n;
-
- // remove the existing shape if there is one
- getShapeLayer(gd, index)
- .selectAll('[data-index="' + index + '"]')
- .remove();
-
- // remember a few things about what was already there,
- var optionsIn = gd.layout.shapes[index];
-
- // (from annos...) not sure how we're getting here... but C12 is seeing a bug
- // where we fail here when they add/remove annotations
- // TODO: clean this up and remove it.
- if(!optionsIn) return;
-
- // alter the input shape as requested
- var optionsEdit = {};
- if(typeof opt === 'string' && opt) optionsEdit[opt] = value;
- else if(Lib.isPlainObject(opt)) optionsEdit = opt;
-
- var optionKeys = Object.keys(optionsEdit);
- for(i = 0; i < optionKeys.length; i++) {
- var k = optionKeys[i];
- Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]);
+ var i, n;
+
+ // remove the existing shape if there is one
+ getShapeLayer(gd, index).selectAll('[data-index="' + index + '"]').remove();
+
+ // remember a few things about what was already there,
+ var optionsIn = gd.layout.shapes[index];
+
+ // (from annos...) not sure how we're getting here... but C12 is seeing a bug
+ // where we fail here when they add/remove annotations
+ // TODO: clean this up and remove it.
+ if (!optionsIn) return;
+
+ // alter the input shape as requested
+ var optionsEdit = {};
+ if (typeof opt === "string" && opt) optionsEdit[opt] = value;
+ else if (Lib.isPlainObject(opt)) optionsEdit = opt;
+
+ var optionKeys = Object.keys(optionsEdit);
+ for (i = 0; i < optionKeys.length; i++) {
+ var k = optionKeys[i];
+ Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]);
+ }
+
+ // return early in visible: false updates
+ if (optionsIn.visible === false) return;
+
+ var oldRef = { xref: optionsIn.xref, yref: optionsIn.yref },
+ posAttrs = ["x0", "x1", "y0", "y1"];
+
+ for (i = 0; i < 4; i++) {
+ var posAttr = posAttrs[i];
+ // if we don't have an explicit position already,
+ // don't set one just because we're changing references
+ // or axis type.
+ // the defaults will be consistent most of the time anyway,
+ // except in log/linear changes
+ if (
+ optionsEdit[posAttr] !== undefined || optionsIn[posAttr] === undefined
+ ) {
+ continue;
}
- // return early in visible: false updates
- if(optionsIn.visible === false) return;
-
- var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref},
- posAttrs = ['x0', 'x1', 'y0', 'y1'];
-
- for(i = 0; i < 4; i++) {
- var posAttr = posAttrs[i];
- // if we don't have an explicit position already,
- // don't set one just because we're changing references
- // or axis type.
- // the defaults will be consistent most of the time anyway,
- // except in log/linear changes
- if(optionsEdit[posAttr] !== undefined ||
- optionsIn[posAttr] === undefined) {
- continue;
- }
-
- var axLetter = posAttr.charAt(0),
- axOld = Axes.getFromId(gd,
- Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')),
- axNew = Axes.getFromId(gd,
- Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')),
- position = optionsIn[posAttr],
- rangePosition;
-
- if(optionsEdit[axLetter + 'ref'] !== undefined) {
- // first convert to fraction of the axis
- if(axOld) {
- rangePosition = helpers.shapePositionToRange(axOld)(position);
- position = axOld.r2fraction(rangePosition);
- } else {
- position = (position - axNew.domain[0]) /
- (axNew.domain[1] - axNew.domain[0]);
- }
-
- if(axNew) {
- // then convert to new data coordinates at the same fraction
- rangePosition = axNew.fraction2r(position);
- position = helpers.rangeToShapePosition(axNew)(rangePosition);
- } else {
- // or scale to the whole plot
- position = axOld.domain[0] +
- position * (axOld.domain[1] - axOld.domain[0]);
- }
- }
-
- optionsIn[posAttr] = position;
+ var axLetter = posAttr.charAt(0),
+ axOld = Axes.getFromId(
+ gd,
+ Axes.coerceRef(oldRef, {}, gd, axLetter, "", "paper")
+ ),
+ axNew = Axes.getFromId(
+ gd,
+ Axes.coerceRef(optionsIn, {}, gd, axLetter, "", "paper")
+ ),
+ position = optionsIn[posAttr],
+ rangePosition;
+
+ if (optionsEdit[axLetter + "ref"] !== undefined) {
+ // first convert to fraction of the axis
+ if (axOld) {
+ rangePosition = helpers.shapePositionToRange(axOld)(position);
+ position = axOld.r2fraction(rangePosition);
+ } else {
+ position = (position - axNew.domain[0]) /
+ (axNew.domain[1] - axNew.domain[0]);
+ }
+
+ if (axNew) {
+ // then convert to new data coordinates at the same fraction
+ rangePosition = axNew.fraction2r(position);
+ position = helpers.rangeToShapePosition(axNew)(rangePosition);
+ } else {
+ // or scale to the whole plot
+ position = axOld.domain[0] +
+ position * (axOld.domain[1] - axOld.domain[0]);
+ }
}
- var options = {};
- handleShapeDefaults(optionsIn, options, gd._fullLayout);
- gd._fullLayout.shapes[index] = options;
-
- var clipAxes;
- if(options.layer !== 'below') {
- clipAxes = (options.xref + options.yref).replace(/paper/g, '');
- drawShape(gd._fullLayout._shapeUpperLayer);
+ optionsIn[posAttr] = position;
+ }
+
+ var options = {};
+ handleShapeDefaults(optionsIn, options, gd._fullLayout);
+ gd._fullLayout.shapes[index] = options;
+
+ var clipAxes;
+ if (options.layer !== "below") {
+ clipAxes = (options.xref + options.yref).replace(/paper/g, "");
+ drawShape(gd._fullLayout._shapeUpperLayer);
+ } else if (options.xref === "paper" && options.yref === "paper") {
+ clipAxes = "";
+ drawShape(gd._fullLayout._shapeLowerLayer);
+ } else {
+ var plots = gd._fullLayout._plots || {},
+ subplots = Object.keys(plots),
+ plotinfo;
+
+ for (i = 0, n = subplots.length; i < n; i++) {
+ plotinfo = plots[subplots[i]];
+ clipAxes = subplots[i];
+
+ if (isShapeInSubplot(gd, options, plotinfo)) {
+ drawShape(plotinfo.shapelayer);
+ }
}
- else if(options.xref === 'paper' && options.yref === 'paper') {
- clipAxes = '';
- drawShape(gd._fullLayout._shapeLowerLayer);
- }
- else {
- var plots = gd._fullLayout._plots || {},
- subplots = Object.keys(plots),
- plotinfo;
-
- for(i = 0, n = subplots.length; i < n; i++) {
- plotinfo = plots[subplots[i]];
- clipAxes = subplots[i];
-
- if(isShapeInSubplot(gd, options, plotinfo)) {
- drawShape(plotinfo.shapelayer);
- }
- }
+ }
+
+ function drawShape(shapeLayer) {
+ var attrs = {
+ "data-index": index,
+ "fill-rule": "evenodd",
+ d: getPathString(gd, options)
+ },
+ lineColor = options.line.width ? options.line.color : "rgba(0,0,0,0)";
+
+ var path = shapeLayer
+ .append("path")
+ .attr(attrs)
+ .style("opacity", options.opacity)
+ .call(Color.stroke, lineColor)
+ .call(Color.fill, options.fillcolor)
+ .call(Drawing.dashLine, options.line.dash, options.line.width);
+
+ if (clipAxes) {
+ path.call(Drawing.setClipUrl, "clip" + gd._fullLayout._uid + clipAxes);
}
- function drawShape(shapeLayer) {
- var attrs = {
- 'data-index': index,
- 'fill-rule': 'evenodd',
- d: getPathString(gd, options)
- },
- lineColor = options.line.width ?
- options.line.color : 'rgba(0,0,0,0)';
-
- var path = shapeLayer.append('path')
- .attr(attrs)
- .style('opacity', options.opacity)
- .call(Color.stroke, lineColor)
- .call(Color.fill, options.fillcolor)
- .call(Drawing.dashLine, options.line.dash, options.line.width);
-
- if(clipAxes) {
- path.call(Drawing.setClipUrl,
- 'clip' + gd._fullLayout._uid + clipAxes);
- }
-
- if(gd._context.editable) setupDragElement(gd, path, options, index);
- }
+ if (gd._context.editable) setupDragElement(gd, path, options, index);
+ }
}
function setupDragElement(gd, shapePath, shapeOptions, index) {
- var MINWIDTH = 10,
- MINHEIGHT = 10;
-
- var update;
- var x0, y0, x1, y1, astrX0, astrY0, astrX1, astrY1;
- var n0, s0, w0, e0, astrN, astrS, astrW, astrE, optN, optS, optW, optE;
- var pathIn, astrPath;
-
- var xa, ya, x2p, y2p, p2x, p2y;
-
- var dragOptions = {
- setCursor: updateDragMode,
- element: shapePath.node(),
- prepFn: startDrag,
- doneFn: endDrag
- },
- dragBBox = dragOptions.element.getBoundingClientRect(),
- dragMode;
-
- dragElement.init(dragOptions);
-
- function updateDragMode(evt) {
- // choose 'move' or 'resize'
- // based on initial position of cursor within the drag element
- var w = dragBBox.right - dragBBox.left,
- h = dragBBox.bottom - dragBBox.top,
- x = evt.clientX - dragBBox.left,
- y = evt.clientY - dragBBox.top,
- cursor = (w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey) ?
- dragElement.getCursor(x / w, 1 - y / h) :
- 'move';
-
- setCursor(shapePath, cursor);
-
- // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w'
- dragMode = cursor.split('-')[0];
- }
-
- function startDrag(evt) {
- // setup conversion functions
- xa = Axes.getFromId(gd, shapeOptions.xref);
- ya = Axes.getFromId(gd, shapeOptions.yref);
-
- x2p = helpers.getDataToPixel(gd, xa);
- y2p = helpers.getDataToPixel(gd, ya, true);
- p2x = helpers.getPixelToData(gd, xa);
- p2y = helpers.getPixelToData(gd, ya, true);
-
- // setup update strings and initial values
- var astr = 'shapes[' + index + ']';
- if(shapeOptions.type === 'path') {
- pathIn = shapeOptions.path;
- astrPath = astr + '.path';
- }
- else {
- x0 = x2p(shapeOptions.x0);
- y0 = y2p(shapeOptions.y0);
- x1 = x2p(shapeOptions.x1);
- y1 = y2p(shapeOptions.y1);
-
- astrX0 = astr + '.x0';
- astrY0 = astr + '.y0';
- astrX1 = astr + '.x1';
- astrY1 = astr + '.y1';
- }
-
- if(x0 < x1) {
- w0 = x0; astrW = astr + '.x0'; optW = 'x0';
- e0 = x1; astrE = astr + '.x1'; optE = 'x1';
- }
- else {
- w0 = x1; astrW = astr + '.x1'; optW = 'x1';
- e0 = x0; astrE = astr + '.x0'; optE = 'x0';
- }
- if(y0 < y1) {
- n0 = y0; astrN = astr + '.y0'; optN = 'y0';
- s0 = y1; astrS = astr + '.y1'; optS = 'y1';
- }
- else {
- n0 = y1; astrN = astr + '.y1'; optN = 'y1';
- s0 = y0; astrS = astr + '.y0'; optS = 'y0';
- }
-
- update = {};
-
- // setup dragMode and the corresponding handler
- updateDragMode(evt);
- dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape;
+ var MINWIDTH = 10, MINHEIGHT = 10;
+
+ var update;
+ var x0, y0, x1, y1, astrX0, astrY0, astrX1, astrY1;
+ var n0, s0, w0, e0, astrN, astrS, astrW, astrE, optN, optS, optW, optE;
+ var pathIn, astrPath;
+
+ var xa, ya, x2p, y2p, p2x, p2y;
+
+ var dragOptions = {
+ setCursor: updateDragMode,
+ element: shapePath.node(),
+ prepFn: startDrag,
+ doneFn: endDrag
+ },
+ dragBBox = dragOptions.element.getBoundingClientRect(),
+ dragMode;
+
+ dragElement.init(dragOptions);
+
+ function updateDragMode(evt) {
+ // choose 'move' or 'resize'
+ // based on initial position of cursor within the drag element
+ var w = dragBBox.right - dragBBox.left,
+ h = dragBBox.bottom - dragBBox.top,
+ x = evt.clientX - dragBBox.left,
+ y = evt.clientY - dragBBox.top,
+ cursor = w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey
+ ? dragElement.getCursor(x / w, 1 - y / h)
+ : "move";
+
+ setCursor(shapePath, cursor);
+
+ // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w'
+ dragMode = cursor.split("-")[0];
+ }
+
+ function startDrag(evt) {
+ // setup conversion functions
+ xa = Axes.getFromId(gd, shapeOptions.xref);
+ ya = Axes.getFromId(gd, shapeOptions.yref);
+
+ x2p = helpers.getDataToPixel(gd, xa);
+ y2p = helpers.getDataToPixel(gd, ya, true);
+ p2x = helpers.getPixelToData(gd, xa);
+ p2y = helpers.getPixelToData(gd, ya, true);
+
+ // setup update strings and initial values
+ var astr = "shapes[" + index + "]";
+ if (shapeOptions.type === "path") {
+ pathIn = shapeOptions.path;
+ astrPath = astr + ".path";
+ } else {
+ x0 = x2p(shapeOptions.x0);
+ y0 = y2p(shapeOptions.y0);
+ x1 = x2p(shapeOptions.x1);
+ y1 = y2p(shapeOptions.y1);
+
+ astrX0 = astr + ".x0";
+ astrY0 = astr + ".y0";
+ astrX1 = astr + ".x1";
+ astrY1 = astr + ".y1";
}
- function endDrag(dragged) {
- setCursor(shapePath);
- if(dragged) {
- Plotly.relayout(gd, update);
- }
+ if (x0 < x1) {
+ w0 = x0;
+ astrW = astr + ".x0";
+ optW = "x0";
+ e0 = x1;
+ astrE = astr + ".x1";
+ optE = "x1";
+ } else {
+ w0 = x1;
+ astrW = astr + ".x1";
+ optW = "x1";
+ e0 = x0;
+ astrE = astr + ".x0";
+ optE = "x0";
}
-
- function moveShape(dx, dy) {
- if(shapeOptions.type === 'path') {
- var moveX = function moveX(x) { return p2x(x2p(x) + dx); };
- if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX);
-
- var moveY = function moveY(y) { return p2y(y2p(y) + dy); };
- if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY);
-
- shapeOptions.path = movePath(pathIn, moveX, moveY);
- update[astrPath] = shapeOptions.path;
- }
- else {
- update[astrX0] = shapeOptions.x0 = p2x(x0 + dx);
- update[astrY0] = shapeOptions.y0 = p2y(y0 + dy);
- update[astrX1] = shapeOptions.x1 = p2x(x1 + dx);
- update[astrY1] = shapeOptions.y1 = p2y(y1 + dy);
- }
-
- shapePath.attr('d', getPathString(gd, shapeOptions));
+ if (y0 < y1) {
+ n0 = y0;
+ astrN = astr + ".y0";
+ optN = "y0";
+ s0 = y1;
+ astrS = astr + ".y1";
+ optS = "y1";
+ } else {
+ n0 = y1;
+ astrN = astr + ".y1";
+ optN = "y1";
+ s0 = y0;
+ astrS = astr + ".y0";
+ optS = "y0";
}
- function resizeShape(dx, dy) {
- if(shapeOptions.type === 'path') {
- // TODO: implement path resize
- var moveX = function moveX(x) { return p2x(x2p(x) + dx); };
- if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX);
-
- var moveY = function moveY(y) { return p2y(y2p(y) + dy); };
- if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY);
-
- shapeOptions.path = movePath(pathIn, moveX, moveY);
- update[astrPath] = shapeOptions.path;
- }
- else {
- var newN = (~dragMode.indexOf('n')) ? n0 + dy : n0,
- newS = (~dragMode.indexOf('s')) ? s0 + dy : s0,
- newW = (~dragMode.indexOf('w')) ? w0 + dx : w0,
- newE = (~dragMode.indexOf('e')) ? e0 + dx : e0;
-
- if(newS - newN > MINHEIGHT) {
- update[astrN] = shapeOptions[optN] = p2y(newN);
- update[astrS] = shapeOptions[optS] = p2y(newS);
- }
-
- if(newE - newW > MINWIDTH) {
- update[astrW] = shapeOptions[optW] = p2x(newW);
- update[astrE] = shapeOptions[optE] = p2x(newE);
- }
- }
-
- shapePath.attr('d', getPathString(gd, shapeOptions));
- }
-}
+ update = {};
-function getShapeLayer(gd, index) {
- var shape = gd._fullLayout.shapes[index],
- shapeLayer = gd._fullLayout._shapeUpperLayer;
+ // setup dragMode and the corresponding handler
+ updateDragMode(evt);
+ dragOptions.moveFn = dragMode === "move" ? moveShape : resizeShape;
+ }
- if(!shape) {
- Lib.log('getShapeLayer: undefined shape: index', index);
+ function endDrag(dragged) {
+ setCursor(shapePath);
+ if (dragged) {
+ Plotly.relayout(gd, update);
}
- else if(shape.layer === 'below') {
- shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ?
- gd._fullLayout._shapeLowerLayer :
- gd._fullLayout._shapeSubplotLayer;
+ }
+
+ function moveShape(dx, dy) {
+ if (shapeOptions.type === "path") {
+ var moveX = function moveX(x) {
+ return p2x(x2p(x) + dx);
+ };
+ if (xa && xa.type === "date") moveX = helpers.encodeDate(moveX);
+
+ var moveY = function moveY(y) {
+ return p2y(y2p(y) + dy);
+ };
+ if (ya && ya.type === "date") moveY = helpers.encodeDate(moveY);
+
+ shapeOptions.path = movePath(pathIn, moveX, moveY);
+ update[astrPath] = shapeOptions.path;
+ } else {
+ update[astrX0] = shapeOptions.x0 = p2x(x0 + dx);
+ update[astrY0] = shapeOptions.y0 = p2y(y0 + dy);
+ update[astrX1] = shapeOptions.x1 = p2x(x1 + dx);
+ update[astrY1] = shapeOptions.y1 = p2y(y1 + dy);
}
- return shapeLayer;
-}
+ shapePath.attr("d", getPathString(gd, shapeOptions));
+ }
-function isShapeInSubplot(gd, shape, plotinfo) {
- var xa = Axes.getFromId(gd, plotinfo.id, 'x')._id,
- ya = Axes.getFromId(gd, plotinfo.id, 'y')._id,
- isBelow = shape.layer === 'below',
- inSuplotAxis = (xa === shape.xref || ya === shape.yref),
- isNotAnOverlaidSubplot = !!plotinfo.shapelayer;
- return isBelow && inSuplotAxis && isNotAnOverlaidSubplot;
-}
+ function resizeShape(dx, dy) {
+ if (shapeOptions.type === "path") {
+ // TODO: implement path resize
+ var moveX = function moveX(x) {
+ return p2x(x2p(x) + dx);
+ };
+ if (xa && xa.type === "date") moveX = helpers.encodeDate(moveX);
-function getPathString(gd, options) {
- var type = options.type,
- xa = Axes.getFromId(gd, options.xref),
- ya = Axes.getFromId(gd, options.yref),
- gs = gd._fullLayout._size,
- x2r,
- x2p,
- y2r,
- y2p;
-
- if(xa) {
- x2r = helpers.shapePositionToRange(xa);
- x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); };
- }
- else {
- x2p = function(v) { return gs.l + gs.w * v; };
- }
+ var moveY = function moveY(y) {
+ return p2y(y2p(y) + dy);
+ };
+ if (ya && ya.type === "date") moveY = helpers.encodeDate(moveY);
- if(ya) {
- y2r = helpers.shapePositionToRange(ya);
- y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); };
- }
- else {
- y2p = function(v) { return gs.t + gs.h * (1 - v); };
+ shapeOptions.path = movePath(pathIn, moveX, moveY);
+ update[astrPath] = shapeOptions.path;
+ } else {
+ var newN = ~dragMode.indexOf("n") ? n0 + dy : n0,
+ newS = ~dragMode.indexOf("s") ? s0 + dy : s0,
+ newW = ~dragMode.indexOf("w") ? w0 + dx : w0,
+ newE = ~dragMode.indexOf("e") ? e0 + dx : e0;
+
+ if (newS - newN > MINHEIGHT) {
+ update[astrN] = shapeOptions[optN] = p2y(newN);
+ update[astrS] = shapeOptions[optS] = p2y(newS);
+ }
+
+ if (newE - newW > MINWIDTH) {
+ update[astrW] = shapeOptions[optW] = p2x(newW);
+ update[astrE] = shapeOptions[optE] = p2x(newE);
+ }
}
- if(type === 'path') {
- if(xa && xa.type === 'date') x2p = helpers.decodeDate(x2p);
- if(ya && ya.type === 'date') y2p = helpers.decodeDate(y2p);
- return convertPath(options.path, x2p, y2p);
- }
+ shapePath.attr("d", getPathString(gd, shapeOptions));
+ }
+}
+
+function getShapeLayer(gd, index) {
+ var shape = gd._fullLayout.shapes[index],
+ shapeLayer = gd._fullLayout._shapeUpperLayer;
+
+ if (!shape) {
+ Lib.log("getShapeLayer: undefined shape: index", index);
+ } else if (shape.layer === "below") {
+ shapeLayer = shape.xref === "paper" && shape.yref === "paper"
+ ? gd._fullLayout._shapeLowerLayer
+ : gd._fullLayout._shapeSubplotLayer;
+ }
+
+ return shapeLayer;
+}
- var x0 = x2p(options.x0),
- x1 = x2p(options.x1),
- y0 = y2p(options.y0),
- y1 = y2p(options.y1);
-
- if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
- if(type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z';
- // circle
- var cx = (x0 + x1) / 2,
- cy = (y0 + y1) / 2,
- rx = Math.abs(cx - x0),
- ry = Math.abs(cy - y0),
- rArc = 'A' + rx + ',' + ry,
- rightPt = (cx + rx) + ',' + cy,
- topPt = cx + ',' + (cy - ry);
- return 'M' + rightPt + rArc + ' 0 1,1 ' + topPt +
- rArc + ' 0 0,1 ' + rightPt + 'Z';
+function isShapeInSubplot(gd, shape, plotinfo) {
+ var xa = Axes.getFromId(gd, plotinfo.id, "x")._id,
+ ya = Axes.getFromId(gd, plotinfo.id, "y")._id,
+ isBelow = shape.layer === "below",
+ inSuplotAxis = xa === shape.xref || ya === shape.yref,
+ isNotAnOverlaidSubplot = !!plotinfo.shapelayer;
+ return isBelow && inSuplotAxis && isNotAnOverlaidSubplot;
}
+function getPathString(gd, options) {
+ var type = options.type,
+ xa = Axes.getFromId(gd, options.xref),
+ ya = Axes.getFromId(gd, options.yref),
+ gs = gd._fullLayout._size,
+ x2r,
+ x2p,
+ y2r,
+ y2p;
+
+ if (xa) {
+ x2r = helpers.shapePositionToRange(xa);
+ x2p = function(v) {
+ return xa._offset + xa.r2p(x2r(v, true));
+ };
+ } else {
+ x2p = function(v) {
+ return gs.l + gs.w * v;
+ };
+ }
+
+ if (ya) {
+ y2r = helpers.shapePositionToRange(ya);
+ y2p = function(v) {
+ return ya._offset + ya.r2p(y2r(v, true));
+ };
+ } else {
+ y2p = function(v) {
+ return gs.t + gs.h * (1 - v);
+ };
+ }
+
+ if (type === "path") {
+ if (xa && xa.type === "date") x2p = helpers.decodeDate(x2p);
+ if (ya && ya.type === "date") y2p = helpers.decodeDate(y2p);
+ return convertPath(options.path, x2p, y2p);
+ }
+
+ var x0 = x2p(options.x0),
+ x1 = x2p(options.x1),
+ y0 = y2p(options.y0),
+ y1 = y2p(options.y1);
+
+ if (type === "line") return "M" + x0 + "," + y0 + "L" + x1 + "," + y1;
+ if (type === "rect") {
+ return "M" + x0 + "," + y0 + "H" + x1 + "V" + y1 + "H" + x0 + "Z";
+ }
+ // circle
+ var cx = (x0 + x1) / 2,
+ cy = (y0 + y1) / 2,
+ rx = Math.abs(cx - x0),
+ ry = Math.abs(cy - y0),
+ rArc = "A" + rx + "," + ry,
+ rightPt = cx + rx + "," + cy,
+ topPt = cx + "," + (cy - ry);
+ return "M" +
+ rightPt +
+ rArc +
+ " 0 1,1 " +
+ topPt +
+ rArc +
+ " 0 0,1 " +
+ rightPt +
+ "Z";
+}
function convertPath(pathIn, x2p, y2p) {
- // convert an SVG path string from data units to pixels
- return pathIn.replace(constants.segmentRE, function(segment) {
- var paramNumber = 0,
- segmentType = segment.charAt(0),
- xParams = constants.paramIsX[segmentType],
- yParams = constants.paramIsY[segmentType],
- nParams = constants.numParams[segmentType];
-
- var paramString = segment.substr(1).replace(constants.paramRE, function(param) {
- if(xParams[paramNumber]) param = x2p(param);
- else if(yParams[paramNumber]) param = y2p(param);
- paramNumber++;
-
- if(paramNumber > nParams) param = 'X';
- return param;
- });
-
- if(paramNumber > nParams) {
- paramString = paramString.replace(/[\s,]*X.*/, '');
- Lib.log('Ignoring extra params in segment ' + segment);
- }
-
- return segmentType + paramString;
- });
+ // convert an SVG path string from data units to pixels
+ return pathIn.replace(constants.segmentRE, function(segment) {
+ var paramNumber = 0,
+ segmentType = segment.charAt(0),
+ xParams = constants.paramIsX[segmentType],
+ yParams = constants.paramIsY[segmentType],
+ nParams = constants.numParams[segmentType];
+
+ var paramString = segment
+ .substr(1)
+ .replace(constants.paramRE, function(param) {
+ if (xParams[paramNumber]) param = x2p(param);
+ else if (yParams[paramNumber]) param = y2p(param);
+ paramNumber++;
+
+ if (paramNumber > nParams) param = "X";
+ return param;
+ });
+
+ if (paramNumber > nParams) {
+ paramString = paramString.replace(/[\s,]*X.*/, "");
+ Lib.log("Ignoring extra params in segment " + segment);
+ }
+
+ return segmentType + paramString;
+ });
}
function movePath(pathIn, moveX, moveY) {
- return pathIn.replace(constants.segmentRE, function(segment) {
- var paramNumber = 0,
- segmentType = segment.charAt(0),
- xParams = constants.paramIsX[segmentType],
- yParams = constants.paramIsY[segmentType],
- nParams = constants.numParams[segmentType];
+ return pathIn.replace(constants.segmentRE, function(segment) {
+ var paramNumber = 0,
+ segmentType = segment.charAt(0),
+ xParams = constants.paramIsX[segmentType],
+ yParams = constants.paramIsY[segmentType],
+ nParams = constants.numParams[segmentType];
- var paramString = segment.substr(1).replace(constants.paramRE, function(param) {
- if(paramNumber >= nParams) return param;
+ var paramString = segment
+ .substr(1)
+ .replace(constants.paramRE, function(param) {
+ if (paramNumber >= nParams) return param;
- if(xParams[paramNumber]) param = moveX(param);
- else if(yParams[paramNumber]) param = moveY(param);
+ if (xParams[paramNumber]) param = moveX(param);
+ else if (yParams[paramNumber]) param = moveY(param);
- paramNumber++;
+ paramNumber++;
- return param;
- });
+ return param;
+ });
- return segmentType + paramString;
- });
+ return segmentType + paramString;
+ });
}
diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js
index 15c5337c52a..0d9ab343d71 100644
--- a/src/components/shapes/helpers.js
+++ b/src/components/shapes/helpers.js
@@ -5,10 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
// special position conversion functions... category axis positions can't be
// specified by their data values, because they don't make a continuous mapping.
// so these have to be specified in terms of the category serial numbers,
@@ -19,61 +16,75 @@
// removed entirely.
exports.rangeToShapePosition = function(ax) {
- return (ax.type === 'log') ? ax.r2d : function(v) { return v; };
+ return ax.type === "log"
+ ? ax.r2d
+ : (function(v) {
+ return v;
+ });
};
exports.shapePositionToRange = function(ax) {
- return (ax.type === 'log') ? ax.d2r : function(v) { return v; };
+ return ax.type === "log"
+ ? ax.d2r
+ : (function(v) {
+ return v;
+ });
};
exports.decodeDate = function(convertToPx) {
- return function(v) {
- if(v.replace) v = v.replace('_', ' ');
- return convertToPx(v);
- };
+ return function(v) {
+ if (v.replace) v = v.replace("_", " ");
+ return convertToPx(v);
+ };
};
exports.encodeDate = function(convertToDate) {
- return function(v) { return convertToDate(v).replace(' ', '_'); };
+ return function(v) {
+ return convertToDate(v).replace(" ", "_");
+ };
};
exports.getDataToPixel = function(gd, axis, isVertical) {
- var gs = gd._fullLayout._size,
- dataToPixel;
+ var gs = gd._fullLayout._size, dataToPixel;
- if(axis) {
- var d2r = exports.shapePositionToRange(axis);
+ if (axis) {
+ var d2r = exports.shapePositionToRange(axis);
- dataToPixel = function(v) {
- return axis._offset + axis.r2p(d2r(v, true));
- };
+ dataToPixel = function(v) {
+ return axis._offset + axis.r2p(d2r(v, true));
+ };
- if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel);
- }
- else if(isVertical) {
- dataToPixel = function(v) { return gs.t + gs.h * (1 - v); };
- }
- else {
- dataToPixel = function(v) { return gs.l + gs.w * v; };
- }
+ if (axis.type === "date") dataToPixel = exports.decodeDate(dataToPixel);
+ } else if (isVertical) {
+ dataToPixel = function(v) {
+ return gs.t + gs.h * (1 - v);
+ };
+ } else {
+ dataToPixel = function(v) {
+ return gs.l + gs.w * v;
+ };
+ }
- return dataToPixel;
+ return dataToPixel;
};
exports.getPixelToData = function(gd, axis, isVertical) {
- var gs = gd._fullLayout._size,
- pixelToData;
+ var gs = gd._fullLayout._size, pixelToData;
- if(axis) {
- var r2d = exports.rangeToShapePosition(axis);
- pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); };
- }
- else if(isVertical) {
- pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; };
- }
- else {
- pixelToData = function(p) { return (p - gs.l) / gs.w; };
- }
+ if (axis) {
+ var r2d = exports.rangeToShapePosition(axis);
+ pixelToData = function(p) {
+ return r2d(axis.p2r(p - axis._offset));
+ };
+ } else if (isVertical) {
+ pixelToData = function(p) {
+ return 1 - (p - gs.t) / gs.h;
+ };
+ } else {
+ pixelToData = function(p) {
+ return (p - gs.l) / gs.w;
+ };
+ }
- return pixelToData;
+ return pixelToData;
};
diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js
index 444ede3cd59..0711a85a820 100644
--- a/src/components/shapes/index.js
+++ b/src/components/shapes/index.js
@@ -5,20 +5,15 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var drawModule = require('./draw');
+"use strict";
+var drawModule = require("./draw");
module.exports = {
- moduleType: 'component',
- name: 'shapes',
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- calcAutorange: require('./calc_autorange'),
- draw: drawModule.draw,
- drawOne: drawModule.drawOne
+ moduleType: "component",
+ name: "shapes",
+ layoutAttributes: require("./attributes"),
+ supplyLayoutDefaults: require("./defaults"),
+ calcAutorange: require("./calc_autorange"),
+ draw: drawModule.draw,
+ drawOne: drawModule.drawOne
};
diff --git a/src/components/shapes/shape_defaults.js b/src/components/shapes/shape_defaults.js
index 44d983fc793..091a1e950cf 100644
--- a/src/components/shapes/shape_defaults.js
+++ b/src/components/shapes/shape_defaults.js
@@ -5,93 +5,95 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-
-var attributes = require('./attributes');
-var helpers = require('./helpers');
-
-
-module.exports = function handleShapeDefaults(shapeIn, shapeOut, fullLayout, opts, itemOpts) {
- opts = opts || {};
- itemOpts = itemOpts || {};
-
- function coerce(attr, dflt) {
- return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt);
- }
-
- var visible = coerce('visible', !itemOpts.itemIsNotPlainObject);
-
- if(!visible) return shapeOut;
-
- coerce('layer');
- coerce('opacity');
- coerce('fillcolor');
- coerce('line.color');
- coerce('line.width');
- coerce('line.dash');
-
- var dfltType = shapeIn.path ? 'path' : 'rect',
- shapeType = coerce('type', dfltType);
-
- // positioning
- var axLetters = ['x', 'y'];
- for(var i = 0; i < 2; i++) {
- var axLetter = axLetters[i],
- gdMock = {_fullLayout: fullLayout};
-
- // xref, yref
- var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper');
-
- if(shapeType !== 'path') {
- var dflt0 = 0.25,
- dflt1 = 0.75,
- ax,
- pos2r,
- r2pos;
-
- if(axRef !== 'paper') {
- ax = Axes.getFromId(gdMock, axRef);
- r2pos = helpers.rangeToShapePosition(ax);
- pos2r = helpers.shapePositionToRange(ax);
- }
- else {
- pos2r = r2pos = Lib.identity;
- }
-
- // hack until V2.0 when log has regular range behavior - make it look like other
- // ranges to send to coerce, then put it back after
- // this is all to give reasonable default position behavior on log axes, which is
- // a pretty unimportant edge case so we could just ignore this.
- var attr0 = axLetter + '0',
- attr1 = axLetter + '1',
- in0 = shapeIn[attr0],
- in1 = shapeIn[attr1];
- shapeIn[attr0] = pos2r(shapeIn[attr0], true);
- shapeIn[attr1] = pos2r(shapeIn[attr1], true);
-
- // x0, x1 (and y0, y1)
- Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0);
- Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1);
-
- // hack part 2
- shapeOut[attr0] = r2pos(shapeOut[attr0]);
- shapeOut[attr1] = r2pos(shapeOut[attr1]);
- shapeIn[attr0] = in0;
- shapeIn[attr1] = in1;
- }
+"use strict";
+var Lib = require("../../lib");
+var Axes = require("../../plots/cartesian/axes");
+
+var attributes = require("./attributes");
+var helpers = require("./helpers");
+
+module.exports = function handleShapeDefaults(
+ shapeIn,
+ shapeOut,
+ fullLayout,
+ opts,
+ itemOpts
+) {
+ opts = opts || {};
+ itemOpts = itemOpts || {};
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt);
+ }
+
+ var visible = coerce("visible", !itemOpts.itemIsNotPlainObject);
+
+ if (!visible) return shapeOut;
+
+ coerce("layer");
+ coerce("opacity");
+ coerce("fillcolor");
+ coerce("line.color");
+ coerce("line.width");
+ coerce("line.dash");
+
+ var dfltType = shapeIn.path ? "path" : "rect",
+ shapeType = coerce("type", dfltType);
+
+ // positioning
+ var axLetters = ["x", "y"];
+ for (var i = 0; i < 2; i++) {
+ var axLetter = axLetters[i], gdMock = { _fullLayout: fullLayout };
+
+ // xref, yref
+ var axRef = Axes.coerceRef(
+ shapeIn,
+ shapeOut,
+ gdMock,
+ axLetter,
+ "",
+ "paper"
+ );
+
+ if (shapeType !== "path") {
+ var dflt0 = 0.25, dflt1 = 0.75, ax, pos2r, r2pos;
+
+ if (axRef !== "paper") {
+ ax = Axes.getFromId(gdMock, axRef);
+ r2pos = helpers.rangeToShapePosition(ax);
+ pos2r = helpers.shapePositionToRange(ax);
+ } else {
+ pos2r = r2pos = Lib.identity;
+ }
+
+ // hack until V2.0 when log has regular range behavior - make it look like other
+ // ranges to send to coerce, then put it back after
+ // this is all to give reasonable default position behavior on log axes, which is
+ // a pretty unimportant edge case so we could just ignore this.
+ var attr0 = axLetter + "0",
+ attr1 = axLetter + "1",
+ in0 = shapeIn[attr0],
+ in1 = shapeIn[attr1];
+ shapeIn[attr0] = pos2r(shapeIn[attr0], true);
+ shapeIn[attr1] = pos2r(shapeIn[attr1], true);
+
+ // x0, x1 (and y0, y1)
+ Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0);
+ Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1);
+
+ // hack part 2
+ shapeOut[attr0] = r2pos(shapeOut[attr0]);
+ shapeOut[attr1] = r2pos(shapeOut[attr1]);
+ shapeIn[attr0] = in0;
+ shapeIn[attr1] = in1;
}
+ }
- if(shapeType === 'path') {
- coerce('path');
- }
- else {
- Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']);
- }
+ if (shapeType === "path") {
+ coerce("path");
+ } else {
+ Lib.noneOrAll(shapeIn, shapeOut, ["x0", "x1", "y0", "y1"]);
+ }
- return shapeOut;
+ return shapeOut;
};
diff --git a/src/components/sliders/attributes.js b/src/components/sliders/attributes.js
index 8e584a2455f..4fb808df73e 100644
--- a/src/components/sliders/attributes.js
+++ b/src/components/sliders/attributes.js
@@ -5,268 +5,249 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var fontAttrs = require('../../plots/font_attributes');
-var padAttrs = require('../../plots/pad_attributes');
-var extendFlat = require('../../lib/extend').extendFlat;
-var extendDeep = require('../../lib/extend').extendDeep;
-var animationAttrs = require('../../plots/animation_attributes');
-var constants = require('./constants');
+"use strict";
+var fontAttrs = require("../../plots/font_attributes");
+var padAttrs = require("../../plots/pad_attributes");
+var extendFlat = require("../../lib/extend").extendFlat;
+var extendDeep = require("../../lib/extend").extendDeep;
+var animationAttrs = require("../../plots/animation_attributes");
+var constants = require("./constants");
var stepsAttrs = {
- _isLinkedToArray: 'step',
-
- method: {
- valType: 'enumerated',
- values: ['restyle', 'relayout', 'animate', 'update'],
- dflt: 'restyle',
- role: 'info',
- description: [
- 'Sets the Plotly method to be called when the slider value is changed.'
- ].join(' ')
- },
- args: {
- valType: 'info_array',
- role: 'info',
- freeLength: true,
- items: [
- { valType: 'any' },
- { valType: 'any' },
- { valType: 'any' }
- ],
- description: [
- 'Sets the arguments values to be passed to the Plotly',
- 'method set in `method` on slide.'
- ].join(' ')
- },
- label: {
- valType: 'string',
- role: 'info',
- description: 'Sets the text label to appear on the slider'
- },
- value: {
- valType: 'string',
- role: 'info',
- description: [
- 'Sets the value of the slider step, used to refer to the step programatically.',
- 'Defaults to the slider label if not provided.'
- ].join(' ')
- }
+ _isLinkedToArray: "step",
+ method: {
+ valType: "enumerated",
+ values: ["restyle", "relayout", "animate", "update"],
+ dflt: "restyle",
+ role: "info",
+ description: [
+ "Sets the Plotly method to be called when the slider value is changed."
+ ].join(" ")
+ },
+ args: {
+ valType: "info_array",
+ role: "info",
+ freeLength: true,
+ items: [{ valType: "any" }, { valType: "any" }, { valType: "any" }],
+ description: [
+ "Sets the arguments values to be passed to the Plotly",
+ "method set in `method` on slide."
+ ].join(" ")
+ },
+ label: {
+ valType: "string",
+ role: "info",
+ description: "Sets the text label to appear on the slider"
+ },
+ value: {
+ valType: "string",
+ role: "info",
+ description: [
+ "Sets the value of the slider step, used to refer to the step programatically.",
+ "Defaults to the slider label if not provided."
+ ].join(" ")
+ }
};
module.exports = {
- _isLinkedToArray: 'slider',
-
+ _isLinkedToArray: "slider",
+ visible: {
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: ["Determines whether or not the slider is visible."].join(" ")
+ },
+ active: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ dflt: 0,
+ description: [
+ "Determines which button (by index starting from 0) is",
+ "considered active."
+ ].join(" ")
+ },
+ steps: stepsAttrs,
+ lenmode: {
+ valType: "enumerated",
+ values: ["fraction", "pixels"],
+ role: "info",
+ dflt: "fraction",
+ description: [
+ "Determines whether this slider length",
+ "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 slider",
+ "This measure excludes the padding of both ends.",
+ "That is, the slider's length is this length minus the",
+ "padding on both ends."
+ ].join(" ")
+ },
+ x: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ dflt: 0,
+ role: "style",
+ description: "Sets the x position (in normalized coordinates) of the slider."
+ },
+ pad: extendDeep(
+ {},
+ padAttrs,
+ { description: "Set the padding of the slider component along each side." },
+ { t: { dflt: 20 } }
+ ),
+ xanchor: {
+ valType: "enumerated",
+ values: ["auto", "left", "center", "right"],
+ dflt: "left",
+ role: "info",
+ description: [
+ "Sets the slider's horizontal position anchor.",
+ "This anchor binds the `x` position to the *left*, *center*",
+ "or *right* of the range selector."
+ ].join(" ")
+ },
+ y: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ dflt: 0,
+ role: "style",
+ description: "Sets the y position (in normalized coordinates) of the slider."
+ },
+ yanchor: {
+ valType: "enumerated",
+ values: ["auto", "top", "middle", "bottom"],
+ dflt: "top",
+ role: "info",
+ description: [
+ "Sets the slider's vertical position anchor",
+ "This anchor binds the `y` position to the *top*, *middle*",
+ "or *bottom* of the range selector."
+ ].join(" ")
+ },
+ transition: {
+ duration: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ dflt: 150,
+ description: "Sets the duration of the slider transition"
+ },
+ easing: {
+ valType: "enumerated",
+ values: animationAttrs.transition.easing.values,
+ role: "info",
+ dflt: "cubic-in-out",
+ description: "Sets the easing function of the slider transition"
+ }
+ },
+ currentvalue: {
visible: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines whether or not the slider is visible.'
- ].join(' ')
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: [
+ "Shows the currently-selected value above the slider."
+ ].join(" ")
},
-
- active: {
- valType: 'number',
- role: 'info',
- min: 0,
- dflt: 0,
- description: [
- 'Determines which button (by index starting from 0) is',
- 'considered active.'
- ].join(' ')
- },
-
- steps: stepsAttrs,
-
- lenmode: {
- valType: 'enumerated',
- values: ['fraction', 'pixels'],
- role: 'info',
- dflt: 'fraction',
- description: [
- 'Determines whether this slider length',
- '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 slider',
- 'This measure excludes the padding of both ends.',
- 'That is, the slider\'s length is this length minus the',
- 'padding on both ends.'
- ].join(' ')
- },
- x: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: 0,
- role: 'style',
- description: 'Sets the x position (in normalized coordinates) of the slider.'
- },
- pad: extendDeep({}, padAttrs, {
- description: 'Set the padding of the slider component along each side.'
- }, {t: {dflt: 20}}),
xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'left',
- role: 'info',
- description: [
- 'Sets the slider\'s horizontal position anchor.',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the range selector.'
- ].join(' ')
- },
- y: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: 0,
- role: 'style',
- description: 'Sets the y position (in normalized coordinates) of the slider.'
+ valType: "enumerated",
+ values: ["left", "center", "right"],
+ dflt: "left",
+ role: "info",
+ description: [
+ "The alignment of the value readout relative to the length of the slider."
+ ].join(" ")
+ },
+ offset: {
+ valType: "number",
+ dflt: 10,
+ role: "info",
+ description: [
+ "The amount of space, in pixels, between the current value label",
+ "and the slider."
+ ].join(" ")
+ },
+ prefix: {
+ valType: "string",
+ role: "info",
+ description: "When currentvalue.visible is true, this sets the prefix of the label."
+ },
+ suffix: {
+ valType: "string",
+ role: "info",
+ description: "When currentvalue.visible is true, this sets the suffix of the label."
},
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'top',
- role: 'info',
- description: [
- 'Sets the slider\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the range selector.'
- ].join(' ')
- },
-
- transition: {
- duration: {
- valType: 'number',
- role: 'info',
- min: 0,
- dflt: 150,
- description: 'Sets the duration of the slider transition'
- },
- easing: {
- valType: 'enumerated',
- values: animationAttrs.transition.easing.values,
- role: 'info',
- dflt: 'cubic-in-out',
- description: 'Sets the easing function of the slider transition'
- },
- },
-
- currentvalue: {
- visible: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Shows the currently-selected value above the slider.'
- ].join(' ')
- },
-
- xanchor: {
- valType: 'enumerated',
- values: ['left', 'center', 'right'],
- dflt: 'left',
- role: 'info',
- description: [
- 'The alignment of the value readout relative to the length of the slider.'
- ].join(' ')
- },
-
- offset: {
- valType: 'number',
- dflt: 10,
- role: 'info',
- description: [
- 'The amount of space, in pixels, between the current value label',
- 'and the slider.'
- ].join(' ')
- },
-
- prefix: {
- valType: 'string',
- role: 'info',
- description: 'When currentvalue.visible is true, this sets the prefix of the label.'
- },
-
- suffix: {
- valType: 'string',
- role: 'info',
- description: 'When currentvalue.visible is true, this sets the suffix of the label.'
- },
-
- font: extendFlat({}, fontAttrs, {
- description: 'Sets the font of the current value label text.'
- }),
- },
-
font: extendFlat({}, fontAttrs, {
- description: 'Sets the font of the slider step labels.'
- }),
-
- activebgcolor: {
- valType: 'color',
- role: 'style',
- dflt: constants.gripBgActiveColor,
- description: [
- 'Sets the background color of the slider grip',
- 'while dragging.'
- ].join(' ')
- },
- bgcolor: {
- valType: 'color',
- role: 'style',
- dflt: constants.railBgColor,
- description: 'Sets the background color of the slider.'
- },
- bordercolor: {
- valType: 'color',
- dflt: constants.railBorderColor,
- role: 'style',
- description: 'Sets the color of the border enclosing the slider.'
- },
- borderwidth: {
- valType: 'number',
- min: 0,
- dflt: constants.railBorderWidth,
- role: 'style',
- description: 'Sets the width (in px) of the border enclosing the slider.'
- },
- ticklen: {
- valType: 'number',
- min: 0,
- dflt: constants.tickLength,
- role: 'style',
- description: 'Sets the length in pixels of step tick marks'
- },
- tickcolor: {
- valType: 'color',
- dflt: constants.tickColor,
- role: 'style',
- description: 'Sets the color of the border enclosing the slider.'
- },
- tickwidth: {
- valType: 'number',
- min: 0,
- dflt: 1,
- role: 'style',
- description: 'Sets the tick width (in px).'
- },
- minorticklen: {
- valType: 'number',
- min: 0,
- dflt: constants.minorTickLength,
- role: 'style',
- description: 'Sets the length in pixels of minor step tick marks'
- },
+ description: "Sets the font of the current value label text."
+ })
+ },
+ font: extendFlat({}, fontAttrs, {
+ description: "Sets the font of the slider step labels."
+ }),
+ activebgcolor: {
+ valType: "color",
+ role: "style",
+ dflt: constants.gripBgActiveColor,
+ description: [
+ "Sets the background color of the slider grip",
+ "while dragging."
+ ].join(" ")
+ },
+ bgcolor: {
+ valType: "color",
+ role: "style",
+ dflt: constants.railBgColor,
+ description: "Sets the background color of the slider."
+ },
+ bordercolor: {
+ valType: "color",
+ dflt: constants.railBorderColor,
+ role: "style",
+ description: "Sets the color of the border enclosing the slider."
+ },
+ borderwidth: {
+ valType: "number",
+ min: 0,
+ dflt: constants.railBorderWidth,
+ role: "style",
+ description: "Sets the width (in px) of the border enclosing the slider."
+ },
+ ticklen: {
+ valType: "number",
+ min: 0,
+ dflt: constants.tickLength,
+ role: "style",
+ description: "Sets the length in pixels of step tick marks"
+ },
+ tickcolor: {
+ valType: "color",
+ dflt: constants.tickColor,
+ role: "style",
+ description: "Sets the color of the border enclosing the slider."
+ },
+ tickwidth: {
+ valType: "number",
+ min: 0,
+ dflt: 1,
+ role: "style",
+ description: "Sets the tick width (in px)."
+ },
+ minorticklen: {
+ valType: "number",
+ min: 0,
+ dflt: constants.minorTickLength,
+ role: "style",
+ description: "Sets the length in pixels of minor step tick marks"
+ }
};
diff --git a/src/components/sliders/constants.js b/src/components/sliders/constants.js
index fedd7c088b5..6004822972e 100644
--- a/src/components/sliders/constants.js
+++ b/src/components/sliders/constants.js
@@ -5,91 +5,70 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
module.exports = {
-
- // layout attribute name
- name: 'sliders',
-
- // class names
- containerClassName: 'slider-container',
- groupClassName: 'slider-group',
- inputAreaClass: 'slider-input-area',
- railRectClass: 'slider-rail-rect',
- railTouchRectClass: 'slider-rail-touch-rect',
- gripRectClass: 'slider-grip-rect',
- tickRectClass: 'slider-tick-rect',
- inputProxyClass: 'slider-input-proxy',
- labelsClass: 'slider-labels',
- labelGroupClass: 'slider-label-group',
- labelClass: 'slider-label',
- currentValueClass: 'slider-current-value',
-
- railHeight: 5,
-
- // DOM attribute name in button group keeping track
- // of active update menu
- menuIndexAttrName: 'slider-active-index',
-
- // id root pass to Plots.autoMargin
- autoMarginIdRoot: 'slider-',
-
- // min item width / height
- minWidth: 30,
- minHeight: 30,
-
- // padding around item text
- textPadX: 40,
-
- // font size to height scale
- fontSizeToHeight: 1.3,
-
- // arrow offset off right edge
- arrowOffsetX: 4,
-
- railRadius: 2,
- railWidth: 5,
- railBorder: 4,
- railBorderWidth: 1,
- railBorderColor: '#bec8d9',
- railBgColor: '#f8fafc',
-
- // The distance of the rail from the edge of the touchable area
- // Slightly less than the step inset because of the curved edges
- // of the rail
- railInset: 8,
-
- // The distance from the extremal tick marks to the edge of the
- // touchable area. This is basically the same as the grip radius,
- // but for other styles it wouldn't really need to be.
- stepInset: 10,
-
- gripRadius: 10,
- gripWidth: 20,
- gripHeight: 20,
- gripBorder: 20,
- gripBorderWidth: 1,
- gripBorderColor: '#bec8d9',
- gripBgColor: '#f6f8fa',
- gripBgActiveColor: '#dbdde0',
-
- labelPadding: 8,
- labelOffset: 0,
-
- tickWidth: 1,
- tickColor: '#333',
- tickOffset: 25,
- tickLength: 7,
-
- minorTickOffset: 25,
- minorTickColor: '#333',
- minorTickLength: 4,
-
- // Extra space below the current value label:
- currentValuePadding: 8,
- currentValueInset: 0,
+ // layout attribute name
+ name: "sliders",
+ // class names
+ containerClassName: "slider-container",
+ groupClassName: "slider-group",
+ inputAreaClass: "slider-input-area",
+ railRectClass: "slider-rail-rect",
+ railTouchRectClass: "slider-rail-touch-rect",
+ gripRectClass: "slider-grip-rect",
+ tickRectClass: "slider-tick-rect",
+ inputProxyClass: "slider-input-proxy",
+ labelsClass: "slider-labels",
+ labelGroupClass: "slider-label-group",
+ labelClass: "slider-label",
+ currentValueClass: "slider-current-value",
+ railHeight: 5,
+ // DOM attribute name in button group keeping track
+ // of active update menu
+ menuIndexAttrName: "slider-active-index",
+ // id root pass to Plots.autoMargin
+ autoMarginIdRoot: "slider-",
+ // min item width / height
+ minWidth: 30,
+ minHeight: 30,
+ // padding around item text
+ textPadX: 40,
+ // font size to height scale
+ fontSizeToHeight: 1.3,
+ // arrow offset off right edge
+ arrowOffsetX: 4,
+ railRadius: 2,
+ railWidth: 5,
+ railBorder: 4,
+ railBorderWidth: 1,
+ railBorderColor: "#bec8d9",
+ railBgColor: "#f8fafc",
+ // The distance of the rail from the edge of the touchable area
+ // Slightly less than the step inset because of the curved edges
+ // of the rail
+ railInset: 8,
+ // The distance from the extremal tick marks to the edge of the
+ // touchable area. This is basically the same as the grip radius,
+ // but for other styles it wouldn't really need to be.
+ stepInset: 10,
+ gripRadius: 10,
+ gripWidth: 20,
+ gripHeight: 20,
+ gripBorder: 20,
+ gripBorderWidth: 1,
+ gripBorderColor: "#bec8d9",
+ gripBgColor: "#f6f8fa",
+ gripBgActiveColor: "#dbdde0",
+ labelPadding: 8,
+ labelOffset: 0,
+ tickWidth: 1,
+ tickColor: "#333",
+ tickOffset: 25,
+ tickLength: 7,
+ minorTickOffset: 25,
+ minorTickColor: "#333",
+ minorTickLength: 4,
+ // Extra space below the current value label:
+ currentValuePadding: 8,
+ currentValueInset: 0
};
diff --git a/src/components/sliders/defaults.js b/src/components/sliders/defaults.js
index b2fed316c7b..6d1057a1f18 100644
--- a/src/components/sliders/defaults.js
+++ b/src/components/sliders/defaults.js
@@ -5,107 +5,101 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Lib = require("../../lib");
+var handleArrayContainerDefaults = require(
+ "../../plots/array_container_defaults"
+);
-'use strict';
-
-var Lib = require('../../lib');
-var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
-
-var attributes = require('./attributes');
-var constants = require('./constants');
+var attributes = require("./attributes");
+var constants = require("./constants");
var name = constants.name;
var stepAttrs = attributes.steps;
-
module.exports = function slidersDefaults(layoutIn, layoutOut) {
- var opts = {
- name: name,
- handleItemDefaults: sliderDefaults
- };
+ var opts = { name: name, handleItemDefaults: sliderDefaults };
- handleArrayContainerDefaults(layoutIn, layoutOut, opts);
+ handleArrayContainerDefaults(layoutIn, layoutOut, opts);
};
function sliderDefaults(sliderIn, sliderOut, layoutOut) {
+ function coerce(attr, dflt) {
+ return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt);
+ }
- function coerce(attr, dflt) {
- return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt);
- }
+ var steps = stepsDefaults(sliderIn, sliderOut);
- var steps = stepsDefaults(sliderIn, sliderOut);
+ var visible = coerce("visible", steps.length > 0);
+ if (!visible) return;
- var visible = coerce('visible', steps.length > 0);
- if(!visible) return;
+ coerce("active");
- coerce('active');
+ coerce("x");
+ coerce("y");
+ Lib.noneOrAll(sliderIn, sliderOut, ["x", "y"]);
- coerce('x');
- coerce('y');
- Lib.noneOrAll(sliderIn, sliderOut, ['x', 'y']);
+ coerce("xanchor");
+ coerce("yanchor");
- coerce('xanchor');
- coerce('yanchor');
+ coerce("len");
+ coerce("lenmode");
- coerce('len');
- coerce('lenmode');
+ coerce("pad.t");
+ coerce("pad.r");
+ coerce("pad.b");
+ coerce("pad.l");
- coerce('pad.t');
- coerce('pad.r');
- coerce('pad.b');
- coerce('pad.l');
+ Lib.coerceFont(coerce, "font", layoutOut.font);
- Lib.coerceFont(coerce, 'font', layoutOut.font);
+ var currentValueIsVisible = coerce("currentvalue.visible");
- var currentValueIsVisible = coerce('currentvalue.visible');
+ if (currentValueIsVisible) {
+ coerce("currentvalue.xanchor");
+ coerce("currentvalue.prefix");
+ coerce("currentvalue.suffix");
+ coerce("currentvalue.offset");
- if(currentValueIsVisible) {
- coerce('currentvalue.xanchor');
- coerce('currentvalue.prefix');
- coerce('currentvalue.suffix');
- coerce('currentvalue.offset');
+ Lib.coerceFont(coerce, "currentvalue.font", sliderOut.font);
+ }
- Lib.coerceFont(coerce, 'currentvalue.font', sliderOut.font);
- }
+ coerce("transition.duration");
+ coerce("transition.easing");
- coerce('transition.duration');
- coerce('transition.easing');
-
- coerce('bgcolor');
- coerce('activebgcolor');
- coerce('bordercolor');
- coerce('borderwidth');
- coerce('ticklen');
- coerce('tickwidth');
- coerce('tickcolor');
- coerce('minorticklen');
+ coerce("bgcolor");
+ coerce("activebgcolor");
+ coerce("bordercolor");
+ coerce("borderwidth");
+ coerce("ticklen");
+ coerce("tickwidth");
+ coerce("tickcolor");
+ coerce("minorticklen");
}
function stepsDefaults(sliderIn, sliderOut) {
- var valuesIn = sliderIn.steps || [],
- valuesOut = sliderOut.steps = [];
+ var valuesIn = sliderIn.steps || [], valuesOut = sliderOut.steps = [];
- var valueIn, valueOut;
+ var valueIn, valueOut;
- function coerce(attr, dflt) {
- return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt);
- }
+ function coerce(attr, dflt) {
+ return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt);
+ }
- for(var i = 0; i < valuesIn.length; i++) {
- valueIn = valuesIn[i];
- valueOut = {};
+ for (var i = 0; i < valuesIn.length; i++) {
+ valueIn = valuesIn[i];
+ valueOut = {};
- if(!Lib.isPlainObject(valueIn) || !Array.isArray(valueIn.args)) {
- continue;
- }
+ if (!Lib.isPlainObject(valueIn) || !Array.isArray(valueIn.args)) {
+ continue;
+ }
- coerce('method');
- coerce('args');
- coerce('label', 'step-' + i);
- coerce('value', valueOut.label);
+ coerce("method");
+ coerce("args");
+ coerce("label", "step-" + i);
+ coerce("value", valueOut.label);
- valuesOut.push(valueOut);
- }
+ valuesOut.push(valueOut);
+ }
- return valuesOut;
+ return valuesOut;
}
diff --git a/src/components/sliders/draw.js b/src/components/sliders/draw.js
index 50c2ef0f35e..e5ce4991ec4 100644
--- a/src/components/sliders/draw.js
+++ b/src/components/sliders/draw.js
@@ -5,596 +5,704 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var Plots = require("../../plots/plots");
+var Color = require("../color");
+var Drawing = require("../drawing");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var anchorUtils = require("../legend/anchor_utils");
-'use strict';
-
-var d3 = require('d3');
-
-var Plots = require('../../plots/plots');
-var Color = require('../color');
-var Drawing = require('../drawing');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var anchorUtils = require('../legend/anchor_utils');
-
-var constants = require('./constants');
-
+var constants = require("./constants");
module.exports = function draw(gd) {
- var fullLayout = gd._fullLayout,
- sliderData = makeSliderData(fullLayout);
-
- // draw a container for *all* sliders:
- var sliders = fullLayout._infolayer
- .selectAll('g.' + constants.containerClassName)
- .data(sliderData.length > 0 ? [0] : []);
-
- sliders.enter().append('g')
- .classed(constants.containerClassName, true)
- .style('cursor', 'ew-resize');
+ var fullLayout = gd._fullLayout, sliderData = makeSliderData(fullLayout);
- sliders.exit().remove();
+ // draw a container for *all* sliders:
+ var sliders = fullLayout._infolayer
+ .selectAll("g." + constants.containerClassName)
+ .data(sliderData.length > 0 ? [0] : []);
- // If no more sliders, clear the margisn:
- if(sliders.exit().size()) clearPushMargins(gd);
+ sliders
+ .enter()
+ .append("g")
+ .classed(constants.containerClassName, true)
+ .style("cursor", "ew-resize");
- // Return early if no menus visible:
- if(sliderData.length === 0) return;
+ sliders.exit().remove();
- var sliderGroups = sliders.selectAll('g.' + constants.groupClassName)
- .data(sliderData, keyFunction);
+ // If no more sliders, clear the margisn:
+ if (sliders.exit().size()) clearPushMargins(gd);
- sliderGroups.enter().append('g')
- .classed(constants.groupClassName, true);
+ // Return early if no menus visible:
+ if (sliderData.length === 0) return;
- sliderGroups.exit().each(function(sliderOpts) {
- d3.select(this).remove();
+ var sliderGroups = sliders
+ .selectAll("g." + constants.groupClassName)
+ .data(sliderData, keyFunction);
- sliderOpts._commandObserver.remove();
- delete sliderOpts._commandObserver;
+ sliderGroups.enter().append("g").classed(constants.groupClassName, true);
- Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index);
- });
+ sliderGroups.exit().each(function(sliderOpts) {
+ d3.select(this).remove();
- // Find the dimensions of the sliders:
- for(var i = 0; i < sliderData.length; i++) {
- var sliderOpts = sliderData[i];
- findDimensions(gd, sliderOpts);
- }
+ sliderOpts._commandObserver.remove();
+ delete sliderOpts._commandObserver;
- sliderGroups.each(function(sliderOpts) {
- // If it has fewer than two options, it's not really a slider:
- if(sliderOpts.steps.length < 2) return;
+ Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index);
+ });
- var gSlider = d3.select(this);
+ // Find the dimensions of the sliders:
+ for (var i = 0; i < sliderData.length; i++) {
+ var sliderOpts = sliderData[i];
+ findDimensions(gd, sliderOpts);
+ }
- computeLabelSteps(sliderOpts);
+ sliderGroups.each(function(sliderOpts) {
+ // If it has fewer than two options, it's not really a slider:
+ if (sliderOpts.steps.length < 2) return;
- Plots.manageCommandObserver(gd, sliderOpts, sliderOpts.steps, function(data) {
- // NB: Same as below. This is *not* always the same as sliderOpts since
- // if a new set of steps comes in, the reference in this callback would
- // be invalid. We need to refetch it from the slider group, which is
- // the join data that creates this slider. So if this slider still exists,
- // the group should be valid, *to the best of my knowledge.* If not,
- // we'd have to look it up by d3 data join index/key.
- var opts = gSlider.data()[0];
+ var gSlider = d3.select(this);
- if(opts.active === data.index) return;
- if(opts._dragging) return;
+ computeLabelSteps(sliderOpts);
- setActive(gd, gSlider, opts, data.index, false, true);
- });
+ Plots.manageCommandObserver(gd, sliderOpts, sliderOpts.steps, function(
+ data
+ ) {
+ // NB: Same as below. This is *not* always the same as sliderOpts since
+ // if a new set of steps comes in, the reference in this callback would
+ // be invalid. We need to refetch it from the slider group, which is
+ // the join data that creates this slider. So if this slider still exists,
+ // the group should be valid, *to the best of my knowledge.* If not,
+ // we'd have to look it up by d3 data join index/key.
+ var opts = gSlider.data()[0];
- drawSlider(gd, d3.select(this), sliderOpts);
+ if (opts.active === data.index) return;
+ if (opts._dragging) return;
- // makeInputProxy(gd, d3.select(this), sliderOpts);
+ setActive(gd, gSlider, opts, data.index, false, true);
});
+
+ drawSlider(gd, d3.select(this), sliderOpts);
+ // makeInputProxy(gd, d3.select(this), sliderOpts);
+ });
};
/* function makeInputProxy(gd, sliderGroup, sliderOpts) {
sliderOpts.inputProxy = gd._fullLayout._paperdiv.selectAll('input.' + constants.inputProxyClass)
.data([0]);
-}*/
-
+} */
// This really only just filters by visibility:
function makeSliderData(fullLayout) {
- var contOpts = fullLayout[constants.name],
- sliderData = [];
+ var contOpts = fullLayout[constants.name], sliderData = [];
- for(var i = 0; i < contOpts.length; i++) {
- var item = contOpts[i];
- if(!item.visible || !item.steps.length) continue;
- sliderData.push(item);
- }
+ for (var i = 0; i < contOpts.length; i++) {
+ var item = contOpts[i];
+ if (!item.visible || !item.steps.length) continue;
+ sliderData.push(item);
+ }
- return sliderData;
+ return sliderData;
}
// This is set in the defaults step:
function keyFunction(opts) {
- return opts._index;
+ return opts._index;
}
// Compute the dimensions (mutates sliderOpts):
function findDimensions(gd, sliderOpts) {
- var sliderLabels = gd._tester.selectAll('g.' + constants.labelGroupClass)
- .data(sliderOpts.steps);
-
- sliderLabels.enter().append('g')
- .classed(constants.labelGroupClass, true);
+ var sliderLabels = gd._tester
+ .selectAll("g." + constants.labelGroupClass)
+ .data(sliderOpts.steps);
- // loop over fake buttons to find width / height
- var maxLabelWidth = 0;
- var labelHeight = 0;
- sliderLabels.each(function(stepOpts) {
- var labelGroup = d3.select(this);
-
- var text = drawLabel(labelGroup, {step: stepOpts}, sliderOpts);
-
- var tWidth = (text.node() && Drawing.bBox(text.node()).width) || 0;
-
- // This just overwrites with the last. Which is fine as long as
- // the bounding box (probably incorrectly) measures the text *on
- // a single line*:
- labelHeight = (text.node() && Drawing.bBox(text.node()).height) || 0;
-
- maxLabelWidth = Math.max(maxLabelWidth, tWidth);
- });
+ sliderLabels.enter().append("g").classed(constants.labelGroupClass, true);
- sliderLabels.remove();
+ // loop over fake buttons to find width / height
+ var maxLabelWidth = 0;
+ var labelHeight = 0;
+ sliderLabels.each(function(stepOpts) {
+ var labelGroup = d3.select(this);
- sliderOpts.inputAreaWidth = Math.max(
- constants.railWidth,
- constants.gripHeight
- );
-
- sliderOpts.currentValueMaxWidth = 0;
- sliderOpts.currentValueHeight = 0;
- sliderOpts.currentValueTotalHeight = 0;
-
- if(sliderOpts.currentvalue.visible) {
- // Get the dimensions of the current value label:
- var dummyGroup = gd._tester.append('g');
+ var text = drawLabel(labelGroup, { step: stepOpts }, sliderOpts);
- sliderLabels.each(function(stepOpts) {
- var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label);
- var curValSize = (curValPrefix.node() && Drawing.bBox(curValPrefix.node())) || {width: 0, height: 0};
- sliderOpts.currentValueMaxWidth = Math.max(sliderOpts.currentValueMaxWidth, Math.ceil(curValSize.width));
- sliderOpts.currentValueHeight = Math.max(sliderOpts.currentValueHeight, Math.ceil(curValSize.height));
- });
+ var tWidth = text.node() && Drawing.bBox(text.node()).width || 0;
- sliderOpts.currentValueTotalHeight = sliderOpts.currentValueHeight + sliderOpts.currentvalue.offset;
+ // This just overwrites with the last. Which is fine as long as
+ // the bounding box (probably incorrectly) measures the text *on
+ // a single line*:
+ labelHeight = text.node() && Drawing.bBox(text.node()).height || 0;
- dummyGroup.remove();
- }
+ maxLabelWidth = Math.max(maxLabelWidth, tWidth);
+ });
- var graphSize = gd._fullLayout._size;
- sliderOpts.lx = graphSize.l + graphSize.w * sliderOpts.x;
- sliderOpts.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y);
+ sliderLabels.remove();
- if(sliderOpts.lenmode === 'fraction') {
- // fraction:
- sliderOpts.outerLength = Math.round(graphSize.w * sliderOpts.len);
- } else {
- // pixels:
- sliderOpts.outerLength = sliderOpts.len;
- }
+ sliderOpts.inputAreaWidth = Math.max(
+ constants.railWidth,
+ constants.gripHeight
+ );
- // Set the length-wise padding so that the grip ends up *on* the end of
- // the bar when at either extreme
- sliderOpts.lenPad = Math.round(constants.gripWidth * 0.5);
+ sliderOpts.currentValueMaxWidth = 0;
+ sliderOpts.currentValueHeight = 0;
+ sliderOpts.currentValueTotalHeight = 0;
- // The length of the rail, *excluding* padding on either end:
- sliderOpts.inputAreaStart = 0;
- sliderOpts.inputAreaLength = Math.round(sliderOpts.outerLength - sliderOpts.pad.l - sliderOpts.pad.r);
+ if (sliderOpts.currentvalue.visible) {
+ // Get the dimensions of the current value label:
+ var dummyGroup = gd._tester.append("g");
- var textableInputLength = sliderOpts.inputAreaLength - 2 * constants.stepInset;
- var availableSpacePerLabel = textableInputLength / (sliderOpts.steps.length - 1);
- var computedSpacePerLabel = maxLabelWidth + constants.labelPadding;
- sliderOpts.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel));
- sliderOpts.labelHeight = labelHeight;
-
- sliderOpts.height = sliderOpts.currentValueTotalHeight + constants.tickOffset + sliderOpts.ticklen + constants.labelOffset + sliderOpts.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b;
-
- var xanchor = 'left';
- if(anchorUtils.isRightAnchor(sliderOpts)) {
- sliderOpts.lx -= sliderOpts.outerLength;
- xanchor = 'right';
- }
- if(anchorUtils.isCenterAnchor(sliderOpts)) {
- sliderOpts.lx -= sliderOpts.outerLength / 2;
- xanchor = 'center';
- }
-
- var yanchor = 'top';
- if(anchorUtils.isBottomAnchor(sliderOpts)) {
- sliderOpts.ly -= sliderOpts.height;
- yanchor = 'bottom';
- }
- if(anchorUtils.isMiddleAnchor(sliderOpts)) {
- sliderOpts.ly -= sliderOpts.height / 2;
- yanchor = 'middle';
- }
-
- sliderOpts.outerLength = Math.ceil(sliderOpts.outerLength);
- sliderOpts.height = Math.ceil(sliderOpts.height);
- sliderOpts.lx = Math.round(sliderOpts.lx);
- sliderOpts.ly = Math.round(sliderOpts.ly);
-
- Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index, {
- x: sliderOpts.x,
- y: sliderOpts.y,
- l: sliderOpts.outerLength * ({right: 1, center: 0.5}[xanchor] || 0),
- r: sliderOpts.outerLength * ({left: 1, center: 0.5}[xanchor] || 0),
- b: sliderOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
- t: sliderOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
+ sliderLabels.each(function(stepOpts) {
+ var curValPrefix = drawCurrentValue(
+ dummyGroup,
+ sliderOpts,
+ stepOpts.label
+ );
+ var curValSize = curValPrefix.node() &&
+ Drawing.bBox(curValPrefix.node()) ||
+ { width: 0, height: 0 };
+ sliderOpts.currentValueMaxWidth = Math.max(
+ sliderOpts.currentValueMaxWidth,
+ Math.ceil(curValSize.width)
+ );
+ sliderOpts.currentValueHeight = Math.max(
+ sliderOpts.currentValueHeight,
+ Math.ceil(curValSize.height)
+ );
});
+
+ sliderOpts.currentValueTotalHeight = sliderOpts.currentValueHeight +
+ sliderOpts.currentvalue.offset;
+
+ dummyGroup.remove();
+ }
+
+ var graphSize = gd._fullLayout._size;
+ sliderOpts.lx = graphSize.l + graphSize.w * sliderOpts.x;
+ sliderOpts.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y);
+
+ if (sliderOpts.lenmode === "fraction") {
+ // fraction:
+ sliderOpts.outerLength = Math.round(graphSize.w * sliderOpts.len);
+ } else {
+ // pixels:
+ sliderOpts.outerLength = sliderOpts.len;
+ }
+
+ // Set the length-wise padding so that the grip ends up *on* the end of
+ // the bar when at either extreme
+ sliderOpts.lenPad = Math.round(constants.gripWidth * 0.5);
+
+ // The length of the rail, *excluding* padding on either end:
+ sliderOpts.inputAreaStart = 0;
+ sliderOpts.inputAreaLength = Math.round(
+ sliderOpts.outerLength - sliderOpts.pad.l - sliderOpts.pad.r
+ );
+
+ var textableInputLength = sliderOpts.inputAreaLength -
+ 2 * constants.stepInset;
+ var availableSpacePerLabel = textableInputLength /
+ (sliderOpts.steps.length - 1);
+ var computedSpacePerLabel = maxLabelWidth + constants.labelPadding;
+ sliderOpts.labelStride = Math.max(
+ 1,
+ Math.ceil(computedSpacePerLabel / availableSpacePerLabel)
+ );
+ sliderOpts.labelHeight = labelHeight;
+
+ sliderOpts.height = sliderOpts.currentValueTotalHeight +
+ constants.tickOffset +
+ sliderOpts.ticklen +
+ constants.labelOffset +
+ sliderOpts.labelHeight +
+ sliderOpts.pad.t +
+ sliderOpts.pad.b;
+
+ var xanchor = "left";
+ if (anchorUtils.isRightAnchor(sliderOpts)) {
+ sliderOpts.lx -= sliderOpts.outerLength;
+ xanchor = "right";
+ }
+ if (anchorUtils.isCenterAnchor(sliderOpts)) {
+ sliderOpts.lx -= sliderOpts.outerLength / 2;
+ xanchor = "center";
+ }
+
+ var yanchor = "top";
+ if (anchorUtils.isBottomAnchor(sliderOpts)) {
+ sliderOpts.ly -= sliderOpts.height;
+ yanchor = "bottom";
+ }
+ if (anchorUtils.isMiddleAnchor(sliderOpts)) {
+ sliderOpts.ly -= sliderOpts.height / 2;
+ yanchor = "middle";
+ }
+
+ sliderOpts.outerLength = Math.ceil(sliderOpts.outerLength);
+ sliderOpts.height = Math.ceil(sliderOpts.height);
+ sliderOpts.lx = Math.round(sliderOpts.lx);
+ sliderOpts.ly = Math.round(sliderOpts.ly);
+
+ Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index, {
+ x: sliderOpts.x,
+ y: sliderOpts.y,
+ l: sliderOpts.outerLength * (({ right: 1, center: 0.5 })[xanchor] || 0),
+ r: sliderOpts.outerLength * (({ left: 1, center: 0.5 })[xanchor] || 0),
+ b: sliderOpts.height * (({ top: 1, middle: 0.5 })[yanchor] || 0),
+ t: sliderOpts.height * (({ bottom: 1, middle: 0.5 })[yanchor] || 0)
+ });
}
function drawSlider(gd, sliderGroup, sliderOpts) {
- // This is related to the other long notes in this file regarding what happens
- // when slider steps disappear. This particular fix handles what happens when
- // the *current* slider step is removed. The drawing functions will error out
- // when they fail to find it, so the fix for now is that it will just draw the
- // slider in the first position but will not execute the command.
- if(sliderOpts.active >= sliderOpts.steps.length) {
- sliderOpts.active = 0;
- }
-
- // These are carefully ordered for proper z-ordering:
- sliderGroup
- .call(drawCurrentValue, sliderOpts)
- .call(drawRail, sliderOpts)
- .call(drawLabelGroup, sliderOpts)
- .call(drawTicks, sliderOpts)
- .call(drawTouchRect, gd, sliderOpts)
- .call(drawGrip, gd, sliderOpts);
-
- // Position the rectangle:
- Drawing.setTranslate(sliderGroup, sliderOpts.lx + sliderOpts.pad.l, sliderOpts.ly + sliderOpts.pad.t);
-
- sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), false);
- sliderGroup.call(drawCurrentValue, sliderOpts);
-
+ // This is related to the other long notes in this file regarding what happens
+ // when slider steps disappear. This particular fix handles what happens when
+ // the *current* slider step is removed. The drawing functions will error out
+ // when they fail to find it, so the fix for now is that it will just draw the
+ // slider in the first position but will not execute the command.
+ if (sliderOpts.active >= sliderOpts.steps.length) {
+ sliderOpts.active = 0;
+ }
+
+ // These are carefully ordered for proper z-ordering:
+ sliderGroup
+ .call(drawCurrentValue, sliderOpts)
+ .call(drawRail, sliderOpts)
+ .call(drawLabelGroup, sliderOpts)
+ .call(drawTicks, sliderOpts)
+ .call(drawTouchRect, gd, sliderOpts)
+ .call(drawGrip, gd, sliderOpts);
+
+ // Position the rectangle:
+ Drawing.setTranslate(
+ sliderGroup,
+ sliderOpts.lx + sliderOpts.pad.l,
+ sliderOpts.ly + sliderOpts.pad.t
+ );
+
+ sliderGroup.call(
+ setGripPosition,
+ sliderOpts,
+ sliderOpts.active / (sliderOpts.steps.length - 1),
+ false
+ );
+ sliderGroup.call(drawCurrentValue, sliderOpts);
}
function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) {
- if(!sliderOpts.currentvalue.visible) return;
-
- var x0, textAnchor;
- var text = sliderGroup.selectAll('text')
- .data([0]);
-
- switch(sliderOpts.currentvalue.xanchor) {
- case 'right':
- // This is anchored left and adjusted by the width of the longest label
- // so that the prefix doesn't move. The goal of this is to emphasize
- // what's actually changing and make the update less distracting.
- x0 = sliderOpts.inputAreaLength - constants.currentValueInset - sliderOpts.currentValueMaxWidth;
- textAnchor = 'left';
- break;
- case 'center':
- x0 = sliderOpts.inputAreaLength * 0.5;
- textAnchor = 'middle';
- break;
- default:
- x0 = constants.currentValueInset;
- textAnchor = 'left';
- }
-
- text.enter().append('text')
- .classed(constants.labelClass, true)
- .classed('user-select-none', true)
- .attr('text-anchor', textAnchor);
-
- var str = sliderOpts.currentvalue.prefix ? sliderOpts.currentvalue.prefix : '';
-
- if(typeof valueOverride === 'string') {
- str += valueOverride;
- } else {
- var curVal = sliderOpts.steps[sliderOpts.active].label;
- str += curVal;
- }
-
- if(sliderOpts.currentvalue.suffix) {
- str += sliderOpts.currentvalue.suffix;
- }
-
- text.call(Drawing.font, sliderOpts.currentvalue.font)
- .text(str)
- .call(svgTextUtils.convertToTspans);
-
- Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight);
-
- return text;
+ if (!sliderOpts.currentvalue.visible) return;
+
+ var x0, textAnchor;
+ var text = sliderGroup.selectAll("text").data([0]);
+
+ switch (sliderOpts.currentvalue.xanchor) {
+ case "right":
+ // This is anchored left and adjusted by the width of the longest label
+ // so that the prefix doesn't move. The goal of this is to emphasize
+ // what's actually changing and make the update less distracting.
+ x0 = sliderOpts.inputAreaLength -
+ constants.currentValueInset -
+ sliderOpts.currentValueMaxWidth;
+ textAnchor = "left";
+ break;
+ case "center":
+ x0 = sliderOpts.inputAreaLength * 0.5;
+ textAnchor = "middle";
+ break;
+ default:
+ x0 = constants.currentValueInset;
+ textAnchor = "left";
+ }
+
+ text
+ .enter()
+ .append("text")
+ .classed(constants.labelClass, true)
+ .classed("user-select-none", true)
+ .attr("text-anchor", textAnchor);
+
+ var str = sliderOpts.currentvalue.prefix
+ ? sliderOpts.currentvalue.prefix
+ : "";
+
+ if (typeof valueOverride === "string") {
+ str += valueOverride;
+ } else {
+ var curVal = sliderOpts.steps[sliderOpts.active].label;
+ str += curVal;
+ }
+
+ if (sliderOpts.currentvalue.suffix) {
+ str += sliderOpts.currentvalue.suffix;
+ }
+
+ text
+ .call(Drawing.font, sliderOpts.currentvalue.font)
+ .text(str)
+ .call(svgTextUtils.convertToTspans);
+
+ Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight);
+
+ return text;
}
function drawGrip(sliderGroup, gd, sliderOpts) {
- var grip = sliderGroup.selectAll('rect.' + constants.gripRectClass)
- .data([0]);
-
- grip.enter().append('rect')
- .classed(constants.gripRectClass, true)
- .call(attachGripEvents, gd, sliderGroup, sliderOpts)
- .style('pointer-events', 'all');
-
- grip.attr({
- width: constants.gripWidth,
- height: constants.gripHeight,
- rx: constants.gripRadius,
- ry: constants.gripRadius,
+ var grip = sliderGroup.selectAll("rect." + constants.gripRectClass).data([0]);
+
+ grip
+ .enter()
+ .append("rect")
+ .classed(constants.gripRectClass, true)
+ .call(attachGripEvents, gd, sliderGroup, sliderOpts)
+ .style("pointer-events", "all");
+
+ grip
+ .attr({
+ width: constants.gripWidth,
+ height: constants.gripHeight,
+ rx: constants.gripRadius,
+ ry: constants.gripRadius
})
- .call(Color.stroke, sliderOpts.bordercolor)
- .call(Color.fill, sliderOpts.bgcolor)
- .style('stroke-width', sliderOpts.borderwidth + 'px');
+ .call(Color.stroke, sliderOpts.bordercolor)
+ .call(Color.fill, sliderOpts.bgcolor)
+ .style("stroke-width", sliderOpts.borderwidth + "px");
}
function drawLabel(item, data, sliderOpts) {
- var text = item.selectAll('text')
- .data([0]);
+ var text = item.selectAll("text").data([0]);
- text.enter().append('text')
- .classed(constants.labelClass, true)
- .classed('user-select-none', true)
- .attr('text-anchor', 'middle');
+ text
+ .enter()
+ .append("text")
+ .classed(constants.labelClass, true)
+ .classed("user-select-none", true)
+ .attr("text-anchor", "middle");
- text.call(Drawing.font, sliderOpts.font)
- .text(data.step.label)
- .call(svgTextUtils.convertToTspans);
+ text
+ .call(Drawing.font, sliderOpts.font)
+ .text(data.step.label)
+ .call(svgTextUtils.convertToTspans);
- return text;
+ return text;
}
function drawLabelGroup(sliderGroup, sliderOpts) {
- var labels = sliderGroup.selectAll('g.' + constants.labelsClass)
- .data([0]);
+ var labels = sliderGroup.selectAll("g." + constants.labelsClass).data([0]);
- labels.enter().append('g')
- .classed(constants.labelsClass, true);
+ labels.enter().append("g").classed(constants.labelsClass, true);
- var labelItems = labels.selectAll('g.' + constants.labelGroupClass)
- .data(sliderOpts.labelSteps);
+ var labelItems = labels
+ .selectAll("g." + constants.labelGroupClass)
+ .data(sliderOpts.labelSteps);
- labelItems.enter().append('g')
- .classed(constants.labelGroupClass, true);
+ labelItems.enter().append("g").classed(constants.labelGroupClass, true);
- labelItems.exit().remove();
+ labelItems.exit().remove();
- labelItems.each(function(d) {
- var item = d3.select(this);
+ labelItems.each(function(d) {
+ var item = d3.select(this);
- item.call(drawLabel, d, sliderOpts);
-
- Drawing.setTranslate(item,
- normalizedValueToPosition(sliderOpts, d.fraction),
- constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight + constants.labelOffset + sliderOpts.currentValueTotalHeight
- );
- });
+ item.call(drawLabel, d, sliderOpts);
+ Drawing.setTranslate(
+ item,
+ normalizedValueToPosition(sliderOpts, d.fraction),
+ constants.tickOffset +
+ sliderOpts.ticklen +
+ sliderOpts.labelHeight +
+ constants.labelOffset +
+ sliderOpts.currentValueTotalHeight
+ );
+ });
}
-function handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, doTransition) {
- var quantizedPosition = Math.round(normalizedPosition * (sliderOpts.steps.length - 1));
-
- if(quantizedPosition !== sliderOpts.active) {
- setActive(gd, sliderGroup, sliderOpts, quantizedPosition, true, doTransition);
- }
+function handleInput(
+ gd,
+ sliderGroup,
+ sliderOpts,
+ normalizedPosition,
+ doTransition
+) {
+ var quantizedPosition = Math.round(
+ normalizedPosition * (sliderOpts.steps.length - 1)
+ );
+
+ if (quantizedPosition !== sliderOpts.active) {
+ setActive(
+ gd,
+ sliderGroup,
+ sliderOpts,
+ quantizedPosition,
+ true,
+ doTransition
+ );
+ }
}
-function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition) {
- var previousActive = sliderOpts.active;
- sliderOpts._input.active = sliderOpts.active = index;
-
- var step = sliderOpts.steps[sliderOpts.active];
-
- sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), doTransition);
- sliderGroup.call(drawCurrentValue, sliderOpts);
-
- gd.emit('plotly_sliderchange', {
- slider: sliderOpts,
- step: sliderOpts.steps[sliderOpts.active],
- interaction: doCallback,
- previousActive: previousActive
- });
-
- if(step && step.method && doCallback) {
- if(sliderGroup._nextMethod) {
- // If we've already queued up an update, just overwrite it with the most recent:
- sliderGroup._nextMethod.step = step;
- sliderGroup._nextMethod.doCallback = doCallback;
- sliderGroup._nextMethod.doTransition = doTransition;
- } else {
- sliderGroup._nextMethod = {step: step, doCallback: doCallback, doTransition: doTransition};
- sliderGroup._nextMethodRaf = window.requestAnimationFrame(function() {
- var _step = sliderGroup._nextMethod.step;
- if(!_step.method) return;
-
- Plots.executeAPICommand(gd, _step.method, _step.args);
-
- sliderGroup._nextMethod = null;
- sliderGroup._nextMethodRaf = null;
- });
- }
+function setActive(
+ gd,
+ sliderGroup,
+ sliderOpts,
+ index,
+ doCallback,
+ doTransition
+) {
+ var previousActive = sliderOpts.active;
+ sliderOpts._input.active = sliderOpts.active = index;
+
+ var step = sliderOpts.steps[sliderOpts.active];
+
+ sliderGroup.call(
+ setGripPosition,
+ sliderOpts,
+ sliderOpts.active / (sliderOpts.steps.length - 1),
+ doTransition
+ );
+ sliderGroup.call(drawCurrentValue, sliderOpts);
+
+ gd.emit("plotly_sliderchange", {
+ slider: sliderOpts,
+ step: sliderOpts.steps[sliderOpts.active],
+ interaction: doCallback,
+ previousActive: previousActive
+ });
+
+ if (step && step.method && doCallback) {
+ if (sliderGroup._nextMethod) {
+ // If we've already queued up an update, just overwrite it with the most recent:
+ sliderGroup._nextMethod.step = step;
+ sliderGroup._nextMethod.doCallback = doCallback;
+ sliderGroup._nextMethod.doTransition = doTransition;
+ } else {
+ sliderGroup._nextMethod = {
+ step: step,
+ doCallback: doCallback,
+ doTransition: doTransition
+ };
+ sliderGroup._nextMethodRaf = window.requestAnimationFrame(function() {
+ var _step = sliderGroup._nextMethod.step;
+ if (!_step.method) return;
+
+ Plots.executeAPICommand(gd, _step.method, _step.args);
+
+ sliderGroup._nextMethod = null;
+ sliderGroup._nextMethodRaf = null;
+ });
}
+ }
}
function attachGripEvents(item, gd, sliderGroup) {
- var node = sliderGroup.node();
- var $gd = d3.select(gd);
-
- // NB: This is *not* the same as sliderOpts itself! These callbacks
- // are in a closure so this array won't actually be correct if the
- // steps have changed since this was initialized. The sliderGroup,
- // however, has not changed since that *is* the slider, so it must
- // be present to receive mouse events.
- function getSliderOpts() {
- return sliderGroup.data()[0];
- }
+ var node = sliderGroup.node();
+ var $gd = d3.select(gd);
+
+ // NB: This is *not* the same as sliderOpts itself! These callbacks
+ // are in a closure so this array won't actually be correct if the
+ // steps have changed since this was initialized. The sliderGroup,
+ // however, has not changed since that *is* the slider, so it must
+ // be present to receive mouse events.
+ function getSliderOpts() {
+ return sliderGroup.data()[0];
+ }
+
+ item.on("mousedown", function() {
+ var sliderOpts = getSliderOpts();
+ gd.emit("plotly_sliderstart", { slider: sliderOpts });
+
+ var grip = sliderGroup.select("." + constants.gripRectClass);
+
+ d3.event.stopPropagation();
+ d3.event.preventDefault();
+ grip.call(Color.fill, sliderOpts.activebgcolor);
+
+ var normalizedPosition = positionToNormalizedValue(
+ sliderOpts,
+ d3.mouse(node)[0]
+ );
+ handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true);
+ sliderOpts._dragging = true;
+
+ $gd.on("mousemove", function() {
+ var sliderOpts = getSliderOpts();
+ var normalizedPosition = positionToNormalizedValue(
+ sliderOpts,
+ d3.mouse(node)[0]
+ );
+ handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false);
+ });
+
+ $gd.on("mouseup", function() {
+ var sliderOpts = getSliderOpts();
+ sliderOpts._dragging = false;
+ grip.call(Color.fill, sliderOpts.bgcolor);
+ $gd.on("mouseup", null);
+ $gd.on("mousemove", null);
- item.on('mousedown', function() {
- var sliderOpts = getSliderOpts();
- gd.emit('plotly_sliderstart', {slider: sliderOpts});
-
- var grip = sliderGroup.select('.' + constants.gripRectClass);
-
- d3.event.stopPropagation();
- d3.event.preventDefault();
- grip.call(Color.fill, sliderOpts.activebgcolor);
-
- var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]);
- handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true);
- sliderOpts._dragging = true;
-
- $gd.on('mousemove', function() {
- var sliderOpts = getSliderOpts();
- var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]);
- handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false);
- });
-
- $gd.on('mouseup', function() {
- var sliderOpts = getSliderOpts();
- sliderOpts._dragging = false;
- grip.call(Color.fill, sliderOpts.bgcolor);
- $gd.on('mouseup', null);
- $gd.on('mousemove', null);
-
- gd.emit('plotly_sliderend', {
- slider: sliderOpts,
- step: sliderOpts.steps[sliderOpts.active]
- });
- });
+ gd.emit("plotly_sliderend", {
+ slider: sliderOpts,
+ step: sliderOpts.steps[sliderOpts.active]
+ });
});
+ });
}
function drawTicks(sliderGroup, sliderOpts) {
- var tick = sliderGroup.selectAll('rect.' + constants.tickRectClass)
- .data(sliderOpts.steps);
+ var tick = sliderGroup
+ .selectAll("rect." + constants.tickRectClass)
+ .data(sliderOpts.steps);
- tick.enter().append('rect')
- .classed(constants.tickRectClass, true);
+ tick.enter().append("rect").classed(constants.tickRectClass, true);
- tick.exit().remove();
+ tick.exit().remove();
- tick.attr({
- width: sliderOpts.tickwidth + 'px',
- 'shape-rendering': 'crispEdges'
- });
-
- tick.each(function(d, i) {
- var isMajor = i % sliderOpts.labelStride === 0;
- var item = d3.select(this);
+ tick.attr({
+ width: sliderOpts.tickwidth + "px",
+ "shape-rendering": "crispEdges"
+ });
- item
- .attr({height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen})
- .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor);
+ tick.each(function(d, i) {
+ var isMajor = i % sliderOpts.labelStride === 0;
+ var item = d3.select(this);
- Drawing.setTranslate(item,
- normalizedValueToPosition(sliderOpts, i / (sliderOpts.steps.length - 1)) - 0.5 * sliderOpts.tickwidth,
- (isMajor ? constants.tickOffset : constants.minorTickOffset) + sliderOpts.currentValueTotalHeight
- );
- });
+ item
+ .attr({ height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen })
+ .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor);
+ Drawing.setTranslate(
+ item,
+ normalizedValueToPosition(sliderOpts, i / (sliderOpts.steps.length - 1)) -
+ 0.5 * sliderOpts.tickwidth,
+ (isMajor ? constants.tickOffset : constants.minorTickOffset) +
+ sliderOpts.currentValueTotalHeight
+ );
+ });
}
function computeLabelSteps(sliderOpts) {
- sliderOpts.labelSteps = [];
- var i0 = 0;
- var nsteps = sliderOpts.steps.length;
-
- for(var i = i0; i < nsteps; i += sliderOpts.labelStride) {
- sliderOpts.labelSteps.push({
- fraction: i / (nsteps - 1),
- step: sliderOpts.steps[i]
- });
- }
+ sliderOpts.labelSteps = [];
+ var i0 = 0;
+ var nsteps = sliderOpts.steps.length;
+
+ for (var i = i0; i < nsteps; i += sliderOpts.labelStride) {
+ sliderOpts.labelSteps.push({
+ fraction: i / (nsteps - 1),
+ step: sliderOpts.steps[i]
+ });
+ }
}
function setGripPosition(sliderGroup, sliderOpts, position, doTransition) {
- var grip = sliderGroup.select('rect.' + constants.gripRectClass);
-
- var x = normalizedValueToPosition(sliderOpts, position);
-
- // If this is true, then *this component* is already invoking its own command
- // and has triggered its own animation.
- if(sliderOpts._invokingCommand) return;
-
- var el = grip;
- if(doTransition && sliderOpts.transition.duration > 0) {
- el = el.transition()
- .duration(sliderOpts.transition.duration)
- .ease(sliderOpts.transition.easing);
- }
-
- // Drawing.setTranslate doesn't work here becasue of the transition duck-typing.
- // It's also not necessary because there are no other transitions to preserve.
- el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts.currentValueTotalHeight) + ')');
+ var grip = sliderGroup.select("rect." + constants.gripRectClass);
+
+ var x = normalizedValueToPosition(sliderOpts, position);
+
+ // If this is true, then *this component* is already invoking its own command
+ // and has triggered its own animation.
+ if (sliderOpts._invokingCommand) return;
+
+ var el = grip;
+ if (doTransition && sliderOpts.transition.duration > 0) {
+ el = el
+ .transition()
+ .duration(sliderOpts.transition.duration)
+ .ease(sliderOpts.transition.easing);
+ }
+
+ // Drawing.setTranslate doesn't work here becasue of the transition duck-typing.
+ // It's also not necessary because there are no other transitions to preserve.
+ el.attr(
+ "transform",
+ "translate(" +
+ (x - constants.gripWidth * 0.5) +
+ "," +
+ sliderOpts.currentValueTotalHeight +
+ ")"
+ );
}
// Convert a number from [0-1] to a pixel position relative to the slider group container:
function normalizedValueToPosition(sliderOpts, normalizedPosition) {
- return sliderOpts.inputAreaStart + constants.stepInset +
- (sliderOpts.inputAreaLength - 2 * constants.stepInset) * Math.min(1, Math.max(0, normalizedPosition));
+ return sliderOpts.inputAreaStart +
+ constants.stepInset +
+ (sliderOpts.inputAreaLength - 2 * constants.stepInset) *
+ Math.min(1, Math.max(0, normalizedPosition));
}
// Convert a position relative to the slider group to a nubmer in [0, 1]
function positionToNormalizedValue(sliderOpts, position) {
- return Math.min(1, Math.max(0, (position - constants.stepInset - sliderOpts.inputAreaStart) / (sliderOpts.inputAreaLength - 2 * constants.stepInset - 2 * sliderOpts.inputAreaStart)));
+ return Math.min(
+ 1,
+ Math.max(
+ 0,
+ (position - constants.stepInset - sliderOpts.inputAreaStart) /
+ (sliderOpts.inputAreaLength -
+ 2 * constants.stepInset -
+ 2 * sliderOpts.inputAreaStart)
+ )
+ );
}
function drawTouchRect(sliderGroup, gd, sliderOpts) {
- var rect = sliderGroup.selectAll('rect.' + constants.railTouchRectClass)
- .data([0]);
-
- rect.enter().append('rect')
- .classed(constants.railTouchRectClass, true)
- .call(attachGripEvents, gd, sliderGroup, sliderOpts)
- .style('pointer-events', 'all');
-
- rect.attr({
- width: sliderOpts.inputAreaLength,
- height: Math.max(sliderOpts.inputAreaWidth, constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight)
+ var rect = sliderGroup
+ .selectAll("rect." + constants.railTouchRectClass)
+ .data([0]);
+
+ rect
+ .enter()
+ .append("rect")
+ .classed(constants.railTouchRectClass, true)
+ .call(attachGripEvents, gd, sliderGroup, sliderOpts)
+ .style("pointer-events", "all");
+
+ rect
+ .attr({
+ width: sliderOpts.inputAreaLength,
+ height: Math.max(
+ sliderOpts.inputAreaWidth,
+ constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight
+ )
})
- .call(Color.fill, sliderOpts.bgcolor)
- .attr('opacity', 0);
+ .call(Color.fill, sliderOpts.bgcolor)
+ .attr("opacity", 0);
- Drawing.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight);
+ Drawing.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight);
}
function drawRail(sliderGroup, sliderOpts) {
- var rect = sliderGroup.selectAll('rect.' + constants.railRectClass)
- .data([0]);
+ var rect = sliderGroup.selectAll("rect." + constants.railRectClass).data([0]);
- rect.enter().append('rect')
- .classed(constants.railRectClass, true);
+ rect.enter().append("rect").classed(constants.railRectClass, true);
- var computedLength = sliderOpts.inputAreaLength - constants.railInset * 2;
+ var computedLength = sliderOpts.inputAreaLength - constants.railInset * 2;
- rect.attr({
- width: computedLength,
- height: constants.railWidth,
- rx: constants.railRadius,
- ry: constants.railRadius,
- 'shape-rendering': 'crispEdges'
+ rect
+ .attr({
+ width: computedLength,
+ height: constants.railWidth,
+ rx: constants.railRadius,
+ ry: constants.railRadius,
+ "shape-rendering": "crispEdges"
})
- .call(Color.stroke, sliderOpts.bordercolor)
- .call(Color.fill, sliderOpts.bgcolor)
- .style('stroke-width', sliderOpts.borderwidth + 'px');
-
- Drawing.setTranslate(rect,
- constants.railInset,
- (sliderOpts.inputAreaWidth - constants.railWidth) * 0.5 + sliderOpts.currentValueTotalHeight
- );
+ .call(Color.stroke, sliderOpts.bordercolor)
+ .call(Color.fill, sliderOpts.bgcolor)
+ .style("stroke-width", sliderOpts.borderwidth + "px");
+
+ Drawing.setTranslate(
+ rect,
+ constants.railInset,
+ (sliderOpts.inputAreaWidth - constants.railWidth) * 0.5 +
+ sliderOpts.currentValueTotalHeight
+ );
}
function clearPushMargins(gd) {
- var pushMargins = gd._fullLayout._pushmargin || {},
- keys = Object.keys(pushMargins);
+ var pushMargins = gd._fullLayout._pushmargin || {},
+ keys = Object.keys(pushMargins);
- for(var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
- if(k.indexOf(constants.autoMarginIdRoot) !== -1) {
- Plots.autoMargin(gd, k);
- }
+ if (k.indexOf(constants.autoMarginIdRoot) !== -1) {
+ Plots.autoMargin(gd, k);
}
+ }
}
diff --git a/src/components/sliders/index.js b/src/components/sliders/index.js
index 366b9aaa1ad..b44b6e9dab9 100644
--- a/src/components/sliders/index.js
+++ b/src/components/sliders/index.js
@@ -5,17 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var constants = require('./constants');
+"use strict";
+var constants = require("./constants");
module.exports = {
- moduleType: 'component',
- name: constants.name,
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- draw: require('./draw')
+ moduleType: "component",
+ name: constants.name,
+ layoutAttributes: require("./attributes"),
+ supplyLayoutDefaults: require("./defaults"),
+ draw: require("./draw")
};
diff --git a/src/components/titles/index.js b/src/components/titles/index.js
index 6fa0e64c6af..33171b7f68b 100644
--- a/src/components/titles/index.js
+++ b/src/components/titles/index.js
@@ -5,21 +5,17 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Plotly = require('../../plotly');
-var Plots = require('../../plots/plots');
-var Lib = require('../../lib');
-var Drawing = require('../drawing');
-var Color = require('../color');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var interactConstants = require('../../constants/interactions');
-
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
+
+var Plotly = require("../../plotly");
+var Plots = require("../../plots/plots");
+var Lib = require("../../lib");
+var Drawing = require("../drawing");
+var Color = require("../color");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var interactConstants = require("../../constants/interactions");
var Titles = module.exports = {};
@@ -52,180 +48,186 @@ var Titles = module.exports = {};
* title, include here. Otherwise it will go in fullLayout._infolayer
*/
Titles.draw = function(gd, titleClass, options) {
- var cont = options.propContainer,
- prop = options.propName,
- traceIndex = options.traceIndex,
- name = options.dfltName,
- avoid = options.avoid || {},
- attributes = options.attributes,
- transform = options.transform,
- group = options.containerGroup,
-
- fullLayout = gd._fullLayout,
- font = cont.titlefont.family,
- fontSize = cont.titlefont.size,
- fontColor = cont.titlefont.color,
-
- opacity = 1,
- isplaceholder = false,
- txt = cont.title.trim();
- if(txt === '') opacity = 0;
- if(txt.match(/Click to enter .+ title/)) {
- opacity = 0.2;
- isplaceholder = true;
- }
-
- if(!group) {
- group = fullLayout._infolayer.selectAll('.g-' + titleClass)
- .data([0]);
- group.enter().append('g')
- .classed('g-' + titleClass, 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', titleClass);
-
- function titleLayout(titleEl) {
- Lib.syncOrAsync([drawTitle, scootTitle], titleEl);
+ var cont = options.propContainer,
+ prop = options.propName,
+ traceIndex = options.traceIndex,
+ name = options.dfltName,
+ avoid = options.avoid || {},
+ attributes = options.attributes,
+ transform = options.transform,
+ group = options.containerGroup,
+ fullLayout = gd._fullLayout,
+ font = cont.titlefont.family,
+ fontSize = cont.titlefont.size,
+ fontColor = cont.titlefont.color,
+ opacity = 1,
+ isplaceholder = false,
+ txt = cont.title.trim();
+ if (txt === "") opacity = 0;
+ if (txt.match(/Click to enter .+ title/)) {
+ opacity = 0.2;
+ isplaceholder = true;
+ }
+
+ if (!group) {
+ group = fullLayout._infolayer.selectAll(".g-" + titleClass).data([0]);
+ group.enter().append("g").classed("g-" + titleClass, true);
+ }
+
+ var el = group.selectAll("text").data([0]);
+ el.enter().append("text");
+ el.text(txt).attr("class", titleClass);
+
+ function titleLayout(titleEl) {
+ Lib.syncOrAsync([drawTitle, scootTitle], titleEl);
+ }
+
+ function drawTitle(titleEl) {
+ titleEl.attr(
+ "transform",
+ transform
+ ? "rotate(" +
+ [transform.rotate, attributes.x, attributes.y] +
+ ") translate(0, " +
+ transform.offset +
+ ")"
+ : null
+ );
+
+ titleEl
+ .style({
+ "font-family": font,
+ "font-size": d3.round(fontSize, 2) + "px",
+ fill: Color.rgb(fontColor),
+ opacity: opacity * Color.opacity(fontColor),
+ "font-weight": Plots.fontWeight
+ })
+ .attr(attributes)
+ .call(svgTextUtils.convertToTspans)
+ .attr(attributes);
+
+ titleEl.selectAll("tspan.line").attr(attributes);
+ 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 = Drawing.bBox(titleGroup.node()),
+ paperbb = {
+ left: 0,
+ top: 0,
+ right: fullLayout.width,
+ bottom: fullLayout.height
+ },
+ maxshift = avoid.maxShift ||
+ (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
+ var offsetLeft = avoid.offsetLeft || 0,
+ offsetTop = avoid.offsetTop || 0;
+ titlebb.left -= offsetLeft;
+ titlebb.right -= offsetLeft;
+ titlebb.top -= offsetTop;
+ titlebb.bottom -= offsetTop;
+
+ // iterate over a set of elements (avoid.selection)
+ // to avoid collisions with
+ avoid.selection.each(function() {
+ var avoidbb = Drawing.bBox(this);
+
+ if (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 + ")");
+ }
}
-
- function drawTitle(titleEl) {
- titleEl.attr('transform', transform ?
- 'rotate(' + [transform.rotate, attributes.x, attributes.y] +
- ') translate(0, ' + transform.offset + ')' :
- null);
-
- titleEl.style({
- 'font-family': font,
- 'font-size': d3.round(fontSize, 2) + 'px',
- fill: Color.rgb(fontColor),
- opacity: opacity * Color.opacity(fontColor),
- 'font-weight': Plots.fontWeight
- })
- .attr(attributes)
- .call(svgTextUtils.convertToTspans)
- .attr(attributes);
-
- titleEl.selectAll('tspan.line')
- .attr(attributes);
- 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 = Drawing.bBox(titleGroup.node()),
- paperbb = {
- left: 0,
- top: 0,
- right: fullLayout.width,
- bottom: fullLayout.height
- },
- maxshift = avoid.maxShift || (
- (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
- var offsetLeft = avoid.offsetLeft || 0,
- offsetTop = avoid.offsetTop || 0;
- titlebb.left -= offsetLeft;
- titlebb.right -= offsetLeft;
- titlebb.top -= offsetTop;
- titlebb.bottom -= offsetTop;
-
- // iterate over a set of elements (avoid.selection)
- // to avoid collisions with
- avoid.selection.each(function() {
- var avoidbb = Drawing.bBox(this);
-
- if(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 + " title";
+
+ function setPlaceholder() {
+ opacity = 0;
+ isplaceholder = true;
+ txt = placeholderText;
+ el
+ .attr({ "data-unformatted": txt })
+ .text(txt)
+ .on("mouseover.opacity", function() {
+ d3
+ .select(this)
+ .transition()
+ .duration(interactConstants.SHOW_PLACEHOLDER)
+ .style("opacity", 1);
+ })
+ .on("mouseout.opacity", function() {
+ d3
+ .select(this)
+ .transition()
+ .duration(interactConstants.HIDE_PLACEHOLDER)
+ .style("opacity", 0);
+ });
+ }
+
+ if (gd._context.editable) {
+ if (!txt) setPlaceholder();
+ else el.on(".opacity", null);
+
+ el
+ .call(svgTextUtils.makeEditable)
+ .on("edit", function(text) {
+ if (traceIndex !== undefined) {
+ Plotly.restyle(gd, prop, text, traceIndex);
+ } else {
+ Plotly.relayout(gd, prop, text);
}
- }
-
- el.attr({'data-unformatted': txt})
- .call(titleLayout);
-
- var placeholderText = 'Click to enter ' + name + ' title';
-
- function setPlaceholder() {
- opacity = 0;
- isplaceholder = true;
- txt = placeholderText;
- el.attr({'data-unformatted': txt})
- .text(txt)
- .on('mouseover.opacity', function() {
- d3.select(this).transition()
- .duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1);
- })
- .on('mouseout.opacity', function() {
- d3.select(this).transition()
- .duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0);
- });
- }
-
- if(gd._context.editable) {
- if(!txt) setPlaceholder();
- else el.on('.opacity', null);
-
- el.call(svgTextUtils.makeEditable)
- .on('edit', function(text) {
- if(traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex);
- 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(attributes)
- .selectAll('tspan.line')
- .attr(attributes);
- });
- }
- else if(!txt || txt.match(/Click to enter .+ title/)) {
- el.remove();
- }
- el.classed('js-placeholder', isplaceholder);
+ })
+ .on("cancel", function() {
+ this.text(this.attr("data-unformatted")).call(titleLayout);
+ })
+ .on("input", function(d) {
+ this
+ .text(d || " ")
+ .attr(attributes)
+ .selectAll("tspan.line")
+ .attr(attributes);
+ });
+ } else if (!txt || txt.match(/Click to enter .+ title/)) {
+ el.remove();
+ }
+ el.classed("js-placeholder", isplaceholder);
};
diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js
index f04465f9ec3..14b36849a96 100644
--- a/src/components/updatemenus/attributes.js
+++ b/src/components/updatemenus/attributes.js
@@ -5,166 +5,147 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var fontAttrs = require('../../plots/font_attributes');
-var colorAttrs = require('../color/attributes');
-var extendFlat = require('../../lib/extend').extendFlat;
-var padAttrs = require('../../plots/pad_attributes');
+"use strict";
+var fontAttrs = require("../../plots/font_attributes");
+var colorAttrs = require("../color/attributes");
+var extendFlat = require("../../lib/extend").extendFlat;
+var padAttrs = require("../../plots/pad_attributes");
var buttonsAttrs = {
- _isLinkedToArray: 'button',
-
- method: {
- valType: 'enumerated',
- values: ['restyle', 'relayout', 'animate', 'update'],
- dflt: 'restyle',
- role: 'info',
- description: [
- 'Sets the Plotly method to be called on click.'
- ].join(' ')
- },
- args: {
- valType: 'info_array',
- role: 'info',
- freeLength: true,
- items: [
- { valType: 'any' },
- { valType: 'any' },
- { valType: 'any' }
- ],
- description: [
- 'Sets the arguments values to be passed to the Plotly',
- 'method set in `method` on click.'
- ].join(' ')
- },
- label: {
- valType: 'string',
- role: 'info',
- dflt: '',
- description: 'Sets the text label to appear on the button.'
- }
+ _isLinkedToArray: "button",
+ method: {
+ valType: "enumerated",
+ values: ["restyle", "relayout", "animate", "update"],
+ dflt: "restyle",
+ role: "info",
+ description: ["Sets the Plotly method to be called on click."].join(" ")
+ },
+ args: {
+ valType: "info_array",
+ role: "info",
+ freeLength: true,
+ items: [{ valType: "any" }, { valType: "any" }, { valType: "any" }],
+ description: [
+ "Sets the arguments values to be passed to the Plotly",
+ "method set in `method` on click."
+ ].join(" ")
+ },
+ label: {
+ valType: "string",
+ role: "info",
+ dflt: "",
+ description: "Sets the text label to appear on the button."
+ }
};
module.exports = {
- _isLinkedToArray: 'updatemenu',
-
- visible: {
- valType: 'boolean',
- role: 'info',
- description: [
- 'Determines whether or not the update menu is visible.'
- ].join(' ')
- },
-
- type: {
- valType: 'enumerated',
- values: ['dropdown', 'buttons'],
- dflt: 'dropdown',
- role: 'info',
- description: [
- 'Determines whether the buttons are accessible via a dropdown menu',
- 'or whether the buttons are stacked horizontally or vertically'
- ].join(' ')
- },
-
- direction: {
- valType: 'enumerated',
- values: ['left', 'right', 'up', 'down'],
- dflt: 'down',
- role: 'info',
- description: [
- 'Determines the direction in which the buttons are laid out, whether',
- 'in a dropdown menu or a row/column of buttons. For `left` and `up`,',
- 'the buttons will still appear in left-to-right or top-to-bottom order',
- 'respectively.'
- ].join(' ')
- },
-
- active: {
- valType: 'integer',
- role: 'info',
- min: -1,
- dflt: 0,
- description: [
- 'Determines which button (by index starting from 0) is',
- 'considered active.'
- ].join(' ')
- },
-
- showactive: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: 'Highlights active dropdown item or active button if true.'
- },
-
- buttons: buttonsAttrs,
-
- x: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: -0.05,
- role: 'style',
- description: 'Sets the x position (in normalized coordinates) of the update menu.'
- },
- xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'right',
- role: 'info',
- description: [
- 'Sets the update menu\'s horizontal position anchor.',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the range selector.'
- ].join(' ')
- },
- y: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: 1,
- role: 'style',
- description: 'Sets the y position (in normalized coordinates) of the update menu.'
- },
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'top',
- role: 'info',
- description: [
- 'Sets the update menu\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the range selector.'
- ].join(' ')
- },
-
- pad: extendFlat({}, padAttrs, {
- description: 'Sets the padding around the buttons or dropdown menu.'
- }),
-
- font: extendFlat({}, fontAttrs, {
- description: 'Sets the font of the update menu button text.'
- }),
-
- bgcolor: {
- valType: 'color',
- role: 'style',
- description: 'Sets the background color of the update menu buttons.'
- },
- bordercolor: {
- valType: 'color',
- dflt: colorAttrs.borderLine,
- role: 'style',
- description: 'Sets the color of the border enclosing the update menu.'
- },
- borderwidth: {
- valType: 'number',
- min: 0,
- dflt: 1,
- role: 'style',
- description: 'Sets the width (in px) of the border enclosing the update menu.'
- }
+ _isLinkedToArray: "updatemenu",
+ visible: {
+ valType: "boolean",
+ role: "info",
+ description: ["Determines whether or not the update menu is visible."].join(
+ " "
+ )
+ },
+ type: {
+ valType: "enumerated",
+ values: ["dropdown", "buttons"],
+ dflt: "dropdown",
+ role: "info",
+ description: [
+ "Determines whether the buttons are accessible via a dropdown menu",
+ "or whether the buttons are stacked horizontally or vertically"
+ ].join(" ")
+ },
+ direction: {
+ valType: "enumerated",
+ values: ["left", "right", "up", "down"],
+ dflt: "down",
+ role: "info",
+ description: [
+ "Determines the direction in which the buttons are laid out, whether",
+ "in a dropdown menu or a row/column of buttons. For `left` and `up`,",
+ "the buttons will still appear in left-to-right or top-to-bottom order",
+ "respectively."
+ ].join(" ")
+ },
+ active: {
+ valType: "integer",
+ role: "info",
+ min: -1,
+ dflt: 0,
+ description: [
+ "Determines which button (by index starting from 0) is",
+ "considered active."
+ ].join(" ")
+ },
+ showactive: {
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: "Highlights active dropdown item or active button if true."
+ },
+ buttons: buttonsAttrs,
+ x: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ dflt: -0.05,
+ role: "style",
+ description: "Sets the x position (in normalized coordinates) of the update menu."
+ },
+ xanchor: {
+ valType: "enumerated",
+ values: ["auto", "left", "center", "right"],
+ dflt: "right",
+ role: "info",
+ description: [
+ "Sets the update menu's horizontal position anchor.",
+ "This anchor binds the `x` position to the *left*, *center*",
+ "or *right* of the range selector."
+ ].join(" ")
+ },
+ y: {
+ valType: "number",
+ min: -2,
+ max: 3,
+ dflt: 1,
+ role: "style",
+ description: "Sets the y position (in normalized coordinates) of the update menu."
+ },
+ yanchor: {
+ valType: "enumerated",
+ values: ["auto", "top", "middle", "bottom"],
+ dflt: "top",
+ role: "info",
+ description: [
+ "Sets the update menu's vertical position anchor",
+ "This anchor binds the `y` position to the *top*, *middle*",
+ "or *bottom* of the range selector."
+ ].join(" ")
+ },
+ pad: extendFlat({}, padAttrs, {
+ description: "Sets the padding around the buttons or dropdown menu."
+ }),
+ font: extendFlat({}, fontAttrs, {
+ description: "Sets the font of the update menu button text."
+ }),
+ bgcolor: {
+ valType: "color",
+ role: "style",
+ description: "Sets the background color of the update menu buttons."
+ },
+ bordercolor: {
+ valType: "color",
+ dflt: colorAttrs.borderLine,
+ role: "style",
+ description: "Sets the color of the border enclosing the update menu."
+ },
+ borderwidth: {
+ valType: "number",
+ min: 0,
+ dflt: 1,
+ role: "style",
+ description: "Sets the width (in px) of the border enclosing the update menu."
+ }
};
diff --git a/src/components/updatemenus/constants.js b/src/components/updatemenus/constants.js
index b1c7a2e3ef0..5f0f0f70c74 100644
--- a/src/components/updatemenus/constants.js
+++ b/src/components/updatemenus/constants.js
@@ -5,70 +5,50 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
module.exports = {
-
- // layout attribute name
- name: 'updatemenus',
-
- // class names
- containerClassName: 'updatemenu-container',
- headerGroupClassName: 'updatemenu-header-group',
- headerClassName: 'updatemenu-header',
- headerArrowClassName: 'updatemenu-header-arrow',
- dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group',
- dropdownButtonClassName: 'updatemenu-dropdown-button',
- buttonClassName: 'updatemenu-button',
- itemRectClassName: 'updatemenu-item-rect',
- itemTextClassName: 'updatemenu-item-text',
-
- // DOM attribute name in button group keeping track
- // of active update menu
- menuIndexAttrName: 'updatemenu-active-index',
-
- // id root pass to Plots.autoMargin
- autoMarginIdRoot: 'updatemenu-',
-
- // options when 'active: -1'
- blankHeaderOpts: { label: ' ' },
-
- // min item width / height
- minWidth: 30,
- minHeight: 30,
-
- // padding around item text
- textPadX: 24,
- arrowPadX: 16,
-
- // font size to height scale
- fontSizeToHeight: 1.3,
-
- // item rect radii
- rx: 2,
- ry: 2,
-
- // item text x offset off left edge
- textOffsetX: 12,
-
- // item text y offset (w.r.t. middle)
- textOffsetY: 3,
-
- // arrow offset off right edge
- arrowOffsetX: 4,
-
- // gap between header and buttons
- gapButtonHeader: 5,
-
- // gap between between buttons
- gapButton: 2,
-
- // color given to active buttons
- activeColor: '#F4FAFF',
-
- // color given to hovered buttons
- hoverColor: '#F4FAFF'
+ // layout attribute name
+ name: "updatemenus",
+ // class names
+ containerClassName: "updatemenu-container",
+ headerGroupClassName: "updatemenu-header-group",
+ headerClassName: "updatemenu-header",
+ headerArrowClassName: "updatemenu-header-arrow",
+ dropdownButtonGroupClassName: "updatemenu-dropdown-button-group",
+ dropdownButtonClassName: "updatemenu-dropdown-button",
+ buttonClassName: "updatemenu-button",
+ itemRectClassName: "updatemenu-item-rect",
+ itemTextClassName: "updatemenu-item-text",
+ // DOM attribute name in button group keeping track
+ // of active update menu
+ menuIndexAttrName: "updatemenu-active-index",
+ // id root pass to Plots.autoMargin
+ autoMarginIdRoot: "updatemenu-",
+ // options when 'active: -1'
+ blankHeaderOpts: { label: " " },
+ // min item width / height
+ minWidth: 30,
+ minHeight: 30,
+ // padding around item text
+ textPadX: 24,
+ arrowPadX: 16,
+ // font size to height scale
+ fontSizeToHeight: 1.3,
+ // item rect radii
+ rx: 2,
+ ry: 2,
+ // item text x offset off left edge
+ textOffsetX: 12,
+ // item text y offset (w.r.t. middle)
+ textOffsetY: 3,
+ // arrow offset off right edge
+ arrowOffsetX: 4,
+ // gap between header and buttons
+ gapButtonHeader: 5,
+ // gap between between buttons
+ gapButton: 2,
+ // color given to active buttons
+ activeColor: "#F4FAFF",
+ // color given to hovered buttons
+ hoverColor: "#F4FAFF"
};
diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js
index 2d4eeae7faa..f98b8e0b50f 100644
--- a/src/components/updatemenus/defaults.js
+++ b/src/components/updatemenus/defaults.js
@@ -5,88 +5,82 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Lib = require("../../lib");
+var handleArrayContainerDefaults = require(
+ "../../plots/array_container_defaults"
+);
-'use strict';
-
-var Lib = require('../../lib');
-var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
-
-var attributes = require('./attributes');
-var constants = require('./constants');
+var attributes = require("./attributes");
+var constants = require("./constants");
var name = constants.name;
var buttonAttrs = attributes.buttons;
-
module.exports = function updateMenusDefaults(layoutIn, layoutOut) {
- var opts = {
- name: name,
- handleItemDefaults: menuDefaults
- };
+ var opts = { name: name, handleItemDefaults: menuDefaults };
- handleArrayContainerDefaults(layoutIn, layoutOut, opts);
+ handleArrayContainerDefaults(layoutIn, layoutOut, opts);
};
function menuDefaults(menuIn, menuOut, layoutOut) {
+ function coerce(attr, dflt) {
+ return Lib.coerce(menuIn, menuOut, attributes, attr, dflt);
+ }
- function coerce(attr, dflt) {
- return Lib.coerce(menuIn, menuOut, attributes, attr, dflt);
- }
-
- var buttons = buttonsDefaults(menuIn, menuOut);
+ var buttons = buttonsDefaults(menuIn, menuOut);
- var visible = coerce('visible', buttons.length > 0);
- if(!visible) return;
+ var visible = coerce("visible", buttons.length > 0);
+ if (!visible) return;
- coerce('active');
- coerce('direction');
- coerce('type');
- coerce('showactive');
+ coerce("active");
+ coerce("direction");
+ coerce("type");
+ coerce("showactive");
- coerce('x');
- coerce('y');
- Lib.noneOrAll(menuIn, menuOut, ['x', 'y']);
+ coerce("x");
+ coerce("y");
+ Lib.noneOrAll(menuIn, menuOut, ["x", "y"]);
- coerce('xanchor');
- coerce('yanchor');
+ coerce("xanchor");
+ coerce("yanchor");
- coerce('pad.t');
- coerce('pad.r');
- coerce('pad.b');
- coerce('pad.l');
+ coerce("pad.t");
+ coerce("pad.r");
+ coerce("pad.b");
+ coerce("pad.l");
- Lib.coerceFont(coerce, 'font', layoutOut.font);
+ Lib.coerceFont(coerce, "font", layoutOut.font);
- coerce('bgcolor', layoutOut.paper_bgcolor);
- coerce('bordercolor');
- coerce('borderwidth');
+ coerce("bgcolor", layoutOut.paper_bgcolor);
+ coerce("bordercolor");
+ coerce("borderwidth");
}
function buttonsDefaults(menuIn, menuOut) {
- var buttonsIn = menuIn.buttons || [],
- buttonsOut = menuOut.buttons = [];
+ var buttonsIn = menuIn.buttons || [], buttonsOut = menuOut.buttons = [];
- var buttonIn, buttonOut;
+ var buttonIn, buttonOut;
- function coerce(attr, dflt) {
- return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
- }
+ function coerce(attr, dflt) {
+ return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
+ }
- for(var i = 0; i < buttonsIn.length; i++) {
- buttonIn = buttonsIn[i];
- buttonOut = {};
+ for (var i = 0; i < buttonsIn.length; i++) {
+ buttonIn = buttonsIn[i];
+ buttonOut = {};
- if(!Lib.isPlainObject(buttonIn) || !Array.isArray(buttonIn.args)) {
- continue;
- }
+ if (!Lib.isPlainObject(buttonIn) || !Array.isArray(buttonIn.args)) {
+ continue;
+ }
- coerce('method');
- coerce('args');
- coerce('label');
+ coerce("method");
+ coerce("args");
+ coerce("label");
- buttonOut._index = i;
- buttonsOut.push(buttonOut);
- }
+ buttonOut._index = i;
+ buttonsOut.push(buttonOut);
+ }
- return buttonsOut;
+ return buttonsOut;
}
diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js
index 3c9c30968b2..ddd70dcf395 100644
--- a/src/components/updatemenus/draw.js
+++ b/src/components/updatemenus/draw.js
@@ -5,26 +5,22 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var Plots = require("../../plots/plots");
+var Color = require("../color");
+var Drawing = require("../drawing");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var anchorUtils = require("../legend/anchor_utils");
-'use strict';
-
-var d3 = require('d3');
-
-var Plots = require('../../plots/plots');
-var Color = require('../color');
-var Drawing = require('../drawing');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var anchorUtils = require('../legend/anchor_utils');
-
-var constants = require('./constants');
-var ScrollBox = require('./scrollbox');
+var constants = require("./constants");
+var ScrollBox = require("./scrollbox");
module.exports = function draw(gd) {
- var fullLayout = gd._fullLayout,
- menuData = makeMenuData(fullLayout);
+ var fullLayout = gd._fullLayout, menuData = makeMenuData(fullLayout);
- /* Update menu data is bound to the header-group.
+ /* Update menu data is bound to the header-group.
* The items in the header group are always present.
*
* Upon clicking on a header its corresponding button
@@ -50,104 +46,113 @@ module.exports = function draw(gd) {
*
* ...
*/
-
- // draw update menu container
- var menus = fullLayout._infolayer
- .selectAll('g.' + constants.containerClassName)
- .data(menuData.length > 0 ? [0] : []);
-
- menus.enter().append('g')
- .classed(constants.containerClassName, true)
- .style('cursor', 'pointer');
-
- menus.exit().remove();
-
- // remove push margin object(s)
- if(menus.exit().size()) clearPushMargins(gd);
-
- // return early if no update menus are visible
- if(menuData.length === 0) return;
-
- // join header group
- var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName)
- .data(menuData, keyFunction);
-
- headerGroups.enter().append('g')
- .classed(constants.headerGroupClassName, true);
-
- // draw dropdown button container
- var gButton = menus.selectAll('g.' + constants.dropdownButtonGroupClassName)
- .data([0]);
-
- gButton.enter().append('g')
- .classed(constants.dropdownButtonGroupClassName, true)
- .style('pointer-events', 'all');
-
- // find dimensions before plotting anything (this mutates menuOpts)
- for(var i = 0; i < menuData.length; i++) {
- var menuOpts = menuData[i];
- findDimensions(gd, menuOpts);
- }
-
- // setup scrollbox
- var scrollBoxId = 'updatemenus' + fullLayout._uid,
- scrollBox = new ScrollBox(gd, gButton, scrollBoxId);
-
- // remove exiting header, remove dropped buttons and reset margins
- if(headerGroups.enter().size()) {
- gButton
- .call(removeAllButtons)
- .attr(constants.menuIndexAttrName, '-1');
- }
-
- headerGroups.exit().each(function(menuOpts) {
- d3.select(this).remove();
-
- gButton
- .call(removeAllButtons)
- .attr(constants.menuIndexAttrName, '-1');
-
- Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
+ // draw update menu container
+ var menus = fullLayout._infolayer
+ .selectAll("g." + constants.containerClassName)
+ .data(menuData.length > 0 ? [0] : []);
+
+ menus
+ .enter()
+ .append("g")
+ .classed(constants.containerClassName, true)
+ .style("cursor", "pointer");
+
+ menus.exit().remove();
+
+ // remove push margin object(s)
+ if (menus.exit().size()) clearPushMargins(gd);
+
+ // return early if no update menus are visible
+ if (menuData.length === 0) return;
+
+ // join header group
+ var headerGroups = menus
+ .selectAll("g." + constants.headerGroupClassName)
+ .data(menuData, keyFunction);
+
+ headerGroups
+ .enter()
+ .append("g")
+ .classed(constants.headerGroupClassName, true);
+
+ // draw dropdown button container
+ var gButton = menus
+ .selectAll("g." + constants.dropdownButtonGroupClassName)
+ .data([0]);
+
+ gButton
+ .enter()
+ .append("g")
+ .classed(constants.dropdownButtonGroupClassName, true)
+ .style("pointer-events", "all");
+
+ // find dimensions before plotting anything (this mutates menuOpts)
+ for (var i = 0; i < menuData.length; i++) {
+ var menuOpts = menuData[i];
+ findDimensions(gd, menuOpts);
+ }
+
+ // setup scrollbox
+ var scrollBoxId = "updatemenus" + fullLayout._uid,
+ scrollBox = new ScrollBox(gd, gButton, scrollBoxId);
+
+ // remove exiting header, remove dropped buttons and reset margins
+ if (headerGroups.enter().size()) {
+ gButton.call(removeAllButtons).attr(constants.menuIndexAttrName, "-1");
+ }
+
+ headerGroups.exit().each(function(menuOpts) {
+ d3.select(this).remove();
+
+ gButton.call(removeAllButtons).attr(constants.menuIndexAttrName, "-1");
+
+ Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
+ });
+
+ // draw headers!
+ headerGroups.each(function(menuOpts) {
+ var gHeader = d3.select(this);
+
+ var _gButton = menuOpts.type === "dropdown" ? gButton : null;
+ Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) {
+ setActive(
+ gd,
+ menuOpts,
+ menuOpts.buttons[data.index],
+ gHeader,
+ _gButton,
+ scrollBox,
+ data.index,
+ true
+ );
});
- // draw headers!
- headerGroups.each(function(menuOpts) {
- var gHeader = d3.select(this);
-
- var _gButton = menuOpts.type === 'dropdown' ? gButton : null;
- Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) {
- setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true);
- });
+ if (menuOpts.type === "dropdown") {
+ drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
- if(menuOpts.type === 'dropdown') {
- drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
-
- // if this menu is active, update the dropdown container
- if(isActive(gButton, menuOpts)) {
- drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
- }
- } else {
- drawButtons(gd, gHeader, null, null, menuOpts);
- }
-
- });
+ // if this menu is active, update the dropdown container
+ if (isActive(gButton, menuOpts)) {
+ drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
+ }
+ } else {
+ drawButtons(gd, gHeader, null, null, menuOpts);
+ }
+ });
};
function makeMenuData(fullLayout) {
- var contOpts = fullLayout[constants.name],
- menuData = [];
+ var contOpts = fullLayout[constants.name], menuData = [];
- // Filter visible dropdowns and attach '_index' to each
- // fullLayout options object to be used for 'object constancy'
- // in the data join key function.
+ // Filter visible dropdowns and attach '_index' to each
+ // fullLayout options object to be used for 'object constancy'
+ // in the data join key function.
+ for (var i = 0; i < contOpts.length; i++) {
+ var item = contOpts[i];
- for(var i = 0; i < contOpts.length; i++) {
- var item = contOpts[i];
+ if (item.visible) menuData.push(item);
+ }
- if(item.visible) menuData.push(item);
- }
-
- return menuData;
+ return menuData;
}
// Note that '_index' is set at the default step,
@@ -155,524 +160,547 @@ function makeMenuData(fullLayout) {
// Because a menu can b set invisible,
// this is a more 'consistent' field than the index in the menuData.
function keyFunction(menuOpts) {
- return menuOpts._index;
+ return menuOpts._index;
}
function isFolded(gButton) {
- return +gButton.attr(constants.menuIndexAttrName) === -1;
+ return +gButton.attr(constants.menuIndexAttrName) === -1;
}
function isActive(gButton, menuOpts) {
- return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index;
+ return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index;
}
-function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) {
- // update 'active' attribute in menuOpts
- menuOpts._input.active = menuOpts.active = buttonIndex;
-
- if(menuOpts.type === 'buttons') {
- drawButtons(gd, gHeader, null, null, menuOpts);
- }
- else if(menuOpts.type === 'dropdown') {
- // fold up buttons and redraw header
- gButton.attr(constants.menuIndexAttrName, '-1');
-
- drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
-
- if(!isSilentUpdate) {
- drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
- }
+function setActive(
+ gd,
+ menuOpts,
+ buttonOpts,
+ gHeader,
+ gButton,
+ scrollBox,
+ buttonIndex,
+ isSilentUpdate
+) {
+ // update 'active' attribute in menuOpts
+ menuOpts._input.active = menuOpts.active = buttonIndex;
+
+ if (menuOpts.type === "buttons") {
+ drawButtons(gd, gHeader, null, null, menuOpts);
+ } else if (menuOpts.type === "dropdown") {
+ // fold up buttons and redraw header
+ gButton.attr(constants.menuIndexAttrName, "-1");
+
+ drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
+
+ if (!isSilentUpdate) {
+ drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
}
+ }
}
function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) {
- var header = gHeader.selectAll('g.' + constants.headerClassName)
- .data([0]);
-
- header.enter().append('g')
- .classed(constants.headerClassName, true)
- .style('pointer-events', 'all');
-
- var active = menuOpts.active,
- headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts,
- posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 },
- positionOverrides = {
- width: menuOpts.headerWidth,
- height: menuOpts.headerHeight
- };
-
- header
- .call(drawItem, menuOpts, headerOpts)
- .call(setItemPosition, menuOpts, posOpts, positionOverrides);
-
- // draw drop arrow at the right edge
- var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName)
- .data([0]);
-
- arrow.enter().append('text')
- .classed(constants.headerArrowClassName, true)
- .classed('user-select-none', true)
- .attr('text-anchor', 'end')
- .call(Drawing.font, menuOpts.font)
- .text('▼');
-
- arrow.attr({
- x: menuOpts.headerWidth - constants.arrowOffsetX + menuOpts.pad.l,
- y: menuOpts.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t
- });
-
- header.on('click', function() {
- gButton.call(removeAllButtons);
-
-
- // if this menu is active, fold the dropdown container
- // otherwise, make this menu active
- gButton.attr(
- constants.menuIndexAttrName,
- isActive(gButton, menuOpts) ?
- -1 :
- String(menuOpts._index)
- );
-
- drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
- });
-
- header.on('mouseover', function() {
- header.call(styleOnMouseOver);
- });
-
- header.on('mouseout', function() {
- header.call(styleOnMouseOut, menuOpts);
- });
+ var header = gHeader.selectAll("g." + constants.headerClassName).data([0]);
+
+ header
+ .enter()
+ .append("g")
+ .classed(constants.headerClassName, true)
+ .style("pointer-events", "all");
+
+ var active = menuOpts.active,
+ headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts,
+ posOpts = {
+ y: menuOpts.pad.t,
+ yPad: 0,
+ x: menuOpts.pad.l,
+ xPad: 0,
+ index: 0
+ },
+ positionOverrides = {
+ width: menuOpts.headerWidth,
+ height: menuOpts.headerHeight
+ };
- // translate header group
- Drawing.setTranslate(gHeader, menuOpts.lx, menuOpts.ly);
+ header
+ .call(drawItem, menuOpts, headerOpts)
+ .call(setItemPosition, menuOpts, posOpts, positionOverrides);
+
+ // draw drop arrow at the right edge
+ var arrow = gHeader
+ .selectAll("text." + constants.headerArrowClassName)
+ .data([0]);
+
+ arrow
+ .enter()
+ .append("text")
+ .classed(constants.headerArrowClassName, true)
+ .classed("user-select-none", true)
+ .attr("text-anchor", "end")
+ .call(Drawing.font, menuOpts.font)
+ .text("\u25BC");
+
+ arrow.attr({
+ x: menuOpts.headerWidth - constants.arrowOffsetX + menuOpts.pad.l,
+ y: menuOpts.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t
+ });
+
+ header.on("click", function() {
+ gButton.call(removeAllButtons);
+
+ // if this menu is active, fold the dropdown container
+ // otherwise, make this menu active
+ gButton.attr(
+ constants.menuIndexAttrName,
+ isActive(gButton, menuOpts) ? -1 : String(menuOpts._index)
+ );
+
+ drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
+ });
+
+ header.on("mouseover", function() {
+ header.call(styleOnMouseOver);
+ });
+
+ header.on("mouseout", function() {
+ header.call(styleOnMouseOut, menuOpts);
+ });
+
+ // translate header group
+ Drawing.setTranslate(gHeader, menuOpts.lx, menuOpts.ly);
}
function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) {
- // If this is a set of buttons, set pointer events = all since we play
- // some minor games with which container is which in order to simplify
- // the drawing of *either* buttons or menus
- if(!gButton) {
- gButton = gHeader;
- gButton.attr('pointer-events', 'all');
- }
+ // If this is a set of buttons, set pointer events = all since we play
+ // some minor games with which container is which in order to simplify
+ // the drawing of *either* buttons or menus
+ if (!gButton) {
+ gButton = gHeader;
+ gButton.attr("pointer-events", "all");
+ }
- var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ?
- menuOpts.buttons :
- [];
+ var buttonData = !isFolded(gButton) || menuOpts.type === "buttons"
+ ? menuOpts.buttons
+ : [];
- var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName;
+ var klass = menuOpts.type === "dropdown"
+ ? constants.dropdownButtonClassName
+ : constants.buttonClassName;
- var buttons = gButton.selectAll('g.' + klass)
- .data(buttonData);
+ var buttons = gButton.selectAll("g." + klass).data(buttonData);
- var enter = buttons.enter().append('g')
- .classed(klass, true);
+ var enter = buttons.enter().append("g").classed(klass, true);
- var exit = buttons.exit();
+ var exit = buttons.exit();
- if(menuOpts.type === 'dropdown') {
- enter.attr('opacity', '0')
- .transition()
- .attr('opacity', '1');
+ if (menuOpts.type === "dropdown") {
+ enter.attr("opacity", "0").transition().attr("opacity", "1");
- exit.transition()
- .attr('opacity', '0')
- .remove();
- } else {
- exit.remove();
- }
+ exit.transition().attr("opacity", "0").remove();
+ } else {
+ exit.remove();
+ }
- var x0 = 0;
- var y0 = 0;
+ var x0 = 0;
+ var y0 = 0;
- var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
+ var isVertical = ["up", "down"].indexOf(menuOpts.direction) !== -1;
- if(menuOpts.type === 'dropdown') {
- if(isVertical) {
- y0 = menuOpts.headerHeight + constants.gapButtonHeader;
- } else {
- x0 = menuOpts.headerWidth + constants.gapButtonHeader;
- }
- }
-
- if(menuOpts.type === 'dropdown' && menuOpts.direction === 'up') {
- y0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openHeight;
- }
-
- if(menuOpts.type === 'dropdown' && menuOpts.direction === 'left') {
- x0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openWidth;
+ if (menuOpts.type === "dropdown") {
+ if (isVertical) {
+ y0 = menuOpts.headerHeight + constants.gapButtonHeader;
+ } else {
+ x0 = menuOpts.headerWidth + constants.gapButtonHeader;
}
-
- var posOpts = {
- x: menuOpts.lx + x0 + menuOpts.pad.l,
- y: menuOpts.ly + y0 + menuOpts.pad.t,
- yPad: constants.gapButton,
- xPad: constants.gapButton,
- index: 0,
- };
-
- var scrollBoxPosition = {
- l: posOpts.x + menuOpts.borderwidth,
- t: posOpts.y + menuOpts.borderwidth
- };
-
- buttons.each(function(buttonOpts, buttonIndex) {
- var button = d3.select(this);
-
- button
- .call(drawItem, menuOpts, buttonOpts)
- .call(setItemPosition, menuOpts, posOpts);
-
- button.on('click', function() {
- // skip `dragend` events
- if(d3.event.defaultPrevented) return;
-
- setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex);
-
- Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args);
-
- gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active});
- });
-
- button.on('mouseover', function() {
- button.call(styleOnMouseOver);
- });
-
- button.on('mouseout', function() {
- button.call(styleOnMouseOut, menuOpts);
- buttons.call(styleButtons, menuOpts);
- });
+ }
+
+ if (menuOpts.type === "dropdown" && menuOpts.direction === "up") {
+ y0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openHeight;
+ }
+
+ if (menuOpts.type === "dropdown" && menuOpts.direction === "left") {
+ x0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openWidth;
+ }
+
+ var posOpts = {
+ x: menuOpts.lx + x0 + menuOpts.pad.l,
+ y: menuOpts.ly + y0 + menuOpts.pad.t,
+ yPad: constants.gapButton,
+ xPad: constants.gapButton,
+ index: 0
+ };
+
+ var scrollBoxPosition = {
+ l: posOpts.x + menuOpts.borderwidth,
+ t: posOpts.y + menuOpts.borderwidth
+ };
+
+ buttons.each(function(buttonOpts, buttonIndex) {
+ var button = d3.select(this);
+
+ button
+ .call(drawItem, menuOpts, buttonOpts)
+ .call(setItemPosition, menuOpts, posOpts);
+
+ button.on("click", function() {
+ // skip `dragend` events
+ if (d3.event.defaultPrevented) return;
+
+ setActive(
+ gd,
+ menuOpts,
+ buttonOpts,
+ gHeader,
+ gButton,
+ scrollBox,
+ buttonIndex
+ );
+
+ Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args);
+
+ gd.emit("plotly_buttonclicked", {
+ menu: menuOpts,
+ button: buttonOpts,
+ active: menuOpts.active
+ });
});
- buttons.call(styleButtons, menuOpts);
-
- if(isVertical) {
- scrollBoxPosition.w = Math.max(menuOpts.openWidth, menuOpts.headerWidth);
- scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t;
- }
- else {
- scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l;
- scrollBoxPosition.h = Math.max(menuOpts.openHeight, menuOpts.headerHeight);
- }
-
- scrollBoxPosition.direction = menuOpts.direction;
+ button.on("mouseover", function() {
+ button.call(styleOnMouseOver);
+ });
- if(scrollBox) {
- if(buttons.size()) {
- drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, scrollBoxPosition);
- }
- else {
- hideScrollBox(scrollBox);
- }
+ button.on("mouseout", function() {
+ button.call(styleOnMouseOut, menuOpts);
+ buttons.call(styleButtons, menuOpts);
+ });
+ });
+
+ buttons.call(styleButtons, menuOpts);
+
+ if (isVertical) {
+ scrollBoxPosition.w = Math.max(menuOpts.openWidth, menuOpts.headerWidth);
+ scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t;
+ } else {
+ scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l;
+ scrollBoxPosition.h = Math.max(menuOpts.openHeight, menuOpts.headerHeight);
+ }
+
+ scrollBoxPosition.direction = menuOpts.direction;
+
+ if (scrollBox) {
+ if (buttons.size()) {
+ drawScrollBox(
+ gd,
+ gHeader,
+ gButton,
+ scrollBox,
+ menuOpts,
+ scrollBoxPosition
+ );
+ } else {
+ hideScrollBox(scrollBox);
}
+ }
}
function drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, position) {
- // enable the scrollbox
- var direction = menuOpts.direction,
- isVertical = (direction === 'up' || direction === 'down');
-
- var active = menuOpts.active,
- translateX, translateY,
- i;
- if(isVertical) {
- translateY = 0;
- for(i = 0; i < active; i++) {
- translateY += menuOpts.heights[i] + constants.gapButton;
- }
+ // enable the scrollbox
+ var direction = menuOpts.direction,
+ isVertical = direction === "up" || direction === "down";
+
+ var active = menuOpts.active, translateX, translateY, i;
+ if (isVertical) {
+ translateY = 0;
+ for (i = 0; i < active; i++) {
+ translateY += menuOpts.heights[i] + constants.gapButton;
}
- else {
- translateX = 0;
- for(i = 0; i < active; i++) {
- translateX += menuOpts.widths[i] + constants.gapButton;
- }
+ } else {
+ translateX = 0;
+ for (i = 0; i < active; i++) {
+ translateX += menuOpts.widths[i] + constants.gapButton;
}
+ }
- scrollBox.enable(position, translateX, translateY);
+ scrollBox.enable(position, translateX, translateY);
- if(scrollBox.hbar) {
- scrollBox.hbar
- .attr('opacity', '0')
- .transition()
- .attr('opacity', '1');
- }
+ if (scrollBox.hbar) {
+ scrollBox.hbar.attr("opacity", "0").transition().attr("opacity", "1");
+ }
- if(scrollBox.vbar) {
- scrollBox.vbar
- .attr('opacity', '0')
- .transition()
- .attr('opacity', '1');
- }
+ if (scrollBox.vbar) {
+ scrollBox.vbar.attr("opacity", "0").transition().attr("opacity", "1");
+ }
}
function hideScrollBox(scrollBox) {
- var hasHBar = !!scrollBox.hbar,
- hasVBar = !!scrollBox.vbar;
-
- if(hasHBar) {
- scrollBox.hbar
- .transition()
- .attr('opacity', '0')
- .each('end', function() {
- hasHBar = false;
- if(!hasVBar) scrollBox.disable();
- });
- }
+ var hasHBar = !!scrollBox.hbar, hasVBar = !!scrollBox.vbar;
- if(hasVBar) {
- scrollBox.vbar
- .transition()
- .attr('opacity', '0')
- .each('end', function() {
- hasVBar = false;
- if(!hasHBar) scrollBox.disable();
- });
- }
+ if (hasHBar) {
+ scrollBox.hbar.transition().attr("opacity", "0").each("end", function() {
+ hasHBar = false;
+ if (!hasVBar) scrollBox.disable();
+ });
+ }
+
+ if (hasVBar) {
+ scrollBox.vbar.transition().attr("opacity", "0").each("end", function() {
+ hasVBar = false;
+ if (!hasHBar) scrollBox.disable();
+ });
+ }
}
function drawItem(item, menuOpts, itemOpts) {
- item.call(drawItemRect, menuOpts)
- .call(drawItemText, menuOpts, itemOpts);
+ item.call(drawItemRect, menuOpts).call(drawItemText, menuOpts, itemOpts);
}
function drawItemRect(item, menuOpts) {
- var rect = item.selectAll('rect')
- .data([0]);
-
- rect.enter().append('rect')
- .classed(constants.itemRectClassName, true)
- .attr({
- rx: constants.rx,
- ry: constants.ry,
- 'shape-rendering': 'crispEdges'
- });
-
- rect.call(Color.stroke, menuOpts.bordercolor)
- .call(Color.fill, menuOpts.bgcolor)
- .style('stroke-width', menuOpts.borderwidth + 'px');
+ var rect = item.selectAll("rect").data([0]);
+
+ rect.enter().append("rect").classed(constants.itemRectClassName, true).attr({
+ rx: constants.rx,
+ ry: constants.ry,
+ "shape-rendering": "crispEdges"
+ });
+
+ rect
+ .call(Color.stroke, menuOpts.bordercolor)
+ .call(Color.fill, menuOpts.bgcolor)
+ .style("stroke-width", menuOpts.borderwidth + "px");
}
function drawItemText(item, menuOpts, itemOpts) {
- var text = item.selectAll('text')
- .data([0]);
-
- text.enter().append('text')
- .classed(constants.itemTextClassName, true)
- .classed('user-select-none', true)
- .attr('text-anchor', 'start');
-
- text.call(Drawing.font, menuOpts.font)
- .text(itemOpts.label)
- .call(svgTextUtils.convertToTspans);
+ var text = item.selectAll("text").data([0]);
+
+ text
+ .enter()
+ .append("text")
+ .classed(constants.itemTextClassName, true)
+ .classed("user-select-none", true)
+ .attr("text-anchor", "start");
+
+ text
+ .call(Drawing.font, menuOpts.font)
+ .text(itemOpts.label)
+ .call(svgTextUtils.convertToTspans);
}
function styleButtons(buttons, menuOpts) {
- var active = menuOpts.active;
+ var active = menuOpts.active;
- buttons.each(function(buttonOpts, i) {
- var button = d3.select(this);
+ buttons.each(function(buttonOpts, i) {
+ var button = d3.select(this);
- if(i === active && menuOpts.showactive) {
- button.select('rect.' + constants.itemRectClassName)
- .call(Color.fill, constants.activeColor);
- }
- });
+ if (i === active && menuOpts.showactive) {
+ button
+ .select("rect." + constants.itemRectClassName)
+ .call(Color.fill, constants.activeColor);
+ }
+ });
}
function styleOnMouseOver(item) {
- item.select('rect.' + constants.itemRectClassName)
- .call(Color.fill, constants.hoverColor);
+ item
+ .select("rect." + constants.itemRectClassName)
+ .call(Color.fill, constants.hoverColor);
}
function styleOnMouseOut(item, menuOpts) {
- item.select('rect.' + constants.itemRectClassName)
- .call(Color.fill, menuOpts.bgcolor);
+ item
+ .select("rect." + constants.itemRectClassName)
+ .call(Color.fill, menuOpts.bgcolor);
}
// find item dimensions (this mutates menuOpts)
function findDimensions(gd, menuOpts) {
- menuOpts.width1 = 0;
- menuOpts.height1 = 0;
- menuOpts.heights = [];
- menuOpts.widths = [];
- menuOpts.totalWidth = 0;
- menuOpts.totalHeight = 0;
- menuOpts.openWidth = 0;
- menuOpts.openHeight = 0;
- menuOpts.lx = 0;
- menuOpts.ly = 0;
-
- var fakeButtons = gd._tester.selectAll('g.' + constants.dropdownButtonClassName)
- .data(menuOpts.buttons);
-
- fakeButtons.enter().append('g')
- .classed(constants.dropdownButtonClassName, true);
-
- var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
-
- // loop over fake buttons to find width / height
- fakeButtons.each(function(buttonOpts, i) {
- var button = d3.select(this);
-
- button.call(drawItem, menuOpts, buttonOpts);
-
- var text = button.select('.' + constants.itemTextClassName),
- tspans = text.selectAll('tspan');
-
- // width is given by max width of all buttons
- var tWidth = text.node() && Drawing.bBox(text.node()).width,
- wEff = Math.max(tWidth + constants.textPadX, constants.minWidth);
-
- // height is determined by item text
- var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
- tLines = tspans[0].length || 1,
- hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY;
-
- hEff = Math.ceil(hEff);
- wEff = Math.ceil(wEff);
-
- // Store per-item sizes since a row of horizontal buttons, for example,
- // don't all need to be the same width:
- menuOpts.widths[i] = wEff;
- menuOpts.heights[i] = hEff;
-
- // Height and width of individual element:
- menuOpts.height1 = Math.max(menuOpts.height1, hEff);
- menuOpts.width1 = Math.max(menuOpts.width1, wEff);
-
- if(isVertical) {
- menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff);
- menuOpts.openWidth = menuOpts.totalWidth;
- menuOpts.totalHeight += hEff + constants.gapButton;
- menuOpts.openHeight += hEff + constants.gapButton;
- } else {
- menuOpts.totalWidth += wEff + constants.gapButton;
- menuOpts.openWidth += wEff + constants.gapButton;
- menuOpts.totalHeight = Math.max(menuOpts.totalHeight, hEff);
- menuOpts.openHeight = menuOpts.totalHeight;
- }
- });
-
- if(isVertical) {
- menuOpts.totalHeight -= constants.gapButton;
+ menuOpts.width1 = 0;
+ menuOpts.height1 = 0;
+ menuOpts.heights = [];
+ menuOpts.widths = [];
+ menuOpts.totalWidth = 0;
+ menuOpts.totalHeight = 0;
+ menuOpts.openWidth = 0;
+ menuOpts.openHeight = 0;
+ menuOpts.lx = 0;
+ menuOpts.ly = 0;
+
+ var fakeButtons = gd._tester
+ .selectAll("g." + constants.dropdownButtonClassName)
+ .data(menuOpts.buttons);
+
+ fakeButtons
+ .enter()
+ .append("g")
+ .classed(constants.dropdownButtonClassName, true);
+
+ var isVertical = ["up", "down"].indexOf(menuOpts.direction) !== -1;
+
+ // loop over fake buttons to find width / height
+ fakeButtons.each(function(buttonOpts, i) {
+ var button = d3.select(this);
+
+ button.call(drawItem, menuOpts, buttonOpts);
+
+ var text = button.select("." + constants.itemTextClassName),
+ tspans = text.selectAll("tspan");
+
+ // width is given by max width of all buttons
+ var tWidth = text.node() && Drawing.bBox(text.node()).width,
+ wEff = Math.max(tWidth + constants.textPadX, constants.minWidth);
+
+ // height is determined by item text
+ var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
+ tLines = tspans[0].length || 1,
+ hEff = Math.max(tHeight * tLines, constants.minHeight) +
+ constants.textOffsetY;
+
+ hEff = Math.ceil(hEff);
+ wEff = Math.ceil(wEff);
+
+ // Store per-item sizes since a row of horizontal buttons, for example,
+ // don't all need to be the same width:
+ menuOpts.widths[i] = wEff;
+ menuOpts.heights[i] = hEff;
+
+ // Height and width of individual element:
+ menuOpts.height1 = Math.max(menuOpts.height1, hEff);
+ menuOpts.width1 = Math.max(menuOpts.width1, wEff);
+
+ if (isVertical) {
+ menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff);
+ menuOpts.openWidth = menuOpts.totalWidth;
+ menuOpts.totalHeight += hEff + constants.gapButton;
+ menuOpts.openHeight += hEff + constants.gapButton;
} else {
- menuOpts.totalWidth -= constants.gapButton;
- }
-
-
- menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX;
- menuOpts.headerHeight = menuOpts.height1;
-
- if(menuOpts.type === 'dropdown') {
- if(isVertical) {
- menuOpts.width1 += constants.arrowPadX;
- menuOpts.totalHeight = menuOpts.height1;
- } else {
- menuOpts.totalWidth = menuOpts.width1;
- }
- menuOpts.totalWidth += constants.arrowPadX;
+ menuOpts.totalWidth += wEff + constants.gapButton;
+ menuOpts.openWidth += wEff + constants.gapButton;
+ menuOpts.totalHeight = Math.max(menuOpts.totalHeight, hEff);
+ menuOpts.openHeight = menuOpts.totalHeight;
}
+ });
- fakeButtons.remove();
+ if (isVertical) {
+ menuOpts.totalHeight -= constants.gapButton;
+ } else {
+ menuOpts.totalWidth -= constants.gapButton;
+ }
- var paddedWidth = menuOpts.totalWidth + menuOpts.pad.l + menuOpts.pad.r;
- var paddedHeight = menuOpts.totalHeight + menuOpts.pad.t + menuOpts.pad.b;
+ menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX;
+ menuOpts.headerHeight = menuOpts.height1;
- var graphSize = gd._fullLayout._size;
- menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x;
- menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y);
-
- var xanchor = 'left';
- if(anchorUtils.isRightAnchor(menuOpts)) {
- menuOpts.lx -= paddedWidth;
- xanchor = 'right';
- }
- if(anchorUtils.isCenterAnchor(menuOpts)) {
- menuOpts.lx -= paddedWidth / 2;
- xanchor = 'center';
- }
-
- var yanchor = 'top';
- if(anchorUtils.isBottomAnchor(menuOpts)) {
- menuOpts.ly -= paddedHeight;
- yanchor = 'bottom';
- }
- if(anchorUtils.isMiddleAnchor(menuOpts)) {
- menuOpts.ly -= paddedHeight / 2;
- yanchor = 'middle';
+ if (menuOpts.type === "dropdown") {
+ if (isVertical) {
+ menuOpts.width1 += constants.arrowPadX;
+ menuOpts.totalHeight = menuOpts.height1;
+ } else {
+ menuOpts.totalWidth = menuOpts.width1;
}
-
- menuOpts.totalWidth = Math.ceil(menuOpts.totalWidth);
- menuOpts.totalHeight = Math.ceil(menuOpts.totalHeight);
- menuOpts.lx = Math.round(menuOpts.lx);
- menuOpts.ly = Math.round(menuOpts.ly);
-
- Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, {
- x: menuOpts.x,
- y: menuOpts.y,
- l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0),
- r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0),
- b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0),
- t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0)
- });
+ menuOpts.totalWidth += constants.arrowPadX;
+ }
+
+ fakeButtons.remove();
+
+ var paddedWidth = menuOpts.totalWidth + menuOpts.pad.l + menuOpts.pad.r;
+ var paddedHeight = menuOpts.totalHeight + menuOpts.pad.t + menuOpts.pad.b;
+
+ var graphSize = gd._fullLayout._size;
+ menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x;
+ menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y);
+
+ var xanchor = "left";
+ if (anchorUtils.isRightAnchor(menuOpts)) {
+ menuOpts.lx -= paddedWidth;
+ xanchor = "right";
+ }
+ if (anchorUtils.isCenterAnchor(menuOpts)) {
+ menuOpts.lx -= paddedWidth / 2;
+ xanchor = "center";
+ }
+
+ var yanchor = "top";
+ if (anchorUtils.isBottomAnchor(menuOpts)) {
+ menuOpts.ly -= paddedHeight;
+ yanchor = "bottom";
+ }
+ if (anchorUtils.isMiddleAnchor(menuOpts)) {
+ menuOpts.ly -= paddedHeight / 2;
+ yanchor = "middle";
+ }
+
+ menuOpts.totalWidth = Math.ceil(menuOpts.totalWidth);
+ menuOpts.totalHeight = Math.ceil(menuOpts.totalHeight);
+ menuOpts.lx = Math.round(menuOpts.lx);
+ menuOpts.ly = Math.round(menuOpts.ly);
+
+ Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, {
+ x: menuOpts.x,
+ y: menuOpts.y,
+ l: paddedWidth * (({ right: 1, center: 0.5 })[xanchor] || 0),
+ r: paddedWidth * (({ left: 1, center: 0.5 })[xanchor] || 0),
+ b: paddedHeight * (({ top: 1, middle: 0.5 })[yanchor] || 0),
+ t: paddedHeight * (({ bottom: 1, middle: 0.5 })[yanchor] || 0)
+ });
}
// set item positions (mutates posOpts)
function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
- overrideOpts = overrideOpts || {};
- var rect = item.select('.' + constants.itemRectClassName),
- text = item.select('.' + constants.itemTextClassName),
- tspans = text.selectAll('tspan'),
- borderWidth = menuOpts.borderwidth,
- index = posOpts.index;
-
- Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y);
-
- var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
-
- rect.attr({
- x: 0,
- y: 0,
- width: overrideOpts.width || (isVertical ? menuOpts.width1 : menuOpts.widths[index]),
- height: overrideOpts.height || (isVertical ? menuOpts.heights[index] : menuOpts.height1)
- });
-
- var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
- tLines = tspans[0].length || 1,
- spanOffset = ((tLines - 1) * tHeight / 4);
-
- var textAttrs = {
- x: constants.textOffsetX,
- y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY
- };
-
- text.attr(textAttrs);
- tspans.attr(textAttrs);
-
- if(isVertical) {
- posOpts.y += menuOpts.heights[index] + posOpts.yPad;
- } else {
- posOpts.x += menuOpts.widths[index] + posOpts.xPad;
- }
-
- posOpts.index++;
+ overrideOpts = overrideOpts || {};
+ var rect = item.select("." + constants.itemRectClassName),
+ text = item.select("." + constants.itemTextClassName),
+ tspans = text.selectAll("tspan"),
+ borderWidth = menuOpts.borderwidth,
+ index = posOpts.index;
+
+ Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y);
+
+ var isVertical = ["up", "down"].indexOf(menuOpts.direction) !== -1;
+
+ rect.attr({
+ x: 0,
+ y: 0,
+ width: (
+ overrideOpts.width ||
+ (isVertical ? menuOpts.width1 : menuOpts.widths[index])
+ ),
+ height: (
+ overrideOpts.height ||
+ (isVertical ? menuOpts.heights[index] : menuOpts.height1)
+ )
+ });
+
+ var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
+ tLines = tspans[0].length || 1,
+ spanOffset = (tLines - 1) * tHeight / 4;
+
+ var textAttrs = {
+ x: constants.textOffsetX,
+ y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY
+ };
+
+ text.attr(textAttrs);
+ tspans.attr(textAttrs);
+
+ if (isVertical) {
+ posOpts.y += menuOpts.heights[index] + posOpts.yPad;
+ } else {
+ posOpts.x += menuOpts.widths[index] + posOpts.xPad;
+ }
+
+ posOpts.index++;
}
function removeAllButtons(gButton) {
- gButton.selectAll('g.' + constants.dropdownButtonClassName).remove();
+ gButton.selectAll("g." + constants.dropdownButtonClassName).remove();
}
function clearPushMargins(gd) {
- var pushMargins = gd._fullLayout._pushmargin || {},
- keys = Object.keys(pushMargins);
+ var pushMargins = gd._fullLayout._pushmargin || {},
+ keys = Object.keys(pushMargins);
- for(var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
- if(k.indexOf(constants.autoMarginIdRoot) !== -1) {
- Plots.autoMargin(gd, k);
- }
+ if (k.indexOf(constants.autoMarginIdRoot) !== -1) {
+ Plots.autoMargin(gd, k);
}
+ }
}
diff --git a/src/components/updatemenus/index.js b/src/components/updatemenus/index.js
index 366b9aaa1ad..b44b6e9dab9 100644
--- a/src/components/updatemenus/index.js
+++ b/src/components/updatemenus/index.js
@@ -5,17 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var constants = require('./constants');
+"use strict";
+var constants = require("./constants");
module.exports = {
- moduleType: 'component',
- name: constants.name,
-
- layoutAttributes: require('./attributes'),
- supplyLayoutDefaults: require('./defaults'),
-
- draw: require('./draw')
+ moduleType: "component",
+ name: constants.name,
+ layoutAttributes: require("./attributes"),
+ supplyLayoutDefaults: require("./defaults"),
+ draw: require("./draw")
};
diff --git a/src/components/updatemenus/scrollbox.js b/src/components/updatemenus/scrollbox.js
index 6c3431536cf..a1ff5141a27 100644
--- a/src/components/updatemenus/scrollbox.js
+++ b/src/components/updatemenus/scrollbox.js
@@ -5,17 +5,15 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = ScrollBox;
-var d3 = require('d3');
+var d3 = require("d3");
-var Color = require('../color');
-var Drawing = require('../drawing');
+var Color = require("../color");
+var Drawing = require("../drawing");
-var Lib = require('../../lib');
+var Lib = require("../../lib");
/**
* Helper class to setup a scroll box
@@ -26,35 +24,33 @@ var Lib = require('../../lib');
* @param {string} id Id for the clip path to implement the scroll box
*/
function ScrollBox(gd, container, id) {
- this.gd = gd;
- this.container = container;
- this.id = id;
-
- // See ScrollBox.prototype.enable for further definition
- this.position = null; // scrollbox position
- this.translateX = null; // scrollbox horizontal translation
- this.translateY = null; // scrollbox vertical translation
- this.hbar = null; // horizontal scrollbar D3 selection
- this.vbar = null; // vertical scrollbar D3 selection
-
- // element to capture pointer events
- this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]);
-
- this.bg.exit()
- .on('.drag', null)
- .on('wheel', null)
- .remove();
-
- this.bg.enter().append('rect')
- .classed('scrollbox-bg', true)
- .style('pointer-events', 'all')
- .attr({
- opacity: 0,
- x: 0,
- y: 0,
- width: 0,
- height: 0
- });
+ this.gd = gd;
+ this.container = container;
+ this.id = id;
+
+ // See ScrollBox.prototype.enable for further definition
+ this.position = null;
+ // scrollbox position
+ this.translateX = null;
+ // scrollbox horizontal translation
+ this.translateY = null;
+ // scrollbox vertical translation
+ this.hbar = null;
+ // horizontal scrollbar D3 selection
+ this.vbar = null;
+
+ // vertical scrollbar D3 selection
+ // element to capture pointer events
+ this.bg = this.container.selectAll("rect.scrollbox-bg").data([0]);
+
+ this.bg.exit().on(".drag", null).on("wheel", null).remove();
+
+ this.bg
+ .enter()
+ .append("rect")
+ .classed("scrollbox-bg", true)
+ .style("pointer-events", "all")
+ .attr({ opacity: 0, x: 0, y: 0, width: 0, height: 0 });
}
// scroll bar dimensions
@@ -62,7 +58,7 @@ ScrollBox.barWidth = 2;
ScrollBox.barLength = 20;
ScrollBox.barRadius = 2;
ScrollBox.barPad = 1;
-ScrollBox.barColor = '#808BA4';
+ScrollBox.barColor = "#808BA4";
/**
* If needed, setup a clip path and scrollbars
@@ -79,238 +75,220 @@ ScrollBox.barColor = '#808BA4';
* @param {number} [translateY=0] Vertical offset (in pixels)
*/
ScrollBox.prototype.enable = function enable(position, translateX, translateY) {
- var fullLayout = this.gd._fullLayout,
- fullWidth = fullLayout.width,
- fullHeight = fullLayout.height;
-
- // compute position of scrollbox
- this.position = position;
-
- var l = this.position.l,
- w = this.position.w,
- t = this.position.t,
- h = this.position.h,
- direction = this.position.direction,
- isDown = (direction === 'down'),
- isLeft = (direction === 'left'),
- isRight = (direction === 'right'),
- isUp = (direction === 'up'),
- boxW = w,
- boxH = h,
- boxL, boxR,
- boxT, boxB;
-
- if(!isDown && !isLeft && !isRight && !isUp) {
- this.position.direction = 'down';
- isDown = true;
- }
-
- var isVertical = isDown || isUp;
- if(isVertical) {
- boxL = l;
- boxR = boxL + boxW;
-
- if(isDown) {
- // anchor to top side
- boxT = t;
- boxB = Math.min(boxT + boxH, fullHeight);
- boxH = boxB - boxT;
- }
- else {
- // anchor to bottom side
- boxB = t + boxH;
- boxT = Math.max(boxB - boxH, 0);
- boxH = boxB - boxT;
- }
+ var fullLayout = this.gd._fullLayout,
+ fullWidth = fullLayout.width,
+ fullHeight = fullLayout.height;
+
+ // compute position of scrollbox
+ this.position = position;
+
+ var l = this.position.l,
+ w = this.position.w,
+ t = this.position.t,
+ h = this.position.h,
+ direction = this.position.direction,
+ isDown = direction === "down",
+ isLeft = direction === "left",
+ isRight = direction === "right",
+ isUp = direction === "up",
+ boxW = w,
+ boxH = h,
+ boxL,
+ boxR,
+ boxT,
+ boxB;
+
+ if (!isDown && !isLeft && !isRight && !isUp) {
+ this.position.direction = "down";
+ isDown = true;
+ }
+
+ var isVertical = isDown || isUp;
+ if (isVertical) {
+ boxL = l;
+ boxR = boxL + boxW;
+
+ if (isDown) {
+ // anchor to top side
+ boxT = t;
+ boxB = Math.min(boxT + boxH, fullHeight);
+ boxH = boxB - boxT;
+ } else {
+ // anchor to bottom side
+ boxB = t + boxH;
+ boxT = Math.max(boxB - boxH, 0);
+ boxH = boxB - boxT;
}
- else {
- boxT = t;
- boxB = boxT + boxH;
-
- if(isLeft) {
- // anchor to right side
- boxR = l + boxW;
- boxL = Math.max(boxR - boxW, 0);
- boxW = boxR - boxL;
- }
- else {
- // anchor to left side
- boxL = l;
- boxR = Math.min(boxL + boxW, fullWidth);
- boxW = boxR - boxL;
- }
+ } else {
+ boxT = t;
+ boxB = boxT + boxH;
+
+ if (isLeft) {
+ // anchor to right side
+ boxR = l + boxW;
+ boxL = Math.max(boxR - boxW, 0);
+ boxW = boxR - boxL;
+ } else {
+ // anchor to left side
+ boxL = l;
+ boxR = Math.min(boxL + boxW, fullWidth);
+ boxW = boxR - boxL;
}
-
- this._box = {
- l: boxL,
- t: boxT,
- w: boxW,
- h: boxH
- };
-
- // compute position of horizontal scroll bar
- var needsHorizontalScrollBar = (w > boxW),
- hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad,
- hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad,
- // draw horizontal scrollbar on the bottom side
- hbarL = l,
- hbarT = t + h;
-
- if(hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH;
-
- var hbar = this.container.selectAll('rect.scrollbar-horizontal').data(
- (needsHorizontalScrollBar) ? [0] : []);
-
- hbar.exit()
- .on('.drag', null)
- .remove();
-
- hbar.enter().append('rect')
- .classed('scrollbar-horizontal', true)
- .call(Color.fill, ScrollBox.barColor);
-
- if(needsHorizontalScrollBar) {
- this.hbar = hbar.attr({
- 'rx': ScrollBox.barRadius,
- 'ry': ScrollBox.barRadius,
- 'x': hbarL,
- 'y': hbarT,
- 'width': hbarW,
- 'height': hbarH
- });
-
- // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax
- this._hbarXMin = hbarL + hbarW / 2;
- this._hbarTranslateMax = boxW - hbarW;
- }
- else {
- delete this.hbar;
- delete this._hbarXMin;
- delete this._hbarTranslateMax;
+ }
+
+ this._box = { l: boxL, t: boxT, w: boxW, h: boxH };
+
+ // compute position of horizontal scroll bar
+ var needsHorizontalScrollBar = w > boxW,
+ hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad,
+ hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad,
+ // draw horizontal scrollbar on the bottom side
+ hbarL = l,
+ hbarT = t + h;
+
+ if (hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH;
+
+ var hbar = this.container
+ .selectAll("rect.scrollbar-horizontal")
+ .data(needsHorizontalScrollBar ? [0] : []);
+
+ hbar.exit().on(".drag", null).remove();
+
+ hbar
+ .enter()
+ .append("rect")
+ .classed("scrollbar-horizontal", true)
+ .call(Color.fill, ScrollBox.barColor);
+
+ if (needsHorizontalScrollBar) {
+ this.hbar = hbar.attr({
+ rx: ScrollBox.barRadius,
+ ry: ScrollBox.barRadius,
+ x: hbarL,
+ y: hbarT,
+ width: hbarW,
+ height: hbarH
+ });
+
+ // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax
+ this._hbarXMin = hbarL + hbarW / 2;
+ this._hbarTranslateMax = boxW - hbarW;
+ } else {
+ delete this.hbar;
+ delete this._hbarXMin;
+ delete this._hbarTranslateMax;
+ }
+
+ // compute position of vertical scroll bar
+ var needsVerticalScrollBar = h > boxH,
+ vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad,
+ vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad,
+ // draw vertical scrollbar on the right side
+ vbarL = l + w,
+ vbarT = t;
+
+ if (vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW;
+
+ var vbar = this.container
+ .selectAll("rect.scrollbar-vertical")
+ .data(needsVerticalScrollBar ? [0] : []);
+
+ vbar.exit().on(".drag", null).remove();
+
+ vbar
+ .enter()
+ .append("rect")
+ .classed("scrollbar-vertical", true)
+ .call(Color.fill, ScrollBox.barColor);
+
+ if (needsVerticalScrollBar) {
+ this.vbar = vbar.attr({
+ rx: ScrollBox.barRadius,
+ ry: ScrollBox.barRadius,
+ x: vbarL,
+ y: vbarT,
+ width: vbarW,
+ height: vbarH
+ });
+
+ // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax
+ this._vbarYMin = vbarT + vbarH / 2;
+ this._vbarTranslateMax = boxH - vbarH;
+ } else {
+ delete this.vbar;
+ delete this._vbarYMin;
+ delete this._vbarTranslateMax;
+ }
+
+ // setup a clip path (if scroll bars are needed)
+ var clipId = this.id,
+ clipL = boxL - 0.5,
+ clipR = needsVerticalScrollBar ? boxR + vbarW + 0.5 : boxR + 0.5,
+ clipT = boxT - 0.5,
+ clipB = needsHorizontalScrollBar ? boxB + hbarH + 0.5 : boxB + 0.5;
+
+ var clipPath = fullLayout._topdefs
+ .selectAll("#" + clipId)
+ .data(needsHorizontalScrollBar || needsVerticalScrollBar ? [0] : []);
+
+ clipPath.exit().remove();
+
+ clipPath.enter().append("clipPath").attr("id", clipId).append("rect");
+
+ if (needsHorizontalScrollBar || needsVerticalScrollBar) {
+ this._clipRect = clipPath.select("rect").attr({
+ x: Math.floor(clipL),
+ y: Math.floor(clipT),
+ width: Math.ceil(clipR) - Math.floor(clipL),
+ height: Math.ceil(clipB) - Math.floor(clipT)
+ });
+
+ this.container.call(Drawing.setClipUrl, clipId);
+
+ this.bg.attr({ x: l, y: t, width: w, height: h });
+ } else {
+ this.bg.attr({ width: 0, height: 0 });
+ this.container
+ .on("wheel", null)
+ .on(".drag", null)
+ .call(Drawing.setClipUrl, null);
+ delete this._clipRect;
+ }
+
+ // set up drag listeners (if scroll bars are needed)
+ if (needsHorizontalScrollBar || needsVerticalScrollBar) {
+ var onBoxDrag = d3.behavior
+ .drag()
+ .on("dragstart", function() {
+ d3.event.sourceEvent.preventDefault();
+ })
+ .on("drag", this._onBoxDrag.bind(this));
+
+ this.container
+ .on("wheel", null)
+ .on("wheel", this._onBoxWheel.bind(this))
+ .on(".drag", null)
+ .call(onBoxDrag);
+
+ var onBarDrag = d3.behavior
+ .drag()
+ .on("dragstart", function() {
+ d3.event.sourceEvent.preventDefault();
+ d3.event.sourceEvent.stopPropagation();
+ })
+ .on("drag", this._onBarDrag.bind(this));
+
+ if (needsHorizontalScrollBar) {
+ this.hbar.on(".drag", null).call(onBarDrag);
}
- // compute position of vertical scroll bar
- var needsVerticalScrollBar = (h > boxH),
- vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad,
- vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad,
- // draw vertical scrollbar on the right side
- vbarL = l + w,
- vbarT = t;
-
- if(vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW;
-
- var vbar = this.container.selectAll('rect.scrollbar-vertical').data(
- (needsVerticalScrollBar) ? [0] : []);
-
- vbar.exit()
- .on('.drag', null)
- .remove();
-
- vbar.enter().append('rect')
- .classed('scrollbar-vertical', true)
- .call(Color.fill, ScrollBox.barColor);
-
- if(needsVerticalScrollBar) {
- this.vbar = vbar.attr({
- 'rx': ScrollBox.barRadius,
- 'ry': ScrollBox.barRadius,
- 'x': vbarL,
- 'y': vbarT,
- 'width': vbarW,
- 'height': vbarH
- });
-
- // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax
- this._vbarYMin = vbarT + vbarH / 2;
- this._vbarTranslateMax = boxH - vbarH;
- }
- else {
- delete this.vbar;
- delete this._vbarYMin;
- delete this._vbarTranslateMax;
+ if (needsVerticalScrollBar) {
+ this.vbar.on(".drag", null).call(onBarDrag);
}
+ }
- // setup a clip path (if scroll bars are needed)
- var clipId = this.id,
- clipL = boxL - 0.5,
- clipR = (needsVerticalScrollBar) ? boxR + vbarW + 0.5 : boxR + 0.5,
- clipT = boxT - 0.5,
- clipB = (needsHorizontalScrollBar) ? boxB + hbarH + 0.5 : boxB + 0.5;
-
- var clipPath = fullLayout._topdefs.selectAll('#' + clipId)
- .data((needsHorizontalScrollBar || needsVerticalScrollBar) ? [0] : []);
-
- clipPath.exit().remove();
-
- clipPath.enter()
- .append('clipPath').attr('id', clipId)
- .append('rect');
-
- if(needsHorizontalScrollBar || needsVerticalScrollBar) {
- this._clipRect = clipPath.select('rect').attr({
- x: Math.floor(clipL),
- y: Math.floor(clipT),
- width: Math.ceil(clipR) - Math.floor(clipL),
- height: Math.ceil(clipB) - Math.floor(clipT)
- });
-
- this.container.call(Drawing.setClipUrl, clipId);
-
- this.bg.attr({
- x: l,
- y: t,
- width: w,
- height: h
- });
- }
- else {
- this.bg.attr({
- width: 0,
- height: 0
- });
- this.container
- .on('wheel', null)
- .on('.drag', null)
- .call(Drawing.setClipUrl, null);
- delete this._clipRect;
- }
-
- // set up drag listeners (if scroll bars are needed)
- if(needsHorizontalScrollBar || needsVerticalScrollBar) {
- var onBoxDrag = d3.behavior.drag()
- .on('dragstart', function() {
- d3.event.sourceEvent.preventDefault();
- })
- .on('drag', this._onBoxDrag.bind(this));
-
- this.container
- .on('wheel', null)
- .on('wheel', this._onBoxWheel.bind(this))
- .on('.drag', null)
- .call(onBoxDrag);
-
- var onBarDrag = d3.behavior.drag()
- .on('dragstart', function() {
- d3.event.sourceEvent.preventDefault();
- d3.event.sourceEvent.stopPropagation();
- })
- .on('drag', this._onBarDrag.bind(this));
-
- if(needsHorizontalScrollBar) {
- this.hbar
- .on('.drag', null)
- .call(onBarDrag);
- }
-
- if(needsVerticalScrollBar) {
- this.vbar
- .on('.drag', null)
- .call(onBarDrag);
- }
- }
-
- // set scrollbox translation
- this.setTranslate(translateX, translateY);
+ // set scrollbox translation
+ this.setTranslate(translateX, translateY);
};
/**
@@ -319,33 +297,30 @@ ScrollBox.prototype.enable = function enable(position, translateX, translateY) {
* @method
*/
ScrollBox.prototype.disable = function disable() {
- if(this.hbar || this.vbar) {
- this.bg.attr({
- width: 0,
- height: 0
- });
- this.container
- .on('wheel', null)
- .on('.drag', null)
- .call(Drawing.setClipUrl, null);
- delete this._clipRect;
- }
-
- if(this.hbar) {
- this.hbar.on('.drag', null);
- this.hbar.remove();
- delete this.hbar;
- delete this._hbarXMin;
- delete this._hbarTranslateMax;
- }
-
- if(this.vbar) {
- this.vbar.on('.drag', null);
- this.vbar.remove();
- delete this.vbar;
- delete this._vbarYMin;
- delete this._vbarTranslateMax;
- }
+ if (this.hbar || this.vbar) {
+ this.bg.attr({ width: 0, height: 0 });
+ this.container
+ .on("wheel", null)
+ .on(".drag", null)
+ .call(Drawing.setClipUrl, null);
+ delete this._clipRect;
+ }
+
+ if (this.hbar) {
+ this.hbar.on(".drag", null);
+ this.hbar.remove();
+ delete this.hbar;
+ delete this._hbarXMin;
+ delete this._hbarTranslateMax;
+ }
+
+ if (this.vbar) {
+ this.vbar.on(".drag", null);
+ this.vbar.remove();
+ delete this.vbar;
+ delete this._vbarYMin;
+ delete this._vbarTranslateMax;
+ }
};
/**
@@ -354,18 +329,17 @@ ScrollBox.prototype.disable = function disable() {
* @method
*/
ScrollBox.prototype._onBoxDrag = function onBarDrag() {
- var translateX = this.translateX,
- translateY = this.translateY;
+ var translateX = this.translateX, translateY = this.translateY;
- if(this.hbar) {
- translateX -= d3.event.dx;
- }
+ if (this.hbar) {
+ translateX -= d3.event.dx;
+ }
- if(this.vbar) {
- translateY -= d3.event.dy;
- }
+ if (this.vbar) {
+ translateY -= d3.event.dy;
+ }
- this.setTranslate(translateX, translateY);
+ this.setTranslate(translateX, translateY);
};
/**
@@ -374,18 +348,17 @@ ScrollBox.prototype._onBoxDrag = function onBarDrag() {
* @method
*/
ScrollBox.prototype._onBoxWheel = function onBarWheel() {
- var translateX = this.translateX,
- translateY = this.translateY;
+ var translateX = this.translateX, translateY = this.translateY;
- if(this.hbar) {
- translateX += d3.event.deltaY;
- }
+ if (this.hbar) {
+ translateX += d3.event.deltaY;
+ }
- if(this.vbar) {
- translateY += d3.event.deltaY;
- }
+ if (this.vbar) {
+ translateY += d3.event.deltaY;
+ }
- this.setTranslate(translateX, translateY);
+ this.setTranslate(translateX, translateY);
};
/**
@@ -394,32 +367,31 @@ ScrollBox.prototype._onBoxWheel = function onBarWheel() {
* @method
*/
ScrollBox.prototype._onBarDrag = function onBarDrag() {
- var translateX = this.translateX,
- translateY = this.translateY;
+ var translateX = this.translateX, translateY = this.translateY;
- if(this.hbar) {
- var xMin = translateX + this._hbarXMin,
- xMax = xMin + this._hbarTranslateMax,
- x = Lib.constrain(d3.event.x, xMin, xMax),
- xf = (x - xMin) / (xMax - xMin);
+ if (this.hbar) {
+ var xMin = translateX + this._hbarXMin,
+ xMax = xMin + this._hbarTranslateMax,
+ x = Lib.constrain(d3.event.x, xMin, xMax),
+ xf = (x - xMin) / (xMax - xMin);
- var translateXMax = this.position.w - this._box.w;
+ var translateXMax = this.position.w - this._box.w;
- translateX = xf * translateXMax;
- }
+ translateX = xf * translateXMax;
+ }
- if(this.vbar) {
- var yMin = translateY + this._vbarYMin,
- yMax = yMin + this._vbarTranslateMax,
- y = Lib.constrain(d3.event.y, yMin, yMax),
- yf = (y - yMin) / (yMax - yMin);
+ if (this.vbar) {
+ var yMin = translateY + this._vbarYMin,
+ yMax = yMin + this._vbarTranslateMax,
+ y = Lib.constrain(d3.event.y, yMin, yMax),
+ yf = (y - yMin) / (yMax - yMin);
- var translateYMax = this.position.h - this._box.h;
+ var translateYMax = this.position.h - this._box.h;
- translateY = yf * translateYMax;
- }
+ translateY = yf * translateYMax;
+ }
- this.setTranslate(translateX, translateY);
+ this.setTranslate(translateX, translateY);
};
/**
@@ -429,41 +401,50 @@ ScrollBox.prototype._onBarDrag = function onBarDrag() {
* @param {number} [translateX=0] Horizontal offset (in pixels)
* @param {number} [translateY=0] Vertical offset (in pixels)
*/
-ScrollBox.prototype.setTranslate = function setTranslate(translateX, translateY) {
- // store translateX and translateY (needed by mouse event handlers)
- var translateXMax = this.position.w - this._box.w,
- translateYMax = this.position.h - this._box.h;
-
- translateX = Lib.constrain(translateX || 0, 0, translateXMax);
- translateY = Lib.constrain(translateY || 0, 0, translateYMax);
-
- this.translateX = translateX;
- this.translateY = translateY;
-
- this.container.call(Drawing.setTranslate,
- this._box.l - this.position.l - translateX,
- this._box.t - this.position.t - translateY);
-
- if(this._clipRect) {
- this._clipRect.attr({
- x: Math.floor(this.position.l + translateX - 0.5),
- y: Math.floor(this.position.t + translateY - 0.5)
- });
- }
-
- if(this.hbar) {
- var xf = translateX / translateXMax;
-
- this.hbar.call(Drawing.setTranslate,
- translateX + xf * this._hbarTranslateMax,
- translateY);
- }
-
- if(this.vbar) {
- var yf = translateY / translateYMax;
-
- this.vbar.call(Drawing.setTranslate,
- translateX,
- translateY + yf * this._vbarTranslateMax);
- }
+ScrollBox.prototype.setTranslate = function setTranslate(
+ translateX,
+ translateY
+) {
+ // store translateX and translateY (needed by mouse event handlers)
+ var translateXMax = this.position.w - this._box.w,
+ translateYMax = this.position.h - this._box.h;
+
+ translateX = Lib.constrain(translateX || 0, 0, translateXMax);
+ translateY = Lib.constrain(translateY || 0, 0, translateYMax);
+
+ this.translateX = translateX;
+ this.translateY = translateY;
+
+ this.container.call(
+ Drawing.setTranslate,
+ this._box.l - this.position.l - translateX,
+ this._box.t - this.position.t - translateY
+ );
+
+ if (this._clipRect) {
+ this._clipRect.attr({
+ x: Math.floor(this.position.l + translateX - 0.5),
+ y: Math.floor(this.position.t + translateY - 0.5)
+ });
+ }
+
+ if (this.hbar) {
+ var xf = translateX / translateXMax;
+
+ this.hbar.call(
+ Drawing.setTranslate,
+ translateX + xf * this._hbarTranslateMax,
+ translateY
+ );
+ }
+
+ if (this.vbar) {
+ var yf = translateY / translateYMax;
+
+ this.vbar.call(
+ Drawing.setTranslate,
+ translateX,
+ translateY + yf * this._vbarTranslateMax
+ );
+ }
};
diff --git a/src/constants/gl2d_dashes.js b/src/constants/gl2d_dashes.js
index 8675739aa56..67d116470c9 100644
--- a/src/constants/gl2d_dashes.js
+++ b/src/constants/gl2d_dashes.js
@@ -5,15 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
module.exports = {
- solid: [1],
- dot: [1, 1],
- dash: [4, 1],
- longdash: [8, 1],
- dashdot: [4, 1, 1, 1],
- longdashdot: [8, 1, 1, 1]
+ solid: [1],
+ dot: [1, 1],
+ dash: [4, 1],
+ longdash: [8, 1],
+ dashdot: [4, 1, 1, 1],
+ longdashdot: [8, 1, 1, 1]
};
diff --git a/src/constants/gl3d_dashes.js b/src/constants/gl3d_dashes.js
index 8e4ac98164a..dee80abd774 100644
--- a/src/constants/gl3d_dashes.js
+++ b/src/constants/gl3d_dashes.js
@@ -5,15 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
module.exports = {
- solid: [[], 0],
- dot: [[0.5, 1], 200],
- dash: [[0.5, 1], 50],
- longdash: [[0.5, 1], 10],
- dashdot: [[0.5, 0.625, 0.875, 1], 50],
- longdashdot: [[0.5, 0.7, 0.8, 1], 10]
+ solid: [[], 0],
+ dot: [[0.5, 1], 200],
+ dash: [[0.5, 1], 50],
+ longdash: [[0.5, 1], 10],
+ dashdot: [[0.5, 0.625, 0.875, 1], 50],
+ longdashdot: [[0.5, 0.7, 0.8, 1], 10]
};
diff --git a/src/constants/gl_markers.js b/src/constants/gl_markers.js
index e10354e84a4..59866362cc7 100644
--- a/src/constants/gl_markers.js
+++ b/src/constants/gl_markers.js
@@ -5,17 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
module.exports = {
- circle: '●',
- 'circle-open': '○',
- square: '■',
- 'square-open': '□',
- diamond: '◆',
- 'diamond-open': '◇',
- cross: '+',
- x: '❌'
+ circle: "\u25CF",
+ "circle-open": "\u25CB",
+ square: "\u25A0",
+ "square-open": "\u25A1",
+ diamond: "\u25C6",
+ "diamond-open": "\u25C7",
+ cross: "+",
+ x: "\u274C"
};
diff --git a/src/constants/interactions.js b/src/constants/interactions.js
index 132e9ca4c37..681772f16d1 100644
--- a/src/constants/interactions.js
+++ b/src/constants/interactions.js
@@ -5,14 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
- /**
+ /**
* Timing information for interactive elements
*/
- SHOW_PLACEHOLDER: 100,
- HIDE_PLACEHOLDER: 1000
+ SHOW_PLACEHOLDER: 100,
+ HIDE_PLACEHOLDER: 1000
};
diff --git a/src/constants/numerical.js b/src/constants/numerical.js
index c881daa72c4..35da533fa1f 100644
--- a/src/constants/numerical.js
+++ b/src/constants/numerical.js
@@ -5,42 +5,40 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
- /**
+ /**
* Standardize all missing data in calcdata to use undefined
* never null or NaN.
* That way we can use !==undefined, or !== BADNUM,
* to test for real data
*/
- BADNUM: undefined,
-
- /*
+ BADNUM: undefined,
+ /*
* Limit certain operations to well below floating point max value
* to avoid glitches: Make sure that even when you multiply it by the
* number of pixels on a giant screen it still works
*/
- FP_SAFE: Number.MAX_VALUE / 10000,
-
- /*
+ FP_SAFE: (
+ Number.MAX_VALUE / 10000
+ ),
+ /*
* conversion of date units to milliseconds
* year and month constants are marked "AVG"
* to remind us that not all years and months
* have the same length
*/
- ONEAVGYEAR: 31557600000, // 365.25 days
- ONEAVGMONTH: 2629800000, // 1/12 of ONEAVGYEAR
- ONEDAY: 86400000,
- ONEHOUR: 3600000,
- ONEMIN: 60000,
- ONESEC: 1000,
-
- /*
+ ONEAVGYEAR: 31557600000,
+ // 365.25 days
+ ONEAVGMONTH: 2629800000,
+ // 1/12 of ONEAVGYEAR
+ ONEDAY: 86400000,
+ ONEHOUR: 3600000,
+ ONEMIN: 60000,
+ ONESEC: 1000,
+ /*
* For fast conversion btwn world calendars and epoch ms, the Julian Day Number
* of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD()
*/
- EPOCHJD: 2440587.5
+ EPOCHJD: 2440587.5
};
diff --git a/src/constants/string_mappings.js b/src/constants/string_mappings.js
index a2760f7b6c0..052735edb7d 100644
--- a/src/constants/string_mappings.js
+++ b/src/constants/string_mappings.js
@@ -5,32 +5,26 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
// N.B. HTML entities are listed without the leading '&' and trailing ';'
module.exports = {
-
- entityToUnicode: {
- 'mu': 'μ',
- 'amp': '&',
- 'lt': '<',
- 'gt': '>',
- 'nbsp': ' ',
- 'times': '×',
- 'plusmn': '±',
- 'deg': '°'
- },
-
- unicodeToEntity: {
- '&': 'amp',
- '<': 'lt',
- '>': 'gt',
- '"': 'quot',
- '\'': '#x27',
- '\/': '#x2F'
- }
-
+ entityToUnicode: {
+ mu: "\u03BC",
+ amp: "&",
+ lt: "<",
+ gt: ">",
+ nbsp: "\xA0",
+ times: "\xD7",
+ plusmn: "\xB1",
+ deg: "\xB0"
+ },
+ unicodeToEntity: {
+ "&": "amp",
+ "<": "lt",
+ ">": "gt",
+ '"': "quot",
+ "'": "#x27",
+ "/": "#x2F"
+ }
};
diff --git a/src/constants/xmlns_namespaces.js b/src/constants/xmlns_namespaces.js
index aaeaea827a3..26baebcbc80 100644
--- a/src/constants/xmlns_namespaces.js
+++ b/src/constants/xmlns_namespaces.js
@@ -5,18 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
-exports.xmlns = 'http://www.w3.org/2000/xmlns/';
-exports.svg = 'http://www.w3.org/2000/svg';
-exports.xlink = 'http://www.w3.org/1999/xlink';
+"use strict";
+exports.xmlns = "http://www.w3.org/2000/xmlns/";
+exports.svg = "http://www.w3.org/2000/svg";
+exports.xlink = "http://www.w3.org/1999/xlink";
// the 'old' d3 quirk got fix in v3.5.7
// https://github.com/mbostock/d3/commit/a6f66e9dd37f764403fc7c1f26be09ab4af24fed
-exports.svgAttrs = {
- xmlns: exports.svg,
- 'xmlns:xlink': exports.xlink
-};
+exports.svgAttrs = { xmlns: exports.svg, "xmlns:xlink": exports.xlink };
diff --git a/src/core.js b/src/core.js
index 23774702e83..c6420fa69ce 100644
--- a/src/core.js
+++ b/src/core.js
@@ -5,26 +5,24 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/*
* Export the plotly.js API methods.
*/
-var Plotly = require('./plotly');
+var Plotly = require("./plotly");
// package version injected by `npm run preprocess`
-exports.version = '1.23.1';
+exports.version = "1.23.1";
// inject promise polyfill
-require('es6-promise').polyfill();
+require("es6-promise").polyfill();
// inject plot css
-require('../build/plotcss');
+require("../build/plotcss");
// inject default MathJax config
-require('./fonts/mathjax_config');
+require("./fonts/mathjax_config");
// plot api
exports.plot = Plotly.plot;
@@ -39,39 +37,39 @@ exports.addTraces = Plotly.addTraces;
exports.deleteTraces = Plotly.deleteTraces;
exports.moveTraces = Plotly.moveTraces;
exports.purge = Plotly.purge;
-exports.setPlotConfig = require('./plot_api/set_plot_config');
-exports.register = require('./plot_api/register');
-exports.toImage = require('./plot_api/to_image');
-exports.downloadImage = require('./snapshot/download');
-exports.validate = require('./plot_api/validate');
+exports.setPlotConfig = require("./plot_api/set_plot_config");
+exports.register = require("./plot_api/register");
+exports.toImage = require("./plot_api/to_image");
+exports.downloadImage = require("./snapshot/download");
+exports.validate = require("./plot_api/validate");
exports.addFrames = Plotly.addFrames;
exports.deleteFrames = Plotly.deleteFrames;
exports.animate = Plotly.animate;
// scatter is the only trace included by default
-exports.register(require('./traces/scatter'));
+exports.register(require("./traces/scatter"));
// register all registrable components modules
exports.register([
- require('./components/legend'),
- require('./components/annotations'),
- require('./components/shapes'),
- require('./components/images'),
- require('./components/updatemenus'),
- require('./components/sliders'),
- require('./components/rangeslider'),
- require('./components/rangeselector')
+ require("./components/legend"),
+ require("./components/annotations"),
+ require("./components/shapes"),
+ require("./components/images"),
+ require("./components/updatemenus"),
+ require("./components/sliders"),
+ require("./components/rangeslider"),
+ require("./components/rangeselector")
]);
// plot icons
-exports.Icons = require('../build/ploticon');
+exports.Icons = require("../build/ploticon");
// unofficial 'beta' plot methods, use at your own risk
exports.Plots = Plotly.Plots;
exports.Fx = Plotly.Fx;
-exports.Snapshot = require('./snapshot');
-exports.PlotSchema = require('./plot_api/plot_schema');
-exports.Queue = require('./lib/queue');
+exports.Snapshot = require("./snapshot");
+exports.PlotSchema = require("./plot_api/plot_schema");
+exports.Queue = require("./lib/queue");
// export d3 used in the bundle
-exports.d3 = require('d3');
+exports.d3 = require("d3");
diff --git a/src/fonts/mathjax_config.js b/src/fonts/mathjax_config.js
index 8005ad86e13..1799482a286 100644
--- a/src/fonts/mathjax_config.js
+++ b/src/fonts/mathjax_config.js
@@ -5,27 +5,23 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/* global MathJax:false */
/**
* Check and configure MathJax
*/
-if(typeof MathJax !== 'undefined') {
- exports.MathJax = true;
+if (typeof MathJax !== "undefined") {
+ exports.MathJax = true;
- MathJax.Hub.Config({
- messageStyle: 'none',
- skipStartupTypeset: true,
- displayAlign: 'left',
- tex2jax: {
- inlineMath: [['$', '$'], ['\\(', '\\)']]
- }
- });
+ MathJax.Hub.Config({
+ messageStyle: "none",
+ skipStartupTypeset: true,
+ displayAlign: "left",
+ tex2jax: { inlineMath: [["$", "$"], ["\\(", "\\)"]] }
+ });
- MathJax.Hub.Configured();
+ MathJax.Hub.Configured();
} else {
- exports.MathJax = false;
+ exports.MathJax = false;
}
diff --git a/src/lib/array_to_calc_item.js b/src/lib/array_to_calc_item.js
index 4a968234f0a..bd326d91b07 100644
--- a/src/lib/array_to_calc_item.js
+++ b/src/lib/array_to_calc_item.js
@@ -5,11 +5,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
// similar to Lib.mergeArray, but using inside a loop
module.exports = function arrayToCalcItem(traceAttr, calcItem, calcAttr, i) {
- if(Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i];
+ if (Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i];
};
diff --git a/src/lib/clean_number.js b/src/lib/clean_number.js
index 922c2db7e94..26d393b6499 100644
--- a/src/lib/clean_number.js
+++ b/src/lib/clean_number.js
@@ -5,13 +5,10 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var BADNUM = require('../constants/numerical').BADNUM;
+var BADNUM = require("../constants/numerical").BADNUM;
// precompile for speed
var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g;
@@ -21,11 +18,11 @@ var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g;
* Always returns either a number or BADNUM.
*/
module.exports = function cleanNumber(v) {
- if(typeof v === 'string') {
- v = v.replace(JUNK, '');
- }
+ if (typeof v === "string") {
+ v = v.replace(JUNK, "");
+ }
- if(isNumeric(v)) return Number(v);
+ if (isNumeric(v)) return Number(v);
- return BADNUM;
+ return BADNUM;
};
diff --git a/src/lib/coerce.js b/src/lib/coerce.js
index f3ef35d6598..346011173c2 100644
--- a/src/lib/coerce.js
+++ b/src/lib/coerce.js
@@ -5,269 +5,273 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var tinycolor = require("tinycolor2");
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-var tinycolor = require('tinycolor2');
-
-var getColorscale = require('../components/colorscale/get_scale');
-var colorscaleNames = Object.keys(require('../components/colorscale/scales'));
-var nestedProperty = require('./nested_property');
+var getColorscale = require("../components/colorscale/get_scale");
+var colorscaleNames = Object.keys(require("../components/colorscale/scales"));
+var nestedProperty = require("./nested_property");
var ID_REGEX = /^([2-9]|[1-9][0-9]+)$/;
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 && v < opts.min) ||
- (opts.max !== undefined && v > opts.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 && v < opts.min) ||
- (opts.max !== undefined && v > opts.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(typeof v !== 'string') {
- var okToCoerce = (typeof v === 'number');
-
- if(opts.strict === true || !okToCoerce) propOut.set(dflt);
- else propOut.set(String(v));
- }
- else if(opts.noBlank && !v) propOut.set(dflt);
- else propOut.set(v);
- }
- },
- 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(getColorscale(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);
- }
- }
+ 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 && v < opts.min ||
+ opts.max !== undefined && v > opts.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 && v < opts.min ||
+ opts.max !== undefined && v > opts.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 (typeof v !== "string") {
+ var okToCoerce = typeof v === "number";
+
+ if (opts.strict === true || !okToCoerce) propOut.set(dflt);
+ else propOut.set(String(v));
+ } else if (opts.noBlank && !v) propOut.set(dflt);
+ else propOut.set(v);
+ }
+ },
+ 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(getColorscale(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);
+ }
+ }
+ },
+ subplotid: {
+ description: [
+ "An id string of a subplot type (given by dflt), optionally",
+ "followed by an integer >1. e.g. if dflt='geo', we can have",
+ "'geo', 'geo2', 'geo3', ..."
+ ].join(" "),
+ requiredOpts: ["dflt"],
+ otherOpts: [],
+ coerceFunction: function(v, propOut, dflt) {
+ var dlen = dflt.length;
+ if (
+ typeof v === "string" &&
+ v.substr(0, dlen) === dflt &&
+ ID_REGEX.test(v.substr(dlen))
+ ) {
+ propOut.set(v);
+ return;
+ }
+ propOut.set(dflt);
},
- subplotid: {
- description: [
- 'An id string of a subplot type (given by dflt), optionally',
- 'followed by an integer >1. e.g. if dflt=\'geo\', we can have',
- '\'geo\', \'geo2\', \'geo3\', ...'
- ].join(' '),
- requiredOpts: ['dflt'],
- otherOpts: [],
- coerceFunction: function(v, propOut, dflt) {
- var dlen = dflt.length;
- if(typeof v === 'string' && v.substr(0, dlen) === dflt &&
- ID_REGEX.test(v.substr(dlen))) {
- propOut.set(v);
- return;
- }
- propOut.set(dflt);
- },
- validateFunction: function(v, opts) {
- var dflt = opts.dflt,
- dlen = dflt.length;
+ validateFunction: function(v, opts) {
+ var dflt = opts.dflt, dlen = dflt.length;
- if(v === dflt) return true;
- if(typeof v !== 'string') return false;
- if(v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) {
- return true;
- }
+ if (v === dflt) return true;
+ if (typeof v !== "string") return false;
+ if (v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) {
+ return true;
+ }
- return false;
- }
- },
- 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 < vParts.length) {
- var vi = vParts[i];
- if(opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) {
- vParts.splice(i, 1);
- }
- else i++;
- }
- if(!vParts.length) propOut.set(dflt);
- else propOut.set(vParts.join('+'));
- }
- },
- any: {
- description: 'Any type.',
- requiredOpts: [],
- otherOpts: ['dflt', 'values', 'arrayOk'],
- coerceFunction: function(v, propOut, dflt) {
- if(v === undefined) propOut.set(dflt);
- else propOut.set(v);
+ return false;
+ }
+ },
+ 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 < vParts.length) {
+ var vi = vParts[i];
+ if (opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) {
+ vParts.splice(i, 1);
+ } else {
+ i++;
}
+ }
+ if (!vParts.length) propOut.set(dflt);
+ else propOut.set(vParts.join("+"));
+ }
+ },
+ any: {
+ description: "Any type.",
+ requiredOpts: [],
+ otherOpts: ["dflt", "values", "arrayOk"],
+ coerceFunction: function(v, propOut, dflt) {
+ if (v === undefined) propOut.set(dflt);
+ else propOut.set(v);
+ }
+ },
+ info_array: {
+ description: ["An {array} of plot information."].join(" "),
+ requiredOpts: ["items"],
+ otherOpts: ["dflt", "freeLength"],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if (!Array.isArray(v)) {
+ propOut.set(dflt);
+ return;
+ }
+
+ var items = opts.items, vOut = [];
+ dflt = Array.isArray(dflt) ? dflt : [];
+
+ for (var i = 0; i < items.length; i++) {
+ exports.coerce(v, vOut, items, "[" + i + "]", dflt[i]);
+ }
+
+ propOut.set(vOut);
},
- info_array: {
- description: [
- 'An {array} of plot information.'
- ].join(' '),
- requiredOpts: ['items'],
- otherOpts: ['dflt', 'freeLength'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(!Array.isArray(v)) {
- propOut.set(dflt);
- return;
- }
-
- var items = opts.items,
- vOut = [];
- dflt = Array.isArray(dflt) ? dflt : [];
-
- for(var i = 0; i < items.length; i++) {
- exports.coerce(v, vOut, items, '[' + i + ']', dflt[i]);
- }
+ validateFunction: function(v, opts) {
+ if (!Array.isArray(v)) return false;
- propOut.set(vOut);
- },
- validateFunction: function(v, opts) {
- if(!Array.isArray(v)) return false;
+ var items = opts.items;
- var items = opts.items;
+ // when free length is off, input and declared lengths must match
+ if (!opts.freeLength && v.length !== items.length) return false;
- // when free length is off, input and declared lengths must match
- if(!opts.freeLength && v.length !== items.length) return false;
+ // valid when all input items are valid
+ for (var i = 0; i < v.length; i++) {
+ var isItemValid = exports.validate(v[i], opts.items[i]);
- // valid when all input items are valid
- for(var i = 0; i < v.length; i++) {
- var isItemValid = exports.validate(v[i], opts.items[i]);
+ if (!isItemValid) return false;
+ }
- if(!isItemValid) return false;
- }
-
- return true;
- }
+ return true;
}
+ }
};
/**
@@ -282,28 +286,34 @@ exports.valObjects = {
* if dflt is provided as an argument to lib.coerce it takes precedence
* as a convenience, returns the value it finally set
*/
-exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt) {
- var opts = nestedProperty(attributes, attribute).get(),
- propIn = nestedProperty(containerIn, attribute),
- propOut = nestedProperty(containerOut, attribute),
- v = propIn.get();
-
- if(dflt === undefined) dflt = opts.dflt;
-
- /**
+exports.coerce = function(
+ containerIn,
+ containerOut,
+ attributes,
+ attribute,
+ dflt
+) {
+ var opts = nestedProperty(attributes, attribute).get(),
+ propIn = nestedProperty(containerIn, attribute),
+ propOut = nestedProperty(containerOut, attribute),
+ v = propIn.get();
+
+ if (dflt === undefined) dflt = opts.dflt;
+
+ /**
* arrayOk: value MAY be an array, then we do no value checking
* at this point, because it can be more complicated than the
* individual form (eg. some array vals can be numbers, even if the
* single values must be color strings)
*/
- if(opts.arrayOk && Array.isArray(v)) {
- propOut.set(v);
- return v;
- }
+ if (opts.arrayOk && Array.isArray(v)) {
+ propOut.set(v);
+ return v;
+ }
- exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts);
+ exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts);
- return propOut.get();
+ return propOut.get();
};
/**
@@ -313,12 +323,24 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt
* returns attribute default if user input it not valid or
* returns false if there is no user input.
*/
-exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dflt) {
- var propIn = nestedProperty(containerIn, attribute),
- propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt),
- valIn = propIn.get();
-
- return (valIn !== undefined && valIn !== null) ? propOut : false;
+exports.coerce2 = function(
+ containerIn,
+ containerOut,
+ attributes,
+ attribute,
+ dflt
+) {
+ var propIn = nestedProperty(containerIn, attribute),
+ propOut = exports.coerce(
+ containerIn,
+ containerOut,
+ attributes,
+ attribute,
+ dflt
+ ),
+ valIn = propIn.get();
+
+ return valIn !== undefined && valIn !== null ? propOut : false;
};
/*
@@ -327,32 +349,35 @@ exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dfl
* 'coerce' is a lib.coerce wrapper with implied first three arguments
*/
exports.coerceFont = function(coerce, attr, dfltObj) {
- var out = {};
+ var out = {};
- dfltObj = dfltObj || {};
+ dfltObj = dfltObj || {};
- out.family = coerce(attr + '.family', dfltObj.family);
- out.size = coerce(attr + '.size', dfltObj.size);
- out.color = coerce(attr + '.color', dfltObj.color);
+ out.family = coerce(attr + ".family", dfltObj.family);
+ out.size = coerce(attr + ".size", dfltObj.size);
+ out.color = coerce(attr + ".color", dfltObj.color);
- return out;
+ return out;
};
exports.validate = function(value, opts) {
- var valObject = exports.valObjects[opts.valType];
-
- if(opts.arrayOk && Array.isArray(value)) return true;
+ var valObject = exports.valObjects[opts.valType];
- if(valObject.validateFunction) {
- return valObject.validateFunction(value, opts);
- }
+ if (opts.arrayOk && Array.isArray(value)) return true;
- var failed = {},
- out = failed,
- propMock = { set: function(v) { out = v; } };
+ if (valObject.validateFunction) {
+ return valObject.validateFunction(value, opts);
+ }
- // 'failed' just something mutable that won't be === anything else
+ var failed = {},
+ out = failed,
+ propMock = {
+ set: function(v) {
+ out = v;
+ }
+ };
- valObject.coerceFunction(value, propMock, failed, opts);
- return out !== failed;
+ // 'failed' just something mutable that won't be === anything else
+ valObject.coerceFunction(value, propMock, failed, opts);
+ return out !== failed;
};
diff --git a/src/lib/dates.js b/src/lib/dates.js
index c5c05fcfda9..dd91ba84ffe 100644
--- a/src/lib/dates.js
+++ b/src/lib/dates.js
@@ -5,17 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
+var logError = require("./loggers").error;
+var mod = require("./mod");
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var logError = require('./loggers').error;
-var mod = require('./mod');
-
-var constants = require('../constants/numerical');
+var constants = require("../constants/numerical");
var BADNUM = constants.BADNUM;
var ONEDAY = constants.ONEDAY;
var ONEHOUR = constants.ONEHOUR;
@@ -23,7 +20,7 @@ var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
var EPOCHJD = constants.EPOCHJD;
-var Registry = require('../registry');
+var Registry = require("../registry");
var utcFormat = d3.time.format.utc;
@@ -35,11 +32,10 @@ var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\
var YFIRST = new Date().getFullYear() - 70;
function isWorldCalendar(calendar) {
- return (
- calendar &&
- Registry.componentsRegistry.calendars &&
- typeof calendar === 'string' && calendar !== 'gregorian'
- );
+ return calendar &&
+ Registry.componentsRegistry.calendars &&
+ typeof calendar === "string" &&
+ calendar !== "gregorian";
}
/*
@@ -48,31 +44,29 @@ function isWorldCalendar(calendar) {
* bool sunday is for week ticks, shift it to a Sunday.
*/
exports.dateTick0 = function(calendar, sunday) {
- if(isWorldCalendar(calendar)) {
- return sunday ?
- Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] :
- Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar];
- }
- else {
- return sunday ? '2000-01-02' : '2000-01-01';
- }
+ if (isWorldCalendar(calendar)) {
+ return sunday
+ ? Registry.getComponentMethod("calendars", "CANONICAL_SUNDAY")[calendar]
+ : Registry.getComponentMethod("calendars", "CANONICAL_TICK")[calendar];
+ } else {
+ return sunday ? "2000-01-02" : "2000-01-01";
+ }
};
/*
* dfltRange: for each calendar, give a valid default range
*/
exports.dfltRange = function(calendar) {
- if(isWorldCalendar(calendar)) {
- return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar];
- }
- else {
- return ['2000-01-01', '2001-01-01'];
- }
+ if (isWorldCalendar(calendar)) {
+ return Registry.getComponentMethod("calendars", "DFLTRANGE")[calendar];
+ } else {
+ return ["2000-01-01", "2001-01-01"];
+ }
};
// is an object a javascript date?
exports.isJSDate = function(v) {
- return typeof v === 'object' && v !== null && typeof v.getTime === 'function';
+ return typeof v === "object" && v !== null && typeof v.getTime === "function";
};
// The absolute limits of our date-time system
@@ -134,97 +128,107 @@ var MIN_MS, MAX_MS;
* 1946-2045
*/
exports.dateTime2ms = function(s, calendar) {
- // first check if s is a date object
- if(exports.isJSDate(s)) {
- // Convert to the UTC milliseconds that give the same
- // hours as this date has in the local timezone
- s = Number(s) - s.getTimezoneOffset() * ONEMIN;
- if(s >= MIN_MS && s <= MAX_MS) return s;
- return BADNUM;
+ // first check if s is a date object
+ if (exports.isJSDate(s)) {
+ // Convert to the UTC milliseconds that give the same
+ // hours as this date has in the local timezone
+ s = Number(s) - s.getTimezoneOffset() * ONEMIN;
+ if (s >= MIN_MS && s <= MAX_MS) return s;
+ return BADNUM;
+ }
+ // otherwise only accept strings and numbers
+ if (typeof s !== "string" && typeof s !== "number") return BADNUM;
+
+ s = String(s);
+
+ var isWorld = isWorldCalendar(calendar);
+
+ // to handle out-of-range dates in international calendars, accept
+ // 'G' as a prefix to force the built-in gregorian calendar.
+ var s0 = s.charAt(0);
+ if (isWorld && (s0 === "G" || s0 === "g")) {
+ s = s.substr(1);
+ calendar = "";
+ }
+
+ var isChinese = isWorld && calendar.substr(0, 7) === "chinese";
+
+ var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
+ if (!match) return BADNUM;
+ var y = match[1],
+ m = match[3] || "1",
+ d = Number(match[5] || 1),
+ H = Number(match[7] || 0),
+ M = Number(match[9] || 0),
+ S = Number(match[11] || 0);
+
+ if (isWorld) {
+ // disallow 2-digit years for world calendars
+ if (y.length === 2) return BADNUM;
+ y = Number(y);
+
+ var cDate;
+ try {
+ var calInstance = Registry.getComponentMethod("calendars", "getCal")(
+ calendar
+ );
+ if (isChinese) {
+ var isIntercalary = m.charAt(m.length - 1) === "i";
+ m = parseInt(m, 10);
+ cDate = calInstance.newDate(
+ y,
+ calInstance.toMonthIndex(y, m, isIntercalary),
+ d
+ );
+ } else {
+ cDate = calInstance.newDate(y, Number(m), d);
+ }
+ } catch (e) {
+ return BADNUM;
}
- // otherwise only accept strings and numbers
- if(typeof s !== 'string' && typeof s !== 'number') return BADNUM;
- s = String(s);
+ // Invalid ... date
+ if (!cDate) return BADNUM;
- var isWorld = isWorldCalendar(calendar);
+ return (cDate.toJD() - EPOCHJD) * ONEDAY +
+ H * ONEHOUR +
+ M * ONEMIN +
+ S * ONESEC;
+ }
- // to handle out-of-range dates in international calendars, accept
- // 'G' as a prefix to force the built-in gregorian calendar.
- var s0 = s.charAt(0);
- if(isWorld && (s0 === 'G' || s0 === 'g')) {
- s = s.substr(1);
- calendar = '';
- }
-
- var isChinese = isWorld && calendar.substr(0, 7) === 'chinese';
-
- var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
- if(!match) return BADNUM;
- var y = match[1],
- m = match[3] || '1',
- d = Number(match[5] || 1),
- H = Number(match[7] || 0),
- M = Number(match[9] || 0),
- S = Number(match[11] || 0);
-
- if(isWorld) {
- // disallow 2-digit years for world calendars
- if(y.length === 2) return BADNUM;
- y = Number(y);
-
- var cDate;
- try {
- var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
- if(isChinese) {
- var isIntercalary = m.charAt(m.length - 1) === 'i';
- m = parseInt(m, 10);
- cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d);
- }
- else {
- cDate = calInstance.newDate(y, Number(m), d);
- }
- }
- catch(e) { return BADNUM; } // Invalid ... date
-
- if(!cDate) return BADNUM;
-
- return ((cDate.toJD() - EPOCHJD) * ONEDAY) +
- (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC);
- }
-
- if(y.length === 2) {
- y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST;
- }
- else y = Number(y);
+ if (y.length === 2) {
+ y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST;
+ } else {
+ y = Number(y);
+ }
- // new Date uses months from 0; subtract 1 here just so we
- // don't have to do it again during the validity test below
- m -= 1;
+ // new Date uses months from 0; subtract 1 here just so we
+ // don't have to do it again during the validity test below
+ m -= 1;
- // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
- // to support years 0-99 we need to use setFullYear explicitly
- // Note that 2000 is a leap year.
- var date = new Date(Date.UTC(2000, m, d, H, M));
- date.setUTCFullYear(y);
+ // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
+ // to support years 0-99 we need to use setFullYear explicitly
+ // Note that 2000 is a leap year.
+ var date = new Date(Date.UTC(2000, m, d, H, M));
+ date.setUTCFullYear(y);
- if(date.getUTCMonth() !== m) return BADNUM;
- if(date.getUTCDate() !== d) return BADNUM;
+ if (date.getUTCMonth() !== m) return BADNUM;
+ if (date.getUTCDate() !== d) return BADNUM;
- return date.getTime() + S * ONESEC;
+ return date.getTime() + S * ONESEC;
};
-MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
-MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
+MIN_MS = exports.MIN_MS = exports.dateTime2ms("-9999");
+MAX_MS = exports.MAX_MS = exports.dateTime2ms("9999-12-31 23:59:59.9999");
// is string s a date? (see above)
exports.isDateTime = function(s, calendar) {
- return (exports.dateTime2ms(s, calendar) !== BADNUM);
+ return exports.dateTime2ms(s, calendar) !== BADNUM;
};
// 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);
+ return String(val + Math.pow(10, digits)).substr(1);
}
/**
@@ -239,58 +243,65 @@ var NINETYDAYS = 90 * ONEDAY;
var THREEHOURS = 3 * ONEHOUR;
var FIVEMIN = 5 * ONEMIN;
exports.ms2DateTime = function(ms, r, calendar) {
- if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM;
-
- if(!r) r = 0;
-
- var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
- msRounded = Math.round(ms - msecTenths / 10),
- dateStr, h, m, s, msec10, d;
-
- if(isWorldCalendar(calendar)) {
- var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD,
- timeMs = Math.floor(mod(ms, ONEDAY));
- try {
- dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar)
- .fromJD(dateJD).formatDate('yyyy-mm-dd');
- }
- catch(e) {
- // invalid date in this calendar - fall back to Gyyyy-mm-dd
- dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
- }
-
- // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
- // other things for a few calendars, so we can't trust it. Just pad
- // it manually (after the '-' if there is one)
- if(dateStr.charAt(0) === '-') {
- while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1);
- }
- else {
- while(dateStr.length < 10) dateStr = '0' + dateStr;
- }
-
- // TODO: if this is faster, we could use this block for extracting
- // the time components of regular gregorian too
- h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0;
- m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0;
- s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0;
- msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0;
+ if (typeof ms !== "number" || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM;
+
+ if (!r) r = 0;
+
+ var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
+ msRounded = Math.round(ms - msecTenths / 10),
+ dateStr,
+ h,
+ m,
+ s,
+ msec10,
+ d;
+
+ if (isWorldCalendar(calendar)) {
+ var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD,
+ timeMs = Math.floor(mod(ms, ONEDAY));
+ try {
+ dateStr = Registry.getComponentMethod("calendars", "getCal")(calendar)
+ .fromJD(dateJD)
+ .formatDate("yyyy-mm-dd");
+ } catch (e) {
+ // invalid date in this calendar - fall back to Gyyyy-mm-dd
+ dateStr = utcFormat("G%Y-%m-%d")(new Date(msRounded));
}
- else {
- d = new Date(msRounded);
-
- dateStr = utcFormat('%Y-%m-%d')(d);
-
- // <90 days: add hours and minutes - never *only* add hours
- h = (r < NINETYDAYS) ? d.getUTCHours() : 0;
- m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0;
- // <3 hours: add seconds
- s = (r < THREEHOURS) ? d.getUTCSeconds() : 0;
- // <5 minutes: add ms (plus one extra digit, this is msec*10)
- msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0;
+
+ // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
+ // other things for a few calendars, so we can't trust it. Just pad
+ // it manually (after the '-' if there is one)
+ if (dateStr.charAt(0) === "-") {
+ while (dateStr.length < 11) {
+ dateStr = "-0" + dateStr.substr(1);
+ }
+ } else {
+ while (dateStr.length < 10) {
+ dateStr = "0" + dateStr;
+ }
}
- return includeTime(dateStr, h, m, s, msec10);
+ // TODO: if this is faster, we could use this block for extracting
+ // the time components of regular gregorian too
+ h = r < NINETYDAYS ? Math.floor(timeMs / ONEHOUR) : 0;
+ m = r < NINETYDAYS ? Math.floor(timeMs % ONEHOUR / ONEMIN) : 0;
+ s = r < THREEHOURS ? Math.floor(timeMs % ONEMIN / ONESEC) : 0;
+ msec10 = r < FIVEMIN ? timeMs % ONESEC * 10 + msecTenths : 0;
+ } else {
+ d = new Date(msRounded);
+
+ dateStr = utcFormat("%Y-%m-%d")(d);
+
+ // <90 days: add hours and minutes - never *only* add hours
+ h = r < NINETYDAYS ? d.getUTCHours() : 0;
+ m = r < NINETYDAYS ? d.getUTCMinutes() : 0;
+ // <3 hours: add seconds
+ s = r < THREEHOURS ? d.getUTCSeconds() : 0;
+ // <5 minutes: add ms (plus one extra digit, this is msec*10)
+ msec10 = r < FIVEMIN ? d.getUTCMilliseconds() * 10 + msecTenths : 0;
+ }
+
+ return includeTime(dateStr, h, m, s, msec10);
};
// For converting old-style milliseconds to date strings,
@@ -300,67 +311,68 @@ exports.ms2DateTime = function(ms, r, calendar) {
// Clip one extra day off our date range though so we can't get
// thrown beyond the range by the timezone shift.
exports.ms2DateTimeLocal = function(ms) {
- if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM;
+ if (!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM;
- var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
- d = new Date(Math.round(ms - msecTenths / 10)),
- dateStr = d3.time.format('%Y-%m-%d')(d),
- h = d.getHours(),
- m = d.getMinutes(),
- s = d.getSeconds(),
- msec10 = d.getUTCMilliseconds() * 10 + msecTenths;
+ var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
+ d = new Date(Math.round(ms - msecTenths / 10)),
+ dateStr = d3.time.format("%Y-%m-%d")(d),
+ h = d.getHours(),
+ m = d.getMinutes(),
+ s = d.getSeconds(),
+ msec10 = d.getUTCMilliseconds() * 10 + msecTenths;
- return includeTime(dateStr, h, m, s, msec10);
+ return includeTime(dateStr, h, m, s, msec10);
};
function includeTime(dateStr, h, m, s, msec10) {
- // include each part that has nonzero data in or after it
- if(h || m || s || msec10) {
- dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2);
- if(s || msec10) {
- dateStr += ':' + lpad(s, 2);
- if(msec10) {
- var digits = 4;
- while(msec10 % 10 === 0) {
- digits -= 1;
- msec10 /= 10;
- }
- dateStr += '.' + lpad(msec10, digits);
- }
+ // include each part that has nonzero data in or after it
+ if (h || m || s || msec10) {
+ dateStr += " " + lpad(h, 2) + ":" + lpad(m, 2);
+ if (s || msec10) {
+ dateStr += ":" + lpad(s, 2);
+ if (msec10) {
+ var digits = 4;
+ while (msec10 % 10 === 0) {
+ digits -= 1;
+ msec10 /= 10;
}
+ dateStr += "." + lpad(msec10, digits);
+ }
}
- return dateStr;
+ }
+ return dateStr;
}
// normalize date format to date string, in case it starts as
// a Date object or milliseconds
// optional dflt is the return value if cleaning fails
exports.cleanDate = function(v, dflt, calendar) {
- if(exports.isJSDate(v) || typeof v === 'number') {
- // do not allow milliseconds (old) or jsdate objects (inherently
- // described as gregorian dates) with world calendars
- if(isWorldCalendar(calendar)) {
- logError('JS Dates and milliseconds are incompatible with world calendars', v);
- return dflt;
- }
-
- // NOTE: if someone puts in a year as a number rather than a string,
- // this will mistakenly convert it thinking it's milliseconds from 1970
- // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
- v = exports.ms2DateTimeLocal(+v);
- if(!v && dflt !== undefined) return dflt;
+ if (exports.isJSDate(v) || typeof v === "number") {
+ // do not allow milliseconds (old) or jsdate objects (inherently
+ // described as gregorian dates) with world calendars
+ if (isWorldCalendar(calendar)) {
+ logError(
+ "JS Dates and milliseconds are incompatible with world calendars",
+ v
+ );
+ return dflt;
}
- else if(!exports.isDateTime(v, calendar)) {
- logError('unrecognized date', v);
- return dflt;
- }
- return v;
+
+ // NOTE: if someone puts in a year as a number rather than a string,
+ // this will mistakenly convert it thinking it's milliseconds from 1970
+ // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
+ v = exports.ms2DateTimeLocal(+v);
+ if (!v && dflt !== undefined) return dflt;
+ } else if (!exports.isDateTime(v, calendar)) {
+ logError("unrecognized date", v);
+ return dflt;
+ }
+ return v;
};
/*
* Date formatting for ticks and hovertext
*/
-
/*
* modDateFormat: Support world calendars, and add one item to
* d3's vocabulary:
@@ -368,26 +380,30 @@ exports.cleanDate = function(v, dflt, calendar) {
*/
var fracMatch = /%\d?f/g;
function modDateFormat(fmt, x, calendar) {
-
- fmt = fmt.replace(fracMatch, function(match) {
- var digits = Math.min(+(match.charAt(1)) || 6, 6),
- fracSecs = ((x / 1000 % 1) + 2)
- .toFixed(digits)
- .substr(2).replace(/0+$/, '') || '0';
- return fracSecs;
- });
-
- var d = new Date(Math.floor(x + 0.05));
-
- if(isWorldCalendar(calendar)) {
- try {
- fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar);
- }
- catch(e) {
- return 'Invalid';
- }
+ fmt = fmt.replace(fracMatch, function(match) {
+ var digits = Math.min(+match.charAt(1) || 6, 6),
+ fracSecs = (x / 1000 % 1 + 2)
+ .toFixed(digits)
+ .substr(2)
+ .replace(/0+$/, "") ||
+ "0";
+ return fracSecs;
+ });
+
+ var d = new Date(Math.floor(x + 0.05));
+
+ if (isWorldCalendar(calendar)) {
+ try {
+ fmt = Registry.getComponentMethod("calendars", "worldCalFmt")(
+ fmt,
+ x,
+ calendar
+ );
+ } catch (e) {
+ return "Invalid";
}
- return utcFormat(fmt)(d);
+ }
+ return utcFormat(fmt)(d);
}
/*
@@ -398,15 +414,17 @@ function modDateFormat(fmt, x, calendar) {
*/
var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999];
function formatTime(x, tr) {
- var timePart = mod(x + 0.05, ONEDAY);
+ var timePart = mod(x + 0.05, ONEDAY);
- var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' +
- lpad(mod(Math.floor(timePart / ONEMIN), 60), 2);
+ var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) +
+ ":" +
+ lpad(mod(Math.floor(timePart / ONEMIN), 60), 2);
- if(tr !== 'M') {
- if(!isNumeric(tr)) tr = 0; // should only be 'S'
+ if (tr !== "M") {
+ if (!isNumeric(tr)) tr = 0;
- /*
+ // should only be 'S'
+ /*
* this is a weird one - and shouldn't come up unless people
* monkey with tick0 in weird ways, but we need to do something!
* IN PARTICULAR we had better not display garbage (see below)
@@ -421,27 +439,35 @@ function formatTime(x, tr) {
* say we round seconds but floor everything else. BUT that means
* we need to never round up to 60 seconds, ie 23:59:60
*/
- var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]);
-
- var secStr = (100 + sec).toFixed(tr).substr(1);
- if(tr > 0) {
- secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, '');
- }
+ var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]);
- timeStr += ':' + secStr;
+ var secStr = (100 + sec).toFixed(tr).substr(1);
+ if (tr > 0) {
+ secStr = secStr.replace(/0+$/, "").replace(/[\.]$/, "");
}
- return timeStr;
+
+ timeStr += ":" + secStr;
+ }
+ return timeStr;
}
-var yearFormat = utcFormat('%Y'),
- monthFormat = utcFormat('%b %Y'),
- dayFormat = utcFormat('%b %-d'),
- yearMonthDayFormat = utcFormat('%b %-d, %Y');
+var yearFormat = utcFormat("%Y"),
+ monthFormat = utcFormat("%b %Y"),
+ dayFormat = utcFormat("%b %-d"),
+ yearMonthDayFormat = utcFormat("%b %-d, %Y");
-function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); }
-function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); }
-function dayFormatWorld(cDate) { return cDate.formatDate('M d'); }
-function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); }
+function yearFormatWorld(cDate) {
+ return cDate.formatDate("yyyy");
+}
+function monthFormatWorld(cDate) {
+ return cDate.formatDate("M yyyy");
+}
+function dayFormatWorld(cDate) {
+ return cDate.formatDate("M d");
+}
+function yearMonthDayFormatWorld(cDate) {
+ return cDate.formatDate("M d, yyyy");
+}
/*
* formatDate: turn a date into tick or hover label text.
@@ -459,48 +485,50 @@ function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy');
* one tick to the next (as it does with automatic formatting)
*/
exports.formatDate = function(x, fmt, tr, calendar) {
- var headStr,
- dateStr;
-
- calendar = isWorldCalendar(calendar) && calendar;
-
- if(fmt) return modDateFormat(fmt, x, calendar);
-
- if(calendar) {
- try {
- var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
- cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar)
- .fromJD(dateJD);
-
- if(tr === 'y') dateStr = yearFormatWorld(cDate);
- else if(tr === 'm') dateStr = monthFormatWorld(cDate);
- else if(tr === 'd') {
- headStr = yearFormatWorld(cDate);
- dateStr = dayFormatWorld(cDate);
- }
- else {
- headStr = yearMonthDayFormatWorld(cDate);
- dateStr = formatTime(x, tr);
- }
- }
- catch(e) { return 'Invalid'; }
+ var headStr, dateStr;
+
+ calendar = isWorldCalendar(calendar) && calendar;
+
+ if (fmt) return modDateFormat(fmt, x, calendar);
+
+ if (calendar) {
+ try {
+ var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
+ cDate = Registry.getComponentMethod("calendars", "getCal")(
+ calendar
+ ).fromJD(dateJD);
+
+ if (tr === "y") {
+ dateStr = yearFormatWorld(cDate);
+ } else if (tr === "m") {
+ dateStr = monthFormatWorld(cDate);
+ } else if (tr === "d") {
+ headStr = yearFormatWorld(cDate);
+ dateStr = dayFormatWorld(cDate);
+ } else {
+ headStr = yearMonthDayFormatWorld(cDate);
+ dateStr = formatTime(x, tr);
+ }
+ } catch (e) {
+ return "Invalid";
}
- else {
- var d = new Date(Math.floor(x + 0.05));
-
- if(tr === 'y') dateStr = yearFormat(d);
- else if(tr === 'm') dateStr = monthFormat(d);
- else if(tr === 'd') {
- headStr = yearFormat(d);
- dateStr = dayFormat(d);
- }
- else {
- headStr = yearMonthDayFormat(d);
- dateStr = formatTime(x, tr);
- }
+ } else {
+ var d = new Date(Math.floor(x + 0.05));
+
+ if (tr === "y") {
+ dateStr = yearFormat(d);
+ } else if (tr === "m") {
+ dateStr = monthFormat(d);
+ } else if (tr === "d") {
+ headStr = yearFormat(d);
+ dateStr = dayFormat(d);
+ } else {
+ headStr = yearMonthDayFormat(d);
+ dateStr = formatTime(x, tr);
}
+ }
- return dateStr + (headStr ? '\n' + headStr : '');
+ return dateStr + (headStr ? "\n" + headStr : "");
};
/*
@@ -531,33 +559,34 @@ exports.formatDate = function(x, fmt, tr, calendar) {
*/
var THREEDAYS = 3 * ONEDAY;
exports.incrementMonth = function(ms, dMonth, calendar) {
- calendar = isWorldCalendar(calendar) && calendar;
-
- // pull time out and operate on pure dates, then add time back at the end
- // this gives maximum precision - not that we *normally* care if we're
- // incrementing by month, but better to be safe!
- var timeMs = mod(ms, ONEDAY);
- ms = Math.round(ms - timeMs);
-
- if(calendar) {
- try {
- var dateJD = Math.round(ms / ONEDAY) + EPOCHJD,
- calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar),
- cDate = calInstance.fromJD(dateJD);
-
- if(dMonth % 12) calInstance.add(cDate, dMonth, 'm');
- else calInstance.add(cDate, dMonth / 12, 'y');
-
- return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
- }
- catch(e) {
- logError('invalid ms ' + ms + ' in calendar ' + calendar);
- // then keep going in gregorian even though the result will be 'Invalid'
- }
+ calendar = isWorldCalendar(calendar) && calendar;
+
+ // pull time out and operate on pure dates, then add time back at the end
+ // this gives maximum precision - not that we *normally* care if we're
+ // incrementing by month, but better to be safe!
+ var timeMs = mod(ms, ONEDAY);
+ ms = Math.round(ms - timeMs);
+
+ if (calendar) {
+ try {
+ var dateJD = Math.round(ms / ONEDAY) + EPOCHJD,
+ calInstance = Registry.getComponentMethod("calendars", "getCal")(
+ calendar
+ ),
+ cDate = calInstance.fromJD(dateJD);
+
+ if (dMonth % 12) calInstance.add(cDate, dMonth, "m");
+ else calInstance.add(cDate, dMonth / 12, "y");
+
+ return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
+ } catch (e) {
+ logError("invalid ms " + ms + " in calendar " + calendar);
+ // then keep going in gregorian even though the result will be 'Invalid'
}
+ }
- var y = new Date(ms + THREEDAYS);
- return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS;
+ var y = new Date(ms + THREEDAYS);
+ return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS;
};
/*
@@ -567,60 +596,52 @@ exports.incrementMonth = function(ms, dMonth, calendar) {
* calendar (string) the calendar to test against
*/
exports.findExactDates = function(data, calendar) {
- var exactYears = 0,
- exactMonths = 0,
- exactDays = 0,
- blankCount = 0,
- d,
- di;
-
- var calInstance = (
- isWorldCalendar(calendar) &&
- Registry.getComponentMethod('calendars', 'getCal')(calendar)
- );
-
- for(var i = 0; i < data.length; i++) {
- di = data[i];
-
- // not date data at all
- if(!isNumeric(di)) {
- blankCount ++;
- continue;
- }
+ var exactYears = 0, exactMonths = 0, exactDays = 0, blankCount = 0, d, di;
- // not an exact date
- if(di % ONEDAY) continue;
-
- if(calInstance) {
- try {
- d = calInstance.fromJD(di / ONEDAY + EPOCHJD);
- if(d.day() === 1) {
- if(d.month() === 1) exactYears++;
- else exactMonths++;
- }
- else exactDays++;
- }
- catch(e) {
- // invalid date in this calendar - ignore it here.
- }
- }
- else {
- d = new Date(di);
- if(d.getUTCDate() === 1) {
- if(d.getUTCMonth() === 0) exactYears++;
- else exactMonths++;
- }
- else exactDays++;
+ var calInstance = isWorldCalendar(calendar) &&
+ Registry.getComponentMethod("calendars", "getCal")(calendar);
+
+ for (var i = 0; i < data.length; i++) {
+ di = data[i];
+
+ // not date data at all
+ if (!isNumeric(di)) {
+ blankCount++;
+ continue;
+ }
+
+ // not an exact date
+ if (di % ONEDAY) continue;
+
+ if (calInstance) {
+ try {
+ d = calInstance.fromJD(di / ONEDAY + EPOCHJD);
+ if (d.day() === 1) {
+ if (d.month() === 1) exactYears++;
+ else exactMonths++;
+ } else {
+ exactDays++;
}
+ } catch (e) {
+ }
+ } else {
+ d = new Date(di);
+ if (d.getUTCDate() === 1) {
+ if (d.getUTCMonth() === 0) exactYears++;
+ else exactMonths++;
+ } else {
+ exactDays++;
+ }
}
- exactMonths += exactYears;
- exactDays += exactMonths;
+ }
+ exactMonths += exactYears;
+ exactDays += exactMonths;
- var dataCount = data.length - blankCount;
+ var dataCount = data.length - blankCount;
- return {
- exactYears: exactYears / dataCount,
- exactMonths: exactMonths / dataCount,
- exactDays: exactDays / dataCount
- };
+ return {
+ exactYears: exactYears / dataCount,
+ exactMonths: exactMonths / dataCount,
+ exactDays: exactDays / dataCount
+ };
};
diff --git a/src/lib/events.js b/src/lib/events.js
index 8238384242a..6648ea2a76f 100644
--- a/src/lib/events.js
+++ b/src/lib/events.js
@@ -5,35 +5,30 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
/* global jQuery:false */
-var EventEmitter = require('events').EventEmitter;
+var EventEmitter = require("events").EventEmitter;
var Events = {
-
- init: function(plotObj) {
-
- /*
+ init: function(plotObj) {
+ /*
* If we have already instantiated an emitter for this plot
* return early.
*/
- if(plotObj._ev instanceof EventEmitter) return plotObj;
+ if (plotObj._ev instanceof EventEmitter) return plotObj;
- var ev = new EventEmitter();
- var internalEv = new EventEmitter();
+ var ev = new EventEmitter();
+ var internalEv = new EventEmitter();
- /*
+ /*
* Assign to plot._ev while we still live in a land
* where plot is a DOM element with stuff attached to it.
* In the future we can make plot the event emitter itself.
*/
- plotObj._ev = ev;
+ plotObj._ev = ev;
- /*
+ /*
* Create a second event handler that will manage events *internally*.
* This allows parts of plotly to respond to thing like relayout without
* having to use the user-facing event handler. They cannot peacefully
@@ -41,9 +36,9 @@ var Events = {
* plotObj.removeAllListeners() would detach internal events, breaking
* plotly.
*/
- plotObj._internalEv = internalEv;
+ plotObj._internalEv = internalEv;
- /*
+ /*
* Assign bound methods from the ev to the plot object. These methods
* will reference the 'this' of plot._ev even though they are methods
* of plot. This will keep the event machinery away from the plot object
@@ -52,39 +47,42 @@ var Events = {
* methods have been bound to `plot` as some do not currently add value to
* the Plotly event API.
*/
- plotObj.on = ev.on.bind(ev);
- plotObj.once = ev.once.bind(ev);
- plotObj.removeListener = ev.removeListener.bind(ev);
- plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
+ plotObj.on = ev.on.bind(ev);
+ plotObj.once = ev.once.bind(ev);
+ plotObj.removeListener = ev.removeListener.bind(ev);
+ plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
- /*
+ /*
* Create funtions for managing internal events. These are *only* triggered
* by the mirroring of external events via the emit function.
*/
- plotObj._internalOn = internalEv.on.bind(internalEv);
- plotObj._internalOnce = internalEv.once.bind(internalEv);
- plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv);
- plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv);
+ plotObj._internalOn = internalEv.on.bind(internalEv);
+ plotObj._internalOnce = internalEv.once.bind(internalEv);
+ plotObj._removeInternalListener = internalEv.removeListener.bind(
+ internalEv
+ );
+ plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(
+ internalEv
+ );
- /*
+ /*
* We must wrap emit to continue to support JQuery events. The idea
* is to check to see if the user is using JQuery events, if they are
* we emit JQuery events to trigger user handlers as well as the EventEmitter
* events.
*/
- plotObj.emit = function(event, data) {
- if(typeof jQuery !== 'undefined') {
- jQuery(plotObj).trigger(event, data);
- }
-
- ev.emit(event, data);
- internalEv.emit(event, data);
- };
-
- return plotObj;
- },
-
- /*
+ plotObj.emit = function(event, data) {
+ if (typeof jQuery !== "undefined") {
+ jQuery(plotObj).trigger(event, data);
+ }
+
+ ev.emit(event, data);
+ internalEv.emit(event, data);
+ };
+
+ return plotObj;
+ },
+ /*
* This function behaves like jQueries triggerHandler. It calls
* all handlers for a particular event and returns the return value
* of the LAST handler. This function also triggers jQuery's
@@ -94,71 +92,70 @@ var Events = {
* so the additional behavior of triggerHandler triggering internal events
* is deliberate excluded in order to avoid reinforcing more usage.
*/
- triggerHandler: function(plotObj, event, data) {
- var jQueryHandlerValue;
- var nodeEventHandlerValue;
- /*
+ triggerHandler: function(plotObj, event, data) {
+ var jQueryHandlerValue;
+ var nodeEventHandlerValue;
+ /*
* If Jquery exists run all its handlers for this event and
* collect the return value of the LAST handler function
*/
- if(typeof jQuery !== 'undefined') {
- jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
- }
+ if (typeof jQuery !== "undefined") {
+ jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
+ }
- /*
+ /*
* Now run all the node style event handlers
*/
- var ev = plotObj._ev;
- if(!ev) return jQueryHandlerValue;
+ var ev = plotObj._ev;
+ if (!ev) return jQueryHandlerValue;
- var handlers = ev._events[event];
- if(!handlers) return jQueryHandlerValue;
+ var handlers = ev._events[event];
+ if (!handlers) return jQueryHandlerValue;
- /*
+ /*
* handlers can be function or an array of functions
*/
- if(typeof handlers === 'function') handlers = [handlers];
- var lastHandler = handlers.pop();
+ if (typeof handlers === "function") handlers = [handlers];
+ var lastHandler = handlers.pop();
- /*
+ /*
* Call all the handlers except the last one.
*/
- for(var i = 0; i < handlers.length; i++) {
- handlers[i](data);
- }
+ for (var i = 0; i < handlers.length; i++) {
+ handlers[i](data);
+ }
- /*
+ /*
* Now call the final handler and collect its value
*/
- nodeEventHandlerValue = lastHandler(data);
+ nodeEventHandlerValue = lastHandler(data);
- /*
+ /*
* Return either the jquery handler value if it exists or the
* nodeEventHandler value. Jquery event value superceeds nodejs
* events for backwards compatability reasons.
*/
- return jQueryHandlerValue !== undefined ? jQueryHandlerValue :
- nodeEventHandlerValue;
- },
-
- purge: function(plotObj) {
- delete plotObj._ev;
- delete plotObj.on;
- delete plotObj.once;
- delete plotObj.removeListener;
- delete plotObj.removeAllListeners;
- delete plotObj.emit;
-
- delete plotObj._ev;
- delete plotObj._internalEv;
- delete plotObj._internalOn;
- delete plotObj._internalOnce;
- delete plotObj._removeInternalListener;
- delete plotObj._removeAllInternalListeners;
-
- return plotObj;
- }
-
+ return jQueryHandlerValue !== undefined
+ ? jQueryHandlerValue
+ : nodeEventHandlerValue;
+ },
+ purge: function(plotObj) {
+ delete plotObj._ev;
+ delete plotObj.on;
+ delete plotObj.once;
+ delete plotObj.removeListener;
+ delete plotObj.removeAllListeners;
+ delete plotObj.emit;
+
+ delete plotObj._ev;
+ delete plotObj._internalEv;
+ delete plotObj._internalOn;
+ delete plotObj._internalOnce;
+ delete plotObj._removeInternalListener;
+ delete plotObj._removeAllInternalListeners;
+
+ return plotObj;
+ }
};
module.exports = Events;
diff --git a/src/lib/extend.js b/src/lib/extend.js
index b0591778b64..06f1b77e2d2 100644
--- a/src/lib/extend.js
+++ b/src/lib/extend.js
@@ -5,41 +5,38 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var isPlainObject = require('./is_plain_object.js');
+"use strict";
+var isPlainObject = require("./is_plain_object.js");
var isArray = Array.isArray;
function primitivesLoopSplice(source, target) {
- var i, value;
- for(i = 0; i < source.length; i++) {
- value = source[i];
- if(value !== null && typeof(value) === 'object') {
- return false;
- }
- if(value !== void(0)) {
- target[i] = value;
- }
+ var i, value;
+ for (i = 0; i < source.length; i++) {
+ value = source[i];
+ if (value !== null && typeof value === "object") {
+ return false;
+ }
+ if (value !== void 0) {
+ target[i] = value;
}
- return true;
+ }
+ return true;
}
exports.extendFlat = function() {
- return _extend(arguments, false, false, false);
+ return _extend(arguments, false, false, false);
};
exports.extendDeep = function() {
- return _extend(arguments, true, false, false);
+ return _extend(arguments, true, false, false);
};
exports.extendDeepAll = function() {
- return _extend(arguments, true, true, false);
+ return _extend(arguments, true, true, false);
};
exports.extendDeepNoArrays = function() {
- return _extend(arguments, true, false, true);
+ return _extend(arguments, true, false, true);
};
/*
@@ -60,53 +57,56 @@ exports.extendDeepNoArrays = function() {
*
*/
function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) {
- var target = inputs[0],
- length = inputs.length;
+ var target = inputs[0], length = inputs.length;
- var input, key, src, copy, copyIsArray, clone, allPrimitives;
+ var input, key, src, copy, copyIsArray, clone, allPrimitives;
- if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) {
+ if (
+ length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0
+ ) {
+ allPrimitives = primitivesLoopSplice(inputs[1], target);
- allPrimitives = primitivesLoopSplice(inputs[1], target);
-
- if(allPrimitives) {
- return target;
+ if (allPrimitives) {
+ return target;
+ } else {
+ target.splice(0, target.length); // reset target and continue to next block
+ }
+ }
+
+ for (var i = 1; i < length; i++) {
+ input = inputs[i];
+
+ for (key in input) {
+ src = target[key];
+ copy = input[key];
+
+ // Stop early and just transfer the array if array copies are disallowed:
+ if (noArrayCopies && isArray(copy)) {
+ target[key] = copy;
+ } else if (
+ isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))
+ ) {
+ // recurse if we're merging plain objects or arrays
+ if (copyIsArray) {
+ copyIsArray = false;
+ clone = src && isArray(src) ? src : [];
} else {
- target.splice(0, target.length); // reset target and continue to next block
+ clone = src && isPlainObject(src) ? src : {};
}
- }
-
- for(var i = 1; i < length; i++) {
- input = inputs[i];
-
- for(key in input) {
- src = target[key];
- copy = input[key];
-
- // Stop early and just transfer the array if array copies are disallowed:
- if(noArrayCopies && isArray(copy)) {
- target[key] = copy;
- }
- // recurse if we're merging plain objects or arrays
- else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
- if(copyIsArray) {
- copyIsArray = false;
- clone = src && isArray(src) ? src : [];
- } else {
- clone = src && isPlainObject(src) ? src : {};
- }
-
- // never move original objects, clone them
- target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies);
- }
-
- // don't bring in undefined values, except for extendDeepAll
- else if(typeof copy !== 'undefined' || keepAllKeys) {
- target[key] = copy;
- }
- }
+ // never move original objects, clone them
+ target[key] = _extend(
+ [clone, copy],
+ isDeep,
+ keepAllKeys,
+ noArrayCopies
+ );
+ } else if (typeof copy !== "undefined" || keepAllKeys) {
+ // don't bring in undefined values, except for extendDeepAll
+ target[key] = copy;
+ }
}
+ }
- return target;
+ return target;
}
diff --git a/src/lib/filter_unique.js b/src/lib/filter_unique.js
index 5d035707696..927fa0fb7bc 100644
--- a/src/lib/filter_unique.js
+++ b/src/lib/filter_unique.js
@@ -5,11 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
/**
* Return news array containing only the unique items
* found in input array.
@@ -32,18 +28,16 @@
* @return {array} new filtered array
*/
module.exports = function filterUnique(array) {
- var seen = {},
- out = [],
- j = 0;
+ var seen = {}, out = [], j = 0;
- for(var i = 0; i < array.length; i++) {
- var item = array[i];
+ for (var i = 0; i < array.length; i++) {
+ var item = array[i];
- if(seen[item] !== 1) {
- seen[item] = 1;
- out[j++] = item;
- }
+ if (seen[item] !== 1) {
+ seen[item] = 1;
+ out[j++] = item;
}
+ }
- return out;
+ return out;
};
diff --git a/src/lib/filter_visible.js b/src/lib/filter_visible.js
index fdcf6674de3..3bde55405af 100644
--- a/src/lib/filter_visible.js
+++ b/src/lib/filter_visible.js
@@ -5,10 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
/** Filter out object items with visible !== true
* insider array container.
*
@@ -17,13 +14,13 @@
*
*/
module.exports = function filterVisible(container) {
- var out = [];
+ var out = [];
- for(var i = 0; i < container.length; i++) {
- var item = container[i];
+ for (var i = 0; i < container.length; i++) {
+ var item = container[i];
- if(item.visible === true) out.push(item);
- }
+ if (item.visible === true) out.push(item);
+ }
- return out;
+ return out;
};
diff --git a/src/lib/geo_location_utils.js b/src/lib/geo_location_utils.js
index 30795820c37..c12e288f5de 100644
--- a/src/lib/geo_location_utils.js
+++ b/src/lib/geo_location_utils.js
@@ -5,56 +5,54 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var countryRegex = require('country-regex');
-var Lib = require('../lib');
-
+"use strict";
+var countryRegex = require("country-regex");
+var Lib = require("../lib");
// make list of all country iso3 ids from at runtime
var countryIds = Object.keys(countryRegex);
var locationmodeToIdFinder = {
- 'ISO-3': Lib.identity,
- 'USA-states': Lib.identity,
- 'country names': countryNameToISO3
+ "ISO-3": Lib.identity,
+ "USA-states": Lib.identity,
+ "country names": countryNameToISO3
};
exports.locationToFeature = function(locationmode, location, features) {
- var locationId = getLocationId(locationmode, location);
+ var locationId = getLocationId(locationmode, location);
- if(locationId) {
- for(var i = 0; i < features.length; i++) {
- var feature = features[i];
+ if (locationId) {
+ for (var i = 0; i < features.length; i++) {
+ var feature = features[i];
- if(feature.id === locationId) return feature;
- }
-
- Lib.warn([
- 'Location with id', locationId,
- 'does not have a matching topojson feature at this resolution.'
- ].join(' '));
+ if (feature.id === locationId) return feature;
}
- return false;
+ Lib.warn(
+ [
+ "Location with id",
+ locationId,
+ "does not have a matching topojson feature at this resolution."
+ ].join(" ")
+ );
+ }
+
+ return false;
};
function getLocationId(locationmode, location) {
- var idFinder = locationmodeToIdFinder[locationmode];
- return idFinder(location);
+ var idFinder = locationmodeToIdFinder[locationmode];
+ return idFinder(location);
}
function countryNameToISO3(countryName) {
- for(var i = 0; i < countryIds.length; i++) {
- var iso3 = countryIds[i],
- regex = new RegExp(countryRegex[iso3]);
+ for (var i = 0; i < countryIds.length; i++) {
+ var iso3 = countryIds[i], regex = new RegExp(countryRegex[iso3]);
- if(regex.test(countryName.toLowerCase())) return iso3;
- }
+ if (regex.test(countryName.toLowerCase())) return iso3;
+ }
- Lib.warn('Unrecognized country name: ' + countryName + '.');
+ Lib.warn("Unrecognized country name: " + countryName + ".");
- return false;
+ return false;
}
diff --git a/src/lib/geojson_utils.js b/src/lib/geojson_utils.js
index 919472f4b0d..b26e74acbab 100644
--- a/src/lib/geojson_utils.js
+++ b/src/lib/geojson_utils.js
@@ -5,10 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
/**
* Convert calcTrace to GeoJSON 'MultiLineString' coordinate arrays
*
@@ -21,29 +18,26 @@
*
*/
exports.calcTraceToLineCoords = function(calcTrace) {
- var trace = calcTrace[0].trace,
- connectgaps = trace.connectgaps;
+ var trace = calcTrace[0].trace, connectgaps = trace.connectgaps;
- var coords = [],
- lineString = [];
+ var coords = [], lineString = [];
- for(var i = 0; i < calcTrace.length; i++) {
- var calcPt = calcTrace[i];
+ for (var i = 0; i < calcTrace.length; i++) {
+ var calcPt = calcTrace[i];
- lineString.push(calcPt.lonlat);
+ lineString.push(calcPt.lonlat);
- if(!connectgaps && calcPt.gapAfter && lineString.length > 0) {
- coords.push(lineString);
- lineString = [];
- }
+ if (!connectgaps && calcPt.gapAfter && lineString.length > 0) {
+ coords.push(lineString);
+ lineString = [];
}
+ }
- coords.push(lineString);
+ coords.push(lineString);
- return coords;
+ return coords;
};
-
/**
* Make line ('LineString' or 'MultiLineString') GeoJSON
*
@@ -57,24 +51,17 @@ exports.calcTraceToLineCoords = function(calcTrace) {
*
*/
exports.makeLine = function(coords, trace) {
- var out = {};
+ var out = {};
- if(coords.length === 1) {
- out = {
- type: 'LineString',
- coordinates: coords[0]
- };
- }
- else {
- out = {
- type: 'MultiLineString',
- coordinates: coords
- };
- }
+ if (coords.length === 1) {
+ out = { type: "LineString", coordinates: coords[0] };
+ } else {
+ out = { type: "MultiLineString", coordinates: coords };
+ }
- if(trace) out.trace = trace;
+ if (trace) out.trace = trace;
- return out;
+ return out;
};
/**
@@ -89,30 +76,23 @@ exports.makeLine = function(coords, trace) {
* GeoJSON object
*/
exports.makePolygon = function(coords, trace) {
- var out = {};
-
- if(coords.length === 1) {
- out = {
- type: 'Polygon',
- coordinates: coords
- };
- }
- else {
- var _coords = new Array(coords.length);
+ var out = {};
- for(var i = 0; i < coords.length; i++) {
- _coords[i] = [coords[i]];
- }
+ if (coords.length === 1) {
+ out = { type: "Polygon", coordinates: coords };
+ } else {
+ var _coords = new Array(coords.length);
- out = {
- type: 'MultiPolygon',
- coordinates: _coords
- };
+ for (var i = 0; i < coords.length; i++) {
+ _coords[i] = [coords[i]];
}
- if(trace) out.trace = trace;
+ out = { type: "MultiPolygon", coordinates: _coords };
+ }
+
+ if (trace) out.trace = trace;
- return out;
+ return out;
};
/**
@@ -123,8 +103,5 @@ exports.makePolygon = function(coords, trace) {
*
*/
exports.makeBlank = function() {
- return {
- type: 'Point',
- coordinates: []
- };
+ return { type: "Point", coordinates: [] };
};
diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js
index ac3f08aeb3e..0804799232d 100644
--- a/src/lib/gl_format_color.js
+++ b/src/lib/gl_format_color.js
@@ -5,77 +5,78 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var tinycolor = require("tinycolor2");
+var isNumeric = require("fast-isnumeric");
+var Colorscale = require("../components/colorscale");
+var colorDflt = require("../components/color/attributes").defaultLine;
-'use strict';
-
-var tinycolor = require('tinycolor2');
-var isNumeric = require('fast-isnumeric');
-
-var Colorscale = require('../components/colorscale');
-var colorDflt = require('../components/color/attributes').defaultLine;
-
-var str2RgbaArray = require('./str2rgbarray');
+var str2RgbaArray = require("./str2rgbarray");
var opacityDflt = 1;
function calculateColor(colorIn, opacityIn) {
- var colorOut = str2RgbaArray(colorIn);
- colorOut[3] *= opacityIn;
- return colorOut;
+ var colorOut = str2RgbaArray(colorIn);
+ colorOut[3] *= opacityIn;
+ return colorOut;
}
function validateColor(colorIn) {
- return tinycolor(colorIn).isValid() ? colorIn : colorDflt;
+ return tinycolor(colorIn).isValid() ? colorIn : colorDflt;
}
function validateOpacity(opacityIn) {
- return isNumeric(opacityIn) ? opacityIn : opacityDflt;
+ return isNumeric(opacityIn) ? opacityIn : opacityDflt;
}
function formatColor(containerIn, opacityIn, len) {
- var colorIn = containerIn.color,
- isArrayColorIn = Array.isArray(colorIn),
- isArrayOpacityIn = Array.isArray(opacityIn),
- colorOut = [];
-
- var sclFunc, getColor, getOpacity, colori, opacityi;
-
- if(containerIn.colorscale !== undefined) {
- sclFunc = Colorscale.makeColorScaleFunc(
- Colorscale.extractScale(
- containerIn.colorscale,
- containerIn.cmin,
- containerIn.cmax
- )
- );
- }
- else sclFunc = validateColor;
-
- if(isArrayColorIn) {
- getColor = function(c, i) {
- return c[i] === undefined ? colorDflt : sclFunc(c[i]);
- };
- }
- else getColor = validateColor;
-
- if(isArrayOpacityIn) {
- getOpacity = function(o, i) {
- return o[i] === undefined ? opacityDflt : validateOpacity(o[i]);
- };
- }
- else getOpacity = validateOpacity;
-
- if(isArrayColorIn || isArrayOpacityIn) {
- for(var i = 0; i < len; i++) {
- colori = getColor(colorIn, i);
- opacityi = getOpacity(opacityIn, i);
- colorOut[i] = calculateColor(colori, opacityi);
- }
+ var colorIn = containerIn.color,
+ isArrayColorIn = Array.isArray(colorIn),
+ isArrayOpacityIn = Array.isArray(opacityIn),
+ colorOut = [];
+
+ var sclFunc, getColor, getOpacity, colori, opacityi;
+
+ if (containerIn.colorscale !== undefined) {
+ sclFunc = Colorscale.makeColorScaleFunc(
+ Colorscale.extractScale(
+ containerIn.colorscale,
+ containerIn.cmin,
+ containerIn.cmax
+ )
+ );
+ } else {
+ sclFunc = validateColor;
+ }
+
+ if (isArrayColorIn) {
+ getColor = function(c, i) {
+ return c[i] === undefined ? colorDflt : sclFunc(c[i]);
+ };
+ } else {
+ getColor = validateColor;
+ }
+
+ if (isArrayOpacityIn) {
+ getOpacity = function(o, i) {
+ return o[i] === undefined ? opacityDflt : validateOpacity(o[i]);
+ };
+ } else {
+ getOpacity = validateOpacity;
+ }
+
+ if (isArrayColorIn || isArrayOpacityIn) {
+ for (var i = 0; i < len; i++) {
+ colori = getColor(colorIn, i);
+ opacityi = getOpacity(opacityIn, i);
+ colorOut[i] = calculateColor(colori, opacityi);
}
- else colorOut = calculateColor(colorIn, opacityIn);
+ } else {
+ colorOut = calculateColor(colorIn, opacityIn);
+ }
- return colorOut;
+ return colorOut;
}
module.exports = formatColor;
diff --git a/src/lib/html2unicode.js b/src/lib/html2unicode.js
index 346ecaaf90f..e184cfb31c5 100644
--- a/src/lib/html2unicode.js
+++ b/src/lib/html2unicode.js
@@ -5,63 +5,57 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var toSuperScript = require('superscript-text');
-var stringMappings = require('../constants/string_mappings');
+"use strict";
+var toSuperScript = require("superscript-text");
+var stringMappings = require("../constants/string_mappings");
function fixSuperScript(x) {
- var idx = 0;
+ var idx = 0;
- while((idx = x.indexOf('', idx)) >= 0) {
- var nidx = x.indexOf('', idx);
- if(nidx < idx) break;
+ while ((idx = x.indexOf("", idx)) >= 0) {
+ var nidx = x.indexOf("", idx);
+ if (nidx < idx) break;
- x = x.slice(0, idx) + toSuperScript(x.slice(idx + 5, nidx)) + x.slice(nidx + 6);
- }
+ x = x.slice(0, idx) +
+ toSuperScript(x.slice(idx + 5, nidx)) +
+ x.slice(nidx + 6);
+ }
- return x;
+ return x;
}
function fixBR(x) {
- return x.replace(/\
/g, '\n');
+ return x.replace(/\
/g, "\n");
}
function stripTags(x) {
- return x.replace(/\<.*\>/g, '');
+ return x.replace(/\<.*\>/g, "");
}
function fixEntities(x) {
- var entityToUnicode = stringMappings.entityToUnicode;
- var idx = 0;
-
- while((idx = x.indexOf('&', idx)) >= 0) {
- var nidx = x.indexOf(';', idx);
- if(nidx < idx) {
- idx += 1;
- continue;
- }
+ var entityToUnicode = stringMappings.entityToUnicode;
+ var idx = 0;
+
+ while ((idx = x.indexOf("&", idx)) >= 0) {
+ var nidx = x.indexOf(";", idx);
+ if (nidx < idx) {
+ idx += 1;
+ continue;
+ }
- var entity = entityToUnicode[x.slice(idx + 1, nidx)];
- if(entity) {
- x = x.slice(0, idx) + entity + x.slice(nidx + 1);
- } else {
- x = x.slice(0, idx) + x.slice(nidx + 1);
- }
+ var entity = entityToUnicode[x.slice(idx + 1, nidx)];
+ if (entity) {
+ x = x.slice(0, idx) + entity + x.slice(nidx + 1);
+ } else {
+ x = x.slice(0, idx) + x.slice(nidx + 1);
}
+ }
- return x;
+ return x;
}
function convertHTMLToUnicode(html) {
- return '' +
- fixEntities(
- stripTags(
- fixSuperScript(
- fixBR(
- html))));
+ return "" + fixEntities(stripTags(fixSuperScript(fixBR(html))));
}
module.exports = convertHTMLToUnicode;
diff --git a/src/lib/index.js b/src/lib/index.js
index 9544f4b3794..cb7c2fe644a 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -5,27 +5,24 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
+"use strict";
+var d3 = require("d3");
var lib = module.exports = {};
-lib.nestedProperty = require('./nested_property');
-lib.isPlainObject = require('./is_plain_object');
-lib.isArray = require('./is_array');
-lib.mod = require('./mod');
+lib.nestedProperty = require("./nested_property");
+lib.isPlainObject = require("./is_plain_object");
+lib.isArray = require("./is_array");
+lib.mod = require("./mod");
-var coerceModule = require('./coerce');
+var coerceModule = require("./coerce");
lib.valObjects = coerceModule.valObjects;
lib.coerce = coerceModule.coerce;
lib.coerce2 = coerceModule.coerce2;
lib.coerceFont = coerceModule.coerceFont;
lib.validate = coerceModule.validate;
-var datesModule = require('./dates');
+var datesModule = require("./dates");
lib.dateTime2ms = datesModule.dateTime2ms;
lib.isDateTime = datesModule.isDateTime;
lib.ms2DateTime = datesModule.ms2DateTime;
@@ -40,14 +37,14 @@ lib.findExactDates = datesModule.findExactDates;
lib.MIN_MS = datesModule.MIN_MS;
lib.MAX_MS = datesModule.MAX_MS;
-var searchModule = require('./search');
+var searchModule = require("./search");
lib.findBin = searchModule.findBin;
lib.sorterAsc = searchModule.sorterAsc;
lib.sorterDes = searchModule.sorterDes;
lib.distinctVals = searchModule.distinctVals;
lib.roundUp = searchModule.roundUp;
-var statsModule = require('./stats');
+var statsModule = require("./stats");
lib.aggNums = statsModule.aggNums;
lib.len = statsModule.len;
lib.mean = statsModule.mean;
@@ -55,7 +52,7 @@ lib.variance = statsModule.variance;
lib.stdev = statsModule.stdev;
lib.interp = statsModule.interp;
-var matrixModule = require('./matrix');
+var matrixModule = require("./matrix");
lib.init2dArray = matrixModule.init2dArray;
lib.transposeRagged = matrixModule.transposeRagged;
lib.dot = matrixModule.dot;
@@ -65,24 +62,23 @@ lib.rotationXYMatrix = matrixModule.rotationXYMatrix;
lib.apply2DTransform = matrixModule.apply2DTransform;
lib.apply2DTransform2 = matrixModule.apply2DTransform2;
-var extendModule = require('./extend');
+var extendModule = require("./extend");
lib.extendFlat = extendModule.extendFlat;
lib.extendDeep = extendModule.extendDeep;
lib.extendDeepAll = extendModule.extendDeepAll;
lib.extendDeepNoArrays = extendModule.extendDeepNoArrays;
-var loggersModule = require('./loggers');
+var loggersModule = require("./loggers");
lib.log = loggersModule.log;
lib.warn = loggersModule.warn;
lib.error = loggersModule.error;
-lib.notifier = require('./notifier');
+lib.notifier = require("./notifier");
-lib.filterUnique = require('./filter_unique');
-lib.filterVisible = require('./filter_visible');
+lib.filterUnique = require("./filter_unique");
+lib.filterVisible = require("./filter_visible");
-
-lib.cleanNumber = require('./clean_number');
+lib.cleanNumber = require("./clean_number");
/**
* swap x and y of the same attribute in container cont
@@ -90,16 +86,16 @@ lib.cleanNumber = require('./clean_number');
* you can also swap other things than x/y by providing part1 and part2
*/
lib.swapAttrs = function(cont, attrList, part1, part2) {
- if(!part1) part1 = 'x';
- if(!part2) part2 = 'y';
- for(var i = 0; i < attrList.length; i++) {
- var attr = attrList[i],
- xp = lib.nestedProperty(cont, attr.replace('?', part1)),
- yp = lib.nestedProperty(cont, attr.replace('?', part2)),
- temp = xp.get();
- xp.set(yp.get());
- yp.set(temp);
- }
+ if (!part1) part1 = "x";
+ if (!part2) part2 = "y";
+ for (var i = 0; i < attrList.length; i++) {
+ var attr = attrList[i],
+ xp = lib.nestedProperty(cont, attr.replace("?", part1)),
+ yp = lib.nestedProperty(cont, attr.replace("?", part2)),
+ temp = xp.get();
+ xp.set(yp.get());
+ yp.set(temp);
+ }
};
/**
@@ -110,16 +106,16 @@ lib.swapAttrs = function(cont, attrList, part1, part2) {
* return pauseEvent(e);
*/
lib.pauseEvent = function(e) {
- if(e.stopPropagation) e.stopPropagation();
- if(e.preventDefault) e.preventDefault();
- e.cancelBubble = true;
- return false;
+ if (e.stopPropagation) e.stopPropagation();
+ if (e.preventDefault) e.preventDefault();
+ e.cancelBubble = true;
+ return false;
};
// constrain - restrict a number v to be between v0 and v1
lib.constrain = function(v, v0, v1) {
- if(v0 > v1) return Math.max(v1, Math.min(v0, v));
- return Math.max(v0, Math.min(v1, v));
+ if (v0 > v1) return Math.max(v1, Math.min(v0, v));
+ return Math.max(v0, Math.min(v1, v));
};
/**
@@ -128,15 +124,17 @@ lib.constrain = function(v, v0, v1) {
* takes optional padding pixels
*/
lib.bBoxIntersect = function(a, b, pad) {
- pad = pad || 0;
- return (a.left <= b.right + pad &&
- b.left <= a.right + pad &&
- a.top <= b.bottom + pad &&
- b.top <= a.bottom + pad);
+ pad = pad || 0;
+ return a.left <= b.right + pad &&
+ b.left <= a.right + pad &&
+ a.top <= b.bottom + pad &&
+ b.top <= a.bottom + pad;
};
// minor convenience/performance booster for d3...
-lib.identity = function(d) { return d; };
+lib.identity = function(d) {
+ return d;
+};
// minor convenience helper
lib.noop = function() {};
@@ -151,55 +149,55 @@ lib.noop = function() {};
* x1, x2: optional extra args
*/
lib.simpleMap = function(array, func, x1, x2) {
- var len = array.length,
- out = new Array(len);
- for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2);
- return out;
+ var len = array.length, out = new Array(len);
+ for (var i = 0; i < len; i++) {
+ out[i] = func(array[i], x1, x2);
+ }
+ return out;
};
// random string generator
lib.randstr = function randstr(existing, bits, base) {
- /*
+ /*
* Include number of bits, the base of the string you want
* and an optional array of existing strings to avoid.
*/
- if(!base) base = 16;
- if(bits === undefined) bits = 24;
- if(bits <= 0) return '0';
-
- var digits = Math.log(Math.pow(2, bits)) / Math.log(base),
- res = '',
- i,
- b,
- x;
-
- for(i = 2; digits === Infinity; i *= 2) {
- digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
- }
-
- var rem = digits - Math.floor(digits);
-
- for(i = 0; i < Math.floor(digits); i++) {
- x = Math.floor(Math.random() * base).toString(base);
- res = x + res;
- }
-
- if(rem) {
- b = Math.pow(base, rem);
- x = Math.floor(Math.random() * b).toString(base);
- res = x + res;
- }
-
- var parsed = parseInt(res, base);
- if((existing && (existing.indexOf(res) > -1)) ||
- (parsed !== Infinity && parsed >= Math.pow(2, bits))) {
- return randstr(existing, bits, base);
- }
- else return res;
+ if (!base) base = 16;
+ if (bits === undefined) bits = 24;
+ if (bits <= 0) return "0";
+
+ var digits = Math.log(Math.pow(2, bits)) / Math.log(base), res = "", i, b, x;
+
+ for (i = 2; digits === Infinity; i *= 2) {
+ digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
+ }
+
+ var rem = digits - Math.floor(digits);
+
+ for (i = 0; i < Math.floor(digits); i++) {
+ x = Math.floor(Math.random() * base).toString(base);
+ res = x + res;
+ }
+
+ if (rem) {
+ b = Math.pow(base, rem);
+ x = Math.floor(Math.random() * b).toString(base);
+ res = x + res;
+ }
+
+ var parsed = parseInt(res, base);
+ if (
+ existing && existing.indexOf(res) > -1 ||
+ parsed !== Infinity && parsed >= Math.pow(2, bits)
+ ) {
+ return randstr(existing, bits, base);
+ } else {
+ return res;
+ }
};
lib.OptionControl = function(opt, optname) {
- /*
+ /*
* An environment to contain all option setters and
* getters that collectively modify opts.
*
@@ -208,20 +206,20 @@ lib.OptionControl = function(opt, optname) {
*
* See FitOpts for example of usage
*/
- if(!opt) opt = {};
- if(!optname) optname = 'opt';
+ if (!opt) opt = {};
+ if (!optname) optname = "opt";
- var self = {};
- self.optionList = [];
+ var self = {};
+ self.optionList = [];
- self._newoption = function(optObj) {
- optObj[optname] = opt;
- self[optObj.name] = optObj;
- self.optionList.push(optObj);
- };
+ self._newoption = function(optObj) {
+ optObj[optname] = opt;
+ self[optObj.name] = optObj;
+ self.optionList.push(optObj);
+ };
- self['_' + optname] = opt;
- return self;
+ self["_" + optname] = opt;
+ return self;
};
/**
@@ -230,44 +228,45 @@ lib.OptionControl = function(opt, optname) {
* bounce the ends in, so the output has the same length as the input
*/
lib.smooth = function(arrayIn, FWHM) {
- FWHM = Math.round(FWHM) || 0; // only makes sense for integers
- if(FWHM < 2) return arrayIn;
-
- var alen = arrayIn.length,
- alen2 = 2 * alen,
- wlen = 2 * FWHM - 1,
- w = new Array(wlen),
- arrayOut = new Array(alen),
- i,
- j,
- k,
- v;
-
- // first make the window array
- for(i = 0; i < wlen; i++) {
- w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM);
- }
-
- // now do the convolution
- for(i = 0; i < alen; i++) {
- v = 0;
- for(j = 0; j < wlen; j++) {
- k = i + j + 1 - FWHM;
-
- // multibounce
- if(k < -alen) k -= alen2 * Math.round(k / alen2);
- else if(k >= alen2) k -= alen2 * Math.floor(k / alen2);
-
- // single bounce
- if(k < 0) k = - 1 - k;
- else if(k >= alen) k = alen2 - 1 - k;
-
- v += arrayIn[k] * w[j];
- }
- arrayOut[i] = v;
+ FWHM = Math.round(FWHM) || 0;
+ // only makes sense for integers
+ if (FWHM < 2) return arrayIn;
+
+ var alen = arrayIn.length,
+ alen2 = 2 * alen,
+ wlen = 2 * FWHM - 1,
+ w = new Array(wlen),
+ arrayOut = new Array(alen),
+ i,
+ j,
+ k,
+ v;
+
+ // first make the window array
+ for (i = 0; i < wlen; i++) {
+ w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM);
+ }
+
+ // now do the convolution
+ for (i = 0; i < alen; i++) {
+ v = 0;
+ for (j = 0; j < wlen; j++) {
+ k = i + j + 1 - FWHM;
+
+ // multibounce
+ if (k < -alen) k -= alen2 * Math.round(k / alen2);
+ else if (k >= alen2) k -= alen2 * Math.floor(k / alen2);
+
+ // single bounce
+ if (k < 0) k = -1 - k;
+ else if (k >= alen) k = alen2 - 1 - k;
+
+ v += arrayIn[k] * w[j];
}
+ arrayOut[i] = v;
+ }
- return arrayOut;
+ return arrayOut;
};
/**
@@ -282,59 +281,54 @@ lib.smooth = function(arrayIn, FWHM) {
* that it gets reported
*/
lib.syncOrAsync = function(sequence, arg, finalStep) {
- var ret, fni;
+ var ret, fni;
- function continueAsync() {
- return lib.syncOrAsync(sequence, arg, finalStep);
- }
+ function continueAsync() {
+ return lib.syncOrAsync(sequence, arg, finalStep);
+ }
- while(sequence.length) {
- fni = sequence.splice(0, 1)[0];
- ret = fni(arg);
+ while (sequence.length) {
+ fni = sequence.splice(0, 1)[0];
+ ret = fni(arg);
- if(ret && ret.then) {
- return ret.then(continueAsync)
- .then(undefined, lib.promiseError);
- }
+ if (ret && ret.then) {
+ return ret.then(continueAsync).then(undefined, lib.promiseError);
}
+ }
- return finalStep && finalStep(arg);
+ return finalStep && finalStep(arg);
};
-
/**
* Helper to strip trailing slash, from
* http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash
*/
lib.stripTrailingSlash = function(str) {
- if(str.substr(-1) === '/') return str.substr(0, str.length - 1);
- return str;
+ if (str.substr(-1) === "/") return str.substr(0, str.length - 1);
+ return str;
};
lib.noneOrAll = function(containerIn, containerOut, attrList) {
- /**
+ /**
* some attributes come together, so if you have one of them
* in the input, you should copy the default values of the others
* to the input as well.
*/
- if(!containerIn) return;
+ if (!containerIn) return;
- var hasAny = false,
- hasAll = true,
- i,
- val;
+ var hasAny = false, hasAll = true, i, val;
- for(i = 0; i < attrList.length; i++) {
- val = containerIn[attrList[i]];
- if(val !== undefined && val !== null) hasAny = true;
- else hasAll = false;
- }
+ for (i = 0; i < attrList.length; i++) {
+ val = containerIn[attrList[i]];
+ if (val !== undefined && val !== null) hasAny = true;
+ else hasAll = false;
+ }
- if(hasAny && !hasAll) {
- for(i = 0; i < attrList.length; i++) {
- containerIn[attrList[i]] = containerOut[attrList[i]];
- }
+ if (hasAny && !hasAll) {
+ for (i = 0; i < attrList.length; i++) {
+ containerIn[attrList[i]] = containerOut[attrList[i]];
}
+ }
};
/**
@@ -349,16 +343,18 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) {
*
*/
lib.pushUnique = function(array, item) {
- if(item && array.indexOf(item) === -1) array.push(item);
+ if (item && array.indexOf(item) === -1) array.push(item);
- return array;
+ return array;
};
lib.mergeArray = function(traceAttr, cd, cdAttr) {
- if(Array.isArray(traceAttr)) {
- var imax = Math.min(traceAttr.length, cd.length);
- for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i];
+ if (Array.isArray(traceAttr)) {
+ var imax = Math.min(traceAttr.length, cd.length);
+ for (var i = 0; i < imax; i++) {
+ cd[i][cdAttr] = traceAttr[i];
}
+ }
};
/**
@@ -368,63 +364,67 @@ lib.mergeArray = function(traceAttr, cd, cdAttr) {
* obj2 is assumed to already be clean of these things (including no arrays)
*/
lib.minExtend = function(obj1, obj2) {
- var objOut = {};
- if(typeof obj2 !== 'object') obj2 = {};
- var arrayLen = 3,
- keys = Object.keys(obj1),
- i,
- k,
- v;
- for(i = 0; i < keys.length; i++) {
- k = keys[i];
- v = obj1[k];
- if(k.charAt(0) === '_' || typeof v === 'function') continue;
- else if(k === 'module') objOut[k] = v;
- else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen);
- else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);
- else objOut[k] = v;
+ var objOut = {};
+ if (typeof obj2 !== "object") obj2 = {};
+ var arrayLen = 3, keys = Object.keys(obj1), i, k, v;
+ for (i = 0; i < keys.length; i++) {
+ k = keys[i];
+ v = obj1[k];
+ if (k.charAt(0) === "_" || typeof v === "function") {
+ continue;
+ } else if (k === "module") {
+ objOut[k] = v;
+ } else if (Array.isArray(v)) {
+ objOut[k] = v.slice(0, arrayLen);
+ } else if (v && typeof v === "object") {
+ objOut[k] = lib.minExtend(obj1[k], obj2[k]);
+ } else {
+ objOut[k] = v;
}
-
- keys = Object.keys(obj2);
- for(i = 0; i < keys.length; i++) {
- k = keys[i];
- v = obj2[k];
- if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') {
- objOut[k] = v;
- }
+ }
+
+ keys = Object.keys(obj2);
+ for (i = 0; i < keys.length; i++) {
+ k = keys[i];
+ v = obj2[k];
+ if (
+ typeof v !== "object" || !(k in objOut) || typeof objOut[k] !== "object"
+ ) {
+ objOut[k] = v;
}
+ }
- return objOut;
+ return objOut;
};
lib.titleCase = function(s) {
- return s.charAt(0).toUpperCase() + s.substr(1);
+ return s.charAt(0).toUpperCase() + s.substr(1);
};
lib.containsAny = function(s, fragments) {
- for(var i = 0; i < fragments.length; i++) {
- if(s.indexOf(fragments[i]) !== -1) return true;
- }
- return false;
+ for (var i = 0; i < fragments.length; i++) {
+ if (s.indexOf(fragments[i]) !== -1) return true;
+ }
+ return false;
};
// get the parent Plotly plot of any element. Whoo jquery-free tree climbing!
lib.getPlotDiv = function(el) {
- for(; el && el.removeAttribute; el = el.parentNode) {
- if(lib.isPlotDiv(el)) return el;
- }
+ for (; el && el.removeAttribute; el = el.parentNode) {
+ if (lib.isPlotDiv(el)) return el;
+ }
};
lib.isPlotDiv = function(el) {
- var el3 = d3.select(el);
- return el3.node() instanceof HTMLElement &&
- el3.size() &&
- el3.classed('js-plotly-plot');
+ var el3 = d3.select(el);
+ return el3.node() instanceof HTMLElement &&
+ el3.size() &&
+ el3.classed("js-plotly-plot");
};
lib.removeElement = function(el) {
- var elParent = el && el.parentNode;
- if(elParent) elParent.removeChild(el);
+ var elParent = el && el.parentNode;
+ if (elParent) elParent.removeChild(el);
};
/**
@@ -433,26 +433,26 @@ lib.removeElement = function(el) {
* by all calls to this function
*/
lib.addStyleRule = function(selector, styleString) {
- if(!lib.styleSheet) {
- var style = document.createElement('style');
- // WebKit hack :(
- style.appendChild(document.createTextNode(''));
- document.head.appendChild(style);
- lib.styleSheet = style.sheet;
- }
- var styleSheet = lib.styleSheet;
-
- if(styleSheet.insertRule) {
- styleSheet.insertRule(selector + '{' + styleString + '}', 0);
- }
- else if(styleSheet.addRule) {
- styleSheet.addRule(selector, styleString, 0);
- }
- else lib.warn('addStyleRule failed');
+ if (!lib.styleSheet) {
+ var style = document.createElement("style");
+ // WebKit hack :(
+ style.appendChild(document.createTextNode(""));
+ document.head.appendChild(style);
+ lib.styleSheet = style.sheet;
+ }
+ var styleSheet = lib.styleSheet;
+
+ if (styleSheet.insertRule) {
+ styleSheet.insertRule(selector + "{" + styleString + "}", 0);
+ } else if (styleSheet.addRule) {
+ styleSheet.addRule(selector, styleString, 0);
+ } else {
+ lib.warn("addStyleRule failed");
+ }
};
lib.isIE = function() {
- return typeof window.navigator.msSaveBlob !== 'undefined';
+ return typeof window.navigator.msSaveBlob !== "undefined";
};
/**
@@ -460,10 +460,9 @@ lib.isIE = function() {
* because it doesn't handle instanceof like modern browsers
*/
lib.isD3Selection = function(obj) {
- return obj && (typeof obj.classed === 'function');
+ return obj && typeof obj.classed === "function";
};
-
/**
* Converts a string path to an object.
*
@@ -480,42 +479,39 @@ lib.isD3Selection = function(obj) {
* @return {Object} the constructed object with a full nested path
*/
lib.objectFromPath = function(path, value) {
- var keys = path.split('.'),
- tmpObj,
- obj = tmpObj = {};
+ var keys = path.split("."), tmpObj, obj = tmpObj = {};
- for(var i = 0; i < keys.length; i++) {
- var key = keys[i];
- var el = null;
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var el = null;
- var parts = keys[i].match(/(.*)\[([0-9]+)\]/);
+ var parts = keys[i].match(/(.*)\[([0-9]+)\]/);
- if(parts) {
- key = parts[1];
- el = parts[2];
+ if (parts) {
+ key = parts[1];
+ el = parts[2];
- tmpObj = tmpObj[key] = [];
+ tmpObj = tmpObj[key] = [];
- if(i === keys.length - 1) {
- tmpObj[el] = value;
- } else {
- tmpObj[el] = {};
- }
+ if (i === keys.length - 1) {
+ tmpObj[el] = value;
+ } else {
+ tmpObj[el] = {};
+ }
- tmpObj = tmpObj[el];
- } else {
+ tmpObj = tmpObj[el];
+ } else {
+ if (i === keys.length - 1) {
+ tmpObj[key] = value;
+ } else {
+ tmpObj[key] = {};
+ }
- if(i === keys.length - 1) {
- tmpObj[key] = value;
- } else {
- tmpObj[key] = {};
- }
-
- tmpObj = tmpObj[key];
- }
+ tmpObj = tmpObj[key];
}
+ }
- return obj;
+ return obj;
};
/**
@@ -541,7 +537,6 @@ lib.objectFromPath = function(path, value) {
* lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4})
* => { marker: [null, {range: 4}] }
*/
-
// Store this to avoid recompiling regex on *every* prop since this may happen many
// many times for animations. Could maybe be inside the function. Not sure about
// scoping vs. recompilation tradeoff, but at least it's not just inlining it into
@@ -550,59 +545,65 @@ var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/;
var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/;
lib.expandObjectPaths = function(data) {
- var match, key, prop, datum, idx, dest, trailingPath;
- if(typeof data === 'object' && !Array.isArray(data)) {
- for(key in data) {
- if(data.hasOwnProperty(key)) {
- if((match = key.match(dottedPropertyRegex))) {
- datum = data[key];
- prop = match[1];
-
- delete data[key];
-
- data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
- } else if((match = key.match(indexedPropertyRegex))) {
- datum = data[key];
-
- prop = match[1];
- idx = parseInt(match[2]);
-
- delete data[key];
-
- data[prop] = data[prop] || [];
-
- if(match[3] === '.') {
- // This is the case where theere are subsequent properties into which
- // we must recurse, e.g. transforms[0].value
- trailingPath = match[4];
- dest = data[prop][idx] = data[prop][idx] || {};
-
- // NB: Extend deep no arrays prevents this from working on multiple
- // nested properties in the same object, e.g.
- //
- // {
- // foo[0].bar[1].range
- // foo[0].bar[0].range
- // }
- //
- // In this case, the extendDeepNoArrays will overwrite one array with
- // the other, so that both properties *will not* be present in the
- // result. Fixing this would require a more intelligent tracking
- // of changes and merging than extendDeepNoArrays currently accomplishes.
- lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum)));
- } else {
- // This is the case where this property is the end of the line,
- // e.g. xaxis.range[0]
- data[prop][idx] = lib.expandObjectPaths(datum);
- }
- } else {
- data[key] = lib.expandObjectPaths(data[key]);
- }
- }
+ var match, key, prop, datum, idx, dest, trailingPath;
+ if (typeof data === "object" && !Array.isArray(data)) {
+ for (key in data) {
+ if (data.hasOwnProperty(key)) {
+ if (match = key.match(dottedPropertyRegex)) {
+ datum = data[key];
+ prop = match[1];
+
+ delete data[key];
+
+ data[prop] = lib.extendDeepNoArrays(
+ data[prop] || {},
+ lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]
+ );
+ } else if (match = key.match(indexedPropertyRegex)) {
+ datum = data[key];
+
+ prop = match[1];
+ idx = parseInt(match[2]);
+
+ delete data[key];
+
+ data[prop] = data[prop] || [];
+
+ if (match[3] === ".") {
+ // This is the case where theere are subsequent properties into which
+ // we must recurse, e.g. transforms[0].value
+ trailingPath = match[4];
+ dest = data[prop][idx] = data[prop][idx] || {};
+
+ // NB: Extend deep no arrays prevents this from working on multiple
+ // nested properties in the same object, e.g.
+ //
+ // {
+ // foo[0].bar[1].range
+ // foo[0].bar[0].range
+ // }
+ //
+ // In this case, the extendDeepNoArrays will overwrite one array with
+ // the other, so that both properties *will not* be present in the
+ // result. Fixing this would require a more intelligent tracking
+ // of changes and merging than extendDeepNoArrays currently accomplishes.
+ lib.extendDeepNoArrays(
+ dest,
+ lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum))
+ );
+ } else {
+ // This is the case where this property is the end of the line,
+ // e.g. xaxis.range[0]
+ data[prop][idx] = lib.expandObjectPaths(datum);
+ }
+ } else {
+ data[key] = lib.expandObjectPaths(data[key]);
}
+ }
}
+ }
- return data;
+ return data;
};
/**
@@ -627,30 +628,30 @@ lib.expandObjectPaths = function(data) {
* @return {string} the value that has been separated
*/
lib.numSeparate = function(value, separators, separatethousands) {
- if(!separatethousands) separatethousands = false;
+ if (!separatethousands) separatethousands = false;
- if(typeof separators !== 'string' || separators.length === 0) {
- throw new Error('Separator string required for formatting!');
- }
+ if (typeof separators !== "string" || separators.length === 0) {
+ throw new Error("Separator string required for formatting!");
+ }
- if(typeof value === 'number') {
- value = String(value);
- }
+ if (typeof value === "number") {
+ value = String(value);
+ }
- var thousandsRe = /(\d+)(\d{3})/,
- decimalSep = separators.charAt(0),
- thouSep = separators.charAt(1);
+ var thousandsRe = /(\d+)(\d{3})/,
+ decimalSep = separators.charAt(0),
+ thouSep = separators.charAt(1);
- var x = value.split('.'),
- x1 = x[0],
- x2 = x.length > 1 ? decimalSep + x[1] : '';
+ var x = value.split("."),
+ x1 = x[0],
+ x2 = x.length > 1 ? decimalSep + x[1] : "";
- // Years are ignored for thousands separators
- if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) {
- while(thousandsRe.test(x1)) {
- x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2');
- }
+ // Years are ignored for thousands separators
+ if (thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) {
+ while (thousandsRe.test(x1)) {
+ x1 = x1.replace(thousandsRe, "$1" + thouSep + "$2");
}
+ }
- return x1 + x2;
+ return x1 + x2;
};
diff --git a/src/lib/is_array.js b/src/lib/is_array.js
index cda78eeb627..c0b7362fd6a 100644
--- a/src/lib/is_array.js
+++ b/src/lib/is_array.js
@@ -5,18 +5,20 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/**
* Return true for arrays, whether they're untyped or not.
*/
// IE9 fallback
-var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ?
- {isView: function() { return false; }} :
- ArrayBuffer;
+var ab = typeof ArrayBuffer === "undefined" || !ArrayBuffer.isView
+ ? {
+ isView: function() {
+ return false;
+ }
+ }
+ : ArrayBuffer;
module.exports = function isArray(a) {
- return Array.isArray(a) || ab.isView(a);
+ return Array.isArray(a) || ab.isView(a);
};
diff --git a/src/lib/is_plain_object.js b/src/lib/is_plain_object.js
index d114e022d2f..b22eee1faa2 100644
--- a/src/lib/is_plain_object.js
+++ b/src/lib/is_plain_object.js
@@ -5,23 +5,17 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing
module.exports = function isPlainObject(obj) {
+ // We need to be a little less strict in the `imagetest` container because
+ // of how async image requests are handled.
+ //
+ // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
+ if (window && window.process && window.process.versions) {
+ return Object.prototype.toString.call(obj) === "[object Object]";
+ }
- // We need to be a little less strict in the `imagetest` container because
- // of how async image requests are handled.
- //
- // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
- if(window && window.process && window.process.versions) {
- return Object.prototype.toString.call(obj) === '[object Object]';
- }
-
- return (
- Object.prototype.toString.call(obj) === '[object Object]' &&
- Object.getPrototypeOf(obj) === Object.prototype
- );
+ return Object.prototype.toString.call(obj) === "[object Object]" &&
+ Object.getPrototypeOf(obj) === Object.prototype;
};
diff --git a/src/lib/loggers.js b/src/lib/loggers.js
index 428f053e000..ff51d598c98 100644
--- a/src/lib/loggers.js
+++ b/src/lib/loggers.js
@@ -5,12 +5,10 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/* eslint-disable no-console */
-var config = require('../plot_api/plot_config');
+var config = require("../plot_api/plot_config");
var loggers = module.exports = {};
@@ -19,41 +17,40 @@ var loggers = module.exports = {};
* debugging tools
* ------------------------------------------
*/
-
loggers.log = function() {
- if(config.logging > 1) {
- var messages = ['LOG:'];
+ if (config.logging > 1) {
+ var messages = ["LOG:"];
- for(var i = 0; i < arguments.length; i++) {
- messages.push(arguments[i]);
- }
-
- apply(console.trace || console.log, messages);
+ for (var i = 0; i < arguments.length; i++) {
+ messages.push(arguments[i]);
}
+
+ apply(console.trace || console.log, messages);
+ }
};
loggers.warn = function() {
- if(config.logging > 0) {
- var messages = ['WARN:'];
+ if (config.logging > 0) {
+ var messages = ["WARN:"];
- for(var i = 0; i < arguments.length; i++) {
- messages.push(arguments[i]);
- }
-
- apply(console.trace || console.log, messages);
+ for (var i = 0; i < arguments.length; i++) {
+ messages.push(arguments[i]);
}
+
+ apply(console.trace || console.log, messages);
+ }
};
loggers.error = function() {
- if(config.logging > 0) {
- var messages = ['ERROR:'];
-
- for(var i = 0; i < arguments.length; i++) {
- messages.push(arguments[i]);
- }
+ if (config.logging > 0) {
+ var messages = ["ERROR:"];
- apply(console.error, messages);
+ for (var i = 0; i < arguments.length; i++) {
+ messages.push(arguments[i]);
}
+
+ apply(console.error, messages);
+ }
};
/*
@@ -61,12 +58,11 @@ loggers.error = function() {
* apply like other functions do
*/
function apply(f, args) {
- if(f.apply) {
- f.apply(f, args);
- }
- else {
- for(var i = 0; i < args.length; i++) {
- f(args[i]);
- }
+ if (f.apply) {
+ f.apply(f, args);
+ } else {
+ for (var i = 0; i < args.length; i++) {
+ f(args[i]);
}
+ }
}
diff --git a/src/lib/matrix.js b/src/lib/matrix.js
index 2429195de05..84968de207c 100644
--- a/src/lib/matrix.js
+++ b/src/lib/matrix.js
@@ -5,15 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
+"use strict";
exports.init2dArray = function(rowLength, colLength) {
- var array = new Array(rowLength);
- for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength);
- return array;
+ var array = new Array(rowLength);
+ for (var i = 0; i < rowLength; i++) {
+ array[i] = new Array(colLength);
+ }
+ return array;
};
/**
@@ -22,87 +20,93 @@ exports.init2dArray = function(rowLength, colLength) {
* transposing-a-2d-array-in-javascript
*/
exports.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];
+ 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;
+ return t;
};
// our own dot function so that we don't need to include numeric
exports.dot = function(x, y) {
- if(!(x.length && y.length) || x.length !== y.length) return null;
+ if (!(x.length && y.length) || x.length !== y.length) return null;
- var len = x.length,
- out,
- i;
+ 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] = exports.dot(x[i], y);
+ if (x[0].length) {
+ // mat-vec or mat-mat
+ out = new Array(len);
+ for (i = 0; i < len; i++) {
+ out[i] = exports.dot(x[i], y);
}
- else if(y[0].length) {
- // vec-mat
- var yTranspose = exports.transposeRagged(y);
- out = new Array(yTranspose.length);
- for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
+ } else if (y[0].length) {
+ // vec-mat
+ var yTranspose = exports.transposeRagged(y);
+ out = new Array(yTranspose.length);
+ for (i = 0; i < yTranspose.length; i++) {
+ out[i] = exports.dot(x, yTranspose[i]);
}
- else {
- // vec-vec
- out = 0;
- for(i = 0; i < len; i++) out += x[i] * y[i];
+ } else {
+ // vec-vec
+ out = 0;
+ for (i = 0; i < len; i++) {
+ out += x[i] * y[i];
}
+ }
- return out;
+ return out;
};
// translate by (x,y)
exports.translationMatrix = function(x, y) {
- return [[1, 0, x], [0, 1, y], [0, 0, 1]];
+ return [[1, 0, x], [0, 1, y], [0, 0, 1]];
};
// rotate by alpha around (0,0)
exports.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]];
+ 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)
exports.rotationXYMatrix = function(a, x, y) {
- return exports.dot(
- exports.dot(exports.translationMatrix(x, y),
- exports.rotationMatrix(a)),
- exports.translationMatrix(-x, -y));
+ return exports.dot(
+ exports.dot(exports.translationMatrix(x, y), exports.rotationMatrix(a)),
+ exports.translationMatrix(-x, -y)
+ );
};
// applies a 2D transformation matrix to either x and y params or an [x,y] array
exports.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 exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
- };
+ 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 exports.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)
exports.apply2DTransform2 = function(transform) {
- var at = exports.apply2DTransform(transform);
- return function(xys) {
- return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
- };
+ var at = exports.apply2DTransform(transform);
+ return function(xys) {
+ return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
+ };
};
diff --git a/src/lib/mod.js b/src/lib/mod.js
index 6ddf24e2563..c3951c6d6d1 100644
--- a/src/lib/mod.js
+++ b/src/lib/mod.js
@@ -5,14 +5,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/**
* sanitized modulus function that always returns in the range [0, d)
* rather than (-d, 0] if v is negative
*/
module.exports = function mod(v, d) {
- var out = v % d;
- return out < 0 ? out + d : out;
+ var out = v % d;
+ return out < 0 ? out + d : out;
};
diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js
index a00cd17137a..b630cad00df 100644
--- a/src/lib/nested_property.js
+++ b/src/lib/nested_property.js
@@ -5,12 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-var isArray = require('./is_array');
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var isArray = require("./is_array");
/**
* convert a string s (such as 'xaxis.range[0]')
@@ -27,89 +24,85 @@ var isArray = require('./is_array');
* but you can do nestedProperty(obj, 'arr').set([5, 5, 5])
*/
module.exports = function nestedProperty(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 < indices.length; i++) {
- j++;
- propParts.splice(j, 0, Number(indices[i]));
- }
- }
+ 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];
+ } else if (
+ j === 0 // allow propStr to start with bracketed array indices
+ ) {
+ propParts.splice(0, 1);
+ } else {
+ throw "bad property string";
+ }
+
+ indices = indexed[2].substr(1, indexed[2].length - 2).split("][");
+
+ for (i = 0; i < indices.length; i++) {
j++;
+ propParts.splice(j, 0, Number(indices[i]));
+ }
}
-
- if(typeof container !== 'object') {
- return badContainer(container, propStr, propParts);
- }
-
- return {
- set: npSet(container, propParts),
- get: npGet(container, propParts),
- astr: propStr,
- parts: propParts,
- obj: container
- };
+ j++;
+ }
+
+ if (typeof container !== "object") {
+ return badContainer(container, propStr, propParts);
+ }
+
+ return {
+ set: npSet(container, propParts),
+ get: npGet(container, propParts),
+ astr: propStr,
+ parts: propParts,
+ obj: container
+ };
};
function npGet(cont, parts) {
- return function() {
- var curCont = cont,
- curPart,
- allSame,
- out,
- i,
- j;
-
- for(i = 0; i < parts.length - 1; i++) {
- curPart = parts[i];
- if(curPart === -1) {
- allSame = true;
- out = [];
- for(j = 0; j < curCont.length; j++) {
- out[j] = npGet(curCont[j], parts.slice(i + 1))();
- if(out[j] !== out[0]) allSame = false;
- }
- return allSame ? out[0] : out;
- }
- if(typeof curPart === 'number' && !isArray(curCont)) {
- return undefined;
- }
- curCont = curCont[curPart];
- if(typeof curCont !== 'object' || curCont === null) {
- return undefined;
- }
+ return function() {
+ var curCont = cont, curPart, allSame, out, i, j;
+
+ for (i = 0; i < parts.length - 1; i++) {
+ curPart = parts[i];
+ if (curPart === -1) {
+ allSame = true;
+ out = [];
+ for (j = 0; j < curCont.length; j++) {
+ out[j] = npGet(curCont[j], parts.slice(i + 1))();
+ if (out[j] !== out[0]) allSame = false;
}
+ return allSame ? out[0] : out;
+ }
+ if (typeof curPart === "number" && !isArray(curCont)) {
+ return undefined;
+ }
+ curCont = curCont[curPart];
+ if (typeof curCont !== "object" || curCont === null) {
+ return undefined;
+ }
+ }
- // only hit this if parts.length === 1
- if(typeof curCont !== 'object' || curCont === null) return undefined;
+ // only hit this if parts.length === 1
+ if (typeof curCont !== "object" || curCont === null) return undefined;
- out = curCont[parts[i]];
- if(out === null) return undefined;
- return out;
- };
+ out = curCont[parts[i]];
+ if (out === null) return undefined;
+ return out;
+ };
}
/*
@@ -119,77 +112,77 @@ function npGet(cont, parts) {
* AND the replacement value is an array.
*/
function isDataArray(val, key) {
+ var containers = ["annotations", "shapes", "range", "domain", "buttons"],
+ isNotAContainer = containers.indexOf(key) === -1;
- var containers = ['annotations', 'shapes', 'range', 'domain', 'buttons'],
- isNotAContainer = containers.indexOf(key) === -1;
-
- return isArray(val) && isNotAContainer;
+ return isArray(val) && isNotAContainer;
}
function npSet(cont, parts) {
- return function(val) {
- var curCont = cont,
- containerLevels = [cont],
- toDelete = emptyObj(val) && !isDataArray(val, parts[parts.length - 1]),
- curPart,
- i;
-
- for(i = 0; i < parts.length - 1; i++) {
- curPart = parts[i];
-
- if(typeof curPart === 'number' && !isArray(curCont)) {
- throw 'array index but container is not an array';
- }
-
- // handle special -1 array index
- if(curPart === -1) {
- toDelete = !setArrayAll(curCont, parts.slice(i + 1), val);
- if(toDelete) break;
- else return;
- }
-
- if(!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) {
- break;
- }
-
- curCont = curCont[curPart];
-
- if(typeof curCont !== 'object' || curCont === null) {
- throw 'container is not an object';
- }
-
- containerLevels.push(curCont);
- }
+ return function(val) {
+ var curCont = cont,
+ containerLevels = [cont],
+ toDelete = emptyObj(val) && !isDataArray(val, parts[parts.length - 1]),
+ curPart,
+ i;
+
+ for (i = 0; i < parts.length - 1; i++) {
+ curPart = parts[i];
+
+ if (typeof curPart === "number" && !isArray(curCont)) {
+ throw "array index but container is not an array";
+ }
+
+ // handle special -1 array index
+ if (curPart === -1) {
+ toDelete = !setArrayAll(curCont, parts.slice(i + 1), val);
+ if (toDelete) break;
+ else return;
+ }
+
+ if (!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) {
+ break;
+ }
+
+ curCont = curCont[curPart];
+
+ if (typeof curCont !== "object" || curCont === null) {
+ throw "container is not an object";
+ }
+
+ containerLevels.push(curCont);
+ }
- if(toDelete) {
- if(i === parts.length - 1) delete curCont[parts[i]];
- pruneContainers(containerLevels);
- }
- else curCont[parts[i]] = val;
- };
+ if (toDelete) {
+ if (i === parts.length - 1) delete curCont[parts[i]];
+ pruneContainers(containerLevels);
+ } else {
+ curCont[parts[i]] = val;
+ }
+ };
}
// handle special -1 array index
function setArrayAll(containerArray, innerParts, val) {
- var arrayVal = isArray(val),
- allSet = true,
- thisVal = val,
- deleteThis = arrayVal ? false : emptyObj(val),
- firstPart = innerParts[0],
- i;
-
- for(i = 0; i < containerArray.length; i++) {
- if(arrayVal) {
- thisVal = val[i % val.length];
- deleteThis = emptyObj(thisVal);
- }
- if(deleteThis) allSet = false;
- if(!checkNewContainer(containerArray, i, firstPart, deleteThis)) {
- continue;
- }
- npSet(containerArray[i], innerParts)(thisVal);
+ var arrayVal = isArray(val),
+ allSet = true,
+ thisVal = val,
+ deleteThis = arrayVal ? false : emptyObj(val),
+ firstPart = innerParts[0],
+ i;
+
+ for (i = 0; i < containerArray.length; i++) {
+ if (arrayVal) {
+ thisVal = val[i % val.length];
+ deleteThis = emptyObj(thisVal);
}
- return allSet;
+ if (deleteThis) allSet = false;
+ if (!checkNewContainer(containerArray, i, firstPart, deleteThis)) {
+ continue;
+ }
+ npSet(containerArray[i], innerParts)(thisVal);
+ }
+ return allSet;
}
/**
@@ -198,58 +191,63 @@ function setArrayAll(containerArray, innerParts, val) {
* because we're only deleting an attribute
*/
function checkNewContainer(container, part, nextPart, toDelete) {
- if(container[part] === undefined) {
- if(toDelete) return false;
+ if (container[part] === undefined) {
+ if (toDelete) return false;
- if(typeof nextPart === 'number') container[part] = [];
- else container[part] = {};
- }
- return true;
+ if (typeof nextPart === "number") container[part] = [];
+ else container[part] = {};
+ }
+ return true;
}
function pruneContainers(containerLevels) {
- var i,
- j,
- curCont,
- keys,
- remainingKeys;
- for(i = containerLevels.length - 1; i >= 0; i--) {
- curCont = containerLevels[i];
- remainingKeys = false;
- if(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;
- }
+ var i, j, curCont, keys, remainingKeys;
+ for (i = containerLevels.length - 1; i >= 0; i--) {
+ curCont = containerLevels[i];
+ remainingKeys = false;
+ if (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;
- }
+ }
+ } 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;
+ }
}
+ if (remainingKeys) return;
+ }
}
function emptyObj(obj) {
- if(obj === undefined || obj === null) return true;
- if(typeof obj !== 'object') return false; // any plain value
- if(isArray(obj)) return !obj.length; // []
- return !Object.keys(obj).length; // {}
+ if (obj === undefined || obj === null) return true;
+ if (typeof obj !== "object") return false;
+ // any plain value
+ if (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
- };
+ 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
index e7443afd7a0..25187c6ec36 100644
--- a/src/lib/notifier.js
+++ b/src/lib/notifier.js
@@ -5,12 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
var NOTEDATA = [];
@@ -22,59 +19,62 @@ var NOTEDATA = [];
* @return {undefined} this function does not return a value
*/
module.exports = function(text, displayLength) {
- if(NOTEDATA.indexOf(text) !== -1) return;
+ if (NOTEDATA.indexOf(text) !== -1) return;
- NOTEDATA.push(text);
+ NOTEDATA.push(text);
- var ts = 1000;
- if(isNumeric(displayLength)) ts = displayLength;
- else if(displayLength === 'long') ts = 3000;
+ 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 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);
+ 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();
- });
- }
+ 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);
+ 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("button")
+ .classed("notifier-close", true)
+ .html("×")
+ .on("click", function() {
+ note.transition().call(killNote);
+ });
- var p = note.append('p');
- var lines = thisText.split(/
/g);
- for(var i = 0; i < lines.length; i++) {
- if(i) p.append('br');
- p.append('span').text(lines[i]);
- }
+ var p = note.append("p");
+ var lines = thisText.split(/
/g);
+ for (var i = 0; i < lines.length; i++) {
+ if (i) p.append("br");
+ p.append("span").text(lines[i]);
+ }
- note.transition()
- .duration(700)
- .style('opacity', 1)
- .transition()
- .delay(ts)
- .call(killNote);
- });
+ note
+ .transition()
+ .duration(700)
+ .style("opacity", 1)
+ .transition()
+ .delay(ts)
+ .call(killNote);
+ });
};
diff --git a/src/lib/override_cursor.js b/src/lib/override_cursor.js
index ebbd290951e..27d25df7ae9 100644
--- a/src/lib/override_cursor.js
+++ b/src/lib/override_cursor.js
@@ -5,14 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var setCursor = require("./setcursor");
-
-'use strict';
-
-var setCursor = require('./setcursor');
-
-var STASHATTR = 'data-savedcursor';
-var NO_CURSOR = '!!';
+var STASHATTR = "data-savedcursor";
+var NO_CURSOR = "!!";
/*
* works with our CSS cursor classes (see css/_cursor.scss)
@@ -21,27 +18,25 @@ var NO_CURSOR = '!!';
* omit cursor to revert to the previously set value.
*/
module.exports = function overrideCursor(el3, csr) {
- var savedCursor = el3.attr(STASHATTR);
- if(csr) {
- if(!savedCursor) {
- var classes = (el3.attr('class') || '').split(' ');
- for(var i = 0; i < classes.length; i++) {
- var cls = classes[i];
- if(cls.indexOf('cursor-') === 0) {
- el3.attr(STASHATTR, cls.substr(7))
- .classed(cls, false);
- }
- }
- if(!el3.attr(STASHATTR)) {
- el3.attr(STASHATTR, NO_CURSOR);
- }
+ var savedCursor = el3.attr(STASHATTR);
+ if (csr) {
+ if (!savedCursor) {
+ var classes = (el3.attr("class") || "").split(" ");
+ for (var i = 0; i < classes.length; i++) {
+ var cls = classes[i];
+ if (cls.indexOf("cursor-") === 0) {
+ el3.attr(STASHATTR, cls.substr(7)).classed(cls, false);
}
- setCursor(el3, csr);
+ }
+ if (!el3.attr(STASHATTR)) {
+ el3.attr(STASHATTR, NO_CURSOR);
+ }
}
- else if(savedCursor) {
- el3.attr(STASHATTR, null);
+ setCursor(el3, csr);
+ } else if (savedCursor) {
+ el3.attr(STASHATTR, null);
- if(savedCursor === NO_CURSOR) setCursor(el3);
- else setCursor(el3, savedCursor);
- }
+ if (savedCursor === NO_CURSOR) setCursor(el3);
+ else setCursor(el3, savedCursor);
+ }
};
diff --git a/src/lib/polygon.js b/src/lib/polygon.js
index befd593e275..59a94cd29ca 100644
--- a/src/lib/polygon.js
+++ b/src/lib/polygon.js
@@ -5,11 +5,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var dot = require('./matrix').dot;
+"use strict";
+var dot = require("./matrix").dot;
var polygon = module.exports = {};
@@ -30,133 +27,139 @@ var polygon = module.exports = {};
* returns boolean: is pt inside the polygon (including on its edges)
*/
polygon.tester = function tester(ptsIn) {
- var pts = ptsIn.slice(),
- xmin = pts[0][0],
- xmax = xmin,
- ymin = pts[0][1],
- ymax = ymin;
-
- pts.push(pts[0]);
- for(var i = 1; i < pts.length; i++) {
- xmin = Math.min(xmin, pts[i][0]);
- xmax = Math.max(xmax, pts[i][0]);
- ymin = Math.min(ymin, pts[i][1]);
- ymax = Math.max(ymax, pts[i][1]);
+ var pts = ptsIn.slice(),
+ xmin = pts[0][0],
+ xmax = xmin,
+ ymin = pts[0][1],
+ ymax = ymin;
+
+ pts.push(pts[0]);
+ for (var i = 1; i < pts.length; i++) {
+ xmin = Math.min(xmin, pts[i][0]);
+ xmax = Math.max(xmax, pts[i][0]);
+ ymin = Math.min(ymin, pts[i][1]);
+ ymax = Math.max(ymax, pts[i][1]);
+ }
+
+ // do we have a rectangle? Handle this here, so we can use the same
+ // tester for the rectangular case without sacrificing speed
+ var isRect = false, rectFirstEdgeTest;
+
+ if (pts.length === 5) {
+ if (pts[0][0] === pts[1][0]) {
+ // vert, horz, vert, horz
+ if (
+ pts[2][0] === pts[3][0] &&
+ pts[0][1] === pts[3][1] &&
+ pts[1][1] === pts[2][1]
+ ) {
+ isRect = true;
+ rectFirstEdgeTest = function(pt) {
+ return pt[0] === pts[0][0];
+ };
+ }
+ } else if (pts[0][1] === pts[1][1]) {
+ // horz, vert, horz, vert
+ if (
+ pts[2][1] === pts[3][1] &&
+ pts[0][0] === pts[3][0] &&
+ pts[1][0] === pts[2][0]
+ ) {
+ isRect = true;
+ rectFirstEdgeTest = function(pt) {
+ return pt[1] === pts[0][1];
+ };
+ }
}
+ }
- // do we have a rectangle? Handle this here, so we can use the same
- // tester for the rectangular case without sacrificing speed
+ function rectContains(pt, omitFirstEdge) {
+ var x = pt[0], y = pt[1];
- var isRect = false,
- rectFirstEdgeTest;
-
- if(pts.length === 5) {
- if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz
- if(pts[2][0] === pts[3][0] &&
- pts[0][1] === pts[3][1] &&
- pts[1][1] === pts[2][1]) {
- isRect = true;
- rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; };
- }
- }
- else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert
- if(pts[2][1] === pts[3][1] &&
- pts[0][0] === pts[3][0] &&
- pts[1][0] === pts[2][0]) {
- isRect = true;
- rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; };
- }
- }
+ if (x < xmin || x > xmax || y < ymin || y > ymax) {
+ // pt is outside the bounding box of polygon
+ return false;
}
+ if (omitFirstEdge && rectFirstEdgeTest(pt)) return false;
- function rectContains(pt, omitFirstEdge) {
- var x = pt[0],
- y = pt[1];
+ return true;
+ }
- if(x < xmin || x > xmax || y < ymin || y > ymax) {
- // pt is outside the bounding box of polygon
- return false;
- }
- if(omitFirstEdge && rectFirstEdgeTest(pt)) return false;
+ function contains(pt, omitFirstEdge) {
+ var x = pt[0], y = pt[1];
- return true;
+ if (x < xmin || x > xmax || y < ymin || y > ymax) {
+ // pt is outside the bounding box of polygon
+ return false;
}
- function contains(pt, omitFirstEdge) {
- var x = pt[0],
- y = pt[1];
-
- if(x < xmin || x > xmax || y < ymin || y > ymax) {
- // pt is outside the bounding box of polygon
- return false;
+ var imax = pts.length,
+ x1 = pts[0][0],
+ y1 = pts[0][1],
+ crossings = 0,
+ i,
+ x0,
+ y0,
+ xmini,
+ ycross;
+
+ for (i = 1; i < imax; i++) {
+ // find all crossings of a vertical line upward from pt with
+ // polygon segments
+ // crossings exactly at xmax don't count, unless the point is
+ // exactly on the segment, then it counts as inside.
+ x0 = x1;
+ y0 = y1;
+ x1 = pts[i][0];
+ y1 = pts[i][1];
+ xmini = Math.min(x0, x1);
+
+ // outside the bounding box of this segment, it's only a crossing
+ // if it's below the box.
+ if (x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) {
+ continue;
+ } else if (y < Math.min(y0, y1)) {
+ // don't count the left-most point of the segment as a crossing
+ // because we don't want to double-count adjacent crossings
+ // UNLESS the polygon turns past vertical at exactly this x
+ // Note that this is repeated below, but we can't factor it out
+ // because
+ if (x !== xmini) crossings++;
+ } else {
+ // inside the bounding box, check the actual line intercept
+ // vertical segment - we know already that the point is exactly
+ // on the segment, so mark the crossing as exactly at the point.
+ if (x1 === x0) {
+ ycross = y;
+ } else {
+ // any other angle
+ ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0);
}
- var imax = pts.length,
- x1 = pts[0][0],
- y1 = pts[0][1],
- crossings = 0,
- i,
- x0,
- y0,
- xmini,
- ycross;
-
- for(i = 1; i < imax; i++) {
- // find all crossings of a vertical line upward from pt with
- // polygon segments
- // crossings exactly at xmax don't count, unless the point is
- // exactly on the segment, then it counts as inside.
- x0 = x1;
- y0 = y1;
- x1 = pts[i][0];
- y1 = pts[i][1];
- xmini = Math.min(x0, x1);
-
- // outside the bounding box of this segment, it's only a crossing
- // if it's below the box.
- if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) {
- continue;
- }
- else if(y < Math.min(y0, y1)) {
- // don't count the left-most point of the segment as a crossing
- // because we don't want to double-count adjacent crossings
- // UNLESS the polygon turns past vertical at exactly this x
- // Note that this is repeated below, but we can't factor it out
- // because
- if(x !== xmini) crossings++;
- }
- // inside the bounding box, check the actual line intercept
- else {
- // vertical segment - we know already that the point is exactly
- // on the segment, so mark the crossing as exactly at the point.
- if(x1 === x0) ycross = y;
- // any other angle
- else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0);
-
- // exactly on the edge: counts as inside the polygon, unless it's the
- // first edge and we're omitting it.
- if(y === ycross) {
- if(i === 1 && omitFirstEdge) return false;
- return true;
- }
-
- if(y <= ycross && x !== xmini) crossings++;
- }
+ // exactly on the edge: counts as inside the polygon, unless it's the
+ // first edge and we're omitting it.
+ if (y === ycross) {
+ if (i === 1 && omitFirstEdge) return false;
+ return true;
}
- // if we've gotten this far, odd crossings means inside, even is outside
- return crossings % 2 === 1;
+ if (y <= ycross && x !== xmini) crossings++;
+ }
}
- return {
- xmin: xmin,
- xmax: xmax,
- ymin: ymin,
- ymax: ymax,
- pts: pts,
- contains: isRect ? rectContains : contains,
- isRect: isRect
- };
+ // if we've gotten this far, odd crossings means inside, even is outside
+ return crossings % 2 === 1;
+ }
+
+ return {
+ xmin: xmin,
+ xmax: xmax,
+ ymin: ymin,
+ ymax: ymax,
+ pts: pts,
+ contains: isRect ? rectContains : contains,
+ isRect: isRect
+ };
};
/**
@@ -169,24 +172,34 @@ polygon.tester = function tester(ptsIn) {
* before the line counts as bent
* @returns boolean: true means this segment is bent, false means straight
*/
-var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance) {
- var startPt = pts[start],
- segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]],
- segmentSquared = dot(segment, segment),
- segmentLen = Math.sqrt(segmentSquared),
- unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen],
- i,
- part,
- partParallel;
-
- for(i = start + 1; i < end; i++) {
- part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
- partParallel = dot(part, segment);
-
- if(partParallel < 0 || partParallel > segmentSquared ||
- Math.abs(dot(part, unitPerp)) > tolerance) return true;
+var isBent = polygon.isSegmentBent = function isBent(
+ pts,
+ start,
+ end,
+ tolerance
+) {
+ var startPt = pts[start],
+ segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]],
+ segmentSquared = dot(segment, segment),
+ segmentLen = Math.sqrt(segmentSquared),
+ unitPerp = [(-segment[1]) / segmentLen, segment[0] / segmentLen],
+ i,
+ part,
+ partParallel;
+
+ for (i = start + 1; i < end; i++) {
+ part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
+ partParallel = dot(part, segment);
+
+ if (
+ partParallel < 0 ||
+ partParallel > segmentSquared ||
+ Math.abs(dot(part, unitPerp)) > tolerance
+ ) {
+ return true;
}
- return false;
+ }
+ return false;
};
/**
@@ -203,36 +216,29 @@ var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance)
* filtered is the resulting filtered Array of [x, y] pairs
*/
polygon.filter = function filter(pts, tolerance) {
- var ptsFiltered = [pts[0]],
- doneRawIndex = 0,
- doneFilteredIndex = 0;
-
- function addPt(pt) {
- pts.push(pt);
- var prevFilterLen = ptsFiltered.length,
- iLast = doneRawIndex;
- ptsFiltered.splice(doneFilteredIndex + 1);
-
- for(var i = iLast + 1; i < pts.length; i++) {
- if(i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) {
- ptsFiltered.push(pts[i]);
- if(ptsFiltered.length < prevFilterLen - 2) {
- doneRawIndex = i;
- doneFilteredIndex = ptsFiltered.length - 1;
- }
- iLast = i;
- }
+ var ptsFiltered = [pts[0]], doneRawIndex = 0, doneFilteredIndex = 0;
+
+ function addPt(pt) {
+ pts.push(pt);
+ var prevFilterLen = ptsFiltered.length, iLast = doneRawIndex;
+ ptsFiltered.splice(doneFilteredIndex + 1);
+
+ for (var i = iLast + 1; i < pts.length; i++) {
+ if (i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) {
+ ptsFiltered.push(pts[i]);
+ if (ptsFiltered.length < prevFilterLen - 2) {
+ doneRawIndex = i;
+ doneFilteredIndex = ptsFiltered.length - 1;
}
+ iLast = i;
+ }
}
+ }
- if(pts.length > 1) {
- var lastPt = pts.pop();
- addPt(lastPt);
- }
+ if (pts.length > 1) {
+ var lastPt = pts.pop();
+ addPt(lastPt);
+ }
- return {
- addPt: addPt,
- raw: pts,
- filtered: ptsFiltered
- };
+ return { addPt: addPt, raw: pts, filtered: ptsFiltered };
};
diff --git a/src/lib/queue.js b/src/lib/queue.js
index 815fd915723..e793887d047 100644
--- a/src/lib/queue.js
+++ b/src/lib/queue.js
@@ -5,13 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Lib = require('../lib');
-var config = require('../plot_api/plot_config');
-
+"use strict";
+var Lib = require("../lib");
+var config = require("../plot_api/plot_config");
/**
* Copy arg array *without* removing `undefined` values from objects.
@@ -21,34 +17,32 @@ var config = require('../plot_api/plot_config');
* @returns {Array}
*/
function copyArgArray(gd, args) {
- var copy = [];
- var arg;
-
- for(var i = 0; i < args.length; i++) {
- arg = args[i];
-
- if(arg === gd) copy[i] = arg;
- else if(typeof arg === 'object') {
- copy[i] = Array.isArray(arg) ?
- Lib.extendDeep([], arg) :
- Lib.extendDeepAll({}, arg);
- }
- else copy[i] = arg;
+ var copy = [];
+ var arg;
+
+ for (var i = 0; i < args.length; i++) {
+ arg = args[i];
+
+ if (arg === gd) {
+ copy[i] = arg;
+ } else if (typeof arg === "object") {
+ copy[i] = Array.isArray(arg)
+ ? Lib.extendDeep([], arg)
+ : Lib.extendDeepAll({}, arg);
+ } else {
+ copy[i] = arg;
}
+ }
- return copy;
+ return copy;
}
-
// -----------------------------------------------------
// Undo/Redo queue for plots
// -----------------------------------------------------
-
-
var queue = {};
// TODO: disable/enable undo and redo buttons appropriately
-
/**
* Add an item to the undoQueue for a graphDiv
*
@@ -59,42 +53,45 @@ var queue = {};
* @param redoArgs Args to supply redoFunc with
*/
queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) {
- var queueObj,
- queueIndex;
-
- // make sure we have the queue and our position in it
- gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
- queueIndex = gd.undoQueue.index;
-
- // if we're already playing an undo or redo, or if this is an auto operation
- // (like pane resize... any others?) then we don't save this to the undo queue
- if(gd.autoplay) {
- if(!gd.undoQueue.inSequence) gd.autoplay = false;
- return;
- }
-
- // if we're not in a sequence or are just starting, we need a new queue item
- if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) {
- queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}};
- gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj);
- gd.undoQueue.index += 1;
- } else {
- queueObj = gd.undoQueue.queue[queueIndex - 1];
- }
- gd.undoQueue.beginSequence = false;
-
- // we unshift to handle calls for undo in a forward for loop later
- if(queueObj) {
- queueObj.undo.calls.unshift(undoFunc);
- queueObj.undo.args.unshift(undoArgs);
- queueObj.redo.calls.push(redoFunc);
- queueObj.redo.args.push(redoArgs);
- }
-
- if(gd.undoQueue.queue.length > config.queueLength) {
- gd.undoQueue.queue.shift();
- gd.undoQueue.index--;
- }
+ var queueObj, queueIndex;
+
+ // make sure we have the queue and our position in it
+ gd.undoQueue = gd.undoQueue || { index: 0, queue: [], sequence: false };
+ queueIndex = gd.undoQueue.index;
+
+ // if we're already playing an undo or redo, or if this is an auto operation
+ // (like pane resize... any others?) then we don't save this to the undo queue
+ if (gd.autoplay) {
+ if (!gd.undoQueue.inSequence) gd.autoplay = false;
+ return;
+ }
+
+ // if we're not in a sequence or are just starting, we need a new queue item
+ if (!gd.undoQueue.sequence || gd.undoQueue.beginSequence) {
+ queueObj = { undo: { calls: [], args: [] }, redo: { calls: [], args: [] } };
+ gd.undoQueue.queue.splice(
+ queueIndex,
+ gd.undoQueue.queue.length - queueIndex,
+ queueObj
+ );
+ gd.undoQueue.index += 1;
+ } else {
+ queueObj = gd.undoQueue.queue[queueIndex - 1];
+ }
+ gd.undoQueue.beginSequence = false;
+
+ // we unshift to handle calls for undo in a forward for loop later
+ if (queueObj) {
+ queueObj.undo.calls.unshift(undoFunc);
+ queueObj.undo.args.unshift(undoArgs);
+ queueObj.redo.calls.push(redoFunc);
+ queueObj.redo.args.push(redoArgs);
+ }
+
+ if (gd.undoQueue.queue.length > config.queueLength) {
+ gd.undoQueue.queue.shift();
+ gd.undoQueue.index--;
+ }
};
/**
@@ -103,9 +100,9 @@ queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) {
* @param gd
*/
queue.startSequence = function(gd) {
- gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
- gd.undoQueue.sequence = true;
- gd.undoQueue.beginSequence = true;
+ gd.undoQueue = gd.undoQueue || { index: 0, queue: [], sequence: false };
+ gd.undoQueue.sequence = true;
+ gd.undoQueue.beginSequence = true;
};
/**
@@ -116,9 +113,9 @@ queue.startSequence = function(gd) {
* @param gd
*/
queue.stopSequence = function(gd) {
- gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
- gd.undoQueue.sequence = false;
- gd.undoQueue.beginSequence = false;
+ gd.undoQueue = gd.undoQueue || { index: 0, queue: [], sequence: false };
+ gd.undoQueue.sequence = false;
+ gd.undoQueue.beginSequence = false;
};
/**
@@ -127,31 +124,33 @@ queue.stopSequence = function(gd) {
* @param gd
*/
queue.undo = function undo(gd) {
- var queueObj, i;
-
- if(gd.framework && gd.framework.isPolar) {
- gd.framework.undo();
- return;
- }
- if(gd.undoQueue === undefined ||
- isNaN(gd.undoQueue.index) ||
- gd.undoQueue.index <= 0) {
- return;
- }
-
- // index is pointing to next *forward* queueObj, point to the one we're undoing
- gd.undoQueue.index--;
-
- // get the queueObj for instructions on how to undo
- queueObj = gd.undoQueue.queue[gd.undoQueue.index];
-
- // this sequence keeps things from adding to the queue during undo/redo
- gd.undoQueue.inSequence = true;
- for(i = 0; i < queueObj.undo.calls.length; i++) {
- queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]);
- }
- gd.undoQueue.inSequence = false;
- gd.autoplay = false;
+ var queueObj, i;
+
+ if (gd.framework && gd.framework.isPolar) {
+ gd.framework.undo();
+ return;
+ }
+ if (
+ gd.undoQueue === undefined ||
+ isNaN(gd.undoQueue.index) ||
+ gd.undoQueue.index <= 0
+ ) {
+ return;
+ }
+
+ // index is pointing to next *forward* queueObj, point to the one we're undoing
+ gd.undoQueue.index--;
+
+ // get the queueObj for instructions on how to undo
+ queueObj = gd.undoQueue.queue[gd.undoQueue.index];
+
+ // this sequence keeps things from adding to the queue during undo/redo
+ gd.undoQueue.inSequence = true;
+ for (i = 0; i < queueObj.undo.calls.length; i++) {
+ queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]);
+ }
+ gd.undoQueue.inSequence = false;
+ gd.autoplay = false;
};
/**
@@ -160,31 +159,33 @@ queue.undo = function undo(gd) {
* @param gd
*/
queue.redo = function redo(gd) {
- var queueObj, i;
-
- if(gd.framework && gd.framework.isPolar) {
- gd.framework.redo();
- return;
- }
- if(gd.undoQueue === undefined ||
- isNaN(gd.undoQueue.index) ||
- gd.undoQueue.index >= gd.undoQueue.queue.length) {
- return;
- }
-
- // get the queueObj for instructions on how to undo
- queueObj = gd.undoQueue.queue[gd.undoQueue.index];
-
- // this sequence keeps things from adding to the queue during undo/redo
- gd.undoQueue.inSequence = true;
- for(i = 0; i < queueObj.redo.calls.length; i++) {
- queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]);
- }
- gd.undoQueue.inSequence = false;
- gd.autoplay = false;
-
- // index is pointing to the thing we just redid, move it
- gd.undoQueue.index++;
+ var queueObj, i;
+
+ if (gd.framework && gd.framework.isPolar) {
+ gd.framework.redo();
+ return;
+ }
+ if (
+ gd.undoQueue === undefined ||
+ isNaN(gd.undoQueue.index) ||
+ gd.undoQueue.index >= gd.undoQueue.queue.length
+ ) {
+ return;
+ }
+
+ // get the queueObj for instructions on how to undo
+ queueObj = gd.undoQueue.queue[gd.undoQueue.index];
+
+ // this sequence keeps things from adding to the queue during undo/redo
+ gd.undoQueue.inSequence = true;
+ for (i = 0; i < queueObj.redo.calls.length; i++) {
+ queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]);
+ }
+ gd.undoQueue.inSequence = false;
+ gd.autoplay = false;
+
+ // index is pointing to the thing we just redid, move it
+ gd.undoQueue.index++;
};
/**
@@ -197,13 +198,13 @@ queue.redo = function redo(gd) {
* @param args
*/
queue.plotDo = function(gd, func, args) {
- gd.autoplay = true;
+ gd.autoplay = true;
- // this *won't* copy gd and it preserves `undefined` properties!
- args = copyArgArray(gd, args);
+ // this *won't* copy gd and it preserves `undefined` properties!
+ args = copyArgArray(gd, args);
- // call the supplied function
- func.apply(null, args);
+ // call the supplied function
+ func.apply(null, args);
};
module.exports = queue;
diff --git a/src/lib/search.js b/src/lib/search.js
index 8cb4275f1f3..e779a81106f 100644
--- a/src/lib/search.js
+++ b/src/lib/search.js
@@ -5,13 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-var loggers = require('./loggers');
-
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var loggers = require("./loggers");
/**
* findBin - find the bin for val - note that it can return outside the
@@ -24,40 +20,47 @@ var loggers = require('./loggers');
* 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);
+ 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;
}
- 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) loggers.log('Long binary search...');
- return n1 - 1;
+ // 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) loggers.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; }
+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; };
-exports.sorterDes = function(a, b) { return b - a; };
+exports.sorterAsc = function(a, b) {
+ return a - b;
+};
+exports.sorterDes = function(a, b) {
+ return b - a;
+};
/**
* find distinct values in an array, lumping together ones that appear to
@@ -65,23 +68,24 @@ exports.sorterDes = function(a, b) { return b - a; };
* 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 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]];
+ 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]);
- }
+ 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 { vals: v2, minDiff: minDiff };
};
/**
@@ -92,18 +96,18 @@ exports.distinctVals = function(valsIn) {
* 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];
+ 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/setcursor.js b/src/lib/setcursor.js
index ef70880a1d5..007605d775c 100644
--- a/src/lib/setcursor.js
+++ b/src/lib/setcursor.js
@@ -5,17 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
// works with our CSS cursor classes (see css/_cursor.scss)
// to apply cursors to d3 single-element selections.
// omit cursor to revert to the default.
module.exports = function setCursor(el3, csr) {
- (el3.attr('class') || '').split(' ').forEach(function(cls) {
- if(cls.indexOf('cursor-') === 0) el3.classed(cls, false);
- });
+ (el3.attr("class") || "").split(" ").forEach(function(cls) {
+ if (cls.indexOf("cursor-") === 0) el3.classed(cls, false);
+ });
- if(csr) el3.classed('cursor-' + csr, true);
+ if (csr) el3.classed("cursor-" + csr, true);
};
diff --git a/src/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js
index 40b84c680bf..a952b1ac2e6 100644
--- a/src/lib/show_no_webgl_msg.js
+++ b/src/lib/show_no_webgl_msg.js
@@ -5,15 +5,11 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Color = require('../components/color');
+"use strict";
+var Color = require("../components/color");
var noop = function() {};
-
/**
* Prints a no webgl error message into the scene container
* @param {scene instance} scene
@@ -22,26 +18,26 @@ var noop = function() {};
*
*/
module.exports = function showWebGlMsg(scene) {
- for(var prop in scene) {
- if(typeof scene[prop] === 'function') scene[prop] = noop;
- }
-
- scene.destroy = function() {
- scene.container.parentNode.removeChild(scene.container);
- };
-
- var div = document.createElement('div');
- div.textContent = 'Webgl is not supported by your browser - visit http://get.webgl.org for more info';
- div.style.cursor = 'pointer';
- div.style.fontSize = '24px';
- div.style.color = Color.defaults[0];
-
- scene.container.appendChild(div);
- scene.container.style.background = '#FFFFFF';
- scene.container.onclick = function() {
- window.open('http://get.webgl.org');
- };
-
- // return before setting up camera and onrender methods
- return false;
+ for (var prop in scene) {
+ if (typeof scene[prop] === "function") scene[prop] = noop;
+ }
+
+ scene.destroy = function() {
+ scene.container.parentNode.removeChild(scene.container);
+ };
+
+ var div = document.createElement("div");
+ div.textContent = "Webgl is not supported by your browser - visit http://get.webgl.org for more info";
+ div.style.cursor = "pointer";
+ div.style.fontSize = "24px";
+ div.style.color = Color.defaults[0];
+
+ scene.container.appendChild(div);
+ scene.container.style.background = "#FFFFFF";
+ scene.container.onclick = function() {
+ window.open("http://get.webgl.org");
+ };
+
+ // return before setting up camera and onrender methods
+ return false;
};
diff --git a/src/lib/stats.js b/src/lib/stats.js
index a365d4ce33f..37e2718354c 100644
--- a/src/lib/stats.js
+++ b/src/lib/stats.js
@@ -5,12 +5,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
+"use strict";
+var isNumeric = require("fast-isnumeric");
/**
* aggNums() returns the result of an aggregate function applied to an array of
@@ -26,21 +22,22 @@ var isNumeric = require('fast-isnumeric');
* @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;
+ 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;
+ for (i = 0; i < len; i++) {
+ if (!isNumeric(v)) v = a[i];
+ else if (isNumeric(a[i])) v = f(+v, +a[i]);
+ }
+ return v;
};
/**
@@ -48,25 +45,43 @@ exports.aggNums = function(f, v, a, len) {
* 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);
+ 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;
+ 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);
+ 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;
+ 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));
+ return Math.sqrt(exports.variance(data, len, mean));
};
/**
@@ -85,10 +100,10 @@ exports.stdev = function(data, len, mean) {
* @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)];
+ 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)];
};
diff --git a/src/lib/str2rgbarray.js b/src/lib/str2rgbarray.js
index 0dd3c027fce..f35fa8ecb80 100644
--- a/src/lib/str2rgbarray.js
+++ b/src/lib/str2rgbarray.js
@@ -5,16 +5,13 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var tinycolor = require('tinycolor2');
-var arrtools = require('arraytools');
+"use strict";
+var tinycolor = require("tinycolor2");
+var arrtools = require("arraytools");
function str2RgbaArray(color) {
- color = tinycolor(color);
- return arrtools.str2RgbaArray(color.toRgbString());
+ color = tinycolor(color);
+ return arrtools.str2RgbaArray(color.toRgbString());
}
module.exports = str2RgbaArray;
diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index a55334d5157..9aa02e19aa2 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -5,298 +5,324 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
/* global MathJax:false */
-var d3 = require('d3');
+var d3 = require("d3");
-var Lib = require('../lib');
-var xmlnsNamespaces = require('../constants/xmlns_namespaces');
-var stringMappings = require('../constants/string_mappings');
+var Lib = require("../lib");
+var xmlnsNamespaces = require("../constants/xmlns_namespaces");
+var stringMappings = require("../constants/string_mappings");
// Append SVG
-
d3.selection.prototype.appendSVG = function(_svgString) {
- var skeleton = [
- ''
- ].join('');
-
- var dom = new DOMParser().parseFromString(skeleton, 'application/xml'),
- childNode = dom.documentElement.firstChild;
-
- while(childNode) {
- this.node().appendChild(this.node().ownerDocument.importNode(childNode, true));
- childNode = childNode.nextSibling;
- }
- if(dom.querySelector('parsererror')) {
- Lib.log(dom.querySelector('parsererror div').textContent);
- return null;
- }
- return d3.select(this.node().lastChild);
+ var skeleton = [
+ '"
+ ].join("");
+
+ var dom = new DOMParser().parseFromString(skeleton, "application/xml"),
+ childNode = dom.documentElement.firstChild;
+
+ while (childNode) {
+ this
+ .node()
+ .appendChild(this.node().ownerDocument.importNode(childNode, true));
+ childNode = childNode.nextSibling;
+ }
+ if (dom.querySelector("parsererror")) {
+ Lib.log(dom.querySelector("parsererror div").textContent);
+ return null;
+ }
+ return d3.select(this.node().lastChild);
};
// Text utilities
-
exports.html_entity_decode = function(s) {
- var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html('');
- var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
- if(d === '<') { return '<'; } // special handling for brackets
- if(d === '&rt;') { return '>'; }
- if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
- return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
- });
- hiddenDiv.remove();
- return replaced;
+ var hiddenDiv = d3
+ .select("body")
+ .append("div")
+ .style({ display: "none" })
+ .html("");
+ var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
+ if (d === "<") {
+ return "<";
+ }
+ // special handling for brackets
+ if (d === "&rt;") {
+ return ">";
+ }
+ if (d.indexOf("<") !== -1 || d.indexOf(">") !== -1) {
+ return "";
+ }
+ return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
+ });
+ hiddenDiv.remove();
+ return replaced;
};
exports.xml_entity_encode = function(str) {
- return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&');
+ return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, "&");
};
// text converter
-
function getSize(_selection, _dimension) {
- return _selection.node().getBoundingClientRect()[_dimension];
+ return _selection.node().getBoundingClientRect()[_dimension];
}
exports.convertToTspans = function(_context, _callback) {
- var str = _context.text();
- var converted = convertToSVG(str);
- var that = _context;
-
- // Until we get tex integrated more fully (so it can be used along with non-tex)
- // allow some elements to prohibit it by attaching 'data-notex' to the original
- var tex = (!that.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/);
- var result = str;
- var parent = d3.select(that.node().parentNode);
- if(parent.empty()) return;
- var svgClass = (that.attr('class')) ? that.attr('class').split(' ')[0] : 'text';
- svgClass += '-math';
- parent.selectAll('svg.' + svgClass).remove();
- parent.selectAll('g.' + svgClass + '-group').remove();
- _context.style({visibility: null});
- for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) {
- up.removeAttribute('data-bb');
+ var str = _context.text();
+ var converted = convertToSVG(str);
+ var that = _context;
+
+ // Until we get tex integrated more fully (so it can be used along with non-tex)
+ // allow some elements to prohibit it by attaching 'data-notex' to the original
+ var tex = !that.attr("data-notex") &&
+ converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/);
+ var result = str;
+ var parent = d3.select(that.node().parentNode);
+ if (parent.empty()) return;
+ var svgClass = that.attr("class") ? that.attr("class").split(" ")[0] : "text";
+ svgClass += "-math";
+ parent.selectAll("svg." + svgClass).remove();
+ parent.selectAll("g." + svgClass + "-group").remove();
+ _context.style({ visibility: null });
+ for (var up = _context.node(); up && up.removeAttribute; up = up.parentNode) {
+ up.removeAttribute("data-bb");
+ }
+
+ function showText() {
+ if (!parent.empty()) {
+ svgClass = that.attr("class") + "-math";
+ parent.select("svg." + svgClass).remove();
+ }
+ _context.text("").style({ visibility: "inherit", "white-space": "pre" });
+
+ result = _context.appendSVG(converted);
+
+ if (!result) _context.text(str);
+
+ if (_context.select("a").size()) {
+ // at least in Chrome, pointer-events does not seem
+ // to be honored in children of elements
+ // so if we have an anchor, we have to make the
+ // whole element respond
+ _context.style("pointer-events", "all");
}
- function showText() {
- if(!parent.empty()) {
- svgClass = that.attr('class') + '-math';
- parent.select('svg.' + svgClass).remove();
+ if (_callback) _callback.call(that);
+ }
+
+ if (tex) {
+ var gd = Lib.getPlotDiv(that.node());
+ (gd && gd._promises || []).push(new Promise(function(resolve) {
+ that.style({ visibility: "hidden" });
+ var config = { fontSize: parseInt(that.style("font-size"), 10) };
+
+ texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
+ parent.selectAll("svg." + svgClass).remove();
+ parent.selectAll("g." + svgClass + "-group").remove();
+
+ var newSvg = _svgEl && _svgEl.select("svg");
+ if (!newSvg || !newSvg.node()) {
+ showText();
+ resolve();
+ return;
}
- _context.text('')
- .style({
- visibility: 'inherit',
- 'white-space': 'pre'
- });
-
- result = _context.appendSVG(converted);
-
- if(!result) _context.text(str);
-
- if(_context.select('a').size()) {
- // at least in Chrome, pointer-events does not seem
- // to be honored in children of elements
- // so if we have an anchor, we have to make the
- // whole element respond
- _context.style('pointer-events', 'all');
+
+ var mathjaxGroup = parent
+ .append("g")
+ .classed(svgClass + "-group", true)
+ .attr({ "pointer-events": "none" });
+
+ mathjaxGroup.node().appendChild(newSvg.node());
+
+ // stitch the glyph defs
+ if (_glyphDefs && _glyphDefs.node()) {
+ newSvg
+ .node()
+ .insertBefore(
+ _glyphDefs.node().cloneNode(true),
+ newSvg.node().firstChild
+ );
}
- if(_callback) _callback.call(that);
- }
+ newSvg
+ .attr({
+ class: svgClass,
+ height: _svgBBox.height,
+ preserveAspectRatio: "xMinYMin meet"
+ })
+ .style({ overflow: "visible", "pointer-events": "none" });
+
+ var fill = that.style("fill") || "black";
+ newSvg.select("g").attr({ fill: fill, stroke: fill });
+
+ var newSvgW = getSize(newSvg, "width"),
+ newSvgH = getSize(newSvg, "height"),
+ newX = +that.attr("x") -
+ newSvgW *
+ ({ start: 0, middle: 0.5, end: 1 })[
+ that.attr("text-anchor") || "start"
+ ],
+ // font baseline is about 1/4 fontSize below centerline
+ textHeight = parseInt(that.style("font-size"), 10) ||
+ getSize(that, "height"),
+ dy = (-textHeight) / 4;
+
+ if (svgClass[0] === "y") {
+ mathjaxGroup.attr({
+ transform: (
+ "rotate(" +
+ [-90, +that.attr("x"), +that.attr("y")] +
+ ") translate(" +
+ [(-newSvgW) / 2, dy - newSvgH / 2] +
+ ")"
+ )
+ });
+ newSvg.attr({ x: +that.attr("x"), y: +that.attr("y") });
+ } else if (svgClass[0] === "l") {
+ newSvg.attr({ x: that.attr("x"), y: dy - newSvgH / 2 });
+ } else if (svgClass[0] === "a") {
+ newSvg.attr({ x: 0, y: dy });
+ } else {
+ newSvg.attr({ x: newX, y: +that.attr("y") + dy - newSvgH / 2 });
+ }
- if(tex) {
- var gd = Lib.getPlotDiv(that.node());
- ((gd && gd._promises) || []).push(new Promise(function(resolve) {
- that.style({visibility: 'hidden'});
- var config = {fontSize: parseInt(that.style('font-size'), 10)};
-
- texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
- parent.selectAll('svg.' + svgClass).remove();
- parent.selectAll('g.' + svgClass + '-group').remove();
-
- var newSvg = _svgEl && _svgEl.select('svg');
- if(!newSvg || !newSvg.node()) {
- showText();
- resolve();
- return;
- }
-
- var mathjaxGroup = parent.append('g')
- .classed(svgClass + '-group', true)
- .attr({'pointer-events': 'none'});
-
- mathjaxGroup.node().appendChild(newSvg.node());
-
- // stitch the glyph defs
- if(_glyphDefs && _glyphDefs.node()) {
- newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true),
- newSvg.node().firstChild);
- }
-
- newSvg.attr({
- 'class': svgClass,
- height: _svgBBox.height,
- preserveAspectRatio: 'xMinYMin meet'
- })
- .style({overflow: 'visible', 'pointer-events': 'none'});
-
- var fill = that.style('fill') || 'black';
- newSvg.select('g').attr({fill: fill, stroke: fill});
-
- var newSvgW = getSize(newSvg, 'width'),
- newSvgH = getSize(newSvg, 'height'),
- newX = +that.attr('x') - newSvgW *
- {start: 0, middle: 0.5, end: 1}[that.attr('text-anchor') || 'start'],
- // font baseline is about 1/4 fontSize below centerline
- textHeight = parseInt(that.style('font-size'), 10) ||
- getSize(that, 'height'),
- dy = -textHeight / 4;
-
- if(svgClass[0] === 'y') {
- mathjaxGroup.attr({
- transform: 'rotate(' + [-90, +that.attr('x'), +that.attr('y')] +
- ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')'
- });
- newSvg.attr({x: +that.attr('x'), y: +that.attr('y')});
- }
- else if(svgClass[0] === 'l') {
- newSvg.attr({x: that.attr('x'), y: dy - (newSvgH / 2)});
- }
- else if(svgClass[0] === 'a') {
- newSvg.attr({x: 0, y: dy});
- }
- else {
- newSvg.attr({x: newX, y: (+that.attr('y') + dy - newSvgH / 2)});
- }
-
- if(_callback) _callback.call(that, mathjaxGroup);
- resolve(mathjaxGroup);
- });
- }));
- }
- else showText();
+ if (_callback) _callback.call(that, mathjaxGroup);
+ resolve(mathjaxGroup);
+ });
+ }));
+ } else {
+ showText();
+ }
- return _context;
+ return _context;
};
-
// MathJax
-
function cleanEscapesForTex(s) {
- return s.replace(/(<|<|<)/g, '\\lt ')
- .replace(/(>|>|>)/g, '\\gt ');
+ return s
+ .replace(/(<|<|<)/g, "\\lt ")
+ .replace(/(>|>|>)/g, "\\gt ");
}
function texToSVG(_texString, _config, _callback) {
- var randomID = 'math-output-' + Lib.randstr([], 64);
- var tmpDiv = d3.select('body').append('div')
- .attr({id: randomID})
- .style({visibility: 'hidden', position: 'absolute'})
- .style({'font-size': _config.fontSize + 'px'})
- .text(cleanEscapesForTex(_texString));
-
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, tmpDiv.node()], function() {
- var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs');
-
- if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) {
- Lib.log('There was an error in the tex syntax.', _texString);
- _callback();
- }
- else {
- var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect();
- _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox);
- }
+ var randomID = "math-output-" + Lib.randstr([], 64);
+ var tmpDiv = d3
+ .select("body")
+ .append("div")
+ .attr({ id: randomID })
+ .style({ visibility: "hidden", position: "absolute" })
+ .style({ "font-size": _config.fontSize + "px" })
+ .text(cleanEscapesForTex(_texString));
+
+ MathJax.Hub.Queue(["Typeset", MathJax.Hub, tmpDiv.node()], function() {
+ var glyphDefs = d3.select("body").select("#MathJax_SVG_glyphs");
+
+ if (tmpDiv.select(".MathJax_SVG").empty() || !tmpDiv.select("svg").node()) {
+ Lib.log("There was an error in the tex syntax.", _texString);
+ _callback();
+ } else {
+ var svgBBox = tmpDiv.select("svg").node().getBoundingClientRect();
+ _callback(tmpDiv.select(".MathJax_SVG"), glyphDefs, svgBBox);
+ }
- tmpDiv.remove();
- });
+ tmpDiv.remove();
+ });
}
var TAG_STYLES = {
- // would like to use baseline-shift but FF doesn't support it yet
- // so we need to use dy along with the uber hacky shift-back-to
- // baseline below
- sup: 'font-size:70%" dy="-0.6em',
- sub: 'font-size:70%" dy="0.3em',
- b: 'font-weight:bold',
- i: 'font-style:italic',
- a: '',
- span: '',
- br: '',
- em: 'font-style:italic;font-weight:bold'
+ // would like to use baseline-shift but FF doesn't support it yet
+ // so we need to use dy along with the uber hacky shift-back-to
+ // baseline below
+ sup: 'font-size:70%" dy="-0.6em',
+ sub: 'font-size:70%" dy="0.3em',
+ b: "font-weight:bold",
+ i: "font-style:italic",
+ a: "",
+ span: "",
+ br: "",
+ em: "font-style:italic;font-weight:bold"
};
-var PROTOCOLS = ['http:', 'https:', 'mailto:'];
+var PROTOCOLS = ["http:", "https:", "mailto:"];
-var STRIP_TAGS = new RegExp('?(' + Object.keys(TAG_STYLES).join('|') + ')( [^>]*)?/?>', 'g');
+var STRIP_TAGS = new RegExp(
+ "?(" + Object.keys(TAG_STYLES).join("|") + ")( [^>]*)?/?>",
+ "g"
+);
-var ENTITY_TO_UNICODE = Object.keys(stringMappings.entityToUnicode).map(function(k) {
- return {
- regExp: new RegExp('&' + k + ';', 'g'),
- sub: stringMappings.entityToUnicode[k]
- };
+var ENTITY_TO_UNICODE = Object.keys(
+ stringMappings.entityToUnicode
+).map(function(k) {
+ return {
+ regExp: new RegExp("&" + k + ";", "g"),
+ sub: stringMappings.entityToUnicode[k]
+ };
});
-var UNICODE_TO_ENTITY = Object.keys(stringMappings.unicodeToEntity).map(function(k) {
- return {
- regExp: new RegExp(k, 'g'),
- sub: '&' + stringMappings.unicodeToEntity[k] + ';'
- };
+var UNICODE_TO_ENTITY = Object.keys(
+ stringMappings.unicodeToEntity
+).map(function(k) {
+ return {
+ regExp: new RegExp(k, "g"),
+ sub: "&" + stringMappings.unicodeToEntity[k] + ";"
+ };
});
var NEWLINES = /(\r\n?|\n)/g;
exports.plainText = function(_str) {
- // strip out our pseudo-html so we have a readable
- // version to put into text fields
- return (_str || '').replace(STRIP_TAGS, ' ');
+ // strip out our pseudo-html so we have a readable
+ // version to put into text fields
+ return (_str || "").replace(STRIP_TAGS, " ");
};
function replaceFromMapObject(_str, list) {
- var out = _str || '';
+ var out = _str || "";
- for(var i = 0; i < list.length; i++) {
- var item = list[i];
- out = out.replace(item.regExp, item.sub);
- }
+ for (var i = 0; i < list.length; i++) {
+ var item = list[i];
+ out = out.replace(item.regExp, item.sub);
+ }
- return out;
+ return out;
}
function convertEntities(_str) {
- return replaceFromMapObject(_str, ENTITY_TO_UNICODE);
+ return replaceFromMapObject(_str, ENTITY_TO_UNICODE);
}
function encodeForHTML(_str) {
- return replaceFromMapObject(_str, UNICODE_TO_ENTITY);
+ return replaceFromMapObject(_str, UNICODE_TO_ENTITY);
}
function convertToSVG(_str) {
- _str = convertEntities(_str);
-
- // normalize behavior between IE and others wrt newlines and whitespace:pre
- // this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
- // Chrome and FF display \n, \r, or \r\n as a space in this mode.
- // I feel like at some point we turned these into
but currently we don't so
- // I'm just going to cement what we do now in Chrome and FF
- _str = _str.replace(NEWLINES, ' ');
-
- var result = _str
- .split(/(<[^<>]*>)/).map(function(d) {
- var match = d.match(/<(\/?)([^ >]*)\s*(.*)>/i),
- tag = match && match[2].toLowerCase(),
- style = TAG_STYLES[tag];
-
- if(style !== undefined) {
- var close = match[1],
- extra = match[3],
- /**
+ _str = convertEntities(_str);
+
+ // normalize behavior between IE and others wrt newlines and whitespace:pre
+ // this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
+ // Chrome and FF display \n, \r, or \r\n as a space in this mode.
+ // I feel like at some point we turned these into
but currently we don't so
+ // I'm just going to cement what we do now in Chrome and FF
+ _str = _str.replace(NEWLINES, " ");
+
+ var result = _str.split(/(<[^<>]*>)/).map(function(d) {
+ var match = d.match(/<(\/?)([^ >]*)\s*(.*)>/i),
+ tag = match && match[2].toLowerCase(),
+ style = TAG_STYLES[tag];
+
+ if (style !== undefined) {
+ var close = match[1],
+ extra = match[3],
+ /**
* extraStyle: any random extra css (that's supported by svg)
* use this like to change font in the middle
*
@@ -304,232 +330,265 @@ function convertToSVG(_str) {
* valid HTML anymore and we dropped it accidentally for many months, we will not
* resurrect it.
*/
- extraStyle = extra.match(/^style\s*=\s*"([^"]+)"\s*/i);
-
- // anchor and br are the only ones that don't turn into a tspan
- if(tag === 'a') {
- if(close) return '';
- else if(extra.substr(0, 4).toLowerCase() !== 'href') return '';
- else {
- // remove quotes, leading '=', replace '&' with '&'
- var href = extra.substr(4)
- .replace(/["']/g, '')
- .replace(/=/, '');
-
- // check protocol
- var dummyAnchor = document.createElement('a');
- dummyAnchor.href = href;
- if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '';
-
- return '';
- }
- }
- else if(tag === 'br') return '
';
- else if(close) {
- // closing tag
-
- // sub/sup: extra tspan with zero-width space to get back to the right baseline
- if(tag === 'sup') return '';
- if(tag === 'sub') return '';
- else return '';
- }
- else {
- var tspanStart = '';
- }
- }
- else {
- return exports.xml_entity_encode(d).replace(/";
+ } else if (extra.substr(0, 4).toLowerCase() !== "href") {
+ return "";
+ } else {
+ // remove quotes, leading '=', replace '&' with '&'
+ var href = extra.substr(4).replace(/["']/g, "").replace(/=/, "");
+
+ // check protocol
+ var dummyAnchor = document.createElement("a");
+ dummyAnchor.href = href;
+ if (PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return "";
+
+ return '';
+ }
+ } else if (tag === "br") {
+ return "
";
+ } else if (close) {
+ // closing tag
+ // sub/sup: extra tspan with zero-width space to get back to the right baseline
+ if (tag === "sup") return '';
+ if (tag === "sub") {
+ return '';
+ } else {
+ return "";
+ }
+ } else {
+ var tspanStart = "'); index > 0; index = result.indexOf('
', index + 1)) {
- indices.push(index);
- }
- var count = 0;
- indices.forEach(function(d) {
- var brIndex = d + count;
- var search = result.slice(0, brIndex);
- var previousOpenTag = '';
- for(var i2 = search.length - 1; i2 >= 0; i2--) {
- var isTag = search[i2].match(/<(\/?).*>/i);
- if(isTag && search[i2] !== '
') {
- if(!isTag[1]) previousOpenTag = search[i2];
- break;
- }
+ if (tag === "sup" || tag === "sub") {
+ // sub/sup: extra zero-width space, fixes problem if new line starts with sub/sup
+ tspanStart = "" + tspanStart;
}
- if(previousOpenTag) {
- result.splice(brIndex + 1, 0, previousOpenTag);
- result.splice(brIndex, 0, '');
- count += 2;
+
+ if (extraStyle) {
+ // most of the svg css users will care about is just like html,
+ // but font color is different. Let our users ignore this.
+ extraStyle = extraStyle[1].replace(/(^|;)\s*color:/, "$1 fill:");
+ style = encodeForHTML(extraStyle) + (style ? ";" + style : "");
}
- });
- var joined = result.join('');
- var splitted = joined.split(/
/gi);
- if(splitted.length > 1) {
- result = splitted.map(function(d, i) {
- // TODO: figure out max font size of this line and alter dy
- // this requires either:
- // 1) bringing the base font size into convertToTspans, or
- // 2) only allowing relative percentage font sizes.
- // I think #2 is the way to go
- return '' + d + '';
- });
+ return tspanStart + (style ? ' style="' + style + '"' : "") + ">";
+ }
+ } else {
+ return exports.xml_entity_encode(d).replace(/");
+ index > 0;
+ index = result.indexOf("
", index + 1)
+ ) {
+ indices.push(index);
+ }
+ var count = 0;
+ indices.forEach(function(d) {
+ var brIndex = d + count;
+ var search = result.slice(0, brIndex);
+ var previousOpenTag = "";
+ for (var i2 = search.length - 1; i2 >= 0; i2--) {
+ var isTag = search[i2].match(/<(\/?).*>/i);
+ if (isTag && search[i2] !== "
") {
+ if (!isTag[1]) previousOpenTag = search[i2];
+ break;
+ }
}
+ if (previousOpenTag) {
+ result.splice(brIndex + 1, 0, previousOpenTag);
+ result.splice(brIndex, 0, "");
+ count += 2;
+ }
+ });
+
+ var joined = result.join("");
+ var splitted = joined.split(/
/gi);
+ if (splitted.length > 1) {
+ result = splitted.map(function(d, i) {
+ // TODO: figure out max font size of this line and alter dy
+ // this requires either:
+ // 1) bringing the base font size into convertToTspans, or
+ // 2) only allowing relative percentage font sizes.
+ // I think #2 is the way to go
+ return '' + d + "";
+ });
+ }
- return result.join('');
+ return result.join("");
}
function alignHTMLWith(_base, container, options) {
- var alignH = options.horizontalAlign,
- alignV = options.verticalAlign || 'top',
- bRect = _base.node().getBoundingClientRect(),
- cRect = container.node().getBoundingClientRect(),
- thisRect,
- getTop,
- getLeft;
-
- if(alignV === 'bottom') {
- getTop = function() { return bRect.bottom - thisRect.height; };
- } else if(alignV === 'middle') {
- getTop = function() { return bRect.top + (bRect.height - thisRect.height) / 2; };
- } else { // default: top
- getTop = function() { return bRect.top; };
- }
-
- if(alignH === 'right') {
- getLeft = function() { return bRect.right - thisRect.width; };
- } else if(alignH === 'center') {
- getLeft = function() { return bRect.left + (bRect.width - thisRect.width) / 2; };
- } else { // default: left
- getLeft = function() { return bRect.left; };
- }
+ var alignH = options.horizontalAlign,
+ alignV = options.verticalAlign || "top",
+ bRect = _base.node().getBoundingClientRect(),
+ cRect = container.node().getBoundingClientRect(),
+ thisRect,
+ getTop,
+ getLeft;
+
+ if (alignV === "bottom") {
+ getTop = function() {
+ return bRect.bottom - thisRect.height;
+ };
+ } else if (alignV === "middle") {
+ getTop = function() {
+ return bRect.top + (bRect.height - thisRect.height) / 2;
+ };
+ } else {
+ // default: top
+ getTop = function() {
+ return bRect.top;
+ };
+ }
- return function() {
- thisRect = this.node().getBoundingClientRect();
- this.style({
- top: (getTop() - cRect.top) + 'px',
- left: (getLeft() - cRect.left) + 'px',
- 'z-index': 1000
- });
- return this;
+ if (alignH === "right") {
+ getLeft = function() {
+ return bRect.right - thisRect.width;
+ };
+ } else if (alignH === "center") {
+ getLeft = function() {
+ return bRect.left + (bRect.width - thisRect.width) / 2;
};
+ } else {
+ // default: left
+ getLeft = function() {
+ return bRect.left;
+ };
+ }
+
+ return function() {
+ thisRect = this.node().getBoundingClientRect();
+ this.style({
+ top: getTop() - cRect.top + "px",
+ left: getLeft() - cRect.left + "px",
+ "z-index": 1000
+ });
+ return this;
+ };
}
// Editable title
-
exports.makeEditable = function(context, _delegate, options) {
- if(!options) options = {};
- var that = this;
- var dispatch = d3.dispatch('edit', 'input', 'cancel');
- var textSelection = d3.select(this.node())
- .style({'pointer-events': 'all'});
-
- var handlerElement = _delegate || textSelection;
- if(_delegate) textSelection.style({'pointer-events': 'none'});
-
- function handleClick() {
- appendEditable();
- that.style({opacity: 0});
- // also hide any mathjax svg
- var svgClass = handlerElement.attr('class'),
- mathjaxClass;
- if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
- else mathjaxClass = '[class*=-math-group]';
- if(mathjaxClass) {
- d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0});
- }
+ if (!options) options = {};
+ var that = this;
+ var dispatch = d3.dispatch("edit", "input", "cancel");
+ var textSelection = d3.select(this.node()).style({ "pointer-events": "all" });
+
+ var handlerElement = _delegate || textSelection;
+ if (_delegate) textSelection.style({ "pointer-events": "none" });
+
+ function handleClick() {
+ appendEditable();
+ that.style({ opacity: 0 });
+ // also hide any mathjax svg
+ var svgClass = handlerElement.attr("class"), mathjaxClass;
+ if (svgClass) mathjaxClass = "." + svgClass.split(" ")[0] + "-math-group";
+ else mathjaxClass = "[class*=-math-group]";
+ if (mathjaxClass) {
+ d3
+ .select(that.node().parentNode)
+ .select(mathjaxClass)
+ .style({ opacity: 0 });
}
-
- function selectElementContents(_el) {
- var el = _el.node();
- var range = document.createRange();
- range.selectNodeContents(el);
- var sel = window.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- el.focus();
- }
-
- function appendEditable() {
- var plotDiv = d3.select(Lib.getPlotDiv(that.node())),
- container = plotDiv.select('.svg-container'),
- div = container.append('div');
- div.classed('plugin-editable editable', true)
- .style({
- position: 'absolute',
- 'font-family': that.style('font-family') || 'Arial',
- 'font-size': that.style('font-size') || 12,
- color: options.fill || that.style('fill') || 'black',
- opacity: 1,
- 'background-color': options.background || 'transparent',
- outline: '#ffffff33 1px solid',
- margin: [-parseFloat(that.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px',
- padding: '0',
- 'box-sizing': 'border-box'
- })
- .attr({contenteditable: true})
- .text(options.text || that.attr('data-unformatted'))
- .call(alignHTMLWith(that, container, options))
- .on('blur', function() {
- that.text(this.textContent)
- .style({opacity: 1});
- var svgClass = d3.select(this).attr('class'),
- mathjaxClass;
- if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
- else mathjaxClass = '[class*=-math-group]';
- if(mathjaxClass) {
- d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0});
- }
- var text = this.textContent;
- d3.select(this).transition().duration(0).remove();
- d3.select(document).on('mouseup', null);
- dispatch.edit.call(that, text);
- })
- .on('focus', function() {
- var context = this;
- d3.select(document).on('mouseup', function() {
- if(d3.event.target === context) return false;
- if(document.activeElement === div.node()) div.node().blur();
- });
- })
- .on('keyup', function() {
- if(d3.event.which === 27) {
- that.style({opacity: 1});
- d3.select(this)
- .style({opacity: 0})
- .on('blur', function() { return false; })
- .transition().remove();
- dispatch.cancel.call(that, this.textContent);
- }
- else {
- dispatch.input.call(that, this.textContent);
- d3.select(this).call(alignHTMLWith(that, container, options));
- }
- })
- .on('keydown', function() {
- if(d3.event.which === 13) this.blur();
+ }
+
+ function selectElementContents(_el) {
+ var el = _el.node();
+ var range = document.createRange();
+ range.selectNodeContents(el);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ el.focus();
+ }
+
+ function appendEditable() {
+ var plotDiv = d3.select(Lib.getPlotDiv(that.node())),
+ container = plotDiv.select(".svg-container"),
+ div = container.append("div");
+ div
+ .classed("plugin-editable editable", true)
+ .style({
+ position: "absolute",
+ "font-family": that.style("font-family") || "Arial",
+ "font-size": that.style("font-size") || 12,
+ color: options.fill || that.style("fill") || "black",
+ opacity: 1,
+ "background-color": options.background || "transparent",
+ outline: "#ffffff33 1px solid",
+ margin: (
+ [(-parseFloat(that.style("font-size"))) / 8 + 1, 0, 0, -1].join(
+ "px "
+ ) +
+ "px"
+ ),
+ padding: "0",
+ "box-sizing": "border-box"
+ })
+ .attr({ contenteditable: true })
+ .text(options.text || that.attr("data-unformatted"))
+ .call(alignHTMLWith(that, container, options))
+ .on("blur", function() {
+ that.text(this.textContent).style({ opacity: 1 });
+ var svgClass = d3.select(this).attr("class"), mathjaxClass;
+ if (svgClass) {
+ mathjaxClass = "." + svgClass.split(" ")[0] + "-math-group";
+ } else {
+ mathjaxClass = "[class*=-math-group]";
+ }
+ if (mathjaxClass) {
+ d3
+ .select(that.node().parentNode)
+ .select(mathjaxClass)
+ .style({ opacity: 0 });
+ }
+ var text = this.textContent;
+ d3.select(this).transition().duration(0).remove();
+ d3.select(document).on("mouseup", null);
+ dispatch.edit.call(that, text);
+ })
+ .on("focus", function() {
+ var context = this;
+ d3.select(document).on("mouseup", function() {
+ if (d3.event.target === context) return false;
+ if (document.activeElement === div.node()) div.node().blur();
+ });
+ })
+ .on("keyup", function() {
+ if (d3.event.which === 27) {
+ that.style({ opacity: 1 });
+ d3
+ .select(this)
+ .style({ opacity: 0 })
+ .on("blur", function() {
+ return false;
})
- .call(selectElementContents);
- }
+ .transition()
+ .remove();
+ dispatch.cancel.call(that, this.textContent);
+ } else {
+ dispatch.input.call(that, this.textContent);
+ d3.select(this).call(alignHTMLWith(that, container, options));
+ }
+ })
+ .on("keydown", function() {
+ if (d3.event.which === 13) this.blur();
+ })
+ .call(selectElementContents);
+ }
- if(options.immediate) handleClick();
- else handlerElement.on('click', handleClick);
+ if (options.immediate) handleClick();
+ else handlerElement.on("click", handleClick);
- return d3.rebind(this, dispatch, 'on');
+ return d3.rebind(this, dispatch, "on");
};
diff --git a/src/lib/topojson_utils.js b/src/lib/topojson_utils.js
index bdd5d7bf196..67574b3d1f6 100644
--- a/src/lib/topojson_utils.js
+++ b/src/lib/topojson_utils.js
@@ -5,30 +5,28 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
+"use strict";
var topojsonUtils = module.exports = {};
-var locationmodeToLayer = require('../plots/geo/constants').locationmodeToLayer;
-var topojsonFeature = require('topojson-client').feature;
-
+var locationmodeToLayer = require("../plots/geo/constants").locationmodeToLayer;
+var topojsonFeature = require("topojson-client").feature;
topojsonUtils.getTopojsonName = function(geoLayout) {
- return [
- geoLayout.scope.replace(/ /g, '-'), '_',
- geoLayout.resolution.toString(), 'm'
- ].join('');
+ return [
+ geoLayout.scope.replace(/ /g, "-"),
+ "_",
+ geoLayout.resolution.toString(),
+ "m"
+ ].join("");
};
topojsonUtils.getTopojsonPath = function(topojsonURL, topojsonName) {
- return topojsonURL + topojsonName + '.json';
+ return topojsonURL + topojsonName + ".json";
};
topojsonUtils.getTopojsonFeatures = function(trace, topojson) {
- var layer = locationmodeToLayer[trace.locationmode],
- obj = topojson.objects[layer];
+ var layer = locationmodeToLayer[trace.locationmode],
+ obj = topojson.objects[layer];
- return topojsonFeature(topojson, obj).features;
+ return topojsonFeature(topojson, obj).features;
};
diff --git a/src/lib/typed_array_truncate.js b/src/lib/typed_array_truncate.js
index 3bacc8ebe56..718402f680b 100644
--- a/src/lib/typed_array_truncate.js
+++ b/src/lib/typed_array_truncate.js
@@ -5,19 +5,21 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
function truncateFloat32(arrayIn, len) {
- var arrayOut = new Float32Array(len);
- for(var i = 0; i < len; i++) arrayOut[i] = arrayIn[i];
- return arrayOut;
+ var arrayOut = new Float32Array(len);
+ for (var i = 0; i < len; i++) {
+ arrayOut[i] = arrayIn[i];
+ }
+ return arrayOut;
}
function truncateFloat64(arrayIn, len) {
- var arrayOut = new Float64Array(len);
- for(var i = 0; i < len; i++) arrayOut[i] = arrayIn[i];
- return arrayOut;
+ var arrayOut = new Float64Array(len);
+ for (var i = 0; i < len; i++) {
+ arrayOut[i] = arrayIn[i];
+ }
+ return arrayOut;
}
/**
@@ -26,7 +28,7 @@ function truncateFloat64(arrayIn, len) {
* 2x as long, therefore we aren't checking for its existence
*/
module.exports = function truncate(arrayIn, len) {
- if(arrayIn instanceof Float32Array) return truncateFloat32(arrayIn, len);
- if(arrayIn instanceof Float64Array) return truncateFloat64(arrayIn, len);
- throw new Error('This array type is not yet supported by `truncate`.');
+ if (arrayIn instanceof Float32Array) return truncateFloat32(arrayIn, len);
+ if (arrayIn instanceof Float64Array) return truncateFloat64(arrayIn, len);
+ throw new Error("This array type is not yet supported by `truncate`.");
};
diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js
index 62a4b7e38d5..6984f674f63 100644
--- a/src/plot_api/helpers.js
+++ b/src/plot_api/helpers.js
@@ -5,436 +5,456 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var m4FromQuat = require("gl-mat4/fromQuat");
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-var m4FromQuat = require('gl-mat4/fromQuat');
-
-var Registry = require('../registry');
-var Lib = require('../lib');
-var Plots = require('../plots/plots');
-var Axes = require('../plots/cartesian/axes');
-var Color = require('../components/color');
-
+var Registry = require("../registry");
+var Lib = require("../lib");
+var Plots = require("../plots/plots");
+var Axes = require("../plots/cartesian/axes");
+var Color = require("../components/color");
// 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)
exports.getGraphDiv = function(gd) {
- var gdElement;
+ var gdElement;
- if(typeof gd === 'string') {
- gdElement = document.getElementById(gd);
+ if (typeof gd === "string") {
+ gdElement = document.getElementById(gd);
- if(gdElement === null) {
- throw new Error('No DOM element with id \'' + gd + '\' exists on the page.');
- }
-
- return gdElement;
- }
- else if(gd === null || gd === undefined) {
- throw new Error('DOM element provided is null or undefined');
+ if (gdElement === null) {
+ throw new Error(
+ "No DOM element with id '" + gd + "' exists on the page."
+ );
}
- return gd; // otherwise assume that gd is a DOM element
+ 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
};
// clear the promise queue if one of them got rejected
exports.clearPromiseQueue = function(gd) {
- if(Array.isArray(gd._promises) && gd._promises.length > 0) {
- Lib.log('Clearing previous rejected promises from queue.');
- }
+ if (Array.isArray(gd._promises) && gd._promises.length > 0) {
+ Lib.log("Clearing previous rejected promises from queue.");
+ }
- gd._promises = [];
+ gd._promises = [];
};
// make a few changes to the layout right away
// before it gets used for anything
// backward compatibility and cleanup of nonstandard options
exports.cleanLayout = function(layout) {
- var i, j;
-
- if(!layout) layout = {};
+ var i, j;
+
+ if (!layout) layout = {};
+
+ // cannot have (x|y)axis1, numbering goes axis, axis2, axis3...
+ if (layout.xaxis1) {
+ if (!layout.xaxis) layout.xaxis = layout.xaxis1;
+ delete layout.xaxis1;
+ }
+ if (layout.yaxis1) {
+ if (!layout.yaxis) layout.yaxis = layout.yaxis1;
+ delete layout.yaxis1;
+ }
+
+ var axList = Axes.list({ _fullLayout: layout });
+ for (i = 0; i < axList.length; i++) {
+ var ax = axList[i];
+ if (ax.anchor && ax.anchor !== "free") {
+ ax.anchor = Axes.cleanId(ax.anchor);
+ }
+ if (ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
- // cannot have (x|y)axis1, numbering goes axis, axis2, axis3...
- if(layout.xaxis1) {
- if(!layout.xaxis) layout.xaxis = layout.xaxis1;
- delete layout.xaxis1;
+ // old method of axis type - isdate and islog (before category existed)
+ if (!ax.type) {
+ if (ax.isdate) ax.type = "date";
+ else if (ax.islog) ax.type = "log";
+ else if (ax.isdate === false && ax.islog === false) ax.type = "linear";
}
- if(layout.yaxis1) {
- if(!layout.yaxis) layout.yaxis = layout.yaxis1;
- delete layout.yaxis1;
+ if (ax.autorange === "withzero" || ax.autorange === "tozero") {
+ ax.autorange = true;
+ ax.rangemode = "tozero";
}
-
- var axList = Axes.list({_fullLayout: layout});
- for(i = 0; i < axList.length; i++) {
- var ax = axList[i];
- if(ax.anchor && ax.anchor !== 'free') {
- ax.anchor = Axes.cleanId(ax.anchor);
- }
- if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
-
- // old method of axis type - isdate and islog (before category existed)
- if(!ax.type) {
- if(ax.isdate) ax.type = 'date';
- else if(ax.islog) ax.type = 'log';
- else if(ax.isdate === false && ax.islog === false) ax.type = 'linear';
- }
- if(ax.autorange === 'withzero' || ax.autorange === 'tozero') {
- ax.autorange = true;
- ax.rangemode = 'tozero';
- }
- delete ax.islog;
- delete ax.isdate;
- delete ax.categories; // replaced by _categories
-
- // prune empty domain arrays made before the new nestedProperty
- if(emptyContainer(ax, 'domain')) delete ax.domain;
-
- // autotick -> tickmode
- if(ax.autotick !== undefined) {
- if(ax.tickmode === undefined) {
- ax.tickmode = ax.autotick ? 'auto' : 'linear';
- }
- delete ax.autotick;
- }
+ delete ax.islog;
+ delete ax.isdate;
+ delete ax.categories;
+
+ // replaced by _categories
+ // prune empty domain arrays made before the new nestedProperty
+ if (emptyContainer(ax, "domain")) delete ax.domain;
+
+ // autotick -> tickmode
+ if (ax.autotick !== undefined) {
+ if (ax.tickmode === undefined) {
+ ax.tickmode = ax.autotick ? "auto" : "linear";
+ }
+ delete ax.autotick;
}
-
- var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0;
- for(i = 0; i < annotationsLen; i++) {
- var ann = layout.annotations[i];
-
- if(!Lib.isPlainObject(ann)) continue;
-
- if(ann.ref) {
- if(ann.ref === 'paper') {
- ann.xref = 'paper';
- ann.yref = 'paper';
- }
- else if(ann.ref === 'data') {
- ann.xref = 'x';
- ann.yref = 'y';
- }
- delete ann.ref;
- }
-
- cleanAxRef(ann, 'xref');
- cleanAxRef(ann, 'yref');
+ }
+
+ var annotationsLen = Array.isArray(layout.annotations)
+ ? layout.annotations.length
+ : 0;
+ for (i = 0; i < annotationsLen; i++) {
+ var ann = layout.annotations[i];
+
+ if (!Lib.isPlainObject(ann)) continue;
+
+ if (ann.ref) {
+ if (ann.ref === "paper") {
+ ann.xref = "paper";
+ ann.yref = "paper";
+ } else if (ann.ref === "data") {
+ ann.xref = "x";
+ ann.yref = "y";
+ }
+ delete ann.ref;
}
- var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0;
- for(i = 0; i < shapesLen; i++) {
- var shape = layout.shapes[i];
-
- if(!Lib.isPlainObject(shape)) continue;
-
- cleanAxRef(shape, 'xref');
- cleanAxRef(shape, 'yref');
+ cleanAxRef(ann, "xref");
+ cleanAxRef(ann, "yref");
+ }
+
+ var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0;
+ for (i = 0; i < shapesLen; i++) {
+ var shape = layout.shapes[i];
+
+ if (!Lib.isPlainObject(shape)) continue;
+
+ cleanAxRef(shape, "xref");
+ cleanAxRef(shape, "yref");
+ }
+
+ var legend = layout.legend;
+ if (legend) {
+ // check for old-style legend positioning (x or y is +/- 100)
+ if (legend.x > 3) {
+ legend.x = 1.02;
+ legend.xanchor = "left";
+ } else if (legend.x < -2) {
+ legend.x = -0.02;
+ legend.xanchor = "right";
}
- var legend = layout.legend;
- if(legend) {
- // check for old-style legend positioning (x or y is +/- 100)
- if(legend.x > 3) {
- legend.x = 1.02;
- legend.xanchor = 'left';
- }
- else if(legend.x < -2) {
- legend.x = -0.02;
- legend.xanchor = 'right';
- }
-
- if(legend.y > 3) {
- legend.y = 1.02;
- legend.yanchor = 'bottom';
- }
- else if(legend.y < -2) {
- legend.y = -0.02;
- legend.yanchor = 'top';
- }
+ if (legend.y > 3) {
+ legend.y = 1.02;
+ legend.yanchor = "bottom";
+ } else if (legend.y < -2) {
+ legend.y = -0.02;
+ legend.yanchor = "top";
}
+ }
- /*
+ /*
* Moved from rotate -> orbit for dragmode
*/
- if(layout.dragmode === 'rotate') layout.dragmode = 'orbit';
+ if (layout.dragmode === "rotate") layout.dragmode = "orbit";
- // cannot have scene1, numbering goes scene, scene2, scene3...
- if(layout.scene1) {
- if(!layout.scene) layout.scene = layout.scene1;
- delete layout.scene1;
- }
+ // cannot have scene1, numbering goes scene, scene2, scene3...
+ if (layout.scene1) {
+ if (!layout.scene) layout.scene = layout.scene1;
+ delete layout.scene1;
+ }
- /*
+ /*
* Clean up Scene layouts
*/
- var sceneIds = Plots.getSubplotIds(layout, 'gl3d');
- for(i = 0; i < sceneIds.length; i++) {
- var scene = layout[sceneIds[i]];
-
- // clean old Camera coords
- var cameraposition = scene.cameraposition;
- if(Array.isArray(cameraposition) && cameraposition[0].length === 4) {
- var rotation = cameraposition[0],
- center = cameraposition[1],
- radius = cameraposition[2],
- mat = m4FromQuat([], rotation),
- eye = [];
-
- for(j = 0; j < 3; ++j) {
- eye[j] = center[i] + radius * mat[2 + 4 * j];
- }
-
- scene.camera = {
- eye: {x: eye[0], y: eye[1], z: eye[2]},
- center: {x: center[0], y: center[1], z: center[2]},
- up: {x: mat[1], y: mat[5], z: mat[9]}
- };
-
- delete scene.cameraposition;
- }
+ var sceneIds = Plots.getSubplotIds(layout, "gl3d");
+ for (i = 0; i < sceneIds.length; i++) {
+ var scene = layout[sceneIds[i]];
+
+ // clean old Camera coords
+ var cameraposition = scene.cameraposition;
+ if (Array.isArray(cameraposition) && cameraposition[0].length === 4) {
+ var rotation = cameraposition[0],
+ center = cameraposition[1],
+ radius = cameraposition[2],
+ mat = m4FromQuat([], rotation),
+ eye = [];
+
+ for (j = 0; j < 3; ++j) {
+ eye[j] = center[i] + radius * mat[2 + 4 * j];
+ }
+
+ scene.camera = {
+ eye: { x: eye[0], y: eye[1], z: eye[2] },
+ center: { x: center[0], y: center[1], z: center[2] },
+ up: { x: mat[1], y: mat[5], z: mat[9] }
+ };
+
+ delete scene.cameraposition;
}
+ }
- // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
- // supported, but new tinycolor does not because they're not valid css
- Color.clean(layout);
+ // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
+ // supported, but new tinycolor does not because they're not valid css
+ Color.clean(layout);
- return layout;
+ return layout;
};
function cleanAxRef(container, attr) {
- var valIn = container[attr],
- axLetter = attr.charAt(0);
- if(valIn && valIn !== 'paper') {
- container[attr] = Axes.cleanId(valIn, axLetter);
- }
+ var valIn = container[attr], axLetter = attr.charAt(0);
+ if (valIn && valIn !== "paper") {
+ container[attr] = Axes.cleanId(valIn, axLetter);
+ }
}
// Make a few changes to the data right away
// before it gets used for anything
exports.cleanData = function(data, existingData) {
+ // Enforce unique IDs
+ var suids = [],
+ // seen uids --- so we can weed out incoming repeats
+ uids = data
+ .concat(Array.isArray(existingData) ? existingData : [])
+ .filter(function(trace) {
+ return "uid" in trace;
+ })
+ .map(function(trace) {
+ return trace.uid;
+ });
+
+ for (var tracei = 0; tracei < data.length; tracei++) {
+ var trace = data[tracei];
+ var i;
- // Enforce unique IDs
- var suids = [], // seen uids --- so we can weed out incoming repeats
- uids = data.concat(Array.isArray(existingData) ? existingData : [])
- .filter(function(trace) { return 'uid' in trace; })
- .map(function(trace) { return trace.uid; });
+ // assign uids to each trace and detect collisions.
+ if (!("uid" in trace) || suids.indexOf(trace.uid) !== -1) {
+ var newUid;
- for(var tracei = 0; tracei < data.length; tracei++) {
- var trace = data[tracei];
- var i;
+ for (i = 0; i < 100; i++) {
+ newUid = Lib.randstr(uids);
+ if (suids.indexOf(newUid) === -1) break;
+ }
+ trace.uid = Lib.randstr(uids);
+ uids.push(trace.uid);
+ }
+ // keep track of already seen uids, so that if there are
+ // doubles we force the trace with a repeat uid to
+ // acquire a new one
+ suids.push(trace.uid);
+
+ // BACKWARD COMPATIBILITY FIXES
+ // use xbins to bin data in x, and ybins to bin data in y
+ if (
+ trace.type === "histogramy" && "xbins" in trace && !("ybins" in trace)
+ ) {
+ trace.ybins = trace.xbins;
+ delete trace.xbins;
+ }
- // assign uids to each trace and detect collisions.
- if(!('uid' in trace) || suids.indexOf(trace.uid) !== -1) {
- var newUid;
+ // error_y.opacity is obsolete - merge into color
+ if (trace.error_y && "opacity" in trace.error_y) {
+ var dc = Color.defaults,
+ yeColor = trace.error_y.color ||
+ (Registry.traceIs(trace, "bar")
+ ? Color.defaultLine
+ : dc[tracei % dc.length]);
+ trace.error_y.color = Color.addOpacity(
+ Color.rgb(yeColor),
+ Color.opacity(yeColor) * trace.error_y.opacity
+ );
+ delete trace.error_y.opacity;
+ }
- for(i = 0; i < 100; i++) {
- newUid = Lib.randstr(uids);
- if(suids.indexOf(newUid) === -1) break;
- }
- trace.uid = Lib.randstr(uids);
- uids.push(trace.uid);
- }
- // keep track of already seen uids, so that if there are
- // doubles we force the trace with a repeat uid to
- // acquire a new one
- suids.push(trace.uid);
+ // convert bardir to orientation, and put the data into
+ // the axes it's eventually going to be used with
+ if ("bardir" in trace) {
+ if (
+ trace.bardir === "h" &&
+ (Registry.traceIs(trace, "bar") ||
+ trace.type.substr(0, 9) === "histogram")
+ ) {
+ trace.orientation = "h";
+ exports.swapXYData(trace);
+ }
+ delete trace.bardir;
+ }
- // BACKWARD COMPATIBILITY FIXES
+ // now we have only one 1D histogram type, and whether
+ // it uses x or y data depends on trace.orientation
+ if (trace.type === "histogramy") exports.swapXYData(trace);
+ if (trace.type === "histogramx" || trace.type === "histogramy") {
+ trace.type = "histogram";
+ }
- // use xbins to bin data in x, and ybins to bin data in y
- if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) {
- trace.ybins = trace.xbins;
- delete trace.xbins;
- }
+ // scl->scale, reversescl->reversescale
+ if ("scl" in trace) {
+ trace.colorscale = trace.scl;
+ delete trace.scl;
+ }
+ if ("reversescl" in trace) {
+ trace.reversescale = trace.reversescl;
+ delete trace.reversescl;
+ }
- // error_y.opacity is obsolete - merge into color
- if(trace.error_y && 'opacity' in trace.error_y) {
- var dc = Color.defaults,
- yeColor = trace.error_y.color ||
- (Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc.length]);
- trace.error_y.color = Color.addOpacity(
- Color.rgb(yeColor),
- Color.opacity(yeColor) * trace.error_y.opacity);
- delete trace.error_y.opacity;
- }
+ // axis ids x1 -> x, y1-> y
+ if (trace.xaxis) trace.xaxis = Axes.cleanId(trace.xaxis, "x");
+ if (trace.yaxis) trace.yaxis = Axes.cleanId(trace.yaxis, "y");
- // convert bardir to orientation, and put the data into
- // the axes it's eventually going to be used with
- if('bardir' in trace) {
- if(trace.bardir === 'h' && (Registry.traceIs(trace, 'bar') ||
- trace.type.substr(0, 9) === 'histogram')) {
- trace.orientation = 'h';
- exports.swapXYData(trace);
- }
- delete trace.bardir;
- }
+ // scene ids scene1 -> scene
+ if (Registry.traceIs(trace, "gl3d") && trace.scene) {
+ trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene);
+ }
- // now we have only one 1D histogram type, and whether
- // it uses x or y data depends on trace.orientation
- if(trace.type === 'histogramy') exports.swapXYData(trace);
- if(trace.type === 'histogramx' || trace.type === 'histogramy') {
- trace.type = 'histogram';
- }
+ if (!Registry.traceIs(trace, "pie") && !Registry.traceIs(trace, "bar")) {
+ if (Array.isArray(trace.textposition)) {
+ trace.textposition = trace.textposition.map(cleanTextPosition);
+ } else if (trace.textposition) {
+ trace.textposition = cleanTextPosition(trace.textposition);
+ }
+ }
- // scl->scale, reversescl->reversescale
- if('scl' in trace) {
- trace.colorscale = trace.scl;
- delete trace.scl;
- }
- if('reversescl' in trace) {
- trace.reversescale = trace.reversescl;
- delete trace.reversescl;
- }
+ // fix typo in colorscale definition
+ if (Registry.traceIs(trace, "2dMap")) {
+ if (trace.colorscale === "YIGnBu") trace.colorscale = "YlGnBu";
+ if (trace.colorscale === "YIOrRd") trace.colorscale = "YlOrRd";
+ }
+ if (Registry.traceIs(trace, "markerColorscale") && trace.marker) {
+ var cont = trace.marker;
+ if (cont.colorscale === "YIGnBu") cont.colorscale = "YlGnBu";
+ if (cont.colorscale === "YIOrRd") cont.colorscale = "YlOrRd";
+ }
- // axis ids x1 -> x, y1-> y
- if(trace.xaxis) trace.xaxis = Axes.cleanId(trace.xaxis, 'x');
- if(trace.yaxis) trace.yaxis = Axes.cleanId(trace.yaxis, 'y');
+ // fix typo in surface 'highlight*' definitions
+ if (trace.type === "surface" && Lib.isPlainObject(trace.contours)) {
+ var dims = ["x", "y", "z"];
- // scene ids scene1 -> scene
- if(Registry.traceIs(trace, 'gl3d') && trace.scene) {
- trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene);
- }
+ for (i = 0; i < dims.length; i++) {
+ var opts = trace.contours[dims[i]];
- if(!Registry.traceIs(trace, 'pie') && !Registry.traceIs(trace, 'bar')) {
- if(Array.isArray(trace.textposition)) {
- trace.textposition = trace.textposition.map(cleanTextPosition);
- }
- else if(trace.textposition) {
- trace.textposition = cleanTextPosition(trace.textposition);
- }
- }
+ if (!Lib.isPlainObject(opts)) continue;
- // fix typo in colorscale definition
- if(Registry.traceIs(trace, '2dMap')) {
- if(trace.colorscale === 'YIGnBu') trace.colorscale = 'YlGnBu';
- if(trace.colorscale === 'YIOrRd') trace.colorscale = 'YlOrRd';
+ if (opts.highlightColor) {
+ opts.highlightcolor = opts.highlightColor;
+ delete opts.highlightColor;
}
- if(Registry.traceIs(trace, 'markerColorscale') && trace.marker) {
- var cont = trace.marker;
- if(cont.colorscale === 'YIGnBu') cont.colorscale = 'YlGnBu';
- if(cont.colorscale === 'YIOrRd') cont.colorscale = 'YlOrRd';
- }
-
- // fix typo in surface 'highlight*' definitions
- if(trace.type === 'surface' && Lib.isPlainObject(trace.contours)) {
- var dims = ['x', 'y', 'z'];
-
- for(i = 0; i < dims.length; i++) {
- var opts = trace.contours[dims[i]];
- if(!Lib.isPlainObject(opts)) continue;
-
- if(opts.highlightColor) {
- opts.highlightcolor = opts.highlightColor;
- delete opts.highlightColor;
- }
-
- if(opts.highlightWidth) {
- opts.highlightwidth = opts.highlightWidth;
- delete opts.highlightWidth;
- }
- }
+ if (opts.highlightWidth) {
+ opts.highlightwidth = opts.highlightWidth;
+ delete opts.highlightWidth;
}
+ }
+ }
- // transforms backward compatibility fixes
- if(Array.isArray(trace.transforms)) {
- var transforms = trace.transforms;
+ // transforms backward compatibility fixes
+ if (Array.isArray(trace.transforms)) {
+ var transforms = trace.transforms;
- for(i = 0; i < transforms.length; i++) {
- var transform = transforms[i];
+ for (i = 0; i < transforms.length; i++) {
+ var transform = transforms[i];
- if(!Lib.isPlainObject(transform)) continue;
+ if (!Lib.isPlainObject(transform)) continue;
- if(transform.type === 'filter') {
- if(transform.filtersrc) {
- transform.target = transform.filtersrc;
- delete transform.filtersrc;
- }
+ if (transform.type === "filter") {
+ if (transform.filtersrc) {
+ transform.target = transform.filtersrc;
+ delete transform.filtersrc;
+ }
- if(transform.calendar) {
- if(!transform.valuecalendar) {
- transform.valuecalendar = transform.calendar;
- }
- delete transform.calendar;
- }
- }
+ if (transform.calendar) {
+ if (!transform.valuecalendar) {
+ transform.valuecalendar = transform.calendar;
}
+ delete transform.calendar;
+ }
}
+ }
+ }
- // prune empty containers made before the new nestedProperty
- if(emptyContainer(trace, 'line')) delete trace.line;
- if('marker' in trace) {
- if(emptyContainer(trace.marker, 'line')) delete trace.marker.line;
- if(emptyContainer(trace, 'marker')) delete trace.marker;
- }
-
- // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
- // supported, but new tinycolor does not because they're not valid css
- Color.clean(trace);
+ // prune empty containers made before the new nestedProperty
+ if (emptyContainer(trace, "line")) delete trace.line;
+ if ("marker" in trace) {
+ if (emptyContainer(trace.marker, "line")) delete trace.marker.line;
+ if (emptyContainer(trace, "marker")) delete trace.marker;
}
+
+ // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
+ // supported, but new tinycolor does not because they're not valid css
+ Color.clean(trace);
+ }
};
// textposition - support partial attributes (ie just 'top')
// and incorrect use of middle / center etc.
function cleanTextPosition(textposition) {
- var posY = 'middle',
- posX = 'center';
- if(textposition.indexOf('top') !== -1) posY = 'top';
- else if(textposition.indexOf('bottom') !== -1) posY = 'bottom';
+ var posY = "middle", posX = "center";
+ if (textposition.indexOf("top") !== -1) posY = "top";
+ else if (textposition.indexOf("bottom") !== -1) posY = "bottom";
- if(textposition.indexOf('left') !== -1) posX = 'left';
- else if(textposition.indexOf('right') !== -1) posX = 'right';
+ if (textposition.indexOf("left") !== -1) posX = "left";
+ else if (textposition.indexOf("right") !== -1) posX = "right";
- return posY + ' ' + posX;
+ return posY + " " + posX;
}
function emptyContainer(outer, innerStr) {
- return (innerStr in outer) &&
- (typeof outer[innerStr] === 'object') &&
- (Object.keys(outer[innerStr]).length === 0);
+ return innerStr in outer &&
+ typeof outer[innerStr] === "object" &&
+ Object.keys(outer[innerStr]).length === 0;
}
-
// swap all the data and data attributes associated with x and y
exports.swapXYData = function(trace) {
- var i;
- Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']);
- if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) {
- if(trace.transpose) delete trace.transpose;
- else trace.transpose = true;
+ var i;
+ Lib.swapAttrs(trace, [
+ "?",
+ "?0",
+ "d?",
+ "?bins",
+ "nbins?",
+ "autobin?",
+ "?src",
+ "error_?"
+ ]);
+ if (Array.isArray(trace.z) && Array.isArray(trace.z[0])) {
+ if (trace.transpose) delete trace.transpose;
+ else trace.transpose = true;
+ }
+ if (trace.error_x && trace.error_y) {
+ var errorY = trace.error_y,
+ copyYstyle = "copy_ystyle" in errorY
+ ? errorY.copy_ystyle
+ : !(errorY.color || errorY.thickness || errorY.width);
+ Lib.swapAttrs(trace, ["error_?.copy_ystyle"]);
+ if (copyYstyle) {
+ Lib.swapAttrs(trace, [
+ "error_?.color",
+ "error_?.thickness",
+ "error_?.width"
+ ]);
}
- if(trace.error_x && trace.error_y) {
- var errorY = trace.error_y,
- copyYstyle = ('copy_ystyle' in errorY) ? errorY.copy_ystyle :
- !(errorY.color || errorY.thickness || errorY.width);
- Lib.swapAttrs(trace, ['error_?.copy_ystyle']);
- if(copyYstyle) {
- Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']);
- }
- }
- if(trace.hoverinfo) {
- var hoverInfoParts = trace.hoverinfo.split('+');
- for(i = 0; i < hoverInfoParts.length; i++) {
- if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y';
- else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x';
- }
- trace.hoverinfo = hoverInfoParts.join('+');
+ }
+ if (trace.hoverinfo) {
+ var hoverInfoParts = trace.hoverinfo.split("+");
+ for (i = 0; i < hoverInfoParts.length; i++) {
+ if (hoverInfoParts[i] === "x") hoverInfoParts[i] = "y";
+ else if (hoverInfoParts[i] === "y") hoverInfoParts[i] = "x";
}
+ trace.hoverinfo = hoverInfoParts.join("+");
+ }
};
// coerce traceIndices input to array of trace indices
exports.coerceTraceIndices = function(gd, traceIndices) {
- if(isNumeric(traceIndices)) {
- return [traceIndices];
- }
- else if(!Array.isArray(traceIndices) || !traceIndices.length) {
- return gd.data.map(function(_, i) { return i; });
- }
-
- return traceIndices;
+ if (isNumeric(traceIndices)) {
+ return [traceIndices];
+ } else if (!Array.isArray(traceIndices) || !traceIndices.length) {
+ return gd.data.map(function(_, i) {
+ return i;
+ });
+ }
+
+ return traceIndices;
};
/**
@@ -450,37 +470,31 @@ exports.coerceTraceIndices = function(gd, traceIndices) {
*
*/
exports.manageArrayContainers = function(np, newVal, undoit) {
- var obj = np.obj,
- parts = np.parts,
- pLength = parts.length,
- pLast = parts[pLength - 1];
-
- var pLastIsNumber = isNumeric(pLast);
-
- // delete item
- if(pLastIsNumber && newVal === null) {
-
- // Clear item in array container when new value is null
- var contPath = parts.slice(0, pLength - 1).join('.'),
- cont = Lib.nestedProperty(obj, contPath).get();
- cont.splice(pLast, 1);
-
- // Note that nested property clears null / undefined at end of
- // array container, but not within them.
- }
+ var obj = np.obj,
+ parts = np.parts,
+ pLength = parts.length,
+ pLast = parts[pLength - 1];
+
+ var pLastIsNumber = isNumeric(pLast);
+
+ // delete item
+ if (pLastIsNumber && newVal === null) {
+ // Clear item in array container when new value is null
+ var contPath = parts.slice(0, pLength - 1).join("."),
+ cont = Lib.nestedProperty(obj, contPath).get();
+ cont.splice(pLast, 1);
+ // Note that nested property clears null / undefined at end of
+ // array container, but not within them.
+ } else if (pLastIsNumber && np.get() === undefined) {
// create item
- else if(pLastIsNumber && np.get() === undefined) {
-
- // When adding a new item, make sure undo command will remove it
- if(np.get() === undefined) undoit[np.astr] = null;
+ // When adding a new item, make sure undo command will remove it
+ if (np.get() === undefined) undoit[np.astr] = null;
- np.set(newVal);
- }
+ np.set(newVal);
+ } else {
// update item
- else {
-
- // If the last part of attribute string isn't a number,
- // np.set is all we need.
- np.set(newVal);
- }
+ // If the last part of attribute string isn't a number,
+ // np.set is all we need.
+ np.set(newVal);
+ }
};
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 3f5f80444d5..7a6d6062308 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -5,32 +5,27 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
+var Plotly = require("../plotly");
+var Lib = require("../lib");
+var Events = require("../lib/events");
+var Queue = require("../lib/queue");
-'use strict';
+var Registry = require("../registry");
+var Plots = require("../plots/plots");
+var Fx = require("../plots/cartesian/graph_interact");
+var Polar = require("../plots/polar");
+var Drawing = require("../components/drawing");
+var ErrorBars = require("../components/errorbars");
+var xmlnsNamespaces = require("../constants/xmlns_namespaces");
+var svgTextUtils = require("../lib/svg_text_utils");
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Plotly = require('../plotly');
-var Lib = require('../lib');
-var Events = require('../lib/events');
-var Queue = require('../lib/queue');
-
-var Registry = require('../registry');
-var Plots = require('../plots/plots');
-var Fx = require('../plots/cartesian/graph_interact');
-var Polar = require('../plots/polar');
-
-var Drawing = require('../components/drawing');
-var ErrorBars = require('../components/errorbars');
-var xmlnsNamespaces = require('../constants/xmlns_namespaces');
-var svgTextUtils = require('../lib/svg_text_utils');
-
-var helpers = require('./helpers');
-var subroutines = require('./subroutines');
-
+var helpers = require("./helpers");
+var subroutines = require("./subroutines");
/**
* Main plot-creation function
@@ -47,465 +42,468 @@ var subroutines = require('./subroutines');
*
*/
Plotly.plot = function(gd, data, layout, config) {
- var frames;
-
- gd = helpers.getGraphDiv(gd);
-
- // Events.init is idempotent and bails early if gd has already been init'd
- Events.init(gd);
-
- if(Lib.isPlainObject(data)) {
- var obj = data;
- data = obj.data;
- layout = obj.layout;
- config = obj.config;
- frames = obj.frames;
- }
-
- var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]);
- if(okToPlot === false) return Promise.reject();
-
- // if there's no data or layout, and this isn't yet a plotly plot
- // container, log a warning to help plotly.js users debug
- if(!data && !layout && !Lib.isPlotDiv(gd)) {
- Lib.warn('Calling Plotly.plot as if redrawing ' +
- 'but this container doesn\'t yet have a plot.', gd);
- }
+ var frames;
+
+ gd = helpers.getGraphDiv(gd);
+
+ // Events.init is idempotent and bails early if gd has already been init'd
+ Events.init(gd);
+
+ if (Lib.isPlainObject(data)) {
+ var obj = data;
+ data = obj.data;
+ layout = obj.layout;
+ config = obj.config;
+ frames = obj.frames;
+ }
+
+ var okToPlot = Events.triggerHandler(gd, "plotly_beforeplot", [
+ data,
+ layout,
+ config
+ ]);
+ if (okToPlot === false) return Promise.reject();
+
+ // if there's no data or layout, and this isn't yet a plotly plot
+ // container, log a warning to help plotly.js users debug
+ if (!data && !layout && !Lib.isPlotDiv(gd)) {
+ Lib.warn(
+ "Calling Plotly.plot as if redrawing " +
+ "but this container doesn't yet have a plot.",
+ gd
+ );
+ }
- function addFrames() {
- if(frames) {
- return Plotly.addFrames(gd, frames);
- }
+ function addFrames() {
+ if (frames) {
+ return Plotly.addFrames(gd, frames);
}
+ }
- // transfer configuration options to gd until we move over to
- // a more OO like model
- setPlotContext(gd, config);
+ // transfer configuration options to gd until we move over to
+ // a more OO like model
+ setPlotContext(gd, config);
- if(!layout) layout = {};
+ if (!layout) layout = {};
- // hook class for plots main container (in case of plotly.js
- // this won't be #embedded-graph or .js-tab-contents)
- d3.select(gd).classed('js-plotly-plot', true);
+ // hook class for plots main container (in case of plotly.js
+ // this won't be #embedded-graph or .js-tab-contents)
+ d3.select(gd).classed("js-plotly-plot", true);
- // off-screen getBoundingClientRect testing space,
- // in #js-plotly-tester (and stored as gd._tester)
- // so we can share cached text across tabs
- Drawing.makeTester(gd);
+ // off-screen getBoundingClientRect testing space,
+ // in #js-plotly-tester (and stored as gd._tester)
+ // so we can share cached text across tabs
+ Drawing.makeTester(gd);
- // collect promises for any async actions during plotting
- // any part of the plotting code can push to gd._promises, then
- // before we move to the next step, we check that they're all
- // complete, and empty out the promise list again.
- gd._promises = [];
+ // collect promises for any async actions during plotting
+ // any part of the plotting code can push to gd._promises, then
+ // before we move to the next step, we check that they're all
+ // complete, and empty out the promise list again.
+ gd._promises = [];
- var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data));
+ var graphWasEmpty = (gd.data || []).length === 0 && Array.isArray(data);
- // if there is already data on the graph, append the new data
- // if you only want to redraw, pass a non-array for data
- if(Array.isArray(data)) {
- helpers.cleanData(data, gd.data);
+ // if there is already data on the graph, append the new data
+ // if you only want to redraw, pass a non-array for data
+ if (Array.isArray(data)) {
+ helpers.cleanData(data, gd.data);
- if(graphWasEmpty) gd.data = data;
- else gd.data.push.apply(gd.data, data);
-
- // for routines outside graph_obj that want a clean tab
- // (rather than appending to an existing one) gd.empty
- // is used to determine whether to make a new tab
- gd.empty = false;
- }
+ if (graphWasEmpty) gd.data = data;
+ else gd.data.push.apply(gd.data, data);
- if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout);
+ // for routines outside graph_obj that want a clean tab
+ // (rather than appending to an existing one) gd.empty
+ // is used to determine whether to make a new tab
+ gd.empty = false;
+ }
- // if the user is trying to drag the axes, allow new data and layout
- // to come in but don't allow a replot.
- if(gd._dragging && !gd._transitioning) {
- // signal to drag handler that after everything else is done
- // we need to replot, because something has changed
- gd._replotPending = true;
- return Promise.reject();
- } else {
- // we're going ahead with a replot now
- gd._replotPending = false;
- }
+ if (!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout);
- Plots.supplyDefaults(gd);
+ // if the user is trying to drag the axes, allow new data and layout
+ // to come in but don't allow a replot.
+ if (gd._dragging && !gd._transitioning) {
+ // signal to drag handler that after everything else is done
+ // we need to replot, because something has changed
+ gd._replotPending = true;
+ return Promise.reject();
+ } else {
+ // we're going ahead with a replot now
+ gd._replotPending = false;
+ }
- // Polar plots
- if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
+ Plots.supplyDefaults(gd);
- // so we don't try to re-call Plotly.plot from inside
- // legend and colorbar, if margins changed
- gd._replotting = true;
+ // Polar plots
+ if (data && data[0] && data[0].r) return plotPolar(gd, data, layout);
- // make or remake the framework if we need to
- if(graphWasEmpty) makePlotFramework(gd);
+ // so we don't try to re-call Plotly.plot from inside
+ // legend and colorbar, if margins changed
+ gd._replotting = true;
- // polar need a different framework
- if(gd.framework !== makePlotFramework) {
- gd.framework = makePlotFramework;
- makePlotFramework(gd);
- }
+ // make or remake the framework if we need to
+ if (graphWasEmpty) makePlotFramework(gd);
- // save initial axis range once per graph
- if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
+ // polar need a different framework
+ if (gd.framework !== makePlotFramework) {
+ gd.framework = makePlotFramework;
+ makePlotFramework(gd);
+ }
- var fullLayout = gd._fullLayout;
+ // save initial axis range once per graph
+ if (graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
- // prepare the data and find the autorange
+ var fullLayout = gd._fullLayout;
- // generate calcdata, if we need to
- // to force redoing calcdata, just delete it before calling Plotly.plot
- var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
- if(recalc) Plots.doCalcdata(gd);
+ // prepare the data and find the autorange
+ // generate calcdata, if we need to
+ // to force redoing calcdata, just delete it before calling Plotly.plot
+ var recalc = !gd.calcdata ||
+ gd.calcdata.length !== (gd._fullData || []).length;
+ if (recalc) Plots.doCalcdata(gd);
- // in case it has changed, attach fullData traces to calcdata
- for(var i = 0; i < gd.calcdata.length; i++) {
- gd.calcdata[i][0].trace = gd._fullData[i];
- }
+ // in case it has changed, attach fullData traces to calcdata
+ for (var i = 0; i < gd.calcdata.length; i++) {
+ gd.calcdata[i][0].trace = gd._fullData[i];
+ }
- /*
+ /*
* start async-friendly code - now we're actually drawing things
*/
+ var oldmargins = JSON.stringify(fullLayout._size);
+
+ // draw framework first so that margin-pushing
+ // components can position themselves correctly
+ function drawFramework() {
+ var basePlotModules = fullLayout._basePlotModules;
+
+ for (var i = 0; i < basePlotModules.length; i++) {
+ if (basePlotModules[i].drawFramework) {
+ basePlotModules[i].drawFramework(gd);
+ }
+ }
+
+ return Lib.syncOrAsync([subroutines.layoutStyles, drawAxes, Fx.init], gd);
+ }
+
+ // draw anything that can affect margins.
+ // currently this is legend and colorbars
+ function marginPushers() {
+ var calcdata = gd.calcdata;
+ var i, cd, trace;
+
+ Registry.getComponentMethod("legend", "draw")(gd);
+ Registry.getComponentMethod("rangeselector", "draw")(gd);
+ Registry.getComponentMethod("sliders", "draw")(gd);
+ Registry.getComponentMethod("updatemenus", "draw")(gd);
+
+ for (i = 0; i < calcdata.length; i++) {
+ cd = calcdata[i];
+ trace = cd[0].trace;
+ if (trace.visible !== true || !trace._module.colorbar) {
+ Plots.autoMargin(gd, "cb" + trace.uid);
+ } else {
+ trace._module.colorbar(gd, cd);
+ }
+ }
+
+ Plots.doAutoMargin(gd);
+ return Plots.previousPromises(gd);
+ }
+
+ // in case the margins changed, draw margin pushers again
+ function marginPushersAgain() {
+ var seq = JSON.stringify(fullLayout._size) === oldmargins
+ ? []
+ : [marginPushers, subroutines.layoutStyles];
+
+ // re-initialize cartesian interaction,
+ // which are sometimes cleared during marginPushers
+ seq = seq.concat(Fx.init);
+
+ return Lib.syncOrAsync(seq, gd);
+ }
+
+ function positionAndAutorange() {
+ if (!recalc) return;
+
+ var subplots = Plots.getSubplotIds(fullLayout, "cartesian"),
+ modules = fullLayout._modules;
+
+ // position and range calculations for traces that
+ // depend on each other ie bars (stacked or grouped)
+ // and boxes (grouped) push each other out of the way
+ var subplotInfo, _module;
+
+ for (var i = 0; i < subplots.length; i++) {
+ subplotInfo = fullLayout._plots[subplots[i]];
+
+ for (var j = 0; j < modules.length; j++) {
+ _module = modules[j];
+ if (_module.setPositions) _module.setPositions(gd, subplotInfo);
+ }
+ }
+
+ // calc and autorange for errorbars
+ ErrorBars.calc(gd);
+
+ // TODO: autosize extra for text markers
+ return Lib.syncOrAsync(
+ [
+ Registry.getComponentMethod("shapes", "calcAutorange"),
+ Registry.getComponentMethod("annotations", "calcAutorange"),
+ doAutoRange
+ ],
+ gd
+ );
+ }
- var oldmargins = JSON.stringify(fullLayout._size);
-
- // draw framework first so that margin-pushing
- // components can position themselves correctly
- function drawFramework() {
- var basePlotModules = fullLayout._basePlotModules;
-
- for(var i = 0; i < basePlotModules.length; i++) {
- if(basePlotModules[i].drawFramework) {
- basePlotModules[i].drawFramework(gd);
- }
- }
-
- return Lib.syncOrAsync([
- subroutines.layoutStyles,
- drawAxes,
- Fx.init
- ], gd);
- }
-
- // draw anything that can affect margins.
- // currently this is legend and colorbars
- function marginPushers() {
- var calcdata = gd.calcdata;
- var i, cd, trace;
-
- Registry.getComponentMethod('legend', 'draw')(gd);
- Registry.getComponentMethod('rangeselector', 'draw')(gd);
- Registry.getComponentMethod('sliders', 'draw')(gd);
- Registry.getComponentMethod('updatemenus', 'draw')(gd);
-
- for(i = 0; i < calcdata.length; i++) {
- cd = calcdata[i];
- trace = cd[0].trace;
- if(trace.visible !== true || !trace._module.colorbar) {
- Plots.autoMargin(gd, 'cb' + trace.uid);
- }
- else trace._module.colorbar(gd, cd);
- }
-
- Plots.doAutoMargin(gd);
- return Plots.previousPromises(gd);
- }
-
- // in case the margins changed, draw margin pushers again
- function marginPushersAgain() {
- var seq = JSON.stringify(fullLayout._size) === oldmargins ?
- [] :
- [marginPushers, subroutines.layoutStyles];
-
- // re-initialize cartesian interaction,
- // which are sometimes cleared during marginPushers
- seq = seq.concat(Fx.init);
+ function doAutoRange() {
+ if (gd._transitioning) return;
- return Lib.syncOrAsync(seq, gd);
+ var axList = Plotly.Axes.list(gd, "", true);
+ for (var i = 0; i < axList.length; i++) {
+ Plotly.Axes.doAutoRange(axList[i]);
}
+ }
- function positionAndAutorange() {
- if(!recalc) return;
+ // draw ticks, titles, and calculate axis scaling (._b, ._m)
+ function drawAxes() {
+ return Plotly.Axes.doTicks(gd, "redraw");
+ }
- var subplots = Plots.getSubplotIds(fullLayout, 'cartesian'),
- modules = fullLayout._modules;
+ // Now plot the data
+ function drawData() {
+ var calcdata = gd.calcdata, i;
- // position and range calculations for traces that
- // depend on each other ie bars (stacked or grouped)
- // and boxes (grouped) push each other out of the way
-
- var subplotInfo, _module;
-
- for(var i = 0; i < subplots.length; i++) {
- subplotInfo = fullLayout._plots[subplots[i]];
-
- for(var j = 0; j < modules.length; j++) {
- _module = modules[j];
- if(_module.setPositions) _module.setPositions(gd, subplotInfo);
- }
- }
+ // in case of traces that were heatmaps or contour maps
+ // previously, remove them and their colorbars explicitly
+ for (i = 0; i < calcdata.length; i++) {
+ var trace = calcdata[i][0].trace,
+ isVisible = trace.visible === true,
+ uid = trace.uid;
- // calc and autorange for errorbars
- ErrorBars.calc(gd);
+ if (!isVisible || !Registry.traceIs(trace, "2dMap")) {
+ fullLayout._paper
+ .selectAll(".hm" + uid + ",.contour" + uid + ",#clip" + uid)
+ .remove();
+ }
- // TODO: autosize extra for text markers
- return Lib.syncOrAsync([
- Registry.getComponentMethod('shapes', 'calcAutorange'),
- Registry.getComponentMethod('annotations', 'calcAutorange'),
- doAutoRange
- ], gd);
+ if (!isVisible || !trace._module.colorbar) {
+ fullLayout._infolayer.selectAll(".cb" + uid).remove();
+ }
}
- function doAutoRange() {
- if(gd._transitioning) return;
-
- var axList = Plotly.Axes.list(gd, '', true);
- for(var i = 0; i < axList.length; i++) {
- Plotly.Axes.doAutoRange(axList[i]);
- }
+ // loop over the base plot modules present on graph
+ var basePlotModules = fullLayout._basePlotModules;
+ for (i = 0; i < basePlotModules.length; i++) {
+ basePlotModules[i].plot(gd);
}
- // draw ticks, titles, and calculate axis scaling (._b, ._m)
- function drawAxes() {
- return Plotly.Axes.doTicks(gd, 'redraw');
- }
+ // keep reference to shape layers in subplots
+ var layerSubplot = fullLayout._paper.selectAll(".layer-subplot");
+ fullLayout._imageSubplotLayer = layerSubplot.selectAll(".imagelayer");
+ fullLayout._shapeSubplotLayer = layerSubplot.selectAll(".shapelayer");
- // Now plot the data
- function drawData() {
- var calcdata = gd.calcdata,
- i;
-
- // in case of traces that were heatmaps or contour maps
- // previously, remove them and their colorbars explicitly
- for(i = 0; i < calcdata.length; i++) {
- var trace = calcdata[i][0].trace,
- isVisible = (trace.visible === true),
- uid = trace.uid;
-
- if(!isVisible || !Registry.traceIs(trace, '2dMap')) {
- fullLayout._paper.selectAll(
- '.hm' + uid +
- ',.contour' + uid +
- ',#clip' + uid
- ).remove();
- }
+ // styling separate from drawing
+ Plots.style(gd);
- if(!isVisible || !trace._module.colorbar) {
- fullLayout._infolayer.selectAll('.cb' + uid).remove();
- }
- }
+ // show annotations and shapes
+ Registry.getComponentMethod("shapes", "draw")(gd);
+ Registry.getComponentMethod("annotations", "draw")(gd);
- // loop over the base plot modules present on graph
- var basePlotModules = fullLayout._basePlotModules;
- for(i = 0; i < basePlotModules.length; i++) {
- basePlotModules[i].plot(gd);
- }
-
- // keep reference to shape layers in subplots
- var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
- fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
- fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');
-
- // styling separate from drawing
- Plots.style(gd);
-
- // show annotations and shapes
- Registry.getComponentMethod('shapes', 'draw')(gd);
- Registry.getComponentMethod('annotations', 'draw')(gd);
-
- // source links
- Plots.addLinks(gd);
-
- // Mark the first render as complete
- gd._replotting = false;
-
- return Plots.previousPromises(gd);
- }
-
- // An initial paint must be completed before these components can be
- // correctly sized and the whole plot re-margined. gd._replotting must
- // be set to false before these will work properly.
- function finalDraw() {
- Registry.getComponentMethod('shapes', 'draw')(gd);
- Registry.getComponentMethod('images', 'draw')(gd);
- Registry.getComponentMethod('annotations', 'draw')(gd);
- Registry.getComponentMethod('legend', 'draw')(gd);
- Registry.getComponentMethod('rangeslider', 'draw')(gd);
- Registry.getComponentMethod('rangeselector', 'draw')(gd);
- Registry.getComponentMethod('sliders', 'draw')(gd);
- Registry.getComponentMethod('updatemenus', 'draw')(gd);
- }
+ // source links
+ Plots.addLinks(gd);
- Lib.syncOrAsync([
- Plots.previousPromises,
- addFrames,
- drawFramework,
- marginPushers,
- marginPushersAgain,
- positionAndAutorange,
- subroutines.layoutStyles,
- drawAxes,
- drawData,
- finalDraw
- ], gd);
-
- // even if everything we did was synchronous, return a promise
- // so that the caller doesn't care which route we took
- return Promise.all(gd._promises).then(function() {
- gd.emit('plotly_afterplot');
- return gd;
- });
+ // Mark the first render as complete
+ gd._replotting = false;
+
+ return Plots.previousPromises(gd);
+ }
+
+ // An initial paint must be completed before these components can be
+ // correctly sized and the whole plot re-margined. gd._replotting must
+ // be set to false before these will work properly.
+ function finalDraw() {
+ Registry.getComponentMethod("shapes", "draw")(gd);
+ Registry.getComponentMethod("images", "draw")(gd);
+ Registry.getComponentMethod("annotations", "draw")(gd);
+ Registry.getComponentMethod("legend", "draw")(gd);
+ Registry.getComponentMethod("rangeslider", "draw")(gd);
+ Registry.getComponentMethod("rangeselector", "draw")(gd);
+ Registry.getComponentMethod("sliders", "draw")(gd);
+ Registry.getComponentMethod("updatemenus", "draw")(gd);
+ }
+
+ Lib.syncOrAsync(
+ [
+ Plots.previousPromises,
+ addFrames,
+ drawFramework,
+ marginPushers,
+ marginPushersAgain,
+ positionAndAutorange,
+ subroutines.layoutStyles,
+ drawAxes,
+ drawData,
+ finalDraw
+ ],
+ gd
+ );
+
+ // even if everything we did was synchronous, return a promise
+ // so that the caller doesn't care which route we took
+ return Promise.all(gd._promises).then(function() {
+ gd.emit("plotly_afterplot");
+ return gd;
+ });
};
-
function opaqueSetBackground(gd, bgColor) {
- gd._fullLayout._paperdiv.style('background', 'white');
- Plotly.defaultConfig.setBackground(gd, bgColor);
+ gd._fullLayout._paperdiv.style("background", "white");
+ Plotly.defaultConfig.setBackground(gd, bgColor);
}
function setPlotContext(gd, config) {
- if(!gd._context) gd._context = 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];
- }
- });
-
- // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility
- if(config.plot3dPixelRatio && !context.plotGlPixelRatio) {
- context.plotGlPixelRatio = context.plot3dPixelRatio;
+ if (!gd._context) gd._context = 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];
}
- }
+ }
+ });
- // 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;
- }
+ // 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 plotPolar(gd, data, layout) {
- // build or reuse the container skeleton
- var plotContainer = d3.select(gd).selectAll('.plot-container')
- .data([0]);
- plotContainer.enter()
- .insert('div', ':first-child')
- .classed('plot-container plotly', true);
- var paperDiv = plotContainer.selectAll('.svg-container')
- .data([0]);
- paperDiv.enter().append('div')
- .classed('svg-container', true)
- .style('position', 'relative');
-
- // empty it everytime for now
- paperDiv.html('');
-
- // fulfill gd requirements
- if(data) gd.data = data;
- if(layout) gd.layout = layout;
- Polar.manager.fillLayout(gd);
-
- // resize canvas
- paperDiv.style({
- width: gd._fullLayout.width + 'px',
- height: gd._fullLayout.height + 'px'
- });
-
- // instantiate framework
- gd.framework = Polar.manager.framework(gd);
-
- // plot
- gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node());
-
- // set undo point
- gd.framework.setUndoPoint();
-
- // get the resulting svg for extending it
- var polarPlotSVG = gd.framework.svg();
-
- // editable title
- var opacity = 1;
- var txt = gd._fullLayout.title;
- if(txt === '' || !txt) opacity = 0;
- var placeholderText = 'Click to enter title';
+ // build or reuse the container skeleton
+ var plotContainer = d3.select(gd).selectAll(".plot-container").data([0]);
+ plotContainer
+ .enter()
+ .insert("div", ":first-child")
+ .classed("plot-container plotly", true);
+ var paperDiv = plotContainer.selectAll(".svg-container").data([0]);
+ paperDiv
+ .enter()
+ .append("div")
+ .classed("svg-container", true)
+ .style("position", "relative");
+
+ // empty it everytime for now
+ paperDiv.html("");
+
+ // fulfill gd requirements
+ if (data) gd.data = data;
+ if (layout) gd.layout = layout;
+ Polar.manager.fillLayout(gd);
+
+ // resize canvas
+ paperDiv.style({
+ width: gd._fullLayout.width + "px",
+ height: gd._fullLayout.height + "px"
+ });
+
+ // instantiate framework
+ gd.framework = Polar.manager.framework(gd);
+
+ // plot
+ gd.framework({ data: gd.data, layout: gd.layout }, paperDiv.node());
+
+ // set undo point
+ gd.framework.setUndoPoint();
+
+ // get the resulting svg for extending it
+ var polarPlotSVG = gd.framework.svg();
+
+ // editable title
+ var opacity = 1;
+ var txt = gd._fullLayout.title;
+ if (txt === "" || !txt) opacity = 0;
+ var placeholderText = "Click to enter title";
+
+ var titleLayout = function() {
+ this.call(svgTextUtils.convertToTspans);
+ // TODO: html/mathjax
+ // TODO: center title
+ };
+
+ var title = polarPlotSVG.select(".title-group text").call(titleLayout);
+
+ if (gd._context.editable) {
+ title.attr({ "data-unformatted": txt });
+ if (!txt || txt === placeholderText) {
+ opacity = 0.2;
+ title
+ .attr({ "data-unformatted": placeholderText })
+ .text(placeholderText)
+ .style({ opacity: opacity })
+ .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);
+ });
+ }
- var titleLayout = function() {
- this.call(svgTextUtils.convertToTspans);
- // TODO: html/mathjax
- // TODO: center title
+ var setContenteditable = function() {
+ this
+ .call(svgTextUtils.makeEditable)
+ .on("edit", function(text) {
+ gd.framework({ layout: { title: text } });
+ this.attr({ "data-unformatted": text }).text(text).call(titleLayout);
+ this.call(setContenteditable);
+ })
+ .on("cancel", function() {
+ var txt = this.attr("data-unformatted");
+ this.text(txt).call(titleLayout);
+ });
};
+ title.call(setContenteditable);
+ }
- var title = polarPlotSVG.select('.title-group text')
- .call(titleLayout);
-
- if(gd._context.editable) {
- title.attr({'data-unformatted': txt});
- if(!txt || txt === placeholderText) {
- opacity = 0.2;
- title.attr({'data-unformatted': placeholderText})
- .text(placeholderText)
- .style({opacity: opacity})
- .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);
- });
- }
-
- var setContenteditable = function() {
- this.call(svgTextUtils.makeEditable)
- .on('edit', function(text) {
- gd.framework({layout: {title: text}});
- this.attr({'data-unformatted': text})
- .text(text)
- .call(titleLayout);
- this.call(setContenteditable);
- })
- .on('cancel', function() {
- var txt = this.attr('data-unformatted');
- this.text(txt).call(titleLayout);
- });
- };
- title.call(setContenteditable);
- }
-
- gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor);
- Plots.addLinks(gd);
+ gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor);
+ Plots.addLinks(gd);
- return Promise.resolve();
+ return Promise.resolve();
}
// convenience function to force a full redraw, mostly for use by plotly.js
Plotly.redraw = function(gd) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- if(!Lib.isPlotDiv(gd)) {
- throw new Error('This element is not a Plotly plot: ' + gd);
- }
+ if (!Lib.isPlotDiv(gd)) {
+ throw new Error("This element is not a Plotly plot: " + gd);
+ }
- helpers.cleanData(gd.data, gd.data);
- helpers.cleanLayout(gd.layout);
+ helpers.cleanData(gd.data, gd.data);
+ helpers.cleanLayout(gd.layout);
- gd.calcdata = undefined;
- return Plotly.plot(gd).then(function() {
- gd.emit('plotly_redraw');
- return gd;
- });
+ gd.calcdata = undefined;
+ return Plotly.plot(gd).then(function() {
+ gd.emit("plotly_redraw");
+ return gd;
+ });
};
/**
@@ -517,13 +515,13 @@ Plotly.redraw = function(gd) {
* @param {Object} config
*/
Plotly.newPlot = function(gd, data, layout, config) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- // remove gl contexts
- Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
+ // remove gl contexts
+ Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
- Plots.purge(gd);
- return Plotly.plot(gd, data, layout, config);
+ Plots.purge(gd);
+ return Plotly.plot(gd, data, layout, config);
};
/**
@@ -533,20 +531,17 @@ Plotly.newPlot = function(gd, data, layout, config) {
* @param {Number} maxIndex The maximum index allowable (arr.length - 1)
*/
function positivifyIndices(indices, maxIndex) {
- var parentLength = maxIndex + 1,
- positiveIndices = [],
- i,
- index;
-
- for(i = 0; i < indices.length; i++) {
- index = indices[i];
- if(index < 0) {
- positiveIndices.push(parentLength + index);
- } else {
- positiveIndices.push(index);
- }
+ var parentLength = maxIndex + 1, positiveIndices = [], i, index;
+
+ for (i = 0; i < indices.length; i++) {
+ index = indices[i];
+ if (index < 0) {
+ positiveIndices.push(parentLength + index);
+ } else {
+ positiveIndices.push(index);
}
- return positiveIndices;
+ }
+ return positiveIndices;
}
/**
@@ -559,29 +554,30 @@ function positivifyIndices(indices, maxIndex) {
* @param arrayName
*/
function assertIndexArray(gd, indices, arrayName) {
- var i,
- index;
+ var i, index;
- for(i = 0; i < indices.length; i++) {
- index = indices[i];
+ for (i = 0; i < indices.length; i++) {
+ index = indices[i];
- // validate that indices are indeed integers
- if(index !== parseInt(index, 10)) {
- throw new Error('all values in ' + arrayName + ' must be integers');
- }
+ // validate that indices are indeed integers
+ if (index !== parseInt(index, 10)) {
+ throw new Error("all values in " + arrayName + " must be integers");
+ }
- // check that all indices are in bounds for given gd.data array length
- if(index >= gd.data.length || index < -gd.data.length) {
- throw new Error(arrayName + ' must be valid indices for gd.data.');
- }
+ // check that all indices are in bounds for given gd.data array length
+ if (index >= gd.data.length || index < -gd.data.length) {
+ throw new Error(arrayName + " must be valid indices for gd.data.");
+ }
- // check that indices aren't repeated
- if(indices.indexOf(index, i + 1) > -1 ||
- index >= 0 && indices.indexOf(-gd.data.length + index) > -1 ||
- index < 0 && indices.indexOf(gd.data.length + index) > -1) {
- throw new Error('each index in ' + arrayName + ' must be unique.');
- }
+ // check that indices aren't repeated
+ if (
+ indices.indexOf(index, i + 1) > -1 ||
+ index >= 0 && indices.indexOf(-gd.data.length + index) > -1 ||
+ index < 0 && indices.indexOf(gd.data.length + index) > -1
+ ) {
+ throw new Error("each index in " + arrayName + " must be unique.");
}
+ }
}
/**
@@ -592,33 +588,34 @@ function assertIndexArray(gd, indices, arrayName) {
* @param newIndices
*/
function checkMoveTracesArgs(gd, currentIndices, newIndices) {
-
- // check that gd has attribute 'data' and 'data' is array
- if(!Array.isArray(gd.data)) {
- throw new Error('gd.data must be an array.');
- }
-
- // validate currentIndices array
- if(typeof currentIndices === 'undefined') {
- throw new Error('currentIndices is a required argument.');
- } else if(!Array.isArray(currentIndices)) {
- currentIndices = [currentIndices];
- }
- assertIndexArray(gd, currentIndices, 'currentIndices');
-
- // validate newIndices array if it exists
- if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
- newIndices = [newIndices];
- }
- if(typeof newIndices !== 'undefined') {
- assertIndexArray(gd, newIndices, 'newIndices');
- }
-
- // check currentIndices and newIndices are the same length if newIdices exists
- if(typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) {
- throw new Error('current and new indices must be of equal length.');
- }
-
+ // check that gd has attribute 'data' and 'data' is array
+ if (!Array.isArray(gd.data)) {
+ throw new Error("gd.data must be an array.");
+ }
+
+ // validate currentIndices array
+ if (typeof currentIndices === "undefined") {
+ throw new Error("currentIndices is a required argument.");
+ } else if (!Array.isArray(currentIndices)) {
+ currentIndices = [currentIndices];
+ }
+ assertIndexArray(gd, currentIndices, "currentIndices");
+
+ // validate newIndices array if it exists
+ if (typeof newIndices !== "undefined" && !Array.isArray(newIndices)) {
+ newIndices = [newIndices];
+ }
+ if (typeof newIndices !== "undefined") {
+ assertIndexArray(gd, newIndices, "newIndices");
+ }
+
+ // check currentIndices and newIndices are the same length if newIdices exists
+ if (
+ typeof newIndices !== "undefined" &&
+ currentIndices.length !== newIndices.length
+ ) {
+ throw new Error("current and new indices must be of equal length.");
+ }
}
/**
* A private function to reduce the type checking clutter in addTraces.
@@ -628,40 +625,42 @@ function checkMoveTracesArgs(gd, currentIndices, newIndices) {
* @param newIndices
*/
function checkAddTracesArgs(gd, traces, newIndices) {
- var i, value;
-
- // check that gd has attribute 'data' and 'data' is array
- if(!Array.isArray(gd.data)) {
- throw new Error('gd.data must be an array.');
- }
-
- // make sure traces exists
- if(typeof traces === 'undefined') {
- throw new Error('traces must be defined.');
- }
-
- // make sure traces is an array
- if(!Array.isArray(traces)) {
- traces = [traces];
- }
-
- // make sure each value in traces is an object
- for(i = 0; i < traces.length; i++) {
- value = traces[i];
- if(typeof value !== 'object' || (Array.isArray(value) || value === null)) {
- throw new Error('all values in traces array must be non-array objects');
- }
- }
-
- // make sure we have an index for each trace
- if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
- newIndices = [newIndices];
- }
- if(typeof newIndices !== 'undefined' && newIndices.length !== traces.length) {
- throw new Error(
- 'if indices is specified, traces.length must equal indices.length'
- );
- }
+ var i, value;
+
+ // check that gd has attribute 'data' and 'data' is array
+ if (!Array.isArray(gd.data)) {
+ throw new Error("gd.data must be an array.");
+ }
+
+ // make sure traces exists
+ if (typeof traces === "undefined") {
+ throw new Error("traces must be defined.");
+ }
+
+ // make sure traces is an array
+ if (!Array.isArray(traces)) {
+ traces = [traces];
+ }
+
+ // make sure each value in traces is an object
+ for (i = 0; i < traces.length; i++) {
+ value = traces[i];
+ if (typeof value !== "object" || (Array.isArray(value) || value === null)) {
+ throw new Error("all values in traces array must be non-array objects");
+ }
+ }
+
+ // make sure we have an index for each trace
+ if (typeof newIndices !== "undefined" && !Array.isArray(newIndices)) {
+ newIndices = [newIndices];
+ }
+ if (
+ typeof newIndices !== "undefined" && newIndices.length !== traces.length
+ ) {
+ throw new Error(
+ "if indices is specified, traces.length must equal indices.length"
+ );
+ }
}
/**
@@ -675,42 +674,49 @@ function checkAddTracesArgs(gd, traces, newIndices) {
* @param maxPoints
*/
function assertExtendTracesArgs(gd, update, indices, maxPoints) {
+ var maxPointsIsObject = Lib.isPlainObject(maxPoints);
- var maxPointsIsObject = Lib.isPlainObject(maxPoints);
-
- if(!Array.isArray(gd.data)) {
- throw new Error('gd.data must be an array');
- }
- if(!Lib.isPlainObject(update)) {
- throw new Error('update must be a key:value object');
- }
-
- if(typeof indices === 'undefined') {
- throw new Error('indices must be an integer or array of integers');
- }
+ if (!Array.isArray(gd.data)) {
+ throw new Error("gd.data must be an array");
+ }
+ if (!Lib.isPlainObject(update)) {
+ throw new Error("update must be a key:value object");
+ }
- assertIndexArray(gd, indices, 'indices');
+ if (typeof indices === "undefined") {
+ throw new Error("indices must be an integer or array of integers");
+ }
- for(var key in update) {
+ assertIndexArray(gd, indices, "indices");
- /*
+ for (var key in update) {
+ /*
* Verify that the attribute to be updated contains as many trace updates
* as indices. Failure must result in throw and no-op
*/
- if(!Array.isArray(update[key]) || update[key].length !== indices.length) {
- throw new Error('attribute ' + key + ' must be an array of length equal to indices array length');
- }
+ if (!Array.isArray(update[key]) || update[key].length !== indices.length) {
+ throw new Error(
+ "attribute " +
+ key +
+ " must be an array of length equal to indices array length"
+ );
+ }
- /*
+ /*
* if maxPoints is an object it must match keys and array lengths of 'update' 1:1
*/
- if(maxPointsIsObject &&
- (!(key in maxPoints) || !Array.isArray(maxPoints[key]) ||
- maxPoints[key].length !== update[key].length)) {
- throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' +
- 'corrispondence with the keys and number of traces in the update object');
- }
- }
+ if (
+ maxPointsIsObject &&
+ (!(key in maxPoints) ||
+ !Array.isArray(maxPoints[key]) ||
+ maxPoints[key].length !== update[key].length)
+ ) {
+ throw new Error(
+ "when maxPoints is set as a key:value object it must contain a 1:1 " +
+ "corrispondence with the keys and number of traces in the update object"
+ );
+ }
+ }
}
/**
@@ -723,68 +729,66 @@ function assertExtendTracesArgs(gd, update, indices, maxPoints) {
* @return {Object[]}
*/
function getExtendProperties(gd, update, indices, maxPoints) {
+ var maxPointsIsObject = Lib.isPlainObject(maxPoints), updateProps = [];
+ var trace, target, prop, insert, maxp;
- var maxPointsIsObject = Lib.isPlainObject(maxPoints),
- updateProps = [];
- var trace, target, prop, insert, maxp;
+ // allow scalar index to represent a single trace position
+ if (!Array.isArray(indices)) indices = [indices];
- // allow scalar index to represent a single trace position
- if(!Array.isArray(indices)) indices = [indices];
+ // negative indices are wrapped around to their positive value. Equivalent to python indexing.
+ indices = positivifyIndices(indices, gd.data.length - 1);
- // negative indices are wrapped around to their positive value. Equivalent to python indexing.
- indices = positivifyIndices(indices, gd.data.length - 1);
-
- // loop through all update keys and traces and harvest validated data.
- for(var key in update) {
-
- for(var j = 0; j < indices.length; j++) {
-
- /*
+ // loop through all update keys and traces and harvest validated data.
+ for (var key in update) {
+ for (var j = 0; j < indices.length; j++) {
+ /*
* Choose the trace indexed by the indices map argument and get the prop setter-getter
* instance that references the key and value for this particular trace.
*/
- trace = gd.data[indices[j]];
- prop = Lib.nestedProperty(trace, key);
+ trace = gd.data[indices[j]];
+ prop = Lib.nestedProperty(trace, key);
- /*
+ /*
* Target is the existing gd.data.trace.dataArray value like "x" or "marker.size"
* Target must exist as an Array to allow the extend operation to be performed.
*/
- target = prop.get();
- insert = update[key][j];
+ target = prop.get();
+ insert = update[key][j];
- if(!Array.isArray(insert)) {
- throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array');
- }
- if(!Array.isArray(target)) {
- throw new Error('cannot extend missing or non-array attribute: ' + key);
- }
+ if (!Array.isArray(insert)) {
+ throw new Error(
+ "attribute: " + key + " index: " + j + " must be an array"
+ );
+ }
+ if (!Array.isArray(target)) {
+ throw new Error("cannot extend missing or non-array attribute: " + key);
+ }
- /*
+ /*
* maxPoints may be an object map or a scalar. If object select the key:value, else
* Use the scalar maxPoints for all key and trace combinations.
*/
- maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints;
+ maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints;
- // could have chosen null here, -1 just tells us to not take a window
- if(!isNumeric(maxp)) maxp = -1;
+ // could have chosen null here, -1 just tells us to not take a window
+ if (!isNumeric(maxp)) maxp = -1;
- /*
+ /*
* Wrap the nestedProperty in an object containing required data
* for lengthening and windowing this particular trace - key combination.
* Flooring maxp mirrors the behaviour of floats in the Array.slice JSnative function.
*/
- updateProps.push({
- prop: prop,
- target: target,
- insert: insert,
- maxp: Math.floor(maxp)
- });
- }
+ updateProps.push({
+ prop: prop,
+ target: target,
+ insert: insert,
+ maxp: Math.floor(maxp)
+ });
}
+ }
- // all target and insertion data now validated
- return updateProps;
+ // all target and insertion data now validated
+ return updateProps;
}
/**
@@ -798,58 +802,65 @@ function getExtendProperties(gd, update, indices, maxPoints) {
* @param {Function} spliceArray
* @return {Object}
*/
-function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray) {
-
- assertExtendTracesArgs(gd, update, indices, maxPoints);
-
- var updateProps = getExtendProperties(gd, update, indices, maxPoints),
- remainder = [],
- undoUpdate = {},
- undoPoints = {};
- var target, prop, maxp;
-
- for(var i = 0; i < updateProps.length; i++) {
-
- /*
+function spliceTraces(
+ gd,
+ update,
+ indices,
+ maxPoints,
+ lengthenArray,
+ spliceArray
+) {
+ assertExtendTracesArgs(gd, update, indices, maxPoints);
+
+ var updateProps = getExtendProperties(gd, update, indices, maxPoints),
+ remainder = [],
+ undoUpdate = {},
+ undoPoints = {};
+ var target, prop, maxp;
+
+ for (var i = 0; i < updateProps.length; i++) {
+ /*
* prop is the object returned by Lib.nestedProperties
*/
- prop = updateProps[i].prop;
- maxp = updateProps[i].maxp;
+ prop = updateProps[i].prop;
+ maxp = updateProps[i].maxp;
- target = lengthenArray(updateProps[i].target, updateProps[i].insert);
+ target = lengthenArray(updateProps[i].target, updateProps[i].insert);
- /*
+ /*
* If maxp is set within post-extension trace.length, splice to maxp length.
* Otherwise skip function call as splice op will have no effect anyway.
*/
- if(maxp >= 0 && maxp < target.length) remainder = spliceArray(target, maxp);
+ if (maxp >= 0 && maxp < target.length) {
+ remainder = spliceArray(target, maxp);
+ }
- /*
+ /*
* to reverse this operation we need the size of the original trace as the reverse
* operation will need to window out any lengthening operation performed in this pass.
*/
- maxp = updateProps[i].target.length;
+ maxp = updateProps[i].target.length;
- /*
+ /*
* Magic happens here! update gd.data.trace[key] with new array data.
*/
- prop.set(target);
+ prop.set(target);
- if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = [];
- if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = [];
+ if (!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = [];
+ if (!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = [];
- /*
+ /*
* build the inverse update object for the undo operation
*/
- undoUpdate[prop.astr].push(remainder);
+ undoUpdate[prop.astr].push(remainder);
- /*
+ /*
* build the matching maxPoints undo object containing original trace lengths.
*/
- undoPoints[prop.astr].push(maxp);
- }
+ undoPoints[prop.astr].push(maxp);
+ }
- return {update: undoUpdate, maxPoints: undoPoints};
+ return { update: undoUpdate, maxPoints: undoPoints };
}
/**
@@ -870,57 +881,63 @@ function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray
*
*/
Plotly.extendTraces = function extendTraces(gd, update, indices, maxPoints) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- var undo = spliceTraces(gd, update, indices, maxPoints,
-
- /*
+ var undo = spliceTraces(
+ gd,
+ update,
+ indices,
+ maxPoints,
+ /*
* The Lengthen operation extends trace from end with insert
*/
- function(target, insert) {
- return target.concat(insert);
- },
-
- /*
+ function(target, insert) {
+ return target.concat(insert);
+ },
+ /*
* Window the trace keeping maxPoints, counting back from the end
*/
- function(target, maxPoints) {
- return target.splice(0, target.length - maxPoints);
- });
+ function(target, maxPoints) {
+ return target.splice(0, target.length - maxPoints);
+ }
+ );
- var promise = Plotly.redraw(gd);
+ var promise = Plotly.redraw(gd);
- var undoArgs = [gd, undo.update, indices, undo.maxPoints];
- Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments);
+ var undoArgs = [gd, undo.update, indices, undo.maxPoints];
+ Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments);
- return promise;
+ return promise;
};
Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- var undo = spliceTraces(gd, update, indices, maxPoints,
-
- /*
+ var undo = spliceTraces(
+ gd,
+ update,
+ indices,
+ maxPoints,
+ /*
* The Lengthen operation extends trace by appending insert to start
*/
- function(target, insert) {
- return insert.concat(target);
- },
-
- /*
+ function(target, insert) {
+ return insert.concat(target);
+ },
+ /*
* Window the trace keeping maxPoints, counting forward from the start
*/
- function(target, maxPoints) {
- return target.splice(maxPoints, target.length);
- });
+ function(target, maxPoints) {
+ return target.splice(maxPoints, target.length);
+ }
+ );
- var promise = Plotly.redraw(gd);
+ var promise = Plotly.redraw(gd);
- var undoArgs = [gd, undo.update, indices, undo.maxPoints];
- Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments);
+ var undoArgs = [gd, undo.update, indices, undo.maxPoints];
+ Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments);
- return promise;
+ return promise;
};
/**
@@ -933,73 +950,71 @@ Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) {
*
*/
Plotly.addTraces = function addTraces(gd, traces, newIndices) {
- gd = helpers.getGraphDiv(gd);
-
- var currentIndices = [],
- undoFunc = Plotly.deleteTraces,
- redoFunc = addTraces,
- undoArgs = [gd, currentIndices],
- redoArgs = [gd, traces], // no newIndices here
- i,
- promise;
-
- // all validation is done elsewhere to remove clutter here
- checkAddTracesArgs(gd, traces, newIndices);
-
- // make sure traces is an array
- if(!Array.isArray(traces)) {
- traces = [traces];
- }
-
- // make sure traces do not repeat existing ones
- traces = traces.map(function(trace) {
- return Lib.extendFlat({}, trace);
- });
-
- helpers.cleanData(traces, gd.data);
-
- // add the traces to gd.data (no redrawing yet!)
- for(i = 0; i < traces.length; i++) {
- gd.data.push(traces[i]);
- }
-
- // to continue, we need to call moveTraces which requires currentIndices
- for(i = 0; i < traces.length; i++) {
- currentIndices.push(-traces.length + i);
- }
-
- // if the user didn't define newIndices, they just want the traces appended
- // i.e., we can simply redraw and be done
- if(typeof newIndices === 'undefined') {
- promise = Plotly.redraw(gd);
- Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
- return promise;
- }
-
- // make sure indices is property defined
- if(!Array.isArray(newIndices)) {
- newIndices = [newIndices];
- }
-
- try {
-
- // this is redundant, but necessary to not catch later possible errors!
- checkMoveTracesArgs(gd, currentIndices, newIndices);
- }
- catch(error) {
-
- // something went wrong, reset gd to be safe and rethrow error
- gd.data.splice(gd.data.length - traces.length, traces.length);
- throw error;
- }
-
- // if we're here, the user has defined specific places to place the new traces
- // this requires some extra work that moveTraces will do
- Queue.startSequence(gd);
+ gd = helpers.getGraphDiv(gd);
+
+ var currentIndices = [],
+ undoFunc = Plotly.deleteTraces,
+ redoFunc = addTraces,
+ undoArgs = [gd, currentIndices],
+ redoArgs = [gd, traces],
+ // no newIndices here
+ i,
+ promise;
+
+ // all validation is done elsewhere to remove clutter here
+ checkAddTracesArgs(gd, traces, newIndices);
+
+ // make sure traces is an array
+ if (!Array.isArray(traces)) {
+ traces = [traces];
+ }
+
+ // make sure traces do not repeat existing ones
+ traces = traces.map(function(trace) {
+ return Lib.extendFlat({}, trace);
+ });
+
+ helpers.cleanData(traces, gd.data);
+
+ // add the traces to gd.data (no redrawing yet!)
+ for (i = 0; i < traces.length; i++) {
+ gd.data.push(traces[i]);
+ }
+
+ // to continue, we need to call moveTraces which requires currentIndices
+ for (i = 0; i < traces.length; i++) {
+ currentIndices.push(-traces.length + i);
+ }
+
+ // if the user didn't define newIndices, they just want the traces appended
+ // i.e., we can simply redraw and be done
+ if (typeof newIndices === "undefined") {
+ promise = Plotly.redraw(gd);
Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
- promise = Plotly.moveTraces(gd, currentIndices, newIndices);
- Queue.stopSequence(gd);
return promise;
+ }
+
+ // make sure indices is property defined
+ if (!Array.isArray(newIndices)) {
+ newIndices = [newIndices];
+ }
+
+ try {
+ // this is redundant, but necessary to not catch later possible errors!
+ checkMoveTracesArgs(gd, currentIndices, newIndices);
+ } catch (error) {
+ // something went wrong, reset gd to be safe and rethrow error
+ gd.data.splice(gd.data.length - traces.length, traces.length);
+ throw error;
+ }
+
+ // if we're here, the user has defined specific places to place the new traces
+ // this requires some extra work that moveTraces will do
+ Queue.startSequence(gd);
+ Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+ promise = Plotly.moveTraces(gd, currentIndices, newIndices);
+ Queue.stopSequence(gd);
+ return promise;
};
/**
@@ -1010,38 +1025,38 @@ Plotly.addTraces = function addTraces(gd, traces, newIndices) {
* @param {Number|Number[]} indices The indices
*/
Plotly.deleteTraces = function deleteTraces(gd, indices) {
- gd = helpers.getGraphDiv(gd);
-
- var traces = [],
- undoFunc = Plotly.addTraces,
- redoFunc = deleteTraces,
- undoArgs = [gd, traces, indices],
- redoArgs = [gd, indices],
- i,
- deletedTrace;
-
- // make sure indices are defined
- if(typeof indices === 'undefined') {
- throw new Error('indices must be an integer or array of integers.');
- } else if(!Array.isArray(indices)) {
- indices = [indices];
- }
- assertIndexArray(gd, indices, 'indices');
-
- // convert negative indices to positive indices
- indices = positivifyIndices(indices, gd.data.length - 1);
-
- // we want descending here so that splicing later doesn't affect indexing
- indices.sort(Lib.sorterDes);
- for(i = 0; i < indices.length; i += 1) {
- deletedTrace = gd.data.splice(indices[i], 1)[0];
- traces.push(deletedTrace);
- }
-
- var promise = Plotly.redraw(gd);
- Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
-
- return promise;
+ gd = helpers.getGraphDiv(gd);
+
+ var traces = [],
+ undoFunc = Plotly.addTraces,
+ redoFunc = deleteTraces,
+ undoArgs = [gd, traces, indices],
+ redoArgs = [gd, indices],
+ i,
+ deletedTrace;
+
+ // make sure indices are defined
+ if (typeof indices === "undefined") {
+ throw new Error("indices must be an integer or array of integers.");
+ } else if (!Array.isArray(indices)) {
+ indices = [indices];
+ }
+ assertIndexArray(gd, indices, "indices");
+
+ // convert negative indices to positive indices
+ indices = positivifyIndices(indices, gd.data.length - 1);
+
+ // we want descending here so that splicing later doesn't affect indexing
+ indices.sort(Lib.sorterDes);
+ for (i = 0; i < indices.length; i += 1) {
+ deletedTrace = gd.data.splice(indices[i], 1)[0];
+ traces.push(deletedTrace);
+ }
+
+ var promise = Plotly.redraw(gd);
+ Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+
+ return promise;
};
/**
@@ -1076,70 +1091,73 @@ Plotly.deleteTraces = function deleteTraces(gd, indices) {
* Plotly.moveTraces(gd, [b, d, e, a, c]) // same as 'move to end'
*/
Plotly.moveTraces = function moveTraces(gd, currentIndices, newIndices) {
- gd = helpers.getGraphDiv(gd);
-
- var newData = [],
- movingTraceMap = [],
- undoFunc = moveTraces,
- redoFunc = moveTraces,
- undoArgs = [gd, newIndices, currentIndices],
- redoArgs = [gd, currentIndices, newIndices],
- i;
-
- // to reduce complexity here, check args elsewhere
- // this throws errors where appropriate
- checkMoveTracesArgs(gd, currentIndices, newIndices);
-
- // make sure currentIndices is an array
- currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices];
-
- // if undefined, define newIndices to point to the end of gd.data array
- if(typeof newIndices === 'undefined') {
- newIndices = [];
- for(i = 0; i < currentIndices.length; i++) {
- newIndices.push(-currentIndices.length + i);
- }
- }
-
- // make sure newIndices is an array if it's user-defined
- newIndices = Array.isArray(newIndices) ? newIndices : [newIndices];
-
- // convert negative indices to positive indices (they're the same length)
- currentIndices = positivifyIndices(currentIndices, gd.data.length - 1);
- newIndices = positivifyIndices(newIndices, gd.data.length - 1);
-
- // at this point, we've coerced the index arrays into predictable forms
-
- // get the traces that aren't being moved around
- for(i = 0; i < gd.data.length; i++) {
-
- // if index isn't in currentIndices, include it in ignored!
- if(currentIndices.indexOf(i) === -1) {
- newData.push(gd.data[i]);
- }
- }
-
- // get a mapping of indices to moving traces
- for(i = 0; i < currentIndices.length; i++) {
- movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]});
- }
-
- // reorder this mapping by newIndex, ascending
- movingTraceMap.sort(function(a, b) {
- return a.newIndex - b.newIndex;
+ gd = helpers.getGraphDiv(gd);
+
+ var newData = [],
+ movingTraceMap = [],
+ undoFunc = moveTraces,
+ redoFunc = moveTraces,
+ undoArgs = [gd, newIndices, currentIndices],
+ redoArgs = [gd, currentIndices, newIndices],
+ i;
+
+ // to reduce complexity here, check args elsewhere
+ // this throws errors where appropriate
+ checkMoveTracesArgs(gd, currentIndices, newIndices);
+
+ // make sure currentIndices is an array
+ currentIndices = Array.isArray(currentIndices)
+ ? currentIndices
+ : [currentIndices];
+
+ // if undefined, define newIndices to point to the end of gd.data array
+ if (typeof newIndices === "undefined") {
+ newIndices = [];
+ for (i = 0; i < currentIndices.length; i++) {
+ newIndices.push(-currentIndices.length + i);
+ }
+ }
+
+ // make sure newIndices is an array if it's user-defined
+ newIndices = Array.isArray(newIndices) ? newIndices : [newIndices];
+
+ // convert negative indices to positive indices (they're the same length)
+ currentIndices = positivifyIndices(currentIndices, gd.data.length - 1);
+ newIndices = positivifyIndices(newIndices, gd.data.length - 1);
+
+ // at this point, we've coerced the index arrays into predictable forms
+ // get the traces that aren't being moved around
+ for (i = 0; i < gd.data.length; i++) {
+ // if index isn't in currentIndices, include it in ignored!
+ if (currentIndices.indexOf(i) === -1) {
+ newData.push(gd.data[i]);
+ }
+ }
+
+ // get a mapping of indices to moving traces
+ for (i = 0; i < currentIndices.length; i++) {
+ movingTraceMap.push({
+ newIndex: newIndices[i],
+ trace: gd.data[currentIndices[i]]
});
+ }
- // now, add the moving traces back in, in order!
- for(i = 0; i < movingTraceMap.length; i += 1) {
- newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace);
- }
+ // reorder this mapping by newIndex, ascending
+ movingTraceMap.sort(function(a, b) {
+ return a.newIndex - b.newIndex;
+ });
- gd.data = newData;
+ // now, add the moving traces back in, in order!
+ for (i = 0; i < movingTraceMap.length; i += 1) {
+ newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace);
+ }
- var promise = Plotly.redraw(gd);
- Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+ gd.data = newData;
- return promise;
+ var promise = Plotly.redraw(gd);
+ Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+
+ return promise;
};
/**
@@ -1173,497 +1191,625 @@ Plotly.moveTraces = function moveTraces(gd, currentIndices, newIndices) {
* style files that want to specify cyclical default values).
*/
Plotly.restyle = function restyle(gd, astr, val, traces) {
- gd = helpers.getGraphDiv(gd);
- helpers.clearPromiseQueue(gd);
-
- var aobj = {};
- if(typeof astr === 'string') aobj[astr] = val;
- else if(Lib.isPlainObject(astr)) {
- // the 3-arg form
- aobj = astr;
- if(traces === undefined) traces = val;
- }
- else {
- Lib.warn('Restyle fail.', astr, val, traces);
- return Promise.reject();
- }
+ gd = helpers.getGraphDiv(gd);
+ helpers.clearPromiseQueue(gd);
- if(Object.keys(aobj).length) gd.changed = true;
+ var aobj = {};
+ if (typeof astr === "string") {
+ aobj[astr] = val;
+ } else if (Lib.isPlainObject(astr)) {
+ // the 3-arg form
+ aobj = astr;
+ if (traces === undefined) traces = val;
+ } else {
+ Lib.warn("Restyle fail.", astr, val, traces);
+ return Promise.reject();
+ }
- var specs = _restyle(gd, aobj, traces),
- flags = specs.flags;
+ if (Object.keys(aobj).length) gd.changed = true;
- // clear calcdata if required
- if(flags.clearCalc) gd.calcdata = undefined;
+ var specs = _restyle(gd, aobj, traces), flags = specs.flags;
- // fill in redraw sequence
- var seq = [];
+ // clear calcdata if required
+ if (flags.clearCalc) gd.calcdata = undefined;
- if(flags.fullReplot) {
- seq.push(Plotly.plot);
- }
- else {
- seq.push(Plots.previousPromises);
+ // fill in redraw sequence
+ var seq = [];
- Plots.supplyDefaults(gd);
+ if (flags.fullReplot) {
+ seq.push(Plotly.plot);
+ } else {
+ seq.push(Plots.previousPromises);
- if(flags.dostyle) seq.push(subroutines.doTraceStyle);
- if(flags.docolorbars) seq.push(subroutines.doColorBars);
- }
+ Plots.supplyDefaults(gd);
- Queue.add(gd,
- restyle, [gd, specs.undoit, specs.traces],
- restyle, [gd, specs.redoit, specs.traces]
- );
+ if (flags.dostyle) seq.push(subroutines.doTraceStyle);
+ if (flags.docolorbars) seq.push(subroutines.doColorBars);
+ }
- var plotDone = Lib.syncOrAsync(seq, gd);
- if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
+ Queue.add(gd, restyle, [gd, specs.undoit, specs.traces], restyle, [
+ gd,
+ specs.redoit,
+ specs.traces
+ ]);
- return plotDone.then(function() {
- gd.emit('plotly_restyle', specs.eventData);
- return gd;
- });
+ var plotDone = Lib.syncOrAsync(seq, gd);
+ if (!plotDone || !plotDone.then) plotDone = Promise.resolve();
+
+ return plotDone.then(function() {
+ gd.emit("plotly_restyle", specs.eventData);
+ return gd;
+ });
};
function _restyle(gd, aobj, _traces) {
- var fullLayout = gd._fullLayout,
- fullData = gd._fullData,
- data = gd.data,
- i;
-
- var traces = helpers.coerceTraceIndices(gd, _traces);
-
- // initialize flags
- var flags = {
- docalc: false,
- docalcAutorange: false,
- doplot: false,
- dostyle: false,
- docolorbars: false,
- autorangeOn: false,
- clearCalc: false,
- fullReplot: false
- };
-
- // copies of the change (and previous values of anything affected)
- // for the undo / redo queue
- var redoit = {},
- undoit = {},
- axlist,
- flagAxForDelete = {};
-
- // recalcAttrs attributes need a full regeneration of calcdata
- // as well as a replot, because the right objects may not exist,
- // or autorange may need recalculating
- // in principle we generally shouldn't need to redo ALL traces... that's
- // harder though.
- var recalcAttrs = [
- 'mode', 'visible', 'type', 'orientation', 'fill',
- 'histfunc', 'histnorm', 'text',
- 'x', 'y', 'z',
- 'a', 'b', 'c',
- 'open', 'high', 'low', 'close',
- 'base', 'width', 'offset',
- 'xtype', 'x0', 'dx', 'ytype', 'y0', 'dy', 'xaxis', 'yaxis',
- 'line.width',
- 'connectgaps', 'transpose', 'zsmooth',
- 'showscale', 'marker.showscale',
- 'zauto', 'marker.cauto',
- 'autocolorscale', 'marker.autocolorscale',
- 'colorscale', 'marker.colorscale',
- 'reversescale', 'marker.reversescale',
- 'autobinx', 'nbinsx', 'xbins', 'xbins.start', 'xbins.end', 'xbins.size',
- 'autobiny', 'nbinsy', 'ybins', 'ybins.start', 'ybins.end', 'ybins.size',
- 'autocontour', 'ncontours', 'contours', 'contours.coloring',
- 'error_y', 'error_y.visible', 'error_y.value', 'error_y.type',
- 'error_y.traceref', 'error_y.array', 'error_y.symmetric',
- 'error_y.arrayminus', 'error_y.valueminus', 'error_y.tracerefminus',
- 'error_x', 'error_x.visible', 'error_x.value', 'error_x.type',
- 'error_x.traceref', 'error_x.array', 'error_x.symmetric',
- 'error_x.arrayminus', 'error_x.valueminus', 'error_x.tracerefminus',
- 'swapxy', 'swapxyaxes', 'orientationaxes',
- 'marker.colors', 'values', 'labels', 'label0', 'dlabel', 'sort',
- 'textinfo', 'textposition', 'textfont.size', 'textfont.family', 'textfont.color',
- 'insidetextfont.size', 'insidetextfont.family', 'insidetextfont.color',
- 'outsidetextfont.size', 'outsidetextfont.family', 'outsidetextfont.color',
- 'hole', 'scalegroup', 'domain', 'domain.x', 'domain.y',
- 'domain.x[0]', 'domain.x[1]', 'domain.y[0]', 'domain.y[1]',
- 'tilt', 'tiltaxis', 'depth', 'direction', 'rotation', 'pull',
- 'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale',
- 'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale',
- 'xcalendar', 'ycalendar',
- 'cumulative', 'cumulative.enabled', 'cumulative.direction', 'cumulative.currentbin'
- ];
-
- for(i = 0; i < traces.length; i++) {
- if(Registry.traceIs(fullData[traces[i]], 'box')) {
- recalcAttrs.push('name');
- break;
- }
- }
-
- // autorangeAttrs attributes need a full redo of calcdata
- // only if an axis is autoranged,
- // because .calc() is where the autorange gets determined
- // TODO: could we break this out as well?
- var autorangeAttrs = [
- 'marker', 'marker.size', 'textfont',
- 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean',
- 'tickwidth'
- ];
-
- // replotAttrs attributes need a replot (because different
- // objects need to be made) but not a recalc
- var replotAttrs = [
- 'zmin', 'zmax', 'zauto',
- 'xgap', 'ygap',
- 'marker.cmin', 'marker.cmax', 'marker.cauto',
- 'line.cmin', 'line.cmax',
- 'marker.line.cmin', 'marker.line.cmax',
- 'contours.start', 'contours.end', 'contours.size',
- 'contours.showlines',
- 'line', 'line.smoothing', 'line.shape',
- 'error_y.width', 'error_x.width', 'error_x.copy_ystyle',
- 'marker.maxdisplayed'
- ];
-
- // these ones may alter the axis type
- // (at least if the first trace is involved)
- var axtypeAttrs = [
- 'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis'
- ];
-
- var zscl = ['zmin', 'zmax'],
- xbins = ['xbins.start', 'xbins.end', 'xbins.size'],
- ybins = ['ybins.start', 'ybins.end', 'ybins.size'],
- contourAttrs = ['contours.start', 'contours.end', 'contours.size'];
-
- // At the moment, only cartesian, pie and ternary plot types can afford
- // to not go through a full replot
- var doPlotWhiteList = ['cartesian', 'pie', 'ternary'];
- fullLayout._basePlotModules.forEach(function(_module) {
- if(doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true;
- });
-
- // make a new empty vals array for undoit
- function a0() { return traces.map(function() { return undefined; }); }
-
- // for autoranging multiple axes
- function addToAxlist(axid) {
- var axName = Plotly.Axes.id2name(axid);
- if(axlist.indexOf(axName) === -1) axlist.push(axName);
- }
-
- function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
-
- function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
-
- // for attrs that interact (like scales & autoscales), save the
- // old vals before making the change
- // val=undefined will not set a value, just record what the value was.
- // val=null will delete the attribute
- // attr can be an array to set several at once (all to the same val)
- function doextra(attr, val, i) {
- if(Array.isArray(attr)) {
- attr.forEach(function(a) { doextra(a, val, i); });
- return;
- }
- // quit if explicitly setting this elsewhere
- if(attr in aobj) return;
-
- var extraparam;
- if(attr.substr(0, 6) === 'LAYOUT') {
- extraparam = Lib.nestedProperty(gd.layout, attr.replace('LAYOUT', ''));
- } else {
- extraparam = Lib.nestedProperty(data[traces[i]], attr);
- }
-
- if(!(attr in undoit)) {
- undoit[attr] = a0();
- }
- if(undoit[attr][i] === undefined) {
- undoit[attr][i] = extraparam.get();
- }
- if(val !== undefined) {
- extraparam.set(val);
- }
- }
-
- // now make the changes to gd.data (and occasionally gd.layout)
- // and figure out what kind of graphics update we need to do
- for(var ai in aobj) {
- var vi = aobj[ai],
- cont,
- contFull,
- param,
- oldVal,
- newVal;
-
- redoit[ai] = vi;
-
- if(ai.substr(0, 6) === 'LAYOUT') {
- param = Lib.nestedProperty(gd.layout, ai.replace('LAYOUT', ''));
- undoit[ai] = [param.get()];
- // since we're allowing val to be an array, allow it here too,
- // even though that's meaningless
- param.set(Array.isArray(vi) ? vi[0] : vi);
- // ironically, the layout attrs in restyle only require replot,
- // not relayout
- flags.docalc = true;
- continue;
- }
-
- // set attribute in gd.data
- undoit[ai] = a0();
- for(i = 0; i < traces.length; i++) {
- cont = data[traces[i]];
- contFull = fullData[traces[i]];
- param = Lib.nestedProperty(cont, ai);
- oldVal = param.get();
- newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
-
- if(newVal === undefined) continue;
-
- // setting bin or z settings should turn off auto
- // and setting auto should save bin or z settings
- if(zscl.indexOf(ai) !== -1) {
- doextra('zauto', false, i);
- }
- else if(ai === 'colorscale') {
- doextra('autocolorscale', false, i);
- }
- else if(ai === 'autocolorscale') {
- doextra('colorscale', undefined, i);
- }
- else if(ai === 'marker.colorscale') {
- doextra('marker.autocolorscale', false, i);
- }
- else if(ai === 'marker.autocolorscale') {
- doextra('marker.colorscale', undefined, i);
- }
- else if(ai === 'zauto') {
- doextra(zscl, undefined, i);
- }
- else if(xbins.indexOf(ai) !== -1) {
- doextra('autobinx', false, i);
- }
- else if(ai === 'autobinx') {
- doextra(xbins, undefined, i);
- }
- else if(ybins.indexOf(ai) !== -1) {
- doextra('autobiny', false, i);
- }
- else if(ai === 'autobiny') {
- doextra(ybins, undefined, i);
- }
- else if(contourAttrs.indexOf(ai) !== -1) {
- doextra('autocontour', false, i);
- }
- else if(ai === 'autocontour') {
- doextra(contourAttrs, undefined, i);
- }
- // heatmaps: setting x0 or dx, y0 or dy,
- // should turn xtype/ytype to 'scaled' if 'array'
- else if(['x0', 'dx'].indexOf(ai) !== -1 &&
- contFull.x && contFull.xtype !== 'scaled') {
- doextra('xtype', 'scaled', i);
- }
- else if(['y0', 'dy'].indexOf(ai) !== -1 &&
- contFull.y && contFull.ytype !== 'scaled') {
- doextra('ytype', 'scaled', i);
- }
- // changing colorbar size modes,
- // make the resulting size not change
- // note that colorbar fractional sizing is based on the
- // original plot size, before anything (like a colorbar)
- // increases the margins
- else if(ai === 'colorbar.thicknessmode' && param.get() !== newVal &&
- ['fraction', 'pixels'].indexOf(newVal) !== -1 &&
- contFull.colorbar) {
- var thicknorm =
- ['top', 'bottom'].indexOf(contFull.colorbar.orient) !== -1 ?
- (fullLayout.height - fullLayout.margin.t - fullLayout.margin.b) :
- (fullLayout.width - fullLayout.margin.l - fullLayout.margin.r);
- doextra('colorbar.thickness', contFull.colorbar.thickness *
- (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i);
- }
- else if(ai === 'colorbar.lenmode' && param.get() !== newVal &&
- ['fraction', 'pixels'].indexOf(newVal) !== -1 &&
- contFull.colorbar) {
- var lennorm =
- ['top', 'bottom'].indexOf(contFull.colorbar.orient) !== -1 ?
- (fullLayout.width - fullLayout.margin.l - fullLayout.margin.r) :
- (fullLayout.height - fullLayout.margin.t - fullLayout.margin.b);
- doextra('colorbar.len', contFull.colorbar.len *
- (newVal === 'fraction' ? 1 / lennorm : lennorm), i);
- }
- else if(ai === 'colorbar.tick0' || ai === 'colorbar.dtick') {
- doextra('colorbar.tickmode', 'linear', i);
- }
- else if(ai === 'colorbar.tickmode') {
- doextra(['colorbar.tick0', 'colorbar.dtick'], undefined, i);
- }
-
-
- if(ai === 'type' && (newVal === 'pie') !== (oldVal === 'pie')) {
- var labelsTo = 'x',
- valuesTo = 'y';
- if((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') {
- labelsTo = 'y';
- valuesTo = 'x';
- }
- Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo);
- Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo);
- Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo);
-
- if(oldVal === 'pie') {
- Lib.nestedProperty(cont, 'marker.color')
- .set(Lib.nestedProperty(cont, 'marker.colors').get());
-
- // super kludgy - but if all pies are gone we won't remove them otherwise
- fullLayout._pielayer.selectAll('g.trace').remove();
- } else if(Registry.traceIs(cont, 'cartesian')) {
- Lib.nestedProperty(cont, 'marker.colors')
- .set(Lib.nestedProperty(cont, 'marker.color').get());
- // look for axes that are no longer in use and delete them
- flagAxForDelete[cont.xaxis || 'x'] = true;
- flagAxForDelete[cont.yaxis || 'y'] = true;
- }
- }
-
- undoit[ai][i] = oldVal;
- // set the new value - if val is an array, it's one el per trace
- // first check for attributes that get more complex alterations
- var swapAttrs = [
- 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes'
- ];
- if(swapAttrs.indexOf(ai) !== -1) {
- // setting an orientation: make sure it's changing
- // before we swap everything else
- if(ai === 'orientation') {
- param.set(newVal);
- if(param.get() === undoit[ai][i]) continue;
- }
- // orientationaxes has no value,
- // it flips everything and the axes
- else if(ai === 'orientationaxes') {
- cont.orientation =
- {v: 'h', h: 'v'}[contFull.orientation];
- }
- helpers.swapXYData(cont);
- }
- else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) {
- helpers.manageArrayContainers(param, newVal, undoit);
- flags.docalc = true;
- }
- // all the other ones, just modify that one attribute
- else param.set(newVal);
-
- }
-
- // swap the data attributes of the relevant x and y axes?
- if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) {
- Plotly.Axes.swap(gd, traces);
- }
-
- // swap hovermode if set to "compare x/y data"
- if(ai === 'orientationaxes') {
- var hovermode = Lib.nestedProperty(gd.layout, 'hovermode');
- if(hovermode.get() === 'x') {
- hovermode.set('y');
- } else if(hovermode.get() === 'y') {
- hovermode.set('x');
- }
- }
-
- // check if we need to call axis type
- if((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) {
- Plotly.Axes.clearTypes(gd, traces);
- flags.docalc = true;
- }
-
- // switching from auto to manual binning or z scaling doesn't
- // actually do anything but change what you see in the styling
- // box. everything else at least needs to apply styles
- if((['autobinx', 'autobiny', 'zauto'].indexOf(ai) === -1) ||
- newVal !== false) {
- flags.dostyle = true;
- }
- if(['colorbar', 'line'].indexOf(param.parts[0]) !== -1 ||
- param.parts[0] === 'marker' && param.parts[1] === 'colorbar') {
- flags.docolorbars = true;
- }
-
- var aiArrayStart = ai.indexOf('['),
- aiAboveArray = aiArrayStart === -1 ? ai : ai.substr(0, aiArrayStart);
-
- if(recalcAttrs.indexOf(aiAboveArray) !== -1) {
- // major enough changes deserve autoscale, autobin, and
- // non-reversed axes so people don't get confused
- if(['orientation', 'type'].indexOf(ai) !== -1) {
- axlist = [];
- for(i = 0; i < traces.length; i++) {
- var trace = data[traces[i]];
-
- if(Registry.traceIs(trace, 'cartesian')) {
- addToAxlist(trace.xaxis || 'x');
- addToAxlist(trace.yaxis || 'y');
-
- if(ai === 'type') {
- doextra(['autobinx', 'autobiny'], true, i);
- }
- }
- }
-
- doextra(axlist.map(autorangeAttr), true, 0);
- doextra(axlist.map(rangeAttr), [0, 1], 0);
- }
- flags.docalc = true;
- }
- else if(replotAttrs.indexOf(aiAboveArray) !== -1) flags.doplot = true;
- else if(autorangeAttrs.indexOf(aiAboveArray) !== -1) flags.docalcAutorange = true;
- }
-
- // do we need to force a recalc?
- Plotly.Axes.list(gd).forEach(function(ax) {
- if(ax.autorange) flags.autorangeOn = true;
+ var fullLayout = gd._fullLayout, fullData = gd._fullData, data = gd.data, i;
+
+ var traces = helpers.coerceTraceIndices(gd, _traces);
+
+ // initialize flags
+ var flags = {
+ docalc: false,
+ docalcAutorange: false,
+ doplot: false,
+ dostyle: false,
+ docolorbars: false,
+ autorangeOn: false,
+ clearCalc: false,
+ fullReplot: false
+ };
+
+ // copies of the change (and previous values of anything affected)
+ // for the undo / redo queue
+ var redoit = {}, undoit = {}, axlist, flagAxForDelete = {};
+
+ // recalcAttrs attributes need a full regeneration of calcdata
+ // as well as a replot, because the right objects may not exist,
+ // or autorange may need recalculating
+ // in principle we generally shouldn't need to redo ALL traces... that's
+ // harder though.
+ var recalcAttrs = [
+ "mode",
+ "visible",
+ "type",
+ "orientation",
+ "fill",
+ "histfunc",
+ "histnorm",
+ "text",
+ "x",
+ "y",
+ "z",
+ "a",
+ "b",
+ "c",
+ "open",
+ "high",
+ "low",
+ "close",
+ "base",
+ "width",
+ "offset",
+ "xtype",
+ "x0",
+ "dx",
+ "ytype",
+ "y0",
+ "dy",
+ "xaxis",
+ "yaxis",
+ "line.width",
+ "connectgaps",
+ "transpose",
+ "zsmooth",
+ "showscale",
+ "marker.showscale",
+ "zauto",
+ "marker.cauto",
+ "autocolorscale",
+ "marker.autocolorscale",
+ "colorscale",
+ "marker.colorscale",
+ "reversescale",
+ "marker.reversescale",
+ "autobinx",
+ "nbinsx",
+ "xbins",
+ "xbins.start",
+ "xbins.end",
+ "xbins.size",
+ "autobiny",
+ "nbinsy",
+ "ybins",
+ "ybins.start",
+ "ybins.end",
+ "ybins.size",
+ "autocontour",
+ "ncontours",
+ "contours",
+ "contours.coloring",
+ "error_y",
+ "error_y.visible",
+ "error_y.value",
+ "error_y.type",
+ "error_y.traceref",
+ "error_y.array",
+ "error_y.symmetric",
+ "error_y.arrayminus",
+ "error_y.valueminus",
+ "error_y.tracerefminus",
+ "error_x",
+ "error_x.visible",
+ "error_x.value",
+ "error_x.type",
+ "error_x.traceref",
+ "error_x.array",
+ "error_x.symmetric",
+ "error_x.arrayminus",
+ "error_x.valueminus",
+ "error_x.tracerefminus",
+ "swapxy",
+ "swapxyaxes",
+ "orientationaxes",
+ "marker.colors",
+ "values",
+ "labels",
+ "label0",
+ "dlabel",
+ "sort",
+ "textinfo",
+ "textposition",
+ "textfont.size",
+ "textfont.family",
+ "textfont.color",
+ "insidetextfont.size",
+ "insidetextfont.family",
+ "insidetextfont.color",
+ "outsidetextfont.size",
+ "outsidetextfont.family",
+ "outsidetextfont.color",
+ "hole",
+ "scalegroup",
+ "domain",
+ "domain.x",
+ "domain.y",
+ "domain.x[0]",
+ "domain.x[1]",
+ "domain.y[0]",
+ "domain.y[1]",
+ "tilt",
+ "tiltaxis",
+ "depth",
+ "direction",
+ "rotation",
+ "pull",
+ "line.showscale",
+ "line.cauto",
+ "line.autocolorscale",
+ "line.reversescale",
+ "marker.line.showscale",
+ "marker.line.cauto",
+ "marker.line.autocolorscale",
+ "marker.line.reversescale",
+ "xcalendar",
+ "ycalendar",
+ "cumulative",
+ "cumulative.enabled",
+ "cumulative.direction",
+ "cumulative.currentbin"
+ ];
+
+ for (i = 0; i < traces.length; i++) {
+ if (Registry.traceIs(fullData[traces[i]], "box")) {
+ recalcAttrs.push("name");
+ break;
+ }
+ }
+
+ // autorangeAttrs attributes need a full redo of calcdata
+ // only if an axis is autoranged,
+ // because .calc() is where the autorange gets determined
+ // TODO: could we break this out as well?
+ var autorangeAttrs = [
+ "marker",
+ "marker.size",
+ "textfont",
+ "boxpoints",
+ "jitter",
+ "pointpos",
+ "whiskerwidth",
+ "boxmean",
+ "tickwidth"
+ ];
+
+ // replotAttrs attributes need a replot (because different
+ // objects need to be made) but not a recalc
+ var replotAttrs = [
+ "zmin",
+ "zmax",
+ "zauto",
+ "xgap",
+ "ygap",
+ "marker.cmin",
+ "marker.cmax",
+ "marker.cauto",
+ "line.cmin",
+ "line.cmax",
+ "marker.line.cmin",
+ "marker.line.cmax",
+ "contours.start",
+ "contours.end",
+ "contours.size",
+ "contours.showlines",
+ "line",
+ "line.smoothing",
+ "line.shape",
+ "error_y.width",
+ "error_x.width",
+ "error_x.copy_ystyle",
+ "marker.maxdisplayed"
+ ];
+
+ // these ones may alter the axis type
+ // (at least if the first trace is involved)
+ var axtypeAttrs = [
+ "type",
+ "x",
+ "y",
+ "x0",
+ "y0",
+ "orientation",
+ "xaxis",
+ "yaxis"
+ ];
+
+ var zscl = ["zmin", "zmax"],
+ xbins = ["xbins.start", "xbins.end", "xbins.size"],
+ ybins = ["ybins.start", "ybins.end", "ybins.size"],
+ contourAttrs = ["contours.start", "contours.end", "contours.size"];
+
+ // At the moment, only cartesian, pie and ternary plot types can afford
+ // to not go through a full replot
+ var doPlotWhiteList = ["cartesian", "pie", "ternary"];
+ fullLayout._basePlotModules.forEach(function(_module) {
+ if (doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true;
+ });
+
+ // make a new empty vals array for undoit
+ function a0() {
+ return traces.map(function() {
+ return undefined;
});
-
- // check axes we've flagged for possible deletion
- // flagAxForDelete is a hash so we can make sure we only get each axis once
- var axListForDelete = Object.keys(flagAxForDelete);
- axisLoop:
- for(i = 0; i < axListForDelete.length; i++) {
- var axId = axListForDelete[i],
- axLetter = axId.charAt(0),
- axAttr = axLetter + 'axis';
-
- for(var j = 0; j < data.length; j++) {
- if(Registry.traceIs(data[j], 'cartesian') &&
- (data[j][axAttr] || axLetter) === axId) {
- continue axisLoop;
- }
- }
-
- // no data on this axis - delete it.
- doextra('LAYOUT' + Plotly.Axes.id2name(axId), null, 0);
- }
-
- // combine a few flags together;
- if(flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) {
- flags.clearCalc = true;
- }
- if(flags.docalc || flags.doplot || flags.docalcAutorange) {
- flags.fullReplot = true;
- }
-
- return {
- flags: flags,
- undoit: undoit,
- redoit: redoit,
- traces: traces,
- eventData: Lib.extendDeepNoArrays([], [redoit, traces])
- };
+ }
+
+ // for autoranging multiple axes
+ function addToAxlist(axid) {
+ var axName = Plotly.Axes.id2name(axid);
+ if (axlist.indexOf(axName) === -1) axlist.push(axName);
+ }
+
+ function autorangeAttr(axName) {
+ return "LAYOUT" + axName + ".autorange";
+ }
+
+ function rangeAttr(axName) {
+ return "LAYOUT" + axName + ".range";
+ }
+
+ // for attrs that interact (like scales & autoscales), save the
+ // old vals before making the change
+ // val=undefined will not set a value, just record what the value was.
+ // val=null will delete the attribute
+ // attr can be an array to set several at once (all to the same val)
+ function doextra(attr, val, i) {
+ if (Array.isArray(attr)) {
+ attr.forEach(function(a) {
+ doextra(a, val, i);
+ });
+ return;
+ }
+ // quit if explicitly setting this elsewhere
+ if (attr in aobj) return;
+
+ var extraparam;
+ if (attr.substr(0, 6) === "LAYOUT") {
+ extraparam = Lib.nestedProperty(gd.layout, attr.replace("LAYOUT", ""));
+ } else {
+ extraparam = Lib.nestedProperty(data[traces[i]], attr);
+ }
+
+ if (!(attr in undoit)) {
+ undoit[attr] = a0();
+ }
+ if (undoit[attr][i] === undefined) {
+ undoit[attr][i] = extraparam.get();
+ }
+ if (val !== undefined) {
+ extraparam.set(val);
+ }
+ }
+
+ // now make the changes to gd.data (and occasionally gd.layout)
+ // and figure out what kind of graphics update we need to do
+ for (var ai in aobj) {
+ var vi = aobj[ai], cont, contFull, param, oldVal, newVal;
+
+ redoit[ai] = vi;
+
+ if (ai.substr(0, 6) === "LAYOUT") {
+ param = Lib.nestedProperty(gd.layout, ai.replace("LAYOUT", ""));
+ undoit[ai] = [param.get()];
+ // since we're allowing val to be an array, allow it here too,
+ // even though that's meaningless
+ param.set(Array.isArray(vi) ? vi[0] : vi);
+ // ironically, the layout attrs in restyle only require replot,
+ // not relayout
+ flags.docalc = true;
+ continue;
+ }
+
+ // set attribute in gd.data
+ undoit[ai] = a0();
+ for (i = 0; i < traces.length; i++) {
+ cont = data[traces[i]];
+ contFull = fullData[traces[i]];
+ param = Lib.nestedProperty(cont, ai);
+ oldVal = param.get();
+ newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
+
+ if (newVal === undefined) continue;
+
+ // setting bin or z settings should turn off auto
+ // and setting auto should save bin or z settings
+ if (zscl.indexOf(ai) !== -1) {
+ doextra("zauto", false, i);
+ } else if (ai === "colorscale") {
+ doextra("autocolorscale", false, i);
+ } else if (ai === "autocolorscale") {
+ doextra("colorscale", undefined, i);
+ } else if (ai === "marker.colorscale") {
+ doextra("marker.autocolorscale", false, i);
+ } else if (ai === "marker.autocolorscale") {
+ doextra("marker.colorscale", undefined, i);
+ } else if (ai === "zauto") {
+ doextra(zscl, undefined, i);
+ } else if (xbins.indexOf(ai) !== -1) {
+ doextra("autobinx", false, i);
+ } else if (ai === "autobinx") {
+ doextra(xbins, undefined, i);
+ } else if (ybins.indexOf(ai) !== -1) {
+ doextra("autobiny", false, i);
+ } else if (ai === "autobiny") {
+ doextra(ybins, undefined, i);
+ } else if (contourAttrs.indexOf(ai) !== -1) {
+ doextra("autocontour", false, i);
+ } else if (ai === "autocontour") {
+ doextra(contourAttrs, undefined, i);
+ } else if (
+ ["x0", "dx"].indexOf(ai) !== -1 &&
+ contFull.x &&
+ contFull.xtype !== "scaled"
+ ) {
+ // heatmaps: setting x0 or dx, y0 or dy,
+ // should turn xtype/ytype to 'scaled' if 'array'
+ doextra("xtype", "scaled", i);
+ } else if (
+ ["y0", "dy"].indexOf(ai) !== -1 &&
+ contFull.y &&
+ contFull.ytype !== "scaled"
+ ) {
+ doextra("ytype", "scaled", i);
+ } else if (
+ ai === "colorbar.thicknessmode" &&
+ param.get() !== newVal &&
+ ["fraction", "pixels"].indexOf(newVal) !== -1 &&
+ contFull.colorbar
+ ) {
+ // changing colorbar size modes,
+ // make the resulting size not change
+ // note that colorbar fractional sizing is based on the
+ // original plot size, before anything (like a colorbar)
+ // increases the margins
+ var thicknorm = ["top", "bottom"].indexOf(contFull.colorbar.orient) !==
+ -1
+ ? fullLayout.height - fullLayout.margin.t - fullLayout.margin.b
+ : fullLayout.width - fullLayout.margin.l - fullLayout.margin.r;
+ doextra(
+ "colorbar.thickness",
+ contFull.colorbar.thickness *
+ (newVal === "fraction" ? 1 / thicknorm : thicknorm),
+ i
+ );
+ } else if (
+ ai === "colorbar.lenmode" &&
+ param.get() !== newVal &&
+ ["fraction", "pixels"].indexOf(newVal) !== -1 &&
+ contFull.colorbar
+ ) {
+ var lennorm = ["top", "bottom"].indexOf(contFull.colorbar.orient) !== -1
+ ? fullLayout.width - fullLayout.margin.l - fullLayout.margin.r
+ : fullLayout.height - fullLayout.margin.t - fullLayout.margin.b;
+ doextra(
+ "colorbar.len",
+ contFull.colorbar.len *
+ (newVal === "fraction" ? 1 / lennorm : lennorm),
+ i
+ );
+ } else if (ai === "colorbar.tick0" || ai === "colorbar.dtick") {
+ doextra("colorbar.tickmode", "linear", i);
+ } else if (ai === "colorbar.tickmode") {
+ doextra(["colorbar.tick0", "colorbar.dtick"], undefined, i);
+ }
+
+ if (ai === "type" && newVal === "pie" !== (oldVal === "pie")) {
+ var labelsTo = "x", valuesTo = "y";
+ if (
+ (newVal === "bar" || oldVal === "bar") && cont.orientation === "h"
+ ) {
+ labelsTo = "y";
+ valuesTo = "x";
+ }
+ Lib.swapAttrs(cont, ["?", "?src"], "labels", labelsTo);
+ Lib.swapAttrs(cont, ["d?", "?0"], "label", labelsTo);
+ Lib.swapAttrs(cont, ["?", "?src"], "values", valuesTo);
+
+ if (oldVal === "pie") {
+ Lib.nestedProperty(cont, "marker.color").set(
+ Lib.nestedProperty(cont, "marker.colors").get()
+ );
+
+ // super kludgy - but if all pies are gone we won't remove them otherwise
+ fullLayout._pielayer.selectAll("g.trace").remove();
+ } else if (Registry.traceIs(cont, "cartesian")) {
+ Lib.nestedProperty(cont, "marker.colors").set(
+ Lib.nestedProperty(cont, "marker.color").get()
+ );
+ // look for axes that are no longer in use and delete them
+ flagAxForDelete[cont.xaxis || "x"] = true;
+ flagAxForDelete[cont.yaxis || "y"] = true;
+ }
+ }
+
+ undoit[ai][i] = oldVal;
+ // set the new value - if val is an array, it's one el per trace
+ // first check for attributes that get more complex alterations
+ var swapAttrs = [
+ "swapxy",
+ "swapxyaxes",
+ "orientation",
+ "orientationaxes"
+ ];
+ if (swapAttrs.indexOf(ai) !== -1) {
+ // setting an orientation: make sure it's changing
+ // before we swap everything else
+ if (ai === "orientation") {
+ param.set(newVal);
+ if (param.get() === undoit[ai][i]) continue;
+ } else if (ai === "orientationaxes") {
+ // orientationaxes has no value,
+ // it flips everything and the axes
+ cont.orientation = ({ v: "h", h: "v" })[contFull.orientation];
+ }
+ helpers.swapXYData(cont);
+ } else if (Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) {
+ helpers.manageArrayContainers(param, newVal, undoit);
+ flags.docalc = true;
+ } else {
+ // all the other ones, just modify that one attribute
+ param.set(newVal);
+ }
+ }
+
+ // swap the data attributes of the relevant x and y axes?
+ if (["swapxyaxes", "orientationaxes"].indexOf(ai) !== -1) {
+ Plotly.Axes.swap(gd, traces);
+ }
+
+ // swap hovermode if set to "compare x/y data"
+ if (ai === "orientationaxes") {
+ var hovermode = Lib.nestedProperty(gd.layout, "hovermode");
+ if (hovermode.get() === "x") {
+ hovermode.set("y");
+ } else if (hovermode.get() === "y") {
+ hovermode.set("x");
+ }
+ }
+
+ // check if we need to call axis type
+ if (traces.indexOf(0) !== -1 && axtypeAttrs.indexOf(ai) !== -1) {
+ Plotly.Axes.clearTypes(gd, traces);
+ flags.docalc = true;
+ }
+
+ // switching from auto to manual binning or z scaling doesn't
+ // actually do anything but change what you see in the styling
+ // box. everything else at least needs to apply styles
+ if (
+ ["autobinx", "autobiny", "zauto"].indexOf(ai) === -1 || newVal !== false
+ ) {
+ flags.dostyle = true;
+ }
+ if (
+ ["colorbar", "line"].indexOf(param.parts[0]) !== -1 ||
+ param.parts[0] === "marker" && param.parts[1] === "colorbar"
+ ) {
+ flags.docolorbars = true;
+ }
+
+ var aiArrayStart = ai.indexOf("["),
+ aiAboveArray = aiArrayStart === -1 ? ai : ai.substr(0, aiArrayStart);
+
+ if (recalcAttrs.indexOf(aiAboveArray) !== -1) {
+ // major enough changes deserve autoscale, autobin, and
+ // non-reversed axes so people don't get confused
+ if (["orientation", "type"].indexOf(ai) !== -1) {
+ axlist = [];
+ for (i = 0; i < traces.length; i++) {
+ var trace = data[traces[i]];
+
+ if (Registry.traceIs(trace, "cartesian")) {
+ addToAxlist(trace.xaxis || "x");
+ addToAxlist(trace.yaxis || "y");
+
+ if (ai === "type") {
+ doextra(["autobinx", "autobiny"], true, i);
+ }
+ }
+ }
+
+ doextra(axlist.map(autorangeAttr), true, 0);
+ doextra(axlist.map(rangeAttr), [0, 1], 0);
+ }
+ flags.docalc = true;
+ } else if (replotAttrs.indexOf(aiAboveArray) !== -1) {
+ flags.doplot = true;
+ } else if (autorangeAttrs.indexOf(aiAboveArray) !== -1) {
+ flags.docalcAutorange = true;
+ }
+ }
+
+ // do we need to force a recalc?
+ Plotly.Axes.list(gd).forEach(function(ax) {
+ if (ax.autorange) flags.autorangeOn = true;
+ });
+
+ // check axes we've flagged for possible deletion
+ // flagAxForDelete is a hash so we can make sure we only get each axis once
+ var axListForDelete = Object.keys(flagAxForDelete);
+ axisLoop:
+ for (i = 0; i < axListForDelete.length; i++) {
+ var axId = axListForDelete[i],
+ axLetter = axId.charAt(0),
+ axAttr = axLetter + "axis";
+
+ for (var j = 0; j < data.length; j++) {
+ if (
+ Registry.traceIs(data[j], "cartesian") &&
+ (data[j][axAttr] || axLetter) === axId
+ ) {
+ continue axisLoop;
+ }
+ }
+
+ // no data on this axis - delete it.
+ doextra("LAYOUT" + Plotly.Axes.id2name(axId), null, 0);
+ }
+
+ // combine a few flags together;
+ if (flags.docalc || flags.docalcAutorange && flags.autorangeOn) {
+ flags.clearCalc = true;
+ }
+ if (flags.docalc || flags.doplot || flags.docalcAutorange) {
+ flags.fullReplot = true;
+ }
+
+ return {
+ flags: flags,
+ undoit: undoit,
+ redoit: redoit,
+ traces: traces,
+ eventData: Lib.extendDeepNoArrays([], [redoit, traces])
+ };
}
/**
@@ -1687,373 +1833,365 @@ function _restyle(gd, aobj, _traces) {
* allows setting multiple attributes simultaneously
*/
Plotly.relayout = function relayout(gd, astr, val) {
- gd = helpers.getGraphDiv(gd);
- helpers.clearPromiseQueue(gd);
+ gd = helpers.getGraphDiv(gd);
+ helpers.clearPromiseQueue(gd);
- if(gd.framework && gd.framework.isPolar) {
- return Promise.resolve(gd);
- }
+ if (gd.framework && gd.framework.isPolar) {
+ return Promise.resolve(gd);
+ }
- var aobj = {};
- if(typeof astr === 'string') aobj[astr] = val;
- else if(Lib.isPlainObject(astr)) aobj = astr;
- else {
- Lib.warn('Relayout fail.', astr, val);
- return Promise.reject();
- }
+ var aobj = {};
+ if (typeof astr === "string") {
+ aobj[astr] = val;
+ } else if (Lib.isPlainObject(astr)) {
+ aobj = astr;
+ } else {
+ Lib.warn("Relayout fail.", astr, val);
+ return Promise.reject();
+ }
- if(Object.keys(aobj).length) gd.changed = true;
+ if (Object.keys(aobj).length) gd.changed = true;
- var specs = _relayout(gd, aobj),
- flags = specs.flags;
+ var specs = _relayout(gd, aobj), flags = specs.flags;
- // clear calcdata if required
- if(flags.docalc) gd.calcdata = undefined;
+ // clear calcdata if required
+ if (flags.docalc) gd.calcdata = undefined;
- // fill in redraw sequence
- var seq = [];
+ // fill in redraw sequence
+ var seq = [];
- if(flags.layoutReplot) {
- seq.push(subroutines.layoutReplot);
- }
- else if(Object.keys(aobj).length) {
- seq.push(Plots.previousPromises);
- Plots.supplyDefaults(gd);
-
- if(flags.dolegend) seq.push(subroutines.doLegend);
- if(flags.dolayoutstyle) seq.push(subroutines.layoutStyles);
- if(flags.doticks) seq.push(subroutines.doTicksRelayout);
- if(flags.domodebar) seq.push(subroutines.doModeBar);
- if(flags.docamera) seq.push(subroutines.doCamera);
- }
+ if (flags.layoutReplot) {
+ seq.push(subroutines.layoutReplot);
+ } else if (Object.keys(aobj).length) {
+ seq.push(Plots.previousPromises);
+ Plots.supplyDefaults(gd);
- Queue.add(gd,
- relayout, [gd, specs.undoit],
- relayout, [gd, specs.redoit]
- );
+ if (flags.dolegend) seq.push(subroutines.doLegend);
+ if (flags.dolayoutstyle) seq.push(subroutines.layoutStyles);
+ if (flags.doticks) seq.push(subroutines.doTicksRelayout);
+ if (flags.domodebar) seq.push(subroutines.doModeBar);
+ if (flags.docamera) seq.push(subroutines.doCamera);
+ }
- var plotDone = Lib.syncOrAsync(seq, gd);
- if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
+ Queue.add(gd, relayout, [gd, specs.undoit], relayout, [gd, specs.redoit]);
- return plotDone.then(function() {
- gd.emit('plotly_relayout', specs.eventData);
- return gd;
- });
+ var plotDone = Lib.syncOrAsync(seq, gd);
+ if (!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
+
+ return plotDone.then(function() {
+ gd.emit("plotly_relayout", specs.eventData);
+ return gd;
+ });
};
function _relayout(gd, aobj) {
- var layout = gd.layout,
- fullLayout = gd._fullLayout,
- keys = Object.keys(aobj),
- axes = Plotly.Axes.list(gd),
- i;
-
- // look for 'allaxes', split out into all axes
- // in case of 3D the axis are nested within a scene which is held in _id
- for(i = 0; i < keys.length; i++) {
- if(keys[i].indexOf('allaxes') === 0) {
- for(var j = 0; j < axes.length; j++) {
- var scene = axes[j]._id.substr(1),
- axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '',
- newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name);
-
- if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]];
- }
-
- delete aobj[keys[i]];
- }
- }
-
- // initialize flags
- var flags = {
- dolegend: false,
- doticks: false,
- dolayoutstyle: false,
- doplot: false,
- docalc: false,
- domodebar: false,
- docamera: false,
- layoutReplot: false
- };
-
- // copies of the change (and previous values of anything affected)
- // for the undo / redo queue
- var redoit = {},
- undoit = {};
-
- // for attrs that interact (like scales & autoscales), save the
- // old vals before making the change
- // val=undefined will not set a value, just record what the value was.
- // attr can be an array to set several at once (all to the same val)
- function doextra(attr, val) {
- if(Array.isArray(attr)) {
- attr.forEach(function(a) { doextra(a, val); });
- return;
- }
- // quit if explicitly setting this elsewhere
- if(attr in aobj) return;
-
- var p = Lib.nestedProperty(layout, attr);
- if(!(attr in undoit)) undoit[attr] = p.get();
- if(val !== undefined) p.set(val);
- }
-
- // for editing annotations or shapes - is it on autoscaled axes?
- function refAutorange(obj, axletter) {
- var axName = Plotly.Axes.id2name(obj[axletter + 'ref'] || axletter);
- return (fullLayout[axName] || {}).autorange;
- }
-
- // alter gd.layout
- for(var ai in aobj) {
- var p = Lib.nestedProperty(layout, ai),
- vi = aobj[ai],
- plen = p.parts.length,
- // p.parts may end with an index integer if the property is an array
- pend = typeof p.parts[plen - 1] === 'string' ? (plen - 1) : (plen - 2),
- // last property in chain (leaf node)
- pleaf = p.parts[pend],
- // leaf plus immediate parent
- pleafPlus = p.parts[pend - 1] + '.' + pleaf,
- // trunk nodes (everything except the leaf)
- ptrunk = p.parts.slice(0, pend).join('.'),
- parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(),
- parentFull = Lib.nestedProperty(fullLayout, ptrunk).get();
-
- if(vi === undefined) continue;
-
- redoit[ai] = vi;
-
- // axis reverse is special - it is its own inverse
- // op and has no flag.
- undoit[ai] = (pleaf === 'reverse') ? vi : p.get();
-
- // Setting width or height to null must reset the graph's width / height
- // back to its initial value as computed during the first pass in Plots.plotAutoSize.
- //
- // To do so, we must manually set them back here using the _initialAutoSize cache.
- if(['width', 'height'].indexOf(ai) !== -1 && vi === null) {
- gd._fullLayout[ai] = gd._initialAutoSize[ai];
- }
- // check autorange vs range
- else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) {
- doextra(ptrunk + '.autorange', false);
- }
- else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) {
- doextra([ptrunk + '.range[0]', ptrunk + '.range[1]'],
- undefined);
- }
- else if(pleafPlus.match(/^aspectratio\.[xyz]$/)) {
- doextra(p.parts[0] + '.aspectmode', 'manual');
- }
- else if(pleafPlus.match(/^aspectmode$/)) {
- doextra([ptrunk + '.x', ptrunk + '.y', ptrunk + '.z'], undefined);
- }
- else if(pleaf === 'tick0' || pleaf === 'dtick') {
- doextra(ptrunk + '.tickmode', 'linear');
- }
- else if(pleaf === 'tickmode') {
- doextra([ptrunk + '.tick0', ptrunk + '.dtick'], undefined);
- }
- else if(/[xy]axis[0-9]*?$/.test(pleaf) && !Object.keys(vi || {}).length) {
- flags.docalc = true;
- }
- else if(/[xy]axis[0-9]*\.categoryorder$/.test(pleafPlus)) {
- flags.docalc = true;
- }
- else if(/[xy]axis[0-9]*\.categoryarray/.test(pleafPlus)) {
- flags.docalc = true;
- }
-
- if(pleafPlus.indexOf('rangeslider') !== -1) {
- flags.docalc = true;
- }
-
- // toggling log without autorange: need to also recalculate ranges
- // logical XOR (ie are we toggling log)
- if(pleaf === 'type' && ((parentFull.type === 'log') !== (vi === 'log'))) {
- var ax = parentIn;
-
- if(!ax || !ax.range) {
- doextra(ptrunk + '.autorange', true);
- }
- else if(!parentFull.autorange) {
- var r0 = ax.range[0],
- r1 = ax.range[1];
- if(vi === 'log') {
- // if both limits are negative, autorange
- if(r0 <= 0 && r1 <= 0) {
- doextra(ptrunk + '.autorange', true);
- }
- // if one is negative, set it 6 orders below the other.
- if(r0 <= 0) r0 = r1 / 1e6;
- else if(r1 <= 0) r1 = r0 / 1e6;
- // now set the range values as appropriate
- doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10);
- doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10);
- }
- else {
- doextra(ptrunk + '.range[0]', Math.pow(10, r0));
- doextra(ptrunk + '.range[1]', Math.pow(10, r1));
- }
- }
- else if(vi === 'log') {
- // just make sure the range is positive and in the right
- // order, it'll get recalculated later
- ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1];
- }
- }
-
- // handle axis reversal explicitly, as there's no 'reverse' flag
- if(pleaf === 'reverse') {
- if(parentIn.range) parentIn.range.reverse();
- else {
- doextra(ptrunk + '.autorange', true);
- parentIn.range = [1, 0];
- }
-
- if(parentFull.autorange) flags.docalc = true;
- else flags.doplot = true;
- }
- // send annotation and shape mods one-by-one through Annotations.draw(),
- // don't set via nestedProperty
- // that's because add and remove are special
- else if(p.parts[0] === 'annotations' || p.parts[0] === 'shapes') {
- var objNum = p.parts[1],
- objType = p.parts[0],
- objList = layout[objType] || [],
- obji = objList[objNum] || {};
-
- // if p.parts is just an annotation number, and val is either
- // 'add' or an entire annotation to add, the undo is 'remove'
- // if val is 'remove' then undo is the whole annotation object
- if(p.parts.length === 2) {
-
- // new API, remove annotation / shape with `null`
- if(vi === null) aobj[ai] = 'remove';
-
- if(aobj[ai] === 'add' || Lib.isPlainObject(aobj[ai])) {
- undoit[ai] = 'remove';
- }
- else if(aobj[ai] === 'remove') {
- if(objNum === -1) {
- undoit[objType] = objList;
- delete undoit[ai];
- }
- else undoit[ai] = obji;
- }
- else Lib.log('???', aobj);
- }
-
- if((refAutorange(obji, 'x') || refAutorange(obji, 'y')) &&
- !Lib.containsAny(ai, ['color', 'opacity', 'align', 'dash'])) {
- flags.docalc = true;
- }
-
- // TODO: combine all edits to a given annotation / shape into one call
- // as it is we get separate calls for x and y (or ax and ay) on move
-
- var drawOne = Registry.getComponentMethod(objType, 'drawOne');
- drawOne(gd, objNum, p.parts.slice(2).join('.'), aobj[ai]);
- delete aobj[ai];
- }
- else if(
- Plots.layoutArrayContainers.indexOf(p.parts[0]) !== -1 ||
- (p.parts[0] === 'mapbox' && p.parts[1] === 'layers')
- ) {
- helpers.manageArrayContainers(p, vi, undoit);
- flags.doplot = true;
- }
- // alter gd.layout
- else {
- var pp1 = String(p.parts[1] || '');
- // check whether we can short-circuit a full redraw
- // 3d or geo at this point just needs to redraw.
- if(p.parts[0].indexOf('scene') === 0) {
- if(p.parts[1] === 'camera') flags.docamera = true;
- else flags.doplot = true;
- }
- else if(p.parts[0].indexOf('geo') === 0) flags.doplot = true;
- else if(p.parts[0].indexOf('ternary') === 0) flags.doplot = true;
- else if(ai === 'paper_bgcolor') flags.doplot = true;
- else if(fullLayout._has('gl2d') &&
- (ai.indexOf('axis') !== -1 || p.parts[0] === 'plot_bgcolor')
- ) flags.doplot = true;
- else if(ai === 'hiddenlabels') flags.docalc = true;
- else if(p.parts[0].indexOf('legend') !== -1) flags.dolegend = true;
- else if(ai.indexOf('title') !== -1) flags.doticks = true;
- else if(p.parts[0].indexOf('bgcolor') !== -1) flags.dolayoutstyle = true;
- else if(p.parts.length > 1 &&
- Lib.containsAny(pp1, ['tick', 'exponent', 'grid', 'zeroline'])) {
- flags.doticks = true;
- }
- else if(ai.indexOf('.linewidth') !== -1 &&
- ai.indexOf('axis') !== -1) {
- flags.doticks = flags.dolayoutstyle = true;
- }
- else if(p.parts.length > 1 && pp1.indexOf('line') !== -1) {
- flags.dolayoutstyle = true;
- }
- else if(p.parts.length > 1 && pp1 === 'mirror') {
- flags.doticks = flags.dolayoutstyle = true;
- }
- else if(ai === 'margin.pad') {
- flags.doticks = flags.dolayoutstyle = true;
- }
- else if(p.parts[0] === 'margin' ||
- p.parts[1] === 'autorange' ||
- p.parts[1] === 'rangemode' ||
- p.parts[1] === 'type' ||
- p.parts[1] === 'domain' ||
- ai.indexOf('calendar') !== -1 ||
- ai.match(/^(bar|box|font)/)) {
- flags.docalc = true;
- }
- /*
+ var layout = gd.layout,
+ fullLayout = gd._fullLayout,
+ keys = Object.keys(aobj),
+ axes = Plotly.Axes.list(gd),
+ i;
+
+ // look for 'allaxes', split out into all axes
+ // in case of 3D the axis are nested within a scene which is held in _id
+ for (i = 0; i < keys.length; i++) {
+ if (keys[i].indexOf("allaxes") === 0) {
+ for (var j = 0; j < axes.length; j++) {
+ var scene = axes[j]._id.substr(1),
+ axisAttr = scene.indexOf("scene") !== -1 ? scene + "." : "",
+ newkey = keys[i].replace("allaxes", axisAttr + axes[j]._name);
+
+ if (!aobj[newkey]) aobj[newkey] = aobj[keys[i]];
+ }
+
+ delete aobj[keys[i]];
+ }
+ }
+
+ // initialize flags
+ var flags = {
+ dolegend: false,
+ doticks: false,
+ dolayoutstyle: false,
+ doplot: false,
+ docalc: false,
+ domodebar: false,
+ docamera: false,
+ layoutReplot: false
+ };
+
+ // copies of the change (and previous values of anything affected)
+ // for the undo / redo queue
+ var redoit = {}, undoit = {};
+
+ // for attrs that interact (like scales & autoscales), save the
+ // old vals before making the change
+ // val=undefined will not set a value, just record what the value was.
+ // attr can be an array to set several at once (all to the same val)
+ function doextra(attr, val) {
+ if (Array.isArray(attr)) {
+ attr.forEach(function(a) {
+ doextra(a, val);
+ });
+ return;
+ }
+ // quit if explicitly setting this elsewhere
+ if (attr in aobj) return;
+
+ var p = Lib.nestedProperty(layout, attr);
+ if (!(attr in undoit)) undoit[attr] = p.get();
+ if (val !== undefined) p.set(val);
+ }
+
+ // for editing annotations or shapes - is it on autoscaled axes?
+ function refAutorange(obj, axletter) {
+ var axName = Plotly.Axes.id2name(obj[axletter + "ref"] || axletter);
+ return (fullLayout[axName] || {}).autorange;
+ }
+
+ // alter gd.layout
+ for (var ai in aobj) {
+ var p = Lib.nestedProperty(layout, ai),
+ vi = aobj[ai],
+ plen = p.parts.length,
+ // p.parts may end with an index integer if the property is an array
+ pend = typeof p.parts[plen - 1] === "string" ? plen - 1 : plen - 2,
+ // last property in chain (leaf node)
+ pleaf = p.parts[pend],
+ // leaf plus immediate parent
+ pleafPlus = p.parts[pend - 1] + "." + pleaf,
+ // trunk nodes (everything except the leaf)
+ ptrunk = p.parts.slice(0, pend).join("."),
+ parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(),
+ parentFull = Lib.nestedProperty(fullLayout, ptrunk).get();
+
+ if (vi === undefined) continue;
+
+ redoit[ai] = vi;
+
+ // axis reverse is special - it is its own inverse
+ // op and has no flag.
+ undoit[ai] = pleaf === "reverse" ? vi : p.get();
+
+ // Setting width or height to null must reset the graph's width / height
+ // back to its initial value as computed during the first pass in Plots.plotAutoSize.
+ //
+ // To do so, we must manually set them back here using the _initialAutoSize cache.
+ if (["width", "height"].indexOf(ai) !== -1 && vi === null) {
+ gd._fullLayout[ai] = gd._initialAutoSize[ai];
+ } else if (pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) {
+ // check autorange vs range
+ doextra(ptrunk + ".autorange", false);
+ } else if (pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) {
+ doextra([ptrunk + ".range[0]", ptrunk + ".range[1]"], undefined);
+ } else if (pleafPlus.match(/^aspectratio\.[xyz]$/)) {
+ doextra(p.parts[0] + ".aspectmode", "manual");
+ } else if (pleafPlus.match(/^aspectmode$/)) {
+ doextra([ptrunk + ".x", ptrunk + ".y", ptrunk + ".z"], undefined);
+ } else if (pleaf === "tick0" || pleaf === "dtick") {
+ doextra(ptrunk + ".tickmode", "linear");
+ } else if (pleaf === "tickmode") {
+ doextra([ptrunk + ".tick0", ptrunk + ".dtick"], undefined);
+ } else if (
+ /[xy]axis[0-9]*?$/.test(pleaf) && !Object.keys(vi || {}).length
+ ) {
+ flags.docalc = true;
+ } else if (/[xy]axis[0-9]*\.categoryorder$/.test(pleafPlus)) {
+ flags.docalc = true;
+ } else if (/[xy]axis[0-9]*\.categoryarray/.test(pleafPlus)) {
+ flags.docalc = true;
+ }
+
+ if (pleafPlus.indexOf("rangeslider") !== -1) {
+ flags.docalc = true;
+ }
+
+ // toggling log without autorange: need to also recalculate ranges
+ // logical XOR (ie are we toggling log)
+ if (pleaf === "type" && parentFull.type === "log" !== (vi === "log")) {
+ var ax = parentIn;
+
+ if (!ax || !ax.range) {
+ doextra(ptrunk + ".autorange", true);
+ } else if (!parentFull.autorange) {
+ var r0 = ax.range[0], r1 = ax.range[1];
+ if (vi === "log") {
+ // if both limits are negative, autorange
+ if (r0 <= 0 && r1 <= 0) {
+ doextra(ptrunk + ".autorange", true);
+ }
+ // if one is negative, set it 6 orders below the other.
+ if (r0 <= 0) r0 = r1 / 1e6;
+ else if (r1 <= 0) r1 = r0 / 1e6;
+ // now set the range values as appropriate
+ doextra(ptrunk + ".range[0]", Math.log(r0) / Math.LN10);
+ doextra(ptrunk + ".range[1]", Math.log(r1) / Math.LN10);
+ } else {
+ doextra(ptrunk + ".range[0]", Math.pow(10, r0));
+ doextra(ptrunk + ".range[1]", Math.pow(10, r1));
+ }
+ } else if (vi === "log") {
+ // just make sure the range is positive and in the right
+ // order, it'll get recalculated later
+ ax.range = ax.range[1] > ax.range[0] ? [1, 2] : [2, 1];
+ }
+ }
+
+ // handle axis reversal explicitly, as there's no 'reverse' flag
+ if (pleaf === "reverse") {
+ if (parentIn.range) {
+ parentIn.range.reverse();
+ } else {
+ doextra(ptrunk + ".autorange", true);
+ parentIn.range = [1, 0];
+ }
+
+ if (parentFull.autorange) flags.docalc = true;
+ else flags.doplot = true;
+ } else if (p.parts[0] === "annotations" || p.parts[0] === "shapes") {
+ // send annotation and shape mods one-by-one through Annotations.draw(),
+ // don't set via nestedProperty
+ // that's because add and remove are special
+ var objNum = p.parts[1],
+ objType = p.parts[0],
+ objList = layout[objType] || [],
+ obji = objList[objNum] || {};
+
+ // if p.parts is just an annotation number, and val is either
+ // 'add' or an entire annotation to add, the undo is 'remove'
+ // if val is 'remove' then undo is the whole annotation object
+ if (p.parts.length === 2) {
+ // new API, remove annotation / shape with `null`
+ if (vi === null) aobj[ai] = "remove";
+
+ if (aobj[ai] === "add" || Lib.isPlainObject(aobj[ai])) {
+ undoit[ai] = "remove";
+ } else if (aobj[ai] === "remove") {
+ if (objNum === -1) {
+ undoit[objType] = objList;
+ delete undoit[ai];
+ } else {
+ undoit[ai] = obji;
+ }
+ } else {
+ Lib.log("???", aobj);
+ }
+ }
+
+ if (
+ (refAutorange(obji, "x") || refAutorange(obji, "y")) &&
+ !Lib.containsAny(ai, ["color", "opacity", "align", "dash"])
+ ) {
+ flags.docalc = true;
+ }
+
+ // TODO: combine all edits to a given annotation / shape into one call
+ // as it is we get separate calls for x and y (or ax and ay) on move
+ var drawOne = Registry.getComponentMethod(objType, "drawOne");
+ drawOne(gd, objNum, p.parts.slice(2).join("."), aobj[ai]);
+ delete aobj[ai];
+ } else if (
+ Plots.layoutArrayContainers.indexOf(p.parts[0]) !== -1 ||
+ p.parts[0] === "mapbox" && p.parts[1] === "layers"
+ ) {
+ helpers.manageArrayContainers(p, vi, undoit);
+ flags.doplot = true;
+ } else {
+ // alter gd.layout
+ var pp1 = String(p.parts[1] || "");
+ // check whether we can short-circuit a full redraw
+ // 3d or geo at this point just needs to redraw.
+ if (p.parts[0].indexOf("scene") === 0) {
+ if (p.parts[1] === "camera") flags.docamera = true;
+ else flags.doplot = true;
+ } else if (p.parts[0].indexOf("geo") === 0) {
+ flags.doplot = true;
+ } else if (p.parts[0].indexOf("ternary") === 0) {
+ flags.doplot = true;
+ } else if (ai === "paper_bgcolor") {
+ flags.doplot = true;
+ } else if (
+ fullLayout._has("gl2d") &&
+ (ai.indexOf("axis") !== -1 || p.parts[0] === "plot_bgcolor")
+ ) {
+ flags.doplot = true;
+ } else if (ai === "hiddenlabels") {
+ flags.docalc = true;
+ } else if (p.parts[0].indexOf("legend") !== -1) {
+ flags.dolegend = true;
+ } else if (ai.indexOf("title") !== -1) {
+ flags.doticks = true;
+ } else if (p.parts[0].indexOf("bgcolor") !== -1) {
+ flags.dolayoutstyle = true;
+ } else if (
+ p.parts.length > 1 &&
+ Lib.containsAny(pp1, ["tick", "exponent", "grid", "zeroline"])
+ ) {
+ flags.doticks = true;
+ } else if (ai.indexOf(".linewidth") !== -1 && ai.indexOf("axis") !== -1) {
+ flags.doticks = flags.dolayoutstyle = true;
+ } else if (p.parts.length > 1 && pp1.indexOf("line") !== -1) {
+ flags.dolayoutstyle = true;
+ } else if (p.parts.length > 1 && pp1 === "mirror") {
+ flags.doticks = flags.dolayoutstyle = true;
+ } else if (ai === "margin.pad") {
+ flags.doticks = flags.dolayoutstyle = true;
+ } else if (
+ p.parts[0] === "margin" ||
+ p.parts[1] === "autorange" ||
+ p.parts[1] === "rangemode" ||
+ p.parts[1] === "type" ||
+ p.parts[1] === "domain" ||
+ ai.indexOf("calendar") !== -1 ||
+ ai.match(/^(bar|box|font)/)
+ ) {
+ flags.docalc = true;
+ } else if (["hovermode", "dragmode"].indexOf(ai) !== -1) {
+ /*
* hovermode and dragmode don't need any redrawing, since they just
* affect reaction to user input, everything else, assume full replot.
* height, width, autosize get dealt with below. Except for the case of
* of subplots - scenes - which require scene.updateFx to be called.
*/
- else if(['hovermode', 'dragmode'].indexOf(ai) !== -1) flags.domodebar = true;
- else if(['hovermode', 'dragmode', 'height',
- 'width', 'autosize'].indexOf(ai) === -1) {
- flags.doplot = true;
- }
-
- p.set(vi);
- }
- }
-
- var oldWidth = gd._fullLayout.width,
- oldHeight = gd._fullLayout.height;
-
- // coerce the updated layout
- Plots.supplyDefaults(gd);
-
- // calculate autosizing
- if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, gd._fullLayout);
-
- // avoid unnecessary redraws
- var hasSizechanged = aobj.height || aobj.width ||
- (gd._fullLayout.width !== oldWidth) ||
- (gd._fullLayout.height !== oldHeight);
-
- if(hasSizechanged) flags.docalc = true;
-
- if(flags.doplot || flags.docalc) {
- flags.layoutReplot = true;
- }
-
- // now all attribute mods are done, as are
- // redo and undo so we can save them
-
- return {
- flags: flags,
- undoit: undoit,
- redoit: redoit,
- eventData: Lib.extendDeep({}, redoit)
- };
+ flags.domodebar = true;
+ } else if (
+ ["hovermode", "dragmode", "height", "width", "autosize"].indexOf(ai) ===
+ -1
+ ) {
+ flags.doplot = true;
+ }
+
+ p.set(vi);
+ }
+ }
+
+ var oldWidth = gd._fullLayout.width, oldHeight = gd._fullLayout.height;
+
+ // coerce the updated layout
+ Plots.supplyDefaults(gd);
+
+ // calculate autosizing
+ if (gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, gd._fullLayout);
+
+ // avoid unnecessary redraws
+ var hasSizechanged = aobj.height ||
+ aobj.width ||
+ gd._fullLayout.width !== oldWidth ||
+ gd._fullLayout.height !== oldHeight;
+
+ if (hasSizechanged) flags.docalc = true;
+
+ if (flags.doplot || flags.docalc) {
+ flags.layoutReplot = true;
+ }
+
+ // now all attribute mods are done, as are
+ // redo and undo so we can save them
+ return {
+ flags: flags,
+ undoit: undoit,
+ redoit: redoit,
+ eventData: Lib.extendDeep({}, redoit)
+ };
}
/**
@@ -2072,77 +2210,78 @@ function _relayout(gd, aobj) {
*
*/
Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) {
- gd = helpers.getGraphDiv(gd);
- helpers.clearPromiseQueue(gd);
-
- if(gd.framework && gd.framework.isPolar) {
- return Promise.resolve(gd);
- }
+ gd = helpers.getGraphDiv(gd);
+ helpers.clearPromiseQueue(gd);
- if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {};
- if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {};
+ if (gd.framework && gd.framework.isPolar) {
+ return Promise.resolve(gd);
+ }
- if(Object.keys(traceUpdate).length) gd.changed = true;
- if(Object.keys(layoutUpdate).length) gd.changed = true;
+ if (!Lib.isPlainObject(traceUpdate)) traceUpdate = {};
+ if (!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {};
- var restyleSpecs = _restyle(gd, traceUpdate, traces),
- restyleFlags = restyleSpecs.flags;
+ if (Object.keys(traceUpdate).length) gd.changed = true;
+ if (Object.keys(layoutUpdate).length) gd.changed = true;
- var relayoutSpecs = _relayout(gd, layoutUpdate),
- relayoutFlags = relayoutSpecs.flags;
+ var restyleSpecs = _restyle(gd, traceUpdate, traces),
+ restyleFlags = restyleSpecs.flags;
- // clear calcdata if required
- if(restyleFlags.clearCalc || relayoutFlags.docalc) gd.calcdata = undefined;
+ var relayoutSpecs = _relayout(gd, layoutUpdate),
+ relayoutFlags = relayoutSpecs.flags;
- // fill in redraw sequence
- var seq = [];
+ // clear calcdata if required
+ if (restyleFlags.clearCalc || relayoutFlags.docalc) gd.calcdata = undefined;
- if(restyleFlags.fullReplot && relayoutFlags.layoutReplot) {
- var data = gd.data,
- layout = gd.layout;
-
- // clear existing data/layout on gd
- // so that Plotly.plot doesn't try to extend them
- gd.data = undefined;
- gd.layout = undefined;
-
- seq.push(function() { return Plotly.plot(gd, data, layout); });
- }
- else if(restyleFlags.fullReplot) {
- seq.push(Plotly.plot);
- }
- else if(relayoutFlags.layoutReplot) {
- seq.push(subroutines.layoutReplot);
- }
- else {
- seq.push(Plots.previousPromises);
- Plots.supplyDefaults(gd);
-
- if(restyleFlags.dostyle) seq.push(subroutines.doTraceStyle);
- if(restyleFlags.docolorbars) seq.push(subroutines.doColorBars);
- if(relayoutFlags.dolegend) seq.push(subroutines.doLegend);
- if(relayoutFlags.dolayoutstyle) seq.push(subroutines.layoutStyles);
- if(relayoutFlags.doticks) seq.push(subroutines.doTicksRelayout);
- if(relayoutFlags.domodebar) seq.push(subroutines.doModeBar);
- if(relayoutFlags.doCamera) seq.push(subroutines.doCamera);
- }
+ // fill in redraw sequence
+ var seq = [];
- Queue.add(gd,
- update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces],
- update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces]
- );
+ if (restyleFlags.fullReplot && relayoutFlags.layoutReplot) {
+ var data = gd.data, layout = gd.layout;
- var plotDone = Lib.syncOrAsync(seq, gd);
- if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
+ // clear existing data/layout on gd
+ // so that Plotly.plot doesn't try to extend them
+ gd.data = undefined;
+ gd.layout = undefined;
- return plotDone.then(function() {
- gd.emit('plotly_update', {
- data: restyleSpecs.eventData,
- layout: relayoutSpecs.eventData
- });
+ seq.push(function() {
+ return Plotly.plot(gd, data, layout);
+ });
+ } else if (restyleFlags.fullReplot) {
+ seq.push(Plotly.plot);
+ } else if (relayoutFlags.layoutReplot) {
+ seq.push(subroutines.layoutReplot);
+ } else {
+ seq.push(Plots.previousPromises);
+ Plots.supplyDefaults(gd);
- return gd;
+ if (restyleFlags.dostyle) seq.push(subroutines.doTraceStyle);
+ if (restyleFlags.docolorbars) seq.push(subroutines.doColorBars);
+ if (relayoutFlags.dolegend) seq.push(subroutines.doLegend);
+ if (relayoutFlags.dolayoutstyle) seq.push(subroutines.layoutStyles);
+ if (relayoutFlags.doticks) seq.push(subroutines.doTicksRelayout);
+ if (relayoutFlags.domodebar) seq.push(subroutines.doModeBar);
+ if (relayoutFlags.doCamera) seq.push(subroutines.doCamera);
+ }
+
+ Queue.add(
+ gd,
+ update,
+ [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces],
+ update,
+ [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces]
+ );
+
+ var plotDone = Lib.syncOrAsync(seq, gd);
+ if (!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
+
+ return plotDone.then(function() {
+ gd.emit("plotly_update", {
+ data: restyleSpecs.eventData,
+ layout: relayoutSpecs.eventData
});
+
+ return gd;
+ });
};
/**
@@ -2173,348 +2312,364 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) {
* configuration for the animation
*/
Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
- gd = helpers.getGraphDiv(gd);
-
- if(!Lib.isPlotDiv(gd)) {
- throw new Error(
- 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
- 'to create a plot before animating it. For more details, see ' +
- 'https://plot.ly/javascript/animations/'
- );
- }
-
- var trans = gd._transitionData;
-
- // This is the queue of frames that will be animated as soon as possible. They
- // are popped immediately upon the *start* of a transition:
- if(!trans._frameQueue) {
- trans._frameQueue = [];
+ gd = helpers.getGraphDiv(gd);
+
+ if (!Lib.isPlotDiv(gd)) {
+ throw new Error(
+ "This element is not a Plotly plot: " +
+ gd +
+ ". It's likely that you've failed " +
+ "to create a plot before animating it. For more details, see " +
+ "https://plot.ly/javascript/animations/"
+ );
+ }
+
+ var trans = gd._transitionData;
+
+ // This is the queue of frames that will be animated as soon as possible. They
+ // are popped immediately upon the *start* of a transition:
+ if (!trans._frameQueue) {
+ trans._frameQueue = [];
+ }
+
+ animationOpts = Plots.supplyAnimationDefaults(animationOpts);
+ var transitionOpts = animationOpts.transition;
+ var frameOpts = animationOpts.frame;
+
+ // Since frames are popped immediately, an empty queue only means all frames have
+ // *started* to transition, not that the animation is complete. To solve that,
+ // track a separate counter that increments at the same time as frames are added
+ // to the queue, but decrements only when the transition is complete.
+ if (trans._frameWaitingCnt === undefined) {
+ trans._frameWaitingCnt = 0;
+ }
+
+ function getTransitionOpts(i) {
+ if (Array.isArray(transitionOpts)) {
+ if (i >= transitionOpts.length) {
+ return transitionOpts[0];
+ } else {
+ return transitionOpts[i];
+ }
+ } else {
+ return transitionOpts;
}
+ }
- animationOpts = Plots.supplyAnimationDefaults(animationOpts);
- var transitionOpts = animationOpts.transition;
- var frameOpts = animationOpts.frame;
-
- // Since frames are popped immediately, an empty queue only means all frames have
- // *started* to transition, not that the animation is complete. To solve that,
- // track a separate counter that increments at the same time as frames are added
- // to the queue, but decrements only when the transition is complete.
- if(trans._frameWaitingCnt === undefined) {
- trans._frameWaitingCnt = 0;
- }
+ function getFrameOpts(i) {
+ if (Array.isArray(frameOpts)) {
+ if (i >= frameOpts.length) {
+ return frameOpts[0];
+ } else {
+ return frameOpts[i];
+ }
+ } else {
+ return frameOpts;
+ }
+ }
+
+ // Execute a callback after the wrapper function has been called n times.
+ // This is used to defer the resolution until a transition has resovled *and*
+ // the frame has completed. If it's not done this way, then we get a race
+ // condition in which the animation might resolve before a transition is complete
+ // or vice versa.
+ function callbackOnNthTime(cb, n) {
+ var cnt = 0;
+ return function() {
+ if (cb && ++cnt === n) {
+ return cb();
+ }
+ };
+ }
- function getTransitionOpts(i) {
- if(Array.isArray(transitionOpts)) {
- if(i >= transitionOpts.length) {
- return transitionOpts[0];
- } else {
- return transitionOpts[i];
- }
- } else {
- return transitionOpts;
- }
- }
+ return new Promise(function(resolve, reject) {
+ function discardExistingFrames() {
+ if (trans._frameQueue.length === 0) {
+ return;
+ }
- function getFrameOpts(i) {
- if(Array.isArray(frameOpts)) {
- if(i >= frameOpts.length) {
- return frameOpts[0];
- } else {
- return frameOpts[i];
- }
- } else {
- return frameOpts;
+ while (trans._frameQueue.length) {
+ var next = trans._frameQueue.pop();
+ if (next.onInterrupt) {
+ next.onInterrupt();
}
- }
+ }
- // Execute a callback after the wrapper function has been called n times.
- // This is used to defer the resolution until a transition has resovled *and*
- // the frame has completed. If it's not done this way, then we get a race
- // condition in which the animation might resolve before a transition is complete
- // or vice versa.
- function callbackOnNthTime(cb, n) {
- var cnt = 0;
- return function() {
- if(cb && ++cnt === n) {
- return cb();
- }
- };
+ gd.emit("plotly_animationinterrupted", []);
}
- return new Promise(function(resolve, reject) {
- function discardExistingFrames() {
- if(trans._frameQueue.length === 0) {
- return;
- }
+ function queueFrames(frameList) {
+ if (frameList.length === 0) return;
- while(trans._frameQueue.length) {
- var next = trans._frameQueue.pop();
- if(next.onInterrupt) {
- next.onInterrupt();
- }
- }
+ for (var i = 0; i < frameList.length; i++) {
+ var computedFrame;
- gd.emit('plotly_animationinterrupted', []);
- }
-
- function queueFrames(frameList) {
- if(frameList.length === 0) return;
-
- for(var i = 0; i < frameList.length; i++) {
- var computedFrame;
-
- if(frameList[i].type === 'byname') {
- // If it's a named frame, compute it:
- computedFrame = Plots.computeFrame(gd, frameList[i].name);
- } else {
- // Otherwise we must have been given a simple object, so treat
- // the input itself as the computed frame.
- computedFrame = frameList[i].data;
- }
-
- var frameOpts = getFrameOpts(i);
- var transitionOpts = getTransitionOpts(i);
-
- // It doesn't make much sense for the transition duration to be greater than
- // the frame duration, so limit it:
- transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration);
-
- var nextFrame = {
- frame: computedFrame,
- name: frameList[i].name,
- frameOpts: frameOpts,
- transitionOpts: transitionOpts,
- };
- if(i === frameList.length - 1) {
- // The last frame in this .animate call stores the promise resolve
- // and reject callbacks. This is how we ensure that the animation
- // loop (which may exist as a result of a *different* .animate call)
- // still resolves or rejecdts this .animate call's promise. once it's
- // complete.
- nextFrame.onComplete = callbackOnNthTime(resolve, 2);
- nextFrame.onInterrupt = reject;
- }
-
- trans._frameQueue.push(nextFrame);
- }
-
- // Set it as never having transitioned to a frame. This will cause the animation
- // loop to immediately transition to the next frame (which, for immediate mode,
- // is the first frame in the list since all others would have been discarded
- // below)
- if(animationOpts.mode === 'immediate') {
- trans._lastFrameAt = -Infinity;
- }
-
- // Only it's not already running, start a RAF loop. This could be avoided in the
- // case that there's only one frame, but it significantly complicated the logic
- // and only sped things up by about 5% or so for a lorenz attractor simulation.
- // It would be a fine thing to implement, but the benefit of that optimization
- // doesn't seem worth the extra complexity.
- if(!trans._animationRaf) {
- beginAnimationLoop();
- }
- }
-
- function stopAnimationLoop() {
- gd.emit('plotly_animated');
-
- // Be sure to unset also since it's how we know whether a loop is already running:
- window.cancelAnimationFrame(trans._animationRaf);
- trans._animationRaf = null;
- }
-
- function nextFrame() {
- if(trans._currentFrame && trans._currentFrame.onComplete) {
- // Execute the callback and unset it to ensure it doesn't
- // accidentally get called twice
- trans._currentFrame.onComplete();
- }
-
- var newFrame = trans._currentFrame = trans._frameQueue.shift();
-
- if(newFrame) {
- // Since it's sometimes necessary to do deep digging into frame data,
- // we'll consider it not 100% impossible for nulls or numbers to sneak through,
- // so check when casting the name, just to be absolutely certain:
- var stringName = newFrame.name ? newFrame.name.toString() : null;
- gd._fullLayout._currentFrame = stringName;
-
- trans._lastFrameAt = Date.now();
- trans._timeToNext = newFrame.frameOpts.duration;
-
- // This is simply called and it's left to .transition to decide how to manage
- // interrupting current transitions. That means we don't need to worry about
- // how it resolves or what happens after this:
- Plots.transition(gd,
- newFrame.frame.data,
- newFrame.frame.layout,
- helpers.coerceTraceIndices(gd, newFrame.frame.traces),
- newFrame.frameOpts,
- newFrame.transitionOpts
- ).then(function() {
- if(newFrame.onComplete) {
- newFrame.onComplete();
- }
-
- });
-
- gd.emit('plotly_animatingframe', {
- name: stringName,
- frame: newFrame.frame,
- animation: {
- frame: newFrame.frameOpts,
- transition: newFrame.transitionOpts,
- }
- });
- } else {
- // If there are no more frames, then stop the RAF loop:
- stopAnimationLoop();
- }
+ if (frameList[i].type === "byname") {
+ // If it's a named frame, compute it:
+ computedFrame = Plots.computeFrame(gd, frameList[i].name);
+ } else {
+ // Otherwise we must have been given a simple object, so treat
+ // the input itself as the computed frame.
+ computedFrame = frameList[i].data;
}
- function beginAnimationLoop() {
- gd.emit('plotly_animating');
+ var frameOpts = getFrameOpts(i);
+ var transitionOpts = getTransitionOpts(i);
- // If no timer is running, then set last frame = long ago so that the next
- // frame is immediately transitioned:
- trans._lastFrameAt = -Infinity;
- trans._timeToNext = 0;
- trans._runningTransitions = 0;
- trans._currentFrame = null;
-
- var doFrame = function() {
- // This *must* be requested before nextFrame since nextFrame may decide
- // to cancel it if there's nothing more to animated:
- trans._animationRaf = window.requestAnimationFrame(doFrame);
-
- // Check if we're ready for a new frame:
- if(Date.now() - trans._lastFrameAt > trans._timeToNext) {
- nextFrame();
- }
- };
+ // It doesn't make much sense for the transition duration to be greater than
+ // the frame duration, so limit it:
+ transitionOpts.duration = Math.min(
+ transitionOpts.duration,
+ frameOpts.duration
+ );
- doFrame();
- }
+ var nextFrame = {
+ frame: computedFrame,
+ name: frameList[i].name,
+ frameOpts: frameOpts,
+ transitionOpts: transitionOpts
+ };
+ if (i === frameList.length - 1) {
+ // The last frame in this .animate call stores the promise resolve
+ // and reject callbacks. This is how we ensure that the animation
+ // loop (which may exist as a result of a *different* .animate call)
+ // still resolves or rejecdts this .animate call's promise. once it's
+ // complete.
+ nextFrame.onComplete = callbackOnNthTime(resolve, 2);
+ nextFrame.onInterrupt = reject;
+ }
+
+ trans._frameQueue.push(nextFrame);
+ }
+
+ // Set it as never having transitioned to a frame. This will cause the animation
+ // loop to immediately transition to the next frame (which, for immediate mode,
+ // is the first frame in the list since all others would have been discarded
+ // below)
+ if (animationOpts.mode === "immediate") {
+ trans._lastFrameAt = -Infinity;
+ }
+
+ // Only it's not already running, start a RAF loop. This could be avoided in the
+ // case that there's only one frame, but it significantly complicated the logic
+ // and only sped things up by about 5% or so for a lorenz attractor simulation.
+ // It would be a fine thing to implement, but the benefit of that optimization
+ // doesn't seem worth the extra complexity.
+ if (!trans._animationRaf) {
+ beginAnimationLoop();
+ }
+ }
+
+ function stopAnimationLoop() {
+ gd.emit("plotly_animated");
+
+ // Be sure to unset also since it's how we know whether a loop is already running:
+ window.cancelAnimationFrame(trans._animationRaf);
+ trans._animationRaf = null;
+ }
+
+ function nextFrame() {
+ if (trans._currentFrame && trans._currentFrame.onComplete) {
+ // Execute the callback and unset it to ensure it doesn't
+ // accidentally get called twice
+ trans._currentFrame.onComplete();
+ }
+
+ var newFrame = trans._currentFrame = trans._frameQueue.shift();
+
+ if (newFrame) {
+ // Since it's sometimes necessary to do deep digging into frame data,
+ // we'll consider it not 100% impossible for nulls or numbers to sneak through,
+ // so check when casting the name, just to be absolutely certain:
+ var stringName = newFrame.name ? newFrame.name.toString() : null;
+ gd._fullLayout._currentFrame = stringName;
+
+ trans._lastFrameAt = Date.now();
+ trans._timeToNext = newFrame.frameOpts.duration;
+
+ // This is simply called and it's left to .transition to decide how to manage
+ // interrupting current transitions. That means we don't need to worry about
+ // how it resolves or what happens after this:
+ Plots.transition(
+ gd,
+ newFrame.frame.data,
+ newFrame.frame.layout,
+ helpers.coerceTraceIndices(gd, newFrame.frame.traces),
+ newFrame.frameOpts,
+ newFrame.transitionOpts
+ ).then(function() {
+ if (newFrame.onComplete) {
+ newFrame.onComplete();
+ }
+ });
- // This is an animate-local counter that helps match up option input list
- // items with the particular frame.
- var configCounter = 0;
- function setTransitionConfig(frame) {
- if(Array.isArray(transitionOpts)) {
- if(configCounter >= transitionOpts.length) {
- frame.transitionOpts = transitionOpts[configCounter];
- } else {
- frame.transitionOpts = transitionOpts[0];
- }
- } else {
- frame.transitionOpts = transitionOpts;
- }
- configCounter++;
- return frame;
- }
+ gd.emit("plotly_animatingframe", {
+ name: stringName,
+ frame: newFrame.frame,
+ animation: {
+ frame: newFrame.frameOpts,
+ transition: newFrame.transitionOpts
+ }
+ });
+ } else {
+ // If there are no more frames, then stop the RAF loop:
+ stopAnimationLoop();
+ }
+ }
- // Disambiguate what's sort of frames have been received
- var i, frame;
- var frameList = [];
- var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null;
- var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList);
- var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList);
-
- if(isSingleFrame) {
- // In this case, a simple object has been passed to animate.
- frameList.push({
- type: 'object',
- data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList))
- });
- } else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) {
- // In this case, null or undefined has been passed so that we want to
- // animate *all* currently defined frames
- for(i = 0; i < trans._frames.length; i++) {
- frame = trans._frames[i];
-
- if(!frame) continue;
-
- if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) {
- frameList.push({
- type: 'byname',
- name: String(frame.name),
- data: setTransitionConfig({name: frame.name})
- });
- }
- }
- } else if(isFrameArray) {
- for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) {
- var frameOrName = frameOrGroupNameOrFrameList[i];
- if(['number', 'string'].indexOf(typeof frameOrName) !== -1) {
- frameOrName = String(frameOrName);
- // In this case, there's an array and this frame is a string name:
- frameList.push({
- type: 'byname',
- name: frameOrName,
- data: setTransitionConfig({name: frameOrName})
- });
- } else if(Lib.isPlainObject(frameOrName)) {
- frameList.push({
- type: 'object',
- data: setTransitionConfig(Lib.extendFlat({}, frameOrName))
- });
- }
- }
- }
+ function beginAnimationLoop() {
+ gd.emit("plotly_animating");
- // Verify that all of these frames actually exist; return and reject if not:
- for(i = 0; i < frameList.length; i++) {
- frame = frameList[i];
- if(frame.type === 'byname' && !trans._frameHash[frame.data.name]) {
- Lib.warn('animate failure: frame not found: "' + frame.data.name + '"');
- reject();
- return;
- }
- }
+ // If no timer is running, then set last frame = long ago so that the next
+ // frame is immediately transitioned:
+ trans._lastFrameAt = -Infinity;
+ trans._timeToNext = 0;
+ trans._runningTransitions = 0;
+ trans._currentFrame = null;
- // If the mode is either next or immediate, then all currently queued frames must
- // be dumped and the corresponding .animate promises rejected.
- if(['next', 'immediate'].indexOf(animationOpts.mode) !== -1) {
- discardExistingFrames();
- }
+ var doFrame = function() {
+ // This *must* be requested before nextFrame since nextFrame may decide
+ // to cancel it if there's nothing more to animated:
+ trans._animationRaf = window.requestAnimationFrame(doFrame);
- if(animationOpts.direction === 'reverse') {
- frameList.reverse();
+ // Check if we're ready for a new frame:
+ if (Date.now() - trans._lastFrameAt > trans._timeToNext) {
+ nextFrame();
}
+ };
- var currentFrame = gd._fullLayout._currentFrame;
- if(currentFrame && animationOpts.fromcurrent) {
- var idx = -1;
- for(i = 0; i < frameList.length; i++) {
- frame = frameList[i];
- if(frame.type === 'byname' && frame.name === currentFrame) {
- idx = i;
- break;
- }
- }
-
- if(idx > 0 && idx < frameList.length - 1) {
- var filteredFrameList = [];
- for(i = 0; i < frameList.length; i++) {
- frame = frameList[i];
- if(frameList[i].type !== 'byname' || i > idx) {
- filteredFrameList.push(frame);
- }
- }
- frameList = filteredFrameList;
- }
- }
+ doFrame();
+ }
- if(frameList.length > 0) {
- queueFrames(frameList);
+ // This is an animate-local counter that helps match up option input list
+ // items with the particular frame.
+ var configCounter = 0;
+ function setTransitionConfig(frame) {
+ if (Array.isArray(transitionOpts)) {
+ if (configCounter >= transitionOpts.length) {
+ frame.transitionOpts = transitionOpts[configCounter];
} else {
- // This is the case where there were simply no frames. It's a little strange
- // since there's not much to do:
- gd.emit('plotly_animated');
- resolve();
- }
- });
+ frame.transitionOpts = transitionOpts[0];
+ }
+ } else {
+ frame.transitionOpts = transitionOpts;
+ }
+ configCounter++;
+ return frame;
+ }
+
+ // Disambiguate what's sort of frames have been received
+ var i, frame;
+ var frameList = [];
+ var allFrames = frameOrGroupNameOrFrameList === undefined ||
+ frameOrGroupNameOrFrameList === null;
+ var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList);
+ var isSingleFrame = !allFrames &&
+ !isFrameArray &&
+ Lib.isPlainObject(frameOrGroupNameOrFrameList);
+
+ if (isSingleFrame) {
+ // In this case, a simple object has been passed to animate.
+ frameList.push({
+ type: "object",
+ data: setTransitionConfig(
+ Lib.extendFlat({}, frameOrGroupNameOrFrameList)
+ )
+ });
+ } else if (
+ allFrames ||
+ ["string", "number"].indexOf(typeof frameOrGroupNameOrFrameList) !== -1
+ ) {
+ // In this case, null or undefined has been passed so that we want to
+ // animate *all* currently defined frames
+ for (i = 0; i < trans._frames.length; i++) {
+ frame = trans._frames[i];
+
+ if (!frame) continue;
+
+ if (
+ allFrames ||
+ String(frame.group) === String(frameOrGroupNameOrFrameList)
+ ) {
+ frameList.push({
+ type: "byname",
+ name: String(frame.name),
+ data: setTransitionConfig({ name: frame.name })
+ });
+ }
+ }
+ } else if (isFrameArray) {
+ for (i = 0; i < frameOrGroupNameOrFrameList.length; i++) {
+ var frameOrName = frameOrGroupNameOrFrameList[i];
+ if (["number", "string"].indexOf(typeof frameOrName) !== -1) {
+ frameOrName = String(frameOrName);
+ // In this case, there's an array and this frame is a string name:
+ frameList.push({
+ type: "byname",
+ name: frameOrName,
+ data: setTransitionConfig({ name: frameOrName })
+ });
+ } else if (Lib.isPlainObject(frameOrName)) {
+ frameList.push({
+ type: "object",
+ data: setTransitionConfig(Lib.extendFlat({}, frameOrName))
+ });
+ }
+ }
+ }
+
+ // Verify that all of these frames actually exist; return and reject if not:
+ for (i = 0; i < frameList.length; i++) {
+ frame = frameList[i];
+ if (frame.type === "byname" && !trans._frameHash[frame.data.name]) {
+ Lib.warn('animate failure: frame not found: "' + frame.data.name + '"');
+ reject();
+ return;
+ }
+ }
+
+ // If the mode is either next or immediate, then all currently queued frames must
+ // be dumped and the corresponding .animate promises rejected.
+ if (["next", "immediate"].indexOf(animationOpts.mode) !== -1) {
+ discardExistingFrames();
+ }
+
+ if (animationOpts.direction === "reverse") {
+ frameList.reverse();
+ }
+
+ var currentFrame = gd._fullLayout._currentFrame;
+ if (currentFrame && animationOpts.fromcurrent) {
+ var idx = -1;
+ for (i = 0; i < frameList.length; i++) {
+ frame = frameList[i];
+ if (frame.type === "byname" && frame.name === currentFrame) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx > 0 && idx < frameList.length - 1) {
+ var filteredFrameList = [];
+ for (i = 0; i < frameList.length; i++) {
+ frame = frameList[i];
+ if (frameList[i].type !== "byname" || i > idx) {
+ filteredFrameList.push(frame);
+ }
+ }
+ frameList = filteredFrameList;
+ }
+ }
+
+ if (frameList.length > 0) {
+ queueFrames(frameList);
+ } else {
+ // This is the case where there were simply no frames. It's a little strange
+ // since there's not much to do:
+ gd.emit("plotly_animated");
+ resolve();
+ }
+ });
};
/**
@@ -2537,118 +2692,133 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
* will be overwritten.
*/
Plotly.addFrames = function(gd, frameList, indices) {
- gd = helpers.getGraphDiv(gd);
-
- var numericNameWarningCount = 0;
-
- if(frameList === null || frameList === undefined) {
- return Promise.resolve();
- }
-
- if(!Lib.isPlotDiv(gd)) {
- throw new Error(
- 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
- 'to create a plot before adding frames. For more details, see ' +
- 'https://plot.ly/javascript/animations/'
- );
- }
+ gd = helpers.getGraphDiv(gd);
- var i, frame, j, idx;
- var _frames = gd._transitionData._frames;
- var _hash = gd._transitionData._frameHash;
+ var numericNameWarningCount = 0;
+ if (frameList === null || frameList === undefined) {
+ return Promise.resolve();
+ }
+
+ if (!Lib.isPlotDiv(gd)) {
+ throw new Error(
+ "This element is not a Plotly plot: " +
+ gd +
+ ". It's likely that you've failed " +
+ "to create a plot before adding frames. For more details, see " +
+ "https://plot.ly/javascript/animations/"
+ );
+ }
- if(!Array.isArray(frameList)) {
- throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList);
- }
-
- // Create a sorted list of insertions since we run into lots of problems if these
- // aren't in ascending order of index:
- //
- // Strictly for sorting. Make sure this is guaranteed to never collide with any
- // already-exisisting indices:
- var bigIndex = _frames.length + frameList.length * 2;
-
- var insertions = [];
- for(i = frameList.length - 1; i >= 0; i--) {
- if(!Lib.isPlainObject(frameList[i])) continue;
-
- var name = (_hash[frameList[i].name] || {}).name;
- var newName = frameList[i].name;
-
- if(name && newName && typeof newName === 'number' && _hash[name]) {
- numericNameWarningCount++;
-
- Lib.warn('addFrames: overwriting frame "' + _hash[name].name +
- '" with a frame whose name of type "number" also equates to "' +
- name + '". This is valid but may potentially lead to unexpected ' +
- 'behavior since all plotly.js frame names are stored internally ' +
- 'as strings.');
-
- if(numericNameWarningCount > 5) {
- Lib.warn('addFrames: This API call has yielded too many warnings. ' +
- 'For the rest of this call, further warnings about numeric frame ' +
- 'names will be suppressed.');
- }
- }
+ var i, frame, j, idx;
+ var _frames = gd._transitionData._frames;
+ var _hash = gd._transitionData._frameHash;
- insertions.push({
- frame: Plots.supplyFrameDefaults(frameList[i]),
- index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i
- });
+ if (!Array.isArray(frameList)) {
+ throw new Error(
+ "addFrames failure: frameList must be an Array of frame definitions" +
+ frameList
+ );
+ }
+
+ // Create a sorted list of insertions since we run into lots of problems if these
+ // aren't in ascending order of index:
+ //
+ // Strictly for sorting. Make sure this is guaranteed to never collide with any
+ // already-exisisting indices:
+ var bigIndex = _frames.length + frameList.length * 2;
+
+ var insertions = [];
+ for (i = frameList.length - 1; i >= 0; i--) {
+ if (!Lib.isPlainObject(frameList[i])) continue;
+
+ var name = (_hash[frameList[i].name] || {}).name;
+ var newName = frameList[i].name;
+
+ if (name && newName && typeof newName === "number" && _hash[name]) {
+ numericNameWarningCount++;
+
+ Lib.warn(
+ 'addFrames: overwriting frame "' +
+ _hash[name].name +
+ '" with a frame whose name of type "number" also equates to "' +
+ name +
+ '". This is valid but may potentially lead to unexpected ' +
+ "behavior since all plotly.js frame names are stored internally " +
+ "as strings."
+ );
+
+ if (numericNameWarningCount > 5) {
+ Lib.warn(
+ "addFrames: This API call has yielded too many warnings. " +
+ "For the rest of this call, further warnings about numeric frame " +
+ "names will be suppressed."
+ );
+ }
}
- // Sort this, taking note that undefined insertions end up at the end:
- insertions.sort(function(a, b) {
- if(a.index > b.index) return -1;
- if(a.index < b.index) return 1;
- return 0;
+ insertions.push({
+ frame: Plots.supplyFrameDefaults(frameList[i]),
+ index: (
+ indices && indices[i] !== undefined && indices[i] !== null
+ ? indices[i]
+ : bigIndex + i
+ )
});
+ }
+
+ // Sort this, taking note that undefined insertions end up at the end:
+ insertions.sort(function(a, b) {
+ if (a.index > b.index) return -1;
+ if (a.index < b.index) return 1;
+ return 0;
+ });
+
+ var ops = [];
+ var revops = [];
+ var frameCount = _frames.length;
+
+ for (i = insertions.length - 1; i >= 0; i--) {
+ frame = insertions[i].frame;
+
+ if (typeof frame.name === "number") {
+ Lib.warn(
+ "Warning: addFrames accepts frames with numeric names, but the numbers are" +
+ "implicitly cast to strings"
+ );
+ }
+
+ if (!frame.name) {
+ // Repeatedly assign a default name, incrementing the counter each time until
+ // we get a name that's not in the hashed lookup table:
+ while (_hash[frame.name = "frame " + gd._transitionData._counter++]);
+ }
+
+ if (_hash[frame.name]) {
+ // If frame is present, overwrite its definition:
+ for (j = 0; j < _frames.length; j++) {
+ if ((_frames[j] || {}).name === frame.name) break;
+ }
+ ops.push({ type: "replace", index: j, value: frame });
+ revops.unshift({ type: "replace", index: j, value: _frames[j] });
+ } else {
+ // Otherwise insert it at the end of the list:
+ idx = Math.max(0, Math.min(insertions[i].index, frameCount));
- var ops = [];
- var revops = [];
- var frameCount = _frames.length;
-
- for(i = insertions.length - 1; i >= 0; i--) {
- frame = insertions[i].frame;
-
- if(typeof frame.name === 'number') {
- Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' +
- 'implicitly cast to strings');
-
- }
-
- if(!frame.name) {
- // Repeatedly assign a default name, incrementing the counter each time until
- // we get a name that's not in the hashed lookup table:
- while(_hash[(frame.name = 'frame ' + gd._transitionData._counter++)]);
- }
-
- if(_hash[frame.name]) {
- // If frame is present, overwrite its definition:
- for(j = 0; j < _frames.length; j++) {
- if((_frames[j] || {}).name === frame.name) break;
- }
- ops.push({type: 'replace', index: j, value: frame});
- revops.unshift({type: 'replace', index: j, value: _frames[j]});
- } else {
- // Otherwise insert it at the end of the list:
- idx = Math.max(0, Math.min(insertions[i].index, frameCount));
-
- ops.push({type: 'insert', index: idx, value: frame});
- revops.unshift({type: 'delete', index: idx});
- frameCount++;
- }
+ ops.push({ type: "insert", index: idx, value: frame });
+ revops.unshift({ type: "delete", index: idx });
+ frameCount++;
}
+ }
- var undoFunc = Plots.modifyFrames,
- redoFunc = Plots.modifyFrames,
- undoArgs = [gd, revops],
- redoArgs = [gd, ops];
+ var undoFunc = Plots.modifyFrames,
+ redoFunc = Plots.modifyFrames,
+ undoArgs = [gd, revops],
+ redoArgs = [gd, ops];
- if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+ if (Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
- return Plots.modifyFrames(gd, ops);
+ return Plots.modifyFrames(gd, ops);
};
/**
@@ -2661,34 +2831,34 @@ Plotly.addFrames = function(gd, frameList, indices) {
* list of integer indices of frames to be deleted
*/
Plotly.deleteFrames = function(gd, frameList) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- if(!Lib.isPlotDiv(gd)) {
- throw new Error('This element is not a Plotly plot: ' + gd);
- }
+ if (!Lib.isPlotDiv(gd)) {
+ throw new Error("This element is not a Plotly plot: " + gd);
+ }
- var i, idx;
- var _frames = gd._transitionData._frames;
- var ops = [];
- var revops = [];
+ var i, idx;
+ var _frames = gd._transitionData._frames;
+ var ops = [];
+ var revops = [];
- frameList = frameList.slice(0);
- frameList.sort();
+ frameList = frameList.slice(0);
+ frameList.sort();
- for(i = frameList.length - 1; i >= 0; i--) {
- idx = frameList[i];
- ops.push({type: 'delete', index: idx});
- revops.unshift({type: 'insert', index: idx, value: _frames[idx]});
- }
+ for (i = frameList.length - 1; i >= 0; i--) {
+ idx = frameList[i];
+ ops.push({ type: "delete", index: idx });
+ revops.unshift({ type: "insert", index: idx, value: _frames[idx] });
+ }
- var undoFunc = Plots.modifyFrames,
- redoFunc = Plots.modifyFrames,
- undoArgs = [gd, revops],
- redoArgs = [gd, ops];
+ var undoFunc = Plots.modifyFrames,
+ redoFunc = Plots.modifyFrames,
+ undoArgs = [gd, revops],
+ redoArgs = [gd, ops];
- if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
+ if (Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
- return Plots.modifyFrames(gd, ops);
+ return Plots.modifyFrames(gd, ops);
};
/**
@@ -2698,131 +2868,157 @@ Plotly.deleteFrames = function(gd, frameList) {
* the id or DOM element of the graph container div
*/
Plotly.purge = function purge(gd) {
- gd = helpers.getGraphDiv(gd);
+ gd = helpers.getGraphDiv(gd);
- var fullLayout = gd._fullLayout || {},
- fullData = gd._fullData || [];
+ var fullLayout = gd._fullLayout || {}, fullData = gd._fullData || [];
- // remove gl contexts
- Plots.cleanPlot([], {}, fullData, fullLayout);
+ // remove gl contexts
+ Plots.cleanPlot([], {}, fullData, fullLayout);
- // purge properties
- Plots.purge(gd);
+ // purge properties
+ Plots.purge(gd);
- // purge event emitter methods
- Events.purge(gd);
+ // purge event emitter methods
+ Events.purge(gd);
- // remove plot container
- if(fullLayout._container) fullLayout._container.remove();
+ // remove plot container
+ if (fullLayout._container) fullLayout._container.remove();
- delete gd._context;
- delete gd._replotPending;
- delete gd._mouseDownTime;
- delete gd._hmpixcount;
- delete gd._hmlumcount;
+ delete gd._context;
+ delete gd._replotPending;
+ delete gd._mouseDownTime;
+ delete gd._hmpixcount;
+ delete gd._hmlumcount;
- return gd;
+ return gd;
};
// -------------------------------------------------------
// makePlotFramework: Create the plot container and axes
// -------------------------------------------------------
function makePlotFramework(gd) {
- var gd3 = d3.select(gd),
- fullLayout = gd._fullLayout;
-
- // Plot container
- fullLayout._container = gd3.selectAll('.plot-container').data([0]);
- fullLayout._container.enter().insert('div', ':first-child')
- .classed('plot-container', true)
- .classed('plotly', true);
-
- // Make the svg container
- fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]);
- fullLayout._paperdiv.enter().append('div')
- .classed('svg-container', true)
- .style('position', 'relative');
-
- // Make the graph containers
- // start fresh each time we get here, so we know the order comes out
- // right, rather than enter/exit which can muck up the order
- // TODO: sort out all the ordering so we don't have to
- // explicitly delete anything
- fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container')
- .data([0]);
- fullLayout._glcontainer.enter().append('div')
- .classed('gl-container', true);
-
- fullLayout._geocontainer = fullLayout._paperdiv.selectAll('.geo-container')
- .data([0]);
- fullLayout._geocontainer.enter().append('div')
- .classed('geo-container', true);
-
- fullLayout._paperdiv.selectAll('.main-svg').remove();
-
- fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child')
- .classed('main-svg', true);
-
- fullLayout._toppaper = fullLayout._paperdiv.append('svg')
- .classed('main-svg', true);
-
- if(!fullLayout._uid) {
- var otherUids = [];
- d3.selectAll('defs').each(function() {
- if(this.id) otherUids.push(this.id.split('-')[1]);
- });
- fullLayout._uid = Lib.randstr(otherUids);
- }
-
- fullLayout._paperdiv.selectAll('.main-svg')
- .attr(xmlnsNamespaces.svgAttrs);
-
- fullLayout._defs = fullLayout._paper.append('defs')
- .attr('id', 'defs-' + fullLayout._uid);
-
- fullLayout._topdefs = fullLayout._toppaper.append('defs')
- .attr('id', 'topdefs-' + fullLayout._uid);
-
- fullLayout._draggers = fullLayout._paper.append('g')
- .classed('draglayer', true);
-
- // lower shape layer
- // (only for shapes to be drawn below the whole plot)
- var layerBelow = fullLayout._paper.append('g')
- .classed('layer-below', true);
- fullLayout._imageLowerLayer = layerBelow.append('g')
- .classed('imagelayer', true);
- fullLayout._shapeLowerLayer = layerBelow.append('g')
- .classed('shapelayer', true);
-
- // single cartesian layer for the whole plot
- fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true);
-
- // single ternary layer for the whole plot
- fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);
-
- // upper shape layer
- // (only for shapes to be drawn above the whole plot, including subplots)
- var layerAbove = fullLayout._paper.append('g')
- .classed('layer-above', true);
- fullLayout._imageUpperLayer = layerAbove.append('g')
- .classed('imagelayer', true);
- fullLayout._shapeUpperLayer = layerAbove.append('g')
- .classed('shapelayer', true);
-
- // single pie layer for the whole plot
- fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
-
- // fill in image server scrape-svg
- fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
- fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true);
-
- // lastly info (legend, annotations) and hover layers go on top
- // these are in a different svg element normally, but get collapsed into a single
- // svg when exporting (after inserting 3D)
- fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
- fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true);
- fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true);
-
- gd.emit('plotly_framework');
+ var gd3 = d3.select(gd), fullLayout = gd._fullLayout;
+
+ // Plot container
+ fullLayout._container = gd3.selectAll(".plot-container").data([0]);
+ fullLayout._container
+ .enter()
+ .insert("div", ":first-child")
+ .classed("plot-container", true)
+ .classed("plotly", true);
+
+ // Make the svg container
+ fullLayout._paperdiv = fullLayout._container
+ .selectAll(".svg-container")
+ .data([0]);
+ fullLayout._paperdiv
+ .enter()
+ .append("div")
+ .classed("svg-container", true)
+ .style("position", "relative");
+
+ // Make the graph containers
+ // start fresh each time we get here, so we know the order comes out
+ // right, rather than enter/exit which can muck up the order
+ // TODO: sort out all the ordering so we don't have to
+ // explicitly delete anything
+ fullLayout._glcontainer = fullLayout._paperdiv
+ .selectAll(".gl-container")
+ .data([0]);
+ fullLayout._glcontainer.enter().append("div").classed("gl-container", true);
+
+ fullLayout._geocontainer = fullLayout._paperdiv
+ .selectAll(".geo-container")
+ .data([0]);
+ fullLayout._geocontainer.enter().append("div").classed("geo-container", true);
+
+ fullLayout._paperdiv.selectAll(".main-svg").remove();
+
+ fullLayout._paper = fullLayout._paperdiv
+ .insert("svg", ":first-child")
+ .classed("main-svg", true);
+
+ fullLayout._toppaper = fullLayout._paperdiv
+ .append("svg")
+ .classed("main-svg", true);
+
+ if (!fullLayout._uid) {
+ var otherUids = [];
+ d3.selectAll("defs").each(function() {
+ if (this.id) otherUids.push(this.id.split("-")[1]);
+ });
+ fullLayout._uid = Lib.randstr(otherUids);
+ }
+
+ fullLayout._paperdiv.selectAll(".main-svg").attr(xmlnsNamespaces.svgAttrs);
+
+ fullLayout._defs = fullLayout._paper
+ .append("defs")
+ .attr("id", "defs-" + fullLayout._uid);
+
+ fullLayout._topdefs = fullLayout._toppaper
+ .append("defs")
+ .attr("id", "topdefs-" + fullLayout._uid);
+
+ fullLayout._draggers = fullLayout._paper
+ .append("g")
+ .classed("draglayer", true);
+
+ // lower shape layer
+ // (only for shapes to be drawn below the whole plot)
+ var layerBelow = fullLayout._paper.append("g").classed("layer-below", true);
+ fullLayout._imageLowerLayer = layerBelow
+ .append("g")
+ .classed("imagelayer", true);
+ fullLayout._shapeLowerLayer = layerBelow
+ .append("g")
+ .classed("shapelayer", true);
+
+ // single cartesian layer for the whole plot
+ fullLayout._cartesianlayer = fullLayout._paper
+ .append("g")
+ .classed("cartesianlayer", true);
+
+ // single ternary layer for the whole plot
+ fullLayout._ternarylayer = fullLayout._paper
+ .append("g")
+ .classed("ternarylayer", true);
+
+ // upper shape layer
+ // (only for shapes to be drawn above the whole plot, including subplots)
+ var layerAbove = fullLayout._paper.append("g").classed("layer-above", true);
+ fullLayout._imageUpperLayer = layerAbove
+ .append("g")
+ .classed("imagelayer", true);
+ fullLayout._shapeUpperLayer = layerAbove
+ .append("g")
+ .classed("shapelayer", true);
+
+ // single pie layer for the whole plot
+ fullLayout._pielayer = fullLayout._paper
+ .append("g")
+ .classed("pielayer", true);
+
+ // fill in image server scrape-svg
+ fullLayout._glimages = fullLayout._paper
+ .append("g")
+ .classed("glimages", true);
+ fullLayout._geoimages = fullLayout._paper
+ .append("g")
+ .classed("geoimages", true);
+
+ // lastly info (legend, annotations) and hover layers go on top
+ // these are in a different svg element normally, but get collapsed into a single
+ // svg when exporting (after inserting 3D)
+ fullLayout._infolayer = fullLayout._toppaper
+ .append("g")
+ .classed("infolayer", true);
+ fullLayout._zoomlayer = fullLayout._toppaper
+ .append("g")
+ .classed("zoomlayer", true);
+ fullLayout._hoverlayer = fullLayout._toppaper
+ .append("g")
+ .classed("hoverlayer", true);
+
+ gd.emit("plotly_framework");
}
diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js
index d54dd1a05a6..f382090aa12 100644
--- a/src/plot_api/plot_config.js
+++ b/src/plot_api/plot_config.js
@@ -5,9 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/* eslint-disable no-console */
/**
@@ -19,100 +17,75 @@
*/
module.exports = {
-
- // no interactivity, for export or image generation
- staticPlot: false,
-
- // we can edit titles, move annotations, etc
- editable: false,
-
- // DO autosize once regardless of layout.autosize
- // (use default width or height values otherwise)
- autosizable: false,
-
- // set the length of the undo/redo queue
- queueLength: 0,
-
- // if we DO autosize, do we fill the container or the screen?
- fillFrame: false,
-
- // if we DO autosize, set the frame margins in percents of plot size
- frameMargins: 0,
-
- // mousewheel or two-finger scroll zooms the plot
- scrollZoom: false,
-
- // double click interaction (false, 'reset', 'autosize' or 'reset+autosize')
- doubleClick: 'reset+autosize',
-
- // new users see some hints about interactivity
- showTips: true,
-
- // link to open this plot in plotly
- showLink: false,
-
- // if we show a link, does it contain data or just link to a plotly file?
- sendData: true,
-
- // text appearing in the sendData link
- linkText: 'Edit chart',
-
- // false or function adding source(s) to linkText
- showSources: false,
-
- // display the mode bar (true, false, or 'hover')
- displayModeBar: 'hover',
-
- // remove mode bar button by name
- // (see ./components/modebar/buttons.js for the list of names)
- modeBarButtonsToRemove: [],
-
- // add mode bar button using config objects
- // (see ./components/modebar/buttons.js for list of arguments)
- modeBarButtonsToAdd: [],
-
- // fully custom mode bar buttons as nested array,
- // where the outer arrays represents button groups, and
- // the inner arrays have buttons config objects or names of default buttons
- // (see ./components/modebar/buttons.js for more info)
- modeBarButtons: false,
-
- // add the plotly logo on the end of the mode bar
- displaylogo: true,
-
- // increase the pixel ratio for Gl plot images
- plotGlPixelRatio: 2,
-
- // function to add the background color to a different container
- // or 'opaque' to ensure there's white behind it
- setBackground: defaultSetBackground,
-
- // URL to topojson files used in geo charts
- topojsonURL: 'https://cdn.plot.ly/',
-
- // Mapbox access token (required to plot mapbox trace types)
- // If using an Mapbox Atlas server, set this option to '',
- // so that plotly.js won't attempt to authenticate to the public Mapbox server.
- mapboxAccessToken: null,
-
- // Turn all console logging on or off (errors will be thrown)
- // This should ONLY be set via Plotly.setPlotConfig
- logging: false,
-
- // Set global transform to be applied to all traces with no
- // specification needed
- globalTransforms: []
+ // no interactivity, for export or image generation
+ staticPlot: false,
+ // we can edit titles, move annotations, etc
+ editable: false,
+ // DO autosize once regardless of layout.autosize
+ // (use default width or height values otherwise)
+ autosizable: false,
+ // set the length of the undo/redo queue
+ queueLength: 0,
+ // if we DO autosize, do we fill the container or the screen?
+ fillFrame: false,
+ // if we DO autosize, set the frame margins in percents of plot size
+ frameMargins: 0,
+ // mousewheel or two-finger scroll zooms the plot
+ scrollZoom: false,
+ // double click interaction (false, 'reset', 'autosize' or 'reset+autosize')
+ doubleClick: "reset+autosize",
+ // new users see some hints about interactivity
+ showTips: true,
+ // link to open this plot in plotly
+ showLink: false,
+ // if we show a link, does it contain data or just link to a plotly file?
+ sendData: true,
+ // text appearing in the sendData link
+ linkText: "Edit chart",
+ // false or function adding source(s) to linkText
+ showSources: false,
+ // display the mode bar (true, false, or 'hover')
+ displayModeBar: "hover",
+ // remove mode bar button by name
+ // (see ./components/modebar/buttons.js for the list of names)
+ modeBarButtonsToRemove: [],
+ // add mode bar button using config objects
+ // (see ./components/modebar/buttons.js for list of arguments)
+ modeBarButtonsToAdd: [],
+ // fully custom mode bar buttons as nested array,
+ // where the outer arrays represents button groups, and
+ // the inner arrays have buttons config objects or names of default buttons
+ // (see ./components/modebar/buttons.js for more info)
+ modeBarButtons: false,
+ // add the plotly logo on the end of the mode bar
+ displaylogo: true,
+ // increase the pixel ratio for Gl plot images
+ plotGlPixelRatio: 2,
+ // function to add the background color to a different container
+ // or 'opaque' to ensure there's white behind it
+ setBackground: defaultSetBackground,
+ // URL to topojson files used in geo charts
+ topojsonURL: "https://cdn.plot.ly/",
+ // Mapbox access token (required to plot mapbox trace types)
+ // If using an Mapbox Atlas server, set this option to '',
+ // so that plotly.js won't attempt to authenticate to the public Mapbox server.
+ mapboxAccessToken: null,
+ // Turn all console logging on or off (errors will be thrown)
+ // This should ONLY be set via Plotly.setPlotConfig
+ logging: false,
+ // Set global transform to be applied to all traces with no
+ // specification needed
+ globalTransforms: []
};
// where and how the background gets set can be overridden by context
// so we define the default (plotly.js) behavior here
function defaultSetBackground(gd, bgColor) {
- try {
- gd._fullLayout._paper.style('background', bgColor);
- }
- catch(e) {
- if(module.exports.logging > 0) {
- console.error(e);
- }
+ try {
+ gd._fullLayout._paper.style("background", bgColor);
+ } catch (e) {
+ if (module.exports.logging > 0) {
+ console.error(e);
}
+ }
}
diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js
index 2075674108a..2cd76726ed6 100644
--- a/src/plot_api/plot_schema.js
+++ b/src/plot_api/plot_schema.js
@@ -5,28 +5,25 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Registry = require("../registry");
+var Lib = require("../lib");
-
-'use strict';
-
-var Registry = require('../registry');
-var Lib = require('../lib');
-
-var baseAttributes = require('../plots/attributes');
-var baseLayoutAttributes = require('../plots/layout_attributes');
-var frameAttributes = require('../plots/frame_attributes');
-var animationAttributes = require('../plots/animation_attributes');
+var baseAttributes = require("../plots/attributes");
+var baseLayoutAttributes = require("../plots/layout_attributes");
+var frameAttributes = require("../plots/frame_attributes");
+var animationAttributes = require("../plots/animation_attributes");
// polar attributes are not part of the Registry yet
-var polarAreaAttrs = require('../plots/polar/area_attributes');
-var polarAxisAttrs = require('../plots/polar/axis_attributes');
+var polarAreaAttrs = require("../plots/polar/area_attributes");
+var polarAxisAttrs = require("../plots/polar/axis_attributes");
var extendFlat = Lib.extendFlat;
var extendDeep = Lib.extendDeep;
-var IS_SUBPLOT_OBJ = '_isSubplotObj';
-var IS_LINKED_TO_ARRAY = '_isLinkedToArray';
-var DEPRECATED = '_deprecated';
+var IS_SUBPLOT_OBJ = "_isSubplotObj";
+var IS_LINKED_TO_ARRAY = "_isLinkedToArray";
+var DEPRECATED = "_deprecated";
var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, DEPRECATED];
exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ;
@@ -46,32 +43,29 @@ exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS;
* - config (coming soon ...)
*/
exports.get = function() {
- var traces = {};
-
- Registry.allTypes.concat('area').forEach(function(type) {
- traces[type] = getTraceAttributes(type);
- });
-
- var transforms = {};
-
- Object.keys(Registry.transformsRegistry).forEach(function(type) {
- transforms[type] = getTransformAttributes(type);
- });
-
- return {
- defs: {
- valObjects: Lib.valObjects,
- metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role'])
- },
-
- traces: traces,
- layout: getLayoutAttributes(),
-
- transforms: transforms,
-
- frames: getFramesAttributes(),
- animation: formatAttributes(animationAttributes)
- };
+ var traces = {};
+
+ Registry.allTypes.concat("area").forEach(function(type) {
+ traces[type] = getTraceAttributes(type);
+ });
+
+ var transforms = {};
+
+ Object.keys(Registry.transformsRegistry).forEach(function(type) {
+ transforms[type] = getTransformAttributes(type);
+ });
+
+ return {
+ defs: {
+ valObjects: Lib.valObjects,
+ metaKeys: UNDERSCORE_ATTRS.concat(["description", "role"])
+ },
+ traces: traces,
+ layout: getLayoutAttributes(),
+ transforms: transforms,
+ frames: getFramesAttributes(),
+ animation: formatAttributes(animationAttributes)
+ };
};
/**
@@ -98,19 +92,19 @@ exports.get = function() {
* copy of transformIn that contains attribute defaults
*/
exports.crawl = function(attrs, callback, specifiedLevel) {
- var level = specifiedLevel || 0;
+ var level = specifiedLevel || 0;
- Object.keys(attrs).forEach(function(attrName) {
- var attr = attrs[attrName];
+ Object.keys(attrs).forEach(function(attrName) {
+ var attr = attrs[attrName];
- if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return;
+ if (UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return;
- callback(attr, attrName, attrs, level);
+ callback(attr, attrName, attrs, level);
- if(exports.isValObject(attr)) return;
+ if (exports.isValObject(attr)) return;
- if(Lib.isPlainObject(attr)) exports.crawl(attr, callback, level + 1);
- });
+ if (Lib.isPlainObject(attr)) exports.crawl(attr, callback, level + 1);
+ });
};
/** Is object a value object (or a container object)?
@@ -121,7 +115,7 @@ exports.crawl = function(attrs, callback, specifiedLevel) {
* false for tree nodes in the attribute hierarchy
*/
exports.isValObject = function(obj) {
- return obj && obj.valType !== undefined;
+ return obj && obj.valType !== undefined;
};
/**
@@ -135,272 +129,267 @@ exports.isValObject = function(obj) {
* list of array attributes for the given trace
*/
exports.findArrayAttributes = function(trace) {
- var arrayAttributes = [],
- stack = [];
+ var arrayAttributes = [], stack = [];
- function callback(attr, attrName, attrs, level) {
- stack = stack.slice(0, level).concat([attrName]);
+ function callback(attr, attrName, attrs, level) {
+ stack = stack.slice(0, level).concat([attrName]);
- var splittableAttr = attr && (attr.valType === 'data_array' || attr.arrayOk === true);
- if(!splittableAttr) return;
+ var splittableAttr = attr &&
+ (attr.valType === "data_array" || attr.arrayOk === true);
+ if (!splittableAttr) return;
- var astr = toAttrString(stack);
- var val = Lib.nestedProperty(trace, astr).get();
- if(!Array.isArray(val)) return;
+ var astr = toAttrString(stack);
+ var val = Lib.nestedProperty(trace, astr).get();
+ if (!Array.isArray(val)) return;
- arrayAttributes.push(astr);
- }
+ arrayAttributes.push(astr);
+ }
- function toAttrString(stack) {
- return stack.join('.');
- }
+ function toAttrString(stack) {
+ return stack.join(".");
+ }
- exports.crawl(trace._module.attributes, callback);
+ exports.crawl(trace._module.attributes, callback);
- if(trace.transforms) {
- var transforms = trace.transforms;
+ if (trace.transforms) {
+ var transforms = trace.transforms;
- for(var i = 0; i < transforms.length; i++) {
- var transform = transforms[i];
+ for (var i = 0; i < transforms.length; i++) {
+ var transform = transforms[i];
- stack = ['transforms[' + i + ']'];
+ stack = ["transforms[" + i + "]"];
- exports.crawl(transform._module.attributes, callback, 1);
- }
+ exports.crawl(transform._module.attributes, callback, 1);
}
+ }
- // Look into the fullInput module attributes for array attributes
- // to make sure that 'custom' array attributes are detected.
- //
- // At the moment, we need this block to make sure that
- // ohlc and candlestick 'open', 'high', 'low', 'close' can be
- // used with filter ang groupby transforms.
- if(trace._fullInput) {
- exports.crawl(trace._fullInput._module.attributes, callback);
+ // Look into the fullInput module attributes for array attributes
+ // to make sure that 'custom' array attributes are detected.
+ //
+ // At the moment, we need this block to make sure that
+ // ohlc and candlestick 'open', 'high', 'low', 'close' can be
+ // used with filter ang groupby transforms.
+ if (trace._fullInput) {
+ exports.crawl(trace._fullInput._module.attributes, callback);
- arrayAttributes = Lib.filterUnique(arrayAttributes);
- }
+ arrayAttributes = Lib.filterUnique(arrayAttributes);
+ }
- return arrayAttributes;
+ return arrayAttributes;
};
function getTraceAttributes(type) {
- var _module, basePlotModule;
-
- if(type === 'area') {
- _module = { attributes: polarAreaAttrs };
- basePlotModule = {};
- }
- else {
- _module = Registry.modules[type]._module,
- basePlotModule = _module.basePlotModule;
- }
-
- var attributes = {};
-
- // make 'type' the first attribute in the object
- attributes.type = null;
-
- // base attributes (same for all trace types)
- extendDeep(attributes, baseAttributes);
-
- // module attributes
- extendDeep(attributes, _module.attributes);
-
- // subplot attributes
- if(basePlotModule.attributes) {
- extendDeep(attributes, basePlotModule.attributes);
+ var _module, basePlotModule;
+
+ if (type === "area") {
+ _module = { attributes: polarAreaAttrs };
+ basePlotModule = {};
+ } else {
+ _module = Registry.modules[
+ type
+ ]._module, basePlotModule = _module.basePlotModule;
+ }
+
+ var attributes = {};
+
+ // make 'type' the first attribute in the object
+ attributes.type = null;
+
+ // base attributes (same for all trace types)
+ extendDeep(attributes, baseAttributes);
+
+ // module attributes
+ extendDeep(attributes, _module.attributes);
+
+ // subplot attributes
+ if (basePlotModule.attributes) {
+ extendDeep(attributes, basePlotModule.attributes);
+ }
+
+ // add registered components trace attributes
+ Object.keys(Registry.componentsRegistry).forEach(function(k) {
+ var _module = Registry.componentsRegistry[k];
+
+ if (
+ _module.schema && _module.schema.traces && _module.schema.traces[type]
+ ) {
+ Object.keys(_module.schema.traces[type]).forEach(function(v) {
+ insertAttrs(attributes, _module.schema.traces[type][v], v);
+ });
}
+ });
- // add registered components trace attributes
- Object.keys(Registry.componentsRegistry).forEach(function(k) {
- var _module = Registry.componentsRegistry[k];
+ // 'type' gets overwritten by baseAttributes; reset it here
+ attributes.type = type;
- if(_module.schema && _module.schema.traces && _module.schema.traces[type]) {
- Object.keys(_module.schema.traces[type]).forEach(function(v) {
- insertAttrs(attributes, _module.schema.traces[type][v], v);
- });
- }
- });
+ var out = {
+ meta: _module.meta || {},
+ attributes: formatAttributes(attributes)
+ };
- // 'type' gets overwritten by baseAttributes; reset it here
- attributes.type = type;
-
- var out = {
- meta: _module.meta || {},
- attributes: formatAttributes(attributes),
- };
-
- // trace-specific layout attributes
- if(_module.layoutAttributes) {
- var layoutAttributes = {};
+ // trace-specific layout attributes
+ if (_module.layoutAttributes) {
+ var layoutAttributes = {};
- extendDeep(layoutAttributes, _module.layoutAttributes);
- out.layoutAttributes = formatAttributes(layoutAttributes);
- }
+ extendDeep(layoutAttributes, _module.layoutAttributes);
+ out.layoutAttributes = formatAttributes(layoutAttributes);
+ }
- return out;
+ return out;
}
function getLayoutAttributes() {
- var layoutAttributes = {};
+ var layoutAttributes = {};
- // global layout attributes
- extendDeep(layoutAttributes, baseLayoutAttributes);
+ // global layout attributes
+ extendDeep(layoutAttributes, baseLayoutAttributes);
- // add base plot module layout attributes
- Object.keys(Registry.subplotsRegistry).forEach(function(k) {
- var _module = Registry.subplotsRegistry[k];
+ // add base plot module layout attributes
+ Object.keys(Registry.subplotsRegistry).forEach(function(k) {
+ var _module = Registry.subplotsRegistry[k];
- if(!_module.layoutAttributes) return;
+ if (!_module.layoutAttributes) return;
- if(_module.name === 'cartesian') {
- handleBasePlotModule(layoutAttributes, _module, 'xaxis');
- handleBasePlotModule(layoutAttributes, _module, 'yaxis');
- }
- else {
- var astr = _module.attr === 'subplot' ? _module.name : _module.attr;
+ if (_module.name === "cartesian") {
+ handleBasePlotModule(layoutAttributes, _module, "xaxis");
+ handleBasePlotModule(layoutAttributes, _module, "yaxis");
+ } else {
+ var astr = _module.attr === "subplot" ? _module.name : _module.attr;
- handleBasePlotModule(layoutAttributes, _module, astr);
- }
- });
+ handleBasePlotModule(layoutAttributes, _module, astr);
+ }
+ });
- // polar layout attributes
- layoutAttributes = assignPolarLayoutAttrs(layoutAttributes);
+ // polar layout attributes
+ layoutAttributes = assignPolarLayoutAttrs(layoutAttributes);
- // add registered components layout attributes
- Object.keys(Registry.componentsRegistry).forEach(function(k) {
- var _module = Registry.componentsRegistry[k];
+ // add registered components layout attributes
+ Object.keys(Registry.componentsRegistry).forEach(function(k) {
+ var _module = Registry.componentsRegistry[k];
- if(!_module.layoutAttributes) return;
+ if (!_module.layoutAttributes) return;
- if(_module.schema && _module.schema.layout) {
- Object.keys(_module.schema.layout).forEach(function(v) {
- insertAttrs(layoutAttributes, _module.schema.layout[v], v);
- });
- }
- else {
- insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name);
- }
- });
+ if (_module.schema && _module.schema.layout) {
+ Object.keys(_module.schema.layout).forEach(function(v) {
+ insertAttrs(layoutAttributes, _module.schema.layout[v], v);
+ });
+ } else {
+ insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name);
+ }
+ });
- return {
- layoutAttributes: formatAttributes(layoutAttributes)
- };
+ return { layoutAttributes: formatAttributes(layoutAttributes) };
}
function getTransformAttributes(type) {
- var _module = Registry.transformsRegistry[type];
- var attributes = extendDeep({}, _module.attributes);
-
- // add registered components transform attributes
- Object.keys(Registry.componentsRegistry).forEach(function(k) {
- var _module = Registry.componentsRegistry[k];
-
- if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) {
- Object.keys(_module.schema.transforms[type]).forEach(function(v) {
- insertAttrs(attributes, _module.schema.transforms[type][v], v);
- });
- }
- });
+ var _module = Registry.transformsRegistry[type];
+ var attributes = extendDeep({}, _module.attributes);
+
+ // add registered components transform attributes
+ Object.keys(Registry.componentsRegistry).forEach(function(k) {
+ var _module = Registry.componentsRegistry[k];
+
+ if (
+ _module.schema &&
+ _module.schema.transforms &&
+ _module.schema.transforms[type]
+ ) {
+ Object.keys(_module.schema.transforms[type]).forEach(function(v) {
+ insertAttrs(attributes, _module.schema.transforms[type][v], v);
+ });
+ }
+ });
- return {
- attributes: formatAttributes(attributes)
- };
+ return { attributes: formatAttributes(attributes) };
}
function getFramesAttributes() {
- var attrs = {
- frames: Lib.extendDeep({}, frameAttributes)
- };
+ var attrs = { frames: Lib.extendDeep({}, frameAttributes) };
- formatAttributes(attrs);
+ formatAttributes(attrs);
- return attrs.frames;
+ return attrs.frames;
}
function formatAttributes(attrs) {
- mergeValTypeAndRole(attrs);
- formatArrayContainers(attrs);
+ mergeValTypeAndRole(attrs);
+ formatArrayContainers(attrs);
- return attrs;
+ return attrs;
}
function mergeValTypeAndRole(attrs) {
-
- function makeSrcAttr(attrName) {
- return {
- valType: 'string',
- role: 'info',
- description: [
- 'Sets the source reference on plot.ly for ',
- attrName, '.'
- ].join(' ')
- };
- }
-
- function callback(attr, attrName, attrs) {
- if(exports.isValObject(attr)) {
- if(attr.valType === 'data_array') {
- // all 'data_array' attrs have role 'data'
- attr.role = 'data';
- // all 'data_array' attrs have a corresponding 'src' attr
- attrs[attrName + 'src'] = makeSrcAttr(attrName);
- }
- else if(attr.arrayOk === true) {
- // all 'arrayOk' attrs have a corresponding 'src' attr
- attrs[attrName + 'src'] = makeSrcAttr(attrName);
- }
- }
- else if(Lib.isPlainObject(attr)) {
- // all attrs container objects get role 'object'
- attr.role = 'object';
- }
+ function makeSrcAttr(attrName) {
+ return {
+ valType: "string",
+ role: "info",
+ description: [
+ "Sets the source reference on plot.ly for ",
+ attrName,
+ "."
+ ].join(" ")
+ };
+ }
+
+ function callback(attr, attrName, attrs) {
+ if (exports.isValObject(attr)) {
+ if (attr.valType === "data_array") {
+ // all 'data_array' attrs have role 'data'
+ attr.role = "data";
+ // all 'data_array' attrs have a corresponding 'src' attr
+ attrs[attrName + "src"] = makeSrcAttr(attrName);
+ } else if (attr.arrayOk === true) {
+ // all 'arrayOk' attrs have a corresponding 'src' attr
+ attrs[attrName + "src"] = makeSrcAttr(attrName);
+ }
+ } else if (Lib.isPlainObject(attr)) {
+ // all attrs container objects get role 'object'
+ attr.role = "object";
}
+ }
- exports.crawl(attrs, callback);
+ exports.crawl(attrs, callback);
}
function formatArrayContainers(attrs) {
+ function callback(attr, attrName, attrs) {
+ if (!attr) return;
- function callback(attr, attrName, attrs) {
- if(!attr) return;
+ var itemName = attr[IS_LINKED_TO_ARRAY];
- var itemName = attr[IS_LINKED_TO_ARRAY];
+ if (!itemName) return;
- if(!itemName) return;
+ delete attr[IS_LINKED_TO_ARRAY];
- delete attr[IS_LINKED_TO_ARRAY];
-
- attrs[attrName] = { items: {} };
- attrs[attrName].items[itemName] = attr;
- attrs[attrName].role = 'object';
- }
+ attrs[attrName] = { items: {} };
+ attrs[attrName].items[itemName] = attr;
+ attrs[attrName].role = "object";
+ }
- exports.crawl(attrs, callback);
+ exports.crawl(attrs, callback);
}
function assignPolarLayoutAttrs(layoutAttributes) {
- extendFlat(layoutAttributes, {
- radialaxis: polarAxisAttrs.radialaxis,
- angularaxis: polarAxisAttrs.angularaxis
- });
+ extendFlat(layoutAttributes, {
+ radialaxis: polarAxisAttrs.radialaxis,
+ angularaxis: polarAxisAttrs.angularaxis
+ });
- extendFlat(layoutAttributes, polarAxisAttrs.layout);
+ extendFlat(layoutAttributes, polarAxisAttrs.layout);
- return layoutAttributes;
+ return layoutAttributes;
}
function handleBasePlotModule(layoutAttributes, _module, astr) {
- var np = Lib.nestedProperty(layoutAttributes, astr),
- attrs = extendDeep({}, _module.layoutAttributes);
+ var np = Lib.nestedProperty(layoutAttributes, astr),
+ attrs = extendDeep({}, _module.layoutAttributes);
- attrs[IS_SUBPLOT_OBJ] = true;
- np.set(attrs);
+ attrs[IS_SUBPLOT_OBJ] = true;
+ np.set(attrs);
}
function insertAttrs(baseAttrs, newAttrs, astr) {
- var np = Lib.nestedProperty(baseAttrs, astr);
+ var np = Lib.nestedProperty(baseAttrs, astr);
- np.set(extendDeep(np.get() || {}, newAttrs));
+ np.set(extendDeep(np.get() || {}, newAttrs));
}
diff --git a/src/plot_api/register.js b/src/plot_api/register.js
index 87dae2e49b2..7f578029b5a 100644
--- a/src/plot_api/register.js
+++ b/src/plot_api/register.js
@@ -5,93 +5,97 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Registry = require('../registry');
-var Lib = require('../lib');
-
+"use strict";
+var Registry = require("../registry");
+var Lib = require("../lib");
module.exports = function register(_modules) {
- if(!_modules) {
- throw new Error('No argument passed to Plotly.register.');
- }
- else if(_modules && !Array.isArray(_modules)) {
- _modules = [_modules];
- }
+ if (!_modules) {
+ throw new Error("No argument passed to Plotly.register.");
+ } else if (_modules && !Array.isArray(_modules)) {
+ _modules = [_modules];
+ }
- for(var i = 0; i < _modules.length; i++) {
- var newModule = _modules[i];
+ for (var i = 0; i < _modules.length; i++) {
+ var newModule = _modules[i];
- if(!newModule) {
- throw new Error('Invalid module was attempted to be registered!');
- }
+ if (!newModule) {
+ throw new Error("Invalid module was attempted to be registered!");
+ }
- switch(newModule.moduleType) {
- case 'trace':
- registerTraceModule(newModule);
- break;
+ switch (newModule.moduleType) {
+ case "trace":
+ registerTraceModule(newModule);
+ break;
- case 'transform':
- registerTransformModule(newModule);
- break;
+ case "transform":
+ registerTransformModule(newModule);
+ break;
- case 'component':
- registerComponentModule(newModule);
- break;
+ case "component":
+ registerComponentModule(newModule);
+ break;
- default:
- throw new Error('Invalid module was attempted to be registered!');
- }
+ default:
+ throw new Error("Invalid module was attempted to be registered!");
}
+ }
};
function registerTraceModule(newModule) {
- Registry.register(newModule, newModule.name, newModule.categories, newModule.meta);
-
- if(!Registry.subplotsRegistry[newModule.basePlotModule.name]) {
- Registry.registerSubplot(newModule.basePlotModule);
- }
+ Registry.register(
+ newModule,
+ newModule.name,
+ newModule.categories,
+ newModule.meta
+ );
+
+ if (!Registry.subplotsRegistry[newModule.basePlotModule.name]) {
+ Registry.registerSubplot(newModule.basePlotModule);
+ }
}
function registerTransformModule(newModule) {
- if(typeof newModule.name !== 'string') {
- throw new Error('Transform module *name* must be a string.');
- }
-
- var prefix = 'Transform module ' + newModule.name;
-
- var hasTransform = typeof newModule.transform === 'function',
- hasCalcTransform = typeof newModule.calcTransform === 'function';
-
-
- if(!hasTransform && !hasCalcTransform) {
- throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.');
- }
-
- if(hasTransform && hasCalcTransform) {
- Lib.log([
- prefix + ' has both a *transform* and *calcTransform* methods.',
- 'Please note that all *transform* methods are executed',
- 'before all *calcTransform* methods.'
- ].join(' '));
- }
-
- if(!Lib.isPlainObject(newModule.attributes)) {
- Lib.log(prefix + ' registered without an *attributes* object.');
- }
-
- if(typeof newModule.supplyDefaults !== 'function') {
- Lib.log(prefix + ' registered without a *supplyDefaults* method.');
- }
-
- Registry.transformsRegistry[newModule.name] = newModule;
+ if (typeof newModule.name !== "string") {
+ throw new Error("Transform module *name* must be a string.");
+ }
+
+ var prefix = "Transform module " + newModule.name;
+
+ var hasTransform = typeof newModule.transform === "function",
+ hasCalcTransform = typeof newModule.calcTransform === "function";
+
+ if (!hasTransform && !hasCalcTransform) {
+ throw new Error(
+ prefix + " is missing a *transform* or *calcTransform* method."
+ );
+ }
+
+ if (hasTransform && hasCalcTransform) {
+ Lib.log(
+ [
+ prefix + " has both a *transform* and *calcTransform* methods.",
+ "Please note that all *transform* methods are executed",
+ "before all *calcTransform* methods."
+ ].join(" ")
+ );
+ }
+
+ if (!Lib.isPlainObject(newModule.attributes)) {
+ Lib.log(prefix + " registered without an *attributes* object.");
+ }
+
+ if (typeof newModule.supplyDefaults !== "function") {
+ Lib.log(prefix + " registered without a *supplyDefaults* method.");
+ }
+
+ Registry.transformsRegistry[newModule.name] = newModule;
}
function registerComponentModule(newModule) {
- if(typeof newModule.name !== 'string') {
- throw new Error('Component module *name* must be a string.');
- }
+ if (typeof newModule.name !== "string") {
+ throw new Error("Component module *name* must be a string.");
+ }
- Registry.registerComponent(newModule);
+ Registry.registerComponent(newModule);
}
diff --git a/src/plot_api/set_plot_config.js b/src/plot_api/set_plot_config.js
index e4f9e058ca4..316c79a776e 100644
--- a/src/plot_api/set_plot_config.js
+++ b/src/plot_api/set_plot_config.js
@@ -5,12 +5,9 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var Plotly = require('../plotly');
-var Lib = require('../lib');
+"use strict";
+var Plotly = require("../plotly");
+var Lib = require("../lib");
/**
* Extends the plot config
@@ -20,5 +17,5 @@ var Lib = require('../lib');
*
*/
module.exports = function setPlotConfig(configObj) {
- return Lib.extendFlat(Plotly.defaultConfig, configObj);
+ return Lib.extendFlat(Plotly.defaultConfig, configObj);
};
diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js
index ceb2a4dbf64..0a97354fd96 100644
--- a/src/plot_api/subroutines.js
+++ b/src/plot_api/subroutines.js
@@ -5,225 +5,221 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Plotly = require("../plotly");
+var Registry = require("../registry");
+var Plots = require("../plots/plots");
+var Lib = require("../lib");
+var Color = require("../components/color");
+var Drawing = require("../components/drawing");
+var Titles = require("../components/titles");
+var ModeBar = require("../components/modebar");
-'use strict';
+exports.layoutStyles = function(gd) {
+ return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd);
+};
-var Plotly = require('../plotly');
-var Registry = require('../registry');
-var Plots = require('../plots/plots');
-var Lib = require('../lib');
+exports.lsInner = function(gd) {
+ var fullLayout = gd._fullLayout,
+ gs = fullLayout._size,
+ axList = Plotly.Axes.list(gd),
+ i;
+
+ // clear axis line positions, to be set in the subplot loop below
+ for (i = 0; i < axList.length; i++) {
+ axList[i]._linepositions = {};
+ }
+
+ fullLayout._paperdiv
+ .style({ width: fullLayout.width + "px", height: fullLayout.height + "px" })
+ .selectAll(".main-svg")
+ .call(Drawing.setSize, fullLayout.width, fullLayout.height);
+
+ gd._context.setBackground(gd, fullLayout.paper_bgcolor);
+
+ var freefinished = [];
+ fullLayout._paper.selectAll("g.subplot").each(function(subplot) {
+ var plotinfo = fullLayout._plots[subplot],
+ xa = Plotly.Axes.getFromId(gd, subplot, "x"),
+ ya = Plotly.Axes.getFromId(gd, subplot, "y");
+
+ xa.setScale();
+ // this may already be done... not sure
+ ya.setScale();
+
+ if (plotinfo.bg) {
+ plotinfo.bg
+ .call(
+ Drawing.setRect,
+ xa._offset - gs.p,
+ ya._offset - gs.p,
+ xa._length + 2 * gs.p,
+ ya._length + 2 * gs.p
+ )
+ .call(Color.fill, fullLayout.plot_bgcolor);
+ }
-var Color = require('../components/color');
-var Drawing = require('../components/drawing');
-var Titles = require('../components/titles');
-var ModeBar = require('../components/modebar');
+ // Clip so that data only shows up on the plot area.
+ plotinfo.clipId = "clip" + fullLayout._uid + subplot + "plot";
+
+ var plotClip = fullLayout._defs
+ .selectAll("g.clips")
+ .selectAll("#" + plotinfo.clipId)
+ .data([0]);
+
+ plotClip
+ .enter()
+ .append("clipPath")
+ .attr({ class: "plotclip", id: plotinfo.clipId })
+ .append("rect");
+
+ plotClip.selectAll("rect").attr({ width: xa._length, height: ya._length });
+
+ plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset);
+ plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId);
+
+ var xlw = Drawing.crispRound(gd, xa.linewidth, 1),
+ ylw = Drawing.crispRound(gd, ya.linewidth, 1),
+ xp = gs.p + ylw,
+ xpathPrefix = "M" + (-xp) + ",",
+ xpathSuffix = "h" + (xa._length + 2 * xp),
+ showfreex = xa.anchor === "free" && freefinished.indexOf(xa._id) === -1,
+ freeposx = gs.h * (1 - (xa.position || 0)) + xlw / 2 % 1,
+ showbottom = xa.anchor === ya._id && (xa.mirror || xa.side !== "top") ||
+ xa.mirror === "all" ||
+ xa.mirror === "allticks" ||
+ xa.mirrors && xa.mirrors[ya._id + "bottom"],
+ bottompos = ya._length + gs.p + xlw / 2,
+ showtop = xa.anchor === ya._id && (xa.mirror || xa.side === "top") ||
+ xa.mirror === "all" ||
+ xa.mirror === "allticks" ||
+ xa.mirrors && xa.mirrors[ya._id + "top"],
+ toppos = -gs.p - xlw / 2,
+ // shorten y axis lines so they don't overlap x axis lines
+ yp = gs.p,
+ // except where there's no x line
+ // TODO: this gets more complicated with multiple x and y axes
+ ypbottom = showbottom ? 0 : xlw,
+ yptop = showtop ? 0 : xlw,
+ ypathSuffix = "," +
+ (-yp - yptop) +
+ "v" +
+ (ya._length + 2 * yp + yptop + ypbottom),
+ showfreey = ya.anchor === "free" && freefinished.indexOf(ya._id) === -1,
+ freeposy = gs.w * (ya.position || 0) + ylw / 2 % 1,
+ showleft = ya.anchor === xa._id && (ya.mirror || ya.side !== "right") ||
+ ya.mirror === "all" ||
+ ya.mirror === "allticks" ||
+ ya.mirrors && ya.mirrors[xa._id + "left"],
+ leftpos = -gs.p - ylw / 2,
+ showright = ya.anchor === xa._id && (ya.mirror || ya.side === "right") ||
+ ya.mirror === "all" ||
+ ya.mirror === "allticks" ||
+ ya.mirrors && ya.mirrors[xa._id + "right"],
+ rightpos = xa._length + gs.p + ylw / 2;
+
+ // save axis line positions for ticks, draggers, etc to reference
+ // each subplot gets an entry:
+ // [left or bottom, right or top, free, main]
+ // main is the position at which to draw labels and draggers, if any
+ xa._linepositions[subplot] = [
+ showbottom ? bottompos : undefined,
+ showtop ? toppos : undefined,
+ showfreex ? freeposx : undefined
+ ];
+ if (xa.anchor === ya._id) {
+ xa._linepositions[subplot][3] = xa.side === "top" ? toppos : bottompos;
+ } else if (showfreex) {
+ xa._linepositions[subplot][3] = freeposx;
+ }
+ ya._linepositions[subplot] = [
+ showleft ? leftpos : undefined,
+ showright ? rightpos : undefined,
+ showfreey ? freeposy : undefined
+ ];
+ if (ya.anchor === xa._id) {
+ ya._linepositions[subplot][3] = ya.side === "right" ? rightpos : leftpos;
+ } else if (showfreey) {
+ ya._linepositions[subplot][3] = freeposy;
+ }
-exports.layoutStyles = function(gd) {
- return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd);
-};
+ // translate all the extra stuff to have the
+ // same origin as the plot area or axes
+ var origin = "translate(" + xa._offset + "," + ya._offset + ")",
+ originx = origin,
+ originy = origin;
+ if (showfreex) {
+ originx = "translate(" + xa._offset + "," + gs.t + ")";
+ toppos += ya._offset - gs.t;
+ bottompos += ya._offset - gs.t;
+ }
+ if (showfreey) {
+ originy = "translate(" + gs.l + "," + ya._offset + ")";
+ leftpos += xa._offset - gs.l;
+ rightpos += xa._offset - gs.l;
+ }
-exports.lsInner = function(gd) {
- var fullLayout = gd._fullLayout,
- gs = fullLayout._size,
- axList = Plotly.Axes.list(gd),
- i;
-
- // clear axis line positions, to be set in the subplot loop below
- for(i = 0; i < axList.length; i++) axList[i]._linepositions = {};
-
- fullLayout._paperdiv
- .style({
- width: fullLayout.width + 'px',
- height: fullLayout.height + 'px'
- })
- .selectAll('.main-svg')
- .call(Drawing.setSize, fullLayout.width, fullLayout.height);
-
- gd._context.setBackground(gd, fullLayout.paper_bgcolor);
-
- var freefinished = [];
- fullLayout._paper.selectAll('g.subplot').each(function(subplot) {
- var plotinfo = fullLayout._plots[subplot],
- xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
- ya = Plotly.Axes.getFromId(gd, subplot, 'y');
-
- xa.setScale(); // this may already be done... not sure
- ya.setScale();
-
- if(plotinfo.bg) {
- plotinfo.bg
- .call(Drawing.setRect,
- xa._offset - gs.p, ya._offset - gs.p,
- xa._length + 2 * gs.p, ya._length + 2 * gs.p)
- .call(Color.fill, fullLayout.plot_bgcolor);
- }
-
- // Clip so that data only shows up on the plot area.
- plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
-
- var plotClip = fullLayout._defs.selectAll('g.clips')
- .selectAll('#' + plotinfo.clipId)
- .data([0]);
-
- plotClip.enter().append('clipPath')
- .attr({
- 'class': 'plotclip',
- 'id': plotinfo.clipId
- })
- .append('rect');
-
- plotClip.selectAll('rect')
- .attr({
- 'width': xa._length,
- 'height': ya._length
- });
-
-
- plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset);
- plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId);
-
- var xlw = Drawing.crispRound(gd, xa.linewidth, 1),
- ylw = Drawing.crispRound(gd, ya.linewidth, 1),
- xp = gs.p + ylw,
- xpathPrefix = 'M' + (-xp) + ',',
- xpathSuffix = 'h' + (xa._length + 2 * xp),
- showfreex = xa.anchor === 'free' &&
- freefinished.indexOf(xa._id) === -1,
- freeposx = gs.h * (1 - (xa.position||0)) + ((xlw / 2) % 1),
- showbottom =
- (xa.anchor === ya._id && (xa.mirror || xa.side !== 'top')) ||
- xa.mirror === 'all' || xa.mirror === 'allticks' ||
- (xa.mirrors && xa.mirrors[ya._id + 'bottom']),
- bottompos = ya._length + gs.p + xlw / 2,
- showtop =
- (xa.anchor === ya._id && (xa.mirror || xa.side === 'top')) ||
- xa.mirror === 'all' || xa.mirror === 'allticks' ||
- (xa.mirrors && xa.mirrors[ya._id + 'top']),
- toppos = -gs.p - xlw / 2,
-
- // shorten y axis lines so they don't overlap x axis lines
- yp = gs.p,
- // except where there's no x line
- // TODO: this gets more complicated with multiple x and y axes
- ypbottom = showbottom ? 0 : xlw,
- yptop = showtop ? 0 : xlw,
- ypathSuffix = ',' + (-yp - yptop) +
- 'v' + (ya._length + 2 * yp + yptop + ypbottom),
- showfreey = ya.anchor === 'free' &&
- freefinished.indexOf(ya._id) === -1,
- freeposy = gs.w * (ya.position||0) + ((ylw / 2) % 1),
- showleft =
- (ya.anchor === xa._id && (ya.mirror || ya.side !== 'right')) ||
- ya.mirror === 'all' || ya.mirror === 'allticks' ||
- (ya.mirrors && ya.mirrors[xa._id + 'left']),
- leftpos = -gs.p - ylw / 2,
- showright =
- (ya.anchor === xa._id && (ya.mirror || ya.side === 'right')) ||
- ya.mirror === 'all' || ya.mirror === 'allticks' ||
- (ya.mirrors && ya.mirrors[xa._id + 'right']),
- rightpos = xa._length + gs.p + ylw / 2;
-
- // save axis line positions for ticks, draggers, etc to reference
- // each subplot gets an entry:
- // [left or bottom, right or top, free, main]
- // main is the position at which to draw labels and draggers, if any
- xa._linepositions[subplot] = [
- showbottom ? bottompos : undefined,
- showtop ? toppos : undefined,
- showfreex ? freeposx : undefined
- ];
- if(xa.anchor === ya._id) {
- xa._linepositions[subplot][3] = xa.side === 'top' ?
- toppos : bottompos;
- }
- else if(showfreex) {
- xa._linepositions[subplot][3] = freeposx;
- }
-
- ya._linepositions[subplot] = [
- showleft ? leftpos : undefined,
- showright ? rightpos : undefined,
- showfreey ? freeposy : undefined
- ];
- if(ya.anchor === xa._id) {
- ya._linepositions[subplot][3] = ya.side === 'right' ?
- rightpos : leftpos;
- }
- else if(showfreey) {
- ya._linepositions[subplot][3] = freeposy;
- }
-
- // translate all the extra stuff to have the
- // same origin as the plot area or axes
- var origin = 'translate(' + xa._offset + ',' + ya._offset + ')',
- originx = origin,
- originy = origin;
- if(showfreex) {
- originx = 'translate(' + xa._offset + ',' + gs.t + ')';
- toppos += ya._offset - gs.t;
- bottompos += ya._offset - gs.t;
- }
- if(showfreey) {
- originy = 'translate(' + gs.l + ',' + ya._offset + ')';
- leftpos += xa._offset - gs.l;
- rightpos += xa._offset - gs.l;
- }
-
- plotinfo.xlines
- .attr('transform', originx)
- .attr('d', (
- (showbottom ? (xpathPrefix + bottompos + xpathSuffix) : '') +
- (showtop ? (xpathPrefix + toppos + xpathSuffix) : '') +
- (showfreex ? (xpathPrefix + freeposx + xpathSuffix) : '')) ||
- // so it doesn't barf with no lines shown
- 'M0,0')
- .style('stroke-width', xlw + 'px')
- .call(Color.stroke, xa.showline ?
- xa.linecolor : 'rgba(0,0,0,0)');
- plotinfo.ylines
- .attr('transform', originy)
- .attr('d', (
- (showleft ? ('M' + leftpos + ypathSuffix) : '') +
- (showright ? ('M' + rightpos + ypathSuffix) : '') +
- (showfreey ? ('M' + freeposy + ypathSuffix) : '')) ||
- 'M0,0')
- .attr('stroke-width', ylw + 'px')
- .call(Color.stroke, ya.showline ?
- ya.linecolor : 'rgba(0,0,0,0)');
-
- plotinfo.xaxislayer.attr('transform', originx);
- plotinfo.yaxislayer.attr('transform', originy);
- plotinfo.gridlayer.attr('transform', origin);
- plotinfo.zerolinelayer.attr('transform', origin);
- plotinfo.draglayer.attr('transform', origin);
-
- // mark free axes as displayed, so we don't draw them again
- if(showfreex) { freefinished.push(xa._id); }
- if(showfreey) { freefinished.push(ya._id); }
- });
-
- Plotly.Axes.makeClipPaths(gd);
- exports.drawMainTitle(gd);
- ModeBar.manage(gd);
-
- return gd._promises.length && Promise.all(gd._promises);
+ plotinfo.xlines
+ .attr("transform", originx)
+ .attr(
+ "d",
+ (showbottom ? xpathPrefix + bottompos + xpathSuffix : "") +
+ (showtop ? xpathPrefix + toppos + xpathSuffix : "") +
+ (showfreex ? xpathPrefix + freeposx + xpathSuffix : "") ||
+ // so it doesn't barf with no lines shown
+ "M0,0"
+ )
+ .style("stroke-width", xlw + "px")
+ .call(Color.stroke, xa.showline ? xa.linecolor : "rgba(0,0,0,0)");
+ plotinfo.ylines
+ .attr("transform", originy)
+ .attr(
+ "d",
+ (showleft ? "M" + leftpos + ypathSuffix : "") +
+ (showright ? "M" + rightpos + ypathSuffix : "") +
+ (showfreey ? "M" + freeposy + ypathSuffix : "") ||
+ "M0,0"
+ )
+ .attr("stroke-width", ylw + "px")
+ .call(Color.stroke, ya.showline ? ya.linecolor : "rgba(0,0,0,0)");
+
+ plotinfo.xaxislayer.attr("transform", originx);
+ plotinfo.yaxislayer.attr("transform", originy);
+ plotinfo.gridlayer.attr("transform", origin);
+ plotinfo.zerolinelayer.attr("transform", origin);
+ plotinfo.draglayer.attr("transform", origin);
+
+ // mark free axes as displayed, so we don't draw them again
+ if (showfreex) {
+ freefinished.push(xa._id);
+ }
+ if (showfreey) {
+ freefinished.push(ya._id);
+ }
+ });
+
+ Plotly.Axes.makeClipPaths(gd);
+ exports.drawMainTitle(gd);
+ ModeBar.manage(gd);
+
+ return gd._promises.length && Promise.all(gd._promises);
};
exports.drawMainTitle = function(gd) {
- var fullLayout = gd._fullLayout;
-
- Titles.draw(gd, 'gtitle', {
- propContainer: fullLayout,
- propName: 'title',
- dfltName: 'Plot',
- attributes: {
- x: fullLayout.width / 2,
- y: fullLayout._size.t / 2,
- 'text-anchor': 'middle'
- }
- });
+ var fullLayout = gd._fullLayout;
+
+ Titles.draw(gd, "gtitle", {
+ propContainer: fullLayout,
+ propName: "title",
+ dfltName: "Plot",
+ attributes: {
+ x: fullLayout.width / 2,
+ y: fullLayout._size.t / 2,
+ "text-anchor": "middle"
+ }
+ });
};
// First, see if we need to do arraysToCalcdata
@@ -231,98 +227,98 @@ exports.drawMainTitle = function(gd) {
// supplyDefaults brought in an array that was already
// in gd.data but not in gd._fullData previously
exports.doTraceStyle = function(gd) {
- for(var i = 0; i < gd.calcdata.length; i++) {
- var cdi = gd.calcdata[i],
- _module = ((cdi[0] || {}).trace || {})._module || {},
- arraysToCalcdata = _module.arraysToCalcdata;
+ for (var i = 0; i < gd.calcdata.length; i++) {
+ var cdi = gd.calcdata[i],
+ _module = ((cdi[0] || {}).trace || {})._module || {},
+ arraysToCalcdata = _module.arraysToCalcdata;
- if(arraysToCalcdata) arraysToCalcdata(cdi, cdi[0].trace);
- }
+ if (arraysToCalcdata) arraysToCalcdata(cdi, cdi[0].trace);
+ }
- Plots.style(gd);
- Registry.getComponentMethod('legend', 'draw')(gd);
+ Plots.style(gd);
+ Registry.getComponentMethod("legend", "draw")(gd);
- return Plots.previousPromises(gd);
+ return Plots.previousPromises(gd);
};
exports.doColorBars = function(gd) {
- for(var i = 0; i < gd.calcdata.length; i++) {
- var cdi0 = gd.calcdata[i][0];
-
- if((cdi0.t || {}).cb) {
- var trace = cdi0.trace,
- cb = cdi0.t.cb;
-
- if(Registry.traceIs(trace, 'contour')) {
- cb.line({
- width: trace.contours.showlines !== false ?
- trace.line.width : 0,
- dash: trace.line.dash,
- color: trace.contours.coloring === 'line' ?
- cb._opts.line.color : trace.line.color
- });
- }
- if(Registry.traceIs(trace, 'markerColorscale')) {
- cb.options(trace.marker.colorbar)();
- }
- else cb.options(trace.colorbar)();
- }
+ for (var i = 0; i < gd.calcdata.length; i++) {
+ var cdi0 = gd.calcdata[i][0];
+
+ if ((cdi0.t || {}).cb) {
+ var trace = cdi0.trace, cb = cdi0.t.cb;
+
+ if (Registry.traceIs(trace, "contour")) {
+ cb.line({
+ width: trace.contours.showlines !== false ? trace.line.width : 0,
+ dash: trace.line.dash,
+ color: (
+ trace.contours.coloring === "line"
+ ? cb._opts.line.color
+ : trace.line.color
+ )
+ });
+ }
+ if (Registry.traceIs(trace, "markerColorscale")) {
+ cb.options(trace.marker.colorbar)();
+ } else {
+ cb.options(trace.colorbar)();
+ }
}
+ }
- return Plots.previousPromises(gd);
+ return Plots.previousPromises(gd);
};
// force plot() to redo the layout and replot with the modified layout
exports.layoutReplot = function(gd) {
- var layout = gd.layout;
- gd.layout = undefined;
- return Plotly.plot(gd, '', layout);
+ var layout = gd.layout;
+ gd.layout = undefined;
+ return Plotly.plot(gd, "", layout);
};
exports.doLegend = function(gd) {
- Registry.getComponentMethod('legend', 'draw')(gd);
- return Plots.previousPromises(gd);
+ Registry.getComponentMethod("legend", "draw")(gd);
+ return Plots.previousPromises(gd);
};
exports.doTicksRelayout = function(gd) {
- Plotly.Axes.doTicks(gd, 'redraw');
- exports.drawMainTitle(gd);
- return Plots.previousPromises(gd);
+ Plotly.Axes.doTicks(gd, "redraw");
+ exports.drawMainTitle(gd);
+ return Plots.previousPromises(gd);
};
exports.doModeBar = function(gd) {
- var fullLayout = gd._fullLayout;
- var subplotIds, i;
-
- ModeBar.manage(gd);
- Plotly.Fx.init(gd);
-
- subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
- for(i = 0; i < subplotIds.length; i++) {
- var scene = fullLayout[subplotIds[i]]._scene;
- scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
- }
-
- // no need to do this for gl2d subplots,
- // Plots.linkSubplots takes care of it all.
-
- subplotIds = Plots.getSubplotIds(fullLayout, 'geo');
- for(i = 0; i < subplotIds.length; i++) {
- var geo = fullLayout[subplotIds[i]]._subplot;
- geo.updateFx(fullLayout.hovermode);
- }
-
- return Plots.previousPromises(gd);
+ var fullLayout = gd._fullLayout;
+ var subplotIds, i;
+
+ ModeBar.manage(gd);
+ Plotly.Fx.init(gd);
+
+ subplotIds = Plots.getSubplotIds(fullLayout, "gl3d");
+ for (i = 0; i < subplotIds.length; i++) {
+ var scene = fullLayout[subplotIds[i]]._scene;
+ scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
+ }
+
+ // no need to do this for gl2d subplots,
+ // Plots.linkSubplots takes care of it all.
+ subplotIds = Plots.getSubplotIds(fullLayout, "geo");
+ for (i = 0; i < subplotIds.length; i++) {
+ var geo = fullLayout[subplotIds[i]]._subplot;
+ geo.updateFx(fullLayout.hovermode);
+ }
+
+ return Plots.previousPromises(gd);
};
exports.doCamera = function(gd) {
- var fullLayout = gd._fullLayout,
- sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d');
+ var fullLayout = gd._fullLayout,
+ sceneIds = Plots.getSubplotIds(fullLayout, "gl3d");
- for(var i = 0; i < sceneIds.length; i++) {
- var sceneLayout = fullLayout[sceneIds[i]],
- scene = sceneLayout._scene;
+ for (var i = 0; i < sceneIds.length; i++) {
+ var sceneLayout = fullLayout[sceneIds[i]], scene = sceneLayout._scene;
- scene.setCamera(sceneLayout.camera);
- }
+ scene.setCamera(sceneLayout.camera);
+ }
};
diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js
index 6ebcf75b367..a9b3ce975f4 100644
--- a/src/plot_api/to_image.js
+++ b/src/plot_api/to_image.js
@@ -5,18 +5,16 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
-'use strict';
+var Plotly = require("../plotly");
+var Lib = require("../lib");
-var isNumeric = require('fast-isnumeric');
-
-var Plotly = require('../plotly');
-var Lib = require('../lib');
-
-var helpers = require('../snapshot/helpers');
-var clonePlot = require('../snapshot/cloneplot');
-var toSVG = require('../snapshot/tosvg');
-var svgToImg = require('../snapshot/svgtoimg');
+var helpers = require("../snapshot/helpers");
+var clonePlot = require("../snapshot/cloneplot");
+var toSVG = require("../snapshot/tosvg");
+var svgToImg = require("../snapshot/svgtoimg");
/**
* @param {object} gd figure Object
@@ -26,83 +24,92 @@ var svgToImg = require('../snapshot/svgtoimg');
* @param opts.height height of snapshot in px
*/
function toImage(gd, opts) {
-
- var promise = new Promise(function(resolve, reject) {
- // check for undefined opts
- opts = opts || {};
- // default to png
- opts.format = opts.format || 'png';
-
- var isSizeGood = function(size) {
- // undefined and null are valid options
- if(size === undefined || size === null) {
- return true;
- }
-
- if(isNumeric(size) && size > 1) {
- return true;
- }
-
- return false;
- };
-
- if(!isSizeGood(opts.width) || !isSizeGood(opts.height)) {
- reject(new Error('Height and width should be pixel values.'));
- }
-
- // first clone the GD so we can operate in a clean environment
- var clone = clonePlot(gd, {format: 'png', height: opts.height, width: opts.width});
- var clonedGd = clone.gd;
-
- // put the cloned div somewhere off screen before attaching to DOM
- clonedGd.style.position = 'absolute';
- clonedGd.style.left = '-5000px';
- document.body.appendChild(clonedGd);
-
- function wait() {
- var delay = helpers.getDelay(clonedGd._fullLayout);
-
- return new Promise(function(resolve, reject) {
- setTimeout(function() {
- var svg = toSVG(clonedGd);
-
- var canvas = document.createElement('canvas');
- canvas.id = Lib.randstr();
-
- svgToImg({
- format: opts.format,
- width: clonedGd._fullLayout.width,
- height: clonedGd._fullLayout.height,
- canvas: canvas,
- svg: svg,
- // ask svgToImg to return a Promise
- // rather than EventEmitter
- // leave EventEmitter for backward
- // compatibility
- promise: true
- }).then(function(url) {
- if(clonedGd) document.body.removeChild(clonedGd);
- resolve(url);
- }).catch(function(err) {
- reject(err);
- });
-
- }, delay);
- });
- }
-
- var redrawFunc = helpers.getRedrawFunc(clonedGd);
-
- Plotly.plot(clonedGd, clone.data, clone.layout, clone.config)
- .then(redrawFunc)
- .then(wait)
- .then(function(url) { resolve(url); })
- .catch(function(err) {
- reject(err);
- });
+ var promise = new Promise(function(resolve, reject) {
+ // check for undefined opts
+ opts = opts || {};
+ // default to png
+ opts.format = opts.format || "png";
+
+ var isSizeGood = function(size) {
+ // undefined and null are valid options
+ if (size === undefined || size === null) {
+ return true;
+ }
+
+ if (isNumeric(size) && size > 1) {
+ return true;
+ }
+
+ return false;
+ };
+
+ if (!isSizeGood(opts.width) || !isSizeGood(opts.height)) {
+ reject(new Error("Height and width should be pixel values."));
+ }
+
+ // first clone the GD so we can operate in a clean environment
+ var clone = clonePlot(gd, {
+ format: "png",
+ height: opts.height,
+ width: opts.width
});
-
- return promise;
+ var clonedGd = clone.gd;
+
+ // put the cloned div somewhere off screen before attaching to DOM
+ clonedGd.style.position = "absolute";
+ clonedGd.style.left = "-5000px";
+ document.body.appendChild(clonedGd);
+
+ function wait() {
+ var delay = helpers.getDelay(clonedGd._fullLayout);
+
+ return new Promise(function(resolve, reject) {
+ setTimeout(
+ function() {
+ var svg = toSVG(clonedGd);
+
+ var canvas = document.createElement("canvas");
+ canvas.id = Lib.randstr();
+
+ svgToImg({
+ format: opts.format,
+ width: clonedGd._fullLayout.width,
+ height: clonedGd._fullLayout.height,
+ canvas: canvas,
+ svg: svg,
+ // ask svgToImg to return a Promise
+ // rather than EventEmitter
+ // leave EventEmitter for backward
+ // compatibility
+ promise: true
+ })
+ .then(function(url) {
+ if (clonedGd) document.body.removeChild(clonedGd);
+ resolve(url);
+ })
+ .catch(function(err) {
+ reject(err);
+ });
+ },
+ delay
+ );
+ });
+ }
+
+ var redrawFunc = helpers.getRedrawFunc(clonedGd);
+
+ Plotly.plot(clonedGd, clone.data, clone.layout, clone.config)
+ .then(redrawFunc)
+ .then(wait)
+ .then(function(url) {
+ resolve(url);
+ })
+ .catch(function(err) {
+ reject(err);
+ });
+ });
+
+ return promise;
}
module.exports = toImage;
diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js
index b06e5506f4d..e4759202133 100644
--- a/src/plot_api/validate.js
+++ b/src/plot_api/validate.js
@@ -5,19 +5,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-
-var Lib = require('../lib');
-var Plots = require('../plots/plots');
-var PlotSchema = require('./plot_schema');
+"use strict";
+var Lib = require("../lib");
+var Plots = require("../plots/plots");
+var PlotSchema = require("./plot_schema");
var isPlainObject = Lib.isPlainObject;
var isArray = Array.isArray;
-
/**
* Validate a data array and layout object.
*
@@ -40,329 +35,310 @@ var isArray = Array.isArray;
* error message (shown in console in logger config argument is enable)
*/
module.exports = function valiate(data, layout) {
- var schema = PlotSchema.get(),
- errorList = [],
- gd = {};
+ var schema = PlotSchema.get(), errorList = [], gd = {};
+
+ var dataIn, layoutIn;
+
+ if (isArray(data)) {
+ gd.data = Lib.extendDeep([], data);
+ dataIn = data;
+ } else {
+ gd.data = [];
+ dataIn = [];
+ errorList.push(format("array", "data"));
+ }
+
+ if (isPlainObject(layout)) {
+ gd.layout = Lib.extendDeep({}, layout);
+ layoutIn = layout;
+ } else {
+ gd.layout = {};
+ layoutIn = {};
+ if (arguments.length > 1) {
+ errorList.push(format("object", "layout"));
+ }
+ }
- var dataIn, layoutIn;
+ // N.B. dataIn and layoutIn are in general not the same as
+ // gd.data and gd.layout after supplyDefaults as some attributes
+ // in gd.data and gd.layout (still) get mutated during this step.
+ Plots.supplyDefaults(gd);
- if(isArray(data)) {
- gd.data = Lib.extendDeep([], data);
- dataIn = data;
- }
- else {
- gd.data = [];
- dataIn = [];
- errorList.push(format('array', 'data'));
- }
+ var dataOut = gd._fullData, len = dataIn.length;
- if(isPlainObject(layout)) {
- gd.layout = Lib.extendDeep({}, layout);
- layoutIn = layout;
- }
- else {
- gd.layout = {};
- layoutIn = {};
- if(arguments.length > 1) {
- errorList.push(format('object', 'layout'));
- }
+ for (var i = 0; i < len; i++) {
+ var traceIn = dataIn[i], base = ["data", i];
+
+ if (!isPlainObject(traceIn)) {
+ errorList.push(format("object", base));
+ continue;
}
- // N.B. dataIn and layoutIn are in general not the same as
- // gd.data and gd.layout after supplyDefaults as some attributes
- // in gd.data and gd.layout (still) get mutated during this step.
+ var traceOut = dataOut[i],
+ traceType = traceOut.type,
+ traceSchema = schema.traces[traceType].attributes;
+
+ // PlotSchema does something fancy with trace 'type', reset it here
+ // to make the trace schema compatible with Lib.validate.
+ traceSchema.type = { valType: "enumerated", values: [traceType] };
- Plots.supplyDefaults(gd);
+ if (traceOut.visible === false && traceIn.visible !== false) {
+ errorList.push(format("invisible", base));
+ }
- var dataOut = gd._fullData,
- len = dataIn.length;
+ crawl(traceIn, traceOut, traceSchema, errorList, base);
- for(var i = 0; i < len; i++) {
- var traceIn = dataIn[i],
- base = ['data', i];
+ var transformsIn = traceIn.transforms, transformsOut = traceOut.transforms;
- if(!isPlainObject(traceIn)) {
- errorList.push(format('object', base));
- continue;
- }
+ if (transformsIn) {
+ if (!isArray(transformsIn)) {
+ errorList.push(format("array", base, ["transforms"]));
+ }
- var traceOut = dataOut[i],
- traceType = traceOut.type,
- traceSchema = schema.traces[traceType].attributes;
+ base.push("transforms");
- // PlotSchema does something fancy with trace 'type', reset it here
- // to make the trace schema compatible with Lib.validate.
- traceSchema.type = {
- valType: 'enumerated',
- values: [traceType]
- };
+ for (var j = 0; j < transformsIn.length; j++) {
+ var path = ["transforms", j], transformType = transformsIn[j].type;
- if(traceOut.visible === false && traceIn.visible !== false) {
- errorList.push(format('invisible', base));
+ if (!isPlainObject(transformsIn[j])) {
+ errorList.push(format("object", base, path));
+ continue;
}
- crawl(traceIn, traceOut, traceSchema, errorList, base);
+ var transformSchema = schema.transforms[transformType]
+ ? schema.transforms[transformType].attributes
+ : {};
- var transformsIn = traceIn.transforms,
- transformsOut = traceOut.transforms;
+ // add 'type' to transform schema to validate the transform type
+ transformSchema.type = {
+ valType: "enumerated",
+ values: Object.keys(schema.transforms)
+ };
- if(transformsIn) {
- if(!isArray(transformsIn)) {
- errorList.push(format('array', base, ['transforms']));
- }
+ crawl(
+ transformsIn[j],
+ transformsOut[j],
+ transformSchema,
+ errorList,
+ base,
+ path
+ );
+ }
+ }
+ }
- base.push('transforms');
+ var layoutOut = gd._fullLayout,
+ layoutSchema = fillLayoutSchema(schema, dataOut);
- for(var j = 0; j < transformsIn.length; j++) {
- var path = ['transforms', j],
- transformType = transformsIn[j].type;
+ crawl(layoutIn, layoutOut, layoutSchema, errorList, "layout");
- if(!isPlainObject(transformsIn[j])) {
- errorList.push(format('object', base, path));
- continue;
- }
+ // return undefined if no validation errors were found
+ return errorList.length === 0 ? void 0 : errorList;
+};
- var transformSchema = schema.transforms[transformType] ?
- schema.transforms[transformType].attributes :
- {};
+function crawl(objIn, objOut, schema, list, base, path) {
+ path = path || [];
- // add 'type' to transform schema to validate the transform type
- transformSchema.type = {
- valType: 'enumerated',
- values: Object.keys(schema.transforms)
- };
+ var keys = Object.keys(objIn);
- crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path);
- }
- }
- }
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
- var layoutOut = gd._fullLayout,
- layoutSchema = fillLayoutSchema(schema, dataOut);
+ // transforms are handled separately
+ if (k === "transforms") continue;
- crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout');
+ var p = path.slice();
+ p.push(k);
- // return undefined if no validation errors were found
- return (errorList.length === 0) ? void(0) : errorList;
-};
+ var valIn = objIn[k], valOut = objOut[k];
-function crawl(objIn, objOut, schema, list, base, path) {
- path = path || [];
+ var nestedSchema = getNestedSchema(schema, k),
+ isInfoArray = (nestedSchema || {}).valType === "info_array";
- var keys = Object.keys(objIn);
+ if (!isInSchema(schema, k)) {
+ list.push(format("schema", base, p));
+ } else if (isPlainObject(valIn) && isPlainObject(valOut)) {
+ crawl(valIn, valOut, nestedSchema, list, base, p);
+ } else if (nestedSchema.items && !isInfoArray && isArray(valIn)) {
+ var items = nestedSchema.items,
+ _nestedSchema = items[Object.keys(items)[0]],
+ indexList = [];
- for(var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ var j, _p;
- // transforms are handled separately
- if(k === 'transforms') continue;
+ // loop over valOut items while keeping track of their
+ // corresponding input container index (given by _index)
+ for (j = 0; j < valOut.length; j++) {
+ var _index = valOut[j]._index || j;
- var p = path.slice();
- p.push(k);
+ _p = p.slice();
+ _p.push(_index);
- var valIn = objIn[k],
- valOut = objOut[k];
+ if (isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) {
+ indexList.push(_index);
+ crawl(valIn[_index], valOut[j], _nestedSchema, list, base, _p);
+ }
+ }
- var nestedSchema = getNestedSchema(schema, k),
- isInfoArray = (nestedSchema || {}).valType === 'info_array';
+ // loop over valIn to determine where it went wrong for some items
+ for (j = 0; j < valIn.length; j++) {
+ _p = p.slice();
+ _p.push(j);
- if(!isInSchema(schema, k)) {
- list.push(format('schema', base, p));
- }
- else if(isPlainObject(valIn) && isPlainObject(valOut)) {
- crawl(valIn, valOut, nestedSchema, list, base, p);
- }
- else if(nestedSchema.items && !isInfoArray && isArray(valIn)) {
- var items = nestedSchema.items,
- _nestedSchema = items[Object.keys(items)[0]],
- indexList = [];
-
- var j, _p;
-
- // loop over valOut items while keeping track of their
- // corresponding input container index (given by _index)
- for(j = 0; j < valOut.length; j++) {
- var _index = valOut[j]._index || j;
-
- _p = p.slice();
- _p.push(_index);
-
- if(isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) {
- indexList.push(_index);
- crawl(valIn[_index], valOut[j], _nestedSchema, list, base, _p);
- }
- }
-
- // loop over valIn to determine where it went wrong for some items
- for(j = 0; j < valIn.length; j++) {
- _p = p.slice();
- _p.push(j);
-
- if(!isPlainObject(valIn[j])) {
- list.push(format('object', base, _p, valIn[j]));
- }
- else if(indexList.indexOf(j) === -1) {
- list.push(format('unused', base, _p));
- }
- }
- }
- else if(!isPlainObject(valIn) && isPlainObject(valOut)) {
- list.push(format('object', base, p, valIn));
- }
- else if(!isArray(valIn) && isArray(valOut) && !isInfoArray) {
- list.push(format('array', base, p, valIn));
- }
- else if(!(k in objOut)) {
- list.push(format('unused', base, p, valIn));
- }
- else if(!Lib.validate(valIn, nestedSchema)) {
- list.push(format('value', base, p, valIn));
+ if (!isPlainObject(valIn[j])) {
+ list.push(format("object", base, _p, valIn[j]));
+ } else if (indexList.indexOf(j) === -1) {
+ list.push(format("unused", base, _p));
}
+ }
+ } else if (!isPlainObject(valIn) && isPlainObject(valOut)) {
+ list.push(format("object", base, p, valIn));
+ } else if (!isArray(valIn) && isArray(valOut) && !isInfoArray) {
+ list.push(format("array", base, p, valIn));
+ } else if (!(k in objOut)) {
+ list.push(format("unused", base, p, valIn));
+ } else if (!Lib.validate(valIn, nestedSchema)) {
+ list.push(format("value", base, p, valIn));
}
+ }
- return list;
+ return list;
}
// the 'full' layout schema depends on the traces types presents
function fillLayoutSchema(schema, dataOut) {
- for(var i = 0; i < dataOut.length; i++) {
- var traceType = dataOut[i].type,
- traceLayoutAttr = schema.traces[traceType].layoutAttributes;
+ for (var i = 0; i < dataOut.length; i++) {
+ var traceType = dataOut[i].type,
+ traceLayoutAttr = schema.traces[traceType].layoutAttributes;
- if(traceLayoutAttr) {
- Lib.extendFlat(schema.layout.layoutAttributes, traceLayoutAttr);
- }
+ if (traceLayoutAttr) {
+ Lib.extendFlat(schema.layout.layoutAttributes, traceLayoutAttr);
}
+ }
- return schema.layout.layoutAttributes;
+ return schema.layout.layoutAttributes;
}
// validation error codes
var code2msgFunc = {
- object: function(base, astr) {
- var prefix;
-
- if(base === 'layout' && astr === '') prefix = 'The layout argument';
- else if(base[0] === 'data' && astr === '') {
- prefix = 'Trace ' + base[1] + ' in the data argument';
- }
- else prefix = inBase(base) + 'key ' + astr;
-
- return prefix + ' must be linked to an object container';
- },
- array: function(base, astr) {
- var prefix;
-
- if(base === 'data') prefix = 'The data argument';
- else prefix = inBase(base) + 'key ' + astr;
-
- return prefix + ' must be linked to an array container';
- },
- schema: function(base, astr) {
- return inBase(base) + 'key ' + astr + ' is not part of the schema';
- },
- unused: function(base, astr, valIn) {
- var target = isPlainObject(valIn) ? 'container' : 'key';
-
- return inBase(base) + target + ' ' + astr + ' did not get coerced';
- },
- invisible: function(base) {
- return 'Trace ' + base[1] + ' got defaulted to be not visible';
- },
- value: function(base, astr, valIn) {
- return [
- inBase(base) + 'key ' + astr,
- 'is set to an invalid value (' + valIn + ')'
- ].join(' ');
+ object: function(base, astr) {
+ var prefix;
+
+ if (base === "layout" && astr === "") {
+ prefix = "The layout argument";
+ } else if (base[0] === "data" && astr === "") {
+ prefix = "Trace " + base[1] + " in the data argument";
+ } else {
+ prefix = inBase(base) + "key " + astr;
}
+
+ return prefix + " must be linked to an object container";
+ },
+ array: function(base, astr) {
+ var prefix;
+
+ if (base === "data") prefix = "The data argument";
+ else prefix = inBase(base) + "key " + astr;
+
+ return prefix + " must be linked to an array container";
+ },
+ schema: function(base, astr) {
+ return inBase(base) + "key " + astr + " is not part of the schema";
+ },
+ unused: function(base, astr, valIn) {
+ var target = isPlainObject(valIn) ? "container" : "key";
+
+ return inBase(base) + target + " " + astr + " did not get coerced";
+ },
+ invisible: function(base) {
+ return "Trace " + base[1] + " got defaulted to be not visible";
+ },
+ value: function(base, astr, valIn) {
+ return [
+ inBase(base) + "key " + astr,
+ "is set to an invalid value (" + valIn + ")"
+ ].join(" ");
+ }
};
function inBase(base) {
- if(isArray(base)) return 'In data trace ' + base[1] + ', ';
+ if (isArray(base)) return "In data trace " + base[1] + ", ";
- return 'In ' + base + ', ';
+ return "In " + base + ", ";
}
function format(code, base, path, valIn) {
- path = path || '';
-
- var container, trace;
-
- // container is either 'data' or 'layout
- // trace is the trace index if 'data', null otherwise
-
- if(isArray(base)) {
- container = base[0];
- trace = base[1];
- }
- else {
- container = base;
- trace = null;
- }
-
- var astr = convertPathToAttributeString(path),
- msg = code2msgFunc[code](base, astr, valIn);
-
- // log to console if logger config option is enabled
- Lib.log(msg);
-
- return {
- code: code,
- container: container,
- trace: trace,
- path: path,
- astr: astr,
- msg: msg
- };
+ path = path || "";
+
+ var container, trace;
+
+ // container is either 'data' or 'layout
+ // trace is the trace index if 'data', null otherwise
+ if (isArray(base)) {
+ container = base[0];
+ trace = base[1];
+ } else {
+ container = base;
+ trace = null;
+ }
+
+ var astr = convertPathToAttributeString(path),
+ msg = code2msgFunc[code](base, astr, valIn);
+
+ // log to console if logger config option is enabled
+ Lib.log(msg);
+
+ return {
+ code: code,
+ container: container,
+ trace: trace,
+ path: path,
+ astr: astr,
+ msg: msg
+ };
}
function isInSchema(schema, key) {
- var parts = splitKey(key),
- keyMinusId = parts.keyMinusId,
- id = parts.id;
+ var parts = splitKey(key), keyMinusId = parts.keyMinusId, id = parts.id;
- if((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) {
- return true;
- }
+ if (keyMinusId in schema && schema[keyMinusId]._isSubplotObj && id) {
+ return true;
+ }
- return (key in schema);
+ return key in schema;
}
function getNestedSchema(schema, key) {
- var parts = splitKey(key);
+ var parts = splitKey(key);
- return schema[parts.keyMinusId];
+ return schema[parts.keyMinusId];
}
function splitKey(key) {
- var idRegex = /([2-9]|[1-9][0-9]+)$/;
+ var idRegex = /([2-9]|[1-9][0-9]+)$/;
- var keyMinusId = key.split(idRegex)[0],
- id = key.substr(keyMinusId.length, key.length);
+ var keyMinusId = key.split(idRegex)[0],
+ id = key.substr(keyMinusId.length, key.length);
- return {
- keyMinusId: keyMinusId,
- id: id
- };
+ return { keyMinusId: keyMinusId, id: id };
}
function convertPathToAttributeString(path) {
- if(!isArray(path)) return String(path);
+ if (!isArray(path)) return String(path);
- var astr = '';
+ var astr = "";
- for(var i = 0; i < path.length; i++) {
- var p = path[i];
+ for (var i = 0; i < path.length; i++) {
+ var p = path[i];
- if(typeof p === 'number') {
- astr = astr.substr(0, astr.length - 1) + '[' + p + ']';
- }
- else {
- astr += p;
- }
-
- if(i < path.length - 1) astr += '.';
+ if (typeof p === "number") {
+ astr = astr.substr(0, astr.length - 1) + "[" + p + "]";
+ } else {
+ astr += p;
}
- return astr;
+ if (i < path.length - 1) astr += ".";
+ }
+
+ return astr;
}
diff --git a/src/plotly.js b/src/plotly.js
index 042166b05bc..adeba58a286 100644
--- a/src/plotly.js
+++ b/src/plotly.js
@@ -5,9 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
/*
* Pack internal modules unto an object.
*
@@ -19,13 +17,13 @@
*/
// configuration
-exports.defaultConfig = require('./plot_api/plot_config');
+exports.defaultConfig = require("./plot_api/plot_config");
// plots
-exports.Plots = require('./plots/plots');
-exports.Axes = require('./plots/cartesian/axes');
-exports.Fx = require('./plots/cartesian/graph_interact');
-exports.ModeBar = require('./components/modebar');
+exports.Plots = require("./plots/plots");
+exports.Axes = require("./plots/cartesian/axes");
+exports.Fx = require("./plots/cartesian/graph_interact");
+exports.ModeBar = require("./components/modebar");
// plot api
-require('./plot_api/plot_api');
+require("./plot_api/plot_api");
diff --git a/src/plots/animation_attributes.js b/src/plots/animation_attributes.js
index 8b7316270cc..48ccd51d340 100644
--- a/src/plots/animation_attributes.js
+++ b/src/plots/animation_attributes.js
@@ -5,118 +5,116 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
+"use strict";
module.exports = {
- mode: {
- valType: 'enumerated',
- dflt: 'afterall',
- role: 'info',
- values: ['immediate', 'next', 'afterall'],
- description: [
- 'Describes how a new animate call interacts with currently-running',
- 'animations. If `immediate`, current animations are interrupted and',
- 'the new animation is started. If `next`, the current frame is allowed',
- 'to complete, after which the new animation is started. If `afterall`',
- 'all existing frames are animated to completion before the new animation',
- 'is started.'
- ].join(' ')
+ mode: {
+ valType: "enumerated",
+ dflt: "afterall",
+ role: "info",
+ values: ["immediate", "next", "afterall"],
+ description: [
+ "Describes how a new animate call interacts with currently-running",
+ "animations. If `immediate`, current animations are interrupted and",
+ "the new animation is started. If `next`, the current frame is allowed",
+ "to complete, after which the new animation is started. If `afterall`",
+ "all existing frames are animated to completion before the new animation",
+ "is started."
+ ].join(" ")
+ },
+ direction: {
+ valType: "enumerated",
+ role: "info",
+ values: ["forward", "reverse"],
+ dflt: "forward",
+ description: [
+ "The direction in which to play the frames triggered by the animation call"
+ ].join(" ")
+ },
+ fromcurrent: {
+ valType: "boolean",
+ dflt: false,
+ role: "info",
+ description: [
+ "Play frames starting at the current frame instead of the beginning."
+ ].join(" ")
+ },
+ frame: {
+ duration: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ dflt: 500,
+ description: [
+ "The duration in milliseconds of each frame. If greater than the frame",
+ "duration, it will be limited to the frame duration."
+ ].join(" ")
},
- direction: {
- valType: 'enumerated',
- role: 'info',
- values: ['forward', 'reverse'],
- dflt: 'forward',
- description: [
- 'The direction in which to play the frames triggered by the animation call'
- ].join(' ')
- },
- fromcurrent: {
- valType: 'boolean',
- dflt: false,
- role: 'info',
- description: [
- 'Play frames starting at the current frame instead of the beginning.'
- ].join(' ')
- },
- frame: {
- duration: {
- valType: 'number',
- role: 'info',
- min: 0,
- dflt: 500,
- description: [
- 'The duration in milliseconds of each frame. If greater than the frame',
- 'duration, it will be limited to the frame duration.'
- ].join(' ')
- },
- redraw: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Redraw the plot at completion of the transition. This is desirable',
- 'for transitions that include properties that cannot be transitioned,',
- 'but may significantly slow down updates that do not require a full',
- 'redraw of the plot'
- ].join(' ')
- },
+ redraw: {
+ valType: "boolean",
+ role: "info",
+ dflt: true,
+ description: [
+ "Redraw the plot at completion of the transition. This is desirable",
+ "for transitions that include properties that cannot be transitioned,",
+ "but may significantly slow down updates that do not require a full",
+ "redraw of the plot"
+ ].join(" ")
+ }
+ },
+ transition: {
+ duration: {
+ valType: "number",
+ role: "info",
+ min: 0,
+ dflt: 500,
+ description: [
+ "The duration of the transition, in milliseconds. If equal to zero,",
+ "updates are synchronous."
+ ].join(" ")
},
- transition: {
- duration: {
- valType: 'number',
- role: 'info',
- min: 0,
- dflt: 500,
- description: [
- 'The duration of the transition, in milliseconds. If equal to zero,',
- 'updates are synchronous.'
- ].join(' ')
- },
- easing: {
- valType: 'enumerated',
- dflt: 'cubic-in-out',
- values: [
- 'linear',
- 'quad',
- 'cubic',
- 'sin',
- 'exp',
- 'circle',
- 'elastic',
- 'back',
- 'bounce',
- 'linear-in',
- 'quad-in',
- 'cubic-in',
- 'sin-in',
- 'exp-in',
- 'circle-in',
- 'elastic-in',
- 'back-in',
- 'bounce-in',
- 'linear-out',
- 'quad-out',
- 'cubic-out',
- 'sin-out',
- 'exp-out',
- 'circle-out',
- 'elastic-out',
- 'back-out',
- 'bounce-out',
- 'linear-in-out',
- 'quad-in-out',
- 'cubic-in-out',
- 'sin-in-out',
- 'exp-in-out',
- 'circle-in-out',
- 'elastic-in-out',
- 'back-in-out',
- 'bounce-in-out'
- ],
- role: 'info',
- description: 'The easing function used for the transition'
- },
+ easing: {
+ valType: "enumerated",
+ dflt: "cubic-in-out",
+ values: [
+ "linear",
+ "quad",
+ "cubic",
+ "sin",
+ "exp",
+ "circle",
+ "elastic",
+ "back",
+ "bounce",
+ "linear-in",
+ "quad-in",
+ "cubic-in",
+ "sin-in",
+ "exp-in",
+ "circle-in",
+ "elastic-in",
+ "back-in",
+ "bounce-in",
+ "linear-out",
+ "quad-out",
+ "cubic-out",
+ "sin-out",
+ "exp-out",
+ "circle-out",
+ "elastic-out",
+ "back-out",
+ "bounce-out",
+ "linear-in-out",
+ "quad-in-out",
+ "cubic-in-out",
+ "sin-in-out",
+ "exp-in-out",
+ "circle-in-out",
+ "elastic-in-out",
+ "back-in-out",
+ "bounce-in-out"
+ ],
+ role: "info",
+ description: "The easing function used for the transition"
}
+ }
};
diff --git a/src/plots/array_container_defaults.js b/src/plots/array_container_defaults.js
index 2754ed613c2..539f7391fde 100644
--- a/src/plots/array_container_defaults.js
+++ b/src/plots/array_container_defaults.js
@@ -5,11 +5,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-var Lib = require('../lib');
-
+"use strict";
+var Lib = require("../lib");
/** Convenience wrapper for making array container logic DRY and consistent
*
@@ -41,27 +38,29 @@ var Lib = require('../lib');
* links to supplementary data (e.g. fullData for layout components)
*
*/
-module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {
- var name = opts.name;
+module.exports = function handleArrayContainerDefaults(
+ parentObjIn,
+ parentObjOut,
+ opts
+) {
+ var name = opts.name;
- var contIn = Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [],
- contOut = parentObjOut[name] = [];
+ var contIn = Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [],
+ contOut = parentObjOut[name] = [];
- for(var i = 0; i < contIn.length; i++) {
- var itemIn = contIn[i],
- itemOut = {},
- itemOpts = {};
+ for (var i = 0; i < contIn.length; i++) {
+ var itemIn = contIn[i], itemOut = {}, itemOpts = {};
- if(!Lib.isPlainObject(itemIn)) {
- itemOpts.itemIsNotPlainObject = true;
- itemIn = {};
- }
+ if (!Lib.isPlainObject(itemIn)) {
+ itemOpts.itemIsNotPlainObject = true;
+ itemIn = {};
+ }
- opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);
+ opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);
- itemOut._input = itemIn;
- itemOut._index = i;
+ itemOut._input = itemIn;
+ itemOut._index = i;
- contOut.push(itemOut);
- }
+ contOut.push(itemOut);
+ }
};
diff --git a/src/plots/attributes.js b/src/plots/attributes.js
index 594fba13a6d..b5a1c44dd4b 100644
--- a/src/plots/attributes.js
+++ b/src/plots/attributes.js
@@ -5,104 +5,98 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
- type: {
- valType: 'enumerated',
- role: 'info',
- values: [], // listed dynamically
- dflt: 'scatter'
+ type: {
+ valType: "enumerated",
+ role: "info",
+ values: [],
+ // listed dynamically
+ 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", "skip"],
+ dflt: "all",
+ description: [
+ "Determines which trace information appear on hover.",
+ "If `none` or `skip` are set, no information is displayed upon hovering.",
+ "But, if `none` is set, click and hover events are still fired."
+ ].join(" ")
+ },
+ 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(" ")
},
- 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', 'skip'],
- dflt: 'all',
- description: [
- 'Determines which trace information appear on hover.',
- 'If `none` or `skip` are set, no information is displayed upon hovering.',
- 'But, if `none` is set, click and hover events are still fired.'
- ].join(' ')
- },
- 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,
- max: 10000,
- dflt: 500,
- 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(' ')
- }
+ maxpoints: {
+ valType: "number",
+ min: 0,
+ max: 10000,
+ dflt: 500,
+ 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/cartesian/attributes.js b/src/plots/cartesian/attributes.js
index a472892edc6..40c1f408b2d 100644
--- a/src/plots/cartesian/attributes.js
+++ b/src/plots/cartesian/attributes.js
@@ -5,33 +5,30 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
- xaxis: {
- valType: 'subplotid',
- 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: 'subplotid',
- 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(' ')
- }
+ xaxis: {
+ valType: "subplotid",
+ 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: "subplotid",
+ 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 603a933d548..f3c02d80eb4 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -5,21 +5,18 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
-var isNumeric = require('fast-isnumeric');
-
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var Titles = require('../../components/titles');
-var Color = require('../../components/color');
-var Drawing = require('../../components/drawing');
-
-var constants = require('../../constants/numerical');
+"use strict";
+var d3 = require("d3");
+var isNumeric = require("fast-isnumeric");
+
+var Registry = require("../../registry");
+var Lib = require("../../lib");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var Titles = require("../../components/titles");
+var Color = require("../../components/color");
+var Drawing = require("../../components/drawing");
+
+var constants = require("../../constants/numerical");
var FP_SAFE = constants.FP_SAFE;
var ONEAVGYEAR = constants.ONEAVGYEAR;
var ONEAVGMONTH = constants.ONEAVGMONTH;
@@ -29,15 +26,14 @@ var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
var BADNUM = constants.BADNUM;
-
var axes = module.exports = {};
-axes.layoutAttributes = require('./layout_attributes');
-axes.supplyLayoutDefaults = require('./layout_defaults');
+axes.layoutAttributes = require("./layout_attributes");
+axes.supplyLayoutDefaults = require("./layout_defaults");
-axes.setConvert = require('./set_convert');
+axes.setConvert = require("./set_convert");
-var axisIds = require('./axis_ids');
+var axisIds = require("./axis_ids");
axes.id2name = axisIds.id2name;
axes.cleanId = axisIds.cleanId;
axes.list = axisIds.list;
@@ -45,7 +41,6 @@ axes.listIds = axisIds.listIds;
axes.getFromId = axisIds.getFromId;
axes.getFromTrace = axisIds.getFromTrace;
-
/*
* find the list of possible axes to reference with an xref or yref attribute
* and coerce it to that list
@@ -57,25 +52,31 @@ axes.getFromTrace = axisIds.getFromTrace;
* extraOption: aside from existing axes with this letter, what non-axis value is allowed?
* Only required if it's different from `dflt`
*/
-axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
- var axLetter = attr.charAt(attr.length - 1),
- axlist = axes.listIds(gd, axLetter),
- refAttr = attr + 'ref',
- attrDef = {};
-
- if(!dflt) dflt = axlist[0] || extraOption;
- if(!extraOption) extraOption = dflt;
-
- // data-ref annotations are not supported in gl2d yet
-
- attrDef[refAttr] = {
- valType: 'enumerated',
- values: axlist.concat(extraOption ? [extraOption] : []),
- dflt: dflt
- };
-
- // xref, yref
- return Lib.coerce(containerIn, containerOut, attrDef, refAttr);
+axes.coerceRef = function(
+ containerIn,
+ containerOut,
+ gd,
+ attr,
+ dflt,
+ extraOption
+) {
+ var axLetter = attr.charAt(attr.length - 1),
+ axlist = axes.listIds(gd, axLetter),
+ refAttr = attr + "ref",
+ attrDef = {};
+
+ if (!dflt) dflt = axlist[0] || extraOption;
+ if (!extraOption) extraOption = dflt;
+
+ // data-ref annotations are not supported in gl2d yet
+ attrDef[refAttr] = {
+ valType: "enumerated",
+ values: axlist.concat(extraOption ? [extraOption] : []),
+ dflt: dflt
+ };
+
+ // xref, yref
+ return Lib.coerce(containerIn, containerOut, attrDef, refAttr);
};
/*
@@ -101,54 +102,53 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption
* - for other types: coerce them to numbers
*/
axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) {
- var pos,
- newPos;
+ var pos, newPos;
- if(axRef === 'paper' || axRef === 'pixel') {
- pos = coerce(attr, dflt);
- }
- else {
- var ax = axes.getFromId(gd, axRef);
+ if (axRef === "paper" || axRef === "pixel") {
+ pos = coerce(attr, dflt);
+ } else {
+ var ax = axes.getFromId(gd, axRef);
- dflt = ax.fraction2r(dflt);
- pos = coerce(attr, dflt);
+ dflt = ax.fraction2r(dflt);
+ pos = coerce(attr, dflt);
- if(ax.type === 'category') {
- // if position is given as a category name, convert it to a number
- if(typeof pos === 'string' && (ax._categories || []).length) {
- newPos = ax._categories.indexOf(pos);
- containerOut[attr] = (newPos === -1) ? dflt : newPos;
- return;
- }
- }
- else if(ax.type === 'date') {
- containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar);
- return;
- }
- }
- // finally make sure we have a number (unless date type already returned a string)
- containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt;
+ if (ax.type === "category") {
+ // if position is given as a category name, convert it to a number
+ if (typeof pos === "string" && (ax._categories || []).length) {
+ newPos = ax._categories.indexOf(pos);
+ containerOut[attr] = newPos === -1 ? dflt : newPos;
+ return;
+ }
+ } else if (ax.type === "date") {
+ containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar);
+ return;
+ }
+ }
+ // finally make sure we have a number (unless date type already returned a string)
+ containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt;
};
// 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;
+ 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;
+ });
};
// 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) {
- var axLetter = id.charAt(0);
- if(axLetter === 'x') return 'y';
- if(axLetter === 'y') return 'x';
+ var axLetter = id.charAt(0);
+ if (axLetter === "x") return "y";
+ if (axLetter === "y") return "x";
};
// incorporate a new minimum difference and first tick into
@@ -156,35 +156,34 @@ axes.counterLetter = function(id) {
// note that _forceTick0 is linearized, so needs to be turned into
// a range value for setting tick0
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;
- }
+ // 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;
+ } else if (ax._minDtick === undefined) {
// undefined means there's nothing there yet
- else if(ax._minDtick === undefined) {
- 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;
- }
- }
+ 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;
+ } else if (
+ (newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 ||
+ ((newFirst - ax._forceTick0) / ax._minDtick % 1 + 1.000001) % 1 > 2e-6
+ ) {
+ // 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
+ ax._minDtick = 0;
+ }
+ }
};
// Find the autorange for this axis
@@ -201,168 +200,152 @@ axes.minDtick = function(ax, newDiff, newFirst, allow) {
// though, because otherwise values between categories (or outside all categories)
// would be impossible.
axes.getAutoRange = function(ax) {
- var newRange = [];
-
- 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 = false;
-
- if(ax.range) {
- var rng = Lib.simpleMap(ax.range, ax.r2l);
- axReverse = rng[1] < rng[0];
- }
-
- // one-time setting to easily reverse the axis
- // when plotting from code
- if(ax.autorange === 'reversed') {
- axReverse = true;
- ax.autorange = true;
- }
-
- for(i = 0; i < ax._min.length; i++) {
- minpt = ax._min[i];
- for(j = 0; j < ax._max.length; j++) {
- maxpt = ax._max[j];
- dv = maxpt.val - minpt.val;
- dp = ax._length - minpt.pad - maxpt.pad;
- if(dv > 0 && dp > 0 && dv / dp > mbest) {
- minbest = minpt;
- maxbest = maxpt;
- mbest = dv / dp;
- }
- }
- }
-
- if(minmin === maxmax) {
- var lower = minmin - 1;
- var upper = minmin + 1;
- if(ax.rangemode === 'tozero') {
- newRange = minmin < 0 ? [lower, 0] : [0, upper];
- }
- else if(ax.rangemode === 'nonnegative') {
- newRange = [Math.max(0, lower), Math.max(0, upper)];
- }
- else {
- newRange = [lower, upper];
- }
- }
- else if(mbest) {
- if(ax.type === 'linear' || ax.type === '-') {
- if(ax.rangemode === 'tozero') {
- if(minbest.val >= 0) {
- minbest = {val: 0, pad: 0};
- }
- if(maxbest.val <= 0) {
- maxbest = {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);
+ var newRange = [];
- }
+ var minmin = ax._min[0].val, maxmax = ax._max[0].val, i;
- newRange = [
- minbest.val - mbest * minbest.pad,
- maxbest.val + mbest * maxbest.pad
- ];
- }
+ 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);
+ }
- // don't let axis have zero size, while still respecting tozero and nonnegative
- if(newRange[0] === newRange[1]) {
- if(ax.rangemode === 'tozero') {
- if(newRange[0] < 0) {
- newRange = [newRange[0], 0];
- }
- else if(newRange[0] > 0) {
- newRange = [0, newRange[0]];
- }
- else {
- newRange = [0, 1];
- }
- }
- else {
- newRange = [newRange[0] - 1, newRange[0] + 1];
- if(ax.rangemode === 'nonnegative') {
- newRange[0] = Math.max(0, newRange[0]);
- }
- }
- }
+ var j, minpt, maxpt, minbest, maxbest, dp, dv, mbest = 0, axReverse = false;
- // maintain reversal
- if(axReverse) newRange.reverse();
-
- return Lib.simpleMap(newRange, ax.l2r || Number);
+ if (ax.range) {
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
+ axReverse = rng[1] < rng[0];
+ }
+
+ // one-time setting to easily reverse the axis
+ // when plotting from code
+ if (ax.autorange === "reversed") {
+ axReverse = true;
+ ax.autorange = true;
+ }
+
+ for (i = 0; i < ax._min.length; i++) {
+ minpt = ax._min[i];
+ for (j = 0; j < ax._max.length; j++) {
+ maxpt = ax._max[j];
+ dv = maxpt.val - minpt.val;
+ dp = ax._length - minpt.pad - maxpt.pad;
+ if (dv > 0 && dp > 0 && dv / dp > mbest) {
+ minbest = minpt;
+ maxbest = maxpt;
+ mbest = dv / dp;
+ }
+ }
+ }
+
+ if (minmin === maxmax) {
+ var lower = minmin - 1;
+ var upper = minmin + 1;
+ if (ax.rangemode === "tozero") {
+ newRange = minmin < 0 ? [lower, 0] : [0, upper];
+ } else if (ax.rangemode === "nonnegative") {
+ newRange = [Math.max(0, lower), Math.max(0, upper)];
+ } else {
+ newRange = [lower, upper];
+ }
+ } else if (mbest) {
+ if (ax.type === "linear" || ax.type === "-") {
+ if (ax.rangemode === "tozero") {
+ if (minbest.val >= 0) {
+ minbest = { val: 0, pad: 0 };
+ }
+ if (maxbest.val <= 0) {
+ maxbest = { 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);
+ }
+
+ newRange = [
+ minbest.val - mbest * minbest.pad,
+ maxbest.val + mbest * maxbest.pad
+ ];
+ }
+
+ // don't let axis have zero size, while still respecting tozero and nonnegative
+ if (newRange[0] === newRange[1]) {
+ if (ax.rangemode === "tozero") {
+ if (newRange[0] < 0) {
+ newRange = [newRange[0], 0];
+ } else if (newRange[0] > 0) {
+ newRange = [0, newRange[0]];
+ } else {
+ newRange = [0, 1];
+ }
+ } else {
+ newRange = [newRange[0] - 1, newRange[0] + 1];
+ if (ax.rangemode === "nonnegative") {
+ newRange[0] = Math.max(0, newRange[0]);
+ }
+ }
+ }
+
+ // maintain reversal
+ if (axReverse) newRange.reverse();
+
+ return Lib.simpleMap(newRange, ax.l2r || Number);
};
axes.doAutoRange = function(ax) {
- if(!ax._length) ax.setScale();
+ if (!ax._length) ax.setScale();
- // TODO do we really need this?
- var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length);
+ // TODO do we really need this?
+ var hasDeps = ax._min && ax._max && ax._min.length && ax._max.length;
- if(ax.autorange && hasDeps) {
- ax.range = axes.getAutoRange(ax);
+ if (ax.autorange && hasDeps) {
+ ax.range = axes.getAutoRange(ax);
- // doAutoRange will get called on fullLayout,
- // but we want to report its results back to layout
- var axIn = ax._gd.layout[ax._name];
+ // doAutoRange will get called on fullLayout,
+ // but we want to report its results back to layout
+ var axIn = ax._gd.layout[ax._name];
- if(!axIn) ax._gd.layout[ax._name] = axIn = {};
+ if (!axIn) ax._gd.layout[ax._name] = axIn = {};
- if(axIn !== ax) {
- axIn.range = ax.range.slice();
- axIn.autorange = ax.autorange;
- }
+ 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 mode bar and dblclick events
axes.saveRangeInitial = function(gd, overwrite) {
- var axList = axes.list(gd, '', true),
- hasOneAxisChanged = false;
-
- for(var i = 0; i < axList.length; i++) {
- var ax = axList[i];
-
- var isNew = (ax._rangeInitial === undefined);
- var 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;
- }
+ var axList = axes.list(gd, "", true), hasOneAxisChanged = false;
+
+ for (var i = 0; i < axList.length; i++) {
+ var ax = axList[i];
+
+ var isNew = ax._rangeInitial === undefined;
+ var 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;
+ return hasOneAxisChanged;
};
// axes.expand: if autoranging, include new data in the outer limits
@@ -378,435 +361,457 @@ axes.saveRangeInitial = function(gd, overwrite) {
// tozero: (boolean) make sure to include zero if axis is linear,
// and make it a tight bound if possible
axes.expand = function(ax, data, options) {
- if(!(ax.autorange || ax._needsExpand) || !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 < vmax / 10) { vmin = vmax / 10; }
-
- dmin = ax.c2l(vmin);
- dmax = ax.c2l(vmax);
-
- if(tozero) {
- dmin = Math.min(0, dmin);
- dmax = Math.max(0, dmax);
- }
-
- // In order to stop overflow errors, don't consider points
- // too close to the limits of js floating point
- function goodNumber(v) {
- return isNumeric(v) && Math.abs(v) < FP_SAFE;
- }
-
- if(goodNumber(dmin)) {
- includeThis = true;
- // take items v from ax._min and compare them to the
- // presently active point:
- // - if the item supercedes the new point, set includethis false
- // - if the new pt supercedes the item, delete it from ax._min
- for(j = 0; j < ax._min.length && includeThis; j++) {
- v = ax._min[j];
- if(v.val <= dmin && v.pad >= 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 < ax._max.length && includeThis; j++) {
- v = ax._max[j];
- if(v.val >= 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);
-
+ if (!(ax.autorange || ax._needsExpand) || !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 < vmax / 10) {
+ vmin = vmax / 10;
+ }
+
+ dmin = ax.c2l(vmin);
+ dmax = ax.c2l(vmax);
+
+ if (tozero) {
+ dmin = Math.min(0, dmin);
+ dmax = Math.max(0, dmax);
+ }
+
+ // In order to stop overflow errors, don't consider points
+ // too close to the limits of js floating point
+ function goodNumber(v) {
+ return isNumeric(v) && Math.abs(v) < FP_SAFE;
+ }
+
+ if (goodNumber(dmin)) {
+ includeThis = true;
+ // take items v from ax._min and compare them to the
+ // presently active point:
+ // - if the item supercedes the new point, set includethis false
+ // - if the new pt supercedes the item, delete it from ax._min
+ for (j = 0; j < ax._min.length && includeThis; j++) {
+ v = ax._min[j];
+ if (v.val <= dmin && v.pad >= 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 < ax._max.length && includeThis; j++) {
+ v = ax._max[j];
+ if (v.val >= 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, calendar) {
- var dataMin = Lib.aggNums(Math.min, null, data),
- dataMax = Lib.aggNums(Math.max, null, data);
-
- if(!calendar) calendar = ax.calendar;
-
- 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 = Lib.distinctVals(data),
- msexp = Math.pow(10, Math.floor(
- Math.log(distinctData.minDiff) / Math.LN10)),
- minSize = msexp * Lib.roundUp(
- distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
- size0 = Math.max(minSize, 2 * Lib.stdev(data) /
- Math.pow(data.length, is2d ? 0.25 : 0.4));
-
- // fallback if ax.d2c output BADNUMs
- // e.g. when user try to plot categorical bins
- // on a layout.xaxis.type: 'linear'
- if(!isNumeric(size0)) size0 = 1;
- }
-
- // piggyback off autotick code to make "nice" bin sizes
- var dummyAx;
- if(ax.type === 'log') {
- dummyAx = {
- type: 'linear',
- range: [dataMin, dataMax]
- };
- }
- else {
- dummyAx = {
- type: ax.type,
- range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
- calendar: calendar
- };
- }
- axes.setConvert(dummyAx);
-
- axes.autoTicks(dummyAx, size0);
- var binStart = axes.tickIncrement(
- axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
- binEnd;
-
- // 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') {
- binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax);
-
- var bincount = 1 + Math.floor((dataMax - binStart) / dummyAx.dtick);
- binEnd = binStart + bincount * dummyAx.dtick;
- }
- else {
- // month ticks - should be the only nonlinear kind we have at this point.
- // dtick (as supplied by axes.autoTick) only has nonlinear values on
- // date and log axes, but even if you display a histogram on a log axis
- // we bin it on a linear axis (which one could argue against, but that's
- // a separate issue)
- if(dummyAx.dtick.charAt(0) === 'M') {
- binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin, calendar);
- }
-
- // calculate the endpoint for nonlinear ticks - you have to
- // just increment until you're done
- binEnd = binStart;
- while(binEnd <= dataMax) {
- binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar);
- }
- }
+ var dataMin = Lib.aggNums(Math.min, null, data),
+ dataMax = Lib.aggNums(Math.max, null, data);
+
+ if (!calendar) calendar = ax.calendar;
+
+ 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 = Lib.distinctVals(data),
+ msexp = Math.pow(
+ 10,
+ Math.floor(Math.log(distinctData.minDiff) / Math.LN10)
+ ),
+ minSize = msexp *
+ Lib.roundUp(distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
+ size0 = Math.max(
+ minSize,
+ 2 * Lib.stdev(data) / Math.pow(data.length, is2d ? 0.25 : 0.4)
+ );
- return {
- start: ax.c2r(binStart, 0, calendar),
- end: ax.c2r(binEnd, 0, calendar),
- size: dummyAx.dtick
+ // fallback if ax.d2c output BADNUMs
+ // e.g. when user try to plot categorical bins
+ // on a layout.xaxis.type: 'linear'
+ if (!isNumeric(size0)) size0 = 1;
+ }
+
+ // piggyback off autotick code to make "nice" bin sizes
+ var dummyAx;
+ if (ax.type === "log") {
+ dummyAx = { type: "linear", range: [dataMin, dataMax] };
+ } else {
+ dummyAx = {
+ type: ax.type,
+ range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
+ calendar: calendar
};
+ }
+ axes.setConvert(dummyAx);
+
+ axes.autoTicks(dummyAx, size0);
+ var binStart = axes.tickIncrement(
+ axes.tickFirst(dummyAx),
+ dummyAx.dtick,
+ "reverse",
+ calendar
+ ),
+ binEnd;
+
+ // 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") {
+ binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax);
+
+ var bincount = 1 + Math.floor((dataMax - binStart) / dummyAx.dtick);
+ binEnd = binStart + bincount * dummyAx.dtick;
+ } else {
+ // month ticks - should be the only nonlinear kind we have at this point.
+ // dtick (as supplied by axes.autoTick) only has nonlinear values on
+ // date and log axes, but even if you display a histogram on a log axis
+ // we bin it on a linear axis (which one could argue against, but that's
+ // a separate issue)
+ if (dummyAx.dtick.charAt(0) === "M") {
+ binStart = autoShiftMonthBins(
+ binStart,
+ data,
+ dummyAx.dtick,
+ dataMin,
+ calendar
+ );
+ }
+
+ // calculate the endpoint for nonlinear ticks - you have to
+ // just increment until you're done
+ binEnd = binStart;
+ while (binEnd <= dataMax) {
+ binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar);
+ }
+ }
+
+ return {
+ start: ax.c2r(binStart, 0, calendar),
+ end: ax.c2r(binEnd, 0, calendar),
+ size: dummyAx.dtick
+ };
};
-
function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) {
- var edgecount = 0,
- midcount = 0,
- intcount = 0,
- blankCount = 0;
-
- function nearEdge(v) {
- // is a value within 1% of a bin edge?
- return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2;
- }
-
- for(var i = 0; i < data.length; i++) {
- if(data[i] % 1 === 0) intcount++;
- else if(!isNumeric(data[i])) blankCount++;
-
- if(nearEdge(data[i])) edgecount++;
- if(nearEdge(data[i] + ax.dtick / 2)) midcount++;
- }
- var dataCount = data.length - blankCount;
-
- if(intcount === dataCount && ax.type !== 'date') {
- // all integers: if bin size is <1, it's because
- // that was specifically requested (large nbins)
- // so respect that... but center the bins containing
- // integers on those integers
- if(ax.dtick < 1) {
- binStart = dataMin - 0.5 * ax.dtick;
- }
- // otherwise start half an integer down regardless of
- // the bin size, just enough to clear up endpoint
- // ambiguity about which integers are in which bins.
- else {
- binStart -= 0.5;
- if(binStart + ax.dtick < dataMin) binStart += ax.dtick;
- }
- }
- else if(midcount < dataCount * 0.1) {
- if(edgecount > dataCount * 0.3 ||
- nearEdge(dataMin) || nearEdge(dataMax)) {
- // lots of points at the edge, not many in the middle
- // shift half a bin
- var binshift = ax.dtick / 2;
- binStart += (binStart + binshift < dataMin) ? binshift : -binshift;
- }
- }
- return binStart;
+ var edgecount = 0, midcount = 0, intcount = 0, blankCount = 0;
+
+ function nearEdge(v) {
+ // is a value within 1% of a bin edge?
+ return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2;
+ }
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] % 1 === 0) intcount++;
+ else if (!isNumeric(data[i])) blankCount++;
+
+ if (nearEdge(data[i])) edgecount++;
+ if (nearEdge(data[i] + ax.dtick / 2)) midcount++;
+ }
+ var dataCount = data.length - blankCount;
+
+ if (intcount === dataCount && ax.type !== "date") {
+ // all integers: if bin size is <1, it's because
+ // that was specifically requested (large nbins)
+ // so respect that... but center the bins containing
+ // integers on those integers
+ if (ax.dtick < 1) {
+ binStart = dataMin - 0.5 * ax.dtick;
+ } else {
+ // otherwise start half an integer down regardless of
+ // the bin size, just enough to clear up endpoint
+ // ambiguity about which integers are in which bins.
+ binStart -= 0.5;
+ if (binStart + ax.dtick < dataMin) binStart += ax.dtick;
+ }
+ } else if (midcount < dataCount * 0.1) {
+ if (edgecount > dataCount * 0.3 || nearEdge(dataMin) || nearEdge(dataMax)) {
+ // lots of points at the edge, not many in the middle
+ // shift half a bin
+ var binshift = ax.dtick / 2;
+ binStart += binStart + binshift < dataMin ? binshift : -binshift;
+ }
+ }
+ return binStart;
}
-
function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
- var stats = Lib.findExactDates(data, calendar);
- // number of data points that needs to be an exact value
- // to shift that increment to (near) the bin center
- var threshold = 0.8;
-
- if(stats.exactDays > threshold) {
- var numMonths = Number(dtick.substr(1));
-
- if((stats.exactYears > threshold) && (numMonths % 12 === 0)) {
- // The exact middle of a non-leap-year is 1.5 days into July
- // so if we start the bins here, all but leap years will
- // get hover-labeled as exact years.
- binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5;
- }
- else if(stats.exactMonths > threshold) {
- // Months are not as clean, but if we shift half the *longest*
- // month (31/2 days) then 31-day months will get labeled exactly
- // and shorter months will get labeled with the correct month
- // but shifted 12-36 hours into it.
- binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5;
- }
- else {
- // Shifting half a day is exact, but since these are month bins it
- // will always give a somewhat odd-looking label, until we do something
- // smarter like showing the bin boundaries (or the bounds of the actual
- // data in each bin)
- binStart -= ONEDAY / 2;
- }
- var nextBinStart = axes.tickIncrement(binStart, dtick);
-
- if(nextBinStart <= dataMin) return nextBinStart;
- }
- return binStart;
+ var stats = Lib.findExactDates(data, calendar);
+ // number of data points that needs to be an exact value
+ // to shift that increment to (near) the bin center
+ var threshold = 0.8;
+
+ if (stats.exactDays > threshold) {
+ var numMonths = Number(dtick.substr(1));
+
+ if (stats.exactYears > threshold && numMonths % 12 === 0) {
+ // The exact middle of a non-leap-year is 1.5 days into July
+ // so if we start the bins here, all but leap years will
+ // get hover-labeled as exact years.
+ binStart = axes.tickIncrement(binStart, "M6", "reverse") + ONEDAY * 1.5;
+ } else if (stats.exactMonths > threshold) {
+ // Months are not as clean, but if we shift half the *longest*
+ // month (31/2 days) then 31-day months will get labeled exactly
+ // and shorter months will get labeled with the correct month
+ // but shifted 12-36 hours into it.
+ binStart = axes.tickIncrement(binStart, "M1", "reverse") + ONEDAY * 15.5;
+ } else {
+ // Shifting half a day is exact, but since these are month bins it
+ // will always give a somewhat odd-looking label, until we do something
+ // smarter like showing the bin boundaries (or the bounds of the actual
+ // data in each bin)
+ binStart -= ONEDAY / 2;
+ }
+ var nextBinStart = axes.tickIncrement(binStart, dtick);
+
+ if (nextBinStart <= dataMin) return nextBinStart;
+ }
+ return binStart;
}
// ----------------------------------------------------
// Ticks and grids
// ----------------------------------------------------
-
// calculate the ticks: text, values, positioning
// if ticks are set to automatic, determine the right values (tick0,dtick)
// in any case, set tickround to # of digits to round tick labels to,
// or codes to this effect for log and date scales
axes.calcTicks = function calcTicks(ax) {
- var rng = Lib.simpleMap(ax.range, ax.r2l);
-
- // calculate max number of (auto) ticks to display based on plot size
- if(ax.tickmode === 'auto' || !ax.dtick) {
- var nt = ax.nticks,
- minPx;
- if(!nt) {
- if(ax.type === 'category') {
- minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
- nt = ax._length / minPx;
- }
- else {
- minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
- nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
- }
- }
-
- // add a couple of extra digits for filling in ticks when we
- // have explicit tickvals without tick text
- if(ax.tickmode === 'array') nt *= 100;
-
- axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt);
- // check for a forced minimum dtick
- if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
- ax.dtick = ax._minDtick;
- ax.tick0 = ax.l2r(ax._forceTick0);
- }
- }
-
- // check for missing tick0
- if(!ax.tick0) {
- ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0;
- }
-
- // now figure out rounding of tick values
- autoTickRound(ax);
-
- // now that we've figured out the auto values for formatting
- // in case we're missing some ticktext, we can break out for array ticks
- if(ax.tickmode === 'array') return arrayTicks(ax);
-
- // find the first tick
- ax._tmin = axes.tickFirst(ax);
-
- // check for reversed axis
- var axrev = (rng[1] < rng[0]);
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
+
+ // calculate max number of (auto) ticks to display based on plot size
+ if (ax.tickmode === "auto" || !ax.dtick) {
+ var nt = ax.nticks, minPx;
+ if (!nt) {
+ if (ax.type === "category") {
+ minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
+ nt = ax._length / minPx;
+ } else {
+ minPx = ax._id.charAt(0) === "y" ? 40 : 80;
+ nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
+ }
+ }
+
+ // add a couple of extra digits for filling in ticks when we
+ // have explicit tickvals without tick text
+ if (ax.tickmode === "array") nt *= 100;
+
+ axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt);
+ // check for a forced minimum dtick
+ if (ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
+ ax.dtick = ax._minDtick;
+ ax.tick0 = ax.l2r(ax._forceTick0);
+ }
+ }
+
+ // check for missing tick0
+ if (!ax.tick0) {
+ ax.tick0 = ax.type === "date" ? "2000-01-01" : 0;
+ }
+
+ // now figure out rounding of tick values
+ autoTickRound(ax);
+
+ // now that we've figured out the auto values for formatting
+ // in case we're missing some ticktext, we can break out for array ticks
+ if (ax.tickmode === "array") return arrayTicks(ax);
+
+ // find the first tick
+ ax._tmin = axes.tickFirst(ax);
+
+ // check for reversed axis
+ var axrev = rng[1] < rng[0];
+
+ // return the full set of tick vals
+ var vals = [],
+ // add a tiny bit so we get ticks which may have rounded out
+ endtick = rng[1] * 1.0001 - rng[0] * 0.0001;
+ if (ax.type === "category") {
+ endtick = axrev
+ ? Math.max(-0.5, endtick)
+ : Math.min(ax._categories.length - 0.5, endtick);
+ }
+ for (
+ var x = ax._tmin;
+ axrev ? x >= endtick : x <= endtick;
+ x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)
+ ) {
+ vals.push(x);
- // return the full set of tick vals
- var vals = [],
- // add a tiny bit so we get ticks which may have rounded out
- endtick = rng[1] * 1.0001 - rng[0] * 0.0001;
- if(ax.type === 'category') {
- endtick = (axrev) ? Math.max(-0.5, endtick) :
- Math.min(ax._categories.length - 0.5, endtick);
- }
- for(var x = ax._tmin;
- (axrev) ? (x >= endtick) : (x <= endtick);
- x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) {
- vals.push(x);
-
- // prevent infinite loops
- if(vals.length > 1000) break;
- }
+ // 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];
+ // 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];
- // for showing the rest of a date when the main tick label is only the
- // latter part: ax._prevDateHead holds what we showed most recently.
- // Start with it cleared and mark that we're in calcTicks (ie calculating a
- // whole string of these so we should care what the previous date head was!)
- ax._prevDateHead = '';
- ax._inCalcTicks = true;
+ // for showing the rest of a date when the main tick label is only the
+ // latter part: ax._prevDateHead holds what we showed most recently.
+ // Start with it cleared and mark that we're in calcTicks (ie calculating a
+ // whole string of these so we should care what the previous date head was!)
+ ax._prevDateHead = "";
+ ax._inCalcTicks = true;
- var ticksOut = new Array(vals.length);
- for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]);
+ var ticksOut = new Array(vals.length);
+ for (var i = 0; i < vals.length; i++) {
+ ticksOut[i] = axes.tickText(ax, vals[i]);
+ }
- ax._inCalcTicks = false;
+ ax._inCalcTicks = false;
- return ticksOut;
+ return ticksOut;
};
function arrayTicks(ax) {
- var vals = ax.tickvals,
- text = ax.ticktext,
- ticksOut = new Array(vals.length),
- rng = Lib.simpleMap(ax.range, ax.r2l),
- r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001,
- r1expanded = rng[1] * 1.0001 - rng[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 = [];
-
- // make sure showing ticks doesn't accidentally add new categories
- var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
-
- // array ticks on log axes always show the full number
- // (if no explicit ticktext overrides it)
- if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') {
- ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1);
- }
-
- for(i = 0; i < vals.length; i++) {
- vali = tickVal2l(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 vals = ax.tickvals,
+ text = ax.ticktext,
+ ticksOut = new Array(vals.length),
+ rng = Lib.simpleMap(ax.range, ax.r2l),
+ r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001,
+ r1expanded = rng[1] * 1.0001 - rng[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 = [];
+
+ // make sure showing ticks doesn't accidentally add new categories
+ var tickVal2l = ax.type === "category" ? ax.d2l_noadd : ax.d2l;
+
+ // array ticks on log axes always show the full number
+ // (if no explicit ticktext overrides it)
+ if (ax.type === "log" && String(ax.dtick).charAt(0) !== "L") {
+ ax.dtick = "L" +
+ Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1);
+ }
+
+ for (i = 0; i < vals.length; i++) {
+ vali = tickVal2l(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];
+ 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 * Lib.roundUp(roughDTick / base, roundingSet);
+ return base * Lib.roundUp(roughDTick / base, roundingSet);
}
// autoTicks: calculate best guess at pleasant ticks for this axis
@@ -826,90 +831,78 @@ function roundDTick(roughDTick, base, roundingSet) {
// 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 = Lib.dateTick0(ax.calendar);
- // the criteria below are all based on the rough spacing we calculate
- // being > half of the final unit - so precalculate twice the rough val
- var roughX2 = 2 * roughDTick;
-
- if(roughX2 > ONEAVGYEAR) {
- roughDTick /= ONEAVGYEAR;
- base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
- ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10));
- }
- else if(roughX2 > ONEAVGMONTH) {
- roughDTick /= ONEAVGMONTH;
- ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24);
- }
- else if(roughX2 > ONEDAY) {
- ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays);
- // get week ticks on sunday
- // this will also move the base tick off 2000-01-01 if dtick is
- // 2 or 3 days... but that's a weird enough case that we'll ignore it.
- ax.tick0 = Lib.dateTick0(ax.calendar, true);
- }
- else if(roughX2 > ONEHOUR) {
- ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24);
- }
- else if(roughX2 > ONEMIN) {
- ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60);
- }
- else if(roughX2 > ONESEC) {
- ax.dtick = roundDTick(roughDTick, ONESEC, 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;
- var rng = Lib.simpleMap(ax.range, ax.r2l);
-
- if(roughDTick > 0.7) {
- // only show powers of 10
- ax.dtick = Math.ceil(roughDTick);
- }
- else if(Math.abs(rng[1] - rng[0]) < 1) {
- // span is less than one power of 10
- var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick);
-
- // ticks on a linear scale, labeled fully
- roughDTick = Math.abs(Math.pow(10, rng[1]) -
- Math.pow(10, rng[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;
+ var base;
+
+ if (ax.type === "date") {
+ ax.tick0 = Lib.dateTick0(ax.calendar);
+ // the criteria below are all based on the rough spacing we calculate
+ // being > half of the final unit - so precalculate twice the rough val
+ var roughX2 = 2 * roughDTick;
+
+ if (roughX2 > ONEAVGYEAR) {
+ roughDTick /= ONEAVGYEAR;
+ base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
+ ax.dtick = "M" + 12 * roundDTick(roughDTick, base, roundBase10);
+ } else if (roughX2 > ONEAVGMONTH) {
+ roughDTick /= ONEAVGMONTH;
+ ax.dtick = "M" + roundDTick(roughDTick, 1, roundBase24);
+ } else if (roughX2 > ONEDAY) {
+ ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays);
+ // get week ticks on sunday
+ // this will also move the base tick off 2000-01-01 if dtick is
+ // 2 or 3 days... but that's a weird enough case that we'll ignore it.
+ ax.tick0 = Lib.dateTick0(ax.calendar, true);
+ } else if (roughX2 > ONEHOUR) {
+ ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24);
+ } else if (roughX2 > ONEMIN) {
+ ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60);
+ } else if (roughX2 > ONESEC) {
+ ax.dtick = roundDTick(roughDTick, ONESEC, 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;
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
- // 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);
- }
+ if (roughDTick > 0.7) {
+ // only show powers of 10
+ ax.dtick = Math.ceil(roughDTick);
+ } else if (Math.abs(rng[1] - rng[0]) < 1) {
+ // span is less than one power of 10
+ var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick);
+
+ // ticks on a linear scale, labeled fully
+ roughDTick = Math.abs(Math.pow(10, rng[1]) - Math.pow(10, rng[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
@@ -918,61 +911,67 @@ axes.autoTicks = function(ax, roughDTick) {
// 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;
-
- ax._tickexponent = 0;
- if(!isNumeric(dtick) && typeof dtick !== 'string') {
- dtick = 1;
- }
-
- if(ax.type === 'category') {
- ax._tickround = null;
- }
- if(ax.type === 'date') {
- // If tick0 is unusual, give tickround a bit more information
- // not necessarily *all* the information in tick0 though, if it's really odd
- // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
- // take off a leading minus (year < 0) and i (intercalary month) so length is consistent
- var tick0ms = ax.r2l(ax.tick0),
- tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''),
- tick0len = tick0str.length;
-
- if(String(dtick).charAt(0) === 'M') {
- // any tick0 more specific than a year: alway show the full date
- if(tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd';
- // show the month unless ticks are full multiples of a year
- else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm';
- }
- else if((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd';
- else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M';
- else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S';
- else {
- // tickround is a number of digits of fractional seconds
- // of any two adjacent ticks, at least one will have the maximum fractional digits
- // of all possible ticks - so take the max. length of tick0 and the next one
- var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length;
- ax._tickround = Math.max(tick0len, tick1len) - 20;
- }
- }
- else if(isNumeric(dtick) || dtick.charAt(0) === 'L') {
- // linear or log (except D1, D2)
- var rng = ax.range.map(ax.r2d || Number);
- 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);
-
- var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[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;
- }
- }
+ var dtick = ax.dtick;
+
+ ax._tickexponent = 0;
+ if (!isNumeric(dtick) && typeof dtick !== "string") {
+ dtick = 1;
+ }
+
+ if (ax.type === "category") {
+ ax._tickround = null;
+ }
+ if (ax.type === "date") {
+ // If tick0 is unusual, give tickround a bit more information
+ // not necessarily *all* the information in tick0 though, if it's really odd
+ // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
+ // take off a leading minus (year < 0) and i (intercalary month) so length is consistent
+ var tick0ms = ax.r2l(ax.tick0),
+ tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ""),
+ tick0len = tick0str.length;
+
+ if (String(dtick).charAt(0) === "M") {
+ // any tick0 more specific than a year: alway show the full date
+ if (tick0len > 10 || tick0str.substr(5) !== "01-01") {
+ ax._tickround = "d";
+ } else {
+ // show the month unless ticks are full multiples of a year
+ ax._tickround = (+dtick.substr(1)) % 12 === 0 ? "y" : "m";
+ }
+ } else if (dtick >= ONEDAY && tick0len <= 10 || dtick >= ONEDAY * 15) {
+ ax._tickround = "d";
+ } else if (dtick >= ONEMIN && tick0len <= 16 || dtick >= ONEHOUR) {
+ ax._tickround = "M";
+ } else if (dtick >= ONESEC && tick0len <= 19 || dtick >= ONEMIN) {
+ ax._tickround = "S";
+ } else {
+ // tickround is a number of digits of fractional seconds
+ // of any two adjacent ticks, at least one will have the maximum fractional digits
+ // of all possible ticks - so take the max. length of tick0 and the next one
+ var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, "").length;
+ ax._tickround = Math.max(tick0len, tick1len) - 20;
+ }
+ } else if (isNumeric(dtick) || dtick.charAt(0) === "L") {
+ // linear or log (except D1, D2)
+ var rng = ax.range.map(ax.r2d || Number);
+ 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);
+
+ var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[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 {
// D1 or D2 (log)
- else ax._tickround = null;
+ ax._tickround = null;
+ }
}
// months and years don't have constant millisecond values
@@ -982,98 +981,99 @@ function autoTickRound(ax) {
// numeric ticks always have constant differences, other datetime ticks
// can all be calculated as constant number of milliseconds
axes.tickIncrement = function(x, dtick, axrev, calendar) {
- var axSign = axrev ? -1 : 1;
-
- // includes linear, all dates smaller than month, and pure 10^n in log
- if(isNumeric(dtick)) return x + axSign * dtick;
-
- // everything else is a string, one character plus a number
- var tType = dtick.charAt(0),
- dtSigned = axSign * Number(dtick.substr(1));
-
- // Dates: months (or years - see Lib.incrementMonth)
- if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
-
- // Log scales: Linear, Digits
- else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
-
+ var axSign = axrev ? -1 : 1;
+
+ // includes linear, all dates smaller than month, and pure 10^n in log
+ if (isNumeric(dtick)) return x + axSign * dtick;
+
+ // everything else is a string, one character plus a number
+ var tType = dtick.charAt(0), dtSigned = axSign * Number(dtick.substr(1));
+
+ // Dates: months (or years - see Lib.incrementMonth)
+ if (tType === "M") {
+ return Lib.incrementMonth(x, dtSigned, calendar);
+ } else if (
+ tType === "L" // Log scales: Linear, Digits
+ ) {
+ return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
+ } else if (tType === "D") {
// 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 = Lib.roundUp(Lib.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);
+ var tickset = dtick === "D2" ? roundLog2 : roundLog1,
+ x2 = x + axSign * 0.01,
+ frac = Lib.roundUp(Lib.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 r2l = ax.r2l || Number,
- rng = Lib.simpleMap(ax.range, r2l),
- axrev = rng[1] < rng[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 = rng[0] * 1.0001 - rng[1] * 0.0001,
- dtick = ax.dtick,
- tick0 = r2l(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 = Lib.constrain(tmin, 0, ax._categories.length - 1);
- }
- return tmin;
- }
-
- var tType = dtick.charAt(0),
- dtNum = Number(dtick.substr(1));
-
- // Dates: months (or years)
- if(tType === 'M') {
- var cnt = 0,
- t0 = tick0,
- t1,
- mult,
- newDTick;
-
- // This algorithm should work for *any* nonlinear (but close to linear!)
- // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3.
- while(cnt < 10) {
- t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar);
- if((t1 - r0) * (t0 - r0) <= 0) {
- // t1 and t0 are on opposite sides of r0! we've succeeded!
- if(axrev) return Math.min(t0, t1);
- return Math.max(t0, t1);
- }
- mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0);
- newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum);
- t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar);
- cnt++;
- }
- Lib.error('tickFirst did not converge', ax);
- return t0;
- }
-
+ var r2l = ax.r2l || Number,
+ rng = Lib.simpleMap(ax.range, r2l),
+ axrev = rng[1] < rng[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 = rng[0] * 1.0001 - rng[1] * 0.0001,
+ dtick = ax.dtick,
+ tick0 = r2l(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 = Lib.constrain(tmin, 0, ax._categories.length - 1);
+ }
+ return tmin;
+ }
+
+ var tType = dtick.charAt(0), dtNum = Number(dtick.substr(1));
+
+ // Dates: months (or years)
+ if (tType === "M") {
+ var cnt = 0, t0 = tick0, t1, mult, newDTick;
+
+ // This algorithm should work for *any* nonlinear (but close to linear!)
+ // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3.
+ while (cnt < 10) {
+ t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar);
+ if ((t1 - r0) * (t0 - r0) <= 0) {
+ // t1 and t0 are on opposite sides of r0! we've succeeded!
+ if (axrev) return Math.min(t0, t1);
+ return Math.max(t0, t1);
+ }
+ mult = (r0 - (t0 + t1) / 2) / (t1 - t0);
+ newDTick = tType + (Math.abs(Math.round(mult)) || 1) * dtNum;
+ t0 = axes.tickIncrement(
+ t0,
+ newDTick,
+ mult < 0 ? !axrev : axrev,
+ ax.calendar
+ );
+ cnt++;
+ }
+ Lib.error("tickFirst did not converge", ax);
+ return t0;
+ } else if (tType === "L") {
// 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 = Lib.roundUp(Lib.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);
+ 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 = Lib.roundUp(Lib.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);
+ }
};
// draw the text for one tick.
@@ -1083,301 +1083,305 @@ axes.tickFirst = function(ax) {
// 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,
- i,
- tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
-
- if(arrayMode && Array.isArray(ax.ticktext)) {
- var rng = Lib.simpleMap(ax.range, ax.r2l),
- minDiff = Math.abs(rng[1] - rng[0]) / 10000;
- for(i = 0; i < ax.ticktext.length; i++) {
- if(Math.abs(x - tickVal2l(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;
+ var out = tickTextObj(ax, x),
+ hideexp,
+ arrayMode = ax.tickmode === "array",
+ extraPrecision = hover || arrayMode,
+ i,
+ tickVal2l = ax.type === "category" ? ax.d2l_noadd : ax.d2l;
+
+ if (arrayMode && Array.isArray(ax.ticktext)) {
+ var rng = Lib.simpleMap(ax.range, ax.r2l),
+ minDiff = Math.abs(rng[1] - rng[0]) / 10000;
+ for (i = 0; i < ax.ticktext.length; i++) {
+ if (Math.abs(x - tickVal2l(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._gd._fullLayout.font;
-
- return {
- x: x,
- dx: 0,
- dy: 0,
- text: text || '',
- fontSize: tf.size,
- font: tf.family,
- fontColor: tf.color
- };
+ var tf = ax.tickfont || ax._gd._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 tr = ax._tickround,
- fmt = (hover && ax.hoverformat) || ax.tickformat;
-
- if(extraPrecision) {
- // second or sub-second precision: extra always shows max digits.
- // for other fields, extra precision just adds one field.
- if(isNumeric(tr)) tr = 4;
- else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
- }
-
- var dateStr = Lib.formatDate(out.x, fmt, tr, ax.calendar),
- headStr;
-
- var splitIndex = dateStr.indexOf('\n');
- if(splitIndex !== -1) {
- headStr = dateStr.substr(splitIndex + 1);
- dateStr = dateStr.substr(0, splitIndex);
- }
-
- if(extraPrecision) {
- // if extraPrecision led to trailing zeros, strip them off
- // actually, this can lead to removing even more zeros than
- // in the original rounding, but that's fine because in these
- // contexts uniformity is not so important (if there's even
- // anything to be uniform with!)
-
- // can we remove the whole time part?
- if(dateStr === '00:00:00' || dateStr === '00:00') {
- dateStr = headStr;
- headStr = '';
- }
- else if(dateStr.length === 8) {
- // strip off seconds if they're zero (zero fractional seconds
- // are already omitted)
- // but we never remove minutes and leave just hours
- dateStr = dateStr.replace(/:00$/, '');
- }
- }
-
- if(headStr) {
- if(hover) {
- // hover puts it all on one line, so headPart works best up front
- // except for year headPart: turn this into "Jan 1, 2000" etc.
- if(tr === 'd') dateStr += ', ' + headStr;
- else dateStr = headStr + (dateStr ? ', ' + dateStr : '');
- }
- else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) {
- dateStr += '
' + headStr;
- ax._prevDateHead = headStr;
- }
- }
-
- out.text = dateStr;
+ var tr = ax._tickround, fmt = hover && ax.hoverformat || ax.tickformat;
+
+ if (extraPrecision) {
+ // second or sub-second precision: extra always shows max digits.
+ // for other fields, extra precision just adds one field.
+ if (isNumeric(tr)) tr = 4;
+ else tr = ({ y: "m", m: "d", d: "M", M: "S", S: 4 })[tr];
+ }
+
+ var dateStr = Lib.formatDate(out.x, fmt, tr, ax.calendar), headStr;
+
+ var splitIndex = dateStr.indexOf("\n");
+ if (splitIndex !== -1) {
+ headStr = dateStr.substr(splitIndex + 1);
+ dateStr = dateStr.substr(0, splitIndex);
+ }
+
+ if (extraPrecision) {
+ // if extraPrecision led to trailing zeros, strip them off
+ // actually, this can lead to removing even more zeros than
+ // in the original rounding, but that's fine because in these
+ // contexts uniformity is not so important (if there's even
+ // anything to be uniform with!)
+ // can we remove the whole time part?
+ if (dateStr === "00:00:00" || dateStr === "00:00") {
+ dateStr = headStr;
+ headStr = "";
+ } else if (dateStr.length === 8) {
+ // strip off seconds if they're zero (zero fractional seconds
+ // are already omitted)
+ // but we never remove minutes and leave just hours
+ dateStr = dateStr.replace(/:00$/, "");
+ }
+ }
+
+ if (headStr) {
+ if (hover) {
+ // hover puts it all on one line, so headPart works best up front
+ // except for year headPart: turn this into "Jan 1, 2000" etc.
+ if (tr === "d") dateStr += ", " + headStr;
+ else dateStr = headStr + (dateStr ? ", " + dateStr : "");
+ } else if (!ax._inCalcTicks || headStr !== ax._prevDateHead) {
+ dateStr += "
" + headStr;
+ ax._prevDateHead = headStr;
+ }
+ }
+
+ out.text = dateStr;
}
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') && (Lib.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, Lib.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);
- }
- }
- }
+ 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" && Lib.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, Lib.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);
+ 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);
+ // 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'];
+var SIPREFIXES = ["f", "p", "n", "\u03BC", "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,
- separatethousands = ax.separatethousands;
-
- // 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.map(ax.r2d) : [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 = Lib.numSeparate(v, ax._gd._fullLayout.separators, separatethousands);
- }
-
- // 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;
+ // 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,
+ separatethousands = ax.separatethousands;
+
+ // 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.map(ax.r2d) : [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 = Lib.numSeparate(v, ax._gd._fullLayout.separators, separatethousands);
+ }
+
+ // 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 += "\xD710" + 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;
}
-
axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
// getSubplots - extract all combinations of axes we need to make plots for
@@ -1387,153 +1391,155 @@ axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
// 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;
+ var subplots = [];
+ var i, j, sp;
- // look for subplots in the data
- var data = gd._fullData || gd.data || [];
+ // look for subplots in the data
+ var data = gd._fullData || gd.data || [];
- for(i = 0; i < data.length; i++) {
- var trace = data[i];
+ for (i = 0; i < data.length; i++) {
+ var trace = data[i];
- if(trace.visible === false || trace.visible === 'legendonly' ||
- !(Registry.traceIs(trace, 'cartesian') || Registry.traceIs(trace, 'gl2d'))
- ) continue;
+ if (
+ trace.visible === false ||
+ trace.visible === "legendonly" ||
+ !(Registry.traceIs(trace, "cartesian") ||
+ Registry.traceIs(trace, "gl2d"))
+ ) {
+ continue;
+ }
- var xId = trace.xaxis || 'x',
- yId = trace.yaxis || 'y';
- sp = xId + yId;
+ var xId = trace.xaxis || "x", yId = trace.yaxis || "y";
+ sp = xId + yId;
- if(subplots.indexOf(sp) === -1) subplots.push(sp);
- }
+ 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);
+ // 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;
- }
+ 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);
+ 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);
- // look if ax2 is already represented in the data
- var foundAx2 = false;
- for(j = 0; j < subplots.length; j++) {
- if(hasAx2(subplots[j], ax2)) {
- foundAx2 = true;
- break;
- }
- }
+ // look if ax2 is already represented in the data
+ var foundAx2 = false;
+ for (j = 0; j < subplots.length; j++) {
+ if (hasAx2(subplots[j], ax2)) {
+ foundAx2 = true;
+ break;
+ }
+ }
- // ignore free axes that already represented in the data
- if(ax2.anchor === 'free' && foundAx2) continue;
+ // ignore free axes that already represented in the data
+ if (ax2.anchor === "free" && foundAx2) continue;
- // ignore anchor-less axes
- if(!ax3) continue;
+ // ignore anchor-less axes
+ if (!ax3) continue;
- sp = (ax2Letter === 'x') ?
- ax2._id + ax3._id :
- ax3._id + ax2._id;
+ sp = ax2Letter === "x" ? ax2._id + ax3._id : ax3._id + ax2._id;
- if(subplots.indexOf(sp) === -1) subplots.push(sp);
- }
+ if (subplots.indexOf(sp) === -1) subplots.push(sp);
+ }
- // filter invalid subplots
- var spMatch = axes.subplotMatch,
- allSubplots = [];
+ // 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);
- }
+ 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);
+ // 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);
- }
+ if (aMatch[1] === bMatch[1]) {
+ return +(aMatch[2] || 1) - (bMatch[2] || 1);
+ }
- return +(aMatch[1]||0) - (bMatch[1]||0);
- });
+ return +(aMatch[1] || 0) - (bMatch[1] || 0);
+ });
- if(ax) return axes.findSubplotsWithAxis(allSubplots, ax);
- return allSubplots;
+ 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 = [];
+ 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);
- }
+ for (var i = 0; i < subplots.length; i++) {
+ var sp = subplots[i];
+ if (axMatch.test(sp)) subplotsWithAxis.push(sp);
+ }
- return subplotsWithAxis;
+ return subplotsWithAxis;
};
// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings
axes.makeClipPaths = function(gd) {
- var fullLayout = gd._fullLayout,
- defs = fullLayout._defs,
- fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''},
- fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''},
- xaList = axes.list(gd, 'x', true),
- yaList = axes.list(gd, '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' + fullLayout._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
- });
+ var fullLayout = gd._fullLayout,
+ defs = fullLayout._defs,
+ fullWidth = { _offset: 0, _length: fullLayout.width, _id: "" },
+ fullHeight = { _offset: 0, _length: fullLayout.height, _id: "" },
+ xaList = axes.list(gd, "x", true),
+ yaList = axes.list(gd, "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" + fullLayout._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,
@@ -1542,663 +1548,737 @@ axes.makeClipPaths = function(gd) {
// ax._rl (stored linearized range for use by zoom/pan)
// or can pass in an axis object directly
axes.doTicks = function(gd, axid, skipTitle) {
- var fullLayout = gd._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(gd, axid);
-
- if(axid === 'redraw') {
- fullLayout._paper.selectAll('g.subplot').each(function(subplot) {
- var plotinfo = fullLayout._plots[subplot],
- xa = plotinfo.xaxis,
- ya = plotinfo.yaxis;
-
- 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 Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) {
- return function() {
- if(!ax._id) return;
- var axDone = axes.doTicks(gd, ax._id);
- if(axid === 'redraw') {
- ax._r = ax.range.slice();
- ax._rl = Lib.simpleMap(ax._r, ax.r2l);
- }
- 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';
- }
- }
-
- // 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].join('_'); },
- tcls = axid + 'tick',
- gcls = axid + 'grid',
- zcls = axid + 'zl',
- pad = (ax.linewidth || 1) / 2,
- labelStandoff =
- (ax.ticks === 'outside' ? ax.ticklen : 1) + (ax.linewidth || 0),
- labelShift = 0,
- gridWidth = Drawing.crispRound(gd, ax.gridwidth, 1),
- zeroLineWidth = Drawing.crispRound(gd, ax.zerolinewidth, gridWidth),
- tickWidth = Drawing.crispRound(gd, ax.tickwidth, 1),
- sides, transfn, tickpathfn,
- i;
-
- if(ax._counterangle && ax.ticks === 'outside') {
- var caRad = ax._counterangle * Math.PI / 180;
- labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0);
- labelShift = ax.ticklen * Math.sin(caRad);
- }
-
- // positioning arguments for x vs y axes
- if(axletter === 'x') {
- sides = ['bottom', 'top'];
- transfn = function(d) {
- return 'translate(' + ax.l2p(d.x) + ',0)';
- };
- tickpathfn = function(shift, len) {
- if(ax._counterangle) {
- var caRad = ax._counterangle * Math.PI / 180;
- return 'M0,' + shift + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len);
- }
- else return 'M0,' + shift + 'v' + len;
- };
- }
- else if(axletter === 'y') {
- sides = ['left', 'right'];
- transfn = function(d) {
- return 'translate(0,' + ax.l2p(d.x) + ')';
- };
- tickpathfn = function(shift, len) {
- if(ax._counterangle) {
- var caRad = ax._counterangle * Math.PI / 180;
- return 'M' + shift + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len);
+ var fullLayout = gd._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(gd, axid);
+
+ if (axid === "redraw") {
+ fullLayout._paper.selectAll("g.subplot").each(function(subplot) {
+ var plotinfo = fullLayout._plots[subplot],
+ xa = plotinfo.xaxis,
+ ya = plotinfo.yaxis;
+
+ 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 Lib.syncOrAsync(
+ axes.list(gd, "", true).map(function(ax) {
+ return function() {
+ if (!ax._id) return;
+ var axDone = axes.doTicks(gd, ax._id);
+ if (axid === "redraw") {
+ ax._r = ax.range.slice();
+ ax._rl = Lib.simpleMap(ax._r, ax.r2l);
}
- else return 'M' + shift + ',0h' + len;
- };
- }
- else {
- Lib.warn('Unrecognized doTicks axis:', axid);
- return;
- }
- var axside = ax.side || sides[0],
+ 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";
+ }
+ }
+
+ // 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].join("_");
+ },
+ tcls = axid + "tick",
+ gcls = axid + "grid",
+ zcls = axid + "zl",
+ pad = (ax.linewidth || 1) / 2,
+ labelStandoff = (ax.ticks === "outside" ? ax.ticklen : 1) +
+ (ax.linewidth || 0),
+ labelShift = 0,
+ gridWidth = Drawing.crispRound(gd, ax.gridwidth, 1),
+ zeroLineWidth = Drawing.crispRound(gd, ax.zerolinewidth, gridWidth),
+ tickWidth = Drawing.crispRound(gd, ax.tickwidth, 1),
+ sides,
+ transfn,
+ tickpathfn,
+ i;
+
+ if (ax._counterangle && ax.ticks === "outside") {
+ var caRad = ax._counterangle * Math.PI / 180;
+ labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0);
+ labelShift = ax.ticklen * Math.sin(caRad);
+ }
+
+ // positioning arguments for x vs y axes
+ if (axletter === "x") {
+ sides = ["bottom", "top"];
+ transfn = function(d) {
+ return "translate(" + ax.l2p(d.x) + ",0)";
+ };
+ tickpathfn = function(shift, len) {
+ if (ax._counterangle) {
+ var caRad = ax._counterangle * Math.PI / 180;
+ return "M0," +
+ shift +
+ "l" +
+ Math.sin(caRad) * len +
+ "," +
+ Math.cos(caRad) * len;
+ } else {
+ return "M0," + shift + "v" + len;
+ }
+ };
+ } else if (axletter === "y") {
+ sides = ["left", "right"];
+ transfn = function(d) {
+ return "translate(0," + ax.l2p(d.x) + ")";
+ };
+ tickpathfn = function(shift, len) {
+ if (ax._counterangle) {
+ var caRad = ax._counterangle * Math.PI / 180;
+ return "M" +
+ shift +
+ ",0l" +
+ Math.cos(caRad) * len +
+ "," +
+ (-Math.sin(caRad)) * len;
+ } else {
+ return "M" + shift + ",0h" + len;
+ }
+ };
+ } else {
+ Lib.warn("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 && p < ax._length - 1);
- }
- var valsClipped = vals.filter(clipEnds);
-
- function drawTicks(container, tickpath) {
- var ticks = container.selectAll('path.' + tcls)
- .data(ax.ticks === 'inside' ? valsClipped : vals, datafn);
- if(tickpath && ax.ticks) {
- ticks.enter().append('path').classed(tcls, 1).classed('ticks', 1)
- .classed('crisp', 1)
- .call(Color.stroke, ax.tickcolor)
- .style('stroke-width', tickWidth + 'px')
- .attr('d', tickpath);
- ticks.attr('transform', transfn);
- ticks.exit().remove();
- }
- else ticks.remove();
- }
-
- function drawLabels(container, position) {
- // tick labels - for now just the main labels.
- // TODO: mirror labels, esp for subplots
- var tickLabels = container.selectAll('g.' + tcls).data(vals, datafn);
- if(!ax.showticklabels || !isNumeric(position)) {
- tickLabels.remove();
- drawAxTitle(axid);
- return;
- }
-
- var labelx, labely, labelanchor, labelpos0, flipit;
- if(axletter === 'x') {
- flipit = (axside === 'bottom') ? 1 : -1;
- labelx = function(d) { return d.dx + labelShift * flipit; };
- labelpos0 = position + (labelStandoff + pad) * flipit;
- labely = function(d) {
- return d.dy + labelpos0 + d.fontSize *
- ((axside === 'bottom') ? 1 : -0.5);
- };
- labelanchor = function(angle) {
- if(!isNumeric(angle) || angle === 0 || angle === 180) {
- return 'middle';
- }
- return (angle * flipit < 0) ? 'end' : 'start';
- };
- }
- else {
- flipit = (axside === 'right') ? 1 : -1;
- labely = function(d) { return d.dy + d.fontSize / 2 - labelShift * flipit; };
- labelx = function(d) {
- return d.dx + position + (labelStandoff + pad +
- ((Math.abs(ax.tickangle) === 90) ? d.fontSize / 2 : 0)) * flipit;
- };
- labelanchor = function(angle) {
- if(isNumeric(angle) && Math.abs(angle) === 90) {
- return 'middle';
- }
- return axside === 'right' ? 'start' : 'end';
- };
- }
- var maxFontSize = 0,
- autoangle = 0,
- labelsReady = [];
- tickLabels.enter().append('g').classed(tcls, 1)
- .append('text')
- // only so tex has predictable alignment that we can
- // alter later
- .attr('text-anchor', 'middle')
- .each(function(d) {
- var thisLabel = d3.select(this),
- newPromise = gd._promises.length;
- thisLabel
- .call(Drawing.setPosition, labelx(d), labely(d))
- .call(Drawing.font, d.font, d.fontSize, d.fontColor)
- .text(d.text)
- .call(svgTextUtils.convertToTspans);
- newPromise = gd._promises[newPromise];
- if(newPromise) {
- // if we have an async label, we'll deal with that
- // all here so take it out of gd._promises and
- // instead position the label and promise this in
- // labelsReady
- labelsReady.push(gd._promises.pop().then(function() {
- positionLabels(thisLabel, ax.tickangle);
- }));
- }
- else {
- // sync label: just position it now.
- positionLabels(thisLabel, ax.tickangle);
- }
- });
- tickLabels.exit().remove();
+ 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 && p < ax._length - 1;
+ }
+ var valsClipped = vals.filter(clipEnds);
+
+ function drawTicks(container, tickpath) {
+ var ticks = container
+ .selectAll("path." + tcls)
+ .data(ax.ticks === "inside" ? valsClipped : vals, datafn);
+ if (tickpath && ax.ticks) {
+ ticks
+ .enter()
+ .append("path")
+ .classed(tcls, 1)
+ .classed("ticks", 1)
+ .classed("crisp", 1)
+ .call(Color.stroke, ax.tickcolor)
+ .style("stroke-width", tickWidth + "px")
+ .attr("d", tickpath);
+ ticks.attr("transform", transfn);
+ ticks.exit().remove();
+ } else {
+ ticks.remove();
+ }
+ }
+
+ function drawLabels(container, position) {
+ // tick labels - for now just the main labels.
+ // TODO: mirror labels, esp for subplots
+ var tickLabels = container.selectAll("g." + tcls).data(vals, datafn);
+ if (!ax.showticklabels || !isNumeric(position)) {
+ tickLabels.remove();
+ drawAxTitle(axid);
+ return;
+ }
+
+ var labelx, labely, labelanchor, labelpos0, flipit;
+ if (axletter === "x") {
+ flipit = axside === "bottom" ? 1 : -1;
+ labelx = function(d) {
+ return d.dx + labelShift * flipit;
+ };
+ labelpos0 = position + (labelStandoff + pad) * flipit;
+ labely = function(d) {
+ return d.dy + labelpos0 + d.fontSize * (axside === "bottom" ? 1 : -0.5);
+ };
+ labelanchor = function(angle) {
+ if (!isNumeric(angle) || angle === 0 || angle === 180) {
+ return "middle";
+ }
+ return angle * flipit < 0 ? "end" : "start";
+ };
+ } else {
+ flipit = axside === "right" ? 1 : -1;
+ labely = function(d) {
+ return d.dy + d.fontSize / 2 - labelShift * flipit;
+ };
+ labelx = function(d) {
+ return d.dx +
+ position +
+ (labelStandoff +
+ pad +
+ (Math.abs(ax.tickangle) === 90 ? d.fontSize / 2 : 0)) *
+ flipit;
+ };
+ labelanchor = function(angle) {
+ if (isNumeric(angle) && Math.abs(angle) === 90) {
+ return "middle";
+ }
+ return axside === "right" ? "start" : "end";
+ };
+ }
+ var maxFontSize = 0, autoangle = 0, labelsReady = [];
+ tickLabels
+ .enter()
+ .append("g")
+ .classed(tcls, 1)
+ .append("text")
+ .attr("text-anchor", "middle")
+ .each(function(d) {
+ var thisLabel = d3.select(this), newPromise = gd._promises.length;
+ thisLabel
+ .call(Drawing.setPosition, labelx(d), labely(d))
+ .call(Drawing.font, d.font, d.fontSize, d.fontColor)
+ .text(d.text)
+ .call(svgTextUtils.convertToTspans);
+ newPromise = gd._promises[newPromise];
+ if (newPromise) {
+ // if we have an async label, we'll deal with that
+ // all here so take it out of gd._promises and
+ // instead position the label and promise this in
+ // labelsReady
+ labelsReady.push(
+ gd._promises.pop().then(function() {
+ positionLabels(thisLabel, ax.tickangle);
+ })
+ );
+ } else {
+ // sync label: just position it now.
+ positionLabels(thisLabel, ax.tickangle);
+ }
+ });
+ tickLabels.exit().remove();
+
+ tickLabels.each(function(d) {
+ maxFontSize = Math.max(maxFontSize, d.fontSize);
+ });
+ function positionLabels(s, angle) {
+ s.each(function(d) {
+ var anchor = labelanchor(angle);
+ var thisLabel = d3.select(this),
+ mathjaxGroup = thisLabel.select(".text-math-group"),
+ transform = transfn(d) +
+ (isNumeric(angle) && +angle !== 0
+ ? " rotate(" +
+ angle +
+ "," +
+ labelx(d) +
+ "," +
+ (labely(d) - d.fontSize / 2) +
+ ")"
+ : "");
+ if (mathjaxGroup.empty()) {
+ var txt = thisLabel
+ .select("text")
+ .attr({ transform: transform, "text-anchor": anchor });
+
+ if (!txt.empty()) {
+ txt
+ .selectAll("tspan.line")
+ .attr({ x: txt.attr("x"), y: txt.attr("y") });
+ }
+ } else {
+ var mjShift = Drawing.bBox(mathjaxGroup.node()).width *
+ ({ end: -0.5, start: 0.5 })[anchor];
+ mathjaxGroup.attr(
+ "transform",
+ transform + (mjShift ? "translate(" + mjShift + ",0)" : "")
+ );
+ }
+ });
+ }
+
+ // make sure all labels are correctly positioned at their base angle
+ // the positionLabels call above is only for newly drawn labels.
+ // do this without waiting, using the last calculated angle to
+ // minimize flicker, then do it again when we know all labels are
+ // there, putting back the prescribed angle to check for overlaps.
+ positionLabels(tickLabels, ax._lastangle || ax.tickangle);
+
+ function allLabelsReady() {
+ return labelsReady.length && Promise.all(labelsReady);
+ }
+
+ function fixLabelOverlaps() {
+ positionLabels(tickLabels, ax.tickangle);
+
+ // check for auto-angling if x labels overlap
+ // don't auto-angle at all for log axes with
+ // base and digit format
+ if (
+ axletter === "x" &&
+ !isNumeric(ax.tickangle) &&
+ (ax.type !== "log" || String(ax.dtick).charAt(0) !== "D")
+ ) {
+ var lbbArray = [];
tickLabels.each(function(d) {
- maxFontSize = Math.max(maxFontSize, d.fontSize);
+ var s = d3.select(this),
+ thisLabel = s.select(".text-math-group"),
+ x = ax.l2p(d.x);
+ if (thisLabel.empty()) thisLabel = s.select("text");
+
+ var bb = Drawing.bBox(thisLabel.node());
+
+ lbbArray.push({
+ // ignore about y, just deal with x overlaps
+ top: 0,
+ bottom: 10,
+ height: 10,
+ left: x - bb.width / 2,
+ // impose a 2px gap
+ right: (
+ x + bb.width / 2 + 2
+ ),
+ width: bb.width + 2
+ });
});
-
- function positionLabels(s, angle) {
- s.each(function(d) {
- var anchor = labelanchor(angle);
- var thisLabel = d3.select(this),
- mathjaxGroup = thisLabel.select('.text-math-group'),
- transform = transfn(d) +
- ((isNumeric(angle) && +angle !== 0) ?
- (' rotate(' + angle + ',' + labelx(d) + ',' +
- (labely(d) - d.fontSize / 2) + ')') :
- '');
- if(mathjaxGroup.empty()) {
- var txt = thisLabel.select('text').attr({
- transform: transform,
- 'text-anchor': anchor
- });
-
- if(!txt.empty()) {
- txt.selectAll('tspan.line').attr({
- x: txt.attr('x'),
- y: txt.attr('y')
- });
- }
- }
- else {
- var mjShift =
- Drawing.bBox(mathjaxGroup.node()).width *
- {end: -0.5, start: 0.5}[anchor];
- mathjaxGroup.attr('transform', transform +
- (mjShift ? 'translate(' + mjShift + ',0)' : ''));
- }
- });
- }
-
- // make sure all labels are correctly positioned at their base angle
- // the positionLabels call above is only for newly drawn labels.
- // do this without waiting, using the last calculated angle to
- // minimize flicker, then do it again when we know all labels are
- // there, putting back the prescribed angle to check for overlaps.
- positionLabels(tickLabels, ax._lastangle || ax.tickangle);
-
- function allLabelsReady() {
- return labelsReady.length && Promise.all(labelsReady);
- }
-
- function fixLabelOverlaps() {
- positionLabels(tickLabels, ax.tickangle);
-
- // check for auto-angling if x labels overlap
- // don't auto-angle at all for log axes with
- // base and digit format
- if(axletter === 'x' && !isNumeric(ax.tickangle) &&
- (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')) {
- var lbbArray = [];
- tickLabels.each(function(d) {
- var s = d3.select(this),
- thisLabel = s.select('.text-math-group'),
- x = ax.l2p(d.x);
- if(thisLabel.empty()) thisLabel = s.select('text');
-
- var bb = Drawing.bBox(thisLabel.node());
-
- lbbArray.push({
- // ignore about y, just deal with x overlaps
- top: 0,
- bottom: 10,
- height: 10,
- left: x - bb.width / 2,
- // impose a 2px gap
- right: x + bb.width / 2 + 2,
- width: bb.width + 2
- });
- });
- for(i = 0; i < lbbArray.length - 1; i++) {
- if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) {
- // any overlap at all - set 30 degrees
- autoangle = 30;
- break;
- }
- }
- if(autoangle) {
- var tickspacing = Math.abs(
- (vals[vals.length - 1].x - vals[0].x) * ax._m
- ) / (vals.length - 1);
- if(tickspacing < maxFontSize * 2.5) {
- autoangle = 90;
- }
- positionLabels(tickLabels, autoangle);
- }
- ax._lastangle = autoangle;
+ for (i = 0; i < lbbArray.length - 1; i++) {
+ if (Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) {
+ // any overlap at all - set 30 degrees
+ autoangle = 30;
+ break;
+ }
+ }
+ if (autoangle) {
+ var tickspacing = Math.abs(
+ (vals[vals.length - 1].x - vals[0].x) * ax._m
+ ) /
+ (vals.length - 1);
+ if (tickspacing < maxFontSize * 2.5) {
+ autoangle = 90;
+ }
+ positionLabels(tickLabels, autoangle);
+ }
+ ax._lastangle = autoangle;
+ }
+
+ // update the axis title
+ // (so it can move out of the way if needed)
+ // TODO: separate out scoot so we don't need to do
+ // a full redraw of the title (mostly relevant for MathJax)
+ drawAxTitle(axid);
+ return axid + " done";
+ }
+
+ function calcBoundingBox() {
+ ax._boundingBox = container.node().getBoundingClientRect();
+ }
+
+ var done = Lib.syncOrAsync([
+ allLabelsReady,
+ fixLabelOverlaps,
+ calcBoundingBox
+ ]);
+ if (done && done.then) gd._promises.push(done);
+ return done;
+ }
+
+ function drawAxTitle(axid) {
+ if (skipTitle) return;
+
+ // now this only applies to regular cartesian axes; colorbars and
+ // others ALWAYS call doTicks with skipTitle=true so they can
+ // configure their own titles.
+ var ax = axisIds.getFromId(gd, axid),
+ avoidSelection = d3.select(gd).selectAll("g." + axid + "tick"),
+ avoid = { selection: avoidSelection, side: ax.side },
+ axLetter = axid.charAt(0),
+ gs = gd._fullLayout._size,
+ offsetBase = 1.5,
+ fontSize = ax.titlefont.size,
+ transform,
+ counterAxis,
+ x,
+ y;
+ if (avoidSelection.size()) {
+ var translation = Drawing.getTranslate(avoidSelection.node().parentNode);
+ avoid.offsetLeft = translation.x;
+ avoid.offsetTop = translation.y;
+ }
+
+ if (axLetter === "x") {
+ counterAxis = ax.anchor === "free"
+ ? { _offset: gs.t + (1 - (ax.position || 0)) * gs.h, _length: 0 }
+ : axisIds.getFromId(gd, ax.anchor);
+
+ x = ax._offset + ax._length / 2;
+ y = counterAxis._offset +
+ (ax.side === "top"
+ ? -10 - fontSize * (offsetBase + (ax.showticklabels ? 1 : 0))
+ : counterAxis._length +
+ 10 +
+ fontSize * (offsetBase + (ax.showticklabels ? 1.5 : 0.5)));
+
+ if (ax.rangeslider && ax.rangeslider.visible && ax._boundingBox) {
+ y += (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) *
+ ax.rangeslider.thickness +
+ ax._boundingBox.height;
+ }
+
+ if (!avoid.side) avoid.side = "bottom";
+ } else {
+ counterAxis = ax.anchor === "free"
+ ? { _offset: gs.l + (ax.position || 0) * gs.w, _length: 0 }
+ : axisIds.getFromId(gd, ax.anchor);
+
+ y = ax._offset + ax._length / 2;
+ x = counterAxis._offset +
+ (ax.side === "right"
+ ? counterAxis._length +
+ 10 +
+ fontSize * (offsetBase + (ax.showticklabels ? 1 : 0.5))
+ : -10 - fontSize * (offsetBase + (ax.showticklabels ? 0.5 : 0)));
+
+ transform = { rotate: "-90", offset: 0 };
+ if (!avoid.side) avoid.side = "left";
+ }
+
+ Titles.draw(gd, axid + "title", {
+ propContainer: ax,
+ propName: ax._name + ".title",
+ dfltName: axLetter.toUpperCase() + " axis",
+ avoid: avoid,
+ transform: transform,
+ attributes: { x: x, y: y, "text-anchor": "middle" }
+ });
+ }
+
+ function traceHasBarsOrFill(trace, subplot) {
+ if (trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) {
+ return false;
+ }
+ if (
+ Registry.traceIs(trace, "bar") &&
+ trace.orientation === ({ x: "h", y: "v" })[axletter]
+ ) {
+ return true;
+ }
+ return trace.fill && trace.fill.charAt(trace.fill.length - 1) === axletter;
+ }
+
+ function drawGrid(plotinfo, counteraxis, subplot) {
+ var gridcontainer = plotinfo.gridlayer,
+ zlcontainer = plotinfo.zerolinelayer,
+ gridvals = plotinfo["hidegrid" + axletter] ? [] : valsClipped,
+ gridpath = ax._gridpath ||
+ "M0,0" + (axletter === "x" ? "v" : "h") + counteraxis._length,
+ grid = gridcontainer
+ .selectAll("path." + gcls)
+ .data(ax.showgrid === false ? [] : gridvals, datafn);
+ grid
+ .enter()
+ .append("path")
+ .classed(gcls, 1)
+ .classed("crisp", 1)
+ .attr("d", gridpath)
+ .each(function(d) {
+ if (
+ ax.zeroline &&
+ (ax.type === "linear" || ax.type === "-") &&
+ Math.abs(d.x) < ax.dtick / 100
+ ) {
+ d3.select(this).remove();
+ }
+ });
+ grid
+ .attr("transform", transfn)
+ .call(Color.stroke, ax.gridcolor || "#ddd")
+ .style("stroke-width", gridWidth + "px");
+ grid.exit().remove();
+
+ // zero line
+ if (zlcontainer) {
+ var hasBarsOrFill = false;
+ for (var i = 0; i < gd._fullData.length; i++) {
+ if (traceHasBarsOrFill(gd._fullData[i], subplot)) {
+ hasBarsOrFill = true;
+ break;
+ }
+ }
+ var rng = Lib.simpleMap(ax.range, ax.r2l),
+ showZl = rng[0] * rng[1] <= 0 &&
+ ax.zeroline &&
+ (ax.type === "linear" || ax.type === "-") &&
+ gridvals.length &&
+ (hasBarsOrFill || clipEnds({ x: 0 }) || !ax.showline);
+ var zl = zlcontainer
+ .selectAll("path." + zcls)
+ .data(showZl ? [{ x: 0 }] : []);
+ zl
+ .enter()
+ .append("path")
+ .classed(zcls, 1)
+ .classed("zl", 1)
+ .classed("crisp", 1)
+ .attr("d", gridpath);
+ zl
+ .attr("transform", transfn)
+ .call(Color.stroke, ax.zerolinecolor || Color.defaultLine)
+ .style("stroke-width", zeroLineWidth + "px");
+ zl.exit().remove();
+ }
+ }
+
+ if (independent) {
+ drawTicks(
+ ax._axislayer,
+ tickpathfn(ax._pos + pad * ticksign[2], ticksign[2] * ax.ticklen)
+ );
+ if (ax._counteraxis) {
+ var fictionalPlotinfo = {
+ gridlayer: ax._gridlayer,
+ zerolinelayer: ax._zerolinelayer
+ };
+ drawGrid(fictionalPlotinfo, ax._counteraxis);
+ }
+ return drawLabels(ax._axislayer, ax._pos);
+ } else {
+ var alldone = axes
+ .getSubplots(gd, ax)
+ .map(function(subplot) {
+ var plotinfo = fullLayout._plots[subplot];
+
+ if (!fullLayout._has("cartesian")) return;
+
+ var container = plotinfo[axletter + "axislayer"],
+ // [bottom or left, top or right, free, main]
+ linepositions = ax._linepositions[subplot] || [],
+ counteraxis = plotinfo[counterLetter + "axis"],
+ mainSubplot = counteraxis._id === ax.anchor,
+ ticksides = [false, false, false],
+ tickpath = "";
+
+ // ticks
+ if (ax.mirror === "allticks") {
+ ticksides = [true, true, false];
+ } else if (mainSubplot) {
+ if (ax.mirror === "ticks") ticksides = [true, true, false];
+ else ticksides[sides.indexOf(axside)] = true;
+ }
+ if (ax.mirrors) {
+ for (i = 0; i < 2; i++) {
+ var thisMirror = ax.mirrors[counteraxis._id + sides[i]];
+ if (thisMirror === "ticks" || thisMirror === "labels") {
+ ticksides[i] = true;
}
-
- // update the axis title
- // (so it can move out of the way if needed)
- // TODO: separate out scoot so we don't need to do
- // a full redraw of the title (mostly relevant for MathJax)
- drawAxTitle(axid);
- return axid + ' done';
+ }
}
- function calcBoundingBox() {
- ax._boundingBox = container.node().getBoundingClientRect();
- }
+ // free axis ticks
+ if (linepositions[2] !== undefined) ticksides[2] = true;
- var done = Lib.syncOrAsync([
- allLabelsReady,
- fixLabelOverlaps,
- calcBoundingBox
- ]);
- if(done && done.then) gd._promises.push(done);
- return done;
- }
-
- function drawAxTitle(axid) {
- if(skipTitle) return;
-
- // now this only applies to regular cartesian axes; colorbars and
- // others ALWAYS call doTicks with skipTitle=true so they can
- // configure their own titles.
- var ax = axisIds.getFromId(gd, axid),
- avoidSelection = d3.select(gd).selectAll('g.' + axid + 'tick'),
- avoid = {
- selection: avoidSelection,
- side: ax.side
- },
- axLetter = axid.charAt(0),
- gs = gd._fullLayout._size,
- offsetBase = 1.5,
- fontSize = ax.titlefont.size,
- transform,
- counterAxis,
- x,
- y;
- if(avoidSelection.size()) {
- var translation = Drawing.getTranslate(avoidSelection.node().parentNode);
- avoid.offsetLeft = translation.x;
- avoid.offsetTop = translation.y;
- }
-
- if(axLetter === 'x') {
- counterAxis = (ax.anchor === 'free') ?
- {_offset: gs.t + (1 - (ax.position || 0)) * gs.h, _length: 0} :
- axisIds.getFromId(gd, ax.anchor);
-
- x = ax._offset + ax._length / 2;
- y = counterAxis._offset + ((ax.side === 'top') ?
- -10 - fontSize * (offsetBase + (ax.showticklabels ? 1 : 0)) :
- counterAxis._length + 10 +
- fontSize * (offsetBase + (ax.showticklabels ? 1.5 : 0.5)));
-
- if(ax.rangeslider && ax.rangeslider.visible && ax._boundingBox) {
- y += (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) *
- ax.rangeslider.thickness + ax._boundingBox.height;
- }
-
- if(!avoid.side) avoid.side = 'bottom';
- }
- else {
- counterAxis = (ax.anchor === 'free') ?
- {_offset: gs.l + (ax.position || 0) * gs.w, _length: 0} :
- axisIds.getFromId(gd, ax.anchor);
-
- y = ax._offset + ax._length / 2;
- x = counterAxis._offset + ((ax.side === 'right') ?
- counterAxis._length + 10 +
- fontSize * (offsetBase + (ax.showticklabels ? 1 : 0.5)) :
- -10 - fontSize * (offsetBase + (ax.showticklabels ? 0.5 : 0)));
-
- transform = {rotate: '-90', offset: 0};
- if(!avoid.side) avoid.side = 'left';
- }
-
- Titles.draw(gd, axid + 'title', {
- propContainer: ax,
- propName: ax._name + '.title',
- dfltName: axLetter.toUpperCase() + ' axis',
- avoid: avoid,
- transform: transform,
- attributes: {x: x, y: y, 'text-anchor': 'middle'}
+ ticksides.forEach(function(showside, sidei) {
+ var pos = linepositions[sidei], tsign = ticksign[sidei];
+ if (showside && isNumeric(pos)) {
+ tickpath += tickpathfn(pos + pad * tsign, tsign * ax.ticklen);
+ }
});
- }
-
- function traceHasBarsOrFill(trace, subplot) {
- if(trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) return false;
- if(Registry.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axletter]) return true;
- return trace.fill && trace.fill.charAt(trace.fill.length - 1) === axletter;
- }
-
- function drawGrid(plotinfo, counteraxis, subplot) {
- var gridcontainer = plotinfo.gridlayer,
- zlcontainer = plotinfo.zerolinelayer,
- gridvals = plotinfo['hidegrid' + axletter] ? [] : valsClipped,
- gridpath = ax._gridpath ||
- 'M0,0' + ((axletter === 'x') ? 'v' : 'h') + counteraxis._length,
- grid = gridcontainer.selectAll('path.' + gcls)
- .data((ax.showgrid === false) ? [] : gridvals, datafn);
- grid.enter().append('path').classed(gcls, 1)
- .classed('crisp', 1)
- .attr('d', gridpath)
- .each(function(d) {
- if(ax.zeroline && (ax.type === 'linear' || ax.type === '-') &&
- Math.abs(d.x) < ax.dtick / 100) {
- d3.select(this).remove();
- }
- });
- grid.attr('transform', transfn)
- .call(Color.stroke, ax.gridcolor || '#ddd')
- .style('stroke-width', gridWidth + 'px');
- grid.exit().remove();
-
- // zero line
- if(zlcontainer) {
- var hasBarsOrFill = false;
- for(var i = 0; i < gd._fullData.length; i++) {
- if(traceHasBarsOrFill(gd._fullData[i], subplot)) {
- hasBarsOrFill = true;
- break;
- }
- }
- var rng = Lib.simpleMap(ax.range, ax.r2l),
- showZl = (rng[0] * rng[1] <= 0) && ax.zeroline &&
- (ax.type === 'linear' || ax.type === '-') && gridvals.length &&
- (hasBarsOrFill || clipEnds({x: 0}) || !ax.showline);
- var zl = zlcontainer.selectAll('path.' + zcls)
- .data(showZl ? [{x: 0}] : []);
- zl.enter().append('path').classed(zcls, 1).classed('zl', 1)
- .classed('crisp', 1)
- .attr('d', gridpath);
- zl.attr('transform', transfn)
- .call(Color.stroke, ax.zerolinecolor || Color.defaultLine)
- .style('stroke-width', zeroLineWidth + 'px');
- zl.exit().remove();
- }
- }
-
- if(independent) {
- drawTicks(ax._axislayer, tickpathfn(ax._pos + pad * ticksign[2], ticksign[2] * ax.ticklen));
- if(ax._counteraxis) {
- var fictionalPlotinfo = {
- gridlayer: ax._gridlayer,
- zerolinelayer: ax._zerolinelayer
- };
- drawGrid(fictionalPlotinfo, ax._counteraxis);
- }
- return drawLabels(ax._axislayer, ax._pos);
- }
- else {
- var alldone = axes.getSubplots(gd, ax).map(function(subplot) {
- var plotinfo = fullLayout._plots[subplot];
- if(!fullLayout._has('cartesian')) return;
-
- var container = plotinfo[axletter + 'axislayer'],
-
- // [bottom or left, top or right, free, main]
- linepositions = ax._linepositions[subplot] || [],
- counteraxis = plotinfo[counterLetter + 'axis'],
- mainSubplot = counteraxis._id === ax.anchor,
- ticksides = [false, false, false],
- tickpath = '';
-
- // ticks
- if(ax.mirror === 'allticks') ticksides = [true, true, false];
- else if(mainSubplot) {
- if(ax.mirror === 'ticks') ticksides = [true, true, false];
- else ticksides[sides.indexOf(axside)] = true;
- }
- if(ax.mirrors) {
- for(i = 0; i < 2; i++) {
- var thisMirror = ax.mirrors[counteraxis._id + sides[i]];
- if(thisMirror === 'ticks' || thisMirror === 'labels') {
- ticksides[i] = true;
- }
- }
- }
+ drawTicks(container, tickpath);
+ drawGrid(plotinfo, counteraxis, subplot);
+ return drawLabels(container, linepositions[3]);
+ })
+ .filter(function(onedone) {
+ return onedone && onedone.then;
+ });
- // free axis ticks
- if(linepositions[2] !== undefined) ticksides[2] = true;
-
- ticksides.forEach(function(showside, sidei) {
- var pos = linepositions[sidei],
- tsign = ticksign[sidei];
- if(showside && isNumeric(pos)) {
- tickpath += tickpathfn(pos + pad * tsign, tsign * ax.ticklen);
- }
- });
-
- drawTicks(container, tickpath);
- drawGrid(plotinfo, counteraxis, subplot);
- return drawLabels(container, linepositions[3]);
- }).filter(function(onedone) { return onedone && onedone.then; });
-
- return alldone.length ? Promise.all(alldone) : 0;
- }
+ return alldone.length ? Promise.all(alldone) : 0;
+ }
};
// swap all the presentation attributes of the axes showing these traces
axes.swap = function(gd, traces) {
- var axGroups = makeAxisGroups(gd, traces);
+ var axGroups = makeAxisGroups(gd, traces);
- for(var i = 0; i < axGroups.length; i++) {
- swapAxisGroup(gd, axGroups[i].x, axGroups[i].y);
- }
+ for (var i = 0; i < axGroups.length; i++) {
+ swapAxisGroup(gd, axGroups[i].x, axGroups[i].y);
+ }
};
function makeAxisGroups(gd, traces) {
- var groups = [],
- i,
- j;
-
- for(i = 0; i < traces.length; i++) {
- var groupsi = [],
- xi = gd._fullData[traces[i]].xaxis,
- yi = gd._fullData[traces[i]].yaxis;
- if(!xi || !yi) continue; // not a 2D cartesian trace?
-
- for(j = 0; j < groups.length; j++) {
- if(groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) {
- groupsi.push(j);
- }
- }
+ var groups = [], i, j;
- if(!groupsi.length) {
- groups.push({x: [xi], y: [yi]});
- continue;
- }
+ for (i = 0; i < traces.length; i++) {
+ var groupsi = [],
+ xi = gd._fullData[traces[i]].xaxis,
+ yi = gd._fullData[traces[i]].yaxis;
+ if (!xi || !yi) continue;
- var group0 = groups[groupsi[0]],
- groupj;
+ // not a 2D cartesian trace?
+ for (j = 0; j < groups.length; j++) {
+ if (groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) {
+ groupsi.push(j);
+ }
+ }
- if(groupsi.length > 1) {
- 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]);
+ if (!groupsi.length) {
+ groups.push({ x: [xi], y: [yi] });
+ continue;
+ }
+
+ var group0 = groups[groupsi[0]], groupj;
+
+ if (groupsi.length > 1) {
+ 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;
+ return groups;
}
function mergeAxisGroups(intoSet, fromSet) {
- for(var i = 0; i < fromSet.length; i++) {
- if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]);
- }
+ 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) {
- Lib.swapAttrs(layout.annotations[i], ['?']);
- }
- }
+ 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) {
+ 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 = 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);
- }
+ // 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 = 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);
+ }
}
diff --git a/src/plots/cartesian/axis_autotype.js b/src/plots/cartesian/axis_autotype.js
index 476c4ec4bff..dbc0714e9ca 100644
--- a/src/plots/cartesian/axis_autotype.js
+++ b/src/plots/cartesian/axis_autotype.js
@@ -5,32 +5,29 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var isNumeric = require("fast-isnumeric");
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-var BADNUM = require('../../constants/numerical').BADNUM;
+var Lib = require("../../lib");
+var BADNUM = require("../../constants/numerical").BADNUM;
module.exports = function autoType(array, calendar) {
- if(moreDates(array, calendar)) return 'date';
- if(category(array)) return 'category';
- if(linearOK(array)) return 'linear';
- else return '-';
+ if (moreDates(array, calendar)) return "date";
+ if (category(array)) return "category";
+ if (linearOK(array)) return "linear";
+ else return "-";
};
// 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;
+ if (!array) return false;
- for(var i = 0; i < array.length; i++) {
- if(isNumeric(array[i])) return true;
- }
+ for (var i = 0; i < array.length; i++) {
+ if (isNumeric(array[i])) return true;
+ }
- return false;
+ return false;
}
// does the array a have mostly dates rather than numbers?
@@ -39,35 +36,35 @@ function linearOK(array) {
// dates as non-dates, to exclude cases with mostly 2 & 4 digit
// numbers and a few dates
function moreDates(a, calendar) {
- var dcnt = 0,
- ncnt = 0,
- // test at most 1000 points, evenly spaced
- inc = Math.max(1, (a.length - 1) / 1000),
- ai;
+ 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; i < a.length; i += inc) {
- ai = a[Math.round(i)];
- if(Lib.isDateTime(ai, calendar)) dcnt += 1;
- if(isNumeric(ai)) ncnt += 1;
- }
+ for (var i = 0; i < a.length; i += inc) {
+ ai = a[Math.round(i)];
+ if (Lib.isDateTime(ai, calendar)) dcnt += 1;
+ if (isNumeric(ai)) ncnt += 1;
+ }
- return (dcnt > ncnt * 2);
+ return dcnt > ncnt * 2;
}
// are the (x,y)-values in gd.data mostly text?
// require twice as many categories as numbers
function category(a) {
- // test at most 1000 points
- var inc = Math.max(1, (a.length - 1) / 1000),
- curvenums = 0,
- curvecats = 0,
- ai;
+ // 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 = a[Math.round(i)];
- if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
- else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++;
- }
+ for (var i = 0; i < a.length; i += inc) {
+ ai = a[Math.round(i)];
+ if (Lib.cleanNumber(ai) !== BADNUM) curvenums++;
+ else if (typeof ai === "string" && ai !== "" && ai !== "None") curvecats++;
+ }
- return curvecats > curvenums * 2;
+ return curvecats > curvenums * 2;
}
diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js
index e4e99bf8294..93199e01e18 100644
--- a/src/plots/cartesian/axis_defaults.js
+++ b/src/plots/cartesian/axis_defaults.js
@@ -5,27 +5,23 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var isNumeric = require('fast-isnumeric');
-var colorMix = require('tinycolor2').mix;
-
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-var lightFraction = require('../../components/color/attributes').lightFraction;
-
-var layoutAttributes = require('./layout_attributes');
-var handleTickValueDefaults = require('./tick_value_defaults');
-var handleTickMarkDefaults = require('./tick_mark_defaults');
-var handleTickLabelDefaults = require('./tick_label_defaults');
-var handleCategoryOrderDefaults = require('./category_order_defaults');
-var setConvert = require('./set_convert');
-var orderedCategories = require('./ordered_categories');
-var axisIds = require('./axis_ids');
-var autoType = require('./axis_autotype');
-
+"use strict";
+var isNumeric = require("fast-isnumeric");
+var colorMix = require("tinycolor2").mix;
+
+var Registry = require("../../registry");
+var Lib = require("../../lib");
+var lightFraction = require("../../components/color/attributes").lightFraction;
+
+var layoutAttributes = require("./layout_attributes");
+var handleTickValueDefaults = require("./tick_value_defaults");
+var handleTickMarkDefaults = require("./tick_mark_defaults");
+var handleTickLabelDefaults = require("./tick_label_defaults");
+var handleCategoryOrderDefaults = require("./category_order_defaults");
+var setConvert = require("./set_convert");
+var orderedCategories = require("./ordered_categories");
+var axisIds = require("./axis_ids");
+var autoType = require("./axis_autotype");
/**
* options: object containing:
@@ -40,193 +36,216 @@ var autoType = require('./axis_autotype');
* data: the plot data to use in choosing auto type
* bgColor: the plot background color, to calculate default gridline colors
*/
-module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options) {
- var letter = options.letter,
- font = options.font || {},
- defaultTitle = 'Click to enter ' +
- (options.title || (letter.toUpperCase() + ' axis')) +
- ' title';
-
- function coerce2(attr, dflt) {
- return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt);
+module.exports = function handleAxisDefaults(
+ containerIn,
+ containerOut,
+ coerce,
+ options
+) {
+ var letter = options.letter,
+ font = options.font || {},
+ defaultTitle = "Click to enter " +
+ (options.title || letter.toUpperCase() + " axis") +
+ " title";
+
+ function coerce2(attr, dflt) {
+ return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt);
+ }
+
+ // set up some private properties
+ if (options.name) {
+ containerOut._name = options.name;
+ containerOut._id = axisIds.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;
}
+ }
- // set up some private properties
- if(options.name) {
- containerOut._name = options.name;
- containerOut._id = axisIds.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;
- }
- }
-
- if(axType === 'date') {
- var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
- handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar);
- }
-
- setConvert(containerOut);
-
- var dfltColor = coerce('color');
- // if axis.color was provided, use it for fonts too; otherwise,
- // inherit from global font color in case that was provided.
- var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : font.color;
-
- coerce('title', defaultTitle);
- Lib.coerceFont(coerce, 'titlefont', {
- family: font.family,
- size: Math.round(font.size * 1.2),
- color: dfltFontColor
- });
-
- var validRange = (
- (containerIn.range || []).length === 2 &&
- isNumeric(containerOut.r2l(containerIn.range[0])) &&
- isNumeric(containerOut.r2l(containerIn.range[1]))
+ if (axType === "date") {
+ var handleCalendarDefaults = Registry.getComponentMethod(
+ "calendars",
+ "handleDefaults"
+ );
+ handleCalendarDefaults(
+ containerIn,
+ containerOut,
+ "calendar",
+ options.calendar
+ );
+ }
+
+ setConvert(containerOut);
+
+ var dfltColor = coerce("color");
+ // if axis.color was provided, use it for fonts too; otherwise,
+ // inherit from global font color in case that was provided.
+ var dfltFontColor = dfltColor === containerIn.color ? dfltColor : font.color;
+
+ coerce("title", defaultTitle);
+ Lib.coerceFont(coerce, "titlefont", {
+ family: font.family,
+ size: Math.round(font.size * 1.2),
+ color: dfltFontColor
+ });
+
+ var validRange = (containerIn.range || []).length === 2 &&
+ isNumeric(containerOut.r2l(containerIn.range[0])) &&
+ isNumeric(containerOut.r2l(containerIn.range[1]));
+ var autoRange = coerce("autorange", !validRange);
+
+ if (autoRange) coerce("rangemode");
+
+ coerce("range");
+ containerOut.cleanRange();
+
+ handleTickValueDefaults(containerIn, containerOut, coerce, axType);
+ handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options);
+ handleTickMarkDefaults(containerIn, containerOut, coerce, options);
+ handleCategoryOrderDefaults(containerIn, containerOut, coerce);
+
+ var lineColor = coerce2("linecolor", dfltColor),
+ lineWidth = coerce2("linewidth"),
+ showLine = coerce("showline", !!lineColor || !!lineWidth);
+
+ if (!showLine) {
+ delete containerOut.linecolor;
+ delete containerOut.linewidth;
+ }
+
+ if (showLine || containerOut.ticks) coerce("mirror");
+
+ var gridColor = coerce2(
+ "gridcolor",
+ colorMix(dfltColor, options.bgColor, lightFraction).toRgbString()
+ ),
+ gridWidth = coerce2("gridwidth"),
+ showGridLines = coerce(
+ "showgrid",
+ options.showGrid || !!gridColor || !!gridWidth
);
- var autoRange = coerce('autorange', !validRange);
-
- if(autoRange) coerce('rangemode');
-
- coerce('range');
- containerOut.cleanRange();
-
- handleTickValueDefaults(containerIn, containerOut, coerce, axType);
- handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options);
- handleTickMarkDefaults(containerIn, containerOut, coerce, options);
- handleCategoryOrderDefaults(containerIn, containerOut, coerce);
-
- var lineColor = coerce2('linecolor', dfltColor),
- lineWidth = coerce2('linewidth'),
- showLine = coerce('showline', !!lineColor || !!lineWidth);
-
- if(!showLine) {
- delete containerOut.linecolor;
- delete containerOut.linewidth;
- }
-
- if(showLine || containerOut.ticks) coerce('mirror');
-
- var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, lightFraction).toRgbString()),
- gridWidth = coerce2('gridwidth'),
- showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth);
-
- if(!showGridLines) {
- delete containerOut.gridcolor;
- delete containerOut.gridwidth;
- }
-
- var zeroLineColor = coerce2('zerolinecolor', dfltColor),
- zeroLineWidth = coerce2('zerolinewidth'),
- showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth);
- if(!showZeroLine) {
- delete containerOut.zerolinecolor;
- delete containerOut.zerolinewidth;
- }
+ if (!showGridLines) {
+ delete containerOut.gridcolor;
+ delete containerOut.gridwidth;
+ }
- // fill in categories
- containerOut._initialCategories = axType === 'category' ?
- orderedCategories(letter, containerOut.categoryorder, containerOut.categoryarray, options.data) :
- [];
+ var zeroLineColor = coerce2("zerolinecolor", dfltColor),
+ zeroLineWidth = coerce2("zerolinewidth"),
+ showZeroLine = coerce(
+ "zeroline",
+ options.showGrid || !!zeroLineColor || !!zeroLineWidth
+ );
- return containerOut;
+ if (!showZeroLine) {
+ delete containerOut.zerolinecolor;
+ delete containerOut.zerolinewidth;
+ }
+
+ // fill in categories
+ containerOut._initialCategories = axType === "category"
+ ? orderedCategories(
+ letter,
+ containerOut.categoryorder,
+ containerOut.categoryarray,
+ options.data
+ )
+ : [];
+
+ return containerOut;
};
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;
+ // 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;
+ }
+
+ var calAttr = axLetter + "calendar", calendar = d0[calAttr];
+
+ // 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 (
+ !Registry.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");
+
+ if (trace[calAttr] !== calendar) calendar = undefined;
}
- var calAttr = axLetter + 'calendar',
- calendar = d0[calAttr];
-
- // 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(!Registry.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');
-
- if(trace[calAttr] !== calendar) calendar = undefined;
- }
-
- ax.type = autoType(boxPositions, calendar);
- }
- else {
- ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar);
- }
+ ax.type = autoType(boxPositions, calendar);
+ } else {
+ ax.type = autoType(d0[axLetter] || [d0[axLetter + "0"]], calendar);
+ }
}
function getBoxPosLetter(trace) {
- return {v: 'x', h: 'y'}[trace.orientation || 'v'];
+ return ({ v: "x", h: "y" })[trace.orientation || "v"];
}
function isBoxWithoutPositionCoords(trace, axLetter) {
- var posLetter = getBoxPosLetter(trace),
- isBox = Registry.traceIs(trace, 'box'),
- isCandlestick = Registry.traceIs(trace._fullInput || {}, 'candlestick');
-
- return (
- isBox &&
- !isCandlestick &&
- axLetter === posLetter &&
- trace[posLetter] === undefined &&
- trace[posLetter + '0'] === undefined
- );
+ var posLetter = getBoxPosLetter(trace),
+ isBox = Registry.traceIs(trace, "box"),
+ isCandlestick = Registry.traceIs(trace._fullInput || {}, "candlestick");
+
+ return isBox &&
+ !isCandlestick &&
+ axLetter === posLetter &&
+ trace[posLetter] === undefined &&
+ trace[posLetter + "0"] === undefined;
}
function getFirstNonEmptyTrace(data, id, axLetter) {
- for(var i = 0; i < data.length; i++) {
- var 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;
- }
- }
+ for (var i = 0; i < data.length; i++) {
+ var 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;
+ }
}
+ }
}
diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js
index 63b9fae6e77..fe7212f6bd0 100644
--- a/src/plots/cartesian/axis_ids.js
+++ b/src/plots/cartesian/axis_ids.js
@@ -5,116 +5,107 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+var Registry = require("../../registry");
+var Plots = require("../plots");
+var Lib = require("../../lib");
-'use strict';
-
-var Registry = require('../../registry');
-var Plots = require('../plots');
-var Lib = require('../../lib');
-
-var constants = require('./constants');
-
+var constants = require("./constants");
// convert between axis names (xaxis, xaxis2, etc, elements of gd.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.
exports.id2name = function id2name(id) {
- if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
- var axNum = id.substr(1);
- if(axNum === '1') axNum = '';
- return id.charAt(0) + 'axis' + axNum;
+ if (typeof id !== "string" || !id.match(constants.AX_ID_PATTERN)) return;
+ var axNum = id.substr(1);
+ if (axNum === "1") axNum = "";
+ return id.charAt(0) + "axis" + axNum;
};
exports.name2id = function name2id(name) {
- if(!name.match(constants.AX_NAME_PATTERN)) return;
- var axNum = name.substr(5);
- if(axNum === '1') axNum = '';
- return name.charAt(0) + axNum;
+ if (!name.match(constants.AX_NAME_PATTERN)) return;
+ var axNum = name.substr(5);
+ if (axNum === "1") axNum = "";
+ return name.charAt(0) + axNum;
};
exports.cleanId = function cleanId(id, axLetter) {
- if(!id.match(constants.AX_ID_PATTERN)) return;
- if(axLetter && id.charAt(0) !== axLetter) return;
+ if (!id.match(constants.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;
+ var axNum = id.substr(1).replace(/^0+/, "");
+ if (axNum === "1") axNum = "";
+ return id.charAt(0) + axNum;
};
// 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(gd, axLetter, only2d) {
- var fullLayout = gd._fullLayout;
- if(!fullLayout) return [];
-
- function filterAxis(obj, extra) {
- var keys = Object.keys(obj),
- axMatch = /^[xyz]axis[0-9]*/,
- out = [];
+ var fullLayout = gd._fullLayout;
+ if (!fullLayout) return [];
- 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);
- }
+ function filterAxis(obj, extra) {
+ var keys = Object.keys(obj), axMatch = /^[xyz]axis[0-9]*/, out = [];
- return out.sort();
+ 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);
}
- var names = filterAxis(fullLayout, '');
- if(only2d) return names;
+ return out.sort();
+ }
- var sceneIds3D = Plots.getSubplotIds(fullLayout, 'gl3d') || [];
- for(var i = 0; i < sceneIds3D.length; i++) {
- var sceneId = sceneIds3D[i];
- names = names.concat(
- filterAxis(fullLayout[sceneId], sceneId + '.')
- );
- }
+ var names = filterAxis(fullLayout, "");
+ if (only2d) return names;
+
+ var sceneIds3D = 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;
+ return names;
}
// get all axis objects, as restricted in listNames
exports.list = function(gd, axletter, only2d) {
- return listNames(gd, axletter, only2d)
- .map(function(axName) {
- return Lib.nestedProperty(gd._fullLayout, axName).get();
- });
+ return listNames(gd, axletter, only2d).map(function(axName) {
+ return Lib.nestedProperty(gd._fullLayout, axName).get();
+ });
};
// get all axis ids, optionally restricted by letter
// this only makes sense for 2d axes
exports.listIds = function(gd, axletter) {
- return listNames(gd, axletter, true).map(exports.name2id);
+ return listNames(gd, axletter, true).map(exports.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
exports.getFromId = function(gd, id, type) {
- var fullLayout = gd._fullLayout;
+ var fullLayout = gd._fullLayout;
- if(type === 'x') id = id.replace(/y[0-9]*/, '');
- else if(type === 'y') id = id.replace(/x[0-9]*/, '');
+ if (type === "x") id = id.replace(/y[0-9]*/, "");
+ else if (type === "y") id = id.replace(/x[0-9]*/, "");
- return fullLayout[exports.id2name(id)];
+ return fullLayout[exports.id2name(id)];
};
// get an axis object of specified type from the containing trace
exports.getFromTrace = function(gd, fullTrace, type) {
- var fullLayout = gd._fullLayout;
- var ax = null;
-
- if(Registry.traceIs(fullTrace, 'gl3d')) {
- var scene = fullTrace.scene;
- if(scene.substr(0, 5) === 'scene') {
- ax = fullLayout[scene][type + 'axis'];
- }
- }
- else {
- ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type);
+ var fullLayout = gd._fullLayout;
+ var ax = null;
+
+ if (Registry.traceIs(fullTrace, "gl3d")) {
+ var scene = fullTrace.scene;
+ if (scene.substr(0, 5) === "scene") {
+ ax = fullLayout[scene][type + "axis"];
}
+ } else {
+ ax = exports.getFromId(gd, fullTrace[type + "axis"] || type);
+ }
- return ax;
+ return ax;
};
diff --git a/src/plots/cartesian/category_order_defaults.js b/src/plots/cartesian/category_order_defaults.js
index c115dd28c23..e82611c2b9a 100644
--- a/src/plots/cartesian/category_order_defaults.js
+++ b/src/plots/cartesian/category_order_defaults.js
@@ -5,28 +5,28 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+"use strict";
+module.exports = function handleCategoryOrderDefaults(
+ containerIn,
+ containerOut,
+ coerce
+) {
+ if (containerOut.type !== "category") return;
-'use strict';
+ var arrayIn = containerIn.categoryarray, orderDefault;
+ var isValidArray = Array.isArray(arrayIn) && arrayIn.length > 0;
-module.exports = function handleCategoryOrderDefaults(containerIn, containerOut, coerce) {
- if(containerOut.type !== 'category') return;
+ // override default 'categoryorder' value when non-empty array is supplied
+ if (isValidArray) orderDefault = "array";
- var arrayIn = containerIn.categoryarray,
- orderDefault;
+ var order = coerce("categoryorder", orderDefault);
- var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0);
+ // coerce 'categoryarray' only in array order case
+ if (order === "array") coerce("categoryarray");
- // override default 'categoryorder' value when non-empty array is supplied
- if(isValidArray) orderDefault = 'array';
-
- var order = coerce('categoryorder', orderDefault);
-
- // coerce 'categoryarray' only in array order case
- if(order === 'array') coerce('categoryarray');
-
- // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'
- if(!isValidArray && order === 'array') {
- containerOut.categoryorder = 'trace';
- }
+ // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'
+ if (!isValidArray && order === "array") {
+ containerOut.categoryorder = "trace";
+ }
};
diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js
index 8d7ed20df45..c3643cab011 100644
--- a/src/plots/cartesian/constants.js
+++ b/src/plots/cartesian/constants.js
@@ -5,68 +5,48 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-'use strict';
-
-
+"use strict";
module.exports = {
-
- idRegex: {
- x: /^x([2-9]|[1-9][0-9]+)?$/,
- y: /^y([2-9]|[1-9][0-9]+)?$/
- },
-
- attrRegex: {
- x: /^xaxis([2-9]|[1-9][0-9]+)?$/,
- y: /^yaxis([2-9]|[1-9][0-9]+)?$/
- },
-
- // axis match regular expression
- xAxisMatch: /^xaxis[0-9]*$/,
- yAxisMatch: /^yaxis[0-9]*$/,
-
- // pattern matching axis ids and names
- AX_ID_PATTERN: /^[xyz][0-9]*$/,
- AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/,
-
- // ms between first mousedown and 2nd mouseup to constitute dblclick...
- // we don't seem to have access to the system setting
- DBLCLICKDELAY: 300,
-
- // pixels to move mouse before you stop clamping to starting point
- MINDRAG: 8,
-
- // smallest dimension allowed for a select box
- MINSELECT: 12,
-
- // smallest dimension allowed for a zoombox
- MINZOOM: 20,
-
- // width of axis drag regions
- DRAGGERSIZE: 20,
-
- // max pixels away from mouse to allow a point to highlight
- MAXDIST: 20,
-
- // hover labels for multiple horizontal bars get tilted by this angle
- YANGLE: 60,
-
- // size and display constants for hover text
- HOVERARROWSIZE: 6, // pixel size of hover arrows
- HOVERTEXTPAD: 3, // pixels padding around text
- HOVERFONTSIZE: 13,
- HOVERFONT: 'Arial, sans-serif',
-
- // minimum time (msec) between hover calls
- HOVERMINTIME: 50,
-
- // max pixels off straight before a lasso select line counts as bent
- BENDPX: 1.5,
-
- // delay before a redraw (relayout) after smooth panning and zooming
- REDRAWDELAY: 50,
-
- // last resort axis ranges for x and y axes if we have no data
- DFLTRANGEX: [-1, 6],
- DFLTRANGEY: [-1, 4]
+ idRegex: { x: /^x([2-9]|[1-9][0-9]+)?$/, y: /^y([2-9]|[1-9][0-9]+)?$/ },
+ attrRegex: {
+ x: /^xaxis([2-9]|[1-9][0-9]+)?$/,
+ y: /^yaxis([2-9]|[1-9][0-9]+)?$/
+ },
+ // axis match regular expression
+ xAxisMatch: /^xaxis[0-9]*$/,
+ yAxisMatch: /^yaxis[0-9]*$/,
+ // pattern matching axis ids and names
+ AX_ID_PATTERN: /^[xyz][0-9]*$/,
+ AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/,
+ // ms between first mousedown and 2nd mouseup to constitute dblclick...
+ // we don't seem to have access to the system setting
+ DBLCLICKDELAY: 300,
+ // pixels to move mouse before you stop clamping to starting point
+ MINDRAG: 8,
+ // smallest dimension allowed for a select box
+ MINSELECT: 12,
+ // smallest dimension allowed for a zoombox
+ MINZOOM: 20,
+ // width of axis drag regions
+ DRAGGERSIZE: 20,
+ // max pixels away from mouse to allow a point to highlight
+ MAXDIST: 20,
+ // hover labels for multiple horizontal bars get tilted by this angle
+ YANGLE: 60,
+ // size and display constants for hover text
+ HOVERARROWSIZE: 6,
+ // pixel size of hover arrows
+ HOVERTEXTPAD: 3,
+ // pixels padding around text
+ HOVERFONTSIZE: 13,
+ HOVERFONT: "Arial, sans-serif",
+ // minimum time (msec) between hover calls
+ HOVERMINTIME: 50,
+ // max pixels off straight before a lasso select line counts as bent
+ BENDPX: 1.5,
+ // delay before a redraw (relayout) after smooth panning and zooming
+ REDRAWDELAY: 50,
+ // last resort axis ranges for x and y axes if we have no data
+ DFLTRANGEX: [-1, 6],
+ DFLTRANGEY: [-1, 4]
};
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 22a253f287e..fcc8808315e 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -5,26 +5,22 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
-var tinycolor = require('tinycolor2');
-
-var Plotly = require('../../plotly');
-var Registry = require('../../registry');
-var Lib = require('../../lib');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var Color = require('../../components/color');
-var Drawing = require('../../components/drawing');
-var setCursor = require('../../lib/setcursor');
-var dragElement = require('../../components/dragelement');
-
-var Axes = require('./axes');
-var prepSelect = require('./select');
-var constants = require('./constants');
-
+"use strict";
+var d3 = require("d3");
+var tinycolor = require("tinycolor2");
+
+var Plotly = require("../../plotly");
+var Registry = require("../../registry");
+var Lib = require("../../lib");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var Color = require("../../components/color");
+var Drawing = require("../../components/drawing");
+var setCursor = require("../../lib/setcursor");
+var dragElement = require("../../components/dragelement");
+
+var Axes = require("./axes");
+var prepSelect = require("./select");
+var constants = require("./constants");
// flag for showing "doubleclick to zoom out" only at the beginning
var SHOWZOOMOUTTIP = true;
@@ -39,725 +35,817 @@ var SHOWZOOMOUTTIP = true;
// 'ns' - top and bottom together, difference unchanged
// ew - same for horizontal axis
module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
- // mouseDown stores ms of first mousedown event in the last
- // DBLCLICKDELAY ms on the drag bars
- // numClicks stores how many mousedowns have been seen
- // within DBLCLICKDELAY so we can check for click or doubleclick events
- // dragged stores whether a drag has occurred, so we don't have to
- // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px
- var fullLayout = gd._fullLayout,
- // if we're dragging two axes at once, also drag overlays
- subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []),
- xa = [plotinfo.xaxis],
- ya = [plotinfo.yaxis],
- pw = xa[0]._length,
- ph = ya[0]._length,
- MINDRAG = constants.MINDRAG,
- MINZOOM = constants.MINZOOM,
- isMainDrag = (ns + ew === 'nsew');
-
- for(var i = 1; i < subplots.length; i++) {
- var subplotXa = subplots[i].xaxis,
- subplotYa = subplots[i].yaxis;
- if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa);
- if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa);
+ // mouseDown stores ms of first mousedown event in the last
+ // DBLCLICKDELAY ms on the drag bars
+ // numClicks stores how many mousedowns have been seen
+ // within DBLCLICKDELAY so we can check for click or doubleclick events
+ // dragged stores whether a drag has occurred, so we don't have to
+ // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px
+ var fullLayout = gd._fullLayout,
+ // if we're dragging two axes at once, also drag overlays
+ subplots = [plotinfo].concat(ns && ew ? plotinfo.overlays : []),
+ xa = [plotinfo.xaxis],
+ ya = [plotinfo.yaxis],
+ pw = xa[0]._length,
+ ph = ya[0]._length,
+ MINDRAG = constants.MINDRAG,
+ MINZOOM = constants.MINZOOM,
+ isMainDrag = ns + ew === "nsew";
+
+ for (var i = 1; i < subplots.length; i++) {
+ var subplotXa = subplots[i].xaxis, subplotYa = subplots[i].yaxis;
+ if (xa.indexOf(subplotXa) === -1) xa.push(subplotXa);
+ if (ya.indexOf(subplotYa) === -1) ya.push(subplotYa);
+ }
+
+ function isDirectionActive(axList, activeVal) {
+ for (var i = 0; i < axList.length; i++) {
+ if (!axList[i].fixedrange) return activeVal;
}
-
- function isDirectionActive(axList, activeVal) {
- for(var i = 0; i < axList.length; i++) {
- if(!axList[i].fixedrange) return activeVal;
- }
- return '';
+ return "";
+ }
+
+ var allaxes = xa.concat(ya),
+ xActive = isDirectionActive(xa, ew),
+ yActive = isDirectionActive(ya, ns),
+ cursor = getDragCursor(yActive + xActive, fullLayout.dragmode),
+ dragClass = ns + ew + "drag";
+
+ var dragger3 = plotinfo.draglayer.selectAll("." + dragClass).data([0]);
+
+ dragger3
+ .enter()
+ .append("rect")
+ .classed("drag", true)
+ .classed(dragClass, true)
+ .style({ fill: "transparent", "stroke-width": 0 })
+ .attr("data-subplot", plotinfo.id);
+
+ dragger3.call(Drawing.setRect, x, y, w, h).call(setCursor, cursor);
+
+ var dragger = dragger3.node();
+
+ // still need to make the element if the axes are disabled
+ // but nuke its events (except for maindrag which needs them for hover)
+ // and stop there
+ if (!yActive && !xActive && !isSelectOrLasso(fullLayout.dragmode)) {
+ dragger.onmousedown = null;
+ dragger.style.pointerEvents = isMainDrag ? "all" : "none";
+ return dragger;
+ }
+
+ var dragOptions = {
+ element: dragger,
+ gd: gd,
+ plotinfo: plotinfo,
+ xaxes: xa,
+ yaxes: ya,
+ doubleclick: doubleClick,
+ prepFn: function(e, startX, startY) {
+ var dragModeNow = gd._fullLayout.dragmode;
+
+ if (isMainDrag) {
+ // main dragger handles all drag modes, and changes
+ // to pan (or to zoom if it already is pan) on shift
+ if (e.shiftKey) {
+ if (dragModeNow === "pan") dragModeNow = "zoom";
+ else dragModeNow = "pan";
+ }
+ } else {
+ // all other draggers just pan
+ dragModeNow = "pan";
+ }
+
+ if (dragModeNow === "lasso") dragOptions.minDrag = 1;
+ else dragOptions.minDrag = undefined;
+
+ if (dragModeNow === "zoom") {
+ dragOptions.moveFn = zoomMove;
+ dragOptions.doneFn = zoomDone;
+ zoomPrep(e, startX, startY);
+ } else if (dragModeNow === "pan") {
+ dragOptions.moveFn = plotDrag;
+ dragOptions.doneFn = dragDone;
+ clearSelect();
+ } else if (isSelectOrLasso(dragModeNow)) {
+ prepSelect(e, startX, startY, dragOptions, dragModeNow);
+ }
+ }
+ };
+
+ dragElement.init(dragOptions);
+
+ var zoomlayer = gd._fullLayout._zoomlayer,
+ xs = plotinfo.xaxis._offset,
+ ys = plotinfo.yaxis._offset,
+ x0,
+ y0,
+ box,
+ lum,
+ path0,
+ dimmed,
+ zoomMode,
+ zb,
+ corners;
+
+ function recomputeAxisLists() {
+ xa = [plotinfo.xaxis];
+ ya = [plotinfo.yaxis];
+ pw = xa[0]._length;
+ ph = ya[0]._length;
+
+ for (var i = 1; i < subplots.length; i++) {
+ var subplotXa = subplots[i].xaxis, subplotYa = subplots[i].yaxis;
+ if (xa.indexOf(subplotXa) === -1) xa.push(subplotXa);
+ if (ya.indexOf(subplotYa) === -1) ya.push(subplotYa);
+ }
+ allaxes = xa.concat(ya);
+ xActive = isDirectionActive(xa, ew);
+ yActive = isDirectionActive(ya, ns);
+ cursor = getDragCursor(yActive + xActive, fullLayout.dragmode);
+ xs = plotinfo.xaxis._offset;
+ ys = plotinfo.yaxis._offset;
+ dragOptions.xa = xa;
+ dragOptions.ya = ya;
+ }
+
+ function zoomPrep(e, startX, startY) {
+ var dragBBox = dragger.getBoundingClientRect();
+ x0 = startX - dragBBox.left;
+ y0 = startY - dragBBox.top;
+ box = { l: x0, r: x0, w: 0, t: y0, b: y0, h: 0 };
+ lum = gd._hmpixcount
+ ? gd._hmlumcount / gd._hmpixcount
+ : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance();
+ path0 = "M0,0H" + pw + "V" + ph + "H0V0";
+ dimmed = false;
+ zoomMode = "xy";
+
+ zb = zoomlayer
+ .append("path")
+ .attr("class", "zoombox")
+ .style({
+ fill: lum > 0.2 ? "rgba(0,0,0,0)" : "rgba(255,255,255,0)",
+ "stroke-width": 0
+ })
+ .attr("transform", "translate(" + xs + ", " + ys + ")")
+ .attr("d", path0 + "Z");
+
+ corners = zoomlayer
+ .append("path")
+ .attr("class", "zoombox-corners")
+ .style({
+ fill: Color.background,
+ stroke: Color.defaultLine,
+ "stroke-width": 1,
+ opacity: 0
+ })
+ .attr("transform", "translate(" + xs + ", " + ys + ")")
+ .attr("d", "M0,0Z");
+
+ clearSelect();
+ }
+
+ function clearSelect() {
+ // until we get around to persistent selections, remove the outline
+ // here. The selection itself will be removed when the plot redraws
+ // at the end.
+ zoomlayer.selectAll(".select-outline").remove();
+ }
+
+ function zoomMove(dx0, dy0) {
+ if (gd._transitioningWithDuration) {
+ return false;
}
- var allaxes = xa.concat(ya),
- xActive = isDirectionActive(xa, ew),
- yActive = isDirectionActive(ya, ns),
- cursor = getDragCursor(yActive + xActive, fullLayout.dragmode),
- dragClass = ns + ew + 'drag';
+ var x1 = Math.max(0, Math.min(pw, dx0 + x0)),
+ y1 = Math.max(0, Math.min(ph, dy0 + y0)),
+ dx = Math.abs(x1 - x0),
+ dy = Math.abs(y1 - y0),
+ clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2);
+
+ box.l = Math.min(x0, x1);
+ box.r = Math.max(x0, x1);
+ box.t = Math.min(y0, y1);
+ box.b = Math.max(y0, y1);
+
+ // look for small drags in one direction or the other,
+ // and only drag the other axis
+ if (!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) {
+ if (dx < MINDRAG) {
+ zoomMode = "";
+ box.r = box.l;
+ box.t = box.b;
+ corners.attr("d", "M0,0Z");
+ } else {
+ box.t = 0;
+ box.b = ph;
+ zoomMode = "x";
+ corners.attr(
+ "d",
+ "M" +
+ (box.l - 0.5) +
+ "," +
+ (y0 - MINZOOM - 0.5) +
+ "h-3v" +
+ (2 * MINZOOM + 1) +
+ "h3ZM" +
+ (box.r + 0.5) +
+ "," +
+ (y0 - MINZOOM - 0.5) +
+ "h3v" +
+ (2 * MINZOOM + 1) +
+ "h-3Z"
+ );
+ }
+ } else if (!xActive || dx < Math.min(dy * 0.6, MINZOOM)) {
+ box.l = 0;
+ box.r = pw;
+ zoomMode = "y";
+ corners.attr(
+ "d",
+ "M" +
+ (x0 - MINZOOM - 0.5) +
+ "," +
+ (box.t - 0.5) +
+ "v-3h" +
+ (2 * MINZOOM + 1) +
+ "v3ZM" +
+ (x0 - MINZOOM - 0.5) +
+ "," +
+ (box.b + 0.5) +
+ "v3h" +
+ (2 * MINZOOM + 1) +
+ "v-3Z"
+ );
+ } else {
+ zoomMode = "xy";
+ corners.attr(
+ "d",
+ "M" +
+ (box.l - 3.5) +
+ "," +
+ (box.t - 0.5 + clen) +
+ "h3v" +
+ (-clen) +
+ "h" +
+ clen +
+ "v-3h-" +
+ (clen + 3) +
+ "ZM" +
+ (box.r + 3.5) +
+ "," +
+ (box.t - 0.5 + clen) +
+ "h-3v" +
+ (-clen) +
+ "h" +
+ (-clen) +
+ "v-3h" +
+ (clen + 3) +
+ "ZM" +
+ (box.r + 3.5) +
+ "," +
+ (box.b + 0.5 - clen) +
+ "h-3v" +
+ clen +
+ "h" +
+ (-clen) +
+ "v3h" +
+ (clen + 3) +
+ "ZM" +
+ (box.l - 3.5) +
+ "," +
+ (box.b + 0.5 - clen) +
+ "h3v" +
+ clen +
+ "h" +
+ clen +
+ "v3h-" +
+ (clen + 3) +
+ "Z"
+ );
+ }
+ box.w = box.r - box.l;
+ box.h = box.b - box.t;
+
+ // Not sure about the addition of window.scrollX/Y...
+ // seems to work but doesn't seem robust.
+ zb.attr(
+ "d",
+ path0 +
+ "M" +
+ box.l +
+ "," +
+ box.t +
+ "v" +
+ box.h +
+ "h" +
+ box.w +
+ "v-" +
+ box.h +
+ "h-" +
+ box.w +
+ "Z"
+ );
+ if (!dimmed) {
+ zb
+ .transition()
+ .style("fill", lum > 0.2 ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.3)")
+ .duration(200);
+ corners.transition().style("opacity", 1).duration(200);
+ dimmed = true;
+ }
+ }
- var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]);
+ function zoomAxRanges(axList, r0Fraction, r1Fraction) {
+ var i, axi, axRangeLinear0, axRangeLinearSpan;
- dragger3.enter().append('rect')
- .classed('drag', true)
- .classed(dragClass, true)
- .style({fill: 'transparent', 'stroke-width': 0})
- .attr('data-subplot', plotinfo.id);
+ for (i = 0; i < axList.length; i++) {
+ axi = axList[i];
+ if (axi.fixedrange) continue;
- dragger3.call(Drawing.setRect, x, y, w, h)
- .call(setCursor, cursor);
+ axRangeLinear0 = axi._rl[0];
+ axRangeLinearSpan = axi._rl[1] - axRangeLinear0;
+ axi.range = [
+ axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction),
+ axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction)
+ ];
+ }
+ }
- var dragger = dragger3.node();
+ function zoomDone(dragged, numClicks) {
+ if (Math.min(box.h, box.w) < MINDRAG * 2) {
+ if (numClicks === 2) doubleClick();
- // still need to make the element if the axes are disabled
- // but nuke its events (except for maindrag which needs them for hover)
- // and stop there
- if(!yActive && !xActive && !isSelectOrLasso(fullLayout.dragmode)) {
- dragger.onmousedown = null;
- dragger.style.pointerEvents = isMainDrag ? 'all' : 'none';
- return dragger;
+ return removeZoombox(gd);
}
- var dragOptions = {
- element: dragger,
- gd: gd,
- plotinfo: plotinfo,
- xaxes: xa,
- yaxes: ya,
- doubleclick: doubleClick,
- prepFn: function(e, startX, startY) {
- var dragModeNow = gd._fullLayout.dragmode;
-
- if(isMainDrag) {
- // main dragger handles all drag modes, and changes
- // to pan (or to zoom if it already is pan) on shift
- if(e.shiftKey) {
- if(dragModeNow === 'pan') dragModeNow = 'zoom';
- else dragModeNow = 'pan';
- }
- }
- // all other draggers just pan
- else dragModeNow = 'pan';
-
- if(dragModeNow === 'lasso') dragOptions.minDrag = 1;
- else dragOptions.minDrag = undefined;
-
- if(dragModeNow === 'zoom') {
- dragOptions.moveFn = zoomMove;
- dragOptions.doneFn = zoomDone;
- zoomPrep(e, startX, startY);
- }
- else if(dragModeNow === 'pan') {
- dragOptions.moveFn = plotDrag;
- dragOptions.doneFn = dragDone;
- clearSelect();
- }
- else if(isSelectOrLasso(dragModeNow)) {
- prepSelect(e, startX, startY, dragOptions, dragModeNow);
- }
- }
- };
-
- dragElement.init(dragOptions);
-
- var zoomlayer = gd._fullLayout._zoomlayer,
- xs = plotinfo.xaxis._offset,
- ys = plotinfo.yaxis._offset,
- x0,
- y0,
- box,
- lum,
- path0,
- dimmed,
- zoomMode,
- zb,
- corners;
-
- function recomputeAxisLists() {
- xa = [plotinfo.xaxis];
- ya = [plotinfo.yaxis];
- pw = xa[0]._length;
- ph = ya[0]._length;
-
- for(var i = 1; i < subplots.length; i++) {
- var subplotXa = subplots[i].xaxis,
- subplotYa = subplots[i].yaxis;
- if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa);
- if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa);
- }
- allaxes = xa.concat(ya);
- xActive = isDirectionActive(xa, ew);
- yActive = isDirectionActive(ya, ns);
- cursor = getDragCursor(yActive + xActive, fullLayout.dragmode);
- xs = plotinfo.xaxis._offset;
- ys = plotinfo.yaxis._offset;
- dragOptions.xa = xa;
- dragOptions.ya = ya;
+ if (zoomMode === "xy" || zoomMode === "x") {
+ zoomAxRanges(xa, box.l / pw, box.r / pw);
+ }
+ if (zoomMode === "xy" || zoomMode === "y") {
+ zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph);
}
- function zoomPrep(e, startX, startY) {
- var dragBBox = dragger.getBoundingClientRect();
- x0 = startX - dragBBox.left;
- y0 = startY - dragBBox.top;
- box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0};
- lum = gd._hmpixcount ?
- (gd._hmlumcount / gd._hmpixcount) :
- tinycolor(gd._fullLayout.plot_bgcolor).getLuminance();
- path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0';
- dimmed = false;
- zoomMode = 'xy';
-
- zb = zoomlayer.append('path')
- .attr('class', 'zoombox')
- .style({
- 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
- 'stroke-width': 0
- })
- .attr('transform', 'translate(' + xs + ', ' + ys + ')')
- .attr('d', path0 + 'Z');
-
- corners = zoomlayer.append('path')
- .attr('class', 'zoombox-corners')
- .style({
- fill: Color.background,
- stroke: Color.defaultLine,
- 'stroke-width': 1,
- opacity: 0
- })
- .attr('transform', 'translate(' + xs + ', ' + ys + ')')
- .attr('d', 'M0,0Z');
+ removeZoombox(gd);
+ dragTail(zoomMode);
- clearSelect();
+ if (SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
+ Lib.notifier("Double-click to
zoom back out", "long");
+ SHOWZOOMOUTTIP = false;
}
-
- function clearSelect() {
- // until we get around to persistent selections, remove the outline
- // here. The selection itself will be removed when the plot redraws
- // at the end.
- zoomlayer.selectAll('.select-outline').remove();
+ }
+
+ function dragDone(dragged, numClicks) {
+ var singleEnd = (ns + ew).length === 1;
+ if (dragged) {
+ dragTail();
+ } else if (numClicks === 2 && !singleEnd) {
+ doubleClick();
+ } else if (numClicks === 1 && singleEnd) {
+ var ax = ns ? ya[0] : xa[0],
+ end = ns === "s" || ew === "w" ? 0 : 1,
+ attrStr = ax._name + ".range[" + end + "]",
+ initialText = getEndText(ax, end),
+ hAlign = "left",
+ vAlign = "middle";
+
+ if (ax.fixedrange) return;
+
+ if (ns) {
+ vAlign = ns === "n" ? "top" : "bottom";
+ if (ax.side === "right") hAlign = "right";
+ } else if (ew === "e") hAlign = "right";
+
+ dragger3
+ .call(svgTextUtils.makeEditable, null, {
+ immediate: true,
+ background: fullLayout.paper_bgcolor,
+ text: String(initialText),
+ fill: ax.tickfont ? ax.tickfont.color : "#444",
+ horizontalAlign: hAlign,
+ verticalAlign: vAlign
+ })
+ .on("edit", function(text) {
+ var v = ax.d2r(text);
+ if (v !== undefined) {
+ Plotly.relayout(gd, attrStr, v);
+ }
+ });
}
-
- function zoomMove(dx0, dy0) {
- if(gd._transitioningWithDuration) {
- return false;
- }
-
- var x1 = Math.max(0, Math.min(pw, dx0 + x0)),
- y1 = Math.max(0, Math.min(ph, dy0 + y0)),
- dx = Math.abs(x1 - x0),
- dy = Math.abs(y1 - y0),
- clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2);
-
- box.l = Math.min(x0, x1);
- box.r = Math.max(x0, x1);
- box.t = Math.min(y0, y1);
- box.b = Math.max(y0, y1);
-
- // look for small drags in one direction or the other,
- // and only drag the other axis
- if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) {
- if(dx < MINDRAG) {
- zoomMode = '';
- box.r = box.l;
- box.t = box.b;
- corners.attr('d', 'M0,0Z');
- }
- else {
- box.t = 0;
- box.b = ph;
- zoomMode = 'x';
- corners.attr('d',
- 'M' + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) +
- 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' +
- (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) +
- 'h3v' + (2 * MINZOOM + 1) + 'h-3Z');
- }
- }
- else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) {
- box.l = 0;
- box.r = pw;
- zoomMode = 'y';
- corners.attr('d',
- 'M' + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) +
- 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' +
- (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) +
- 'v3h' + (2 * MINZOOM + 1) + 'v-3Z');
- }
- else {
- zoomMode = 'xy';
- corners.attr('d',
- 'M' + (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) +
- 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' +
- (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) +
- 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' +
- (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen +
- 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' +
- (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen +
- 'h' + clen + 'v3h-' + (clen + 3) + 'Z');
- }
- box.w = box.r - box.l;
- box.h = box.b - box.t;
-
- // Not sure about the addition of window.scrollX/Y...
- // seems to work but doesn't seem robust.
- zb.attr('d',
- path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) +
- 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z');
- if(!dimmed) {
- zb.transition()
- .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' :
- 'rgba(255,255,255,0.3)')
- .duration(200);
- corners.transition()
- .style('opacity', 1)
- .duration(200);
- dimmed = true;
- }
+ }
+
+ // scroll zoom, on all draggers except corners
+ var scrollViewBox = [0, 0, pw, ph],
+ // wait a little after scrolling before redrawing
+ redrawTimer = null,
+ REDRAWDELAY = constants.REDRAWDELAY,
+ mainplot = plotinfo.mainplot
+ ? fullLayout._plots[plotinfo.mainplot]
+ : plotinfo;
+
+ function zoomWheel(e) {
+ // deactivate mousewheel scrolling on embedded graphs
+ // devs can override this with layout._enablescrollzoom,
+ // but _ ensures this setting won't leave their page
+ if (!gd._context.scrollZoom && !fullLayout._enablescrollzoom) {
+ return;
}
- function zoomAxRanges(axList, r0Fraction, r1Fraction) {
- var i,
- axi,
- axRangeLinear0,
- axRangeLinearSpan;
-
- for(i = 0; i < axList.length; i++) {
- axi = axList[i];
- if(axi.fixedrange) continue;
-
- axRangeLinear0 = axi._rl[0];
- axRangeLinearSpan = axi._rl[1] - axRangeLinear0;
- axi.range = [
- axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction),
- axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction)
- ];
- }
+ // If a transition is in progress, then disable any behavior:
+ if (gd._transitioningWithDuration) {
+ return Lib.pauseEvent(e);
}
- function zoomDone(dragged, numClicks) {
- if(Math.min(box.h, box.w) < MINDRAG * 2) {
- if(numClicks === 2) doubleClick();
+ var pc = gd.querySelector(".plotly");
- return removeZoombox(gd);
- }
+ recomputeAxisLists();
- if(zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw);
- if(zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph);
+ // if the plot has scrollbars (more than a tiny excess)
+ // disable scrollzoom too.
+ if (
+ pc.scrollHeight - pc.clientHeight > 10 ||
+ pc.scrollWidth - pc.clientWidth > 10
+ ) {
+ return;
+ }
- removeZoombox(gd);
- dragTail(zoomMode);
+ clearTimeout(redrawTimer);
- if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
- Lib.notifier('Double-click to
zoom back out', 'long');
- SHOWZOOMOUTTIP = false;
- }
+ var wheelDelta = -e.deltaY;
+ if (!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10;
+ if (!isFinite(wheelDelta)) {
+ Lib.log("Did not find wheel motion attributes: ", e);
+ return;
}
- function dragDone(dragged, numClicks) {
- var singleEnd = (ns + ew).length === 1;
- if(dragged) dragTail();
- else if(numClicks === 2 && !singleEnd) doubleClick();
- else if(numClicks === 1 && singleEnd) {
- var ax = ns ? ya[0] : xa[0],
- end = (ns === 's' || ew === 'w') ? 0 : 1,
- attrStr = ax._name + '.range[' + end + ']',
- initialText = getEndText(ax, end),
- hAlign = 'left',
- vAlign = 'middle';
-
- if(ax.fixedrange) return;
-
- if(ns) {
- vAlign = (ns === 'n') ? 'top' : 'bottom';
- if(ax.side === 'right') hAlign = 'right';
- }
- else if(ew === 'e') hAlign = 'right';
-
- dragger3
- .call(svgTextUtils.makeEditable, null, {
- immediate: true,
- background: fullLayout.paper_bgcolor,
- text: String(initialText),
- fill: ax.tickfont ? ax.tickfont.color : '#444',
- horizontalAlign: hAlign,
- verticalAlign: vAlign
- })
- .on('edit', function(text) {
- var v = ax.d2r(text);
- if(v !== undefined) {
- Plotly.relayout(gd, attrStr, v);
- }
- });
- }
+ var zoom = Math.exp((-Math.min(Math.max(wheelDelta, -20), 20)) / 100),
+ gbb = mainplot.draglayer
+ .select(".nsewdrag")
+ .node()
+ .getBoundingClientRect(),
+ xfrac = (e.clientX - gbb.left) / gbb.width,
+ vbx0 = scrollViewBox[0] + scrollViewBox[2] * xfrac,
+ yfrac = (gbb.bottom - e.clientY) / gbb.height,
+ vby0 = scrollViewBox[1] + scrollViewBox[3] * (1 - yfrac),
+ i;
+
+ function zoomWheelOneAxis(ax, centerFraction, zoom) {
+ if (ax.fixedrange) return;
+
+ var axRange = Lib.simpleMap(ax.range, ax.r2l),
+ v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction;
+ function doZoom(v) {
+ return ax.l2r(v0 + (v - v0) * zoom);
+ }
+ ax.range = axRange.map(doZoom);
}
- // scroll zoom, on all draggers except corners
- var scrollViewBox = [0, 0, pw, ph],
- // wait a little after scrolling before redrawing
- redrawTimer = null,
- REDRAWDELAY = constants.REDRAWDELAY,
- mainplot = plotinfo.mainplot ?
- fullLayout._plots[plotinfo.mainplot] : plotinfo;
-
- function zoomWheel(e) {
- // deactivate mousewheel scrolling on embedded graphs
- // devs can override this with layout._enablescrollzoom,
- // but _ ensures this setting won't leave their page
- if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) {
- return;
- }
-
- // If a transition is in progress, then disable any behavior:
- if(gd._transitioningWithDuration) {
- return Lib.pauseEvent(e);
- }
-
- var pc = gd.querySelector('.plotly');
-
- recomputeAxisLists();
-
- // if the plot has scrollbars (more than a tiny excess)
- // disable scrollzoom too.
- if(pc.scrollHeight - pc.clientHeight > 10 ||
- pc.scrollWidth - pc.clientWidth > 10) {
- return;
- }
-
- clearTimeout(redrawTimer);
-
- var wheelDelta = -e.deltaY;
- if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10;
- if(!isFinite(wheelDelta)) {
- Lib.log('Did not find wheel motion attributes: ', e);
- return;
- }
-
- var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 100),
- gbb = mainplot.draglayer.select('.nsewdrag')
- .node().getBoundingClientRect(),
- xfrac = (e.clientX - gbb.left) / gbb.width,
- vbx0 = scrollViewBox[0] + scrollViewBox[2] * xfrac,
- yfrac = (gbb.bottom - e.clientY) / gbb.height,
- vby0 = scrollViewBox[1] + scrollViewBox[3] * (1 - yfrac),
- i;
-
- function zoomWheelOneAxis(ax, centerFraction, zoom) {
- if(ax.fixedrange) return;
-
- var axRange = Lib.simpleMap(ax.range, ax.r2l),
- v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction;
- function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); }
- ax.range = axRange.map(doZoom);
- }
-
- if(ew) {
- for(i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom);
- scrollViewBox[2] *= zoom;
- scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac;
- }
- if(ns) {
- for(i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom);
- scrollViewBox[3] *= zoom;
- scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac);
- }
-
- // viewbox redraw at first
- updateSubplots(scrollViewBox);
- ticksAndAnnotations(ns, ew);
-
- // then replot after a delay to make sure
- // no more scrolling is coming
- redrawTimer = setTimeout(function() {
- scrollViewBox = [0, 0, pw, ph];
- dragTail();
- }, REDRAWDELAY);
-
- return Lib.pauseEvent(e);
+ if (ew) {
+ for (i = 0; i < xa.length; i++) {
+ zoomWheelOneAxis(xa[i], xfrac, zoom);
+ }
+ scrollViewBox[2] *= zoom;
+ scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac;
}
-
- // everything but the corners gets wheel zoom
- if(ns.length * ew.length !== 1) {
- // still seems to be some confusion about onwheel vs onmousewheel...
- if(dragger.onwheel !== undefined) dragger.onwheel = zoomWheel;
- else if(dragger.onmousewheel !== undefined) dragger.onmousewheel = zoomWheel;
+ if (ns) {
+ for (i = 0; i < ya.length; i++) {
+ zoomWheelOneAxis(ya[i], yfrac, zoom);
+ }
+ scrollViewBox[3] *= zoom;
+ scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac);
}
- // plotDrag: move the plot in response to a drag
- function plotDrag(dx, dy) {
- // If a transition is in progress, then disable any behavior:
- if(gd._transitioningWithDuration) {
- return;
- }
-
- recomputeAxisLists();
-
- function dragAxList(axList, pix) {
- for(var i = 0; i < axList.length; i++) {
- var axi = axList[i];
- if(!axi.fixedrange) {
- axi.range = [
- axi.l2r(axi._rl[0] - pix / axi._m),
- axi.l2r(axi._rl[1] - pix / axi._m)
- ];
- }
- }
- }
-
- if(xActive === 'ew' || yActive === 'ns') {
- if(xActive) dragAxList(xa, dx);
- if(yActive) dragAxList(ya, dy);
- updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
- ticksAndAnnotations(yActive, xActive);
- return;
- }
-
- // common transform for dragging one end of an axis
- // d>0 is compressing scale (cursor is over the plot,
- // the axis end should move with the cursor)
- // d<0 is expanding (cursor is off the plot, axis end moves
- // nonlinearly so you can expand far)
- function dZoom(d) {
- return 1 - ((d >= 0) ? Math.min(d, 0.9) :
- 1 / (1 / Math.max(d, -0.3) + 3.222));
- }
-
- // dz: set a new value for one end (0 or 1) of an axis array axArray,
- // and return a pixel shift for that end for the viewbox
- // based on pixel drag distance d
- // TODO: this makes (generally non-fatal) errors when you get
- // near floating point limits
- function dz(axArray, end, d) {
- var otherEnd = 1 - end,
- movedAx,
- newLinearizedEnd;
- for(var i = 0; i < axArray.length; i++) {
- var axi = axArray[i];
- if(axi.fixedrange) continue;
- movedAx = axi;
- newLinearizedEnd = axi._rl[otherEnd] +
- (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length);
- var newEnd = axi.l2r(newLinearizedEnd);
-
- // if l2r comes back false or undefined, it means we've dragged off
- // the end of valid ranges - so stop.
- if(newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd;
- }
- return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) /
- (movedAx._rl[end] - movedAx._rl[otherEnd]);
- }
-
- if(xActive === 'w') dx = dz(xa, 0, dx);
- else if(xActive === 'e') dx = dz(xa, 1, -dx);
- else if(!xActive) dx = 0;
-
- if(yActive === 'n') dy = dz(ya, 1, dy);
- else if(yActive === 's') dy = dz(ya, 0, -dy);
- else if(!yActive) dy = 0;
-
- updateSubplots([
- (xActive === 'w') ? dx : 0,
- (yActive === 'n') ? dy : 0,
- pw - dx,
- ph - dy
- ]);
- ticksAndAnnotations(yActive, xActive);
+ // viewbox redraw at first
+ updateSubplots(scrollViewBox);
+ ticksAndAnnotations(ns, ew);
+
+ // then replot after a delay to make sure
+ // no more scrolling is coming
+ redrawTimer = setTimeout(
+ function() {
+ scrollViewBox = [0, 0, pw, ph];
+ dragTail();
+ },
+ REDRAWDELAY
+ );
+
+ return Lib.pauseEvent(e);
+ }
+
+ // everything but the corners gets wheel zoom
+ if (ns.length * ew.length !== 1) {
+ // still seems to be some confusion about onwheel vs onmousewheel...
+ if (dragger.onwheel !== undefined) {
+ dragger.onwheel = zoomWheel;
+ } else if (dragger.onmousewheel !== undefined) {
+ dragger.onmousewheel = zoomWheel;
}
+ }
- function ticksAndAnnotations(ns, ew) {
- var activeAxIds = [],
- i;
-
- function pushActiveAxIds(axList) {
- for(i = 0; i < axList.length; i++) {
- if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id);
- }
- }
+ // plotDrag: move the plot in response to a drag
+ function plotDrag(dx, dy) {
+ // If a transition is in progress, then disable any behavior:
+ if (gd._transitioningWithDuration) {
+ return;
+ }
- if(ew) pushActiveAxIds(xa);
- if(ns) pushActiveAxIds(ya);
+ recomputeAxisLists();
- for(i = 0; i < activeAxIds.length; i++) {
- Axes.doTicks(gd, activeAxIds[i], true);
+ function dragAxList(axList, pix) {
+ for (var i = 0; i < axList.length; i++) {
+ var axi = axList[i];
+ if (!axi.fixedrange) {
+ axi.range = [
+ axi.l2r(axi._rl[0] - pix / axi._m),
+ axi.l2r(axi._rl[1] - pix / axi._m)
+ ];
}
+ }
+ }
- function redrawObjs(objArray, method) {
- for(i = 0; i < objArray.length; i++) {
- var obji = objArray[i];
+ if (xActive === "ew" || yActive === "ns") {
+ if (xActive) dragAxList(xa, dx);
+ if (yActive) dragAxList(ya, dy);
+ updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
+ ticksAndAnnotations(yActive, xActive);
+ return;
+ }
- if((ew && activeAxIds.indexOf(obji.xref) !== -1) ||
- (ns && activeAxIds.indexOf(obji.yref) !== -1)) {
- method(gd, i);
- }
- }
- }
+ // common transform for dragging one end of an axis
+ // d>0 is compressing scale (cursor is over the plot,
+ // the axis end should move with the cursor)
+ // d<0 is expanding (cursor is off the plot, axis end moves
+ // nonlinearly so you can expand far)
+ function dZoom(d) {
+ return 1 -
+ (d >= 0 ? Math.min(d, 0.9) : 1 / (1 / Math.max(d, -0.3) + 3.222));
+ }
- // annotations and shapes 'draw' method is slow,
- // use the finer-grained 'drawOne' method instead
+ // dz: set a new value for one end (0 or 1) of an axis array axArray,
+ // and return a pixel shift for that end for the viewbox
+ // based on pixel drag distance d
+ // TODO: this makes (generally non-fatal) errors when you get
+ // near floating point limits
+ function dz(axArray, end, d) {
+ var otherEnd = 1 - end, movedAx, newLinearizedEnd;
+ for (var i = 0; i < axArray.length; i++) {
+ var axi = axArray[i];
+ if (axi.fixedrange) continue;
+ movedAx = axi;
+ newLinearizedEnd = axi._rl[otherEnd] +
+ (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length);
+ var newEnd = axi.l2r(newLinearizedEnd);
+
+ // if l2r comes back false or undefined, it means we've dragged off
+ // the end of valid ranges - so stop.
+ if (newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd;
+ }
+ return movedAx._length *
+ (movedAx._rl[end] - newLinearizedEnd) /
+ (movedAx._rl[end] - movedAx._rl[otherEnd]);
+ }
- redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne'));
- redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne'));
- redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw'));
+ if (xActive === "w") dx = dz(xa, 0, dx);
+ else if (xActive === "e") dx = dz(xa, 1, -dx);
+ else if (!xActive) dx = 0;
+
+ if (yActive === "n") dy = dz(ya, 1, dy);
+ else if (yActive === "s") dy = dz(ya, 0, -dy);
+ else if (!yActive) dy = 0;
+
+ updateSubplots([
+ xActive === "w" ? dx : 0,
+ yActive === "n" ? dy : 0,
+ pw - dx,
+ ph - dy
+ ]);
+ ticksAndAnnotations(yActive, xActive);
+ }
+
+ function ticksAndAnnotations(ns, ew) {
+ var activeAxIds = [], i;
+
+ function pushActiveAxIds(axList) {
+ for (i = 0; i < axList.length; i++) {
+ if (!axList[i].fixedrange) activeAxIds.push(axList[i]._id);
+ }
}
- function doubleClick() {
- if(gd._transitioningWithDuration) return;
+ if (ew) pushActiveAxIds(xa);
+ if (ns) pushActiveAxIds(ya);
- var doubleClickConfig = gd._context.doubleClick,
- axList = (xActive ? xa : []).concat(yActive ? ya : []),
- attrs = {};
+ for (i = 0; i < activeAxIds.length; i++) {
+ Axes.doTicks(gd, activeAxIds[i], true);
+ }
- var ax, i, rangeInitial;
+ function redrawObjs(objArray, method) {
+ for (i = 0; i < objArray.length; i++) {
+ var obji = objArray[i];
- if(doubleClickConfig === 'autosize') {
- for(i = 0; i < axList.length; i++) {
- ax = axList[i];
- if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true;
- }
- }
- else if(doubleClickConfig === 'reset') {
- for(i = 0; i < axList.length; i++) {
- ax = axList[i];
-
- if(!ax._rangeInitial) {
- attrs[ax._name + '.autorange'] = true;
- }
- else {
- rangeInitial = ax._rangeInitial.slice();
- attrs[ax._name + '.range[0]'] = rangeInitial[0];
- attrs[ax._name + '.range[1]'] = rangeInitial[1];
- }
- }
+ if (
+ ew && activeAxIds.indexOf(obji.xref) !== -1 ||
+ ns && activeAxIds.indexOf(obji.yref) !== -1
+ ) {
+ method(gd, i);
}
- else if(doubleClickConfig === 'reset+autosize') {
- for(i = 0; i < axList.length; i++) {
- ax = axList[i];
-
- if(ax.fixedrange) continue;
- if(ax._rangeInitial === undefined ||
- ax.range[0] === ax._rangeInitial[0] &&
- ax.range[1] === ax._rangeInitial[1]
- ) {
- attrs[ax._name + '.autorange'] = true;
- }
- else {
- rangeInitial = ax._rangeInitial.slice();
- attrs[ax._name + '.range[0]'] = rangeInitial[0];
- attrs[ax._name + '.range[1]'] = rangeInitial[1];
- }
- }
- }
-
- gd.emit('plotly_doubleclick', null);
- Plotly.relayout(gd, attrs);
+ }
}
- // dragTail - finish a drag event with a redraw
- function dragTail(zoommode) {
- var attrs = {};
- // revert to the previous axis settings, then apply the new ones
- // through relayout - this lets relayout manage undo/redo
- for(var i = 0; i < allaxes.length; i++) {
- var axi = allaxes[i];
- if(zoommode && zoommode.indexOf(axi._id.charAt(0)) === -1) {
- continue;
- }
- if(axi._r[0] !== axi.range[0]) attrs[axi._name + '.range[0]'] = axi.range[0];
- if(axi._r[1] !== axi.range[1]) attrs[axi._name + '.range[1]'] = axi.range[1];
-
- axi.range = axi._r.slice();
- }
+ // annotations and shapes 'draw' method is slow,
+ // use the finer-grained 'drawOne' method instead
+ redrawObjs(
+ fullLayout.annotations || [],
+ Registry.getComponentMethod("annotations", "drawOne")
+ );
+ redrawObjs(
+ fullLayout.shapes || [],
+ Registry.getComponentMethod("shapes", "drawOne")
+ );
+ redrawObjs(
+ fullLayout.images || [],
+ Registry.getComponentMethod("images", "draw")
+ );
+ }
+
+ function doubleClick() {
+ if (gd._transitioningWithDuration) return;
+
+ var doubleClickConfig = gd._context.doubleClick,
+ axList = (xActive ? xa : []).concat(yActive ? ya : []),
+ attrs = {};
+
+ var ax, i, rangeInitial;
+
+ if (doubleClickConfig === "autosize") {
+ for (i = 0; i < axList.length; i++) {
+ ax = axList[i];
+ if (!ax.fixedrange) attrs[ax._name + ".autorange"] = true;
+ }
+ } else if (doubleClickConfig === "reset") {
+ for (i = 0; i < axList.length; i++) {
+ ax = axList[i];
+
+ if (!ax._rangeInitial) {
+ attrs[ax._name + ".autorange"] = true;
+ } else {
+ rangeInitial = ax._rangeInitial.slice();
+ attrs[ax._name + ".range[0]"] = rangeInitial[0];
+ attrs[ax._name + ".range[1]"] = rangeInitial[1];
+ }
+ }
+ } else if (doubleClickConfig === "reset+autosize") {
+ for (i = 0; i < axList.length; i++) {
+ ax = axList[i];
+
+ if (ax.fixedrange) continue;
+ if (
+ ax._rangeInitial === undefined ||
+ ax.range[0] === ax._rangeInitial[0] &&
+ ax.range[1] === ax._rangeInitial[1]
+ ) {
+ attrs[ax._name + ".autorange"] = true;
+ } else {
+ rangeInitial = ax._rangeInitial.slice();
+ attrs[ax._name + ".range[0]"] = rangeInitial[0];
+ attrs[ax._name + ".range[1]"] = rangeInitial[1];
+ }
+ }
+ }
- updateSubplots([0, 0, pw, ph]);
- Plotly.relayout(gd, attrs);
+ gd.emit("plotly_doubleclick", null);
+ Plotly.relayout(gd, attrs);
+ }
+
+ // dragTail - finish a drag event with a redraw
+ function dragTail(zoommode) {
+ var attrs = {};
+ // revert to the previous axis settings, then apply the new ones
+ // through relayout - this lets relayout manage undo/redo
+ for (var i = 0; i < allaxes.length; i++) {
+ var axi = allaxes[i];
+ if (zoommode && zoommode.indexOf(axi._id.charAt(0)) === -1) {
+ continue;
+ }
+ if (axi._r[0] !== axi.range[0]) {
+ attrs[axi._name + ".range[0]"] = axi.range[0];
+ }
+ if (axi._r[1] !== axi.range[1]) {
+ attrs[axi._name + ".range[1]"] = axi.range[1];
+ }
+
+ axi.range = axi._r.slice();
}
- // updateSubplots - find all plot viewboxes that should be
- // affected by this drag, and update them. look for all plots
- // sharing an affected axis (including the one being dragged)
- function updateSubplots(viewBox) {
- var j;
- var plotinfos = fullLayout._plots,
- subplots = Object.keys(plotinfos);
-
- for(var i = 0; i < subplots.length; i++) {
-
- var subplot = plotinfos[subplots[i]],
- xa2 = subplot.xaxis,
- ya2 = subplot.yaxis,
- editX = ew && !xa2.fixedrange,
- editY = ns && !ya2.fixedrange;
-
- if(editX) {
- var isInX = false;
- for(j = 0; j < xa.length; j++) {
- if(xa[j]._id === xa2._id) {
- isInX = true;
- break;
- }
- }
- editX = editX && isInX;
- }
-
- if(editY) {
- var isInY = false;
- for(j = 0; j < ya.length; j++) {
- if(ya[j]._id === ya2._id) {
- isInY = true;
- break;
- }
- }
- editY = editY && isInY;
- }
-
- var xScaleFactor = editX ? xa2._length / viewBox[2] : 1,
- yScaleFactor = editY ? ya2._length / viewBox[3] : 1;
-
- var clipDx = editX ? viewBox[0] : 0,
- clipDy = editY ? viewBox[1] : 0;
-
- var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0,
- fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0;
-
- var plotDx = xa2._offset - fracDx,
- plotDy = ya2._offset - fracDy;
-
- fullLayout._defs.selectAll('#' + subplot.clipId)
- .call(Drawing.setTranslate, clipDx, clipDy)
- .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
-
- subplot.plot
- .call(Drawing.setTranslate, plotDx, plotDy)
- .call(Drawing.setScale, xScaleFactor, yScaleFactor)
-
- // This is specifically directed at scatter traces, applying an inverse
- // scale to individual points to counteract the scale of the trace
- // as a whole:
- .select('.scatterlayer').selectAll('.points').selectAll('.point')
- .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor);
- }
+ updateSubplots([0, 0, pw, ph]);
+ Plotly.relayout(gd, attrs);
+ }
+
+ // updateSubplots - find all plot viewboxes that should be
+ // affected by this drag, and update them. look for all plots
+ // sharing an affected axis (including the one being dragged)
+ function updateSubplots(viewBox) {
+ var j;
+ var plotinfos = fullLayout._plots, subplots = Object.keys(plotinfos);
+
+ for (var i = 0; i < subplots.length; i++) {
+ var subplot = plotinfos[subplots[i]],
+ xa2 = subplot.xaxis,
+ ya2 = subplot.yaxis,
+ editX = ew && !xa2.fixedrange,
+ editY = ns && !ya2.fixedrange;
+
+ if (editX) {
+ var isInX = false;
+ for (j = 0; j < xa.length; j++) {
+ if (xa[j]._id === xa2._id) {
+ isInX = true;
+ break;
+ }
+ }
+ editX = editX && isInX;
+ }
+
+ if (editY) {
+ var isInY = false;
+ for (j = 0; j < ya.length; j++) {
+ if (ya[j]._id === ya2._id) {
+ isInY = true;
+ break;
+ }
+ }
+ editY = editY && isInY;
+ }
+
+ var xScaleFactor = editX ? xa2._length / viewBox[2] : 1,
+ yScaleFactor = editY ? ya2._length / viewBox[3] : 1;
+
+ var clipDx = editX ? viewBox[0] : 0, clipDy = editY ? viewBox[1] : 0;
+
+ var fracDx = editX ? viewBox[0] / viewBox[2] * xa2._length : 0,
+ fracDy = editY ? viewBox[1] / viewBox[3] * ya2._length : 0;
+
+ var plotDx = xa2._offset - fracDx, plotDy = ya2._offset - fracDy;
+
+ fullLayout._defs
+ .selectAll("#" + subplot.clipId)
+ .call(Drawing.setTranslate, clipDx, clipDy)
+ .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
+
+ subplot.plot
+ .call(Drawing.setTranslate, plotDx, plotDy)
+ .call(Drawing.setScale, xScaleFactor, yScaleFactor)
+ .select(".scatterlayer")
+ .selectAll(".points")
+ .selectAll(".point")
+ .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor);
}
+ }
- return dragger;
+ return dragger;
};
function getEndText(ax, end) {
- var initialVal = ax.range[end],
- diff = Math.abs(initialVal - ax.range[1 - end]),
- dig;
-
- // TODO: this should basically be ax.r2d but we're doing extra
- // rounding here... can we clean up at all?
- if(ax.type === 'date') {
- return initialVal;
- }
- else if(ax.type === 'log') {
- dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3;
- return d3.format('.' + dig + 'g')(Math.pow(10, initialVal));
- }
- else { // linear numeric (or category... but just show numbers here)
- dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) -
- Math.floor(Math.log(diff) / Math.LN10) + 4;
- return d3.format('.' + String(dig) + 'g')(initialVal);
- }
+ var initialVal = ax.range[end],
+ diff = Math.abs(initialVal - ax.range[1 - end]),
+ dig;
+
+ // TODO: this should basically be ax.r2d but we're doing extra
+ // rounding here... can we clean up at all?
+ if (ax.type === "date") {
+ return initialVal;
+ } else if (ax.type === "log") {
+ dig = Math.ceil(Math.max(0, (-Math.log(diff)) / Math.LN10)) + 3;
+ return d3.format("." + dig + "g")(Math.pow(10, initialVal));
+ } else {
+ // linear numeric (or category... but just show numbers here)
+ dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) -
+ Math.floor(Math.log(diff) / Math.LN10) +
+ 4;
+ return d3.format("." + String(dig) + "g")(initialVal);
+ }
}
function getDragCursor(nsew, dragmode) {
- if(!nsew) return 'pointer';
- if(nsew === 'nsew') {
- if(dragmode === 'pan') return 'move';
- return 'crosshair';
- }
- return nsew.toLowerCase() + '-resize';
+ if (!nsew) return "pointer";
+ if (nsew === "nsew") {
+ if (dragmode === "pan") return "move";
+ return "crosshair";
+ }
+ return nsew.toLowerCase() + "-resize";
}
function removeZoombox(gd) {
- d3.select(gd)
- .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
- .remove();
+ d3
+ .select(gd)
+ .selectAll(
+ ".zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners"
+ )
+ .remove();
}
function isSelectOrLasso(dragmode) {
- var modes = ['lasso', 'select'];
+ var modes = ["lasso", "select"];
- return modes.indexOf(dragmode) !== -1;
+ return modes.indexOf(dragmode) !== -1;
}
diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js
index 7fea9cb893e..ffcd6639b15 100644
--- a/src/plots/cartesian/graph_interact.js
+++ b/src/plots/cartesian/graph_interact.js
@@ -5,28 +5,24 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-
-
-'use strict';
-
-var d3 = require('d3');
-var tinycolor = require('tinycolor2');
-var isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-var Events = require('../../lib/events');
-var svgTextUtils = require('../../lib/svg_text_utils');
-var Color = require('../../components/color');
-var Drawing = require('../../components/drawing');
-var dragElement = require('../../components/dragelement');
-var overrideCursor = require('../../lib/override_cursor');
-var Registry = require('../../registry');
-
-var Axes = require('./axes');
-var constants = require('./constants');
-var dragBox = require('./dragbox');
-var layoutAttributes = require('../layout_attributes');
-
+"use strict";
+var d3 = require("d3");
+var tinycolor = require("tinycolor2");
+var isNumeric = require("fast-isnumeric");
+
+var Lib = require("../../lib");
+var Events = require("../../lib/events");
+var svgTextUtils = require("../../lib/svg_text_utils");
+var Color = require("../../components/color");
+var Drawing = require("../../components/drawing");
+var dragElement = require("../../components/dragelement");
+var overrideCursor = require("../../lib/override_cursor");
+var Registry = require("../../registry");
+
+var Axes = require("./axes");
+var constants = require("./constants");
+var dragBox = require("./dragbox");
+var layoutAttributes = require("../layout_attributes");
var fx = module.exports = {};
@@ -35,201 +31,271 @@ var fx = module.exports = {};
fx.unhover = dragElement.unhover;
fx.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) {
-
- function coerce(attr, dflt) {
- return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
- }
-
- coerce('dragmode');
-
- var hovermodeDflt;
- if(layoutOut._has('cartesian')) {
- // flag for 'horizontal' plots:
- // determines the state of the mode bar 'compare' hovermode button
- var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData);
- hovermodeDflt = isHoriz ? 'y' : 'x';
- }
- else hovermodeDflt = 'closest';
-
- coerce('hovermode', hovermodeDflt);
+ function coerce(attr, dflt) {
+ return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
+ }
+
+ coerce("dragmode");
+
+ var hovermodeDflt;
+ if (layoutOut._has("cartesian")) {
+ // flag for 'horizontal' plots:
+ // determines the state of the mode bar 'compare' hovermode button
+ var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData);
+ hovermodeDflt = isHoriz ? "y" : "x";
+ } else {
+ hovermodeDflt = "closest";
+ }
+
+ coerce("hovermode", hovermodeDflt);
};
fx.isHoriz = function(fullData) {
- var isHoriz = true;
+ var isHoriz = true;
- for(var i = 0; i < fullData.length; i++) {
- var trace = fullData[i];
+ for (var i = 0; i < fullData.length; i++) {
+ var trace = fullData[i];
- if(trace.orientation !== 'h') {
- isHoriz = false;
- break;
- }
+ if (trace.orientation !== "h") {
+ isHoriz = false;
+ break;
}
+ }
- return isHoriz;
+ return isHoriz;
};
fx.init = function(gd) {
- var fullLayout = gd._fullLayout;
-
- if(!fullLayout._has('cartesian') || gd._context.staticPlot) return;
-
- var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
- // sort overlays last, then by x axis number, then y axis number
- if((fullLayout._plots[a].mainplot && true) ===
- (fullLayout._plots[b].mainplot && true)) {
- var aParts = a.split('y'),
- bParts = b.split('y');
- return (aParts[0] === bParts[0]) ?
- (Number(aParts[1] || 1) - Number(bParts[1] || 1)) :
- (Number(aParts[0] || 1) - Number(bParts[0] || 1));
- }
- return fullLayout._plots[a].mainplot ? 1 : -1;
- });
-
- subplots.forEach(function(subplot) {
- var plotinfo = fullLayout._plots[subplot];
-
- if(!fullLayout._has('cartesian')) return;
-
- var xa = plotinfo.xaxis,
- ya = plotinfo.yaxis,
-
- // the y position of the main x axis line
- y0 = (xa._linepositions[subplot] || [])[3],
-
- // the x position of the main y axis line
- x0 = (ya._linepositions[subplot] || [])[3];
-
- var DRAGGERSIZE = constants.DRAGGERSIZE;
- if(isNumeric(y0) && xa.side === 'top') y0 -= DRAGGERSIZE;
- if(isNumeric(x0) && ya.side !== 'right') x0 -= DRAGGERSIZE;
-
- // main and corner draggers need not be repeated for
- // overlaid subplots - these draggers drag them all
- if(!plotinfo.mainplot) {
- // main dragger goes over the grids and data, so we use its
- // mousemove events for all data hover effects
- var maindrag = dragBox(gd, plotinfo, 0, 0,
- xa._length, ya._length, 'ns', 'ew');
-
- maindrag.onmousemove = function(evt) {
- fx.hover(gd, evt, subplot);
- fullLayout._lasthover = maindrag;
- fullLayout._hoversubplot = subplot;
- };
-
- /*
+ var fullLayout = gd._fullLayout;
+
+ if (!fullLayout._has("cartesian") || gd._context.staticPlot) return;
+
+ var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
+ // sort overlays last, then by x axis number, then y axis number
+ if (
+ (fullLayout._plots[a].mainplot && true) ===
+ (fullLayout._plots[b].mainplot && true)
+ ) {
+ var aParts = a.split("y"), bParts = b.split("y");
+ return aParts[0] === bParts[0]
+ ? Number(aParts[1] || 1) - Number(bParts[1] || 1)
+ : Number(aParts[0] || 1) - Number(bParts[0] || 1);
+ }
+ return fullLayout._plots[a].mainplot ? 1 : -1;
+ });
+
+ subplots.forEach(function(subplot) {
+ var plotinfo = fullLayout._plots[subplot];
+
+ if (!fullLayout._has("cartesian")) return;
+
+ var xa = plotinfo.xaxis,
+ ya = plotinfo.yaxis,
+ // the y position of the main x axis line
+ y0 = (xa._linepositions[subplot] || [])[3],
+ // the x position of the main y axis line
+ x0 = (ya._linepositions[subplot] || [])[3];
+
+ var DRAGGERSIZE = constants.DRAGGERSIZE;
+ if (isNumeric(y0) && xa.side === "top") y0 -= DRAGGERSIZE;
+ if (isNumeric(x0) && ya.side !== "right") x0 -= DRAGGERSIZE;
+
+ // main and corner draggers need not be repeated for
+ // overlaid subplots - these draggers drag them all
+ if (!plotinfo.mainplot) {
+ // main dragger goes over the grids and data, so we use its
+ // mousemove events for all data hover effects
+ var maindrag = dragBox(
+ gd,
+ plotinfo,
+ 0,
+ 0,
+ xa._length,
+ ya._length,
+ "ns",
+ "ew"
+ );
+
+ maindrag.onmousemove = function(evt) {
+ fx.hover(gd, evt, subplot);
+ fullLayout._lasthover = maindrag;
+ fullLayout._hoversubplot = subplot;
+ };
+
+ /*
* IMPORTANT:
* We must check for the presence of the drag cover here.
* If we don't, a 'mouseout' event is triggered on the
* maindrag before each 'click' event, which has the effect
* of clearing the hoverdata; thus, cancelling the click event.
*/
- maindrag.onmouseout = function(evt) {
- if(gd._dragging) return;
-
- dragElement.unhover(gd, evt);
- };
-
- maindrag.onclick = function(evt) {
- fx.click(gd, evt);
- };
-
- // corner draggers
- dragBox(gd, plotinfo, -DRAGGERSIZE, -DRAGGERSIZE,
- DRAGGERSIZE, DRAGGERSIZE, 'n', 'w');
- dragBox(gd, plotinfo, xa._length, -DRAGGERSIZE,
- DRAGGERSIZE, DRAGGERSIZE, 'n', 'e');
- dragBox(gd, plotinfo, -DRAGGERSIZE, ya._length,
- DRAGGERSIZE, DRAGGERSIZE, 's', 'w');
- dragBox(gd, plotinfo, xa._length, ya._length,
- DRAGGERSIZE, DRAGGERSIZE, 's', 'e');
- }
+ maindrag.onmouseout = function(evt) {
+ if (gd._dragging) return;
- // x axis draggers - if you have overlaid plots,
- // these drag each axis separately
- if(isNumeric(y0)) {
- if(xa.anchor === 'free') y0 -= fullLayout._size.h * (1 - ya.domain[1]);
- dragBox(gd, plotinfo, xa._length * 0.1, y0,
- xa._length * 0.8, DRAGGERSIZE, '', 'ew');
- dragBox(gd, plotinfo, 0, y0,
- xa._length * 0.1, DRAGGERSIZE, '', 'w');
- dragBox(gd, plotinfo, xa._length * 0.9, y0,
- xa._length * 0.1, DRAGGERSIZE, '', 'e');
- }
- // y axis draggers
- if(isNumeric(x0)) {
- if(ya.anchor === 'free') x0 -= fullLayout._size.w * xa.domain[0];
- dragBox(gd, plotinfo, x0, ya._length * 0.1,
- DRAGGERSIZE, ya._length * 0.8, 'ns', '');
- dragBox(gd, plotinfo, x0, ya._length * 0.9,
- DRAGGERSIZE, ya._length * 0.1, 's', '');
- dragBox(gd, plotinfo, x0, 0,
- DRAGGERSIZE, ya._length * 0.1, 'n', '');
- }
- });
-
- // In case you mousemove over some hovertext, send it to fx.hover too
- // we do this so that we can put the hover text in front of everything,
- // but still be able to interact with everything as if it isn't there
- var hoverLayer = fullLayout._hoverlayer.node();
+ dragElement.unhover(gd, evt);
+ };
- hoverLayer.onmousemove = function(evt) {
- evt.target = fullLayout._lasthover;
- fx.hover(gd, evt, fullLayout._hoversubplot);
- };
-
- hoverLayer.onclick = function(evt) {
- evt.target = fullLayout._lasthover;
+ maindrag.onclick = function(evt) {
fx.click(gd, evt);
- };
+ };
+
+ // corner draggers
+ dragBox(
+ gd,
+ plotinfo,
+ -DRAGGERSIZE,
+ -DRAGGERSIZE,
+ DRAGGERSIZE,
+ DRAGGERSIZE,
+ "n",
+ "w"
+ );
+ dragBox(
+ gd,
+ plotinfo,
+ xa._length,
+ -DRAGGERSIZE,
+ DRAGGERSIZE,
+ DRAGGERSIZE,
+ "n",
+ "e"
+ );
+ dragBox(
+ gd,
+ plotinfo,
+ -DRAGGERSIZE,
+ ya._length,
+ DRAGGERSIZE,
+ DRAGGERSIZE,
+ "s",
+ "w"
+ );
+ dragBox(
+ gd,
+ plotinfo,
+ xa._length,
+ ya._length,
+ DRAGGERSIZE,
+ DRAGGERSIZE,
+ "s",
+ "e"
+ );
+ }
- // also delegate mousedowns... TODO: does this actually work?
- hoverLayer.onmousedown = function(evt) {
- fullLayout._lasthover.onmousedown(evt);
- };
+ // x axis draggers - if you have overlaid plots,
+ // these drag each axis separately
+ if (isNumeric(y0)) {
+ if (xa.anchor === "free") y0 -= fullLayout._size.h * (1 - ya.domain[1]);
+ dragBox(
+ gd,
+ plotinfo,
+ xa._length * 0.1,
+ y0,
+ xa._length * 0.8,
+ DRAGGERSIZE,
+ "",
+ "ew"
+ );
+ dragBox(gd, plotinfo, 0, y0, xa._length * 0.1, DRAGGERSIZE, "", "w");
+ dragBox(
+ gd,
+ plotinfo,
+ xa._length * 0.9,
+ y0,
+ xa._length * 0.1,
+ DRAGGERSIZE,
+ "",
+ "e"
+ );
+ }
+ // y axis draggers
+ if (isNumeric(x0)) {
+ if (ya.anchor === "free") x0 -= fullLayout._size.w * xa.domain[0];
+ dragBox(
+ gd,
+ plotinfo,
+ x0,
+ ya._length * 0.1,
+ DRAGGERSIZE,
+ ya._length * 0.8,
+ "ns",
+ ""
+ );
+ dragBox(
+ gd,
+ plotinfo,
+ x0,
+ ya._length * 0.9,
+ DRAGGERSIZE,
+ ya._length * 0.1,
+ "s",
+ ""
+ );
+ dragBox(gd, plotinfo, x0, 0, DRAGGERSIZE, ya._length * 0.1, "n", "");
+ }
+ });
+
+ // In case you mousemove over some hovertext, send it to fx.hover too
+ // we do this so that we can put the hover text in front of everything,
+ // but still be able to interact with everything as if it isn't there
+ var hoverLayer = fullLayout._hoverlayer.node();
+
+ hoverLayer.onmousemove = function(evt) {
+ evt.target = fullLayout._lasthover;
+ fx.hover(gd, evt, fullLayout._hoversubplot);
+ };
+
+ hoverLayer.onclick = function(evt) {
+ evt.target = fullLayout._lasthover;
+ fx.click(gd, evt);
+ };
+
+ // also delegate mousedowns... TODO: does this actually work?
+ hoverLayer.onmousedown = function(evt) {
+ fullLayout._lasthover.onmousedown(evt);
+ };
};
// hover labels for multiple horizontal bars get tilted by some angle,
// then need to be offset differently if they overlap
var YANGLE = constants.YANGLE,
- YA_RADIANS = Math.PI * YANGLE / 180,
-
- // expansion of projected height
- YFACTOR = 1 / Math.sin(YA_RADIANS),
-
- // to make the appropriate post-rotation x offset,
- // you need both x and y offsets
- YSHIFTX = Math.cos(YA_RADIANS),
- YSHIFTY = Math.sin(YA_RADIANS);
+ YA_RADIANS = Math.PI * YANGLE / 180,
+ // expansion of projected height
+ YFACTOR = 1 / Math.sin(YA_RADIANS),
+ // to make the appropriate post-rotation x offset,
+ // you need both x and y offsets
+ YSHIFTX = Math.cos(YA_RADIANS),
+ YSHIFTY = Math.sin(YA_RADIANS);
// convenience functions for mapping all relevant axes
function flat(subplots, v) {
- var out = [];
- for(var i = subplots.length; i > 0; i--) out.push(v);
- return out;
+ var out = [];
+ for (var i = subplots.length; i > 0; i--) {
+ out.push(v);
+ }
+ return out;
}
function p2c(axArray, v) {
- var out = [];
- for(var i = 0; i < axArray.length; i++) out.push(axArray[i].p2c(v));
- return out;
+ var out = [];
+ for (var i = 0; i < axArray.length; i++) {
+ out.push(axArray[i].p2c(v));
+ }
+ return out;
}
function quadrature(dx, dy) {
- return function(di) {
- var x = dx(di),
- y = dy(di);
- return Math.sqrt(x * x + y * y);
- };
+ return function(di) {
+ var x = dx(di), y = dy(di);
+ return Math.sqrt(x * x + y * y);
+ };
}
// size and display constants for hover text
var HOVERARROWSIZE = constants.HOVERARROWSIZE,
- HOVERTEXTPAD = constants.HOVERTEXTPAD,
- HOVERFONTSIZE = constants.HOVERFONTSIZE,
- HOVERFONT = constants.HOVERFONT;
+ HOVERTEXTPAD = constants.HOVERTEXTPAD,
+ HOVERFONTSIZE = constants.HOVERFONTSIZE,
+ HOVERFONT = constants.HOVERFONT;
// fx.hover: highlight data on hover
// evt can be a mousemove event, or an object with data about what points
@@ -256,830 +322,901 @@ var HOVERARROWSIZE = constants.HOVERARROWSIZE,
// We wrap the hovers in a timer, to limit their frequency.
// The actual rendering is done by private functions
// hover() and unhover().
-
fx.hover = function(gd, evt, subplot) {
- if(typeof gd === 'string') gd = document.getElementById(gd);
- if(gd._lastHoverTime === undefined) gd._lastHoverTime = 0;
-
- // If we have an update queued, discard it now
- if(gd._hoverTimer !== undefined) {
- clearTimeout(gd._hoverTimer);
- gd._hoverTimer = undefined;
- }
- // Is it more than 100ms since the last update? If so, force
- // an update now (synchronously) and exit
- if(Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) {
- hover(gd, evt, subplot);
- gd._lastHoverTime = Date.now();
- return;
- }
- // Queue up the next hover for 100ms from now (if no further events)
- gd._hoverTimer = setTimeout(function() {
- hover(gd, evt, subplot);
- gd._lastHoverTime = Date.now();
- gd._hoverTimer = undefined;
- }, constants.HOVERMINTIME);
+ if (typeof gd === "string") gd = document.getElementById(gd);
+ if (gd._lastHoverTime === undefined) gd._lastHoverTime = 0;
+
+ // If we have an update queued, discard it now
+ if (gd._hoverTimer !== undefined) {
+ clearTimeout(gd._hoverTimer);
+ gd._hoverTimer = undefined;
+ }
+ // Is it more than 100ms since the last update? If so, force
+ // an update now (synchronously) and exit
+ if (Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) {
+ hover(gd, evt, subplot);
+ gd._lastHoverTime = Date.now();
+ return;
+ }
+ // Queue up the next hover for 100ms from now (if no further events)
+ gd._hoverTimer = setTimeout(
+ function() {
+ hover(gd, evt, subplot);
+ gd._lastHoverTime = Date.now();
+ gd._hoverTimer = undefined;
+ },
+ constants.HOVERMINTIME
+ );
};
// The actual implementation is here:
-
function hover(gd, evt, subplot) {
- if(subplot === 'pie') {
- gd.emit('plotly_hover', {
- points: [evt]
- });
- return;
- }
-
- if(!subplot) subplot = 'xy';
+ if (subplot === "pie") {
+ gd.emit("plotly_hover", { points: [evt] });
+ return;
+ }
- // if the user passed in an array of subplots,
- // use those instead of finding overlayed plots
- var subplots = Array.isArray(subplot) ? subplot : [subplot];
+ if (!subplot) subplot = "xy";
- var fullLayout = gd._fullLayout,
- plots = fullLayout._plots || [],
- plotinfo = plots[subplot];
+ // if the user passed in an array of subplots,
+ // use those instead of finding overlayed plots
+ var subplots = Array.isArray(subplot) ? subplot : [subplot];
- // list of all overlaid subplots to look at
- if(plotinfo) {
- var overlayedSubplots = plotinfo.overlays.map(function(pi) {
- return pi.id;
- });
+ var fullLayout = gd._fullLayout,
+ plots = fullLayout._plots || [],
+ plotinfo = plots[subplot];
- subplots = subplots.concat(overlayedSubplots);
- }
-
- var len = subplots.length,
- xaArray = new Array(len),
- yaArray = new Array(len);
-
- for(var i = 0; i < len; i++) {
- var spId = subplots[i];
+ // list of all overlaid subplots to look at
+ if (plotinfo) {
+ var overlayedSubplots = plotinfo.overlays.map(function(pi) {
+ return pi.id;
+ });
- // 'cartesian' case
- var plotObj = plots[spId];
- if(plotObj) {
+ subplots = subplots.concat(overlayedSubplots);
+ }
- // TODO make sure that fullLayout_plots axis refs
- // get updated properly so that we don't have
- // to use Axes.getFromId in general.
+ var len = subplots.length, xaArray = new Array(len), yaArray = new Array(len);
- xaArray[i] = Axes.getFromId(gd, plotObj.xaxis._id);
- yaArray[i] = Axes.getFromId(gd, plotObj.yaxis._id);
- continue;
- }
+ for (var i = 0; i < len; i++) {
+ var spId = subplots[i];
- // other subplot types
- var _subplot = fullLayout[spId]._subplot;
- xaArray[i] = _subplot.xaxis;
- yaArray[i] = _subplot.yaxis;
+ // 'cartesian' case
+ var plotObj = plots[spId];
+ if (plotObj) {
+ // TODO make sure that fullLayout_plots axis refs
+ // get updated properly so that we don't have
+ // to use Axes.getFromId in general.
+ xaArray[i] = Axes.getFromId(gd, plotObj.xaxis._id);
+ yaArray[i] = Axes.getFromId(gd, plotObj.yaxis._id);
+ continue;
}
- var hovermode = evt.hovermode || fullLayout.hovermode;
-
- if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata ||
- gd.querySelector('.zoombox') || gd._dragging) {
- return dragElement.unhoverRaw(gd, evt);
+ // other subplot types
+ var _subplot = fullLayout[spId]._subplot;
+ xaArray[i] = _subplot.xaxis;
+ yaArray[i] = _subplot.yaxis;
+ }
+
+ var hovermode = evt.hovermode || fullLayout.hovermode;
+
+ if (
+ ["x", "y", "closest"].indexOf(hovermode) === -1 ||
+ !gd.calcdata ||
+ gd.querySelector(".zoombox") ||
+ gd._dragging
+ ) {
+ return dragElement.unhoverRaw(gd, evt);
+ }
+
+ // hoverData: the set of candidate points we've found to highlight
+ var hoverData = [],
+ // searchData: the data to search in. Mostly this is just a copy of
+ // gd.calcdata, filtered to the subplot and overlays we're on
+ // but if a point array is supplied it will be a mapping
+ // of indicated curves
+ searchData = [],
+ // [x|y]valArray: the axis values of the hover event
+ // mapped onto each of the currently selected overlaid subplots
+ xvalArray,
+ yvalArray,
+ // used in loops
+ itemnum,
+ curvenum,
+ cd,
+ trace,
+ subplotId,
+ subploti,
+ mode,
+ xval,
+ yval,
+ pointData,
+ closedataPreviousLength;
+
+ // Figure out what we're hovering on:
+ // mouse location or user-supplied data
+ if (Array.isArray(evt)) {
+ // user specified an array of points to highlight
+ hovermode = "array";
+ for (itemnum = 0; itemnum < evt.length; itemnum++) {
+ cd = gd.calcdata[evt[itemnum].curveNumber || 0];
+ if (cd[0].trace.hoverinfo !== "skip") {
+ searchData.push(cd);
+ }
}
-
- // hoverData: the set of candidate points we've found to highlight
- var hoverData = [],
-
- // searchData: the data to search in. Mostly this is just a copy of
- // gd.calcdata, filtered to the subplot and overlays we're on
- // but if a point array is supplied it will be a mapping
- // of indicated curves
- searchData = [],
-
- // [x|y]valArray: the axis values of the hover event
- // mapped onto each of the currently selected overlaid subplots
- xvalArray,
- yvalArray,
-
- // used in loops
- itemnum,
- curvenum,
- cd,
- trace,
- subplotId,
- subploti,
- mode,
- xval,
- yval,
- pointData,
- closedataPreviousLength;
-
- // Figure out what we're hovering on:
- // mouse location or user-supplied data
-
- if(Array.isArray(evt)) {
- // user specified an array of points to highlight
- hovermode = 'array';
- for(itemnum = 0; itemnum < evt.length; itemnum++) {
- cd = gd.calcdata[evt[itemnum].curveNumber||0];
- if(cd[0].trace.hoverinfo !== 'skip') {
- searchData.push(cd);
- }
- }
+ } else {
+ for (curvenum = 0; curvenum < gd.calcdata.length; curvenum++) {
+ cd = gd.calcdata[curvenum];
+ trace = cd[0].trace;
+ if (
+ trace.hoverinfo !== "skip" && subplots.indexOf(getSubplot(trace)) !== -1
+ ) {
+ searchData.push(cd);
+ }
}
- else {
- for(curvenum = 0; curvenum < gd.calcdata.length; curvenum++) {
- cd = gd.calcdata[curvenum];
- trace = cd[0].trace;
- if(trace.hoverinfo !== 'skip' && subplots.indexOf(getSubplot(trace)) !== -1) {
- searchData.push(cd);
- }
- }
-
- // [x|y]px: the pixels (from top left) of the mouse location
- // on the currently selected plot area
- var xpx, ypx;
- // mouse event? ie is there a target element with
- // clientX and clientY values?
- if(evt.target && ('clientX' in evt) && ('clientY' in evt)) {
-
- // fire the beforehover event and quit if it returns false
- // note that we're only calling this on real mouse events, so
- // manual calls to fx.hover will always run.
- if(Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) {
- return;
- }
+ // [x|y]px: the pixels (from top left) of the mouse location
+ // on the currently selected plot area
+ var xpx, ypx;
+
+ // mouse event? ie is there a target element with
+ // clientX and clientY values?
+ if (evt.target && "clientX" in evt && "clientY" in evt) {
+ // fire the beforehover event and quit if it returns false
+ // note that we're only calling this on real mouse events, so
+ // manual calls to fx.hover will always run.
+ if (Events.triggerHandler(gd, "plotly_beforehover", evt) === false) {
+ return;
+ }
- var dbb = evt.target.getBoundingClientRect();
+ var dbb = evt.target.getBoundingClientRect();
- xpx = evt.clientX - dbb.left;
- ypx = evt.clientY - dbb.top;
+ xpx = evt.clientX - dbb.left;
+ ypx = evt.clientY - dbb.top;
- // in case hover was called from mouseout into hovertext,
- // it's possible you're not actually over the plot anymore
- if(xpx < 0 || xpx > dbb.width || ypx < 0 || ypx > dbb.height) {
- return dragElement.unhoverRaw(gd, evt);
- }
- }
- else {
- if('xpx' in evt) xpx = evt.xpx;
- else xpx = xaArray[0]._length / 2;
+ // in case hover was called from mouseout into hovertext,
+ // it's possible you're not actually over the plot anymore
+ if (xpx < 0 || xpx > dbb.width || ypx < 0 || ypx > dbb.height) {
+ return dragElement.unhoverRaw(gd, evt);
+ }
+ } else {
+ if ("xpx" in evt) xpx = evt.xpx;
+ else xpx = xaArray[0]._length / 2;
- if('ypx' in evt) ypx = evt.ypx;
- else ypx = yaArray[0]._length / 2;
- }
+ if ("ypx" in evt) ypx = evt.ypx;
+ else ypx = yaArray[0]._length / 2;
+ }
- if('xval' in evt) xvalArray = flat(subplots, evt.xval);
- else xvalArray = p2c(xaArray, xpx);
+ if ("xval" in evt) xvalArray = flat(subplots, evt.xval);
+ else xvalArray = p2c(xaArray, xpx);
- if('yval' in evt) yvalArray = flat(subplots, evt.yval);
- else yvalArray = p2c(yaArray, ypx);
+ if ("yval" in evt) yvalArray = flat(subplots, evt.yval);
+ else yvalArray = p2c(yaArray, ypx);
- if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) {
- Lib.warn('Fx.hover failed', evt, gd);
- return dragElement.unhoverRaw(gd, evt);
- }
+ if (!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) {
+ Lib.warn("Fx.hover failed", evt, gd);
+ return dragElement.unhoverRaw(gd, evt);
}
+ }
+
+ // the pixel distance to beat as a matching point
+ // in 'x' or 'y' mode this resets for each trace
+ var distance = Infinity;
+
+ // find the closest point in each trace
+ // this is minimum dx and/or dy, depending on mode
+ // and the pixel position for the label (labelXpx, labelYpx)
+ for (curvenum = 0; curvenum < searchData.length; curvenum++) {
+ cd = searchData[curvenum];
+
+ // filter out invisible or broken data
+ if (!cd || !cd[0] || !cd[0].trace || cd[0].trace.visible !== true) continue;
+
+ trace = cd[0].trace;
+ subplotId = getSubplot(trace);
+ subploti = subplots.indexOf(subplotId);
+
+ // within one trace mode can sometimes be overridden
+ mode = hovermode;
+
+ // container for new point, also used to pass info into module.hoverPoints
+ pointData = {
+ // trace properties
+ cd: cd,
+ trace: trace,
+ xa: xaArray[subploti],
+ ya: yaArray[subploti],
+ name: (
+ gd.data.length > 1 || trace.hoverinfo.indexOf("name") !== -1
+ ? trace.name
+ : undefined
+ ),
+ // point properties - override all of these
+ index: false,
+ // point index in trace - only used by plotly.js hoverdata consumers
+ distance: Math.min(distance, constants.MAXDIST),
+ // pixel distance or pseudo-distance
+ color: Color.defaultLine,
+ // trace color
+ x0: undefined,
+ x1: undefined,
+ y0: undefined,
+ y1: undefined,
+ xLabelVal: undefined,
+ yLabelVal: undefined,
+ zLabelVal: undefined,
+ text: undefined
+ };
- // the pixel distance to beat as a matching point
- // in 'x' or 'y' mode this resets for each trace
- var distance = Infinity;
-
- // find the closest point in each trace
- // this is minimum dx and/or dy, depending on mode
- // and the pixel position for the label (labelXpx, labelYpx)
- for(curvenum = 0; curvenum < searchData.length; curvenum++) {
- cd = searchData[curvenum];
-
- // filter out invisible or broken data
- if(!cd || !cd[0] || !cd[0].trace || cd[0].trace.visible !== true) continue;
-
- trace = cd[0].trace;
- subplotId = getSubplot(trace);
- subploti = subplots.indexOf(subplotId);
-
- // within one trace mode can sometimes be overridden
- mode = hovermode;
-
- // container for new point, also used to pass info into module.hoverPoints
- pointData = {
- // trace properties
- cd: cd,
- trace: trace,
- xa: xaArray[subploti],
- ya: yaArray[subploti],
- name: (gd.data.length > 1 || trace.hoverinfo.indexOf('name') !== -1) ? trace.name : undefined,
- // point properties - override all of these
- index: false, // point index in trace - only used by plotly.js hoverdata consumers
- distance: Math.min(distance, constants.MAXDIST), // pixel distance or pseudo-distance
- color: Color.defaultLine, // trace color
- x0: undefined,
- x1: undefined,
- y0: undefined,
- y1: undefined,
- xLabelVal: undefined,
- yLabelVal: undefined,
- zLabelVal: undefined,
- text: undefined
- };
-
- // add ref to subplot object (non-cartesian case)
- if(fullLayout[subplotId]) {
- pointData.subplot = fullLayout[subplotId]._subplot;
- }
-
- closedataPreviousLength = hoverData.length;
-
- // for a highlighting array, figure out what
- // we're searching for with this element
- if(mode === 'array') {
- var selection = evt[curvenum];
- if('pointNumber' in selection) {
- pointData.index = selection.pointNumber;
- mode = 'closest';
- }
- else {
- mode = '';
- if('xval' in selection) {
- xval = selection.xval;
- mode = 'x';
- }
- if('yval' in selection) {
- yval = selection.yval;
- mode = mode ? 'closest' : 'y';
- }
- }
- }
- else {
- xval = xvalArray[subploti];
- yval = yvalArray[subploti];
- }
+ // add ref to subplot object (non-cartesian case)
+ if (fullLayout[subplotId]) {
+ pointData.subplot = fullLayout[subplotId]._subplot;
+ }
- // Now find the points.
- if(trace._module && trace._module.hoverPoints) {
- var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode);
- if(newPoints) {
- var newPoint;
- for(var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) {
- newPoint = newPoints[newPointNum];
- if(isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) {
- hoverData.push(cleanPoint(newPoint, hovermode));
- }
- }
- }
+ closedataPreviousLength = hoverData.length;
+
+ // for a highlighting array, figure out what
+ // we're searching for with this element
+ if (mode === "array") {
+ var selection = evt[curvenum];
+ if ("pointNumber" in selection) {
+ pointData.index = selection.pointNumber;
+ mode = "closest";
+ } else {
+ mode = "";
+ if ("xval" in selection) {
+ xval = selection.xval;
+ mode = "x";
}
- else {
- Lib.log('Unrecognized trace type in hover:', trace);
- }
-
- // in closest mode, remove any existing (farther) points
- // and don't look any farther than this latest point (or points, if boxes)
- if(hovermode === 'closest' && hoverData.length > closedataPreviousLength) {
- hoverData.splice(0, closedataPreviousLength);
- distance = hoverData[0].distance;
+ if ("yval" in selection) {
+ yval = selection.yval;
+ mode = mode ? "closest" : "y";
}
+ }
+ } else {
+ xval = xvalArray[subploti];
+ yval = yvalArray[subploti];
}
- // nothing left: remove all labels and quit
- if(hoverData.length === 0) return dragElement.unhoverRaw(gd, evt);
-
- // if there's more than one horz bar trace,
- // rotate the labels so they don't overlap
- var rotateLabels = hovermode === 'y' && searchData.length > 1;
-
- hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });
-
- var bgColor = Color.combine(
- fullLayout.plot_bgcolor || Color.background,
- fullLayout.paper_bgcolor
- );
-
- var labelOpts = {
- hovermode: hovermode,
- rotateLabels: rotateLabels,
- bgColor: bgColor,
- container: fullLayout._hoverlayer,
- outerContainer: fullLayout._paperdiv
- };
- var hoverLabels = createHoverText(hoverData, labelOpts);
-
- hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya');
-
- alignHoverText(hoverLabels, rotateLabels);
-
- // lastly, emit custom hover/unhover events
- var oldhoverdata = gd._hoverdata,
- newhoverdata = [];
-
- // pull out just the data that's useful to
- // other people and send it to the event
- for(itemnum = 0; itemnum < hoverData.length; itemnum++) {
- var pt = hoverData[itemnum];
-
- var out = {
- data: pt.trace._input,
- fullData: pt.trace,
- curveNumber: pt.trace.index,
- pointNumber: pt.index
- };
-
- if(pt.trace._module.eventData) out = pt.trace._module.eventData(out, pt);
- else {
- out.x = pt.xVal;
- out.y = pt.yVal;
- out.xaxis = pt.xa;
- out.yaxis = pt.ya;
-
- if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
+ // Now find the points.
+ if (trace._module && trace._module.hoverPoints) {
+ var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode);
+ if (newPoints) {
+ var newPoint;
+ for (
+ var newPointNum = 0;
+ newPointNum < newPoints.length;
+ newPointNum++
+ ) {
+ newPoint = newPoints[newPointNum];
+ if (isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) {
+ hoverData.push(cleanPoint(newPoint, hovermode));
+ }
}
-
- newhoverdata.push(out);
+ }
+ } else {
+ Lib.log("Unrecognized trace type in hover:", trace);
}
- gd._hoverdata = newhoverdata;
-
- // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
- // we should improve the "fx" API so other plots can use it without these hack.
- if(evt.target && evt.target.tagName) {
- var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata);
- overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : '');
+ // in closest mode, remove any existing (farther) points
+ // and don't look any farther than this latest point (or points, if boxes)
+ if (hovermode === "closest" && hoverData.length > closedataPreviousLength) {
+ hoverData.splice(0, closedataPreviousLength);
+ distance = hoverData[0].distance;
}
+ }
+
+ // nothing left: remove all labels and quit
+ if (hoverData.length === 0) return dragElement.unhoverRaw(gd, evt);
+
+ // if there's more than one horz bar trace,
+ // rotate the labels so they don't overlap
+ var rotateLabels = hovermode === "y" && searchData.length > 1;
+
+ hoverData.sort(function(d1, d2) {
+ return d1.distance - d2.distance;
+ });
+
+ var bgColor = Color.combine(
+ fullLayout.plot_bgcolor || Color.background,
+ fullLayout.paper_bgcolor
+ );
+
+ var labelOpts = {
+ hovermode: hovermode,
+ rotateLabels: rotateLabels,
+ bgColor: bgColor,
+ container: fullLayout._hoverlayer,
+ outerContainer: fullLayout._paperdiv
+ };
+ var hoverLabels = createHoverText(hoverData, labelOpts);
+
+ hoverAvoidOverlaps(hoverData, rotateLabels ? "xa" : "ya");
+
+ alignHoverText(hoverLabels, rotateLabels);
+
+ // lastly, emit custom hover/unhover events
+ var oldhoverdata = gd._hoverdata, newhoverdata = [];
+
+ // pull out just the data that's useful to
+ // other people and send it to the event
+ for (itemnum = 0; itemnum < hoverData.length; itemnum++) {
+ var pt = hoverData[itemnum];
+
+ var out = {
+ data: pt.trace._input,
+ fullData: pt.trace,
+ curveNumber: pt.trace.index,
+ pointNumber: pt.index
+ };
- if(!hoverChanged(gd, evt, oldhoverdata)) return;
+ if (pt.trace._module.eventData) {
+ out = pt.trace._module.eventData(out, pt);
+ } else {
+ out.x = pt.xVal;
+ out.y = pt.yVal;
+ out.xaxis = pt.xa;
+ out.yaxis = pt.ya;
- if(oldhoverdata) {
- gd.emit('plotly_unhover', { points: oldhoverdata });
+ if (pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
}
- gd.emit('plotly_hover', {
- points: gd._hoverdata,
- xaxes: xaArray,
- yaxes: yaArray,
- xvals: xvalArray,
- yvals: yvalArray
- });
+ newhoverdata.push(out);
+ }
+
+ gd._hoverdata = newhoverdata;
+
+ // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
+ // we should improve the "fx" API so other plots can use it without these hack.
+ if (evt.target && evt.target.tagName) {
+ var hasClickToShow = Registry.getComponentMethod(
+ "annotations",
+ "hasClickToShow"
+ )(gd, newhoverdata);
+ overrideCursor(d3.select(evt.target), hasClickToShow ? "pointer" : "");
+ }
+
+ if (!hoverChanged(gd, evt, oldhoverdata)) return;
+
+ if (oldhoverdata) {
+ gd.emit("plotly_unhover", { points: oldhoverdata });
+ }
+
+ gd.emit("plotly_hover", {
+ points: gd._hoverdata,
+ xaxes: xaArray,
+ yaxes: yaArray,
+ xvals: xvalArray,
+ yvals: yvalArray
+ });
}
// look for either .subplot (currently just ternary)
// or xaxis and yaxis attributes
function getSubplot(trace) {
- return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo;
+ return trace.subplot || trace.xaxis + trace.yaxis || trace.geo;
}
fx.getDistanceFunction = function(mode, dx, dy, dxy) {
- if(mode === 'closest') return dxy || quadrature(dx, dy);
- return mode === 'x' ? dx : dy;
+ if (mode === "closest") return dxy || quadrature(dx, dy);
+ return mode === "x" ? dx : dy;
};
fx.getClosest = function(cd, distfn, pointData) {
- // do we already have a point number? (array mode only)
- if(pointData.index !== false) {
- if(pointData.index >= 0 && pointData.index < cd.length) {
- pointData.distance = 0;
- }
- else pointData.index = false;
+ // do we already have a point number? (array mode only)
+ if (pointData.index !== false) {
+ if (pointData.index >= 0 && pointData.index < cd.length) {
+ pointData.distance = 0;
+ } else {
+ pointData.index = false;
}
- else {
- // apply the distance function to each data point
- // this is the longest loop... if this bogs down, we may need
- // to create pre-sorted data (by x or y), not sure how to
- // do this for 'closest'
- for(var i = 0; i < cd.length; i++) {
- var newDistance = distfn(cd[i]);
- if(newDistance <= pointData.distance) {
- pointData.index = i;
- pointData.distance = newDistance;
- }
- }
+ } else {
+ // apply the distance function to each data point
+ // this is the longest loop... if this bogs down, we may need
+ // to create pre-sorted data (by x or y), not sure how to
+ // do this for 'closest'
+ for (var i = 0; i < cd.length; i++) {
+ var newDistance = distfn(cd[i]);
+ if (newDistance <= pointData.distance) {
+ pointData.index = i;
+ pointData.distance = newDistance;
+ }
}
- return pointData;
+ }
+ return pointData;
};
function cleanPoint(d, hovermode) {
- d.posref = hovermode === 'y' ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2;
-
- // then constrain all the positions to be on the plot
- d.x0 = Lib.constrain(d.x0, 0, d.xa._length);
- d.x1 = Lib.constrain(d.x1, 0, d.xa._length);
- d.y0 = Lib.constrain(d.y0, 0, d.ya._length);
- d.y1 = Lib.constrain(d.y1, 0, d.ya._length);
-
- // and convert the x and y label values into objects
- // formatted as text, with font info
- var logOffScale;
- if(d.xLabelVal !== undefined) {
- logOffScale = (d.xa.type === 'log' && d.xLabelVal <= 0);
- var xLabelObj = Axes.tickText(d.xa,
- d.xa.c2l(logOffScale ? -d.xLabelVal : d.xLabelVal), 'hover');
- if(logOffScale) {
- if(d.xLabelVal === 0) d.xLabel = '0';
- else d.xLabel = '-' + xLabelObj.text;
- }
- // TODO: should we do something special if the axis calendar and
- // the data calendar are different? Somehow display both dates with
- // their system names? Right now it will just display in the axis calendar
- // but users could add the other one as text.
- else d.xLabel = xLabelObj.text;
- d.xVal = d.xa.c2d(d.xLabelVal);
+ d.posref = hovermode === "y" ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2;
+
+ // then constrain all the positions to be on the plot
+ d.x0 = Lib.constrain(d.x0, 0, d.xa._length);
+ d.x1 = Lib.constrain(d.x1, 0, d.xa._length);
+ d.y0 = Lib.constrain(d.y0, 0, d.ya._length);
+ d.y1 = Lib.constrain(d.y1, 0, d.ya._length);
+
+ // and convert the x and y label values into objects
+ // formatted as text, with font info
+ var logOffScale;
+ if (d.xLabelVal !== undefined) {
+ logOffScale = d.xa.type === "log" && d.xLabelVal <= 0;
+ var xLabelObj = Axes.tickText(
+ d.xa,
+ d.xa.c2l(logOffScale ? -d.xLabelVal : d.xLabelVal),
+ "hover"
+ );
+ if (logOffScale) {
+ if (d.xLabelVal === 0) d.xLabel = "0";
+ else d.xLabel = "-" + xLabelObj.text;
+ } else {
+ // TODO: should we do something special if the axis calendar and
+ // the data calendar are different? Somehow display both dates with
+ // their system names? Right now it will just display in the axis calendar
+ // but users could add the other one as text.
+ d.xLabel = xLabelObj.text;
}
-
- if(d.yLabelVal !== undefined) {
- logOffScale = (d.ya.type === 'log' && d.yLabelVal <= 0);
- var yLabelObj = Axes.tickText(d.ya,
- d.ya.c2l(logOffScale ? -d.yLabelVal : d.yLabelVal), 'hover');
- if(logOffScale) {
- if(d.yLabelVal === 0) d.yLabel = '0';
- else d.yLabel = '-' + yLabelObj.text;
- }
- // TODO: see above TODO
- else d.yLabel = yLabelObj.text;
- d.yVal = d.ya.c2d(d.yLabelVal);
+ d.xVal = d.xa.c2d(d.xLabelVal);
+ }
+
+ if (d.yLabelVal !== undefined) {
+ logOffScale = d.ya.type === "log" && d.yLabelVal <= 0;
+ var yLabelObj = Axes.tickText(
+ d.ya,
+ d.ya.c2l(logOffScale ? -d.yLabelVal : d.yLabelVal),
+ "hover"
+ );
+ if (logOffScale) {
+ if (d.yLabelVal === 0) d.yLabel = "0";
+ else d.yLabel = "-" + yLabelObj.text;
+ } else {
+ // TODO: see above TODO
+ d.yLabel = yLabelObj.text;
}
-
- if(d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal);
-
- // for box means and error bars, add the range to the label
- if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) {
- var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text;
- if(d.xerrneg !== undefined) {
- d.xLabel += ' +' + xeText + ' / -' +
- Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text;
- }
- else d.xLabel += ' ± ' + xeText;
-
- // small distance penalty for error bars, so that if there are
- // traces with errors and some without, the error bar label will
- // hoist up to the point
- if(hovermode === 'x') d.distance += 1;
+ d.yVal = d.ya.c2d(d.yLabelVal);
+ }
+
+ if (d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal);
+
+ // for box means and error bars, add the range to the label
+ if (!isNaN(d.xerr) && !(d.xa.type === "log" && d.xerr <= 0)) {
+ var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), "hover").text;
+ if (d.xerrneg !== undefined) {
+ d.xLabel += " +" +
+ xeText +
+ " / -" +
+ Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), "hover").text;
+ } else {
+ d.xLabel += " \xB1 " + xeText;
}
- if(!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) {
- var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text;
- if(d.yerrneg !== undefined) {
- d.yLabel += ' +' + yeText + ' / -' +
- Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text;
- }
- else d.yLabel += ' ± ' + yeText;
- if(hovermode === 'y') d.distance += 1;
+ // small distance penalty for error bars, so that if there are
+ // traces with errors and some without, the error bar label will
+ // hoist up to the point
+ if (hovermode === "x") d.distance += 1;
+ }
+ if (!isNaN(d.yerr) && !(d.ya.type === "log" && d.yerr <= 0)) {
+ var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), "hover").text;
+ if (d.yerrneg !== undefined) {
+ d.yLabel += " +" +
+ yeText +
+ " / -" +
+ Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), "hover").text;
+ } else {
+ d.yLabel += " \xB1 " + yeText;
}
- var infomode = d.trace.hoverinfo;
- if(infomode !== 'all') {
- infomode = infomode.split('+');
- if(infomode.indexOf('x') === -1) d.xLabel = undefined;
- if(infomode.indexOf('y') === -1) d.yLabel = undefined;
- if(infomode.indexOf('z') === -1) d.zLabel = undefined;
- if(infomode.indexOf('text') === -1) d.text = undefined;
- if(infomode.indexOf('name') === -1) d.name = undefined;
- }
+ if (hovermode === "y") d.distance += 1;
+ }
+
+ var infomode = d.trace.hoverinfo;
+ if (infomode !== "all") {
+ infomode = infomode.split("+");
+ if (infomode.indexOf("x") === -1) d.xLabel = undefined;
+ if (infomode.indexOf("y") === -1) d.yLabel = undefined;
+ if (infomode.indexOf("z") === -1) d.zLabel = undefined;
+ if (infomode.indexOf("text") === -1) d.text = undefined;
+ if (infomode.indexOf("name") === -1) d.name = undefined;
+ }
- return d;
+ return d;
}
fx.loneHover = function(hoverItem, opts) {
- // draw a single hover item in a pre-existing svg container somewhere
- // hoverItem should have keys:
- // - x and y (or x0, x1, y0, and y1):
- // the pixel position to mark, relative to opts.container
- // - xLabel, yLabel, zLabel, text, and name:
- // info to go in the label
- // - color:
- // the background color for the label. text & outline color will
- // be chosen black or white to contrast with this
- // opts should have keys:
- // - bgColor:
- // the background color this is against, used if the trace is
- // non-opaque, and for the name, which goes outside the box
- // - container:
- // a dom