diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 00000000000..2190b0e1c7c
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,72 @@
+{
+ // environments
+ "browser": true, // Define globals exposed by modern browsers.
+ "jquery": false, // Define globals exposed by jQuery.
+ "node": true, // Define globals exposed by Node.js.
+ "jasmine": true, // Define globals exposed by jasmine
+
+ // enforcing (true means bug us about it)
+ "camelcase": false, // Force all variable names to use either camelCase style or UPPER_CASE with underscores.
+ "curly": false, // This option requires you to always put curly braces around blocks in loops and conditionals.
+ "eqeqeq": true, // Prohibit use of == and != in favor of === and !==.
+ "es3": true, // This option tells JSHint that your code needs to adhere to ECMAScript 3 spec (old browsers)
+ "forin": false, // This option requires all for in loops to filter object's items.
+ "freeze": true, // This options prohibits overwriting prototypes of native objects such as Array, Date and so on.
+ "immed": true, // This option prohibits the use of immediate function invocations without wrapping them in parentheses.
+ "indent": 4, // Enforce tab width of 4 spaces.
+ "latedef": "nofunc", // Prohibit use of a variable before it is defined.
+ "maxcomplexity": false, // This option lets you control cyclomatic complexity throughout your code.
+ "maxdepth": 6, // This option lets you control how nested do you want your blocks to be
+ "maxlen": 120, // Enforce line length to 120 characters
+ "newcap": true, // Require capitalized names for constructor functions.
+ "noarg": true, // This option prohibits the use of arguments.caller and arguments.callee.
+ "noempty": true, // This option warns when you have an empty block in your code.
+ "nonbsp": true, // This option warns about "non-breaking whitespace" characters.
+ "nonew": true, // This option prohibits the use of constructor functions for side-effects.
+ "plusplus": false, // This option prohibits the use of unary increment and decrement operators.
+ "quotmark": "single", // Enforce use of single quotation marks for strings.
+ "strict": true, // Enforce placing 'use strict' at the top function scope
+ "trailing": true, // Prohibit trailing whitespace.
+ "undef": true, // Prohibit use of explicitly undeclared variables.
+ "unused": true, // Warn when variables are defined but never used.
+
+ // relaxing (true means DON'T bug us about it)
+ "asi": false, // This option suppresses warnings about missing semicolons.
+ "boss": false, // This option suppresses warnings about the use of assignments in cases where comparisons are expected.
+ "debug": false, // This option suppresses warnings about the debugger statements in your code.
+ "eqnull": true, // Suppress warnings about == null comparisons.
+ "esnext": true, // This option tells JSHint that your code uses ECMAScript 6 specific syntax.
+ "evil": false, // This option suppresses warnings about the use of eval.
+ "expr": false, // This option suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls.
+ "funcscope": false, // This option suppresses warnings about declaring variables inside of control structures while accessing them later from the outside.
+ "globalstrict": false, // This option suppresses warnings about the use of global strict mode.
+ "iterator": false, // This option suppresses warnings about the __iterator__ property.
+ "lastsemic": false, // This option suppresses warnings about missing semicolons
+ "laxbreak": false, // This option suppresses most of the warnings about possibly unsafe line breakings in your code.
+ "laxcomma": false, // This option suppresses warnings about comma-first coding style
+ "loopfunc": false, // This option suppresses warnings about functions inside of loops.
+ "maxerr": 500, // This options allows you to set the maximum amount of warnings JSHint will produce before giving up.
+ "moz": false, // This options tells JSHint that your code uses Mozilla JavaScript extensions.
+ "multistr": false, // This option suppresses warnings about multi-line strings.
+ "notypeof": false, // This option suppresses warnings about invalid typeof operator values.
+ "proto": false, // This option suppresses warnings about the __proto__ property.
+ "scripturl": false, // This option suppresses warnings about the use of script-targeted URLs—such as javascript:...
+ "shadow": false, // This option suppresses warnings about variable shadowing
+ "sub": false, // This option suppresses warnings about using [] notation when it can be expressed in dot notation
+ "supernew": false, // This option suppresses warnings about "weird" constructions like new function () { ... } and new Object;
+ "validthis": false, // This option suppresses warnings about possible strict violations when the code is running in strict mode
+ "noyield": false, // This option suppresses warnings about generator functions with no yield statement in them.
+
+ // global pre defined variables
+ "predef": [
+ "JSON",
+ "Uint8Array",
+ "Uint16Array",
+ "Uint32Array",
+ "Int8Array",
+ "Int16Array",
+ "Int32Array",
+ "Float32Array",
+ "Float64Array"
+ ]
+}
diff --git a/devtools/test_dashboard/test-geo.js b/devtools/test_dashboard/test-geo.js
index d6c307b1442..cf27d98e4e3 100644
--- a/devtools/test_dashboard/test-geo.js
+++ b/devtools/test_dashboard/test-geo.js
@@ -1,6 +1,6 @@
var plotButtons = require('./buttons');
-var figDir = '../../test/image/baseline/geo_';
+var figDir = '../../test/image/baselines/geo_';
var plots = {};
diff --git a/package.json b/package.json
index e44a8776301..4f52ce7cdda 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "1.0.0",
"description": "The premier javascript graphing library",
"license": "MIT",
- "main": "./src/plotly.js",
+ "main": "./src/index.js",
"repository": {
"type": "git",
"url": "https://github.com/plotly/plotly.js.git"
@@ -43,9 +43,11 @@
],
"scripts": {
"preprocess": "node tasks/preprocess.js",
+ "postinstall": "npm run preprocess",
"bundle": "node tasks/bundle.js",
"build": "npm run preprocess && npm run bundle",
"watch": "node tasks/watch_plotly.js",
+ "lint": "cd src && jshint . || true",
"test-jasmine": "karma start test/jasmine/karma.conf.js",
"test-image": "echo TODO",
"test": "npm run test-jasmine && npm test-image",
@@ -97,6 +99,7 @@
"browserify-transform-tools": "^1.5.0",
"ecstatic": "^1.2.0",
"jasmine-core": "^2.3.4",
+ "jshint": "^2.8.0",
"karma": "^0.13.15",
"karma-browserify": "^4.4.0",
"karma-chrome-launcher": "^0.2.1",
diff --git a/src/geo/geo-assets.js b/src/assets/geo_assets.js
similarity index 100%
rename from src/geo/geo-assets.js
rename to src/assets/geo_assets.js
diff --git a/src/geo/topojson/africa_110m.json b/src/assets/topojson/africa_110m.json
similarity index 100%
rename from src/geo/topojson/africa_110m.json
rename to src/assets/topojson/africa_110m.json
diff --git a/src/geo/topojson/africa_50m.json b/src/assets/topojson/africa_50m.json
similarity index 100%
rename from src/geo/topojson/africa_50m.json
rename to src/assets/topojson/africa_50m.json
diff --git a/src/geo/topojson/asia_110m.json b/src/assets/topojson/asia_110m.json
similarity index 100%
rename from src/geo/topojson/asia_110m.json
rename to src/assets/topojson/asia_110m.json
diff --git a/src/geo/topojson/asia_50m.json b/src/assets/topojson/asia_50m.json
similarity index 100%
rename from src/geo/topojson/asia_50m.json
rename to src/assets/topojson/asia_50m.json
diff --git a/src/geo/topojson/europe_110m.json b/src/assets/topojson/europe_110m.json
similarity index 100%
rename from src/geo/topojson/europe_110m.json
rename to src/assets/topojson/europe_110m.json
diff --git a/src/geo/topojson/europe_50m.json b/src/assets/topojson/europe_50m.json
similarity index 100%
rename from src/geo/topojson/europe_50m.json
rename to src/assets/topojson/europe_50m.json
diff --git a/src/geo/topojson/north-america_110m.json b/src/assets/topojson/north-america_110m.json
similarity index 100%
rename from src/geo/topojson/north-america_110m.json
rename to src/assets/topojson/north-america_110m.json
diff --git a/src/geo/topojson/north-america_50m.json b/src/assets/topojson/north-america_50m.json
similarity index 100%
rename from src/geo/topojson/north-america_50m.json
rename to src/assets/topojson/north-america_50m.json
diff --git a/src/geo/topojson/south-america_110m.json b/src/assets/topojson/south-america_110m.json
similarity index 100%
rename from src/geo/topojson/south-america_110m.json
rename to src/assets/topojson/south-america_110m.json
diff --git a/src/geo/topojson/south-america_50m.json b/src/assets/topojson/south-america_50m.json
similarity index 100%
rename from src/geo/topojson/south-america_50m.json
rename to src/assets/topojson/south-america_50m.json
diff --git a/src/geo/topojson/usa_110m.json b/src/assets/topojson/usa_110m.json
similarity index 100%
rename from src/geo/topojson/usa_110m.json
rename to src/assets/topojson/usa_110m.json
diff --git a/src/geo/topojson/usa_50m.json b/src/assets/topojson/usa_50m.json
similarity index 100%
rename from src/geo/topojson/usa_50m.json
rename to src/assets/topojson/usa_50m.json
diff --git a/src/geo/topojson/world_110m.json b/src/assets/topojson/world_110m.json
similarity index 100%
rename from src/geo/topojson/world_110m.json
rename to src/assets/topojson/world_110m.json
diff --git a/src/geo/topojson/world_50m.json b/src/assets/topojson/world_50m.json
similarity index 100%
rename from src/geo/topojson/world_50m.json
rename to src/assets/topojson/world_50m.json
diff --git a/src/components/annotations/arrow_paths.js b/src/components/annotations/arrow_paths.js
new file mode 100644
index 00000000000..64c662f6ae0
--- /dev/null
+++ b/src/components/annotations/arrow_paths.js
@@ -0,0 +1,49 @@
+/**
+ * centerx is a center of scaling tuned for maximum scalability of
+ * the arrowhead ie throughout mag=0.3..3 the head is joined smoothly
+ * to the line, but the endpoint moves.
+ * backoff is the distance to move the arrowhead, and the end of the
+ * line, in order to end at the right place
+ *
+ * TODO: option to have the pointed-to point a little in front of the
+ * end of the line, as people tend to want a bit of a gap there...
+ */
+module.exports = [
+ // no arrow
+ '',
+ // wide with flat back
+ {
+ path: 'M-2.4,-3V3L0.6,0Z',
+ backoff: 0.6
+ },
+ // narrower with flat back
+ {
+ path: 'M-3.7,-2.5V2.5L1.3,0Z',
+ backoff: 1.3
+ },
+ // barbed
+ {
+ path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z',
+ backoff: 1.55
+ },
+ // wide line-drawn
+ {
+ path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z',
+ backoff: 1.6
+ },
+ // narrower line-drawn
+ {
+ path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z',
+ backoff: 2
+ },
+ // circle
+ {
+ path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z',
+ backoff: 0
+ },
+ // square
+ {
+ path: 'M2,2V-2H-2V2Z',
+ backoff: 0
+ }
+];
diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js
new file mode 100644
index 00000000000..2338a81707c
--- /dev/null
+++ b/src/components/annotations/attributes.js
@@ -0,0 +1,240 @@
+var Plotly = require('../../plotly');
+var ARROWPATHS = require('./arrow_paths');
+var fontAttrs = require('../../plots/font_attributes');
+var extendFlat = require('../../lib/extend').extendFlat;
+
+module.exports = {
+ _isLinkedToArray: true,
+
+ text: {
+ valType: 'string',
+ role: 'info',
+ description: [
+ 'Sets the text associated with this annotation.',
+ 'Plotly uses a subset of HTML tags to do things like',
+ 'newline (
), bold (), italics (),',
+ 'hyperlinks (). Tags , , ',
+ ' are also supported.'
+ ].join(' ')
+ },
+ textangle: {
+ valType: 'angle',
+ dflt: 0,
+ role: 'style',
+ description: [
+ 'Sets the angle at which the `text` is drawn',
+ 'with respect to the horizontal.'
+ ].join(' ')
+ },
+ font: extendFlat({}, fontAttrs, {
+ description: 'Sets the annotation text font.'
+ }),
+ opacity: {
+ valType: 'number',
+ min: 0,
+ max: 1,
+ dflt: 1,
+ role: 'style',
+ description: 'Sets the opacity of the annotation (text + arrow).'
+ },
+ align: {
+ valType: 'enumerated',
+ values: ['left', 'center', 'right'],
+ dflt: 'center',
+ role: 'style',
+ description: [
+ 'Sets the vertical alignment of the `text` with',
+ 'respect to the set `x` and `y` position.',
+ 'Has only an effect if `text` spans more two or more lines',
+ '(i.e. `text` contains one or more
HTML tags).'
+ ].join(' ')
+ },
+ bgcolor: {
+ valType: 'color',
+ dflt: 'rgba(0,0,0,0)',
+ role: 'style',
+ description: 'Sets the background color of the annotation.'
+ },
+ bordercolor: {
+ valType: 'color',
+ dflt: 'rgba(0,0,0,0)',
+ role: 'style',
+ description: [
+ 'Sets the color of the border enclosing the annotation `text`.'
+ ].join(' ')
+ },
+ borderpad: {
+ valType: 'number',
+ min: 0,
+ dflt: 1,
+ role: 'style',
+ description: [
+ 'Sets the padding (in px) between the `text`',
+ 'and the enclosing border.'
+ ].join(' ')
+ },
+ borderwidth: {
+ valType: 'number',
+ min: 0,
+ dflt: 1,
+ role: 'style',
+ description: [
+ 'Sets the width (in px) of the border enclosing',
+ 'the annotation `text`.'
+ ].join(' ')
+ },
+ // arrow
+ showarrow: {
+ valType: 'boolean',
+ dflt: true,
+ role: 'style',
+ description: [
+ 'Determines whether or not the annotation is drawn with an arrow.',
+ 'If *true*, `text` is placed near the arrow\'s tail.',
+ 'If *false*, `text` lines up with the `x` and `y` provided.'
+ ].join(' ')
+ },
+ arrowcolor: {
+ valType: 'color',
+ role: 'style',
+ description: 'Sets the color of the annotation arrow.'
+ },
+ arrowhead: {
+ valType: 'integer',
+ min: 0,
+ max: ARROWPATHS.length,
+ dflt: 1,
+ role: 'style',
+ description: 'Sets the annotation arrow head style.'
+ },
+ arrowsize: {
+ valType: 'number',
+ min: 0.3,
+ dflt: 1,
+ role: 'style',
+ description: 'Sets the size (in px) of annotation arrow head.'
+ },
+ arrowwidth: {
+ valType: 'number',
+ min: 0.1,
+ role: 'style',
+ description: 'Sets the width (in px) of annotation arrow.'
+ },
+ ax: {
+ valType: 'number',
+ dflt: -10,
+ role: 'info',
+ description: [
+ 'Sets the x component of the arrow tail about the arrow head.',
+ 'A positive (negative) component corresponds to an arrow pointing',
+ 'from right to left (left to right)'
+ ].join(' ')
+ },
+ ay: {
+ valType: 'number',
+ dflt: -30,
+ role: 'info',
+ description: [
+ 'Sets the y component of the arrow tail about the arrow head.',
+ 'A positive (negative) component corresponds to an arrow pointing',
+ 'from bottom to top (top to bottom)'
+ ].join(' ')
+ },
+ // positioning
+ xref: {
+ valType: 'enumerated',
+ values: [
+ 'paper',
+ Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString()
+ ],
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s x coordinate axis.',
+ 'If set to an x axis id (e.g. *x* or *x2*), the `x` position',
+ 'refers to an x coordinate',
+ 'If set to *paper*, the `x` position refers to the distance from',
+ 'the left side of the plotting area in normalized coordinates',
+ 'where 0 (1) corresponds to the left (right) side.'
+ ].join(' ')
+ },
+ x: {
+ valType: 'number',
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s x position.',
+ 'Note that dates and categories are converted to numbers.'
+ ].join(' ')
+ },
+ xanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'left', 'center', 'right'],
+ dflt: 'auto',
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s horizontal position anchor',
+ 'This anchor binds the `x` position to the *left*, *center*',
+ 'or *right* of the annotation.',
+ 'For example, if `x` is set to 1, `xref` to *paper* and',
+ '`xanchor` to *right* then the right-most portion of the',
+ 'annotation lines up with the right-most edge of the',
+ 'plotting area.',
+ 'If *auto*, the anchor is equivalent to *center* for',
+ 'data-referenced annotations',
+ 'whereas for paper-referenced, the anchor picked corresponds',
+ 'to the closest side.'
+ ].join(' ')
+ },
+ yref: {
+ valType: 'enumerated',
+ values: [
+ 'paper',
+ Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString()
+ ],
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s y coordinate axis.',
+ 'If set to an y axis id (e.g. *y* or *y2*), the `y` position',
+ 'refers to an y coordinate',
+ 'If set to *paper*, the `y` position refers to the distance from',
+ 'the bottom of the plotting area in normalized coordinates',
+ 'where 0 (1) corresponds to the bottom (top).'
+ ].join(' ')
+ },
+ y: {
+ valType: 'number',
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s y position.',
+ 'Note that dates and categories are converted to numbers.'
+ ].join(' ')
+ },
+ yanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'top', 'middle', 'bottom'],
+ dflt: 'auto',
+ role: 'info',
+ description: [
+ 'Sets the annotation\'s vertical position anchor',
+ 'This anchor binds the `y` position to the *top*, *middle*',
+ 'or *bottom* of the annotation.',
+ 'For example, if `y` is set to 1, `yref` to *paper* and',
+ '`yanchor` to *top* then the top-most portion of the',
+ 'annotation lines up with the top-most edge of the',
+ 'plotting area.',
+ 'If *auto*, the anchor is equivalent to *middle* for',
+ 'data-referenced annotations',
+ 'whereas for paper-referenced, the anchor picked corresponds',
+ 'to the closest side.'
+ ].join(' ')
+ },
+
+ _deprecated: {
+ ref: {
+ valType: 'string',
+ role: 'info',
+ description: [
+ 'Obsolete. Set `xref` and `yref` separately instead.'
+ ].join(' ')
+ }
+ }
+};
diff --git a/src/annotations.js b/src/components/annotations/index.js
similarity index 79%
rename from src/annotations.js
rename to src/components/annotations/index.js
index e58f7a3c0b9..c829af7c3de 100644
--- a/src/annotations.js
+++ b/src/components/annotations/index.js
@@ -1,293 +1,14 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var annotations = module.exports = {};
-// centerx is a center of scaling tuned for maximum scalability of
-// the arrowhead ie throughout mag=0.3..3 the head is joined smoothly
-// to the line, but the endpoint moves.
-// backoff is the distance to move the arrowhead, and the end of the
-// line, in order to end at the right place
-// TODO: option to have the pointed-to point a little in front of the
-// end of the line, as people tend to want a bit of a gap there...
-annotations.ARROWPATHS = [
- // no arrow
- '',
- // wide with flat back
- {
- path: 'M-2.4,-3V3L0.6,0Z',
- backoff: 0.6
- },
- // narrower with flat back
- {
- path: 'M-3.7,-2.5V2.5L1.3,0Z',
- backoff: 1.3
- },
- // barbed
- {
- path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z',
- backoff: 1.55
- },
- // wide line-drawn
- {
- path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z',
- backoff: 1.6
- },
- // narrower line-drawn
- {
- path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z',
- backoff: 2
- },
- // circle
- {
- path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z',
- backoff: 0
- },
- // square
- {
- path: 'M2,2V-2H-2V2Z',
- backoff: 0
- }
-];
-
-annotations.layoutAttributes = {
- _isLinkedToArray: true,
-
- text: {
- valType: 'string',
- role: 'info',
- description: [
- 'Sets the text associated with this annotation.',
- 'Plotly uses a subset of HTML tags to do things like',
- 'newline (
), bold (), italics (),',
- 'hyperlinks (). Tags , , ',
- ' are also supported.'
- ].join(' ')
- },
- textangle: {
- valType: 'angle',
- dflt: 0,
- role: 'style',
- description: [
- 'Sets the angle at which the `text` is drawn',
- 'with respect to the horizontal.'
- ].join(' ')
- },
- font: Plotly.Lib.extendFlat({}, Plotly.Plots.fontAttrs, {
- description: 'Sets the annotation text font.'
- }),
- opacity: {
- valType: 'number',
- min: 0,
- max: 1,
- dflt: 1,
- role: 'style',
- description: 'Sets the opacity of the annotation (text + arrow).'
- },
- align: {
- valType: 'enumerated',
- values: ['left', 'center', 'right'],
- dflt: 'center',
- role: 'style',
- description: [
- 'Sets the vertical alignment of the `text` with',
- 'respect to the set `x` and `y` position.',
- 'Has only an effect if `text` spans more two or more lines',
- '(i.e. `text` contains one or more
HTML tags).'
- ].join(' ')
- },
- bgcolor: {
- valType: 'color',
- dflt: 'rgba(0,0,0,0)',
- role: 'style',
- description: 'Sets the background color of the annotation.'
- },
- bordercolor: {
- valType: 'color',
- dflt: 'rgba(0,0,0,0)',
- role: 'style',
- description: [
- 'Sets the color of the border enclosing the annotation `text`.'
- ].join(' ')
- },
- borderpad: {
- valType: 'number',
- min: 0,
- dflt: 1,
- role: 'style',
- description: [
- 'Sets the padding (in px) between the `text`',
- 'and the enclosing border.'
- ].join(' ')
- },
- borderwidth: {
- valType: 'number',
- min: 0,
- dflt: 1,
- role: 'style',
- description: [
- 'Sets the width (in px) of the border enclosing',
- 'the annotation `text`.'
- ].join(' ')
- },
- // arrow
- showarrow: {
- valType: 'boolean',
- dflt: true,
- role: 'style',
- description: [
- 'Determines whether or not the annotation is drawn with an arrow.',
- 'If *true*, `text` is placed near the arrow\'s tail.',
- 'If *false*, `text` lines up with the `x` and `y` provided.'
- ].join(' ')
- },
- arrowcolor: {
- valType: 'color',
- role: 'style',
- description: 'Sets the color of the annotation arrow.'
- },
- arrowhead: {
- valType: 'integer',
- min: 0,
- max: annotations.ARROWPATHS.length,
- dflt: 1,
- role: 'style',
- description: 'Sets the annotation arrow head style.'
- },
- arrowsize: {
- valType: 'number',
- min: 0.3,
- dflt: 1,
- role: 'style',
- description: 'Sets the size (in px) of annotation arrow head.'
- },
- arrowwidth: {
- valType: 'number',
- min: 0.1,
- role: 'style',
- description: 'Sets the width (in px) of annotation arrow.'
- },
- ax: {
- valType: 'number',
- dflt: -10,
- role: 'info',
- description: [
- 'Sets the x component of the arrow tail about the arrow head.',
- 'A positive (negative) component corresponds to an arrow pointing',
- 'from right to left (left to right)'
- ].join(' ')
- },
- ay: {
- valType: 'number',
- dflt: -30,
- role: 'info',
- description: [
- 'Sets the y component of the arrow tail about the arrow head.',
- 'A positive (negative) component corresponds to an arrow pointing',
- 'from bottom to top (top to bottom)'
- ].join(' ')
- },
- // positioning
- xref: {
- valType: 'enumerated',
- values: [
- 'paper',
- Plotly.Plots.subplotsRegistry.cartesian.idRegex.x.toString()
- ],
- role: 'info',
- description: [
- 'Sets the annotation\'s x coordinate axis.',
- 'If set to an x axis id (e.g. *x* or *x2*), the `x` position',
- 'refers to an x coordinate',
- 'If set to *paper*, the `x` position refers to the distance from',
- 'the left side of the plotting area in normalized coordinates',
- 'where 0 (1) corresponds to the left (right) side.'
- ].join(' ')
- },
- x: {
- valType: 'number',
- role: 'info',
- description: [
- 'Sets the annotation\'s x position.',
- 'Note that dates and categories are converted to numbers.'
- ].join(' ')
- },
- xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'auto',
- role: 'info',
- description: [
- 'Sets the annotation\'s horizontal position anchor',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the annotation.',
- 'For example, if `x` is set to 1, `xref` to *paper* and',
- '`xanchor` to *right* then the right-most portion of the',
- 'annotation lines up with the right-most edge of the',
- 'plotting area.',
- 'If *auto*, the anchor is equivalent to *center* for',
- 'data-referenced annotations',
- 'whereas for paper-referenced, the anchor picked corresponds',
- 'to the closest side.'
- ].join(' ')
- },
- yref: {
- valType: 'enumerated',
- values: [
- 'paper',
- Plotly.Plots.subplotsRegistry.cartesian.idRegex.y.toString()
- ],
- role: 'info',
- description: [
- 'Sets the annotation\'s y coordinate axis.',
- 'If set to an y axis id (e.g. *y* or *y2*), the `y` position',
- 'refers to an y coordinate',
- 'If set to *paper*, the `y` position refers to the distance from',
- 'the bottom of the plotting area in normalized coordinates',
- 'where 0 (1) corresponds to the bottom (top).'
- ].join(' ')
- },
- y: {
- valType: 'number',
- role: 'info',
- description: [
- 'Sets the annotation\'s y position.',
- 'Note that dates and categories are converted to numbers.'
- ].join(' ')
- },
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'auto',
- role: 'info',
- description: [
- 'Sets the annotation\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the annotation.',
- 'For example, if `y` is set to 1, `yref` to *paper* and',
- '`yanchor` to *top* then the top-most portion of the',
- 'annotation lines up with the top-most edge of the',
- 'plotting area.',
- 'If *auto*, the anchor is equivalent to *middle* for',
- 'data-referenced annotations',
- 'whereas for paper-referenced, the anchor picked corresponds',
- 'to the closest side.'
- ].join(' ')
- },
-
- _deprecated: {
- ref: {
- valType: 'string',
- role: 'info',
- description: [
- 'Obsolete. Set `xref` and `yref` separately instead.'
- ].join(' ')
- }
- }
-};
+annotations.ARROWPATHS = require('./arrow_paths');
+
+annotations.layoutAttributes = require('./attributes');
annotations.supplyLayoutDefaults = function(layoutIn, layoutOut) {
var containerIn = layoutIn.annotations || [],
@@ -302,9 +23,7 @@ function handleAnnotationDefaults(annIn, fullLayout) {
var annOut = {};
function coerce(attr, dflt) {
- return Plotly.Lib.coerce(annIn, annOut,
- annotations.layoutAttributes,
- attr, dflt);
+ return Plotly.Lib.coerce(annIn, annOut, annotations.layoutAttributes, attr, dflt);
}
coerce('opacity');
diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js
new file mode 100644
index 00000000000..dac67382220
--- /dev/null
+++ b/src/components/color/attributes.js
@@ -0,0 +1,19 @@
+// IMPORTANT - default colors should be in hex for compatibility
+exports.defaults = [
+ '#1f77b4', // muted blue
+ '#ff7f0e', // safety orange
+ '#2ca02c', // cooked asparagus green
+ '#d62728', // brick red
+ '#9467bd', // muted purple
+ '#8c564b', // chestnut brown
+ '#e377c2', // raspberry yogurt pink
+ '#7f7f7f', // middle gray
+ '#bcbd22', // curry yellow-green
+ '#17becf' // blue-teal
+];
+
+exports.defaultLine = '#444';
+
+exports.lightLine = '#eee';
+
+exports.background = '#fff';
diff --git a/src/color.js b/src/components/color/index.js
similarity index 89%
rename from src/color.js
rename to src/components/color/index.js
index 4e7780b6629..2f4dfac805d 100644
--- a/src/color.js
+++ b/src/components/color/index.js
@@ -5,23 +5,11 @@ var isNumeric = require('fast-isnumeric');
var color = module.exports = {};
-// IMPORTANT - default colors should be in hex for grid.js
-color.defaults = [
- '#1f77b4', // muted blue
- '#ff7f0e', // safety orange
- '#2ca02c', // cooked asparagus green
- '#d62728', // brick red
- '#9467bd', // muted purple
- '#8c564b', // chestnut brown
- '#e377c2', // raspberry yogurt pink
- '#7f7f7f', // middle gray
- '#bcbd22', // curry yellow-green
- '#17becf' // blue-teal
-];
-
-color.defaultLine = '#444';
-color.lightLine = '#eee';
-color.background = '#fff';
+var colorAttrs = require('./attributes');
+color.defaults = colorAttrs.defaults;
+color.defaultLine = colorAttrs.defaultLine;
+color.lightLine = colorAttrs.lightLine;
+color.background = colorAttrs.background;
color.tinyRGB = function(tc) {
var c = tc.toRgb();
diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js
new file mode 100644
index 00000000000..0dd0b65f01d
--- /dev/null
+++ b/src/components/colorbar/attributes.js
@@ -0,0 +1,182 @@
+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,
+ 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/colorbar.js b/src/components/colorbar/index.js
similarity index 76%
rename from src/colorbar.js
rename to src/components/colorbar/index.js
index 74d14df5557..0f6073ead96 100644
--- a/src/colorbar.js
+++ b/src/components/colorbar/index.js
@@ -1,9 +1,10 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
+
var colorbar = module.exports = function(td, id) {
// opts: options object, containing everything from attributes
// plus a few others that are the equivalent of the colorbar "data"
@@ -238,7 +239,7 @@ var colorbar = module.exports = function(td, id) {
if(['top','bottom'].indexOf(opts.titleside)!==-1) {
// draw the title so we know how much room it needs
// when we squish the axis
- Plotly.Plots.titles(td, cbAxisOut._id + 'title');
+ Plotly.Titles.draw(td, cbAxisOut._id + 'title');
}
function drawAxis(){
@@ -519,187 +520,7 @@ var colorbar = module.exports = function(td, id) {
return component;
};
-var axesAttrs = Plotly.Axes.layoutAttributes,
- extendFlat = Plotly.Lib.extendFlat;
-
-colorbar.attributes = {
-// TODO: only right is supported currently
-// orient: {
-// valType: 'enumerated',
-// role: 'info',
-// values: ['left', 'right', 'top', 'bottom'],
-// dflt: 'right',
-// description: [
-// 'Determines which side are the labels on',
-// '(so left and right make vertical bars, etc.)'
-// ].join(' ')
-// },
- thicknessmode: {
- valType: 'enumerated',
- values: ['fraction', 'pixels'],
- role: 'style',
- dflt: 'pixels',
- description: [
- 'Determines whether this color bar\'s thickness',
- '(i.e. the measure in the constant color direction)',
- 'is set in units of plot *fraction* or in *pixels*.',
- 'Use `thickness` to set the value.'
- ].join(' ')
- },
- thickness: {
- valType: 'number',
- role: 'style',
- min: 0,
- dflt: 30,
- description: [
- 'Sets the thickness of the color bar',
- 'This measure excludes the size of the padding, ticks and labels.'
- ].join(' ')
- },
- lenmode: {
- valType: 'enumerated',
- values: ['fraction', 'pixels'],
- role: 'info',
- dflt: 'fraction',
- description: [
- 'Determines whether this color bar\'s length',
- '(i.e. the measure in the color variation direction)',
- 'is set in units of plot *fraction* or in *pixels.',
- 'Use `len` to set the value.'
- ].join(' ')
- },
- len: {
- valType: 'number',
- min: 0,
- dflt: 1,
- role: 'style',
- description: [
- 'Sets the length of the color bar',
- 'This measure excludes the padding of both ends.',
- 'That is, the color bar length is this length minus the',
- 'padding on both ends.'
- ].join(' ')
- },
- x: {
- valType: 'number',
- dflt: 1.02,
- min: -2,
- max: 3,
- role: 'style',
- description: [
- 'Sets the x position of the color bar (in plot fraction).'
- ].join(' ')
- },
- xanchor: {
- valType: 'enumerated',
- values: ['left', 'center', 'right'],
- dflt: 'left',
- role: 'style',
- description: [
- 'Sets this color bar\'s horizontal position anchor.',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the color bar.'
- ].join(' ')
- },
- xpad: {
- valType: 'number',
- role: 'style',
- min: 0,
- dflt: 10,
- description: 'Sets the amount of padding (in px) along the x direction.'
- },
- y: {
- valType: 'number',
- role: 'style',
- dflt: 0.5,
- min: -2,
- max: 3,
- description: [
- 'Sets the y position of the color bar (in plot fraction).'
- ].join(' ')
- },
- yanchor: {
- valType: 'enumerated',
- values: ['top', 'middle', 'bottom'],
- role: 'style',
- dflt: 'middle',
- description: [
- 'Sets this color bar\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the color bar.'
- ].join(' ')
- },
- ypad: {
- valType: 'number',
- role: 'style',
- min: 0,
- dflt: 10,
- description: 'Sets the amount of padding (in px) along the y direction.'
- },
- // a possible line around the bar itself
- outlinecolor: axesAttrs.linecolor,
- outlinewidth: axesAttrs.linewidth,
- // Should outlinewidth have {dflt: 0} ?
- // another possible line outside the padding and tick labels
- bordercolor: axesAttrs.linecolor,
- borderwidth: {
- valType: 'number',
- role: 'style',
- min: 0,
- dflt: 0,
- description: [
- 'Sets the width (in px) or the border enclosing this color bar.'
- ].join(' ')
- },
- bgcolor: {
- valType: 'color',
- role: 'style',
- dflt: 'rgba(0,0,0,0)',
- description: 'Sets the color of padded area.'
- },
- // tick and title properties named and function exactly as in axes
- tickmode: axesAttrs.tickmode,
- nticks: axesAttrs.nticks,
- tick0: axesAttrs.tick0,
- dtick: axesAttrs.dtick,
- tickvals: axesAttrs.tickvals,
- ticktext: axesAttrs.ticktext,
- ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}),
- ticklen: axesAttrs.ticklen,
- tickwidth: axesAttrs.tickwidth,
- tickcolor: axesAttrs.tickcolor,
- showticklabels: axesAttrs.showticklabels,
- tickfont: axesAttrs.tickfont,
- tickangle: axesAttrs.tickangle,
- tickformat: axesAttrs.tickformat,
- tickprefix: axesAttrs.tickprefix,
- showtickprefix: axesAttrs.showtickprefix,
- ticksuffix: axesAttrs.ticksuffix,
- showticksuffix: axesAttrs.showticksuffix,
- exponentformat: axesAttrs.exponentformat,
- showexponent: axesAttrs.showexponent,
- title: {
- valType: 'string',
- role: 'info',
- dflt: 'Click to enter colorscale title',
- description: 'Sets the title of the color bar.'
- },
- titlefont: extendFlat({}, Plotly.Plots.fontAttrs, {
- description: [
- 'Sets this color bar\'s title font.'
- ].join(' ')
- }),
- titleside: {
- valType: 'enumerated',
- values: ['right', 'top', 'bottom'],
- role: 'style',
- dflt: 'top',
- description: [
- 'Determines the location of the colorbar title',
- 'with respect to the color bar.'
- ].join(' ')
- }
-};
+colorbar.attributes = require('./attributes');
colorbar.supplyDefaults = function(containerIn, containerOut, layout) {
var colorbarOut = containerOut.colorbar = {},
@@ -771,68 +592,3 @@ colorbar.traceColorbar = function(gd, cd) {
Plotly.Lib.markTime('done colorbar');
};
-
-colorbar.traceColorbarAttributes = {
- zauto: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines the whether or not the color domain is computed',
- 'with respect to the input data.'
- ].join(' ')
- },
- zmin: {
- valType: 'number',
- role: 'info',
- dflt: null,
- description: 'Sets the lower bound of color domain.'
- },
- zmax: {
- valType: 'number',
- role: 'info',
- dflt: null,
- description: 'Sets the upper bound of color domain.'
- },
- colorscale: {
- valType: 'colorscale',
- role: 'style',
- description: 'Sets the colorscale.'
- },
- autocolorscale: {
- valType: 'boolean',
- role: 'style',
- dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp.
- description: [
- 'Determines whether or not the colorscale is picked using the sign of',
- 'the input z values.'
- ].join(' ')
- },
- reversescale: {
- valType: 'boolean',
- role: 'style',
- dflt: false,
- description: 'Reverses the colorscale.'
- },
- showscale: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- description: [
- 'Determines whether or not a colorbar is displayed for this trace.'
- ].join(' ')
- },
-
- _deprecated: {
- scl: {
- valType: 'colorscale',
- role: 'style',
- description: 'Renamed to `colorscale`.'
- },
- reversescl: {
- valType: 'boolean',
- role: 'style',
- description: 'Renamed to `reversescale`.'
- }
- }
-};
diff --git a/src/components/colorscale/attributes.js b/src/components/colorscale/attributes.js
new file mode 100644
index 00000000000..61def16d1bc
--- /dev/null
+++ b/src/components/colorscale/attributes.js
@@ -0,0 +1,64 @@
+module.exports = {
+ zauto: {
+ valType: 'boolean',
+ role: 'info',
+ dflt: true,
+ description: [
+ 'Determines the whether or not the color domain is computed',
+ 'with respect to the input data.'
+ ].join(' ')
+ },
+ zmin: {
+ valType: 'number',
+ role: 'info',
+ dflt: null,
+ description: 'Sets the lower bound of color domain.'
+ },
+ zmax: {
+ valType: 'number',
+ role: 'info',
+ dflt: null,
+ description: 'Sets the upper bound of color domain.'
+ },
+ colorscale: {
+ valType: 'colorscale',
+ role: 'style',
+ description: 'Sets the colorscale.'
+ },
+ autocolorscale: {
+ valType: 'boolean',
+ role: 'style',
+ dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp.
+ description: [
+ 'Determines whether or not the colorscale is picked using the sign of',
+ 'the input z values.'
+ ].join(' ')
+ },
+ reversescale: {
+ valType: 'boolean',
+ role: 'style',
+ dflt: false,
+ description: 'Reverses the colorscale.'
+ },
+ showscale: {
+ valType: 'boolean',
+ role: 'info',
+ dflt: true,
+ description: [
+ 'Determines whether or not a colorbar is displayed for this trace.'
+ ].join(' ')
+ },
+
+ _deprecated: {
+ scl: {
+ valType: 'colorscale',
+ role: 'style',
+ description: 'Renamed to `colorscale`.'
+ },
+ reversescl: {
+ valType: 'boolean',
+ role: 'style',
+ description: 'Renamed to `reversescale`.'
+ }
+ }
+};
diff --git a/src/colorscale.js b/src/components/colorscale/index.js
similarity index 62%
rename from src/colorscale.js
rename to src/components/colorscale/index.js
index 4f0aa7c06ea..48f213c5e1a 100644
--- a/src/colorscale.js
+++ b/src/components/colorscale/index.js
@@ -1,97 +1,17 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var d3 = require('d3');
var tinycolor = require('tinycolor2');
var isNumeric = require('fast-isnumeric');
var colorscale = module.exports = {};
-colorscale.scales = {
- 'Greys':[[0,'rgb(0,0,0)'],[1,'rgb(255,255,255)']],
-
- 'YIGnBu':[[0,'rgb(8, 29, 88)'],[0.125,'rgb(37, 52, 148)'],
- [0.25,'rgb(34, 94, 168)'],[0.375,'rgb(29, 145, 192)'],
- [0.5,'rgb(65, 182, 196)'],[0.625,'rgb(127, 205, 187)'],
- [0.75,'rgb(199, 233, 180)'],[0.875,'rgb(237, 248, 217)'],
- [1,'rgb(255, 255, 217)']],
-
- 'Greens':[[0,'rgb(0, 68, 27)'],[0.125,'rgb(0, 109, 44)'],
- [0.25,'rgb(35, 139, 69)'],[0.375,'rgb(65, 171, 93)'],
- [0.5,'rgb(116, 196, 118)'],[0.625,'rgb(161, 217, 155)'],
- [0.75,'rgb(199, 233, 192)'],[0.875,'rgb(229, 245, 224)'],
- [1,'rgb(247, 252, 245)']],
-
- 'YIOrRd':[[0,'rgb(128, 0, 38)'],[0.125,'rgb(189, 0, 38)'],
- [0.25,'rgb(227, 26, 28)'],[0.375,'rgb(252, 78, 42)'],
- [0.5,'rgb(253, 141, 60)'],[0.625,'rgb(254, 178, 76)'],
- [0.75,'rgb(254, 217, 118)'],[0.875,'rgb(255, 237, 160)'],
- [1,'rgb(255, 255, 204)']],
-
- 'Bluered':[[0,'rgb(0,0,255)'],[1,'rgb(255,0,0)']],
-
- // modified RdBu based on
- // www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf
- 'RdBu':[[0,'rgb(5, 10, 172)'],[0.35,'rgb(106, 137, 247)'],
- [0.5,'rgb(190,190,190)'],[0.6,'rgb(220, 170, 132)'],
- [0.7,'rgb(230, 145, 90)'],[1,'rgb(178, 10, 28)']],
- // Scale for non-negative numeric values
- 'Reds': [[0, 'rgb(220, 220, 220)'], [0.2,'rgb(245, 195, 157)'],
- [0.4,'rgb(245, 160, 105)'], [1, 'rgb(178, 10, 28)']],
- // Scale for non-positive numeric values
- 'Blues': [[0, 'rgb(5, 10, 172)'], [0.35, 'rgb(40, 60, 190)'],
- [0.5, 'rgb(70, 100, 245)'], [0.6, 'rgb(90, 120, 245)'],
- [0.7, 'rgb(106, 137, 247)'], [1, 'rgb(220, 220, 220)']],
-
- 'Picnic':[[0,'rgb(0,0,255)'],[0.1,'rgb(51,153,255)'],
- [0.2,'rgb(102,204,255)'],[0.3,'rgb(153,204,255)'],
- [0.4,'rgb(204,204,255)'],[0.5,'rgb(255,255,255)'],
- [0.6,'rgb(255,204,255)'],[0.7,'rgb(255,153,255)'],
- [0.8,'rgb(255,102,204)'],[0.9,'rgb(255,102,102)'],
- [1,'rgb(255,0,0)']],
-
- 'Rainbow':[[0,'rgb(150,0,90)'],[0.125,'rgb(0, 0, 200)'],
- [0.25,'rgb(0, 25, 255)'],[0.375,'rgb(0, 152, 255)'],
- [0.5,'rgb(44, 255, 150)'],[0.625,'rgb(151, 255, 0)'],
- [0.75,'rgb(255, 234, 0)'],[0.875,'rgb(255, 111, 0)'],
- [1,'rgb(255, 0, 0)']],
-
- 'Portland':[[0,'rgb(12,51,131)'],[0.25,'rgb(10,136,186)'],
- [0.5,'rgb(242,211,56)'],[0.75,'rgb(242,143,56)'],
- [1,'rgb(217,30,30)']],
-
- 'Jet':[[0,'rgb(0,0,131)'],[0.125,'rgb(0,60,170)'],
- [0.375,'rgb(5,255,255)'],[0.625,'rgb(255,255,0)'],
- [0.875,'rgb(250,0,0)'],[1,'rgb(128,0,0)']],
-
- 'Hot':[[0,'rgb(0,0,0)'],[0.3,'rgb(230,0,0)'],
- [0.6,'rgb(255,210,0)'],[1,'rgb(255,255,255)']],
-
- 'Blackbody':[[0,'rgb(0,0,0)'],[0.2,'rgb(230,0,0)'],
- [0.4,'rgb(230,210,0)'],[0.7,'rgb(255,255,255)'],
- [1,'rgb(160,200,255)']],
-
- 'Earth':[[0,'rgb(0,0,130)'],[0.1,'rgb(0,180,180)'],
- [0.2,'rgb(40,210,40)'],[0.4,'rgb(230,230,50)'],
- [0.6,'rgb(120,70,20)'],[1,'rgb(255,255,255)']],
-
- 'Electric':[[0,'rgb(0,0,0)'],[0.15,'rgb(30,0,100)'],
- [0.4,'rgb(120,0,100)'],[0.6,'rgb(160,90,0)'],
- [0.8,'rgb(230,200,0)'],[1,'rgb(255,250,220)']],
-
- 'Viridis': [[0,'#440154'],[0.06274509803921569,'#48186a'],
- [0.12549019607843137,'#472d7b'],[0.18823529411764706,'#424086'],
- [0.25098039215686274,'#3b528b'],[0.3137254901960784,'#33638d'],
- [0.3764705882352941,'#2c728e'],[0.4392156862745098,'#26828e'],
- [0.5019607843137255,'#21918c'],[0.5647058823529412,'#1fa088'],
- [0.6274509803921569,'#28ae80'],[0.6901960784313725,'#3fbc73'],
- [0.7529411764705882,'#5ec962'],[0.8156862745098039,'#84d44b'],
- [0.8784313725490196,'#addc30'],[0.9411764705882353,'#d8e219'],
- [1,'#fde725']]
-};
-
+colorscale.scales = require('./scales');
colorscale.defaultScale = colorscale.scales.RdBu;
+colorscale.attributes = require('./attributes');
+
function isValidScaleArray(scl) {
var isValid = true,
highestVal = 0,
diff --git a/src/components/colorscale/scales.js b/src/components/colorscale/scales.js
new file mode 100644
index 00000000000..c5163c91a62
--- /dev/null
+++ b/src/components/colorscale/scales.js
@@ -0,0 +1,82 @@
+module.exports = {
+ 'Greys':[[0,'rgb(0,0,0)'],[1,'rgb(255,255,255)']],
+
+ 'YIGnBu':[[0,'rgb(8, 29, 88)'],[0.125,'rgb(37, 52, 148)'],
+ [0.25,'rgb(34, 94, 168)'],[0.375,'rgb(29, 145, 192)'],
+ [0.5,'rgb(65, 182, 196)'],[0.625,'rgb(127, 205, 187)'],
+ [0.75,'rgb(199, 233, 180)'],[0.875,'rgb(237, 248, 217)'],
+ [1,'rgb(255, 255, 217)']],
+
+ 'Greens':[[0,'rgb(0, 68, 27)'],[0.125,'rgb(0, 109, 44)'],
+ [0.25,'rgb(35, 139, 69)'],[0.375,'rgb(65, 171, 93)'],
+ [0.5,'rgb(116, 196, 118)'],[0.625,'rgb(161, 217, 155)'],
+ [0.75,'rgb(199, 233, 192)'],[0.875,'rgb(229, 245, 224)'],
+ [1,'rgb(247, 252, 245)']],
+
+ 'YIOrRd':[[0,'rgb(128, 0, 38)'],[0.125,'rgb(189, 0, 38)'],
+ [0.25,'rgb(227, 26, 28)'],[0.375,'rgb(252, 78, 42)'],
+ [0.5,'rgb(253, 141, 60)'],[0.625,'rgb(254, 178, 76)'],
+ [0.75,'rgb(254, 217, 118)'],[0.875,'rgb(255, 237, 160)'],
+ [1,'rgb(255, 255, 204)']],
+
+ 'Bluered':[[0,'rgb(0,0,255)'],[1,'rgb(255,0,0)']],
+
+ // modified RdBu based on
+ // www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf
+ 'RdBu':[[0,'rgb(5, 10, 172)'],[0.35,'rgb(106, 137, 247)'],
+ [0.5,'rgb(190,190,190)'],[0.6,'rgb(220, 170, 132)'],
+ [0.7,'rgb(230, 145, 90)'],[1,'rgb(178, 10, 28)']],
+ // Scale for non-negative numeric values
+ 'Reds': [[0, 'rgb(220, 220, 220)'], [0.2,'rgb(245, 195, 157)'],
+ [0.4,'rgb(245, 160, 105)'], [1, 'rgb(178, 10, 28)']],
+ // Scale for non-positive numeric values
+ 'Blues': [[0, 'rgb(5, 10, 172)'], [0.35, 'rgb(40, 60, 190)'],
+ [0.5, 'rgb(70, 100, 245)'], [0.6, 'rgb(90, 120, 245)'],
+ [0.7, 'rgb(106, 137, 247)'], [1, 'rgb(220, 220, 220)']],
+
+ 'Picnic':[[0,'rgb(0,0,255)'],[0.1,'rgb(51,153,255)'],
+ [0.2,'rgb(102,204,255)'],[0.3,'rgb(153,204,255)'],
+ [0.4,'rgb(204,204,255)'],[0.5,'rgb(255,255,255)'],
+ [0.6,'rgb(255,204,255)'],[0.7,'rgb(255,153,255)'],
+ [0.8,'rgb(255,102,204)'],[0.9,'rgb(255,102,102)'],
+ [1,'rgb(255,0,0)']],
+
+ 'Rainbow':[[0,'rgb(150,0,90)'],[0.125,'rgb(0, 0, 200)'],
+ [0.25,'rgb(0, 25, 255)'],[0.375,'rgb(0, 152, 255)'],
+ [0.5,'rgb(44, 255, 150)'],[0.625,'rgb(151, 255, 0)'],
+ [0.75,'rgb(255, 234, 0)'],[0.875,'rgb(255, 111, 0)'],
+ [1,'rgb(255, 0, 0)']],
+
+ 'Portland':[[0,'rgb(12,51,131)'],[0.25,'rgb(10,136,186)'],
+ [0.5,'rgb(242,211,56)'],[0.75,'rgb(242,143,56)'],
+ [1,'rgb(217,30,30)']],
+
+ 'Jet':[[0,'rgb(0,0,131)'],[0.125,'rgb(0,60,170)'],
+ [0.375,'rgb(5,255,255)'],[0.625,'rgb(255,255,0)'],
+ [0.875,'rgb(250,0,0)'],[1,'rgb(128,0,0)']],
+
+ 'Hot':[[0,'rgb(0,0,0)'],[0.3,'rgb(230,0,0)'],
+ [0.6,'rgb(255,210,0)'],[1,'rgb(255,255,255)']],
+
+ 'Blackbody':[[0,'rgb(0,0,0)'],[0.2,'rgb(230,0,0)'],
+ [0.4,'rgb(230,210,0)'],[0.7,'rgb(255,255,255)'],
+ [1,'rgb(160,200,255)']],
+
+ 'Earth':[[0,'rgb(0,0,130)'],[0.1,'rgb(0,180,180)'],
+ [0.2,'rgb(40,210,40)'],[0.4,'rgb(230,230,50)'],
+ [0.6,'rgb(120,70,20)'],[1,'rgb(255,255,255)']],
+
+ 'Electric':[[0,'rgb(0,0,0)'],[0.15,'rgb(30,0,100)'],
+ [0.4,'rgb(120,0,100)'],[0.6,'rgb(160,90,0)'],
+ [0.8,'rgb(230,200,0)'],[1,'rgb(255,250,220)']],
+
+ 'Viridis': [[0,'#440154'],[0.06274509803921569,'#48186a'],
+ [0.12549019607843137,'#472d7b'],[0.18823529411764706,'#424086'],
+ [0.25098039215686274,'#3b528b'],[0.3137254901960784,'#33638d'],
+ [0.3764705882352941,'#2c728e'],[0.4392156862745098,'#26828e'],
+ [0.5019607843137255,'#21918c'],[0.5647058823529412,'#1fa088'],
+ [0.6274509803921569,'#28ae80'],[0.6901960784313725,'#3fbc73'],
+ [0.7529411764705882,'#5ec962'],[0.8156862745098039,'#84d44b'],
+ [0.8784313725490196,'#addc30'],[0.9411764705882353,'#d8e219'],
+ [1,'#fde725']]
+};
diff --git a/src/drawing.js b/src/components/drawing/index.js
similarity index 57%
rename from src/drawing.js
rename to src/components/drawing/index.js
index 1cd1526be04..1b17ca6486c 100644
--- a/src/drawing.js
+++ b/src/components/drawing/index.js
@@ -1,6 +1,6 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
@@ -110,471 +110,14 @@ drawing.fillGroupStyle = function(s) {
});
};
-// marker symbol definitions
-// users can specify markers either by number or name
-// add 100 (or '-open') and you get an open marker
-// open markers have no fill and use line color as the stroke color
-// add 200 (or '-dot') and you get a dot in the middle
-// add both and you get both
-var SYMBOLDEFS = {
- circle: {
- n: 0,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
- 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
- }
- },
- square: {
- n: 1,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
- }
- },
- diamond: {
- n: 2,
- f: function(r) {
- var rd = d3.round(r*1.3,2);
- return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z';
- }
- },
- cross: {
- n: 3,
- f: function(r) {
- var rc = d3.round(r*0.4,2),
- rc2 = d3.round(r*1.2,2);
- return 'M'+rc2+','+rc+'H'+rc+'V'+rc2+'H-'+rc+
- 'V'+rc+'H-'+rc2+'V-'+rc+'H-'+rc+'V-'+rc2+
- 'H'+rc+'V-'+rc+'H'+rc2+'Z';
- }
- },
- x: {
- n: 4,
- f: function(r) {
- var rx = d3.round(r*0.8/Math.sqrt(2),2),
- ne = 'l'+rx+','+rx,
- se = 'l'+rx+',-'+rx,
- sw = 'l-'+rx+',-'+rx,
- nw = 'l-'+rx+','+rx;
- return 'M0,'+rx+ne+se+sw+se+sw+nw+sw+nw+ne+nw+ne+'Z';
- }
- },
- 'triangle-up': {
- n: 5,
- f: function(r) {
- var rt = d3.round(r*2/Math.sqrt(3),2),
- r2 = d3.round(r/2,2),
- rs = d3.round(r,2);
- return 'M-'+rt+','+r2+'H'+rt+'L0,-'+rs+'Z';
- }
- },
- 'triangle-down': {
- n: 6,
- f: function(r) {
- var rt = d3.round(r*2/Math.sqrt(3),2),
- r2 = d3.round(r/2,2),
- rs = d3.round(r,2);
- return 'M-'+rt+',-'+r2+'H'+rt+'L0,'+rs+'Z';
- }
- },
- 'triangle-left': {
- n: 7,
- f: function(r) {
- var rt = d3.round(r*2/Math.sqrt(3),2),
- r2 = d3.round(r/2,2),
- rs = d3.round(r,2);
- return 'M'+r2+',-'+rt+'V'+rt+'L-'+rs+',0Z';
- }
- },
- 'triangle-right': {
- n: 8,
- f: function(r) {
- var rt = d3.round(r*2/Math.sqrt(3),2),
- r2 = d3.round(r/2,2),
- rs = d3.round(r,2);
- return 'M-'+r2+',-'+rt+'V'+rt+'L'+rs+',0Z';
- }
- },
- 'triangle-ne': {
- n: 9,
- f: function(r) {
- var r1 = d3.round(r*0.6,2),
- r2 = d3.round(r*1.2,2);
- return 'M-'+r2+',-'+r1+'H'+r1+'V'+r2+'Z';
- }
- },
- 'triangle-se': {
- n: 10,
- f: function(r) {
- var r1 = d3.round(r*0.6,2),
- r2 = d3.round(r*1.2,2);
- return 'M'+r1+',-'+r2+'V'+r1+'H-'+r2+'Z';
- }
- },
- 'triangle-sw': {
- n: 11,
- f: function(r) {
- var r1 = d3.round(r*0.6,2),
- r2 = d3.round(r*1.2,2);
- return 'M'+r2+','+r1+'H-'+r1+'V-'+r2+'Z';
- }
- },
- 'triangle-nw': {
- n: 12,
- f: function(r) {
- var r1 = d3.round(r*0.6,2),
- r2 = d3.round(r*1.2,2);
- return 'M-'+r1+','+r2+'V-'+r1+'H'+r2+'Z';
- }
- },
- pentagon: {
- n: 13,
- f: function(r) {
- var x1 = d3.round(r*0.951,2),
- x2 = d3.round(r*0.588,2),
- y0 = d3.round(-r,2),
- y1 = d3.round(r*-0.309,2),
- y2 = d3.round(r*0.809,2);
- return 'M'+x1+','+y1+'L'+x2+','+y2+'H-'+x2+
- 'L-'+x1+','+y1+'L0,'+y0+'Z';
- }
- },
- hexagon: {
- n: 14,
- f: function(r) {
- var y0 = d3.round(r,2),
- y1 = d3.round(r/2,2),
- x = d3.round(r*Math.sqrt(3)/2,2);
- return 'M'+x+',-'+y1+'V'+y1+'L0,'+y0+
- 'L-'+x+','+y1+'V-'+y1+'L0,-'+y0+'Z';
- }
- },
- hexagon2: {
- n: 15,
- f: function(r) {
- var x0 = d3.round(r,2),
- x1 = d3.round(r/2,2),
- y = d3.round(r*Math.sqrt(3)/2,2);
- return 'M-'+x1+','+y+'H'+x1+'L'+x0+
- ',0L'+x1+',-'+y+'H-'+x1+'L-'+x0+',0Z';
- }
- },
- octagon: {
- n: 16,
- f: function(r) {
- var a = d3.round(r*0.924,2),
- b = d3.round(r*0.383,2);
- return 'M-'+b+',-'+a+'H'+b+'L'+a+',-'+b+'V'+b+
- 'L'+b+','+a+'H-'+b+'L-'+a+','+b+'V-'+b+'Z';
- }
- },
- star: {
- n: 17,
- f: function(r) {
- var rs = r*1.4,
- x1 = d3.round(rs*0.225,2),
- x2 = d3.round(rs*0.951,2),
- x3 = d3.round(rs*0.363,2),
- x4 = d3.round(rs*0.588,2),
- y0 = d3.round(-rs,2),
- y1 = d3.round(rs*-0.309,2),
- y3 = d3.round(rs*0.118,2),
- y4 = d3.round(rs*0.809,2),
- y5 = d3.round(rs*0.382,2);
- return 'M'+x1+','+y1+'H'+x2+'L'+x3+','+y3+
- 'L'+x4+','+y4+'L0,'+y5+'L-'+x4+','+y4+
- 'L-'+x3+','+y3+'L-'+x2+','+y1+'H-'+x1+
- 'L0,'+y0+'Z';
- }
- },
- hexagram: {
- n: 18,
- f: function(r) {
- var y = d3.round(r*0.66,2),
- x1 = d3.round(r*0.38,2),
- x2 = d3.round(r*0.76,2);
- return 'M-'+x2+',0l-'+x1+',-'+y+'h'+x2+
- 'l'+x1+',-'+y+'l'+x1+','+y+'h'+x2+
- 'l-'+x1+','+y+'l'+x1+','+y+'h-'+x2+
- 'l-'+x1+','+y+'l-'+x1+',-'+y+'h-'+x2+'Z';
- }
- },
- 'star-triangle-up': {
- n: 19,
- f: function(r) {
- var x = d3.round(r*Math.sqrt(3)*0.8,2),
- y1 = d3.round(r*0.8,2),
- y2 = d3.round(r*1.6,2),
- rc = d3.round(r*4,2),
- aPart = 'A '+rc+','+rc+' 0 0 1 ';
- return 'M-'+x+','+y1+aPart+x+','+y1+
- aPart+'0,-'+y2+aPart+'-'+x+','+y1+'Z';
- }
- },
- 'star-triangle-down': {
- n: 20,
- f: function(r) {
- var x = d3.round(r*Math.sqrt(3)*0.8,2),
- y1 = d3.round(r*0.8,2),
- y2 = d3.round(r*1.6,2),
- rc = d3.round(r*4,2),
- aPart = 'A '+rc+','+rc+' 0 0 1 ';
- return 'M'+x+',-'+y1+aPart+'-'+x+',-'+y1+
- aPart+'0,'+y2+aPart+x+',-'+y1+'Z';
- }
- },
- 'star-square': {
- n: 21,
- f: function(r) {
- var rp = d3.round(r*1.1,2),
- rc = d3.round(r*2,2),
- aPart = 'A '+rc+','+rc+' 0 0 1 ';
- return 'M-'+rp+',-'+rp+aPart+'-'+rp+','+rp+
- aPart+rp+','+rp+aPart+rp+',-'+rp+
- aPart+'-'+rp+',-'+rp+'Z';
- }
- },
- 'star-diamond': {
- n: 22,
- f: function(r) {
- var rp = d3.round(r*1.4,2),
- rc = d3.round(r*1.9,2),
- aPart = 'A '+rc+','+rc+' 0 0 1 ';
- return 'M-'+rp+',0'+aPart+'0,'+rp+
- aPart+rp+',0'+aPart+'0,-'+rp+
- aPart+'-'+rp+',0'+'Z';
- }
- },
- 'diamond-tall': {
- n: 23,
- f: function(r) {
- var x = d3.round(r*0.7,2),
- y = d3.round(r*1.4,2);
- return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z';
- }
- },
- 'diamond-wide': {
- n: 24,
- f: function(r) {
- var x = d3.round(r*1.4,2),
- y = d3.round(r*0.7,2);
- return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z';
- }
- },
- hourglass: {
- n: 25,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M'+rs+','+rs+'H-'+rs+'L'+rs+',-'+rs+'H-'+rs+'Z';
- },
- noDot: true
- },
- bowtie: {
- n: 26,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M'+rs+','+rs+'V-'+rs+'L-'+rs+','+rs+'V-'+rs+'Z';
- },
- noDot: true
- },
- 'circle-cross': {
- n: 27,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+
- 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
- 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
- },
- needLine: true,
- noDot: true
- },
- 'circle-x': {
- n: 28,
- f: function(r) {
- var rs = d3.round(r,2),
- rc = d3.round(r/Math.sqrt(2),2);
- return 'M'+rc+','+rc+'L-'+rc+',-'+rc+
- 'M'+rc+',-'+rc+'L-'+rc+','+rc+
- 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
- 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
- },
- needLine: true,
- noDot: true
- },
- 'square-cross': {
- n: 29,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+
- 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
- },
- needLine: true,
- noDot: true
- },
- 'square-x': {
- n: 30,
- f: function(r) {
- var rs = d3.round(r,2);
- return 'M'+rs+','+rs+'L-'+rs+',-'+rs+
- 'M'+rs+',-'+rs+'L-'+rs+','+rs+
- 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
- },
- needLine: true,
- noDot: true
- },
- 'diamond-cross': {
- n: 31,
- f: function(r) {
- var rd = d3.round(r*1.3,2);
- return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+
- 'M0,-'+rd+'V'+rd+'M-'+rd+',0H'+rd;
- },
- needLine: true,
- noDot: true
- },
- 'diamond-x': {
- n: 32,
- f: function(r) {
- var rd = d3.round(r*1.3,2),
- r2 = d3.round(r*0.65,2);
- return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+
- 'M-'+r2+',-'+r2+'L'+r2+','+r2+
- 'M-'+r2+','+r2+'L'+r2+',-'+r2;
- },
- needLine: true,
- noDot: true
- },
- 'cross-thin': {
- n: 33,
- f: function(r) {
- var rc = d3.round(r*1.4,2);
- return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc;
- },
- needLine: true,
- noDot: true
- },
- 'x-thin': {
- n: 34,
- f: function(r) {
- var rx = d3.round(r,2);
- return 'M'+rx+','+rx+'L-'+rx+',-'+rx+
- 'M'+rx+',-'+rx+'L-'+rx+','+rx;
- },
- needLine: true,
- noDot: true
- },
- asterisk: {
- n: 35,
- f: function(r) {
- var rc = d3.round(r*1.2,2);
- var rs = d3.round(r*0.85,2);
- return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc+
- 'M'+rs+','+rs+'L-'+rs+',-'+rs+
- 'M'+rs+',-'+rs+'L-'+rs+','+rs;
- },
- needLine: true,
- noDot: true
- },
- hash: {
- n: 36,
- f: function(r) {
- var r1 = d3.round(r/2,2),
- r2 = d3.round(r,2);
- return 'M'+r1+','+r2+'V-'+r2+
- 'm-'+r2+',0V'+r2+
- 'M'+r2+','+r1+'H-'+r2+
- 'm0,-'+r2+'H'+r2;
- },
- needLine: true
- },
- 'y-up': {
- n: 37,
- f: function(r) {
- var x = d3.round(r*1.2,2),
- y0 = d3.round(r*1.6,2),
- y1 = d3.round(r*0.8,2);
- return 'M-'+x+','+y1+'L0,0M'+x+','+y1+'L0,0M0,-'+y0+'L0,0';
- },
- needLine: true,
- noDot: true
- },
- 'y-down': {
- n: 38,
- f: function(r) {
- var x = d3.round(r*1.2,2),
- y0 = d3.round(r*1.6,2),
- y1 = d3.round(r*0.8,2);
- return 'M-'+x+',-'+y1+'L0,0M'+x+',-'+y1+'L0,0M0,'+y0+'L0,0';
- },
- needLine: true,
- noDot: true
- },
- 'y-left': {
- n: 39,
- f: function(r) {
- var y = d3.round(r*1.2,2),
- x0 = d3.round(r*1.6,2),
- x1 = d3.round(r*0.8,2);
- return 'M'+x1+','+y+'L0,0M'+x1+',-'+y+'L0,0M-'+x0+',0L0,0';
- },
- needLine: true,
- noDot: true
- },
- 'y-right': {
- n: 40,
- f: function(r) {
- var y = d3.round(r*1.2,2),
- x0 = d3.round(r*1.6,2),
- x1 = d3.round(r*0.8,2);
- return 'M-'+x1+','+y+'L0,0M-'+x1+',-'+y+'L0,0M'+x0+',0L0,0';
- },
- needLine: true,
- noDot: true
- },
- 'line-ew': {
- n: 41,
- f: function(r) {
- var rc = d3.round(r*1.4,2);
- return 'M'+rc+',0H-'+rc;
- },
- needLine: true,
- noDot: true
- },
- 'line-ns': {
- n: 42,
- f: function(r) {
- var rc = d3.round(r*1.4,2);
- return 'M0,'+rc+'V-'+rc;
- },
- needLine: true,
- noDot: true
- },
- 'line-ne': {
- n: 43,
- f: function(r) {
- var rx = d3.round(r,2);
- return 'M'+rx+',-'+rx+'L-'+rx+','+rx;
- },
- needLine: true,
- noDot: true
- },
- 'line-nw': {
- n: 44,
- f: function(r) {
- var rx = d3.round(r,2);
- return 'M'+rx+','+rx+'L-'+rx+',-'+rx;
- },
- needLine: true,
- noDot: true
- }
-};
+var SYMBOLDEFS = require('./symbol_defs');
drawing.symbolNames = [];
drawing.symbolFuncs = [];
drawing.symbolNeedLines = {};
drawing.symbolNoDot = {};
drawing.symbolList = [];
+
Object.keys(SYMBOLDEFS).forEach(function(k) {
var symDef = SYMBOLDEFS[k];
drawing.symbolList = drawing.symbolList.concat(
diff --git a/src/components/drawing/symbol_defs.js b/src/components/drawing/symbol_defs.js
new file mode 100644
index 00000000000..2c461f2f003
--- /dev/null
+++ b/src/components/drawing/symbol_defs.js
@@ -0,0 +1,465 @@
+'use strict';
+
+var d3 = require('d3');
+
+/** Marker symbol definitions
+ * users can specify markers either by number or name
+ * add 100 (or '-open') and you get an open marker
+ * open markers have no fill and use line color as the stroke color
+ * add 200 (or '-dot') and you get a dot in the middle
+ * add both and you get both
+ */
+
+module.exports = {
+ circle: {
+ n: 0,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
+ 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
+ }
+ },
+ square: {
+ n: 1,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
+ }
+ },
+ diamond: {
+ n: 2,
+ f: function(r) {
+ var rd = d3.round(r*1.3,2);
+ return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z';
+ }
+ },
+ cross: {
+ n: 3,
+ f: function(r) {
+ var rc = d3.round(r*0.4,2),
+ rc2 = d3.round(r*1.2,2);
+ return 'M'+rc2+','+rc+'H'+rc+'V'+rc2+'H-'+rc+
+ 'V'+rc+'H-'+rc2+'V-'+rc+'H-'+rc+'V-'+rc2+
+ 'H'+rc+'V-'+rc+'H'+rc2+'Z';
+ }
+ },
+ x: {
+ n: 4,
+ f: function(r) {
+ var rx = d3.round(r*0.8/Math.sqrt(2),2),
+ ne = 'l'+rx+','+rx,
+ se = 'l'+rx+',-'+rx,
+ sw = 'l-'+rx+',-'+rx,
+ nw = 'l-'+rx+','+rx;
+ return 'M0,'+rx+ne+se+sw+se+sw+nw+sw+nw+ne+nw+ne+'Z';
+ }
+ },
+ 'triangle-up': {
+ n: 5,
+ f: function(r) {
+ var rt = d3.round(r*2/Math.sqrt(3),2),
+ r2 = d3.round(r/2,2),
+ rs = d3.round(r,2);
+ return 'M-'+rt+','+r2+'H'+rt+'L0,-'+rs+'Z';
+ }
+ },
+ 'triangle-down': {
+ n: 6,
+ f: function(r) {
+ var rt = d3.round(r*2/Math.sqrt(3),2),
+ r2 = d3.round(r/2,2),
+ rs = d3.round(r,2);
+ return 'M-'+rt+',-'+r2+'H'+rt+'L0,'+rs+'Z';
+ }
+ },
+ 'triangle-left': {
+ n: 7,
+ f: function(r) {
+ var rt = d3.round(r*2/Math.sqrt(3),2),
+ r2 = d3.round(r/2,2),
+ rs = d3.round(r,2);
+ return 'M'+r2+',-'+rt+'V'+rt+'L-'+rs+',0Z';
+ }
+ },
+ 'triangle-right': {
+ n: 8,
+ f: function(r) {
+ var rt = d3.round(r*2/Math.sqrt(3),2),
+ r2 = d3.round(r/2,2),
+ rs = d3.round(r,2);
+ return 'M-'+r2+',-'+rt+'V'+rt+'L'+rs+',0Z';
+ }
+ },
+ 'triangle-ne': {
+ n: 9,
+ f: function(r) {
+ var r1 = d3.round(r*0.6,2),
+ r2 = d3.round(r*1.2,2);
+ return 'M-'+r2+',-'+r1+'H'+r1+'V'+r2+'Z';
+ }
+ },
+ 'triangle-se': {
+ n: 10,
+ f: function(r) {
+ var r1 = d3.round(r*0.6,2),
+ r2 = d3.round(r*1.2,2);
+ return 'M'+r1+',-'+r2+'V'+r1+'H-'+r2+'Z';
+ }
+ },
+ 'triangle-sw': {
+ n: 11,
+ f: function(r) {
+ var r1 = d3.round(r*0.6,2),
+ r2 = d3.round(r*1.2,2);
+ return 'M'+r2+','+r1+'H-'+r1+'V-'+r2+'Z';
+ }
+ },
+ 'triangle-nw': {
+ n: 12,
+ f: function(r) {
+ var r1 = d3.round(r*0.6,2),
+ r2 = d3.round(r*1.2,2);
+ return 'M-'+r1+','+r2+'V-'+r1+'H'+r2+'Z';
+ }
+ },
+ pentagon: {
+ n: 13,
+ f: function(r) {
+ var x1 = d3.round(r*0.951,2),
+ x2 = d3.round(r*0.588,2),
+ y0 = d3.round(-r,2),
+ y1 = d3.round(r*-0.309,2),
+ y2 = d3.round(r*0.809,2);
+ return 'M'+x1+','+y1+'L'+x2+','+y2+'H-'+x2+
+ 'L-'+x1+','+y1+'L0,'+y0+'Z';
+ }
+ },
+ hexagon: {
+ n: 14,
+ f: function(r) {
+ var y0 = d3.round(r,2),
+ y1 = d3.round(r/2,2),
+ x = d3.round(r*Math.sqrt(3)/2,2);
+ return 'M'+x+',-'+y1+'V'+y1+'L0,'+y0+
+ 'L-'+x+','+y1+'V-'+y1+'L0,-'+y0+'Z';
+ }
+ },
+ hexagon2: {
+ n: 15,
+ f: function(r) {
+ var x0 = d3.round(r,2),
+ x1 = d3.round(r/2,2),
+ y = d3.round(r*Math.sqrt(3)/2,2);
+ return 'M-'+x1+','+y+'H'+x1+'L'+x0+
+ ',0L'+x1+',-'+y+'H-'+x1+'L-'+x0+',0Z';
+ }
+ },
+ octagon: {
+ n: 16,
+ f: function(r) {
+ var a = d3.round(r*0.924,2),
+ b = d3.round(r*0.383,2);
+ return 'M-'+b+',-'+a+'H'+b+'L'+a+',-'+b+'V'+b+
+ 'L'+b+','+a+'H-'+b+'L-'+a+','+b+'V-'+b+'Z';
+ }
+ },
+ star: {
+ n: 17,
+ f: function(r) {
+ var rs = r*1.4,
+ x1 = d3.round(rs*0.225,2),
+ x2 = d3.round(rs*0.951,2),
+ x3 = d3.round(rs*0.363,2),
+ x4 = d3.round(rs*0.588,2),
+ y0 = d3.round(-rs,2),
+ y1 = d3.round(rs*-0.309,2),
+ y3 = d3.round(rs*0.118,2),
+ y4 = d3.round(rs*0.809,2),
+ y5 = d3.round(rs*0.382,2);
+ return 'M'+x1+','+y1+'H'+x2+'L'+x3+','+y3+
+ 'L'+x4+','+y4+'L0,'+y5+'L-'+x4+','+y4+
+ 'L-'+x3+','+y3+'L-'+x2+','+y1+'H-'+x1+
+ 'L0,'+y0+'Z';
+ }
+ },
+ hexagram: {
+ n: 18,
+ f: function(r) {
+ var y = d3.round(r*0.66,2),
+ x1 = d3.round(r*0.38,2),
+ x2 = d3.round(r*0.76,2);
+ return 'M-'+x2+',0l-'+x1+',-'+y+'h'+x2+
+ 'l'+x1+',-'+y+'l'+x1+','+y+'h'+x2+
+ 'l-'+x1+','+y+'l'+x1+','+y+'h-'+x2+
+ 'l-'+x1+','+y+'l-'+x1+',-'+y+'h-'+x2+'Z';
+ }
+ },
+ 'star-triangle-up': {
+ n: 19,
+ f: function(r) {
+ var x = d3.round(r*Math.sqrt(3)*0.8,2),
+ y1 = d3.round(r*0.8,2),
+ y2 = d3.round(r*1.6,2),
+ rc = d3.round(r*4,2),
+ aPart = 'A '+rc+','+rc+' 0 0 1 ';
+ return 'M-'+x+','+y1+aPart+x+','+y1+
+ aPart+'0,-'+y2+aPart+'-'+x+','+y1+'Z';
+ }
+ },
+ 'star-triangle-down': {
+ n: 20,
+ f: function(r) {
+ var x = d3.round(r*Math.sqrt(3)*0.8,2),
+ y1 = d3.round(r*0.8,2),
+ y2 = d3.round(r*1.6,2),
+ rc = d3.round(r*4,2),
+ aPart = 'A '+rc+','+rc+' 0 0 1 ';
+ return 'M'+x+',-'+y1+aPart+'-'+x+',-'+y1+
+ aPart+'0,'+y2+aPart+x+',-'+y1+'Z';
+ }
+ },
+ 'star-square': {
+ n: 21,
+ f: function(r) {
+ var rp = d3.round(r*1.1,2),
+ rc = d3.round(r*2,2),
+ aPart = 'A '+rc+','+rc+' 0 0 1 ';
+ return 'M-'+rp+',-'+rp+aPart+'-'+rp+','+rp+
+ aPart+rp+','+rp+aPart+rp+',-'+rp+
+ aPart+'-'+rp+',-'+rp+'Z';
+ }
+ },
+ 'star-diamond': {
+ n: 22,
+ f: function(r) {
+ var rp = d3.round(r*1.4,2),
+ rc = d3.round(r*1.9,2),
+ aPart = 'A '+rc+','+rc+' 0 0 1 ';
+ return 'M-'+rp+',0'+aPart+'0,'+rp+
+ aPart+rp+',0'+aPart+'0,-'+rp+
+ aPart+'-'+rp+',0'+'Z';
+ }
+ },
+ 'diamond-tall': {
+ n: 23,
+ f: function(r) {
+ var x = d3.round(r*0.7,2),
+ y = d3.round(r*1.4,2);
+ return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z';
+ }
+ },
+ 'diamond-wide': {
+ n: 24,
+ f: function(r) {
+ var x = d3.round(r*1.4,2),
+ y = d3.round(r*0.7,2);
+ return 'M0,'+y+'L'+x+',0L0,-'+y+'L-'+x+',0Z';
+ }
+ },
+ hourglass: {
+ n: 25,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M'+rs+','+rs+'H-'+rs+'L'+rs+',-'+rs+'H-'+rs+'Z';
+ },
+ noDot: true
+ },
+ bowtie: {
+ n: 26,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M'+rs+','+rs+'V-'+rs+'L-'+rs+','+rs+'V-'+rs+'Z';
+ },
+ noDot: true
+ },
+ 'circle-cross': {
+ n: 27,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+
+ 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
+ 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'circle-x': {
+ n: 28,
+ f: function(r) {
+ var rs = d3.round(r,2),
+ rc = d3.round(r/Math.sqrt(2),2);
+ return 'M'+rc+','+rc+'L-'+rc+',-'+rc+
+ 'M'+rc+',-'+rc+'L-'+rc+','+rc+
+ 'M'+rs+',0A'+rs+','+rs+' 0 1,1 0,-'+rs+
+ 'A'+rs+','+rs+' 0 0,1 '+rs+',0Z';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'square-cross': {
+ n: 29,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M0,'+rs+'V-'+rs+'M'+rs+',0H-'+rs+
+ 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'square-x': {
+ n: 30,
+ f: function(r) {
+ var rs = d3.round(r,2);
+ return 'M'+rs+','+rs+'L-'+rs+',-'+rs+
+ 'M'+rs+',-'+rs+'L-'+rs+','+rs+
+ 'M'+rs+','+rs+'H-'+rs+'V-'+rs+'H'+rs+'Z';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'diamond-cross': {
+ n: 31,
+ f: function(r) {
+ var rd = d3.round(r*1.3,2);
+ return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+
+ 'M0,-'+rd+'V'+rd+'M-'+rd+',0H'+rd;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'diamond-x': {
+ n: 32,
+ f: function(r) {
+ var rd = d3.round(r*1.3,2),
+ r2 = d3.round(r*0.65,2);
+ return 'M'+rd+',0L0,'+rd+'L-'+rd+',0L0,-'+rd+'Z'+
+ 'M-'+r2+',-'+r2+'L'+r2+','+r2+
+ 'M-'+r2+','+r2+'L'+r2+',-'+r2;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'cross-thin': {
+ n: 33,
+ f: function(r) {
+ var rc = d3.round(r*1.4,2);
+ return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'x-thin': {
+ n: 34,
+ f: function(r) {
+ var rx = d3.round(r,2);
+ return 'M'+rx+','+rx+'L-'+rx+',-'+rx+
+ 'M'+rx+',-'+rx+'L-'+rx+','+rx;
+ },
+ needLine: true,
+ noDot: true
+ },
+ asterisk: {
+ n: 35,
+ f: function(r) {
+ var rc = d3.round(r*1.2,2);
+ var rs = d3.round(r*0.85,2);
+ return 'M0,'+rc+'V-'+rc+'M'+rc+',0H-'+rc+
+ 'M'+rs+','+rs+'L-'+rs+',-'+rs+
+ 'M'+rs+',-'+rs+'L-'+rs+','+rs;
+ },
+ needLine: true,
+ noDot: true
+ },
+ hash: {
+ n: 36,
+ f: function(r) {
+ var r1 = d3.round(r/2,2),
+ r2 = d3.round(r,2);
+ return 'M'+r1+','+r2+'V-'+r2+
+ 'm-'+r2+',0V'+r2+
+ 'M'+r2+','+r1+'H-'+r2+
+ 'm0,-'+r2+'H'+r2;
+ },
+ needLine: true
+ },
+ 'y-up': {
+ n: 37,
+ f: function(r) {
+ var x = d3.round(r*1.2,2),
+ y0 = d3.round(r*1.6,2),
+ y1 = d3.round(r*0.8,2);
+ return 'M-'+x+','+y1+'L0,0M'+x+','+y1+'L0,0M0,-'+y0+'L0,0';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'y-down': {
+ n: 38,
+ f: function(r) {
+ var x = d3.round(r*1.2,2),
+ y0 = d3.round(r*1.6,2),
+ y1 = d3.round(r*0.8,2);
+ return 'M-'+x+',-'+y1+'L0,0M'+x+',-'+y1+'L0,0M0,'+y0+'L0,0';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'y-left': {
+ n: 39,
+ f: function(r) {
+ var y = d3.round(r*1.2,2),
+ x0 = d3.round(r*1.6,2),
+ x1 = d3.round(r*0.8,2);
+ return 'M'+x1+','+y+'L0,0M'+x1+',-'+y+'L0,0M-'+x0+',0L0,0';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'y-right': {
+ n: 40,
+ f: function(r) {
+ var y = d3.round(r*1.2,2),
+ x0 = d3.round(r*1.6,2),
+ x1 = d3.round(r*0.8,2);
+ return 'M-'+x1+','+y+'L0,0M-'+x1+',-'+y+'L0,0M'+x0+',0L0,0';
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'line-ew': {
+ n: 41,
+ f: function(r) {
+ var rc = d3.round(r*1.4,2);
+ return 'M'+rc+',0H-'+rc;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'line-ns': {
+ n: 42,
+ f: function(r) {
+ var rc = d3.round(r*1.4,2);
+ return 'M0,'+rc+'V-'+rc;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'line-ne': {
+ n: 43,
+ f: function(r) {
+ var rx = d3.round(r,2);
+ return 'M'+rx+',-'+rx+'L-'+rx+','+rx;
+ },
+ needLine: true,
+ noDot: true
+ },
+ 'line-nw': {
+ n: 44,
+ f: function(r) {
+ var rx = d3.round(r,2);
+ return 'M'+rx+','+rx+'L-'+rx+',-'+rx;
+ },
+ needLine: true,
+ noDot: true
+ }
+};
diff --git a/src/components/errorbars/attributes.js b/src/components/errorbars/attributes.js
new file mode 100644
index 00000000000..9d20961e876
--- /dev/null
+++ b/src/components/errorbars/attributes.js
@@ -0,0 +1,129 @@
+module.exports = {
+ visible: {
+ valType: 'boolean',
+ role: 'info',
+ description: [
+ 'Determines whether or not this set of error bars is visible.'
+ ].join(' ')
+ },
+ type: {
+ valType: 'enumerated',
+ values: ['percent', 'constant', 'sqrt', 'data'],
+ role: 'info',
+ description: [
+ 'Determines the rule used to generate the error bars.',
+
+ 'If *constant`, the bar lengths are of a constant value.',
+ 'Set this constant in `value`.',
+
+ 'If *percent*, the bar lengths correspond to a percentage of',
+ 'underlying data. Set this percentage in `value`.',
+
+ 'If *sqrt*, the bar lengths correspond to the sqaure of the',
+ 'underlying data.',
+
+ 'If *array*, the bar lengths are set with data set `array`.'
+ ].join(' ')
+ },
+ symmetric: {
+ valType: 'boolean',
+ role: 'info',
+ description: [
+ 'Determines whether or not the error bars have the same length',
+ 'in both direction',
+ '(top/bottom for vertical bars, left/right for horizontal bars.'
+ ].join(' ')
+ },
+ array: {
+ valType: 'data_array',
+ description: [
+ 'Sets the data corresponding the length of each error bar.',
+ 'Values are plotted relative to the underlying data.'
+ ].join(' ')
+ },
+ arrayminus: {
+ valType: 'data_array',
+ description: [
+ 'Sets the data corresponding the length of each error bar in the',
+ 'bottom (left) direction for vertical (horizontal) bars',
+ 'Values are plotted relative to the underlying data.'
+ ].join(' ')
+ },
+ value: {
+ valType: 'number',
+ min: 0,
+ dflt: 10,
+ role: 'info',
+ description: [
+ 'Sets the value of either the percentage',
+ '(if `type` is set to *percent*) or the constant',
+ '(if `type` is set to *constant*) corresponding to the lengths of',
+ 'the error bars.'
+ ].join(' ')
+ },
+ valueminus: {
+ valType: 'number',
+ min: 0,
+ dflt: 10,
+ role: 'info',
+ description: [
+ 'Sets the value of either the percentage',
+ '(if `type` is set to *percent*) or the constant',
+ '(if `type` is set to *constant*) corresponding to the lengths of',
+ 'the error bars in the',
+ 'bottom (left) direction for vertical (horizontal) bars'
+ ].join(' ')
+ },
+ traceref: {
+ valType: 'integer',
+ min: 0,
+ dflt: 0,
+ role: 'info'
+ },
+ tracerefminus: {
+ valType: 'integer',
+ min: 0,
+ dflt: 0,
+ role: 'info'
+ },
+ copy_ystyle: {
+ valType: 'boolean',
+ role: 'style'
+ },
+ copy_zstyle: {
+ valType: 'boolean',
+ role: 'style'
+ },
+ color: {
+ valType: 'color',
+ role: 'style',
+ description: 'Sets the stoke color of the error bars.'
+ },
+ thickness: {
+ valType: 'number',
+ min: 0,
+ dflt: 2,
+ role: 'style',
+ description: 'Sets the thickness (in px) of the error bars.'
+ },
+ width: {
+ valType: 'number',
+ min: 0,
+ role: 'style',
+ description: [
+ 'Sets the width (in px) of the cross-bar at both ends',
+ 'of the error bars.'
+ ].join(' ')
+ },
+
+ _deprecated: {
+ opacity: {
+ valType: 'number',
+ role: 'style',
+ description: [
+ 'Obsolete.',
+ 'Use the alpha channel in error bar `color` to set the opacity.'
+ ].join(' ')
+ }
+ }
+};
diff --git a/src/errorbars.js b/src/components/errorbars/index.js
similarity index 74%
rename from src/errorbars.js
rename to src/components/errorbars/index.js
index 05e2aff4c17..39537a73030 100644
--- a/src/errorbars.js
+++ b/src/components/errorbars/index.js
@@ -1,142 +1,12 @@
'use strict';
-/* jshint camelcase: false */
-
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var errorBars = module.exports = {};
-errorBars.attributes = {
- visible: {
- valType: 'boolean',
- role: 'info',
- description: [
- 'Determines whether or not this set of error bars is visible.'
- ].join(' ')
- },
- type: {
- valType: 'enumerated',
- values: ['percent', 'constant', 'sqrt', 'data'],
- role: 'info',
- description: [
- 'Determines the rule used to generate the error bars.',
-
- 'If *constant`, the bar lengths are of a constant value.',
- 'Set this constant in `value`.',
-
- 'If *percent*, the bar lengths correspond to a percentage of',
- 'underlying data. Set this percentage in `value`.',
-
- 'If *sqrt*, the bar lengths correspond to the sqaure of the',
- 'underlying data.',
-
- 'If *array*, the bar lengths are set with data set `array`.'
- ].join(' ')
- },
- symmetric: {
- valType: 'boolean',
- role: 'info',
- description: [
- 'Determines whether or not the error bars have the same length',
- 'in both direction',
- '(top/bottom for vertical bars, left/right for horizontal bars.'
- ].join(' ')
- },
- array: {
- valType: 'data_array',
- description: [
- 'Sets the data corresponding the length of each error bar.',
- 'Values are plotted relative to the underlying data.'
- ].join(' ')
- },
- arrayminus: {
- valType: 'data_array',
- description: [
- 'Sets the data corresponding the length of each error bar in the',
- 'bottom (left) direction for vertical (horizontal) bars',
- 'Values are plotted relative to the underlying data.'
- ].join(' ')
- },
- value: {
- valType: 'number',
- min: 0,
- dflt: 10,
- role: 'info',
- description: [
- 'Sets the value of either the percentage',
- '(if `type` is set to *percent*) or the constant',
- '(if `type` is set to *constant*) corresponding to the lengths of',
- 'the error bars.'
- ].join(' ')
- },
- valueminus: {
- valType: 'number',
- min: 0,
- dflt: 10,
- role: 'info',
- description: [
- 'Sets the value of either the percentage',
- '(if `type` is set to *percent*) or the constant',
- '(if `type` is set to *constant*) corresponding to the lengths of',
- 'the error bars in the',
- 'bottom (left) direction for vertical (horizontal) bars'
- ].join(' ')
- },
- traceref: {
- valType: 'integer',
- min: 0,
- dflt: 0,
- role: 'info'
- },
- tracerefminus: {
- valType: 'integer',
- min: 0,
- dflt: 0,
- role: 'info'
- },
- copy_ystyle: {
- valType: 'boolean',
- role: 'style'
- },
- copy_zstyle: {
- valType: 'boolean',
- role: 'style'
- },
- color: {
- valType: 'color',
- role: 'style',
- description: 'Sets the stoke color of the error bars.'
- },
- thickness: {
- valType: 'number',
- min: 0,
- dflt: 2,
- role: 'style',
- description: 'Sets the thickness (in px) of the error bars.'
- },
- width: {
- valType: 'number',
- min: 0,
- role: 'style',
- description: [
- 'Sets the width (in px) of the cross-bar at both ends',
- 'of the error bars.'
- ].join(' ')
- },
-
- _deprecated: {
- opacity: {
- valType: 'number',
- role: 'style',
- description: [
- 'Obsolete.',
- 'Use the alpha channel in error bar `color` to set the opacity.'
- ].join(' ')
- }
- }
-};
+errorBars.attributes = require('./attributes');
errorBars.supplyDefaults = function(traceIn, traceOut, defaultColor, opts) {
var objName = 'error_' + opts.axis,
diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js
new file mode 100644
index 00000000000..94c3033fc55
--- /dev/null
+++ b/src/components/legend/attributes.js
@@ -0,0 +1,96 @@
+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.'
+ }),
+ traceorder: {
+ valType: 'flaglist',
+ flags: ['reversed', 'grouped'],
+ extras: ['normal'],
+ role: 'style',
+ description: [
+ 'Determines the order at which the legend items are displayed.',
+
+ 'If *normal*, the items are displayed top-to-bottom in the same',
+ 'order as the input data.',
+
+ 'If *reversed*, the items are displayed in the opposite order',
+ 'as *normal*.',
+
+ 'If *grouped*, the items are displayed in groups',
+ '(when a trace `legendgroup` is provided).',
+
+ 'if *grouped+reversed*, the items are displayed in the opposite order',
+ 'as *grouped*.'
+ ].join(' ')
+ },
+ tracegroupgap: {
+ valType: 'number',
+ min: 0,
+ dflt: 10,
+ role: 'style',
+ description: [
+ 'Sets the amount of vertical space (in px) between legend groups.'
+ ].join(' ')
+ },
+ x: {
+ valType: 'number',
+ min: -2,
+ max: 3,
+ dflt: 1.02,
+ role: 'style',
+ description: 'Sets the x position (in normalized coordinates) of the legend.'
+ },
+ xanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'left', 'center', 'right'],
+ dflt: 'left',
+ role: 'info',
+ description: [
+ 'Sets the legend\'s horizontal position anchor.',
+ 'This anchor binds the `x` position to the *left*, *center*',
+ 'or *right* of the legend.'
+ ].join(' ')
+ },
+ y: {
+ valType: 'number',
+ min: -2,
+ max: 3,
+ dflt: 1,
+ role: 'style',
+ description: 'Sets the y position (in normalized coordinates) of the legend.'
+ },
+ yanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'top', 'middle', 'bottom'],
+ dflt: 'auto',
+ role: 'info',
+ description: [
+ 'Sets the legend\'s vertical position anchor',
+ 'This anchor binds the `y` position to the *top*, *middle*',
+ 'or *bottom* of the legend.'
+ ].join(' ')
+ }
+};
diff --git a/src/legend.js b/src/components/legend/index.js
similarity index 88%
rename from src/legend.js
rename to src/components/legend/index.js
index 42fb366d050..9ee54e11ee0 100644
--- a/src/legend.js
+++ b/src/components/legend/index.js
@@ -1,103 +1,11 @@
'use strict';
-/* jshint camelcase: false */
-
-var Plotly = require('./plotly'),
- d3 = require('d3');
+var Plotly = require('../../plotly');
+var d3 = require('d3');
var legend = module.exports = {};
-legend.layoutAttributes = {
- bgcolor: {
- valType: 'color',
- role: 'style',
- description: 'Sets the legend background color.'
- },
- bordercolor: {
- valType: 'color',
- dflt: Plotly.Color.defaultLine,
- role: 'style',
- description: 'Sets the color of the border enclosing the legend.'
- },
- borderwidth: {
- valType: 'number',
- min: 0,
- dflt: 0,
- role: 'style',
- description: 'Sets the width (in px) of the border enclosing the legend.'
- },
- font: Plotly.Lib.extendFlat({}, Plotly.Plots.fontAttrs, {
- description: 'Sets the font used to text the legend items.'
- }),
- traceorder: {
- valType: 'flaglist',
- flags: ['reversed', 'grouped'],
- extras: ['normal'],
- role: 'style',
- description: [
- 'Determines the order at which the legend items are displayed.',
-
- 'If *normal*, the items are displayed top-to-bottom in the same',
- 'order as the input data.',
-
- 'If *reversed*, the items are displayed in the opposite order',
- 'as *normal*.',
-
- 'If *grouped*, the items are displayed in groups',
- '(when a trace `legendgroup` is provided).',
-
- 'if *grouped+reversed*, the items are displayed in the opposite order',
- 'as *grouped*.'
- ].join(' ')
- },
- tracegroupgap: {
- valType: 'number',
- min: 0,
- dflt: 10,
- role: 'style',
- description: [
- 'Sets the amount of vertical space (in px) between legend groups.'
- ].join(' ')
- },
- x: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: 1.02,
- role: 'style',
- description: 'Sets the x position (in normalized coordinates) of the legend.'
- },
- xanchor: {
- valType: 'enumerated',
- values: ['auto', 'left', 'center', 'right'],
- dflt: 'left',
- role: 'info',
- description: [
- 'Sets the legend\'s horizontal position anchor.',
- 'This anchor binds the `x` position to the *left*, *center*',
- 'or *right* of the legend.'
- ].join(' ')
- },
- y: {
- valType: 'number',
- min: -2,
- max: 3,
- dflt: 1,
- role: 'style',
- description: 'Sets the y position (in normalized coordinates) of the legend.'
- },
- yanchor: {
- valType: 'enumerated',
- values: ['auto', 'top', 'middle', 'bottom'],
- dflt: 'auto',
- role: 'info',
- description: [
- 'Sets the legend\'s vertical position anchor',
- 'This anchor binds the `y` position to the *top*, *middle*',
- 'or *bottom* of the legend.'
- ].join(' ')
- }
-};
+legend.layoutAttributes = require('./attributes');
legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) {
var containerIn = layoutIn.legend || {},
diff --git a/src/modebar.js b/src/components/modebar/index.js
similarity index 99%
rename from src/modebar.js
rename to src/components/modebar/index.js
index c47fbbddb61..0396a1928af 100644
--- a/src/modebar.js
+++ b/src/components/modebar/index.js
@@ -1,7 +1,7 @@
'use strict';
-var Plotly = require('./plotly'),
- d3 = require('d3');
+var Plotly = require('../../plotly');
+var d3 = require('d3');
/**
* UI controller for interactive plots
diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js
new file mode 100644
index 00000000000..f19c3384d17
--- /dev/null
+++ b/src/components/shapes/attributes.js
@@ -0,0 +1,134 @@
+var annAttrs = require('../annotations/attributes');
+var scatterAttrs = require('../../traces/scatter/attributes');
+var extendFlat = require('../../lib/extend').extendFlat;
+
+var scatterLineAttrs = scatterAttrs.line;
+
+module.exports = {
+ _isLinkedToArray: true,
+
+ type: {
+ valType: 'enumerated',
+ values: ['circle', 'rect', 'path', 'line'],
+ role: 'info',
+ description: [
+ 'Specifies the shape type to be drawn.',
+
+ 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)',
+
+ 'If *circle*, a circle is drawn from',
+ '((`x0`+`x1`)/2, (`y0`+`y1`)/2))',
+ 'with radius',
+ '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)',
+
+ 'If *rect*, a rectangle is drawn linking',
+ '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)',
+
+ 'If *path*, draw a custom SVG path using `path`.'
+ ].join(' ')
+ },
+
+ xref: extendFlat({}, 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.'
+ ].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/shapes.js b/src/components/shapes/index.js
similarity index 77%
rename from src/shapes.js
rename to src/components/shapes/index.js
index 1459d2d2ea3..6ed2dd97c31 100644
--- a/src/shapes.js
+++ b/src/components/shapes/index.js
@@ -1,140 +1,11 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../../plotly');
var isNumeric = require('fast-isnumeric');
var shapes = module.exports = {};
-var extendFlat = Plotly.Lib.extendFlat,
- scatterLineAttrs = Plotly.Scatter.attributes.line;
-
-shapes.layoutAttributes = {
- _isLinkedToArray: true,
- type: {
- valType: 'enumerated',
- values: ['circle', 'rect', 'path', 'line'],
- role: 'info',
- description: [
- 'Specifies the shape type to be drawn.',
-
- 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)',
-
- 'If *circle*, a circle is drawn from',
- '((`x0`+`x1`)/2, (`y0`+`y1`)/2))',
- 'with radius',
- '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)',
-
- 'If *rect*, a rectangle is drawn linking',
- '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)',
-
- 'If *path*, draw a custom SVG path using `path`.'
- ].join(' ')
- },
-
- xref: extendFlat({}, Plotly.Annotations.layoutAttributes.xref, {
- description: [
- 'Sets the shape\'s x coordinate axis.',
- 'If set to an x axis id (e.g. *x* or *x2*), the `x` position',
- 'refers to an x coordinate',
- 'If set to *paper*, the `x` position refers to the distance from',
- 'the left side of the plotting area in normalized coordinates',
- 'where *0* (*1*) corresponds to the left (right) side.'
- ].join(' ')
- }),
- x0: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the shape\'s starting x position.',
- 'See `type` for more info.'
- ].join(' ')
- },
- x1: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the shape\'s end x position.',
- 'See `type` for more info.'
- ].join(' ')
- },
-
- yref: extendFlat({}, Plotly.Annotations.layoutAttributes.yref, {
- description: [
- 'Sets the annotation\'s y coordinate axis.',
- 'If set to an y axis id (e.g. *y* or *y2*), the `y` position',
- 'refers to an y coordinate',
- 'If set to *paper*, the `y` position refers to the distance from',
- 'the bottom of the plotting area in normalized coordinates',
- 'where *0* (*1*) corresponds to the bottom (top).'
- ].join(' ')
- }),
- y0: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the shape\'s starting y position.',
- 'See `type` for more info.'
- ].join(' ')
- },
- y1: {
- valType: 'any',
- role: 'info',
- description: [
- 'Sets the shape\'s end y position.',
- 'See `type` for more info.'
- ].join(' ')
- },
-
- path: {
- valType: 'string',
- role: 'info',
- description: [
- 'For `type` *path* - a valid SVG path but with the pixel values',
- 'replaced by data values. There are a few restrictions / quirks',
- 'only absolute instructions, not relative. So the allowed segments',
- 'are: M, L, H, V, Q, C, T, S, and Z',
- 'arcs (A) are not allowed because radius rx and ry are relative.',
-
- 'In the future we could consider supporting relative commands,',
- 'but we would have to decide on how to handle date and log axes.',
- 'Note that even as is, Q and C Bezier paths that are smooth on',
- 'linear axes may not be smooth on log, and vice versa.',
- 'no chained "polybezier" commands - specify the segment type for',
- 'each one.',
-
- 'On category axes, values are numbers scaled to the serial numbers',
- 'of categories because using the categories themselves there would',
- 'be no way to describe fractional positions',
- 'On data axes: because space and T are both normal components of path',
- 'strings, we can\'t use either to separate date from time parts.',
- 'Therefore we\'ll use underscore for this purpose:',
- '2015-02-21_13:45:56.789'
- ].join(' ')
- },
-
- opacity: {
- valType: 'number',
- min: 0,
- max: 1,
- dflt: 1,
- role: 'info',
- description: 'Sets the opacity of the shape.'
- },
- line: {
- color: scatterLineAttrs.color,
- width: scatterLineAttrs.width,
- dash: scatterLineAttrs.dash,
- role: 'info'
- },
- fillcolor: {
- valType: 'color',
- dflt: 'rgba(0,0,0,0)',
- role: 'info',
- description: [
- 'Sets the color filling the shape\'s interior.'
- ].join(' ')
- }
-};
+shapes.layoutAttributes = require('./attributes');
shapes.supplyLayoutDefaults = function(layoutIn, layoutOut) {
var containerIn = layoutIn.shapes || [],
diff --git a/src/components/titles/index.js b/src/components/titles/index.js
new file mode 100644
index 00000000000..e390d09107c
--- /dev/null
+++ b/src/components/titles/index.js
@@ -0,0 +1,304 @@
+'use strict';
+
+var Plotly = require('../../plotly');
+var d3 = require('d3');
+var isNumeric = require('fast-isnumeric');
+
+var plots = Plotly.Plots;
+
+var Titles = module.exports = {};
+
+// titles - (re)draw titles on the axes and plot
+// title can be 'xtitle', 'ytitle', 'gtitle',
+// or empty to draw all
+Titles.draw = function(gd, title) {
+ if(!title) {
+ Plotly.Axes.listIds(gd).forEach(function(axId) {
+ Titles.draw(gd, axId + 'title');
+ });
+ Titles.draw(gd, 'gtitle');
+ return;
+ }
+
+ var fullLayout = gd._fullLayout,
+ gs = fullLayout._size,
+ axletter = title.charAt(0),
+ colorbar = title.substr(1,2)==='cb';
+
+ var cbnum, cont, options;
+
+ if(colorbar) {
+ var uid = title.substr(3).replace('title','');
+ gd._fullData.some(function(trace, i) {
+ if(trace.uid===uid) {
+ cbnum = i;
+ cont = gd.calcdata[i][0].t.cb.axis;
+ return true;
+ }
+ });
+ }
+ else cont = fullLayout[Plotly.Axes.id2name(title.replace('title',''))] || fullLayout;
+
+ var prop = cont===fullLayout ? 'title' : cont._name+'.title',
+ name = colorbar ? 'colorscale' :
+ ((cont._id||axletter).toUpperCase()+' axis'),
+ font = cont.titlefont.family,
+ fontSize = cont.titlefont.size,
+ fontColor = cont.titlefont.color,
+ x,
+ y,
+ transform='',
+ attr = {},
+ xa,
+ ya,
+ avoid = {
+ selection:d3.select(gd).selectAll('g.'+cont._id+'tick'),
+ side:cont.side
+ },
+ // multiples of fontsize to offset label from axis
+ offsetBase = colorbar ? 0 : 1.5,
+ avoidTransform;
+
+ // find the transform applied to the parents of the avoid selection
+ // which doesn't get picked up by Plotly.Drawing.bBox
+ if(colorbar) {
+ avoid.offsetLeft = gs.l;
+ avoid.offsetTop = gs.t;
+ }
+ else if(avoid.selection.size()) {
+ avoidTransform = d3.select(avoid.selection.node().parentNode)
+ .attr('transform')
+ .match(/translate\(([-\.\d]+),([-\.\d]+)\)/);
+ if(avoidTransform) {
+ avoid.offsetLeft = +avoidTransform[1];
+ avoid.offsetTop = +avoidTransform[2];
+ }
+ }
+
+ if(colorbar && cont.titleside) {
+ // argh, we only make it here if the title is on top or bottom,
+ // not right
+ x = gs.l+cont.titlex*gs.w;
+ y = gs.t+(1-cont.titley)*gs.h + ((cont.titleside==='top') ?
+ 3+fontSize*0.75 : - 3-fontSize*0.25);
+ options = {x: x, y: y, 'text-anchor':'start'};
+ avoid = {};
+
+ // convertToTspans rotates any 'y...' by 90 degrees...
+ // TODO: need a better solution than this hack
+ title = 'h'+title;
+ }
+ else if(axletter==='x'){
+ xa = cont;
+ ya = (xa.anchor==='free') ?
+ {_offset:gs.t+(1-(xa.position||0))*gs.h, _length:0} :
+ Plotly.Axes.getFromId(gd, xa.anchor);
+ x = xa._offset+xa._length/2;
+ y = ya._offset + ((xa.side==='top') ?
+ -10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) :
+ ya._length + 10 +
+ fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5)));
+ options = {x: x, y: y, 'text-anchor': 'middle'};
+ if(!avoid.side) { avoid.side = 'bottom'; }
+ }
+ else if(axletter==='y'){
+ ya = cont;
+ xa = (ya.anchor==='free') ?
+ {_offset:gs.l+(ya.position||0)*gs.w, _length:0} :
+ Plotly.Axes.getFromId(gd, ya.anchor);
+ y = ya._offset+ya._length/2;
+ x = xa._offset + ((ya.side==='right') ?
+ xa._length + 10 +
+ fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) :
+ -10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0)));
+ attr = {center: 0};
+ options = {x: x, y: y, 'text-anchor': 'middle'};
+ transform = {rotate: '-90', offset: 0};
+ if(!avoid.side) { avoid.side = 'left'; }
+ }
+ else{
+ // plot title
+ name = 'Plot';
+ fontSize = fullLayout.titlefont.size;
+ x = fullLayout.width/2;
+ y = fullLayout._size.t/2;
+ options = {x: x, y: y, 'text-anchor': 'middle'};
+ avoid = {};
+ }
+
+ var opacity = 1,
+ isplaceholder = false,
+ txt = cont.title.trim();
+ if(txt === '') { opacity = 0; }
+ if(txt.match(/Click to enter .+ title/)) {
+ opacity = 0.2;
+ isplaceholder = true;
+ }
+
+ var group;
+ if(colorbar) {
+ group = d3.select(gd)
+ .selectAll('.'+cont._id.substr(1)+' .cbtitle');
+ // this class-to-rotate thing with convertToTspans is
+ // getting hackier and hackier... delete groups with the
+ // wrong class
+ var otherClass = title.charAt(0)==='h' ?
+ title.substr(1) : ('h'+title);
+ group.selectAll('.'+otherClass+',.'+otherClass+'-math-group')
+ .remove();
+ }
+ else {
+ group = fullLayout._infolayer.selectAll('.g-'+title)
+ .data([0]);
+ group.enter().append('g')
+ .classed('g-'+title, true);
+ }
+
+ var el = group.selectAll('text')
+ .data([0]);
+ el.enter().append('text');
+ el.text(txt)
+ // this is hacky, but convertToTspans uses the class
+ // to determine whether to rotate mathJax...
+ // so we need to clear out any old class and put the
+ // correct one (only relevant for colorbars, at least
+ // for now) - ie don't use .classed
+ .attr('class', title);
+
+ function titleLayout(titleEl){
+ Plotly.Lib.syncOrAsync([drawTitle,scootTitle], titleEl);
+ }
+
+ function drawTitle(titleEl) {
+ titleEl.attr('transform', transform ?
+ 'rotate(' + [transform.rotate, options.x, options.y] +
+ ') translate(0, '+transform.offset+')' :
+ null);
+ titleEl.style({
+ 'font-family': font,
+ 'font-size': d3.round(fontSize,2)+'px',
+ fill: Plotly.Color.rgb(fontColor),
+ opacity: opacity*Plotly.Color.opacity(fontColor),
+ 'font-weight': plots.fontWeight
+ })
+ .attr(options)
+ .call(Plotly.util.convertToTspans)
+ .attr(options);
+ titleEl.selectAll('tspan.line')
+ .attr(options);
+ return plots.previousPromises(gd);
+ }
+
+ function scootTitle(titleElIn) {
+ var titleGroup = d3.select(titleElIn.node().parentNode);
+
+ if(avoid && avoid.selection && avoid.side && txt){
+ titleGroup.attr('transform',null);
+
+ // move toward avoid.side (= left, right, top, bottom) if needed
+ // can include pad (pixels, default 2)
+ var shift = 0,
+ backside = {
+ left: 'right',
+ right: 'left',
+ top: 'bottom',
+ bottom: 'top'
+ }[avoid.side],
+ shiftSign = (['left','top'].indexOf(avoid.side)!==-1) ?
+ -1 : 1,
+ pad = isNumeric(avoid.pad) ? avoid.pad : 2,
+ titlebb = Plotly.Drawing.bBox(titleGroup.node()),
+ paperbb = {
+ left: 0,
+ top: 0,
+ right: fullLayout.width,
+ bottom: fullLayout.height
+ },
+ maxshift = colorbar ? fullLayout.width:
+ (paperbb[avoid.side]-titlebb[avoid.side]) *
+ ((avoid.side==='left' || avoid.side==='top') ? -1 : 1);
+ // Prevent the title going off the paper
+ if(maxshift<0) shift = maxshift;
+ else {
+ // so we don't have to offset each avoided element,
+ // give the title the opposite offset
+ titlebb.left -= avoid.offsetLeft;
+ titlebb.right -= avoid.offsetLeft;
+ titlebb.top -= avoid.offsetTop;
+ titlebb.bottom -= avoid.offsetTop;
+
+ // iterate over a set of elements (avoid.selection)
+ // to avoid collisions with
+ avoid.selection.each(function(){
+ var avoidbb = Plotly.Drawing.bBox(this);
+
+ if(Plotly.Lib.bBoxIntersect(titlebb,avoidbb,pad)) {
+ shift = Math.max(shift, shiftSign * (
+ avoidbb[avoid.side] - titlebb[backside]) + pad);
+ }
+ });
+ shift = Math.min(maxshift, shift);
+ }
+ if(shift>0 || maxshift<0) {
+ var shiftTemplate = {
+ left: [-shift, 0],
+ right: [shift, 0],
+ top: [0, -shift],
+ bottom: [0, shift]
+ }[avoid.side];
+ titleGroup.attr('transform',
+ 'translate(' + shiftTemplate + ')');
+ }
+ }
+ }
+
+ el.attr({'data-unformatted': txt})
+ .call(titleLayout);
+
+ var placeholderText = 'Click to enter '+name.replace(/\d+/,'')+' title';
+
+ function setPlaceholder(){
+ opacity = 0;
+ isplaceholder = true;
+ txt = placeholderText;
+ fullLayout._infolayer.select('.'+title)
+ .attr({'data-unformatted': txt})
+ .text(txt)
+ .on('mouseover.opacity',function(){
+ d3.select(this).transition()
+ .duration(100).style('opacity',1);
+ })
+ .on('mouseout.opacity',function(){
+ d3.select(this).transition()
+ .duration(1000).style('opacity',0);
+ });
+ }
+
+ if(gd._context.editable){
+ if(!txt) setPlaceholder();
+
+ el.call(Plotly.util.makeEditable)
+ .on('edit', function(text){
+ if(colorbar) {
+ var trace = gd._fullData[cbnum];
+ if(plots.traceIs(trace, 'markerColorscale')) {
+ Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum);
+ } else Plotly.restyle(gd, 'colorbar.title', text, cbnum);
+ }
+ else Plotly.relayout(gd,prop,text);
+ })
+ .on('cancel', function(){
+ this.text(this.attr('data-unformatted'))
+ .call(titleLayout);
+ })
+ .on('input', function(d){
+ this.text(d || ' ').attr(options)
+ .selectAll('tspan.line')
+ .attr(options);
+ });
+ }
+ else if(!txt || txt.match(/Click to enter .+ title/)) {
+ el.remove();
+ }
+ el.classed('js-placeholder',isplaceholder);
+};
diff --git a/src/geo/raw/country-name_to_iso3.json b/src/constants/country-name_to_iso3.json
similarity index 100%
rename from src/geo/raw/country-name_to_iso3.json
rename to src/constants/country-name_to_iso3.json
diff --git a/src/geo/lib/params.js b/src/constants/geo_constants.js
similarity index 100%
rename from src/geo/lib/params.js
rename to src/constants/geo_constants.js
diff --git a/src/gl2d/lib/dashes.json b/src/constants/gl2d_dashes.json
similarity index 100%
rename from src/gl2d/lib/dashes.json
rename to src/constants/gl2d_dashes.json
diff --git a/src/gl3d/lib/dashes.json b/src/constants/gl3d_dashes.json
similarity index 100%
rename from src/gl3d/lib/dashes.json
rename to src/constants/gl3d_dashes.json
diff --git a/src/gl3d/lib/markers.json b/src/constants/gl_markers.json
similarity index 100%
rename from src/gl3d/lib/markers.json
rename to src/constants/gl_markers.json
diff --git a/src/geo/defaults/choropleth.js b/src/geo/defaults/choropleth.js
deleted file mode 100644
index fc33e060793..00000000000
--- a/src/geo/defaults/choropleth.js
+++ /dev/null
@@ -1,61 +0,0 @@
-'use strict';
-
-var Plotly = require('../../plotly');
-
-var Choropleth = module.exports = {};
-
-Plotly.Plots.register(Choropleth, 'choropleth', ['geo', 'noOpacity'], {
- description: [
- 'The data that describes the choropleth value-to-color mapping',
- 'is set in `z`.',
- 'The geographic locations corresponding to each value in `z`',
- 'are set in `locations`.'
- ].join(' ')
-});
-
-Choropleth.attributes = require('../attributes/choropleth');
-
-Choropleth.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) {
- var locations, len, z;
-
- function coerce(attr, dflt) {
- return Plotly.Lib.coerce(traceIn, traceOut,
- Choropleth.attributes, attr, dflt);
- }
-
- locations = coerce('locations');
- if(locations) len = locations.length;
- if(!locations || !len) {
- traceOut.visible = false;
- return;
- }
-
- z = coerce('z');
- if(!Array.isArray(z)) {
- traceOut.visible = false;
- return;
- }
-
- if(z.length > len) traceOut.z = z.slice(0, len);
-
- coerce('locationmode');
-
- coerce('text');
-
- coerce('marker.line.color');
- coerce('marker.line.width');
-
- Plotly.Colorscale.handleDefaults(
- traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}
- );
-
- coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined);
-};
-
-Choropleth.colorbar = Plotly.Colorbar.traceColorbar;
-
-Choropleth.calc = function(gd, trace) {
-
- Plotly.Colorscale.calc(trace, trace.z, '', 'z');
-
-};
diff --git a/src/gl3d/defaults/gl3daxes.js b/src/gl3d/defaults/gl3daxes.js
deleted file mode 100644
index e4f26fc4932..00000000000
--- a/src/gl3d/defaults/gl3daxes.js
+++ /dev/null
@@ -1,82 +0,0 @@
-'use strict';
-
-var Plotly = require('../../plotly');
-
-var Gl3dAxes = module.exports = {};
-
-Gl3dAxes.axesNames = ['xaxis', 'yaxis', 'zaxis'];
-
-Gl3dAxes.layoutAttributes = require('../attributes/gl3daxes');
-
-var noop = function () {};
-
-Gl3dAxes.supplyLayoutDefaults = function(layoutIn, layoutOut, options) {
-
- var Axes = Plotly.Axes;
- var containerIn, containerOut;
-
- function coerce(attr, dflt) {
- return Plotly.Lib.coerce(containerIn, containerOut,
- Gl3dAxes.layoutAttributes, attr, dflt);
- }
-
- for (var j = 0; j < Gl3dAxes.axesNames.length; j++) {
- var axName = Gl3dAxes.axesNames[j];
- containerIn = layoutIn[axName] || {};
-
- containerOut = {
- _id: axName[0] + options.scene,
- _name: axName
- };
-
- layoutOut[axName] = containerOut = Axes.handleAxisDefaults(
- containerIn,
- containerOut,
- coerce,
- {
- font: options.font,
- letter: axName[0],
- data: options.data,
- showGrid: true
- });
-
- coerce('gridcolor');
- coerce('title', axName[0]); // shouldn't this be on-par with 2D?
-
- containerOut.setScale = noop;
-
- if (coerce('showspikes')) {
- coerce('spikesides');
- coerce('spikethickness');
- coerce('spikecolor');
- }
- if (coerce('showbackground')) coerce('backgroundcolor');
-
- coerce('showaxeslabels');
- }
-};
-
-Gl3dAxes.setConvert = function (containerOut) {
- Plotly.Axes.setConvert(containerOut);
- containerOut.setScale = noop;
-};
-
-Gl3dAxes.initAxes = function (td) {
- var fullLayout = td._fullLayout;
-
- // until they play better together
- delete fullLayout.xaxis;
- delete fullLayout.yaxis;
-
- var sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');
-
- for (var i = 0; i < sceneIds.length; ++i) {
- var sceneId = sceneIds[i];
- var sceneLayout = fullLayout[sceneId];
- for (var j = 0; j < 3; ++j) {
- var axisName = Gl3dAxes.axesNames[j];
- var ax = sceneLayout[axisName];
- ax._td = td;
- }
- }
-};
diff --git a/src/gl3d/defaults/mesh3d.js b/src/gl3d/defaults/mesh3d.js
deleted file mode 100644
index 200633fc1f9..00000000000
--- a/src/gl3d/defaults/mesh3d.js
+++ /dev/null
@@ -1,94 +0,0 @@
-'use strict';
-
-var Plotly = require('../../plotly');
-
-var Mesh3D = module.exports = {};
-
-Plotly.Plots.register(Mesh3D, 'mesh3d', ['gl3d'], {
- description: [
- ''
- ].join(' ')
-});
-
-Mesh3D.attributes = require('../attributes/mesh3d');
-
-Mesh3D.supplyDefaults = function(traceIn, traceOut, defaultColor, layout) {
- var self = this;
- function coerce(attr, dflt) {
- return Plotly.Lib.coerce(traceIn, traceOut, self.attributes, attr, dflt);
- }
-
- //Read in face/vertex properties
- function readComponents(array) {
- var ret = array.map(function(attr) {
- var result = coerce(attr);
- if(result && Array.isArray(result)) {
- return result;
- }
- return null;
- });
- return ret.every(function(x) {
- return x && x.length === ret[0].length;
- }) && ret;
- }
-
- var coords = readComponents(['x', 'y', 'z']);
- var indices = readComponents(['i', 'j', 'k']);
-
- if(!coords) {
- traceOut.visible = false;
- return;
- }
-
- if(indices) {
- //Otherwise, convert all face indices to ints
- indices.forEach(function(index) {
- for(var i=0; i= 0) coerce('surfacecolor', lineColor || markerColor);
-
- var dims = ['x', 'y', 'z'];
- for(var i = 0; i < 3; ++i) {
- var projection = 'projection.' + dims[i];
- if(coerce(projection + '.show')) {
- coerce(projection + '.opacity');
- coerce(projection + '.scale');
- }
- }
-
- if(Plotly.ErrorBars) {
- Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'});
- Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'});
- Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});
- }
-};
-
-Scatter3D.colorbar = Plotly.Scatter.colorbar;
-
-Scatter3D.calc = function(gd, trace) {
- // this is a kludge to put the array attributes into
- // calcdata the way Scatter.plot does, so that legends and
- // popovers know what to do with them.
- var cd = [{x: false, y: false, trace: trace, t: {}}];
- Plotly.Scatter.arraysToCalcdata(cd);
-
- Plotly.Scatter.calcMarkerColorscales(trace);
-
- return cd;
-};
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 00000000000..e0a8c258a10
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,30 @@
+/*
+ * Export the plotly.js API methods.
+ *
+ * This file is browserify'ed into a standalone 'Plotly' object.
+ *
+ */
+
+var Plotly = require('./plotly');
+
+// plot api
+exports.plot = Plotly.plot;
+exports.newPlot = Plotly.newPlot;
+exports.restyle = Plotly.restyle;
+exports.relayout = Plotly.relayout;
+exports.redraw = Plotly.redraw;
+exports.extendTraces = Plotly.extendTraces;
+exports.prependTraces = Plotly.prependTraces;
+exports.addTraces = Plotly.addTraces;
+exports.deleteTraces = Plotly.deleteTraces;
+exports.moveTraces = Plotly.moveTraces;
+exports.setPlotConfig = require('./plot_api/set_plot_config');
+
+// unofficial 'beta' plot methods, use at your own risk
+exports.Plots = Plotly.Plots;
+exports.Fx = Plotly.Fx;
+exports.Snapshot = Plotly.Snapshot;
+exports.PlotSchema = Plotly.PlotSchema;
+
+// export d3 used in the bundle
+exports.d3 = require('d3');
diff --git a/src/geo/lib/array-to-calc-item.js b/src/lib/array_to_calc_item.js
similarity index 100%
rename from src/geo/lib/array-to-calc-item.js
rename to src/lib/array_to_calc_item.js
diff --git a/src/lib/coerce.js b/src/lib/coerce.js
new file mode 100644
index 00000000000..e62f560cb29
--- /dev/null
+++ b/src/lib/coerce.js
@@ -0,0 +1,334 @@
+'use strict';
+
+var Plotly = require('../plotly');
+var isNumeric = require('fast-isnumeric');
+var tinycolor = require('tinycolor2');
+var nestedProperty = require('./nested_property');
+
+var colorscaleNames = Object.keys(require('../components/colorscale/scales'));
+
+
+exports.valObjects = {
+ data_array: {
+ // You can use *dflt=[] to force said array to exist though.
+ description: [
+ 'An {array} of data.',
+ 'The value MUST be an {array}, or we ignore it.'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(Array.isArray(v)) propOut.set(v);
+ else if(dflt!==undefined) propOut.set(dflt);
+ }
+ },
+ enumerated: {
+ description: [
+ 'Enumerated value type. The available values are listed',
+ 'in `values`.'
+ ].join(' '),
+ requiredOpts: ['values'],
+ otherOpts: ['dflt', 'coerceNumber', 'arrayOk'],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if(opts.coerceNumber) v = +v;
+ if(opts.values.indexOf(v)===-1) propOut.set(dflt);
+ else propOut.set(v);
+ }
+ },
+ 'boolean': {
+ description: 'A boolean (true/false) value.',
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(v===true || v===false) propOut.set(v);
+ else propOut.set(dflt);
+ }
+ },
+ number: {
+ description: [
+ 'A number or a numeric value',
+ '(e.g. a number inside a string).',
+ 'When applicable, values greater (less) than `max` (`min`)',
+ 'are coerced to the `dflt`.'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt', 'min', 'max', 'arrayOk'],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if(!isNumeric(v) ||
+ (opts.min!==undefined && vopts.max)) {
+ propOut.set(dflt);
+ }
+ else propOut.set(+v);
+ }
+ },
+ integer: {
+ description: [
+ 'An integer or an integer inside a string.',
+ 'When applicable, values greater (less) than `max` (`min`)',
+ 'are coerced to the `dflt`.'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt', 'min', 'max'],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if(v%1 || !isNumeric(v) ||
+ (opts.min!==undefined && vopts.max)) {
+ propOut.set(dflt);
+ }
+ else propOut.set(+v);
+ }
+ },
+ string: {
+ description: [
+ 'A string value.',
+ 'Numbers are converted to strings except for attributes with',
+ '`strict` set to true.'
+ ].join(' '),
+ requiredOpts: [],
+ // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter)
+ otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if(opts.strict===true && typeof v !== 'string') {
+ propOut.set(dflt);
+ return;
+ }
+
+ var s = String(v);
+ if(v===undefined || (opts.noBlank===true && !s)) {
+ propOut.set(dflt);
+ }
+ else propOut.set(s);
+ }
+ },
+ color: {
+ description: [
+ 'A string describing color.',
+ 'Supported formats:',
+ '- hex (e.g. \'#d3d3d3\')',
+ '- rgb (e.g. \'rgb(255, 0, 0)\')',
+ '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')',
+ '- hsl (e.g. \'hsl(0, 100%, 50%)\')',
+ '- hsv (e.g. \'hsv(0, 100%, 100%)\')',
+ '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt', 'arrayOk'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(tinycolor(v).isValid()) propOut.set(v);
+ else propOut.set(dflt);
+ }
+ },
+ colorscale: {
+ description: [
+ 'A Plotly colorscale either picked by a name:',
+ '(any of', colorscaleNames.join(', '), ')',
+ 'customized as an {array} of 2-element {arrays} where',
+ 'the first element is the normalized color level value',
+ '(starting at *0* and ending at *1*),',
+ 'and the second item is a valid color string.'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ propOut.set(Plotly.Colorscale.getScale(v, dflt));
+ }
+ },
+ angle: {
+ description: [
+ 'A number (in degree) between -180 and 180.'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(v==='auto') propOut.set('auto');
+ else if(!isNumeric(v)) propOut.set(dflt);
+ else {
+ if(Math.abs(v)>180) v -= Math.round(v/360)*360;
+ propOut.set(+v);
+ }
+ }
+ },
+ axisid: {
+ description: [
+ 'An axis id string (e.g. \'x\', \'x2\', \'x3\', ...).'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(typeof v === 'string' && v.charAt(0)===dflt) {
+ var axnum = Number(v.substr(1));
+ if(axnum%1 === 0 && axnum>1) {
+ propOut.set(v);
+ return;
+ }
+ }
+ propOut.set(dflt);
+ }
+ },
+ sceneid: {
+ description: [
+ 'A scene id string (e.g. \'scene\', \'scene2\', \'scene3\', ...).'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(typeof v === 'string' && v.substr(0,5)===dflt) {
+ var scenenum = Number(v.substr(5));
+ if(scenenum%1 === 0 && scenenum>1) {
+ propOut.set(v);
+ return;
+ }
+ }
+ propOut.set(dflt);
+ }
+ },
+ geoid: {
+ description: [
+ 'A geo id string (e.g. \'geo\', \'geo2\', \'geo3\', ...).'
+ ].join(' '),
+ requiredOpts: [],
+ otherOpts: ['dflt'],
+ coerceFunction: function(v, propOut, dflt) {
+ if(typeof v === 'string' && v.substr(0,3)===dflt) {
+ var geonum = Number(v.substr(3));
+ if(geonum%1 === 0 && geonum>1) {
+ propOut.set(v);
+ return;
+ }
+ }
+ propOut.set(dflt);
+ }
+ },
+ flaglist: {
+ description: [
+ 'A string representing a combination of flags',
+ '(order does not matter here).',
+ 'Combine any of the available `flags` with *+*.',
+ '(e.g. (\'lines+markers\')).',
+ 'Values in `extras` cannot be combined.'
+ ].join(' '),
+ requiredOpts: ['flags'],
+ otherOpts: ['dflt', 'extras'],
+ coerceFunction: function(v, propOut, dflt, opts) {
+ if(typeof v !== 'string') {
+ propOut.set(dflt);
+ return;
+ }
+ if(opts.extras.indexOf(v)!==-1) {
+ propOut.set(v);
+ return;
+ }
+ var vParts = v.split('+'),
+ i = 0;
+ while(i3 digits, though javascript dates truncate to milliseconds
+ * returns false if it doesn't find a date
+ *
+ * 2-digit to 4-digit year conversion, where to cut off?
+ * from http://support.microsoft.com/kb/244664:
+ * 1930-2029 (the most retro of all...)
+ * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
+ * 1950-2049
+ * by Java, from http://stackoverflow.com/questions/2024273/:
+ * now-80 - now+20
+ * or FileMaker Pro, from
+ * http://www.filemaker.com/12help/html/add_view_data.4.21.html:
+ * now-70 - now+30
+ * but python strptime etc, via
+ * http://docs.python.org/py3k/library/time.html:
+ * 1969-2068 (super forward-looking, but static, not sliding!)
+ *
+ * lets go with now-70 to now+30, and if anyone runs into this problem
+ * they can learn the hard way not to use 2-digit years, as no choice we
+ * make now will cover all possibilities. mostly this will all be taken
+ * care of in initial parsing, should only be an issue for hand-entered data
+ * currently (2012) this range is:
+ * 1942-2041
+ */
+
+exports.dateTime2ms = function(s) {
+ // first check if s is a date object
+ try {
+ if (s.getTime) return +s;
+ }
+ catch(e) {
+ return false;
+ }
+
+ var y, m, d, h;
+ // split date and time parts
+ var datetime = String(s).split(' ');
+ if (datetime.length > 2) return false;
+
+ var p = datetime[0].split('-'); // date part
+ if (p.length > 3 || (p.length !== 3 && datetime[1])) return false;
+
+ // year
+ if (p[0].length === 4) y = Number(p[0]);
+ else if (p[0].length === 2) {
+ var yNow = new Date().getFullYear();
+ y = ((Number(p[0]) - yNow + 70)%100 + 200)%100 + yNow - 70;
+ }
+ else return false;
+ if (!isNumeric(y)) return false;
+ if (p.length === 1) return new Date(y,0,1).getTime(); // year only
+
+ // month
+ m = Number(p[1]) - 1; // new Date() uses zero-based months
+ if (p[1].length > 2 || !(m >= 0 && m <= 11)) return false;
+ if (p.length === 2) return new Date(y, m, 1).getTime(); // year-month
+
+ // day
+ d = Number(p[2]);
+ if (p[2].length > 2 || !(d >= 1 && d <= 31)) return false;
+
+ // now save the date part
+ d = new Date(y, m, d).getTime();
+ if (!datetime[1]) return d; // year-month-day
+ p = datetime[1].split(':');
+ if (p.length > 3) return false;
+
+ // hour
+ h = Number(p[0]);
+ if (p[0].length > 2 || !(h >= 0 && h <= 23)) return false;
+ d += 3600000*h;
+ if (p.length === 1) return d;
+
+ // minute
+ m = Number(p[1]);
+ if (p[1].length > 2 || !(m >= 0 && m <= 59)) return false;
+ d += 60000*m;
+ if (p.length === 2) return d;
+
+ // second
+ s = Number(p[2]);
+ if (!(s >= 0 && s < 60)) return false;
+ return d+s*1000;
+};
+
+// is string s a date? (see above)
+exports.isDateTime = function(s) {
+ return (exports.dateTime2ms(s) !== false);
+};
+
+// pad a number with zeroes, to given # of digits before the decimal point
+function lpad(val, digits){
+ return String(val + Math.pow(10, digits)).substr(1);
+}
+
+/**
+ * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss
+ * Crop any trailing zeros in time, but always leave full date
+ * (we could choose to crop '-01' from date too)...
+ * Optional range r is the data range that applies, also in ms.
+ * If rng is big, the later parts of time will be omitted
+ */
+exports.ms2DateTime = function(ms, r) {
+ if(typeof(d3)==='undefined'){
+ console.log('d3 is not defined');
+ return;
+ }
+
+ if(!r) r=0;
+ var d = new Date(ms),
+ s = d3.time.format('%Y-%m-%d')(d);
+ if(r<7776000000) {
+ // <90 days: add hours
+ s+=' '+lpad(d.getHours(),2);
+ if(r<432000000) {
+ // <5 days: add minutes
+ s+=':'+lpad(d.getMinutes(),2);
+ if(r<10800000) {
+ // <3 hours: add seconds
+ s+=':'+lpad(d.getSeconds(),2);
+ if(r<300000) {
+ // <5 minutes: add ms
+ s+='.'+lpad(d.getMilliseconds(),3);
+ }
+ }
+ }
+ // strip trailing zeros
+ return s.replace(/([:\s]00)*\.?[0]*$/,'');
+ }
+ return s;
+};
+
+/**
+ * parseDate: forgiving attempt to turn any date string
+ * into a javascript date object
+ *
+ * first collate all the date formats we want to support, precompiled
+ * to d3 format objects see below for the string cleaning that happens
+ * before this separate out 2-digit (y) and 4-digit-year (Y) formats,
+ * formats with month names (b), and formats with am/pm (I) or no time (D)
+ * (also includes hour only, as the test is really for a colon) so we can
+ * cut down the number of tests we need to run for any given string
+ * (right now all are between 15 and 32 tests)
+ */
+
+// TODO: this is way out of date vs. the server-side version
+var timeFormats = {
+ // 24 hour
+ H:['%H:%M:%S~%L', '%H:%M:%S', '%H:%M'],
+ // with am/pm
+ I:['%I:%M:%S~%L%p', '%I:%M:%S%p', '%I:%M%p'],
+ // no colon, ie only date or date with hour (could also support eg 12h34m?)
+ D:['%H', '%I%p', '%Hh']
+};
+
+var dateFormats = {
+ Y:[
+ '%Y~%m~%d',
+ '%Y%m%d',
+ '%y%m%d', // YYMMDD, has 6 digits together so will match Y, not y
+ '%m~%d~%Y', // MM/DD/YYYY has first precedence
+ '%d~%m~%Y' // then DD/MM/YYYY
+ ],
+ Yb:[
+ '%b~%d~%Y', // eg nov 21 2013
+ '%d~%b~%Y', // eg 21 nov 2013
+ '%Y~%d~%b', // eg 2013 21 nov (or 2013 q3, after replacement)
+ '%Y~%b~%d' // eg 2013 nov 21
+ ],
+ /**
+ * the two-digit year cases have so many potential ambiguities
+ * it's not even funny, but we'll try them anyway.
+ */
+ y:[
+ '%m~%d~%y',
+ '%d~%m~%y',
+ '%y~%m~%d'
+ ],
+ yb:[
+ '%b~%d~%y',
+ '%d~%b~%y',
+ '%y~%d~%b',
+ '%y~%b~%d'
+ ]
+};
+
+// use utc formatter since we're ignoring timezone info
+var formatter = d3.time.format.utc;
+
+/**
+ * ISO8601 and YYYYMMDDHHMMSS are the only ones where date and time
+ * are not separated by a space, so they get inserted specially here.
+ * Also a couple formats with no day (so time makes no sense)
+ */
+var dateTimeFormats = {
+ Y: {
+ H: ['%Y~%m~%dT%H:%M:%S', '%Y~%m~%dT%H:%M:%S~%L'].map(formatter),
+ I: [],
+ D: ['%Y%m%d%H%M%S', '%Y~%m', '%m~%Y'].map(formatter)
+ },
+ Yb: {H: [], I: [], D: ['%Y~%b', '%b~%Y'].map(formatter)},
+ y: {H: [], I: [], D: []},
+ yb: {H: [], I: [], D: []}
+};
+// all others get inserted in all possible combinations from dateFormats and timeFormats
+['Y', 'Yb', 'y', 'yb'].forEach(function(dateType) {
+ dateFormats[dateType].forEach(function(dateFormat) {
+ // just a date (don't do just a time)
+ dateTimeFormats[dateType].D.push(formatter(dateFormat));
+ ['H', 'I', 'D'].forEach(function(timeType) {
+ timeFormats[timeType].forEach(function(timeFormat) {
+ var a = dateTimeFormats[dateType][timeType];
+ // 'date time', then 'time date'
+ a.push(formatter(dateFormat+'~'+timeFormat));
+ a.push(formatter(timeFormat+'~'+dateFormat));
+ });
+ });
+ });
+});
+
+// precompiled regexps for performance
+var matchword = /[a-z]*/g,
+ shortenword = function(m) { return m.substr(0,3); },
+ weekdaymatch = /(mon|tue|wed|thu|fri|sat|sun|the|of|st|nd|rd|th)/g,
+ separatormatch = /[\s,\/\-\.\(\)]+/g,
+ ampmmatch = /~?([ap])~?m(~|$)/,
+ replaceampm = function(m,ap) { return ap+'m '; },
+ match4Y = /\d\d\d\d/,
+ matchMonthName = /(^|~)[a-z]{3}/,
+ matchAMPM = /[ap]m/,
+ matchcolon = /:/,
+ matchquarter = /q([1-4])/,
+ quarters = ['31~mar','30~jun','30~sep','31~dec'],
+ replacequarter = function(m,n) { return quarters[n-1]; },
+ matchTZ = / ?([+\-]\d\d:?\d\d|Z)$/;
+
+function getDateType(v) {
+ var dateType;
+ dateType = (match4Y.test(v) ? 'Y' : 'y');
+ dateType = dateType + (matchMonthName.test(v) ? 'b' : '');
+ return dateType;
+}
+
+function getTimeType(v) {
+ var timeType;
+ timeType = matchcolon.test(v) ? (matchAMPM.test(v) ? 'I' : 'H') : 'D';
+ return timeType;
+}
+
+exports.parseDate = function(v) {
+ // is it already a date? just return it
+ if (v.getTime) return v;
+ /**
+ * otherwise, if it's not a string, return nothing
+ * the case of numbers that just have years will get
+ * dealt with elsewhere.
+ */
+ if (typeof v !== 'string') return false;
+
+ // first clean up the string a bit to reduce the number of formats we have to test
+ v = v.toLowerCase()
+ /**
+ * cut all words down to 3 characters - this will result in
+ * some spurious matches, ie whenever the first three characters
+ * of a word match a month or weekday but that seems more likely
+ * to fix typos than to make dates where they shouldn't be...
+ * and then we can omit the long form of months from our testing
+ */
+ .replace(matchword, shortenword)
+ /**
+ * remove weekday names, as they get overridden anyway if they're
+ * inconsistent also removes a few more words
+ * (ie "tuesday the 26th of november")
+ * TODO: language support?
+ * for months too, but these seem to be built into d3
+ */
+ .replace(weekdaymatch, '')
+ /**
+ * collapse all separators one ~ at a time, except : which seems
+ * pretty consistent for the time part use ~ instead of space or
+ * something since d3 can eat a space as padding on 1-digit numbers
+ */
+ .replace(separatormatch, '~')
+ // in case of a.m. or p.m. (also take off any space before am/pm)
+ .replace(ampmmatch, replaceampm)
+ // turn quarters Q1-4 into dates (quarter ends)
+ .replace(matchquarter, replacequarter)
+ .trim()
+ // also try to ignore timezone info, at least for now
+ .replace(matchTZ, '');
+
+ // now test against the various formats that might match
+ var out = null,
+ dateType = getDateType(v),
+ timeType = getTimeType(v),
+ formatList,
+ len;
+
+ formatList = dateTimeFormats[dateType][timeType];
+ len = formatList.length;
+
+ for (var i = 0; i < len; i++) {
+ out = formatList[i].parse(v);
+ if (out) break;
+ }
+
+ // If not an instance of Date at this point, just return it.
+ if (!(out instanceof Date)) return false;
+ // parse() method interprets arguments with local time zone.
+ var tzoff = out.getTimezoneOffset();
+ // In general (default) this is not what we want, so force into UTC:
+ out.setTime(out.getTime() + tzoff * 60 * 1000);
+ return out;
+};
diff --git a/src/events.js b/src/lib/events.js
similarity index 100%
rename from src/events.js
rename to src/lib/events.js
diff --git a/src/geo/lib/location-utils.js b/src/lib/geo_location_utils.js
similarity index 89%
rename from src/geo/lib/location-utils.js
rename to src/lib/geo_location_utils.js
index 0ae35fd6661..6bb51cc3aa8 100644
--- a/src/geo/lib/location-utils.js
+++ b/src/lib/geo_location_utils.js
@@ -2,9 +2,10 @@
var locationUtils = module.exports = {};
-var Plotly = require('../../plotly'),
- // an hash object iso3 to regex string
- countryNameData = require('../raw/country-name_to_iso3.json');
+var Plotly = require('../plotly');
+
+// an hash object iso3 to regex string
+var countryNameData = require('../constants/country-name_to_iso3.json');
// make list of all country iso3 ids from at runtime
var countryIds = Object.keys(countryNameData);
diff --git a/src/gl3d/lib/format-color.js b/src/lib/gl_format_color.js
similarity index 93%
rename from src/gl3d/lib/format-color.js
rename to src/lib/gl_format_color.js
index 08a7bc7b15c..18ff6897292 100644
--- a/src/gl3d/lib/format-color.js
+++ b/src/lib/gl_format_color.js
@@ -1,12 +1,12 @@
'use strict';
-var Plotly = require('../../plotly');
+var Plotly = require('../plotly');
var tinycolor = require('tinycolor2');
var isNumeric = require('fast-isnumeric');
var str2RgbaArray = require('./str2rgbarray');
-var colorDflt = Plotly.Color.defaultLine,
- opacityDflt = 1;
+var colorDflt = require('../components/color/attributes').defaultLine;
+var opacityDflt = 1;
function calculateColor(colorIn, opacityIn) {
var colorOut = str2RgbaArray(colorIn);
diff --git a/src/gl3d/lib/html2unicode.js b/src/lib/html2unicode.js
similarity index 100%
rename from src/gl3d/lib/html2unicode.js
rename to src/lib/html2unicode.js
diff --git a/src/lib/index.js b/src/lib/index.js
new file mode 100644
index 00000000000..44ed10f12c7
--- /dev/null
+++ b/src/lib/index.js
@@ -0,0 +1,425 @@
+'use strict';
+
+var d3 = require('d3');
+
+var lib = module.exports = {};
+
+lib.nestedProperty = require('./nested_property');
+lib.isPlainObject = require('./is_plain_object');
+
+var coerceModule = require('./coerce');
+lib.valObjects = coerceModule.valObjects;
+lib.coerce = coerceModule.coerce;
+lib.coerce2 = coerceModule.coerce2;
+lib.coerceFont = coerceModule.coerceFont;
+
+var datesModule = require('./dates');
+lib.dateTime2ms = datesModule.dateTime2ms;
+lib.isDateTime = datesModule.isDateTime;
+lib.ms2DateTime = datesModule.ms2DateTime;
+lib.parseDate = datesModule.parseDate;
+
+var searchModule = require('./search');
+lib.findBin = searchModule.findBin;
+lib.sorterAsc = searchModule.sorterAsc;
+lib.distinctVals = searchModule.distinctVals;
+lib.roundUp = searchModule.roundUp;
+
+var statsModule = require('./stats');
+lib.aggNums = statsModule.aggNums;
+lib.len = statsModule.len;
+lib.mean = statsModule.mean;
+lib.variance = statsModule.variance;
+lib.stdev = statsModule.stdev;
+lib.interp = statsModule.interp;
+
+var matrixModule = require('./matrix');
+lib.init2dArray = matrixModule.init2dArray;
+lib.transposeRagged = matrixModule.transposeRagged;
+lib.dot = matrixModule.dot;
+lib.translationMatrix = matrixModule.translationMatrix;
+lib.rotationMatrix = matrixModule.rotationMatrix;
+lib.rotationXYMatrix = matrixModule.rotationXYMatrix;
+lib.apply2DTransform = matrixModule.apply2DTransform;
+lib.apply2DTransform2 = matrixModule.apply2DTransform2;
+
+var extendModule = require('./extend');
+lib.extendFlat = extendModule.extendFlat;
+lib.extendDeep = extendModule.extendDeep;
+lib.extendDeepAll = extendModule.extendDeepAll;
+
+lib.notifier = require('./notifier');
+
+/**
+ * swap x and y of the same attribute in container cont
+ * specify attr with a ? in place of x/y
+ * 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);
+ }
+};
+
+/**
+ * to prevent event bubbling, in particular text selection during drag.
+ * see http://stackoverflow.com/questions/5429827/
+ * how-can-i-prevent-text-element-selection-with-cursor-drag
+ * for maximum effect use:
+ * return pauseEvent(e);
+ */
+lib.pauseEvent = function(e){
+ if(e.stopPropagation) e.stopPropagation();
+ if(e.preventDefault) e.preventDefault();
+ e.cancelBubble = true;
+ return false;
+};
+
+/**
+ * ------------------------------------------
+ * debugging tools
+ * ------------------------------------------
+ */
+
+// set VERBOSE to true to get a lot more logging and tracing
+lib.VERBOSE = false;
+
+// first markTime call will return time from page load
+lib.TIMER = new Date().getTime();
+
+// console.log that only runs if VERBOSE is on
+lib.log = function(){
+ if(lib.VERBOSE) console.log.apply(console, arguments);
+};
+
+/**
+ * markTime - for debugging, mark the number of milliseconds
+ * since the previous call to markTime and log arbitrary info too
+ */
+lib.markTime = function(v){
+ if(!lib.VERBOSE) return;
+ var t2 = new Date().getTime();
+ console.log(v, t2 - lib.TIMER, '(msec)');
+ if(lib.VERBOSE === 'trace') console.trace();
+ lib.TIMER = t2;
+};
+
+// 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));
+};
+
+/**
+ * do two bounding boxes from getBoundingClientRect,
+ * ie {left,right,top,bottom,width,height}, overlap?
+ * 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);
+};
+
+// minor convenience/performance booster for d3...
+lib.identity = function(d) { return d; };
+
+// 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;
+};
+
+lib.OptionControl = function(opt, optname) {
+ /*
+ * An environment to contain all option setters and
+ * getters that collectively modify opts.
+ *
+ * You can call up opts from any function in new object
+ * as this.optname || this.opt
+ *
+ * See FitOpts for example of usage
+ */
+ if (!opt) opt = {};
+ if (!optname) optname = 'opt';
+
+ var self = {};
+ self.optionList = [];
+
+ self._newoption = function(optObj) {
+ optObj[optname] = opt;
+ self[optObj.name] = optObj;
+ self.optionList.push(optObj);
+ };
+
+ self['_'+optname] = opt;
+ return self;
+};
+
+/**
+ * lib.smooth: smooth arrayIn by convolving with
+ * a hann window with given full width at half max
+ * 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;
+ }
+
+ return arrayOut;
+};
+
+// helpers for promises
+
+/**
+ * promiseError: log errors properly inside promises
+ * use:
+ * .then(undefined,Plotly.Lib.promiseError) (for IE compatibility)
+ * or .catch(Plotly.Lib.promiseError)
+ * TODO: I guess we need another step to send this error to Sentry?
+ */
+lib.promiseError = function(err) { console.log(err, err.stack); };
+
+/**
+ * syncOrAsync: run a sequence of functions synchronously
+ * as long as its returns are not promises (ie have no .then)
+ * includes one argument arg to send to all functions...
+ * this is mainly just to prevent us having to make wrapper functions
+ * when the only purpose of the wrapper is to reference gd / td
+ * and a final step to be executed at the end
+ * TODO: if there's an error and everything is sync,
+ * this doesn't happen yet because we want to make sure
+ * that it gets reported
+ */
+lib.syncOrAsync = function(sequence, arg, finalStep) {
+ var ret, fni;
+
+ function continueAsync(){
+ lib.markTime('async done ' + fni.name);
+ return lib.syncOrAsync(sequence, arg, finalStep);
+ }
+ while(sequence.length) {
+ fni = sequence.splice(0, 1)[0];
+ ret = fni(arg);
+ // lib.markTime('done calling '+fni.name)
+ if(ret && ret.then) {
+ return ret.then(continueAsync)
+ .then(undefined, lib.promiseError);
+ }
+ lib.markTime('sync done ' + fni.name);
+ }
+
+ 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;
+};
+
+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;
+
+ 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;
+ }
+
+ if(hasAny && !hasAll) {
+ for(i = 0; i < attrList.length; i++) {
+ containerIn[attrList[i]] = containerOut[attrList[i]];
+ }
+ }
+};
+
+lib.mergeArray = function(traceAttr, cd, cdAttr) {
+ if(Array.isArray(traceAttr)) {
+ var imax = Math.min(traceAttr.length, cd.length);
+ for(var i=0; i or 1 elements
+ * because extend-like algorithms are hella slow
+ * 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;
+ }
+
+ 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;
+};
+
+lib.titleCase = function(s) {
+ 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;
+};
+
+// 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;
+ }
+};
+
+lib.isPlotDiv = function(el) {
+ var el3 = d3.select(el);
+ return el3.size() && el3.classed('js-plotly-plot');
+};
+
+lib.removeElement = function(el) {
+ var elParent = el && el.parentNode;
+ if(elParent) elParent.removeChild(el);
+};
+
+/**
+ * for dynamically adding style rules
+ * makes one stylesheet that contains all rules added
+ * 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 console.warn('addStyleRule failed');
+};
+
+lib.isIE = function() {
+ return typeof window.navigator.msSaveBlob !== 'undefined';
+};
diff --git a/src/lib/lib.js b/src/lib/lib.js
deleted file mode 100644
index df380b74758..00000000000
--- a/src/lib/lib.js
+++ /dev/null
@@ -1,1607 +0,0 @@
-'use strict';
-
-var Plotly = require('../plotly');
-var d3 = require('d3');
-var tinycolor = require('tinycolor2');
-var isNumeric = require('fast-isnumeric');
-
-var lib = module.exports = {};
-
-/**
- * dateTime2ms - turn a date object or string s of the form
- * YYYY-mm-dd HH:MM:SS.sss into milliseconds (relative to 1970-01-01,
- * per javascript standard)
- * may truncate after any full field, and sss can be any length
- * even >3 digits, though javascript dates truncate to milliseconds
- * returns false if it doesn't find a date
- *
- * 2-digit to 4-digit year conversion, where to cut off?
- * from http://support.microsoft.com/kb/244664:
- * 1930-2029 (the most retro of all...)
- * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
- * 1950-2049
- * by Java, from http://stackoverflow.com/questions/2024273/:
- * now-80 - now+20
- * or FileMaker Pro, from
- * http://www.filemaker.com/12help/html/add_view_data.4.21.html:
- * now-70 - now+30
- * but python strptime etc, via
- * http://docs.python.org/py3k/library/time.html:
- * 1969-2068 (super forward-looking, but static, not sliding!)
- *
- * lets go with now-70 to now+30, and if anyone runs into this problem
- * they can learn the hard way not to use 2-digit years, as no choice we
- * make now will cover all possibilities. mostly this will all be taken
- * care of in initial parsing, should only be an issue for hand-entered data
- * currently (2012) this range is:
- * 1942-2041
- */
-
-lib.dateTime2ms = function(s) {
- // first check if s is a date object
- try {
- if (s.getTime) return +s;
- }
- catch(e) {
- return false;
- }
-
- var y, m, d, h;
- // split date and time parts
- var datetime = String(s).split(' ');
- if (datetime.length > 2) return false;
-
- var p = datetime[0].split('-'); // date part
- if (p.length > 3 || (p.length !== 3 && datetime[1])) return false;
-
- // year
- if (p[0].length === 4) y = Number(p[0]);
- else if (p[0].length === 2) {
- var yNow = new Date().getFullYear();
- y = ((Number(p[0]) - yNow + 70)%100 + 200)%100 + yNow - 70;
- }
- else return false;
- if (!isNumeric(y)) return false;
- if (p.length === 1) return new Date(y,0,1).getTime(); // year only
-
- // month
- m = Number(p[1]) - 1; // new Date() uses zero-based months
- if (p[1].length > 2 || !(m >= 0 && m <= 11)) return false;
- if (p.length === 2) return new Date(y, m, 1).getTime(); // year-month
-
- // day
- d = Number(p[2]);
- if (p[2].length > 2 || !(d >= 1 && d <= 31)) return false;
-
- // now save the date part
- d = new Date(y, m, d).getTime();
- if (!datetime[1]) return d; // year-month-day
- p = datetime[1].split(':');
- if (p.length > 3) return false;
-
- // hour
- h = Number(p[0]);
- if (p[0].length > 2 || !(h >= 0 && h <= 23)) return false;
- d += 3600000*h;
- if (p.length === 1) return d;
-
- // minute
- m = Number(p[1]);
- if (p[1].length > 2 || !(m >= 0 && m <= 59)) return false;
- d += 60000*m;
- if (p.length === 2) return d;
-
- // second
- s = Number(p[2]);
- if (!(s >= 0 && s < 60)) return false;
- return d+s*1000;
-};
-
-// is string s a date? (see above)
-lib.isDateTime = function(s) {
- return (lib.dateTime2ms(s) !== false);
-};
-
-/**
- * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.sss
- * Crop any trailing zeros in time, but always leave full date
- * (we could choose to crop '-01' from date too)...
- * Optional range r is the data range that applies, also in ms.
- * If rng is big, the later parts of time will be omitted
- */
-lib.ms2DateTime = function(ms,r) {
- if(typeof(d3)==='undefined'){
- console.log('d3 is not defined');
- return;
- }
-
- if(!r) r=0;
- var d = new Date(ms),
- s = d3.time.format('%Y-%m-%d')(d);
- if(r<7776000000) {
- // <90 days: add hours
- s+=' '+lib.lpad(d.getHours(),2);
- if(r<432000000) {
- // <5 days: add minutes
- s+=':'+lib.lpad(d.getMinutes(),2);
- if(r<10800000) {
- // <3 hours: add seconds
- s+=':'+lib.lpad(d.getSeconds(),2);
- if(r<300000) {
- // <5 minutes: add ms
- s+='.'+lib.lpad(d.getMilliseconds(),3);
- }
- }
- }
- // strip trailing zeros
- return s.replace(/([:\s]00)*\.?[0]*$/,'');
- }
- return s;
-};
-
-/**
- * Plotly.Lib.parseDate: forgiving attempt to turn any date string
- * into a javascript date object
- *
- * first collate all the date formats we want to support, precompiled
- * to d3 format objects see below for the string cleaning that happens
- * before this separate out 2-digit (y) and 4-digit-year (Y) formats,
- * formats with month names (b), and formats with am/pm (I) or no time (D)
- * (also includes hour only, as the test is really for a colon) so we can
- * cut down the number of tests we need to run for any given string
- * (right now all are between 15 and 32 tests)
- */
-
-// TODO: this is way out of date vs. the server-side version
-var timeFormats = {
- // 24 hour
- H:['%H:%M:%S~%L', '%H:%M:%S', '%H:%M'],
- // with am/pm
- I:['%I:%M:%S~%L%p', '%I:%M:%S%p', '%I:%M%p'],
- // no colon, ie only date or date with hour (could also support eg 12h34m?)
- D:['%H', '%I%p', '%Hh']
-};
-var dateFormats = {
- Y:[
- '%Y~%m~%d',
- '%Y%m%d',
- '%y%m%d', // YYMMDD, has 6 digits together so will match Y, not y
- '%m~%d~%Y', // MM/DD/YYYY has first precedence
- '%d~%m~%Y' // then DD/MM/YYYY
- ],
- Yb:[
- '%b~%d~%Y', // eg nov 21 2013
- '%d~%b~%Y', // eg 21 nov 2013
- '%Y~%d~%b', // eg 2013 21 nov (or 2013 q3, after replacement)
- '%Y~%b~%d' // eg 2013 nov 21
- ],
- /**
- * the two-digit year cases have so many potential ambiguities
- * it's not even funny, but we'll try them anyway.
- */
- y:[
- '%m~%d~%y',
- '%d~%m~%y',
- '%y~%m~%d'
- ],
- yb:[
- '%b~%d~%y',
- '%d~%b~%y',
- '%y~%d~%b',
- '%y~%b~%d'
- ]
-};
-
-// use utc formatter since we're ignoring timezone info
-var formatter = d3.time.format.utc;
-
-/**
- * ISO8601 and YYYYMMDDHHMMSS are the only ones where date and time
- * are not separated by a space, so they get inserted specially here.
- * Also a couple formats with no day (so time makes no sense)
- */
-var dateTimeFormats = {
- Y: {
- H: ['%Y~%m~%dT%H:%M:%S', '%Y~%m~%dT%H:%M:%S~%L'].map(formatter),
- I: [],
- D: ['%Y%m%d%H%M%S', '%Y~%m', '%m~%Y'].map(formatter)
- },
- Yb: {H: [], I: [], D: ['%Y~%b', '%b~%Y'].map(formatter)},
- y: {H: [], I: [], D: []},
- yb: {H: [], I: [], D: []}
-};
-// all others get inserted in all possible combinations from dateFormats and timeFormats
-['Y', 'Yb', 'y', 'yb'].forEach(function(dateType) {
- dateFormats[dateType].forEach(function(dateFormat) {
- // just a date (don't do just a time)
- dateTimeFormats[dateType].D.push(formatter(dateFormat));
- ['H', 'I', 'D'].forEach(function(timeType) {
- timeFormats[timeType].forEach(function(timeFormat) {
- var a = dateTimeFormats[dateType][timeType];
- // 'date time', then 'time date'
- a.push(formatter(dateFormat+'~'+timeFormat));
- a.push(formatter(timeFormat+'~'+dateFormat));
- });
- });
- });
-});
-
-// precompiled regexps for performance
-var matchword = /[a-z]*/g,
- shortenword = function(m) { return m.substr(0,3); },
- weekdaymatch = /(mon|tue|wed|thu|fri|sat|sun|the|of|st|nd|rd|th)/g,
- separatormatch = /[\s,\/\-\.\(\)]+/g,
- ampmmatch = /~?([ap])~?m(~|$)/,
- replaceampm = function(m,ap) { return ap+'m '; },
- match4Y = /\d\d\d\d/,
- matchMonthName = /(^|~)[a-z]{3}/,
- matchAMPM = /[ap]m/,
- matchcolon = /:/,
- matchquarter = /q([1-4])/,
- quarters = ['31~mar','30~jun','30~sep','31~dec'],
- replacequarter = function(m,n) { return quarters[n-1]; },
- matchTZ = / ?([+\-]\d\d:?\d\d|Z)$/;
-
-function getDateType(v) {
- var dateType;
- dateType = (match4Y.test(v) ? 'Y' : 'y');
- dateType = dateType + (matchMonthName.test(v) ? 'b' : '');
- return dateType;
-}
-
-function getTimeType(v) {
- var timeType;
- timeType = matchcolon.test(v) ? (matchAMPM.test(v) ? 'I' : 'H') : 'D';
- return timeType;
-}
-
-lib.parseDate = function(v) {
- // is it already a date? just return it
- if (v.getTime) return v;
- /**
- * otherwise, if it's not a string, return nothing
- * the case of numbers that just have years will get
- * dealt with elsewhere.
- */
- if (typeof v !== 'string') return false;
-
- // first clean up the string a bit to reduce the number of formats we have to test
- v = v.toLowerCase()
- /**
- * cut all words down to 3 characters - this will result in
- * some spurious matches, ie whenever the first three characters
- * of a word match a month or weekday but that seems more likely
- * to fix typos than to make dates where they shouldn't be...
- * and then we can omit the long form of months from our testing
- */
- .replace(matchword, shortenword)
- /**
- * remove weekday names, as they get overridden anyway if they're
- * inconsistent also removes a few more words
- * (ie "tuesday the 26th of november")
- * TODO: language support?
- * for months too, but these seem to be built into d3
- */
- .replace(weekdaymatch, '')
- /**
- * collapse all separators one ~ at a time, except : which seems
- * pretty consistent for the time part use ~ instead of space or
- * something since d3 can eat a space as padding on 1-digit numbers
- */
- .replace(separatormatch, '~')
- // in case of a.m. or p.m. (also take off any space before am/pm)
- .replace(ampmmatch, replaceampm)
- // turn quarters Q1-4 into dates (quarter ends)
- .replace(matchquarter, replacequarter)
- .trim()
- // also try to ignore timezone info, at least for now
- .replace(matchTZ, '');
-
- // now test against the various formats that might match
- var out = null,
- dateType = getDateType(v),
- timeType = getTimeType(v),
- formatList,
- len;
-
- formatList = dateTimeFormats[dateType][timeType];
- len = formatList.length;
-
- for (var i = 0; i < len; i++) {
- out = formatList[i].parse(v);
- if (out) break;
- }
-
- // If not an instance of Date at this point, just return it.
- if (!(out instanceof Date)) return false;
- // parse() method interprets arguments with local time zone.
- var tzoff = out.getTimezoneOffset();
- // In general (default) this is not what we want, so force into UTC:
- out.setTime(out.getTime() + tzoff * 60 * 1000);
- return out;
-};
-
-/**
- * findBin - find the bin for val - note that it can return outside the
- * bin range any pos. or neg. integer for linear bins, or -1 or
- * bins.length-1 for explicit.
- * bins is either an object {start,size,end} or an array length #bins+1
- * bins can be either increasing or decreasing but must be monotonic
- * for linear bins, we can just calculate. For listed bins, run a binary
- * search linelow (truthy) says the bin boundary should be attributed to
- * the lower bin rather than the default upper bin
- */
-lib.findBin = function(val, bins, linelow) {
- if(isNumeric(bins.start)) {
- return linelow ?
- Math.ceil((val - bins.start) / bins.size) - 1 :
- Math.floor((val - bins.start) / bins.size);
- }
- else {
- var n1 = 0,
- n2 = bins.length,
- c = 0,
- n,
- test;
- if(bins[bins.length - 1] > bins[0]) {
- test = linelow ? lessThan : lessOrEqual;
- } else {
- test = linelow ? greaterOrEqual : greaterThan;
- }
- // c is just to avoid infinite loops if there's an error
- while(n1 < n2 && c++ < 100){
- n = Math.floor((n1 + n2) / 2);
- if(test(bins[n], val)) n1 = n + 1;
- else n2 = n;
- }
- if(c > 90) console.log('Long binary search...');
- return n1 - 1;
- }
-};
-
-function lessThan(a, b) { return a < b; }
-function lessOrEqual(a, b) { return a <= b; }
-function greaterThan(a, b) { return a > b; }
-function greaterOrEqual(a, b) { return a >= b; }
-
-lib.sorterAsc = function(a, b) { return a - b; };
-
-/**
- * find distinct values in an array, lumping together ones that appear to
- * just be off by a rounding error
- * return the distinct values and the minimum difference between any two
- */
-lib.distinctVals = function(valsIn) {
- var vals = valsIn.slice(); // otherwise we sort the original array...
- vals.sort(lib.sorterAsc);
-
- var l = vals.length - 1,
- minDiff = (vals[l] - vals[0]) || 1,
- errDiff = minDiff / (l || 1) / 10000,
- v2 = [vals[0]];
-
- for(var i = 0; i < l; i++) {
- // make sure values aren't just off by a rounding error
- if(vals[i + 1] > vals[i] + errDiff) {
- minDiff = Math.min(minDiff, vals[i + 1] - vals[i]);
- v2.push(vals[i + 1]);
- }
- }
-
- return {vals: v2, minDiff: minDiff};
-};
-
-/**
- * return the smallest element from (sorted) array arrayIn that's bigger than val,
- * or (reverse) the largest element smaller than val
- * used to find the best tick given the minimum (non-rounded) tick
- * particularly useful for date/time where things are not powers of 10
- * binary search is probably overkill here...
- */
-lib.roundUp = function(val, arrayIn, reverse){
- var low = 0,
- high = arrayIn.length - 1,
- mid,
- c = 0,
- dlow = reverse ? 0 : 1,
- dhigh = reverse ? 1 : 0,
- rounded = reverse ? Math.ceil : Math.floor;
- // c is just to avoid infinite loops if there's an error
- while(low < high && c++ < 100){
- mid = rounded((low + high) / 2);
- if(arrayIn[mid] <= val) low = mid + dlow;
- else high = mid - dhigh;
- }
- return arrayIn[low];
-};
-
-/**
- * convert a string s (such as 'xaxis.range[0]')
- * representing a property of nested object into set and get methods
- * also return the string and object so we don't have to keep track of them
- * allows [-1] for an array index, to set a property inside all elements
- * of an array
- * eg if obj = {arr: [{a: 1}, {a: 2}]}
- * you can do p = nestedProperty(obj, 'arr[-1].a')
- * but you cannot set the array itself this way, to do that
- * just set the whole array.
- * eg if obj = {arr: [1, 2, 3]}
- * you can't do nestedProperty(obj, 'arr[-1]').set(5)
- * but you can do nestedProperty(obj, 'arr').set([5, 5, 5])
- */
-lib.nestedProperty = function(container, propStr) {
- if(isNumeric(propStr)) propStr = String(propStr);
- else if(typeof propStr !== 'string' ||
- propStr.substr(propStr.length - 4) === '[-1]') {
- throw 'bad property string';
- }
-
- var j = 0,
- propParts = propStr.split('.'),
- indexed,
- indices,
- i;
-
- // check for parts of the nesting hierarchy that are numbers (ie array elements)
- while(j < propParts.length) {
- // look for non-bracket chars, then any number of [##] blocks
- indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/);
- if(indexed) {
- if(indexed[1]) propParts[j] = indexed[1];
- // allow propStr to start with bracketed array indices
- else if(j === 0) propParts.splice(0,1);
- else throw 'bad property string';
-
- indices = indexed[2]
- .substr(1,indexed[2].length-2)
- .split('][');
-
- for(i=0; i= 0; i--) {
- curCont = containerLevels[i];
- remainingKeys = false;
- if(Array.isArray(curCont)) {
- for(j = curCont.length - 1; j >= 0; j--) {
- if(emptyObj(curCont[j])) {
- if(remainingKeys) curCont[j] = undefined;
- else curCont.pop();
- }
- else remainingKeys = true;
- }
- }
- else if(typeof curCont === 'object' && curCont !== null) {
- keys = Object.keys(curCont);
- remainingKeys = false;
- for(j = keys.length - 1; j >= 0; j--) {
- if(emptyObj(curCont[keys[j]]) && !isDataArray(curCont[keys[j]], keys[j])) delete curCont[keys[j]];
- else remainingKeys = true;
- }
- }
- if(remainingKeys) return;
- }
-}
-
-function emptyObj(obj) {
- if(obj===undefined || obj===null) return true;
- if(typeof obj !== 'object') return false; // any plain value
- if(Array.isArray(obj)) return !obj.length; // []
- return !Object.keys(obj).length; // {}
-}
-
-function badContainer(container, propStr, propParts) {
- return {
- set: function() { throw 'bad container'; },
- get: function() {},
- astr: propStr,
- parts: propParts,
- obj: container
- };
-}
-
-/**
- * swap x and y of the same attribute in container cont
- * specify attr with a ? in place of x/y
- * 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);
- }
-};
-
-/**
- * to prevent event bubbling, in particular text selection during drag.
- * see http://stackoverflow.com/questions/5429827/
- * how-can-i-prevent-text-element-selection-with-cursor-drag
- * for maximum effect use:
- * return pauseEvent(e);
- */
-lib.pauseEvent = function(e){
- if(e.stopPropagation) e.stopPropagation();
- if(e.preventDefault) e.preventDefault();
- e.cancelBubble = true;
- return false;
-};
-
-// pad a number with zeroes, to given # of digits before the decimal point
-lib.lpad = function(val, digits){
- return String(val + Math.pow(10, digits)).substr(1);
-};
-
-// STATISTICS FUNCTIONS
-
-/**
- * aggNums() returns the result of an aggregate function applied to an array of
- * values, where non-numerical values have been tossed out.
- *
- * @param {function} f - aggregation function (e.g., Math.min)
- * @param {Number} v - initial value (continuing from previous calls)
- * if there's no continuing value, use null for selector-type
- * functions (max,min), or 0 for summations
- * @param {Array} a - array to aggregate (may be nested, we will recurse,
- * but all elements must have the same dimension)
- * @param {Number} len - maximum length of a to aggregate
- * @return {Number} - result of f applied to a starting from v
- */
-lib.aggNums = function(f, v, a, len) {
- var i,
- b;
- if (!len) len = a.length;
- if (!isNumeric(v)) v = false;
- if (Array.isArray(a[0])) {
- b = new Array(len);
- for(i = 0; i < len; i++) b[i] = lib.aggNums(f, v, a[i]);
- a = b;
- }
-
- for (i = 0; i < len; i++) {
- if (!isNumeric(v)) v = a[i];
- else if (isNumeric(a[i])) v = f(+v, +a[i]);
- }
- return v;
-};
-
-/**
- * mean & std dev functions using aggNums, so it handles non-numerics nicely
- * even need to use aggNums instead of .length, to toss out non-numerics
- */
-lib.len = function(data) {
- return lib.aggNums(function(a){ return a + 1; }, 0, data);
-};
-
-lib.mean = function(data, len) {
- if(!len) len = lib.len(data);
- return lib.aggNums(function(a, b){ return a + b; }, 0, data) / len;
-};
-
-lib.variance = function(data, len, mean) {
- if (!len) len = lib.len(data);
- if (!isNumeric(mean)) mean = lib.mean(data, len);
-
- return lib.aggNums(function(a, b) {
- return a + Math.pow(b - mean, 2);
- }, 0, data)/len;
-};
-
-lib.stdev = function(data, len, mean) {
- return Math.sqrt(lib.variance(data, len, mean));
-};
-
-/**
- * interp() computes a percentile (quantile) for a given distribution.
- * We interpolate the distribution (to compute quantiles, we follow method #10 here:
- * http://www.amstat.org/publications/jse/v14n3/langford.html).
- * Typically the index or rank (n * arr.length) may be non-integer.
- * For reference: ends are clipped to the extreme values in the array;
- * For box plots: index you get is half a point too high (see
- * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition
- * indexes from 1 rather than 0, so we subtract 1/2 (instead of add).
- *
- * @param {Array} arr - This array contains the values that make up the distribution.
- * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile.
- * For example, the 50th percentile (or median) corresponds to n = 0.5
- * @return {Number} - percentile
- */
-lib.interp = function(arr, n) {
- if (!isNumeric(n)) throw 'n should be a finite number';
- n = n * arr.length - 0.5;
- if (n < 0) return arr[0];
- if (n > arr.length - 1) return arr[arr.length - 1];
- var frac = n % 1;
- return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)];
-};
-
-/**
- * ------------------------------------------
- * debugging tools
- * ------------------------------------------
- */
-
-// set VERBOSE to true to get a lot more logging and tracing
-lib.VERBOSE = false;
-
-// first markTime call will return time from page load
-lib.TIMER = new Date().getTime();
-
-// console.log that only runs if VERBOSE is on
-lib.log = function(){
- if(lib.VERBOSE) console.log.apply(console, arguments);
-};
-
-/**
- * markTime - for debugging, mark the number of milliseconds
- * since the previous call to markTime and log arbitrary info too
- */
-lib.markTime = function(v){
- if(!lib.VERBOSE) return;
- var t2 = new Date().getTime();
- console.log(v, t2 - lib.TIMER, '(msec)');
- if(lib.VERBOSE === 'trace') console.trace();
- lib.TIMER = t2;
-};
-
-// 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));
-};
-
-/**
- * notifier
- * @param {String} text The person's user name
- * @param {Number} [delay=1000] The delay time in milliseconds
- * or 'long' which provides 2000 ms delay time.
- * @return {undefined} this function does not return a value
- */
-var NOTEDATA = [];
-lib.notifier = function(text, displayLength) {
-
- if(NOTEDATA.indexOf(text) !== -1) return;
-
- NOTEDATA.push(text);
-
- var ts = 1000;
- if (isNumeric(displayLength)) ts = displayLength;
- else if (displayLength === 'long') ts = 3000;
-
- var notifierContainer = d3.select('body')
- .selectAll('.plotly-notifier')
- .data([0]);
- notifierContainer.enter()
- .append('div')
- .classed('plotly-notifier', true);
-
- var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
-
- function killNote(transition) {
- transition
- .duration(700)
- .style('opacity', 0)
- .each('end', function(thisText) {
- var thisIndex = NOTEDATA.indexOf(thisText);
- if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
- d3.select(this).remove();
- });
- }
-
- notes.enter().append('div')
- .classed('notifier-note', true)
- .style('opacity', 0)
- .each(function(thisText) {
- var note = d3.select(this);
-
- note.append('button')
- .classed('notifier-close', true)
- .html('×')
- .on('click', function() {
- note.transition().call(killNote);
- });
-
- note.append('p').html(thisText);
-
- note.transition()
- .duration(700)
- .style('opacity', 1)
- .transition()
- .delay(ts)
- .call(killNote);
- });
-};
-
-/**
- * do two bounding boxes from getBoundingClientRect,
- * ie {left,right,top,bottom,width,height}, overlap?
- * 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);
-};
-
-// minor convenience/performance booster for d3...
-lib.identity = function(d) { return d; };
-
-// 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;
-};
-
-
-lib.OptionControl = function(opt, optname) {
- /*
- * An environment to contain all option setters and
- * getters that collectively modify opts.
- *
- * You can call up opts from any function in new object
- * as this.optname || this.opt
- *
- * See FitOpts for example of usage
- */
- if (!opt) opt = {};
- if (!optname) optname = 'opt';
-
- var self = {};
- self.optionList = [];
-
- self._newoption = function(optObj) {
- optObj[optname] = opt;
- self[optObj.name] = optObj;
- self.optionList.push(optObj);
- };
-
- self['_'+optname] = opt;
- return self;
-};
-
-/**
- * lib.smooth: smooth arrayIn by convolving with
- * a hann window with given full width at half max
- * 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;
- }
-
- return arrayOut;
-};
-
-// helpers for promises
-
-/**
- * promiseError: log errors properly inside promises
- * use:
- * .then(undefined,Plotly.Lib.promiseError) (for IE compatibility)
- * or .catch(Plotly.Lib.promiseError)
- * TODO: I guess we need another step to send this error to Sentry?
- */
-lib.promiseError = function(err) { console.log(err, err.stack); };
-
-/**
- * syncOrAsync: run a sequence of functions synchronously
- * as long as its returns are not promises (ie have no .then)
- * includes one argument arg to send to all functions...
- * this is mainly just to prevent us having to make wrapper functions
- * when the only purpose of the wrapper is to reference gd / td
- * and a final step to be executed at the end
- * TODO: if there's an error and everything is sync,
- * this doesn't happen yet because we want to make sure
- * that it gets reported
- */
-lib.syncOrAsync = function(sequence, arg, finalStep) {
- var ret, fni;
-
- function continueAsync(){
- lib.markTime('async done ' + fni.name);
- return lib.syncOrAsync(sequence, arg, finalStep);
- }
- while(sequence.length) {
- fni = sequence.splice(0, 1)[0];
- ret = fni(arg);
- // lib.markTime('done calling '+fni.name)
- if(ret && ret.then) {
- return ret.then(continueAsync)
- .then(undefined, lib.promiseError);
- }
- lib.markTime('sync done ' + fni.name);
- }
-
- return finalStep && finalStep(arg);
-};
-
-lib.init2dArray = function(rowLength, colLength) {
- var array = new Array(rowLength);
- for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength);
- return array;
-};
-
-/**
- * transpose a (possibly ragged) 2d array z. inspired by
- * http://stackoverflow.com/questions/17428587/
- * transposing-a-2d-array-in-javascript
- */
-lib.transposeRagged = function(z) {
- var maxlen = 0,
- zlen = z.length,
- i,
- j;
- // Maximum row length:
- for (i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length);
-
- var t = new Array(maxlen);
- for (i = 0; i < maxlen; i++) {
- t[i] = new Array(zlen);
- for (j = 0; j < zlen; j++) t[i][j] = z[j][i];
- }
-
- return t;
-};
-
-// our own dot function so that we don't need to include numeric
-lib.dot = function(x, y) {
- if (!(x.length && y.length) || x.length !== y.length) return null;
-
- var len = x.length,
- out,
- i;
-
- if(x[0].length) {
- // mat-vec or mat-mat
- out = new Array(len);
- for(i = 0; i < len; i++) out[i] = lib.dot(x[i], y);
- }
- else if(y[0].length) {
- // vec-mat
- var yTranspose = lib.transposeRagged(y);
- out = new Array(yTranspose.length);
- for(i = 0; i < yTranspose.length; i++) out[i] = lib.dot(x, yTranspose[i]);
- }
- else {
- // vec-vec
- out = 0;
- for(i = 0; i < len; i++) out += x[i] * y[i];
- }
-
- return out;
-};
-
-
-// Functions to manipulate 2D transformation matrices
-
-// translate by (x,y)
-lib.translationMatrix = function (x, y) {
- return [[1, 0, x], [0, 1, y], [0, 0, 1]];
-};
-
-// rotate by alpha around (0,0)
-lib.rotationMatrix = function (alpha) {
- var a = alpha*Math.PI/180;
- return [[Math.cos(a), -Math.sin(a), 0],
- [Math.sin(a), Math.cos(a), 0],
- [0, 0, 1]];
-};
-
-// rotate by alpha around (x,y)
-lib.rotationXYMatrix = function(a, x, y) {
- return lib.dot(
- lib.dot(lib.translationMatrix(x, y),
- lib.rotationMatrix(a)),
- lib.translationMatrix(-x, -y));
-};
-
-// applies a 2D transformation matrix to either x and y params or an [x,y] array
-lib.apply2DTransform = function(transform) {
- return function() {
- var args = arguments;
- if (args.length === 3) {
- args = args[0];
- }//from map
- var xy = arguments.length === 1 ? args[0] : [args[0], args[1]];
- return lib.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
- };
-};
-
-// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment)
-lib.apply2DTransform2 = function(transform) {
- var at = lib.apply2DTransform(transform);
- return function(xys) {
- return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
- };
-};
-
-/**
- * Helper to strip trailing slash, from
- * 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;
-};
-
-var colorscaleNames = Object.keys(require('../colorscale').scales);
-
-lib.valObjects = {
- data_array: {
- // You can use *dflt=[] to force said array to exist though.
- description: [
- 'An {array} of data.',
- 'The value MUST be an {array}, or we ignore it.'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(Array.isArray(v)) propOut.set(v);
- else if(dflt!==undefined) propOut.set(dflt);
- }
- },
- enumerated: {
- description: [
- 'Enumerated value type. The available values are listed',
- 'in `values`.'
- ].join(' '),
- requiredOpts: ['values'],
- otherOpts: ['dflt', 'coerceNumber', 'arrayOk'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(opts.coerceNumber) v = +v;
- if(opts.values.indexOf(v)===-1) propOut.set(dflt);
- else propOut.set(v);
- }
- },
- 'boolean': {
- description: 'A boolean (true/false) value.',
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(v===true || v===false) propOut.set(v);
- else propOut.set(dflt);
- }
- },
- number: {
- description: [
- 'A number or a numeric value',
- '(e.g. a number inside a string).',
- 'When applicable, values greater (less) than `max` (`min`)',
- 'are coerced to the `dflt`.'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt', 'min', 'max', 'arrayOk'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(!isNumeric(v) ||
- (opts.min!==undefined && vopts.max)) {
- propOut.set(dflt);
- }
- else propOut.set(+v);
- }
- },
- integer: {
- description: [
- 'An integer or an integer inside a string.',
- 'When applicable, values greater (less) than `max` (`min`)',
- 'are coerced to the `dflt`.'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt', 'min', 'max'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(v%1 || !isNumeric(v) ||
- (opts.min!==undefined && vopts.max)) {
- propOut.set(dflt);
- }
- else propOut.set(+v);
- }
- },
- string: {
- description: [
- 'A string value.',
- 'Numbers are converted to strings except for attributes with',
- '`strict` set to true.'
- ].join(' '),
- requiredOpts: [],
- // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter)
- otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(opts.strict===true && typeof v !== 'string') {
- propOut.set(dflt);
- return;
- }
-
- var s = String(v);
- if(v===undefined || (opts.noBlank===true && !s)) {
- propOut.set(dflt);
- }
- else propOut.set(s);
- }
- },
- color: {
- description: [
- 'A string describing color.',
- 'Supported formats:',
- '- hex (e.g. \'#d3d3d3\')',
- '- rgb (e.g. \'rgb(255, 0, 0)\')',
- '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')',
- '- hsl (e.g. \'hsl(0, 100%, 50%)\')',
- '- hsv (e.g. \'hsv(0, 100%, 100%)\')',
- '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt', 'arrayOk'],
- coerceFunction: function(v, propOut, dflt) {
- if(tinycolor(v).isValid()) propOut.set(v);
- else propOut.set(dflt);
- }
- },
- colorscale: {
- description: [
- 'A Plotly colorscale either picked by a name:',
- '(any of', colorscaleNames.join(', '), ')',
- 'customized as an {array} of 2-element {arrays} where',
- 'the first element is the normalized color level value',
- '(starting at *0* and ending at *1*),',
- 'and the second item is a valid color string.'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- propOut.set(Plotly.Colorscale.getScale(v, dflt));
- }
- },
- angle: {
- description: [
- 'A number (in degree) between -180 and 180.'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(v==='auto') propOut.set('auto');
- else if(!isNumeric(v)) propOut.set(dflt);
- else {
- if(Math.abs(v)>180) v -= Math.round(v/360)*360;
- propOut.set(+v);
- }
- }
- },
- axisid: {
- description: [
- 'An axis id string (e.g. \'x\', \'x2\', \'x3\', ...).'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(typeof v === 'string' && v.charAt(0)===dflt) {
- var axnum = Number(v.substr(1));
- if(axnum%1 === 0 && axnum>1) {
- propOut.set(v);
- return;
- }
- }
- propOut.set(dflt);
- }
- },
- sceneid: {
- description: [
- 'A scene id string (e.g. \'scene\', \'scene2\', \'scene3\', ...).'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(typeof v === 'string' && v.substr(0,5)===dflt) {
- var scenenum = Number(v.substr(5));
- if(scenenum%1 === 0 && scenenum>1) {
- propOut.set(v);
- return;
- }
- }
- propOut.set(dflt);
- }
- },
- geoid: {
- description: [
- 'A geo id string (e.g. \'geo\', \'geo2\', \'geo3\', ...).'
- ].join(' '),
- requiredOpts: [],
- otherOpts: ['dflt'],
- coerceFunction: function(v, propOut, dflt) {
- if(typeof v === 'string' && v.substr(0,3)===dflt) {
- var geonum = Number(v.substr(3));
- if(geonum%1 === 0 && geonum>1) {
- propOut.set(v);
- return;
- }
- }
- propOut.set(dflt);
- }
- },
- flaglist: {
- description: [
- 'A string representing a combination of flags',
- '(order does not matter here).',
- 'Combine any of the available `flags` with *+*.',
- '(e.g. (\'lines+markers\')).',
- 'Values in `extras` cannot be combined.'
- ].join(' '),
- requiredOpts: ['flags'],
- otherOpts: ['dflt', 'extras'],
- coerceFunction: function(v, propOut, dflt, opts) {
- if(typeof v !== 'string') {
- propOut.set(dflt);
- return;
- }
- if(opts.extras.indexOf(v)!==-1) {
- propOut.set(v);
- return;
- }
- var vParts = v.split('+'),
- i = 0;
- while(i or 1 elements
- * because extend-like algorithms are hella slow
- * 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;
- }
-
- 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;
-};
-
-lib.titleCase = function(s) {
- 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;
-};
-
-// 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;
- }
-};
-
-lib.isPlotDiv = function(el) {
- var el3 = d3.select(el);
- return el3.size() && el3.classed('js-plotly-plot');
-};
-
-lib.removeElement = function(el) {
- var elParent = el && el.parentNode;
- if(elParent) elParent.removeChild(el);
-};
-
-/**
- * for dynamically adding style rules
- * makes one stylesheet that contains all rules added
- * 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 console.warn('addStyleRule failed');
-};
-
-lib.isIE = function() {
- return typeof window.navigator.msSaveBlob !== 'undefined';
-};
-
-lib.isPlainObject = require('./is_plain_object');
-
-var extendModule = require('./extend');
-lib.extendFlat = extendModule.extendFlat;
-lib.extendDeep = extendModule.extendDeep;
-lib.extendDeepAll = extendModule.extendDeepAll;
diff --git a/src/lib/matrix.js b/src/lib/matrix.js
new file mode 100644
index 00000000000..4fbc4087ad6
--- /dev/null
+++ b/src/lib/matrix.js
@@ -0,0 +1,99 @@
+'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;
+};
+
+/**
+ * transpose a (possibly ragged) 2d array z. inspired by
+ * http://stackoverflow.com/questions/17428587/
+ * 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];
+ }
+
+ 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;
+
+ 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);
+ }
+ 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];
+ }
+
+ return out;
+};
+
+// translate by (x,y)
+exports.translationMatrix = function (x, y) {
+ 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]];
+};
+
+// 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));
+};
+
+// 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);
+ };
+};
+
+// 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)));
+ };
+};
diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js
new file mode 100644
index 00000000000..19c4a346fd9
--- /dev/null
+++ b/src/lib/nested_property.js
@@ -0,0 +1,245 @@
+'use strict';
+
+var isNumeric = require('fast-isnumeric');
+
+/**
+ * convert a string s (such as 'xaxis.range[0]')
+ * representing a property of nested object into set and get methods
+ * also return the string and object so we don't have to keep track of them
+ * allows [-1] for an array index, to set a property inside all elements
+ * of an array
+ * eg if obj = {arr: [{a: 1}, {a: 2}]}
+ * you can do p = nestedProperty(obj, 'arr[-1].a')
+ * but you cannot set the array itself this way, to do that
+ * just set the whole array.
+ * eg if obj = {arr: [1, 2, 3]}
+ * you can't do nestedProperty(obj, 'arr[-1]').set(5)
+ * but you can do nestedProperty(obj, 'arr').set([5, 5, 5])
+ */
+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= 0; i--) {
+ curCont = containerLevels[i];
+ remainingKeys = false;
+ if(Array.isArray(curCont)) {
+ for(j = curCont.length - 1; j >= 0; j--) {
+ if(emptyObj(curCont[j])) {
+ if(remainingKeys) curCont[j] = undefined;
+ else curCont.pop();
+ }
+ else remainingKeys = true;
+ }
+ }
+ else if(typeof curCont === 'object' && curCont !== null) {
+ keys = Object.keys(curCont);
+ remainingKeys = false;
+ for(j = keys.length - 1; j >= 0; j--) {
+ if(emptyObj(curCont[keys[j]]) && !isDataArray(curCont[keys[j]], keys[j])) delete curCont[keys[j]];
+ else remainingKeys = true;
+ }
+ }
+ if(remainingKeys) return;
+ }
+}
+
+function emptyObj(obj) {
+ if(obj===undefined || obj===null) return true;
+ if(typeof obj !== 'object') return false; // any plain value
+ if(Array.isArray(obj)) return !obj.length; // []
+ return !Object.keys(obj).length; // {}
+}
+
+function badContainer(container, propStr, propParts) {
+ return {
+ set: function() { throw 'bad container'; },
+ get: function() {},
+ astr: propStr,
+ parts: propParts,
+ obj: container
+ };
+}
diff --git a/src/lib/notifier.js b/src/lib/notifier.js
new file mode 100644
index 00000000000..a1742717f80
--- /dev/null
+++ b/src/lib/notifier.js
@@ -0,0 +1,66 @@
+'use strict';
+
+var d3 = require('d3');
+var isNumeric = require('fast-isnumeric');
+
+var NOTEDATA = [];
+
+/**
+ * notifier
+ * @param {String} text The person's user name
+ * @param {Number} [delay=1000] The delay time in milliseconds
+ * or 'long' which provides 2000 ms delay time.
+ * @return {undefined} this function does not return a value
+ */
+module.exports = function(text, displayLength) {
+ if(NOTEDATA.indexOf(text) !== -1) return;
+
+ NOTEDATA.push(text);
+
+ var ts = 1000;
+ if (isNumeric(displayLength)) ts = displayLength;
+ else if (displayLength === 'long') ts = 3000;
+
+ var notifierContainer = d3.select('body')
+ .selectAll('.plotly-notifier')
+ .data([0]);
+ notifierContainer.enter()
+ .append('div')
+ .classed('plotly-notifier', true);
+
+ var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
+
+ function killNote(transition) {
+ transition
+ .duration(700)
+ .style('opacity', 0)
+ .each('end', function(thisText) {
+ var thisIndex = NOTEDATA.indexOf(thisText);
+ if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
+ d3.select(this).remove();
+ });
+ }
+
+ notes.enter().append('div')
+ .classed('notifier-note', true)
+ .style('opacity', 0)
+ .each(function(thisText) {
+ var note = d3.select(this);
+
+ note.append('button')
+ .classed('notifier-close', true)
+ .html('×')
+ .on('click', function() {
+ note.transition().call(killNote);
+ });
+
+ note.append('p').html(thisText);
+
+ note.transition()
+ .duration(700)
+ .style('opacity', 1)
+ .transition()
+ .delay(ts)
+ .call(killNote);
+ });
+};
diff --git a/src/queue.js b/src/lib/queue.js
similarity index 99%
rename from src/queue.js
rename to src/lib/queue.js
index 240b3a6cf4d..f8852f41ff9 100644
--- a/src/queue.js
+++ b/src/lib/queue.js
@@ -1,6 +1,6 @@
'use strict';
-var Plotly = require('./plotly');
+var Plotly = require('../plotly');
/**
* Copy arg array *without* removing `undefined` values from objects.
diff --git a/src/lib/search.js b/src/lib/search.js
new file mode 100644
index 00000000000..45297c5d394
--- /dev/null
+++ b/src/lib/search.js
@@ -0,0 +1,98 @@
+'use strict';
+
+var isNumeric = require('fast-isnumeric');
+
+
+/**
+ * findBin - find the bin for val - note that it can return outside the
+ * bin range any pos. or neg. integer for linear bins, or -1 or
+ * bins.length-1 for explicit.
+ * bins is either an object {start,size,end} or an array length #bins+1
+ * bins can be either increasing or decreasing but must be monotonic
+ * for linear bins, we can just calculate. For listed bins, run a binary
+ * search linelow (truthy) says the bin boundary should be attributed to
+ * the lower bin rather than the default upper bin
+ */
+exports.findBin = function(val, bins, linelow) {
+ if(isNumeric(bins.start)) {
+ return linelow ?
+ Math.ceil((val - bins.start) / bins.size) - 1 :
+ Math.floor((val - bins.start) / bins.size);
+ }
+ else {
+ var n1 = 0,
+ n2 = bins.length,
+ c = 0,
+ n,
+ test;
+ if(bins[bins.length - 1] > bins[0]) {
+ test = linelow ? lessThan : lessOrEqual;
+ } else {
+ test = linelow ? greaterOrEqual : greaterThan;
+ }
+ // c is just to avoid infinite loops if there's an error
+ while(n1 < n2 && c++ < 100){
+ n = Math.floor((n1 + n2) / 2);
+ if(test(bins[n], val)) n1 = n + 1;
+ else n2 = n;
+ }
+ if(c > 90) console.log('Long binary search...');
+ return n1 - 1;
+ }
+};
+
+function lessThan(a, b) { return a < b; }
+function lessOrEqual(a, b) { return a <= b; }
+function greaterThan(a, b) { return a > b; }
+function greaterOrEqual(a, b) { return a >= b; }
+
+exports.sorterAsc = function(a, b) { return a - b; };
+
+/**
+ * find distinct values in an array, lumping together ones that appear to
+ * just be off by a rounding error
+ * return the distinct values and the minimum difference between any two
+ */
+exports.distinctVals = function(valsIn) {
+ var vals = valsIn.slice(); // otherwise we sort the original array...
+ vals.sort(exports.sorterAsc);
+
+ var l = vals.length - 1,
+ minDiff = (vals[l] - vals[0]) || 1,
+ errDiff = minDiff / (l || 1) / 10000,
+ v2 = [vals[0]];
+
+ for(var i = 0; i < l; i++) {
+ // make sure values aren't just off by a rounding error
+ if(vals[i + 1] > vals[i] + errDiff) {
+ minDiff = Math.min(minDiff, vals[i + 1] - vals[i]);
+ v2.push(vals[i + 1]);
+ }
+ }
+
+ return {vals: v2, minDiff: minDiff};
+};
+
+/**
+ * return the smallest element from (sorted) array arrayIn that's bigger than val,
+ * or (reverse) the largest element smaller than val
+ * used to find the best tick given the minimum (non-rounded) tick
+ * particularly useful for date/time where things are not powers of 10
+ * binary search is probably overkill here...
+ */
+exports.roundUp = function(val, arrayIn, reverse){
+ var low = 0,
+ high = arrayIn.length - 1,
+ mid,
+ c = 0,
+ dlow = reverse ? 0 : 1,
+ dhigh = reverse ? 1 : 0,
+ rounded = reverse ? Math.ceil : Math.floor;
+ // c is just to avoid infinite loops if there's an error
+ while(low < high && c++ < 100){
+ mid = rounded((low + high) / 2);
+ if(arrayIn[mid] <= val) low = mid + dlow;
+ else high = mid - dhigh;
+ }
+ return arrayIn[low];
+};
diff --git a/src/gl3d/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js
similarity index 96%
rename from src/gl3d/lib/show_no_webgl_msg.js
rename to src/lib/show_no_webgl_msg.js
index 5e382318ea2..0118e1e0d9b 100644
--- a/src/gl3d/lib/show_no_webgl_msg.js
+++ b/src/lib/show_no_webgl_msg.js
@@ -1,6 +1,6 @@
'use strict';
-var Plotly = require('../../plotly');
+var Plotly = require('../plotly');
/**
* Prints a no webgl error message into the scene container
diff --git a/src/lib/stats.js b/src/lib/stats.js
new file mode 100644
index 00000000000..962f9d8c569
--- /dev/null
+++ b/src/lib/stats.js
@@ -0,0 +1,85 @@
+'use strict';
+
+var isNumeric = require('fast-isnumeric');
+
+
+/**
+ * aggNums() returns the result of an aggregate function applied to an array of
+ * values, where non-numerical values have been tossed out.
+ *
+ * @param {function} f - aggregation function (e.g., Math.min)
+ * @param {Number} v - initial value (continuing from previous calls)
+ * if there's no continuing value, use null for selector-type
+ * functions (max,min), or 0 for summations
+ * @param {Array} a - array to aggregate (may be nested, we will recurse,
+ * but all elements must have the same dimension)
+ * @param {Number} len - maximum length of a to aggregate
+ * @return {Number} - result of f applied to a starting from v
+ */
+exports.aggNums = function(f, v, a, len) {
+ var i,
+ b;
+ if (!len) len = a.length;
+ if (!isNumeric(v)) v = false;
+ if (Array.isArray(a[0])) {
+ b = new Array(len);
+ for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
+ a = b;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (!isNumeric(v)) v = a[i];
+ else if (isNumeric(a[i])) v = f(+v, +a[i]);
+ }
+ return v;
+};
+
+/**
+ * mean & std dev functions using aggNums, so it handles non-numerics nicely
+ * even need to use aggNums instead of .length, to toss out non-numerics
+ */
+exports.len = function(data) {
+ return exports.aggNums(function(a){ return a + 1; }, 0, data);
+};
+
+exports.mean = function(data, len) {
+ if(!len) len = exports.len(data);
+ return exports.aggNums(function(a, b){ return a + b; }, 0, data) / len;
+};
+
+exports.variance = function(data, len, mean) {
+ if (!len) len = exports.len(data);
+ if (!isNumeric(mean)) mean = exports.mean(data, len);
+
+ return exports.aggNums(function(a, b) {
+ return a + Math.pow(b - mean, 2);
+ }, 0, data)/len;
+};
+
+exports.stdev = function(data, len, mean) {
+ return Math.sqrt(exports.variance(data, len, mean));
+};
+
+/**
+ * interp() computes a percentile (quantile) for a given distribution.
+ * We interpolate the distribution (to compute quantiles, we follow method #10 here:
+ * http://www.amstat.org/publications/jse/v14n3/langford.html).
+ * Typically the index or rank (n * arr.length) may be non-integer.
+ * For reference: ends are clipped to the extreme values in the array;
+ * For box plots: index you get is half a point too high (see
+ * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition
+ * indexes from 1 rather than 0, so we subtract 1/2 (instead of add).
+ *
+ * @param {Array} arr - This array contains the values that make up the distribution.
+ * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile.
+ * For example, the 50th percentile (or median) corresponds to n = 0.5
+ * @return {Number} - percentile
+ */
+exports.interp = function(arr, n) {
+ if (!isNumeric(n)) throw 'n should be a finite number';
+ n = n * arr.length - 0.5;
+ if (n < 0) return arr[0];
+ if (n > arr.length - 1) return arr[arr.length - 1];
+ var frac = n % 1;
+ return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)];
+};
diff --git a/src/gl3d/lib/str2rgbarray.js b/src/lib/str2rgbarray.js
similarity index 68%
rename from src/gl3d/lib/str2rgbarray.js
rename to src/lib/str2rgbarray.js
index 5593ab75e5a..2dfcd0bb3f3 100644
--- a/src/gl3d/lib/str2rgbarray.js
+++ b/src/lib/str2rgbarray.js
@@ -1,7 +1,7 @@
'use strict';
-var tinycolor = require('tinycolor2'),
- arrtools = require('arraytools');
+var tinycolor = require('tinycolor2');
+var arrtools = require('arraytools');
function str2RgbaArray(color) {
color = tinycolor(color);
diff --git a/src/lib/plotly_util.js b/src/lib/svg_text_utils.js
similarity index 98%
rename from src/lib/plotly_util.js
rename to src/lib/svg_text_utils.js
index a299113d549..40ca14d3a93 100644
--- a/src/lib/plotly_util.js
+++ b/src/lib/svg_text_utils.js
@@ -1,14 +1,13 @@
'use strict';
-/* global MathJax:false, Promise:false */
+/* global MathJax:false */
-var Plotly = require('../plotly'),
- d3 = require('d3');
+var Plotly = require('../plotly');
+var d3 = require('d3');
var util = module.exports = {};
// Append SVG
-/////////////////////////////
d3.selection.prototype.appendSVG = function(_svgString) {
var skeleton = '