diff --git a/src/assets/geo_assets.js b/src/assets/geo_assets.js index bc2792add3b..4f494ffc81d 100644 --- a/src/assets/geo_assets.js +++ b/src/assets/geo_assets.js @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var saneTopojson = require('sane-topojson'); +'use strict' +var saneTopojson = require('sane-topojson') // package version injected by `npm run preprocess` -exports.version = '1.23.0'; +exports.version = '1.23.0' -exports.topojson = saneTopojson; +exports.topojson = saneTopojson diff --git a/src/components/annotations/annotation_defaults.js b/src/components/annotations/annotation_defaults.js index 7df91978c59..c87e1514814 100644 --- a/src/components/annotations/annotation_defaults.js +++ b/src/components/annotations/annotation_defaults.js @@ -6,102 +6,100 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Color = require('../color') +var Axes = require('../../plots/cartesian/axes') -var Lib = require('../../lib'); -var Color = require('../color'); -var Axes = require('../../plots/cartesian/axes'); +var attributes = require('./attributes') -var attributes = require('./attributes'); +module.exports = function handleAnnotationDefaults (annIn, annOut, fullLayout, opts, itemOpts) { + opts = opts || {} + itemOpts = itemOpts || {} + function coerce (attr, dflt) { + return Lib.coerce(annIn, annOut, attributes, attr, dflt) + } -module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, opts, itemOpts) { - opts = opts || {}; - itemOpts = itemOpts || {}; + var visible = coerce('visible', !itemOpts.itemIsNotPlainObject) + var clickToShow = coerce('clicktoshow') - function coerce(attr, dflt) { - return Lib.coerce(annIn, annOut, attributes, attr, dflt); - } - - var visible = coerce('visible', !itemOpts.itemIsNotPlainObject); - var clickToShow = coerce('clicktoshow'); + if (!(visible || clickToShow)) return annOut - if(!(visible || clickToShow)) return annOut; + coerce('opacity') + coerce('align') + coerce('bgcolor') - coerce('opacity'); - coerce('align'); - coerce('bgcolor'); + var borderColor = coerce('bordercolor'), + borderOpacity = Color.opacity(borderColor) - var borderColor = coerce('bordercolor'), - borderOpacity = Color.opacity(borderColor); + coerce('borderpad') - coerce('borderpad'); + var borderWidth = coerce('borderwidth') + var showArrow = coerce('showarrow') - var borderWidth = coerce('borderwidth'); - var showArrow = coerce('showarrow'); - - coerce('text', showArrow ? ' ' : 'new text'); - coerce('textangle'); - Lib.coerceFont(coerce, 'font', fullLayout.font); + coerce('text', showArrow ? ' ' : 'new text') + coerce('textangle') + Lib.coerceFont(coerce, 'font', fullLayout.font) // positioning - var axLetters = ['x', 'y'], - arrowPosDflt = [-10, -30], - gdMock = {_fullLayout: fullLayout}; - for(var i = 0; i < 2; i++) { - var axLetter = axLetters[i]; + var axLetters = ['x', 'y'], + arrowPosDflt = [-10, -30], + gdMock = {_fullLayout: fullLayout} + for (var i = 0; i < 2; i++) { + var axLetter = axLetters[i] // xref, yref - var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper'); + var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper') // x, y - Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5); + Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5) - if(showArrow) { - var arrowPosAttr = 'a' + axLetter, + if (showArrow) { + var arrowPosAttr = 'a' + axLetter, // axref, ayref - aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel'); + aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel') // for now the arrow can only be on the same axis or specified as pixels // TODO: sometime it might be interesting to allow it to be on *any* axis // but that would require updates to drawing & autorange code and maybe more - if(aaxRef !== 'pixel' && aaxRef !== axRef) { - aaxRef = annOut[arrowPosAttr] = 'pixel'; - } + if (aaxRef !== 'pixel' && aaxRef !== axRef) { + aaxRef = annOut[arrowPosAttr] = 'pixel' + } // ax, ay - var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4; - Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt); - } + var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4 + Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt) + } // xanchor, yanchor - coerce(axLetter + 'anchor'); - } + coerce(axLetter + 'anchor') + } // if you have one coordinate you should have both - Lib.noneOrAll(annIn, annOut, ['x', 'y']); + Lib.noneOrAll(annIn, annOut, ['x', 'y']) - if(showArrow) { - coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine); - coerce('arrowhead'); - coerce('arrowsize'); - coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2); - coerce('standoff'); + if (showArrow) { + coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine) + coerce('arrowhead') + coerce('arrowsize') + coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2) + coerce('standoff') // if you have one part of arrow length you should have both - Lib.noneOrAll(annIn, annOut, ['ax', 'ay']); - } + Lib.noneOrAll(annIn, annOut, ['ax', 'ay']) + } - if(clickToShow) { - var xClick = coerce('xclick'); - var yClick = coerce('yclick'); + if (clickToShow) { + var xClick = coerce('xclick') + var yClick = coerce('yclick') // put the actual click data to bind to into private attributes // so we don't have to do this little bit of logic on every hover event - annOut._xclick = (xClick === undefined) ? annOut.x : xClick; - annOut._yclick = (yClick === undefined) ? annOut.y : yClick; - } + annOut._xclick = (xClick === undefined) ? annOut.x : xClick + annOut._yclick = (yClick === undefined) ? annOut.y : yClick + } - return annOut; -}; + return annOut +} diff --git a/src/components/annotations/arrow_paths.js b/src/components/annotations/arrow_paths.js index 3f27bbaf83a..6dd790f04ee 100644 --- a/src/components/annotations/arrow_paths.js +++ b/src/components/annotations/arrow_paths.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /** * centerx is a center of scaling tuned for maximum scalability of @@ -21,43 +21,43 @@ module.exports = [ // no arrow - { - path: '', - backoff: 0 - }, + { + path: '', + backoff: 0 + }, // wide with flat back - { - path: 'M-2.4,-3V3L0.6,0Z', - backoff: 0.6 - }, + { + 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 - }, + { + 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 - }, + { + 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 - }, + { + 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 - }, + { + 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 - }, + { + 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 - } -]; + { + path: 'M2,2V-2H-2V2Z', + backoff: 0 + } +] diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index cdaccd8bc73..e1e69f0e804 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -6,354 +6,353 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var ARROWPATHS = require('./arrow_paths'); -var fontAttrs = require('../../plots/font_attributes'); -var cartesianConstants = require('../../plots/cartesian/constants'); -var extendFlat = require('../../lib/extend').extendFlat; +'use strict' +var ARROWPATHS = require('./arrow_paths') +var fontAttrs = require('../../plots/font_attributes') +var cartesianConstants = require('../../plots/cartesian/constants') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { - _isLinkedToArray: 'annotation', + _isLinkedToArray: 'annotation', - visible: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not this annotation is visible.' - ].join(' ') - }, + visible: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not this annotation is visible.' + ].join(' ') + }, - text: { - valType: 'string', - role: 'info', - description: [ - 'Sets the text associated with this annotation.', - 'Plotly uses a subset of HTML tags to do things like', - 'newline (
), bold (), italics (),', - 'hyperlinks (). Tags , , ', - ' are also supported.' - ].join(' ') - }, - textangle: { - valType: 'angle', - dflt: 0, - role: 'style', - description: [ - 'Sets the angle at which the `text` is drawn', - 'with respect to the horizontal.' - ].join(' ') - }, - font: extendFlat({}, fontAttrs, { - description: 'Sets the annotation text font.' - }), - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - role: 'style', - description: 'Sets the opacity of the annotation (text + arrow).' - }, - align: { - valType: 'enumerated', - values: ['left', 'center', 'right'], - dflt: 'center', - role: 'style', - description: [ - 'Sets the vertical alignment of the `text` with', - 'respect to the set `x` and `y` position.', - 'Has only an effect if `text` spans more two or more lines', - '(i.e. `text` contains one or more
HTML tags).' - ].join(' ') - }, - bgcolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'style', - description: 'Sets the background color of the annotation.' - }, - bordercolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'style', - description: [ - 'Sets the color of the border enclosing the annotation `text`.' - ].join(' ') - }, - borderpad: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the padding (in px) between the `text`', - 'and the enclosing border.' - ].join(' ') - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the width (in px) of the border enclosing', - 'the annotation `text`.' - ].join(' ') - }, + text: { + valType: 'string', + role: 'info', + description: [ + 'Sets the text associated with this annotation.', + 'Plotly uses a subset of HTML tags to do things like', + 'newline (
), bold (), italics (),', + 'hyperlinks (). Tags , , ', + ' are also supported.' + ].join(' ') + }, + textangle: { + valType: 'angle', + dflt: 0, + role: 'style', + description: [ + 'Sets the angle at which the `text` is drawn', + 'with respect to the horizontal.' + ].join(' ') + }, + font: extendFlat({}, fontAttrs, { + description: 'Sets the annotation text font.' + }), + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + role: 'style', + description: 'Sets the opacity of the annotation (text + arrow).' + }, + align: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'center', + role: 'style', + description: [ + 'Sets the vertical alignment of the `text` with', + 'respect to the set `x` and `y` position.', + 'Has only an effect if `text` spans more two or more lines', + '(i.e. `text` contains one or more
HTML tags).' + ].join(' ') + }, + bgcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'style', + description: 'Sets the background color of the annotation.' + }, + bordercolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'style', + description: [ + 'Sets the color of the border enclosing the annotation `text`.' + ].join(' ') + }, + borderpad: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the padding (in px) between the `text`', + 'and the enclosing border.' + ].join(' ') + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the width (in px) of the border enclosing', + 'the annotation `text`.' + ].join(' ') + }, // arrow - showarrow: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the annotation is drawn with an arrow.', - 'If *true*, `text` is placed near the arrow\'s tail.', - 'If *false*, `text` lines up with the `x` and `y` provided.' - ].join(' ') - }, - arrowcolor: { - valType: 'color', - role: 'style', - description: 'Sets the color of the annotation arrow.' - }, - arrowhead: { - valType: 'integer', - min: 0, - max: ARROWPATHS.length, - dflt: 1, - role: 'style', - description: 'Sets the annotation arrow head style.' - }, - arrowsize: { - valType: 'number', - min: 0.3, - dflt: 1, - role: 'style', - description: 'Sets the size (in px) of annotation arrow head.' - }, - arrowwidth: { - valType: 'number', - min: 0.1, - role: 'style', - description: 'Sets the width (in px) of annotation arrow.' - }, - standoff: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Sets a distance, in pixels, to move the arrowhead away from the', - 'position it is pointing at, for example to point at the edge of', - 'a marker independent of zoom.' - ].join(' ') - }, - ax: { - valType: 'any', - role: 'info', - description: [ - 'Sets the x component of the arrow tail about the arrow head.', - 'If `axref` is `pixel`, a positive (negative) ', - 'component corresponds to an arrow pointing', - 'from right to left (left to right).', - 'If `axref` is an axis, this is an absolute value on that axis,', - 'like `x`, NOT a relative value.' - ].join(' ') - }, - ay: { - valType: 'any', - role: 'info', - description: [ - 'Sets the y component of the arrow tail about the arrow head.', - 'If `ayref` is `pixel`, a positive (negative) ', - 'component corresponds to an arrow pointing', - 'from bottom to top (top to bottom).', - 'If `ayref` is an axis, this is an absolute value on that axis,', - 'like `y`, NOT a relative value.' - ].join(' ') - }, - axref: { - valType: 'enumerated', - dflt: 'pixel', - values: [ - 'pixel', - cartesianConstants.idRegex.x.toString() - ], - role: 'info', - description: [ - 'Indicates in what terms the tail of the annotation (ax,ay) ', - 'is specified. If `pixel`, `ax` is a relative offset in pixels ', - 'from `x`. If set to an x axis id (e.g. *x* or *x2*), `ax` is ', - 'specified in the same terms as that axis. This is useful ', - 'for trendline annotations which should continue to indicate ', - 'the correct trend when zoomed.' - ].join(' ') - }, - ayref: { - valType: 'enumerated', - dflt: 'pixel', - values: [ - 'pixel', - cartesianConstants.idRegex.y.toString() - ], - role: 'info', - description: [ - 'Indicates in what terms the tail of the annotation (ax,ay) ', - 'is specified. If `pixel`, `ay` is a relative offset in pixels ', - 'from `y`. If set to a y axis id (e.g. *y* or *y2*), `ay` is ', - 'specified in the same terms as that axis. This is useful ', - 'for trendline annotations which should continue to indicate ', - 'the correct trend when zoomed.' - ].join(' ') - }, + showarrow: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the annotation is drawn with an arrow.', + 'If *true*, `text` is placed near the arrow\'s tail.', + 'If *false*, `text` lines up with the `x` and `y` provided.' + ].join(' ') + }, + arrowcolor: { + valType: 'color', + role: 'style', + description: 'Sets the color of the annotation arrow.' + }, + arrowhead: { + valType: 'integer', + min: 0, + max: ARROWPATHS.length, + dflt: 1, + role: 'style', + description: 'Sets the annotation arrow head style.' + }, + arrowsize: { + valType: 'number', + min: 0.3, + dflt: 1, + role: 'style', + description: 'Sets the size (in px) of annotation arrow head.' + }, + arrowwidth: { + valType: 'number', + min: 0.1, + role: 'style', + description: 'Sets the width (in px) of annotation arrow.' + }, + standoff: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Sets a distance, in pixels, to move the arrowhead away from the', + 'position it is pointing at, for example to point at the edge of', + 'a marker independent of zoom.' + ].join(' ') + }, + ax: { + valType: 'any', + role: 'info', + description: [ + 'Sets the x component of the arrow tail about the arrow head.', + 'If `axref` is `pixel`, a positive (negative) ', + 'component corresponds to an arrow pointing', + 'from right to left (left to right).', + 'If `axref` is an axis, this is an absolute value on that axis,', + 'like `x`, NOT a relative value.' + ].join(' ') + }, + ay: { + valType: 'any', + role: 'info', + description: [ + 'Sets the y component of the arrow tail about the arrow head.', + 'If `ayref` is `pixel`, a positive (negative) ', + 'component corresponds to an arrow pointing', + 'from bottom to top (top to bottom).', + 'If `ayref` is an axis, this is an absolute value on that axis,', + 'like `y`, NOT a relative value.' + ].join(' ') + }, + axref: { + valType: 'enumerated', + dflt: 'pixel', + values: [ + 'pixel', + cartesianConstants.idRegex.x.toString() + ], + role: 'info', + description: [ + 'Indicates in what terms the tail of the annotation (ax,ay) ', + 'is specified. If `pixel`, `ax` is a relative offset in pixels ', + 'from `x`. If set to an x axis id (e.g. *x* or *x2*), `ax` is ', + 'specified in the same terms as that axis. This is useful ', + 'for trendline annotations which should continue to indicate ', + 'the correct trend when zoomed.' + ].join(' ') + }, + ayref: { + valType: 'enumerated', + dflt: 'pixel', + values: [ + 'pixel', + cartesianConstants.idRegex.y.toString() + ], + role: 'info', + description: [ + 'Indicates in what terms the tail of the annotation (ax,ay) ', + 'is specified. If `pixel`, `ay` is a relative offset in pixels ', + 'from `y`. If set to a y axis id (e.g. *y* or *y2*), `ay` is ', + 'specified in the same terms as that axis. This is useful ', + 'for trendline annotations which should continue to indicate ', + 'the correct trend when zoomed.' + ].join(' ') + }, // positioning - xref: { - valType: 'enumerated', - values: [ - 'paper', - cartesianConstants.idRegex.x.toString() - ], - role: 'info', - description: [ - 'Sets the annotation\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the left (right) side.' - ].join(' ') - }, - x: { - valType: 'any', - role: 'info', - description: [ - 'Sets the annotation\'s x position.', - 'If the axis `type` is *log*, then you must take the', - 'log of your desired range.', - 'If the axis `type` is *date*, it should be date strings,', - 'like date data, though Date objects and unix milliseconds', - 'will be accepted and converted to strings.', - 'If the axis `type` is *category*, it should be numbers,', - 'using the scale where each category is assigned a serial', - 'number from zero in the order it appears.' - ].join(' ') - }, - xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'auto', - role: 'info', - description: [ - 'Sets the text box\'s horizontal position anchor', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the annotation.', - 'For example, if `x` is set to 1, `xref` to *paper* and', - '`xanchor` to *right* then the right-most portion of the', - 'annotation lines up with the right-most edge of the', - 'plotting area.', - 'If *auto*, the anchor is equivalent to *center* for', - 'data-referenced annotations or if there is an arrow,', - 'whereas for paper-referenced with no arrow, the anchor picked', - 'corresponds to the closest side.' - ].join(' ') - }, - yref: { - valType: 'enumerated', - values: [ - 'paper', - cartesianConstants.idRegex.y.toString() - ], - role: 'info', - description: [ - 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where 0 (1) corresponds to the bottom (top).' - ].join(' ') - }, - y: { - valType: 'any', - role: 'info', - description: [ - 'Sets the annotation\'s y position.', - 'If the axis `type` is *log*, then you must take the', - 'log of your desired range.', - 'If the axis `type` is *date*, it should be date strings,', - 'like date data, though Date objects and unix milliseconds', - 'will be accepted and converted to strings.', - 'If the axis `type` is *category*, it should be numbers,', - 'using the scale where each category is assigned a serial', - 'number from zero in the order it appears.' - ].join(' ') - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'auto', - role: 'info', - description: [ - 'Sets the text box\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the annotation.', - 'For example, if `y` is set to 1, `yref` to *paper* and', - '`yanchor` to *top* then the top-most portion of the', - 'annotation lines up with the top-most edge of the', - 'plotting area.', - 'If *auto*, the anchor is equivalent to *middle* for', - 'data-referenced annotations or if there is an arrow,', - 'whereas for paper-referenced with no arrow, the anchor picked', - 'corresponds to the closest side.' - ].join(' ') - }, - clicktoshow: { - valType: 'enumerated', - values: [false, 'onoff', 'onout'], - dflt: false, - role: 'style', - description: [ - 'Makes this annotation respond to clicks on the plot.', - 'If you click a data point that exactly matches the `x` and `y`', - 'values of this annotation, and it is hidden (visible: false),', - 'it will appear. In *onoff* mode, you must click the same point', - 'again to make it disappear, so if you click multiple points,', - 'you can show multiple annotations. In *onout* mode, a click', - 'anywhere else in the plot (on another data point or not) will', - 'hide this annotation.', - 'If you need to show/hide this annotation in response to different', - '`x` or `y` values, you can set `xclick` and/or `yclick`. This is', - 'useful for example to label the side of a bar. To label markers', - 'though, `standoff` is preferred over `xclick` and `yclick`.' - ].join(' ') - }, - xclick: { - valType: 'any', - role: 'info', - description: [ - 'Toggle this annotation when clicking a data point whose `x` value', - 'is `xclick` rather than the annotation\'s `x` value.' - ].join(' ') - }, - yclick: { - valType: 'any', - role: 'info', - description: [ - 'Toggle this annotation when clicking a data point whose `y` value', - 'is `yclick` rather than the annotation\'s `y` value.' - ].join(' ') - }, + xref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.x.toString() + ], + role: 'info', + description: [ + 'Sets the annotation\'s x coordinate axis.', + 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left side of the plotting area in normalized coordinates', + 'where 0 (1) corresponds to the left (right) side.' + ].join(' ') + }, + x: { + valType: 'any', + role: 'info', + description: [ + 'Sets the annotation\'s x position.', + 'If the axis `type` is *log*, then you must take the', + 'log of your desired range.', + 'If the axis `type` is *date*, it should be date strings,', + 'like date data, though Date objects and unix milliseconds', + 'will be accepted and converted to strings.', + 'If the axis `type` is *category*, it should be numbers,', + 'using the scale where each category is assigned a serial', + 'number from zero in the order it appears.' + ].join(' ') + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the text box\'s horizontal position anchor', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the annotation.', + 'For example, if `x` is set to 1, `xref` to *paper* and', + '`xanchor` to *right* then the right-most portion of the', + 'annotation lines up with the right-most edge of the', + 'plotting area.', + 'If *auto*, the anchor is equivalent to *center* for', + 'data-referenced annotations or if there is an arrow,', + 'whereas for paper-referenced with no arrow, the anchor picked', + 'corresponds to the closest side.' + ].join(' ') + }, + yref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.y.toString() + ], + role: 'info', + description: [ + 'Sets the annotation\'s y coordinate axis.', + 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to an y coordinate', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plotting area in normalized coordinates', + 'where 0 (1) corresponds to the bottom (top).' + ].join(' ') + }, + y: { + valType: 'any', + role: 'info', + description: [ + 'Sets the annotation\'s y position.', + 'If the axis `type` is *log*, then you must take the', + 'log of your desired range.', + 'If the axis `type` is *date*, it should be date strings,', + 'like date data, though Date objects and unix milliseconds', + 'will be accepted and converted to strings.', + 'If the axis `type` is *category*, it should be numbers,', + 'using the scale where each category is assigned a serial', + 'number from zero in the order it appears.' + ].join(' ') + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the text box\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the annotation.', + 'For example, if `y` is set to 1, `yref` to *paper* and', + '`yanchor` to *top* then the top-most portion of the', + 'annotation lines up with the top-most edge of the', + 'plotting area.', + 'If *auto*, the anchor is equivalent to *middle* for', + 'data-referenced annotations or if there is an arrow,', + 'whereas for paper-referenced with no arrow, the anchor picked', + 'corresponds to the closest side.' + ].join(' ') + }, + clicktoshow: { + valType: 'enumerated', + values: [false, 'onoff', 'onout'], + dflt: false, + role: 'style', + description: [ + 'Makes this annotation respond to clicks on the plot.', + 'If you click a data point that exactly matches the `x` and `y`', + 'values of this annotation, and it is hidden (visible: false),', + 'it will appear. In *onoff* mode, you must click the same point', + 'again to make it disappear, so if you click multiple points,', + 'you can show multiple annotations. In *onout* mode, a click', + 'anywhere else in the plot (on another data point or not) will', + 'hide this annotation.', + 'If you need to show/hide this annotation in response to different', + '`x` or `y` values, you can set `xclick` and/or `yclick`. This is', + 'useful for example to label the side of a bar. To label markers', + 'though, `standoff` is preferred over `xclick` and `yclick`.' + ].join(' ') + }, + xclick: { + valType: 'any', + role: 'info', + description: [ + 'Toggle this annotation when clicking a data point whose `x` value', + 'is `xclick` rather than the annotation\'s `x` value.' + ].join(' ') + }, + yclick: { + valType: 'any', + role: 'info', + description: [ + 'Toggle this annotation when clicking a data point whose `y` value', + 'is `yclick` rather than the annotation\'s `y` value.' + ].join(' ') + }, - _deprecated: { - ref: { - valType: 'string', - role: 'info', - description: [ - 'Obsolete. Set `xref` and `yref` separately instead.' - ].join(' ') - } + _deprecated: { + ref: { + valType: 'string', + role: 'info', + description: [ + 'Obsolete. Set `xref` and `yref` separately instead.' + ].join(' ') } -}; + } +} diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index f68ea537c63..7687439790e 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -6,88 +6,84 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var draw = require('./draw').draw -var draw = require('./draw').draw; +module.exports = function calcAutorange (gd) { + var fullLayout = gd._fullLayout, + annotationList = Lib.filterVisible(fullLayout.annotations) + if (!annotationList.length || !gd._fullData.length) return -module.exports = function calcAutorange(gd) { - var fullLayout = gd._fullLayout, - annotationList = Lib.filterVisible(fullLayout.annotations); + var annotationAxes = {} + annotationList.forEach(function (ann) { + annotationAxes[ann.xref] = true + annotationAxes[ann.yref] = true + }) - if(!annotationList.length || !gd._fullData.length) return; + var autorangedAnnos = Axes.list(gd).filter(function (ax) { + return ax.autorange && annotationAxes[ax._id] + }) + if (!autorangedAnnos.length) return - var annotationAxes = {}; - annotationList.forEach(function(ann) { - annotationAxes[ann.xref] = true; - annotationAxes[ann.yref] = true; - }); - - var autorangedAnnos = Axes.list(gd).filter(function(ax) { - return ax.autorange && annotationAxes[ax._id]; - }); - if(!autorangedAnnos.length) return; - - return Lib.syncOrAsync([ - draw, - annAutorange - ], gd); -}; + return Lib.syncOrAsync([ + draw, + annAutorange + ], gd) +} -function annAutorange(gd) { - var fullLayout = gd._fullLayout; +function annAutorange (gd) { + var fullLayout = gd._fullLayout // find the bounding boxes for each of these annotations' // relative to their anchor points // use the arrow and the text bg rectangle, // as the whole anno may include hidden text in its bbox - Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { - var xa = Axes.getFromId(gd, ann.xref), - ya = Axes.getFromId(gd, ann.yref), - headSize = 3 * ann.arrowsize * ann.arrowwidth || 0; + Lib.filterVisible(fullLayout.annotations).forEach(function (ann) { + var xa = Axes.getFromId(gd, ann.xref), + ya = Axes.getFromId(gd, ann.yref), + headSize = 3 * ann.arrowsize * ann.arrowwidth || 0 - if(xa && xa.autorange) { - if(ann.axref === ann.xref) { + if (xa && xa.autorange) { + if (ann.axref === ann.xref) { // expand for the arrowhead (padded by arrowhead) - Axes.expand(xa, [xa.r2c(ann.x)], { - ppadplus: headSize, - ppadminus: headSize - }); + Axes.expand(xa, [xa.r2c(ann.x)], { + ppadplus: headSize, + ppadminus: headSize + }) // again for the textbox (padded by textbox) - Axes.expand(xa, [xa.r2c(ann.ax)], { - ppadplus: ann._xpadplus, - ppadminus: ann._xpadminus - }); - } - else { - Axes.expand(xa, [xa.r2c(ann.x)], { - ppadplus: Math.max(ann._xpadplus, headSize), - ppadminus: Math.max(ann._xpadminus, headSize) - }); - } - } + Axes.expand(xa, [xa.r2c(ann.ax)], { + ppadplus: ann._xpadplus, + ppadminus: ann._xpadminus + }) + } else { + Axes.expand(xa, [xa.r2c(ann.x)], { + ppadplus: Math.max(ann._xpadplus, headSize), + ppadminus: Math.max(ann._xpadminus, headSize) + }) + } + } - if(ya && ya.autorange) { - if(ann.ayref === ann.yref) { - Axes.expand(ya, [ya.r2c(ann.y)], { - ppadplus: headSize, - ppadminus: headSize - }); - Axes.expand(ya, [ya.r2c(ann.ay)], { - ppadplus: ann._ypadplus, - ppadminus: ann._ypadminus - }); - } - else { - Axes.expand(ya, [ya.r2c(ann.y)], { - ppadplus: Math.max(ann._ypadplus, headSize), - ppadminus: Math.max(ann._ypadminus, headSize) - }); - } - } - }); + if (ya && ya.autorange) { + if (ann.ayref === ann.yref) { + Axes.expand(ya, [ya.r2c(ann.y)], { + ppadplus: headSize, + ppadminus: headSize + }) + Axes.expand(ya, [ya.r2c(ann.ay)], { + ppadplus: ann._ypadplus, + ppadminus: ann._ypadminus + }) + } else { + Axes.expand(ya, [ya.r2c(ann.y)], { + ppadplus: Math.max(ann._ypadplus, headSize), + ppadminus: Math.max(ann._ypadminus, headSize) + }) + } + } + }) } diff --git a/src/components/annotations/click.js b/src/components/annotations/click.js index 8fe77ce8286..88aa961ddd0 100644 --- a/src/components/annotations/click.js +++ b/src/components/annotations/click.js @@ -6,16 +6,14 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Plotly = require('../../plotly'); - +var Plotly = require('../../plotly') module.exports = { - hasClickToShow: hasClickToShow, - onClick: onClick -}; + hasClickToShow: hasClickToShow, + onClick: onClick +} /* * hasClickToShow: does the given hoverData have ANY annotations which will @@ -27,9 +25,9 @@ module.exports = { * * returns: boolean */ -function hasClickToShow(gd, hoverData) { - var sets = getToggleSets(gd, hoverData); - return sets.on.length > 0 || sets.explicitOff.length > 0; +function hasClickToShow (gd, hoverData) { + var sets = getToggleSets(gd, hoverData) + return sets.on.length > 0 || sets.explicitOff.length > 0 } /* @@ -42,24 +40,24 @@ function hasClickToShow(gd, hoverData) { * * returns: Promise that the update is complete */ -function onClick(gd, hoverData) { - var toggleSets = getToggleSets(gd, hoverData), - onSet = toggleSets.on, - offSet = toggleSets.off.concat(toggleSets.explicitOff), - update = {}, - i; +function onClick (gd, hoverData) { + var toggleSets = getToggleSets(gd, hoverData), + onSet = toggleSets.on, + offSet = toggleSets.off.concat(toggleSets.explicitOff), + update = {}, + i - if(!(onSet.length || offSet.length)) return; + if (!(onSet.length || offSet.length)) return - for(i = 0; i < onSet.length; i++) { - update['annotations[' + onSet[i] + '].visible'] = true; - } + for (i = 0; i < onSet.length; i++) { + update['annotations[' + onSet[i] + '].visible'] = true + } - for(i = 0; i < offSet.length; i++) { - update['annotations[' + offSet[i] + '].visible'] = false; - } + for (i = 0; i < offSet.length; i++) { + update['annotations[' + offSet[i] + '].visible'] = false + } - return Plotly.update(gd, {}, update); + return Plotly.update(gd, {}, update) } /* @@ -76,46 +74,45 @@ function onClick(gd, hoverData) { * explicitOff: Array (indices to turn off because you *are* hovering on them) * } */ -function getToggleSets(gd, hoverData) { - var annotations = gd._fullLayout.annotations, - onSet = [], - offSet = [], - explicitOffSet = [], - hoverLen = (hoverData || []).length; - - var i, j, anni, showMode, pointj, toggleType; - - for(i = 0; i < annotations.length; i++) { - anni = annotations[i]; - showMode = anni.clicktoshow; - if(showMode) { - for(j = 0; j < hoverLen; j++) { - pointj = hoverData[j]; - if(pointj.x === anni._xclick && pointj.y === anni._yclick && +function getToggleSets (gd, hoverData) { + var annotations = gd._fullLayout.annotations, + onSet = [], + offSet = [], + explicitOffSet = [], + hoverLen = (hoverData || []).length + + var i, j, anni, showMode, pointj, toggleType + + for (i = 0; i < annotations.length; i++) { + anni = annotations[i] + showMode = anni.clicktoshow + if (showMode) { + for (j = 0; j < hoverLen; j++) { + pointj = hoverData[j] + if (pointj.x === anni._xclick && pointj.y === anni._yclick && pointj.xaxis._id === anni.xref && pointj.yaxis._id === anni.yref) { // match! toggle this annotation // regardless of its clicktoshow mode // but if it's onout mode, off is implicit - if(anni.visible) { - if(showMode === 'onout') toggleType = offSet; - else toggleType = explicitOffSet; - } - else { - toggleType = onSet; - } - toggleType.push(i); - break; - } - } + if (anni.visible) { + if (showMode === 'onout') toggleType = offSet + else toggleType = explicitOffSet + } else { + toggleType = onSet + } + toggleType.push(i) + break + } + } - if(j === hoverLen) { + if (j === hoverLen) { // no match - only turn this annotation OFF, and only if // showmode is 'onout' - if(anni.visible && showMode === 'onout') offSet.push(i); - } - } + if (anni.visible && showMode === 'onout') offSet.push(i) + } } + } - return {on: onSet, off: offSet, explicitOff: explicitOffSet}; + return {on: onSet, off: offSet, explicitOff: explicitOffSet} } diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index a4e9b9b45df..47b9d1473e5 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -6,18 +6,16 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var handleArrayContainerDefaults = require('../../plots/array_container_defaults') +var handleAnnotationDefaults = require('./annotation_defaults') -var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); -var handleAnnotationDefaults = require('./annotation_defaults'); +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut) { + var opts = { + name: 'annotations', + handleItemDefaults: handleAnnotationDefaults + } - -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var opts = { - name: 'annotations', - handleItemDefaults: handleAnnotationDefaults - }; - - handleArrayContainerDefaults(layoutIn, layoutOut, opts); -}; + handleArrayContainerDefaults(layoutIn, layoutOut, opts) +} diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index f6cb37b8515..137fc017f57 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); - -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); -var Color = require('../color'); -var Drawing = require('../drawing'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var setCursor = require('../../lib/setcursor'); -var dragElement = require('../dragelement'); - -var handleAnnotationDefaults = require('./annotation_defaults'); -var supplyLayoutDefaults = require('./defaults'); -var drawArrowHead = require('./draw_arrow_head'); +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') +var Color = require('../color') +var Drawing = require('../drawing') +var svgTextUtils = require('../../lib/svg_text_utils') +var setCursor = require('../../lib/setcursor') +var dragElement = require('../dragelement') +var handleAnnotationDefaults = require('./annotation_defaults') +var supplyLayoutDefaults = require('./defaults') +var drawArrowHead = require('./draw_arrow_head') // Annotations are stored in gd.layout.annotations, an array of objects // index can point to one item in this array, @@ -37,452 +35,434 @@ var drawArrowHead = require('./draw_arrow_head'); // annotation at that point in the array, or 'remove' to delete this one module.exports = { - draw: draw, - drawOne: drawOne -}; + draw: draw, + drawOne: drawOne +} -function draw(gd) { - var fullLayout = gd._fullLayout; +function draw (gd) { + var fullLayout = gd._fullLayout - fullLayout._infolayer.selectAll('.annotation').remove(); + fullLayout._infolayer.selectAll('.annotation').remove() - for(var i = 0; i < fullLayout.annotations.length; i++) { - if(fullLayout.annotations[i].visible) { - drawOne(gd, i); - } + for (var i = 0; i < fullLayout.annotations.length; i++) { + if (fullLayout.annotations[i].visible) { + drawOne(gd, i) } + } - return Plots.previousPromises(gd); + return Plots.previousPromises(gd) } -function drawOne(gd, index, opt, value) { - var layout = gd.layout, - fullLayout = gd._fullLayout, - i; - - if(!isNumeric(index) || index === -1) { +function drawOne (gd, index, opt, value) { + var layout = gd.layout, + fullLayout = gd._fullLayout, + i + if (!isNumeric(index) || index === -1) { // no index provided - we're operating on ALL annotations - if(!index && Array.isArray(value)) { + if (!index && Array.isArray(value)) { // a whole annotation array is passed in // (as in, redo of delete all) - layout.annotations = value; - supplyLayoutDefaults(layout, fullLayout); - draw(gd); - return; - } - else if(value === 'remove') { + layout.annotations = value + supplyLayoutDefaults(layout, fullLayout) + draw(gd) + return + } else if (value === 'remove') { // delete all - delete layout.annotations; - fullLayout.annotations = []; - draw(gd); - return; - } - else if(opt && value !== 'add') { + delete layout.annotations + fullLayout.annotations = [] + draw(gd) + return + } else if (opt && value !== 'add') { // make the same change to all annotations - for(i = 0; i < fullLayout.annotations.length; i++) { - drawOne(gd, i, opt, value); - } - return; - } - else { + for (i = 0; i < fullLayout.annotations.length; i++) { + drawOne(gd, i, opt, value) + } + return + } else { // add a new empty annotation - index = fullLayout.annotations.length; - fullLayout.annotations.push({}); - } + index = fullLayout.annotations.length + fullLayout.annotations.push({}) } - - if(!opt && value) { - if(value === 'remove') { - fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]') - .remove(); - fullLayout.annotations.splice(index, 1); - layout.annotations.splice(index, 1); - for(i = index; i < fullLayout.annotations.length; i++) { - fullLayout._infolayer + } + + if (!opt && value) { + if (value === 'remove') { + fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]') + .remove() + fullLayout.annotations.splice(index, 1) + layout.annotations.splice(index, 1) + for (i = index; i < fullLayout.annotations.length; i++) { + fullLayout._infolayer .selectAll('.annotation[data-index="' + (i + 1) + '"]') - .attr('data-index', String(i)); + .attr('data-index', String(i)) // redraw all annotations past the removed one, // so they bind to the right events - drawOne(gd, i); - } - return; - } - else if(value === 'add' || Lib.isPlainObject(value)) { - fullLayout.annotations.splice(index, 0, {}); + drawOne(gd, i) + } + return + } else if (value === 'add' || Lib.isPlainObject(value)) { + fullLayout.annotations.splice(index, 0, {}) - var rule = Lib.isPlainObject(value) ? + var rule = Lib.isPlainObject(value) ? Lib.extendFlat({}, value) : - {text: 'New text'}; + {text: 'New text'} - if(layout.annotations) { - layout.annotations.splice(index, 0, rule); - } else { - layout.annotations = [rule]; - } + if (layout.annotations) { + layout.annotations.splice(index, 0, rule) + } else { + layout.annotations = [rule] + } - for(i = fullLayout.annotations.length - 1; i > index; i--) { - fullLayout._infolayer + for (i = fullLayout.annotations.length - 1; i > index; i--) { + fullLayout._infolayer .selectAll('.annotation[data-index="' + (i - 1) + '"]') - .attr('data-index', String(i)); - drawOne(gd, i); - } - } + .attr('data-index', String(i)) + drawOne(gd, i) + } } + } // remove the existing annotation if there is one - fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]').remove(); + fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]').remove() // remember a few things about what was already there, - var optionsIn = layout.annotations[index], - oldPrivate = fullLayout.annotations[index]; + var optionsIn = layout.annotations[index], + oldPrivate = fullLayout.annotations[index] // not sure how we're getting here... but C12 is seeing a bug // where we fail here when they add/remove annotations - if(!optionsIn) return; + if (!optionsIn) return // alter the input annotation as requested - var optionsEdit = {}; - if(typeof opt === 'string' && opt) optionsEdit[opt] = value; - else if(Lib.isPlainObject(opt)) optionsEdit = opt; - - var optionKeys = Object.keys(optionsEdit); - for(i = 0; i < optionKeys.length; i++) { - var k = optionKeys[i]; - Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]); - } + var optionsEdit = {} + if (typeof opt === 'string' && opt) optionsEdit[opt] = value + else if (Lib.isPlainObject(opt)) optionsEdit = opt + + var optionKeys = Object.keys(optionsEdit) + for (i = 0; i < optionKeys.length; i++) { + var k = optionKeys[i] + Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]) + } // return early in visible: false updates - if(optionsIn.visible === false) return; + if (optionsIn.visible === false) return - var gs = fullLayout._size; - var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref}; + var gs = fullLayout._size + var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref} - var axLetters = ['x', 'y']; - for(i = 0; i < 2; i++) { - var axLetter = axLetters[i]; + var axLetters = ['x', 'y'] + for (i = 0; i < 2; i++) { + var axLetter = axLetters[i] // if we don't have an explicit position already, // don't set one just because we're changing references // or axis type. // the defaults will be consistent most of the time anyway, // except in log/linear changes - if(optionsEdit[axLetter] !== undefined || + if (optionsEdit[axLetter] !== undefined || optionsIn[axLetter] === undefined) { - continue; - } - - var axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')), - axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')), - position = optionsIn[axLetter], - axTypeOld = oldPrivate['_' + axLetter + 'type']; + continue + } - if(optionsEdit[axLetter + 'ref'] !== undefined) { + var axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')), + axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')), + position = optionsIn[axLetter], + axTypeOld = oldPrivate['_' + axLetter + 'type'] + if (optionsEdit[axLetter + 'ref'] !== undefined) { // TODO: include ax / ay / axref / ayref here if not 'pixel' // or even better, move all of this machinery out of here and into // streambed as extra attributes to a regular relayout call // we should do this after v2.0 when it can work equivalently for // annotations, shapes, and images. - var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto', - plotSize = (axLetter === 'x' ? gs.w : gs.h), - halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) / - (2 * plotSize); - if(axOld && axNew) { // data -> different data + var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto', + plotSize = (axLetter === 'x' ? gs.w : gs.h), + halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) / + (2 * plotSize) + if (axOld && axNew) { // data -> different data // go to the same fraction of the axis length // whether or not these axes share a domain - position = axNew.fraction2r(axOld.r2fraction(position)); - } - else if(axOld) { // data -> paper + position = axNew.fraction2r(axOld.r2fraction(position)) + } else if (axOld) { // data -> paper // first convert to fraction of the axis - position = axOld.r2fraction(position); + position = axOld.r2fraction(position) // next scale the axis to the whole plot - position = axOld.domain[0] + - position * (axOld.domain[1] - axOld.domain[0]); + position = axOld.domain[0] + + position * (axOld.domain[1] - axOld.domain[0]) // finally see if we need to adjust auto alignment // because auto always means middle / center alignment for data, // but it changes for page alignment based on the closest side - if(autoAnchor) { - var posPlus = position + halfSizeFrac, - posMinus = position - halfSizeFrac; - if(position + posMinus < 2 / 3) position = posMinus; - else if(position + posPlus > 4 / 3) position = posPlus; - } - } - else if(axNew) { // paper -> data + if (autoAnchor) { + var posPlus = position + halfSizeFrac, + posMinus = position - halfSizeFrac + if (position + posMinus < 2 / 3) position = posMinus + else if (position + posPlus > 4 / 3) position = posPlus + } + } else if (axNew) { // paper -> data // first see if we need to adjust auto alignment - if(autoAnchor) { - if(position < 1 / 3) position += halfSizeFrac; - else if(position > 2 / 3) position -= halfSizeFrac; - } + if (autoAnchor) { + if (position < 1 / 3) position += halfSizeFrac + else if (position > 2 / 3) position -= halfSizeFrac + } // next convert to fraction of the axis - position = (position - axNew.domain[0]) / - (axNew.domain[1] - axNew.domain[0]); + position = (position - axNew.domain[0]) / + (axNew.domain[1] - axNew.domain[0]) // finally convert to data coordinates - position = axNew.fraction2r(position); - } - } - - if(axNew && axNew === axOld && axTypeOld) { - if(axTypeOld === 'log' && axNew.type !== 'log') { - position = Math.pow(10, position); - } - else if(axTypeOld !== 'log' && axNew.type === 'log') { - position = (position > 0) ? - Math.log(position) / Math.LN10 : undefined; - } - } + position = axNew.fraction2r(position) + } + } - optionsIn[axLetter] = position; + if (axNew && axNew === axOld && axTypeOld) { + if (axTypeOld === 'log' && axNew.type !== 'log') { + position = Math.pow(10, position) + } else if (axTypeOld !== 'log' && axNew.type === 'log') { + position = (position > 0) ? + Math.log(position) / Math.LN10 : undefined + } } - var options = {}; - handleAnnotationDefaults(optionsIn, options, fullLayout); - fullLayout.annotations[index] = options; + optionsIn[axLetter] = position + } - var xa = Axes.getFromId(gd, options.xref), - ya = Axes.getFromId(gd, options.yref), + var options = {} + handleAnnotationDefaults(optionsIn, options, fullLayout) + fullLayout.annotations[index] = options + + var xa = Axes.getFromId(gd, options.xref), + ya = Axes.getFromId(gd, options.yref), // calculated pixel positions // x & y each will get text, head, and tail as appropriate - annPosPx = {x: {}, y: {}}, - textangle = +options.textangle || 0; + annPosPx = {x: {}, y: {}}, + textangle = +options.textangle || 0 // create the components // made a single group to contain all, so opacity can work right // with border/arrow together this could handle a whole bunch of // cleanup at this point, but works for now - var annGroup = fullLayout._infolayer.append('g') + var annGroup = fullLayout._infolayer.append('g') .classed('annotation', true) .attr('data-index', String(index)) .style('opacity', options.opacity) - .on('click', function() { - gd._dragging = false; - gd.emit('plotly_clickannotation', { - index: index, - annotation: optionsIn, - fullAnnotation: options - }); - }); + .on('click', function () { + gd._dragging = false + gd.emit('plotly_clickannotation', { + index: index, + annotation: optionsIn, + fullAnnotation: options + }) + }) // another group for text+background so that they can rotate together - var annTextGroup = annGroup.append('g') + var annTextGroup = annGroup.append('g') .classed('annotation-text-g', true) - .attr('data-index', String(index)); + .attr('data-index', String(index)) - var annTextGroupInner = annTextGroup.append('g'); + var annTextGroupInner = annTextGroup.append('g') - var borderwidth = options.borderwidth, - borderpad = options.borderpad, - borderfull = borderwidth + borderpad; + var borderwidth = options.borderwidth, + borderpad = options.borderpad, + borderfull = borderwidth + borderpad - var annTextBG = annTextGroupInner.append('rect') + var annTextBG = annTextGroupInner.append('rect') .attr('class', 'bg') .style('stroke-width', borderwidth + 'px') .call(Color.stroke, options.bordercolor) - .call(Color.fill, options.bgcolor); + .call(Color.fill, options.bgcolor) - var font = options.font; + var font = options.font - var annText = annTextGroupInner.append('text') + var annText = annTextGroupInner.append('text') .classed('annotation', true) .attr('data-unformatted', options.text) - .text(options.text); + .text(options.text) - function textLayout(s) { - s.call(Drawing.font, font) + function textLayout (s) { + s.call(Drawing.font, font) .attr({ - 'text-anchor': { - left: 'start', - right: 'end' - }[options.align] || 'middle' - }); - - svgTextUtils.convertToTspans(s, drawGraphicalElements); - return s; - } + 'text-anchor': { + left: 'start', + right: 'end' + }[options.align] || 'middle' + }) - function drawGraphicalElements() { + svgTextUtils.convertToTspans(s, drawGraphicalElements) + return s + } + function drawGraphicalElements () { // make sure lines are aligned the way they will be // at the end, even if their position changes - annText.selectAll('tspan.line').attr({y: 0, x: 0}); + annText.selectAll('tspan.line').attr({y: 0, x: 0}) - var mathjaxGroup = annTextGroupInner.select('.annotation-math-group'), - hasMathjax = !mathjaxGroup.empty(), - anntextBB = Drawing.bBox( + var mathjaxGroup = annTextGroupInner.select('.annotation-math-group'), + hasMathjax = !mathjaxGroup.empty(), + anntextBB = Drawing.bBox( (hasMathjax ? mathjaxGroup : annText).node()), - annwidth = anntextBB.width, - annheight = anntextBB.height, - outerwidth = Math.round(annwidth + 2 * borderfull), - outerheight = Math.round(annheight + 2 * borderfull); - + annwidth = anntextBB.width, + annheight = anntextBB.height, + outerwidth = Math.round(annwidth + 2 * borderfull), + outerheight = Math.round(annheight + 2 * borderfull) // save size in the annotation object for use by autoscale - options._w = annwidth; - options._h = annheight; - - function shiftFraction(v, anchor) { - if(anchor === 'auto') { - if(v < 1 / 3) anchor = 'left'; - else if(v > 2 / 3) anchor = 'right'; - else anchor = 'center'; - } - return { - center: 0, - middle: 0, - left: 0.5, - bottom: -0.5, - right: -0.5, - top: 0.5 - }[anchor]; - } + options._w = annwidth + options._h = annheight + + function shiftFraction (v, anchor) { + if (anchor === 'auto') { + if (v < 1 / 3) anchor = 'left' + else if (v > 2 / 3) anchor = 'right' + else anchor = 'center' + } + return { + center: 0, + middle: 0, + left: 0.5, + bottom: -0.5, + right: -0.5, + top: 0.5 + }[anchor] + } - var annotationIsOffscreen = false; - ['x', 'y'].forEach(function(axLetter) { - var axRef = options[axLetter + 'ref'] || axLetter, - tailRef = options['a' + axLetter + 'ref'], - ax = Axes.getFromId(gd, axRef), - dimAngle = (textangle + (axLetter === 'x' ? 0 : -90)) * Math.PI / 180, + var annotationIsOffscreen = false; + ['x', 'y'].forEach(function (axLetter) { + var axRef = options[axLetter + 'ref'] || axLetter, + tailRef = options['a' + axLetter + 'ref'], + ax = Axes.getFromId(gd, axRef), + dimAngle = (textangle + (axLetter === 'x' ? 0 : -90)) * Math.PI / 180, // note that these two can be either positive or negative - annSizeFromWidth = outerwidth * Math.cos(dimAngle), - annSizeFromHeight = outerheight * Math.sin(dimAngle), + annSizeFromWidth = outerwidth * Math.cos(dimAngle), + annSizeFromHeight = outerheight * Math.sin(dimAngle), // but this one is the positive total size - annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight), - anchor = options[axLetter + 'anchor'], - posPx = annPosPx[axLetter], - basePx, - textPadShift, - alignPosition, - autoAlignFraction, - textShift; + annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight), + anchor = options[axLetter + 'anchor'], + posPx = annPosPx[axLetter], + basePx, + textPadShift, + alignPosition, + autoAlignFraction, + textShift /* * calculate the *primary* pixel position * which is the arrowhead if there is one, * otherwise the text anchor point */ - if(ax) { + if (ax) { /* * hide the annotation if it's pointing outside the visible plot * as long as the axis isn't autoranged - then we need to draw it * anyway to get its bounding box. When we're dragging, an axis can * still look autoranged even though it won't be when the drag finishes. */ - var posFraction = ax.r2fraction(options[axLetter]); - if((gd._dragging || !ax.autorange) && (posFraction < 0 || posFraction > 1)) { - if(tailRef === axRef) { - posFraction = ax.r2fraction(options['a' + axLetter]); - if(posFraction < 0 || posFraction > 1) { - annotationIsOffscreen = true; - } - } - else { - annotationIsOffscreen = true; - } - - if(annotationIsOffscreen) return; - } - basePx = ax._offset + ax.r2p(options[axLetter]); - autoAlignFraction = 0.5; - } - else { - if(axLetter === 'x') { - alignPosition = options[axLetter]; - basePx = gs.l + gs.w * alignPosition; - } - else { - alignPosition = 1 - options[axLetter]; - basePx = gs.t + gs.h * alignPosition; - } - autoAlignFraction = options.showarrow ? 0.5 : alignPosition; + var posFraction = ax.r2fraction(options[axLetter]) + if ((gd._dragging || !ax.autorange) && (posFraction < 0 || posFraction > 1)) { + if (tailRef === axRef) { + posFraction = ax.r2fraction(options['a' + axLetter]) + if (posFraction < 0 || posFraction > 1) { + annotationIsOffscreen = true } + } else { + annotationIsOffscreen = true + } + + if (annotationIsOffscreen) return + } + basePx = ax._offset + ax.r2p(options[axLetter]) + autoAlignFraction = 0.5 + } else { + if (axLetter === 'x') { + alignPosition = options[axLetter] + basePx = gs.l + gs.w * alignPosition + } else { + alignPosition = 1 - options[axLetter] + basePx = gs.t + gs.h * alignPosition + } + autoAlignFraction = options.showarrow ? 0.5 : alignPosition + } // now translate this into pixel positions of head, tail, and text // as well as paddings for autorange - if(options.showarrow) { - posPx.head = basePx; + if (options.showarrow) { + posPx.head = basePx - var arrowLength = options['a' + axLetter]; + var arrowLength = options['a' + axLetter] // with an arrow, the text rotates around the anchor point - textShift = annSizeFromWidth * shiftFraction(0.5, options.xanchor) - - annSizeFromHeight * shiftFraction(0.5, options.yanchor); + textShift = annSizeFromWidth * shiftFraction(0.5, options.xanchor) - + annSizeFromHeight * shiftFraction(0.5, options.yanchor) - if(tailRef === axRef) { - posPx.tail = ax._offset + ax.r2p(arrowLength); + if (tailRef === axRef) { + posPx.tail = ax._offset + ax.r2p(arrowLength) // tail is data-referenced: autorange pads the text in px from the tail - textPadShift = textShift; - } - else { - posPx.tail = basePx + arrowLength; + textPadShift = textShift + } else { + posPx.tail = basePx + arrowLength // tail is specified in px from head, so autorange also pads vs head - textPadShift = textShift + arrowLength; - } + textPadShift = textShift + arrowLength + } - posPx.text = posPx.tail + textShift; + posPx.text = posPx.tail + textShift // constrain pixel/paper referenced so the draggers are at least // partially visible - var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height']; - if(axRef === 'paper') { - posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1); - } - if(tailRef === 'pixel') { - var shiftPlus = -Math.max(posPx.tail - 3, posPx.text), - shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx; - if(shiftPlus > 0) { - posPx.tail += shiftPlus; - posPx.text += shiftPlus; - } - else if(shiftMinus > 0) { - posPx.tail -= shiftMinus; - posPx.text -= shiftMinus; - } - } - } - else { + var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height'] + if (axRef === 'paper') { + posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1) + } + if (tailRef === 'pixel') { + var shiftPlus = -Math.max(posPx.tail - 3, posPx.text), + shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx + if (shiftPlus > 0) { + posPx.tail += shiftPlus + posPx.text += shiftPlus + } else if (shiftMinus > 0) { + posPx.tail -= shiftMinus + posPx.text -= shiftMinus + } + } + } else { // with no arrow, the text rotates and *then* we put the anchor // relative to the new bounding box - textShift = annSize * shiftFraction(autoAlignFraction, anchor); - textPadShift = textShift; - posPx.text = basePx + textShift; - } + textShift = annSize * shiftFraction(autoAlignFraction, anchor) + textPadShift = textShift + posPx.text = basePx + textShift + } - options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift; - options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift; + options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift + options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift // save the current axis type for later log/linear changes - options['_' + axLetter + 'type'] = ax && ax.type; - }); + options['_' + axLetter + 'type'] = ax && ax.type + }) - if(annotationIsOffscreen) { - annTextGroupInner.remove(); - return; - } + if (annotationIsOffscreen) { + annTextGroupInner.remove() + return + } - if(hasMathjax) { - mathjaxGroup.select('svg').attr({x: borderfull - 1, y: borderfull}); - } - else { - var texty = borderfull - anntextBB.top, - textx = borderfull - anntextBB.left; - annText.attr({x: textx, y: texty}); - annText.selectAll('tspan.line').attr({y: texty, x: textx}); - } + if (hasMathjax) { + mathjaxGroup.select('svg').attr({x: borderfull - 1, y: borderfull}) + } else { + var texty = borderfull - anntextBB.top, + textx = borderfull - anntextBB.left + annText.attr({x: textx, y: texty}) + annText.selectAll('tspan.line').attr({y: texty, x: textx}) + } - annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2, - outerwidth - borderwidth, outerheight - borderwidth); + annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2, + outerwidth - borderwidth, outerheight - borderwidth) - annTextGroupInner.call(Drawing.setTranslate, + annTextGroupInner.call(Drawing.setTranslate, Math.round(annPosPx.x.text - outerwidth / 2), - Math.round(annPosPx.y.text - outerheight / 2)); + Math.round(annPosPx.y.text - outerheight / 2)) /* * rotate text and background @@ -490,10 +470,10 @@ function drawOne(gd, index, opt, value) { * because we needed that for autoranging anyway, so now whether * we have an arrow or not, we rotate about the text center. */ - annTextGroup.attr({transform: 'rotate(' + textangle + ',' + - annPosPx.x.text + ',' + annPosPx.y.text + ')'}); + annTextGroup.attr({transform: 'rotate(' + textangle + ',' + + annPosPx.x.text + ',' + annPosPx.y.text + ')'}) - var annbase = 'annotations[' + index + ']'; + var annbase = 'annotations[' + index + ']' /* * add the arrow @@ -501,275 +481,273 @@ function drawOne(gd, index, opt, value) { * dx and dy are normally zero, but when you are dragging the textbox * while the head stays put, dx and dy are the pixel offsets */ - var drawArrow = function(dx, dy) { - d3.select(gd) + var drawArrow = function (dx, dy) { + d3.select(gd) .selectAll('.annotation-arrow-g[data-index="' + index + '"]') - .remove(); + .remove() - var headX = annPosPx.x.head, - headY = annPosPx.y.head, - tailX = annPosPx.x.tail + dx, - tailY = annPosPx.y.tail + dy, - textX = annPosPx.x.text + dx, - textY = annPosPx.y.text + dy, + var headX = annPosPx.x.head, + headY = annPosPx.y.head, + tailX = annPosPx.x.tail + dx, + tailY = annPosPx.y.tail + dy, + textX = annPosPx.x.text + dx, + textY = annPosPx.y.text + dy, // find the edge of the text box, where we'll start the arrow: // create transform matrix to rotate the text box corners - transform = Lib.rotationXYMatrix(textangle, textX, textY), - applyTransform = Lib.apply2DTransform(transform), - applyTransform2 = Lib.apply2DTransform2(transform), + transform = Lib.rotationXYMatrix(textangle, textX, textY), + applyTransform = Lib.apply2DTransform(transform), + applyTransform2 = Lib.apply2DTransform2(transform), // calculate and transform bounding box - width = +annTextBG.attr('width'), - height = +annTextBG.attr('height'), - xLeft = textX - 0.5 * width, - xRight = xLeft + width, - yTop = textY - 0.5 * height, - yBottom = yTop + height, - edges = [ + width = +annTextBG.attr('width'), + height = +annTextBG.attr('height'), + xLeft = textX - 0.5 * width, + xRight = xLeft + width, + yTop = textY - 0.5 * height, + yBottom = yTop + height, + edges = [ [xLeft, yTop, xLeft, yBottom], [xLeft, yBottom, xRight, yBottom], [xRight, yBottom, xRight, yTop], [xRight, yTop, xLeft, yTop] - ].map(applyTransform2); + ].map(applyTransform2) // Remove the line if it ends inside the box. Use ray // casting for rotated boxes: see which edges intersect a // line from the arrowhead to far away and reduce with xor // to get the parity of the number of intersections. - if(edges.reduce(function(a, x) { - return a ^ + if (edges.reduce(function (a, x) { + return a ^ !!lineIntersect(headX, headY, headX + 1e6, headY + 1e6, - x[0], x[1], x[2], x[3]); - }, false)) { + x[0], x[1], x[2], x[3]) + }, false)) { // no line or arrow - so quit drawArrow now - return; - } - - edges.forEach(function(x) { - var p = lineIntersect(tailX, tailY, headX, headY, - x[0], x[1], x[2], x[3]); - if(p) { - tailX = p.x; - tailY = p.y; - } - }); + return + } + + edges.forEach(function (x) { + var p = lineIntersect(tailX, tailY, headX, headY, + x[0], x[1], x[2], x[3]) + if (p) { + tailX = p.x + tailY = p.y + } + }) - var strokewidth = options.arrowwidth, - arrowColor = options.arrowcolor; + var strokewidth = options.arrowwidth, + arrowColor = options.arrowcolor - var arrowGroup = annGroup.append('g') + var arrowGroup = annGroup.append('g') .style({opacity: Color.opacity(arrowColor)}) .classed('annotation-arrow-g', true) - .attr('data-index', String(index)); + .attr('data-index', String(index)) - var arrow = arrowGroup.append('path') + var arrow = arrowGroup.append('path') .attr('d', 'M' + tailX + ',' + tailY + 'L' + headX + ',' + headY) .style('stroke-width', strokewidth + 'px') - .call(Color.stroke, Color.rgb(arrowColor)); + .call(Color.stroke, Color.rgb(arrowColor)) - drawArrowHead(arrow, options.arrowhead, 'end', options.arrowsize, options.standoff); + drawArrowHead(arrow, options.arrowhead, 'end', options.arrowsize, options.standoff) // the arrow dragger is a small square right at the head, then a line to the tail, // all expanded by a stroke width of 6px plus the arrow line width - if(gd._context.editable && arrow.node().parentNode) { - var arrowDragHeadX = headX; - var arrowDragHeadY = headY; - if(options.standoff) { - var arrowLength = Math.sqrt(Math.pow(headX - tailX, 2) + Math.pow(headY - tailY, 2)); - arrowDragHeadX += options.standoff * (tailX - headX) / arrowLength; - arrowDragHeadY += options.standoff * (tailY - headY) / arrowLength; - } - var arrowDrag = arrowGroup.append('path') + if (gd._context.editable && arrow.node().parentNode) { + var arrowDragHeadX = headX + var arrowDragHeadY = headY + if (options.standoff) { + var arrowLength = Math.sqrt(Math.pow(headX - tailX, 2) + Math.pow(headY - tailY, 2)) + arrowDragHeadX += options.standoff * (tailX - headX) / arrowLength + arrowDragHeadY += options.standoff * (tailY - headY) / arrowLength + } + var arrowDrag = arrowGroup.append('path') .classed('annotation', true) .classed('anndrag', true) .attr({ - 'data-index': String(index), - d: 'M3,3H-3V-3H3ZM0,0L' + (tailX - arrowDragHeadX) + ',' + (tailY - arrowDragHeadY), - transform: 'translate(' + arrowDragHeadX + ',' + arrowDragHeadY + ')' + 'data-index': String(index), + d: 'M3,3H-3V-3H3ZM0,0L' + (tailX - arrowDragHeadX) + ',' + (tailY - arrowDragHeadY), + transform: 'translate(' + arrowDragHeadX + ',' + arrowDragHeadY + ')' }) .style('stroke-width', (strokewidth + 6) + 'px') .call(Color.stroke, 'rgba(0,0,0,0)') - .call(Color.fill, 'rgba(0,0,0,0)'); + .call(Color.fill, 'rgba(0,0,0,0)') - var update, - annx0, - anny0; + var update, + annx0, + anny0 // dragger for the arrow & head: translates the whole thing // (head/tail/text) all together - dragElement.init({ - element: arrowDrag.node(), - prepFn: function() { - var pos = Drawing.getTranslate(annTextGroupInner); - - annx0 = pos.x; - anny0 = pos.y; - update = {}; - if(xa && xa.autorange) { - update[xa._name + '.autorange'] = true; - } - if(ya && ya.autorange) { - update[ya._name + '.autorange'] = true; - } - }, - moveFn: function(dx, dy) { - var annxy0 = applyTransform(annx0, anny0), - xcenter = annxy0[0] + dx, - ycenter = annxy0[1] + dy; - annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); - - update[annbase + '.x'] = xa ? + dragElement.init({ + element: arrowDrag.node(), + prepFn: function () { + var pos = Drawing.getTranslate(annTextGroupInner) + + annx0 = pos.x + anny0 = pos.y + update = {} + if (xa && xa.autorange) { + update[xa._name + '.autorange'] = true + } + if (ya && ya.autorange) { + update[ya._name + '.autorange'] = true + } + }, + moveFn: function (dx, dy) { + var annxy0 = applyTransform(annx0, anny0), + xcenter = annxy0[0] + dx, + ycenter = annxy0[1] + dy + annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter) + + update[annbase + '.x'] = xa ? xa.p2r(xa.r2p(options.x) + dx) : - ((headX + dx - gs.l) / gs.w); - update[annbase + '.y'] = ya ? + ((headX + dx - gs.l) / gs.w) + update[annbase + '.y'] = ya ? ya.p2r(ya.r2p(options.y) + dy) : - (1 - ((headY + dy - gs.t) / gs.h)); + (1 - ((headY + dy - gs.t) / gs.h)) - if(options.axref === options.xref) { - update[annbase + '.ax'] = xa ? + if (options.axref === options.xref) { + update[annbase + '.ax'] = xa ? xa.p2r(xa.r2p(options.ax) + dx) : - ((headX + dx - gs.l) / gs.w); - } + ((headX + dx - gs.l) / gs.w) + } - if(options.ayref === options.yref) { - update[annbase + '.ay'] = ya ? + if (options.ayref === options.yref) { + update[annbase + '.ay'] = ya ? ya.p2r(ya.r2p(options.ay) + dy) : - (1 - ((headY + dy - gs.t) / gs.h)); - } + (1 - ((headY + dy - gs.t) / gs.h)) + } - arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); - annTextGroup.attr({ - transform: 'rotate(' + textangle + ',' + + arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')') + annTextGroup.attr({ + transform: 'rotate(' + textangle + ',' + xcenter + ',' + ycenter + ')' - }); - }, - doneFn: function(dragged) { - if(dragged) { - Plotly.relayout(gd, update); - var notesBox = document.querySelector('.js-notes-box-panel'); - if(notesBox) notesBox.redraw(notesBox.selectedObj); - } - } - }); + }) + }, + doneFn: function (dragged) { + if (dragged) { + Plotly.relayout(gd, update) + var notesBox = document.querySelector('.js-notes-box-panel') + if (notesBox) notesBox.redraw(notesBox.selectedObj) } - }; + } + }) + } + } - if(options.showarrow) drawArrow(0, 0); + if (options.showarrow) drawArrow(0, 0) // user dragging the annotation (text, not arrow) - if(gd._context.editable) { - var update, - baseTextTransform; + if (gd._context.editable) { + var update, + baseTextTransform // dragger for the textbox: if there's an arrow, just drag the // textbox and tail, leave the head untouched - dragElement.init({ - element: annTextGroupInner.node(), - prepFn: function() { - baseTextTransform = annTextGroup.attr('transform'); - update = {}; - }, - moveFn: function(dx, dy) { - var csr = 'pointer'; - if(options.showarrow) { - if(options.axref === options.xref) { - update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx); - } else { - update[annbase + '.ax'] = options.ax + dx; - } - - if(options.ayref === options.yref) { - update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy); - } else { - update[annbase + '.ay'] = options.ay + dy; - } - - drawArrow(dx, dy); - } - else { - if(xa) update[annbase + '.x'] = options.x + dx / xa._m; - else { - var widthFraction = options._xsize / gs.w, - xLeft = options.x + options._xshift / gs.w - widthFraction / 2; - - update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w, - widthFraction, 0, 1, options.xanchor); - } - - if(ya) update[annbase + '.y'] = options.y + dy / ya._m; - else { - var heightFraction = options._ysize / gs.h, - yBottom = options.y - options._yshift / gs.h - heightFraction / 2; - - update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h, - heightFraction, 0, 1, options.yanchor); - } - if(!xa || !ya) { - csr = dragElement.getCursor( + dragElement.init({ + element: annTextGroupInner.node(), + prepFn: function () { + baseTextTransform = annTextGroup.attr('transform') + update = {} + }, + moveFn: function (dx, dy) { + var csr = 'pointer' + if (options.showarrow) { + if (options.axref === options.xref) { + update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx) + } else { + update[annbase + '.ax'] = options.ax + dx + } + + if (options.ayref === options.yref) { + update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy) + } else { + update[annbase + '.ay'] = options.ay + dy + } + + drawArrow(dx, dy) + } else { + if (xa) update[annbase + '.x'] = options.x + dx / xa._m + else { + var widthFraction = options._xsize / gs.w, + xLeft = options.x + options._xshift / gs.w - widthFraction / 2 + + update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w, + widthFraction, 0, 1, options.xanchor) + } + + if (ya) update[annbase + '.y'] = options.y + dy / ya._m + else { + var heightFraction = options._ysize / gs.h, + yBottom = options.y - options._yshift / gs.h - heightFraction / 2 + + update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h, + heightFraction, 0, 1, options.yanchor) + } + if (!xa || !ya) { + csr = dragElement.getCursor( xa ? 0.5 : update[annbase + '.x'], ya ? 0.5 : update[annbase + '.y'], options.xanchor, options.yanchor - ); - } - } - - annTextGroup.attr({ - transform: 'translate(' + dx + ',' + dy + ')' + baseTextTransform - }); - - setCursor(annTextGroupInner, csr); - }, - doneFn: function(dragged) { - setCursor(annTextGroupInner); - if(dragged) { - Plotly.relayout(gd, update); - var notesBox = document.querySelector('.js-notes-box-panel'); - if(notesBox) notesBox.redraw(notesBox.selectedObj); - } - } - }); + ) + } + } + + annTextGroup.attr({ + transform: 'translate(' + dx + ',' + dy + ')' + baseTextTransform + }) + + setCursor(annTextGroupInner, csr) + }, + doneFn: function (dragged) { + setCursor(annTextGroupInner) + if (dragged) { + Plotly.relayout(gd, update) + var notesBox = document.querySelector('.js-notes-box-panel') + if (notesBox) notesBox.redraw(notesBox.selectedObj) + } } + }) } + } - if(gd._context.editable) { - annText.call(svgTextUtils.makeEditable, annTextGroupInner) + if (gd._context.editable) { + annText.call(svgTextUtils.makeEditable, annTextGroupInner) .call(textLayout) - .on('edit', function(_text) { - options.text = _text; - this.attr({'data-unformatted': options.text}); - this.call(textLayout); - var update = {}; - update['annotations[' + index + '].text'] = options.text; - if(xa && xa.autorange) { - update[xa._name + '.autorange'] = true; - } - if(ya && ya.autorange) { - update[ya._name + '.autorange'] = true; - } - Plotly.relayout(gd, update); - }); - } - else annText.call(textLayout); + .on('edit', function (_text) { + options.text = _text + this.attr({'data-unformatted': options.text}) + this.call(textLayout) + var update = {} + update['annotations[' + index + '].text'] = options.text + if (xa && xa.autorange) { + update[xa._name + '.autorange'] = true + } + if (ya && ya.autorange) { + update[ya._name + '.autorange'] = true + } + Plotly.relayout(gd, update) + }) + } else annText.call(textLayout) } // look for intersection of two line segments // (1->2 and 3->4) - returns array [x,y] if they do, null if not -function lineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) { - var a = x2 - x1, - b = x3 - x1, - c = x4 - x3, - d = y2 - y1, - e = y3 - y1, - f = y4 - y3, - det = a * f - c * d; +function lineIntersect (x1, y1, x2, y2, x3, y3, x4, y4) { + var a = x2 - x1, + b = x3 - x1, + c = x4 - x3, + d = y2 - y1, + e = y3 - y1, + f = y4 - y3, + det = a * f - c * d // parallel lines? intersection is undefined // ignore the case where they are colinear - if(det === 0) return null; - var t = (b * f - c * e) / det, - u = (b * d - a * e) / det; + if (det === 0) return null + var t = (b * f - c * e) / det, + u = (b * d - a * e) / det // segments do not intersect? - if(u < 0 || u > 1 || t < 0 || t > 1) return null; + if (u < 0 || u > 1 || t < 0 || t > 1) return null - return {x: x1 + a * t, y: y1 + d * t}; + return {x: x1 + a * t, y: y1 + d * t} } diff --git a/src/components/annotations/draw_arrow_head.js b/src/components/annotations/draw_arrow_head.js index 69e5181914c..ead46c7b28d 100644 --- a/src/components/annotations/draw_arrow_head.js +++ b/src/components/annotations/draw_arrow_head.js @@ -6,127 +6,124 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Color = require('../color') +var Drawing = require('../drawing') -var Color = require('../color'); -var Drawing = require('../drawing'); - -var ARROWPATHS = require('./arrow_paths'); +var ARROWPATHS = require('./arrow_paths') // add arrowhead(s) to a path or line d3 element el3 // style: 1-6, first 5 are pointers, 6 is circle, 7 is square, 8 is none // ends is 'start', 'end' (default), 'start+end' // mag is magnification vs. default (default 1) -module.exports = function drawArrowHead(el3, style, ends, mag, standoff) { - if(!isNumeric(mag)) mag = 1; - var el = el3.node(), - headStyle = ARROWPATHS[style||0]; - - if(typeof ends !== 'string' || !ends) ends = 'end'; - - var scale = (Drawing.getPx(el3, 'stroke-width') || 1) * mag, - stroke = el3.style('stroke') || Color.defaultLine, - opacity = el3.style('stroke-opacity') || 1, - doStart = ends.indexOf('start') >= 0, - doEnd = ends.indexOf('end') >= 0, - backOff = headStyle.backoff * scale + standoff, - start, - end, - startRot, - endRot; - - if(el.nodeName === 'line') { - start = {x: +el3.attr('x1'), y: +el3.attr('y1')}; - end = {x: +el3.attr('x2'), y: +el3.attr('y2')}; - - var dx = start.x - end.x, - dy = start.y - end.y; - - startRot = Math.atan2(dy, dx); - endRot = startRot + Math.PI; - if(backOff) { - if(backOff * backOff > dx * dx + dy * dy) { - hideLine(); - return; - } - var backOffX = backOff * Math.cos(startRot), - backOffY = backOff * Math.sin(startRot); - - if(doStart) { - start.x -= backOffX; - start.y -= backOffY; - el3.attr({x1: start.x, y1: start.y}); - } - if(doEnd) { - end.x += backOffX; - end.y += backOffY; - el3.attr({x2: end.x, y2: end.y}); - } - } +module.exports = function drawArrowHead (el3, style, ends, mag, standoff) { + if (!isNumeric(mag)) mag = 1 + var el = el3.node(), + headStyle = ARROWPATHS[style || 0] + + if (typeof ends !== 'string' || !ends) ends = 'end' + + var scale = (Drawing.getPx(el3, 'stroke-width') || 1) * mag, + stroke = el3.style('stroke') || Color.defaultLine, + opacity = el3.style('stroke-opacity') || 1, + doStart = ends.indexOf('start') >= 0, + doEnd = ends.indexOf('end') >= 0, + backOff = headStyle.backoff * scale + standoff, + start, + end, + startRot, + endRot + + if (el.nodeName === 'line') { + start = {x: +el3.attr('x1'), y: +el3.attr('y1')} + end = {x: +el3.attr('x2'), y: +el3.attr('y2')} + + var dx = start.x - end.x, + dy = start.y - end.y + + startRot = Math.atan2(dy, dx) + endRot = startRot + Math.PI + if (backOff) { + if (backOff * backOff > dx * dx + dy * dy) { + hideLine() + return + } + var backOffX = backOff * Math.cos(startRot), + backOffY = backOff * Math.sin(startRot) + + if (doStart) { + start.x -= backOffX + start.y -= backOffY + el3.attr({x1: start.x, y1: start.y}) + } + if (doEnd) { + end.x += backOffX + end.y += backOffY + el3.attr({x2: end.x, y2: end.y}) + } } - else if(el.nodeName === 'path') { - var pathlen = el.getTotalLength(), + } else if (el.nodeName === 'path') { + var pathlen = el.getTotalLength(), // using dash to hide the backOff region of the path. // if we ever allow dash for the arrow we'll have to // do better than this hack... maybe just manually // combine the two - dashArray = ''; - - if(pathlen < backOff) { - hideLine(); - return; - } - - if(doStart) { - var start0 = el.getPointAtLength(0), - dstart = el.getPointAtLength(0.1); - startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x); - start = el.getPointAtLength(Math.min(backOff, pathlen)); - if(backOff) dashArray = '0px,' + backOff + 'px,'; - } - - if(doEnd) { - var end0 = el.getPointAtLength(pathlen), - dend = el.getPointAtLength(pathlen - 0.1); - endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x); - end = el.getPointAtLength(Math.max(0, pathlen - backOff)); - - if(backOff) { - var shortening = dashArray ? 2 * backOff : backOff; - dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px'; - } - } - else if(dashArray) dashArray += pathlen + 'px'; - - if(dashArray) el3.style('stroke-dasharray', dashArray); + dashArray = '' + + if (pathlen < backOff) { + hideLine() + return } - function hideLine() { el3.style('stroke-dasharray', '0px,100px'); } + if (doStart) { + var start0 = el.getPointAtLength(0), + dstart = el.getPointAtLength(0.1) + startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x) + start = el.getPointAtLength(Math.min(backOff, pathlen)) + if (backOff) dashArray = '0px,' + backOff + 'px,' + } + + if (doEnd) { + var end0 = el.getPointAtLength(pathlen), + dend = el.getPointAtLength(pathlen - 0.1) + endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x) + end = el.getPointAtLength(Math.max(0, pathlen - backOff)) + + if (backOff) { + var shortening = dashArray ? 2 * backOff : backOff + dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px' + } + } else if (dashArray) dashArray += pathlen + 'px' - function drawhead(p, rot) { - if(!headStyle.path) return; - if(style > 5) rot = 0; // don't rotate square or circle - d3.select(el.parentElement).append('path') + if (dashArray) el3.style('stroke-dasharray', dashArray) + } + + function hideLine () { el3.style('stroke-dasharray', '0px,100px') } + + function drawhead (p, rot) { + if (!headStyle.path) return + if (style > 5) rot = 0 // don't rotate square or circle + d3.select(el.parentElement).append('path') .attr({ - 'class': el3.attr('class'), - d: headStyle.path, - transform: + 'class': el3.attr('class'), + d: headStyle.path, + transform: 'translate(' + p.x + ',' + p.y + ')' + 'rotate(' + (rot * 180 / Math.PI) + ')' + 'scale(' + scale + ')' }) .style({ - fill: stroke, - opacity: opacity, - 'stroke-width': 0 - }); - } + fill: stroke, + opacity: opacity, + 'stroke-width': 0 + }) + } - if(doStart) drawhead(start, startRot); - if(doEnd) drawhead(end, endRot); -}; + if (doStart) drawhead(start, startRot) + if (doEnd) drawhead(end, endRot) +} diff --git a/src/components/annotations/index.js b/src/components/annotations/index.js index bb32b6b69df..363f221588b 100644 --- a/src/components/annotations/index.js +++ b/src/components/annotations/index.js @@ -6,23 +6,22 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var drawModule = require('./draw'); -var clickModule = require('./click'); +var drawModule = require('./draw') +var clickModule = require('./click') module.exports = { - moduleType: 'component', - name: 'annotations', + moduleType: 'component', + name: 'annotations', - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - calcAutorange: require('./calc_autorange'), - draw: drawModule.draw, - drawOne: drawModule.drawOne, + calcAutorange: require('./calc_autorange'), + draw: drawModule.draw, + drawOne: drawModule.drawOne, - hasClickToShow: clickModule.hasClickToShow, - onClick: clickModule.onClick -}; + hasClickToShow: clickModule.hasClickToShow, + onClick: clickModule.onClick +} diff --git a/src/components/calendars/calendars.js b/src/components/calendars/calendars.js index 2f2a1dec9d5..f6aab2e4778 100644 --- a/src/components/calendars/calendars.js +++ b/src/components/calendars/calendars.js @@ -6,26 +6,26 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' // a trimmed down version of: // https://github.com/alexcjohnson/world-calendars/blob/master/dist/index.js -module.exports = require('world-calendars/dist/main'); +module.exports = require('world-calendars/dist/main') -require('world-calendars/dist/plus'); +require('world-calendars/dist/plus') -require('world-calendars/dist/calendars/chinese'); -require('world-calendars/dist/calendars/coptic'); -require('world-calendars/dist/calendars/discworld'); -require('world-calendars/dist/calendars/ethiopian'); -require('world-calendars/dist/calendars/hebrew'); -require('world-calendars/dist/calendars/islamic'); -require('world-calendars/dist/calendars/julian'); -require('world-calendars/dist/calendars/mayan'); -require('world-calendars/dist/calendars/nanakshahi'); -require('world-calendars/dist/calendars/nepali'); -require('world-calendars/dist/calendars/persian'); -require('world-calendars/dist/calendars/taiwan'); -require('world-calendars/dist/calendars/thai'); -require('world-calendars/dist/calendars/ummalqura'); +require('world-calendars/dist/calendars/chinese') +require('world-calendars/dist/calendars/coptic') +require('world-calendars/dist/calendars/discworld') +require('world-calendars/dist/calendars/ethiopian') +require('world-calendars/dist/calendars/hebrew') +require('world-calendars/dist/calendars/islamic') +require('world-calendars/dist/calendars/julian') +require('world-calendars/dist/calendars/mayan') +require('world-calendars/dist/calendars/nanakshahi') +require('world-calendars/dist/calendars/nepali') +require('world-calendars/dist/calendars/persian') +require('world-calendars/dist/calendars/taiwan') +require('world-calendars/dist/calendars/thai') +require('world-calendars/dist/calendars/ummalqura') diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js index eca51c1ac8a..0fbaa96370f 100644 --- a/src/components/calendars/index.js +++ b/src/components/calendars/index.js @@ -6,252 +6,250 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var calendars = require('./calendars'); +var calendars = require('./calendars') -var Lib = require('../../lib'); -var constants = require('../../constants/numerical'); +var Lib = require('../../lib') +var constants = require('../../constants/numerical') -var EPOCHJD = constants.EPOCHJD; -var ONEDAY = constants.ONEDAY; +var EPOCHJD = constants.EPOCHJD +var ONEDAY = constants.ONEDAY var attributes = { - valType: 'enumerated', - values: Object.keys(calendars.calendars), - role: 'info', - dflt: 'gregorian' -}; + valType: 'enumerated', + values: Object.keys(calendars.calendars), + role: 'info', + dflt: 'gregorian' +} -var handleDefaults = function(contIn, contOut, attr, dflt) { - var attrs = {}; - attrs[attr] = attributes; +var handleDefaults = function (contIn, contOut, attr, dflt) { + var attrs = {} + attrs[attr] = attributes - return Lib.coerce(contIn, contOut, attrs, attr, dflt); -}; + return Lib.coerce(contIn, contOut, attrs, attr, dflt) +} -var handleTraceDefaults = function(traceIn, traceOut, coords, layout) { - for(var i = 0; i < coords.length; i++) { - handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar); - } -}; +var handleTraceDefaults = function (traceIn, traceOut, coords, layout) { + for (var i = 0; i < coords.length; i++) { + handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar) + } +} // each calendar needs its own default canonical tick. I would love to use // 2000-01-01 (or even 0000-01-01) for them all but they don't necessarily // all support either of those dates. Instead I'll use the most significant // number they *do* support, biased toward the present day. var CANONICAL_TICK = { - chinese: '2000-01-01', - coptic: '2000-01-01', - discworld: '2000-01-01', - ethiopian: '2000-01-01', - hebrew: '5000-01-01', - islamic: '1000-01-01', - julian: '2000-01-01', - mayan: '5000-01-01', - nanakshahi: '1000-01-01', - nepali: '2000-01-01', - persian: '1000-01-01', - jalali: '1000-01-01', - taiwan: '1000-01-01', - thai: '2000-01-01', - ummalqura: '1400-01-01' -}; + chinese: '2000-01-01', + coptic: '2000-01-01', + discworld: '2000-01-01', + ethiopian: '2000-01-01', + hebrew: '5000-01-01', + islamic: '1000-01-01', + julian: '2000-01-01', + mayan: '5000-01-01', + nanakshahi: '1000-01-01', + nepali: '2000-01-01', + persian: '1000-01-01', + jalali: '1000-01-01', + taiwan: '1000-01-01', + thai: '2000-01-01', + ummalqura: '1400-01-01' +} // Start on a Sunday - for week ticks // Discworld and Mayan calendars don't have 7-day weeks but we're going to give them // 7-day week ticks so start on our Sundays. // If anyone really cares we can customize the auto tick spacings for these calendars. var CANONICAL_SUNDAY = { - chinese: '2000-01-02', - coptic: '2000-01-03', - discworld: '2000-01-03', - ethiopian: '2000-01-05', - hebrew: '5000-01-01', - islamic: '1000-01-02', - julian: '2000-01-03', - mayan: '5000-01-01', - nanakshahi: '1000-01-05', - nepali: '2000-01-05', - persian: '1000-01-01', - jalali: '1000-01-01', - taiwan: '1000-01-04', - thai: '2000-01-04', - ummalqura: '1400-01-06' -}; + chinese: '2000-01-02', + coptic: '2000-01-03', + discworld: '2000-01-03', + ethiopian: '2000-01-05', + hebrew: '5000-01-01', + islamic: '1000-01-02', + julian: '2000-01-03', + mayan: '5000-01-01', + nanakshahi: '1000-01-05', + nepali: '2000-01-05', + persian: '1000-01-01', + jalali: '1000-01-01', + taiwan: '1000-01-04', + thai: '2000-01-04', + ummalqura: '1400-01-06' +} var DFLTRANGE = { - chinese: ['2000-01-01', '2001-01-01'], - coptic: ['1700-01-01', '1701-01-01'], - discworld: ['1800-01-01', '1801-01-01'], - ethiopian: ['2000-01-01', '2001-01-01'], - hebrew: ['5700-01-01', '5701-01-01'], - islamic: ['1400-01-01', '1401-01-01'], - julian: ['2000-01-01', '2001-01-01'], - mayan: ['5200-01-01', '5201-01-01'], - nanakshahi: ['0500-01-01', '0501-01-01'], - nepali: ['2000-01-01', '2001-01-01'], - persian: ['1400-01-01', '1401-01-01'], - jalali: ['1400-01-01', '1401-01-01'], - taiwan: ['0100-01-01', '0101-01-01'], - thai: ['2500-01-01', '2501-01-01'], - ummalqura: ['1400-01-01', '1401-01-01'] -}; + chinese: ['2000-01-01', '2001-01-01'], + coptic: ['1700-01-01', '1701-01-01'], + discworld: ['1800-01-01', '1801-01-01'], + ethiopian: ['2000-01-01', '2001-01-01'], + hebrew: ['5700-01-01', '5701-01-01'], + islamic: ['1400-01-01', '1401-01-01'], + julian: ['2000-01-01', '2001-01-01'], + mayan: ['5200-01-01', '5201-01-01'], + nanakshahi: ['0500-01-01', '0501-01-01'], + nepali: ['2000-01-01', '2001-01-01'], + persian: ['1400-01-01', '1401-01-01'], + jalali: ['1400-01-01', '1401-01-01'], + taiwan: ['0100-01-01', '0101-01-01'], + thai: ['2500-01-01', '2501-01-01'], + ummalqura: ['1400-01-01', '1401-01-01'] +} /* * convert d3 templates to world-calendars templates, so our users only need * to know d3's specifiers. Map space padding to no padding, and unknown fields * to an ugly placeholder */ -var UNKNOWN = '##'; +var UNKNOWN = '##' var d3ToWorldCalendars = { - 'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month - 'e': {'0': 'd', '-': 'd'}, // alternate, always unpadded day of month - 'a': {'0': 'D', '-': 'D'}, // short weekday name - 'A': {'0': 'DD', '-': 'DD'}, // full weekday name - 'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year - 'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first) - 'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number - 'b': {'0': 'M', '-': 'M'}, // short month name - 'B': {'0': 'MM', '-': 'MM'}, // full month name - 'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded) - 'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded) - 'U': UNKNOWN, // Sunday-first week of the year - 'w': UNKNOWN, // day of the week [0(sunday),6] + 'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month + 'e': {'0': 'd', '-': 'd'}, // alternate, always unpadded day of month + 'a': {'0': 'D', '-': 'D'}, // short weekday name + 'A': {'0': 'DD', '-': 'DD'}, // full weekday name + 'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year + 'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first) + 'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number + 'b': {'0': 'M', '-': 'M'}, // short month name + 'B': {'0': 'MM', '-': 'MM'}, // full month name + 'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded) + 'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded) + 'U': UNKNOWN, // Sunday-first week of the year + 'w': UNKNOWN, // day of the week [0(sunday),6] // combined format, we replace the date part with the world-calendar version // and the %X stays there for d3 to handle with time parts - 'c': {'0': 'D M d %X yyyy', '-': 'D M d %X yyyy'}, - 'x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'} -}; + 'c': {'0': 'D M d %X yyyy', '-': 'D M d %X yyyy'}, + 'x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'} +} -function worldCalFmt(fmt, x, calendar) { - var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, - cDate = getCal(calendar).fromJD(dateJD), - i = 0, - modifier, directive, directiveLen, directiveObj, replacementPart; - while((i = fmt.indexOf('%', i)) !== -1) { - modifier = fmt.charAt(i + 1); - if(modifier === '0' || modifier === '-' || modifier === '_') { - directiveLen = 3; - directive = fmt.charAt(i + 2); - if(modifier === '_') modifier = '-'; - } - else { - directive = modifier; - modifier = '0'; - directiveLen = 2; - } - directiveObj = d3ToWorldCalendars[directive]; - if(!directiveObj) { - i += directiveLen; - } - else { +function worldCalFmt (fmt, x, calendar) { + var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, + cDate = getCal(calendar).fromJD(dateJD), + i = 0, + modifier, directive, directiveLen, directiveObj, replacementPart + while ((i = fmt.indexOf('%', i)) !== -1) { + modifier = fmt.charAt(i + 1) + if (modifier === '0' || modifier === '-' || modifier === '_') { + directiveLen = 3 + directive = fmt.charAt(i + 2) + if (modifier === '_') modifier = '-' + } else { + directive = modifier + modifier = '0' + directiveLen = 2 + } + directiveObj = d3ToWorldCalendars[directive] + if (!directiveObj) { + i += directiveLen + } else { // code is recognized as a date part but world-calendars doesn't support it - if(directiveObj === UNKNOWN) replacementPart = UNKNOWN; + if (directiveObj === UNKNOWN) replacementPart = UNKNOWN // format the cDate according to the translated directive - else replacementPart = cDate.formatDate(directiveObj[modifier]); + else replacementPart = cDate.formatDate(directiveObj[modifier]) - fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen); - i += replacementPart.length; - } + fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen) + i += replacementPart.length } - return fmt; + } + return fmt } // cache world calendars, so we don't have to reinstantiate // during each date-time conversion -var allCals = {}; -function getCal(calendar) { - var calendarObj = allCals[calendar]; - if(calendarObj) return calendarObj; +var allCals = {} +function getCal (calendar) { + var calendarObj = allCals[calendar] + if (calendarObj) return calendarObj - calendarObj = allCals[calendar] = calendars.instance(calendar); - return calendarObj; + calendarObj = allCals[calendar] = calendars.instance(calendar) + return calendarObj } -function makeAttrs(description) { - return Lib.extendFlat({}, attributes, { description: description }); +function makeAttrs (description) { + return Lib.extendFlat({}, attributes, { description: description }) } -function makeTraceAttrsDescription(coord) { - return 'Sets the calendar system to use with `' + coord + '` date data.'; +function makeTraceAttrsDescription (coord) { + return 'Sets the calendar system to use with `' + coord + '` date data.' } var xAttrs = { - xcalendar: makeAttrs(makeTraceAttrsDescription('x')) -}; + xcalendar: makeAttrs(makeTraceAttrsDescription('x')) +} var xyAttrs = Lib.extendFlat({}, xAttrs, { - ycalendar: makeAttrs(makeTraceAttrsDescription('y')) -}); + ycalendar: makeAttrs(makeTraceAttrsDescription('y')) +}) var xyzAttrs = Lib.extendFlat({}, xyAttrs, { - zcalendar: makeAttrs(makeTraceAttrsDescription('z')) -}); + zcalendar: makeAttrs(makeTraceAttrsDescription('z')) +}) var axisAttrs = makeAttrs([ - 'Sets the calendar system to use for `range` and `tick0`', - 'if this is a date axis. This does not set the calendar for', - 'interpreting data on this axis, that\'s specified in the trace', - 'or via the global `layout.calendar`' -].join(' ')); + 'Sets the calendar system to use for `range` and `tick0`', + 'if this is a date axis. This does not set the calendar for', + 'interpreting data on this axis, that\'s specified in the trace', + 'or via the global `layout.calendar`' +].join(' ')) module.exports = { - moduleType: 'component', - name: 'calendars', - - schema: { - traces: { - scatter: xyAttrs, - bar: xyAttrs, - heatmap: xyAttrs, - contour: xyAttrs, - histogram: xyAttrs, - histogram2d: xyAttrs, - histogram2dcontour: xyAttrs, - scatter3d: xyzAttrs, - surface: xyzAttrs, - mesh3d: xyzAttrs, - scattergl: xyAttrs, - ohlc: xAttrs, - candlestick: xAttrs - }, - layout: { - calendar: makeAttrs([ - 'Sets the default calendar system to use for interpreting and', - 'displaying dates throughout the plot.' - ].join(' ')), - 'xaxis.calendar': axisAttrs, - 'yaxis.calendar': axisAttrs, - 'scene.xaxis.calendar': axisAttrs, - 'scene.yaxis.calendar': axisAttrs, - 'scene.zaxis.calendar': axisAttrs - }, - transforms: { - filter: { - valuecalendar: makeAttrs([ - 'Sets the calendar system to use for `value`, if it is a date.' - ].join(' ')), - targetcalendar: makeAttrs([ - 'Sets the calendar system to use for `target`, if it is an', - 'array of dates. If `target` is a string (eg *x*) we use the', - 'corresponding trace attribute (eg `xcalendar`) if it exists,', - 'even if `targetcalendar` is provided.' - ].join(' ')) - } - } + moduleType: 'component', + name: 'calendars', + + schema: { + traces: { + scatter: xyAttrs, + bar: xyAttrs, + heatmap: xyAttrs, + contour: xyAttrs, + histogram: xyAttrs, + histogram2d: xyAttrs, + histogram2dcontour: xyAttrs, + scatter3d: xyzAttrs, + surface: xyzAttrs, + mesh3d: xyzAttrs, + scattergl: xyAttrs, + ohlc: xAttrs, + candlestick: xAttrs }, + layout: { + calendar: makeAttrs([ + 'Sets the default calendar system to use for interpreting and', + 'displaying dates throughout the plot.' + ].join(' ')), + 'xaxis.calendar': axisAttrs, + 'yaxis.calendar': axisAttrs, + 'scene.xaxis.calendar': axisAttrs, + 'scene.yaxis.calendar': axisAttrs, + 'scene.zaxis.calendar': axisAttrs + }, + transforms: { + filter: { + valuecalendar: makeAttrs([ + 'Sets the calendar system to use for `value`, if it is a date.' + ].join(' ')), + targetcalendar: makeAttrs([ + 'Sets the calendar system to use for `target`, if it is an', + 'array of dates. If `target` is a string (eg *x*) we use the', + 'corresponding trace attribute (eg `xcalendar`) if it exists,', + 'even if `targetcalendar` is provided.' + ].join(' ')) + } + } + }, - layoutAttributes: attributes, + layoutAttributes: attributes, - handleDefaults: handleDefaults, - handleTraceDefaults: handleTraceDefaults, + handleDefaults: handleDefaults, + handleTraceDefaults: handleTraceDefaults, - CANONICAL_SUNDAY: CANONICAL_SUNDAY, - CANONICAL_TICK: CANONICAL_TICK, - DFLTRANGE: DFLTRANGE, + CANONICAL_SUNDAY: CANONICAL_SUNDAY, + CANONICAL_TICK: CANONICAL_TICK, + DFLTRANGE: DFLTRANGE, - getCal: getCal, - worldCalFmt: worldCalFmt -}; + getCal: getCal, + worldCalFmt: worldCalFmt +} diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js index e4a5c6d2c35..6c3f05c9f1e 100644 --- a/src/components/color/attributes.js +++ b/src/components/color/attributes.js @@ -6,33 +6,32 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' // IMPORTANT - default colors should be in hex for compatibility exports.defaults = [ - '#1f77b4', // muted blue - '#ff7f0e', // safety orange - '#2ca02c', // cooked asparagus green - '#d62728', // brick red - '#9467bd', // muted purple - '#8c564b', // chestnut brown - '#e377c2', // raspberry yogurt pink - '#7f7f7f', // middle gray - '#bcbd22', // curry yellow-green - '#17becf' // blue-teal -]; + '#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.defaultLine = '#444' -exports.lightLine = '#eee'; +exports.lightLine = '#eee' -exports.background = '#fff'; +exports.background = '#fff' -exports.borderLine = '#BEC8D9'; +exports.borderLine = '#BEC8D9' // with axis.color and Color.interp we aren't using lightLine // itself anymore, instead interpolating between axis.color // and the background color using tinycolor.mix. lightFraction // gives back exactly lightLine if the other colors are defaults. -exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4); +exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4) diff --git a/src/components/color/index.js b/src/components/color/index.js index 714d5a05cad..1051657f8e4 100644 --- a/src/components/color/index.js +++ b/src/components/color/index.js @@ -6,150 +6,146 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var tinycolor = require('tinycolor2') +var isNumeric = require('fast-isnumeric') -var tinycolor = require('tinycolor2'); -var isNumeric = require('fast-isnumeric'); +var color = module.exports = {} -var color = module.exports = {}; +var colorAttrs = require('./attributes') +color.defaults = colorAttrs.defaults +color.defaultLine = colorAttrs.defaultLine +color.lightLine = colorAttrs.lightLine +color.background = colorAttrs.background -var colorAttrs = require('./attributes'); -color.defaults = colorAttrs.defaults; -color.defaultLine = colorAttrs.defaultLine; -color.lightLine = colorAttrs.lightLine; -color.background = colorAttrs.background; - -color.tinyRGB = function(tc) { - var c = tc.toRgb(); - return 'rgb(' + Math.round(c.r) + ', ' + - Math.round(c.g) + ', ' + Math.round(c.b) + ')'; -}; +color.tinyRGB = function (tc) { + var c = tc.toRgb() + return 'rgb(' + Math.round(c.r) + ', ' + + Math.round(c.g) + ', ' + Math.round(c.b) + ')' +} -color.rgb = function(cstr) { return color.tinyRGB(tinycolor(cstr)); }; +color.rgb = function (cstr) { return color.tinyRGB(tinycolor(cstr)) } -color.opacity = function(cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0; }; +color.opacity = function (cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0 } -color.addOpacity = function(cstr, op) { - var c = tinycolor(cstr).toRgb(); - return 'rgba(' + Math.round(c.r) + ', ' + - Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')'; -}; +color.addOpacity = function (cstr, op) { + var c = tinycolor(cstr).toRgb() + return 'rgba(' + Math.round(c.r) + ', ' + + Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')' +} // combine two colors into one apparent color // if back has transparency or is missing, // color.background is assumed behind it -color.combine = function(front, back) { - var fc = tinycolor(front).toRgb(); - if(fc.a === 1) return tinycolor(front).toRgbString(); - - var bc = tinycolor(back || color.background).toRgb(), - bcflat = bc.a === 1 ? bc : { - r: 255 * (1 - bc.a) + bc.r * bc.a, - g: 255 * (1 - bc.a) + bc.g * bc.a, - b: 255 * (1 - bc.a) + bc.b * bc.a - }, - fcflat = { - r: bcflat.r * (1 - fc.a) + fc.r * fc.a, - g: bcflat.g * (1 - fc.a) + fc.g * fc.a, - b: bcflat.b * (1 - fc.a) + fc.b * fc.a - }; - return tinycolor(fcflat).toRgbString(); -}; - -color.contrast = function(cstr, lightAmount, darkAmount) { - var tc = tinycolor(cstr); - - var newColor = tc.isLight() ? +color.combine = function (front, back) { + var fc = tinycolor(front).toRgb() + if (fc.a === 1) return tinycolor(front).toRgbString() + + var bc = tinycolor(back || color.background).toRgb(), + bcflat = bc.a === 1 ? bc : { + r: 255 * (1 - bc.a) + bc.r * bc.a, + g: 255 * (1 - bc.a) + bc.g * bc.a, + b: 255 * (1 - bc.a) + bc.b * bc.a + }, + fcflat = { + r: bcflat.r * (1 - fc.a) + fc.r * fc.a, + g: bcflat.g * (1 - fc.a) + fc.g * fc.a, + b: bcflat.b * (1 - fc.a) + fc.b * fc.a + } + return tinycolor(fcflat).toRgbString() +} + +color.contrast = function (cstr, lightAmount, darkAmount) { + var tc = tinycolor(cstr) + + var newColor = tc.isLight() ? tc.darken(darkAmount) : - tc.lighten(lightAmount); + tc.lighten(lightAmount) - return newColor.toString(); -}; + return newColor.toString() +} -color.stroke = function(s, c) { - var tc = tinycolor(c); - s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()}); -}; +color.stroke = function (s, c) { + var tc = tinycolor(c) + s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()}) +} -color.fill = function(s, c) { - var tc = tinycolor(c); - s.style({ - 'fill': color.tinyRGB(tc), - 'fill-opacity': tc.getAlpha() - }); -}; +color.fill = function (s, c) { + var tc = tinycolor(c) + s.style({ + 'fill': color.tinyRGB(tc), + 'fill-opacity': tc.getAlpha() + }) +} // search container for colors with the deprecated rgb(fractions) format // and convert them to rgb(0-255 values) -color.clean = function(container) { - if(!container || typeof container !== 'object') return; +color.clean = function (container) { + if (!container || typeof container !== 'object') return - var keys = Object.keys(container), - i, - j, - key, - val; + var keys = Object.keys(container), + i, + j, + key, + val - for(i = 0; i < keys.length; i++) { - key = keys[i]; - val = container[key]; + for (i = 0; i < keys.length; i++) { + key = keys[i] + val = container[key] // only sanitize keys that end in "color" or "colorscale" - if(key.substr(key.length - 5) === 'color') { - if(Array.isArray(val)) { - for(j = 0; j < val.length; j++) val[j] = cleanOne(val[j]); - } - else container[key] = cleanOne(val); - } - else if(key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) { + if (key.substr(key.length - 5) === 'color') { + if (Array.isArray(val)) { + for (j = 0; j < val.length; j++) val[j] = cleanOne(val[j]) + } else container[key] = cleanOne(val) + } else if (key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) { // colorscales have the format [[0, color1], [frac, color2], ... [1, colorN]] - for(j = 0; j < val.length; j++) { - if(Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]); - } - } - // recurse into arrays of objects, and plain objects - else if(Array.isArray(val)) { - var el0 = val[0]; - if(!Array.isArray(el0) && el0 && typeof el0 === 'object') { - for(j = 0; j < val.length; j++) color.clean(val[j]); - } - } - else if(val && typeof val === 'object') color.clean(val); + for (j = 0; j < val.length; j++) { + if (Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]) + } } -}; + // recurse into arrays of objects, and plain objects + else if (Array.isArray(val)) { + var el0 = val[0] + if (!Array.isArray(el0) && el0 && typeof el0 === 'object') { + for (j = 0; j < val.length; j++) color.clean(val[j]) + } + } else if (val && typeof val === 'object') color.clean(val) + } +} -function cleanOne(val) { - if(isNumeric(val) || typeof val !== 'string') return val; +function cleanOne (val) { + if (isNumeric(val) || typeof val !== 'string') return val - var valTrim = val.trim(); - if(valTrim.substr(0, 3) !== 'rgb') return val; + var valTrim = val.trim() + if (valTrim.substr(0, 3) !== 'rgb') return val - var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/); - if(!match) return val; + var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/) + if (!match) return val - var parts = match[1].trim().split(/\s*[\s,]\s*/), - rgba = valTrim.charAt(3) === 'a' && parts.length === 4; - if(!rgba && parts.length !== 3) return val; + var parts = match[1].trim().split(/\s*[\s,]\s*/), + rgba = valTrim.charAt(3) === 'a' && parts.length === 4 + if (!rgba && parts.length !== 3) return val - for(var i = 0; i < parts.length; i++) { - if(!parts[i].length) return val; - parts[i] = Number(parts[i]); + for (var i = 0; i < parts.length; i++) { + if (!parts[i].length) return val + parts[i] = Number(parts[i]) // all parts must be non-negative numbers - if(!(parts[i] >= 0)) return val; + if (!(parts[i] >= 0)) return val // alpha>1 gets clipped to 1 - if(i === 3) { - if(parts[i] > 1) parts[i] = 1; - } - // r, g, b must be < 1 (ie 1 itself is not allowed) - else if(parts[i] >= 1) return val; + if (i === 3) { + if (parts[i] > 1) parts[i] = 1 } + // r, g, b must be < 1 (ie 1 itself is not allowed) + else if (parts[i] >= 1) return val + } - var rgbStr = Math.round(parts[0] * 255) + ', ' + + var rgbStr = Math.round(parts[0] * 255) + ', ' + Math.round(parts[1] * 255) + ', ' + - Math.round(parts[2] * 255); + Math.round(parts[2] * 255) - if(rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')'; - return 'rgb(' + rgbStr + ')'; + if (rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')' + return 'rgb(' + rgbStr + ')' } diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index 62f1e031ff8..fdc69b4299e 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var axesAttrs = require('../../plots/cartesian/layout_attributes'); -var fontAttrs = require('../../plots/font_attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +'use strict' +var axesAttrs = require('../../plots/cartesian/layout_attributes') +var fontAttrs = require('../../plots/font_attributes') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { // TODO: only right is supported currently @@ -25,170 +24,170 @@ module.exports = { // '(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.' - }, + 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, + 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.' - }, + bordercolor: axesAttrs.linecolor, + borderwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 0, + description: [ + 'Sets the width (in px) or the border enclosing this color bar.' + ].join(' ') + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: 'rgba(0,0,0,0)', + description: 'Sets the color of padded area.' + }, // tick and title properties named and function exactly as in axes - tickmode: axesAttrs.tickmode, - nticks: axesAttrs.nticks, - tick0: axesAttrs.tick0, - dtick: axesAttrs.dtick, - tickvals: axesAttrs.tickvals, - ticktext: axesAttrs.ticktext, - ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), - ticklen: axesAttrs.ticklen, - tickwidth: axesAttrs.tickwidth, - tickcolor: axesAttrs.tickcolor, - showticklabels: axesAttrs.showticklabels, - tickfont: axesAttrs.tickfont, - tickangle: axesAttrs.tickangle, - tickformat: axesAttrs.tickformat, - tickprefix: axesAttrs.tickprefix, - showtickprefix: axesAttrs.showtickprefix, - ticksuffix: axesAttrs.ticksuffix, - showticksuffix: axesAttrs.showticksuffix, - separatethousands: axesAttrs.separatethousands, - exponentformat: axesAttrs.exponentformat, - showexponent: axesAttrs.showexponent, - title: { - valType: 'string', - role: 'info', - dflt: 'Click to enter colorscale title', - description: 'Sets the title of the color bar.' - }, - titlefont: extendFlat({}, fontAttrs, { - description: [ - 'Sets this color bar\'s title font.' - ].join(' ') - }), - titleside: { - valType: 'enumerated', - values: ['right', 'top', 'bottom'], - role: 'style', - dflt: 'top', - description: [ - 'Determines the location of the colorbar title', - 'with respect to the color bar.' - ].join(' ') - } -}; + tickmode: axesAttrs.tickmode, + nticks: axesAttrs.nticks, + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}), + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + tickprefix: axesAttrs.tickprefix, + showtickprefix: axesAttrs.showtickprefix, + ticksuffix: axesAttrs.ticksuffix, + showticksuffix: axesAttrs.showticksuffix, + separatethousands: axesAttrs.separatethousands, + exponentformat: axesAttrs.exponentformat, + showexponent: axesAttrs.showexponent, + title: { + valType: 'string', + role: 'info', + dflt: 'Click to enter colorscale title', + description: 'Sets the title of the color bar.' + }, + titlefont: extendFlat({}, fontAttrs, { + description: [ + 'Sets this color bar\'s title font.' + ].join(' ') + }), + titleside: { + valType: 'enumerated', + values: ['right', 'top', 'bottom'], + role: 'style', + dflt: 'top', + description: [ + 'Determines the location of the colorbar title', + 'with respect to the color bar.' + ].join(' ') + } +} diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index be767c67524..e222728b67f 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -6,60 +6,58 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var handleTickValueDefaults = require('../../plots/cartesian/tick_value_defaults') +var handleTickMarkDefaults = require('../../plots/cartesian/tick_mark_defaults') +var handleTickLabelDefaults = require('../../plots/cartesian/tick_label_defaults') -var Lib = require('../../lib'); -var handleTickValueDefaults = require('../../plots/cartesian/tick_value_defaults'); -var handleTickMarkDefaults = require('../../plots/cartesian/tick_mark_defaults'); -var handleTickLabelDefaults = require('../../plots/cartesian/tick_label_defaults'); +var attributes = require('./attributes') -var attributes = require('./attributes'); +module.exports = function colorbarDefaults (containerIn, containerOut, layout) { + var colorbarOut = containerOut.colorbar = {}, + colorbarIn = containerIn.colorbar || {} + function coerce (attr, dflt) { + return Lib.coerce(colorbarIn, colorbarOut, attributes, attr, dflt) + } -module.exports = function colorbarDefaults(containerIn, containerOut, layout) { - var colorbarOut = containerOut.colorbar = {}, - colorbarIn = containerIn.colorbar || {}; - - function coerce(attr, dflt) { - return Lib.coerce(colorbarIn, colorbarOut, attributes, attr, dflt); - } - - var thicknessmode = coerce('thicknessmode'); - coerce('thickness', (thicknessmode === 'fraction') ? + var thicknessmode = coerce('thicknessmode') + coerce('thickness', (thicknessmode === 'fraction') ? 30 / (layout.width - layout.margin.l - layout.margin.r) : 30 - ); + ) - var lenmode = coerce('lenmode'); - coerce('len', (lenmode === 'fraction') ? + var lenmode = coerce('lenmode') + coerce('len', (lenmode === 'fraction') ? 1 : layout.height - layout.margin.t - layout.margin.b - ); - - coerce('x'); - coerce('xanchor'); - coerce('xpad'); - coerce('y'); - coerce('yanchor'); - coerce('ypad'); - Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']); - - coerce('outlinecolor'); - coerce('outlinewidth'); - coerce('bordercolor'); - coerce('borderwidth'); - coerce('bgcolor'); - - handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); - - handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', - {outerTicks: false, font: layout.font, noHover: true}); - - handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', - {outerTicks: false, font: layout.font, noHover: true}); - - coerce('title'); - Lib.coerceFont(coerce, 'titlefont', layout.font); - coerce('titleside'); -}; + ) + + coerce('x') + coerce('xanchor') + coerce('xpad') + coerce('y') + coerce('yanchor') + coerce('ypad') + Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']) + + coerce('outlinecolor') + coerce('outlinewidth') + coerce('bordercolor') + coerce('borderwidth') + coerce('bgcolor') + + handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear') + + handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', + {outerTicks: false, font: layout.font, noHover: true}) + + handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', + {outerTicks: false, font: layout.font, noHover: true}) + + coerce('title') + Lib.coerceFont(coerce, 'titlefont', layout.font) + coerce('titleside') +} diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index b7eef65140c..48f20b6b6a1 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -6,106 +6,102 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var d3 = require('d3'); -var tinycolor = require('tinycolor2'); - -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); -var dragElement = require('../dragelement'); -var Lib = require('../../lib'); -var extendFlat = require('../../lib/extend').extendFlat; -var setCursor = require('../../lib/setcursor'); -var Drawing = require('../drawing'); -var Color = require('../color'); -var Titles = require('../titles'); - -var handleAxisDefaults = require('../../plots/cartesian/axis_defaults'); -var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults'); -var axisLayoutAttrs = require('../../plots/cartesian/layout_attributes'); - -var attributes = require('./attributes'); - - -module.exports = function draw(gd, id) { +'use strict' + +var d3 = require('d3') +var tinycolor = require('tinycolor2') + +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') +var dragElement = require('../dragelement') +var Lib = require('../../lib') +var extendFlat = require('../../lib/extend').extendFlat +var setCursor = require('../../lib/setcursor') +var Drawing = require('../drawing') +var Color = require('../color') +var Titles = require('../titles') + +var handleAxisDefaults = require('../../plots/cartesian/axis_defaults') +var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults') +var axisLayoutAttrs = require('../../plots/cartesian/layout_attributes') + +var attributes = require('./attributes') + +module.exports = function draw (gd, id) { // opts: options object, containing everything from attributes // plus a few others that are the equivalent of the colorbar "data" - var opts = {}; - Object.keys(attributes).forEach(function(k) { - opts[k] = null; - }); + var opts = {} + Object.keys(attributes).forEach(function (k) { + opts[k] = null + }) // fillcolor can be a d3 scale, domain is z values, range is colors // or leave it out for no fill, // or set to a string constant for single-color fill - opts.fillcolor = null; + opts.fillcolor = null // line.color has the same options as fillcolor - opts.line = {color: null, width: null, dash: null}; + opts.line = {color: null, width: null, dash: null} // levels of lines to draw. // note that this DOES NOT determine the extent of the bar // that's given by the domain of fillcolor // (or line.color if no fillcolor domain) - opts.levels = {start: null, end: null, size: null}; + opts.levels = {start: null, end: null, size: null} // separate fill levels (for example, heatmap coloring of a // contour map) if this is omitted, fillcolors will be // evaluated halfway between levels - opts.filllevels = null; + opts.filllevels = null - function component() { - var fullLayout = gd._fullLayout, - gs = fullLayout._size; - if((typeof opts.fillcolor !== 'function') && + function component () { + var fullLayout = gd._fullLayout, + gs = fullLayout._size + if ((typeof opts.fillcolor !== 'function') && (typeof opts.line.color !== 'function')) { - fullLayout._infolayer.selectAll('g.' + id).remove(); - return; - } - var zrange = d3.extent(((typeof opts.fillcolor === 'function') ? + fullLayout._infolayer.selectAll('g.' + id).remove() + return + } + var zrange = d3.extent(((typeof opts.fillcolor === 'function') ? opts.fillcolor : opts.line.color).domain()), - linelevels = [], - filllevels = [], - l, - linecolormap = typeof opts.line.color === 'function' ? - opts.line.color : function() { return opts.line.color; }, - fillcolormap = typeof opts.fillcolor === 'function' ? - opts.fillcolor : function() { return opts.fillcolor; }; - - var l0 = opts.levels.end + opts.levels.size / 100, - ls = opts.levels.size, - zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]), - zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]); - for(l = opts.levels.start; (l - l0) * ls < 0; l += ls) { - if(l > zr0 && l < zr1) linelevels.push(l); - } + linelevels = [], + filllevels = [], + l, + linecolormap = typeof opts.line.color === 'function' ? + opts.line.color : function () { return opts.line.color }, + fillcolormap = typeof opts.fillcolor === 'function' ? + opts.fillcolor : function () { return opts.fillcolor } + + var l0 = opts.levels.end + opts.levels.size / 100, + ls = opts.levels.size, + zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]), + zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]) + for (l = opts.levels.start; (l - l0) * ls < 0; l += ls) { + if (l > zr0 && l < zr1) linelevels.push(l) + } - if(typeof opts.fillcolor === 'function') { - if(opts.filllevels) { - l0 = opts.filllevels.end + opts.filllevels.size / 100; - ls = opts.filllevels.size; - for(l = opts.filllevels.start; (l - l0) * ls < 0; l += ls) { - if(l > zrange[0] && l < zrange[1]) filllevels.push(l); - } - } - else { - filllevels = linelevels.map(function(v) { - return v - opts.levels.size / 2; - }); - filllevels.push(filllevels[filllevels.length - 1] + - opts.levels.size); - } + if (typeof opts.fillcolor === 'function') { + if (opts.filllevels) { + l0 = opts.filllevels.end + opts.filllevels.size / 100 + ls = opts.filllevels.size + for (l = opts.filllevels.start; (l - l0) * ls < 0; l += ls) { + if (l > zrange[0] && l < zrange[1]) filllevels.push(l) } - else if(opts.fillcolor && typeof opts.fillcolor === 'string') { + } else { + filllevels = linelevels.map(function (v) { + return v - opts.levels.size / 2 + }) + filllevels.push(filllevels[filllevels.length - 1] + + opts.levels.size) + } + } else if (opts.fillcolor && typeof opts.fillcolor === 'string') { // doesn't matter what this value is, with a single value // we'll make a single fill rect covering the whole bar - filllevels = [0]; - } + filllevels = [0] + } - if(opts.levels.size < 0) { - linelevels.reverse(); - filllevels.reverse(); - } + if (opts.levels.size < 0) { + linelevels.reverse() + filllevels.reverse() + } // now make a Plotly Axes object to scale with and draw ticks // TODO: does not support orientation other than right @@ -116,516 +112,510 @@ module.exports = function draw(gd, id) { // when the colorbar itself is pushing the margins. // but then the fractional size is calculated based on the // actual graph size, so that the axes will size correctly. - var originalPlotHeight = fullLayout.height - fullLayout.margin.t - fullLayout.margin.b, - originalPlotWidth = fullLayout.width - fullLayout.margin.l - fullLayout.margin.r, - thickPx = Math.round(opts.thickness * + var originalPlotHeight = fullLayout.height - fullLayout.margin.t - fullLayout.margin.b, + originalPlotWidth = fullLayout.width - fullLayout.margin.l - fullLayout.margin.r, + thickPx = Math.round(opts.thickness * (opts.thicknessmode === 'fraction' ? originalPlotWidth : 1)), - thickFrac = thickPx / gs.w, - lenPx = Math.round(opts.len * + thickFrac = thickPx / gs.w, + lenPx = Math.round(opts.len * (opts.lenmode === 'fraction' ? originalPlotHeight : 1)), - lenFrac = lenPx / gs.h, - xpadFrac = opts.xpad / gs.w, - yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2, - ypadFrac = opts.ypad / gs.h, + lenFrac = lenPx / gs.h, + xpadFrac = opts.xpad / gs.w, + yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2, + ypadFrac = opts.ypad / gs.h, // x positioning: do it initially just for left anchor, // then fix at the end (since we don't know the width yet) - xLeft = Math.round(opts.x * gs.w + opts.xpad), + xLeft = Math.round(opts.x * gs.w + opts.xpad), // for dragging... this is getting a little muddled... - xLeftFrac = opts.x - thickFrac * - ({middle: 0.5, right: 1}[opts.xanchor]||0), + xLeftFrac = opts.x - thickFrac * + ({middle: 0.5, right: 1}[opts.xanchor] || 0), // y positioning we can do correctly from the start - yBottomFrac = opts.y + lenFrac * + yBottomFrac = opts.y + lenFrac * (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5), - yBottomPx = Math.round(gs.h * (1 - yBottomFrac)), - yTopPx = yBottomPx - lenPx, - titleEl, - cbAxisIn = { - type: 'linear', - range: zrange, - tickmode: opts.tickmode, - nticks: opts.nticks, - tick0: opts.tick0, - dtick: opts.dtick, - tickvals: opts.tickvals, - ticktext: opts.ticktext, - ticks: opts.ticks, - ticklen: opts.ticklen, - tickwidth: opts.tickwidth, - tickcolor: opts.tickcolor, - showticklabels: opts.showticklabels, - tickfont: opts.tickfont, - tickangle: opts.tickangle, - tickformat: opts.tickformat, - exponentformat: opts.exponentformat, - separatethousands: opts.separatethousands, - showexponent: opts.showexponent, - showtickprefix: opts.showtickprefix, - tickprefix: opts.tickprefix, - showticksuffix: opts.showticksuffix, - ticksuffix: opts.ticksuffix, - title: opts.title, - titlefont: opts.titlefont, - anchor: 'free', - position: 1 - }, - cbAxisOut = {}, - axisOptions = { - letter: 'y', - font: fullLayout.font, - noHover: true, - calendar: fullLayout.calendar // not really necessary (yet?) - }; + yBottomPx = Math.round(gs.h * (1 - yBottomFrac)), + yTopPx = yBottomPx - lenPx, + titleEl, + cbAxisIn = { + type: 'linear', + range: zrange, + tickmode: opts.tickmode, + nticks: opts.nticks, + tick0: opts.tick0, + dtick: opts.dtick, + tickvals: opts.tickvals, + ticktext: opts.ticktext, + ticks: opts.ticks, + ticklen: opts.ticklen, + tickwidth: opts.tickwidth, + tickcolor: opts.tickcolor, + showticklabels: opts.showticklabels, + tickfont: opts.tickfont, + tickangle: opts.tickangle, + tickformat: opts.tickformat, + exponentformat: opts.exponentformat, + separatethousands: opts.separatethousands, + showexponent: opts.showexponent, + showtickprefix: opts.showtickprefix, + tickprefix: opts.tickprefix, + showticksuffix: opts.showticksuffix, + ticksuffix: opts.ticksuffix, + title: opts.title, + titlefont: opts.titlefont, + anchor: 'free', + position: 1 + }, + cbAxisOut = {}, + axisOptions = { + letter: 'y', + font: fullLayout.font, + noHover: true, + calendar: fullLayout.calendar // not really necessary (yet?) + } // Coerce w.r.t. Axes layoutAttributes: // re-use axes.js logic without updating _fullData - function coerce(attr, dflt) { - return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt) + } // Prepare the Plotly axis object - handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions); - handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions); + handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions) + handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions) - cbAxisOut._id = 'y' + id; - cbAxisOut._gd = gd; + cbAxisOut._id = 'y' + id + cbAxisOut._gd = gd // position can't go in through supplyDefaults // because that restricts it to [0,1] - cbAxisOut.position = opts.x + xpadFrac + thickFrac; + cbAxisOut.position = opts.x + xpadFrac + thickFrac // save for other callers to access this axis - component.axis = cbAxisOut; + component.axis = cbAxisOut - if(['top', 'bottom'].indexOf(opts.titleside) !== -1) { - cbAxisOut.titleside = opts.titleside; - cbAxisOut.titlex = opts.x + xpadFrac; - cbAxisOut.titley = yBottomFrac + - (opts.titleside === 'top' ? lenFrac - ypadFrac : ypadFrac); - } + if (['top', 'bottom'].indexOf(opts.titleside) !== -1) { + cbAxisOut.titleside = opts.titleside + cbAxisOut.titlex = opts.x + xpadFrac + cbAxisOut.titley = yBottomFrac + + (opts.titleside === 'top' ? lenFrac - ypadFrac : ypadFrac) + } - if(opts.line.color && opts.tickmode === 'auto') { - cbAxisOut.tickmode = 'linear'; - cbAxisOut.tick0 = opts.levels.start; - var dtick = opts.levels.size; + if (opts.line.color && opts.tickmode === 'auto') { + cbAxisOut.tickmode = 'linear' + cbAxisOut.tick0 = opts.levels.start + var dtick = opts.levels.size // expand if too many contours, so we don't get too many ticks - var autoNtick = Lib.constrain( + var autoNtick = Lib.constrain( (yBottomPx - yTopPx) / 50, 4, 15) + 1, - dtFactor = (zrange[1] - zrange[0]) / - ((opts.nticks || autoNtick) * dtick); - if(dtFactor > 1) { - var dtexp = Math.pow(10, Math.floor( - Math.log(dtFactor) / Math.LN10)); - dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]); + dtFactor = (zrange[1] - zrange[0]) / + ((opts.nticks || autoNtick) * dtick) + if (dtFactor > 1) { + var dtexp = Math.pow(10, Math.floor( + Math.log(dtFactor) / Math.LN10)) + dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]) // if the contours are at round multiples, reset tick0 // so they're still at round multiples. Otherwise, // keep the first label on the first contour level - if((Math.abs(opts.levels.start) / + if ((Math.abs(opts.levels.start) / opts.levels.size + 1e-6) % 1 < 2e-6) { - cbAxisOut.tick0 = 0; - } - } - cbAxisOut.dtick = dtick; + cbAxisOut.tick0 = 0 } + } + cbAxisOut.dtick = dtick + } // set domain after init, because we may want to // allow it outside [0,1] - cbAxisOut.domain = [ - yBottomFrac + ypadFrac, - yBottomFrac + lenFrac - ypadFrac - ]; - cbAxisOut.setScale(); + cbAxisOut.domain = [ + yBottomFrac + ypadFrac, + yBottomFrac + lenFrac - ypadFrac + ] + cbAxisOut.setScale() // now draw the elements - var container = fullLayout._infolayer.selectAll('g.' + id).data([0]); - container.enter().append('g').classed(id, true) - .each(function() { - var s = d3.select(this); - s.append('rect').classed('cbbg', true); - s.append('g').classed('cbfills', true); - s.append('g').classed('cblines', true); - s.append('g').classed('cbaxis', true).classed('crisp', true); - s.append('g').classed('cbtitleunshift', true) - .append('g').classed('cbtitle', true); - s.append('rect').classed('cboutline', true); - s.select('.cbtitle').datum(0); - }); - container.attr('transform', 'translate(' + Math.round(gs.l) + - ',' + Math.round(gs.t) + ')'); + var container = fullLayout._infolayer.selectAll('g.' + id).data([0]) + container.enter().append('g').classed(id, true) + .each(function () { + var s = d3.select(this) + s.append('rect').classed('cbbg', true) + s.append('g').classed('cbfills', true) + s.append('g').classed('cblines', true) + s.append('g').classed('cbaxis', true).classed('crisp', true) + s.append('g').classed('cbtitleunshift', true) + .append('g').classed('cbtitle', true) + s.append('rect').classed('cboutline', true) + s.select('.cbtitle').datum(0) + }) + container.attr('transform', 'translate(' + Math.round(gs.l) + + ',' + Math.round(gs.t) + ')') // TODO: this opposite transform is a hack until we make it // more rational which items get this offset - var titleCont = container.select('.cbtitleunshift') + var titleCont = container.select('.cbtitleunshift') .attr('transform', 'translate(-' + Math.round(gs.l) + ',-' + - Math.round(gs.t) + ')'); + Math.round(gs.t) + ')') - cbAxisOut._axislayer = container.select('.cbaxis'); - var titleHeight = 0; - if(['top', 'bottom'].indexOf(opts.titleside) !== -1) { + cbAxisOut._axislayer = container.select('.cbaxis') + var titleHeight = 0 + if (['top', 'bottom'].indexOf(opts.titleside) !== -1) { // draw the title so we know how much room it needs // when we squish the axis. This one only applies to // top or bottom titles, not right side. - var x = gs.l + (opts.x + xpadFrac) * gs.w, - fontSize = cbAxisOut.titlefont.size, - y; - - if(opts.titleside === 'top') { - y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h + - gs.t + 3 + fontSize * 0.75; - } - else { - y = (1 - (yBottomFrac + ypadFrac)) * gs.h + - gs.t - 3 - fontSize * 0.25; - } - drawTitle(cbAxisOut._id + 'title', { - attributes: {x: x, y: y, 'text-anchor': 'start'} - }); - } + var x = gs.l + (opts.x + xpadFrac) * gs.w, + fontSize = cbAxisOut.titlefont.size, + y + + if (opts.titleside === 'top') { + y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h + + gs.t + 3 + fontSize * 0.75 + } else { + y = (1 - (yBottomFrac + ypadFrac)) * gs.h + + gs.t - 3 - fontSize * 0.25 + } + drawTitle(cbAxisOut._id + 'title', { + attributes: {x: x, y: y, 'text-anchor': 'start'} + }) + } - function drawAxis() { - if(['top', 'bottom'].indexOf(opts.titleside) !== -1) { + function drawAxis () { + if (['top', 'bottom'].indexOf(opts.titleside) !== -1) { // squish the axis top to make room for the title - var titleGroup = container.select('.cbtitle'), - titleText = titleGroup.select('text'), - titleTrans = + var titleGroup = container.select('.cbtitle'), + titleText = titleGroup.select('text'), + titleTrans = [-opts.outlinewidth / 2, opts.outlinewidth / 2], - mathJaxNode = titleGroup + mathJaxNode = titleGroup .select('.h' + cbAxisOut._id + 'title-math-group') .node(), - lineSize = 15.6; - if(titleText.node()) { - lineSize = - parseInt(titleText.style('font-size'), 10) * 1.3; - } - if(mathJaxNode) { - titleHeight = Drawing.bBox(mathJaxNode).height; - if(titleHeight > lineSize) { + lineSize = 15.6 + if (titleText.node()) { + lineSize = + parseInt(titleText.style('font-size'), 10) * 1.3 + } + if (mathJaxNode) { + titleHeight = Drawing.bBox(mathJaxNode).height + if (titleHeight > lineSize) { // not entirely sure how mathjax is doing // vertical alignment, but this seems to work. - titleTrans[1] -= (titleHeight - lineSize) / 2; - } - } - else if(titleText.node() && + titleTrans[1] -= (titleHeight - lineSize) / 2 + } + } else if (titleText.node() && !titleText.classed('js-placeholder')) { - titleHeight = Drawing.bBox( - titleGroup.node()).height; - } - if(titleHeight) { + titleHeight = Drawing.bBox( + titleGroup.node()).height + } + if (titleHeight) { // buffer btwn colorbar and title // TODO: configurable - titleHeight += 5; - - if(opts.titleside === 'top') { - cbAxisOut.domain[1] -= titleHeight / gs.h; - titleTrans[1] *= -1; - } - else { - cbAxisOut.domain[0] += titleHeight / gs.h; - var nlines = Math.max(1, - titleText.selectAll('tspan.line').size()); - titleTrans[1] += (1 - nlines) * lineSize; - } - - titleGroup.attr('transform', - 'translate(' + titleTrans + ')'); - - cbAxisOut.setScale(); - } - } - - container.selectAll('.cbfills,.cblines,.cbaxis') + titleHeight += 5 + + if (opts.titleside === 'top') { + cbAxisOut.domain[1] -= titleHeight / gs.h + titleTrans[1] *= -1 + } else { + cbAxisOut.domain[0] += titleHeight / gs.h + var nlines = Math.max(1, + titleText.selectAll('tspan.line').size()) + titleTrans[1] += (1 - nlines) * lineSize + } + + titleGroup.attr('transform', + 'translate(' + titleTrans + ')') + + cbAxisOut.setScale() + } + } + + container.selectAll('.cbfills,.cblines,.cbaxis') .attr('transform', 'translate(0,' + - Math.round(gs.h * (1 - cbAxisOut.domain[1])) + ')'); + Math.round(gs.h * (1 - cbAxisOut.domain[1])) + ')') - var fills = container.select('.cbfills') + var fills = container.select('.cbfills') .selectAll('rect.cbfill') - .data(filllevels); - fills.enter().append('rect') + .data(filllevels) + fills.enter().append('rect') .classed('cbfill', true) - .style('stroke', 'none'); - fills.exit().remove(); - fills.each(function(d, i) { - var z = [ - (i === 0) ? zrange[0] : + .style('stroke', 'none') + fills.exit().remove() + fills.each(function (d, i) { + var z = [ + (i === 0) ? zrange[0] : (filllevels[i] + filllevels[i - 1]) / 2, - (i === filllevels.length - 1) ? zrange[1] : + (i === filllevels.length - 1) ? zrange[1] : (filllevels[i] + filllevels[i + 1]) / 2 - ] + ] .map(cbAxisOut.c2p) - .map(Math.round); + .map(Math.round) // offset the side adjoining the next rectangle so they // overlap, to prevent antialiasing gaps - if(i !== filllevels.length - 1) { - z[1] += (z[1] > z[0]) ? 1 : -1; - } - + if (i !== filllevels.length - 1) { + z[1] += (z[1] > z[0]) ? 1 : -1 + } // Tinycolor can't handle exponents and // at this scale, removing it makes no difference. - var colorString = fillcolormap(d).replace('e-', ''), - opaqueColor = tinycolor(colorString).toHexString(); + var colorString = fillcolormap(d).replace('e-', ''), + opaqueColor = tinycolor(colorString).toHexString() // Colorbar cannot currently support opacities so we // use an opaque fill even when alpha channels present - d3.select(this).attr({ - x: xLeft, - width: Math.max(thickPx, 2), - y: d3.min(z), - height: Math.max(d3.max(z) - d3.min(z), 2), - fill: opaqueColor - }); - }); - - var lines = container.select('.cblines') + d3.select(this).attr({ + x: xLeft, + width: Math.max(thickPx, 2), + y: d3.min(z), + height: Math.max(d3.max(z) - d3.min(z), 2), + fill: opaqueColor + }) + }) + + var lines = container.select('.cblines') .selectAll('path.cbline') .data(opts.line.color && opts.line.width ? - linelevels : []); - lines.enter().append('path') - .classed('cbline', true); - lines.exit().remove(); - lines.each(function(d) { - d3.select(this) + linelevels : []) + lines.enter().append('path') + .classed('cbline', true) + lines.exit().remove() + lines.each(function (d) { + d3.select(this) .attr('d', 'M' + xLeft + ',' + (Math.round(cbAxisOut.c2p(d)) + (opts.line.width / 2) % 1) + 'h' + thickPx) .call(Drawing.lineGroupStyle, - opts.line.width, linecolormap(d), opts.line.dash); - }); + opts.line.width, linecolormap(d), opts.line.dash) + }) // force full redraw of labels and ticks - cbAxisOut._axislayer.selectAll('g.' + cbAxisOut._id + 'tick,path') - .remove(); + cbAxisOut._axislayer.selectAll('g.' + cbAxisOut._id + 'tick,path') + .remove() - cbAxisOut._pos = xLeft + thickPx + - (opts.outlinewidth||0) / 2 - (opts.ticks === 'outside' ? 1 : 0); - cbAxisOut.side = 'right'; + cbAxisOut._pos = xLeft + thickPx + + (opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0) + cbAxisOut.side = 'right' // separate out axis and title drawing, // so we don't need such complicated logic in Titles.draw // if title is on the top or bottom, we've already drawn it // this title call only handles side=right - return Lib.syncOrAsync([ - function() { - return Axes.doTicks(gd, cbAxisOut, true); - }, - function() { - if(['top', 'bottom'].indexOf(opts.titleside) === -1) { - var fontSize = cbAxisOut.titlefont.size, - y = cbAxisOut._offset + cbAxisOut._length / 2, - x = gs.l + (cbAxisOut.position || 0) * gs.w + ((cbAxisOut.side === 'right') ? + return Lib.syncOrAsync([ + function () { + return Axes.doTicks(gd, cbAxisOut, true) + }, + function () { + if (['top', 'bottom'].indexOf(opts.titleside) === -1) { + var fontSize = cbAxisOut.titlefont.size, + y = cbAxisOut._offset + cbAxisOut._length / 2, + x = gs.l + (cbAxisOut.position || 0) * gs.w + ((cbAxisOut.side === 'right') ? 10 + fontSize * ((cbAxisOut.showticklabels ? 1 : 0.5)) : - -10 - fontSize * ((cbAxisOut.showticklabels ? 0.5 : 0))); + -10 - fontSize * ((cbAxisOut.showticklabels ? 0.5 : 0))) // the 'h' + is a hack to get around the fact that // convertToTspans rotates any 'y...' class by 90 degrees. // TODO: find a better way to control this. - drawTitle('h' + cbAxisOut._id + 'title', { - avoid: { - selection: d3.select(gd).selectAll('g.' + cbAxisOut._id + 'tick'), - side: opts.titleside, - offsetLeft: gs.l, - offsetTop: gs.t, - maxShift: fullLayout.width - }, - attributes: {x: x, y: y, 'text-anchor': 'middle'}, - transform: {rotate: '-90', offset: 0} - }); - } - }]); - } + drawTitle('h' + cbAxisOut._id + 'title', { + avoid: { + selection: d3.select(gd).selectAll('g.' + cbAxisOut._id + 'tick'), + side: opts.titleside, + offsetLeft: gs.l, + offsetTop: gs.t, + maxShift: fullLayout.width + }, + attributes: {x: x, y: y, 'text-anchor': 'middle'}, + transform: {rotate: '-90', offset: 0} + }) + } + }]) + } - function drawTitle(titleClass, titleOpts) { - var trace = getTrace(), - propName; - if(Registry.traceIs(trace, 'markerColorscale')) { - propName = 'marker.colorbar.title'; - } - else propName = 'colorbar.title'; - - var dfltTitleOpts = { - propContainer: cbAxisOut, - propName: propName, - traceIndex: trace.index, - dfltName: 'colorscale', - containerGroup: container.select('.cbtitle') - }; + function drawTitle (titleClass, titleOpts) { + var trace = getTrace(), + propName + if (Registry.traceIs(trace, 'markerColorscale')) { + propName = 'marker.colorbar.title' + } else propName = 'colorbar.title' + + var dfltTitleOpts = { + propContainer: cbAxisOut, + propName: propName, + traceIndex: trace.index, + dfltName: 'colorscale', + containerGroup: container.select('.cbtitle') + } // this class-to-rotate thing with convertToTspans is // getting hackier and hackier... delete groups with the // wrong class (in case earlier the colorbar was drawn on // a different side, I think?) - var otherClass = titleClass.charAt(0) === 'h' ? - titleClass.substr(1) : ('h' + titleClass); - container.selectAll('.' + otherClass + ',.' + otherClass + '-math-group') - .remove(); + var otherClass = titleClass.charAt(0) === 'h' ? + titleClass.substr(1) : ('h' + titleClass) + container.selectAll('.' + otherClass + ',.' + otherClass + '-math-group') + .remove() - Titles.draw(gd, titleClass, - extendFlat(dfltTitleOpts, titleOpts || {})); - } + Titles.draw(gd, titleClass, + extendFlat(dfltTitleOpts, titleOpts || {})) + } - function positionCB() { + function positionCB () { // wait for the axis & title to finish rendering before // continuing positioning // TODO: why are we redrawing multiple times now with this? // I guess autoMargin doesn't like being post-promise? - var innerWidth = thickPx + opts.outlinewidth / 2 + - Drawing.bBox(cbAxisOut._axislayer.node()).width; - titleEl = titleCont.select('text'); - if(titleEl.node() && !titleEl.classed('js-placeholder')) { - var mathJaxNode = titleCont + var innerWidth = thickPx + opts.outlinewidth / 2 + + Drawing.bBox(cbAxisOut._axislayer.node()).width + titleEl = titleCont.select('text') + if (titleEl.node() && !titleEl.classed('js-placeholder')) { + var mathJaxNode = titleCont .select('.h' + cbAxisOut._id + 'title-math-group') .node(), - titleWidth; - if(mathJaxNode && + titleWidth + if (mathJaxNode && ['top', 'bottom'].indexOf(opts.titleside) !== -1) { - titleWidth = Drawing.bBox(mathJaxNode).width; - } - else { + titleWidth = Drawing.bBox(mathJaxNode).width + } else { // note: the formula below works for all titlesides, // (except for top/bottom mathjax, above) // but the weird gs.l is because the titleunshift // transform gets removed by Drawing.bBox - titleWidth = + titleWidth = Drawing.bBox(titleCont.node()).right - - xLeft - gs.l; - } - innerWidth = Math.max(innerWidth, titleWidth); - } + xLeft - gs.l + } + innerWidth = Math.max(innerWidth, titleWidth) + } - var outerwidth = 2 * opts.xpad + innerWidth + + var outerwidth = 2 * opts.xpad + innerWidth + opts.borderwidth + opts.outlinewidth / 2, - outerheight = yBottomPx - yTopPx; + outerheight = yBottomPx - yTopPx - container.select('.cbbg').attr({ - x: xLeft - opts.xpad - + container.select('.cbbg').attr({ + x: xLeft - opts.xpad - (opts.borderwidth + opts.outlinewidth) / 2, - y: yTopPx - yExtraPx, - width: Math.max(outerwidth, 2), - height: Math.max(outerheight + 2 * yExtraPx, 2) - }) + y: yTopPx - yExtraPx, + width: Math.max(outerwidth, 2), + height: Math.max(outerheight + 2 * yExtraPx, 2) + }) .call(Color.fill, opts.bgcolor) .call(Color.stroke, opts.bordercolor) - .style({'stroke-width': opts.borderwidth}); + .style({'stroke-width': opts.borderwidth}) - container.selectAll('.cboutline').attr({ - x: xLeft, - y: yTopPx + opts.ypad + + container.selectAll('.cboutline').attr({ + x: xLeft, + y: yTopPx + opts.ypad + (opts.titleside === 'top' ? titleHeight : 0), - width: Math.max(thickPx, 2), - height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2) - }) + width: Math.max(thickPx, 2), + height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2) + }) .call(Color.stroke, opts.outlinecolor) .style({ - fill: 'None', - 'stroke-width': opts.outlinewidth - }); + fill: 'None', + 'stroke-width': opts.outlinewidth + }) // fix positioning for xanchor!='left' - var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * - outerwidth; - container.attr('transform', - 'translate(' + (gs.l - xoffset) + ',' + gs.t + ')'); + var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * + outerwidth + container.attr('transform', + 'translate(' + (gs.l - xoffset) + ',' + gs.t + ')') // auto margin adjustment - Plots.autoMargin(gd, id, { - x: opts.x, - y: opts.y, - l: outerwidth * ({right: 1, center: 0.5}[opts.xanchor] || 0), - r: outerwidth * ({left: 1, center: 0.5}[opts.xanchor] || 0), - t: outerheight * ({bottom: 1, middle: 0.5}[opts.yanchor] || 0), - b: outerheight * ({top: 1, middle: 0.5}[opts.yanchor] || 0) - }); - } + Plots.autoMargin(gd, id, { + x: opts.x, + y: opts.y, + l: outerwidth * ({right: 1, center: 0.5}[opts.xanchor] || 0), + r: outerwidth * ({left: 1, center: 0.5}[opts.xanchor] || 0), + t: outerheight * ({bottom: 1, middle: 0.5}[opts.yanchor] || 0), + b: outerheight * ({top: 1, middle: 0.5}[opts.yanchor] || 0) + }) + } - var cbDone = Lib.syncOrAsync([ - Plots.previousPromises, - drawAxis, - Plots.previousPromises, - positionCB - ], gd); + var cbDone = Lib.syncOrAsync([ + Plots.previousPromises, + drawAxis, + Plots.previousPromises, + positionCB + ], gd) - if(cbDone && cbDone.then) (gd._promises || []).push(cbDone); + if (cbDone && cbDone.then) (gd._promises || []).push(cbDone) // dragging... - if(gd._context.editable) { - var t0, - xf, - yf; - - dragElement.init({ - element: container.node(), - prepFn: function() { - t0 = container.attr('transform'); - setCursor(container); - }, - moveFn: function(dx, dy) { - container.attr('transform', - t0 + ' ' + 'translate(' + dx + ',' + dy + ')'); - - xf = dragElement.align(xLeftFrac + (dx / gs.w), thickFrac, - 0, 1, opts.xanchor); - yf = dragElement.align(yBottomFrac - (dy / gs.h), lenFrac, - 0, 1, opts.yanchor); - - var csr = dragElement.getCursor(xf, yf, - opts.xanchor, opts.yanchor); - setCursor(container, csr); - }, - doneFn: function(dragged) { - setCursor(container); - - if(dragged && xf !== undefined && yf !== undefined) { - Plotly.restyle(gd, + if (gd._context.editable) { + var t0, + xf, + yf + + dragElement.init({ + element: container.node(), + prepFn: function () { + t0 = container.attr('transform') + setCursor(container) + }, + moveFn: function (dx, dy) { + container.attr('transform', + t0 + ' ' + 'translate(' + dx + ',' + dy + ')') + + xf = dragElement.align(xLeftFrac + (dx / gs.w), thickFrac, + 0, 1, opts.xanchor) + yf = dragElement.align(yBottomFrac - (dy / gs.h), lenFrac, + 0, 1, opts.yanchor) + + var csr = dragElement.getCursor(xf, yf, + opts.xanchor, opts.yanchor) + setCursor(container, csr) + }, + doneFn: function (dragged) { + setCursor(container) + + if (dragged && xf !== undefined && yf !== undefined) { + Plotly.restyle(gd, {'colorbar.x': xf, 'colorbar.y': yf}, - getTrace().index); - } - } - }); + getTrace().index) + } } - return cbDone; + }) } - - function getTrace() { - var idNum = id.substr(2), - i, - trace; - for(i = 0; i < gd._fullData.length; i++) { - trace = gd._fullData[i]; - if(trace.uid === idNum) return trace; - } + return cbDone + } + + function getTrace () { + var idNum = id.substr(2), + i, + trace + for (i = 0; i < gd._fullData.length; i++) { + trace = gd._fullData[i] + if (trace.uid === idNum) return trace } + } // setter/getters for every item defined in opts - Object.keys(opts).forEach(function(name) { - component[name] = function(v) { + Object.keys(opts).forEach(function (name) { + component[name] = function (v) { // getter - if(!arguments.length) return opts[name]; + if (!arguments.length) return opts[name] // setter - for multi-part properties, // set only the parts that are provided - opts[name] = Lib.isPlainObject(opts[name]) ? + opts[name] = Lib.isPlainObject(opts[name]) ? Lib.extendFlat(opts[name], v) : - v; + v - return component; - }; - }); + return component + } + }) // or use .options to set multiple options at once via a dictionary - component.options = function(o) { - Object.keys(o).forEach(function(name) { + component.options = function (o) { + Object.keys(o).forEach(function (name) { // in case something random comes through // that's not an option, ignore it - if(typeof component[name] === 'function') { - component[name](o[name]); - } - }); - return component; - }; + if (typeof component[name] === 'function') { + component[name](o[name]) + } + }) + return component + } - component._opts = opts; + component._opts = opts - return component; -}; + return component +} diff --git a/src/components/colorbar/has_colorbar.js b/src/components/colorbar/has_colorbar.js index fb32bc8b6cc..5923441ab54 100644 --- a/src/components/colorbar/has_colorbar.js +++ b/src/components/colorbar/has_colorbar.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); - - -module.exports = function hasColorbar(container) { - return Lib.isPlainObject(container.colorbar); -}; +module.exports = function hasColorbar (container) { + return Lib.isPlainObject(container.colorbar) +} diff --git a/src/components/colorbar/index.js b/src/components/colorbar/index.js index c0960b78f7c..440e4f4961a 100644 --- a/src/components/colorbar/index.js +++ b/src/components/colorbar/index.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +exports.attributes = require('./attributes') +exports.supplyDefaults = require('./defaults') -exports.attributes = require('./attributes'); +exports.draw = require('./draw') -exports.supplyDefaults = require('./defaults'); - -exports.draw = require('./draw'); - -exports.hasColorbar = require('./has_colorbar'); +exports.hasColorbar = require('./has_colorbar') diff --git a/src/components/colorscale/attributes.js b/src/components/colorscale/attributes.js index bbbfc60e9ff..cd47aaaf1c9 100644 --- a/src/components/colorscale/attributes.js +++ b/src/components/colorscale/attributes.js @@ -6,66 +6,66 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - zauto: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines the whether or not the color domain is computed', - 'with respect to the input data.' - ].join(' ') - }, - zmin: { - valType: 'number', - role: 'info', - dflt: null, - description: 'Sets the lower bound of color domain.' - }, - zmax: { - valType: 'number', - role: 'info', - dflt: null, - description: 'Sets the upper bound of color domain.' - }, - colorscale: { - valType: 'colorscale', - role: 'style', - description: [ - 'Sets the colorscale.', - 'The colorscale must be an array containing', - 'arrays mapping a normalized value to an', - 'rgb, rgba, hex, hsl, hsv, or named color string.', - 'At minimum, a mapping for the lowest (0) and highest (1)', - 'values are required. For example,', - '`[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.', - 'To control the bounds of the colorscale in z space,', - 'use zmin and zmax' - ].join(' ') - }, - autocolorscale: { - valType: 'boolean', - role: 'style', - dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp. - description: [ - 'Determines whether or not the colorscale is picked using the sign of', - 'the input z values.' - ].join(' ') - }, - reversescale: { - valType: 'boolean', - role: 'style', - dflt: false, - description: 'Reverses the colorscale.' - }, - showscale: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not a colorbar is displayed for this trace.' - ].join(' ') - } -}; + zauto: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines the whether or not the color domain is computed', + 'with respect to the input data.' + ].join(' ') + }, + zmin: { + valType: 'number', + role: 'info', + dflt: null, + description: 'Sets the lower bound of color domain.' + }, + zmax: { + valType: 'number', + role: 'info', + dflt: null, + description: 'Sets the upper bound of color domain.' + }, + colorscale: { + valType: 'colorscale', + role: 'style', + description: [ + 'Sets the colorscale.', + 'The colorscale must be an array containing', + 'arrays mapping a normalized value to an', + 'rgb, rgba, hex, hsl, hsv, or named color string.', + 'At minimum, a mapping for the lowest (0) and highest (1)', + 'values are required. For example,', + '`[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.', + 'To control the bounds of the colorscale in z space,', + 'use zmin and zmax' + ].join(' ') + }, + autocolorscale: { + valType: 'boolean', + role: 'style', + dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp. + description: [ + 'Determines whether or not the colorscale is picked using the sign of', + 'the input z values.' + ].join(' ') + }, + reversescale: { + valType: 'boolean', + role: 'style', + dflt: false, + description: 'Reverses the colorscale.' + }, + showscale: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not a colorbar is displayed for this trace.' + ].join(' ') + } +} diff --git a/src/components/colorscale/calc.js b/src/components/colorscale/calc.js index 8d095e8642d..733676d6732 100644 --- a/src/components/colorscale/calc.js +++ b/src/components/colorscale/calc.js @@ -6,53 +6,50 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var scales = require('./scales') +var flipScale = require('./flip_scale') -var scales = require('./scales'); -var flipScale = require('./flip_scale'); +module.exports = function calc (trace, vals, containerStr, cLetter) { + var container, inputContainer + if (containerStr) { + container = Lib.nestedProperty(trace, containerStr).get() + inputContainer = Lib.nestedProperty(trace._input, containerStr).get() + } else { + container = trace + inputContainer = trace._input + } -module.exports = function calc(trace, vals, containerStr, cLetter) { - var container, inputContainer; + var autoAttr = cLetter + 'auto', + minAttr = cLetter + 'min', + maxAttr = cLetter + 'max', + auto = container[autoAttr], + min = container[minAttr], + max = container[maxAttr], + scl = container.colorscale - if(containerStr) { - container = Lib.nestedProperty(trace, containerStr).get(); - inputContainer = Lib.nestedProperty(trace._input, containerStr).get(); - } - else { - container = trace; - inputContainer = trace._input; - } + if (auto !== false || min === undefined) { + min = Lib.aggNums(Math.min, null, vals) + } - var autoAttr = cLetter + 'auto', - minAttr = cLetter + 'min', - maxAttr = cLetter + 'max', - auto = container[autoAttr], - min = container[minAttr], - max = container[maxAttr], - scl = container.colorscale; + if (auto !== false || max === undefined) { + max = Lib.aggNums(Math.max, null, vals) + } - if(auto !== false || min === undefined) { - min = Lib.aggNums(Math.min, null, vals); - } + if (min === max) { + min -= 0.5 + max += 0.5 + } - if(auto !== false || max === undefined) { - max = Lib.aggNums(Math.max, null, vals); - } + container[minAttr] = min + container[maxAttr] = max - if(min === max) { - min -= 0.5; - max += 0.5; - } - - container[minAttr] = min; - container[maxAttr] = max; - - inputContainer[minAttr] = min; - inputContainer[maxAttr] = max; + inputContainer[minAttr] = min + inputContainer[maxAttr] = max /* * If auto was explicitly false but min or max was missing, @@ -61,17 +58,17 @@ module.exports = function calc(trace, vals, containerStr, cLetter) { * Otherwise make sure the trace still looks auto as far as later * changes are concerned. */ - inputContainer[autoAttr] = (auto !== false || - (min === undefined && max === undefined)); + inputContainer[autoAttr] = (auto !== false || + (min === undefined && max === undefined)) - if(container.autocolorscale) { - if(min * max < 0) scl = scales.RdBu; - else if(min >= 0) scl = scales.Reds; - else scl = scales.Blues; + if (container.autocolorscale) { + if (min * max < 0) scl = scales.RdBu + else if (min >= 0) scl = scales.Reds + else scl = scales.Blues // reversescale is handled at the containerOut level - inputContainer.colorscale = scl; - if(container.reversescale) scl = flipScale(scl); - container.colorscale = scl; - } -}; + inputContainer.colorscale = scl + if (container.reversescale) scl = flipScale(scl) + container.colorscale = scl + } +} diff --git a/src/components/colorscale/color_attributes.js b/src/components/colorscale/color_attributes.js index 9c8f6cdb065..99d720357f3 100644 --- a/src/components/colorscale/color_attributes.js +++ b/src/components/colorscale/color_attributes.js @@ -6,83 +6,83 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var colorScaleAttributes = require('./attributes'); -var extendDeep = require('../../lib/extend').extendDeep; -var palettes = require('./scales.js'); +var colorScaleAttributes = require('./attributes') +var extendDeep = require('../../lib/extend').extendDeep +var palettes = require('./scales.js') -module.exports = function makeColorScaleAttributes(context) { - return { - color: { - valType: 'color', - arrayOk: true, - role: 'style', - description: [ - 'Sets the ', context, ' color. It accepts either a specific color', - ' or an array of numbers that are mapped to the colorscale', - ' relative to the max and min values of the array or relative to', - ' `cmin` and `cmax` if set.' - ].join('') - }, - colorscale: extendDeep({}, colorScaleAttributes.colorscale, { - description: [ - 'Sets the colorscale and only has an effect', - ' if `', context, '.color` is set to a numerical array.', - ' The colorscale must be an array containing', - ' arrays mapping a normalized value to an', - ' rgb, rgba, hex, hsl, hsv, or named color string.', - ' At minimum, a mapping for the lowest (0) and highest (1)', - ' values are required. For example,', - ' `[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.', - ' To control the bounds of the colorscale in color space,', - ' use `', context, '.cmin` and `', context, '.cmax`.', - ' Alternatively, `colorscale` may be a palette name string', - ' of the following list: ' - ].join('').concat(Object.keys(palettes).join(', ')) - }), - cauto: extendDeep({}, colorScaleAttributes.zauto, { - description: [ - 'Has an effect only if `', context, '.color` is set to a numerical array', - ' and `cmin`, `cmax` are set by the user. In this case,', - ' it controls whether the range of colors in `colorscale` is mapped to', - ' the range of values in the `color` array (`cauto: true`), or the `cmin`/`cmax`', - ' values (`cauto: false`).', - ' Defaults to `false` when `cmin`, `cmax` are set by the user.' - ].join('') - }), - cmax: extendDeep({}, colorScaleAttributes.zmax, { - description: [ - 'Has an effect only if `', context, '.color` is set to a numerical array.', - ' Sets the upper bound of the color domain.', - ' Value should be associated to the `', context, '.color` array index,', - ' and if set, `', context, '.cmin` must be set as well.' - ].join('') - }), - cmin: extendDeep({}, colorScaleAttributes.zmin, { - description: [ - 'Has an effect only if `', context, '.color` is set to a numerical array.', - ' Sets the lower bound of the color domain.', - ' Value should be associated to the `', context, '.color` array index,', - ' and if set, `', context, '.cmax` must be set as well.' - ].join('') - }), - autocolorscale: extendDeep({}, colorScaleAttributes.autocolorscale, { - description: [ - 'Has an effect only if `', context, '.color` is set to a numerical array.', - ' Determines whether the colorscale is a default palette (`autocolorscale: true`)', - ' or the palette determined by `', context, '.colorscale`.', - ' In case `colorscale` is unspecified or `autocolorscale` is true, the default ', - ' palette will be chosen according to whether numbers in the `color` array are', - ' all positive, all negative or mixed.' - ].join('') - }), - reversescale: extendDeep({}, colorScaleAttributes.reversescale, { - description: [ - 'Has an effect only if `', context, '.color` is set to a numerical array.', - ' Reverses the color mapping if true (`cmin` will correspond to the last color', - ' in the array and `cmax` will correspond to the first color).' - ].join('') - }) - }; -}; +module.exports = function makeColorScaleAttributes (context) { + return { + color: { + valType: 'color', + arrayOk: true, + role: 'style', + description: [ + 'Sets the ', context, ' color. It accepts either a specific color', + ' or an array of numbers that are mapped to the colorscale', + ' relative to the max and min values of the array or relative to', + ' `cmin` and `cmax` if set.' + ].join('') + }, + colorscale: extendDeep({}, colorScaleAttributes.colorscale, { + description: [ + 'Sets the colorscale and only has an effect', + ' if `', context, '.color` is set to a numerical array.', + ' The colorscale must be an array containing', + ' arrays mapping a normalized value to an', + ' rgb, rgba, hex, hsl, hsv, or named color string.', + ' At minimum, a mapping for the lowest (0) and highest (1)', + ' values are required. For example,', + ' `[[0, \'rgb(0,0,255)\', [1, \'rgb(255,0,0)\']]`.', + ' To control the bounds of the colorscale in color space,', + ' use `', context, '.cmin` and `', context, '.cmax`.', + ' Alternatively, `colorscale` may be a palette name string', + ' of the following list: ' + ].join('').concat(Object.keys(palettes).join(', ')) + }), + cauto: extendDeep({}, colorScaleAttributes.zauto, { + description: [ + 'Has an effect only if `', context, '.color` is set to a numerical array', + ' and `cmin`, `cmax` are set by the user. In this case,', + ' it controls whether the range of colors in `colorscale` is mapped to', + ' the range of values in the `color` array (`cauto: true`), or the `cmin`/`cmax`', + ' values (`cauto: false`).', + ' Defaults to `false` when `cmin`, `cmax` are set by the user.' + ].join('') + }), + cmax: extendDeep({}, colorScaleAttributes.zmax, { + description: [ + 'Has an effect only if `', context, '.color` is set to a numerical array.', + ' Sets the upper bound of the color domain.', + ' Value should be associated to the `', context, '.color` array index,', + ' and if set, `', context, '.cmin` must be set as well.' + ].join('') + }), + cmin: extendDeep({}, colorScaleAttributes.zmin, { + description: [ + 'Has an effect only if `', context, '.color` is set to a numerical array.', + ' Sets the lower bound of the color domain.', + ' Value should be associated to the `', context, '.color` array index,', + ' and if set, `', context, '.cmax` must be set as well.' + ].join('') + }), + autocolorscale: extendDeep({}, colorScaleAttributes.autocolorscale, { + description: [ + 'Has an effect only if `', context, '.color` is set to a numerical array.', + ' Determines whether the colorscale is a default palette (`autocolorscale: true`)', + ' or the palette determined by `', context, '.colorscale`.', + ' In case `colorscale` is unspecified or `autocolorscale` is true, the default ', + ' palette will be chosen according to whether numbers in the `color` array are', + ' all positive, all negative or mixed.' + ].join('') + }), + reversescale: extendDeep({}, colorScaleAttributes.reversescale, { + description: [ + 'Has an effect only if `', context, '.color` is set to a numerical array.', + ' Reverses the color mapping if true (`cmin` will correspond to the last color', + ' in the array and `cmax` will correspond to the first color).' + ].join('') + }) + } +} diff --git a/src/components/colorscale/default_scale.js b/src/components/colorscale/default_scale.js index 286663dac37..0c94200204b 100644 --- a/src/components/colorscale/default_scale.js +++ b/src/components/colorscale/default_scale.js @@ -6,9 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scales = require('./scales'); +var scales = require('./scales') - -module.exports = scales.RdBu; +module.exports = scales.RdBu diff --git a/src/components/colorscale/defaults.js b/src/components/colorscale/defaults.js index 55444bb4094..3a0075f5f44 100644 --- a/src/components/colorscale/defaults.js +++ b/src/components/colorscale/defaults.js @@ -6,57 +6,55 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') -var Lib = require('../../lib'); +var hasColorbar = require('../colorbar/has_colorbar') +var colorbarDefaults = require('../colorbar/defaults') +var isValidScale = require('./is_valid_scale') +var flipScale = require('./flip_scale') -var hasColorbar = require('../colorbar/has_colorbar'); -var colorbarDefaults = require('../colorbar/defaults'); -var isValidScale = require('./is_valid_scale'); -var flipScale = require('./flip_scale'); - - -module.exports = function colorScaleDefaults(traceIn, traceOut, layout, coerce, opts) { - var prefix = opts.prefix, - cLetter = opts.cLetter, - containerStr = prefix.slice(0, prefix.length - 1), - containerIn = prefix ? +module.exports = function colorScaleDefaults (traceIn, traceOut, layout, coerce, opts) { + var prefix = opts.prefix, + cLetter = opts.cLetter, + containerStr = prefix.slice(0, prefix.length - 1), + containerIn = prefix ? Lib.nestedProperty(traceIn, containerStr).get() || {} : traceIn, - containerOut = prefix ? + containerOut = prefix ? Lib.nestedProperty(traceOut, containerStr).get() || {} : traceOut, - minIn = containerIn[cLetter + 'min'], - maxIn = containerIn[cLetter + 'max'], - sclIn = containerIn.colorscale; + minIn = containerIn[cLetter + 'min'], + maxIn = containerIn[cLetter + 'max'], + sclIn = containerIn.colorscale - var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn); - coerce(prefix + cLetter + 'auto', !validMinMax); - coerce(prefix + cLetter + 'min'); - coerce(prefix + cLetter + 'max'); + var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn) + coerce(prefix + cLetter + 'auto', !validMinMax) + coerce(prefix + cLetter + 'min') + coerce(prefix + cLetter + 'max') // handles both the trace case (autocolorscale is false by default) and // the marker and marker.line case (autocolorscale is true by default) - var autoColorscaleDftl; - if(sclIn !== undefined) autoColorscaleDftl = !isValidScale(sclIn); - coerce(prefix + 'autocolorscale', autoColorscaleDftl); - var sclOut = coerce(prefix + 'colorscale'); + var autoColorscaleDftl + if (sclIn !== undefined) autoColorscaleDftl = !isValidScale(sclIn) + coerce(prefix + 'autocolorscale', autoColorscaleDftl) + var sclOut = coerce(prefix + 'colorscale') // reversescale is handled at the containerOut level - var reverseScale = coerce(prefix + 'reversescale'); - if(reverseScale) containerOut.colorscale = flipScale(sclOut); + var reverseScale = coerce(prefix + 'reversescale') + if (reverseScale) containerOut.colorscale = flipScale(sclOut) // ... until Scatter.colorbar can handle marker line colorbars - if(prefix === 'marker.line.') return; + if (prefix === 'marker.line.') return // handle both the trace case where the dflt is listed in attributes and // the marker case where the dflt is determined by hasColorbar - var showScaleDftl; - if(prefix) showScaleDftl = hasColorbar(containerIn); - var showScale = coerce(prefix + 'showscale', showScaleDftl); + var showScaleDftl + if (prefix) showScaleDftl = hasColorbar(containerIn) + var showScale = coerce(prefix + 'showscale', showScaleDftl) - if(showScale) colorbarDefaults(containerIn, containerOut, layout); -}; + if (showScale) colorbarDefaults(containerIn, containerOut, layout) +} diff --git a/src/components/colorscale/extract_scale.js b/src/components/colorscale/extract_scale.js index d1e3c83d4ff..2e5c053b503 100644 --- a/src/components/colorscale/extract_scale.js +++ b/src/components/colorscale/extract_scale.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /** * Extract colorscale into numeric domain and color range. @@ -16,20 +15,20 @@ * @param {number} cmin minimum color value (used to clamp scale) * @param {number} cmax maximum color value (used to clamp scale) */ -module.exports = function extractScale(scl, cmin, cmax) { - var N = scl.length, - domain = new Array(N), - range = new Array(N); +module.exports = function extractScale (scl, cmin, cmax) { + var N = scl.length, + domain = new Array(N), + range = new Array(N) - for(var i = 0; i < N; i++) { - var si = scl[i]; + for (var i = 0; i < N; i++) { + var si = scl[i] - domain[i] = cmin + si[0] * (cmax - cmin); - range[i] = si[1]; - } + domain[i] = cmin + si[0] * (cmax - cmin) + range[i] = si[1] + } - return { - domain: domain, - range: range - }; -}; + return { + domain: domain, + range: range + } +} diff --git a/src/components/colorscale/flip_scale.js b/src/components/colorscale/flip_scale.js index 5e974846ea6..7cb8899875f 100644 --- a/src/components/colorscale/flip_scale.js +++ b/src/components/colorscale/flip_scale.js @@ -6,18 +6,17 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +module.exports = function flipScale (scl) { + var N = scl.length, + sclNew = new Array(N), + si -module.exports = function flipScale(scl) { - var N = scl.length, - sclNew = new Array(N), - si; + for (var i = N - 1, j = 0; i >= 0; i--, j++) { + si = scl[i] + sclNew[j] = [1 - si[0], si[1]] + } - for(var i = N - 1, j = 0; i >= 0; i--, j++) { - si = scl[i]; - sclNew[j] = [1 - si[0], si[1]]; - } - - return sclNew; -}; + return sclNew +} diff --git a/src/components/colorscale/get_scale.js b/src/components/colorscale/get_scale.js index 1f88c328a42..c2248220252 100644 --- a/src/components/colorscale/get_scale.js +++ b/src/components/colorscale/get_scale.js @@ -6,33 +6,30 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scales = require('./scales') +var defaultScale = require('./default_scale') +var isValidScaleArray = require('./is_valid_scale_array') -var scales = require('./scales'); -var defaultScale = require('./default_scale'); -var isValidScaleArray = require('./is_valid_scale_array'); +module.exports = function getScale (scl, dflt) { + if (!dflt) dflt = defaultScale + if (!scl) return dflt - -module.exports = function getScale(scl, dflt) { - if(!dflt) dflt = defaultScale; - if(!scl) return dflt; - - function parseScale() { - try { - scl = scales[scl] || JSON.parse(scl); - } - catch(e) { - scl = dflt; - } + function parseScale () { + try { + scl = scales[scl] || JSON.parse(scl) + } catch (e) { + scl = dflt } + } - if(typeof scl === 'string') { - parseScale(); + if (typeof scl === 'string') { + parseScale() // occasionally scl is double-JSON encoded... - if(typeof scl === 'string') parseScale(); - } + if (typeof scl === 'string') parseScale() + } - if(!isValidScaleArray(scl)) return dflt; - return scl; -}; + if (!isValidScaleArray(scl)) return dflt + return scl +} diff --git a/src/components/colorscale/has_colorscale.js b/src/components/colorscale/has_colorscale.js index 2744e956442..8d8de34ccca 100644 --- a/src/components/colorscale/has_colorscale.js +++ b/src/components/colorscale/has_colorscale.js @@ -6,33 +6,31 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') -var Lib = require('../../lib'); +var isValidScale = require('./is_valid_scale') -var isValidScale = require('./is_valid_scale'); - - -module.exports = function hasColorscale(trace, containerStr) { - var container = containerStr ? +module.exports = function hasColorscale (trace, containerStr) { + var container = containerStr ? Lib.nestedProperty(trace, containerStr).get() || {} : trace, - color = container.color, - isArrayWithOneNumber = false; - - if(Array.isArray(color)) { - for(var i = 0; i < color.length; i++) { - if(isNumeric(color[i])) { - isArrayWithOneNumber = true; - break; - } - } + color = container.color, + isArrayWithOneNumber = false + + if (Array.isArray(color)) { + for (var i = 0; i < color.length; i++) { + if (isNumeric(color[i])) { + isArrayWithOneNumber = true + break + } } + } - return ( + return ( Lib.isPlainObject(container) && ( isArrayWithOneNumber || container.showscale === true || @@ -40,5 +38,5 @@ module.exports = function hasColorscale(trace, containerStr) { isValidScale(container.colorscale) || Lib.isPlainObject(container.colorbar) ) - ); -}; + ) +} diff --git a/src/components/colorscale/index.js b/src/components/colorscale/index.js index 0e07e23c32c..2784d35530a 100644 --- a/src/components/colorscale/index.js +++ b/src/components/colorscale/index.js @@ -6,27 +6,26 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +exports.scales = require('./scales') -exports.scales = require('./scales'); +exports.defaultScale = require('./default_scale') -exports.defaultScale = require('./default_scale'); +exports.attributes = require('./attributes') -exports.attributes = require('./attributes'); +exports.handleDefaults = require('./defaults') -exports.handleDefaults = require('./defaults'); +exports.calc = require('./calc') -exports.calc = require('./calc'); +exports.hasColorscale = require('./has_colorscale') -exports.hasColorscale = require('./has_colorscale'); +exports.isValidScale = require('./is_valid_scale') -exports.isValidScale = require('./is_valid_scale'); +exports.getScale = require('./get_scale') -exports.getScale = require('./get_scale'); +exports.flipScale = require('./flip_scale') -exports.flipScale = require('./flip_scale'); +exports.extractScale = require('./extract_scale') -exports.extractScale = require('./extract_scale'); - -exports.makeColorScaleFunc = require('./make_color_scale_func'); +exports.makeColorScaleFunc = require('./make_color_scale_func') diff --git a/src/components/colorscale/is_valid_scale.js b/src/components/colorscale/is_valid_scale.js index f3137486694..bd6ea6e3069 100644 --- a/src/components/colorscale/is_valid_scale.js +++ b/src/components/colorscale/is_valid_scale.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scales = require('./scales') +var isValidScaleArray = require('./is_valid_scale_array') -var scales = require('./scales'); -var isValidScaleArray = require('./is_valid_scale_array'); - - -module.exports = function isValidScale(scl) { - if(scales[scl] !== undefined) return true; - else return isValidScaleArray(scl); -}; +module.exports = function isValidScale (scl) { + if (scales[scl] !== undefined) return true + else return isValidScaleArray(scl) +} diff --git a/src/components/colorscale/is_valid_scale_array.js b/src/components/colorscale/is_valid_scale_array.js index 324b576b50f..5b8117bdd4b 100644 --- a/src/components/colorscale/is_valid_scale_array.js +++ b/src/components/colorscale/is_valid_scale_array.js @@ -6,30 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var tinycolor = require('tinycolor2') -var tinycolor = require('tinycolor2'); +module.exports = function isValidScaleArray (scl) { + var highestVal = 0 + if (!Array.isArray(scl) || scl.length < 2) return false -module.exports = function isValidScaleArray(scl) { - var highestVal = 0; + if (!scl[0] || !scl[scl.length - 1]) return false - if(!Array.isArray(scl) || scl.length < 2) return false; + if (+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false - if(!scl[0] || !scl[scl.length - 1]) return false; + for (var i = 0; i < scl.length; i++) { + var si = scl[i] - if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false; - - for(var i = 0; i < scl.length; i++) { - var si = scl[i]; - - if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) { - return false; - } - - highestVal = +si[0]; + if (si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) { + return false } - return true; -}; + highestVal = +si[0] + } + + return true +} diff --git a/src/components/colorscale/make_color_scale_func.js b/src/components/colorscale/make_color_scale_func.js index 562e104b00a..68e30bf26c3 100644 --- a/src/components/colorscale/make_color_scale_func.js +++ b/src/components/colorscale/make_color_scale_func.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var tinycolor = require('tinycolor2') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var tinycolor = require('tinycolor2'); -var isNumeric = require('fast-isnumeric'); - -var Color = require('../color'); +var Color = require('../color') /** * General colorscale function generator. @@ -28,67 +27,64 @@ var Color = require('../color'); * * @return {function} */ -module.exports = function makeColorScaleFunc(specs, opts) { - opts = opts || {}; +module.exports = function makeColorScaleFunc (specs, opts) { + opts = opts || {} - var domain = specs.domain, - range = specs.range, - N = range.length, - _range = new Array(N); + var domain = specs.domain, + range = specs.range, + N = range.length, + _range = new Array(N) - for(var i = 0; i < N; i++) { - var rgba = tinycolor(range[i]).toRgb(); - _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a]; - } + for (var i = 0; i < N; i++) { + var rgba = tinycolor(range[i]).toRgb() + _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a] + } - var _sclFunc = d3.scale.linear() + var _sclFunc = d3.scale.linear() .domain(domain) .range(_range) - .clamp(true); + .clamp(true) - var noNumericCheck = opts.noNumericCheck, - returnArray = opts.returnArray, - sclFunc; + var noNumericCheck = opts.noNumericCheck, + returnArray = opts.returnArray, + sclFunc - if(noNumericCheck && returnArray) { - sclFunc = _sclFunc; + if (noNumericCheck && returnArray) { + sclFunc = _sclFunc + } else if (noNumericCheck) { + sclFunc = function (v) { + return colorArray2rbga(_sclFunc(v)) } - else if(noNumericCheck) { - sclFunc = function(v) { - return colorArray2rbga(_sclFunc(v)); - }; + } else if (returnArray) { + sclFunc = function (v) { + if (isNumeric(v)) return _sclFunc(v) + else if (tinycolor(v).isValid()) return v + else return Color.defaultLine } - else if(returnArray) { - sclFunc = function(v) { - if(isNumeric(v)) return _sclFunc(v); - else if(tinycolor(v).isValid()) return v; - else return Color.defaultLine; - }; - } - else { - sclFunc = function(v) { - if(isNumeric(v)) return colorArray2rbga(_sclFunc(v)); - else if(tinycolor(v).isValid()) return v; - else return Color.defaultLine; - }; + } else { + sclFunc = function (v) { + if (isNumeric(v)) return colorArray2rbga(_sclFunc(v)) + else if (tinycolor(v).isValid()) return v + else return Color.defaultLine } + } // colorbar draw looks into the d3 scale closure for domain and range - sclFunc.domain = _sclFunc.domain; + sclFunc.domain = _sclFunc.domain - sclFunc.range = function() { return range; }; + sclFunc.range = function () { return range } - return sclFunc; -}; + return sclFunc +} -function colorArray2rbga(colorArray) { - var colorObj = { - r: colorArray[0], - g: colorArray[1], - b: colorArray[2], - a: colorArray[3] - }; +function colorArray2rbga (colorArray) { + var colorObj = { + r: colorArray[0], + g: colorArray[1], + b: colorArray[2], + a: colorArray[3] + } - return tinycolor(colorObj).toRgbString(); + return tinycolor(colorObj).toRgbString() } diff --git a/src/components/colorscale/scales.js b/src/components/colorscale/scales.js index 1993b937264..39718b5cd1d 100644 --- a/src/components/colorscale/scales.js +++ b/src/components/colorscale/scales.js @@ -6,116 +6,115 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - 'Greys': [ + 'Greys': [ [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)'] - ], + ], - 'YlGnBu': [ + 'YlGnBu': [ [0, 'rgb(8,29,88)'], [0.125, 'rgb(37,52,148)'], [0.25, 'rgb(34,94,168)'], [0.375, 'rgb(29,145,192)'], [0.5, 'rgb(65,182,196)'], [0.625, 'rgb(127,205,187)'], [0.75, 'rgb(199,233,180)'], [0.875, 'rgb(237,248,217)'], [1, 'rgb(255,255,217)'] - ], + ], - 'Greens': [ + 'Greens': [ [0, 'rgb(0,68,27)'], [0.125, 'rgb(0,109,44)'], [0.25, 'rgb(35,139,69)'], [0.375, 'rgb(65,171,93)'], [0.5, 'rgb(116,196,118)'], [0.625, 'rgb(161,217,155)'], [0.75, 'rgb(199,233,192)'], [0.875, 'rgb(229,245,224)'], [1, 'rgb(247,252,245)'] - ], + ], - 'YlOrRd': [ + 'YlOrRd': [ [0, 'rgb(128,0,38)'], [0.125, 'rgb(189,0,38)'], [0.25, 'rgb(227,26,28)'], [0.375, 'rgb(252,78,42)'], [0.5, 'rgb(253,141,60)'], [0.625, 'rgb(254,178,76)'], [0.75, 'rgb(254,217,118)'], [0.875, 'rgb(255,237,160)'], [1, 'rgb(255,255,204)'] - ], + ], - 'Bluered': [ + 'Bluered': [ [0, 'rgb(0,0,255)'], [1, 'rgb(255,0,0)'] - ], + ], // modified RdBu based on // www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf - 'RdBu': [ + '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': [ + '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': [ + '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': [ + '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': [ + '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': [ + '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': [ + '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': [ + 'Hot': [ [0, 'rgb(0,0,0)'], [0.3, 'rgb(230,0,0)'], [0.6, 'rgb(255,210,0)'], [1, 'rgb(255,255,255)'] - ], + ], - 'Blackbody': [ + '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': [ + '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': [ + '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': [ + 'Viridis': [ [0, '#440154'], [0.06274509803921569, '#48186a'], [0.12549019607843137, '#472d7b'], [0.18823529411764706, '#424086'], [0.25098039215686274, '#3b528b'], [0.3137254901960784, '#33638d'], @@ -125,5 +124,5 @@ module.exports = { [0.7529411764705882, '#5ec962'], [0.8156862745098039, '#84d44b'], [0.8784313725490196, '#addc30'], [0.9411764705882353, '#d8e219'], [1, '#fde725'] - ] -}; + ] +} diff --git a/src/components/dragelement/align.js b/src/components/dragelement/align.js index 9503473ed6c..43effdf233e 100644 --- a/src/components/dragelement/align.js +++ b/src/components/dragelement/align.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' // for automatic alignment on dragging, <1/3 means left align, // >2/3 means right, and between is center. Pick the right fraction // based on where you are, and return the fraction corresponding to // that position on the object -module.exports = function align(v, dv, v0, v1, anchor) { - var vmin = (v - v0) / (v1 - v0), - vmax = vmin + dv / (v1 - v0), - vc = (vmin + vmax) / 2; +module.exports = function align (v, dv, v0, v1, anchor) { + var vmin = (v - v0) / (v1 - v0), + vmax = vmin + dv / (v1 - v0), + vc = (vmin + vmax) / 2 // explicitly specified anchor - if(anchor === 'left' || anchor === 'bottom') return vmin; - if(anchor === 'center' || anchor === 'middle') return vc; - if(anchor === 'right' || anchor === 'top') return vmax; + if (anchor === 'left' || anchor === 'bottom') return vmin + if (anchor === 'center' || anchor === 'middle') return vc + if (anchor === 'right' || anchor === 'top') return vmax // automatic based on position - if(vmin < (2 / 3) - vc) return vmin; - if(vmax > (4 / 3) - vc) return vmax; - return vc; -}; + if (vmin < (2 / 3) - vc) return vmin + if (vmax > (4 / 3) - vc) return vmax + return vc +} diff --git a/src/components/dragelement/cursor.js b/src/components/dragelement/cursor.js index 5601c8aaa30..7cac7a2d3b1 100644 --- a/src/components/dragelement/cursor.js +++ b/src/components/dragelement/cursor.js @@ -6,11 +6,9 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - +var Lib = require('../../lib') // set cursors pointing toward the closest corner/side, // to indicate alignment @@ -19,18 +17,18 @@ var cursorset = [ ['sw-resize', 's-resize', 'se-resize'], ['w-resize', 'move', 'e-resize'], ['nw-resize', 'n-resize', 'ne-resize'] -]; +] -module.exports = function getCursor(x, y, xanchor, yanchor) { - if(xanchor === 'left') x = 0; - else if(xanchor === 'center') x = 1; - else if(xanchor === 'right') x = 2; - else x = Lib.constrain(Math.floor(x * 3), 0, 2); +module.exports = function getCursor (x, y, xanchor, yanchor) { + if (xanchor === 'left') x = 0 + else if (xanchor === 'center') x = 1 + else if (xanchor === 'right') x = 2 + else x = Lib.constrain(Math.floor(x * 3), 0, 2) - if(yanchor === 'bottom') y = 0; - else if(yanchor === 'middle') y = 1; - else if(yanchor === 'top') y = 2; - else y = Lib.constrain(Math.floor(y * 3), 0, 2); + if (yanchor === 'bottom') y = 0 + else if (yanchor === 'middle') y = 1 + else if (yanchor === 'top') y = 2 + else y = Lib.constrain(Math.floor(y * 3), 0, 2) - return cursorset[y][x]; -}; + return cursorset[y][x] +} diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 8b531a1b5a8..1055835695b 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -6,23 +6,21 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Plotly = require('../../plotly') +var Lib = require('../../lib') -var Plotly = require('../../plotly'); -var Lib = require('../../lib'); +var constants = require('../../plots/cartesian/constants') -var constants = require('../../plots/cartesian/constants'); +var dragElement = module.exports = {} +dragElement.align = require('./align') +dragElement.getCursor = require('./cursor') -var dragElement = module.exports = {}; - -dragElement.align = require('./align'); -dragElement.getCursor = require('./cursor'); - -var unhover = require('./unhover'); -dragElement.unhover = unhover.wrapped; -dragElement.unhoverRaw = unhover.raw; +var unhover = require('./unhover') +dragElement.unhover = unhover.wrapped +dragElement.unhoverRaw = unhover.raw /** * Abstracts click & drag interactions @@ -48,138 +46,137 @@ dragElement.unhoverRaw = unhover.raw; * the purpose of this callback is to update the mouse cursor before * the click & drag interaction has been initiated */ -dragElement.init = function init(options) { - var gd = Lib.getPlotDiv(options.element) || {}, - numClicks = 1, - DBLCLICKDELAY = constants.DBLCLICKDELAY, - startX, - startY, - newMouseDownTime, - dragCover, - initialTarget, - initialOnMouseMove; - - if(!gd._mouseDownTime) gd._mouseDownTime = 0; - - function onStart(e) { +dragElement.init = function init (options) { + var gd = Lib.getPlotDiv(options.element) || {}, + numClicks = 1, + DBLCLICKDELAY = constants.DBLCLICKDELAY, + startX, + startY, + newMouseDownTime, + dragCover, + initialTarget, + initialOnMouseMove + + if (!gd._mouseDownTime) gd._mouseDownTime = 0 + + function onStart (e) { // disable call to options.setCursor(evt) - options.element.onmousemove = initialOnMouseMove; + options.element.onmousemove = initialOnMouseMove // make dragging and dragged into properties of gd // so that others can look at and modify them - gd._dragged = false; - gd._dragging = true; - startX = e.clientX; - startY = e.clientY; - initialTarget = e.target; - - newMouseDownTime = (new Date()).getTime(); - if(newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) { + gd._dragged = false + gd._dragging = true + startX = e.clientX + startY = e.clientY + initialTarget = e.target + + newMouseDownTime = (new Date()).getTime() + if (newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) { // in a click train - numClicks += 1; - } - else { + numClicks += 1 + } else { // new click train - numClicks = 1; - gd._mouseDownTime = newMouseDownTime; - } + numClicks = 1 + gd._mouseDownTime = newMouseDownTime + } - if(options.prepFn) options.prepFn(e, startX, startY); + if (options.prepFn) options.prepFn(e, startX, startY) - dragCover = coverSlip(); + dragCover = coverSlip() - dragCover.onmousemove = onMove; - dragCover.onmouseup = onDone; - dragCover.onmouseout = onDone; + dragCover.onmousemove = onMove + dragCover.onmouseup = onDone + dragCover.onmouseout = onDone - dragCover.style.cursor = window.getComputedStyle(options.element).cursor; + dragCover.style.cursor = window.getComputedStyle(options.element).cursor - return Lib.pauseEvent(e); - } + return Lib.pauseEvent(e) + } - function onMove(e) { - var dx = e.clientX - startX, - dy = e.clientY - startY, - minDrag = options.minDrag || constants.MINDRAG; + function onMove (e) { + var dx = e.clientX - startX, + dy = e.clientY - startY, + minDrag = options.minDrag || constants.MINDRAG - if(Math.abs(dx) < minDrag) dx = 0; - if(Math.abs(dy) < minDrag) dy = 0; - if(dx || dy) { - gd._dragged = true; - dragElement.unhover(gd); - } + if (Math.abs(dx) < minDrag) dx = 0 + if (Math.abs(dy) < minDrag) dy = 0 + if (dx || dy) { + gd._dragged = true + dragElement.unhover(gd) + } - if(options.moveFn) options.moveFn(dx, dy, gd._dragged); + if (options.moveFn) options.moveFn(dx, dy, gd._dragged) - return Lib.pauseEvent(e); - } + return Lib.pauseEvent(e) + } - function onDone(e) { + function onDone (e) { // re-enable call to options.setCursor(evt) - initialOnMouseMove = options.element.onmousemove; - if(options.setCursor) options.element.onmousemove = options.setCursor; + initialOnMouseMove = options.element.onmousemove + if (options.setCursor) options.element.onmousemove = options.setCursor - dragCover.onmousemove = null; - dragCover.onmouseup = null; - dragCover.onmouseout = null; - Lib.removeElement(dragCover); + dragCover.onmousemove = null + dragCover.onmouseup = null + dragCover.onmouseout = null + Lib.removeElement(dragCover) - if(!gd._dragging) { - gd._dragged = false; - return; - } - gd._dragging = false; + if (!gd._dragging) { + gd._dragged = false + return + } + gd._dragging = false // don't count as a dblClick unless the mouseUp is also within // the dblclick delay - if((new Date()).getTime() - gd._mouseDownTime > DBLCLICKDELAY) { - numClicks = Math.max(numClicks - 1, 1); - } + if ((new Date()).getTime() - gd._mouseDownTime > DBLCLICKDELAY) { + numClicks = Math.max(numClicks - 1, 1) + } - if(options.doneFn) options.doneFn(gd._dragged, numClicks); + if (options.doneFn) options.doneFn(gd._dragged, numClicks) - if(!gd._dragged) { - var e2 = document.createEvent('MouseEvents'); - e2.initEvent('click', true, true); - initialTarget.dispatchEvent(e2); - } + if (!gd._dragged) { + var e2 = document.createEvent('MouseEvents') + e2.initEvent('click', true, true) + initialTarget.dispatchEvent(e2) + } - finishDrag(gd); + finishDrag(gd) - gd._dragged = false; + gd._dragged = false - return Lib.pauseEvent(e); - } + return Lib.pauseEvent(e) + } // enable call to options.setCursor(evt) - initialOnMouseMove = options.element.onmousemove; - if(options.setCursor) options.element.onmousemove = options.setCursor; + initialOnMouseMove = options.element.onmousemove + if (options.setCursor) options.element.onmousemove = options.setCursor - options.element.onmousedown = onStart; - options.element.style.pointerEvents = 'all'; -}; + options.element.onmousedown = onStart + options.element.style.pointerEvents = 'all' +} -function coverSlip() { - var cover = document.createElement('div'); +function coverSlip () { + var cover = document.createElement('div') - cover.className = 'dragcover'; - var cStyle = cover.style; - cStyle.position = 'fixed'; - cStyle.left = 0; - cStyle.right = 0; - cStyle.top = 0; - cStyle.bottom = 0; - cStyle.zIndex = 999999999; - cStyle.background = 'none'; + cover.className = 'dragcover' + var cStyle = cover.style + cStyle.position = 'fixed' + cStyle.left = 0 + cStyle.right = 0 + cStyle.top = 0 + cStyle.bottom = 0 + cStyle.zIndex = 999999999 + cStyle.background = 'none' - document.body.appendChild(cover); + document.body.appendChild(cover) - return cover; + return cover } -dragElement.coverSlip = coverSlip; +dragElement.coverSlip = coverSlip -function finishDrag(gd) { - gd._dragging = false; - if(gd._replotPending) Plotly.plot(gd); +function finishDrag (gd) { + gd._dragging = false + if (gd._replotPending) Plotly.plot(gd) } diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js index 61b602ac6e6..c06e064e340 100644 --- a/src/components/dragelement/unhover.js +++ b/src/components/dragelement/unhover.js @@ -6,44 +6,39 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Events = require('../../lib/events') +var unhover = module.exports = {} -var Events = require('../../lib/events'); - - -var unhover = module.exports = {}; - - -unhover.wrapped = function(gd, evt, subplot) { - if(typeof gd === 'string') gd = document.getElementById(gd); +unhover.wrapped = function (gd, evt, subplot) { + if (typeof gd === 'string') gd = document.getElementById(gd) // Important, clear any queued hovers - if(gd._hoverTimer) { - clearTimeout(gd._hoverTimer); - gd._hoverTimer = undefined; - } - - unhover.raw(gd, evt, subplot); -}; + if (gd._hoverTimer) { + clearTimeout(gd._hoverTimer) + gd._hoverTimer = undefined + } + unhover.raw(gd, evt, subplot) +} // remove hover effects on mouse out, and emit unhover event -unhover.raw = function unhoverRaw(gd, evt) { - var fullLayout = gd._fullLayout; +unhover.raw = function unhoverRaw (gd, evt) { + var fullLayout = gd._fullLayout - if(!evt) evt = {}; - if(evt.target && + if (!evt) evt = {} + if (evt.target && Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { - return; - } + return + } - fullLayout._hoverlayer.selectAll('g').remove(); + fullLayout._hoverlayer.selectAll('g').remove() - if(evt.target && gd._hoverdata) { - gd.emit('plotly_unhover', {points: gd._hoverdata}); - } + if (evt.target && gd._hoverdata) { + gd.emit('plotly_unhover', {points: gd._hoverdata}) + } - gd._hoverdata = undefined; -}; + gd._hoverdata = undefined +} diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index fa995b0641e..54ac8f09980 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -6,675 +6,663 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Registry = require('../../registry') +var Color = require('../color') +var Colorscale = require('../colorscale') +var Lib = require('../../lib') +var svgTextUtils = require('../../lib/svg_text_utils') -var Registry = require('../../registry'); -var Color = require('../color'); -var Colorscale = require('../colorscale'); -var Lib = require('../../lib'); -var svgTextUtils = require('../../lib/svg_text_utils'); +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') +var subTypes = require('../../traces/scatter/subtypes') +var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func') -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); -var subTypes = require('../../traces/scatter/subtypes'); -var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func'); - -var drawing = module.exports = {}; +var drawing = module.exports = {} // ----------------------------------------------------- // styling functions for plot elements // ----------------------------------------------------- -drawing.font = function(s, family, size, color) { +drawing.font = function (s, family, size, color) { // also allow the form font(s, {family, size, color}) - if(family && family.family) { - color = family.color; - size = family.size; - family = family.family; - } - if(family) s.style('font-family', family); - if(size + 1) s.style('font-size', size + 'px'); - if(color) s.call(Color.fill, color); -}; - -drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); }; -drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); }; -drawing.setRect = function(s, x, y, w, h) { - s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h); -}; - -drawing.translatePoint = function(d, sel, xa, ya) { + if (family && family.family) { + color = family.color + size = family.size + family = family.family + } + if (family) s.style('font-family', family) + if (size + 1) s.style('font-size', size + 'px') + if (color) s.call(Color.fill, color) +} + +drawing.setPosition = function (s, x, y) { s.attr('x', x).attr('y', y) } +drawing.setSize = function (s, w, h) { s.attr('width', w).attr('height', h) } +drawing.setRect = function (s, x, y, w, h) { + s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h) +} + +drawing.translatePoint = function (d, sel, xa, ya) { // put xp and yp into d if pixel scaling is already done - var x = d.xp || xa.c2p(d.x), - y = d.yp || ya.c2p(d.y); + var x = d.xp || xa.c2p(d.x), + y = d.yp || ya.c2p(d.y) - if(isNumeric(x) && isNumeric(y) && sel.node()) { + if (isNumeric(x) && isNumeric(y) && sel.node()) { // for multiline text this works better - if(sel.node().nodeName === 'text') { - sel.attr('x', x).attr('y', y); - } else { - sel.attr('transform', 'translate(' + x + ',' + y + ')'); - } + if (sel.node().nodeName === 'text') { + sel.attr('x', x).attr('y', y) + } else { + sel.attr('transform', 'translate(' + x + ',' + y + ')') } - else sel.remove(); -}; + } else sel.remove() +} -drawing.translatePoints = function(s, xa, ya, trace) { - s.each(function(d) { - var sel = d3.select(this); - drawing.translatePoint(d, sel, xa, ya, trace); - }); -}; +drawing.translatePoints = function (s, xa, ya, trace) { + s.each(function (d) { + var sel = d3.select(this) + drawing.translatePoint(d, sel, xa, ya, trace) + }) +} -drawing.getPx = function(s, styleAttr) { +drawing.getPx = function (s, styleAttr) { // helper to pull out a px value from a style that may contain px units // s is a d3 selection (will pull from the first one) - return Number(s.style(styleAttr).replace(/px$/, '')); -}; + return Number(s.style(styleAttr).replace(/px$/, '')) +} -drawing.crispRound = function(gd, lineWidth, dflt) { +drawing.crispRound = function (gd, lineWidth, dflt) { // for lines that disable antialiasing we want to // make sure the width is an integer, and at least 1 if it's nonzero - if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0; + if (!lineWidth || !isNumeric(lineWidth)) return dflt || 0 // but not for static plots - these don't get antialiased anyway. - if(gd._context.staticPlot) return lineWidth; - - if(lineWidth < 1) return 1; - return Math.round(lineWidth); -}; - -drawing.singleLineStyle = function(d, s, lw, lc, ld) { - s.style('fill', 'none'); - var line = (((d || [])[0] || {}).trace || {}).line || {}, - lw1 = lw || line.width||0, - dash = ld || line.dash || ''; - - Color.stroke(s, lc || line.color); - drawing.dashLine(s, dash, lw1); -}; - -drawing.lineGroupStyle = function(s, lw, lc, ld) { - s.style('fill', 'none') - .each(function(d) { - var line = (((d || [])[0] || {}).trace || {}).line || {}, - lw1 = lw || line.width||0, - dash = ld || line.dash || ''; - - d3.select(this) + if (gd._context.staticPlot) return lineWidth + + if (lineWidth < 1) return 1 + return Math.round(lineWidth) +} + +drawing.singleLineStyle = function (d, s, lw, lc, ld) { + s.style('fill', 'none') + var line = (((d || [])[0] || {}).trace || {}).line || {}, + lw1 = lw || line.width || 0, + dash = ld || line.dash || '' + + Color.stroke(s, lc || line.color) + drawing.dashLine(s, dash, lw1) +} + +drawing.lineGroupStyle = function (s, lw, lc, ld) { + s.style('fill', 'none') + .each(function (d) { + var line = (((d || [])[0] || {}).trace || {}).line || {}, + lw1 = lw || line.width || 0, + dash = ld || line.dash || '' + + d3.select(this) .call(Color.stroke, lc || line.color) - .call(drawing.dashLine, dash, lw1); - }); -}; - -drawing.dashLine = function(s, dash, lineWidth) { - lineWidth = +lineWidth || 0; - var dlw = Math.max(lineWidth, 3); - - if(dash === 'solid') dash = ''; - else if(dash === 'dot') dash = dlw + 'px,' + dlw + 'px'; - else if(dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px'; - else if(dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px'; - else if(dash === 'dashdot') { - dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px'; - } - else if(dash === 'longdashdot') { - dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px'; - } + .call(drawing.dashLine, dash, lw1) + }) +} + +drawing.dashLine = function (s, dash, lineWidth) { + lineWidth = +lineWidth || 0 + var dlw = Math.max(lineWidth, 3) + + if (dash === 'solid') dash = '' + else if (dash === 'dot') dash = dlw + 'px,' + dlw + 'px' + else if (dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px' + else if (dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px' + else if (dash === 'dashdot') { + dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px' + } else if (dash === 'longdashdot') { + dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px' + } // otherwise user wrote the dasharray themselves - leave it be - s.style({ - 'stroke-dasharray': dash, - 'stroke-width': lineWidth + 'px' - }); -}; - -drawing.fillGroupStyle = function(s) { - s.style('stroke-width', 0) - .each(function(d) { - var shape = d3.select(this); - try { - shape.call(Color.fill, d[0].trace.fillcolor); - } - catch(e) { - Lib.error(e, s); - shape.remove(); - } - }); -}; - -var SYMBOLDEFS = require('./symbol_defs'); - -drawing.symbolNames = []; -drawing.symbolFuncs = []; -drawing.symbolNeedLines = {}; -drawing.symbolNoDot = {}; -drawing.symbolList = []; - -Object.keys(SYMBOLDEFS).forEach(function(k) { - var symDef = SYMBOLDEFS[k]; + s.style({ + 'stroke-dasharray': dash, + 'stroke-width': lineWidth + 'px' + }) +} + +drawing.fillGroupStyle = function (s) { + s.style('stroke-width', 0) + .each(function (d) { + var shape = d3.select(this) + try { + shape.call(Color.fill, d[0].trace.fillcolor) + } catch (e) { + Lib.error(e, s) + shape.remove() + } + }) +} + +var SYMBOLDEFS = require('./symbol_defs') + +drawing.symbolNames = [] +drawing.symbolFuncs = [] +drawing.symbolNeedLines = {} +drawing.symbolNoDot = {} +drawing.symbolList = [] + +Object.keys(SYMBOLDEFS).forEach(function (k) { + var symDef = SYMBOLDEFS[k] + drawing.symbolList = drawing.symbolList.concat( + [symDef.n, k, symDef.n + 100, k + '-open']) + drawing.symbolNames[symDef.n] = k + drawing.symbolFuncs[symDef.n] = symDef.f + if (symDef.needLine) { + drawing.symbolNeedLines[symDef.n] = true + } + if (symDef.noDot) { + drawing.symbolNoDot[symDef.n] = true + } else { drawing.symbolList = drawing.symbolList.concat( - [symDef.n, k, symDef.n + 100, k + '-open']); - drawing.symbolNames[symDef.n] = k; - drawing.symbolFuncs[symDef.n] = symDef.f; - if(symDef.needLine) { - drawing.symbolNeedLines[symDef.n] = true; - } - if(symDef.noDot) { - drawing.symbolNoDot[symDef.n] = true; - } - else { - drawing.symbolList = drawing.symbolList.concat( - [symDef.n + 200, k + '-dot', symDef.n + 300, k + '-open-dot']); - } -}); + [symDef.n + 200, k + '-dot', symDef.n + 300, k + '-open-dot']) + } +}) var MAXSYMBOL = drawing.symbolNames.length, // add a dot in the middle of the symbol - DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z'; - -drawing.symbolNumber = function(v) { - if(typeof v === 'string') { - var vbase = 0; - if(v.indexOf('-open') > 0) { - vbase = 100; - v = v.replace('-open', ''); - } - if(v.indexOf('-dot') > 0) { - vbase += 200; - v = v.replace('-dot', ''); - } - v = drawing.symbolNames.indexOf(v); - if(v >= 0) { v += vbase; } + DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z' + +drawing.symbolNumber = function (v) { + if (typeof v === 'string') { + var vbase = 0 + if (v.indexOf('-open') > 0) { + vbase = 100 + v = v.replace('-open', '') } - if((v % 100 >= MAXSYMBOL) || v >= 400) { return 0; } - return Math.floor(Math.max(v, 0)); -}; + if (v.indexOf('-dot') > 0) { + vbase += 200 + v = v.replace('-dot', '') + } + v = drawing.symbolNames.indexOf(v) + if (v >= 0) { v += vbase } + } + if ((v % 100 >= MAXSYMBOL) || v >= 400) { return 0 } + return Math.floor(Math.max(v, 0)) +} -function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) { +function singlePointStyle (d, sel, trace, markerScale, lineScale, marker, markerLine) { // only scatter & box plots get marker path and opacity // bars, histograms don't - if(Registry.traceIs(trace, 'symbols')) { - var sizeFn = makeBubbleSizeFn(trace); + if (Registry.traceIs(trace, 'symbols')) { + var sizeFn = makeBubbleSizeFn(trace) - sel.attr('d', function(d) { - var r; + sel.attr('d', function (d) { + var r // handle multi-trace graph edit case - if(d.ms === 'various' || marker.size === 'various') r = 3; - else { - r = subTypes.isBubble(trace) ? - sizeFn(d.ms) : (marker.size || 6) / 2; - } + if (d.ms === 'various' || marker.size === 'various') r = 3 + else { + r = subTypes.isBubble(trace) ? + sizeFn(d.ms) : (marker.size || 6) / 2 + } // store the calculated size so hover can use it - d.mrc = r; + d.mrc = r // turn the symbol into a sanitized number - var x = drawing.symbolNumber(d.mx || marker.symbol) || 0, - xBase = x % 100; + var x = drawing.symbolNumber(d.mx || marker.symbol) || 0, + xBase = x % 100 // save if this marker is open // because that impacts how to handle colors - d.om = x % 200 >= 100; + d.om = x % 200 >= 100 - return drawing.symbolFuncs[xBase](r) + - (x >= 200 ? DOTPATH : ''); + return drawing.symbolFuncs[xBase](r) + + (x >= 200 ? DOTPATH : '') + }) + .style('opacity', function (d) { + return (d.mo + 1 || marker.opacity + 1) - 1 }) - .style('opacity', function(d) { - return (d.mo + 1 || marker.opacity + 1) - 1; - }); - } + } // 'so' is suspected outliers, for box plots - var fillColor, - lineColor, - lineWidth; - if(d.so) { - lineWidth = markerLine.outlierwidth; - lineColor = markerLine.outliercolor; - fillColor = marker.outliercolor; - } - else { - lineWidth = (d.mlw + 1 || markerLine.width + 1 || + var fillColor, + lineColor, + lineWidth + if (d.so) { + lineWidth = markerLine.outlierwidth + lineColor = markerLine.outliercolor + fillColor = marker.outliercolor + } else { + lineWidth = (d.mlw + 1 || markerLine.width + 1 || // TODO: we need the latter for legends... can we get rid of it? - (d.trace ? d.trace.marker.line.width : 0) + 1) - 1; + (d.trace ? d.trace.marker.line.width : 0) + 1) - 1 - if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc); + if ('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc) // weird case: array wasn't long enough to apply to every point - else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine; - else lineColor = markerLine.color; + else if (Array.isArray(markerLine.color)) lineColor = Color.defaultLine + else lineColor = markerLine.color - if('mc' in d) fillColor = d.mcc = markerScale(d.mc); - else if(Array.isArray(marker.color)) fillColor = Color.defaultLine; - else fillColor = marker.color || 'rgba(0,0,0,0)'; - } + if ('mc' in d) fillColor = d.mcc = markerScale(d.mc) + else if (Array.isArray(marker.color)) fillColor = Color.defaultLine + else fillColor = marker.color || 'rgba(0,0,0,0)' + } - if(d.om) { + if (d.om) { // open markers can't have zero linewidth, default to 1px, // and use fill color as stroke color - sel.call(Color.stroke, fillColor) + sel.call(Color.stroke, fillColor) .style({ - 'stroke-width': (lineWidth || 1) + 'px', - fill: 'none' - }); - } - else { - sel.style('stroke-width', lineWidth + 'px') - .call(Color.fill, fillColor); - if(lineWidth) { - sel.call(Color.stroke, lineColor); - } + 'stroke-width': (lineWidth || 1) + 'px', + fill: 'none' + }) + } else { + sel.style('stroke-width', lineWidth + 'px') + .call(Color.fill, fillColor) + if (lineWidth) { + sel.call(Color.stroke, lineColor) } + } } -drawing.singlePointStyle = function(d, sel, trace) { - var marker = trace.marker, - markerLine = marker.line; +drawing.singlePointStyle = function (d, sel, trace) { + var marker = trace.marker, + markerLine = marker.line // allow array marker and marker line colors to be // scaled by given max and min to colorscales - var markerScale = drawing.tryColorscale(marker, ''), - lineScale = drawing.tryColorscale(marker, 'line'); + var markerScale = drawing.tryColorscale(marker, ''), + lineScale = drawing.tryColorscale(marker, 'line') - singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine); - -}; + singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) +} -drawing.pointStyle = function(s, trace) { - if(!s.size()) return; +drawing.pointStyle = function (s, trace) { + if (!s.size()) return // allow array marker and marker line colors to be // scaled by given max and min to colorscales - var marker = trace.marker; - var markerScale = drawing.tryColorscale(marker, ''), - lineScale = drawing.tryColorscale(marker, 'line'); - - s.each(function(d) { - drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale); - }); -}; - -drawing.tryColorscale = function(marker, prefix) { - var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker, - scl = cont.colorscale, - colorArray = cont.color; - - if(scl && Array.isArray(colorArray)) { - return Colorscale.makeColorScaleFunc( + var marker = trace.marker + var markerScale = drawing.tryColorscale(marker, ''), + lineScale = drawing.tryColorscale(marker, 'line') + + s.each(function (d) { + drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale) + }) +} + +drawing.tryColorscale = function (marker, prefix) { + var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker, + scl = cont.colorscale, + colorArray = cont.color + + if (scl && Array.isArray(colorArray)) { + return Colorscale.makeColorScaleFunc( Colorscale.extractScale(scl, cont.cmin, cont.cmax) - ); - } - else return Lib.identity; -}; + ) + } else return Lib.identity +} // draw text at points var TEXTOFFSETSIGN = {start: 1, end: -1, middle: 0, bottom: 1, top: -1}, - LINEEXPAND = 1.3; -drawing.textPointStyle = function(s, trace) { - s.each(function(d) { - var p = d3.select(this), - text = d.tx || trace.text; + LINEEXPAND = 1.3 +drawing.textPointStyle = function (s, trace) { + s.each(function (d) { + var p = d3.select(this), + text = d.tx || trace.text - if(!text || Array.isArray(text)) { + if (!text || Array.isArray(text)) { // isArray test handles the case of (intentionally) missing // or empty text within a text array - p.remove(); - return; - } + p.remove() + return + } - var pos = d.tp || trace.textposition, - v = pos.indexOf('top') !== -1 ? 'top' : + var pos = d.tp || trace.textposition, + v = pos.indexOf('top') !== -1 ? 'top' : pos.indexOf('bottom') !== -1 ? 'bottom' : 'middle', - h = pos.indexOf('left') !== -1 ? 'end' : + h = pos.indexOf('left') !== -1 ? 'end' : pos.indexOf('right') !== -1 ? 'start' : 'middle', - fontSize = d.ts || trace.textfont.size, + fontSize = d.ts || trace.textfont.size, // if markers are shown, offset a little more than // the nominal marker size // ie 2/1.6 * nominal, bcs some markers are a bit bigger - r = d.mrc ? (d.mrc / 0.8 + 1) : 0; + r = d.mrc ? (d.mrc / 0.8 + 1) : 0 - fontSize = (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0; + fontSize = (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0 - p.call(drawing.font, + p.call(drawing.font, d.tf || trace.textfont.family, fontSize, d.tc || trace.textfont.color) .attr('text-anchor', h) .text(text) - .call(svgTextUtils.convertToTspans); - var pgroup = d3.select(this.parentNode), - tspans = p.selectAll('tspan.line'), - numLines = ((tspans[0].length || 1) - 1) * LINEEXPAND + 1, - dx = TEXTOFFSETSIGN[h] * r, - dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + - (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2; + .call(svgTextUtils.convertToTspans) + var pgroup = d3.select(this.parentNode), + tspans = p.selectAll('tspan.line'), + numLines = ((tspans[0].length || 1) - 1) * LINEEXPAND + 1, + dx = TEXTOFFSETSIGN[h] * r, + dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + + (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2 // fix the overall text group position - pgroup.attr('transform', 'translate(' + dx + ',' + dy + ')'); + pgroup.attr('transform', 'translate(' + dx + ',' + dy + ')') // then fix multiline text - if(numLines > 1) { - tspans.attr({ x: p.attr('x'), y: p.attr('y') }); - } - }); -}; + if (numLines > 1) { + tspans.attr({ x: p.attr('x'), y: p.attr('y') }) + } + }) +} // generalized Catmull-Rom splines, per // http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf -var CatmullRomExp = 0.5; -drawing.smoothopen = function(pts, smoothness) { - if(pts.length < 3) { return 'M' + pts.join('L');} - var path = 'M' + pts[0], - tangents = [], i; - for(i = 1; i < pts.length - 1; i++) { - tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); - } - path += 'Q' + tangents[0][0] + ' ' + pts[1]; - for(i = 2; i < pts.length - 1; i++) { - path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i]; - } - path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1]; - return path; -}; - -drawing.smoothclosed = function(pts, smoothness) { - if(pts.length < 3) { return 'M' + pts.join('L') + 'Z'; } - var path = 'M' + pts[0], - pLast = pts.length - 1, - tangents = [makeTangent(pts[pLast], +var CatmullRomExp = 0.5 +drawing.smoothopen = function (pts, smoothness) { + if (pts.length < 3) { return 'M' + pts.join('L') } + var path = 'M' + pts[0], + tangents = [], i + for (i = 1; i < pts.length - 1; i++) { + tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)) + } + path += 'Q' + tangents[0][0] + ' ' + pts[1] + for (i = 2; i < pts.length - 1; i++) { + path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i] + } + path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1] + return path +} + +drawing.smoothclosed = function (pts, smoothness) { + if (pts.length < 3) { return 'M' + pts.join('L') + 'Z' } + var path = 'M' + pts[0], + pLast = pts.length - 1, + tangents = [makeTangent(pts[pLast], pts[0], pts[1], smoothness)], - i; - for(i = 1; i < pLast; i++) { - tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); - } - tangents.push( + i + for (i = 1; i < pLast; i++) { + tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)) + } + tangents.push( makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness) - ); + ) - for(i = 1; i <= pLast; i++) { - path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i]; - } - path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z'; - return path; -}; - -function makeTangent(prevpt, thispt, nextpt, smoothness) { - var d1x = prevpt[0] - thispt[0], - d1y = prevpt[1] - thispt[1], - d2x = nextpt[0] - thispt[0], - d2y = nextpt[1] - thispt[1], - d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2), - d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2), - numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness, - numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness, - denom1 = 3 * d2a * (d1a + d2a), - denom2 = 3 * d1a * (d1a + d2a); - return [ - [ - d3.round(thispt[0] + (denom1 && numx / denom1), 2), - d3.round(thispt[1] + (denom1 && numy / denom1), 2) - ], [ - d3.round(thispt[0] - (denom2 && numx / denom2), 2), - d3.round(thispt[1] - (denom2 && numy / denom2), 2) - ] - ]; + for (i = 1; i <= pLast; i++) { + path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i] + } + path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z' + return path +} + +function makeTangent (prevpt, thispt, nextpt, smoothness) { + var d1x = prevpt[0] - thispt[0], + d1y = prevpt[1] - thispt[1], + d2x = nextpt[0] - thispt[0], + d2y = nextpt[1] - thispt[1], + d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2), + d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2), + numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness, + numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness, + denom1 = 3 * d2a * (d1a + d2a), + denom2 = 3 * d1a * (d1a + d2a) + return [ + [ + d3.round(thispt[0] + (denom1 && numx / denom1), 2), + d3.round(thispt[1] + (denom1 && numy / denom1), 2) + ], [ + d3.round(thispt[0] - (denom2 && numx / denom2), 2), + d3.round(thispt[1] - (denom2 && numy / denom2), 2) + ] + ] } // step paths - returns a generator function for paths // with the given step shape var STEPPATH = { - hv: function(p0, p1) { - return 'H' + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2); - }, - vh: function(p0, p1) { - return 'V' + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2); - }, - hvh: function(p0, p1) { - return 'H' + d3.round((p0[0] + p1[0]) / 2, 2) + 'V' + - d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2); - }, - vhv: function(p0, p1) { - return 'V' + d3.round((p0[1] + p1[1]) / 2, 2) + 'H' + - d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2); + hv: function (p0, p1) { + return 'H' + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2) + }, + vh: function (p0, p1) { + return 'V' + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2) + }, + hvh: function (p0, p1) { + return 'H' + d3.round((p0[0] + p1[0]) / 2, 2) + 'V' + + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2) + }, + vhv: function (p0, p1) { + return 'V' + d3.round((p0[1] + p1[1]) / 2, 2) + 'H' + + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2) + } +} +var STEPLINEAR = function (p0, p1) { + return 'L' + d3.round(p1[0], 2) + ',' + d3.round(p1[1], 2) +} +drawing.steps = function (shape) { + var onestep = STEPPATH[shape] || STEPLINEAR + return function (pts) { + var path = 'M' + d3.round(pts[0][0], 2) + ',' + d3.round(pts[0][1], 2) + for (var i = 1; i < pts.length; i++) { + path += onestep(pts[i - 1], pts[i]) } -}; -var STEPLINEAR = function(p0, p1) { - return 'L' + d3.round(p1[0], 2) + ',' + d3.round(p1[1], 2); -}; -drawing.steps = function(shape) { - var onestep = STEPPATH[shape] || STEPLINEAR; - return function(pts) { - var path = 'M' + d3.round(pts[0][0], 2) + ',' + d3.round(pts[0][1], 2); - for(var i = 1; i < pts.length; i++) { - path += onestep(pts[i - 1], pts[i]); - } - return path; - }; -}; + return path + } +} // off-screen svg render testing element, shared by the whole page // uses the id 'js-plotly-tester' and stores it in gd._tester // makes a hash of cached text items in tester.node()._cache // so we can add references to rendered text (including all info // needed to fully determine its bounding rect) -drawing.makeTester = function(gd) { - var tester = d3.select('body') +drawing.makeTester = function (gd) { + var tester = d3.select('body') .selectAll('#js-plotly-tester') - .data([0]); + .data([0]) - tester.enter().append('svg') + tester.enter().append('svg') .attr('id', 'js-plotly-tester') .attr(xmlnsNamespaces.svgAttrs) .style({ - position: 'absolute', - left: '-10000px', - top: '-10000px', - width: '9000px', - height: '9000px', - 'z-index': '1' - }); + position: 'absolute', + left: '-10000px', + top: '-10000px', + width: '9000px', + height: '9000px', + 'z-index': '1' + }) // browsers differ on how they describe the bounding rect of // the svg if its contents spill over... so make a 1x1px // reference point we can measure off of. - var testref = tester.selectAll('.js-reference-point').data([0]); - testref.enter().append('path') + var testref = tester.selectAll('.js-reference-point').data([0]) + testref.enter().append('path') .classed('js-reference-point', true) .attr('d', 'M0,0H1V1H0Z') .style({ - 'stroke-width': 0, - fill: 'black' - }); + 'stroke-width': 0, + fill: 'black' + }) - if(!tester.node()._cache) { - tester.node()._cache = {}; - } + if (!tester.node()._cache) { + tester.node()._cache = {} + } - gd._tester = tester; - gd._testref = testref; -}; + gd._tester = tester + gd._testref = testref +} // use our offscreen tester to get a clientRect for an element, // in a reference frame where it isn't translated and its anchor // point is at (0,0) // always returns a copy of the bbox, so the caller can modify it safely var savedBBoxes = [], - maxSavedBBoxes = 10000; -drawing.bBox = function(node) { + maxSavedBBoxes = 10000 +drawing.bBox = function (node) { // cache elements we've already measured so we don't have to // remeasure the same thing many times - var saveNum = node.attributes['data-bb']; - if(saveNum && saveNum.value) { - return Lib.extendFlat({}, savedBBoxes[saveNum.value]); - } + var saveNum = node.attributes['data-bb'] + if (saveNum && saveNum.value) { + return Lib.extendFlat({}, savedBBoxes[saveNum.value]) + } - var test3 = d3.select('#js-plotly-tester'), - tester = test3.node(); + var test3 = d3.select('#js-plotly-tester'), + tester = test3.node() // copy the node to test into the tester - var testNode = node.cloneNode(true); - tester.appendChild(testNode); + var testNode = node.cloneNode(true) + tester.appendChild(testNode) // standardize its position... do we really want to do this? - d3.select(testNode).attr({ - x: 0, - y: 0, - transform: '' - }); - - var testRect = testNode.getBoundingClientRect(), - refRect = test3.select('.js-reference-point') - .node().getBoundingClientRect(); - - tester.removeChild(testNode); - - var bb = { - height: testRect.height, - width: testRect.width, - left: testRect.left - refRect.left, - top: testRect.top - refRect.top, - right: testRect.right - refRect.left, - bottom: testRect.bottom - refRect.top - }; + d3.select(testNode).attr({ + x: 0, + y: 0, + transform: '' + }) + + var testRect = testNode.getBoundingClientRect(), + refRect = test3.select('.js-reference-point') + .node().getBoundingClientRect() + + tester.removeChild(testNode) + + var bb = { + height: testRect.height, + width: testRect.width, + left: testRect.left - refRect.left, + top: testRect.top - refRect.top, + right: testRect.right - refRect.left, + bottom: testRect.bottom - refRect.top + } // make sure we don't have too many saved boxes, // or a long session could overload on memory // by saving boxes for long-gone elements - if(savedBBoxes.length >= maxSavedBBoxes) { - d3.selectAll('[data-bb]').attr('data-bb', null); - savedBBoxes = []; - } + if (savedBBoxes.length >= maxSavedBBoxes) { + d3.selectAll('[data-bb]').attr('data-bb', null) + savedBBoxes = [] + } // cache this bbox - node.setAttribute('data-bb', savedBBoxes.length); - savedBBoxes.push(bb); + node.setAttribute('data-bb', savedBBoxes.length) + savedBBoxes.push(bb) - return Lib.extendFlat({}, bb); -}; + return Lib.extendFlat({}, bb) +} /* * make a robust clipPath url from a local id * note! We'd better not be exporting from a page * with a or the svg will not be portable! */ -drawing.setClipUrl = function(s, localId) { - if(!localId) { - s.attr('clip-path', null); - return; - } +drawing.setClipUrl = function (s, localId) { + if (!localId) { + s.attr('clip-path', null) + return + } - var url = '#' + localId, - base = d3.select('base'); + var url = '#' + localId, + base = d3.select('base') // add id to location href w/o hashes if any) - if(base.size() && base.attr('href')) { - url = window.location.href.split('#')[0] + url; - } + if (base.size() && base.attr('href')) { + url = window.location.href.split('#')[0] + url + } - s.attr('clip-path', 'url(' + url + ')'); -}; + s.attr('clip-path', 'url(' + url + ')') +} -drawing.getTranslate = function(element) { +drawing.getTranslate = function (element) { // Note the separator [^\d] between x and y in this regex // We generally use ',' but IE will convert it to ' ' - var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/, - getter = element.attr ? 'attr' : 'getAttribute', - transform = element[getter]('transform') || ''; - - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); - - return { - x: +translate[0] || 0, - y: +translate[1] || 0 - }; -}; - -drawing.setTranslate = function(element, x, y) { - - var re = /(\btranslate\(.*?\);?)/, - getter = element.attr ? 'attr' : 'getAttribute', - setter = element.attr ? 'attr' : 'setAttribute', - transform = element[getter]('transform') || ''; - - x = x || 0; - y = y || 0; + var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || '' + + var translate = transform.replace(re, function (match, p1, p2) { + return [p1, p2].join(' ') + }) + .split(' ') + + return { + x: +translate[0] || 0, + y: +translate[1] || 0 + } +} - transform = transform.replace(re, '').trim(); - transform += ' translate(' + x + ', ' + y + ')'; - transform = transform.trim(); +drawing.setTranslate = function (element, x, y) { + var re = /(\btranslate\(.*?\);?)/, + getter = element.attr ? 'attr' : 'getAttribute', + setter = element.attr ? 'attr' : 'setAttribute', + transform = element[getter]('transform') || '' - element[setter]('transform', transform); + x = x || 0 + y = y || 0 - return transform; -}; + transform = transform.replace(re, '').trim() + transform += ' translate(' + x + ', ' + y + ')' + transform = transform.trim() -drawing.getScale = function(element) { + element[setter]('transform', transform) - var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, - getter = element.attr ? 'attr' : 'getAttribute', - transform = element[getter]('transform') || ''; + return transform +} - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); +drawing.getScale = function (element) { + var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || '' - return { - x: +translate[0] || 1, - y: +translate[1] || 1 - }; -}; + var translate = transform.replace(re, function (match, p1, p2) { + return [p1, p2].join(' ') + }) + .split(' ') -drawing.setScale = function(element, x, y) { + return { + x: +translate[0] || 1, + y: +translate[1] || 1 + } +} - var re = /(\bscale\(.*?\);?)/, - getter = element.attr ? 'attr' : 'getAttribute', - setter = element.attr ? 'attr' : 'setAttribute', - transform = element[getter]('transform') || ''; +drawing.setScale = function (element, x, y) { + var re = /(\bscale\(.*?\);?)/, + getter = element.attr ? 'attr' : 'getAttribute', + setter = element.attr ? 'attr' : 'setAttribute', + transform = element[getter]('transform') || '' - x = x || 1; - y = y || 1; + x = x || 1 + y = y || 1 - transform = transform.replace(re, '').trim(); - transform += ' scale(' + x + ', ' + y + ')'; - transform = transform.trim(); + transform = transform.replace(re, '').trim() + transform += ' scale(' + x + ', ' + y + ')' + transform = transform.trim() - element[setter]('transform', transform); + element[setter]('transform', transform) - return transform; -}; + return transform +} -drawing.setPointGroupScale = function(selection, x, y) { - var t, scale, re; +drawing.setPointGroupScale = function (selection, x, y) { + var t, scale, re - x = x || 1; - y = y || 1; + x = x || 1 + y = y || 1 - if(x === 1 && y === 1) { - scale = ''; - } else { + if (x === 1 && y === 1) { + scale = '' + } else { // The same scale transform for every point: - scale = ' scale(' + x + ',' + y + ')'; - } + scale = ' scale(' + x + ',' + y + ')' + } // A regex to strip any existing scale: - re = /\s*sc.*/; + re = /\s*sc.*/ - selection.each(function() { + selection.each(function () { // Get the transform: - t = (this.getAttribute('transform') || '').replace(re, ''); - t += scale; - t = t.trim(); + t = (this.getAttribute('transform') || '').replace(re, '') + t += scale + t = t.trim() // Append the scale transform - this.setAttribute('transform', t); - }); + this.setAttribute('transform', t) + }) - return scale; -}; + return scale +} diff --git a/src/components/drawing/symbol_defs.js b/src/components/drawing/symbol_defs.js index 548a8c5c307..e393716a867 100644 --- a/src/components/drawing/symbol_defs.js +++ b/src/components/drawing/symbol_defs.js @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var d3 = require('d3'); +var d3 = require('d3') /** Marker symbol definitions * users can specify markers either by number or name @@ -20,455 +19,455 @@ var d3 = require('d3'); */ 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 + + 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 + + '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 + + '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 + + '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 + ',-' + 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 + + 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 + + '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 + + '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' + 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-' + 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' + 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 - } -}; + 'm0,-' + r2 + 'H' + r2 + }, + needLine: true + }, + 'y-up': { + n: 37, + f: function (r) { + var x = d3.round(r * 1.2, 2), + y0 = d3.round(r * 1.6, 2), + y1 = d3.round(r * 0.8, 2) + return 'M-' + x + ',' + y1 + 'L0,0M' + x + ',' + y1 + 'L0,0M0,-' + y0 + 'L0,0' + }, + needLine: true, + noDot: true + }, + 'y-down': { + n: 38, + f: function (r) { + var x = d3.round(r * 1.2, 2), + y0 = d3.round(r * 1.6, 2), + y1 = d3.round(r * 0.8, 2) + return 'M-' + x + ',-' + y1 + 'L0,0M' + x + ',-' + y1 + 'L0,0M0,' + y0 + 'L0,0' + }, + needLine: true, + noDot: true + }, + 'y-left': { + n: 39, + f: function (r) { + var y = d3.round(r * 1.2, 2), + x0 = d3.round(r * 1.6, 2), + x1 = d3.round(r * 0.8, 2) + return 'M' + x1 + ',' + y + 'L0,0M' + x1 + ',-' + y + 'L0,0M-' + x0 + ',0L0,0' + }, + needLine: true, + noDot: true + }, + 'y-right': { + n: 40, + f: function (r) { + var y = d3.round(r * 1.2, 2), + x0 = d3.round(r * 1.6, 2), + x1 = d3.round(r * 0.8, 2) + return 'M-' + x1 + ',' + y + 'L0,0M-' + x1 + ',-' + y + 'L0,0M' + x0 + ',0L0,0' + }, + needLine: true, + noDot: true + }, + 'line-ew': { + n: 41, + f: function (r) { + var rc = d3.round(r * 1.4, 2) + return 'M' + rc + ',0H-' + rc + }, + needLine: true, + noDot: true + }, + 'line-ns': { + n: 42, + f: function (r) { + var rc = d3.round(r * 1.4, 2) + return 'M0,' + rc + 'V-' + rc + }, + needLine: true, + noDot: true + }, + 'line-ne': { + n: 43, + f: function (r) { + var rx = d3.round(r, 2) + return 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx + }, + needLine: true, + noDot: true + }, + 'line-nw': { + n: 44, + f: function (r) { + var rx = d3.round(r, 2) + return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx + }, + needLine: true, + noDot: true + } +} diff --git a/src/components/errorbars/attributes.js b/src/components/errorbars/attributes.js index be441d7b364..791fe8a8eb7 100644 --- a/src/components/errorbars/attributes.js +++ b/src/components/errorbars/attributes.js @@ -6,135 +6,134 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - visible: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not this set of error bars is visible.' - ].join(' ') - }, - type: { - valType: 'enumerated', - values: ['percent', 'constant', 'sqrt', 'data'], - role: 'info', - description: [ - 'Determines the rule used to generate the error bars.', + 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 *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 *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 *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(' ') - }, + '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(' ') - } + _deprecated: { + opacity: { + valType: 'number', + role: 'style', + description: [ + 'Obsolete.', + 'Use the alpha channel in error bar `color` to set the opacity.' + ].join(' ') } -}; + } +} diff --git a/src/components/errorbars/calc.js b/src/components/errorbars/calc.js index d631758fcb6..533f532461f 100644 --- a/src/components/errorbars/calc.js +++ b/src/components/errorbars/calc.js @@ -6,56 +6,54 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); +var makeComputeError = require('./compute_error') -var makeComputeError = require('./compute_error'); +module.exports = function calc (gd) { + var calcdata = gd.calcdata + for (var i = 0; i < calcdata.length; i++) { + var calcTrace = calcdata[i], + trace = calcTrace[0].trace -module.exports = function calc(gd) { - var calcdata = gd.calcdata; + if (!Registry.traceIs(trace, 'errorBarsOK')) continue - for(var i = 0; i < calcdata.length; i++) { - var calcTrace = calcdata[i], - trace = calcTrace[0].trace; + var xa = Axes.getFromId(gd, trace.xaxis), + ya = Axes.getFromId(gd, trace.yaxis) - if(!Registry.traceIs(trace, 'errorBarsOK')) continue; - - var xa = Axes.getFromId(gd, trace.xaxis), - ya = Axes.getFromId(gd, trace.yaxis); - - calcOneAxis(calcTrace, trace, xa, 'x'); - calcOneAxis(calcTrace, trace, ya, 'y'); - } -}; + calcOneAxis(calcTrace, trace, xa, 'x') + calcOneAxis(calcTrace, trace, ya, 'y') + } +} -function calcOneAxis(calcTrace, trace, axis, coord) { - var opts = trace['error_' + coord] || {}, - isVisible = (opts.visible && ['linear', 'log'].indexOf(axis.type) !== -1), - vals = []; +function calcOneAxis (calcTrace, trace, axis, coord) { + var opts = trace['error_' + coord] || {}, + isVisible = (opts.visible && ['linear', 'log'].indexOf(axis.type) !== -1), + vals = [] - if(!isVisible) return; + if (!isVisible) return - var computeError = makeComputeError(opts); + var computeError = makeComputeError(opts) - for(var i = 0; i < calcTrace.length; i++) { - var calcPt = calcTrace[i], - calcCoord = calcPt[coord]; + for (var i = 0; i < calcTrace.length; i++) { + var calcPt = calcTrace[i], + calcCoord = calcPt[coord] - if(!isNumeric(axis.c2l(calcCoord))) continue; + if (!isNumeric(axis.c2l(calcCoord))) continue - var errors = computeError(calcCoord, i); - if(isNumeric(errors[0]) && isNumeric(errors[1])) { - var shoe = calcPt[coord + 's'] = calcCoord - errors[0], - hat = calcPt[coord + 'h'] = calcCoord + errors[1]; - vals.push(shoe, hat); - } + var errors = computeError(calcCoord, i) + if (isNumeric(errors[0]) && isNumeric(errors[1])) { + var shoe = calcPt[coord + 's'] = calcCoord - errors[0], + hat = calcPt[coord + 'h'] = calcCoord + errors[1] + vals.push(shoe, hat) } + } - Axes.expand(axis, vals, {padded: true}); + Axes.expand(axis, vals, {padded: true}) } diff --git a/src/components/errorbars/compute_error.js b/src/components/errorbars/compute_error.js index dd0b189662d..ea3431516cc 100644 --- a/src/components/errorbars/compute_error.js +++ b/src/components/errorbars/compute_error.js @@ -6,9 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' /** * Error bar computing function generator @@ -25,46 +23,43 @@ * - error[0] : error magnitude in the negative direction * - error[1] : " " " " positive " */ -module.exports = function makeComputeError(opts) { - var type = opts.type, - symmetric = opts.symmetric; +module.exports = function makeComputeError (opts) { + var type = opts.type, + symmetric = opts.symmetric - if(type === 'data') { - var array = opts.array, - arrayminus = opts.arrayminus; + if (type === 'data') { + var array = opts.array, + arrayminus = opts.arrayminus - if(symmetric || arrayminus === undefined) { - return function computeError(dataPt, index) { - var val = +(array[index]); - return [val, val]; - }; - } - else { - return function computeError(dataPt, index) { - return [+arrayminus[index], +array[index]]; - }; - } + if (symmetric || arrayminus === undefined) { + return function computeError (dataPt, index) { + var val = +(array[index]) + return [val, val] + } + } else { + return function computeError (dataPt, index) { + return [+arrayminus[index], +array[index]] + } } - else { - var computeErrorValue = makeComputeErrorValue(type, opts.value), - computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus); + } else { + var computeErrorValue = makeComputeErrorValue(type, opts.value), + computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus) - if(symmetric || opts.valueminus === undefined) { - return function computeError(dataPt) { - var val = computeErrorValue(dataPt); - return [val, val]; - }; - } - else { - return function computeError(dataPt) { - return [ - computeErrorValueMinus(dataPt), - computeErrorValue(dataPt) - ]; - }; - } + if (symmetric || opts.valueminus === undefined) { + return function computeError (dataPt) { + var val = computeErrorValue(dataPt) + return [val, val] + } + } else { + return function computeError (dataPt) { + return [ + computeErrorValueMinus(dataPt), + computeErrorValue(dataPt) + ] + } } -}; + } +} /** * Compute error bar magnitude (for all types except data) @@ -75,20 +70,20 @@ module.exports = function makeComputeError(opts) { * @return {function} : * @param {numeric} dataPt */ -function makeComputeErrorValue(type, value) { - if(type === 'percent') { - return function(dataPt) { - return Math.abs(dataPt * value / 100); - }; +function makeComputeErrorValue (type, value) { + if (type === 'percent') { + return function (dataPt) { + return Math.abs(dataPt * value / 100) } - if(type === 'constant') { - return function() { - return Math.abs(value); - }; + } + if (type === 'constant') { + return function () { + return Math.abs(value) } - if(type === 'sqrt') { - return function(dataPt) { - return Math.sqrt(Math.abs(dataPt)); - }; + } + if (type === 'sqrt') { + return function (dataPt) { + return Math.sqrt(Math.abs(dataPt)) } + } } diff --git a/src/components/errorbars/defaults.js b/src/components/errorbars/defaults.js index 433f585352b..42e7eecf4e0 100644 --- a/src/components/errorbars/defaults.js +++ b/src/components/errorbars/defaults.js @@ -6,70 +6,68 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var isNumeric = require('fast-isnumeric'); +var isNumeric = require('fast-isnumeric') -var Registry = require('../../registry'); -var Lib = require('../../lib'); +var Registry = require('../../registry') +var Lib = require('../../lib') -var attributes = require('./attributes'); +var attributes = require('./attributes') +module.exports = function (traceIn, traceOut, defaultColor, opts) { + var objName = 'error_' + opts.axis, + containerOut = traceOut[objName] = {}, + containerIn = traceIn[objName] || {} -module.exports = function(traceIn, traceOut, defaultColor, opts) { - var objName = 'error_' + opts.axis, - containerOut = traceOut[objName] = {}, - containerIn = traceIn[objName] || {}; + function coerce (attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); - } - - var hasErrorBars = ( + var hasErrorBars = ( containerIn.array !== undefined || containerIn.value !== undefined || containerIn.type === 'sqrt' - ); + ) - var visible = coerce('visible', hasErrorBars); + var visible = coerce('visible', hasErrorBars) - if(visible === false) return; + if (visible === false) return - var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'), - symmetric = true; + var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'), + symmetric = true - if(type !== 'sqrt') { - symmetric = coerce('symmetric', - !((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn)); - } + if (type !== 'sqrt') { + symmetric = coerce('symmetric', + !((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn)) + } - if(type === 'data') { - var array = coerce('array'); - if(!array) containerOut.array = []; - coerce('traceref'); - if(!symmetric) { - var arrayminus = coerce('arrayminus'); - if(!arrayminus) containerOut.arrayminus = []; - coerce('tracerefminus'); - } - } - else if(type === 'percent' || type === 'constant') { - coerce('value'); - if(!symmetric) coerce('valueminus'); + if (type === 'data') { + var array = coerce('array') + if (!array) containerOut.array = [] + coerce('traceref') + if (!symmetric) { + var arrayminus = coerce('arrayminus') + if (!arrayminus) containerOut.arrayminus = [] + coerce('tracerefminus') } + } else if (type === 'percent' || type === 'constant') { + coerce('value') + if (!symmetric) coerce('valueminus') + } - var copyAttr = 'copy_' + opts.inherit + 'style'; - if(opts.inherit) { - var inheritObj = traceOut['error_' + opts.inherit]; - if((inheritObj || {}).visible) { - coerce(copyAttr, !(containerIn.color || + var copyAttr = 'copy_' + opts.inherit + 'style' + if (opts.inherit) { + var inheritObj = traceOut['error_' + opts.inherit] + if ((inheritObj || {}).visible) { + coerce(copyAttr, !(containerIn.color || isNumeric(containerIn.thickness) || - isNumeric(containerIn.width))); - } - } - if(!opts.inherit || !containerOut[copyAttr]) { - coerce('color', defaultColor); - coerce('thickness'); - coerce('width', Registry.traceIs(traceOut, 'gl3d') ? 0 : 4); + isNumeric(containerIn.width))) } -}; + } + if (!opts.inherit || !containerOut[copyAttr]) { + coerce('color', defaultColor) + coerce('thickness') + coerce('width', Registry.traceIs(traceOut, 'gl3d') ? 0 : 4) + } +} diff --git a/src/components/errorbars/index.js b/src/components/errorbars/index.js index a27378ad6e7..f847d423ada 100644 --- a/src/components/errorbars/index.js +++ b/src/components/errorbars/index.js @@ -6,52 +6,51 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var errorBars = module.exports = {} -var errorBars = module.exports = {}; +errorBars.attributes = require('./attributes') -errorBars.attributes = require('./attributes'); +errorBars.supplyDefaults = require('./defaults') -errorBars.supplyDefaults = require('./defaults'); +errorBars.calc = require('./calc') -errorBars.calc = require('./calc'); +errorBars.calcFromTrace = function (trace, layout) { + var x = trace.x || [], + y = trace.y, + len = x.length || y.length -errorBars.calcFromTrace = function(trace, layout) { - var x = trace.x || [], - y = trace.y, - len = x.length || y.length; + var calcdataMock = new Array(len) - var calcdataMock = new Array(len); - - for(var i = 0; i < len; i++) { - calcdataMock[i] = { - x: x[i], - y: y[i] - }; + for (var i = 0; i < len; i++) { + calcdataMock[i] = { + x: x[i], + y: y[i] } + } - calcdataMock[0].trace = trace; + calcdataMock[0].trace = trace - errorBars.calc({ - calcdata: [calcdataMock], - _fullLayout: layout - }); + errorBars.calc({ + calcdata: [calcdataMock], + _fullLayout: layout + }) - return calcdataMock; -}; + return calcdataMock +} -errorBars.plot = require('./plot'); +errorBars.plot = require('./plot') -errorBars.style = require('./style'); +errorBars.style = require('./style') -errorBars.hoverInfo = function(calcPoint, trace, hoverPoint) { - if((trace.error_y || {}).visible) { - hoverPoint.yerr = calcPoint.yh - calcPoint.y; - if(!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys; - } - if((trace.error_x || {}).visible) { - hoverPoint.xerr = calcPoint.xh - calcPoint.x; - if(!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs; - } -}; +errorBars.hoverInfo = function (calcPoint, trace, hoverPoint) { + if ((trace.error_y || {}).visible) { + hoverPoint.yerr = calcPoint.yh - calcPoint.y + if (!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys + } + if ((trace.error_x || {}).visible) { + hoverPoint.xerr = calcPoint.xh - calcPoint.x + if (!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs + } +} diff --git a/src/components/errorbars/plot.js b/src/components/errorbars/plot.js index 84bc05504bf..fad700f1bb8 100644 --- a/src/components/errorbars/plot.js +++ b/src/components/errorbars/plot.js @@ -6,157 +6,155 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var subTypes = require('../../traces/scatter/subtypes') -var subTypes = require('../../traces/scatter/subtypes'); +module.exports = function plot (traces, plotinfo, transitionOpts) { + var isNew -module.exports = function plot(traces, plotinfo, transitionOpts) { - var isNew; + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis; + var hasAnimation = transitionOpts && transitionOpts.duration > 0 - var hasAnimation = transitionOpts && transitionOpts.duration > 0; - - traces.each(function(d) { - var trace = d[0].trace, + traces.each(function (d) { + var trace = d[0].trace, // || {} is in case the trace (specifically scatterternary) // doesn't support error bars at all, but does go through // the scatter.plot mechanics, which calls ErrorBars.plot // internally - xObj = trace.error_x || {}, - yObj = trace.error_y || {}; + xObj = trace.error_x || {}, + yObj = trace.error_y || {} - var keyFunc; + var keyFunc - if(trace.ids) { - keyFunc = function(d) {return d.id;}; - } + if (trace.ids) { + keyFunc = function (d) { return d.id } + } - var sparse = ( + var sparse = ( subTypes.hasMarkers(trace) && trace.marker.maxdisplayed > 0 - ); + ) - if(!yObj.visible && !xObj.visible) return; + if (!yObj.visible && !xObj.visible) return - var errorbars = d3.select(this).selectAll('g.errorbar') - .data(d, keyFunc); + var errorbars = d3.select(this).selectAll('g.errorbar') + .data(d, keyFunc) - errorbars.exit().remove(); + errorbars.exit().remove() - errorbars.style('opacity', 1); + errorbars.style('opacity', 1) - var enter = errorbars.enter().append('g') - .classed('errorbar', true); + var enter = errorbars.enter().append('g') + .classed('errorbar', true) - if(hasAnimation) { - enter.style('opacity', 0).transition() + if (hasAnimation) { + enter.style('opacity', 0).transition() .duration(transitionOpts.duration) - .style('opacity', 1); - } + .style('opacity', 1) + } - errorbars.each(function(d) { - var errorbar = d3.select(this); - var coords = errorCoords(d, xa, ya); + errorbars.each(function (d) { + var errorbar = d3.select(this) + var coords = errorCoords(d, xa, ya) - if(sparse && !d.vis) return; + if (sparse && !d.vis) return - var path; + var path - if(yObj.visible && isNumeric(coords.x) && + if (yObj.visible && isNumeric(coords.x) && isNumeric(coords.yh) && isNumeric(coords.ys)) { - var yw = yObj.width; + var yw = yObj.width - path = 'M' + (coords.x - yw) + ',' + + path = 'M' + (coords.x - yw) + ',' + coords.yh + 'h' + (2 * yw) + // hat - 'm-' + yw + ',0V' + coords.ys; // bar - + 'm-' + yw + ',0V' + coords.ys // bar - if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe + if (!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw) // shoe - var yerror = errorbar.select('path.yerror'); + var yerror = errorbar.select('path.yerror') - isNew = !yerror.size(); + isNew = !yerror.size() - if(isNew) { - yerror = errorbar.append('path') - .classed('yerror', true); - } else if(hasAnimation) { - yerror = yerror + if (isNew) { + yerror = errorbar.append('path') + .classed('yerror', true) + } else if (hasAnimation) { + yerror = yerror .transition() .duration(transitionOpts.duration) - .ease(transitionOpts.easing); - } + .ease(transitionOpts.easing) + } - yerror.attr('d', path); - } + yerror.attr('d', path) + } - if(xObj.visible && isNumeric(coords.y) && + if (xObj.visible && isNumeric(coords.y) && isNumeric(coords.xh) && isNumeric(coords.xs)) { - var xw = (xObj.copy_ystyle ? yObj : xObj).width; + var xw = (xObj.copy_ystyle ? yObj : xObj).width - path = 'M' + coords.xh + ',' + + path = 'M' + coords.xh + ',' + (coords.y - xw) + 'v' + (2 * xw) + // hat - 'm0,-' + xw + 'H' + coords.xs; // bar + 'm0,-' + xw + 'H' + coords.xs // bar - if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe + if (!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw) // shoe - var xerror = errorbar.select('path.xerror'); + var xerror = errorbar.select('path.xerror') - isNew = !xerror.size(); + isNew = !xerror.size() - if(isNew) { - xerror = errorbar.append('path') - .classed('xerror', true); - } else if(hasAnimation) { - xerror = xerror + if (isNew) { + xerror = errorbar.append('path') + .classed('xerror', true) + } else if (hasAnimation) { + xerror = xerror .transition() .duration(transitionOpts.duration) - .ease(transitionOpts.easing); - } + .ease(transitionOpts.easing) + } - xerror.attr('d', path); - } - }); - }); -}; + xerror.attr('d', path) + } + }) + }) +} // compute the coordinates of the error-bar objects -function errorCoords(d, xa, ya) { - var out = { - x: xa.c2p(d.x), - y: ya.c2p(d.y) - }; +function errorCoords (d, xa, ya) { + var out = { + x: xa.c2p(d.x), + y: ya.c2p(d.y) + } // calculate the error bar size and hat and shoe locations - if(d.yh !== undefined) { - out.yh = ya.c2p(d.yh); - out.ys = ya.c2p(d.ys); + if (d.yh !== undefined) { + out.yh = ya.c2p(d.yh) + out.ys = ya.c2p(d.ys) // if the shoes go off-scale (ie log scale, error bars past zero) // clip the bar and hide the shoes - if(!isNumeric(out.ys)) { - out.noYS = true; - out.ys = ya.c2p(d.ys, true); - } + if (!isNumeric(out.ys)) { + out.noYS = true + out.ys = ya.c2p(d.ys, true) } + } - if(d.xh !== undefined) { - out.xh = xa.c2p(d.xh); - out.xs = xa.c2p(d.xs); + if (d.xh !== undefined) { + out.xh = xa.c2p(d.xh) + out.xs = xa.c2p(d.xs) - if(!isNumeric(out.xs)) { - out.noXS = true; - out.xs = xa.c2p(d.xs, true); - } + if (!isNumeric(out.xs)) { + out.noXS = true + out.xs = xa.c2p(d.xs, true) } + } - return out; + return out } diff --git a/src/components/errorbars/style.js b/src/components/errorbars/style.js index b6c81feb662..ddb2758be26 100644 --- a/src/components/errorbars/style.js +++ b/src/components/errorbars/style.js @@ -6,30 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Color = require('../color') -var Color = require('../color'); +module.exports = function style (traces) { + traces.each(function (d) { + var trace = d[0].trace, + yObj = trace.error_y || {}, + xObj = trace.error_x || {} + var s = d3.select(this) -module.exports = function style(traces) { - traces.each(function(d) { - var trace = d[0].trace, - yObj = trace.error_y || {}, - xObj = trace.error_x || {}; - - var s = d3.select(this); - - s.selectAll('path.yerror') + s.selectAll('path.yerror') .style('stroke-width', yObj.thickness + 'px') - .call(Color.stroke, yObj.color); + .call(Color.stroke, yObj.color) - if(xObj.copy_ystyle) xObj = yObj; + if (xObj.copy_ystyle) xObj = yObj - s.selectAll('path.xerror') + s.selectAll('path.xerror') .style('stroke-width', xObj.thickness + 'px') - .call(Color.stroke, xObj.color); - }); -}; + .call(Color.stroke, xObj.color) + }) +} diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index 2f15c72f908..29ce135fdb7 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -6,162 +6,161 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var cartesianConstants = require('../../plots/cartesian/constants'); +'use strict' +var cartesianConstants = require('../../plots/cartesian/constants') module.exports = { - _isLinkedToArray: 'image', - - visible: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not this image is visible.' - ].join(' ') - }, - - source: { - valType: 'string', - role: 'info', - description: [ - 'Specifies the URL of the image to be used.', - 'The URL must be accessible from the domain where the', - 'plot code is run, and can be either relative or absolute.' - - ].join(' ') - }, - - layer: { - valType: 'enumerated', - values: ['below', 'above'], - dflt: 'above', - role: 'info', - description: [ - 'Specifies whether images are drawn below or above traces.', - 'When `xref` and `yref` are both set to `paper`,', - 'image is drawn below the entire plot area.' - ].join(' ') - }, - - sizex: { - valType: 'number', - role: 'info', - dflt: 0, - description: [ - 'Sets the image container size horizontally.', - 'The image will be sized based on the `position` value.', - 'When `xref` is set to `paper`, units are sized relative', - 'to the plot width.' - ].join(' ') - }, - - sizey: { - valType: 'number', - role: 'info', - dflt: 0, - description: [ - 'Sets the image container size vertically.', - 'The image will be sized based on the `position` value.', - 'When `yref` is set to `paper`, units are sized relative', - 'to the plot height.' - ].join(' ') - }, - - sizing: { - valType: 'enumerated', - values: ['fill', 'contain', 'stretch'], - dflt: 'contain', - role: 'info', - description: [ - 'Specifies which dimension of the image to constrain.' - ].join(' ') - }, - - opacity: { - valType: 'number', - role: 'info', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the opacity of the image.' - }, - - x: { - valType: 'any', - role: 'info', - dflt: 0, - description: [ - 'Sets the image\'s x position.', - 'When `xref` is set to `paper`, units are sized relative', - 'to the plot height.', - 'See `xref` for more info' - ].join(' ') - }, - - y: { - valType: 'any', - role: 'info', - dflt: 0, - description: [ - 'Sets the image\'s y position.', - 'When `yref` is set to `paper`, units are sized relative', - 'to the plot height.', - 'See `yref` for more info' - ].join(' ') - }, - - xanchor: { - valType: 'enumerated', - values: ['left', 'center', 'right'], - dflt: 'left', - role: 'info', - description: 'Sets the anchor for the x position' - }, - - yanchor: { - valType: 'enumerated', - values: ['top', 'middle', 'bottom'], - dflt: 'top', - role: 'info', - description: 'Sets the anchor for the y position.' - }, - - xref: { - valType: 'enumerated', - values: [ - 'paper', - cartesianConstants.idRegex.x.toString() - ], - dflt: 'paper', - role: 'info', - description: [ - 'Sets the images\'s x coordinate axis.', - 'If set to a x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x data coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left of plot in normalized coordinates', - 'where *0* (*1*) corresponds to the left (right).' - ].join(' ') - }, - - yref: { - valType: 'enumerated', - values: [ - 'paper', - cartesianConstants.idRegex.y.toString() - ], - dflt: 'paper', - role: 'info', - description: [ - 'Sets the images\'s y coordinate axis.', - 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to a y data coordinate.', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plot in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).' - ].join(' ') - } -}; + _isLinkedToArray: 'image', + + visible: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not this image is visible.' + ].join(' ') + }, + + source: { + valType: 'string', + role: 'info', + description: [ + 'Specifies the URL of the image to be used.', + 'The URL must be accessible from the domain where the', + 'plot code is run, and can be either relative or absolute.' + + ].join(' ') + }, + + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + role: 'info', + description: [ + 'Specifies whether images are drawn below or above traces.', + 'When `xref` and `yref` are both set to `paper`,', + 'image is drawn below the entire plot area.' + ].join(' ') + }, + + sizex: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image container size horizontally.', + 'The image will be sized based on the `position` value.', + 'When `xref` is set to `paper`, units are sized relative', + 'to the plot width.' + ].join(' ') + }, + + sizey: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Sets the image container size vertically.', + 'The image will be sized based on the `position` value.', + 'When `yref` is set to `paper`, units are sized relative', + 'to the plot height.' + ].join(' ') + }, + + sizing: { + valType: 'enumerated', + values: ['fill', 'contain', 'stretch'], + dflt: 'contain', + role: 'info', + description: [ + 'Specifies which dimension of the image to constrain.' + ].join(' ') + }, + + opacity: { + valType: 'number', + role: 'info', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of the image.' + }, + + x: { + valType: 'any', + role: 'info', + dflt: 0, + description: [ + 'Sets the image\'s x position.', + 'When `xref` is set to `paper`, units are sized relative', + 'to the plot height.', + 'See `xref` for more info' + ].join(' ') + }, + + y: { + valType: 'any', + role: 'info', + dflt: 0, + description: [ + 'Sets the image\'s y position.', + 'When `yref` is set to `paper`, units are sized relative', + 'to the plot height.', + 'See `yref` for more info' + ].join(' ') + }, + + xanchor: { + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: 'Sets the anchor for the x position' + }, + + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + dflt: 'top', + role: 'info', + description: 'Sets the anchor for the y position.' + }, + + xref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.x.toString() + ], + dflt: 'paper', + role: 'info', + description: [ + 'Sets the images\'s x coordinate axis.', + 'If set to a x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x data coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left of plot in normalized coordinates', + 'where *0* (*1*) corresponds to the left (right).' + ].join(' ') + }, + + yref: { + valType: 'enumerated', + values: [ + 'paper', + cartesianConstants.idRegex.y.toString() + ], + dflt: 'paper', + role: 'info', + description: [ + 'Sets the images\'s y coordinate axis.', + 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to a y data coordinate.', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plot in normalized coordinates', + 'where *0* (*1*) corresponds to the bottom (top).' + ].join(' ') + } +} diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 0c6c5b32c93..de9e7bada65 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -6,53 +6,51 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); -var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') +var handleArrayContainerDefaults = require('../../plots/array_container_defaults') -var attributes = require('./attributes'); -var name = 'images'; +var attributes = require('./attributes') +var name = 'images' -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var opts = { - name: name, - handleItemDefaults: imageDefaults - }; +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut) { + var opts = { + name: name, + handleItemDefaults: imageDefaults + } - handleArrayContainerDefaults(layoutIn, layoutOut, opts); -}; - - -function imageDefaults(imageIn, imageOut, fullLayout) { + handleArrayContainerDefaults(layoutIn, layoutOut, opts) +} - function coerce(attr, dflt) { - return Lib.coerce(imageIn, imageOut, attributes, attr, dflt); - } +function imageDefaults (imageIn, imageOut, fullLayout) { + function coerce (attr, dflt) { + return Lib.coerce(imageIn, imageOut, attributes, attr, dflt) + } - var source = coerce('source'); - var visible = coerce('visible', !!source); + var source = coerce('source') + var visible = coerce('visible', !!source) - if(!visible) return imageOut; + if (!visible) return imageOut - coerce('layer'); - coerce('x'); - coerce('y'); - coerce('xanchor'); - coerce('yanchor'); - coerce('sizex'); - coerce('sizey'); - coerce('sizing'); - coerce('opacity'); + coerce('layer') + coerce('x') + coerce('y') + coerce('xanchor') + coerce('yanchor') + coerce('sizex') + coerce('sizey') + coerce('sizing') + coerce('opacity') - var gdMock = { _fullLayout: fullLayout }, - axLetters = ['x', 'y']; + var gdMock = { _fullLayout: fullLayout }, + axLetters = ['x', 'y'] - for(var i = 0; i < 2; i++) { + for (var i = 0; i < 2; i++) { // 'paper' is the fallback axref - Axes.coerceRef(imageIn, imageOut, gdMock, axLetters[i], 'paper'); - } + Axes.coerceRef(imageIn, imageOut, gdMock, axLetters[i], 'paper') + } - return imageOut; + return imageOut } diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 228916d3de4..85679432b20 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -6,173 +6,167 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); -var Drawing = require('../drawing'); -var Axes = require('../../plots/cartesian/axes'); -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); +var d3 = require('d3') +var Drawing = require('../drawing') +var Axes = require('../../plots/cartesian/axes') +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') -module.exports = function draw(gd) { - var fullLayout = gd._fullLayout, - imageDataAbove = [], - imageDataSubplot = [], - imageDataBelow = []; +module.exports = function draw (gd) { + var fullLayout = gd._fullLayout, + imageDataAbove = [], + imageDataSubplot = [], + imageDataBelow = [] // Sort into top, subplot, and bottom layers - for(var i = 0; i < fullLayout.images.length; i++) { - var img = fullLayout.images[i]; - - if(img.visible) { - if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { - imageDataSubplot.push(img); - } else if(img.layer === 'above') { - imageDataAbove.push(img); - } else { - imageDataBelow.push(img); - } - } + for (var i = 0; i < fullLayout.images.length; i++) { + var img = fullLayout.images[i] + + if (img.visible) { + if (img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') { + imageDataSubplot.push(img) + } else if (img.layer === 'above') { + imageDataAbove.push(img) + } else { + imageDataBelow.push(img) + } } - - - var anchors = { - x: { - left: { sizing: 'xMin', offset: 0 }, - center: { sizing: 'xMid', offset: -1 / 2 }, - right: { sizing: 'xMax', offset: -1 } - }, - y: { - top: { sizing: 'YMin', offset: 0 }, - middle: { sizing: 'YMid', offset: -1 / 2 }, - bottom: { sizing: 'YMax', offset: -1 } - } - }; - + } + + var anchors = { + x: { + left: { sizing: 'xMin', offset: 0 }, + center: { sizing: 'xMid', offset: -1 / 2 }, + right: { sizing: 'xMax', offset: -1 } + }, + y: { + top: { sizing: 'YMin', offset: 0 }, + middle: { sizing: 'YMid', offset: -1 / 2 }, + bottom: { sizing: 'YMax', offset: -1 } + } + } // Images must be converted to dataURL's for exporting. - function setImage(d) { - var thisImage = d3.select(this); - - if(this.img && this.img.src === d.source) { - return; - } + function setImage (d) { + var thisImage = d3.select(this) - thisImage.attr('xmlns', xmlnsNamespaces.svg); + if (this.img && this.img.src === d.source) { + return + } - var imagePromise = new Promise(function(resolve) { + thisImage.attr('xmlns', xmlnsNamespaces.svg) - var img = new Image(); - this.img = img; + var imagePromise = new Promise(function (resolve) { + var img = new Image() + this.img = img // If not set, a `tainted canvas` error is thrown - img.setAttribute('crossOrigin', 'anonymous'); - img.onerror = errorHandler; - img.onload = function() { - var canvas = document.createElement('canvas'); - canvas.width = this.width; - canvas.height = this.height; - - var ctx = canvas.getContext('2d'); - ctx.drawImage(this, 0, 0); + img.setAttribute('crossOrigin', 'anonymous') + img.onerror = errorHandler + img.onload = function () { + var canvas = document.createElement('canvas') + canvas.width = this.width + canvas.height = this.height - var dataURL = canvas.toDataURL('image/png'); + var ctx = canvas.getContext('2d') + ctx.drawImage(this, 0, 0) - thisImage.attr('xlink:href', dataURL); - }; + var dataURL = canvas.toDataURL('image/png') + thisImage.attr('xlink:href', dataURL) + } - thisImage.on('error', errorHandler); - thisImage.on('load', resolve); + thisImage.on('error', errorHandler) + thisImage.on('load', resolve) - img.src = d.source; + img.src = d.source - function errorHandler() { - thisImage.remove(); - resolve(); - } - }.bind(this)); + function errorHandler () { + thisImage.remove() + resolve() + } + }.bind(this)) - gd._promises.push(imagePromise); - } + gd._promises.push(imagePromise) + } - function applyAttributes(d) { - var thisImage = d3.select(this); + function applyAttributes (d) { + var thisImage = d3.select(this) // Axes if specified - var xa = Axes.getFromId(gd, d.xref), - ya = Axes.getFromId(gd, d.yref); + var xa = Axes.getFromId(gd, d.xref), + ya = Axes.getFromId(gd, d.yref) - var size = fullLayout._size, - width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w, - height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h; + var size = fullLayout._size, + width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w, + height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h // Offsets for anchor positioning - var xOffset = width * anchors.x[d.xanchor].offset, - yOffset = height * anchors.y[d.yanchor].offset; + var xOffset = width * anchors.x[d.xanchor].offset, + yOffset = height * anchors.y[d.yanchor].offset - var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing; + var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing // Final positions - var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset, - yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset; - + var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset, + yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset // Construct the proper aspectRatio attribute - switch(d.sizing) { - case 'fill': - sizing += ' slice'; - break; - - case 'stretch': - sizing = 'none'; - break; - } - - thisImage.attr({ - x: xPos, - y: yPos, - width: width, - height: height, - preserveAspectRatio: sizing, - opacity: d.opacity - }); + switch (d.sizing) { + case 'fill': + sizing += ' slice' + break + + case 'stretch': + sizing = 'none' + break + } + thisImage.attr({ + x: xPos, + y: yPos, + width: width, + height: height, + preserveAspectRatio: sizing, + opacity: d.opacity + }) // Set proper clipping on images - var xId = xa ? xa._id : '', - yId = ya ? ya._id : '', - clipAxes = xId + yId; + var xId = xa ? xa._id : '', + yId = ya ? ya._id : '', + clipAxes = xId + yId - if(clipAxes) { - thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes); - } + if (clipAxes) { + thisImage.call(Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes) } + } - var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') + var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') .data(imageDataBelow), - imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image') + imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image') .data(imageDataSubplot), - imagesAbove = fullLayout._imageUpperLayer.selectAll('image') - .data(imageDataAbove); - - imagesBelow.enter().append('image'); - imagesSubplot.enter().append('image'); - imagesAbove.enter().append('image'); - - imagesBelow.exit().remove(); - imagesSubplot.exit().remove(); - imagesAbove.exit().remove(); - - imagesBelow.each(function(d) { - setImage.bind(this)(d); - applyAttributes.bind(this)(d); - }); - imagesSubplot.each(function(d) { - setImage.bind(this)(d); - applyAttributes.bind(this)(d); - }); - imagesAbove.each(function(d) { - setImage.bind(this)(d); - applyAttributes.bind(this)(d); - }); -}; + imagesAbove = fullLayout._imageUpperLayer.selectAll('image') + .data(imageDataAbove) + + imagesBelow.enter().append('image') + imagesSubplot.enter().append('image') + imagesAbove.enter().append('image') + + imagesBelow.exit().remove() + imagesSubplot.exit().remove() + imagesAbove.exit().remove() + + imagesBelow.each(function (d) { + setImage.bind(this)(d) + applyAttributes.bind(this)(d) + }) + imagesSubplot.each(function (d) { + setImage.bind(this)(d) + applyAttributes.bind(this)(d) + }) + imagesAbove.each(function (d) { + setImage.bind(this)(d) + applyAttributes.bind(this)(d) + }) +} diff --git a/src/components/images/index.js b/src/components/images/index.js index d7ce308ae28..5fa0a99bbdd 100644 --- a/src/components/images/index.js +++ b/src/components/images/index.js @@ -6,14 +6,14 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - moduleType: 'component', - name: 'images', + moduleType: 'component', + name: 'images', - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - draw: require('./draw') -}; + draw: require('./draw') +} diff --git a/src/components/legend/anchor_utils.js b/src/components/legend/anchor_utils.js index 2dcc0161538..e27dc1b12cb 100644 --- a/src/components/legend/anchor_utils.js +++ b/src/components/legend/anchor_utils.js @@ -6,9 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' /** * Determine the position anchor property of x/y xanchor/yanchor components. @@ -18,30 +16,30 @@ * - values > 2/3 align the right at that fraction. */ -exports.isRightAnchor = function isRightAnchor(opts) { - return ( +exports.isRightAnchor = function isRightAnchor (opts) { + return ( opts.xanchor === 'right' || (opts.xanchor === 'auto' && opts.x >= 2 / 3) - ); -}; + ) +} -exports.isCenterAnchor = function isCenterAnchor(opts) { - return ( +exports.isCenterAnchor = function isCenterAnchor (opts) { + return ( opts.xanchor === 'center' || (opts.xanchor === 'auto' && opts.x > 1 / 3 && opts.x < 2 / 3) - ); -}; + ) +} -exports.isBottomAnchor = function isBottomAnchor(opts) { - return ( +exports.isBottomAnchor = function isBottomAnchor (opts) { + return ( opts.yanchor === 'bottom' || (opts.yanchor === 'auto' && opts.y <= 1 / 3) - ); -}; + ) +} -exports.isMiddleAnchor = function isMiddleAnchor(opts) { - return ( +exports.isMiddleAnchor = function isMiddleAnchor (opts) { + return ( opts.yanchor === 'middle' || (opts.yanchor === 'auto' && opts.y > 1 / 3 && opts.y < 2 / 3) - ); -}; + ) +} diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 8ae61ac29be..ea1524eedd7 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -6,108 +6,107 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var fontAttrs = require('../../plots/font_attributes'); -var colorAttrs = require('../color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +'use strict' +var fontAttrs = require('../../plots/font_attributes') +var colorAttrs = require('../color/attributes') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { - bgcolor: { - valType: 'color', - role: 'style', - description: 'Sets the legend background color.' - }, - bordercolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the color of the border enclosing the legend.' - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: 'Sets the width (in px) of the border enclosing the legend.' - }, - font: extendFlat({}, fontAttrs, { - description: 'Sets the font used to text the legend items.' - }), - orientation: { - valType: 'enumerated', - values: ['v', 'h'], - dflt: 'v', - role: 'info', - description: 'Sets the orientation of the legend.' - }, - traceorder: { - valType: 'flaglist', - flags: ['reversed', 'grouped'], - extras: ['normal'], - role: 'style', - description: [ - 'Determines the order at which the legend items are displayed.', + bgcolor: { + valType: 'color', + role: 'style', + description: 'Sets the legend background color.' + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the color of the border enclosing the legend.' + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: 'Sets the width (in px) of the border enclosing the legend.' + }, + font: extendFlat({}, fontAttrs, { + description: 'Sets the font used to text the legend items.' + }), + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'v', + role: 'info', + description: 'Sets the orientation of the legend.' + }, + traceorder: { + valType: 'flaglist', + flags: ['reversed', 'grouped'], + extras: ['normal'], + role: 'style', + description: [ + 'Determines the order at which the legend items are displayed.', - 'If *normal*, the items are displayed top-to-bottom in the same', - 'order as the input data.', + 'If *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 *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*, 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(' ') - } -}; + 'if *grouped+reversed*, the items are displayed in the opposite order', + 'as *grouped*.' + ].join(' ') + }, + tracegroupgap: { + valType: 'number', + min: 0, + dflt: 10, + role: 'style', + description: [ + 'Sets the amount of vertical space (in px) between legend groups.' + ].join(' ') + }, + x: { + valType: 'number', + min: -2, + max: 3, + dflt: 1.02, + role: 'style', + description: 'Sets the x position (in normalized coordinates) of the legend.' + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: [ + 'Sets the legend\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the legend.' + ].join(' ') + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 1, + role: 'style', + description: 'Sets the y position (in normalized coordinates) of the legend.' + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'auto', + role: 'info', + description: [ + 'Sets the legend\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the legend.' + ].join(' ') + } +} diff --git a/src/components/legend/constants.js b/src/components/legend/constants.js index 527fa7ba190..2da55b86ca3 100644 --- a/src/components/legend/constants.js +++ b/src/components/legend/constants.js @@ -6,11 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - scrollBarWidth: 4, - scrollBarHeight: 20, - scrollBarColor: '#808BA4', - scrollBarMargin: 4 -}; + scrollBarWidth: 4, + scrollBarHeight: 20, + scrollBarColor: '#808BA4', + scrollBarMargin: 4 +} diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index a094d5799ba..c4969f2e85d 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -6,86 +6,83 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Lib = require('../../lib') -var Registry = require('../../registry'); -var Lib = require('../../lib'); +var attributes = require('./attributes') +var basePlotLayoutAttributes = require('../../plots/layout_attributes') +var helpers = require('./helpers') -var attributes = require('./attributes'); -var basePlotLayoutAttributes = require('../../plots/layout_attributes'); -var helpers = require('./helpers'); +module.exports = function legendDefaults (layoutIn, layoutOut, fullData) { + var containerIn = layoutIn.legend || {}, + containerOut = layoutOut.legend = {} + var visibleTraces = 0, + defaultOrder = 'normal', + defaultX, + defaultY, + defaultXAnchor, + defaultYAnchor -module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { - var containerIn = layoutIn.legend || {}, - containerOut = layoutOut.legend = {}; + for (var i = 0; i < fullData.length; i++) { + var trace = fullData[i] - var visibleTraces = 0, - defaultOrder = 'normal', - defaultX, - defaultY, - defaultXAnchor, - defaultYAnchor; - - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; - - if(helpers.legendGetsTrace(trace)) { - visibleTraces++; + if (helpers.legendGetsTrace(trace)) { + visibleTraces++ // always show the legend by default if there's a pie - if(Registry.traceIs(trace, 'pie')) visibleTraces++; - } + if (Registry.traceIs(trace, 'pie')) visibleTraces++ + } - if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') || + if ((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') || ['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) { - defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ? - 'grouped+reversed' : 'reversed'; - } - - if(trace.legendgroup !== undefined && trace.legendgroup !== '') { - defaultOrder = helpers.isReversed({traceorder: defaultOrder}) ? - 'reversed+grouped' : 'grouped'; - } + defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ? + 'grouped+reversed' : 'reversed' } - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + if (trace.legendgroup !== undefined && trace.legendgroup !== '') { + defaultOrder = helpers.isReversed({traceorder: defaultOrder}) ? + 'reversed+grouped' : 'grouped' } - - var showLegend = Lib.coerce(layoutIn, layoutOut, - basePlotLayoutAttributes, 'showlegend', visibleTraces > 1); - - if(showLegend === false) return; - - coerce('bgcolor', layoutOut.paper_bgcolor); - coerce('bordercolor'); - coerce('borderwidth'); - Lib.coerceFont(coerce, 'font', layoutOut.font); - - coerce('orientation'); - if(containerOut.orientation === 'h') { - var xaxis = layoutIn.xaxis; - if(xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) { - defaultX = 0; - defaultXAnchor = 'left'; - defaultY = 1.1; - defaultYAnchor = 'bottom'; - } - else { - defaultX = 0; - defaultXAnchor = 'left'; - defaultY = -0.1; - defaultYAnchor = 'top'; - } + } + + function coerce (attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt) + } + + var showLegend = Lib.coerce(layoutIn, layoutOut, + basePlotLayoutAttributes, 'showlegend', visibleTraces > 1) + + if (showLegend === false) return + + coerce('bgcolor', layoutOut.paper_bgcolor) + coerce('bordercolor') + coerce('borderwidth') + Lib.coerceFont(coerce, 'font', layoutOut.font) + + coerce('orientation') + if (containerOut.orientation === 'h') { + var xaxis = layoutIn.xaxis + if (xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) { + defaultX = 0 + defaultXAnchor = 'left' + defaultY = 1.1 + defaultYAnchor = 'bottom' + } else { + defaultX = 0 + defaultXAnchor = 'left' + defaultY = -0.1 + defaultYAnchor = 'top' } + } - coerce('traceorder', defaultOrder); - if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap'); + coerce('traceorder', defaultOrder) + if (helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap') - coerce('x', defaultX); - coerce('xanchor', defaultXAnchor); - coerce('y', defaultY); - coerce('yanchor', defaultYAnchor); - Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); -}; + coerce('x', defaultX) + coerce('xanchor', defaultXAnchor) + coerce('y', defaultY) + coerce('yanchor', defaultYAnchor) + Lib.noneOrAll(containerIn, containerOut, ['x', 'y']) +} diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index d6c836c14e1..9be02d4ad99 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -6,709 +6,693 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Plotly = require('../../plotly') +var Lib = require('../../lib') +var Plots = require('../../plots/plots') +var Registry = require('../../registry') +var dragElement = require('../dragelement') +var Drawing = require('../drawing') +var Color = require('../color') +var svgTextUtils = require('../../lib/svg_text_utils') -var Plotly = require('../../plotly'); -var Lib = require('../../lib'); -var Plots = require('../../plots/plots'); -var Registry = require('../../registry'); -var dragElement = require('../dragelement'); -var Drawing = require('../drawing'); -var Color = require('../color'); -var svgTextUtils = require('../../lib/svg_text_utils'); +var constants = require('./constants') +var getLegendData = require('./get_legend_data') +var style = require('./style') +var helpers = require('./helpers') +var anchorUtils = require('./anchor_utils') -var constants = require('./constants'); -var getLegendData = require('./get_legend_data'); -var style = require('./style'); -var helpers = require('./helpers'); -var anchorUtils = require('./anchor_utils'); +module.exports = function draw (gd) { + var fullLayout = gd._fullLayout + var clipId = 'legend' + fullLayout._uid + if (!fullLayout._infolayer || !gd.calcdata) return -module.exports = function draw(gd) { - var fullLayout = gd._fullLayout; - var clipId = 'legend' + fullLayout._uid; + var opts = fullLayout.legend, + legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts), + hiddenSlices = fullLayout.hiddenlabels || [] - if(!fullLayout._infolayer || !gd.calcdata) return; + if (!fullLayout.showlegend || !legendData.length) { + fullLayout._infolayer.selectAll('.legend').remove() + fullLayout._topdefs.select('#' + clipId).remove() - var opts = fullLayout.legend, - legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts), - hiddenSlices = fullLayout.hiddenlabels || []; + Plots.autoMargin(gd, 'legend') + return + } - if(!fullLayout.showlegend || !legendData.length) { - fullLayout._infolayer.selectAll('.legend').remove(); - fullLayout._topdefs.select('#' + clipId).remove(); + var legend = fullLayout._infolayer.selectAll('g.legend') + .data([0]) - Plots.autoMargin(gd, 'legend'); - return; - } - - var legend = fullLayout._infolayer.selectAll('g.legend') - .data([0]); - - legend.enter().append('g') + legend.enter().append('g') .attr({ - 'class': 'legend', - 'pointer-events': 'all' - }); + 'class': 'legend', + 'pointer-events': 'all' + }) - var clipPath = fullLayout._topdefs.selectAll('#' + clipId) - .data([0]); + var clipPath = fullLayout._topdefs.selectAll('#' + clipId) + .data([0]) - clipPath.enter().append('clipPath') + clipPath.enter().append('clipPath') .attr('id', clipId) - .append('rect'); + .append('rect') - var bg = legend.selectAll('rect.bg') - .data([0]); + var bg = legend.selectAll('rect.bg') + .data([0]) - bg.enter().append('rect').attr({ - 'class': 'bg', - 'shape-rendering': 'crispEdges' - }); + bg.enter().append('rect').attr({ + 'class': 'bg', + 'shape-rendering': 'crispEdges' + }) - bg.call(Color.stroke, opts.bordercolor); - bg.call(Color.fill, opts.bgcolor); - bg.style('stroke-width', opts.borderwidth + 'px'); + bg.call(Color.stroke, opts.bordercolor) + bg.call(Color.fill, opts.bgcolor) + bg.style('stroke-width', opts.borderwidth + 'px') - var scrollBox = legend.selectAll('g.scrollbox') - .data([0]); + var scrollBox = legend.selectAll('g.scrollbox') + .data([0]) - scrollBox.enter().append('g') - .attr('class', 'scrollbox'); + scrollBox.enter().append('g') + .attr('class', 'scrollbox') - var scrollBar = legend.selectAll('rect.scrollbar') - .data([0]); + var scrollBar = legend.selectAll('rect.scrollbar') + .data([0]) - scrollBar.enter().append('rect') + scrollBar.enter().append('rect') .attr({ - 'class': 'scrollbar', - 'rx': 20, - 'ry': 2, - 'width': 0, - 'height': 0 + 'class': 'scrollbar', + 'rx': 20, + 'ry': 2, + 'width': 0, + 'height': 0 }) - .call(Color.fill, '#808BA4'); + .call(Color.fill, '#808BA4') - var groups = scrollBox.selectAll('g.groups') - .data(legendData); + var groups = scrollBox.selectAll('g.groups') + .data(legendData) - groups.enter().append('g') - .attr('class', 'groups'); + groups.enter().append('g') + .attr('class', 'groups') - groups.exit().remove(); + groups.exit().remove() - var traces = groups.selectAll('g.traces') - .data(Lib.identity); + var traces = groups.selectAll('g.traces') + .data(Lib.identity) - traces.enter().append('g').attr('class', 'traces'); - traces.exit().remove(); + traces.enter().append('g').attr('class', 'traces') + traces.exit().remove() - traces.call(style) - .style('opacity', function(d) { - var trace = d[0].trace; - if(Registry.traceIs(trace, 'pie')) { - return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1; - } else { - return trace.visible === 'legendonly' ? 0.5 : 1; - } + traces.call(style) + .style('opacity', function (d) { + var trace = d[0].trace + if (Registry.traceIs(trace, 'pie')) { + return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1 + } else { + return trace.visible === 'legendonly' ? 0.5 : 1 + } }) - .each(function() { - d3.select(this) + .each(function () { + d3.select(this) .call(drawTexts, gd) - .call(setupTraceToggle, gd); - }); + .call(setupTraceToggle, gd) + }) - var firstRender = legend.enter().size() !== 0; - if(firstRender) { - computeLegendDimensions(gd, groups, traces); - expandMargin(gd); - } + var firstRender = legend.enter().size() !== 0 + if (firstRender) { + computeLegendDimensions(gd, groups, traces) + expandMargin(gd) + } // Position and size the legend - var lxMin = 0, - lxMax = fullLayout.width, - lyMin = 0, - lyMax = fullLayout.height; + var lxMin = 0, + lxMax = fullLayout.width, + lyMin = 0, + lyMax = fullLayout.height - computeLegendDimensions(gd, groups, traces); + computeLegendDimensions(gd, groups, traces) - if(opts.height > lyMax) { + if (opts.height > lyMax) { // If the legend doesn't fit in the plot area, // do not expand the vertical margins. - expandHorizontalMargin(gd); - } else { - expandMargin(gd); - } + expandHorizontalMargin(gd) + } else { + expandMargin(gd) + } // Scroll section must be executed after repositionLegend. // It requires the legend width, height, x and y to position the scrollbox // and these values are mutated in repositionLegend. - var gs = fullLayout._size, - lx = gs.l + gs.w * opts.x, - ly = gs.t + gs.h * (1 - opts.y); - - if(anchorUtils.isRightAnchor(opts)) { - lx -= opts.width; - } - else if(anchorUtils.isCenterAnchor(opts)) { - lx -= opts.width / 2; - } - - if(anchorUtils.isBottomAnchor(opts)) { - ly -= opts.height; - } - else if(anchorUtils.isMiddleAnchor(opts)) { - ly -= opts.height / 2; - } + var gs = fullLayout._size, + lx = gs.l + gs.w * opts.x, + ly = gs.t + gs.h * (1 - opts.y) + + if (anchorUtils.isRightAnchor(opts)) { + lx -= opts.width + } else if (anchorUtils.isCenterAnchor(opts)) { + lx -= opts.width / 2 + } + + if (anchorUtils.isBottomAnchor(opts)) { + ly -= opts.height + } else if (anchorUtils.isMiddleAnchor(opts)) { + ly -= opts.height / 2 + } // Make sure the legend left and right sides are visible - var legendWidth = opts.width, - legendWidthMax = gs.w; - - if(legendWidth > legendWidthMax) { - lx = gs.l; - legendWidth = legendWidthMax; - } - else { - if(lx + legendWidth > lxMax) lx = lxMax - legendWidth; - if(lx < lxMin) lx = lxMin; - legendWidth = Math.min(lxMax - lx, opts.width); - } + var legendWidth = opts.width, + legendWidthMax = gs.w + + if (legendWidth > legendWidthMax) { + lx = gs.l + legendWidth = legendWidthMax + } else { + if (lx + legendWidth > lxMax) lx = lxMax - legendWidth + if (lx < lxMin) lx = lxMin + legendWidth = Math.min(lxMax - lx, opts.width) + } // Make sure the legend top and bottom are visible // (legends with a scroll bar are not allowed to stretch beyond the extended // margins) - var legendHeight = opts.height, - legendHeightMax = gs.h; - - if(legendHeight > legendHeightMax) { - ly = gs.t; - legendHeight = legendHeightMax; - } - else { - if(ly + legendHeight > lyMax) ly = lyMax - legendHeight; - if(ly < lyMin) ly = lyMin; - legendHeight = Math.min(lyMax - ly, opts.height); - } + var legendHeight = opts.height, + legendHeightMax = gs.h + + if (legendHeight > legendHeightMax) { + ly = gs.t + legendHeight = legendHeightMax + } else { + if (ly + legendHeight > lyMax) ly = lyMax - legendHeight + if (ly < lyMin) ly = lyMin + legendHeight = Math.min(lyMax - ly, opts.height) + } // Set size and position of all the elements that make up a legend: // legend, background and border, scroll box and scroll bar - Drawing.setTranslate(legend, lx, ly); + Drawing.setTranslate(legend, lx, ly) - var scrollBarYMax = legendHeight - + var scrollBarYMax = legendHeight - constants.scrollBarHeight - 2 * constants.scrollBarMargin, - scrollBoxYMax = opts.height - legendHeight, - scrollBarY, - scrollBoxY; + scrollBoxYMax = opts.height - legendHeight, + scrollBarY, + scrollBoxY - if(opts.height <= legendHeight || gd._context.staticPlot) { + if (opts.height <= legendHeight || gd._context.staticPlot) { // if scrollbar should not be shown. - bg.attr({ - width: legendWidth - opts.borderwidth, - height: legendHeight - opts.borderwidth, - x: opts.borderwidth / 2, - y: opts.borderwidth / 2 - }); - - Drawing.setTranslate(scrollBox, 0, 0); - - clipPath.select('rect').attr({ - width: legendWidth - 2 * opts.borderwidth, - height: legendHeight - 2 * opts.borderwidth, - x: opts.borderwidth, - y: opts.borderwidth - }); - - scrollBox.call(Drawing.setClipUrl, clipId); - } - else { - scrollBarY = constants.scrollBarMargin, - scrollBoxY = scrollBox.attr('data-scroll') || 0; + bg.attr({ + width: legendWidth - opts.borderwidth, + height: legendHeight - opts.borderwidth, + x: opts.borderwidth / 2, + y: opts.borderwidth / 2 + }) + + Drawing.setTranslate(scrollBox, 0, 0) + + clipPath.select('rect').attr({ + width: legendWidth - 2 * opts.borderwidth, + height: legendHeight - 2 * opts.borderwidth, + x: opts.borderwidth, + y: opts.borderwidth + }) + + scrollBox.call(Drawing.setClipUrl, clipId) + } else { + scrollBarY = constants.scrollBarMargin, + scrollBoxY = scrollBox.attr('data-scroll') || 0 // increase the background and clip-path width // by the scrollbar width and margin - bg.attr({ - width: legendWidth - + bg.attr({ + width: legendWidth - 2 * opts.borderwidth + constants.scrollBarWidth + constants.scrollBarMargin, - height: legendHeight - opts.borderwidth, - x: opts.borderwidth / 2, - y: opts.borderwidth / 2 - }); + height: legendHeight - opts.borderwidth, + x: opts.borderwidth / 2, + y: opts.borderwidth / 2 + }) - clipPath.select('rect').attr({ - width: legendWidth - + clipPath.select('rect').attr({ + width: legendWidth - 2 * opts.borderwidth + constants.scrollBarWidth + constants.scrollBarMargin, - height: legendHeight - 2 * opts.borderwidth, - x: opts.borderwidth, - y: opts.borderwidth - scrollBoxY - }); + height: legendHeight - 2 * opts.borderwidth, + x: opts.borderwidth, + y: opts.borderwidth - scrollBoxY + }) - scrollBox.call(Drawing.setClipUrl, clipId); + scrollBox.call(Drawing.setClipUrl, clipId) - if(firstRender) scrollHandler(scrollBarY, scrollBoxY); + if (firstRender) scrollHandler(scrollBarY, scrollBoxY) - legend.on('wheel', null); // to be safe, remove previous listeners - legend.on('wheel', function() { - scrollBoxY = Lib.constrain( + legend.on('wheel', null) // to be safe, remove previous listeners + legend.on('wheel', function () { + scrollBoxY = Lib.constrain( scrollBox.attr('data-scroll') - d3.event.deltaY / scrollBarYMax * scrollBoxYMax, - -scrollBoxYMax, 0); - scrollBarY = constants.scrollBarMargin - - scrollBoxY / scrollBoxYMax * scrollBarYMax; - scrollHandler(scrollBarY, scrollBoxY); - d3.event.preventDefault(); - }); + -scrollBoxYMax, 0) + scrollBarY = constants.scrollBarMargin - + scrollBoxY / scrollBoxYMax * scrollBarYMax + scrollHandler(scrollBarY, scrollBoxY) + d3.event.preventDefault() + }) // to be safe, remove previous listeners - scrollBar.on('.drag', null); - scrollBox.on('.drag', null); + scrollBar.on('.drag', null) + scrollBox.on('.drag', null) - var drag = d3.behavior.drag().on('drag', function() { - scrollBarY = Lib.constrain( + var drag = d3.behavior.drag().on('drag', function () { + scrollBarY = Lib.constrain( d3.event.y - constants.scrollBarHeight / 2, constants.scrollBarMargin, - constants.scrollBarMargin + scrollBarYMax); - scrollBoxY = - (scrollBarY - constants.scrollBarMargin) / - scrollBarYMax * scrollBoxYMax; - scrollHandler(scrollBarY, scrollBoxY); - }); - - scrollBar.call(drag); - scrollBox.call(drag); - } + constants.scrollBarMargin + scrollBarYMax) + scrollBoxY = -(scrollBarY - constants.scrollBarMargin) / + scrollBarYMax * scrollBoxYMax + scrollHandler(scrollBarY, scrollBoxY) + }) + scrollBar.call(drag) + scrollBox.call(drag) + } - function scrollHandler(scrollBarY, scrollBoxY) { - scrollBox + function scrollHandler (scrollBarY, scrollBoxY) { + scrollBox .attr('data-scroll', scrollBoxY) - .call(Drawing.setTranslate, 0, scrollBoxY); + .call(Drawing.setTranslate, 0, scrollBoxY) - scrollBar.call( + scrollBar.call( Drawing.setRect, legendWidth, scrollBarY, constants.scrollBarWidth, constants.scrollBarHeight - ); - clipPath.select('rect').attr({ - y: opts.borderwidth - scrollBoxY - }); - } + ) + clipPath.select('rect').attr({ + y: opts.borderwidth - scrollBoxY + }) + } - if(gd._context.editable) { - var xf, yf, x0, y0; - - legend.classed('cursor-move', true); - - dragElement.init({ - element: legend.node(), - prepFn: function() { - var transform = Drawing.getTranslate(legend); - - x0 = transform.x; - y0 = transform.y; - }, - moveFn: function(dx, dy) { - var newX = x0 + dx, - newY = y0 + dy; - - Drawing.setTranslate(legend, newX, newY); - - xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor); - yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor); - }, - doneFn: function(dragged) { - if(dragged && xf !== undefined && yf !== undefined) { - Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf}); - } - } - }); - } -}; + if (gd._context.editable) { + var xf, yf, x0, y0 -function drawTexts(g, gd) { - var legendItem = g.data()[0][0], - fullLayout = gd._fullLayout, - trace = legendItem.trace, - isPie = Registry.traceIs(trace, 'pie'), - traceIndex = trace.index, - name = isPie ? legendItem.label : trace.name; - - var text = g.selectAll('text.legendtext') - .data([0]); - text.enter().append('text').classed('legendtext', true); - text.attr({ - x: 40, - y: 0, - 'data-unformatted': name + legend.classed('cursor-move', true) + + dragElement.init({ + element: legend.node(), + prepFn: function () { + var transform = Drawing.getTranslate(legend) + + x0 = transform.x + y0 = transform.y + }, + moveFn: function (dx, dy) { + var newX = x0 + dx, + newY = y0 + dy + + Drawing.setTranslate(legend, newX, newY) + + xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor) + yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor) + }, + doneFn: function (dragged) { + if (dragged && xf !== undefined && yf !== undefined) { + Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf}) + } + } }) + } +} + +function drawTexts (g, gd) { + var legendItem = g.data()[0][0], + fullLayout = gd._fullLayout, + trace = legendItem.trace, + isPie = Registry.traceIs(trace, 'pie'), + traceIndex = trace.index, + name = isPie ? legendItem.label : trace.name + + var text = g.selectAll('text.legendtext') + .data([0]) + text.enter().append('text').classed('legendtext', true) + text.attr({ + x: 40, + y: 0, + 'data-unformatted': name + }) .style('text-anchor', 'start') .classed('user-select-none', true) .call(Drawing.font, fullLayout.legend.font) - .text(name); + .text(name) - function textLayout(s) { - svgTextUtils.convertToTspans(s, function() { - s.selectAll('tspan.line').attr({x: s.attr('x')}); - g.call(computeTextDimensions, gd); - }); - } + function textLayout (s) { + svgTextUtils.convertToTspans(s, function () { + s.selectAll('tspan.line').attr({x: s.attr('x')}) + g.call(computeTextDimensions, gd) + }) + } - if(gd._context.editable && !isPie) { - text.call(svgTextUtils.makeEditable) + if (gd._context.editable && !isPie) { + text.call(svgTextUtils.makeEditable) .call(textLayout) - .on('edit', function(text) { - this.attr({'data-unformatted': text}); + .on('edit', function (text) { + this.attr({'data-unformatted': text}) - this.text(text) - .call(textLayout); + this.text(text) + .call(textLayout) - if(!this.text()) text = ' \u0020\u0020 '; + if (!this.text()) text = ' \u0020\u0020 ' - var fullInput = legendItem.trace._fullInput || {}, - astr; + var fullInput = legendItem.trace._fullInput || {}, + astr // N.B. this block isn't super clean, // is unfortunately untested at the moment, // and only works for for 'ohlc' and 'candlestick', // but should be generalized for other one-to-many transforms - if(['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) { - var transforms = legendItem.trace.transforms, - direction = transforms[transforms.length - 1].direction; + if (['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) { + var transforms = legendItem.trace.transforms, + direction = transforms[transforms.length - 1].direction - astr = direction + '.name'; - } - else astr = 'name'; + astr = direction + '.name' + } else astr = 'name' - Plotly.restyle(gd, astr, text, traceIndex); - }); - } - else text.call(textLayout); + Plotly.restyle(gd, astr, text, traceIndex) + }) + } else text.call(textLayout) } -function setupTraceToggle(g, gd) { - var hiddenSlices = gd._fullLayout.hiddenlabels ? +function setupTraceToggle (g, gd) { + var hiddenSlices = gd._fullLayout.hiddenlabels ? gd._fullLayout.hiddenlabels.slice() : - []; + [] - var traceToggle = g.selectAll('rect') - .data([0]); + var traceToggle = g.selectAll('rect') + .data([0]) - traceToggle.enter().append('rect') + traceToggle.enter().append('rect') .classed('legendtoggle', true) .style('cursor', 'pointer') .attr('pointer-events', 'all') - .call(Color.fill, 'rgba(0,0,0,0)'); - - traceToggle.on('click', function() { - if(gd._dragged) return; - - var legendItem = g.data()[0][0], - fullData = gd._fullData, - trace = legendItem.trace, - legendgroup = trace.legendgroup, - traceIndicesInGroup = [], - tracei, - newVisible; - - if(Registry.traceIs(trace, 'pie')) { - var thisLabel = legendItem.label, - thisLabelIndex = hiddenSlices.indexOf(thisLabel); - - if(thisLabelIndex === -1) hiddenSlices.push(thisLabel); - else hiddenSlices.splice(thisLabelIndex, 1); - - Plotly.relayout(gd, 'hiddenlabels', hiddenSlices); - } else { - if(legendgroup === '') { - traceIndicesInGroup = [trace.index]; - } else { - for(var i = 0; i < fullData.length; i++) { - tracei = fullData[i]; - if(tracei.legendgroup === legendgroup) { - traceIndicesInGroup.push(tracei.index); - } - } - } - - newVisible = trace.visible === true ? 'legendonly' : true; - Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup); - } - }); -} + .call(Color.fill, 'rgba(0,0,0,0)') + + traceToggle.on('click', function () { + if (gd._dragged) return -function computeTextDimensions(g, gd) { var legendItem = g.data()[0][0], - mathjaxGroup = g.select('g[class*=math-group]'), - opts = gd._fullLayout.legend, - lineHeight = opts.font.size * 1.3, - height, - width; - - if(!legendItem.trace.showlegend) { - g.remove(); - return; - } + fullData = gd._fullData, + trace = legendItem.trace, + legendgroup = trace.legendgroup, + traceIndicesInGroup = [], + tracei, + newVisible - if(mathjaxGroup.node()) { - var mathjaxBB = Drawing.bBox(mathjaxGroup.node()); + if (Registry.traceIs(trace, 'pie')) { + var thisLabel = legendItem.label, + thisLabelIndex = hiddenSlices.indexOf(thisLabel) - height = mathjaxBB.height; - width = mathjaxBB.width; + if (thisLabelIndex === -1) hiddenSlices.push(thisLabel) + else hiddenSlices.splice(thisLabelIndex, 1) - Drawing.setTranslate(mathjaxGroup, 0, (height / 4)); + Plotly.relayout(gd, 'hiddenlabels', hiddenSlices) + } else { + if (legendgroup === '') { + traceIndicesInGroup = [trace.index] + } else { + for (var i = 0; i < fullData.length; i++) { + tracei = fullData[i] + if (tracei.legendgroup === legendgroup) { + traceIndicesInGroup.push(tracei.index) + } + } + } + + newVisible = trace.visible === true ? 'legendonly' : true + Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup) } - else { - var text = g.selectAll('.legendtext'), - textSpans = g.selectAll('.legendtext>tspan'), - textLines = textSpans[0].length || 1; + }) +} - height = lineHeight * textLines; - width = text.node() && Drawing.bBox(text.node()).width; +function computeTextDimensions (g, gd) { + var legendItem = g.data()[0][0], + mathjaxGroup = g.select('g[class*=math-group]'), + opts = gd._fullLayout.legend, + lineHeight = opts.font.size * 1.3, + height, + width + + if (!legendItem.trace.showlegend) { + g.remove() + return + } + + if (mathjaxGroup.node()) { + var mathjaxBB = Drawing.bBox(mathjaxGroup.node()) + + height = mathjaxBB.height + width = mathjaxBB.width + + Drawing.setTranslate(mathjaxGroup, 0, (height / 4)) + } else { + var text = g.selectAll('.legendtext'), + textSpans = g.selectAll('.legendtext>tspan'), + textLines = textSpans[0].length || 1 + + height = lineHeight * textLines + width = text.node() && Drawing.bBox(text.node()).width // approximation to height offset to center the font // to avoid getBoundingClientRect - var textY = lineHeight * (0.3 + (1 - textLines) / 2); - text.attr('y', textY); - textSpans.attr('y', textY); - } + var textY = lineHeight * (0.3 + (1 - textLines) / 2) + text.attr('y', textY) + textSpans.attr('y', textY) + } - height = Math.max(height, 16) + 3; + height = Math.max(height, 16) + 3 - legendItem.height = height; - legendItem.width = width; + legendItem.height = height + legendItem.width = width } -function computeLegendDimensions(gd, groups, traces) { - var fullLayout = gd._fullLayout, - opts = fullLayout.legend, - borderwidth = opts.borderwidth, - isGrouped = helpers.isGrouped(opts); - - if(helpers.isVertical(opts)) { - if(isGrouped) { - groups.each(function(d, i) { - Drawing.setTranslate(this, 0, i * opts.tracegroupgap); - }); - } +function computeLegendDimensions (gd, groups, traces) { + var fullLayout = gd._fullLayout, + opts = fullLayout.legend, + borderwidth = opts.borderwidth, + isGrouped = helpers.isGrouped(opts) + + if (helpers.isVertical(opts)) { + if (isGrouped) { + groups.each(function (d, i) { + Drawing.setTranslate(this, 0, i * opts.tracegroupgap) + }) + } - opts.width = 0; - opts.height = 0; + opts.width = 0 + opts.height = 0 - traces.each(function(d) { - var legendItem = d[0], - textHeight = legendItem.height, - textWidth = legendItem.width; + traces.each(function (d) { + var legendItem = d[0], + textHeight = legendItem.height, + textWidth = legendItem.width - Drawing.setTranslate(this, + Drawing.setTranslate(this, borderwidth, - (5 + borderwidth + opts.height + textHeight / 2)); + (5 + borderwidth + opts.height + textHeight / 2)) - opts.height += textHeight; - opts.width = Math.max(opts.width, textWidth); - }); + opts.height += textHeight + opts.width = Math.max(opts.width, textWidth) + }) - opts.width += 45 + borderwidth * 2; - opts.height += 10 + borderwidth * 2; + opts.width += 45 + borderwidth * 2 + opts.height += 10 + borderwidth * 2 - if(isGrouped) { - opts.height += (opts._lgroupsLength - 1) * opts.tracegroupgap; - } + if (isGrouped) { + opts.height += (opts._lgroupsLength - 1) * opts.tracegroupgap + } // make sure we're only getting full pixels - opts.width = Math.ceil(opts.width); - opts.height = Math.ceil(opts.height); + opts.width = Math.ceil(opts.width) + opts.height = Math.ceil(opts.height) - traces.each(function(d) { - var legendItem = d[0], - bg = d3.select(this).select('.legendtoggle'); + traces.each(function (d) { + var legendItem = d[0], + bg = d3.select(this).select('.legendtoggle') - bg.call(Drawing.setRect, + bg.call(Drawing.setRect, 0, -legendItem.height / 2, (gd._context.editable ? 0 : opts.width) + 40, legendItem.height - ); - }); - } - else if(isGrouped) { - opts.width = 0; - opts.height = 0; + ) + }) + } else if (isGrouped) { + opts.width = 0 + opts.height = 0 - var groupXOffsets = [opts.width], - groupData = groups.data(); + var groupXOffsets = [opts.width], + groupData = groups.data() - for(var i = 0, n = groupData.length; i < n; i++) { - var textWidths = groupData[i].map(function(legendItemArray) { - return legendItemArray[0].width; - }); + for (var i = 0, n = groupData.length; i < n; i++) { + var textWidths = groupData[i].map(function (legendItemArray) { + return legendItemArray[0].width + }) - var groupWidth = 40 + Math.max.apply(null, textWidths); + var groupWidth = 40 + Math.max.apply(null, textWidths) - opts.width += opts.tracegroupgap + groupWidth; + opts.width += opts.tracegroupgap + groupWidth - groupXOffsets.push(opts.width); - } + groupXOffsets.push(opts.width) + } - groups.each(function(d, i) { - Drawing.setTranslate(this, groupXOffsets[i], 0); - }); + groups.each(function (d, i) { + Drawing.setTranslate(this, groupXOffsets[i], 0) + }) - groups.each(function() { - var group = d3.select(this), - groupTraces = group.selectAll('g.traces'), - groupHeight = 0; + groups.each(function () { + var group = d3.select(this), + groupTraces = group.selectAll('g.traces'), + groupHeight = 0 - groupTraces.each(function(d) { - var legendItem = d[0], - textHeight = legendItem.height; + groupTraces.each(function (d) { + var legendItem = d[0], + textHeight = legendItem.height - Drawing.setTranslate(this, + Drawing.setTranslate(this, 0, - (5 + borderwidth + groupHeight + textHeight / 2)); + (5 + borderwidth + groupHeight + textHeight / 2)) - groupHeight += textHeight; - }); + groupHeight += textHeight + }) - opts.height = Math.max(opts.height, groupHeight); - }); + opts.height = Math.max(opts.height, groupHeight) + }) - opts.height += 10 + borderwidth * 2; - opts.width += borderwidth * 2; + opts.height += 10 + borderwidth * 2 + opts.width += borderwidth * 2 // make sure we're only getting full pixels - opts.width = Math.ceil(opts.width); - opts.height = Math.ceil(opts.height); + opts.width = Math.ceil(opts.width) + opts.height = Math.ceil(opts.height) - traces.each(function(d) { - var legendItem = d[0], - bg = d3.select(this).select('.legendtoggle'); + traces.each(function (d) { + var legendItem = d[0], + bg = d3.select(this).select('.legendtoggle') - bg.call(Drawing.setRect, + bg.call(Drawing.setRect, 0, -legendItem.height / 2, (gd._context.editable ? 0 : opts.width), legendItem.height - ); - }); - } - else { - opts.width = 0; - opts.height = 0; - var rowHeight = 0, - maxTraceHeight = 0, - maxTraceWidth = 0, - offsetX = 0; + ) + }) + } else { + opts.width = 0 + opts.height = 0 + var rowHeight = 0, + maxTraceHeight = 0, + maxTraceWidth = 0, + offsetX = 0 // calculate largest width for traces and use for width of all legend items - traces.each(function(d) { - maxTraceWidth = Math.max(40 + d[0].width, maxTraceWidth); - }); - - traces.each(function(d) { - var legendItem = d[0], - traceWidth = maxTraceWidth, - traceGap = opts.tracegroupgap || 5; - - if((borderwidth + offsetX + traceGap + traceWidth) > (fullLayout.width - (fullLayout.margin.r + fullLayout.margin.l))) { - offsetX = 0; - rowHeight = rowHeight + maxTraceHeight; - opts.height = opts.height + maxTraceHeight; + traces.each(function (d) { + maxTraceWidth = Math.max(40 + d[0].width, maxTraceWidth) + }) + + traces.each(function (d) { + var legendItem = d[0], + traceWidth = maxTraceWidth, + traceGap = opts.tracegroupgap || 5 + + if ((borderwidth + offsetX + traceGap + traceWidth) > (fullLayout.width - (fullLayout.margin.r + fullLayout.margin.l))) { + offsetX = 0 + rowHeight = rowHeight + maxTraceHeight + opts.height = opts.height + maxTraceHeight // reset for next row - maxTraceHeight = 0; - } + maxTraceHeight = 0 + } - Drawing.setTranslate(this, + Drawing.setTranslate(this, (borderwidth + offsetX), - (5 + borderwidth + legendItem.height / 2) + rowHeight); + (5 + borderwidth + legendItem.height / 2) + rowHeight) - opts.width += traceGap + traceWidth; - opts.height = Math.max(opts.height, legendItem.height); + opts.width += traceGap + traceWidth + opts.height = Math.max(opts.height, legendItem.height) // keep track of tallest trace in group - offsetX += traceGap + traceWidth; - maxTraceHeight = Math.max(legendItem.height, maxTraceHeight); - }); + offsetX += traceGap + traceWidth + maxTraceHeight = Math.max(legendItem.height, maxTraceHeight) + }) - opts.width += borderwidth * 2; - opts.height += 10 + borderwidth * 2; + opts.width += borderwidth * 2 + opts.height += 10 + borderwidth * 2 // make sure we're only getting full pixels - opts.width = Math.ceil(opts.width); - opts.height = Math.ceil(opts.height); + opts.width = Math.ceil(opts.width) + opts.height = Math.ceil(opts.height) - traces.each(function(d) { - var legendItem = d[0], - bg = d3.select(this).select('.legendtoggle'); + traces.each(function (d) { + var legendItem = d[0], + bg = d3.select(this).select('.legendtoggle') - bg.call(Drawing.setRect, + bg.call(Drawing.setRect, 0, -legendItem.height / 2, (gd._context.editable ? 0 : opts.width), legendItem.height - ); - }); - } + ) + }) + } } -function expandMargin(gd) { - var fullLayout = gd._fullLayout, - opts = fullLayout.legend; +function expandMargin (gd) { + var fullLayout = gd._fullLayout, + opts = fullLayout.legend - var xanchor = 'left'; - if(anchorUtils.isRightAnchor(opts)) { - xanchor = 'right'; - } - else if(anchorUtils.isCenterAnchor(opts)) { - xanchor = 'center'; - } + var xanchor = 'left' + if (anchorUtils.isRightAnchor(opts)) { + xanchor = 'right' + } else if (anchorUtils.isCenterAnchor(opts)) { + xanchor = 'center' + } - var yanchor = 'top'; - if(anchorUtils.isBottomAnchor(opts)) { - yanchor = 'bottom'; - } - else if(anchorUtils.isMiddleAnchor(opts)) { - yanchor = 'middle'; - } + var yanchor = 'top' + if (anchorUtils.isBottomAnchor(opts)) { + yanchor = 'bottom' + } else if (anchorUtils.isMiddleAnchor(opts)) { + yanchor = 'middle' + } // lastly check if the margin auto-expand has changed - Plots.autoMargin(gd, 'legend', { - x: opts.x, - y: opts.y, - l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), - r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), - b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0), - t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) - }); + Plots.autoMargin(gd, 'legend', { + x: opts.x, + y: opts.y, + l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), + r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), + b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0), + t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) + }) } -function expandHorizontalMargin(gd) { - var fullLayout = gd._fullLayout, - opts = fullLayout.legend; +function expandHorizontalMargin (gd) { + var fullLayout = gd._fullLayout, + opts = fullLayout.legend - var xanchor = 'left'; - if(anchorUtils.isRightAnchor(opts)) { - xanchor = 'right'; - } - else if(anchorUtils.isCenterAnchor(opts)) { - xanchor = 'center'; - } + var xanchor = 'left' + if (anchorUtils.isRightAnchor(opts)) { + xanchor = 'right' + } else if (anchorUtils.isCenterAnchor(opts)) { + xanchor = 'center' + } // lastly check if the margin auto-expand has changed - Plots.autoMargin(gd, 'legend', { - x: opts.x, - y: 0.5, - l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), - r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), - b: 0, - t: 0 - }); + Plots.autoMargin(gd, 'legend', { + x: opts.x, + y: 0.5, + l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), + r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), + b: 0, + t: 0 + }) } diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 4ad4636d442..17a6edf107a 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -6,98 +6,91 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var helpers = require('./helpers') -var Registry = require('../../registry'); -var helpers = require('./helpers'); +module.exports = function getLegendData (calcdata, opts) { + var lgroupToTraces = {}, + lgroups = [], + hasOneNonBlankGroup = false, + slicesShown = {}, + lgroupi = 0 + var i, j -module.exports = function getLegendData(calcdata, opts) { - var lgroupToTraces = {}, - lgroups = [], - hasOneNonBlankGroup = false, - slicesShown = {}, - lgroupi = 0; + function addOneItem (legendGroup, legendItem) { + // each '' legend group is treated as a separate group + if (legendGroup === '' || !helpers.isGrouped(opts)) { + var uniqueGroup = '~~i' + lgroupi // TODO: check this against fullData legendgroups? + + lgroups.push(uniqueGroup) + lgroupToTraces[uniqueGroup] = [[legendItem]] + lgroupi++ + } else if (lgroups.indexOf(legendGroup) === -1) { + lgroups.push(legendGroup) + hasOneNonBlankGroup = true + lgroupToTraces[legendGroup] = [[legendItem]] + } else lgroupToTraces[legendGroup].push([legendItem]) + } - var i, j; + // build an { legendgroup: [cd0, cd0], ... } object + for (i = 0; i < calcdata.length; i++) { + var cd = calcdata[i], + cd0 = cd[0], + trace = cd0.trace, + lgroup = trace.legendgroup - function addOneItem(legendGroup, legendItem) { - // each '' legend group is treated as a separate group - if(legendGroup === '' || !helpers.isGrouped(opts)) { - var uniqueGroup = '~~i' + lgroupi; // TODO: check this against fullData legendgroups? + if (!helpers.legendGetsTrace(trace) || !trace.showlegend) continue - lgroups.push(uniqueGroup); - lgroupToTraces[uniqueGroup] = [[legendItem]]; - lgroupi++; - } - else if(lgroups.indexOf(legendGroup) === -1) { - lgroups.push(legendGroup); - hasOneNonBlankGroup = true; - lgroupToTraces[legendGroup] = [[legendItem]]; - } - else lgroupToTraces[legendGroup].push([legendItem]); - } + if (Registry.traceIs(trace, 'pie')) { + if (!slicesShown[lgroup]) slicesShown[lgroup] = {} - // build an { legendgroup: [cd0, cd0], ... } object - for(i = 0; i < calcdata.length; i++) { - var cd = calcdata[i], - cd0 = cd[0], - trace = cd0.trace, - lgroup = trace.legendgroup; - - if(!helpers.legendGetsTrace(trace) || !trace.showlegend) continue; - - if(Registry.traceIs(trace, 'pie')) { - if(!slicesShown[lgroup]) slicesShown[lgroup] = {}; - - for(j = 0; j < cd.length; j++) { - var labelj = cd[j].label; - - if(!slicesShown[lgroup][labelj]) { - addOneItem(lgroup, { - label: labelj, - color: cd[j].color, - i: cd[j].i, - trace: trace - }); - - slicesShown[lgroup][labelj] = true; - } - } - } + for (j = 0; j < cd.length; j++) { + var labelj = cd[j].label - else addOneItem(lgroup, cd0); - } + if (!slicesShown[lgroup][labelj]) { + addOneItem(lgroup, { + label: labelj, + color: cd[j].color, + i: cd[j].i, + trace: trace + }) + + slicesShown[lgroup][labelj] = true + } + } + } else addOneItem(lgroup, cd0) + } // won't draw a legend in this case - if(!lgroups.length) return []; + if (!lgroups.length) return [] // rearrange lgroupToTraces into a d3-friendly array of arrays - var lgroupsLength = lgroups.length, - ltraces, - legendData; + var lgroupsLength = lgroups.length, + ltraces, + legendData - if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { - legendData = new Array(lgroupsLength); + if (hasOneNonBlankGroup && helpers.isGrouped(opts)) { + legendData = new Array(lgroupsLength) - for(i = 0; i < lgroupsLength; i++) { - ltraces = lgroupToTraces[lgroups[i]]; - legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces; - } + for (i = 0; i < lgroupsLength; i++) { + ltraces = lgroupToTraces[lgroups[i]] + legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces } - else { + } else { // collapse all groups into one if all groups are blank - legendData = [new Array(lgroupsLength)]; + legendData = [new Array(lgroupsLength)] - for(i = 0; i < lgroupsLength; i++) { - ltraces = lgroupToTraces[lgroups[i]][0]; - legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces; - } - lgroupsLength = 1; + for (i = 0; i < lgroupsLength; i++) { + ltraces = lgroupToTraces[lgroups[i]][0] + legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces } + lgroupsLength = 1 + } // needed in repositionLegend - opts._lgroupsLength = lgroupsLength; - return legendData; -}; + opts._lgroupsLength = lgroupsLength + return legendData +} diff --git a/src/components/legend/helpers.js b/src/components/legend/helpers.js index 140fa6d7f5f..3beab88691d 100644 --- a/src/components/legend/helpers.js +++ b/src/components/legend/helpers.js @@ -6,24 +6,22 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') -var Registry = require('../../registry'); +exports.legendGetsTrace = function legendGetsTrace (trace) { + return trace.visible && Registry.traceIs(trace, 'showLegend') +} +exports.isGrouped = function isGrouped (legendLayout) { + return (legendLayout.traceorder || '').indexOf('grouped') !== -1 +} -exports.legendGetsTrace = function legendGetsTrace(trace) { - return trace.visible && Registry.traceIs(trace, 'showLegend'); -}; +exports.isVertical = function isVertical (legendLayout) { + return legendLayout.orientation !== 'h' +} -exports.isGrouped = function isGrouped(legendLayout) { - return (legendLayout.traceorder || '').indexOf('grouped') !== -1; -}; - -exports.isVertical = function isVertical(legendLayout) { - return legendLayout.orientation !== 'h'; -}; - -exports.isReversed = function isReversed(legendLayout) { - return (legendLayout.traceorder || '').indexOf('reversed') !== -1; -}; +exports.isReversed = function isReversed (legendLayout) { + return (legendLayout.traceorder || '').indexOf('reversed') !== -1 +} diff --git a/src/components/legend/index.js b/src/components/legend/index.js index 71e45a0b723..86b044b9c22 100644 --- a/src/components/legend/index.js +++ b/src/components/legend/index.js @@ -6,17 +6,15 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { - moduleType: 'component', - name: 'legend', + moduleType: 'component', + name: 'legend', - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - draw: require('./draw'), - style: require('./style') -}; + draw: require('./draw'), + style: require('./style') +} diff --git a/src/components/legend/style.js b/src/components/legend/style.js index c3c2101e62e..926f9784e95 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -6,221 +6,219 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Registry = require('../../registry') +var Lib = require('../../lib') +var Drawing = require('../drawing') +var Color = require('../color') -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var Drawing = require('../drawing'); -var Color = require('../color'); +var subTypes = require('../../traces/scatter/subtypes') +var stylePie = require('../../traces/pie/style_one') -var subTypes = require('../../traces/scatter/subtypes'); -var stylePie = require('../../traces/pie/style_one'); +module.exports = function style (s) { + s.each(function (d) { + var traceGroup = d3.select(this) + var layers = traceGroup.selectAll('g.layers') + .data([0]) + layers.enter().append('g') + .classed('layers', true) + layers.style('opacity', d[0].trace.opacity) -module.exports = function style(s) { - s.each(function(d) { - var traceGroup = d3.select(this); - - var layers = traceGroup.selectAll('g.layers') - .data([0]); - layers.enter().append('g') - .classed('layers', true); - layers.style('opacity', d[0].trace.opacity); - - var fill = layers + var fill = layers .selectAll('g.legendfill') - .data([d]); - fill.enter().append('g') - .classed('legendfill', true); + .data([d]) + fill.enter().append('g') + .classed('legendfill', true) - var line = layers + var line = layers .selectAll('g.legendlines') - .data([d]); - line.enter().append('g') - .classed('legendlines', true); + .data([d]) + line.enter().append('g') + .classed('legendlines', true) - var symbol = layers + var symbol = layers .selectAll('g.legendsymbols') - .data([d]); - symbol.enter().append('g') - .classed('legendsymbols', true); + .data([d]) + symbol.enter().append('g') + .classed('legendsymbols', true) - symbol.selectAll('g.legendpoints') + symbol.selectAll('g.legendpoints') .data([d]) .enter().append('g') - .classed('legendpoints', true); - }) + .classed('legendpoints', true) + }) .each(styleBars) .each(styleBoxes) .each(stylePies) .each(styleLines) - .each(stylePoints); -}; - -function styleLines(d) { - var trace = d[0].trace, - showFill = trace.visible && trace.fill && trace.fill !== 'none', - showLine = subTypes.hasLines(trace); - - var fill = d3.select(this).select('.legendfill').selectAll('path') - .data(showFill ? [d] : []); - fill.enter().append('path').classed('js-fill', true); - fill.exit().remove(); - fill.attr('d', 'M5,0h30v6h-30z') - .call(Drawing.fillGroupStyle); - - var line = d3.select(this).select('.legendlines').selectAll('path') - .data(showLine ? [d] : []); - line.enter().append('path').classed('js-line', true) - .attr('d', 'M5,0h30'); - line.exit().remove(); - line.call(Drawing.lineGroupStyle); + .each(stylePoints) +} + +function styleLines (d) { + var trace = d[0].trace, + showFill = trace.visible && trace.fill && trace.fill !== 'none', + showLine = subTypes.hasLines(trace) + + var fill = d3.select(this).select('.legendfill').selectAll('path') + .data(showFill ? [d] : []) + fill.enter().append('path').classed('js-fill', true) + fill.exit().remove() + fill.attr('d', 'M5,0h30v6h-30z') + .call(Drawing.fillGroupStyle) + + var line = d3.select(this).select('.legendlines').selectAll('path') + .data(showLine ? [d] : []) + line.enter().append('path').classed('js-line', true) + .attr('d', 'M5,0h30') + line.exit().remove() + line.call(Drawing.lineGroupStyle) } -function stylePoints(d) { - var d0 = d[0], - trace = d0.trace, - showMarkers = subTypes.hasMarkers(trace), - showText = subTypes.hasText(trace), - showLines = subTypes.hasLines(trace); +function stylePoints (d) { + var d0 = d[0], + trace = d0.trace, + showMarkers = subTypes.hasMarkers(trace), + showText = subTypes.hasText(trace), + showLines = subTypes.hasLines(trace) - var dMod, tMod; + var dMod, tMod // 'scatter3d' and 'scattergeo' don't use gd.calcdata yet; // use d0.trace to infer arrayOk attributes - function boundVal(attrIn, arrayToValFn, bounds) { - var valIn = Lib.nestedProperty(trace, attrIn).get(), - valToBound = (Array.isArray(valIn) && arrayToValFn) ? - arrayToValFn(valIn) : valIn; + function boundVal (attrIn, arrayToValFn, bounds) { + var valIn = Lib.nestedProperty(trace, attrIn).get(), + valToBound = (Array.isArray(valIn) && arrayToValFn) ? + arrayToValFn(valIn) : valIn - if(bounds) { - if(valToBound < bounds[0]) return bounds[0]; - else if(valToBound > bounds[1]) return bounds[1]; - } - return valToBound; + if (bounds) { + if (valToBound < bounds[0]) return bounds[0] + else if (valToBound > bounds[1]) return bounds[1] } + return valToBound + } - function pickFirst(array) { return array[0]; } + function pickFirst (array) { return array[0] } // constrain text, markers, etc so they'll fit on the legend - if(showMarkers || showText || showLines) { - var dEdit = {}, - tEdit = {}; - - if(showMarkers) { - dEdit.mc = boundVal('marker.color', pickFirst); - dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]); - dEdit.ms = boundVal('marker.size', Lib.mean, [2, 16]); - dEdit.mlc = boundVal('marker.line.color', pickFirst); - dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]); - tEdit.marker = { - sizeref: 1, - sizemin: 1, - sizemode: 'diameter' - }; - } - - if(showLines) { - tEdit.line = { - width: boundVal('line.width', pickFirst, [0, 10]) - }; - } - - if(showText) { - dEdit.tx = 'Aa'; - dEdit.tp = boundVal('textposition', pickFirst); - dEdit.ts = 10; - dEdit.tc = boundVal('textfont.color', pickFirst); - dEdit.tf = boundVal('textfont.family', pickFirst); - } - - dMod = [Lib.minExtend(d0, dEdit)]; - tMod = Lib.minExtend(trace, tEdit); + if (showMarkers || showText || showLines) { + var dEdit = {}, + tEdit = {} + + if (showMarkers) { + dEdit.mc = boundVal('marker.color', pickFirst) + dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]) + dEdit.ms = boundVal('marker.size', Lib.mean, [2, 16]) + dEdit.mlc = boundVal('marker.line.color', pickFirst) + dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]) + tEdit.marker = { + sizeref: 1, + sizemin: 1, + sizemode: 'diameter' + } } - var ptgroup = d3.select(this).select('g.legendpoints'); + if (showLines) { + tEdit.line = { + width: boundVal('line.width', pickFirst, [0, 10]) + } + } + + if (showText) { + dEdit.tx = 'Aa' + dEdit.tp = boundVal('textposition', pickFirst) + dEdit.ts = 10 + dEdit.tc = boundVal('textfont.color', pickFirst) + dEdit.tf = boundVal('textfont.family', pickFirst) + } - var pts = ptgroup.selectAll('path.scatterpts') - .data(showMarkers ? dMod : []); - pts.enter().append('path').classed('scatterpts', true) - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); - pts.call(Drawing.pointStyle, tMod); + dMod = [Lib.minExtend(d0, dEdit)] + tMod = Lib.minExtend(trace, tEdit) + } + + var ptgroup = d3.select(this).select('g.legendpoints') + + var pts = ptgroup.selectAll('path.scatterpts') + .data(showMarkers ? dMod : []) + pts.enter().append('path').classed('scatterpts', true) + .attr('transform', 'translate(20,0)') + pts.exit().remove() + pts.call(Drawing.pointStyle, tMod) // 'mrc' is set in pointStyle and used in textPointStyle: // constrain it here - if(showMarkers) dMod[0].mrc = 3; + if (showMarkers) dMod[0].mrc = 3 - var txt = ptgroup.selectAll('g.pointtext') - .data(showText ? dMod : []); - txt.enter() + var txt = ptgroup.selectAll('g.pointtext') + .data(showText ? dMod : []) + txt.enter() .append('g').classed('pointtext', true) - .append('text').attr('transform', 'translate(20,0)'); - txt.exit().remove(); - txt.selectAll('text').call(Drawing.textPointStyle, tMod); + .append('text').attr('transform', 'translate(20,0)') + txt.exit().remove() + txt.selectAll('text').call(Drawing.textPointStyle, tMod) } -function styleBars(d) { - var trace = d[0].trace, - marker = trace.marker || {}, - markerLine = marker.line || {}, - barpath = d3.select(this).select('g.legendpoints') +function styleBars (d) { + var trace = d[0].trace, + marker = trace.marker || {}, + markerLine = marker.line || {}, + barpath = d3.select(this).select('g.legendpoints') .selectAll('path.legendbar') - .data(Registry.traceIs(trace, 'bar') ? [d] : []); - barpath.enter().append('path').classed('legendbar', true) + .data(Registry.traceIs(trace, 'bar') ? [d] : []) + barpath.enter().append('path').classed('legendbar', true) .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - barpath.exit().remove(); - barpath.each(function(d) { - var p = d3.select(this), - d0 = d[0], - w = (d0.mlw + 1 || markerLine.width + 1) - 1; - - p.style('stroke-width', w + 'px') - .call(Color.fill, d0.mc || marker.color); - - if(w) { - p.call(Color.stroke, d0.mlc || markerLine.color); - } - }); + .attr('transform', 'translate(20,0)') + barpath.exit().remove() + barpath.each(function (d) { + var p = d3.select(this), + d0 = d[0], + w = (d0.mlw + 1 || markerLine.width + 1) - 1 + + p.style('stroke-width', w + 'px') + .call(Color.fill, d0.mc || marker.color) + + if (w) { + p.call(Color.stroke, d0.mlc || markerLine.color) + } + }) } -function styleBoxes(d) { - var trace = d[0].trace, - pts = d3.select(this).select('g.legendpoints') +function styleBoxes (d) { + var trace = d[0].trace, + pts = d3.select(this).select('g.legendpoints') .selectAll('path.legendbox') - .data(Registry.traceIs(trace, 'box') && trace.visible ? [d] : []); - pts.enter().append('path').classed('legendbox', true) + .data(Registry.traceIs(trace, 'box') && trace.visible ? [d] : []) + pts.enter().append('path').classed('legendbox', true) // if we want the median bar, prepend M6,0H-6 .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); - pts.each(function() { - var w = trace.line.width, - p = d3.select(this); - - p.style('stroke-width', w + 'px') - .call(Color.fill, trace.fillcolor); - - if(w) { - p.call(Color.stroke, trace.line.color); - } - }); + .attr('transform', 'translate(20,0)') + pts.exit().remove() + pts.each(function () { + var w = trace.line.width, + p = d3.select(this) + + p.style('stroke-width', w + 'px') + .call(Color.fill, trace.fillcolor) + + if (w) { + p.call(Color.stroke, trace.line.color) + } + }) } -function stylePies(d) { - var trace = d[0].trace, - pts = d3.select(this).select('g.legendpoints') +function stylePies (d) { + var trace = d[0].trace, + pts = d3.select(this).select('g.legendpoints') .selectAll('path.legendpie') - .data(Registry.traceIs(trace, 'pie') && trace.visible ? [d] : []); - pts.enter().append('path').classed('legendpie', true) + .data(Registry.traceIs(trace, 'pie') && trace.visible ? [d] : []) + pts.enter().append('path').classed('legendpie', true) .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); + .attr('transform', 'translate(20,0)') + pts.exit().remove() - if(pts.size()) pts.call(stylePie, d[0], trace); + if (pts.size()) pts.call(stylePie, d[0], trace) } diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index aae0503e368..354f08c76f7 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -6,18 +6,16 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') +var Axes = require('../../plots/cartesian/axes') +var Lib = require('../../lib') +var downloadImage = require('../../snapshot/download') +var Icons = require('../../../build/ploticon') -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); -var Axes = require('../../plots/cartesian/axes'); -var Lib = require('../../lib'); -var downloadImage = require('../../snapshot/download'); -var Icons = require('../../../build/ploticon'); - - -var modeBarButtons = module.exports = {}; +var modeBarButtons = module.exports = {} /** * ModeBar buttons configuration @@ -46,475 +44,468 @@ var modeBarButtons = module.exports = {}; */ modeBarButtons.toImage = { - name: 'toImage', - title: 'Download plot as a png', - icon: Icons.camera, - click: function(gd) { - var format = 'png'; + name: 'toImage', + title: 'Download plot as a png', + icon: Icons.camera, + click: function (gd) { + var format = 'png' - Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); + Lib.notifier('Taking snapshot - this may take a few seconds', 'long') - if(Lib.isIE()) { - Lib.notifier('IE only supports svg. Changing format to svg.', 'long'); - format = 'svg'; - } + if (Lib.isIE()) { + Lib.notifier('IE only supports svg. Changing format to svg.', 'long') + format = 'svg' + } - downloadImage(gd, {'format': format}) - .then(function(filename) { - Lib.notifier('Snapshot succeeded - ' + filename, 'long'); + downloadImage(gd, {'format': format}) + .then(function (filename) { + Lib.notifier('Snapshot succeeded - ' + filename, 'long') }) - .catch(function() { - Lib.notifier('Sorry there was a problem downloading your snapshot!', 'long'); - }); - } -}; + .catch(function () { + Lib.notifier('Sorry there was a problem downloading your snapshot!', 'long') + }) + } +} modeBarButtons.sendDataToCloud = { - name: 'sendDataToCloud', - title: 'Save and edit plot in cloud', - icon: Icons.disk, - click: function(gd) { - Plots.sendDataToCloud(gd); - } -}; + name: 'sendDataToCloud', + title: 'Save and edit plot in cloud', + icon: Icons.disk, + click: function (gd) { + Plots.sendDataToCloud(gd) + } +} modeBarButtons.zoom2d = { - name: 'zoom2d', - title: 'Zoom', - attr: 'dragmode', - val: 'zoom', - icon: Icons.zoombox, - click: handleCartesian -}; + name: 'zoom2d', + title: 'Zoom', + attr: 'dragmode', + val: 'zoom', + icon: Icons.zoombox, + click: handleCartesian +} modeBarButtons.pan2d = { - name: 'pan2d', - title: 'Pan', - attr: 'dragmode', - val: 'pan', - icon: Icons.pan, - click: handleCartesian -}; + name: 'pan2d', + title: 'Pan', + attr: 'dragmode', + val: 'pan', + icon: Icons.pan, + click: handleCartesian +} modeBarButtons.select2d = { - name: 'select2d', - title: 'Box Select', - attr: 'dragmode', - val: 'select', - icon: Icons.selectbox, - click: handleCartesian -}; + name: 'select2d', + title: 'Box Select', + attr: 'dragmode', + val: 'select', + icon: Icons.selectbox, + click: handleCartesian +} modeBarButtons.lasso2d = { - name: 'lasso2d', - title: 'Lasso Select', - attr: 'dragmode', - val: 'lasso', - icon: Icons.lasso, - click: handleCartesian -}; + name: 'lasso2d', + title: 'Lasso Select', + attr: 'dragmode', + val: 'lasso', + icon: Icons.lasso, + click: handleCartesian +} modeBarButtons.zoomIn2d = { - name: 'zoomIn2d', - title: 'Zoom in', - attr: 'zoom', - val: 'in', - icon: Icons.zoom_plus, - click: handleCartesian -}; + name: 'zoomIn2d', + title: 'Zoom in', + attr: 'zoom', + val: 'in', + icon: Icons.zoom_plus, + click: handleCartesian +} modeBarButtons.zoomOut2d = { - name: 'zoomOut2d', - title: 'Zoom out', - attr: 'zoom', - val: 'out', - icon: Icons.zoom_minus, - click: handleCartesian -}; + name: 'zoomOut2d', + title: 'Zoom out', + attr: 'zoom', + val: 'out', + icon: Icons.zoom_minus, + click: handleCartesian +} modeBarButtons.autoScale2d = { - name: 'autoScale2d', - title: 'Autoscale', - attr: 'zoom', - val: 'auto', - icon: Icons.autoscale, - click: handleCartesian -}; + name: 'autoScale2d', + title: 'Autoscale', + attr: 'zoom', + val: 'auto', + icon: Icons.autoscale, + click: handleCartesian +} modeBarButtons.resetScale2d = { - name: 'resetScale2d', - title: 'Reset axes', - attr: 'zoom', - val: 'reset', - icon: Icons.home, - click: handleCartesian -}; + name: 'resetScale2d', + title: 'Reset axes', + attr: 'zoom', + val: 'reset', + icon: Icons.home, + click: handleCartesian +} modeBarButtons.hoverClosestCartesian = { - name: 'hoverClosestCartesian', - title: 'Show closest data on hover', - attr: 'hovermode', - val: 'closest', - icon: Icons.tooltip_basic, - gravity: 'ne', - click: handleCartesian -}; + name: 'hoverClosestCartesian', + title: 'Show closest data on hover', + attr: 'hovermode', + val: 'closest', + icon: Icons.tooltip_basic, + gravity: 'ne', + click: handleCartesian +} modeBarButtons.hoverCompareCartesian = { - name: 'hoverCompareCartesian', - title: 'Compare data on hover', - attr: 'hovermode', - val: function(gd) { - return gd._fullLayout._isHoriz ? 'y' : 'x'; - }, - icon: Icons.tooltip_compare, - gravity: 'ne', - click: handleCartesian -}; - -function handleCartesian(gd, ev) { - var button = ev.currentTarget, - astr = button.getAttribute('data-attr'), - val = button.getAttribute('data-val') || true, - fullLayout = gd._fullLayout, - aobj = {}; - - if(astr === 'zoom') { - var mag = (val === 'in') ? 0.5 : 2, - r0 = (1 + mag) / 2, - r1 = (1 - mag) / 2, - axList = Axes.list(gd, null, true); - - var ax, axName; - - for(var i = 0; i < axList.length; i++) { - ax = axList[i]; - - if(!ax.fixedrange) { - axName = ax._name; - if(val === 'auto') aobj[axName + '.autorange'] = true; - else if(val === 'reset') { - if(ax._rangeInitial === undefined) { - aobj[axName + '.autorange'] = true; - } - else { - var rangeInitial = ax._rangeInitial.slice(); - aobj[axName + '.range[0]'] = rangeInitial[0]; - aobj[axName + '.range[1]'] = rangeInitial[1]; - } - } - else { - var rangeNow = [ - ax.r2l(ax.range[0]), - ax.r2l(ax.range[1]), - ]; - - var rangeNew = [ - r0 * rangeNow[0] + r1 * rangeNow[1], - r0 * rangeNow[1] + r1 * rangeNow[0] - ]; - - aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]); - aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]); - } - } + name: 'hoverCompareCartesian', + title: 'Compare data on hover', + attr: 'hovermode', + val: function (gd) { + return gd._fullLayout._isHoriz ? 'y' : 'x' + }, + icon: Icons.tooltip_compare, + gravity: 'ne', + click: handleCartesian +} + +function handleCartesian (gd, ev) { + var button = ev.currentTarget, + astr = button.getAttribute('data-attr'), + val = button.getAttribute('data-val') || true, + fullLayout = gd._fullLayout, + aobj = {} + + if (astr === 'zoom') { + var mag = (val === 'in') ? 0.5 : 2, + r0 = (1 + mag) / 2, + r1 = (1 - mag) / 2, + axList = Axes.list(gd, null, true) + + var ax, axName + + for (var i = 0; i < axList.length; i++) { + ax = axList[i] + + if (!ax.fixedrange) { + axName = ax._name + if (val === 'auto') aobj[axName + '.autorange'] = true + else if (val === 'reset') { + if (ax._rangeInitial === undefined) { + aobj[axName + '.autorange'] = true + } else { + var rangeInitial = ax._rangeInitial.slice() + aobj[axName + '.range[0]'] = rangeInitial[0] + aobj[axName + '.range[1]'] = rangeInitial[1] + } + } else { + var rangeNow = [ + ax.r2l(ax.range[0]), + ax.r2l(ax.range[1]) + ] + + var rangeNew = [ + r0 * rangeNow[0] + r1 * rangeNow[1], + r0 * rangeNow[1] + r1 * rangeNow[0] + ] + + aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]) + aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]) } + } } - else { + } else { // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y' - if(astr === 'hovermode' && (val === 'x' || val === 'y')) { - val = fullLayout._isHoriz ? 'y' : 'x'; - button.setAttribute('data-val', val); - } - - aobj[astr] = val; + if (astr === 'hovermode' && (val === 'x' || val === 'y')) { + val = fullLayout._isHoriz ? 'y' : 'x' + button.setAttribute('data-val', val) } - Plotly.relayout(gd, aobj); + aobj[astr] = val + } + + Plotly.relayout(gd, aobj) } modeBarButtons.zoom3d = { - name: 'zoom3d', - title: 'Zoom', - attr: 'scene.dragmode', - val: 'zoom', - icon: Icons.zoombox, - click: handleDrag3d -}; + name: 'zoom3d', + title: 'Zoom', + attr: 'scene.dragmode', + val: 'zoom', + icon: Icons.zoombox, + click: handleDrag3d +} modeBarButtons.pan3d = { - name: 'pan3d', - title: 'Pan', - attr: 'scene.dragmode', - val: 'pan', - icon: Icons.pan, - click: handleDrag3d -}; + name: 'pan3d', + title: 'Pan', + attr: 'scene.dragmode', + val: 'pan', + icon: Icons.pan, + click: handleDrag3d +} modeBarButtons.orbitRotation = { - name: 'orbitRotation', - title: 'orbital rotation', - attr: 'scene.dragmode', - val: 'orbit', - icon: Icons['3d_rotate'], - click: handleDrag3d -}; + name: 'orbitRotation', + title: 'orbital rotation', + attr: 'scene.dragmode', + val: 'orbit', + icon: Icons['3d_rotate'], + click: handleDrag3d +} modeBarButtons.tableRotation = { - name: 'tableRotation', - title: 'turntable rotation', - attr: 'scene.dragmode', - val: 'turntable', - icon: Icons['z-axis'], - click: handleDrag3d -}; - -function handleDrag3d(gd, ev) { - var button = ev.currentTarget, - attr = button.getAttribute('data-attr'), - val = button.getAttribute('data-val') || true, - fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), - layoutUpdate = {}; - - var parts = attr.split('.'); - - for(var i = 0; i < sceneIds.length; i++) { - layoutUpdate[sceneIds[i] + '.' + parts[1]] = val; - } + name: 'tableRotation', + title: 'turntable rotation', + attr: 'scene.dragmode', + val: 'turntable', + icon: Icons['z-axis'], + click: handleDrag3d +} + +function handleDrag3d (gd, ev) { + var button = ev.currentTarget, + attr = button.getAttribute('data-attr'), + val = button.getAttribute('data-val') || true, + fullLayout = gd._fullLayout, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), + layoutUpdate = {} + + var parts = attr.split('.') - Plotly.relayout(gd, layoutUpdate); + for (var i = 0; i < sceneIds.length; i++) { + layoutUpdate[sceneIds[i] + '.' + parts[1]] = val + } + + Plotly.relayout(gd, layoutUpdate) } modeBarButtons.resetCameraDefault3d = { - name: 'resetCameraDefault3d', - title: 'Reset camera to default', - attr: 'resetDefault', - icon: Icons.home, - click: handleCamera3d -}; + name: 'resetCameraDefault3d', + title: 'Reset camera to default', + attr: 'resetDefault', + icon: Icons.home, + click: handleCamera3d +} modeBarButtons.resetCameraLastSave3d = { - name: 'resetCameraLastSave3d', - title: 'Reset camera to last save', - attr: 'resetLastSave', - icon: Icons.movie, - click: handleCamera3d -}; - -function handleCamera3d(gd, ev) { - var button = ev.currentTarget, - attr = button.getAttribute('data-attr'), - fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), - aobj = {}; - - for(var i = 0; i < sceneIds.length; i++) { - var sceneId = sceneIds[i], - key = sceneId + '.camera', - scene = fullLayout[sceneId]._scene; - - if(attr === 'resetDefault') { - aobj[key] = null; - } - else if(attr === 'resetLastSave') { - aobj[key] = Lib.extendDeep({}, scene.cameraInitial); - } + name: 'resetCameraLastSave3d', + title: 'Reset camera to last save', + attr: 'resetLastSave', + icon: Icons.movie, + click: handleCamera3d +} + +function handleCamera3d (gd, ev) { + var button = ev.currentTarget, + attr = button.getAttribute('data-attr'), + fullLayout = gd._fullLayout, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), + aobj = {} + + for (var i = 0; i < sceneIds.length; i++) { + var sceneId = sceneIds[i], + key = sceneId + '.camera', + scene = fullLayout[sceneId]._scene + + if (attr === 'resetDefault') { + aobj[key] = null + } else if (attr === 'resetLastSave') { + aobj[key] = Lib.extendDeep({}, scene.cameraInitial) } + } - Plotly.relayout(gd, aobj); + Plotly.relayout(gd, aobj) } modeBarButtons.hoverClosest3d = { - name: 'hoverClosest3d', - title: 'Toggle show closest data on hover', - attr: 'hovermode', - val: null, - toggle: true, - icon: Icons.tooltip_basic, - gravity: 'ne', - click: handleHover3d -}; - -function handleHover3d(gd, ev) { - var button = ev.currentTarget, - val = button._previousVal || false, - layout = gd.layout, - fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'); - - var axes = ['xaxis', 'yaxis', 'zaxis'], - spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor']; + name: 'hoverClosest3d', + title: 'Toggle show closest data on hover', + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: handleHover3d +} - // initialize 'current spike' object to be stored in the DOM - var currentSpikes = {}, - axisSpikes = {}, - layoutUpdate = {}; +function handleHover3d (gd, ev) { + var button = ev.currentTarget, + val = button._previousVal || false, + layout = gd.layout, + fullLayout = gd._fullLayout, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d') - if(val) { - layoutUpdate = Lib.extendDeep(layout, val); - button._previousVal = null; + var axes = ['xaxis', 'yaxis', 'zaxis'], + spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'] + + // initialize 'current spike' object to be stored in the DOM + var currentSpikes = {}, + axisSpikes = {}, + layoutUpdate = {} + + if (val) { + layoutUpdate = Lib.extendDeep(layout, val) + button._previousVal = null + } else { + layoutUpdate = { + 'allaxes.showspikes': false } - else { - layoutUpdate = { - 'allaxes.showspikes': false - }; - for(var i = 0; i < sceneIds.length; i++) { - var sceneId = sceneIds[i], - sceneLayout = fullLayout[sceneId], - sceneSpikes = currentSpikes[sceneId] = {}; + for (var i = 0; i < sceneIds.length; i++) { + var sceneId = sceneIds[i], + sceneLayout = fullLayout[sceneId], + sceneSpikes = currentSpikes[sceneId] = {} - sceneSpikes.hovermode = sceneLayout.hovermode; - layoutUpdate[sceneId + '.hovermode'] = false; + sceneSpikes.hovermode = sceneLayout.hovermode + layoutUpdate[sceneId + '.hovermode'] = false // copy all the current spike attrs - for(var j = 0; j < 3; j++) { - var axis = axes[j]; - axisSpikes = sceneSpikes[axis] = {}; - - for(var k = 0; k < spikeAttrs.length; k++) { - var spikeAttr = spikeAttrs[k]; - axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr]; - } - } - } + for (var j = 0; j < 3; j++) { + var axis = axes[j] + axisSpikes = sceneSpikes[axis] = {} - button._previousVal = Lib.extendDeep({}, currentSpikes); + for (var k = 0; k < spikeAttrs.length; k++) { + var spikeAttr = spikeAttrs[k] + axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr] + } + } } - Plotly.relayout(gd, layoutUpdate); + button._previousVal = Lib.extendDeep({}, currentSpikes) + } + + Plotly.relayout(gd, layoutUpdate) } modeBarButtons.zoomInGeo = { - name: 'zoomInGeo', - title: 'Zoom in', - attr: 'zoom', - val: 'in', - icon: Icons.zoom_plus, - click: handleGeo -}; + name: 'zoomInGeo', + title: 'Zoom in', + attr: 'zoom', + val: 'in', + icon: Icons.zoom_plus, + click: handleGeo +} modeBarButtons.zoomOutGeo = { - name: 'zoomOutGeo', - title: 'Zoom out', - attr: 'zoom', - val: 'out', - icon: Icons.zoom_minus, - click: handleGeo -}; + name: 'zoomOutGeo', + title: 'Zoom out', + attr: 'zoom', + val: 'out', + icon: Icons.zoom_minus, + click: handleGeo +} modeBarButtons.resetGeo = { - name: 'resetGeo', - title: 'Reset', - attr: 'reset', - val: null, - icon: Icons.autoscale, - click: handleGeo -}; + name: 'resetGeo', + title: 'Reset', + attr: 'reset', + val: null, + icon: Icons.autoscale, + click: handleGeo +} modeBarButtons.hoverClosestGeo = { - name: 'hoverClosestGeo', - title: 'Toggle show closest data on hover', - attr: 'hovermode', - val: null, - toggle: true, - icon: Icons.tooltip_basic, - gravity: 'ne', - click: toggleHover -}; - -function handleGeo(gd, ev) { - var button = ev.currentTarget, - attr = button.getAttribute('data-attr'), - val = button.getAttribute('data-val') || true, - fullLayout = gd._fullLayout, - geoIds = Plots.getSubplotIds(fullLayout, 'geo'); - - for(var i = 0; i < geoIds.length; i++) { - var geo = fullLayout[geoIds[i]]._subplot; - - if(attr === 'zoom') { - var scale = geo.projection.scale(); - var newScale = (val === 'in') ? 2 * scale : 0.5 * scale; - geo.projection.scale(newScale); - geo.zoom.scale(newScale); - geo.render(); - } - else if(attr === 'reset') geo.zoomReset(); - } + name: 'hoverClosestGeo', + title: 'Toggle show closest data on hover', + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +} + +function handleGeo (gd, ev) { + var button = ev.currentTarget, + attr = button.getAttribute('data-attr'), + val = button.getAttribute('data-val') || true, + fullLayout = gd._fullLayout, + geoIds = Plots.getSubplotIds(fullLayout, 'geo') + + for (var i = 0; i < geoIds.length; i++) { + var geo = fullLayout[geoIds[i]]._subplot + + if (attr === 'zoom') { + var scale = geo.projection.scale() + var newScale = (val === 'in') ? 2 * scale : 0.5 * scale + geo.projection.scale(newScale) + geo.zoom.scale(newScale) + geo.render() + } else if (attr === 'reset') geo.zoomReset() + } } modeBarButtons.hoverClosestGl2d = { - name: 'hoverClosestGl2d', - title: 'Toggle show closest data on hover', - attr: 'hovermode', - val: null, - toggle: true, - icon: Icons.tooltip_basic, - gravity: 'ne', - click: toggleHover -}; + name: 'hoverClosestGl2d', + title: 'Toggle show closest data on hover', + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +} modeBarButtons.hoverClosestPie = { - name: 'hoverClosestPie', - title: 'Toggle show closest data on hover', - attr: 'hovermode', - val: 'closest', - icon: Icons.tooltip_basic, - gravity: 'ne', - click: toggleHover -}; - -function toggleHover(gd) { - var fullLayout = gd._fullLayout; - - var onHoverVal; - if(fullLayout._has('cartesian')) { - onHoverVal = fullLayout._isHoriz ? 'y' : 'x'; - } - else onHoverVal = 'closest'; + name: 'hoverClosestPie', + title: 'Toggle show closest data on hover', + attr: 'hovermode', + val: 'closest', + icon: Icons.tooltip_basic, + gravity: 'ne', + click: toggleHover +} + +function toggleHover (gd) { + var fullLayout = gd._fullLayout - var newHover = gd._fullLayout.hovermode ? false : onHoverVal; + var onHoverVal + if (fullLayout._has('cartesian')) { + onHoverVal = fullLayout._isHoriz ? 'y' : 'x' + } else onHoverVal = 'closest' - Plotly.relayout(gd, 'hovermode', newHover); + var newHover = gd._fullLayout.hovermode ? false : onHoverVal + + Plotly.relayout(gd, 'hovermode', newHover) } // buttons when more then one plot types are present modeBarButtons.toggleHover = { - name: 'toggleHover', - title: 'Toggle show closest data on hover', - attr: 'hovermode', - val: null, - toggle: true, - icon: Icons.tooltip_basic, - gravity: 'ne', - click: function(gd, ev) { - toggleHover(gd); + name: 'toggleHover', + title: 'Toggle show closest data on hover', + attr: 'hovermode', + val: null, + toggle: true, + icon: Icons.tooltip_basic, + gravity: 'ne', + click: function (gd, ev) { + toggleHover(gd) // the 3d hovermode update must come // last so that layout.hovermode update does not // override scene?.hovermode?.layout. - handleHover3d(gd, ev); - } -}; + handleHover3d(gd, ev) + } +} modeBarButtons.resetViews = { - name: 'resetViews', - title: 'Reset views', - icon: Icons.home, - click: function(gd, ev) { - var button = ev.currentTarget; + name: 'resetViews', + title: 'Reset views', + icon: Icons.home, + click: function (gd, ev) { + var button = ev.currentTarget - button.setAttribute('data-attr', 'zoom'); - button.setAttribute('data-val', 'reset'); - handleCartesian(gd, ev); + button.setAttribute('data-attr', 'zoom') + button.setAttribute('data-val', 'reset') + handleCartesian(gd, ev) - button.setAttribute('data-attr', 'resetLastSave'); - handleCamera3d(gd, ev); + button.setAttribute('data-attr', 'resetLastSave') + handleCamera3d(gd, ev) // N.B handleCamera3d also triggers a replot for // geo subplots. - } -}; + } +} diff --git a/src/components/modebar/index.js b/src/components/modebar/index.js index 787fa706d4b..3e3a30bbee6 100644 --- a/src/components/modebar/index.js +++ b/src/components/modebar/index.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -exports.manage = require('./manage'); +exports.manage = require('./manage') diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 57e5e2d17b3..b9dc84c7438 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Axes = require('../../plots/cartesian/axes') +var scatterSubTypes = require('../../traces/scatter/subtypes') -var Axes = require('../../plots/cartesian/axes'); -var scatterSubTypes = require('../../traces/scatter/subtypes'); - -var createModeBar = require('./modebar'); -var modeBarButtons = require('./buttons'); +var createModeBar = require('./modebar') +var modeBarButtons = require('./buttons') /** * ModeBar wrapper around 'create' and 'update', @@ -23,204 +22,198 @@ var modeBarButtons = require('./buttons'); * @param {object} gd main plot object * */ -module.exports = function manageModeBar(gd) { - var fullLayout = gd._fullLayout, - context = gd._context, - modeBar = fullLayout._modeBar; - - if(!context.displayModeBar) { - if(modeBar) { - modeBar.destroy(); - delete fullLayout._modeBar; - } - return; - } - - if(!Array.isArray(context.modeBarButtonsToRemove)) { - throw new Error([ - '*modeBarButtonsToRemove* configuration options', - 'must be an array.' - ].join(' ')); - } - - if(!Array.isArray(context.modeBarButtonsToAdd)) { - throw new Error([ - '*modeBarButtonsToAdd* configuration options', - 'must be an array.' - ].join(' ')); - } - - var customButtons = context.modeBarButtons; - var buttonGroups; - - if(Array.isArray(customButtons) && customButtons.length) { - buttonGroups = fillCustomButton(customButtons); - } - else { - buttonGroups = getButtonGroups( +module.exports = function manageModeBar (gd) { + var fullLayout = gd._fullLayout, + context = gd._context, + modeBar = fullLayout._modeBar + + if (!context.displayModeBar) { + if (modeBar) { + modeBar.destroy() + delete fullLayout._modeBar + } + return + } + + if (!Array.isArray(context.modeBarButtonsToRemove)) { + throw new Error([ + '*modeBarButtonsToRemove* configuration options', + 'must be an array.' + ].join(' ')) + } + + if (!Array.isArray(context.modeBarButtonsToAdd)) { + throw new Error([ + '*modeBarButtonsToAdd* configuration options', + 'must be an array.' + ].join(' ')) + } + + var customButtons = context.modeBarButtons + var buttonGroups + + if (Array.isArray(customButtons) && customButtons.length) { + buttonGroups = fillCustomButton(customButtons) + } else { + buttonGroups = getButtonGroups( gd, context.modeBarButtonsToRemove, context.modeBarButtonsToAdd - ); - } + ) + } - if(modeBar) modeBar.update(gd, buttonGroups); - else fullLayout._modeBar = createModeBar(gd, buttonGroups); -}; + if (modeBar) modeBar.update(gd, buttonGroups) + else fullLayout._modeBar = createModeBar(gd, buttonGroups) +} // logic behind which buttons are displayed by default -function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { - var fullLayout = gd._fullLayout, - fullData = gd._fullData; - - var hasCartesian = fullLayout._has('cartesian'), - hasGL3D = fullLayout._has('gl3d'), - hasGeo = fullLayout._has('geo'), - hasPie = fullLayout._has('pie'), - hasGL2D = fullLayout._has('gl2d'), - hasTernary = fullLayout._has('ternary'); - - var groups = []; - - function addGroup(newGroup) { - var out = []; - - for(var i = 0; i < newGroup.length; i++) { - var button = newGroup[i]; - if(buttonsToRemove.indexOf(button) !== -1) continue; - out.push(modeBarButtons[button]); - } - - groups.push(out); - } - - // buttons common to all plot types - addGroup(['toImage', 'sendDataToCloud']); - - // graphs with more than one plot types get 'union buttons' - // which reset the view or toggle hover labels across all subplots. - if((hasCartesian || hasGL2D || hasPie || hasTernary) + hasGeo + hasGL3D > 1) { - addGroup(['resetViews', 'toggleHover']); - return appendButtonsToGroups(groups, buttonsToAdd); - } +function getButtonGroups (gd, buttonsToRemove, buttonsToAdd) { + var fullLayout = gd._fullLayout, + fullData = gd._fullData - if(hasGL3D) { - addGroup(['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']); - addGroup(['resetCameraDefault3d', 'resetCameraLastSave3d']); - addGroup(['hoverClosest3d']); - } + var hasCartesian = fullLayout._has('cartesian'), + hasGL3D = fullLayout._has('gl3d'), + hasGeo = fullLayout._has('geo'), + hasPie = fullLayout._has('pie'), + hasGL2D = fullLayout._has('gl2d'), + hasTernary = fullLayout._has('ternary') - if(hasGeo) { - addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']); - addGroup(['hoverClosestGeo']); - } + var groups = [] - var allAxesFixed = areAllAxesFixed(fullLayout), - dragModeGroup = []; + function addGroup (newGroup) { + var out = [] - if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { - dragModeGroup = ['zoom2d', 'pan2d']; - } - if((hasCartesian || hasTernary) && isSelectable(fullData)) { - dragModeGroup.push('select2d'); - dragModeGroup.push('lasso2d'); + for (var i = 0; i < newGroup.length; i++) { + var button = newGroup[i] + if (buttonsToRemove.indexOf(button) !== -1) continue + out.push(modeBarButtons[button]) } - if(dragModeGroup.length) addGroup(dragModeGroup); - if((hasCartesian || hasGL2D) && !allAxesFixed && !hasTernary) { - addGroup(['zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']); - } + groups.push(out) + } - if(hasCartesian && hasPie) { - addGroup(['toggleHover']); - } - else if(hasGL2D) { - addGroup(['hoverClosestGl2d']); - } - else if(hasCartesian) { - addGroup(['hoverClosestCartesian', 'hoverCompareCartesian']); - } - else if(hasPie) { - addGroup(['hoverClosestPie']); - } + // buttons common to all plot types + addGroup(['toImage', 'sendDataToCloud']) - return appendButtonsToGroups(groups, buttonsToAdd); + // graphs with more than one plot types get 'union buttons' + // which reset the view or toggle hover labels across all subplots. + if ((hasCartesian || hasGL2D || hasPie || hasTernary) + hasGeo + hasGL3D > 1) { + addGroup(['resetViews', 'toggleHover']) + return appendButtonsToGroups(groups, buttonsToAdd) + } + + if (hasGL3D) { + addGroup(['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']) + addGroup(['resetCameraDefault3d', 'resetCameraLastSave3d']) + addGroup(['hoverClosest3d']) + } + + if (hasGeo) { + addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']) + addGroup(['hoverClosestGeo']) + } + + var allAxesFixed = areAllAxesFixed(fullLayout), + dragModeGroup = [] + + if (((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { + dragModeGroup = ['zoom2d', 'pan2d'] + } + if ((hasCartesian || hasTernary) && isSelectable(fullData)) { + dragModeGroup.push('select2d') + dragModeGroup.push('lasso2d') + } + if (dragModeGroup.length) addGroup(dragModeGroup) + + if ((hasCartesian || hasGL2D) && !allAxesFixed && !hasTernary) { + addGroup(['zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']) + } + + if (hasCartesian && hasPie) { + addGroup(['toggleHover']) + } else if (hasGL2D) { + addGroup(['hoverClosestGl2d']) + } else if (hasCartesian) { + addGroup(['hoverClosestCartesian', 'hoverCompareCartesian']) + } else if (hasPie) { + addGroup(['hoverClosestPie']) + } + + return appendButtonsToGroups(groups, buttonsToAdd) } -function areAllAxesFixed(fullLayout) { - var axList = Axes.list({_fullLayout: fullLayout}, null, true); - var allFixed = true; +function areAllAxesFixed (fullLayout) { + var axList = Axes.list({_fullLayout: fullLayout}, null, true) + var allFixed = true - for(var i = 0; i < axList.length; i++) { - if(!axList[i].fixedrange) { - allFixed = false; - break; - } + for (var i = 0; i < axList.length; i++) { + if (!axList[i].fixedrange) { + allFixed = false + break } + } - return allFixed; + return allFixed } // look for traces that support selection // to be updated as we add more selectPoints handlers -function isSelectable(fullData) { - var selectable = false; +function isSelectable (fullData) { + var selectable = false - for(var i = 0; i < fullData.length; i++) { - if(selectable) break; + for (var i = 0; i < fullData.length; i++) { + if (selectable) break - var trace = fullData[i]; + var trace = fullData[i] - if(!trace._module || !trace._module.selectPoints) continue; + if (!trace._module || !trace._module.selectPoints) continue - if(trace.type === 'scatter' || trace.type === 'scatterternary') { - if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) { - selectable = true; - } - } + if (trace.type === 'scatter' || trace.type === 'scatterternary') { + if (scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) { + selectable = true + } + } // assume that in general if the trace module has selectPoints, // then it's selectable. Scatter is an exception to this because it must // have markers or text, not just be a scatter type. - else selectable = true; - } + else selectable = true + } - return selectable; + return selectable } -function appendButtonsToGroups(groups, buttons) { - if(buttons.length) { - if(Array.isArray(buttons[0])) { - for(var i = 0; i < buttons.length; i++) { - groups.push(buttons[i]); - } - } - else groups.push(buttons); - } +function appendButtonsToGroups (groups, buttons) { + if (buttons.length) { + if (Array.isArray(buttons[0])) { + for (var i = 0; i < buttons.length; i++) { + groups.push(buttons[i]) + } + } else groups.push(buttons) + } - return groups; + return groups } // fill in custom buttons referring to default mode bar buttons -function fillCustomButton(customButtons) { - for(var i = 0; i < customButtons.length; i++) { - var buttonGroup = customButtons[i]; - - for(var j = 0; j < buttonGroup.length; j++) { - var button = buttonGroup[j]; - - if(typeof button === 'string') { - if(modeBarButtons[button] !== undefined) { - customButtons[i][j] = modeBarButtons[button]; - } - else { - throw new Error([ - '*modeBarButtons* configuration options', - 'invalid button name' - ].join(' ')); - } - } +function fillCustomButton (customButtons) { + for (var i = 0; i < customButtons.length; i++) { + var buttonGroup = customButtons[i] + + for (var j = 0; j < buttonGroup.length; j++) { + var button = buttonGroup[j] + + if (typeof button === 'string') { + if (modeBarButtons[button] !== undefined) { + customButtons[i][j] = modeBarButtons[button] + } else { + throw new Error([ + '*modeBarButtons* configuration options', + 'invalid button name' + ].join(' ')) } + } } + } - return customButtons; + return customButtons } diff --git a/src/components/modebar/modebar.js b/src/components/modebar/modebar.js index 054a8a91928..7799710da9f 100644 --- a/src/components/modebar/modebar.js +++ b/src/components/modebar/modebar.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var d3 = require('d3'); - -var Lib = require('../../lib'); -var Icons = require('../../../build/ploticon'); +var d3 = require('d3') +var Lib = require('../../lib') +var Icons = require('../../../build/ploticon') /** * UI controller for interactive plots @@ -23,16 +21,16 @@ var Icons = require('../../../build/ploticon'); * @Param {object} opts.container container div to append modeBar * @Param {object} opts.graphInfo primary plot object containing data and layout */ -function ModeBar(opts) { - this.container = opts.container; - this.element = document.createElement('div'); +function ModeBar (opts) { + this.container = opts.container + this.element = document.createElement('div') - this.update(opts.graphInfo, opts.buttons); + this.update(opts.graphInfo, opts.buttons) - this.container.appendChild(this.element); + this.container.appendChild(this.element) } -var proto = ModeBar.prototype; +var proto = ModeBar.prototype /** * Update modeBar (buttons and logo) @@ -41,119 +39,117 @@ var proto = ModeBar.prototype; * @param {array of arrays} buttons nested arrays of grouped buttons to initialize * */ -proto.update = function(graphInfo, buttons) { - this.graphInfo = graphInfo; +proto.update = function (graphInfo, buttons) { + this.graphInfo = graphInfo - var context = this.graphInfo._context; + var context = this.graphInfo._context - if(context.displayModeBar === 'hover') { - this.element.className = 'modebar modebar--hover'; - } - else this.element.className = 'modebar'; + if (context.displayModeBar === 'hover') { + this.element.className = 'modebar modebar--hover' + } else this.element.className = 'modebar' // if buttons or logo have changed, redraw modebar interior - var needsNewButtons = !this.hasButtons(buttons), - needsNewLogo = (this.hasLogo !== context.displaylogo); + var needsNewButtons = !this.hasButtons(buttons), + needsNewLogo = (this.hasLogo !== context.displaylogo) - if(needsNewButtons || needsNewLogo) { - this.removeAllButtons(); + if (needsNewButtons || needsNewLogo) { + this.removeAllButtons() - this.updateButtons(buttons); + this.updateButtons(buttons) - if(context.displaylogo) { - this.element.appendChild(this.getLogo()); - this.hasLogo = true; - } + if (context.displaylogo) { + this.element.appendChild(this.getLogo()) + this.hasLogo = true } + } - this.updateActiveButton(); -}; - -proto.updateButtons = function(buttons) { - var _this = this; - - this.buttons = buttons; - this.buttonElements = []; - this.buttonsNames = []; - - this.buttons.forEach(function(buttonGroup) { - var group = _this.createGroup(); - - buttonGroup.forEach(function(buttonConfig) { - var buttonName = buttonConfig.name; - if(!buttonName) { - throw new Error('must provide button \'name\' in button config'); - } - if(_this.buttonsNames.indexOf(buttonName) !== -1) { - throw new Error('button name \'' + buttonName + '\' is taken'); - } - _this.buttonsNames.push(buttonName); - - var button = _this.createButton(buttonConfig); - _this.buttonElements.push(button); - group.appendChild(button); - }); + this.updateActiveButton() +} - _this.element.appendChild(group); - }); -}; +proto.updateButtons = function (buttons) { + var _this = this + + this.buttons = buttons + this.buttonElements = [] + this.buttonsNames = [] + + this.buttons.forEach(function (buttonGroup) { + var group = _this.createGroup() + + buttonGroup.forEach(function (buttonConfig) { + var buttonName = buttonConfig.name + if (!buttonName) { + throw new Error('must provide button \'name\' in button config') + } + if (_this.buttonsNames.indexOf(buttonName) !== -1) { + throw new Error('button name \'' + buttonName + '\' is taken') + } + _this.buttonsNames.push(buttonName) + + var button = _this.createButton(buttonConfig) + _this.buttonElements.push(button) + group.appendChild(button) + }) + + _this.element.appendChild(group) + }) +} /** * Empty div for containing a group of buttons * @Return {HTMLelement} */ -proto.createGroup = function() { - var group = document.createElement('div'); - group.className = 'modebar-group'; +proto.createGroup = function () { + var group = document.createElement('div') + group.className = 'modebar-group' - return group; -}; + return group +} /** * Create a new button div and set constant and configurable attributes * @Param {object} config (see ./buttons.js for more info) * @Return {HTMLelement} */ -proto.createButton = function(config) { - var _this = this, - button = document.createElement('a'); +proto.createButton = function (config) { + var _this = this, + button = document.createElement('a') - button.setAttribute('rel', 'tooltip'); - button.className = 'modebar-btn'; + button.setAttribute('rel', 'tooltip') + button.className = 'modebar-btn' - var title = config.title; - if(title === undefined) title = config.name; - if(title || title === 0) button.setAttribute('data-title', title); + var title = config.title + if (title === undefined) title = config.name + if (title || title === 0) button.setAttribute('data-title', title) - if(config.attr !== undefined) button.setAttribute('data-attr', config.attr); + if (config.attr !== undefined) button.setAttribute('data-attr', config.attr) - var val = config.val; - if(val !== undefined) { - if(typeof val === 'function') val = val(this.graphInfo); - button.setAttribute('data-val', val); - } + var val = config.val + if (val !== undefined) { + if (typeof val === 'function') val = val(this.graphInfo) + button.setAttribute('data-val', val) + } - var click = config.click; - if(typeof click !== 'function') { - throw new Error('must provide button \'click\' function in button config'); - } - else { - button.addEventListener('click', function(ev) { - config.click(_this.graphInfo, ev); + var click = config.click + if (typeof click !== 'function') { + throw new Error('must provide button \'click\' function in button config') + } else { + button.addEventListener('click', function (ev) { + config.click(_this.graphInfo, ev) // only needed for 'hoverClosestGeo' which does not call relayout - _this.updateActiveButton(ev.currentTarget); - }); - } + _this.updateActiveButton(ev.currentTarget) + }) + } - button.setAttribute('data-toggle', config.toggle || false); - if(config.toggle) d3.select(button).classed('active', true); + button.setAttribute('data-toggle', config.toggle || false) + if (config.toggle) d3.select(button).classed('active', true) - button.appendChild(this.createIcon(config.icon || Icons.question)); - button.setAttribute('data-gravity', config.gravity || 'n'); + button.appendChild(this.createIcon(config.icon || Icons.question)) + button.setAttribute('data-gravity', config.gravity || 'n') - return button; -}; + return button +} /** * Add an icon to a button @@ -162,57 +158,55 @@ proto.createButton = function(config) { * @Param {string} thisIcon.path * @Return {HTMLelement} */ -proto.createIcon = function(thisIcon) { - var iconHeight = thisIcon.ascent - thisIcon.descent, - svgNS = 'http://www.w3.org/2000/svg', - icon = document.createElementNS(svgNS, 'svg'), - path = document.createElementNS(svgNS, 'path'); +proto.createIcon = function (thisIcon) { + var iconHeight = thisIcon.ascent - thisIcon.descent, + svgNS = 'http://www.w3.org/2000/svg', + icon = document.createElementNS(svgNS, 'svg'), + path = document.createElementNS(svgNS, 'path') - icon.setAttribute('height', '1em'); - icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em'); - icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' ')); + icon.setAttribute('height', '1em') + icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em') + icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' ')) - path.setAttribute('d', thisIcon.path); - path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')'); - icon.appendChild(path); + path.setAttribute('d', thisIcon.path) + path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')') + icon.appendChild(path) - return icon; -}; + return icon +} /** * Updates active button with attribute specified in layout * @Param {object} graphInfo plot object containing data and layout * @Return {HTMLelement} */ -proto.updateActiveButton = function(buttonClicked) { - var fullLayout = this.graphInfo._fullLayout, - dataAttrClicked = (buttonClicked !== undefined) ? +proto.updateActiveButton = function (buttonClicked) { + var fullLayout = this.graphInfo._fullLayout, + dataAttrClicked = (buttonClicked !== undefined) ? buttonClicked.getAttribute('data-attr') : - null; + null - this.buttonElements.forEach(function(button) { - var thisval = button.getAttribute('data-val') || true, - dataAttr = button.getAttribute('data-attr'), - isToggleButton = (button.getAttribute('data-toggle') === 'true'), - button3 = d3.select(button); + this.buttonElements.forEach(function (button) { + var thisval = button.getAttribute('data-val') || true, + dataAttr = button.getAttribute('data-attr'), + isToggleButton = (button.getAttribute('data-toggle') === 'true'), + button3 = d3.select(button) // Use 'data-toggle' and 'buttonClicked' to toggle buttons // that have no one-to-one equivalent in fullLayout - if(isToggleButton) { - if(dataAttr === dataAttrClicked) { - button3.classed('active', !button3.classed('active')); - } - } - else { - var val = (dataAttr === null) ? + if (isToggleButton) { + if (dataAttr === dataAttrClicked) { + button3.classed('active', !button3.classed('active')) + } + } else { + var val = (dataAttr === null) ? dataAttr : - Lib.nestedProperty(fullLayout, dataAttr).get(); + Lib.nestedProperty(fullLayout, dataAttr).get() - button3.classed('active', val === thisval); - } - - }); -}; + button3.classed('active', val === thisval) + } + }) +} /** * Check if modeBar is configured as button configuration argument @@ -220,69 +214,69 @@ proto.updateActiveButton = function(buttonClicked) { * @Param {object} buttons 2d array of grouped button config objects * @Return {boolean} */ -proto.hasButtons = function(buttons) { - var currentButtons = this.buttons; +proto.hasButtons = function (buttons) { + var currentButtons = this.buttons - if(!currentButtons) return false; + if (!currentButtons) return false - if(buttons.length !== currentButtons.length) return false; + if (buttons.length !== currentButtons.length) return false - for(var i = 0; i < buttons.length; ++i) { - if(buttons[i].length !== currentButtons[i].length) return false; - for(var j = 0; j < buttons[i].length; j++) { - if(buttons[i][j].name !== currentButtons[i][j].name) return false; - } + for (var i = 0; i < buttons.length; ++i) { + if (buttons[i].length !== currentButtons[i].length) return false + for (var j = 0; j < buttons[i].length; j++) { + if (buttons[i][j].name !== currentButtons[i][j].name) return false } + } - return true; -}; + return true +} /** * @return {HTMLDivElement} The logo image wrapped in a group */ -proto.getLogo = function() { - var group = this.createGroup(), - a = document.createElement('a'); +proto.getLogo = function () { + var group = this.createGroup(), + a = document.createElement('a') - a.href = 'https://plot.ly/'; - a.target = '_blank'; - a.setAttribute('data-title', 'Produced with Plotly'); - a.className = 'modebar-btn plotlyjsicon modebar-btn--logo'; + a.href = 'https://plot.ly/' + a.target = '_blank' + a.setAttribute('data-title', 'Produced with Plotly') + a.className = 'modebar-btn plotlyjsicon modebar-btn--logo' - a.appendChild(this.createIcon(Icons.plotlylogo)); + a.appendChild(this.createIcon(Icons.plotlylogo)) - group.appendChild(a); - return group; -}; + group.appendChild(a) + return group +} -proto.removeAllButtons = function() { - while(this.element.firstChild) { - this.element.removeChild(this.element.firstChild); - } +proto.removeAllButtons = function () { + while (this.element.firstChild) { + this.element.removeChild(this.element.firstChild) + } - this.hasLogo = false; -}; + this.hasLogo = false +} -proto.destroy = function() { - Lib.removeElement(this.container.querySelector('.modebar')); -}; +proto.destroy = function () { + Lib.removeElement(this.container.querySelector('.modebar')) +} -function createModeBar(gd, buttons) { - var fullLayout = gd._fullLayout; +function createModeBar (gd, buttons) { + var fullLayout = gd._fullLayout - var modeBar = new ModeBar({ - graphInfo: gd, - container: fullLayout._paperdiv.node(), - buttons: buttons - }); + var modeBar = new ModeBar({ + graphInfo: gd, + container: fullLayout._paperdiv.node(), + buttons: buttons + }) - if(fullLayout._privateplot) { - d3.select(modeBar.element).append('span') + if (fullLayout._privateplot) { + d3.select(modeBar.element).append('span') .classed('badge-private float--left', true) - .text('PRIVATE'); - } + .text('PRIVATE') + } - return modeBar; + return modeBar } -module.exports = createModeBar; +module.exports = createModeBar diff --git a/src/components/rangeselector/attributes.js b/src/components/rangeselector/attributes.js index 390afe3e200..5f477151aef 100644 --- a/src/components/rangeselector/attributes.js +++ b/src/components/rangeselector/attributes.js @@ -6,98 +6,98 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var fontAttrs = require('../../plots/font_attributes'); -var colorAttrs = require('../color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; -var buttonAttrs = require('./button_attributes'); +var fontAttrs = require('../../plots/font_attributes') +var colorAttrs = require('../color/attributes') +var extendFlat = require('../../lib/extend').extendFlat +var buttonAttrs = require('./button_attributes') buttonAttrs = extendFlat(buttonAttrs, { - _isLinkedToArray: 'button', + _isLinkedToArray: 'button', - description: [ - 'Sets the specifications for each buttons.', - 'By default, a range selector comes with no buttons.' - ].join(' ') -}); + description: [ + 'Sets the specifications for each buttons.', + 'By default, a range selector comes with no buttons.' + ].join(' ') +}) module.exports = { - visible: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not this range selector is visible.', - 'Note that range selectors are only available for x axes of', - '`type` set to or auto-typed to *date*.' - ].join(' ') - }, + visible: { + valType: 'boolean', + role: 'info', + description: [ + 'Determines whether or not this range selector is visible.', + 'Note that range selectors are only available for x axes of', + '`type` set to or auto-typed to *date*.' + ].join(' ') + }, - buttons: buttonAttrs, + buttons: buttonAttrs, - x: { - valType: 'number', - min: -2, - max: 3, - role: 'style', - description: 'Sets the x position (in normalized coordinates) of the range selector.' - }, - xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'left', - role: 'info', - description: [ - 'Sets the range selector\'s horizontal position anchor.', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the range selector.' - ].join(' ') - }, - y: { - valType: 'number', - min: -2, - max: 3, - role: 'style', - description: 'Sets the y position (in normalized coordinates) of the range selector.' - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'bottom', - role: 'info', - description: [ - 'Sets the range selector\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the range selector.' - ].join(' ') - }, + x: { + valType: 'number', + min: -2, + max: 3, + role: 'style', + description: 'Sets the x position (in normalized coordinates) of the range selector.' + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: [ + 'Sets the range selector\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the range selector.' + ].join(' ') + }, + y: { + valType: 'number', + min: -2, + max: 3, + role: 'style', + description: 'Sets the y position (in normalized coordinates) of the range selector.' + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'bottom', + role: 'info', + description: [ + 'Sets the range selector\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the range selector.' + ].join(' ') + }, - font: extendFlat({}, fontAttrs, { - description: 'Sets the font of the range selector button text.' - }), + font: extendFlat({}, fontAttrs, { + description: 'Sets the font of the range selector button text.' + }), - bgcolor: { - valType: 'color', - dflt: colorAttrs.lightLine, - role: 'style', - description: 'Sets the background color of the range selector buttons.' - }, - activecolor: { - valType: 'color', - role: 'style', - description: 'Sets the background color of the active range selector button.' - }, - bordercolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the color of the border enclosing the range selector.' - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: 'Sets the width (in px) of the border enclosing the range selector.' - } -}; + bgcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + role: 'style', + description: 'Sets the background color of the range selector buttons.' + }, + activecolor: { + valType: 'color', + role: 'style', + description: 'Sets the background color of the active range selector button.' + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the color of the border enclosing the range selector.' + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: 'Sets the width (in px) of the border enclosing the range selector.' + } +} diff --git a/src/components/rangeselector/button_attributes.js b/src/components/rangeselector/button_attributes.js index 14fd193a0d4..6f53558b0a7 100644 --- a/src/components/rangeselector/button_attributes.js +++ b/src/components/rangeselector/button_attributes.js @@ -6,51 +6,50 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - step: { - valType: 'enumerated', - role: 'info', - values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'], - dflt: 'month', - description: [ - 'The unit of measurement that the `count` value will set the range by.' - ].join(' ') - }, - stepmode: { - valType: 'enumerated', - role: 'info', - values: ['backward', 'todate'], - dflt: 'backward', - description: [ - 'Sets the range update mode.', - 'If *backward*, the range update shifts the start of range', - 'back *count* times *step* milliseconds.', - 'If *todate*, the range update shifts the start of range', - 'back to the first timestamp from *count* times', - '*step* milliseconds back.', - 'For example, with `step` set to *year* and `count` set to *1*', - 'the range update shifts the start of the range back to', - 'January 01 of the current year.', - 'Month and year *todate* are currently available only', - 'for the built-in (Gregorian) calendar.' - ].join(' ') - }, - count: { - valType: 'number', - role: 'info', - min: 0, - dflt: 1, - description: [ - 'Sets the number of steps to take to update the range.', - 'Use with `step` to specify the update interval.' - ].join(' ') - }, - label: { - valType: 'string', - role: 'info', - description: 'Sets the text label to appear on the button.' - } -}; + step: { + valType: 'enumerated', + role: 'info', + values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'], + dflt: 'month', + description: [ + 'The unit of measurement that the `count` value will set the range by.' + ].join(' ') + }, + stepmode: { + valType: 'enumerated', + role: 'info', + values: ['backward', 'todate'], + dflt: 'backward', + description: [ + 'Sets the range update mode.', + 'If *backward*, the range update shifts the start of range', + 'back *count* times *step* milliseconds.', + 'If *todate*, the range update shifts the start of range', + 'back to the first timestamp from *count* times', + '*step* milliseconds back.', + 'For example, with `step` set to *year* and `count` set to *1*', + 'the range update shifts the start of the range back to', + 'January 01 of the current year.', + 'Month and year *todate* are currently available only', + 'for the built-in (Gregorian) calendar.' + ].join(' ') + }, + count: { + valType: 'number', + role: 'info', + min: 0, + dflt: 1, + description: [ + 'Sets the number of steps to take to update the range.', + 'Use with `step` to specify the update interval.' + ].join(' ') + }, + label: { + valType: 'string', + role: 'info', + description: 'Sets the text label to appear on the button.' + } +} diff --git a/src/components/rangeselector/constants.js b/src/components/rangeselector/constants.js index 202e73a1cc9..9f138da15a9 100644 --- a/src/components/rangeselector/constants.js +++ b/src/components/rangeselector/constants.js @@ -6,22 +6,21 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { // 'y' position pad above counter axis domain - yPad: 0.02, + yPad: 0.02, // minimum button width (regardless of text size) - minButtonWidth: 30, + minButtonWidth: 30, // buttons rect radii - rx: 3, - ry: 3, + rx: 3, + ry: 3, // light fraction used to compute the 'activecolor' default - lightAmount: 25, - darkAmount: 10 -}; + lightAmount: 25, + darkAmount: 10 +} diff --git a/src/components/rangeselector/defaults.js b/src/components/rangeselector/defaults.js index a2523d69621..37109239367 100644 --- a/src/components/rangeselector/defaults.js +++ b/src/components/rangeselector/defaults.js @@ -6,92 +6,90 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var Color = require('../color'); +var Lib = require('../../lib') +var Color = require('../color') -var attributes = require('./attributes'); -var buttonAttrs = require('./button_attributes'); -var constants = require('./constants'); +var attributes = require('./attributes') +var buttonAttrs = require('./button_attributes') +var constants = require('./constants') +module.exports = function handleDefaults (containerIn, containerOut, layout, counterAxes, calendar) { + var selectorIn = containerIn.rangeselector || {}, + selectorOut = containerOut.rangeselector = {} -module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) { - var selectorIn = containerIn.rangeselector || {}, - selectorOut = containerOut.rangeselector = {}; + function coerce (attr, dflt) { + return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt); - } - - var buttons = buttonsDefaults(selectorIn, selectorOut, calendar); + var buttons = buttonsDefaults(selectorIn, selectorOut, calendar) - var visible = coerce('visible', buttons.length > 0); - if(!visible) return; + var visible = coerce('visible', buttons.length > 0) + if (!visible) return - var posDflt = getPosDflt(containerOut, layout, counterAxes); - coerce('x', posDflt[0]); - coerce('y', posDflt[1]); - Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); + var posDflt = getPosDflt(containerOut, layout, counterAxes) + coerce('x', posDflt[0]) + coerce('y', posDflt[1]) + Lib.noneOrAll(containerIn, containerOut, ['x', 'y']) - coerce('xanchor'); - coerce('yanchor'); + coerce('xanchor') + coerce('yanchor') - Lib.coerceFont(coerce, 'font', layout.font); + Lib.coerceFont(coerce, 'font', layout.font) - var bgColor = coerce('bgcolor'); - coerce('activecolor', Color.contrast(bgColor, constants.lightAmount, constants.darkAmount)); - coerce('bordercolor'); - coerce('borderwidth'); -}; + var bgColor = coerce('bgcolor') + coerce('activecolor', Color.contrast(bgColor, constants.lightAmount, constants.darkAmount)) + coerce('bordercolor') + coerce('borderwidth') +} -function buttonsDefaults(containerIn, containerOut, calendar) { - var buttonsIn = containerIn.buttons || [], - buttonsOut = containerOut.buttons = []; +function buttonsDefaults (containerIn, containerOut, calendar) { + var buttonsIn = containerIn.buttons || [], + buttonsOut = containerOut.buttons = [] - var buttonIn, buttonOut; + var buttonIn, buttonOut - function coerce(attr, dflt) { - return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt) + } - for(var i = 0; i < buttonsIn.length; i++) { - buttonIn = buttonsIn[i]; - buttonOut = {}; + for (var i = 0; i < buttonsIn.length; i++) { + buttonIn = buttonsIn[i] + buttonOut = {} - if(!Lib.isPlainObject(buttonIn)) continue; + if (!Lib.isPlainObject(buttonIn)) continue - var step = coerce('step'); - if(step !== 'all') { - if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) { - buttonOut.stepmode = 'backward'; - } - else { - coerce('stepmode'); - } + var step = coerce('step') + if (step !== 'all') { + if (calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) { + buttonOut.stepmode = 'backward' + } else { + coerce('stepmode') + } - coerce('count'); - } + coerce('count') + } - coerce('label'); + coerce('label') - buttonOut._index = i; - buttonsOut.push(buttonOut); - } + buttonOut._index = i + buttonsOut.push(buttonOut) + } - return buttonsOut; + return buttonsOut } -function getPosDflt(containerOut, layout, counterAxes) { - var anchoredList = counterAxes.filter(function(ax) { - return layout[ax].anchor === containerOut._id; - }); +function getPosDflt (containerOut, layout, counterAxes) { + var anchoredList = counterAxes.filter(function (ax) { + return layout[ax].anchor === containerOut._id + }) - var posY = 0; - for(var i = 0; i < anchoredList.length; i++) { - var domain = layout[anchoredList[i]].domain; - if(domain) posY = Math.max(domain[1], posY); - } + var posY = 0 + for (var i = 0; i < anchoredList.length; i++) { + var domain = layout[anchoredList[i]].domain + if (domain) posY = Math.max(domain[1], posY) + } - return [containerOut.domain[0], posY + constants.yPad]; + return [containerOut.domain[0], posY + constants.yPad] } diff --git a/src/components/rangeselector/draw.js b/src/components/rangeselector/draw.js index 8dbc8ff4774..088afd70c11 100644 --- a/src/components/rangeselector/draw.js +++ b/src/components/rangeselector/draw.js @@ -6,268 +6,264 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') +var Color = require('../color') +var Drawing = require('../drawing') +var svgTextUtils = require('../../lib/svg_text_utils') +var axisIds = require('../../plots/cartesian/axis_ids') +var anchorUtils = require('../legend/anchor_utils') -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); -var Color = require('../color'); -var Drawing = require('../drawing'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var axisIds = require('../../plots/cartesian/axis_ids'); -var anchorUtils = require('../legend/anchor_utils'); +var constants = require('./constants') +var getUpdateObject = require('./get_update_object') -var constants = require('./constants'); -var getUpdateObject = require('./get_update_object'); +module.exports = function draw (gd) { + var fullLayout = gd._fullLayout + var selectors = fullLayout._infolayer.selectAll('.rangeselector') + .data(makeSelectorData(gd), selectorKeyFunc) -module.exports = function draw(gd) { - var fullLayout = gd._fullLayout; + selectors.enter().append('g') + .classed('rangeselector', true) - var selectors = fullLayout._infolayer.selectAll('.rangeselector') - .data(makeSelectorData(gd), selectorKeyFunc); + selectors.exit().remove() - selectors.enter().append('g') - .classed('rangeselector', true); + selectors.style({ + cursor: 'pointer', + 'pointer-events': 'all' + }) - selectors.exit().remove(); + selectors.each(function (d) { + var selector = d3.select(this), + axisLayout = d, + selectorLayout = axisLayout.rangeselector - selectors.style({ - cursor: 'pointer', - 'pointer-events': 'all' - }); + var buttons = selector.selectAll('g.button') + .data(selectorLayout.buttons) - selectors.each(function(d) { - var selector = d3.select(this), - axisLayout = d, - selectorLayout = axisLayout.rangeselector; + buttons.enter().append('g') + .classed('button', true) - var buttons = selector.selectAll('g.button') - .data(selectorLayout.buttons); + buttons.exit().remove() - buttons.enter().append('g') - .classed('button', true); + buttons.each(function (d) { + var button = d3.select(this) + var update = getUpdateObject(axisLayout, d) - buttons.exit().remove(); + d.isActive = isActive(axisLayout, d, update) - buttons.each(function(d) { - var button = d3.select(this); - var update = getUpdateObject(axisLayout, d); + button.call(drawButtonRect, selectorLayout, d) + button.call(drawButtonText, selectorLayout, d) - d.isActive = isActive(axisLayout, d, update); + button.on('click', function () { + if (gd._dragged) return - button.call(drawButtonRect, selectorLayout, d); - button.call(drawButtonText, selectorLayout, d); + Plotly.relayout(gd, update) + }) - button.on('click', function() { - if(gd._dragged) return; + button.on('mouseover', function () { + d.isHovered = true + button.call(drawButtonRect, selectorLayout, d) + }) - Plotly.relayout(gd, update); - }); - - button.on('mouseover', function() { - d.isHovered = true; - button.call(drawButtonRect, selectorLayout, d); - }); - - button.on('mouseout', function() { - d.isHovered = false; - button.call(drawButtonRect, selectorLayout, d); - }); - }); + button.on('mouseout', function () { + d.isHovered = false + button.call(drawButtonRect, selectorLayout, d) + }) + }) // N.B. this mutates selectorLayout - reposition(gd, buttons, selectorLayout, axisLayout._name); + reposition(gd, buttons, selectorLayout, axisLayout._name) - selector.attr('transform', 'translate(' + + selector.attr('transform', 'translate(' + selectorLayout.lx + ',' + selectorLayout.ly + - ')'); - }); - -}; + ')') + }) +} -function makeSelectorData(gd) { - var axes = axisIds.list(gd, 'x', true); - var data = []; +function makeSelectorData (gd) { + var axes = axisIds.list(gd, 'x', true) + var data = [] - for(var i = 0; i < axes.length; i++) { - var axis = axes[i]; + for (var i = 0; i < axes.length; i++) { + var axis = axes[i] - if(axis.rangeselector && axis.rangeselector.visible) { - data.push(axis); - } + if (axis.rangeselector && axis.rangeselector.visible) { + data.push(axis) } + } - return data; + return data } -function selectorKeyFunc(d) { - return d._id; +function selectorKeyFunc (d) { + return d._id } -function isActive(axisLayout, opts, update) { - if(opts.step === 'all') { - return axisLayout.autorange === true; - } - else { - var keys = Object.keys(update); +function isActive (axisLayout, opts, update) { + if (opts.step === 'all') { + return axisLayout.autorange === true + } else { + var keys = Object.keys(update) - return ( + return ( axisLayout.range[0] === update[keys[0]] && axisLayout.range[1] === update[keys[1]] - ); - } + ) + } } -function drawButtonRect(button, selectorLayout, d) { - var rect = button.selectAll('rect') - .data([0]); +function drawButtonRect (button, selectorLayout, d) { + var rect = button.selectAll('rect') + .data([0]) - rect.enter().append('rect') - .classed('selector-rect', true); + rect.enter().append('rect') + .classed('selector-rect', true) - rect.attr('shape-rendering', 'crispEdges'); + rect.attr('shape-rendering', 'crispEdges') - rect.attr({ - 'rx': constants.rx, - 'ry': constants.ry - }); + rect.attr({ + 'rx': constants.rx, + 'ry': constants.ry + }) - rect.call(Color.stroke, selectorLayout.bordercolor) + rect.call(Color.stroke, selectorLayout.bordercolor) .call(Color.fill, getFillColor(selectorLayout, d)) - .style('stroke-width', selectorLayout.borderwidth + 'px'); + .style('stroke-width', selectorLayout.borderwidth + 'px') } -function getFillColor(selectorLayout, d) { - return (d.isActive || d.isHovered) ? +function getFillColor (selectorLayout, d) { + return (d.isActive || d.isHovered) ? selectorLayout.activecolor : - selectorLayout.bgcolor; + selectorLayout.bgcolor } -function drawButtonText(button, selectorLayout, d) { - function textLayout(s) { - svgTextUtils.convertToTspans(s); +function drawButtonText (button, selectorLayout, d) { + function textLayout (s) { + svgTextUtils.convertToTspans(s) // TODO do we need anything else here? - } + } - var text = button.selectAll('text') - .data([0]); + var text = button.selectAll('text') + .data([0]) - text.enter().append('text') + text.enter().append('text') .classed('selector-text', true) - .classed('user-select-none', true); + .classed('user-select-none', true) - text.attr('text-anchor', 'middle'); + text.attr('text-anchor', 'middle') - text.call(Drawing.font, selectorLayout.font) + text.call(Drawing.font, selectorLayout.font) .text(getLabel(d)) - .call(textLayout); + .call(textLayout) } -function getLabel(opts) { - if(opts.label) return opts.label; +function getLabel (opts) { + if (opts.label) return opts.label - if(opts.step === 'all') return 'all'; + if (opts.step === 'all') return 'all' - return opts.count + opts.step.charAt(0); + return opts.count + opts.step.charAt(0) } -function reposition(gd, buttons, opts, axName) { - opts.width = 0; - opts.height = 0; +function reposition (gd, buttons, opts, axName) { + opts.width = 0 + opts.height = 0 - var borderWidth = opts.borderwidth; + var borderWidth = opts.borderwidth - buttons.each(function() { - var button = d3.select(this), - text = button.select('.selector-text'), - tspans = text.selectAll('tspan'); + buttons.each(function () { + var button = d3.select(this), + text = button.select('.selector-text'), + tspans = text.selectAll('tspan') - var tHeight = opts.font.size * 1.3, - tLines = tspans[0].length || 1, - hEff = Math.max(tHeight * tLines, 16) + 3; + var tHeight = opts.font.size * 1.3, + tLines = tspans[0].length || 1, + hEff = Math.max(tHeight * tLines, 16) + 3 - opts.height = Math.max(opts.height, hEff); - }); + opts.height = Math.max(opts.height, hEff) + }) - buttons.each(function() { - var button = d3.select(this), - rect = button.select('.selector-rect'), - text = button.select('.selector-text'), - tspans = text.selectAll('tspan'); + buttons.each(function () { + var button = d3.select(this), + rect = button.select('.selector-rect'), + text = button.select('.selector-text'), + tspans = text.selectAll('tspan') - var tWidth = text.node() && Drawing.bBox(text.node()).width, - tHeight = opts.font.size * 1.3, - tLines = tspans[0].length || 1; + var tWidth = text.node() && Drawing.bBox(text.node()).width, + tHeight = opts.font.size * 1.3, + tLines = tspans[0].length || 1 - var wEff = Math.max(tWidth + 10, constants.minButtonWidth); + var wEff = Math.max(tWidth + 10, constants.minButtonWidth) // TODO add MathJax support // TODO add buttongap attribute - button.attr('transform', 'translate(' + + button.attr('transform', 'translate(' + (borderWidth + opts.width) + ',' + borderWidth + - ')'); - - rect.attr({ - x: 0, - y: 0, - width: wEff, - height: opts.height - }); - - var textAttrs = { - x: wEff / 2, - y: opts.height / 2 - ((tLines - 1) * tHeight / 2) + 3 - }; + ')') - text.attr(textAttrs); - tspans.attr(textAttrs); - - opts.width += wEff + 5; - }); - - buttons.selectAll('rect').attr('height', opts.height); - - var graphSize = gd._fullLayout._size; - opts.lx = graphSize.l + graphSize.w * opts.x; - opts.ly = graphSize.t + graphSize.h * (1 - opts.y); - - var xanchor = 'left'; - if(anchorUtils.isRightAnchor(opts)) { - opts.lx -= opts.width; - xanchor = 'right'; - } - if(anchorUtils.isCenterAnchor(opts)) { - opts.lx -= opts.width / 2; - xanchor = 'center'; - } - - var yanchor = 'top'; - if(anchorUtils.isBottomAnchor(opts)) { - opts.ly -= opts.height; - yanchor = 'bottom'; - } - if(anchorUtils.isMiddleAnchor(opts)) { - opts.ly -= opts.height / 2; - yanchor = 'middle'; + rect.attr({ + x: 0, + y: 0, + width: wEff, + height: opts.height + }) + + var textAttrs = { + x: wEff / 2, + y: opts.height / 2 - ((tLines - 1) * tHeight / 2) + 3 } - opts.width = Math.ceil(opts.width); - opts.height = Math.ceil(opts.height); - opts.lx = Math.round(opts.lx); - opts.ly = Math.round(opts.ly); - - Plots.autoMargin(gd, axName + '-range-selector', { - x: opts.x, - y: opts.y, - l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), - r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), - b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0), - t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) - }); + text.attr(textAttrs) + tspans.attr(textAttrs) + + opts.width += wEff + 5 + }) + + buttons.selectAll('rect').attr('height', opts.height) + + var graphSize = gd._fullLayout._size + opts.lx = graphSize.l + graphSize.w * opts.x + opts.ly = graphSize.t + graphSize.h * (1 - opts.y) + + var xanchor = 'left' + if (anchorUtils.isRightAnchor(opts)) { + opts.lx -= opts.width + xanchor = 'right' + } + if (anchorUtils.isCenterAnchor(opts)) { + opts.lx -= opts.width / 2 + xanchor = 'center' + } + + var yanchor = 'top' + if (anchorUtils.isBottomAnchor(opts)) { + opts.ly -= opts.height + yanchor = 'bottom' + } + if (anchorUtils.isMiddleAnchor(opts)) { + opts.ly -= opts.height / 2 + yanchor = 'middle' + } + + opts.width = Math.ceil(opts.width) + opts.height = Math.ceil(opts.height) + opts.lx = Math.round(opts.lx) + opts.ly = Math.round(opts.ly) + + Plots.autoMargin(gd, axName + '-range-selector', { + x: opts.x, + y: opts.y, + l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0), + r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0), + b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0), + t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) + }) } diff --git a/src/components/rangeselector/get_update_object.js b/src/components/rangeselector/get_update_object.js index e8fa28971c7..6ea5b7ee21d 100644 --- a/src/components/rangeselector/get_update_object.js +++ b/src/components/rangeselector/get_update_object.js @@ -6,50 +6,48 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +module.exports = function getUpdateObject (axisLayout, buttonLayout) { + var axName = axisLayout._name + var update = {} -module.exports = function getUpdateObject(axisLayout, buttonLayout) { - var axName = axisLayout._name; - var update = {}; + if (buttonLayout.step === 'all') { + update[axName + '.autorange'] = true + } else { + var xrange = getXRange(axisLayout, buttonLayout) - if(buttonLayout.step === 'all') { - update[axName + '.autorange'] = true; - } - else { - var xrange = getXRange(axisLayout, buttonLayout); + update[axName + '.range[0]'] = xrange[0] + update[axName + '.range[1]'] = xrange[1] + } - update[axName + '.range[0]'] = xrange[0]; - update[axName + '.range[1]'] = xrange[1]; - } - - return update; -}; + return update +} -function getXRange(axisLayout, buttonLayout) { - var currentRange = axisLayout.range; - var base = new Date(axisLayout.r2l(currentRange[1])); +function getXRange (axisLayout, buttonLayout) { + var currentRange = axisLayout.range + var base = new Date(axisLayout.r2l(currentRange[1])) - var step = buttonLayout.step, - count = buttonLayout.count; + var step = buttonLayout.step, + count = buttonLayout.count - var range0; + var range0 - switch(buttonLayout.stepmode) { - case 'backward': - range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count)); - break; + switch (buttonLayout.stepmode) { + case 'backward': + range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count)) + break - case 'todate': - var base2 = d3.time[step].utc.offset(base, -count); + case 'todate': + var base2 = d3.time[step].utc.offset(base, -count) - range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2)); - break; - } + range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2)) + break + } - var range1 = currentRange[1]; + var range1 = currentRange[1] - return [range0, range1]; + return [range0, range1] } diff --git a/src/components/rangeselector/index.js b/src/components/rangeselector/index.js index a4e3e7cfd58..8eb37c41ab6 100644 --- a/src/components/rangeselector/index.js +++ b/src/components/rangeselector/index.js @@ -6,20 +6,20 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - moduleType: 'component', - name: 'rangeselector', + moduleType: 'component', + name: 'rangeselector', - schema: { - layout: { - 'xaxis.rangeselector': require('./attributes') - } - }, + schema: { + layout: { + 'xaxis.rangeselector': require('./attributes') + } + }, - layoutAttributes: require('./attributes'), - handleDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + handleDefaults: require('./defaults'), - draw: require('./draw') -}; + draw: require('./draw') +} diff --git a/src/components/rangeslider/attributes.js b/src/components/rangeslider/attributes.js index d81dfbfaecf..8a2fb63f974 100644 --- a/src/components/rangeslider/attributes.js +++ b/src/components/rangeslider/attributes.js @@ -6,68 +6,68 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var colorAttributes = require('../color/attributes'); +var colorAttributes = require('../color/attributes') module.exports = { - bgcolor: { - valType: 'color', - dflt: colorAttributes.background, - role: 'style', - description: 'Sets the background color of the range slider.' - }, - bordercolor: { - valType: 'color', - dflt: colorAttributes.defaultLine, - role: 'style', - description: 'Sets the border color of the range slider.' - }, - borderwidth: { - valType: 'integer', - dflt: 0, - min: 0, - role: 'style', - description: 'Sets the border color of the range slider.' - }, - range: { - valType: 'info_array', - role: 'info', - items: [ + bgcolor: { + valType: 'color', + dflt: colorAttributes.background, + role: 'style', + description: 'Sets the background color of the range slider.' + }, + bordercolor: { + valType: 'color', + dflt: colorAttributes.defaultLine, + role: 'style', + description: 'Sets the border color of the range slider.' + }, + borderwidth: { + valType: 'integer', + dflt: 0, + min: 0, + role: 'style', + description: 'Sets the border color of the range slider.' + }, + range: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'any'}, {valType: 'any'} - ], - description: [ - 'Sets the range of the range slider.', - 'If not set, defaults to the full xaxis range.', - 'If the axis `type` is *log*, then you must take the', - 'log of your desired range.', - 'If the axis `type` is *date*, it should be date strings,', - 'like date data, though Date objects and unix milliseconds', - 'will be accepted and converted to strings.', - 'If the axis `type` is *category*, it should be numbers,', - 'using the scale where each category is assigned a serial', - 'number from zero in the order it appears.' - ].join(' ') - }, - thickness: { - valType: 'number', - dflt: 0.15, - min: 0, - max: 1, - role: 'style', - description: [ - 'The height of the range slider as a fraction of the', - 'total plot area height.' - ].join(' ') - }, - visible: { - valType: 'boolean', - dflt: true, - role: 'info', - description: [ - 'Determines whether or not the range slider will be visible.', - 'If visible, perpendicular axes will be set to `fixedrange`' - ].join(' ') - } -}; + ], + description: [ + 'Sets the range of the range slider.', + 'If not set, defaults to the full xaxis range.', + 'If the axis `type` is *log*, then you must take the', + 'log of your desired range.', + 'If the axis `type` is *date*, it should be date strings,', + 'like date data, though Date objects and unix milliseconds', + 'will be accepted and converted to strings.', + 'If the axis `type` is *category*, it should be numbers,', + 'using the scale where each category is assigned a serial', + 'number from zero in the order it appears.' + ].join(' ') + }, + thickness: { + valType: 'number', + dflt: 0.15, + min: 0, + max: 1, + role: 'style', + description: [ + 'The height of the range slider as a fraction of the', + 'total plot area height.' + ].join(' ') + }, + visible: { + valType: 'boolean', + dflt: true, + role: 'info', + description: [ + 'Determines whether or not the range slider will be visible.', + 'If visible, perpendicular axes will be set to `fixedrange`' + ].join(' ') + } +} diff --git a/src/components/rangeslider/constants.js b/src/components/rangeslider/constants.js index 9adc42e6407..4139200b7e1 100644 --- a/src/components/rangeslider/constants.js +++ b/src/components/rangeslider/constants.js @@ -6,46 +6,46 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { // attribute container name - name: 'rangeslider', + name: 'rangeslider', // class names - containerClassName: 'rangeslider-container', - bgClassName: 'rangeslider-bg', - rangePlotClassName: 'rangeslider-rangeplot', + containerClassName: 'rangeslider-container', + bgClassName: 'rangeslider-bg', + rangePlotClassName: 'rangeslider-rangeplot', - maskMinClassName: 'rangeslider-mask-min', - maskMaxClassName: 'rangeslider-mask-max', - slideBoxClassName: 'rangeslider-slidebox', + maskMinClassName: 'rangeslider-mask-min', + maskMaxClassName: 'rangeslider-mask-max', + slideBoxClassName: 'rangeslider-slidebox', - grabberMinClassName: 'rangeslider-grabber-min', - grabAreaMinClassName: 'rangeslider-grabarea-min', - handleMinClassName: 'rangeslider-handle-min', + grabberMinClassName: 'rangeslider-grabber-min', + grabAreaMinClassName: 'rangeslider-grabarea-min', + handleMinClassName: 'rangeslider-handle-min', - grabberMaxClassName: 'rangeslider-grabber-max', - grabAreaMaxClassName: 'rangeslider-grabarea-max', - handleMaxClassName: 'rangeslider-handle-max', + grabberMaxClassName: 'rangeslider-grabber-max', + grabAreaMaxClassName: 'rangeslider-grabarea-max', + handleMaxClassName: 'rangeslider-handle-max', // style constants - maskColor: 'rgba(0,0,0,0.4)', + maskColor: 'rgba(0,0,0,0.4)', - slideBoxFill: 'transparent', - slideBoxCursor: 'ew-resize', + slideBoxFill: 'transparent', + slideBoxCursor: 'ew-resize', - grabAreaFill: 'transparent', - grabAreaCursor: 'col-resize', - grabAreaWidth: 10, - grabAreaMinOffset: -6, - grabAreaMaxOffset: -2, + grabAreaFill: 'transparent', + grabAreaCursor: 'col-resize', + grabAreaWidth: 10, + grabAreaMinOffset: -6, + grabAreaMaxOffset: -2, - handleWidth: 2, - handleRadius: 1, - handleFill: '#fff', - handleStroke: '#666', -}; + handleWidth: 2, + handleRadius: 1, + handleFill: '#fff', + handleStroke: '#666' +} diff --git a/src/components/rangeslider/defaults.js b/src/components/rangeslider/defaults.js index 379a4596b84..6d27e4d5235 100644 --- a/src/components/rangeslider/defaults.js +++ b/src/components/rangeslider/defaults.js @@ -6,47 +6,46 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var attributes = require('./attributes'); +var Lib = require('../../lib') +var attributes = require('./attributes') - -module.exports = function handleDefaults(layoutIn, layoutOut, axName) { - if(!layoutIn[axName].rangeslider) return; +module.exports = function handleDefaults (layoutIn, layoutOut, axName) { + if (!layoutIn[axName].rangeslider) return // not super proud of this (maybe store _ in axis object instead - if(!Lib.isPlainObject(layoutIn[axName].rangeslider)) { - layoutIn[axName].rangeslider = {}; - } + if (!Lib.isPlainObject(layoutIn[axName].rangeslider)) { + layoutIn[axName].rangeslider = {} + } - var containerIn = layoutIn[axName].rangeslider, - axOut = layoutOut[axName], - containerOut = axOut.rangeslider = {}; + var containerIn = layoutIn[axName].rangeslider, + axOut = layoutOut[axName], + containerOut = axOut.rangeslider = {} - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt) + } - coerce('bgcolor', layoutOut.plot_bgcolor); - coerce('bordercolor'); - coerce('borderwidth'); - coerce('thickness'); - coerce('visible'); - coerce('range'); + coerce('bgcolor', layoutOut.plot_bgcolor) + coerce('bordercolor') + coerce('borderwidth') + coerce('thickness') + coerce('visible') + coerce('range') // Expand slider range to the axis range - if(containerOut.range && !axOut.autorange) { + if (containerOut.range && !axOut.autorange) { // TODO: what if the ranges are reversed? - var outRange = containerOut.range, - axRange = axOut.range; + var outRange = containerOut.range, + axRange = axOut.range - outRange[0] = axOut.l2r(Math.min(axOut.r2l(outRange[0]), axOut.r2l(axRange[0]))); - outRange[1] = axOut.l2r(Math.max(axOut.r2l(outRange[1]), axOut.r2l(axRange[1]))); - } else { - axOut._needsExpand = true; - } + outRange[0] = axOut.l2r(Math.min(axOut.r2l(outRange[0]), axOut.r2l(axRange[0]))) + outRange[1] = axOut.l2r(Math.max(axOut.r2l(outRange[1]), axOut.r2l(axRange[1]))) + } else { + axOut._needsExpand = true + } // to map back range slider (auto) range - containerOut._input = containerIn; -}; + containerOut._input = containerIn +} diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js index ded3198d316..740ec8b4fcc 100644 --- a/src/components/rangeslider/draw.js +++ b/src/components/rangeslider/draw.js @@ -6,29 +6,28 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); +var d3 = require('d3') -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') -var Lib = require('../../lib'); -var Drawing = require('../drawing'); -var Color = require('../color'); +var Lib = require('../../lib') +var Drawing = require('../drawing') +var Color = require('../color') -var Cartesian = require('../../plots/cartesian'); -var Axes = require('../../plots/cartesian/axes'); +var Cartesian = require('../../plots/cartesian') +var Axes = require('../../plots/cartesian/axes') -var dragElement = require('../dragelement'); -var setCursor = require('../../lib/setcursor'); +var dragElement = require('../dragelement') +var setCursor = require('../../lib/setcursor') -var constants = require('./constants'); +var constants = require('./constants') - -module.exports = function(gd) { - var fullLayout = gd._fullLayout, - rangeSliderData = makeRangeSliderData(fullLayout); +module.exports = function (gd) { + var fullLayout = gd._fullLayout, + rangeSliderData = makeRangeSliderData(fullLayout) /* * @@ -47,483 +46,480 @@ module.exports = function(gd) { * ... */ - function keyFunction(axisOpts) { - return axisOpts._name; - } + function keyFunction (axisOpts) { + return axisOpts._name + } - var rangeSliders = fullLayout._infolayer + var rangeSliders = fullLayout._infolayer .selectAll('g.' + constants.containerClassName) - .data(rangeSliderData, keyFunction); + .data(rangeSliderData, keyFunction) - rangeSliders.enter().append('g') + rangeSliders.enter().append('g') .classed(constants.containerClassName, true) - .attr('pointer-events', 'all'); + .attr('pointer-events', 'all') // remove exiting sliders and their corresponding clip paths - rangeSliders.exit().each(function(axisOpts) { - var rangeSlider = d3.select(this), - opts = axisOpts[constants.name]; + rangeSliders.exit().each(function (axisOpts) { + var rangeSlider = d3.select(this), + opts = axisOpts[constants.name] - rangeSlider.remove(); - fullLayout._topdefs.select('#' + opts._clipId).remove(); - }); + rangeSlider.remove() + fullLayout._topdefs.select('#' + opts._clipId).remove() + }) // remove push margin object(s) - if(rangeSliders.exit().size()) clearPushMargins(gd); + if (rangeSliders.exit().size()) clearPushMargins(gd) // return early if no range slider is visible - if(rangeSliderData.length === 0) return; + if (rangeSliderData.length === 0) return // for all present range sliders - rangeSliders.each(function(axisOpts) { - var rangeSlider = d3.select(this), - opts = axisOpts[constants.name]; + rangeSliders.each(function (axisOpts) { + var rangeSlider = d3.select(this), + opts = axisOpts[constants.name] // compute new slider range using axis autorange if necessary // copy back range to input range slider container to skip // this step in subsequent draw calls - if(!opts.range) { - opts._input.range = opts.range = Axes.getAutoRange(axisOpts); - } + if (!opts.range) { + opts._input.range = opts.range = Axes.getAutoRange(axisOpts) + } // update range slider dimensions - var margin = fullLayout.margin, - graphSize = fullLayout._size, - domain = axisOpts.domain; + var margin = fullLayout.margin, + graphSize = fullLayout._size, + domain = axisOpts.domain - opts._id = constants.name + axisOpts._id; - opts._clipId = opts._id + '-' + fullLayout._uid; + opts._id = constants.name + axisOpts._id + opts._clipId = opts._id + '-' + fullLayout._uid - opts._width = graphSize.w * (domain[1] - domain[0]); - opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness; - opts._offsetShift = Math.floor(opts.borderwidth / 2); + opts._width = graphSize.w * (domain[1] - domain[0]) + opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness + opts._offsetShift = Math.floor(opts.borderwidth / 2) - var x = margin.l + (graphSize.w * domain[0]), - y = fullLayout.height - opts._height - margin.b; + var x = margin.l + (graphSize.w * domain[0]), + y = fullLayout.height - opts._height - margin.b - rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')'); + rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')') // update data <--> pixel coordinate conversion methods - var range0 = axisOpts.r2l(opts.range[0]), - range1 = axisOpts.r2l(opts.range[1]), - dist = range1 - range0; + var range0 = axisOpts.r2l(opts.range[0]), + range1 = axisOpts.r2l(opts.range[1]), + dist = range1 - range0 - opts.p2d = function(v) { - return (v / opts._width) * dist + range0; - }; + opts.p2d = function (v) { + return (v / opts._width) * dist + range0 + } - opts.d2p = function(v) { - return (v - range0) / dist * opts._width; - }; + opts.d2p = function (v) { + return (v - range0) / dist * opts._width + } - opts._rl = [range0, range1]; + opts._rl = [range0, range1] // update inner nodes - rangeSlider + rangeSlider .call(drawBg, gd, axisOpts, opts) .call(addClipPath, gd, axisOpts, opts) .call(drawRangePlot, gd, axisOpts, opts) .call(drawMasks, gd, axisOpts, opts) .call(drawSlideBox, gd, axisOpts, opts) - .call(drawGrabbers, gd, axisOpts, opts); + .call(drawGrabbers, gd, axisOpts, opts) // setup drag element - setupDragElement(rangeSlider, gd, axisOpts, opts); + setupDragElement(rangeSlider, gd, axisOpts, opts) // update current range - setPixelRange(rangeSlider, gd, axisOpts, opts); + setPixelRange(rangeSlider, gd, axisOpts, opts) // update margins - var bb = axisOpts._boundingBox ? axisOpts._boundingBox.height : 0; - - Plots.autoMargin(gd, opts._id, { - x: 0, y: 0, l: 0, r: 0, t: 0, - b: opts._height + fullLayout.margin.b + bb, - pad: 15 + opts._offsetShift * 2 - }); - }); -}; + var bb = axisOpts._boundingBox ? axisOpts._boundingBox.height : 0 -function makeRangeSliderData(fullLayout) { - if(!fullLayout.xaxis) return []; - if(!fullLayout.xaxis[constants.name]) return []; - if(!fullLayout.xaxis[constants.name].visible) return []; - if(fullLayout._has('gl2d')) return []; - - return [fullLayout.xaxis]; + Plots.autoMargin(gd, opts._id, { + x: 0, y: 0, l: 0, r: 0, t: 0, + b: opts._height + fullLayout.margin.b + bb, + pad: 15 + opts._offsetShift * 2 + }) + }) } -function setupDragElement(rangeSlider, gd, axisOpts, opts) { - var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(), - grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(), - grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node(); - - rangeSlider.on('mousedown', function() { - var event = d3.event, - target = event.target, - startX = event.clientX, - offsetX = startX - rangeSlider.node().getBoundingClientRect().left, - minVal = opts.d2p(axisOpts._rl[0]), - maxVal = opts.d2p(axisOpts._rl[1]); - - var dragCover = dragElement.coverSlip(); - - dragCover.addEventListener('mousemove', mouseMove); - dragCover.addEventListener('mouseup', mouseUp); - - function mouseMove(e) { - var delta = +e.clientX - startX; - var pixelMin, pixelMax, cursor; - - switch(target) { - case slideBox: - cursor = 'ew-resize'; - pixelMin = minVal + delta; - pixelMax = maxVal + delta; - break; - - case grabAreaMin: - cursor = 'col-resize'; - pixelMin = minVal + delta; - pixelMax = maxVal; - break; - - case grabAreaMax: - cursor = 'col-resize'; - pixelMin = minVal; - pixelMax = maxVal + delta; - break; - - default: - cursor = 'ew-resize'; - pixelMin = offsetX; - pixelMax = offsetX + delta; - break; - } - - if(pixelMax < pixelMin) { - var tmp = pixelMax; - pixelMax = pixelMin; - pixelMin = tmp; - } - - opts._pixelMin = pixelMin; - opts._pixelMax = pixelMax; - - setCursor(d3.select(dragCover), cursor); - setDataRange(rangeSlider, gd, axisOpts, opts); - } - - function mouseUp() { - dragCover.removeEventListener('mousemove', mouseMove); - dragCover.removeEventListener('mouseup', mouseUp); - Lib.removeElement(dragCover); - } - }); +function makeRangeSliderData (fullLayout) { + if (!fullLayout.xaxis) return [] + if (!fullLayout.xaxis[constants.name]) return [] + if (!fullLayout.xaxis[constants.name].visible) return [] + if (fullLayout._has('gl2d')) return [] + + return [fullLayout.xaxis] } -function setDataRange(rangeSlider, gd, axisOpts, opts) { +function setupDragElement (rangeSlider, gd, axisOpts, opts) { + var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(), + grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(), + grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node() + + rangeSlider.on('mousedown', function () { + var event = d3.event, + target = event.target, + startX = event.clientX, + offsetX = startX - rangeSlider.node().getBoundingClientRect().left, + minVal = opts.d2p(axisOpts._rl[0]), + maxVal = opts.d2p(axisOpts._rl[1]) + + var dragCover = dragElement.coverSlip() + + dragCover.addEventListener('mousemove', mouseMove) + dragCover.addEventListener('mouseup', mouseUp) + + function mouseMove (e) { + var delta = +e.clientX - startX + var pixelMin, pixelMax, cursor + + switch (target) { + case slideBox: + cursor = 'ew-resize' + pixelMin = minVal + delta + pixelMax = maxVal + delta + break + + case grabAreaMin: + cursor = 'col-resize' + pixelMin = minVal + delta + pixelMax = maxVal + break + + case grabAreaMax: + cursor = 'col-resize' + pixelMin = minVal + pixelMax = maxVal + delta + break + + default: + cursor = 'ew-resize' + pixelMin = offsetX + pixelMax = offsetX + delta + break + } + + if (pixelMax < pixelMin) { + var tmp = pixelMax + pixelMax = pixelMin + pixelMin = tmp + } + + opts._pixelMin = pixelMin + opts._pixelMax = pixelMax + + setCursor(d3.select(dragCover), cursor) + setDataRange(rangeSlider, gd, axisOpts, opts) + } - function clamp(v) { - return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1])); + function mouseUp () { + dragCover.removeEventListener('mousemove', mouseMove) + dragCover.removeEventListener('mouseup', mouseUp) + Lib.removeElement(dragCover) } + }) +} + +function setDataRange (rangeSlider, gd, axisOpts, opts) { + function clamp (v) { + return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1])) + } - var dataMin = clamp(opts.p2d(opts._pixelMin)), - dataMax = clamp(opts.p2d(opts._pixelMax)); + var dataMin = clamp(opts.p2d(opts._pixelMin)), + dataMax = clamp(opts.p2d(opts._pixelMax)) - window.requestAnimationFrame(function() { - Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]); - }); + window.requestAnimationFrame(function () { + Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]) + }) } -function setPixelRange(rangeSlider, gd, axisOpts, opts) { +function setPixelRange (rangeSlider, gd, axisOpts, opts) { + function clamp (v) { + return Lib.constrain(v, 0, opts._width) + } - function clamp(v) { - return Lib.constrain(v, 0, opts._width); - } + var pixelMin = clamp(opts.d2p(axisOpts._rl[0])), + pixelMax = clamp(opts.d2p(axisOpts._rl[1])) - var pixelMin = clamp(opts.d2p(axisOpts._rl[0])), - pixelMax = clamp(opts.d2p(axisOpts._rl[1])); - - rangeSlider.select('rect.' + constants.slideBoxClassName) + rangeSlider.select('rect.' + constants.slideBoxClassName) .attr('x', pixelMin) - .attr('width', pixelMax - pixelMin); + .attr('width', pixelMax - pixelMin) - rangeSlider.select('rect.' + constants.maskMinClassName) - .attr('width', pixelMin); + rangeSlider.select('rect.' + constants.maskMinClassName) + .attr('width', pixelMin) - rangeSlider.select('rect.' + constants.maskMaxClassName) + rangeSlider.select('rect.' + constants.maskMaxClassName) .attr('x', pixelMax) - .attr('width', opts._width - pixelMax); + .attr('width', opts._width - pixelMax) - rangeSlider.select('g.' + constants.grabberMinClassName) - .attr('transform', 'translate(' + (pixelMin - constants.handleWidth - 1) + ',0)'); + rangeSlider.select('g.' + constants.grabberMinClassName) + .attr('transform', 'translate(' + (pixelMin - constants.handleWidth - 1) + ',0)') - rangeSlider.select('g.' + constants.grabberMaxClassName) - .attr('transform', 'translate(' + pixelMax + ',0)'); + rangeSlider.select('g.' + constants.grabberMaxClassName) + .attr('transform', 'translate(' + pixelMax + ',0)') } -function drawBg(rangeSlider, gd, axisOpts, opts) { - var bg = rangeSlider.selectAll('rect.' + constants.bgClassName) - .data([0]); +function drawBg (rangeSlider, gd, axisOpts, opts) { + var bg = rangeSlider.selectAll('rect.' + constants.bgClassName) + .data([0]) - bg.enter().append('rect') + bg.enter().append('rect') .classed(constants.bgClassName, true) .attr({ - x: 0, - y: 0, - 'shape-rendering': 'crispEdges' - }); + x: 0, + y: 0, + 'shape-rendering': 'crispEdges' + }) - var borderCorrect = (opts.borderwidth % 2) === 0 ? + var borderCorrect = (opts.borderwidth % 2) === 0 ? opts.borderwidth : - opts.borderwidth - 1; - - var offsetShift = -opts._offsetShift; - - bg.attr({ - width: opts._width + borderCorrect, - height: opts._height + borderCorrect, - transform: 'translate(' + offsetShift + ',' + offsetShift + ')', - fill: opts.bgcolor, - stroke: opts.bordercolor, - 'stroke-width': opts.borderwidth, - }); + opts.borderwidth - 1 + + var offsetShift = -opts._offsetShift + + bg.attr({ + width: opts._width + borderCorrect, + height: opts._height + borderCorrect, + transform: 'translate(' + offsetShift + ',' + offsetShift + ')', + fill: opts.bgcolor, + stroke: opts.bordercolor, + 'stroke-width': opts.borderwidth + }) } -function addClipPath(rangeSlider, gd, axisOpts, opts) { - var fullLayout = gd._fullLayout; +function addClipPath (rangeSlider, gd, axisOpts, opts) { + var fullLayout = gd._fullLayout - var clipPath = fullLayout._topdefs.selectAll('#' + opts._clipId) - .data([0]); + var clipPath = fullLayout._topdefs.selectAll('#' + opts._clipId) + .data([0]) - clipPath.enter().append('clipPath') + clipPath.enter().append('clipPath') .attr('id', opts._clipId) .append('rect') - .attr({ x: 0, y: 0 }); + .attr({ x: 0, y: 0 }) - clipPath.select('rect').attr({ - width: opts._width, - height: opts._height - }); + clipPath.select('rect').attr({ + width: opts._width, + height: opts._height + }) } -function drawRangePlot(rangeSlider, gd, axisOpts, opts) { - var subplotData = Axes.getSubplots(gd, axisOpts), - calcData = gd.calcdata; +function drawRangePlot (rangeSlider, gd, axisOpts, opts) { + var subplotData = Axes.getSubplots(gd, axisOpts), + calcData = gd.calcdata - var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName) - .data(subplotData, Lib.identity); + var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName) + .data(subplotData, Lib.identity) - rangePlots.enter().append('g') - .attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; }) - .call(Drawing.setClipUrl, opts._clipId); + rangePlots.enter().append('g') + .attr('class', function (id) { return constants.rangePlotClassName + ' ' + id }) + .call(Drawing.setClipUrl, opts._clipId) - rangePlots.order(); + rangePlots.order() - rangePlots.exit().remove(); + rangePlots.exit().remove() - var mainplotinfo; + var mainplotinfo - rangePlots.each(function(id, i) { - var plotgroup = d3.select(this), - isMainPlot = (i === 0); + rangePlots.each(function (id, i) { + var plotgroup = d3.select(this), + isMainPlot = (i === 0) - var oppAxisOpts = Axes.getFromId(gd, id, 'y'), - oppAxisName = oppAxisOpts._name; + var oppAxisOpts = Axes.getFromId(gd, id, 'y'), + oppAxisName = oppAxisOpts._name - var mockFigure = { - data: [], - layout: { - xaxis: { - type: axisOpts.type, - domain: [0, 1], - range: opts.range.slice(), - calendar: axisOpts.calendar - }, - width: opts._width, - height: opts._height, - margin: { t: 0, b: 0, l: 0, r: 0 } - } - }; + var mockFigure = { + data: [], + layout: { + xaxis: { + type: axisOpts.type, + domain: [0, 1], + range: opts.range.slice(), + calendar: axisOpts.calendar + }, + width: opts._width, + height: opts._height, + margin: { t: 0, b: 0, l: 0, r: 0 } + } + } - mockFigure.layout[oppAxisName] = { - domain: [0, 1], - range: oppAxisOpts.range.slice(), - calendar: oppAxisOpts.calendar - }; + mockFigure.layout[oppAxisName] = { + domain: [0, 1], + range: oppAxisOpts.range.slice(), + calendar: oppAxisOpts.calendar + } - Plots.supplyDefaults(mockFigure); + Plots.supplyDefaults(mockFigure) - var xa = mockFigure._fullLayout.xaxis, - ya = mockFigure._fullLayout[oppAxisName]; + var xa = mockFigure._fullLayout.xaxis, + ya = mockFigure._fullLayout[oppAxisName] - var plotinfo = { - id: id, - plotgroup: plotgroup, - xaxis: xa, - yaxis: ya - }; + var plotinfo = { + id: id, + plotgroup: plotgroup, + xaxis: xa, + yaxis: ya + } - if(isMainPlot) mainplotinfo = plotinfo; - else { - plotinfo.mainplot = 'xy'; - plotinfo.mainplotinfo = mainplotinfo; - } + if (isMainPlot) mainplotinfo = plotinfo + else { + plotinfo.mainplot = 'xy' + plotinfo.mainplotinfo = mainplotinfo + } - Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id)); + Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id)) // no need for the bg layer, // drawBg handles coloring the background - if(isMainPlot) plotinfo.bg.remove(); - }); + if (isMainPlot) plotinfo.bg.remove() + }) } -function filterRangePlotCalcData(calcData, subplotId) { - var out = []; +function filterRangePlotCalcData (calcData, subplotId) { + var out = [] - for(var i = 0; i < calcData.length; i++) { - var calcTrace = calcData[i], - trace = calcTrace[0].trace; + for (var i = 0; i < calcData.length; i++) { + var calcTrace = calcData[i], + trace = calcTrace[0].trace - if(trace.xaxis + trace.yaxis === subplotId) { - out.push(calcTrace); - } + if (trace.xaxis + trace.yaxis === subplotId) { + out.push(calcTrace) } + } - return out; + return out } -function drawMasks(rangeSlider, gd, axisOpts, opts) { - var maskMin = rangeSlider.selectAll('rect.' + constants.maskMinClassName) - .data([0]); +function drawMasks (rangeSlider, gd, axisOpts, opts) { + var maskMin = rangeSlider.selectAll('rect.' + constants.maskMinClassName) + .data([0]) - maskMin.enter().append('rect') + maskMin.enter().append('rect') .classed(constants.maskMinClassName, true) - .attr({ x: 0, y: 0 }); + .attr({ x: 0, y: 0 }) - maskMin + maskMin .attr('height', opts._height) - .call(Color.fill, constants.maskColor); + .call(Color.fill, constants.maskColor) - var maskMax = rangeSlider.selectAll('rect.' + constants.maskMaxClassName) - .data([0]); + var maskMax = rangeSlider.selectAll('rect.' + constants.maskMaxClassName) + .data([0]) - maskMax.enter().append('rect') + maskMax.enter().append('rect') .classed(constants.maskMaxClassName, true) - .attr('y', 0); + .attr('y', 0) - maskMax + maskMax .attr('height', opts._height) - .call(Color.fill, constants.maskColor); + .call(Color.fill, constants.maskColor) } -function drawSlideBox(rangeSlider, gd, axisOpts, opts) { - if(gd._context.staticPlot) return; +function drawSlideBox (rangeSlider, gd, axisOpts, opts) { + if (gd._context.staticPlot) return - var slideBox = rangeSlider.selectAll('rect.' + constants.slideBoxClassName) - .data([0]); + var slideBox = rangeSlider.selectAll('rect.' + constants.slideBoxClassName) + .data([0]) - slideBox.enter().append('rect') + slideBox.enter().append('rect') .classed(constants.slideBoxClassName, true) .attr('y', 0) - .attr('cursor', constants.slideBoxCursor); + .attr('cursor', constants.slideBoxCursor) - slideBox.attr({ - height: opts._height, - fill: constants.slideBoxFill - }); + slideBox.attr({ + height: opts._height, + fill: constants.slideBoxFill + }) } -function drawGrabbers(rangeSlider, gd, axisOpts, opts) { - +function drawGrabbers (rangeSlider, gd, axisOpts, opts) { // - var grabberMin = rangeSlider.selectAll('g.' + constants.grabberMinClassName) - .data([0]); - grabberMin.enter().append('g') - .classed(constants.grabberMinClassName, true); + var grabberMin = rangeSlider.selectAll('g.' + constants.grabberMinClassName) + .data([0]) + grabberMin.enter().append('g') + .classed(constants.grabberMinClassName, true) - var grabberMax = rangeSlider.selectAll('g.' + constants.grabberMaxClassName) - .data([0]); - grabberMax.enter().append('g') - .classed(constants.grabberMaxClassName, true); + var grabberMax = rangeSlider.selectAll('g.' + constants.grabberMaxClassName) + .data([0]) + grabberMax.enter().append('g') + .classed(constants.grabberMaxClassName, true) // - var handleFixAttrs = { - x: 0, - width: constants.handleWidth, - rx: constants.handleRadius, - fill: constants.handleFill, - stroke: constants.handleStroke, - 'shape-rendering': 'crispEdges' - }; - - var handleDynamicAttrs = { - y: opts._height / 4, - height: opts._height / 2, - }; - - var handleMin = grabberMin.selectAll('rect.' + constants.handleMinClassName) - .data([0]); - handleMin.enter().append('rect') + var handleFixAttrs = { + x: 0, + width: constants.handleWidth, + rx: constants.handleRadius, + fill: constants.handleFill, + stroke: constants.handleStroke, + 'shape-rendering': 'crispEdges' + } + + var handleDynamicAttrs = { + y: opts._height / 4, + height: opts._height / 2 + } + + var handleMin = grabberMin.selectAll('rect.' + constants.handleMinClassName) + .data([0]) + handleMin.enter().append('rect') .classed(constants.handleMinClassName, true) - .attr(handleFixAttrs); - handleMin.attr(handleDynamicAttrs); + .attr(handleFixAttrs) + handleMin.attr(handleDynamicAttrs) - var handleMax = grabberMax.selectAll('rect.' + constants.handleMaxClassName) - .data([0]); - handleMax.enter().append('rect') + var handleMax = grabberMax.selectAll('rect.' + constants.handleMaxClassName) + .data([0]) + handleMax.enter().append('rect') .classed(constants.handleMaxClassName, true) - .attr(handleFixAttrs); - handleMax.attr(handleDynamicAttrs); + .attr(handleFixAttrs) + handleMax.attr(handleDynamicAttrs) // - if(gd._context.staticPlot) return; + if (gd._context.staticPlot) return - var grabAreaFixAttrs = { - width: constants.grabAreaWidth, - y: 0, - fill: constants.grabAreaFill, - cursor: constants.grabAreaCursor - }; + var grabAreaFixAttrs = { + width: constants.grabAreaWidth, + y: 0, + fill: constants.grabAreaFill, + cursor: constants.grabAreaCursor + } - var grabAreaMin = grabberMin.selectAll('rect.' + constants.grabAreaMinClassName) - .data([0]); - grabAreaMin.enter().append('rect') + var grabAreaMin = grabberMin.selectAll('rect.' + constants.grabAreaMinClassName) + .data([0]) + grabAreaMin.enter().append('rect') .classed(constants.grabAreaMinClassName, true) - .attr(grabAreaFixAttrs); - grabAreaMin.attr({ - x: constants.grabAreaMinOffset, - height: opts._height - }); - - var grabAreaMax = grabberMax.selectAll('rect.' + constants.grabAreaMaxClassName) - .data([0]); - grabAreaMax.enter().append('rect') + .attr(grabAreaFixAttrs) + grabAreaMin.attr({ + x: constants.grabAreaMinOffset, + height: opts._height + }) + + var grabAreaMax = grabberMax.selectAll('rect.' + constants.grabAreaMaxClassName) + .data([0]) + grabAreaMax.enter().append('rect') .classed(constants.grabAreaMaxClassName, true) - .attr(grabAreaFixAttrs); - grabAreaMax.attr({ - x: constants.grabAreaMaxOffset, - height: opts._height - }); + .attr(grabAreaFixAttrs) + grabAreaMax.attr({ + x: constants.grabAreaMaxOffset, + height: opts._height + }) } -function clearPushMargins(gd) { - var pushMargins = gd._fullLayout._pushmargin || {}, - keys = Object.keys(pushMargins); +function clearPushMargins (gd) { + var pushMargins = gd._fullLayout._pushmargin || {}, + keys = Object.keys(pushMargins) - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; + for (var i = 0; i < keys.length; i++) { + var k = keys[i] - if(k.indexOf(constants.name) !== -1) { - Plots.autoMargin(gd, k); - } + if (k.indexOf(constants.name) !== -1) { + Plots.autoMargin(gd, k) } + } } diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js index 2d29e3b16fd..3d02c7c3caa 100644 --- a/src/components/rangeslider/index.js +++ b/src/components/rangeslider/index.js @@ -6,20 +6,20 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - moduleType: 'component', - name: 'rangeslider', + moduleType: 'component', + name: 'rangeslider', - schema: { - layout: { - 'xaxis.rangeslider': require('./attributes') - } - }, + schema: { + layout: { + 'xaxis.rangeslider': require('./attributes') + } + }, - layoutAttributes: require('./attributes'), - handleDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + handleDefaults: require('./defaults'), - draw: require('./draw') -}; + draw: require('./draw') +} diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index a8974f2825e..497508b21c5 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -6,160 +6,160 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var annAttrs = require('../annotations/attributes'); -var scatterAttrs = require('../../traces/scatter/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +var annAttrs = require('../annotations/attributes') +var scatterAttrs = require('../../traces/scatter/attributes') +var extendFlat = require('../../lib/extend').extendFlat -var scatterLineAttrs = scatterAttrs.line; +var scatterLineAttrs = scatterAttrs.line module.exports = { - _isLinkedToArray: 'shape', - - visible: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not this shape is visible.' - ].join(' ') - }, - - type: { - valType: 'enumerated', - values: ['circle', 'rect', 'path', 'line'], - role: 'info', - description: [ - 'Specifies the shape type to be drawn.', - - 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)', - - 'If *circle*, a circle is drawn from', - '((`x0`+`x1`)/2, (`y0`+`y1`)/2))', - 'with radius', - '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)', - - 'If *rect*, a rectangle is drawn linking', - '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)', - - 'If *path*, draw a custom SVG path using `path`.' - ].join(' ') - }, - - layer: { - valType: 'enumerated', - values: ['below', 'above'], - dflt: 'above', - role: 'info', - description: 'Specifies whether shapes are drawn below or above traces.' - }, - - xref: extendFlat({}, annAttrs.xref, { - description: [ - 'Sets the shape\'s x coordinate axis.', - 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', - 'refers to an x coordinate', - 'If set to *paper*, the `x` position refers to the distance from', - 'the left side of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the left (right) side.', - 'If the axis `type` is *log*, then you must take the', - 'log of your desired range.', - 'If the axis `type` is *date*, then you must convert', - 'the date to unix time in milliseconds.' - ].join(' ') - }), - x0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s starting x position.', - 'See `type` for more info.' - ].join(' ') - }, - x1: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s end x position.', - 'See `type` for more info.' - ].join(' ') - }, - - yref: extendFlat({}, annAttrs.yref, { - description: [ - 'Sets the annotation\'s y coordinate axis.', - 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', - 'refers to an y coordinate', - 'If set to *paper*, the `y` position refers to the distance from', - 'the bottom of the plotting area in normalized coordinates', - 'where *0* (*1*) corresponds to the bottom (top).' - ].join(' ') - }), - y0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s starting y position.', - 'See `type` for more info.' - ].join(' ') - }, - y1: { - valType: 'any', - role: 'info', - description: [ - 'Sets the shape\'s end y position.', - 'See `type` for more info.' - ].join(' ') - }, - - path: { - valType: 'string', - role: 'info', - description: [ - 'For `type` *path* - a valid SVG path but with the pixel values', - 'replaced by data values. There are a few restrictions / quirks', - 'only absolute instructions, not relative. So the allowed segments', - 'are: M, L, H, V, Q, C, T, S, and Z', - 'arcs (A) are not allowed because radius rx and ry are relative.', - - 'In the future we could consider supporting relative commands,', - 'but we would have to decide on how to handle date and log axes.', - 'Note that even as is, Q and C Bezier paths that are smooth on', - 'linear axes may not be smooth on log, and vice versa.', - 'no chained "polybezier" commands - specify the segment type for', - 'each one.', - - 'On category axes, values are numbers scaled to the serial numbers', - 'of categories because using the categories themselves there would', - 'be no way to describe fractional positions', - 'On data axes: because space and T are both normal components of path', - 'strings, we can\'t use either to separate date from time parts.', - 'Therefore we\'ll use underscore for this purpose:', - '2015-02-21_13:45:56.789' - ].join(' ') - }, - - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - role: 'info', - description: 'Sets the opacity of the shape.' - }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - role: 'info' - }, - fillcolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - role: 'info', - description: [ - 'Sets the color filling the shape\'s interior.' - ].join(' ') - } -}; + _isLinkedToArray: 'shape', + + visible: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not this shape is visible.' + ].join(' ') + }, + + type: { + valType: 'enumerated', + values: ['circle', 'rect', 'path', 'line'], + role: 'info', + description: [ + 'Specifies the shape type to be drawn.', + + 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)', + + 'If *circle*, a circle is drawn from', + '((`x0`+`x1`)/2, (`y0`+`y1`)/2))', + 'with radius', + '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)', + + 'If *rect*, a rectangle is drawn linking', + '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)', + + 'If *path*, draw a custom SVG path using `path`.' + ].join(' ') + }, + + layer: { + valType: 'enumerated', + values: ['below', 'above'], + dflt: 'above', + role: 'info', + description: 'Specifies whether shapes are drawn below or above traces.' + }, + + xref: extendFlat({}, annAttrs.xref, { + description: [ + 'Sets the shape\'s x coordinate axis.', + 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', + 'refers to an x coordinate', + 'If set to *paper*, the `x` position refers to the distance from', + 'the left side of the plotting area in normalized coordinates', + 'where *0* (*1*) corresponds to the left (right) side.', + 'If the axis `type` is *log*, then you must take the', + 'log of your desired range.', + 'If the axis `type` is *date*, then you must convert', + 'the date to unix time in milliseconds.' + ].join(' ') + }), + x0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s starting x position.', + 'See `type` for more info.' + ].join(' ') + }, + x1: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s end x position.', + 'See `type` for more info.' + ].join(' ') + }, + + yref: extendFlat({}, annAttrs.yref, { + description: [ + 'Sets the annotation\'s y coordinate axis.', + 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', + 'refers to an y coordinate', + 'If set to *paper*, the `y` position refers to the distance from', + 'the bottom of the plotting area in normalized coordinates', + 'where *0* (*1*) corresponds to the bottom (top).' + ].join(' ') + }), + y0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s starting y position.', + 'See `type` for more info.' + ].join(' ') + }, + y1: { + valType: 'any', + role: 'info', + description: [ + 'Sets the shape\'s end y position.', + 'See `type` for more info.' + ].join(' ') + }, + + path: { + valType: 'string', + role: 'info', + description: [ + 'For `type` *path* - a valid SVG path but with the pixel values', + 'replaced by data values. There are a few restrictions / quirks', + 'only absolute instructions, not relative. So the allowed segments', + 'are: M, L, H, V, Q, C, T, S, and Z', + 'arcs (A) are not allowed because radius rx and ry are relative.', + + 'In the future we could consider supporting relative commands,', + 'but we would have to decide on how to handle date and log axes.', + 'Note that even as is, Q and C Bezier paths that are smooth on', + 'linear axes may not be smooth on log, and vice versa.', + 'no chained "polybezier" commands - specify the segment type for', + 'each one.', + + 'On category axes, values are numbers scaled to the serial numbers', + 'of categories because using the categories themselves there would', + 'be no way to describe fractional positions', + 'On data axes: because space and T are both normal components of path', + 'strings, we can\'t use either to separate date from time parts.', + 'Therefore we\'ll use underscore for this purpose:', + '2015-02-21_13:45:56.789' + ].join(' ') + }, + + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + role: 'info', + description: 'Sets the opacity of the shape.' + }, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + role: 'info' + }, + fillcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', + role: 'info', + description: [ + 'Sets the color filling the shape\'s interior.' + ].join(' ') + } +} diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index 6f88b4aad96..46243726b2a 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -6,70 +6,68 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var constants = require('./constants') +var helpers = require('./helpers') -var constants = require('./constants'); -var helpers = require('./helpers'); +module.exports = function calcAutorange (gd) { + var fullLayout = gd._fullLayout, + shapeList = Lib.filterVisible(fullLayout.shapes) + if (!shapeList.length || !gd._fullData.length) return -module.exports = function calcAutorange(gd) { - var fullLayout = gd._fullLayout, - shapeList = Lib.filterVisible(fullLayout.shapes); + for (var i = 0; i < shapeList.length; i++) { + var shape = shapeList[i], + ppad = shape.line.width / 2 - if(!shapeList.length || !gd._fullData.length) return; + var ax, bounds - for(var i = 0; i < shapeList.length; i++) { - var shape = shapeList[i], - ppad = shape.line.width / 2; - - var ax, bounds; - - if(shape.xref !== 'paper') { - ax = Axes.getFromId(gd, shape.xref); - bounds = shapeBounds(ax, shape.x0, shape.x1, shape.path, constants.paramIsX); - if(bounds) Axes.expand(ax, bounds, {ppad: ppad}); - } + if (shape.xref !== 'paper') { + ax = Axes.getFromId(gd, shape.xref) + bounds = shapeBounds(ax, shape.x0, shape.x1, shape.path, constants.paramIsX) + if (bounds) Axes.expand(ax, bounds, {ppad: ppad}) + } - if(shape.yref !== 'paper') { - ax = Axes.getFromId(gd, shape.yref); - bounds = shapeBounds(ax, shape.y0, shape.y1, shape.path, constants.paramIsY); - if(bounds) Axes.expand(ax, bounds, {ppad: ppad}); - } + if (shape.yref !== 'paper') { + ax = Axes.getFromId(gd, shape.yref) + bounds = shapeBounds(ax, shape.y0, shape.y1, shape.path, constants.paramIsY) + if (bounds) Axes.expand(ax, bounds, {ppad: ppad}) } -}; + } +} -function shapeBounds(ax, v0, v1, path, paramsToUse) { - var convertVal = (ax.type === 'category') ? Number : ax.d2c; +function shapeBounds (ax, v0, v1, path, paramsToUse) { + var convertVal = (ax.type === 'category') ? Number : ax.d2c - if(v0 !== undefined) return [convertVal(v0), convertVal(v1)]; - if(!path) return; + if (v0 !== undefined) return [convertVal(v0), convertVal(v1)] + if (!path) return - var min = Infinity, - max = -Infinity, - segments = path.match(constants.segmentRE), - i, - segment, - drawnParam, - params, - val; + var min = Infinity, + max = -Infinity, + segments = path.match(constants.segmentRE), + i, + segment, + drawnParam, + params, + val - if(ax.type === 'date') convertVal = helpers.decodeDate(convertVal); + if (ax.type === 'date') convertVal = helpers.decodeDate(convertVal) - for(i = 0; i < segments.length; i++) { - segment = segments[i]; - drawnParam = paramsToUse[segment.charAt(0)].drawn; - if(drawnParam === undefined) continue; + for (i = 0; i < segments.length; i++) { + segment = segments[i] + drawnParam = paramsToUse[segment.charAt(0)].drawn + if (drawnParam === undefined) continue - params = segments[i].substr(1).match(constants.paramRE); - if(!params || params.length < drawnParam) continue; + params = segments[i].substr(1).match(constants.paramRE) + if (!params || params.length < drawnParam) continue - val = convertVal(params[drawnParam]); - if(val < min) min = val; - if(val > max) max = val; - } - if(max >= min) return [min, max]; + val = convertVal(params[drawnParam]) + if (val < min) min = val + if (val > max) max = val + } + if (max >= min) return [min, max] } diff --git a/src/components/shapes/constants.js b/src/components/shapes/constants.js index e0c009ca84e..ac1aa59daf5 100644 --- a/src/components/shapes/constants.js +++ b/src/components/shapes/constants.js @@ -6,13 +6,11 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { - segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g, - paramRE: /[^\s,]+/g, + segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g, + paramRE: /[^\s,]+/g, // which numbers in each path segment are x (or y) values // drawn is which param is a drawn point, as opposed to a @@ -21,42 +19,42 @@ module.exports = { // autorange bounds. This is a bit tricky to get right // unless we revert to bounding boxes, but perhaps there's // a calculation we could do...) - paramIsX: { - M: {0: true, drawn: 0}, - L: {0: true, drawn: 0}, - H: {0: true, drawn: 0}, - V: {}, - Q: {0: true, 2: true, drawn: 2}, - C: {0: true, 2: true, 4: true, drawn: 4}, - T: {0: true, drawn: 0}, - S: {0: true, 2: true, drawn: 2}, + paramIsX: { + M: {0: true, drawn: 0}, + L: {0: true, drawn: 0}, + H: {0: true, drawn: 0}, + V: {}, + Q: {0: true, 2: true, drawn: 2}, + C: {0: true, 2: true, 4: true, drawn: 4}, + T: {0: true, drawn: 0}, + S: {0: true, 2: true, drawn: 2}, // A: {0: true, 5: true}, - Z: {} - }, + Z: {} + }, - paramIsY: { - M: {1: true, drawn: 1}, - L: {1: true, drawn: 1}, - H: {}, - V: {0: true, drawn: 0}, - Q: {1: true, 3: true, drawn: 3}, - C: {1: true, 3: true, 5: true, drawn: 5}, - T: {1: true, drawn: 1}, - S: {1: true, 3: true, drawn: 5}, + paramIsY: { + M: {1: true, drawn: 1}, + L: {1: true, drawn: 1}, + H: {}, + V: {0: true, drawn: 0}, + Q: {1: true, 3: true, drawn: 3}, + C: {1: true, 3: true, 5: true, drawn: 5}, + T: {1: true, drawn: 1}, + S: {1: true, 3: true, drawn: 5}, // A: {1: true, 6: true}, - Z: {} - }, + Z: {} + }, - numParams: { - M: 2, - L: 2, - H: 1, - V: 1, - Q: 4, - C: 6, - T: 2, - S: 4, + numParams: { + M: 2, + L: 2, + H: 1, + V: 1, + Q: 4, + C: 6, + T: 2, + S: 4, // A: 7, - Z: 0 - } -}; + Z: 0 + } +} diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index ceb3b4c0a1a..539f974bf19 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -6,18 +6,16 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var handleArrayContainerDefaults = require('../../plots/array_container_defaults') +var handleShapeDefaults = require('./shape_defaults') -var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); -var handleShapeDefaults = require('./shape_defaults'); +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut) { + var opts = { + name: 'shapes', + handleItemDefaults: handleShapeDefaults + } - -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var opts = { - name: 'shapes', - handleItemDefaults: handleShapeDefaults - }; - - handleArrayContainerDefaults(layoutIn, layoutOut, opts); -}; + handleArrayContainerDefaults(layoutIn, layoutOut, opts) +} diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 314cd339f76..f2443221186 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -6,25 +6,23 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Plotly = require('../../plotly') +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') +var Color = require('../color') +var Drawing = require('../drawing') -var Plotly = require('../../plotly'); -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); -var Color = require('../color'); -var Drawing = require('../drawing'); - -var dragElement = require('../dragelement'); -var setCursor = require('../../lib/setcursor'); - -var constants = require('./constants'); -var helpers = require('./helpers'); -var handleShapeDefaults = require('./shape_defaults'); -var supplyLayoutDefaults = require('./defaults'); +var dragElement = require('../dragelement') +var setCursor = require('../../lib/setcursor') +var constants = require('./constants') +var helpers = require('./helpers') +var handleShapeDefaults = require('./shape_defaults') +var supplyLayoutDefaults = require('./defaults') // Shapes are stored in gd.layout.shapes, an array of objects // index can point to one item in this array, @@ -36,530 +34,514 @@ var supplyLayoutDefaults = require('./defaults'); // annotation at that point in the array, or 'remove' to delete this one module.exports = { - draw: draw, - drawOne: drawOne -}; + draw: draw, + drawOne: drawOne +} -function draw(gd) { - var fullLayout = gd._fullLayout; +function draw (gd) { + var fullLayout = gd._fullLayout // Remove previous shapes before drawing new in shapes in fullLayout.shapes - fullLayout._shapeUpperLayer.selectAll('path').remove(); - fullLayout._shapeLowerLayer.selectAll('path').remove(); - fullLayout._shapeSubplotLayer.selectAll('path').remove(); - - for(var i = 0; i < fullLayout.shapes.length; i++) { - if(fullLayout.shapes[i].visible) { - drawOne(gd, i); - } + fullLayout._shapeUpperLayer.selectAll('path').remove() + fullLayout._shapeLowerLayer.selectAll('path').remove() + fullLayout._shapeSubplotLayer.selectAll('path').remove() + + for (var i = 0; i < fullLayout.shapes.length; i++) { + if (fullLayout.shapes[i].visible) { + drawOne(gd, i) } + } // may need to resurrect this if we put text (LaTeX) in shapes // return Plots.previousPromises(gd); } -function drawOne(gd, index, opt, value) { - if(!isNumeric(index) || index === -1) { - +function drawOne (gd, index, opt, value) { + if (!isNumeric(index) || index === -1) { // no index provided - we're operating on ALL shapes - if(!index && Array.isArray(value)) { - replaceAllShapes(gd, value); - return; - } - else if(value === 'remove') { - deleteAllShapes(gd); - return; - } - else if(opt && value !== 'add') { - updateAllShapes(gd, opt, value); - return; - } - else { + if (!index && Array.isArray(value)) { + replaceAllShapes(gd, value) + return + } else if (value === 'remove') { + deleteAllShapes(gd) + return + } else if (opt && value !== 'add') { + updateAllShapes(gd, opt, value) + return + } else { // add a new empty annotation - index = gd._fullLayout.shapes.length; - gd._fullLayout.shapes.push({}); - } + index = gd._fullLayout.shapes.length + gd._fullLayout.shapes.push({}) } - - if(!opt && value) { - if(value === 'remove') { - deleteShape(gd, index); - return; - } - else if(value === 'add' || Lib.isPlainObject(value)) { - insertShape(gd, index, value); - } + } + + if (!opt && value) { + if (value === 'remove') { + deleteShape(gd, index) + return + } else if (value === 'add' || Lib.isPlainObject(value)) { + insertShape(gd, index, value) } + } - updateShape(gd, index, opt, value); + updateShape(gd, index, opt, value) } -function replaceAllShapes(gd, newShapes) { - gd.layout.shapes = newShapes; - supplyLayoutDefaults(gd.layout, gd._fullLayout); - draw(gd); +function replaceAllShapes (gd, newShapes) { + gd.layout.shapes = newShapes + supplyLayoutDefaults(gd.layout, gd._fullLayout) + draw(gd) } -function deleteAllShapes(gd) { - delete gd.layout.shapes; - gd._fullLayout.shapes = []; - draw(gd); +function deleteAllShapes (gd) { + delete gd.layout.shapes + gd._fullLayout.shapes = [] + draw(gd) } -function updateAllShapes(gd, opt, value) { - for(var i = 0; i < gd._fullLayout.shapes.length; i++) { - drawOne(gd, i, opt, value); - } +function updateAllShapes (gd, opt, value) { + for (var i = 0; i < gd._fullLayout.shapes.length; i++) { + drawOne(gd, i, opt, value) + } } -function deleteShape(gd, index) { - getShapeLayer(gd, index) +function deleteShape (gd, index) { + getShapeLayer(gd, index) .selectAll('[data-index="' + index + '"]') - .remove(); + .remove() - gd._fullLayout.shapes.splice(index, 1); + gd._fullLayout.shapes.splice(index, 1) - gd.layout.shapes.splice(index, 1); + gd.layout.shapes.splice(index, 1) - for(var i = index; i < gd._fullLayout.shapes.length; i++) { + for (var i = index; i < gd._fullLayout.shapes.length; i++) { // redraw all shapes past the removed one, // so they bind to the right events - getShapeLayer(gd, i) + getShapeLayer(gd, i) .selectAll('[data-index="' + (i + 1) + '"]') - .attr('data-index', i); - drawOne(gd, i); - } + .attr('data-index', i) + drawOne(gd, i) + } } -function insertShape(gd, index, newShape) { - gd._fullLayout.shapes.splice(index, 0, {}); +function insertShape (gd, index, newShape) { + gd._fullLayout.shapes.splice(index, 0, {}) - var rule = Lib.isPlainObject(newShape) ? + var rule = Lib.isPlainObject(newShape) ? Lib.extendFlat({}, newShape) : - {text: 'New text'}; + {text: 'New text'} - if(gd.layout.shapes) { - gd.layout.shapes.splice(index, 0, rule); - } else { - gd.layout.shapes = [rule]; - } + if (gd.layout.shapes) { + gd.layout.shapes.splice(index, 0, rule) + } else { + gd.layout.shapes = [rule] + } // there is no need to call shapes.draw(gd, index), // because updateShape() is called from within shapes.draw() - for(var i = gd._fullLayout.shapes.length - 1; i > index; i--) { - getShapeLayer(gd, i) + for (var i = gd._fullLayout.shapes.length - 1; i > index; i--) { + getShapeLayer(gd, i) .selectAll('[data-index="' + (i - 1) + '"]') - .attr('data-index', i); - drawOne(gd, i); - } + .attr('data-index', i) + drawOne(gd, i) + } } -function updateShape(gd, index, opt, value) { - var i, n; +function updateShape (gd, index, opt, value) { + var i, n // remove the existing shape if there is one - getShapeLayer(gd, index) + getShapeLayer(gd, index) .selectAll('[data-index="' + index + '"]') - .remove(); + .remove() // remember a few things about what was already there, - var optionsIn = gd.layout.shapes[index]; + var optionsIn = gd.layout.shapes[index] // (from annos...) not sure how we're getting here... but C12 is seeing a bug // where we fail here when they add/remove annotations // TODO: clean this up and remove it. - if(!optionsIn) return; + if (!optionsIn) return // alter the input shape as requested - var optionsEdit = {}; - if(typeof opt === 'string' && opt) optionsEdit[opt] = value; - else if(Lib.isPlainObject(opt)) optionsEdit = opt; - - var optionKeys = Object.keys(optionsEdit); - for(i = 0; i < optionKeys.length; i++) { - var k = optionKeys[i]; - Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]); - } + var optionsEdit = {} + if (typeof opt === 'string' && opt) optionsEdit[opt] = value + else if (Lib.isPlainObject(opt)) optionsEdit = opt + + var optionKeys = Object.keys(optionsEdit) + for (i = 0; i < optionKeys.length; i++) { + var k = optionKeys[i] + Lib.nestedProperty(optionsIn, k).set(optionsEdit[k]) + } // return early in visible: false updates - if(optionsIn.visible === false) return; + if (optionsIn.visible === false) return - var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref}, - posAttrs = ['x0', 'x1', 'y0', 'y1']; + var oldRef = {xref: optionsIn.xref, yref: optionsIn.yref}, + posAttrs = ['x0', 'x1', 'y0', 'y1'] - for(i = 0; i < 4; i++) { - var posAttr = posAttrs[i]; + for (i = 0; i < 4; i++) { + var posAttr = posAttrs[i] // if we don't have an explicit position already, // don't set one just because we're changing references // or axis type. // the defaults will be consistent most of the time anyway, // except in log/linear changes - if(optionsEdit[posAttr] !== undefined || + if (optionsEdit[posAttr] !== undefined || optionsIn[posAttr] === undefined) { - continue; - } + continue + } - var axLetter = posAttr.charAt(0), - axOld = Axes.getFromId(gd, + var axLetter = posAttr.charAt(0), + axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')), - axNew = Axes.getFromId(gd, + axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')), - position = optionsIn[posAttr], - rangePosition; + position = optionsIn[posAttr], + rangePosition - if(optionsEdit[axLetter + 'ref'] !== undefined) { + if (optionsEdit[axLetter + 'ref'] !== undefined) { // first convert to fraction of the axis - if(axOld) { - rangePosition = helpers.shapePositionToRange(axOld)(position); - position = axOld.r2fraction(rangePosition); - } else { - position = (position - axNew.domain[0]) / - (axNew.domain[1] - axNew.domain[0]); - } - - if(axNew) { + if (axOld) { + rangePosition = helpers.shapePositionToRange(axOld)(position) + position = axOld.r2fraction(rangePosition) + } else { + position = (position - axNew.domain[0]) / + (axNew.domain[1] - axNew.domain[0]) + } + + if (axNew) { // then convert to new data coordinates at the same fraction - rangePosition = axNew.fraction2r(position); - position = helpers.rangeToShapePosition(axNew)(rangePosition); - } else { + rangePosition = axNew.fraction2r(position) + position = helpers.rangeToShapePosition(axNew)(rangePosition) + } else { // or scale to the whole plot - position = axOld.domain[0] + - position * (axOld.domain[1] - axOld.domain[0]); - } - } - - optionsIn[posAttr] = position; + position = axOld.domain[0] + + position * (axOld.domain[1] - axOld.domain[0]) + } } - var options = {}; - handleShapeDefaults(optionsIn, options, gd._fullLayout); - gd._fullLayout.shapes[index] = options; - - var clipAxes; - if(options.layer !== 'below') { - clipAxes = (options.xref + options.yref).replace(/paper/g, ''); - drawShape(gd._fullLayout._shapeUpperLayer); - } - else if(options.xref === 'paper' && options.yref === 'paper') { - clipAxes = ''; - drawShape(gd._fullLayout._shapeLowerLayer); + optionsIn[posAttr] = position + } + + var options = {} + handleShapeDefaults(optionsIn, options, gd._fullLayout) + gd._fullLayout.shapes[index] = options + + var clipAxes + if (options.layer !== 'below') { + clipAxes = (options.xref + options.yref).replace(/paper/g, '') + drawShape(gd._fullLayout._shapeUpperLayer) + } else if (options.xref === 'paper' && options.yref === 'paper') { + clipAxes = '' + drawShape(gd._fullLayout._shapeLowerLayer) + } else { + var plots = gd._fullLayout._plots || {}, + subplots = Object.keys(plots), + plotinfo + + for (i = 0, n = subplots.length; i < n; i++) { + plotinfo = plots[subplots[i]] + clipAxes = subplots[i] + + if (isShapeInSubplot(gd, options, plotinfo)) { + drawShape(plotinfo.shapelayer) + } } - else { - var plots = gd._fullLayout._plots || {}, - subplots = Object.keys(plots), - plotinfo; - - for(i = 0, n = subplots.length; i < n; i++) { - plotinfo = plots[subplots[i]]; - clipAxes = subplots[i]; - - if(isShapeInSubplot(gd, options, plotinfo)) { - drawShape(plotinfo.shapelayer); - } - } - } - - function drawShape(shapeLayer) { - var attrs = { - 'data-index': index, - 'fill-rule': 'evenodd', - d: getPathString(gd, options) - }, - lineColor = options.line.width ? - options.line.color : 'rgba(0,0,0,0)'; - - var path = shapeLayer.append('path') + } + + function drawShape (shapeLayer) { + var attrs = { + 'data-index': index, + 'fill-rule': 'evenodd', + d: getPathString(gd, options) + }, + lineColor = options.line.width ? + options.line.color : 'rgba(0,0,0,0)' + + var path = shapeLayer.append('path') .attr(attrs) .style('opacity', options.opacity) .call(Color.stroke, lineColor) .call(Color.fill, options.fillcolor) - .call(Drawing.dashLine, options.line.dash, options.line.width); + .call(Drawing.dashLine, options.line.dash, options.line.width) - if(clipAxes) { - path.call(Drawing.setClipUrl, - 'clip' + gd._fullLayout._uid + clipAxes); - } - - if(gd._context.editable) setupDragElement(gd, path, options, index); + if (clipAxes) { + path.call(Drawing.setClipUrl, + 'clip' + gd._fullLayout._uid + clipAxes) } + + if (gd._context.editable) setupDragElement(gd, path, options, index) + } } -function setupDragElement(gd, shapePath, shapeOptions, index) { - var MINWIDTH = 10, - MINHEIGHT = 10; +function setupDragElement (gd, shapePath, shapeOptions, index) { + var MINWIDTH = 10, + MINHEIGHT = 10 - var update; - var x0, y0, x1, y1, astrX0, astrY0, astrX1, astrY1; - var n0, s0, w0, e0, astrN, astrS, astrW, astrE, optN, optS, optW, optE; - var pathIn, astrPath; + var update + var x0, y0, x1, y1, astrX0, astrY0, astrX1, astrY1 + var n0, s0, w0, e0, astrN, astrS, astrW, astrE, optN, optS, optW, optE + var pathIn, astrPath - var xa, ya, x2p, y2p, p2x, p2y; + var xa, ya, x2p, y2p, p2x, p2y - var dragOptions = { - setCursor: updateDragMode, - element: shapePath.node(), - prepFn: startDrag, - doneFn: endDrag - }, - dragBBox = dragOptions.element.getBoundingClientRect(), - dragMode; + var dragOptions = { + setCursor: updateDragMode, + element: shapePath.node(), + prepFn: startDrag, + doneFn: endDrag + }, + dragBBox = dragOptions.element.getBoundingClientRect(), + dragMode - dragElement.init(dragOptions); + dragElement.init(dragOptions) - function updateDragMode(evt) { + function updateDragMode (evt) { // choose 'move' or 'resize' // based on initial position of cursor within the drag element - var w = dragBBox.right - dragBBox.left, - h = dragBBox.bottom - dragBBox.top, - x = evt.clientX - dragBBox.left, - y = evt.clientY - dragBBox.top, - cursor = (w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey) ? + var w = dragBBox.right - dragBBox.left, + h = dragBBox.bottom - dragBBox.top, + x = evt.clientX - dragBBox.left, + y = evt.clientY - dragBBox.top, + cursor = (w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey) ? dragElement.getCursor(x / w, 1 - y / h) : - 'move'; + 'move' - setCursor(shapePath, cursor); + setCursor(shapePath, cursor) // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w' - dragMode = cursor.split('-')[0]; - } + dragMode = cursor.split('-')[0] + } - function startDrag(evt) { + function startDrag (evt) { // setup conversion functions - xa = Axes.getFromId(gd, shapeOptions.xref); - ya = Axes.getFromId(gd, shapeOptions.yref); + xa = Axes.getFromId(gd, shapeOptions.xref) + ya = Axes.getFromId(gd, shapeOptions.yref) - x2p = helpers.getDataToPixel(gd, xa); - y2p = helpers.getDataToPixel(gd, ya, true); - p2x = helpers.getPixelToData(gd, xa); - p2y = helpers.getPixelToData(gd, ya, true); + x2p = helpers.getDataToPixel(gd, xa) + y2p = helpers.getDataToPixel(gd, ya, true) + p2x = helpers.getPixelToData(gd, xa) + p2y = helpers.getPixelToData(gd, ya, true) // setup update strings and initial values - var astr = 'shapes[' + index + ']'; - if(shapeOptions.type === 'path') { - pathIn = shapeOptions.path; - astrPath = astr + '.path'; - } - else { - x0 = x2p(shapeOptions.x0); - y0 = y2p(shapeOptions.y0); - x1 = x2p(shapeOptions.x1); - y1 = y2p(shapeOptions.y1); - - astrX0 = astr + '.x0'; - astrY0 = astr + '.y0'; - astrX1 = astr + '.x1'; - astrY1 = astr + '.y1'; - } - - if(x0 < x1) { - w0 = x0; astrW = astr + '.x0'; optW = 'x0'; - e0 = x1; astrE = astr + '.x1'; optE = 'x1'; - } - else { - w0 = x1; astrW = astr + '.x1'; optW = 'x1'; - e0 = x0; astrE = astr + '.x0'; optE = 'x0'; - } - if(y0 < y1) { - n0 = y0; astrN = astr + '.y0'; optN = 'y0'; - s0 = y1; astrS = astr + '.y1'; optS = 'y1'; - } - else { - n0 = y1; astrN = astr + '.y1'; optN = 'y1'; - s0 = y0; astrS = astr + '.y0'; optS = 'y0'; - } - - update = {}; + var astr = 'shapes[' + index + ']' + if (shapeOptions.type === 'path') { + pathIn = shapeOptions.path + astrPath = astr + '.path' + } else { + x0 = x2p(shapeOptions.x0) + y0 = y2p(shapeOptions.y0) + x1 = x2p(shapeOptions.x1) + y1 = y2p(shapeOptions.y1) + + astrX0 = astr + '.x0' + astrY0 = astr + '.y0' + astrX1 = astr + '.x1' + astrY1 = astr + '.y1' + } - // setup dragMode and the corresponding handler - updateDragMode(evt); - dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape; + if (x0 < x1) { + w0 = x0; astrW = astr + '.x0'; optW = 'x0' + e0 = x1; astrE = astr + '.x1'; optE = 'x1' + } else { + w0 = x1; astrW = astr + '.x1'; optW = 'x1' + e0 = x0; astrE = astr + '.x0'; optE = 'x0' + } + if (y0 < y1) { + n0 = y0; astrN = astr + '.y0'; optN = 'y0' + s0 = y1; astrS = astr + '.y1'; optS = 'y1' + } else { + n0 = y1; astrN = astr + '.y1'; optN = 'y1' + s0 = y0; astrS = astr + '.y0'; optS = 'y0' } - function endDrag(dragged) { - setCursor(shapePath); - if(dragged) { - Plotly.relayout(gd, update); - } + update = {} + + // setup dragMode and the corresponding handler + updateDragMode(evt) + dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape + } + + function endDrag (dragged) { + setCursor(shapePath) + if (dragged) { + Plotly.relayout(gd, update) } + } + + function moveShape (dx, dy) { + if (shapeOptions.type === 'path') { + var moveX = function moveX (x) { return p2x(x2p(x) + dx) } + if (xa && xa.type === 'date') moveX = helpers.encodeDate(moveX) + + var moveY = function moveY (y) { return p2y(y2p(y) + dy) } + if (ya && ya.type === 'date') moveY = helpers.encodeDate(moveY) - function moveShape(dx, dy) { - if(shapeOptions.type === 'path') { - var moveX = function moveX(x) { return p2x(x2p(x) + dx); }; - if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX); - - var moveY = function moveY(y) { return p2y(y2p(y) + dy); }; - if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY); - - shapeOptions.path = movePath(pathIn, moveX, moveY); - update[astrPath] = shapeOptions.path; - } - else { - update[astrX0] = shapeOptions.x0 = p2x(x0 + dx); - update[astrY0] = shapeOptions.y0 = p2y(y0 + dy); - update[astrX1] = shapeOptions.x1 = p2x(x1 + dx); - update[astrY1] = shapeOptions.y1 = p2y(y1 + dy); - } - - shapePath.attr('d', getPathString(gd, shapeOptions)); + shapeOptions.path = movePath(pathIn, moveX, moveY) + update[astrPath] = shapeOptions.path + } else { + update[astrX0] = shapeOptions.x0 = p2x(x0 + dx) + update[astrY0] = shapeOptions.y0 = p2y(y0 + dy) + update[astrX1] = shapeOptions.x1 = p2x(x1 + dx) + update[astrY1] = shapeOptions.y1 = p2y(y1 + dy) } - function resizeShape(dx, dy) { - if(shapeOptions.type === 'path') { + shapePath.attr('d', getPathString(gd, shapeOptions)) + } + + function resizeShape (dx, dy) { + if (shapeOptions.type === 'path') { // TODO: implement path resize - var moveX = function moveX(x) { return p2x(x2p(x) + dx); }; - if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX); - - var moveY = function moveY(y) { return p2y(y2p(y) + dy); }; - if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY); - - shapeOptions.path = movePath(pathIn, moveX, moveY); - update[astrPath] = shapeOptions.path; - } - else { - var newN = (~dragMode.indexOf('n')) ? n0 + dy : n0, - newS = (~dragMode.indexOf('s')) ? s0 + dy : s0, - newW = (~dragMode.indexOf('w')) ? w0 + dx : w0, - newE = (~dragMode.indexOf('e')) ? e0 + dx : e0; - - if(newS - newN > MINHEIGHT) { - update[astrN] = shapeOptions[optN] = p2y(newN); - update[astrS] = shapeOptions[optS] = p2y(newS); - } - - if(newE - newW > MINWIDTH) { - update[astrW] = shapeOptions[optW] = p2x(newW); - update[astrE] = shapeOptions[optE] = p2x(newE); - } - } - - shapePath.attr('d', getPathString(gd, shapeOptions)); - } -} + var moveX = function moveX (x) { return p2x(x2p(x) + dx) } + if (xa && xa.type === 'date') moveX = helpers.encodeDate(moveX) -function getShapeLayer(gd, index) { - var shape = gd._fullLayout.shapes[index], - shapeLayer = gd._fullLayout._shapeUpperLayer; + var moveY = function moveY (y) { return p2y(y2p(y) + dy) } + if (ya && ya.type === 'date') moveY = helpers.encodeDate(moveY) - if(!shape) { - Lib.log('getShapeLayer: undefined shape: index', index); - } - else if(shape.layer === 'below') { - shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ? - gd._fullLayout._shapeLowerLayer : - gd._fullLayout._shapeSubplotLayer; + shapeOptions.path = movePath(pathIn, moveX, moveY) + update[astrPath] = shapeOptions.path + } else { + var newN = (~dragMode.indexOf('n')) ? n0 + dy : n0, + newS = (~dragMode.indexOf('s')) ? s0 + dy : s0, + newW = (~dragMode.indexOf('w')) ? w0 + dx : w0, + newE = (~dragMode.indexOf('e')) ? e0 + dx : e0 + + if (newS - newN > MINHEIGHT) { + update[astrN] = shapeOptions[optN] = p2y(newN) + update[astrS] = shapeOptions[optS] = p2y(newS) + } + + if (newE - newW > MINWIDTH) { + update[astrW] = shapeOptions[optW] = p2x(newW) + update[astrE] = shapeOptions[optE] = p2x(newE) + } } - return shapeLayer; + shapePath.attr('d', getPathString(gd, shapeOptions)) + } } -function isShapeInSubplot(gd, shape, plotinfo) { - var xa = Axes.getFromId(gd, plotinfo.id, 'x')._id, - ya = Axes.getFromId(gd, plotinfo.id, 'y')._id, - isBelow = shape.layer === 'below', - inSuplotAxis = (xa === shape.xref || ya === shape.yref), - isNotAnOverlaidSubplot = !!plotinfo.shapelayer; - return isBelow && inSuplotAxis && isNotAnOverlaidSubplot; -} - -function getPathString(gd, options) { - var type = options.type, - xa = Axes.getFromId(gd, options.xref), - ya = Axes.getFromId(gd, options.yref), - gs = gd._fullLayout._size, - x2r, - x2p, - y2r, - y2p; - - if(xa) { - x2r = helpers.shapePositionToRange(xa); - x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); }; - } - else { - x2p = function(v) { return gs.l + gs.w * v; }; - } +function getShapeLayer (gd, index) { + var shape = gd._fullLayout.shapes[index], + shapeLayer = gd._fullLayout._shapeUpperLayer - if(ya) { - y2r = helpers.shapePositionToRange(ya); - y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); }; - } - else { - y2p = function(v) { return gs.t + gs.h * (1 - v); }; - } + if (!shape) { + Lib.log('getShapeLayer: undefined shape: index', index) + } else if (shape.layer === 'below') { + shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ? + gd._fullLayout._shapeLowerLayer : + gd._fullLayout._shapeSubplotLayer + } - if(type === 'path') { - if(xa && xa.type === 'date') x2p = helpers.decodeDate(x2p); - if(ya && ya.type === 'date') y2p = helpers.decodeDate(y2p); - return convertPath(options.path, x2p, y2p); - } + return shapeLayer +} - var x0 = x2p(options.x0), - x1 = x2p(options.x1), - y0 = y2p(options.y0), - y1 = y2p(options.y1); +function isShapeInSubplot (gd, shape, plotinfo) { + var xa = Axes.getFromId(gd, plotinfo.id, 'x')._id, + ya = Axes.getFromId(gd, plotinfo.id, 'y')._id, + isBelow = shape.layer === 'below', + inSuplotAxis = (xa === shape.xref || ya === shape.yref), + isNotAnOverlaidSubplot = !!plotinfo.shapelayer + return isBelow && inSuplotAxis && isNotAnOverlaidSubplot +} - if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1; - if(type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z'; +function getPathString (gd, options) { + var type = options.type, + xa = Axes.getFromId(gd, options.xref), + ya = Axes.getFromId(gd, options.yref), + gs = gd._fullLayout._size, + x2r, + x2p, + y2r, + y2p + + if (xa) { + x2r = helpers.shapePositionToRange(xa) + x2p = function (v) { return xa._offset + xa.r2p(x2r(v, true)) } + } else { + x2p = function (v) { return gs.l + gs.w * v } + } + + if (ya) { + y2r = helpers.shapePositionToRange(ya) + y2p = function (v) { return ya._offset + ya.r2p(y2r(v, true)) } + } else { + y2p = function (v) { return gs.t + gs.h * (1 - v) } + } + + if (type === 'path') { + if (xa && xa.type === 'date') x2p = helpers.decodeDate(x2p) + if (ya && ya.type === 'date') y2p = helpers.decodeDate(y2p) + return convertPath(options.path, x2p, y2p) + } + + var x0 = x2p(options.x0), + x1 = x2p(options.x1), + y0 = y2p(options.y0), + y1 = y2p(options.y1) + + if (type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1 + if (type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z' // circle - var cx = (x0 + x1) / 2, - cy = (y0 + y1) / 2, - rx = Math.abs(cx - x0), - ry = Math.abs(cy - y0), - rArc = 'A' + rx + ',' + ry, - rightPt = (cx + rx) + ',' + cy, - topPt = cx + ',' + (cy - ry); - return 'M' + rightPt + rArc + ' 0 1,1 ' + topPt + - rArc + ' 0 0,1 ' + rightPt + 'Z'; + var cx = (x0 + x1) / 2, + cy = (y0 + y1) / 2, + rx = Math.abs(cx - x0), + ry = Math.abs(cy - y0), + rArc = 'A' + rx + ',' + ry, + rightPt = (cx + rx) + ',' + cy, + topPt = cx + ',' + (cy - ry) + return 'M' + rightPt + rArc + ' 0 1,1 ' + topPt + + rArc + ' 0 0,1 ' + rightPt + 'Z' } - -function convertPath(pathIn, x2p, y2p) { +function convertPath (pathIn, x2p, y2p) { // convert an SVG path string from data units to pixels - return pathIn.replace(constants.segmentRE, function(segment) { - var paramNumber = 0, - segmentType = segment.charAt(0), - xParams = constants.paramIsX[segmentType], - yParams = constants.paramIsY[segmentType], - nParams = constants.numParams[segmentType]; - - var paramString = segment.substr(1).replace(constants.paramRE, function(param) { - if(xParams[paramNumber]) param = x2p(param); - else if(yParams[paramNumber]) param = y2p(param); - paramNumber++; - - if(paramNumber > nParams) param = 'X'; - return param; - }); - - if(paramNumber > nParams) { - paramString = paramString.replace(/[\s,]*X.*/, ''); - Lib.log('Ignoring extra params in segment ' + segment); - } - - return segmentType + paramString; - }); + return pathIn.replace(constants.segmentRE, function (segment) { + var paramNumber = 0, + segmentType = segment.charAt(0), + xParams = constants.paramIsX[segmentType], + yParams = constants.paramIsY[segmentType], + nParams = constants.numParams[segmentType] + + var paramString = segment.substr(1).replace(constants.paramRE, function (param) { + if (xParams[paramNumber]) param = x2p(param) + else if (yParams[paramNumber]) param = y2p(param) + paramNumber++ + + if (paramNumber > nParams) param = 'X' + return param + }) + + if (paramNumber > nParams) { + paramString = paramString.replace(/[\s,]*X.*/, '') + Lib.log('Ignoring extra params in segment ' + segment) + } + + return segmentType + paramString + }) } -function movePath(pathIn, moveX, moveY) { - return pathIn.replace(constants.segmentRE, function(segment) { - var paramNumber = 0, - segmentType = segment.charAt(0), - xParams = constants.paramIsX[segmentType], - yParams = constants.paramIsY[segmentType], - nParams = constants.numParams[segmentType]; +function movePath (pathIn, moveX, moveY) { + return pathIn.replace(constants.segmentRE, function (segment) { + var paramNumber = 0, + segmentType = segment.charAt(0), + xParams = constants.paramIsX[segmentType], + yParams = constants.paramIsY[segmentType], + nParams = constants.numParams[segmentType] - var paramString = segment.substr(1).replace(constants.paramRE, function(param) { - if(paramNumber >= nParams) return param; + var paramString = segment.substr(1).replace(constants.paramRE, function (param) { + if (paramNumber >= nParams) return param - if(xParams[paramNumber]) param = moveX(param); - else if(yParams[paramNumber]) param = moveY(param); + if (xParams[paramNumber]) param = moveX(param) + else if (yParams[paramNumber]) param = moveY(param) - paramNumber++; + paramNumber++ - return param; - }); + return param + }) - return segmentType + paramString; - }); + return segmentType + paramString + }) } diff --git a/src/components/shapes/helpers.js b/src/components/shapes/helpers.js index 15c5337c52a..3c6b43687f2 100644 --- a/src/components/shapes/helpers.js +++ b/src/components/shapes/helpers.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' // special position conversion functions... category axis positions can't be // specified by their data values, because they don't make a continuous mapping. @@ -18,62 +17,58 @@ // will be identical, so rangeToShapePosition and shapePositionToRange can be // removed entirely. -exports.rangeToShapePosition = function(ax) { - return (ax.type === 'log') ? ax.r2d : function(v) { return v; }; -}; - -exports.shapePositionToRange = function(ax) { - return (ax.type === 'log') ? ax.d2r : function(v) { return v; }; -}; +exports.rangeToShapePosition = function (ax) { + return (ax.type === 'log') ? ax.r2d : function (v) { return v } +} -exports.decodeDate = function(convertToPx) { - return function(v) { - if(v.replace) v = v.replace('_', ' '); - return convertToPx(v); - }; -}; +exports.shapePositionToRange = function (ax) { + return (ax.type === 'log') ? ax.d2r : function (v) { return v } +} -exports.encodeDate = function(convertToDate) { - return function(v) { return convertToDate(v).replace(' ', '_'); }; -}; +exports.decodeDate = function (convertToPx) { + return function (v) { + if (v.replace) v = v.replace('_', ' ') + return convertToPx(v) + } +} -exports.getDataToPixel = function(gd, axis, isVertical) { - var gs = gd._fullLayout._size, - dataToPixel; +exports.encodeDate = function (convertToDate) { + return function (v) { return convertToDate(v).replace(' ', '_') } +} - if(axis) { - var d2r = exports.shapePositionToRange(axis); +exports.getDataToPixel = function (gd, axis, isVertical) { + var gs = gd._fullLayout._size, + dataToPixel - dataToPixel = function(v) { - return axis._offset + axis.r2p(d2r(v, true)); - }; + if (axis) { + var d2r = exports.shapePositionToRange(axis) - if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel); - } - else if(isVertical) { - dataToPixel = function(v) { return gs.t + gs.h * (1 - v); }; - } - else { - dataToPixel = function(v) { return gs.l + gs.w * v; }; + dataToPixel = function (v) { + return axis._offset + axis.r2p(d2r(v, true)) } - return dataToPixel; -}; + if (axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel) + } else if (isVertical) { + dataToPixel = function (v) { return gs.t + gs.h * (1 - v) } + } else { + dataToPixel = function (v) { return gs.l + gs.w * v } + } -exports.getPixelToData = function(gd, axis, isVertical) { - var gs = gd._fullLayout._size, - pixelToData; + return dataToPixel +} - if(axis) { - var r2d = exports.rangeToShapePosition(axis); - pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); }; - } - else if(isVertical) { - pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; }; - } - else { - pixelToData = function(p) { return (p - gs.l) / gs.w; }; - } +exports.getPixelToData = function (gd, axis, isVertical) { + var gs = gd._fullLayout._size, + pixelToData + + if (axis) { + var r2d = exports.rangeToShapePosition(axis) + pixelToData = function (p) { return r2d(axis.p2r(p - axis._offset)) } + } else if (isVertical) { + pixelToData = function (p) { return 1 - (p - gs.t) / gs.h } + } else { + pixelToData = function (p) { return (p - gs.l) / gs.w } + } - return pixelToData; -}; + return pixelToData +} diff --git a/src/components/shapes/index.js b/src/components/shapes/index.js index 444ede3cd59..4439798e344 100644 --- a/src/components/shapes/index.js +++ b/src/components/shapes/index.js @@ -6,19 +6,18 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var drawModule = require('./draw'); +var drawModule = require('./draw') module.exports = { - moduleType: 'component', - name: 'shapes', + moduleType: 'component', + name: 'shapes', - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - calcAutorange: require('./calc_autorange'), - draw: drawModule.draw, - drawOne: drawModule.drawOne -}; + calcAutorange: require('./calc_autorange'), + draw: drawModule.draw, + drawOne: drawModule.drawOne +} diff --git a/src/components/shapes/shape_defaults.js b/src/components/shapes/shape_defaults.js index 44d983fc793..10ccd2b0a08 100644 --- a/src/components/shapes/shape_defaults.js +++ b/src/components/shapes/shape_defaults.js @@ -6,92 +6,88 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var attributes = require('./attributes') +var helpers = require('./helpers') -var attributes = require('./attributes'); -var helpers = require('./helpers'); +module.exports = function handleShapeDefaults (shapeIn, shapeOut, fullLayout, opts, itemOpts) { + opts = opts || {} + itemOpts = itemOpts || {} + function coerce (attr, dflt) { + return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt) + } -module.exports = function handleShapeDefaults(shapeIn, shapeOut, fullLayout, opts, itemOpts) { - opts = opts || {}; - itemOpts = itemOpts || {}; + var visible = coerce('visible', !itemOpts.itemIsNotPlainObject) - function coerce(attr, dflt) { - return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt); - } - - var visible = coerce('visible', !itemOpts.itemIsNotPlainObject); - - if(!visible) return shapeOut; + if (!visible) return shapeOut - coerce('layer'); - coerce('opacity'); - coerce('fillcolor'); - coerce('line.color'); - coerce('line.width'); - coerce('line.dash'); + coerce('layer') + coerce('opacity') + coerce('fillcolor') + coerce('line.color') + coerce('line.width') + coerce('line.dash') - var dfltType = shapeIn.path ? 'path' : 'rect', - shapeType = coerce('type', dfltType); + var dfltType = shapeIn.path ? 'path' : 'rect', + shapeType = coerce('type', dfltType) // positioning - var axLetters = ['x', 'y']; - for(var i = 0; i < 2; i++) { - var axLetter = axLetters[i], - gdMock = {_fullLayout: fullLayout}; + var axLetters = ['x', 'y'] + for (var i = 0; i < 2; i++) { + var axLetter = axLetters[i], + gdMock = {_fullLayout: fullLayout} // xref, yref - var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper'); - - if(shapeType !== 'path') { - var dflt0 = 0.25, - dflt1 = 0.75, - ax, - pos2r, - r2pos; - - if(axRef !== 'paper') { - ax = Axes.getFromId(gdMock, axRef); - r2pos = helpers.rangeToShapePosition(ax); - pos2r = helpers.shapePositionToRange(ax); - } - else { - pos2r = r2pos = Lib.identity; - } + var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper') + + if (shapeType !== 'path') { + var dflt0 = 0.25, + dflt1 = 0.75, + ax, + pos2r, + r2pos + + if (axRef !== 'paper') { + ax = Axes.getFromId(gdMock, axRef) + r2pos = helpers.rangeToShapePosition(ax) + pos2r = helpers.shapePositionToRange(ax) + } else { + pos2r = r2pos = Lib.identity + } // hack until V2.0 when log has regular range behavior - make it look like other // ranges to send to coerce, then put it back after // this is all to give reasonable default position behavior on log axes, which is // a pretty unimportant edge case so we could just ignore this. - var attr0 = axLetter + '0', - attr1 = axLetter + '1', - in0 = shapeIn[attr0], - in1 = shapeIn[attr1]; - shapeIn[attr0] = pos2r(shapeIn[attr0], true); - shapeIn[attr1] = pos2r(shapeIn[attr1], true); + var attr0 = axLetter + '0', + attr1 = axLetter + '1', + in0 = shapeIn[attr0], + in1 = shapeIn[attr1] + shapeIn[attr0] = pos2r(shapeIn[attr0], true) + shapeIn[attr1] = pos2r(shapeIn[attr1], true) // x0, x1 (and y0, y1) - Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0); - Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1); + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0) + Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1) // hack part 2 - shapeOut[attr0] = r2pos(shapeOut[attr0]); - shapeOut[attr1] = r2pos(shapeOut[attr1]); - shapeIn[attr0] = in0; - shapeIn[attr1] = in1; - } + shapeOut[attr0] = r2pos(shapeOut[attr0]) + shapeOut[attr1] = r2pos(shapeOut[attr1]) + shapeIn[attr0] = in0 + shapeIn[attr1] = in1 } + } - if(shapeType === 'path') { - coerce('path'); - } - else { - Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']); - } + if (shapeType === 'path') { + coerce('path') + } else { + Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']) + } - return shapeOut; -}; + return shapeOut +} diff --git a/src/components/sliders/attributes.js b/src/components/sliders/attributes.js index 8e584a2455f..acccec900d8 100644 --- a/src/components/sliders/attributes.js +++ b/src/components/sliders/attributes.js @@ -6,267 +6,267 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var fontAttrs = require('../../plots/font_attributes'); -var padAttrs = require('../../plots/pad_attributes'); -var extendFlat = require('../../lib/extend').extendFlat; -var extendDeep = require('../../lib/extend').extendDeep; -var animationAttrs = require('../../plots/animation_attributes'); -var constants = require('./constants'); +var fontAttrs = require('../../plots/font_attributes') +var padAttrs = require('../../plots/pad_attributes') +var extendFlat = require('../../lib/extend').extendFlat +var extendDeep = require('../../lib/extend').extendDeep +var animationAttrs = require('../../plots/animation_attributes') +var constants = require('./constants') var stepsAttrs = { - _isLinkedToArray: 'step', + _isLinkedToArray: 'step', - method: { - valType: 'enumerated', - values: ['restyle', 'relayout', 'animate', 'update'], - dflt: 'restyle', - role: 'info', - description: [ - 'Sets the Plotly method to be called when the slider value is changed.' - ].join(' ') - }, - args: { - valType: 'info_array', - role: 'info', - freeLength: true, - items: [ + method: { + valType: 'enumerated', + values: ['restyle', 'relayout', 'animate', 'update'], + dflt: 'restyle', + role: 'info', + description: [ + 'Sets the Plotly method to be called when the slider value is changed.' + ].join(' ') + }, + args: { + valType: 'info_array', + role: 'info', + freeLength: true, + items: [ { valType: 'any' }, { valType: 'any' }, { valType: 'any' } - ], - description: [ - 'Sets the arguments values to be passed to the Plotly', - 'method set in `method` on slide.' - ].join(' ') - }, - label: { - valType: 'string', - role: 'info', - description: 'Sets the text label to appear on the slider' - }, - value: { - valType: 'string', - role: 'info', - description: [ - 'Sets the value of the slider step, used to refer to the step programatically.', - 'Defaults to the slider label if not provided.' - ].join(' ') - } -}; + ], + description: [ + 'Sets the arguments values to be passed to the Plotly', + 'method set in `method` on slide.' + ].join(' ') + }, + label: { + valType: 'string', + role: 'info', + description: 'Sets the text label to appear on the slider' + }, + value: { + valType: 'string', + role: 'info', + description: [ + 'Sets the value of the slider step, used to refer to the step programatically.', + 'Defaults to the slider label if not provided.' + ].join(' ') + } +} module.exports = { - _isLinkedToArray: 'slider', + _isLinkedToArray: 'slider', - visible: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not the slider is visible.' - ].join(' ') - }, + visible: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not the slider is visible.' + ].join(' ') + }, - active: { - valType: 'number', - role: 'info', - min: 0, - dflt: 0, - description: [ - 'Determines which button (by index starting from 0) is', - 'considered active.' - ].join(' ') - }, + active: { + valType: 'number', + role: 'info', + min: 0, + dflt: 0, + description: [ + 'Determines which button (by index starting from 0) is', + 'considered active.' + ].join(' ') + }, - steps: stepsAttrs, + steps: stepsAttrs, - lenmode: { - valType: 'enumerated', - values: ['fraction', 'pixels'], - role: 'info', - dflt: 'fraction', - description: [ - 'Determines whether this slider length', - 'is set in units of plot *fraction* or in *pixels.', - 'Use `len` to set the value.' - ].join(' ') - }, - len: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the length of the slider', - 'This measure excludes the padding of both ends.', - 'That is, the slider\'s length is this length minus the', - 'padding on both ends.' - ].join(' ') + lenmode: { + valType: 'enumerated', + values: ['fraction', 'pixels'], + role: 'info', + dflt: 'fraction', + description: [ + 'Determines whether this slider length', + 'is set in units of plot *fraction* or in *pixels.', + 'Use `len` to set the value.' + ].join(' ') + }, + len: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: [ + 'Sets the length of the slider', + 'This measure excludes the padding of both ends.', + 'That is, the slider\'s length is this length minus the', + 'padding on both ends.' + ].join(' ') + }, + x: { + valType: 'number', + min: -2, + max: 3, + dflt: 0, + role: 'style', + description: 'Sets the x position (in normalized coordinates) of the slider.' + }, + pad: extendDeep({}, padAttrs, { + description: 'Set the padding of the slider component along each side.' + }, {t: {dflt: 20}}), + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: [ + 'Sets the slider\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the range selector.' + ].join(' ') + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 0, + role: 'style', + description: 'Sets the y position (in normalized coordinates) of the slider.' + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'top', + role: 'info', + description: [ + 'Sets the slider\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the range selector.' + ].join(' ') + }, + + transition: { + duration: { + valType: 'number', + role: 'info', + min: 0, + dflt: 150, + description: 'Sets the duration of the slider transition' }, - x: { - valType: 'number', - min: -2, - max: 3, - dflt: 0, - role: 'style', - description: 'Sets the x position (in normalized coordinates) of the slider.' + easing: { + valType: 'enumerated', + values: animationAttrs.transition.easing.values, + role: 'info', + dflt: 'cubic-in-out', + description: 'Sets the easing function of the slider transition' + } + }, + + currentvalue: { + visible: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Shows the currently-selected value above the slider.' + ].join(' ') }, - pad: extendDeep({}, padAttrs, { - description: 'Set the padding of the slider component along each side.' - }, {t: {dflt: 20}}), + xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'left', - role: 'info', - description: [ - 'Sets the slider\'s horizontal position anchor.', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the range selector.' - ].join(' ') - }, - y: { - valType: 'number', - min: -2, - max: 3, - dflt: 0, - role: 'style', - description: 'Sets the y position (in normalized coordinates) of the slider.' - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'top', - role: 'info', - description: [ - 'Sets the slider\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the range selector.' - ].join(' ') + valType: 'enumerated', + values: ['left', 'center', 'right'], + dflt: 'left', + role: 'info', + description: [ + 'The alignment of the value readout relative to the length of the slider.' + ].join(' ') }, - transition: { - duration: { - valType: 'number', - role: 'info', - min: 0, - dflt: 150, - description: 'Sets the duration of the slider transition' - }, - easing: { - valType: 'enumerated', - values: animationAttrs.transition.easing.values, - role: 'info', - dflt: 'cubic-in-out', - description: 'Sets the easing function of the slider transition' - }, + offset: { + valType: 'number', + dflt: 10, + role: 'info', + description: [ + 'The amount of space, in pixels, between the current value label', + 'and the slider.' + ].join(' ') }, - currentvalue: { - visible: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Shows the currently-selected value above the slider.' - ].join(' ') - }, - - xanchor: { - valType: 'enumerated', - values: ['left', 'center', 'right'], - dflt: 'left', - role: 'info', - description: [ - 'The alignment of the value readout relative to the length of the slider.' - ].join(' ') - }, - - offset: { - valType: 'number', - dflt: 10, - role: 'info', - description: [ - 'The amount of space, in pixels, between the current value label', - 'and the slider.' - ].join(' ') - }, - - prefix: { - valType: 'string', - role: 'info', - description: 'When currentvalue.visible is true, this sets the prefix of the label.' - }, - - suffix: { - valType: 'string', - role: 'info', - description: 'When currentvalue.visible is true, this sets the suffix of the label.' - }, + prefix: { + valType: 'string', + role: 'info', + description: 'When currentvalue.visible is true, this sets the prefix of the label.' + }, - font: extendFlat({}, fontAttrs, { - description: 'Sets the font of the current value label text.' - }), + suffix: { + valType: 'string', + role: 'info', + description: 'When currentvalue.visible is true, this sets the suffix of the label.' }, font: extendFlat({}, fontAttrs, { - description: 'Sets the font of the slider step labels.' - }), + description: 'Sets the font of the current value label text.' + }) + }, - activebgcolor: { - valType: 'color', - role: 'style', - dflt: constants.gripBgActiveColor, - description: [ - 'Sets the background color of the slider grip', - 'while dragging.' - ].join(' ') - }, - bgcolor: { - valType: 'color', - role: 'style', - dflt: constants.railBgColor, - description: 'Sets the background color of the slider.' - }, - bordercolor: { - valType: 'color', - dflt: constants.railBorderColor, - role: 'style', - description: 'Sets the color of the border enclosing the slider.' - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: constants.railBorderWidth, - role: 'style', - description: 'Sets the width (in px) of the border enclosing the slider.' - }, - ticklen: { - valType: 'number', - min: 0, - dflt: constants.tickLength, - role: 'style', - description: 'Sets the length in pixels of step tick marks' - }, - tickcolor: { - valType: 'color', - dflt: constants.tickColor, - role: 'style', - description: 'Sets the color of the border enclosing the slider.' - }, - tickwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the tick width (in px).' - }, - minorticklen: { - valType: 'number', - min: 0, - dflt: constants.minorTickLength, - role: 'style', - description: 'Sets the length in pixels of minor step tick marks' - }, -}; + font: extendFlat({}, fontAttrs, { + description: 'Sets the font of the slider step labels.' + }), + + activebgcolor: { + valType: 'color', + role: 'style', + dflt: constants.gripBgActiveColor, + description: [ + 'Sets the background color of the slider grip', + 'while dragging.' + ].join(' ') + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: constants.railBgColor, + description: 'Sets the background color of the slider.' + }, + bordercolor: { + valType: 'color', + dflt: constants.railBorderColor, + role: 'style', + description: 'Sets the color of the border enclosing the slider.' + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: constants.railBorderWidth, + role: 'style', + description: 'Sets the width (in px) of the border enclosing the slider.' + }, + ticklen: { + valType: 'number', + min: 0, + dflt: constants.tickLength, + role: 'style', + description: 'Sets the length in pixels of step tick marks' + }, + tickcolor: { + valType: 'color', + dflt: constants.tickColor, + role: 'style', + description: 'Sets the color of the border enclosing the slider.' + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the tick width (in px).' + }, + minorticklen: { + valType: 'number', + min: 0, + dflt: constants.minorTickLength, + role: 'style', + description: 'Sets the length in pixels of minor step tick marks' + } +} diff --git a/src/components/sliders/constants.js b/src/components/sliders/constants.js index fedd7c088b5..d2d6a18d8cb 100644 --- a/src/components/sliders/constants.js +++ b/src/components/sliders/constants.js @@ -6,90 +6,88 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { // layout attribute name - name: 'sliders', + name: 'sliders', // class names - containerClassName: 'slider-container', - groupClassName: 'slider-group', - inputAreaClass: 'slider-input-area', - railRectClass: 'slider-rail-rect', - railTouchRectClass: 'slider-rail-touch-rect', - gripRectClass: 'slider-grip-rect', - tickRectClass: 'slider-tick-rect', - inputProxyClass: 'slider-input-proxy', - labelsClass: 'slider-labels', - labelGroupClass: 'slider-label-group', - labelClass: 'slider-label', - currentValueClass: 'slider-current-value', - - railHeight: 5, + containerClassName: 'slider-container', + groupClassName: 'slider-group', + inputAreaClass: 'slider-input-area', + railRectClass: 'slider-rail-rect', + railTouchRectClass: 'slider-rail-touch-rect', + gripRectClass: 'slider-grip-rect', + tickRectClass: 'slider-tick-rect', + inputProxyClass: 'slider-input-proxy', + labelsClass: 'slider-labels', + labelGroupClass: 'slider-label-group', + labelClass: 'slider-label', + currentValueClass: 'slider-current-value', + + railHeight: 5, // DOM attribute name in button group keeping track // of active update menu - menuIndexAttrName: 'slider-active-index', + menuIndexAttrName: 'slider-active-index', // id root pass to Plots.autoMargin - autoMarginIdRoot: 'slider-', + autoMarginIdRoot: 'slider-', // min item width / height - minWidth: 30, - minHeight: 30, + minWidth: 30, + minHeight: 30, // padding around item text - textPadX: 40, + textPadX: 40, // font size to height scale - fontSizeToHeight: 1.3, + fontSizeToHeight: 1.3, // arrow offset off right edge - arrowOffsetX: 4, + arrowOffsetX: 4, - railRadius: 2, - railWidth: 5, - railBorder: 4, - railBorderWidth: 1, - railBorderColor: '#bec8d9', - railBgColor: '#f8fafc', + railRadius: 2, + railWidth: 5, + railBorder: 4, + railBorderWidth: 1, + railBorderColor: '#bec8d9', + railBgColor: '#f8fafc', // The distance of the rail from the edge of the touchable area // Slightly less than the step inset because of the curved edges // of the rail - railInset: 8, + railInset: 8, // The distance from the extremal tick marks to the edge of the // touchable area. This is basically the same as the grip radius, // but for other styles it wouldn't really need to be. - stepInset: 10, + stepInset: 10, - gripRadius: 10, - gripWidth: 20, - gripHeight: 20, - gripBorder: 20, - gripBorderWidth: 1, - gripBorderColor: '#bec8d9', - gripBgColor: '#f6f8fa', - gripBgActiveColor: '#dbdde0', + gripRadius: 10, + gripWidth: 20, + gripHeight: 20, + gripBorder: 20, + gripBorderWidth: 1, + gripBorderColor: '#bec8d9', + gripBgColor: '#f6f8fa', + gripBgActiveColor: '#dbdde0', - labelPadding: 8, - labelOffset: 0, + labelPadding: 8, + labelOffset: 0, - tickWidth: 1, - tickColor: '#333', - tickOffset: 25, - tickLength: 7, + tickWidth: 1, + tickColor: '#333', + tickOffset: 25, + tickLength: 7, - minorTickOffset: 25, - minorTickColor: '#333', - minorTickLength: 4, + minorTickOffset: 25, + minorTickColor: '#333', + minorTickLength: 4, // Extra space below the current value label: - currentValuePadding: 8, - currentValueInset: 0, -}; + currentValuePadding: 8, + currentValueInset: 0 +} diff --git a/src/components/sliders/defaults.js b/src/components/sliders/defaults.js index b2fed316c7b..bffd6d3c301 100644 --- a/src/components/sliders/defaults.js +++ b/src/components/sliders/defaults.js @@ -6,106 +6,104 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); +var Lib = require('../../lib') +var handleArrayContainerDefaults = require('../../plots/array_container_defaults') -var attributes = require('./attributes'); -var constants = require('./constants'); +var attributes = require('./attributes') +var constants = require('./constants') -var name = constants.name; -var stepAttrs = attributes.steps; +var name = constants.name +var stepAttrs = attributes.steps +module.exports = function slidersDefaults (layoutIn, layoutOut) { + var opts = { + name: name, + handleItemDefaults: sliderDefaults + } -module.exports = function slidersDefaults(layoutIn, layoutOut) { - var opts = { - name: name, - handleItemDefaults: sliderDefaults - }; - - handleArrayContainerDefaults(layoutIn, layoutOut, opts); -}; + handleArrayContainerDefaults(layoutIn, layoutOut, opts) +} -function sliderDefaults(sliderIn, sliderOut, layoutOut) { +function sliderDefaults (sliderIn, sliderOut, layoutOut) { + function coerce (attr, dflt) { + return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt); - } + var steps = stepsDefaults(sliderIn, sliderOut) - var steps = stepsDefaults(sliderIn, sliderOut); + var visible = coerce('visible', steps.length > 0) + if (!visible) return - var visible = coerce('visible', steps.length > 0); - if(!visible) return; + coerce('active') - coerce('active'); + coerce('x') + coerce('y') + Lib.noneOrAll(sliderIn, sliderOut, ['x', 'y']) - coerce('x'); - coerce('y'); - Lib.noneOrAll(sliderIn, sliderOut, ['x', 'y']); + coerce('xanchor') + coerce('yanchor') - coerce('xanchor'); - coerce('yanchor'); + coerce('len') + coerce('lenmode') - coerce('len'); - coerce('lenmode'); + coerce('pad.t') + coerce('pad.r') + coerce('pad.b') + coerce('pad.l') - coerce('pad.t'); - coerce('pad.r'); - coerce('pad.b'); - coerce('pad.l'); + Lib.coerceFont(coerce, 'font', layoutOut.font) - Lib.coerceFont(coerce, 'font', layoutOut.font); + var currentValueIsVisible = coerce('currentvalue.visible') - var currentValueIsVisible = coerce('currentvalue.visible'); + if (currentValueIsVisible) { + coerce('currentvalue.xanchor') + coerce('currentvalue.prefix') + coerce('currentvalue.suffix') + coerce('currentvalue.offset') - if(currentValueIsVisible) { - coerce('currentvalue.xanchor'); - coerce('currentvalue.prefix'); - coerce('currentvalue.suffix'); - coerce('currentvalue.offset'); + Lib.coerceFont(coerce, 'currentvalue.font', sliderOut.font) + } - Lib.coerceFont(coerce, 'currentvalue.font', sliderOut.font); - } + coerce('transition.duration') + coerce('transition.easing') - coerce('transition.duration'); - coerce('transition.easing'); - - coerce('bgcolor'); - coerce('activebgcolor'); - coerce('bordercolor'); - coerce('borderwidth'); - coerce('ticklen'); - coerce('tickwidth'); - coerce('tickcolor'); - coerce('minorticklen'); + coerce('bgcolor') + coerce('activebgcolor') + coerce('bordercolor') + coerce('borderwidth') + coerce('ticklen') + coerce('tickwidth') + coerce('tickcolor') + coerce('minorticklen') } -function stepsDefaults(sliderIn, sliderOut) { - var valuesIn = sliderIn.steps || [], - valuesOut = sliderOut.steps = []; +function stepsDefaults (sliderIn, sliderOut) { + var valuesIn = sliderIn.steps || [], + valuesOut = sliderOut.steps = [] - var valueIn, valueOut; + var valueIn, valueOut - function coerce(attr, dflt) { - return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt) + } - for(var i = 0; i < valuesIn.length; i++) { - valueIn = valuesIn[i]; - valueOut = {}; + for (var i = 0; i < valuesIn.length; i++) { + valueIn = valuesIn[i] + valueOut = {} - if(!Lib.isPlainObject(valueIn) || !Array.isArray(valueIn.args)) { - continue; - } + if (!Lib.isPlainObject(valueIn) || !Array.isArray(valueIn.args)) { + continue + } - coerce('method'); - coerce('args'); - coerce('label', 'step-' + i); - coerce('value', valueOut.label); + coerce('method') + coerce('args') + coerce('label', 'step-' + i) + coerce('value', valueOut.label) - valuesOut.push(valueOut); - } + valuesOut.push(valueOut) + } - return valuesOut; + return valuesOut } diff --git a/src/components/sliders/draw.js b/src/components/sliders/draw.js index 50c2ef0f35e..6520168ad61 100644 --- a/src/components/sliders/draw.js +++ b/src/components/sliders/draw.js @@ -6,595 +6,590 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Plots = require('../../plots/plots') +var Color = require('../color') +var Drawing = require('../drawing') +var svgTextUtils = require('../../lib/svg_text_utils') +var anchorUtils = require('../legend/anchor_utils') -var Plots = require('../../plots/plots'); -var Color = require('../color'); -var Drawing = require('../drawing'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var anchorUtils = require('../legend/anchor_utils'); +var constants = require('./constants') -var constants = require('./constants'); - - -module.exports = function draw(gd) { - var fullLayout = gd._fullLayout, - sliderData = makeSliderData(fullLayout); +module.exports = function draw (gd) { + var fullLayout = gd._fullLayout, + sliderData = makeSliderData(fullLayout) // draw a container for *all* sliders: - var sliders = fullLayout._infolayer + var sliders = fullLayout._infolayer .selectAll('g.' + constants.containerClassName) - .data(sliderData.length > 0 ? [0] : []); + .data(sliderData.length > 0 ? [0] : []) - sliders.enter().append('g') + sliders.enter().append('g') .classed(constants.containerClassName, true) - .style('cursor', 'ew-resize'); + .style('cursor', 'ew-resize') - sliders.exit().remove(); + sliders.exit().remove() // If no more sliders, clear the margisn: - if(sliders.exit().size()) clearPushMargins(gd); + if (sliders.exit().size()) clearPushMargins(gd) // Return early if no menus visible: - if(sliderData.length === 0) return; + if (sliderData.length === 0) return - var sliderGroups = sliders.selectAll('g.' + constants.groupClassName) - .data(sliderData, keyFunction); + var sliderGroups = sliders.selectAll('g.' + constants.groupClassName) + .data(sliderData, keyFunction) - sliderGroups.enter().append('g') - .classed(constants.groupClassName, true); + sliderGroups.enter().append('g') + .classed(constants.groupClassName, true) - sliderGroups.exit().each(function(sliderOpts) { - d3.select(this).remove(); + sliderGroups.exit().each(function (sliderOpts) { + d3.select(this).remove() - sliderOpts._commandObserver.remove(); - delete sliderOpts._commandObserver; + sliderOpts._commandObserver.remove() + delete sliderOpts._commandObserver - Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index); - }); + Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index) + }) // Find the dimensions of the sliders: - for(var i = 0; i < sliderData.length; i++) { - var sliderOpts = sliderData[i]; - findDimensions(gd, sliderOpts); - } + for (var i = 0; i < sliderData.length; i++) { + var sliderOpts = sliderData[i] + findDimensions(gd, sliderOpts) + } - sliderGroups.each(function(sliderOpts) { + sliderGroups.each(function (sliderOpts) { // If it has fewer than two options, it's not really a slider: - if(sliderOpts.steps.length < 2) return; + if (sliderOpts.steps.length < 2) return - var gSlider = d3.select(this); + var gSlider = d3.select(this) - computeLabelSteps(sliderOpts); + computeLabelSteps(sliderOpts) - Plots.manageCommandObserver(gd, sliderOpts, sliderOpts.steps, function(data) { + Plots.manageCommandObserver(gd, sliderOpts, sliderOpts.steps, function (data) { // NB: Same as below. This is *not* always the same as sliderOpts since // if a new set of steps comes in, the reference in this callback would // be invalid. We need to refetch it from the slider group, which is // the join data that creates this slider. So if this slider still exists, // the group should be valid, *to the best of my knowledge.* If not, // we'd have to look it up by d3 data join index/key. - var opts = gSlider.data()[0]; + var opts = gSlider.data()[0] - if(opts.active === data.index) return; - if(opts._dragging) return; + if (opts.active === data.index) return + if (opts._dragging) return - setActive(gd, gSlider, opts, data.index, false, true); - }); + setActive(gd, gSlider, opts, data.index, false, true) + }) - drawSlider(gd, d3.select(this), sliderOpts); + drawSlider(gd, d3.select(this), sliderOpts) // makeInputProxy(gd, d3.select(this), sliderOpts); - }); -}; + }) +} /* function makeInputProxy(gd, sliderGroup, sliderOpts) { sliderOpts.inputProxy = gd._fullLayout._paperdiv.selectAll('input.' + constants.inputProxyClass) .data([0]); -}*/ +} */ // This really only just filters by visibility: -function makeSliderData(fullLayout) { - var contOpts = fullLayout[constants.name], - sliderData = []; - - for(var i = 0; i < contOpts.length; i++) { - var item = contOpts[i]; - if(!item.visible || !item.steps.length) continue; - sliderData.push(item); - } +function makeSliderData (fullLayout) { + var contOpts = fullLayout[constants.name], + sliderData = [] - return sliderData; + for (var i = 0; i < contOpts.length; i++) { + var item = contOpts[i] + if (!item.visible || !item.steps.length) continue + sliderData.push(item) + } + + return sliderData } // This is set in the defaults step: -function keyFunction(opts) { - return opts._index; +function keyFunction (opts) { + return opts._index } // Compute the dimensions (mutates sliderOpts): -function findDimensions(gd, sliderOpts) { - var sliderLabels = gd._tester.selectAll('g.' + constants.labelGroupClass) - .data(sliderOpts.steps); +function findDimensions (gd, sliderOpts) { + var sliderLabels = gd._tester.selectAll('g.' + constants.labelGroupClass) + .data(sliderOpts.steps) - sliderLabels.enter().append('g') - .classed(constants.labelGroupClass, true); + sliderLabels.enter().append('g') + .classed(constants.labelGroupClass, true) // loop over fake buttons to find width / height - var maxLabelWidth = 0; - var labelHeight = 0; - sliderLabels.each(function(stepOpts) { - var labelGroup = d3.select(this); + var maxLabelWidth = 0 + var labelHeight = 0 + sliderLabels.each(function (stepOpts) { + var labelGroup = d3.select(this) - var text = drawLabel(labelGroup, {step: stepOpts}, sliderOpts); + var text = drawLabel(labelGroup, {step: stepOpts}, sliderOpts) - var tWidth = (text.node() && Drawing.bBox(text.node()).width) || 0; + var tWidth = (text.node() && Drawing.bBox(text.node()).width) || 0 // This just overwrites with the last. Which is fine as long as // the bounding box (probably incorrectly) measures the text *on // a single line*: - labelHeight = (text.node() && Drawing.bBox(text.node()).height) || 0; + labelHeight = (text.node() && Drawing.bBox(text.node()).height) || 0 - maxLabelWidth = Math.max(maxLabelWidth, tWidth); - }); + maxLabelWidth = Math.max(maxLabelWidth, tWidth) + }) - sliderLabels.remove(); + sliderLabels.remove() - sliderOpts.inputAreaWidth = Math.max( + sliderOpts.inputAreaWidth = Math.max( constants.railWidth, constants.gripHeight - ); + ) - sliderOpts.currentValueMaxWidth = 0; - sliderOpts.currentValueHeight = 0; - sliderOpts.currentValueTotalHeight = 0; + sliderOpts.currentValueMaxWidth = 0 + sliderOpts.currentValueHeight = 0 + sliderOpts.currentValueTotalHeight = 0 - if(sliderOpts.currentvalue.visible) { + if (sliderOpts.currentvalue.visible) { // Get the dimensions of the current value label: - var dummyGroup = gd._tester.append('g'); + var dummyGroup = gd._tester.append('g') - sliderLabels.each(function(stepOpts) { - var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label); - var curValSize = (curValPrefix.node() && Drawing.bBox(curValPrefix.node())) || {width: 0, height: 0}; - sliderOpts.currentValueMaxWidth = Math.max(sliderOpts.currentValueMaxWidth, Math.ceil(curValSize.width)); - sliderOpts.currentValueHeight = Math.max(sliderOpts.currentValueHeight, Math.ceil(curValSize.height)); - }); + sliderLabels.each(function (stepOpts) { + var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label) + var curValSize = (curValPrefix.node() && Drawing.bBox(curValPrefix.node())) || {width: 0, height: 0} + sliderOpts.currentValueMaxWidth = Math.max(sliderOpts.currentValueMaxWidth, Math.ceil(curValSize.width)) + sliderOpts.currentValueHeight = Math.max(sliderOpts.currentValueHeight, Math.ceil(curValSize.height)) + }) - sliderOpts.currentValueTotalHeight = sliderOpts.currentValueHeight + sliderOpts.currentvalue.offset; + sliderOpts.currentValueTotalHeight = sliderOpts.currentValueHeight + sliderOpts.currentvalue.offset - dummyGroup.remove(); - } + dummyGroup.remove() + } - var graphSize = gd._fullLayout._size; - sliderOpts.lx = graphSize.l + graphSize.w * sliderOpts.x; - sliderOpts.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y); + var graphSize = gd._fullLayout._size + sliderOpts.lx = graphSize.l + graphSize.w * sliderOpts.x + sliderOpts.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y) - if(sliderOpts.lenmode === 'fraction') { + if (sliderOpts.lenmode === 'fraction') { // fraction: - sliderOpts.outerLength = Math.round(graphSize.w * sliderOpts.len); - } else { + sliderOpts.outerLength = Math.round(graphSize.w * sliderOpts.len) + } else { // pixels: - sliderOpts.outerLength = sliderOpts.len; - } + sliderOpts.outerLength = sliderOpts.len + } // Set the length-wise padding so that the grip ends up *on* the end of // the bar when at either extreme - sliderOpts.lenPad = Math.round(constants.gripWidth * 0.5); + sliderOpts.lenPad = Math.round(constants.gripWidth * 0.5) // The length of the rail, *excluding* padding on either end: - sliderOpts.inputAreaStart = 0; - sliderOpts.inputAreaLength = Math.round(sliderOpts.outerLength - sliderOpts.pad.l - sliderOpts.pad.r); - - var textableInputLength = sliderOpts.inputAreaLength - 2 * constants.stepInset; - var availableSpacePerLabel = textableInputLength / (sliderOpts.steps.length - 1); - var computedSpacePerLabel = maxLabelWidth + constants.labelPadding; - sliderOpts.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel)); - sliderOpts.labelHeight = labelHeight; - - sliderOpts.height = sliderOpts.currentValueTotalHeight + constants.tickOffset + sliderOpts.ticklen + constants.labelOffset + sliderOpts.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b; - - var xanchor = 'left'; - if(anchorUtils.isRightAnchor(sliderOpts)) { - sliderOpts.lx -= sliderOpts.outerLength; - xanchor = 'right'; - } - if(anchorUtils.isCenterAnchor(sliderOpts)) { - sliderOpts.lx -= sliderOpts.outerLength / 2; - xanchor = 'center'; - } - - var yanchor = 'top'; - if(anchorUtils.isBottomAnchor(sliderOpts)) { - sliderOpts.ly -= sliderOpts.height; - yanchor = 'bottom'; - } - if(anchorUtils.isMiddleAnchor(sliderOpts)) { - sliderOpts.ly -= sliderOpts.height / 2; - yanchor = 'middle'; - } - - sliderOpts.outerLength = Math.ceil(sliderOpts.outerLength); - sliderOpts.height = Math.ceil(sliderOpts.height); - sliderOpts.lx = Math.round(sliderOpts.lx); - sliderOpts.ly = Math.round(sliderOpts.ly); - - Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index, { - x: sliderOpts.x, - y: sliderOpts.y, - l: sliderOpts.outerLength * ({right: 1, center: 0.5}[xanchor] || 0), - r: sliderOpts.outerLength * ({left: 1, center: 0.5}[xanchor] || 0), - b: sliderOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0), - t: sliderOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) - }); + sliderOpts.inputAreaStart = 0 + sliderOpts.inputAreaLength = Math.round(sliderOpts.outerLength - sliderOpts.pad.l - sliderOpts.pad.r) + + var textableInputLength = sliderOpts.inputAreaLength - 2 * constants.stepInset + var availableSpacePerLabel = textableInputLength / (sliderOpts.steps.length - 1) + var computedSpacePerLabel = maxLabelWidth + constants.labelPadding + sliderOpts.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel)) + sliderOpts.labelHeight = labelHeight + + sliderOpts.height = sliderOpts.currentValueTotalHeight + constants.tickOffset + sliderOpts.ticklen + constants.labelOffset + sliderOpts.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b + + var xanchor = 'left' + if (anchorUtils.isRightAnchor(sliderOpts)) { + sliderOpts.lx -= sliderOpts.outerLength + xanchor = 'right' + } + if (anchorUtils.isCenterAnchor(sliderOpts)) { + sliderOpts.lx -= sliderOpts.outerLength / 2 + xanchor = 'center' + } + + var yanchor = 'top' + if (anchorUtils.isBottomAnchor(sliderOpts)) { + sliderOpts.ly -= sliderOpts.height + yanchor = 'bottom' + } + if (anchorUtils.isMiddleAnchor(sliderOpts)) { + sliderOpts.ly -= sliderOpts.height / 2 + yanchor = 'middle' + } + + sliderOpts.outerLength = Math.ceil(sliderOpts.outerLength) + sliderOpts.height = Math.ceil(sliderOpts.height) + sliderOpts.lx = Math.round(sliderOpts.lx) + sliderOpts.ly = Math.round(sliderOpts.ly) + + Plots.autoMargin(gd, constants.autoMarginIdRoot + sliderOpts._index, { + x: sliderOpts.x, + y: sliderOpts.y, + l: sliderOpts.outerLength * ({right: 1, center: 0.5}[xanchor] || 0), + r: sliderOpts.outerLength * ({left: 1, center: 0.5}[xanchor] || 0), + b: sliderOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0), + t: sliderOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) + }) } -function drawSlider(gd, sliderGroup, sliderOpts) { +function drawSlider (gd, sliderGroup, sliderOpts) { // This is related to the other long notes in this file regarding what happens // when slider steps disappear. This particular fix handles what happens when // the *current* slider step is removed. The drawing functions will error out // when they fail to find it, so the fix for now is that it will just draw the // slider in the first position but will not execute the command. - if(sliderOpts.active >= sliderOpts.steps.length) { - sliderOpts.active = 0; - } + if (sliderOpts.active >= sliderOpts.steps.length) { + sliderOpts.active = 0 + } // These are carefully ordered for proper z-ordering: - sliderGroup + sliderGroup .call(drawCurrentValue, sliderOpts) .call(drawRail, sliderOpts) .call(drawLabelGroup, sliderOpts) .call(drawTicks, sliderOpts) .call(drawTouchRect, gd, sliderOpts) - .call(drawGrip, gd, sliderOpts); + .call(drawGrip, gd, sliderOpts) // Position the rectangle: - Drawing.setTranslate(sliderGroup, sliderOpts.lx + sliderOpts.pad.l, sliderOpts.ly + sliderOpts.pad.t); - - sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), false); - sliderGroup.call(drawCurrentValue, sliderOpts); + Drawing.setTranslate(sliderGroup, sliderOpts.lx + sliderOpts.pad.l, sliderOpts.ly + sliderOpts.pad.t) + sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), false) + sliderGroup.call(drawCurrentValue, sliderOpts) } -function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) { - if(!sliderOpts.currentvalue.visible) return; +function drawCurrentValue (sliderGroup, sliderOpts, valueOverride) { + if (!sliderOpts.currentvalue.visible) return - var x0, textAnchor; - var text = sliderGroup.selectAll('text') - .data([0]); + var x0, textAnchor + var text = sliderGroup.selectAll('text') + .data([0]) - switch(sliderOpts.currentvalue.xanchor) { - case 'right': + switch (sliderOpts.currentvalue.xanchor) { + case 'right': // This is anchored left and adjusted by the width of the longest label // so that the prefix doesn't move. The goal of this is to emphasize // what's actually changing and make the update less distracting. - x0 = sliderOpts.inputAreaLength - constants.currentValueInset - sliderOpts.currentValueMaxWidth; - textAnchor = 'left'; - break; - case 'center': - x0 = sliderOpts.inputAreaLength * 0.5; - textAnchor = 'middle'; - break; - default: - x0 = constants.currentValueInset; - textAnchor = 'left'; - } - - text.enter().append('text') + x0 = sliderOpts.inputAreaLength - constants.currentValueInset - sliderOpts.currentValueMaxWidth + textAnchor = 'left' + break + case 'center': + x0 = sliderOpts.inputAreaLength * 0.5 + textAnchor = 'middle' + break + default: + x0 = constants.currentValueInset + textAnchor = 'left' + } + + text.enter().append('text') .classed(constants.labelClass, true) .classed('user-select-none', true) - .attr('text-anchor', textAnchor); + .attr('text-anchor', textAnchor) - var str = sliderOpts.currentvalue.prefix ? sliderOpts.currentvalue.prefix : ''; + var str = sliderOpts.currentvalue.prefix ? sliderOpts.currentvalue.prefix : '' - if(typeof valueOverride === 'string') { - str += valueOverride; - } else { - var curVal = sliderOpts.steps[sliderOpts.active].label; - str += curVal; - } + if (typeof valueOverride === 'string') { + str += valueOverride + } else { + var curVal = sliderOpts.steps[sliderOpts.active].label + str += curVal + } - if(sliderOpts.currentvalue.suffix) { - str += sliderOpts.currentvalue.suffix; - } + if (sliderOpts.currentvalue.suffix) { + str += sliderOpts.currentvalue.suffix + } - text.call(Drawing.font, sliderOpts.currentvalue.font) + text.call(Drawing.font, sliderOpts.currentvalue.font) .text(str) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans) - Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight); + Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight) - return text; + return text } -function drawGrip(sliderGroup, gd, sliderOpts) { - var grip = sliderGroup.selectAll('rect.' + constants.gripRectClass) - .data([0]); +function drawGrip (sliderGroup, gd, sliderOpts) { + var grip = sliderGroup.selectAll('rect.' + constants.gripRectClass) + .data([0]) - grip.enter().append('rect') + grip.enter().append('rect') .classed(constants.gripRectClass, true) .call(attachGripEvents, gd, sliderGroup, sliderOpts) - .style('pointer-events', 'all'); - - grip.attr({ - width: constants.gripWidth, - height: constants.gripHeight, - rx: constants.gripRadius, - ry: constants.gripRadius, - }) + .style('pointer-events', 'all') + + grip.attr({ + width: constants.gripWidth, + height: constants.gripHeight, + rx: constants.gripRadius, + ry: constants.gripRadius + }) .call(Color.stroke, sliderOpts.bordercolor) .call(Color.fill, sliderOpts.bgcolor) - .style('stroke-width', sliderOpts.borderwidth + 'px'); + .style('stroke-width', sliderOpts.borderwidth + 'px') } -function drawLabel(item, data, sliderOpts) { - var text = item.selectAll('text') - .data([0]); +function drawLabel (item, data, sliderOpts) { + var text = item.selectAll('text') + .data([0]) - text.enter().append('text') + text.enter().append('text') .classed(constants.labelClass, true) .classed('user-select-none', true) - .attr('text-anchor', 'middle'); + .attr('text-anchor', 'middle') - text.call(Drawing.font, sliderOpts.font) + text.call(Drawing.font, sliderOpts.font) .text(data.step.label) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans) - return text; + return text } -function drawLabelGroup(sliderGroup, sliderOpts) { - var labels = sliderGroup.selectAll('g.' + constants.labelsClass) - .data([0]); +function drawLabelGroup (sliderGroup, sliderOpts) { + var labels = sliderGroup.selectAll('g.' + constants.labelsClass) + .data([0]) - labels.enter().append('g') - .classed(constants.labelsClass, true); + labels.enter().append('g') + .classed(constants.labelsClass, true) - var labelItems = labels.selectAll('g.' + constants.labelGroupClass) - .data(sliderOpts.labelSteps); + var labelItems = labels.selectAll('g.' + constants.labelGroupClass) + .data(sliderOpts.labelSteps) - labelItems.enter().append('g') - .classed(constants.labelGroupClass, true); + labelItems.enter().append('g') + .classed(constants.labelGroupClass, true) - labelItems.exit().remove(); + labelItems.exit().remove() - labelItems.each(function(d) { - var item = d3.select(this); + labelItems.each(function (d) { + var item = d3.select(this) - item.call(drawLabel, d, sliderOpts); + item.call(drawLabel, d, sliderOpts) - Drawing.setTranslate(item, + Drawing.setTranslate(item, normalizedValueToPosition(sliderOpts, d.fraction), constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight + constants.labelOffset + sliderOpts.currentValueTotalHeight - ); - }); - + ) + }) } -function handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, doTransition) { - var quantizedPosition = Math.round(normalizedPosition * (sliderOpts.steps.length - 1)); +function handleInput (gd, sliderGroup, sliderOpts, normalizedPosition, doTransition) { + var quantizedPosition = Math.round(normalizedPosition * (sliderOpts.steps.length - 1)) - if(quantizedPosition !== sliderOpts.active) { - setActive(gd, sliderGroup, sliderOpts, quantizedPosition, true, doTransition); - } + if (quantizedPosition !== sliderOpts.active) { + setActive(gd, sliderGroup, sliderOpts, quantizedPosition, true, doTransition) + } } -function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition) { - var previousActive = sliderOpts.active; - sliderOpts._input.active = sliderOpts.active = index; +function setActive (gd, sliderGroup, sliderOpts, index, doCallback, doTransition) { + var previousActive = sliderOpts.active + sliderOpts._input.active = sliderOpts.active = index - var step = sliderOpts.steps[sliderOpts.active]; + var step = sliderOpts.steps[sliderOpts.active] - sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), doTransition); - sliderGroup.call(drawCurrentValue, sliderOpts); + sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), doTransition) + sliderGroup.call(drawCurrentValue, sliderOpts) - gd.emit('plotly_sliderchange', { - slider: sliderOpts, - step: sliderOpts.steps[sliderOpts.active], - interaction: doCallback, - previousActive: previousActive - }); + gd.emit('plotly_sliderchange', { + slider: sliderOpts, + step: sliderOpts.steps[sliderOpts.active], + interaction: doCallback, + previousActive: previousActive + }) - if(step && step.method && doCallback) { - if(sliderGroup._nextMethod) { + if (step && step.method && doCallback) { + if (sliderGroup._nextMethod) { // If we've already queued up an update, just overwrite it with the most recent: - sliderGroup._nextMethod.step = step; - sliderGroup._nextMethod.doCallback = doCallback; - sliderGroup._nextMethod.doTransition = doTransition; - } else { - sliderGroup._nextMethod = {step: step, doCallback: doCallback, doTransition: doTransition}; - sliderGroup._nextMethodRaf = window.requestAnimationFrame(function() { - var _step = sliderGroup._nextMethod.step; - if(!_step.method) return; - - Plots.executeAPICommand(gd, _step.method, _step.args); - - sliderGroup._nextMethod = null; - sliderGroup._nextMethodRaf = null; - }); - } + sliderGroup._nextMethod.step = step + sliderGroup._nextMethod.doCallback = doCallback + sliderGroup._nextMethod.doTransition = doTransition + } else { + sliderGroup._nextMethod = {step: step, doCallback: doCallback, doTransition: doTransition} + sliderGroup._nextMethodRaf = window.requestAnimationFrame(function () { + var _step = sliderGroup._nextMethod.step + if (!_step.method) return + + Plots.executeAPICommand(gd, _step.method, _step.args) + + sliderGroup._nextMethod = null + sliderGroup._nextMethodRaf = null + }) } + } } -function attachGripEvents(item, gd, sliderGroup) { - var node = sliderGroup.node(); - var $gd = d3.select(gd); +function attachGripEvents (item, gd, sliderGroup) { + var node = sliderGroup.node() + var $gd = d3.select(gd) // NB: This is *not* the same as sliderOpts itself! These callbacks // are in a closure so this array won't actually be correct if the // steps have changed since this was initialized. The sliderGroup, // however, has not changed since that *is* the slider, so it must // be present to receive mouse events. - function getSliderOpts() { - return sliderGroup.data()[0]; - } + function getSliderOpts () { + return sliderGroup.data()[0] + } + + item.on('mousedown', function () { + var sliderOpts = getSliderOpts() + gd.emit('plotly_sliderstart', {slider: sliderOpts}) + + var grip = sliderGroup.select('.' + constants.gripRectClass) + + d3.event.stopPropagation() + d3.event.preventDefault() + grip.call(Color.fill, sliderOpts.activebgcolor) - item.on('mousedown', function() { - var sliderOpts = getSliderOpts(); - gd.emit('plotly_sliderstart', {slider: sliderOpts}); - - var grip = sliderGroup.select('.' + constants.gripRectClass); - - d3.event.stopPropagation(); - d3.event.preventDefault(); - grip.call(Color.fill, sliderOpts.activebgcolor); - - var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]); - handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true); - sliderOpts._dragging = true; - - $gd.on('mousemove', function() { - var sliderOpts = getSliderOpts(); - var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]); - handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false); - }); - - $gd.on('mouseup', function() { - var sliderOpts = getSliderOpts(); - sliderOpts._dragging = false; - grip.call(Color.fill, sliderOpts.bgcolor); - $gd.on('mouseup', null); - $gd.on('mousemove', null); - - gd.emit('plotly_sliderend', { - slider: sliderOpts, - step: sliderOpts.steps[sliderOpts.active] - }); - }); - }); + var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]) + handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true) + sliderOpts._dragging = true + + $gd.on('mousemove', function () { + var sliderOpts = getSliderOpts() + var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]) + handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false) + }) + + $gd.on('mouseup', function () { + var sliderOpts = getSliderOpts() + sliderOpts._dragging = false + grip.call(Color.fill, sliderOpts.bgcolor) + $gd.on('mouseup', null) + $gd.on('mousemove', null) + + gd.emit('plotly_sliderend', { + slider: sliderOpts, + step: sliderOpts.steps[sliderOpts.active] + }) + }) + }) } -function drawTicks(sliderGroup, sliderOpts) { - var tick = sliderGroup.selectAll('rect.' + constants.tickRectClass) - .data(sliderOpts.steps); +function drawTicks (sliderGroup, sliderOpts) { + var tick = sliderGroup.selectAll('rect.' + constants.tickRectClass) + .data(sliderOpts.steps) - tick.enter().append('rect') - .classed(constants.tickRectClass, true); + tick.enter().append('rect') + .classed(constants.tickRectClass, true) - tick.exit().remove(); + tick.exit().remove() - tick.attr({ - width: sliderOpts.tickwidth + 'px', - 'shape-rendering': 'crispEdges' - }); + tick.attr({ + width: sliderOpts.tickwidth + 'px', + 'shape-rendering': 'crispEdges' + }) - tick.each(function(d, i) { - var isMajor = i % sliderOpts.labelStride === 0; - var item = d3.select(this); + tick.each(function (d, i) { + var isMajor = i % sliderOpts.labelStride === 0 + var item = d3.select(this) - item + item .attr({height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen}) - .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor); + .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor) - Drawing.setTranslate(item, + Drawing.setTranslate(item, normalizedValueToPosition(sliderOpts, i / (sliderOpts.steps.length - 1)) - 0.5 * sliderOpts.tickwidth, (isMajor ? constants.tickOffset : constants.minorTickOffset) + sliderOpts.currentValueTotalHeight - ); - }); - + ) + }) } -function computeLabelSteps(sliderOpts) { - sliderOpts.labelSteps = []; - var i0 = 0; - var nsteps = sliderOpts.steps.length; +function computeLabelSteps (sliderOpts) { + sliderOpts.labelSteps = [] + var i0 = 0 + var nsteps = sliderOpts.steps.length - for(var i = i0; i < nsteps; i += sliderOpts.labelStride) { - sliderOpts.labelSteps.push({ - fraction: i / (nsteps - 1), - step: sliderOpts.steps[i] - }); - } + for (var i = i0; i < nsteps; i += sliderOpts.labelStride) { + sliderOpts.labelSteps.push({ + fraction: i / (nsteps - 1), + step: sliderOpts.steps[i] + }) + } } -function setGripPosition(sliderGroup, sliderOpts, position, doTransition) { - var grip = sliderGroup.select('rect.' + constants.gripRectClass); +function setGripPosition (sliderGroup, sliderOpts, position, doTransition) { + var grip = sliderGroup.select('rect.' + constants.gripRectClass) - var x = normalizedValueToPosition(sliderOpts, position); + var x = normalizedValueToPosition(sliderOpts, position) // If this is true, then *this component* is already invoking its own command // and has triggered its own animation. - if(sliderOpts._invokingCommand) return; + if (sliderOpts._invokingCommand) return - var el = grip; - if(doTransition && sliderOpts.transition.duration > 0) { - el = el.transition() + var el = grip + if (doTransition && sliderOpts.transition.duration > 0) { + el = el.transition() .duration(sliderOpts.transition.duration) - .ease(sliderOpts.transition.easing); - } + .ease(sliderOpts.transition.easing) + } // Drawing.setTranslate doesn't work here becasue of the transition duck-typing. // It's also not necessary because there are no other transitions to preserve. - el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts.currentValueTotalHeight) + ')'); + el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts.currentValueTotalHeight) + ')') } // Convert a number from [0-1] to a pixel position relative to the slider group container: -function normalizedValueToPosition(sliderOpts, normalizedPosition) { - return sliderOpts.inputAreaStart + constants.stepInset + - (sliderOpts.inputAreaLength - 2 * constants.stepInset) * Math.min(1, Math.max(0, normalizedPosition)); +function normalizedValueToPosition (sliderOpts, normalizedPosition) { + return sliderOpts.inputAreaStart + constants.stepInset + + (sliderOpts.inputAreaLength - 2 * constants.stepInset) * Math.min(1, Math.max(0, normalizedPosition)) } // Convert a position relative to the slider group to a nubmer in [0, 1] -function positionToNormalizedValue(sliderOpts, position) { - return Math.min(1, Math.max(0, (position - constants.stepInset - sliderOpts.inputAreaStart) / (sliderOpts.inputAreaLength - 2 * constants.stepInset - 2 * sliderOpts.inputAreaStart))); +function positionToNormalizedValue (sliderOpts, position) { + return Math.min(1, Math.max(0, (position - constants.stepInset - sliderOpts.inputAreaStart) / (sliderOpts.inputAreaLength - 2 * constants.stepInset - 2 * sliderOpts.inputAreaStart))) } -function drawTouchRect(sliderGroup, gd, sliderOpts) { - var rect = sliderGroup.selectAll('rect.' + constants.railTouchRectClass) - .data([0]); +function drawTouchRect (sliderGroup, gd, sliderOpts) { + var rect = sliderGroup.selectAll('rect.' + constants.railTouchRectClass) + .data([0]) - rect.enter().append('rect') + rect.enter().append('rect') .classed(constants.railTouchRectClass, true) .call(attachGripEvents, gd, sliderGroup, sliderOpts) - .style('pointer-events', 'all'); + .style('pointer-events', 'all') - rect.attr({ - width: sliderOpts.inputAreaLength, - height: Math.max(sliderOpts.inputAreaWidth, constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight) - }) + rect.attr({ + width: sliderOpts.inputAreaLength, + height: Math.max(sliderOpts.inputAreaWidth, constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight) + }) .call(Color.fill, sliderOpts.bgcolor) - .attr('opacity', 0); + .attr('opacity', 0) - Drawing.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight); + Drawing.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight) } -function drawRail(sliderGroup, sliderOpts) { - var rect = sliderGroup.selectAll('rect.' + constants.railRectClass) - .data([0]); +function drawRail (sliderGroup, sliderOpts) { + var rect = sliderGroup.selectAll('rect.' + constants.railRectClass) + .data([0]) - rect.enter().append('rect') - .classed(constants.railRectClass, true); + rect.enter().append('rect') + .classed(constants.railRectClass, true) - var computedLength = sliderOpts.inputAreaLength - constants.railInset * 2; + var computedLength = sliderOpts.inputAreaLength - constants.railInset * 2 - rect.attr({ - width: computedLength, - height: constants.railWidth, - rx: constants.railRadius, - ry: constants.railRadius, - 'shape-rendering': 'crispEdges' - }) + rect.attr({ + width: computedLength, + height: constants.railWidth, + rx: constants.railRadius, + ry: constants.railRadius, + 'shape-rendering': 'crispEdges' + }) .call(Color.stroke, sliderOpts.bordercolor) .call(Color.fill, sliderOpts.bgcolor) - .style('stroke-width', sliderOpts.borderwidth + 'px'); + .style('stroke-width', sliderOpts.borderwidth + 'px') - Drawing.setTranslate(rect, + Drawing.setTranslate(rect, constants.railInset, (sliderOpts.inputAreaWidth - constants.railWidth) * 0.5 + sliderOpts.currentValueTotalHeight - ); + ) } -function clearPushMargins(gd) { - var pushMargins = gd._fullLayout._pushmargin || {}, - keys = Object.keys(pushMargins); +function clearPushMargins (gd) { + var pushMargins = gd._fullLayout._pushmargin || {}, + keys = Object.keys(pushMargins) - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; + for (var i = 0; i < keys.length; i++) { + var k = keys[i] - if(k.indexOf(constants.autoMarginIdRoot) !== -1) { - Plots.autoMargin(gd, k); - } + if (k.indexOf(constants.autoMarginIdRoot) !== -1) { + Plots.autoMargin(gd, k) } + } } diff --git a/src/components/sliders/index.js b/src/components/sliders/index.js index 366b9aaa1ad..4a205e9712d 100644 --- a/src/components/sliders/index.js +++ b/src/components/sliders/index.js @@ -6,16 +6,16 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var constants = require('./constants'); +var constants = require('./constants') module.exports = { - moduleType: 'component', - name: constants.name, + moduleType: 'component', + name: constants.name, - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - draw: require('./draw') -}; + draw: require('./draw') +} diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 6fa0e64c6af..f6d8e8b4b1a 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -6,22 +6,20 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Plotly = require('../../plotly') +var Plots = require('../../plots/plots') +var Lib = require('../../lib') +var Drawing = require('../drawing') +var Color = require('../color') +var svgTextUtils = require('../../lib/svg_text_utils') +var interactConstants = require('../../constants/interactions') -var Plotly = require('../../plotly'); -var Plots = require('../../plots/plots'); -var Lib = require('../../lib'); -var Drawing = require('../drawing'); -var Color = require('../color'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var interactConstants = require('../../constants/interactions'); - - -var Titles = module.exports = {}; +var Titles = module.exports = {} /** * Titles - (re)draw titles on the axes and plot: @@ -51,181 +49,180 @@ var Titles = module.exports = {}; * containerGroup - if an svg element already exists to hold this * title, include here. Otherwise it will go in fullLayout._infolayer */ -Titles.draw = function(gd, titleClass, options) { - var cont = options.propContainer, - prop = options.propName, - traceIndex = options.traceIndex, - name = options.dfltName, - avoid = options.avoid || {}, - attributes = options.attributes, - transform = options.transform, - group = options.containerGroup, - - fullLayout = gd._fullLayout, - font = cont.titlefont.family, - fontSize = cont.titlefont.size, - fontColor = cont.titlefont.color, - - opacity = 1, - isplaceholder = false, - txt = cont.title.trim(); - if(txt === '') opacity = 0; - if(txt.match(/Click to enter .+ title/)) { - opacity = 0.2; - isplaceholder = true; - } - - if(!group) { - group = fullLayout._infolayer.selectAll('.g-' + titleClass) - .data([0]); - group.enter().append('g') - .classed('g-' + titleClass, true); - } - - var el = group.selectAll('text') - .data([0]); - el.enter().append('text'); - el.text(txt) +Titles.draw = function (gd, titleClass, options) { + var cont = options.propContainer, + prop = options.propName, + traceIndex = options.traceIndex, + name = options.dfltName, + avoid = options.avoid || {}, + attributes = options.attributes, + transform = options.transform, + group = options.containerGroup, + + fullLayout = gd._fullLayout, + font = cont.titlefont.family, + fontSize = cont.titlefont.size, + fontColor = cont.titlefont.color, + + opacity = 1, + isplaceholder = false, + txt = cont.title.trim() + if (txt === '') opacity = 0 + if (txt.match(/Click to enter .+ title/)) { + opacity = 0.2 + isplaceholder = true + } + + if (!group) { + group = fullLayout._infolayer.selectAll('.g-' + titleClass) + .data([0]) + group.enter().append('g') + .classed('g-' + titleClass, true) + } + + var el = group.selectAll('text') + .data([0]) + el.enter().append('text') + el.text(txt) // this is hacky, but convertToTspans uses the class // to determine whether to rotate mathJax... // so we need to clear out any old class and put the // correct one (only relevant for colorbars, at least // for now) - ie don't use .classed - .attr('class', titleClass); + .attr('class', titleClass) - function titleLayout(titleEl) { - Lib.syncOrAsync([drawTitle, scootTitle], titleEl); - } + function titleLayout (titleEl) { + Lib.syncOrAsync([drawTitle, scootTitle], titleEl) + } - function drawTitle(titleEl) { - titleEl.attr('transform', transform ? + function drawTitle (titleEl) { + titleEl.attr('transform', transform ? 'rotate(' + [transform.rotate, attributes.x, attributes.y] + ') translate(0, ' + transform.offset + ')' : - null); - - titleEl.style({ - 'font-family': font, - 'font-size': d3.round(fontSize, 2) + 'px', - fill: Color.rgb(fontColor), - opacity: opacity * Color.opacity(fontColor), - 'font-weight': Plots.fontWeight - }) + null) + + titleEl.style({ + 'font-family': font, + 'font-size': d3.round(fontSize, 2) + 'px', + fill: Color.rgb(fontColor), + opacity: opacity * Color.opacity(fontColor), + 'font-weight': Plots.fontWeight + }) .attr(attributes) .call(svgTextUtils.convertToTspans) - .attr(attributes); + .attr(attributes) - titleEl.selectAll('tspan.line') - .attr(attributes); - return Plots.previousPromises(gd); - } + titleEl.selectAll('tspan.line') + .attr(attributes) + return Plots.previousPromises(gd) + } - function scootTitle(titleElIn) { - var titleGroup = d3.select(titleElIn.node().parentNode); + function scootTitle (titleElIn) { + var titleGroup = d3.select(titleElIn.node().parentNode) - if(avoid && avoid.selection && avoid.side && txt) { - titleGroup.attr('transform', null); + 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) ? + var shift = 0, + backside = { + left: 'right', + right: 'left', + top: 'bottom', + bottom: 'top' + }[avoid.side], + shiftSign = (['left', 'top'].indexOf(avoid.side) !== -1) ? -1 : 1, - pad = isNumeric(avoid.pad) ? avoid.pad : 2, - titlebb = Drawing.bBox(titleGroup.node()), - paperbb = { - left: 0, - top: 0, - right: fullLayout.width, - bottom: fullLayout.height - }, - maxshift = avoid.maxShift || ( + pad = isNumeric(avoid.pad) ? avoid.pad : 2, + titlebb = Drawing.bBox(titleGroup.node()), + paperbb = { + left: 0, + top: 0, + right: fullLayout.width, + bottom: fullLayout.height + }, + maxshift = avoid.maxShift || ( (paperbb[avoid.side] - titlebb[avoid.side]) * - ((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1)); + ((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1)) // Prevent the title going off the paper - if(maxshift < 0) shift = maxshift; - else { + if (maxshift < 0) shift = maxshift + else { // so we don't have to offset each avoided element, // give the title the opposite offset - var offsetLeft = avoid.offsetLeft || 0, - offsetTop = avoid.offsetTop || 0; - titlebb.left -= offsetLeft; - titlebb.right -= offsetLeft; - titlebb.top -= offsetTop; - titlebb.bottom -= offsetTop; + var offsetLeft = avoid.offsetLeft || 0, + offsetTop = avoid.offsetTop || 0 + titlebb.left -= offsetLeft + titlebb.right -= offsetLeft + titlebb.top -= offsetTop + titlebb.bottom -= offsetTop // iterate over a set of elements (avoid.selection) // to avoid collisions with - avoid.selection.each(function() { - var avoidbb = Drawing.bBox(this); - - if(Lib.bBoxIntersect(titlebb, avoidbb, pad)) { - shift = Math.max(shift, shiftSign * ( - avoidbb[avoid.side] - titlebb[backside]) + pad); - } - }); - shift = Math.min(maxshift, shift); - } - if(shift > 0 || maxshift < 0) { - var shiftTemplate = { - left: [-shift, 0], - right: [shift, 0], - top: [0, -shift], - bottom: [0, shift] - }[avoid.side]; - titleGroup.attr('transform', - 'translate(' + shiftTemplate + ')'); - } - } + avoid.selection.each(function () { + var avoidbb = Drawing.bBox(this) + + if (Lib.bBoxIntersect(titlebb, avoidbb, pad)) { + shift = Math.max(shift, shiftSign * ( + avoidbb[avoid.side] - titlebb[backside]) + pad) + } + }) + shift = Math.min(maxshift, shift) + } + if (shift > 0 || maxshift < 0) { + var shiftTemplate = { + left: [-shift, 0], + right: [shift, 0], + top: [0, -shift], + bottom: [0, shift] + }[avoid.side] + titleGroup.attr('transform', + 'translate(' + shiftTemplate + ')') + } } + } - el.attr({'data-unformatted': txt}) - .call(titleLayout); + el.attr({'data-unformatted': txt}) + .call(titleLayout) - var placeholderText = 'Click to enter ' + name + ' title'; + var placeholderText = 'Click to enter ' + name + ' title' - function setPlaceholder() { - opacity = 0; - isplaceholder = true; - txt = placeholderText; - el.attr({'data-unformatted': txt}) + function setPlaceholder () { + opacity = 0 + isplaceholder = true + txt = placeholderText + el.attr({'data-unformatted': txt}) .text(txt) - .on('mouseover.opacity', function() { - d3.select(this).transition() - .duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1); + .on('mouseover.opacity', function () { + d3.select(this).transition() + .duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1) }) - .on('mouseout.opacity', function() { - d3.select(this).transition() - .duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0); - }); - } + .on('mouseout.opacity', function () { + d3.select(this).transition() + .duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0) + }) + } - if(gd._context.editable) { - if(!txt) setPlaceholder(); - else el.on('.opacity', null); + if (gd._context.editable) { + if (!txt) setPlaceholder() + else el.on('.opacity', null) - el.call(svgTextUtils.makeEditable) - .on('edit', function(text) { - if(traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex); - else Plotly.relayout(gd, prop, text); + el.call(svgTextUtils.makeEditable) + .on('edit', function (text) { + if (traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex) + else Plotly.relayout(gd, prop, text) }) - .on('cancel', function() { - this.text(this.attr('data-unformatted')) - .call(titleLayout); + .on('cancel', function () { + this.text(this.attr('data-unformatted')) + .call(titleLayout) }) - .on('input', function(d) { - this.text(d || ' ').attr(attributes) + .on('input', function (d) { + this.text(d || ' ').attr(attributes) .selectAll('tspan.line') - .attr(attributes); - }); - } - else if(!txt || txt.match(/Click to enter .+ title/)) { - el.remove(); - } - el.classed('js-placeholder', isplaceholder); -}; + .attr(attributes) + }) + } else if (!txt || txt.match(/Click to enter .+ title/)) { + el.remove() + } + el.classed('js-placeholder', isplaceholder) +} diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js index f04465f9ec3..17c039356da 100644 --- a/src/components/updatemenus/attributes.js +++ b/src/components/updatemenus/attributes.js @@ -6,165 +6,165 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var fontAttrs = require('../../plots/font_attributes'); -var colorAttrs = require('../color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; -var padAttrs = require('../../plots/pad_attributes'); +var fontAttrs = require('../../plots/font_attributes') +var colorAttrs = require('../color/attributes') +var extendFlat = require('../../lib/extend').extendFlat +var padAttrs = require('../../plots/pad_attributes') var buttonsAttrs = { - _isLinkedToArray: 'button', + _isLinkedToArray: 'button', - method: { - valType: 'enumerated', - values: ['restyle', 'relayout', 'animate', 'update'], - dflt: 'restyle', - role: 'info', - description: [ - 'Sets the Plotly method to be called on click.' - ].join(' ') - }, - args: { - valType: 'info_array', - role: 'info', - freeLength: true, - items: [ + method: { + valType: 'enumerated', + values: ['restyle', 'relayout', 'animate', 'update'], + dflt: 'restyle', + role: 'info', + description: [ + 'Sets the Plotly method to be called on click.' + ].join(' ') + }, + args: { + valType: 'info_array', + role: 'info', + freeLength: true, + items: [ { valType: 'any' }, { valType: 'any' }, { valType: 'any' } - ], - description: [ - 'Sets the arguments values to be passed to the Plotly', - 'method set in `method` on click.' - ].join(' ') - }, - label: { - valType: 'string', - role: 'info', - dflt: '', - description: 'Sets the text label to appear on the button.' - } -}; + ], + description: [ + 'Sets the arguments values to be passed to the Plotly', + 'method set in `method` on click.' + ].join(' ') + }, + label: { + valType: 'string', + role: 'info', + dflt: '', + description: 'Sets the text label to appear on the button.' + } +} module.exports = { - _isLinkedToArray: 'updatemenu', + _isLinkedToArray: 'updatemenu', - visible: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not the update menu is visible.' - ].join(' ') - }, + visible: { + valType: 'boolean', + role: 'info', + description: [ + 'Determines whether or not the update menu is visible.' + ].join(' ') + }, - type: { - valType: 'enumerated', - values: ['dropdown', 'buttons'], - dflt: 'dropdown', - role: 'info', - description: [ - 'Determines whether the buttons are accessible via a dropdown menu', - 'or whether the buttons are stacked horizontally or vertically' - ].join(' ') - }, + type: { + valType: 'enumerated', + values: ['dropdown', 'buttons'], + dflt: 'dropdown', + role: 'info', + description: [ + 'Determines whether the buttons are accessible via a dropdown menu', + 'or whether the buttons are stacked horizontally or vertically' + ].join(' ') + }, - direction: { - valType: 'enumerated', - values: ['left', 'right', 'up', 'down'], - dflt: 'down', - role: 'info', - description: [ - 'Determines the direction in which the buttons are laid out, whether', - 'in a dropdown menu or a row/column of buttons. For `left` and `up`,', - 'the buttons will still appear in left-to-right or top-to-bottom order', - 'respectively.' - ].join(' ') - }, + direction: { + valType: 'enumerated', + values: ['left', 'right', 'up', 'down'], + dflt: 'down', + role: 'info', + description: [ + 'Determines the direction in which the buttons are laid out, whether', + 'in a dropdown menu or a row/column of buttons. For `left` and `up`,', + 'the buttons will still appear in left-to-right or top-to-bottom order', + 'respectively.' + ].join(' ') + }, - active: { - valType: 'integer', - role: 'info', - min: -1, - dflt: 0, - description: [ - 'Determines which button (by index starting from 0) is', - 'considered active.' - ].join(' ') - }, + active: { + valType: 'integer', + role: 'info', + min: -1, + dflt: 0, + description: [ + 'Determines which button (by index starting from 0) is', + 'considered active.' + ].join(' ') + }, - showactive: { - valType: 'boolean', - role: 'info', - dflt: true, - description: 'Highlights active dropdown item or active button if true.' - }, + showactive: { + valType: 'boolean', + role: 'info', + dflt: true, + description: 'Highlights active dropdown item or active button if true.' + }, - buttons: buttonsAttrs, + buttons: buttonsAttrs, - x: { - valType: 'number', - min: -2, - max: 3, - dflt: -0.05, - role: 'style', - description: 'Sets the x position (in normalized coordinates) of the update menu.' - }, - xanchor: { - valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'right', - role: 'info', - description: [ - 'Sets the update menu\'s horizontal position anchor.', - 'This anchor binds the `x` position to the *left*, *center*', - 'or *right* of the range selector.' - ].join(' ') - }, - y: { - valType: 'number', - min: -2, - max: 3, - dflt: 1, - role: 'style', - description: 'Sets the y position (in normalized coordinates) of the update menu.' - }, - yanchor: { - valType: 'enumerated', - values: ['auto', 'top', 'middle', 'bottom'], - dflt: 'top', - role: 'info', - description: [ - 'Sets the update menu\'s vertical position anchor', - 'This anchor binds the `y` position to the *top*, *middle*', - 'or *bottom* of the range selector.' - ].join(' ') - }, + x: { + valType: 'number', + min: -2, + max: 3, + dflt: -0.05, + role: 'style', + description: 'Sets the x position (in normalized coordinates) of the update menu.' + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'right', + role: 'info', + description: [ + 'Sets the update menu\'s horizontal position anchor.', + 'This anchor binds the `x` position to the *left*, *center*', + 'or *right* of the range selector.' + ].join(' ') + }, + y: { + valType: 'number', + min: -2, + max: 3, + dflt: 1, + role: 'style', + description: 'Sets the y position (in normalized coordinates) of the update menu.' + }, + yanchor: { + valType: 'enumerated', + values: ['auto', 'top', 'middle', 'bottom'], + dflt: 'top', + role: 'info', + description: [ + 'Sets the update menu\'s vertical position anchor', + 'This anchor binds the `y` position to the *top*, *middle*', + 'or *bottom* of the range selector.' + ].join(' ') + }, - pad: extendFlat({}, padAttrs, { - description: 'Sets the padding around the buttons or dropdown menu.' - }), + pad: extendFlat({}, padAttrs, { + description: 'Sets the padding around the buttons or dropdown menu.' + }), - font: extendFlat({}, fontAttrs, { - description: 'Sets the font of the update menu button text.' - }), + font: extendFlat({}, fontAttrs, { + description: 'Sets the font of the update menu button text.' + }), - bgcolor: { - valType: 'color', - role: 'style', - description: 'Sets the background color of the update menu buttons.' - }, - bordercolor: { - valType: 'color', - dflt: colorAttrs.borderLine, - role: 'style', - description: 'Sets the color of the border enclosing the update menu.' - }, - borderwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the border enclosing the update menu.' - } -}; + bgcolor: { + valType: 'color', + role: 'style', + description: 'Sets the background color of the update menu buttons.' + }, + bordercolor: { + valType: 'color', + dflt: colorAttrs.borderLine, + role: 'style', + description: 'Sets the color of the border enclosing the update menu.' + }, + borderwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the border enclosing the update menu.' + } +} diff --git a/src/components/updatemenus/constants.js b/src/components/updatemenus/constants.js index b1c7a2e3ef0..211d25fef64 100644 --- a/src/components/updatemenus/constants.js +++ b/src/components/updatemenus/constants.js @@ -6,69 +6,67 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { // layout attribute name - name: 'updatemenus', + name: 'updatemenus', // class names - containerClassName: 'updatemenu-container', - headerGroupClassName: 'updatemenu-header-group', - headerClassName: 'updatemenu-header', - headerArrowClassName: 'updatemenu-header-arrow', - dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group', - dropdownButtonClassName: 'updatemenu-dropdown-button', - buttonClassName: 'updatemenu-button', - itemRectClassName: 'updatemenu-item-rect', - itemTextClassName: 'updatemenu-item-text', + containerClassName: 'updatemenu-container', + headerGroupClassName: 'updatemenu-header-group', + headerClassName: 'updatemenu-header', + headerArrowClassName: 'updatemenu-header-arrow', + dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group', + dropdownButtonClassName: 'updatemenu-dropdown-button', + buttonClassName: 'updatemenu-button', + itemRectClassName: 'updatemenu-item-rect', + itemTextClassName: 'updatemenu-item-text', // DOM attribute name in button group keeping track // of active update menu - menuIndexAttrName: 'updatemenu-active-index', + menuIndexAttrName: 'updatemenu-active-index', // id root pass to Plots.autoMargin - autoMarginIdRoot: 'updatemenu-', + autoMarginIdRoot: 'updatemenu-', // options when 'active: -1' - blankHeaderOpts: { label: ' ' }, + blankHeaderOpts: { label: ' ' }, // min item width / height - minWidth: 30, - minHeight: 30, + minWidth: 30, + minHeight: 30, // padding around item text - textPadX: 24, - arrowPadX: 16, + textPadX: 24, + arrowPadX: 16, // font size to height scale - fontSizeToHeight: 1.3, + fontSizeToHeight: 1.3, // item rect radii - rx: 2, - ry: 2, + rx: 2, + ry: 2, // item text x offset off left edge - textOffsetX: 12, + textOffsetX: 12, // item text y offset (w.r.t. middle) - textOffsetY: 3, + textOffsetY: 3, // arrow offset off right edge - arrowOffsetX: 4, + arrowOffsetX: 4, // gap between header and buttons - gapButtonHeader: 5, + gapButtonHeader: 5, // gap between between buttons - gapButton: 2, + gapButton: 2, // color given to active buttons - activeColor: '#F4FAFF', + activeColor: '#F4FAFF', // color given to hovered buttons - hoverColor: '#F4FAFF' -}; + hoverColor: '#F4FAFF' +} diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js index 2d4eeae7faa..27bac62e33d 100644 --- a/src/components/updatemenus/defaults.js +++ b/src/components/updatemenus/defaults.js @@ -6,87 +6,85 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); +var Lib = require('../../lib') +var handleArrayContainerDefaults = require('../../plots/array_container_defaults') -var attributes = require('./attributes'); -var constants = require('./constants'); +var attributes = require('./attributes') +var constants = require('./constants') -var name = constants.name; -var buttonAttrs = attributes.buttons; +var name = constants.name +var buttonAttrs = attributes.buttons +module.exports = function updateMenusDefaults (layoutIn, layoutOut) { + var opts = { + name: name, + handleItemDefaults: menuDefaults + } -module.exports = function updateMenusDefaults(layoutIn, layoutOut) { - var opts = { - name: name, - handleItemDefaults: menuDefaults - }; - - handleArrayContainerDefaults(layoutIn, layoutOut, opts); -}; - -function menuDefaults(menuIn, menuOut, layoutOut) { + handleArrayContainerDefaults(layoutIn, layoutOut, opts) +} - function coerce(attr, dflt) { - return Lib.coerce(menuIn, menuOut, attributes, attr, dflt); - } +function menuDefaults (menuIn, menuOut, layoutOut) { + function coerce (attr, dflt) { + return Lib.coerce(menuIn, menuOut, attributes, attr, dflt) + } - var buttons = buttonsDefaults(menuIn, menuOut); + var buttons = buttonsDefaults(menuIn, menuOut) - var visible = coerce('visible', buttons.length > 0); - if(!visible) return; + var visible = coerce('visible', buttons.length > 0) + if (!visible) return - coerce('active'); - coerce('direction'); - coerce('type'); - coerce('showactive'); + coerce('active') + coerce('direction') + coerce('type') + coerce('showactive') - coerce('x'); - coerce('y'); - Lib.noneOrAll(menuIn, menuOut, ['x', 'y']); + coerce('x') + coerce('y') + Lib.noneOrAll(menuIn, menuOut, ['x', 'y']) - coerce('xanchor'); - coerce('yanchor'); + coerce('xanchor') + coerce('yanchor') - coerce('pad.t'); - coerce('pad.r'); - coerce('pad.b'); - coerce('pad.l'); + coerce('pad.t') + coerce('pad.r') + coerce('pad.b') + coerce('pad.l') - Lib.coerceFont(coerce, 'font', layoutOut.font); + Lib.coerceFont(coerce, 'font', layoutOut.font) - coerce('bgcolor', layoutOut.paper_bgcolor); - coerce('bordercolor'); - coerce('borderwidth'); + coerce('bgcolor', layoutOut.paper_bgcolor) + coerce('bordercolor') + coerce('borderwidth') } -function buttonsDefaults(menuIn, menuOut) { - var buttonsIn = menuIn.buttons || [], - buttonsOut = menuOut.buttons = []; +function buttonsDefaults (menuIn, menuOut) { + var buttonsIn = menuIn.buttons || [], + buttonsOut = menuOut.buttons = [] - var buttonIn, buttonOut; + var buttonIn, buttonOut - function coerce(attr, dflt) { - return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt) + } - for(var i = 0; i < buttonsIn.length; i++) { - buttonIn = buttonsIn[i]; - buttonOut = {}; + for (var i = 0; i < buttonsIn.length; i++) { + buttonIn = buttonsIn[i] + buttonOut = {} - if(!Lib.isPlainObject(buttonIn) || !Array.isArray(buttonIn.args)) { - continue; - } + if (!Lib.isPlainObject(buttonIn) || !Array.isArray(buttonIn.args)) { + continue + } - coerce('method'); - coerce('args'); - coerce('label'); + coerce('method') + coerce('args') + coerce('label') - buttonOut._index = i; - buttonsOut.push(buttonOut); - } + buttonOut._index = i + buttonsOut.push(buttonOut) + } - return buttonsOut; + return buttonsOut } diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index 3c9c30968b2..b481ce75592 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -6,23 +6,22 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Plots = require('../../plots/plots') +var Color = require('../color') +var Drawing = require('../drawing') +var svgTextUtils = require('../../lib/svg_text_utils') +var anchorUtils = require('../legend/anchor_utils') -var Plots = require('../../plots/plots'); -var Color = require('../color'); -var Drawing = require('../drawing'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var anchorUtils = require('../legend/anchor_utils'); +var constants = require('./constants') +var ScrollBox = require('./scrollbox') -var constants = require('./constants'); -var ScrollBox = require('./scrollbox'); - -module.exports = function draw(gd) { - var fullLayout = gd._fullLayout, - menuData = makeMenuData(fullLayout); +module.exports = function draw (gd) { + var fullLayout = gd._fullLayout, + menuData = makeMenuData(fullLayout) /* Update menu data is bound to the header-group. * The items in the header group are always present. @@ -52,627 +51,620 @@ module.exports = function draw(gd) { */ // draw update menu container - var menus = fullLayout._infolayer + var menus = fullLayout._infolayer .selectAll('g.' + constants.containerClassName) - .data(menuData.length > 0 ? [0] : []); + .data(menuData.length > 0 ? [0] : []) - menus.enter().append('g') + menus.enter().append('g') .classed(constants.containerClassName, true) - .style('cursor', 'pointer'); + .style('cursor', 'pointer') - menus.exit().remove(); + menus.exit().remove() // remove push margin object(s) - if(menus.exit().size()) clearPushMargins(gd); + if (menus.exit().size()) clearPushMargins(gd) // return early if no update menus are visible - if(menuData.length === 0) return; + if (menuData.length === 0) return // join header group - var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName) - .data(menuData, keyFunction); + var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName) + .data(menuData, keyFunction) - headerGroups.enter().append('g') - .classed(constants.headerGroupClassName, true); + headerGroups.enter().append('g') + .classed(constants.headerGroupClassName, true) // draw dropdown button container - var gButton = menus.selectAll('g.' + constants.dropdownButtonGroupClassName) - .data([0]); + var gButton = menus.selectAll('g.' + constants.dropdownButtonGroupClassName) + .data([0]) - gButton.enter().append('g') + gButton.enter().append('g') .classed(constants.dropdownButtonGroupClassName, true) - .style('pointer-events', 'all'); + .style('pointer-events', 'all') // find dimensions before plotting anything (this mutates menuOpts) - for(var i = 0; i < menuData.length; i++) { - var menuOpts = menuData[i]; - findDimensions(gd, menuOpts); - } + for (var i = 0; i < menuData.length; i++) { + var menuOpts = menuData[i] + findDimensions(gd, menuOpts) + } // setup scrollbox - var scrollBoxId = 'updatemenus' + fullLayout._uid, - scrollBox = new ScrollBox(gd, gButton, scrollBoxId); + var scrollBoxId = 'updatemenus' + fullLayout._uid, + scrollBox = new ScrollBox(gd, gButton, scrollBoxId) // remove exiting header, remove dropped buttons and reset margins - if(headerGroups.enter().size()) { - gButton + if (headerGroups.enter().size()) { + gButton .call(removeAllButtons) - .attr(constants.menuIndexAttrName, '-1'); - } + .attr(constants.menuIndexAttrName, '-1') + } - headerGroups.exit().each(function(menuOpts) { - d3.select(this).remove(); + headerGroups.exit().each(function (menuOpts) { + d3.select(this).remove() - gButton + gButton .call(removeAllButtons) - .attr(constants.menuIndexAttrName, '-1'); + .attr(constants.menuIndexAttrName, '-1') - Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index); - }); + Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index) + }) // draw headers! - headerGroups.each(function(menuOpts) { - var gHeader = d3.select(this); + headerGroups.each(function (menuOpts) { + var gHeader = d3.select(this) - var _gButton = menuOpts.type === 'dropdown' ? gButton : null; - Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) { - setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true); - }); + var _gButton = menuOpts.type === 'dropdown' ? gButton : null + Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function (data) { + setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true) + }) - if(menuOpts.type === 'dropdown') { - drawHeader(gd, gHeader, gButton, scrollBox, menuOpts); + if (menuOpts.type === 'dropdown') { + drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) // if this menu is active, update the dropdown container - if(isActive(gButton, menuOpts)) { - drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); - } - } else { - drawButtons(gd, gHeader, null, null, menuOpts); - } - - }); -}; + if (isActive(gButton, menuOpts)) { + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) + } + } else { + drawButtons(gd, gHeader, null, null, menuOpts) + } + }) +} -function makeMenuData(fullLayout) { - var contOpts = fullLayout[constants.name], - menuData = []; +function makeMenuData (fullLayout) { + var contOpts = fullLayout[constants.name], + menuData = [] // Filter visible dropdowns and attach '_index' to each // fullLayout options object to be used for 'object constancy' // in the data join key function. - for(var i = 0; i < contOpts.length; i++) { - var item = contOpts[i]; + for (var i = 0; i < contOpts.length; i++) { + var item = contOpts[i] - if(item.visible) menuData.push(item); - } + if (item.visible) menuData.push(item) + } - return menuData; + return menuData } // Note that '_index' is set at the default step, // it corresponds to the menu index in the user layout update menu container. // Because a menu can b set invisible, // this is a more 'consistent' field than the index in the menuData. -function keyFunction(menuOpts) { - return menuOpts._index; +function keyFunction (menuOpts) { + return menuOpts._index } -function isFolded(gButton) { - return +gButton.attr(constants.menuIndexAttrName) === -1; +function isFolded (gButton) { + return +gButton.attr(constants.menuIndexAttrName) === -1 } -function isActive(gButton, menuOpts) { - return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index; +function isActive (gButton, menuOpts) { + return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index } -function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) { +function setActive (gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) { // update 'active' attribute in menuOpts - menuOpts._input.active = menuOpts.active = buttonIndex; + menuOpts._input.active = menuOpts.active = buttonIndex - if(menuOpts.type === 'buttons') { - drawButtons(gd, gHeader, null, null, menuOpts); - } - else if(menuOpts.type === 'dropdown') { + if (menuOpts.type === 'buttons') { + drawButtons(gd, gHeader, null, null, menuOpts) + } else if (menuOpts.type === 'dropdown') { // fold up buttons and redraw header - gButton.attr(constants.menuIndexAttrName, '-1'); + gButton.attr(constants.menuIndexAttrName, '-1') - drawHeader(gd, gHeader, gButton, scrollBox, menuOpts); + drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) - if(!isSilentUpdate) { - drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); - } + if (!isSilentUpdate) { + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) } + } } -function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) { - var header = gHeader.selectAll('g.' + constants.headerClassName) - .data([0]); +function drawHeader (gd, gHeader, gButton, scrollBox, menuOpts) { + var header = gHeader.selectAll('g.' + constants.headerClassName) + .data([0]) - header.enter().append('g') + header.enter().append('g') .classed(constants.headerClassName, true) - .style('pointer-events', 'all'); - - var active = menuOpts.active, - headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts, - posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 }, - positionOverrides = { - width: menuOpts.headerWidth, - height: menuOpts.headerHeight - }; + .style('pointer-events', 'all') + + var active = menuOpts.active, + headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts, + posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 }, + positionOverrides = { + width: menuOpts.headerWidth, + height: menuOpts.headerHeight + } - header + header .call(drawItem, menuOpts, headerOpts) - .call(setItemPosition, menuOpts, posOpts, positionOverrides); + .call(setItemPosition, menuOpts, posOpts, positionOverrides) // draw drop arrow at the right edge - var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName) - .data([0]); + var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName) + .data([0]) - arrow.enter().append('text') + arrow.enter().append('text') .classed(constants.headerArrowClassName, true) .classed('user-select-none', true) .attr('text-anchor', 'end') .call(Drawing.font, menuOpts.font) - .text('▼'); - - arrow.attr({ - x: menuOpts.headerWidth - constants.arrowOffsetX + menuOpts.pad.l, - y: menuOpts.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t - }); + .text('▼') - header.on('click', function() { - gButton.call(removeAllButtons); + arrow.attr({ + x: menuOpts.headerWidth - constants.arrowOffsetX + menuOpts.pad.l, + y: menuOpts.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t + }) + header.on('click', function () { + gButton.call(removeAllButtons) // if this menu is active, fold the dropdown container // otherwise, make this menu active - gButton.attr( + gButton.attr( constants.menuIndexAttrName, isActive(gButton, menuOpts) ? -1 : String(menuOpts._index) - ); + ) - drawButtons(gd, gHeader, gButton, scrollBox, menuOpts); - }); + drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) + }) - header.on('mouseover', function() { - header.call(styleOnMouseOver); - }); + header.on('mouseover', function () { + header.call(styleOnMouseOver) + }) - header.on('mouseout', function() { - header.call(styleOnMouseOut, menuOpts); - }); + header.on('mouseout', function () { + header.call(styleOnMouseOut, menuOpts) + }) // translate header group - Drawing.setTranslate(gHeader, menuOpts.lx, menuOpts.ly); + Drawing.setTranslate(gHeader, menuOpts.lx, menuOpts.ly) } -function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) { +function drawButtons (gd, gHeader, gButton, scrollBox, menuOpts) { // If this is a set of buttons, set pointer events = all since we play // some minor games with which container is which in order to simplify // the drawing of *either* buttons or menus - if(!gButton) { - gButton = gHeader; - gButton.attr('pointer-events', 'all'); - } + if (!gButton) { + gButton = gHeader + gButton.attr('pointer-events', 'all') + } - var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ? + var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ? menuOpts.buttons : - []; + [] - var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName; + var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName - var buttons = gButton.selectAll('g.' + klass) - .data(buttonData); + var buttons = gButton.selectAll('g.' + klass) + .data(buttonData) - var enter = buttons.enter().append('g') - .classed(klass, true); + var enter = buttons.enter().append('g') + .classed(klass, true) - var exit = buttons.exit(); + var exit = buttons.exit() - if(menuOpts.type === 'dropdown') { - enter.attr('opacity', '0') + if (menuOpts.type === 'dropdown') { + enter.attr('opacity', '0') .transition() - .attr('opacity', '1'); + .attr('opacity', '1') - exit.transition() + exit.transition() .attr('opacity', '0') - .remove(); - } else { - exit.remove(); - } + .remove() + } else { + exit.remove() + } - var x0 = 0; - var y0 = 0; + var x0 = 0 + var y0 = 0 - var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1 - if(menuOpts.type === 'dropdown') { - if(isVertical) { - y0 = menuOpts.headerHeight + constants.gapButtonHeader; - } else { - x0 = menuOpts.headerWidth + constants.gapButtonHeader; - } + if (menuOpts.type === 'dropdown') { + if (isVertical) { + y0 = menuOpts.headerHeight + constants.gapButtonHeader + } else { + x0 = menuOpts.headerWidth + constants.gapButtonHeader } + } - if(menuOpts.type === 'dropdown' && menuOpts.direction === 'up') { - y0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openHeight; - } + if (menuOpts.type === 'dropdown' && menuOpts.direction === 'up') { + y0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openHeight + } - if(menuOpts.type === 'dropdown' && menuOpts.direction === 'left') { - x0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openWidth; - } + if (menuOpts.type === 'dropdown' && menuOpts.direction === 'left') { + x0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openWidth + } - var posOpts = { - x: menuOpts.lx + x0 + menuOpts.pad.l, - y: menuOpts.ly + y0 + menuOpts.pad.t, - yPad: constants.gapButton, - xPad: constants.gapButton, - index: 0, - }; + var posOpts = { + x: menuOpts.lx + x0 + menuOpts.pad.l, + y: menuOpts.ly + y0 + menuOpts.pad.t, + yPad: constants.gapButton, + xPad: constants.gapButton, + index: 0 + } - var scrollBoxPosition = { - l: posOpts.x + menuOpts.borderwidth, - t: posOpts.y + menuOpts.borderwidth - }; + var scrollBoxPosition = { + l: posOpts.x + menuOpts.borderwidth, + t: posOpts.y + menuOpts.borderwidth + } - buttons.each(function(buttonOpts, buttonIndex) { - var button = d3.select(this); + buttons.each(function (buttonOpts, buttonIndex) { + var button = d3.select(this) - button + button .call(drawItem, menuOpts, buttonOpts) - .call(setItemPosition, menuOpts, posOpts); + .call(setItemPosition, menuOpts, posOpts) - button.on('click', function() { + button.on('click', function () { // skip `dragend` events - if(d3.event.defaultPrevented) return; + if (d3.event.defaultPrevented) return - setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex); + setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex) - Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args); + Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args) - gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active}); - }); + gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active}) + }) - button.on('mouseover', function() { - button.call(styleOnMouseOver); - }); + button.on('mouseover', function () { + button.call(styleOnMouseOver) + }) - button.on('mouseout', function() { - button.call(styleOnMouseOut, menuOpts); - buttons.call(styleButtons, menuOpts); - }); - }); + button.on('mouseout', function () { + button.call(styleOnMouseOut, menuOpts) + buttons.call(styleButtons, menuOpts) + }) + }) - buttons.call(styleButtons, menuOpts); + buttons.call(styleButtons, menuOpts) - if(isVertical) { - scrollBoxPosition.w = Math.max(menuOpts.openWidth, menuOpts.headerWidth); - scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t; - } - else { - scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l; - scrollBoxPosition.h = Math.max(menuOpts.openHeight, menuOpts.headerHeight); - } + if (isVertical) { + scrollBoxPosition.w = Math.max(menuOpts.openWidth, menuOpts.headerWidth) + scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t + } else { + scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l + scrollBoxPosition.h = Math.max(menuOpts.openHeight, menuOpts.headerHeight) + } - scrollBoxPosition.direction = menuOpts.direction; + scrollBoxPosition.direction = menuOpts.direction - if(scrollBox) { - if(buttons.size()) { - drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, scrollBoxPosition); - } - else { - hideScrollBox(scrollBox); - } + if (scrollBox) { + if (buttons.size()) { + drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, scrollBoxPosition) + } else { + hideScrollBox(scrollBox) } + } } -function drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, position) { +function drawScrollBox (gd, gHeader, gButton, scrollBox, menuOpts, position) { // enable the scrollbox - var direction = menuOpts.direction, - isVertical = (direction === 'up' || direction === 'down'); - - var active = menuOpts.active, - translateX, translateY, - i; - if(isVertical) { - translateY = 0; - for(i = 0; i < active; i++) { - translateY += menuOpts.heights[i] + constants.gapButton; - } + var direction = menuOpts.direction, + isVertical = (direction === 'up' || direction === 'down') + + var active = menuOpts.active, + translateX, translateY, + i + if (isVertical) { + translateY = 0 + for (i = 0; i < active; i++) { + translateY += menuOpts.heights[i] + constants.gapButton } - else { - translateX = 0; - for(i = 0; i < active; i++) { - translateX += menuOpts.widths[i] + constants.gapButton; - } + } else { + translateX = 0 + for (i = 0; i < active; i++) { + translateX += menuOpts.widths[i] + constants.gapButton } + } - scrollBox.enable(position, translateX, translateY); + scrollBox.enable(position, translateX, translateY) - if(scrollBox.hbar) { - scrollBox.hbar + if (scrollBox.hbar) { + scrollBox.hbar .attr('opacity', '0') .transition() - .attr('opacity', '1'); - } + .attr('opacity', '1') + } - if(scrollBox.vbar) { - scrollBox.vbar + if (scrollBox.vbar) { + scrollBox.vbar .attr('opacity', '0') .transition() - .attr('opacity', '1'); - } + .attr('opacity', '1') + } } -function hideScrollBox(scrollBox) { - var hasHBar = !!scrollBox.hbar, - hasVBar = !!scrollBox.vbar; +function hideScrollBox (scrollBox) { + var hasHBar = !!scrollBox.hbar, + hasVBar = !!scrollBox.vbar - if(hasHBar) { - scrollBox.hbar + if (hasHBar) { + scrollBox.hbar .transition() .attr('opacity', '0') - .each('end', function() { - hasHBar = false; - if(!hasVBar) scrollBox.disable(); - }); - } - - if(hasVBar) { - scrollBox.vbar + .each('end', function () { + hasHBar = false + if (!hasVBar) scrollBox.disable() + }) + } + + if (hasVBar) { + scrollBox.vbar .transition() .attr('opacity', '0') - .each('end', function() { - hasVBar = false; - if(!hasHBar) scrollBox.disable(); - }); - } + .each('end', function () { + hasVBar = false + if (!hasHBar) scrollBox.disable() + }) + } } -function drawItem(item, menuOpts, itemOpts) { - item.call(drawItemRect, menuOpts) - .call(drawItemText, menuOpts, itemOpts); +function drawItem (item, menuOpts, itemOpts) { + item.call(drawItemRect, menuOpts) + .call(drawItemText, menuOpts, itemOpts) } -function drawItemRect(item, menuOpts) { - var rect = item.selectAll('rect') - .data([0]); +function drawItemRect (item, menuOpts) { + var rect = item.selectAll('rect') + .data([0]) - rect.enter().append('rect') + rect.enter().append('rect') .classed(constants.itemRectClassName, true) .attr({ - rx: constants.rx, - ry: constants.ry, - 'shape-rendering': 'crispEdges' - }); + rx: constants.rx, + ry: constants.ry, + 'shape-rendering': 'crispEdges' + }) - rect.call(Color.stroke, menuOpts.bordercolor) + rect.call(Color.stroke, menuOpts.bordercolor) .call(Color.fill, menuOpts.bgcolor) - .style('stroke-width', menuOpts.borderwidth + 'px'); + .style('stroke-width', menuOpts.borderwidth + 'px') } -function drawItemText(item, menuOpts, itemOpts) { - var text = item.selectAll('text') - .data([0]); +function drawItemText (item, menuOpts, itemOpts) { + var text = item.selectAll('text') + .data([0]) - text.enter().append('text') + text.enter().append('text') .classed(constants.itemTextClassName, true) .classed('user-select-none', true) - .attr('text-anchor', 'start'); + .attr('text-anchor', 'start') - text.call(Drawing.font, menuOpts.font) + text.call(Drawing.font, menuOpts.font) .text(itemOpts.label) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans) } -function styleButtons(buttons, menuOpts) { - var active = menuOpts.active; +function styleButtons (buttons, menuOpts) { + var active = menuOpts.active - buttons.each(function(buttonOpts, i) { - var button = d3.select(this); + buttons.each(function (buttonOpts, i) { + var button = d3.select(this) - if(i === active && menuOpts.showactive) { - button.select('rect.' + constants.itemRectClassName) - .call(Color.fill, constants.activeColor); - } - }); + if (i === active && menuOpts.showactive) { + button.select('rect.' + constants.itemRectClassName) + .call(Color.fill, constants.activeColor) + } + }) } -function styleOnMouseOver(item) { - item.select('rect.' + constants.itemRectClassName) - .call(Color.fill, constants.hoverColor); +function styleOnMouseOver (item) { + item.select('rect.' + constants.itemRectClassName) + .call(Color.fill, constants.hoverColor) } -function styleOnMouseOut(item, menuOpts) { - item.select('rect.' + constants.itemRectClassName) - .call(Color.fill, menuOpts.bgcolor); +function styleOnMouseOut (item, menuOpts) { + item.select('rect.' + constants.itemRectClassName) + .call(Color.fill, menuOpts.bgcolor) } // find item dimensions (this mutates menuOpts) -function findDimensions(gd, menuOpts) { - menuOpts.width1 = 0; - menuOpts.height1 = 0; - menuOpts.heights = []; - menuOpts.widths = []; - menuOpts.totalWidth = 0; - menuOpts.totalHeight = 0; - menuOpts.openWidth = 0; - menuOpts.openHeight = 0; - menuOpts.lx = 0; - menuOpts.ly = 0; - - var fakeButtons = gd._tester.selectAll('g.' + constants.dropdownButtonClassName) - .data(menuOpts.buttons); - - fakeButtons.enter().append('g') - .classed(constants.dropdownButtonClassName, true); - - var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; +function findDimensions (gd, menuOpts) { + menuOpts.width1 = 0 + menuOpts.height1 = 0 + menuOpts.heights = [] + menuOpts.widths = [] + menuOpts.totalWidth = 0 + menuOpts.totalHeight = 0 + menuOpts.openWidth = 0 + menuOpts.openHeight = 0 + menuOpts.lx = 0 + menuOpts.ly = 0 + + var fakeButtons = gd._tester.selectAll('g.' + constants.dropdownButtonClassName) + .data(menuOpts.buttons) + + fakeButtons.enter().append('g') + .classed(constants.dropdownButtonClassName, true) + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1 // loop over fake buttons to find width / height - fakeButtons.each(function(buttonOpts, i) { - var button = d3.select(this); + fakeButtons.each(function (buttonOpts, i) { + var button = d3.select(this) - button.call(drawItem, menuOpts, buttonOpts); + button.call(drawItem, menuOpts, buttonOpts) - var text = button.select('.' + constants.itemTextClassName), - tspans = text.selectAll('tspan'); + var text = button.select('.' + constants.itemTextClassName), + tspans = text.selectAll('tspan') // width is given by max width of all buttons - var tWidth = text.node() && Drawing.bBox(text.node()).width, - wEff = Math.max(tWidth + constants.textPadX, constants.minWidth); + var tWidth = text.node() && Drawing.bBox(text.node()).width, + wEff = Math.max(tWidth + constants.textPadX, constants.minWidth) // height is determined by item text - var tHeight = menuOpts.font.size * constants.fontSizeToHeight, - tLines = tspans[0].length || 1, - hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY; + var tHeight = menuOpts.font.size * constants.fontSizeToHeight, + tLines = tspans[0].length || 1, + hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY - hEff = Math.ceil(hEff); - wEff = Math.ceil(wEff); + hEff = Math.ceil(hEff) + wEff = Math.ceil(wEff) // Store per-item sizes since a row of horizontal buttons, for example, // don't all need to be the same width: - menuOpts.widths[i] = wEff; - menuOpts.heights[i] = hEff; + menuOpts.widths[i] = wEff + menuOpts.heights[i] = hEff // Height and width of individual element: - menuOpts.height1 = Math.max(menuOpts.height1, hEff); - menuOpts.width1 = Math.max(menuOpts.width1, wEff); - - if(isVertical) { - menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff); - menuOpts.openWidth = menuOpts.totalWidth; - menuOpts.totalHeight += hEff + constants.gapButton; - menuOpts.openHeight += hEff + constants.gapButton; - } else { - menuOpts.totalWidth += wEff + constants.gapButton; - menuOpts.openWidth += wEff + constants.gapButton; - menuOpts.totalHeight = Math.max(menuOpts.totalHeight, hEff); - menuOpts.openHeight = menuOpts.totalHeight; - } - }); - - if(isVertical) { - menuOpts.totalHeight -= constants.gapButton; + menuOpts.height1 = Math.max(menuOpts.height1, hEff) + menuOpts.width1 = Math.max(menuOpts.width1, wEff) + + if (isVertical) { + menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff) + menuOpts.openWidth = menuOpts.totalWidth + menuOpts.totalHeight += hEff + constants.gapButton + menuOpts.openHeight += hEff + constants.gapButton } else { - menuOpts.totalWidth -= constants.gapButton; - } - - - menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX; - menuOpts.headerHeight = menuOpts.height1; - - if(menuOpts.type === 'dropdown') { - if(isVertical) { - menuOpts.width1 += constants.arrowPadX; - menuOpts.totalHeight = menuOpts.height1; - } else { - menuOpts.totalWidth = menuOpts.width1; - } - menuOpts.totalWidth += constants.arrowPadX; + menuOpts.totalWidth += wEff + constants.gapButton + menuOpts.openWidth += wEff + constants.gapButton + menuOpts.totalHeight = Math.max(menuOpts.totalHeight, hEff) + menuOpts.openHeight = menuOpts.totalHeight } + }) - fakeButtons.remove(); + if (isVertical) { + menuOpts.totalHeight -= constants.gapButton + } else { + menuOpts.totalWidth -= constants.gapButton + } - var paddedWidth = menuOpts.totalWidth + menuOpts.pad.l + menuOpts.pad.r; - var paddedHeight = menuOpts.totalHeight + menuOpts.pad.t + menuOpts.pad.b; + menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX + menuOpts.headerHeight = menuOpts.height1 - var graphSize = gd._fullLayout._size; - menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x; - menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y); - - var xanchor = 'left'; - if(anchorUtils.isRightAnchor(menuOpts)) { - menuOpts.lx -= paddedWidth; - xanchor = 'right'; - } - if(anchorUtils.isCenterAnchor(menuOpts)) { - menuOpts.lx -= paddedWidth / 2; - xanchor = 'center'; - } - - var yanchor = 'top'; - if(anchorUtils.isBottomAnchor(menuOpts)) { - menuOpts.ly -= paddedHeight; - yanchor = 'bottom'; - } - if(anchorUtils.isMiddleAnchor(menuOpts)) { - menuOpts.ly -= paddedHeight / 2; - yanchor = 'middle'; + if (menuOpts.type === 'dropdown') { + if (isVertical) { + menuOpts.width1 += constants.arrowPadX + menuOpts.totalHeight = menuOpts.height1 + } else { + menuOpts.totalWidth = menuOpts.width1 } - - menuOpts.totalWidth = Math.ceil(menuOpts.totalWidth); - menuOpts.totalHeight = Math.ceil(menuOpts.totalHeight); - menuOpts.lx = Math.round(menuOpts.lx); - menuOpts.ly = Math.round(menuOpts.ly); - - Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, { - x: menuOpts.x, - y: menuOpts.y, - l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0), - r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0), - b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0), - t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0) - }); + menuOpts.totalWidth += constants.arrowPadX + } + + fakeButtons.remove() + + var paddedWidth = menuOpts.totalWidth + menuOpts.pad.l + menuOpts.pad.r + var paddedHeight = menuOpts.totalHeight + menuOpts.pad.t + menuOpts.pad.b + + var graphSize = gd._fullLayout._size + menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x + menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y) + + var xanchor = 'left' + if (anchorUtils.isRightAnchor(menuOpts)) { + menuOpts.lx -= paddedWidth + xanchor = 'right' + } + if (anchorUtils.isCenterAnchor(menuOpts)) { + menuOpts.lx -= paddedWidth / 2 + xanchor = 'center' + } + + var yanchor = 'top' + if (anchorUtils.isBottomAnchor(menuOpts)) { + menuOpts.ly -= paddedHeight + yanchor = 'bottom' + } + if (anchorUtils.isMiddleAnchor(menuOpts)) { + menuOpts.ly -= paddedHeight / 2 + yanchor = 'middle' + } + + menuOpts.totalWidth = Math.ceil(menuOpts.totalWidth) + menuOpts.totalHeight = Math.ceil(menuOpts.totalHeight) + menuOpts.lx = Math.round(menuOpts.lx) + menuOpts.ly = Math.round(menuOpts.ly) + + Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, { + x: menuOpts.x, + y: menuOpts.y, + l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0), + r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0), + b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0), + t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0) + }) } // set item positions (mutates posOpts) -function setItemPosition(item, menuOpts, posOpts, overrideOpts) { - overrideOpts = overrideOpts || {}; - var rect = item.select('.' + constants.itemRectClassName), - text = item.select('.' + constants.itemTextClassName), - tspans = text.selectAll('tspan'), - borderWidth = menuOpts.borderwidth, - index = posOpts.index; - - Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y); - - var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; - - rect.attr({ - x: 0, - y: 0, - width: overrideOpts.width || (isVertical ? menuOpts.width1 : menuOpts.widths[index]), - height: overrideOpts.height || (isVertical ? menuOpts.heights[index] : menuOpts.height1) - }); - - var tHeight = menuOpts.font.size * constants.fontSizeToHeight, - tLines = tspans[0].length || 1, - spanOffset = ((tLines - 1) * tHeight / 4); - - var textAttrs = { - x: constants.textOffsetX, - y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY - }; - - text.attr(textAttrs); - tspans.attr(textAttrs); - - if(isVertical) { - posOpts.y += menuOpts.heights[index] + posOpts.yPad; - } else { - posOpts.x += menuOpts.widths[index] + posOpts.xPad; - } - - posOpts.index++; +function setItemPosition (item, menuOpts, posOpts, overrideOpts) { + overrideOpts = overrideOpts || {} + var rect = item.select('.' + constants.itemRectClassName), + text = item.select('.' + constants.itemTextClassName), + tspans = text.selectAll('tspan'), + borderWidth = menuOpts.borderwidth, + index = posOpts.index + + Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y) + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1 + + rect.attr({ + x: 0, + y: 0, + width: overrideOpts.width || (isVertical ? menuOpts.width1 : menuOpts.widths[index]), + height: overrideOpts.height || (isVertical ? menuOpts.heights[index] : menuOpts.height1) + }) + + var tHeight = menuOpts.font.size * constants.fontSizeToHeight, + tLines = tspans[0].length || 1, + spanOffset = ((tLines - 1) * tHeight / 4) + + var textAttrs = { + x: constants.textOffsetX, + y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY + } + + text.attr(textAttrs) + tspans.attr(textAttrs) + + if (isVertical) { + posOpts.y += menuOpts.heights[index] + posOpts.yPad + } else { + posOpts.x += menuOpts.widths[index] + posOpts.xPad + } + + posOpts.index++ } -function removeAllButtons(gButton) { - gButton.selectAll('g.' + constants.dropdownButtonClassName).remove(); +function removeAllButtons (gButton) { + gButton.selectAll('g.' + constants.dropdownButtonClassName).remove() } -function clearPushMargins(gd) { - var pushMargins = gd._fullLayout._pushmargin || {}, - keys = Object.keys(pushMargins); +function clearPushMargins (gd) { + var pushMargins = gd._fullLayout._pushmargin || {}, + keys = Object.keys(pushMargins) - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; + for (var i = 0; i < keys.length; i++) { + var k = keys[i] - if(k.indexOf(constants.autoMarginIdRoot) !== -1) { - Plots.autoMargin(gd, k); - } + if (k.indexOf(constants.autoMarginIdRoot) !== -1) { + Plots.autoMargin(gd, k) } + } } diff --git a/src/components/updatemenus/index.js b/src/components/updatemenus/index.js index 366b9aaa1ad..4a205e9712d 100644 --- a/src/components/updatemenus/index.js +++ b/src/components/updatemenus/index.js @@ -6,16 +6,16 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var constants = require('./constants'); +var constants = require('./constants') module.exports = { - moduleType: 'component', - name: constants.name, + moduleType: 'component', + name: constants.name, - layoutAttributes: require('./attributes'), - supplyLayoutDefaults: require('./defaults'), + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), - draw: require('./draw') -}; + draw: require('./draw') +} diff --git a/src/components/updatemenus/scrollbox.js b/src/components/updatemenus/scrollbox.js index 6c3431536cf..fe000d311bb 100644 --- a/src/components/updatemenus/scrollbox.js +++ b/src/components/updatemenus/scrollbox.js @@ -6,16 +6,16 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -module.exports = ScrollBox; +module.exports = ScrollBox -var d3 = require('d3'); +var d3 = require('d3') -var Color = require('../color'); -var Drawing = require('../drawing'); +var Color = require('../color') +var Drawing = require('../drawing') -var Lib = require('../../lib'); +var Lib = require('../../lib') /** * Helper class to setup a scroll box @@ -25,44 +25,44 @@ var Lib = require('../../lib'); * @param container Container to be scroll-boxed (as a D3 selection) * @param {string} id Id for the clip path to implement the scroll box */ -function ScrollBox(gd, container, id) { - this.gd = gd; - this.container = container; - this.id = id; +function ScrollBox (gd, container, id) { + this.gd = gd + this.container = container + this.id = id // See ScrollBox.prototype.enable for further definition - this.position = null; // scrollbox position - this.translateX = null; // scrollbox horizontal translation - this.translateY = null; // scrollbox vertical translation - this.hbar = null; // horizontal scrollbar D3 selection - this.vbar = null; // vertical scrollbar D3 selection + this.position = null // scrollbox position + this.translateX = null // scrollbox horizontal translation + this.translateY = null // scrollbox vertical translation + this.hbar = null // horizontal scrollbar D3 selection + this.vbar = null // vertical scrollbar D3 selection // element to capture pointer events - this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]); + this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]) - this.bg.exit() + this.bg.exit() .on('.drag', null) .on('wheel', null) - .remove(); + .remove() - this.bg.enter().append('rect') + this.bg.enter().append('rect') .classed('scrollbox-bg', true) .style('pointer-events', 'all') .attr({ - opacity: 0, - x: 0, - y: 0, - width: 0, - height: 0 - }); + opacity: 0, + x: 0, + y: 0, + width: 0, + height: 0 + }) } // scroll bar dimensions -ScrollBox.barWidth = 2; -ScrollBox.barLength = 20; -ScrollBox.barRadius = 2; -ScrollBox.barPad = 1; -ScrollBox.barColor = '#808BA4'; +ScrollBox.barWidth = 2 +ScrollBox.barLength = 20 +ScrollBox.barRadius = 2 +ScrollBox.barPad = 1 +ScrollBox.barColor = '#808BA4' /** * If needed, setup a clip path and scrollbars @@ -78,349 +78,343 @@ ScrollBox.barColor = '#808BA4'; * @param {number} [translateX=0] Horizontal offset (in pixels) * @param {number} [translateY=0] Vertical offset (in pixels) */ -ScrollBox.prototype.enable = function enable(position, translateX, translateY) { - var fullLayout = this.gd._fullLayout, - fullWidth = fullLayout.width, - fullHeight = fullLayout.height; +ScrollBox.prototype.enable = function enable (position, translateX, translateY) { + var fullLayout = this.gd._fullLayout, + fullWidth = fullLayout.width, + fullHeight = fullLayout.height // compute position of scrollbox - this.position = position; - - var l = this.position.l, - w = this.position.w, - t = this.position.t, - h = this.position.h, - direction = this.position.direction, - isDown = (direction === 'down'), - isLeft = (direction === 'left'), - isRight = (direction === 'right'), - isUp = (direction === 'up'), - boxW = w, - boxH = h, - boxL, boxR, - boxT, boxB; - - if(!isDown && !isLeft && !isRight && !isUp) { - this.position.direction = 'down'; - isDown = true; - } - - var isVertical = isDown || isUp; - if(isVertical) { - boxL = l; - boxR = boxL + boxW; - - if(isDown) { + this.position = position + + var l = this.position.l, + w = this.position.w, + t = this.position.t, + h = this.position.h, + direction = this.position.direction, + isDown = (direction === 'down'), + isLeft = (direction === 'left'), + isRight = (direction === 'right'), + isUp = (direction === 'up'), + boxW = w, + boxH = h, + boxL, boxR, + boxT, boxB + + if (!isDown && !isLeft && !isRight && !isUp) { + this.position.direction = 'down' + isDown = true + } + + var isVertical = isDown || isUp + if (isVertical) { + boxL = l + boxR = boxL + boxW + + if (isDown) { // anchor to top side - boxT = t; - boxB = Math.min(boxT + boxH, fullHeight); - boxH = boxB - boxT; - } - else { + boxT = t + boxB = Math.min(boxT + boxH, fullHeight) + boxH = boxB - boxT + } else { // anchor to bottom side - boxB = t + boxH; - boxT = Math.max(boxB - boxH, 0); - boxH = boxB - boxT; - } + boxB = t + boxH + boxT = Math.max(boxB - boxH, 0) + boxH = boxB - boxT } - else { - boxT = t; - boxB = boxT + boxH; + } else { + boxT = t + boxB = boxT + boxH - if(isLeft) { + if (isLeft) { // anchor to right side - boxR = l + boxW; - boxL = Math.max(boxR - boxW, 0); - boxW = boxR - boxL; - } - else { + boxR = l + boxW + boxL = Math.max(boxR - boxW, 0) + boxW = boxR - boxL + } else { // anchor to left side - boxL = l; - boxR = Math.min(boxL + boxW, fullWidth); - boxW = boxR - boxL; - } + boxL = l + boxR = Math.min(boxL + boxW, fullWidth) + boxW = boxR - boxL } + } - this._box = { - l: boxL, - t: boxT, - w: boxW, - h: boxH - }; + this._box = { + l: boxL, + t: boxT, + w: boxW, + h: boxH + } // compute position of horizontal scroll bar - var needsHorizontalScrollBar = (w > boxW), - hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad, - hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad, + var needsHorizontalScrollBar = (w > boxW), + hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad, + hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad, // draw horizontal scrollbar on the bottom side - hbarL = l, - hbarT = t + h; + hbarL = l, + hbarT = t + h - if(hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH; + if (hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH - var hbar = this.container.selectAll('rect.scrollbar-horizontal').data( - (needsHorizontalScrollBar) ? [0] : []); + var hbar = this.container.selectAll('rect.scrollbar-horizontal').data( + (needsHorizontalScrollBar) ? [0] : []) - hbar.exit() + hbar.exit() .on('.drag', null) - .remove(); + .remove() - hbar.enter().append('rect') + hbar.enter().append('rect') .classed('scrollbar-horizontal', true) - .call(Color.fill, ScrollBox.barColor); - - if(needsHorizontalScrollBar) { - this.hbar = hbar.attr({ - 'rx': ScrollBox.barRadius, - 'ry': ScrollBox.barRadius, - 'x': hbarL, - 'y': hbarT, - 'width': hbarW, - 'height': hbarH - }); + .call(Color.fill, ScrollBox.barColor) + + if (needsHorizontalScrollBar) { + this.hbar = hbar.attr({ + 'rx': ScrollBox.barRadius, + 'ry': ScrollBox.barRadius, + 'x': hbarL, + 'y': hbarT, + 'width': hbarW, + 'height': hbarH + }) // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax - this._hbarXMin = hbarL + hbarW / 2; - this._hbarTranslateMax = boxW - hbarW; - } - else { - delete this.hbar; - delete this._hbarXMin; - delete this._hbarTranslateMax; - } + this._hbarXMin = hbarL + hbarW / 2 + this._hbarTranslateMax = boxW - hbarW + } else { + delete this.hbar + delete this._hbarXMin + delete this._hbarTranslateMax + } // compute position of vertical scroll bar - var needsVerticalScrollBar = (h > boxH), - vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad, - vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad, + var needsVerticalScrollBar = (h > boxH), + vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad, + vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad, // draw vertical scrollbar on the right side - vbarL = l + w, - vbarT = t; + vbarL = l + w, + vbarT = t - if(vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW; + if (vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW - var vbar = this.container.selectAll('rect.scrollbar-vertical').data( - (needsVerticalScrollBar) ? [0] : []); + var vbar = this.container.selectAll('rect.scrollbar-vertical').data( + (needsVerticalScrollBar) ? [0] : []) - vbar.exit() + vbar.exit() .on('.drag', null) - .remove(); + .remove() - vbar.enter().append('rect') + vbar.enter().append('rect') .classed('scrollbar-vertical', true) - .call(Color.fill, ScrollBox.barColor); - - if(needsVerticalScrollBar) { - this.vbar = vbar.attr({ - 'rx': ScrollBox.barRadius, - 'ry': ScrollBox.barRadius, - 'x': vbarL, - 'y': vbarT, - 'width': vbarW, - 'height': vbarH - }); + .call(Color.fill, ScrollBox.barColor) + + if (needsVerticalScrollBar) { + this.vbar = vbar.attr({ + 'rx': ScrollBox.barRadius, + 'ry': ScrollBox.barRadius, + 'x': vbarL, + 'y': vbarT, + 'width': vbarW, + 'height': vbarH + }) // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax - this._vbarYMin = vbarT + vbarH / 2; - this._vbarTranslateMax = boxH - vbarH; - } - else { - delete this.vbar; - delete this._vbarYMin; - delete this._vbarTranslateMax; - } + this._vbarYMin = vbarT + vbarH / 2 + this._vbarTranslateMax = boxH - vbarH + } else { + delete this.vbar + delete this._vbarYMin + delete this._vbarTranslateMax + } // setup a clip path (if scroll bars are needed) - var clipId = this.id, - clipL = boxL - 0.5, - clipR = (needsVerticalScrollBar) ? boxR + vbarW + 0.5 : boxR + 0.5, - clipT = boxT - 0.5, - clipB = (needsHorizontalScrollBar) ? boxB + hbarH + 0.5 : boxB + 0.5; + var clipId = this.id, + clipL = boxL - 0.5, + clipR = (needsVerticalScrollBar) ? boxR + vbarW + 0.5 : boxR + 0.5, + clipT = boxT - 0.5, + clipB = (needsHorizontalScrollBar) ? boxB + hbarH + 0.5 : boxB + 0.5 - var clipPath = fullLayout._topdefs.selectAll('#' + clipId) - .data((needsHorizontalScrollBar || needsVerticalScrollBar) ? [0] : []); + var clipPath = fullLayout._topdefs.selectAll('#' + clipId) + .data((needsHorizontalScrollBar || needsVerticalScrollBar) ? [0] : []) - clipPath.exit().remove(); + clipPath.exit().remove() - clipPath.enter() + clipPath.enter() .append('clipPath').attr('id', clipId) - .append('rect'); - - if(needsHorizontalScrollBar || needsVerticalScrollBar) { - this._clipRect = clipPath.select('rect').attr({ - x: Math.floor(clipL), - y: Math.floor(clipT), - width: Math.ceil(clipR) - Math.floor(clipL), - height: Math.ceil(clipB) - Math.floor(clipT) - }); - - this.container.call(Drawing.setClipUrl, clipId); - - this.bg.attr({ - x: l, - y: t, - width: w, - height: h - }); - } - else { - this.bg.attr({ - width: 0, - height: 0 - }); - this.container + .append('rect') + + if (needsHorizontalScrollBar || needsVerticalScrollBar) { + this._clipRect = clipPath.select('rect').attr({ + x: Math.floor(clipL), + y: Math.floor(clipT), + width: Math.ceil(clipR) - Math.floor(clipL), + height: Math.ceil(clipB) - Math.floor(clipT) + }) + + this.container.call(Drawing.setClipUrl, clipId) + + this.bg.attr({ + x: l, + y: t, + width: w, + height: h + }) + } else { + this.bg.attr({ + width: 0, + height: 0 + }) + this.container .on('wheel', null) .on('.drag', null) - .call(Drawing.setClipUrl, null); - delete this._clipRect; - } + .call(Drawing.setClipUrl, null) + delete this._clipRect + } // set up drag listeners (if scroll bars are needed) - if(needsHorizontalScrollBar || needsVerticalScrollBar) { - var onBoxDrag = d3.behavior.drag() - .on('dragstart', function() { - d3.event.sourceEvent.preventDefault(); + if (needsHorizontalScrollBar || needsVerticalScrollBar) { + var onBoxDrag = d3.behavior.drag() + .on('dragstart', function () { + d3.event.sourceEvent.preventDefault() }) - .on('drag', this._onBoxDrag.bind(this)); + .on('drag', this._onBoxDrag.bind(this)) - this.container + this.container .on('wheel', null) .on('wheel', this._onBoxWheel.bind(this)) .on('.drag', null) - .call(onBoxDrag); + .call(onBoxDrag) - var onBarDrag = d3.behavior.drag() - .on('dragstart', function() { - d3.event.sourceEvent.preventDefault(); - d3.event.sourceEvent.stopPropagation(); + var onBarDrag = d3.behavior.drag() + .on('dragstart', function () { + d3.event.sourceEvent.preventDefault() + d3.event.sourceEvent.stopPropagation() }) - .on('drag', this._onBarDrag.bind(this)); + .on('drag', this._onBarDrag.bind(this)) - if(needsHorizontalScrollBar) { - this.hbar + if (needsHorizontalScrollBar) { + this.hbar .on('.drag', null) - .call(onBarDrag); - } + .call(onBarDrag) + } - if(needsVerticalScrollBar) { - this.vbar + if (needsVerticalScrollBar) { + this.vbar .on('.drag', null) - .call(onBarDrag); - } + .call(onBarDrag) } + } // set scrollbox translation - this.setTranslate(translateX, translateY); -}; + this.setTranslate(translateX, translateY) +} /** * If present, remove clip-path and scrollbars * * @method */ -ScrollBox.prototype.disable = function disable() { - if(this.hbar || this.vbar) { - this.bg.attr({ - width: 0, - height: 0 - }); - this.container +ScrollBox.prototype.disable = function disable () { + if (this.hbar || this.vbar) { + this.bg.attr({ + width: 0, + height: 0 + }) + this.container .on('wheel', null) .on('.drag', null) - .call(Drawing.setClipUrl, null); - delete this._clipRect; - } - - if(this.hbar) { - this.hbar.on('.drag', null); - this.hbar.remove(); - delete this.hbar; - delete this._hbarXMin; - delete this._hbarTranslateMax; - } - - if(this.vbar) { - this.vbar.on('.drag', null); - this.vbar.remove(); - delete this.vbar; - delete this._vbarYMin; - delete this._vbarTranslateMax; - } -}; + .call(Drawing.setClipUrl, null) + delete this._clipRect + } + + if (this.hbar) { + this.hbar.on('.drag', null) + this.hbar.remove() + delete this.hbar + delete this._hbarXMin + delete this._hbarTranslateMax + } + + if (this.vbar) { + this.vbar.on('.drag', null) + this.vbar.remove() + delete this.vbar + delete this._vbarYMin + delete this._vbarTranslateMax + } +} /** * Handles scroll box drag events * * @method */ -ScrollBox.prototype._onBoxDrag = function onBarDrag() { - var translateX = this.translateX, - translateY = this.translateY; +ScrollBox.prototype._onBoxDrag = function onBarDrag () { + var translateX = this.translateX, + translateY = this.translateY - if(this.hbar) { - translateX -= d3.event.dx; - } + if (this.hbar) { + translateX -= d3.event.dx + } - if(this.vbar) { - translateY -= d3.event.dy; - } + if (this.vbar) { + translateY -= d3.event.dy + } - this.setTranslate(translateX, translateY); -}; + this.setTranslate(translateX, translateY) +} /** * Handles scroll box wheel events * * @method */ -ScrollBox.prototype._onBoxWheel = function onBarWheel() { - var translateX = this.translateX, - translateY = this.translateY; +ScrollBox.prototype._onBoxWheel = function onBarWheel () { + var translateX = this.translateX, + translateY = this.translateY - if(this.hbar) { - translateX += d3.event.deltaY; - } + if (this.hbar) { + translateX += d3.event.deltaY + } - if(this.vbar) { - translateY += d3.event.deltaY; - } + if (this.vbar) { + translateY += d3.event.deltaY + } - this.setTranslate(translateX, translateY); -}; + this.setTranslate(translateX, translateY) +} /** * Handles scroll bar drag events * * @method */ -ScrollBox.prototype._onBarDrag = function onBarDrag() { - var translateX = this.translateX, - translateY = this.translateY; +ScrollBox.prototype._onBarDrag = function onBarDrag () { + var translateX = this.translateX, + translateY = this.translateY - if(this.hbar) { - var xMin = translateX + this._hbarXMin, - xMax = xMin + this._hbarTranslateMax, - x = Lib.constrain(d3.event.x, xMin, xMax), - xf = (x - xMin) / (xMax - xMin); + if (this.hbar) { + var xMin = translateX + this._hbarXMin, + xMax = xMin + this._hbarTranslateMax, + x = Lib.constrain(d3.event.x, xMin, xMax), + xf = (x - xMin) / (xMax - xMin) - var translateXMax = this.position.w - this._box.w; + var translateXMax = this.position.w - this._box.w - translateX = xf * translateXMax; - } + translateX = xf * translateXMax + } - if(this.vbar) { - var yMin = translateY + this._vbarYMin, - yMax = yMin + this._vbarTranslateMax, - y = Lib.constrain(d3.event.y, yMin, yMax), - yf = (y - yMin) / (yMax - yMin); + if (this.vbar) { + var yMin = translateY + this._vbarYMin, + yMax = yMin + this._vbarTranslateMax, + y = Lib.constrain(d3.event.y, yMin, yMax), + yf = (y - yMin) / (yMax - yMin) - var translateYMax = this.position.h - this._box.h; + var translateYMax = this.position.h - this._box.h - translateY = yf * translateYMax; - } + translateY = yf * translateYMax + } - this.setTranslate(translateX, translateY); -}; + this.setTranslate(translateX, translateY) +} /** * Set clip path and scroll bar translate transform @@ -429,41 +423,41 @@ ScrollBox.prototype._onBarDrag = function onBarDrag() { * @param {number} [translateX=0] Horizontal offset (in pixels) * @param {number} [translateY=0] Vertical offset (in pixels) */ -ScrollBox.prototype.setTranslate = function setTranslate(translateX, translateY) { +ScrollBox.prototype.setTranslate = function setTranslate (translateX, translateY) { // store translateX and translateY (needed by mouse event handlers) - var translateXMax = this.position.w - this._box.w, - translateYMax = this.position.h - this._box.h; + var translateXMax = this.position.w - this._box.w, + translateYMax = this.position.h - this._box.h - translateX = Lib.constrain(translateX || 0, 0, translateXMax); - translateY = Lib.constrain(translateY || 0, 0, translateYMax); + translateX = Lib.constrain(translateX || 0, 0, translateXMax) + translateY = Lib.constrain(translateY || 0, 0, translateYMax) - this.translateX = translateX; - this.translateY = translateY; + this.translateX = translateX + this.translateY = translateY - this.container.call(Drawing.setTranslate, + this.container.call(Drawing.setTranslate, this._box.l - this.position.l - translateX, - this._box.t - this.position.t - translateY); + this._box.t - this.position.t - translateY) - if(this._clipRect) { - this._clipRect.attr({ - x: Math.floor(this.position.l + translateX - 0.5), - y: Math.floor(this.position.t + translateY - 0.5) - }); - } + if (this._clipRect) { + this._clipRect.attr({ + x: Math.floor(this.position.l + translateX - 0.5), + y: Math.floor(this.position.t + translateY - 0.5) + }) + } - if(this.hbar) { - var xf = translateX / translateXMax; + if (this.hbar) { + var xf = translateX / translateXMax - this.hbar.call(Drawing.setTranslate, + this.hbar.call(Drawing.setTranslate, translateX + xf * this._hbarTranslateMax, - translateY); - } + translateY) + } - if(this.vbar) { - var yf = translateY / translateYMax; + if (this.vbar) { + var yf = translateY / translateYMax - this.vbar.call(Drawing.setTranslate, + this.vbar.call(Drawing.setTranslate, translateX, - translateY + yf * this._vbarTranslateMax); - } -}; + translateY + yf * this._vbarTranslateMax) + } +} diff --git a/src/constants/gl2d_dashes.js b/src/constants/gl2d_dashes.js index 8675739aa56..51a3bb887ba 100644 --- a/src/constants/gl2d_dashes.js +++ b/src/constants/gl2d_dashes.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' module.exports = { - solid: [1], - dot: [1, 1], - dash: [4, 1], - longdash: [8, 1], - dashdot: [4, 1, 1, 1], - longdashdot: [8, 1, 1, 1] -}; + solid: [1], + dot: [1, 1], + dash: [4, 1], + longdash: [8, 1], + dashdot: [4, 1, 1, 1], + longdashdot: [8, 1, 1, 1] +} diff --git a/src/constants/gl3d_dashes.js b/src/constants/gl3d_dashes.js index 8e4ac98164a..71879929d4f 100644 --- a/src/constants/gl3d_dashes.js +++ b/src/constants/gl3d_dashes.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' module.exports = { - solid: [[], 0], - dot: [[0.5, 1], 200], - dash: [[0.5, 1], 50], - longdash: [[0.5, 1], 10], - dashdot: [[0.5, 0.625, 0.875, 1], 50], - longdashdot: [[0.5, 0.7, 0.8, 1], 10] -}; + solid: [[], 0], + dot: [[0.5, 1], 200], + dash: [[0.5, 1], 50], + longdash: [[0.5, 1], 10], + dashdot: [[0.5, 0.625, 0.875, 1], 50], + longdashdot: [[0.5, 0.7, 0.8, 1], 10] +} diff --git a/src/constants/gl_markers.js b/src/constants/gl_markers.js index e10354e84a4..5bc6a4d9f1b 100644 --- a/src/constants/gl_markers.js +++ b/src/constants/gl_markers.js @@ -6,16 +6,15 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' module.exports = { - circle: '●', - 'circle-open': '○', - square: '■', - 'square-open': '□', - diamond: '◆', - 'diamond-open': '◇', - cross: '+', - x: '❌' -}; + circle: '●', + 'circle-open': '○', + square: '■', + 'square-open': '□', + diamond: '◆', + 'diamond-open': '◇', + cross: '+', + x: '❌' +} diff --git a/src/constants/interactions.js b/src/constants/interactions.js index 132e9ca4c37..2d6e122e644 100644 --- a/src/constants/interactions.js +++ b/src/constants/interactions.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { /** * Timing information for interactive elements */ - SHOW_PLACEHOLDER: 100, - HIDE_PLACEHOLDER: 1000 -}; + SHOW_PLACEHOLDER: 100, + HIDE_PLACEHOLDER: 1000 +} diff --git a/src/constants/numerical.js b/src/constants/numerical.js index c881daa72c4..88167219312 100644 --- a/src/constants/numerical.js +++ b/src/constants/numerical.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { /** @@ -16,14 +15,14 @@ module.exports = { * That way we can use !==undefined, or !== BADNUM, * to test for real data */ - BADNUM: undefined, + BADNUM: undefined, /* * Limit certain operations to well below floating point max value * to avoid glitches: Make sure that even when you multiply it by the * number of pixels on a giant screen it still works */ - FP_SAFE: Number.MAX_VALUE / 10000, + FP_SAFE: Number.MAX_VALUE / 10000, /* * conversion of date units to milliseconds @@ -31,16 +30,16 @@ module.exports = { * to remind us that not all years and months * have the same length */ - ONEAVGYEAR: 31557600000, // 365.25 days - ONEAVGMONTH: 2629800000, // 1/12 of ONEAVGYEAR - ONEDAY: 86400000, - ONEHOUR: 3600000, - ONEMIN: 60000, - ONESEC: 1000, + ONEAVGYEAR: 31557600000, // 365.25 days + ONEAVGMONTH: 2629800000, // 1/12 of ONEAVGYEAR + ONEDAY: 86400000, + ONEHOUR: 3600000, + ONEMIN: 60000, + ONESEC: 1000, /* * For fast conversion btwn world calendars and epoch ms, the Julian Day Number * of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD() */ - EPOCHJD: 2440587.5 -}; + EPOCHJD: 2440587.5 +} diff --git a/src/constants/string_mappings.js b/src/constants/string_mappings.js index a2760f7b6c0..4813000d948 100644 --- a/src/constants/string_mappings.js +++ b/src/constants/string_mappings.js @@ -6,31 +6,30 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' // N.B. HTML entities are listed without the leading '&' and trailing ';' module.exports = { - entityToUnicode: { - 'mu': 'μ', - 'amp': '&', - 'lt': '<', - 'gt': '>', - 'nbsp': ' ', - 'times': '×', - 'plusmn': '±', - 'deg': '°' - }, + entityToUnicode: { + 'mu': 'μ', + 'amp': '&', + 'lt': '<', + 'gt': '>', + 'nbsp': ' ', + 'times': '×', + 'plusmn': '±', + 'deg': '°' + }, - unicodeToEntity: { - '&': 'amp', - '<': 'lt', - '>': 'gt', - '"': 'quot', - '\'': '#x27', - '\/': '#x2F' - } + unicodeToEntity: { + '&': 'amp', + '<': 'lt', + '>': 'gt', + '"': 'quot', + '\'': '#x27', + '\/': '#x2F' + } -}; +} diff --git a/src/constants/xmlns_namespaces.js b/src/constants/xmlns_namespaces.js index aaeaea827a3..a99ef07d347 100644 --- a/src/constants/xmlns_namespaces.js +++ b/src/constants/xmlns_namespaces.js @@ -6,17 +6,15 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - - -exports.xmlns = 'http://www.w3.org/2000/xmlns/'; -exports.svg = 'http://www.w3.org/2000/svg'; -exports.xlink = 'http://www.w3.org/1999/xlink'; +exports.xmlns = 'http://www.w3.org/2000/xmlns/' +exports.svg = 'http://www.w3.org/2000/svg' +exports.xlink = 'http://www.w3.org/1999/xlink' // the 'old' d3 quirk got fix in v3.5.7 // https://github.com/mbostock/d3/commit/a6f66e9dd37f764403fc7c1f26be09ab4af24fed exports.svgAttrs = { - xmlns: exports.svg, - 'xmlns:xlink': exports.xlink -}; + xmlns: exports.svg, + 'xmlns:xlink': exports.xlink +} diff --git a/src/core.js b/src/core.js index 5c15942dcc0..12e0625dacf 100644 --- a/src/core.js +++ b/src/core.js @@ -6,72 +6,72 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /* * Export the plotly.js API methods. */ -var Plotly = require('./plotly'); +var Plotly = require('./plotly') // package version injected by `npm run preprocess` -exports.version = '1.23.0'; +exports.version = '1.23.0' // inject promise polyfill -require('es6-promise').polyfill(); +require('es6-promise').polyfill() // inject plot css -require('../build/plotcss'); +require('../build/plotcss') // inject default MathJax config -require('./fonts/mathjax_config'); +require('./fonts/mathjax_config') // plot api -exports.plot = Plotly.plot; -exports.newPlot = Plotly.newPlot; -exports.restyle = Plotly.restyle; -exports.relayout = Plotly.relayout; -exports.redraw = Plotly.redraw; -exports.update = Plotly.update; -exports.extendTraces = Plotly.extendTraces; -exports.prependTraces = Plotly.prependTraces; -exports.addTraces = Plotly.addTraces; -exports.deleteTraces = Plotly.deleteTraces; -exports.moveTraces = Plotly.moveTraces; -exports.purge = Plotly.purge; -exports.setPlotConfig = require('./plot_api/set_plot_config'); -exports.register = require('./plot_api/register'); -exports.toImage = require('./plot_api/to_image'); -exports.downloadImage = require('./snapshot/download'); -exports.validate = require('./plot_api/validate'); -exports.addFrames = Plotly.addFrames; -exports.deleteFrames = Plotly.deleteFrames; -exports.animate = Plotly.animate; +exports.plot = Plotly.plot +exports.newPlot = Plotly.newPlot +exports.restyle = Plotly.restyle +exports.relayout = Plotly.relayout +exports.redraw = Plotly.redraw +exports.update = Plotly.update +exports.extendTraces = Plotly.extendTraces +exports.prependTraces = Plotly.prependTraces +exports.addTraces = Plotly.addTraces +exports.deleteTraces = Plotly.deleteTraces +exports.moveTraces = Plotly.moveTraces +exports.purge = Plotly.purge +exports.setPlotConfig = require('./plot_api/set_plot_config') +exports.register = require('./plot_api/register') +exports.toImage = require('./plot_api/to_image') +exports.downloadImage = require('./snapshot/download') +exports.validate = require('./plot_api/validate') +exports.addFrames = Plotly.addFrames +exports.deleteFrames = Plotly.deleteFrames +exports.animate = Plotly.animate // scatter is the only trace included by default -exports.register(require('./traces/scatter')); +exports.register(require('./traces/scatter')) // register all registrable components modules exports.register([ - require('./components/legend'), - require('./components/annotations'), - require('./components/shapes'), - require('./components/images'), - require('./components/updatemenus'), - require('./components/sliders'), - require('./components/rangeslider'), - require('./components/rangeselector') -]); + require('./components/legend'), + require('./components/annotations'), + require('./components/shapes'), + require('./components/images'), + require('./components/updatemenus'), + require('./components/sliders'), + require('./components/rangeslider'), + require('./components/rangeselector') +]) // plot icons -exports.Icons = require('../build/ploticon'); +exports.Icons = require('../build/ploticon') // unofficial 'beta' plot methods, use at your own risk -exports.Plots = Plotly.Plots; -exports.Fx = Plotly.Fx; -exports.Snapshot = require('./snapshot'); -exports.PlotSchema = require('./plot_api/plot_schema'); -exports.Queue = require('./lib/queue'); +exports.Plots = Plotly.Plots +exports.Fx = Plotly.Fx +exports.Snapshot = require('./snapshot') +exports.PlotSchema = require('./plot_api/plot_schema') +exports.Queue = require('./lib/queue') // export d3 used in the bundle -exports.d3 = require('d3'); +exports.d3 = require('d3') diff --git a/src/fonts/mathjax_config.js b/src/fonts/mathjax_config.js index 8005ad86e13..8d070f22472 100644 --- a/src/fonts/mathjax_config.js +++ b/src/fonts/mathjax_config.js @@ -6,26 +6,26 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /* global MathJax:false */ /** * Check and configure MathJax */ -if(typeof MathJax !== 'undefined') { - exports.MathJax = true; +if (typeof MathJax !== 'undefined') { + exports.MathJax = true - MathJax.Hub.Config({ - messageStyle: 'none', - skipStartupTypeset: true, - displayAlign: 'left', - tex2jax: { - inlineMath: [['$', '$'], ['\\(', '\\)']] - } - }); + MathJax.Hub.Config({ + messageStyle: 'none', + skipStartupTypeset: true, + displayAlign: 'left', + tex2jax: { + inlineMath: [['$', '$'], ['\\(', '\\)']] + } + }) - MathJax.Hub.Configured(); + MathJax.Hub.Configured() } else { - exports.MathJax = false; + exports.MathJax = false } diff --git a/src/lib/array_to_calc_item.js b/src/lib/array_to_calc_item.js index 4a968234f0a..b82faa8bed4 100644 --- a/src/lib/array_to_calc_item.js +++ b/src/lib/array_to_calc_item.js @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' // similar to Lib.mergeArray, but using inside a loop -module.exports = function arrayToCalcItem(traceAttr, calcItem, calcAttr, i) { - if(Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i]; -}; +module.exports = function arrayToCalcItem (traceAttr, calcItem, calcAttr, i) { + if (Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i] +} diff --git a/src/lib/clean_number.js b/src/lib/clean_number.js index 922c2db7e94..ca35ca7724b 100644 --- a/src/lib/clean_number.js +++ b/src/lib/clean_number.js @@ -6,26 +6,25 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); - -var BADNUM = require('../constants/numerical').BADNUM; +var BADNUM = require('../constants/numerical').BADNUM // precompile for speed -var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g; +var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g /** * cleanNumber: remove common leading and trailing cruft * Always returns either a number or BADNUM. */ -module.exports = function cleanNumber(v) { - if(typeof v === 'string') { - v = v.replace(JUNK, ''); - } +module.exports = function cleanNumber (v) { + if (typeof v === 'string') { + v = v.replace(JUNK, '') + } - if(isNumeric(v)) return Number(v); + if (isNumeric(v)) return Number(v) - return BADNUM; -}; + return BADNUM +} diff --git a/src/lib/coerce.js b/src/lib/coerce.js index f3ef35d6598..cc1d297cf35 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -6,269 +6,264 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') +var tinycolor = require('tinycolor2') -var isNumeric = require('fast-isnumeric'); -var tinycolor = require('tinycolor2'); +var getColorscale = require('../components/colorscale/get_scale') +var colorscaleNames = Object.keys(require('../components/colorscale/scales')) +var nestedProperty = require('./nested_property') -var getColorscale = require('../components/colorscale/get_scale'); -var colorscaleNames = Object.keys(require('../components/colorscale/scales')); -var nestedProperty = require('./nested_property'); - -var ID_REGEX = /^([2-9]|[1-9][0-9]+)$/; +var ID_REGEX = /^([2-9]|[1-9][0-9]+)$/ exports.valObjects = { - data_array: { + 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) || + description: [ + 'An {array} of data.', + 'The value MUST be an {array}, or we ignore it.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function (v, propOut, dflt) { + if (Array.isArray(v)) propOut.set(v) + else if (dflt !== undefined) propOut.set(dflt) + } + }, + enumerated: { + description: [ + 'Enumerated value type. The available values are listed', + 'in `values`.' + ].join(' '), + requiredOpts: ['values'], + otherOpts: ['dflt', 'coerceNumber', 'arrayOk'], + coerceFunction: function (v, propOut, dflt, opts) { + if (opts.coerceNumber) v = +v + if (opts.values.indexOf(v) === -1) propOut.set(dflt) + else propOut.set(v) + } + }, + 'boolean': { + description: 'A boolean (true/false) value.', + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function (v, propOut, dflt) { + if (v === true || v === false) propOut.set(v) + else propOut.set(dflt) + } + }, + number: { + description: [ + 'A number or a numeric value', + '(e.g. a number inside a string).', + 'When applicable, values greater (less) than `max` (`min`)', + 'are coerced to the `dflt`.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'min', 'max', 'arrayOk'], + coerceFunction: function (v, propOut, dflt, opts) { + if (!isNumeric(v) || (opts.min !== undefined && v < opts.min) || (opts.max !== undefined && v > opts.max)) { - propOut.set(dflt); - } - else propOut.set(+v); - } - }, - integer: { - description: [ - 'An integer or an integer inside a string.', - 'When applicable, values greater (less) than `max` (`min`)', - 'are coerced to the `dflt`.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt', 'min', 'max'], - coerceFunction: function(v, propOut, dflt, opts) { - if(v % 1 || !isNumeric(v) || + propOut.set(dflt) + } else propOut.set(+v) + } + }, + integer: { + description: [ + 'An integer or an integer inside a string.', + 'When applicable, values greater (less) than `max` (`min`)', + 'are coerced to the `dflt`.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'min', 'max'], + coerceFunction: function (v, propOut, dflt, opts) { + if (v % 1 || !isNumeric(v) || (opts.min !== undefined && v < opts.min) || (opts.max !== undefined && v > opts.max)) { - propOut.set(dflt); - } - else propOut.set(+v); - } - }, - string: { - description: [ - 'A string value.', - 'Numbers are converted to strings except for attributes with', - '`strict` set to true.' - ].join(' '), - requiredOpts: [], + propOut.set(dflt) + } else propOut.set(+v) + } + }, + string: { + description: [ + 'A string value.', + 'Numbers are converted to strings except for attributes with', + '`strict` set to true.' + ].join(' '), + requiredOpts: [], // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter) - otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'], - coerceFunction: function(v, propOut, dflt, opts) { - if(typeof v !== 'string') { - var okToCoerce = (typeof v === 'number'); - - if(opts.strict === true || !okToCoerce) propOut.set(dflt); - else propOut.set(String(v)); - } - else if(opts.noBlank && !v) propOut.set(dflt); - else propOut.set(v); - } - }, - color: { - description: [ - 'A string describing color.', - 'Supported formats:', - '- hex (e.g. \'#d3d3d3\')', - '- rgb (e.g. \'rgb(255, 0, 0)\')', - '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')', - '- hsl (e.g. \'hsl(0, 100%, 50%)\')', - '- hsv (e.g. \'hsv(0, 100%, 100%)\')', - '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt', 'arrayOk'], - coerceFunction: function(v, propOut, dflt) { - if(tinycolor(v).isValid()) propOut.set(v); - else propOut.set(dflt); - } - }, - colorscale: { - description: [ - 'A Plotly colorscale either picked by a name:', - '(any of', colorscaleNames.join(', '), ')', - 'customized as an {array} of 2-element {arrays} where', - 'the first element is the normalized color level value', - '(starting at *0* and ending at *1*),', - 'and the second item is a valid color string.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - propOut.set(getColorscale(v, dflt)); - } - }, - angle: { - description: [ - 'A number (in degree) between -180 and 180.' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(v === 'auto') propOut.set('auto'); - else if(!isNumeric(v)) propOut.set(dflt); - else { - if(Math.abs(v) > 180) v -= Math.round(v / 360) * 360; - propOut.set(+v); - } - } - }, - subplotid: { - description: [ - 'An id string of a subplot type (given by dflt), optionally', - 'followed by an integer >1. e.g. if dflt=\'geo\', we can have', - '\'geo\', \'geo2\', \'geo3\', ...' - ].join(' '), - requiredOpts: ['dflt'], - otherOpts: [], - coerceFunction: function(v, propOut, dflt) { - var dlen = dflt.length; - if(typeof v === 'string' && v.substr(0, dlen) === dflt && + otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'], + coerceFunction: function (v, propOut, dflt, opts) { + if (typeof v !== 'string') { + var okToCoerce = (typeof v === 'number') + + if (opts.strict === true || !okToCoerce) propOut.set(dflt) + else propOut.set(String(v)) + } else if (opts.noBlank && !v) propOut.set(dflt) + else propOut.set(v) + } + }, + color: { + description: [ + 'A string describing color.', + 'Supported formats:', + '- hex (e.g. \'#d3d3d3\')', + '- rgb (e.g. \'rgb(255, 0, 0)\')', + '- rgba (e.g. \'rgb(255, 0, 0, 0.5)\')', + '- hsl (e.g. \'hsl(0, 100%, 50%)\')', + '- hsv (e.g. \'hsv(0, 100%, 100%)\')', + '- named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt', 'arrayOk'], + coerceFunction: function (v, propOut, dflt) { + if (tinycolor(v).isValid()) propOut.set(v) + else propOut.set(dflt) + } + }, + colorscale: { + description: [ + 'A Plotly colorscale either picked by a name:', + '(any of', colorscaleNames.join(', '), ')', + 'customized as an {array} of 2-element {arrays} where', + 'the first element is the normalized color level value', + '(starting at *0* and ending at *1*),', + 'and the second item is a valid color string.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function (v, propOut, dflt) { + propOut.set(getColorscale(v, dflt)) + } + }, + angle: { + description: [ + 'A number (in degree) between -180 and 180.' + ].join(' '), + requiredOpts: [], + otherOpts: ['dflt'], + coerceFunction: function (v, propOut, dflt) { + if (v === 'auto') propOut.set('auto') + else if (!isNumeric(v)) propOut.set(dflt) + else { + if (Math.abs(v) > 180) v -= Math.round(v / 360) * 360 + propOut.set(+v) + } + } + }, + subplotid: { + description: [ + 'An id string of a subplot type (given by dflt), optionally', + 'followed by an integer >1. e.g. if dflt=\'geo\', we can have', + '\'geo\', \'geo2\', \'geo3\', ...' + ].join(' '), + requiredOpts: ['dflt'], + otherOpts: [], + coerceFunction: function (v, propOut, dflt) { + var dlen = dflt.length + if (typeof v === 'string' && v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) { - propOut.set(v); - return; - } - propOut.set(dflt); - }, - validateFunction: function(v, opts) { - var dflt = opts.dflt, - dlen = dflt.length; - - if(v === dflt) return true; - if(typeof v !== 'string') return false; - if(v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) { - return true; - } - - return false; - } - }, - flaglist: { - description: [ - 'A string representing a combination of flags', - '(order does not matter here).', - 'Combine any of the available `flags` with *+*.', - '(e.g. (\'lines+markers\')).', - 'Values in `extras` cannot be combined.' - ].join(' '), - requiredOpts: ['flags'], - otherOpts: ['dflt', 'extras'], - coerceFunction: function(v, propOut, dflt, opts) { - if(typeof v !== 'string') { - propOut.set(dflt); - return; - } - if((opts.extras || []).indexOf(v) !== -1) { - propOut.set(v); - return; - } - var vParts = v.split('+'), - i = 0; - while(i < vParts.length) { - var vi = vParts[i]; - if(opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) { - vParts.splice(i, 1); - } - else i++; - } - if(!vParts.length) propOut.set(dflt); - else propOut.set(vParts.join('+')); - } + propOut.set(v) + return + } + propOut.set(dflt) }, - any: { - description: 'Any type.', - requiredOpts: [], - otherOpts: ['dflt', 'values', 'arrayOk'], - coerceFunction: function(v, propOut, dflt) { - if(v === undefined) propOut.set(dflt); - else propOut.set(v); - } + validateFunction: function (v, opts) { + var dflt = opts.dflt, + dlen = dflt.length + + if (v === dflt) return true + if (typeof v !== 'string') return false + if (v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) { + return true + } + + return false + } + }, + flaglist: { + description: [ + 'A string representing a combination of flags', + '(order does not matter here).', + 'Combine any of the available `flags` with *+*.', + '(e.g. (\'lines+markers\')).', + 'Values in `extras` cannot be combined.' + ].join(' '), + requiredOpts: ['flags'], + otherOpts: ['dflt', 'extras'], + coerceFunction: function (v, propOut, dflt, opts) { + if (typeof v !== 'string') { + propOut.set(dflt) + return + } + if ((opts.extras || []).indexOf(v) !== -1) { + propOut.set(v) + return + } + var vParts = v.split('+'), + i = 0 + while (i < vParts.length) { + var vi = vParts[i] + if (opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) { + vParts.splice(i, 1) + } else i++ + } + if (!vParts.length) propOut.set(dflt) + else propOut.set(vParts.join('+')) + } + }, + any: { + description: 'Any type.', + requiredOpts: [], + otherOpts: ['dflt', 'values', 'arrayOk'], + coerceFunction: function (v, propOut, dflt) { + if (v === undefined) propOut.set(dflt) + else propOut.set(v) + } + }, + info_array: { + description: [ + 'An {array} of plot information.' + ].join(' '), + requiredOpts: ['items'], + otherOpts: ['dflt', 'freeLength'], + coerceFunction: function (v, propOut, dflt, opts) { + if (!Array.isArray(v)) { + propOut.set(dflt) + return + } + + var items = opts.items, + vOut = [] + dflt = Array.isArray(dflt) ? dflt : [] + + for (var i = 0; i < items.length; i++) { + exports.coerce(v, vOut, items, '[' + i + ']', dflt[i]) + } + + propOut.set(vOut) }, - info_array: { - description: [ - 'An {array} of plot information.' - ].join(' '), - requiredOpts: ['items'], - otherOpts: ['dflt', 'freeLength'], - coerceFunction: function(v, propOut, dflt, opts) { - if(!Array.isArray(v)) { - propOut.set(dflt); - return; - } - - var items = opts.items, - vOut = []; - dflt = Array.isArray(dflt) ? dflt : []; - - for(var i = 0; i < items.length; i++) { - exports.coerce(v, vOut, items, '[' + i + ']', dflt[i]); - } - - propOut.set(vOut); - }, - validateFunction: function(v, opts) { - if(!Array.isArray(v)) return false; - - var items = opts.items; + validateFunction: function (v, opts) { + if (!Array.isArray(v)) return false + + var items = opts.items // when free length is off, input and declared lengths must match - if(!opts.freeLength && v.length !== items.length) return false; + if (!opts.freeLength && v.length !== items.length) return false // valid when all input items are valid - for(var i = 0; i < v.length; i++) { - var isItemValid = exports.validate(v[i], opts.items[i]); + for (var i = 0; i < v.length; i++) { + var isItemValid = exports.validate(v[i], opts.items[i]) - if(!isItemValid) return false; - } + if (!isItemValid) return false + } - return true; - } + return true } -}; + } +} /** * Ensures that container[attribute] has a valid value. @@ -282,13 +277,13 @@ exports.valObjects = { * if dflt is provided as an argument to lib.coerce it takes precedence * as a convenience, returns the value it finally set */ -exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt) { - var opts = nestedProperty(attributes, attribute).get(), - propIn = nestedProperty(containerIn, attribute), - propOut = nestedProperty(containerOut, attribute), - v = propIn.get(); +exports.coerce = function (containerIn, containerOut, attributes, attribute, dflt) { + var opts = nestedProperty(attributes, attribute).get(), + propIn = nestedProperty(containerIn, attribute), + propOut = nestedProperty(containerOut, attribute), + v = propIn.get() - if(dflt === undefined) dflt = opts.dflt; + if (dflt === undefined) dflt = opts.dflt /** * arrayOk: value MAY be an array, then we do no value checking @@ -296,15 +291,15 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt * individual form (eg. some array vals can be numbers, even if the * single values must be color strings) */ - if(opts.arrayOk && Array.isArray(v)) { - propOut.set(v); - return v; - } + if (opts.arrayOk && Array.isArray(v)) { + propOut.set(v) + return v + } - exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts); + exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts) - return propOut.get(); -}; + return propOut.get() +} /** * Variation on coerce @@ -313,46 +308,46 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt * returns attribute default if user input it not valid or * returns false if there is no user input. */ -exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dflt) { - var propIn = nestedProperty(containerIn, attribute), - propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt), - valIn = propIn.get(); +exports.coerce2 = function (containerIn, containerOut, attributes, attribute, dflt) { + var propIn = nestedProperty(containerIn, attribute), + propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt), + valIn = propIn.get() - return (valIn !== undefined && valIn !== null) ? propOut : false; -}; + return (valIn !== undefined && valIn !== null) ? propOut : false +} /* * Shortcut to coerce the three font attributes * * 'coerce' is a lib.coerce wrapper with implied first three arguments */ -exports.coerceFont = function(coerce, attr, dfltObj) { - var out = {}; +exports.coerceFont = function (coerce, attr, dfltObj) { + var out = {} - dfltObj = dfltObj || {}; + dfltObj = dfltObj || {} - out.family = coerce(attr + '.family', dfltObj.family); - out.size = coerce(attr + '.size', dfltObj.size); - out.color = coerce(attr + '.color', dfltObj.color); + out.family = coerce(attr + '.family', dfltObj.family) + out.size = coerce(attr + '.size', dfltObj.size) + out.color = coerce(attr + '.color', dfltObj.color) - return out; -}; + return out +} -exports.validate = function(value, opts) { - var valObject = exports.valObjects[opts.valType]; +exports.validate = function (value, opts) { + var valObject = exports.valObjects[opts.valType] - if(opts.arrayOk && Array.isArray(value)) return true; + if (opts.arrayOk && Array.isArray(value)) return true - if(valObject.validateFunction) { - return valObject.validateFunction(value, opts); - } + if (valObject.validateFunction) { + return valObject.validateFunction(value, opts) + } - var failed = {}, - out = failed, - propMock = { set: function(v) { out = v; } }; + var failed = {}, + out = failed, + propMock = { set: function (v) { out = v } } // 'failed' just something mutable that won't be === anything else - valObject.coerceFunction(value, propMock, failed, opts); - return out !== failed; -}; + valObject.coerceFunction(value, propMock, failed, opts) + return out !== failed +} diff --git a/src/lib/dates.js b/src/lib/dates.js index c5c05fcfda9..49df5bd87d9 100644 --- a/src/lib/dates.js +++ b/src/lib/dates.js @@ -6,40 +6,39 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var logError = require('./loggers').error +var mod = require('./mod') -var logError = require('./loggers').error; -var mod = require('./mod'); +var constants = require('../constants/numerical') +var BADNUM = constants.BADNUM +var ONEDAY = constants.ONEDAY +var ONEHOUR = constants.ONEHOUR +var ONEMIN = constants.ONEMIN +var ONESEC = constants.ONESEC +var EPOCHJD = constants.EPOCHJD -var constants = require('../constants/numerical'); -var BADNUM = constants.BADNUM; -var ONEDAY = constants.ONEDAY; -var ONEHOUR = constants.ONEHOUR; -var ONEMIN = constants.ONEMIN; -var ONESEC = constants.ONESEC; -var EPOCHJD = constants.EPOCHJD; +var Registry = require('../registry') -var Registry = require('../registry'); +var utcFormat = d3.time.format.utc -var utcFormat = d3.time.format.utc; - -var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m; +var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m // special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months -var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m; +var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m // for 2-digit years, the first year we map them onto -var YFIRST = new Date().getFullYear() - 70; +var YFIRST = new Date().getFullYear() - 70 -function isWorldCalendar(calendar) { - return ( +function isWorldCalendar (calendar) { + return ( calendar && Registry.componentsRegistry.calendars && typeof calendar === 'string' && calendar !== 'gregorian' - ); + ) } /* @@ -47,38 +46,36 @@ function isWorldCalendar(calendar) { * * bool sunday is for week ticks, shift it to a Sunday. */ -exports.dateTick0 = function(calendar, sunday) { - if(isWorldCalendar(calendar)) { - return sunday ? +exports.dateTick0 = function (calendar, sunday) { + if (isWorldCalendar(calendar)) { + return sunday ? Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : - Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar]; - } - else { - return sunday ? '2000-01-02' : '2000-01-01'; - } -}; + Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar] + } else { + return sunday ? '2000-01-02' : '2000-01-01' + } +} /* * dfltRange: for each calendar, give a valid default range */ -exports.dfltRange = function(calendar) { - if(isWorldCalendar(calendar)) { - return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar]; - } - else { - return ['2000-01-01', '2001-01-01']; - } -}; +exports.dfltRange = function (calendar) { + if (isWorldCalendar(calendar)) { + return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar] + } else { + return ['2000-01-01', '2001-01-01'] + } +} // is an object a javascript date? -exports.isJSDate = function(v) { - return typeof v === 'object' && v !== null && typeof v.getTime === 'function'; -}; +exports.isJSDate = function (v) { + return typeof v === 'object' && v !== null && typeof v.getTime === 'function' +} // The absolute limits of our date-time system // This is a little weird: we use MIN_MS and MAX_MS in dateTime2ms // but we use dateTime2ms to calculate them (after defining it!) -var MIN_MS, MAX_MS; +var MIN_MS, MAX_MS /** * dateTime2ms - turn a date object or string s into milliseconds @@ -133,98 +130,95 @@ var MIN_MS, MAX_MS; * currently (2016) this range is: * 1946-2045 */ -exports.dateTime2ms = function(s, calendar) { +exports.dateTime2ms = function (s, calendar) { // first check if s is a date object - if(exports.isJSDate(s)) { + if (exports.isJSDate(s)) { // Convert to the UTC milliseconds that give the same // hours as this date has in the local timezone - s = Number(s) - s.getTimezoneOffset() * ONEMIN; - if(s >= MIN_MS && s <= MAX_MS) return s; - return BADNUM; - } + s = Number(s) - s.getTimezoneOffset() * ONEMIN + if (s >= MIN_MS && s <= MAX_MS) return s + return BADNUM + } // otherwise only accept strings and numbers - if(typeof s !== 'string' && typeof s !== 'number') return BADNUM; + if (typeof s !== 'string' && typeof s !== 'number') return BADNUM - s = String(s); + s = String(s) - var isWorld = isWorldCalendar(calendar); + var isWorld = isWorldCalendar(calendar) // to handle out-of-range dates in international calendars, accept // 'G' as a prefix to force the built-in gregorian calendar. - var s0 = s.charAt(0); - if(isWorld && (s0 === 'G' || s0 === 'g')) { - s = s.substr(1); - calendar = ''; - } - - var isChinese = isWorld && calendar.substr(0, 7) === 'chinese'; - - var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP); - if(!match) return BADNUM; - var y = match[1], - m = match[3] || '1', - d = Number(match[5] || 1), - H = Number(match[7] || 0), - M = Number(match[9] || 0), - S = Number(match[11] || 0); - - if(isWorld) { + var s0 = s.charAt(0) + if (isWorld && (s0 === 'G' || s0 === 'g')) { + s = s.substr(1) + calendar = '' + } + + var isChinese = isWorld && calendar.substr(0, 7) === 'chinese' + + var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP) + if (!match) return BADNUM + var y = match[1], + m = match[3] || '1', + d = Number(match[5] || 1), + H = Number(match[7] || 0), + M = Number(match[9] || 0), + S = Number(match[11] || 0) + + if (isWorld) { // disallow 2-digit years for world calendars - if(y.length === 2) return BADNUM; - y = Number(y); - - var cDate; - try { - var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar); - if(isChinese) { - var isIntercalary = m.charAt(m.length - 1) === 'i'; - m = parseInt(m, 10); - cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d); - } - else { - cDate = calInstance.newDate(y, Number(m), d); - } - } - catch(e) { return BADNUM; } // Invalid ... date - - if(!cDate) return BADNUM; - - return ((cDate.toJD() - EPOCHJD) * ONEDAY) + - (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC); - } - - if(y.length === 2) { - y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST; - } - else y = Number(y); + if (y.length === 2) return BADNUM + y = Number(y) + + var cDate + try { + var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar) + if (isChinese) { + var isIntercalary = m.charAt(m.length - 1) === 'i' + m = parseInt(m, 10) + cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d) + } else { + cDate = calInstance.newDate(y, Number(m), d) + } + } catch (e) { return BADNUM } // Invalid ... date + + if (!cDate) return BADNUM + + return ((cDate.toJD() - EPOCHJD) * ONEDAY) + + (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC) + } + + if (y.length === 2) { + y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST + } else y = Number(y) // new Date uses months from 0; subtract 1 here just so we // don't have to do it again during the validity test below - m -= 1; + m -= 1 // javascript takes new Date(0..99,m,d) to mean 1900-1999, so // to support years 0-99 we need to use setFullYear explicitly // Note that 2000 is a leap year. - var date = new Date(Date.UTC(2000, m, d, H, M)); - date.setUTCFullYear(y); + var date = new Date(Date.UTC(2000, m, d, H, M)) + date.setUTCFullYear(y) - if(date.getUTCMonth() !== m) return BADNUM; - if(date.getUTCDate() !== d) return BADNUM; + if (date.getUTCMonth() !== m) return BADNUM + if (date.getUTCDate() !== d) return BADNUM - return date.getTime() + S * ONESEC; -}; + return date.getTime() + S * ONESEC +} -MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999'); -MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999'); +MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999') +MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999') // is string s a date? (see above) -exports.isDateTime = function(s, calendar) { - return (exports.dateTime2ms(s, calendar) !== BADNUM); -}; +exports.isDateTime = function (s, calendar) { + return (exports.dateTime2ms(s, calendar) !== BADNUM) +} // pad a number with zeroes, to given # of digits before the decimal point -function lpad(val, digits) { - return String(val + Math.pow(10, digits)).substr(1); +function lpad (val, digits) { + return String(val + Math.pow(10, digits)).substr(1) } /** @@ -235,63 +229,60 @@ function lpad(val, digits) { * Optional range r is the data range that applies, also in ms. * If rng is big, the later parts of time will be omitted */ -var NINETYDAYS = 90 * ONEDAY; -var THREEHOURS = 3 * ONEHOUR; -var FIVEMIN = 5 * ONEMIN; -exports.ms2DateTime = function(ms, r, calendar) { - if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM; - - if(!r) r = 0; - - var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), - msRounded = Math.round(ms - msecTenths / 10), - dateStr, h, m, s, msec10, d; - - if(isWorldCalendar(calendar)) { - var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD, - timeMs = Math.floor(mod(ms, ONEDAY)); - try { - dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) - .fromJD(dateJD).formatDate('yyyy-mm-dd'); - } - catch(e) { +var NINETYDAYS = 90 * ONEDAY +var THREEHOURS = 3 * ONEHOUR +var FIVEMIN = 5 * ONEMIN +exports.ms2DateTime = function (ms, r, calendar) { + if (typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM + + if (!r) r = 0 + + var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), + msRounded = Math.round(ms - msecTenths / 10), + dateStr, h, m, s, msec10, d + + if (isWorldCalendar(calendar)) { + var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD, + timeMs = Math.floor(mod(ms, ONEDAY)) + try { + dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .fromJD(dateJD).formatDate('yyyy-mm-dd') + } catch (e) { // invalid date in this calendar - fall back to Gyyyy-mm-dd - dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded)); - } + dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded)) + } // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does // other things for a few calendars, so we can't trust it. Just pad // it manually (after the '-' if there is one) - if(dateStr.charAt(0) === '-') { - while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1); - } - else { - while(dateStr.length < 10) dateStr = '0' + dateStr; - } + if (dateStr.charAt(0) === '-') { + while (dateStr.length < 11) dateStr = '-0' + dateStr.substr(1) + } else { + while (dateStr.length < 10) dateStr = '0' + dateStr + } // TODO: if this is faster, we could use this block for extracting // the time components of regular gregorian too - h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0; - m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0; - s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0; - msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0; - } - else { - d = new Date(msRounded); + h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0 + m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0 + s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0 + msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0 + } else { + d = new Date(msRounded) - dateStr = utcFormat('%Y-%m-%d')(d); + dateStr = utcFormat('%Y-%m-%d')(d) // <90 days: add hours and minutes - never *only* add hours - h = (r < NINETYDAYS) ? d.getUTCHours() : 0; - m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0; + h = (r < NINETYDAYS) ? d.getUTCHours() : 0 + m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0 // <3 hours: add seconds - s = (r < THREEHOURS) ? d.getUTCSeconds() : 0; + s = (r < THREEHOURS) ? d.getUTCSeconds() : 0 // <5 minutes: add ms (plus one extra digit, this is msec*10) - msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0; - } + msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0 + } - return includeTime(dateStr, h, m, s, msec10); -}; + return includeTime(dateStr, h, m, s, msec10) +} // For converting old-style milliseconds to date strings, // we use the local timezone rather than UTC like we use @@ -299,63 +290,62 @@ exports.ms2DateTime = function(ms, r, calendar) { // because that's how people mostly use javasript date objects. // Clip one extra day off our date range though so we can't get // thrown beyond the range by the timezone shift. -exports.ms2DateTimeLocal = function(ms) { - if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM; - - var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), - d = new Date(Math.round(ms - msecTenths / 10)), - dateStr = d3.time.format('%Y-%m-%d')(d), - h = d.getHours(), - m = d.getMinutes(), - s = d.getSeconds(), - msec10 = d.getUTCMilliseconds() * 10 + msecTenths; - - return includeTime(dateStr, h, m, s, msec10); -}; +exports.ms2DateTimeLocal = function (ms) { + if (!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM + + var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), + d = new Date(Math.round(ms - msecTenths / 10)), + dateStr = d3.time.format('%Y-%m-%d')(d), + h = d.getHours(), + m = d.getMinutes(), + s = d.getSeconds(), + msec10 = d.getUTCMilliseconds() * 10 + msecTenths + + return includeTime(dateStr, h, m, s, msec10) +} -function includeTime(dateStr, h, m, s, msec10) { +function includeTime (dateStr, h, m, s, msec10) { // include each part that has nonzero data in or after it - if(h || m || s || msec10) { - dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2); - if(s || msec10) { - dateStr += ':' + lpad(s, 2); - if(msec10) { - var digits = 4; - while(msec10 % 10 === 0) { - digits -= 1; - msec10 /= 10; - } - dateStr += '.' + lpad(msec10, digits); - } + if (h || m || s || msec10) { + dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2) + if (s || msec10) { + dateStr += ':' + lpad(s, 2) + if (msec10) { + var digits = 4 + while (msec10 % 10 === 0) { + digits -= 1 + msec10 /= 10 } + dateStr += '.' + lpad(msec10, digits) + } } - return dateStr; + } + return dateStr } // normalize date format to date string, in case it starts as // a Date object or milliseconds // optional dflt is the return value if cleaning fails -exports.cleanDate = function(v, dflt, calendar) { - if(exports.isJSDate(v) || typeof v === 'number') { +exports.cleanDate = function (v, dflt, calendar) { + if (exports.isJSDate(v) || typeof v === 'number') { // do not allow milliseconds (old) or jsdate objects (inherently // described as gregorian dates) with world calendars - if(isWorldCalendar(calendar)) { - logError('JS Dates and milliseconds are incompatible with world calendars', v); - return dflt; - } + if (isWorldCalendar(calendar)) { + logError('JS Dates and milliseconds are incompatible with world calendars', v) + return dflt + } // NOTE: if someone puts in a year as a number rather than a string, // this will mistakenly convert it thinking it's milliseconds from 1970 // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds - v = exports.ms2DateTimeLocal(+v); - if(!v && dflt !== undefined) return dflt; - } - else if(!exports.isDateTime(v, calendar)) { - logError('unrecognized date', v); - return dflt; - } - return v; -}; + v = exports.ms2DateTimeLocal(+v) + if (!v && dflt !== undefined) return dflt + } else if (!exports.isDateTime(v, calendar)) { + logError('unrecognized date', v) + return dflt + } + return v +} /* * Date formatting for ticks and hovertext @@ -366,28 +356,26 @@ exports.cleanDate = function(v, dflt, calendar) { * d3's vocabulary: * %{n}f where n is the max number of digits of fractional seconds */ -var fracMatch = /%\d?f/g; -function modDateFormat(fmt, x, calendar) { - - fmt = fmt.replace(fracMatch, function(match) { - var digits = Math.min(+(match.charAt(1)) || 6, 6), - fracSecs = ((x / 1000 % 1) + 2) +var fracMatch = /%\d?f/g +function modDateFormat (fmt, x, calendar) { + fmt = fmt.replace(fracMatch, function (match) { + var digits = Math.min(+(match.charAt(1)) || 6, 6), + fracSecs = ((x / 1000 % 1) + 2) .toFixed(digits) - .substr(2).replace(/0+$/, '') || '0'; - return fracSecs; - }); + .substr(2).replace(/0+$/, '') || '0' + return fracSecs + }) - var d = new Date(Math.floor(x + 0.05)); + var d = new Date(Math.floor(x + 0.05)) - if(isWorldCalendar(calendar)) { - try { - fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar); - } - catch(e) { - return 'Invalid'; - } + if (isWorldCalendar(calendar)) { + try { + fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar) + } catch (e) { + return 'Invalid' } - return utcFormat(fmt)(d); + } + return utcFormat(fmt)(d) } /* @@ -396,15 +384,15 @@ function modDateFormat(fmt, x, calendar) { * tr: tickround ('M', 'S', or # digits) * only supports UTC times (where every day is 24 hours and 0 is at midnight) */ -var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999]; -function formatTime(x, tr) { - var timePart = mod(x + 0.05, ONEDAY); +var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999] +function formatTime (x, tr) { + var timePart = mod(x + 0.05, ONEDAY) - var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' + - lpad(mod(Math.floor(timePart / ONEMIN), 60), 2); + var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' + + lpad(mod(Math.floor(timePart / ONEMIN), 60), 2) - if(tr !== 'M') { - if(!isNumeric(tr)) tr = 0; // should only be 'S' + if (tr !== 'M') { + if (!isNumeric(tr)) tr = 0 // should only be 'S' /* * this is a weird one - and shouldn't come up unless people @@ -421,27 +409,27 @@ function formatTime(x, tr) { * say we round seconds but floor everything else. BUT that means * we need to never round up to 60 seconds, ie 23:59:60 */ - var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]); - - var secStr = (100 + sec).toFixed(tr).substr(1); - if(tr > 0) { - secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, ''); - } + var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]) - timeStr += ':' + secStr; + var secStr = (100 + sec).toFixed(tr).substr(1) + if (tr > 0) { + secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, '') } - return timeStr; + + timeStr += ':' + secStr + } + return timeStr } var yearFormat = utcFormat('%Y'), - monthFormat = utcFormat('%b %Y'), - dayFormat = utcFormat('%b %-d'), - yearMonthDayFormat = utcFormat('%b %-d, %Y'); + monthFormat = utcFormat('%b %Y'), + dayFormat = utcFormat('%b %-d'), + yearMonthDayFormat = utcFormat('%b %-d, %Y') -function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); } -function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); } -function dayFormatWorld(cDate) { return cDate.formatDate('M d'); } -function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); } +function yearFormatWorld (cDate) { return cDate.formatDate('yyyy') } +function monthFormatWorld (cDate) { return cDate.formatDate('M yyyy') } +function dayFormatWorld (cDate) { return cDate.formatDate('M d') } +function yearMonthDayFormatWorld (cDate) { return cDate.formatDate('M d, yyyy') } /* * formatDate: turn a date into tick or hover label text. @@ -458,50 +446,46 @@ function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); * the axis may choose to strip things after it when they don't change from * one tick to the next (as it does with automatic formatting) */ -exports.formatDate = function(x, fmt, tr, calendar) { - var headStr, - dateStr; - - calendar = isWorldCalendar(calendar) && calendar; - - if(fmt) return modDateFormat(fmt, x, calendar); - - if(calendar) { - try { - var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, - cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) - .fromJD(dateJD); - - if(tr === 'y') dateStr = yearFormatWorld(cDate); - else if(tr === 'm') dateStr = monthFormatWorld(cDate); - else if(tr === 'd') { - headStr = yearFormatWorld(cDate); - dateStr = dayFormatWorld(cDate); - } - else { - headStr = yearMonthDayFormatWorld(cDate); - dateStr = formatTime(x, tr); - } - } - catch(e) { return 'Invalid'; } - } - else { - var d = new Date(Math.floor(x + 0.05)); - - if(tr === 'y') dateStr = yearFormat(d); - else if(tr === 'm') dateStr = monthFormat(d); - else if(tr === 'd') { - headStr = yearFormat(d); - dateStr = dayFormat(d); - } - else { - headStr = yearMonthDayFormat(d); - dateStr = formatTime(x, tr); - } +exports.formatDate = function (x, fmt, tr, calendar) { + var headStr, + dateStr + + calendar = isWorldCalendar(calendar) && calendar + + if (fmt) return modDateFormat(fmt, x, calendar) + + if (calendar) { + try { + var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, + cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .fromJD(dateJD) + + if (tr === 'y') dateStr = yearFormatWorld(cDate) + else if (tr === 'm') dateStr = monthFormatWorld(cDate) + else if (tr === 'd') { + headStr = yearFormatWorld(cDate) + dateStr = dayFormatWorld(cDate) + } else { + headStr = yearMonthDayFormatWorld(cDate) + dateStr = formatTime(x, tr) + } + } catch (e) { return 'Invalid' } + } else { + var d = new Date(Math.floor(x + 0.05)) + + if (tr === 'y') dateStr = yearFormat(d) + else if (tr === 'm') dateStr = monthFormat(d) + else if (tr === 'd') { + headStr = yearFormat(d) + dateStr = dayFormat(d) + } else { + headStr = yearMonthDayFormat(d) + dateStr = formatTime(x, tr) } + } - return dateStr + (headStr ? '\n' + headStr : ''); -}; + return dateStr + (headStr ? '\n' + headStr : '') +} /* * incrementMonth: make a new milliseconds value from the given one, @@ -529,36 +513,35 @@ exports.formatDate = function(x, fmt, tr, calendar) { * but at least you can't shift any dates into the wrong month, * and ticks on these days incrementing by month would be very unusual */ -var THREEDAYS = 3 * ONEDAY; -exports.incrementMonth = function(ms, dMonth, calendar) { - calendar = isWorldCalendar(calendar) && calendar; +var THREEDAYS = 3 * ONEDAY +exports.incrementMonth = function (ms, dMonth, calendar) { + calendar = isWorldCalendar(calendar) && calendar // pull time out and operate on pure dates, then add time back at the end // this gives maximum precision - not that we *normally* care if we're // incrementing by month, but better to be safe! - var timeMs = mod(ms, ONEDAY); - ms = Math.round(ms - timeMs); + var timeMs = mod(ms, ONEDAY) + ms = Math.round(ms - timeMs) - if(calendar) { - try { - var dateJD = Math.round(ms / ONEDAY) + EPOCHJD, - calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar), - cDate = calInstance.fromJD(dateJD); + if (calendar) { + try { + var dateJD = Math.round(ms / ONEDAY) + EPOCHJD, + calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar), + cDate = calInstance.fromJD(dateJD) - if(dMonth % 12) calInstance.add(cDate, dMonth, 'm'); - else calInstance.add(cDate, dMonth / 12, 'y'); + if (dMonth % 12) calInstance.add(cDate, dMonth, 'm') + else calInstance.add(cDate, dMonth / 12, 'y') - return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs; - } - catch(e) { - logError('invalid ms ' + ms + ' in calendar ' + calendar); + return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs + } catch (e) { + logError('invalid ms ' + ms + ' in calendar ' + calendar) // then keep going in gregorian even though the result will be 'Invalid' - } } + } - var y = new Date(ms + THREEDAYS); - return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS; -}; + var y = new Date(ms + THREEDAYS) + return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS +} /* * findExactDates: what fraction of data is exact days, months, or years? @@ -566,61 +549,57 @@ exports.incrementMonth = function(ms, dMonth, calendar) { * data: array of millisecond values * calendar (string) the calendar to test against */ -exports.findExactDates = function(data, calendar) { - var exactYears = 0, - exactMonths = 0, - exactDays = 0, - blankCount = 0, - d, - di; - - var calInstance = ( +exports.findExactDates = function (data, calendar) { + var exactYears = 0, + exactMonths = 0, + exactDays = 0, + blankCount = 0, + d, + di + + var calInstance = ( isWorldCalendar(calendar) && Registry.getComponentMethod('calendars', 'getCal')(calendar) - ); + ) - for(var i = 0; i < data.length; i++) { - di = data[i]; + for (var i = 0; i < data.length; i++) { + di = data[i] // not date data at all - if(!isNumeric(di)) { - blankCount ++; - continue; - } + if (!isNumeric(di)) { + blankCount++ + continue + } // not an exact date - if(di % ONEDAY) continue; - - if(calInstance) { - try { - d = calInstance.fromJD(di / ONEDAY + EPOCHJD); - if(d.day() === 1) { - if(d.month() === 1) exactYears++; - else exactMonths++; - } - else exactDays++; - } - catch(e) { + if (di % ONEDAY) continue + + if (calInstance) { + try { + d = calInstance.fromJD(di / ONEDAY + EPOCHJD) + if (d.day() === 1) { + if (d.month() === 1) exactYears++ + else exactMonths++ + } else exactDays++ + } catch (e) { // invalid date in this calendar - ignore it here. - } - } - else { - d = new Date(di); - if(d.getUTCDate() === 1) { - if(d.getUTCMonth() === 0) exactYears++; - else exactMonths++; - } - else exactDays++; - } + } + } else { + d = new Date(di) + if (d.getUTCDate() === 1) { + if (d.getUTCMonth() === 0) exactYears++ + else exactMonths++ + } else exactDays++ } - exactMonths += exactYears; - exactDays += exactMonths; + } + exactMonths += exactYears + exactDays += exactMonths - var dataCount = data.length - blankCount; + var dataCount = data.length - blankCount - return { - exactYears: exactYears / dataCount, - exactMonths: exactMonths / dataCount, - exactDays: exactDays / dataCount - }; -}; + return { + exactYears: exactYears / dataCount, + exactMonths: exactMonths / dataCount, + exactDays: exactDays / dataCount + } +} diff --git a/src/lib/events.js b/src/lib/events.js index 8238384242a..18039b79fd8 100644 --- a/src/lib/events.js +++ b/src/lib/events.js @@ -6,32 +6,30 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /* global jQuery:false */ -var EventEmitter = require('events').EventEmitter; +var EventEmitter = require('events').EventEmitter var Events = { - init: function(plotObj) { - + init: function (plotObj) { /* * If we have already instantiated an emitter for this plot * return early. */ - if(plotObj._ev instanceof EventEmitter) return plotObj; + if (plotObj._ev instanceof EventEmitter) return plotObj - var ev = new EventEmitter(); - var internalEv = new EventEmitter(); + var ev = new EventEmitter() + var internalEv = new EventEmitter() /* * Assign to plot._ev while we still live in a land * where plot is a DOM element with stuff attached to it. * In the future we can make plot the event emitter itself. */ - plotObj._ev = ev; + plotObj._ev = ev /* * Create a second event handler that will manage events *internally*. @@ -41,7 +39,7 @@ var Events = { * plotObj.removeAllListeners() would detach internal events, breaking * plotly. */ - plotObj._internalEv = internalEv; + plotObj._internalEv = internalEv /* * Assign bound methods from the ev to the plot object. These methods @@ -52,19 +50,19 @@ var Events = { * methods have been bound to `plot` as some do not currently add value to * the Plotly event API. */ - plotObj.on = ev.on.bind(ev); - plotObj.once = ev.once.bind(ev); - plotObj.removeListener = ev.removeListener.bind(ev); - plotObj.removeAllListeners = ev.removeAllListeners.bind(ev); + plotObj.on = ev.on.bind(ev) + plotObj.once = ev.once.bind(ev) + plotObj.removeListener = ev.removeListener.bind(ev) + plotObj.removeAllListeners = ev.removeAllListeners.bind(ev) /* * Create funtions for managing internal events. These are *only* triggered * by the mirroring of external events via the emit function. */ - plotObj._internalOn = internalEv.on.bind(internalEv); - plotObj._internalOnce = internalEv.once.bind(internalEv); - plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv); - plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv); + plotObj._internalOn = internalEv.on.bind(internalEv) + plotObj._internalOnce = internalEv.once.bind(internalEv) + plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv) + plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv) /* * We must wrap emit to continue to support JQuery events. The idea @@ -72,17 +70,17 @@ var Events = { * we emit JQuery events to trigger user handlers as well as the EventEmitter * events. */ - plotObj.emit = function(event, data) { - if(typeof jQuery !== 'undefined') { - jQuery(plotObj).trigger(event, data); - } + plotObj.emit = function (event, data) { + if (typeof jQuery !== 'undefined') { + jQuery(plotObj).trigger(event, data) + } - ev.emit(event, data); - internalEv.emit(event, data); - }; + ev.emit(event, data) + internalEv.emit(event, data) + } - return plotObj; - }, + return plotObj + }, /* * This function behaves like jQueries triggerHandler. It calls @@ -94,71 +92,71 @@ var Events = { * so the additional behavior of triggerHandler triggering internal events * is deliberate excluded in order to avoid reinforcing more usage. */ - triggerHandler: function(plotObj, event, data) { - var jQueryHandlerValue; - var nodeEventHandlerValue; + triggerHandler: function (plotObj, event, data) { + var jQueryHandlerValue + var nodeEventHandlerValue /* * If Jquery exists run all its handlers for this event and * collect the return value of the LAST handler function */ - if(typeof jQuery !== 'undefined') { - jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data); - } + if (typeof jQuery !== 'undefined') { + jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data) + } /* * Now run all the node style event handlers */ - var ev = plotObj._ev; - if(!ev) return jQueryHandlerValue; + var ev = plotObj._ev + if (!ev) return jQueryHandlerValue - var handlers = ev._events[event]; - if(!handlers) return jQueryHandlerValue; + var handlers = ev._events[event] + if (!handlers) return jQueryHandlerValue /* * handlers can be function or an array of functions */ - if(typeof handlers === 'function') handlers = [handlers]; - var lastHandler = handlers.pop(); + if (typeof handlers === 'function') handlers = [handlers] + var lastHandler = handlers.pop() /* * Call all the handlers except the last one. */ - for(var i = 0; i < handlers.length; i++) { - handlers[i](data); - } + for (var i = 0; i < handlers.length; i++) { + handlers[i](data) + } /* * Now call the final handler and collect its value */ - nodeEventHandlerValue = lastHandler(data); + nodeEventHandlerValue = lastHandler(data) /* * Return either the jquery handler value if it exists or the * nodeEventHandler value. Jquery event value superceeds nodejs * events for backwards compatability reasons. */ - return jQueryHandlerValue !== undefined ? jQueryHandlerValue : - nodeEventHandlerValue; - }, - - purge: function(plotObj) { - delete plotObj._ev; - delete plotObj.on; - delete plotObj.once; - delete plotObj.removeListener; - delete plotObj.removeAllListeners; - delete plotObj.emit; - - delete plotObj._ev; - delete plotObj._internalEv; - delete plotObj._internalOn; - delete plotObj._internalOnce; - delete plotObj._removeInternalListener; - delete plotObj._removeAllInternalListeners; - - return plotObj; - } - -}; - -module.exports = Events; + return jQueryHandlerValue !== undefined ? jQueryHandlerValue : + nodeEventHandlerValue + }, + + purge: function (plotObj) { + delete plotObj._ev + delete plotObj.on + delete plotObj.once + delete plotObj.removeListener + delete plotObj.removeAllListeners + delete plotObj.emit + + delete plotObj._ev + delete plotObj._internalEv + delete plotObj._internalOn + delete plotObj._internalOnce + delete plotObj._removeInternalListener + delete plotObj._removeAllInternalListeners + + return plotObj + } + +} + +module.exports = Events diff --git a/src/lib/extend.js b/src/lib/extend.js index b0591778b64..403c9ecebde 100644 --- a/src/lib/extend.js +++ b/src/lib/extend.js @@ -6,41 +6,40 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isPlainObject = require('./is_plain_object.js') +var isArray = Array.isArray -var isPlainObject = require('./is_plain_object.js'); -var isArray = Array.isArray; - -function primitivesLoopSplice(source, target) { - var i, value; - for(i = 0; i < source.length; i++) { - value = source[i]; - if(value !== null && typeof(value) === 'object') { - return false; - } - if(value !== void(0)) { - target[i] = value; - } +function primitivesLoopSplice (source, target) { + var i, value + for (i = 0; i < source.length; i++) { + value = source[i] + if (value !== null && typeof (value) === 'object') { + return false + } + if (value !== void (0)) { + target[i] = value } - return true; + } + return true } -exports.extendFlat = function() { - return _extend(arguments, false, false, false); -}; +exports.extendFlat = function () { + return _extend(arguments, false, false, false) +} -exports.extendDeep = function() { - return _extend(arguments, true, false, false); -}; +exports.extendDeep = function () { + return _extend(arguments, true, false, false) +} -exports.extendDeepAll = function() { - return _extend(arguments, true, true, false); -}; +exports.extendDeepAll = function () { + return _extend(arguments, true, true, false) +} -exports.extendDeepNoArrays = function() { - return _extend(arguments, true, false, true); -}; +exports.extendDeepNoArrays = function () { + return _extend(arguments, true, false, true) +} /* * Inspired by https://github.com/justmoon/node-extend/blob/master/index.js @@ -59,54 +58,53 @@ exports.extendDeepNoArrays = function() { * Warning: this might result in infinite loops. * */ -function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) { - var target = inputs[0], - length = inputs.length; - - var input, key, src, copy, copyIsArray, clone, allPrimitives; +function _extend (inputs, isDeep, keepAllKeys, noArrayCopies) { + var target = inputs[0], + length = inputs.length - if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) { + var input, key, src, copy, copyIsArray, clone, allPrimitives - allPrimitives = primitivesLoopSplice(inputs[1], target); + if (length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) { + allPrimitives = primitivesLoopSplice(inputs[1], target) - if(allPrimitives) { - return target; - } else { - target.splice(0, target.length); // reset target and continue to next block - } + if (allPrimitives) { + return target + } else { + target.splice(0, target.length) // reset target and continue to next block } + } - for(var i = 1; i < length; i++) { - input = inputs[i]; + for (var i = 1; i < length; i++) { + input = inputs[i] - for(key in input) { - src = target[key]; - copy = input[key]; + for (key in input) { + src = target[key] + copy = input[key] // Stop early and just transfer the array if array copies are disallowed: - if(noArrayCopies && isArray(copy)) { - target[key] = copy; - } + if (noArrayCopies && isArray(copy)) { + target[key] = copy + } // recurse if we're merging plain objects or arrays - else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { - if(copyIsArray) { - copyIsArray = false; - clone = src && isArray(src) ? src : []; - } else { - clone = src && isPlainObject(src) ? src : {}; - } + else if (isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false + clone = src && isArray(src) ? src : [] + } else { + clone = src && isPlainObject(src) ? src : {} + } // never move original objects, clone them - target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies); - } + target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies) + } // don't bring in undefined values, except for extendDeepAll - else if(typeof copy !== 'undefined' || keepAllKeys) { - target[key] = copy; - } - } + else if (typeof copy !== 'undefined' || keepAllKeys) { + target[key] = copy + } } + } - return target; + return target } diff --git a/src/lib/filter_unique.js b/src/lib/filter_unique.js index 5d035707696..d5ca64bcab5 100644 --- a/src/lib/filter_unique.js +++ b/src/lib/filter_unique.js @@ -6,9 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' /** * Return news array containing only the unique items @@ -31,19 +29,19 @@ * @param {array} array base array * @return {array} new filtered array */ -module.exports = function filterUnique(array) { - var seen = {}, - out = [], - j = 0; +module.exports = function filterUnique (array) { + var seen = {}, + out = [], + j = 0 - for(var i = 0; i < array.length; i++) { - var item = array[i]; + for (var i = 0; i < array.length; i++) { + var item = array[i] - if(seen[item] !== 1) { - seen[item] = 1; - out[j++] = item; - } + if (seen[item] !== 1) { + seen[item] = 1 + out[j++] = item } + } - return out; -}; + return out +} diff --git a/src/lib/filter_visible.js b/src/lib/filter_visible.js index fdcf6674de3..7a0e4a22c91 100644 --- a/src/lib/filter_visible.js +++ b/src/lib/filter_visible.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /** Filter out object items with visible !== true * insider array container. @@ -16,14 +15,14 @@ * @return {array of objects} of length <= container * */ -module.exports = function filterVisible(container) { - var out = []; +module.exports = function filterVisible (container) { + var out = [] - for(var i = 0; i < container.length; i++) { - var item = container[i]; + for (var i = 0; i < container.length; i++) { + var item = container[i] - if(item.visible === true) out.push(item); - } + if (item.visible === true) out.push(item) + } - return out; -}; + return out +} diff --git a/src/lib/geo_location_utils.js b/src/lib/geo_location_utils.js index 30795820c37..246ee16bc12 100644 --- a/src/lib/geo_location_utils.js +++ b/src/lib/geo_location_utils.js @@ -6,55 +6,53 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var countryRegex = require('country-regex'); -var Lib = require('../lib'); - +var countryRegex = require('country-regex') +var Lib = require('../lib') // make list of all country iso3 ids from at runtime -var countryIds = Object.keys(countryRegex); +var countryIds = Object.keys(countryRegex) var locationmodeToIdFinder = { - 'ISO-3': Lib.identity, - 'USA-states': Lib.identity, - 'country names': countryNameToISO3 -}; - -exports.locationToFeature = function(locationmode, location, features) { - var locationId = getLocationId(locationmode, location); + 'ISO-3': Lib.identity, + 'USA-states': Lib.identity, + 'country names': countryNameToISO3 +} - if(locationId) { - for(var i = 0; i < features.length; i++) { - var feature = features[i]; +exports.locationToFeature = function (locationmode, location, features) { + var locationId = getLocationId(locationmode, location) - if(feature.id === locationId) return feature; - } + if (locationId) { + for (var i = 0; i < features.length; i++) { + var feature = features[i] - Lib.warn([ - 'Location with id', locationId, - 'does not have a matching topojson feature at this resolution.' - ].join(' ')); + if (feature.id === locationId) return feature } - return false; -}; + Lib.warn([ + 'Location with id', locationId, + 'does not have a matching topojson feature at this resolution.' + ].join(' ')) + } -function getLocationId(locationmode, location) { - var idFinder = locationmodeToIdFinder[locationmode]; - return idFinder(location); + return false } -function countryNameToISO3(countryName) { - for(var i = 0; i < countryIds.length; i++) { - var iso3 = countryIds[i], - regex = new RegExp(countryRegex[iso3]); +function getLocationId (locationmode, location) { + var idFinder = locationmodeToIdFinder[locationmode] + return idFinder(location) +} - if(regex.test(countryName.toLowerCase())) return iso3; - } +function countryNameToISO3 (countryName) { + for (var i = 0; i < countryIds.length; i++) { + var iso3 = countryIds[i], + regex = new RegExp(countryRegex[iso3]) + + if (regex.test(countryName.toLowerCase())) return iso3 + } - Lib.warn('Unrecognized country name: ' + countryName + '.'); + Lib.warn('Unrecognized country name: ' + countryName + '.') - return false; + return false } diff --git a/src/lib/geojson_utils.js b/src/lib/geojson_utils.js index 919472f4b0d..ff3c9f24ed7 100644 --- a/src/lib/geojson_utils.js +++ b/src/lib/geojson_utils.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /** * Convert calcTrace to GeoJSON 'MultiLineString' coordinate arrays @@ -20,29 +19,28 @@ * return line coords array (or array of arrays) * */ -exports.calcTraceToLineCoords = function(calcTrace) { - var trace = calcTrace[0].trace, - connectgaps = trace.connectgaps; +exports.calcTraceToLineCoords = function (calcTrace) { + var trace = calcTrace[0].trace, + connectgaps = trace.connectgaps - var coords = [], - lineString = []; + var coords = [], + lineString = [] - for(var i = 0; i < calcTrace.length; i++) { - var calcPt = calcTrace[i]; + for (var i = 0; i < calcTrace.length; i++) { + var calcPt = calcTrace[i] - lineString.push(calcPt.lonlat); + lineString.push(calcPt.lonlat) - if(!connectgaps && calcPt.gapAfter && lineString.length > 0) { - coords.push(lineString); - lineString = []; - } + if (!connectgaps && calcPt.gapAfter && lineString.length > 0) { + coords.push(lineString) + lineString = [] } + } - coords.push(lineString); - - return coords; -}; + coords.push(lineString) + return coords +} /** * Make line ('LineString' or 'MultiLineString') GeoJSON @@ -56,26 +54,25 @@ exports.calcTraceToLineCoords = function(calcTrace) { * GeoJSON object * */ -exports.makeLine = function(coords, trace) { - var out = {}; - - if(coords.length === 1) { - out = { - type: 'LineString', - coordinates: coords[0] - }; +exports.makeLine = function (coords, trace) { + var out = {} + + if (coords.length === 1) { + out = { + type: 'LineString', + coordinates: coords[0] } - else { - out = { - type: 'MultiLineString', - coordinates: coords - }; + } else { + out = { + type: 'MultiLineString', + coordinates: coords } + } - if(trace) out.trace = trace; + if (trace) out.trace = trace - return out; -}; + return out +} /** * Make polygon ('Polygon' or 'MultiPolygon') GeoJSON @@ -88,32 +85,31 @@ exports.makeLine = function(coords, trace) { * @return {object} out * GeoJSON object */ -exports.makePolygon = function(coords, trace) { - var out = {}; - - if(coords.length === 1) { - out = { - type: 'Polygon', - coordinates: coords - }; +exports.makePolygon = function (coords, trace) { + var out = {} + + if (coords.length === 1) { + out = { + type: 'Polygon', + coordinates: coords } - else { - var _coords = new Array(coords.length); + } else { + var _coords = new Array(coords.length) - for(var i = 0; i < coords.length; i++) { - _coords[i] = [coords[i]]; - } + for (var i = 0; i < coords.length; i++) { + _coords[i] = [coords[i]] + } - out = { - type: 'MultiPolygon', - coordinates: _coords - }; + out = { + type: 'MultiPolygon', + coordinates: _coords } + } - if(trace) out.trace = trace; + if (trace) out.trace = trace - return out; -}; + return out +} /** * Make blank GeoJSON @@ -122,9 +118,9 @@ exports.makePolygon = function(coords, trace) { * Blank GeoJSON object * */ -exports.makeBlank = function() { - return { - type: 'Point', - coordinates: [] - }; -}; +exports.makeBlank = function () { + return { + type: 'Point', + coordinates: [] + } +} diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js index ac3f08aeb3e..b3815f33b88 100644 --- a/src/lib/gl_format_color.js +++ b/src/lib/gl_format_color.js @@ -6,76 +6,71 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var tinycolor = require('tinycolor2') +var isNumeric = require('fast-isnumeric') -var tinycolor = require('tinycolor2'); -var isNumeric = require('fast-isnumeric'); +var Colorscale = require('../components/colorscale') +var colorDflt = require('../components/color/attributes').defaultLine -var Colorscale = require('../components/colorscale'); -var colorDflt = require('../components/color/attributes').defaultLine; +var str2RgbaArray = require('./str2rgbarray') -var str2RgbaArray = require('./str2rgbarray'); +var opacityDflt = 1 -var opacityDflt = 1; - -function calculateColor(colorIn, opacityIn) { - var colorOut = str2RgbaArray(colorIn); - colorOut[3] *= opacityIn; - return colorOut; +function calculateColor (colorIn, opacityIn) { + var colorOut = str2RgbaArray(colorIn) + colorOut[3] *= opacityIn + return colorOut } -function validateColor(colorIn) { - return tinycolor(colorIn).isValid() ? colorIn : colorDflt; +function validateColor (colorIn) { + return tinycolor(colorIn).isValid() ? colorIn : colorDflt } -function validateOpacity(opacityIn) { - return isNumeric(opacityIn) ? opacityIn : opacityDflt; +function validateOpacity (opacityIn) { + return isNumeric(opacityIn) ? opacityIn : opacityDflt } -function formatColor(containerIn, opacityIn, len) { - var colorIn = containerIn.color, - isArrayColorIn = Array.isArray(colorIn), - isArrayOpacityIn = Array.isArray(opacityIn), - colorOut = []; +function formatColor (containerIn, opacityIn, len) { + var colorIn = containerIn.color, + isArrayColorIn = Array.isArray(colorIn), + isArrayOpacityIn = Array.isArray(opacityIn), + colorOut = [] - var sclFunc, getColor, getOpacity, colori, opacityi; + var sclFunc, getColor, getOpacity, colori, opacityi - if(containerIn.colorscale !== undefined) { - sclFunc = Colorscale.makeColorScaleFunc( + if (containerIn.colorscale !== undefined) { + sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( containerIn.colorscale, containerIn.cmin, containerIn.cmax ) - ); - } - else sclFunc = validateColor; + ) + } else sclFunc = validateColor - if(isArrayColorIn) { - getColor = function(c, i) { - return c[i] === undefined ? colorDflt : sclFunc(c[i]); - }; + if (isArrayColorIn) { + getColor = function (c, i) { + return c[i] === undefined ? colorDflt : sclFunc(c[i]) } - else getColor = validateColor; + } else getColor = validateColor - if(isArrayOpacityIn) { - getOpacity = function(o, i) { - return o[i] === undefined ? opacityDflt : validateOpacity(o[i]); - }; + if (isArrayOpacityIn) { + getOpacity = function (o, i) { + return o[i] === undefined ? opacityDflt : validateOpacity(o[i]) } - else getOpacity = validateOpacity; - - if(isArrayColorIn || isArrayOpacityIn) { - for(var i = 0; i < len; i++) { - colori = getColor(colorIn, i); - opacityi = getOpacity(opacityIn, i); - colorOut[i] = calculateColor(colori, opacityi); - } + } else getOpacity = validateOpacity + + if (isArrayColorIn || isArrayOpacityIn) { + for (var i = 0; i < len; i++) { + colori = getColor(colorIn, i) + opacityi = getOpacity(opacityIn, i) + colorOut[i] = calculateColor(colori, opacityi) } - else colorOut = calculateColor(colorIn, opacityIn); + } else colorOut = calculateColor(colorIn, opacityIn) - return colorOut; + return colorOut } -module.exports = formatColor; +module.exports = formatColor diff --git a/src/lib/html2unicode.js b/src/lib/html2unicode.js index 346ecaaf90f..fb563b42285 100644 --- a/src/lib/html2unicode.js +++ b/src/lib/html2unicode.js @@ -6,62 +6,61 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var toSuperScript = require('superscript-text') +var stringMappings = require('../constants/string_mappings') -var toSuperScript = require('superscript-text'); -var stringMappings = require('../constants/string_mappings'); +function fixSuperScript (x) { + var idx = 0 -function fixSuperScript(x) { - var idx = 0; + while ((idx = x.indexOf('', idx)) >= 0) { + var nidx = x.indexOf('', idx) + if (nidx < idx) break - while((idx = x.indexOf('', idx)) >= 0) { - var nidx = x.indexOf('', idx); - if(nidx < idx) break; + x = x.slice(0, idx) + toSuperScript(x.slice(idx + 5, nidx)) + x.slice(nidx + 6) + } - x = x.slice(0, idx) + toSuperScript(x.slice(idx + 5, nidx)) + x.slice(nidx + 6); - } - - return x; + return x } -function fixBR(x) { - return x.replace(/\/g, '\n'); +function fixBR (x) { + return x.replace(/\/g, '\n') } -function stripTags(x) { - return x.replace(/\<.*\>/g, ''); +function stripTags (x) { + return x.replace(/\<.*\>/g, '') } -function fixEntities(x) { - var entityToUnicode = stringMappings.entityToUnicode; - var idx = 0; +function fixEntities (x) { + var entityToUnicode = stringMappings.entityToUnicode + var idx = 0 - while((idx = x.indexOf('&', idx)) >= 0) { - var nidx = x.indexOf(';', idx); - if(nidx < idx) { - idx += 1; - continue; - } + while ((idx = x.indexOf('&', idx)) >= 0) { + var nidx = x.indexOf(';', idx) + if (nidx < idx) { + idx += 1 + continue + } - var entity = entityToUnicode[x.slice(idx + 1, nidx)]; - if(entity) { - x = x.slice(0, idx) + entity + x.slice(nidx + 1); - } else { - x = x.slice(0, idx) + x.slice(nidx + 1); - } + var entity = entityToUnicode[x.slice(idx + 1, nidx)] + if (entity) { + x = x.slice(0, idx) + entity + x.slice(nidx + 1) + } else { + x = x.slice(0, idx) + x.slice(nidx + 1) } + } - return x; + return x } -function convertHTMLToUnicode(html) { - return '' + +function convertHTMLToUnicode (html) { + return '' + fixEntities( stripTags( fixSuperScript( fixBR( - html)))); + html)))) } -module.exports = convertHTMLToUnicode; +module.exports = convertHTMLToUnicode diff --git a/src/lib/index.js b/src/lib/index.js index 9544f4b3794..26c4a194de4 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -6,101 +6,99 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var d3 = require('d3'); - -var lib = module.exports = {}; - -lib.nestedProperty = require('./nested_property'); -lib.isPlainObject = require('./is_plain_object'); -lib.isArray = require('./is_array'); -lib.mod = require('./mod'); - -var coerceModule = require('./coerce'); -lib.valObjects = coerceModule.valObjects; -lib.coerce = coerceModule.coerce; -lib.coerce2 = coerceModule.coerce2; -lib.coerceFont = coerceModule.coerceFont; -lib.validate = coerceModule.validate; - -var datesModule = require('./dates'); -lib.dateTime2ms = datesModule.dateTime2ms; -lib.isDateTime = datesModule.isDateTime; -lib.ms2DateTime = datesModule.ms2DateTime; -lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal; -lib.cleanDate = datesModule.cleanDate; -lib.isJSDate = datesModule.isJSDate; -lib.formatDate = datesModule.formatDate; -lib.incrementMonth = datesModule.incrementMonth; -lib.dateTick0 = datesModule.dateTick0; -lib.dfltRange = datesModule.dfltRange; -lib.findExactDates = datesModule.findExactDates; -lib.MIN_MS = datesModule.MIN_MS; -lib.MAX_MS = datesModule.MAX_MS; - -var searchModule = require('./search'); -lib.findBin = searchModule.findBin; -lib.sorterAsc = searchModule.sorterAsc; -lib.sorterDes = searchModule.sorterDes; -lib.distinctVals = searchModule.distinctVals; -lib.roundUp = searchModule.roundUp; - -var statsModule = require('./stats'); -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.extendDeepNoArrays = extendModule.extendDeepNoArrays; - -var loggersModule = require('./loggers'); -lib.log = loggersModule.log; -lib.warn = loggersModule.warn; -lib.error = loggersModule.error; - -lib.notifier = require('./notifier'); - -lib.filterUnique = require('./filter_unique'); -lib.filterVisible = require('./filter_visible'); - - -lib.cleanNumber = require('./clean_number'); +'use strict' + +var d3 = require('d3') + +var lib = module.exports = {} + +lib.nestedProperty = require('./nested_property') +lib.isPlainObject = require('./is_plain_object') +lib.isArray = require('./is_array') +lib.mod = require('./mod') + +var coerceModule = require('./coerce') +lib.valObjects = coerceModule.valObjects +lib.coerce = coerceModule.coerce +lib.coerce2 = coerceModule.coerce2 +lib.coerceFont = coerceModule.coerceFont +lib.validate = coerceModule.validate + +var datesModule = require('./dates') +lib.dateTime2ms = datesModule.dateTime2ms +lib.isDateTime = datesModule.isDateTime +lib.ms2DateTime = datesModule.ms2DateTime +lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal +lib.cleanDate = datesModule.cleanDate +lib.isJSDate = datesModule.isJSDate +lib.formatDate = datesModule.formatDate +lib.incrementMonth = datesModule.incrementMonth +lib.dateTick0 = datesModule.dateTick0 +lib.dfltRange = datesModule.dfltRange +lib.findExactDates = datesModule.findExactDates +lib.MIN_MS = datesModule.MIN_MS +lib.MAX_MS = datesModule.MAX_MS + +var searchModule = require('./search') +lib.findBin = searchModule.findBin +lib.sorterAsc = searchModule.sorterAsc +lib.sorterDes = searchModule.sorterDes +lib.distinctVals = searchModule.distinctVals +lib.roundUp = searchModule.roundUp + +var statsModule = require('./stats') +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.extendDeepNoArrays = extendModule.extendDeepNoArrays + +var loggersModule = require('./loggers') +lib.log = loggersModule.log +lib.warn = loggersModule.warn +lib.error = loggersModule.error + +lib.notifier = require('./notifier') + +lib.filterUnique = require('./filter_unique') +lib.filterVisible = require('./filter_visible') + +lib.cleanNumber = require('./clean_number') /** * 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); - } -}; +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. @@ -109,37 +107,37 @@ lib.swapAttrs = function(cont, attrList, part1, part2) { * 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; -}; +lib.pauseEvent = function (e) { + if (e.stopPropagation) e.stopPropagation() + if (e.preventDefault) e.preventDefault() + e.cancelBubble = true + return false +} // constrain - restrict a number v to be between v0 and v1 -lib.constrain = function(v, v0, v1) { - if(v0 > v1) return Math.max(v1, Math.min(v0, v)); - return Math.max(v0, Math.min(v1, v)); -}; +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 && +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); -}; + b.top <= a.bottom + pad) +} // minor convenience/performance booster for d3... -lib.identity = function(d) { return d; }; +lib.identity = function (d) { return d } // minor convenience helper -lib.noop = function() {}; +lib.noop = function () {} /* * simpleMap: alternative to Array.map that only @@ -150,55 +148,54 @@ lib.noop = function() {}; * func: the function to apply * x1, x2: optional extra args */ -lib.simpleMap = function(array, func, x1, x2) { - var len = array.length, - out = new Array(len); - for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2); - return out; -}; +lib.simpleMap = function (array, func, x1, x2) { + var len = array.length, + out = new Array(len) + for (var i = 0; i < len; i++) out[i] = func(array[i], x1, x2) + return out +} // random string generator -lib.randstr = function randstr(existing, bits, base) { +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)) || + 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; -}; + return randstr(existing, bits, base) + } else return res +} -lib.OptionControl = function(opt, optname) { +lib.OptionControl = function (opt, optname) { /* * An environment to contain all option setters and * getters that collectively modify opts. @@ -208,67 +205,67 @@ lib.OptionControl = function(opt, optname) { * * See FitOpts for example of usage */ - if(!opt) opt = {}; - if(!optname) optname = 'opt'; + if (!opt) opt = {} + if (!optname) optname = 'opt' - var self = {}; - self.optionList = []; + var self = {} + self.optionList = [] - self._newoption = function(optObj) { - optObj[optname] = opt; - self[optObj.name] = optObj; - self.optionList.push(optObj); - }; + self._newoption = function (optObj) { + optObj[optname] = opt + self[optObj.name] = optObj + self.optionList.push(optObj) + } - self['_' + optname] = opt; - return self; -}; + self['_' + optname] = opt + return self +} /** * 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; +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); - } + 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; + 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); + 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; + if (k < 0) k = -1 - k + else if (k >= alen) k = alen2 - 1 - k - v += arrayIn[k] * w[j]; - } - arrayOut[i] = v; + v += arrayIn[k] * w[j] } + arrayOut[i] = v + } - return arrayOut; -}; + return arrayOut +} /** * syncOrAsync: run a sequence of functions synchronously @@ -281,61 +278,60 @@ lib.smooth = function(arrayIn, FWHM) { * this doesn't happen yet because we want to make sure * that it gets reported */ -lib.syncOrAsync = function(sequence, arg, finalStep) { - var ret, fni; +lib.syncOrAsync = function (sequence, arg, finalStep) { + var ret, fni - function continueAsync() { - return lib.syncOrAsync(sequence, arg, finalStep); - } + function continueAsync () { + return lib.syncOrAsync(sequence, arg, finalStep) + } - while(sequence.length) { - fni = sequence.splice(0, 1)[0]; - ret = fni(arg); + while (sequence.length) { + fni = sequence.splice(0, 1)[0] + ret = fni(arg) - if(ret && ret.then) { - return ret.then(continueAsync) - .then(undefined, lib.promiseError); - } + if (ret && ret.then) { + return ret.then(continueAsync) + .then(undefined, lib.promiseError) } + } - return finalStep && finalStep(arg); -}; - + return finalStep && finalStep(arg) +} /** * Helper to strip trailing slash, from * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash */ -lib.stripTrailingSlash = function(str) { - if(str.substr(-1) === '/') return str.substr(0, str.length - 1); - return str; -}; +lib.stripTrailingSlash = function (str) { + if (str.substr(-1) === '/') return str.substr(0, str.length - 1) + return str +} -lib.noneOrAll = function(containerIn, containerOut, attrList) { +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]]; - } + 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]] } -}; + } +} /** * Push array with unique items @@ -348,18 +344,18 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) { * ref to array (now possibly containing one more item) * */ -lib.pushUnique = function(array, item) { - if(item && array.indexOf(item) === -1) array.push(item); +lib.pushUnique = function (array, item) { + if (item && array.indexOf(item) === -1) array.push(item) - return array; -}; + return array +} -lib.mergeArray = function(traceAttr, cd, cdAttr) { - if(Array.isArray(traceAttr)) { - var imax = Math.min(traceAttr.length, cd.length); - for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i]; - } -}; +lib.mergeArray = function (traceAttr, cd, cdAttr) { + if (Array.isArray(traceAttr)) { + var imax = Math.min(traceAttr.length, cd.length) + for (var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i] + } +} /** * modified version of jQuery's extend to strip out private objs and functions, @@ -367,102 +363,99 @@ lib.mergeArray = function(traceAttr, cd, cdAttr) { * 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; +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 } + } - keys = Object.keys(obj2); - for(i = 0; i < keys.length; i++) { - k = keys[i]; - v = obj2[k]; - if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') { - objOut[k] = v; - } - } + return objOut +} - return objOut; -}; +lib.titleCase = function (s) { + return s.charAt(0).toUpperCase() + s.substr(1) +} -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; -}; +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.node() instanceof HTMLElement && +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.node() instanceof HTMLElement && el3.size() && - el3.classed('js-plotly-plot'); -}; + el3.classed('js-plotly-plot') +} -lib.removeElement = function(el) { - var elParent = el && el.parentNode; - if(elParent) elParent.removeChild(el); -}; +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'); +lib.addStyleRule = function (selector, styleString) { + if (!lib.styleSheet) { + var style = document.createElement('style') // WebKit hack :( - style.appendChild(document.createTextNode('')); - document.head.appendChild(style); - lib.styleSheet = style.sheet; - } - var styleSheet = lib.styleSheet; - - if(styleSheet.insertRule) { - styleSheet.insertRule(selector + '{' + styleString + '}', 0); - } - else if(styleSheet.addRule) { - styleSheet.addRule(selector, styleString, 0); - } - else lib.warn('addStyleRule failed'); -}; - -lib.isIE = function() { - return typeof window.navigator.msSaveBlob !== 'undefined'; -}; + style.appendChild(document.createTextNode('')) + document.head.appendChild(style) + lib.styleSheet = style.sheet + } + var styleSheet = lib.styleSheet + + if (styleSheet.insertRule) { + styleSheet.insertRule(selector + '{' + styleString + '}', 0) + } else if (styleSheet.addRule) { + styleSheet.addRule(selector, styleString, 0) + } else lib.warn('addStyleRule failed') +} + +lib.isIE = function () { + return typeof window.navigator.msSaveBlob !== 'undefined' +} /** * Duck typing to recognize a d3 selection, mostly for IE9's benefit * because it doesn't handle instanceof like modern browsers */ -lib.isD3Selection = function(obj) { - return obj && (typeof obj.classed === 'function'); -}; - +lib.isD3Selection = function (obj) { + return obj && (typeof obj.classed === 'function') +} /** * Converts a string path to an object. @@ -479,44 +472,43 @@ lib.isD3Selection = function(obj) { * * @return {Object} the constructed object with a full nested path */ -lib.objectFromPath = function(path, value) { - var keys = path.split('.'), - tmpObj, - obj = tmpObj = {}; - - for(var i = 0; i < keys.length; i++) { - var key = keys[i]; - var el = null; - - var parts = keys[i].match(/(.*)\[([0-9]+)\]/); - - if(parts) { - key = parts[1]; - el = parts[2]; - - tmpObj = tmpObj[key] = []; - - if(i === keys.length - 1) { - tmpObj[el] = value; - } else { - tmpObj[el] = {}; - } - - tmpObj = tmpObj[el]; - } else { - - if(i === keys.length - 1) { - tmpObj[key] = value; - } else { - tmpObj[key] = {}; - } - - tmpObj = tmpObj[key]; - } +lib.objectFromPath = function (path, value) { + var keys = path.split('.'), + tmpObj, + obj = tmpObj = {} + + for (var i = 0; i < keys.length; i++) { + var key = keys[i] + var el = null + + var parts = keys[i].match(/(.*)\[([0-9]+)\]/) + + if (parts) { + key = parts[1] + el = parts[2] + + tmpObj = tmpObj[key] = [] + + if (i === keys.length - 1) { + tmpObj[el] = value + } else { + tmpObj[el] = {} + } + + tmpObj = tmpObj[el] + } else { + if (i === keys.length - 1) { + tmpObj[key] = value + } else { + tmpObj[key] = {} + } + + tmpObj = tmpObj[key] } + } - return obj; -}; + return obj +} /** * Iterate through an object in-place, converting dotted properties to objects. @@ -546,36 +538,36 @@ lib.objectFromPath = function(path, value) { // many times for animations. Could maybe be inside the function. Not sure about // scoping vs. recompilation tradeoff, but at least it's not just inlining it into // the inner loop. -var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/; -var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/; +var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/ +var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/ -lib.expandObjectPaths = function(data) { - var match, key, prop, datum, idx, dest, trailingPath; - if(typeof data === 'object' && !Array.isArray(data)) { - for(key in data) { - if(data.hasOwnProperty(key)) { - if((match = key.match(dottedPropertyRegex))) { - datum = data[key]; - prop = match[1]; +lib.expandObjectPaths = function (data) { + var match, key, prop, datum, idx, dest, trailingPath + if (typeof data === 'object' && !Array.isArray(data)) { + for (key in data) { + if (data.hasOwnProperty(key)) { + if ((match = key.match(dottedPropertyRegex))) { + datum = data[key] + prop = match[1] - delete data[key]; + delete data[key] - data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]); - } else if((match = key.match(indexedPropertyRegex))) { - datum = data[key]; + data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]) + } else if ((match = key.match(indexedPropertyRegex))) { + datum = data[key] - prop = match[1]; - idx = parseInt(match[2]); + prop = match[1] + idx = parseInt(match[2]) - delete data[key]; + delete data[key] - data[prop] = data[prop] || []; + data[prop] = data[prop] || [] - if(match[3] === '.') { + if (match[3] === '.') { // This is the case where theere are subsequent properties into which // we must recurse, e.g. transforms[0].value - trailingPath = match[4]; - dest = data[prop][idx] = data[prop][idx] || {}; + trailingPath = match[4] + dest = data[prop][idx] = data[prop][idx] || {} // NB: Extend deep no arrays prevents this from working on multiple // nested properties in the same object, e.g. @@ -589,21 +581,21 @@ lib.expandObjectPaths = function(data) { // the other, so that both properties *will not* be present in the // result. Fixing this would require a more intelligent tracking // of changes and merging than extendDeepNoArrays currently accomplishes. - lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum))); - } else { + lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum))) + } else { // This is the case where this property is the end of the line, // e.g. xaxis.range[0] - data[prop][idx] = lib.expandObjectPaths(datum); - } - } else { - data[key] = lib.expandObjectPaths(data[key]); - } - } + data[prop][idx] = lib.expandObjectPaths(datum) + } + } else { + data[key] = lib.expandObjectPaths(data[key]) } + } } + } - return data; -}; + return data +} /** * Converts value to string separated by the provided separators. @@ -626,31 +618,31 @@ lib.expandObjectPaths = function(data) { * * @return {string} the value that has been separated */ -lib.numSeparate = function(value, separators, separatethousands) { - if(!separatethousands) separatethousands = false; +lib.numSeparate = function (value, separators, separatethousands) { + if (!separatethousands) separatethousands = false - if(typeof separators !== 'string' || separators.length === 0) { - throw new Error('Separator string required for formatting!'); - } + if (typeof separators !== 'string' || separators.length === 0) { + throw new Error('Separator string required for formatting!') + } - if(typeof value === 'number') { - value = String(value); - } + if (typeof value === 'number') { + value = String(value) + } - var thousandsRe = /(\d+)(\d{3})/, - decimalSep = separators.charAt(0), - thouSep = separators.charAt(1); + var thousandsRe = /(\d+)(\d{3})/, + decimalSep = separators.charAt(0), + thouSep = separators.charAt(1) - var x = value.split('.'), - x1 = x[0], - x2 = x.length > 1 ? decimalSep + x[1] : ''; + var x = value.split('.'), + x1 = x[0], + x2 = x.length > 1 ? decimalSep + x[1] : '' // Years are ignored for thousands separators - if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { - while(thousandsRe.test(x1)) { - x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2'); - } + if (thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { + while (thousandsRe.test(x1)) { + x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2') } + } - return x1 + x2; -}; + return x1 + x2 +} diff --git a/src/lib/is_array.js b/src/lib/is_array.js index cda78eeb627..bc06db1aa5e 100644 --- a/src/lib/is_array.js +++ b/src/lib/is_array.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /** * Return true for arrays, whether they're untyped or not. @@ -14,9 +14,9 @@ // IE9 fallback var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ? - {isView: function() { return false; }} : - ArrayBuffer; + {isView: function () { return false }} : + ArrayBuffer -module.exports = function isArray(a) { - return Array.isArray(a) || ab.isView(a); -}; +module.exports = function isArray (a) { + return Array.isArray(a) || ab.isView(a) +} diff --git a/src/lib/is_plain_object.js b/src/lib/is_plain_object.js index d114e022d2f..b248e3ae4bc 100644 --- a/src/lib/is_plain_object.js +++ b/src/lib/is_plain_object.js @@ -6,22 +6,20 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' // more info: http://stackoverflow.com/questions/18531624/isplainobject-thing -module.exports = function isPlainObject(obj) { - +module.exports = function isPlainObject (obj) { // We need to be a little less strict in the `imagetest` container because // of how async image requests are handled. // // N.B. isPlainObject(new Constructor()) will return true in `imagetest` - if(window && window.process && window.process.versions) { - return Object.prototype.toString.call(obj) === '[object Object]'; - } + if (window && window.process && window.process.versions) { + return Object.prototype.toString.call(obj) === '[object Object]' + } - return ( + return ( Object.prototype.toString.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === Object.prototype - ); -}; + ) +} diff --git a/src/lib/loggers.js b/src/lib/loggers.js index 428f053e000..e423f8c3bd6 100644 --- a/src/lib/loggers.js +++ b/src/lib/loggers.js @@ -6,13 +6,13 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /* eslint-disable no-console */ -var config = require('../plot_api/plot_config'); +var config = require('../plot_api/plot_config') -var loggers = module.exports = {}; +var loggers = module.exports = {} /** * ------------------------------------------ @@ -20,53 +20,52 @@ var loggers = module.exports = {}; * ------------------------------------------ */ -loggers.log = function() { - if(config.logging > 1) { - var messages = ['LOG:']; +loggers.log = function () { + if (config.logging > 1) { + var messages = ['LOG:'] - for(var i = 0; i < arguments.length; i++) { - messages.push(arguments[i]); - } - - apply(console.trace || console.log, messages); + for (var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]) } -}; -loggers.warn = function() { - if(config.logging > 0) { - var messages = ['WARN:']; + apply(console.trace || console.log, messages) + } +} - for(var i = 0; i < arguments.length; i++) { - messages.push(arguments[i]); - } +loggers.warn = function () { + if (config.logging > 0) { + var messages = ['WARN:'] - apply(console.trace || console.log, messages); + for (var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]) } -}; -loggers.error = function() { - if(config.logging > 0) { - var messages = ['ERROR:']; + apply(console.trace || console.log, messages) + } +} - for(var i = 0; i < arguments.length; i++) { - messages.push(arguments[i]); - } +loggers.error = function () { + if (config.logging > 0) { + var messages = ['ERROR:'] - apply(console.error, messages); + for (var i = 0; i < arguments.length; i++) { + messages.push(arguments[i]) } -}; + + apply(console.error, messages) + } +} /* * Robust apply, for IE9 where console.log doesn't support * apply like other functions do */ -function apply(f, args) { - if(f.apply) { - f.apply(f, args); - } - else { - for(var i = 0; i < args.length; i++) { - f(args[i]); - } +function apply (f, args) { + if (f.apply) { + f.apply(f, args) + } else { + for (var i = 0; i < args.length; i++) { + f(args[i]) } + } } diff --git a/src/lib/matrix.js b/src/lib/matrix.js index 2429195de05..08614c55db2 100644 --- a/src/lib/matrix.js +++ b/src/lib/matrix.js @@ -6,103 +6,99 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - - -exports.init2dArray = function(rowLength, colLength) { - var array = new Array(rowLength); - for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength); - return array; -}; +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; +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); + for (i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length) - var t = new Array(maxlen); - for(i = 0; i < maxlen; i++) { - t[i] = new Array(zlen); - for(j = 0; j < zlen; j++) t[i][j] = z[j][i]; - } + var t = new Array(maxlen) + for (i = 0; i < maxlen; i++) { + t[i] = new Array(zlen) + for (j = 0; j < zlen; j++) t[i][j] = z[j][i] + } - return t; -}; + return t +} // our own dot function so that we don't need to include numeric -exports.dot = function(x, y) { - if(!(x.length && y.length) || x.length !== y.length) return null; +exports.dot = function (x, y) { + if (!(x.length && y.length) || x.length !== y.length) return null - var len = x.length, - out, - i; + var len = x.length, + out, + i - if(x[0].length) { + 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) { + 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 { + 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]; - } + out = 0 + for (i = 0; i < len; i++) out += x[i] * y[i] + } - return out; -}; + return out +} // translate by (x,y) -exports.translationMatrix = function(x, y) { - return [[1, 0, x], [0, 1, y], [0, 0, 1]]; -}; +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], +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]]; -}; + [0, 0, 1]] +} // rotate by alpha around (x,y) -exports.rotationXYMatrix = function(a, x, y) { - return exports.dot( +exports.rotationXYMatrix = function (a, x, y) { + return exports.dot( exports.dot(exports.translationMatrix(x, y), exports.rotationMatrix(a)), - exports.translationMatrix(-x, -y)); -}; + 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); - }; -}; +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))); - }; -}; +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/mod.js b/src/lib/mod.js index 6ddf24e2563..ec9ed59702e 100644 --- a/src/lib/mod.js +++ b/src/lib/mod.js @@ -6,13 +6,13 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /** * sanitized modulus function that always returns in the range [0, d) * rather than (-d, 0] if v is negative */ -module.exports = function mod(v, d) { - var out = v % d; - return out < 0 ? out + d : out; -}; +module.exports = function mod (v, d) { + var out = v % d + return out < 0 ? out + d : out +} diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js index a00cd17137a..e697f845339 100644 --- a/src/lib/nested_property.js +++ b/src/lib/nested_property.js @@ -6,11 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); -var isArray = require('./is_array'); +var isNumeric = require('fast-isnumeric') +var isArray = require('./is_array') /** * convert a string s (such as 'xaxis.range[0]') @@ -26,90 +25,90 @@ var isArray = require('./is_array'); * 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' || +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'; - } + throw 'bad property string' + } - var j = 0, - propParts = propStr.split('.'), - indexed, - indices, - i; + 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) { + 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]; + 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'; + else if (j === 0) propParts.splice(0, 1) + else throw 'bad property string' - indices = indexed[2] + indices = indexed[2] .substr(1, indexed[2].length - 2) - .split(']['); - - for(i = 0; i < indices.length; i++) { - j++; - propParts.splice(j, 0, Number(indices[i])); - } - } - j++; - } + .split('][') - if(typeof container !== 'object') { - return badContainer(container, propStr, propParts); + for (i = 0; i < indices.length; i++) { + j++ + propParts.splice(j, 0, Number(indices[i])) + } } + j++ + } + + if (typeof container !== 'object') { + return badContainer(container, propStr, propParts) + } + + return { + set: npSet(container, propParts), + get: npGet(container, propParts), + astr: propStr, + parts: propParts, + obj: container + } +} - return { - set: npSet(container, propParts), - get: npGet(container, propParts), - astr: propStr, - parts: propParts, - obj: container - }; -}; - -function npGet(cont, parts) { - return function() { - var curCont = cont, - curPart, - allSame, - out, - i, - j; - - for(i = 0; i < parts.length - 1; i++) { - curPart = parts[i]; - if(curPart === -1) { - allSame = true; - out = []; - for(j = 0; j < curCont.length; j++) { - out[j] = npGet(curCont[j], parts.slice(i + 1))(); - if(out[j] !== out[0]) allSame = false; - } - return allSame ? out[0] : out; - } - if(typeof curPart === 'number' && !isArray(curCont)) { - return undefined; - } - curCont = curCont[curPart]; - if(typeof curCont !== 'object' || curCont === null) { - return undefined; - } +function npGet (cont, parts) { + return function () { + var curCont = cont, + curPart, + allSame, + out, + i, + j + + for (i = 0; i < parts.length - 1; i++) { + curPart = parts[i] + if (curPart === -1) { + allSame = true + out = [] + for (j = 0; j < curCont.length; j++) { + out[j] = npGet(curCont[j], parts.slice(i + 1))() + if (out[j] !== out[0]) allSame = false } + return allSame ? out[0] : out + } + if (typeof curPart === 'number' && !isArray(curCont)) { + return undefined + } + curCont = curCont[curPart] + if (typeof curCont !== 'object' || curCont === null) { + return undefined + } + } // only hit this if parts.length === 1 - if(typeof curCont !== 'object' || curCont === null) return undefined; + if (typeof curCont !== 'object' || curCont === null) return undefined - out = curCont[parts[i]]; - if(out === null) return undefined; - return out; - }; + out = curCont[parts[i]] + if (out === null) return undefined + return out + } } /* @@ -118,78 +117,76 @@ function npGet(cont, parts) { * The ONLY case we are looking for is where the entire array is selected, parts[end] === 'x' * AND the replacement value is an array. */ -function isDataArray(val, key) { +function isDataArray (val, key) { + var containers = ['annotations', 'shapes', 'range', 'domain', 'buttons'], + isNotAContainer = containers.indexOf(key) === -1 - var containers = ['annotations', 'shapes', 'range', 'domain', 'buttons'], - isNotAContainer = containers.indexOf(key) === -1; - - return isArray(val) && isNotAContainer; + return isArray(val) && isNotAContainer } -function npSet(cont, parts) { - return function(val) { - var curCont = cont, - containerLevels = [cont], - toDelete = emptyObj(val) && !isDataArray(val, parts[parts.length - 1]), - curPart, - i; +function npSet (cont, parts) { + return function (val) { + var curCont = cont, + containerLevels = [cont], + toDelete = emptyObj(val) && !isDataArray(val, parts[parts.length - 1]), + curPart, + i - for(i = 0; i < parts.length - 1; i++) { - curPart = parts[i]; + for (i = 0; i < parts.length - 1; i++) { + curPart = parts[i] - if(typeof curPart === 'number' && !isArray(curCont)) { - throw 'array index but container is not an array'; - } + if (typeof curPart === 'number' && !isArray(curCont)) { + throw 'array index but container is not an array' + } // handle special -1 array index - if(curPart === -1) { - toDelete = !setArrayAll(curCont, parts.slice(i + 1), val); - if(toDelete) break; - else return; - } + if (curPart === -1) { + toDelete = !setArrayAll(curCont, parts.slice(i + 1), val) + if (toDelete) break + else return + } - if(!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) { - break; - } + if (!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) { + break + } - curCont = curCont[curPart]; + curCont = curCont[curPart] - if(typeof curCont !== 'object' || curCont === null) { - throw 'container is not an object'; - } + if (typeof curCont !== 'object' || curCont === null) { + throw 'container is not an object' + } - containerLevels.push(curCont); - } + containerLevels.push(curCont) + } - if(toDelete) { - if(i === parts.length - 1) delete curCont[parts[i]]; - pruneContainers(containerLevels); - } - else curCont[parts[i]] = val; - }; + if (toDelete) { + if (i === parts.length - 1) delete curCont[parts[i]] + pruneContainers(containerLevels) + } else curCont[parts[i]] = val + } } // handle special -1 array index -function setArrayAll(containerArray, innerParts, val) { - var arrayVal = isArray(val), - allSet = true, - thisVal = val, - deleteThis = arrayVal ? false : emptyObj(val), - firstPart = innerParts[0], - i; - - for(i = 0; i < containerArray.length; i++) { - if(arrayVal) { - thisVal = val[i % val.length]; - deleteThis = emptyObj(thisVal); - } - if(deleteThis) allSet = false; - if(!checkNewContainer(containerArray, i, firstPart, deleteThis)) { - continue; - } - npSet(containerArray[i], innerParts)(thisVal); +function setArrayAll (containerArray, innerParts, val) { + var arrayVal = isArray(val), + allSet = true, + thisVal = val, + deleteThis = arrayVal ? false : emptyObj(val), + firstPart = innerParts[0], + i + + for (i = 0; i < containerArray.length; i++) { + if (arrayVal) { + thisVal = val[i % val.length] + deleteThis = emptyObj(thisVal) + } + if (deleteThis) allSet = false + if (!checkNewContainer(containerArray, i, firstPart, deleteThis)) { + continue } - return allSet; + npSet(containerArray[i], innerParts)(thisVal) + } + return allSet } /** @@ -197,59 +194,57 @@ function setArrayAll(containerArray, innerParts, val) { * returns false if there's no container and none is needed * because we're only deleting an attribute */ -function checkNewContainer(container, part, nextPart, toDelete) { - if(container[part] === undefined) { - if(toDelete) return false; - - if(typeof nextPart === 'number') container[part] = []; - else container[part] = {}; - } - return true; +function checkNewContainer (container, part, nextPart, toDelete) { + if (container[part] === undefined) { + if (toDelete) return false + + if (typeof nextPart === 'number') container[part] = [] + else container[part] = {} + } + return true } -function pruneContainers(containerLevels) { - var i, - j, - curCont, - keys, - remainingKeys; - for(i = containerLevels.length - 1; i >= 0; i--) { - curCont = containerLevels[i]; - remainingKeys = false; - if(isArray(curCont)) { - for(j = curCont.length - 1; j >= 0; j--) { - if(emptyObj(curCont[j])) { - if(remainingKeys) curCont[j] = undefined; - else curCont.pop(); - } - else remainingKeys = true; - } - } - 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 pruneContainers (containerLevels) { + var i, + j, + curCont, + keys, + remainingKeys + for (i = containerLevels.length - 1; i >= 0; i--) { + curCont = containerLevels[i] + remainingKeys = false + if (isArray(curCont)) { + for (j = curCont.length - 1; j >= 0; j--) { + if (emptyObj(curCont[j])) { + if (remainingKeys) curCont[j] = undefined + else curCont.pop() + } else remainingKeys = true + } + } 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(isArray(obj)) return !obj.length; // [] - return !Object.keys(obj).length; // {} +function emptyObj (obj) { + if (obj === undefined || obj === null) return true + if (typeof obj !== 'object') return false // any plain value + if (isArray(obj)) return !obj.length // [] + return !Object.keys(obj).length // {} } -function badContainer(container, propStr, propParts) { - return { - set: function() { throw 'bad container'; }, - get: function() {}, - astr: propStr, - parts: propParts, - obj: container - }; +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 index e7443afd7a0..27ebd6945a3 100644 --- a/src/lib/notifier.js +++ b/src/lib/notifier.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); - -var NOTEDATA = []; +var NOTEDATA = [] /** * notifier @@ -21,60 +20,60 @@ var NOTEDATA = []; * 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; +module.exports = function (text, displayLength) { + if (NOTEDATA.indexOf(text) !== -1) return - NOTEDATA.push(text); + NOTEDATA.push(text) - var ts = 1000; - if(isNumeric(displayLength)) ts = displayLength; - else if(displayLength === 'long') ts = 3000; + var ts = 1000 + if (isNumeric(displayLength)) ts = displayLength + else if (displayLength === 'long') ts = 3000 - var notifierContainer = d3.select('body') + var notifierContainer = d3.select('body') .selectAll('.plotly-notifier') - .data([0]); - notifierContainer.enter() + .data([0]) + notifierContainer.enter() .append('div') - .classed('plotly-notifier', true); + .classed('plotly-notifier', true) - var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA); + var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA) - function killNote(transition) { - transition + 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(); - }); - } + .each('end', function (thisText) { + var thisIndex = NOTEDATA.indexOf(thisText) + if (thisIndex !== -1) NOTEDATA.splice(thisIndex, 1) + d3.select(this).remove() + }) + } - notes.enter().append('div') + notes.enter().append('div') .classed('notifier-note', true) .style('opacity', 0) - .each(function(thisText) { - var note = d3.select(this); + .each(function (thisText) { + var note = d3.select(this) - note.append('button') + note.append('button') .classed('notifier-close', true) .html('×') - .on('click', function() { - note.transition().call(killNote); - }); + .on('click', function () { + note.transition().call(killNote) + }) - var p = note.append('p'); - var lines = thisText.split(//g); - for(var i = 0; i < lines.length; i++) { - if(i) p.append('br'); - p.append('span').text(lines[i]); - } + var p = note.append('p') + var lines = thisText.split(//g) + for (var i = 0; i < lines.length; i++) { + if (i) p.append('br') + p.append('span').text(lines[i]) + } - note.transition() + note.transition() .duration(700) .style('opacity', 1) .transition() .delay(ts) - .call(killNote); - }); -}; + .call(killNote) + }) +} diff --git a/src/lib/override_cursor.js b/src/lib/override_cursor.js index ebbd290951e..a094baea48a 100644 --- a/src/lib/override_cursor.js +++ b/src/lib/override_cursor.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var setCursor = require('./setcursor') -var setCursor = require('./setcursor'); - -var STASHATTR = 'data-savedcursor'; -var NO_CURSOR = '!!'; +var STASHATTR = 'data-savedcursor' +var NO_CURSOR = '!!' /* * works with our CSS cursor classes (see css/_cursor.scss) @@ -20,28 +19,27 @@ var NO_CURSOR = '!!'; * by moving the name of the original cursor to the data-savedcursor attr. * omit cursor to revert to the previously set value. */ -module.exports = function overrideCursor(el3, csr) { - var savedCursor = el3.attr(STASHATTR); - if(csr) { - if(!savedCursor) { - var classes = (el3.attr('class') || '').split(' '); - for(var i = 0; i < classes.length; i++) { - var cls = classes[i]; - if(cls.indexOf('cursor-') === 0) { - el3.attr(STASHATTR, cls.substr(7)) - .classed(cls, false); - } - } - if(!el3.attr(STASHATTR)) { - el3.attr(STASHATTR, NO_CURSOR); - } +module.exports = function overrideCursor (el3, csr) { + var savedCursor = el3.attr(STASHATTR) + if (csr) { + if (!savedCursor) { + var classes = (el3.attr('class') || '').split(' ') + for (var i = 0; i < classes.length; i++) { + var cls = classes[i] + if (cls.indexOf('cursor-') === 0) { + el3.attr(STASHATTR, cls.substr(7)) + .classed(cls, false) } - setCursor(el3, csr); + } + if (!el3.attr(STASHATTR)) { + el3.attr(STASHATTR, NO_CURSOR) + } } - else if(savedCursor) { - el3.attr(STASHATTR, null); + setCursor(el3, csr) + } else if (savedCursor) { + el3.attr(STASHATTR, null) - if(savedCursor === NO_CURSOR) setCursor(el3); - else setCursor(el3, savedCursor); - } -}; + if (savedCursor === NO_CURSOR) setCursor(el3) + else setCursor(el3, savedCursor) + } +} diff --git a/src/lib/polygon.js b/src/lib/polygon.js index befd593e275..d564ea702a7 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var dot = require('./matrix').dot -var dot = require('./matrix').dot; - -var polygon = module.exports = {}; +var polygon = module.exports = {} /** * Turn an array of [x, y] pairs into a polygon object @@ -29,135 +28,133 @@ var polygon = module.exports = {}; * don't double-count the edge where they meet. * returns boolean: is pt inside the polygon (including on its edges) */ -polygon.tester = function tester(ptsIn) { - var pts = ptsIn.slice(), - xmin = pts[0][0], - xmax = xmin, - ymin = pts[0][1], - ymax = ymin; - - pts.push(pts[0]); - for(var i = 1; i < pts.length; i++) { - xmin = Math.min(xmin, pts[i][0]); - xmax = Math.max(xmax, pts[i][0]); - ymin = Math.min(ymin, pts[i][1]); - ymax = Math.max(ymax, pts[i][1]); - } +polygon.tester = function tester (ptsIn) { + var pts = ptsIn.slice(), + xmin = pts[0][0], + xmax = xmin, + ymin = pts[0][1], + ymax = ymin + + pts.push(pts[0]) + for (var i = 1; i < pts.length; i++) { + xmin = Math.min(xmin, pts[i][0]) + xmax = Math.max(xmax, pts[i][0]) + ymin = Math.min(ymin, pts[i][1]) + ymax = Math.max(ymax, pts[i][1]) + } // do we have a rectangle? Handle this here, so we can use the same // tester for the rectangular case without sacrificing speed - var isRect = false, - rectFirstEdgeTest; + var isRect = false, + rectFirstEdgeTest - if(pts.length === 5) { - if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz - if(pts[2][0] === pts[3][0] && + if (pts.length === 5) { + if (pts[0][0] === pts[1][0]) { // vert, horz, vert, horz + if (pts[2][0] === pts[3][0] && pts[0][1] === pts[3][1] && pts[1][1] === pts[2][1]) { - isRect = true; - rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; }; - } - } - else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert - if(pts[2][1] === pts[3][1] && + isRect = true + rectFirstEdgeTest = function (pt) { return pt[0] === pts[0][0] } + } + } else if (pts[0][1] === pts[1][1]) { // horz, vert, horz, vert + if (pts[2][1] === pts[3][1] && pts[0][0] === pts[3][0] && pts[1][0] === pts[2][0]) { - isRect = true; - rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; }; - } - } + isRect = true + rectFirstEdgeTest = function (pt) { return pt[1] === pts[0][1] } + } } + } - function rectContains(pt, omitFirstEdge) { - var x = pt[0], - y = pt[1]; + function rectContains (pt, omitFirstEdge) { + var x = pt[0], + y = pt[1] - if(x < xmin || x > xmax || y < ymin || y > ymax) { + if (x < xmin || x > xmax || y < ymin || y > ymax) { // pt is outside the bounding box of polygon - return false; - } - if(omitFirstEdge && rectFirstEdgeTest(pt)) return false; - - return true; + return false } + if (omitFirstEdge && rectFirstEdgeTest(pt)) return false - function contains(pt, omitFirstEdge) { - var x = pt[0], - y = pt[1]; + return true + } - if(x < xmin || x > xmax || y < ymin || y > ymax) { + function contains (pt, omitFirstEdge) { + var x = pt[0], + y = pt[1] + + if (x < xmin || x > xmax || y < ymin || y > ymax) { // pt is outside the bounding box of polygon - return false; - } + return false + } - var imax = pts.length, - x1 = pts[0][0], - y1 = pts[0][1], - crossings = 0, - i, - x0, - y0, - xmini, - ycross; - - for(i = 1; i < imax; i++) { + var imax = pts.length, + x1 = pts[0][0], + y1 = pts[0][1], + crossings = 0, + i, + x0, + y0, + xmini, + ycross + + for (i = 1; i < imax; i++) { // find all crossings of a vertical line upward from pt with // polygon segments // crossings exactly at xmax don't count, unless the point is // exactly on the segment, then it counts as inside. - x0 = x1; - y0 = y1; - x1 = pts[i][0]; - y1 = pts[i][1]; - xmini = Math.min(x0, x1); + x0 = x1 + y0 = y1 + x1 = pts[i][0] + y1 = pts[i][1] + xmini = Math.min(x0, x1) // outside the bounding box of this segment, it's only a crossing // if it's below the box. - if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) { - continue; - } - else if(y < Math.min(y0, y1)) { + if (x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) { + continue + } else if (y < Math.min(y0, y1)) { // don't count the left-most point of the segment as a crossing // because we don't want to double-count adjacent crossings // UNLESS the polygon turns past vertical at exactly this x // Note that this is repeated below, but we can't factor it out // because - if(x !== xmini) crossings++; - } + if (x !== xmini) crossings++ + } // inside the bounding box, check the actual line intercept - else { + else { // vertical segment - we know already that the point is exactly // on the segment, so mark the crossing as exactly at the point. - if(x1 === x0) ycross = y; + if (x1 === x0) ycross = y // any other angle - else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0); + else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0) // exactly on the edge: counts as inside the polygon, unless it's the // first edge and we're omitting it. - if(y === ycross) { - if(i === 1 && omitFirstEdge) return false; - return true; - } - - if(y <= ycross && x !== xmini) crossings++; - } + if (y === ycross) { + if (i === 1 && omitFirstEdge) return false + return true } - // if we've gotten this far, odd crossings means inside, even is outside - return crossings % 2 === 1; + if (y <= ycross && x !== xmini) crossings++ + } } - return { - xmin: xmin, - xmax: xmax, - ymin: ymin, - ymax: ymax, - pts: pts, - contains: isRect ? rectContains : contains, - isRect: isRect - }; -}; + // if we've gotten this far, odd crossings means inside, even is outside + return crossings % 2 === 1 + } + + return { + xmin: xmin, + xmax: xmax, + ymin: ymin, + ymax: ymax, + pts: pts, + contains: isRect ? rectContains : contains, + isRect: isRect + } +} /** * Test if a segment of a points array is bent or straight @@ -169,25 +166,25 @@ polygon.tester = function tester(ptsIn) { * before the line counts as bent * @returns boolean: true means this segment is bent, false means straight */ -var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance) { - var startPt = pts[start], - segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]], - segmentSquared = dot(segment, segment), - segmentLen = Math.sqrt(segmentSquared), - unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen], - i, - part, - partParallel; - - for(i = start + 1; i < end; i++) { - part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]]; - partParallel = dot(part, segment); - - if(partParallel < 0 || partParallel > segmentSquared || - Math.abs(dot(part, unitPerp)) > tolerance) return true; - } - return false; -}; +var isBent = polygon.isSegmentBent = function isBent (pts, start, end, tolerance) { + var startPt = pts[start], + segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]], + segmentSquared = dot(segment, segment), + segmentLen = Math.sqrt(segmentSquared), + unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen], + i, + part, + partParallel + + for (i = start + 1; i < end; i++) { + part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]] + partParallel = dot(part, segment) + + if (partParallel < 0 || partParallel > segmentSquared || + Math.abs(dot(part, unitPerp)) > tolerance) return true + } + return false +} /** * Make a filtering polygon, to minimize the number of segments @@ -202,37 +199,37 @@ var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance) * raw is all the input points * filtered is the resulting filtered Array of [x, y] pairs */ -polygon.filter = function filter(pts, tolerance) { - var ptsFiltered = [pts[0]], - doneRawIndex = 0, - doneFilteredIndex = 0; - - function addPt(pt) { - pts.push(pt); - var prevFilterLen = ptsFiltered.length, - iLast = doneRawIndex; - ptsFiltered.splice(doneFilteredIndex + 1); - - for(var i = iLast + 1; i < pts.length; i++) { - if(i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) { - ptsFiltered.push(pts[i]); - if(ptsFiltered.length < prevFilterLen - 2) { - doneRawIndex = i; - doneFilteredIndex = ptsFiltered.length - 1; - } - iLast = i; - } +polygon.filter = function filter (pts, tolerance) { + var ptsFiltered = [pts[0]], + doneRawIndex = 0, + doneFilteredIndex = 0 + + function addPt (pt) { + pts.push(pt) + var prevFilterLen = ptsFiltered.length, + iLast = doneRawIndex + ptsFiltered.splice(doneFilteredIndex + 1) + + for (var i = iLast + 1; i < pts.length; i++) { + if (i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) { + ptsFiltered.push(pts[i]) + if (ptsFiltered.length < prevFilterLen - 2) { + doneRawIndex = i + doneFilteredIndex = ptsFiltered.length - 1 } + iLast = i + } } - - if(pts.length > 1) { - var lastPt = pts.pop(); - addPt(lastPt); - } - - return { - addPt: addPt, - raw: pts, - filtered: ptsFiltered - }; -}; + } + + if (pts.length > 1) { + var lastPt = pts.pop() + addPt(lastPt) + } + + return { + addPt: addPt, + raw: pts, + filtered: ptsFiltered + } +} diff --git a/src/lib/queue.js b/src/lib/queue.js index 815fd915723..200e265d57a 100644 --- a/src/lib/queue.js +++ b/src/lib/queue.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../lib'); -var config = require('../plot_api/plot_config'); - +var Lib = require('../lib') +var config = require('../plot_api/plot_config') /** * Copy arg array *without* removing `undefined` values from objects. @@ -20,32 +18,29 @@ var config = require('../plot_api/plot_config'); * @param args * @returns {Array} */ -function copyArgArray(gd, args) { - var copy = []; - var arg; +function copyArgArray (gd, args) { + var copy = [] + var arg - for(var i = 0; i < args.length; i++) { - arg = args[i]; + for (var i = 0; i < args.length; i++) { + arg = args[i] - if(arg === gd) copy[i] = arg; - else if(typeof arg === 'object') { - copy[i] = Array.isArray(arg) ? + if (arg === gd) copy[i] = arg + else if (typeof arg === 'object') { + copy[i] = Array.isArray(arg) ? Lib.extendDeep([], arg) : - Lib.extendDeepAll({}, arg); - } - else copy[i] = arg; - } + Lib.extendDeepAll({}, arg) + } else copy[i] = arg + } - return copy; + return copy } - // ----------------------------------------------------- // Undo/Redo queue for plots // ----------------------------------------------------- - -var queue = {}; +var queue = {} // TODO: disable/enable undo and redo buttons appropriately @@ -58,55 +53,55 @@ var queue = {}; * @param redoFunc Function to redo this operation * @param redoArgs Args to supply redoFunc with */ -queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) { - var queueObj, - queueIndex; +queue.add = function (gd, undoFunc, undoArgs, redoFunc, redoArgs) { + var queueObj, + queueIndex // make sure we have the queue and our position in it - gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; - queueIndex = gd.undoQueue.index; + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false} + queueIndex = gd.undoQueue.index // if we're already playing an undo or redo, or if this is an auto operation // (like pane resize... any others?) then we don't save this to the undo queue - if(gd.autoplay) { - if(!gd.undoQueue.inSequence) gd.autoplay = false; - return; - } + if (gd.autoplay) { + if (!gd.undoQueue.inSequence) gd.autoplay = false + return + } // if we're not in a sequence or are just starting, we need a new queue item - if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) { - queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}}; - gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj); - gd.undoQueue.index += 1; - } else { - queueObj = gd.undoQueue.queue[queueIndex - 1]; - } - gd.undoQueue.beginSequence = false; + if (!gd.undoQueue.sequence || gd.undoQueue.beginSequence) { + queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}} + gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj) + gd.undoQueue.index += 1 + } else { + queueObj = gd.undoQueue.queue[queueIndex - 1] + } + gd.undoQueue.beginSequence = false // we unshift to handle calls for undo in a forward for loop later - if(queueObj) { - queueObj.undo.calls.unshift(undoFunc); - queueObj.undo.args.unshift(undoArgs); - queueObj.redo.calls.push(redoFunc); - queueObj.redo.args.push(redoArgs); - } - - if(gd.undoQueue.queue.length > config.queueLength) { - gd.undoQueue.queue.shift(); - gd.undoQueue.index--; - } -}; + if (queueObj) { + queueObj.undo.calls.unshift(undoFunc) + queueObj.undo.args.unshift(undoArgs) + queueObj.redo.calls.push(redoFunc) + queueObj.redo.args.push(redoArgs) + } + + if (gd.undoQueue.queue.length > config.queueLength) { + gd.undoQueue.queue.shift() + gd.undoQueue.index-- + } +} /** * Begin a sequence of undoQueue changes * * @param gd */ -queue.startSequence = function(gd) { - gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; - gd.undoQueue.sequence = true; - gd.undoQueue.beginSequence = true; -}; +queue.startSequence = function (gd) { + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false} + gd.undoQueue.sequence = true + gd.undoQueue.beginSequence = true +} /** * Stop a sequence of undoQueue changes @@ -115,77 +110,77 @@ queue.startSequence = function(gd) { * * @param gd */ -queue.stopSequence = function(gd) { - gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; - gd.undoQueue.sequence = false; - gd.undoQueue.beginSequence = false; -}; +queue.stopSequence = function (gd) { + gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false} + gd.undoQueue.sequence = false + gd.undoQueue.beginSequence = false +} /** * Move one step back in the undo queue, and undo the object there. * * @param gd */ -queue.undo = function undo(gd) { - var queueObj, i; - - if(gd.framework && gd.framework.isPolar) { - gd.framework.undo(); - return; - } - if(gd.undoQueue === undefined || +queue.undo = function undo (gd) { + var queueObj, i + + if (gd.framework && gd.framework.isPolar) { + gd.framework.undo() + return + } + if (gd.undoQueue === undefined || isNaN(gd.undoQueue.index) || gd.undoQueue.index <= 0) { - return; - } + return + } // index is pointing to next *forward* queueObj, point to the one we're undoing - gd.undoQueue.index--; + gd.undoQueue.index-- // get the queueObj for instructions on how to undo - queueObj = gd.undoQueue.queue[gd.undoQueue.index]; + queueObj = gd.undoQueue.queue[gd.undoQueue.index] // this sequence keeps things from adding to the queue during undo/redo - gd.undoQueue.inSequence = true; - for(i = 0; i < queueObj.undo.calls.length; i++) { - queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]); - } - gd.undoQueue.inSequence = false; - gd.autoplay = false; -}; + gd.undoQueue.inSequence = true + for (i = 0; i < queueObj.undo.calls.length; i++) { + queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]) + } + gd.undoQueue.inSequence = false + gd.autoplay = false +} /** * Redo the current object in the undo, then move forward in the queue. * * @param gd */ -queue.redo = function redo(gd) { - var queueObj, i; - - if(gd.framework && gd.framework.isPolar) { - gd.framework.redo(); - return; - } - if(gd.undoQueue === undefined || +queue.redo = function redo (gd) { + var queueObj, i + + if (gd.framework && gd.framework.isPolar) { + gd.framework.redo() + return + } + if (gd.undoQueue === undefined || isNaN(gd.undoQueue.index) || gd.undoQueue.index >= gd.undoQueue.queue.length) { - return; - } + return + } // get the queueObj for instructions on how to undo - queueObj = gd.undoQueue.queue[gd.undoQueue.index]; + queueObj = gd.undoQueue.queue[gd.undoQueue.index] // this sequence keeps things from adding to the queue during undo/redo - gd.undoQueue.inSequence = true; - for(i = 0; i < queueObj.redo.calls.length; i++) { - queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]); - } - gd.undoQueue.inSequence = false; - gd.autoplay = false; + gd.undoQueue.inSequence = true + for (i = 0; i < queueObj.redo.calls.length; i++) { + queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]) + } + gd.undoQueue.inSequence = false + gd.autoplay = false // index is pointing to the thing we just redid, move it - gd.undoQueue.index++; -}; + gd.undoQueue.index++ +} /** * Called by undo/redo to make the actual changes. @@ -196,14 +191,14 @@ queue.redo = function redo(gd) { * @param func * @param args */ -queue.plotDo = function(gd, func, args) { - gd.autoplay = true; +queue.plotDo = function (gd, func, args) { + gd.autoplay = true // this *won't* copy gd and it preserves `undefined` properties! - args = copyArgArray(gd, args); + args = copyArgArray(gd, args) // call the supplied function - func.apply(null, args); -}; + func.apply(null, args) +} -module.exports = queue; +module.exports = queue diff --git a/src/lib/search.js b/src/lib/search.js index 8cb4275f1f3..cc08ab0fc6b 100644 --- a/src/lib/search.js +++ b/src/lib/search.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); -var loggers = require('./loggers'); - +var isNumeric = require('fast-isnumeric') +var loggers = require('./loggers') /** * findBin - find the bin for val - note that it can return outside the @@ -23,66 +21,65 @@ var loggers = require('./loggers'); * 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 ? +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); + Math.floor((val - bins.start) / bins.size) + } else { + var n1 = 0, + n2 = bins.length, + c = 0, + n, + test + if (bins[bins.length - 1] >= bins[0]) { + test = linelow ? lessThan : lessOrEqual + } else { + test = linelow ? greaterOrEqual : greaterThan } - else { - var n1 = 0, - n2 = bins.length, - c = 0, - n, - test; - if(bins[bins.length - 1] >= bins[0]) { - test = linelow ? lessThan : lessOrEqual; - } else { - test = linelow ? greaterOrEqual : greaterThan; - } // c is just to avoid infinite loops if there's an error - while(n1 < n2 && c++ < 100) { - n = Math.floor((n1 + n2) / 2); - if(test(bins[n], val)) n1 = n + 1; - else n2 = n; - } - if(c > 90) loggers.log('Long binary search...'); - return n1 - 1; + while (n1 < n2 && c++ < 100) { + n = Math.floor((n1 + n2) / 2) + if (test(bins[n], val)) n1 = n + 1 + else n2 = n } -}; + if (c > 90) loggers.log('Long binary search...') + return n1 - 1 + } +} -function lessThan(a, b) { return a < b; } -function lessOrEqual(a, b) { return a <= b; } -function greaterThan(a, b) { return a > b; } -function greaterOrEqual(a, b) { return a >= b; } +function lessThan (a, b) { return a < b } +function lessOrEqual (a, b) { return a <= b } +function greaterThan (a, b) { return a > b } +function greaterOrEqual (a, b) { return a >= b } -exports.sorterAsc = function(a, b) { return a - b; }; -exports.sorterDes = function(a, b) { return b - a; }; +exports.sorterAsc = function (a, b) { return a - b } +exports.sorterDes = function (a, b) { return b - a } /** * find distinct values in an array, lumping together ones that appear to * 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); +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]]; + 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++) { + 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]); - } + if (vals[i + 1] > vals[i] + errDiff) { + minDiff = Math.min(minDiff, vals[i + 1] - vals[i]) + v2.push(vals[i + 1]) } + } - return {vals: v2, minDiff: minDiff}; -}; + return {vals: v2, minDiff: minDiff} +} /** * return the smallest element from (sorted) array arrayIn that's bigger than val, @@ -91,19 +88,19 @@ exports.distinctVals = function(valsIn) { * 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; +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]; -}; + while (low < high && c++ < 100) { + mid = rounded((low + high) / 2) + if (arrayIn[mid] <= val) low = mid + dlow + else high = mid - dhigh + } + return arrayIn[low] +} diff --git a/src/lib/setcursor.js b/src/lib/setcursor.js index ef70880a1d5..f29a1150452 100644 --- a/src/lib/setcursor.js +++ b/src/lib/setcursor.js @@ -6,16 +6,15 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' // works with our CSS cursor classes (see css/_cursor.scss) // to apply cursors to d3 single-element selections. // omit cursor to revert to the default. -module.exports = function setCursor(el3, csr) { - (el3.attr('class') || '').split(' ').forEach(function(cls) { - if(cls.indexOf('cursor-') === 0) el3.classed(cls, false); - }); +module.exports = function setCursor (el3, csr) { + (el3.attr('class') || '').split(' ').forEach(function (cls) { + if (cls.indexOf('cursor-') === 0) el3.classed(cls, false) + }) - if(csr) el3.classed('cursor-' + csr, true); -}; + if (csr) el3.classed('cursor-' + csr, true) +} diff --git a/src/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js index 40b84c680bf..becb8bf8949 100644 --- a/src/lib/show_no_webgl_msg.js +++ b/src/lib/show_no_webgl_msg.js @@ -6,13 +6,11 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Color = require('../components/color'); - -var noop = function() {}; +var Color = require('../components/color') +var noop = function () {} /** * Prints a no webgl error message into the scene container @@ -21,27 +19,27 @@ var noop = function() {}; * Expects 'scene' to have property 'container' * */ -module.exports = function showWebGlMsg(scene) { - for(var prop in scene) { - if(typeof scene[prop] === 'function') scene[prop] = noop; - } - - scene.destroy = function() { - scene.container.parentNode.removeChild(scene.container); - }; - - var div = document.createElement('div'); - div.textContent = 'Webgl is not supported by your browser - visit http://get.webgl.org for more info'; - div.style.cursor = 'pointer'; - div.style.fontSize = '24px'; - div.style.color = Color.defaults[0]; - - scene.container.appendChild(div); - scene.container.style.background = '#FFFFFF'; - scene.container.onclick = function() { - window.open('http://get.webgl.org'); - }; +module.exports = function showWebGlMsg (scene) { + for (var prop in scene) { + if (typeof scene[prop] === 'function') scene[prop] = noop + } + + scene.destroy = function () { + scene.container.parentNode.removeChild(scene.container) + } + + var div = document.createElement('div') + div.textContent = 'Webgl is not supported by your browser - visit http://get.webgl.org for more info' + div.style.cursor = 'pointer' + div.style.fontSize = '24px' + div.style.color = Color.defaults[0] + + scene.container.appendChild(div) + scene.container.style.background = '#FFFFFF' + scene.container.onclick = function () { + window.open('http://get.webgl.org') + } // return before setting up camera and onrender methods - return false; -}; + return false +} diff --git a/src/lib/stats.js b/src/lib/stats.js index a365d4ce33f..70b0819c07d 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -6,11 +6,9 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); - +var isNumeric = require('fast-isnumeric') /** * aggNums() returns the result of an aggregate function applied to an array of @@ -25,49 +23,49 @@ var isNumeric = require('fast-isnumeric'); * @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; - } +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; -}; + 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.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.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); +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; -}; + 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)); -}; +exports.stdev = function (data, len, mean) { + return Math.sqrt(exports.variance(data, len, mean)) +} /** * interp() computes a percentile (quantile) for a given distribution. @@ -84,11 +82,11 @@ exports.stdev = function(data, len, mean) { * 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)]; -}; +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/lib/str2rgbarray.js b/src/lib/str2rgbarray.js index 0dd3c027fce..62cd4b8e22d 100644 --- a/src/lib/str2rgbarray.js +++ b/src/lib/str2rgbarray.js @@ -6,15 +6,14 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var tinycolor = require('tinycolor2') +var arrtools = require('arraytools') -var tinycolor = require('tinycolor2'); -var arrtools = require('arraytools'); - -function str2RgbaArray(color) { - color = tinycolor(color); - return arrtools.str2RgbaArray(color.toRgbString()); +function str2RgbaArray (color) { + color = tinycolor(color) + return arrtools.str2RgbaArray(color.toRgbString()) } -module.exports = str2RgbaArray; +module.exports = str2RgbaArray diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index a55334d5157..776e1738917 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -6,296 +6,289 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /* global MathJax:false */ -var d3 = require('d3'); +var d3 = require('d3') -var Lib = require('../lib'); -var xmlnsNamespaces = require('../constants/xmlns_namespaces'); -var stringMappings = require('../constants/string_mappings'); +var Lib = require('../lib') +var xmlnsNamespaces = require('../constants/xmlns_namespaces') +var stringMappings = require('../constants/string_mappings') // Append SVG -d3.selection.prototype.appendSVG = function(_svgString) { - var skeleton = [ - '', - _svgString, - '' - ].join(''); - - var dom = new DOMParser().parseFromString(skeleton, 'application/xml'), - childNode = dom.documentElement.firstChild; - - while(childNode) { - this.node().appendChild(this.node().ownerDocument.importNode(childNode, true)); - childNode = childNode.nextSibling; - } - if(dom.querySelector('parsererror')) { - Lib.log(dom.querySelector('parsererror div').textContent); - return null; - } - return d3.select(this.node().lastChild); -}; +d3.selection.prototype.appendSVG = function (_svgString) { + var skeleton = [ + '', + _svgString, + '' + ].join('') + + var dom = new DOMParser().parseFromString(skeleton, 'application/xml'), + childNode = dom.documentElement.firstChild + + while (childNode) { + this.node().appendChild(this.node().ownerDocument.importNode(childNode, true)) + childNode = childNode.nextSibling + } + if (dom.querySelector('parsererror')) { + Lib.log(dom.querySelector('parsererror div').textContent) + return null + } + return d3.select(this.node().lastChild) +} // Text utilities -exports.html_entity_decode = function(s) { - var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html(''); - var replaced = s.replace(/(&[^;]*;)/gi, function(d) { - if(d === '<') { return '<'; } // special handling for brackets - if(d === '&rt;') { return '>'; } - if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; } - return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode - }); - hiddenDiv.remove(); - return replaced; -}; - -exports.xml_entity_encode = function(str) { - return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&'); -}; +exports.html_entity_decode = function (s) { + var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html('') + var replaced = s.replace(/(&[^;]*;)/gi, function (d) { + if (d === '<') { return '<' } // special handling for brackets + if (d === '&rt;') { return '>' } + if (d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return '' } + return hiddenDiv.html(d).text() // everything else, let the browser decode it to unicode + }) + hiddenDiv.remove() + return replaced +} + +exports.xml_entity_encode = function (str) { + return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&') +} // text converter -function getSize(_selection, _dimension) { - return _selection.node().getBoundingClientRect()[_dimension]; +function getSize (_selection, _dimension) { + return _selection.node().getBoundingClientRect()[_dimension] } -exports.convertToTspans = function(_context, _callback) { - var str = _context.text(); - var converted = convertToSVG(str); - var that = _context; +exports.convertToTspans = function (_context, _callback) { + var str = _context.text() + var converted = convertToSVG(str) + var that = _context // Until we get tex integrated more fully (so it can be used along with non-tex) // allow some elements to prohibit it by attaching 'data-notex' to the original - var tex = (!that.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/); - var result = str; - var parent = d3.select(that.node().parentNode); - if(parent.empty()) return; - var svgClass = (that.attr('class')) ? that.attr('class').split(' ')[0] : 'text'; - svgClass += '-math'; - parent.selectAll('svg.' + svgClass).remove(); - parent.selectAll('g.' + svgClass + '-group').remove(); - _context.style({visibility: null}); - for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) { - up.removeAttribute('data-bb'); + var tex = (!that.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/) + var result = str + var parent = d3.select(that.node().parentNode) + if (parent.empty()) return + var svgClass = (that.attr('class')) ? that.attr('class').split(' ')[0] : 'text' + svgClass += '-math' + parent.selectAll('svg.' + svgClass).remove() + parent.selectAll('g.' + svgClass + '-group').remove() + _context.style({visibility: null}) + for (var up = _context.node(); up && up.removeAttribute; up = up.parentNode) { + up.removeAttribute('data-bb') + } + + function showText () { + if (!parent.empty()) { + svgClass = that.attr('class') + '-math' + parent.select('svg.' + svgClass).remove() } - - function showText() { - if(!parent.empty()) { - svgClass = that.attr('class') + '-math'; - parent.select('svg.' + svgClass).remove(); - } - _context.text('') + _context.text('') .style({ - visibility: 'inherit', - 'white-space': 'pre' - }); + visibility: 'inherit', + 'white-space': 'pre' + }) - result = _context.appendSVG(converted); + result = _context.appendSVG(converted) - if(!result) _context.text(str); + if (!result) _context.text(str) - if(_context.select('a').size()) { + if (_context.select('a').size()) { // at least in Chrome, pointer-events does not seem // to be honored in children of elements // so if we have an anchor, we have to make the // whole element respond - _context.style('pointer-events', 'all'); - } - - if(_callback) _callback.call(that); + _context.style('pointer-events', 'all') } - if(tex) { - var gd = Lib.getPlotDiv(that.node()); - ((gd && gd._promises) || []).push(new Promise(function(resolve) { - that.style({visibility: 'hidden'}); - var config = {fontSize: parseInt(that.style('font-size'), 10)}; + if (_callback) _callback.call(that) + } - texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) { - parent.selectAll('svg.' + svgClass).remove(); - parent.selectAll('g.' + svgClass + '-group').remove(); + if (tex) { + var gd = Lib.getPlotDiv(that.node()); + ((gd && gd._promises) || []).push(new Promise(function (resolve) { + that.style({visibility: 'hidden'}) + var config = {fontSize: parseInt(that.style('font-size'), 10)} - var newSvg = _svgEl && _svgEl.select('svg'); - if(!newSvg || !newSvg.node()) { - showText(); - resolve(); - return; - } + texToSVG(tex[2], config, function (_svgEl, _glyphDefs, _svgBBox) { + parent.selectAll('svg.' + svgClass).remove() + parent.selectAll('g.' + svgClass + '-group').remove() - var mathjaxGroup = parent.append('g') + var newSvg = _svgEl && _svgEl.select('svg') + if (!newSvg || !newSvg.node()) { + showText() + resolve() + return + } + + var mathjaxGroup = parent.append('g') .classed(svgClass + '-group', true) - .attr({'pointer-events': 'none'}); + .attr({'pointer-events': 'none'}) - mathjaxGroup.node().appendChild(newSvg.node()); + mathjaxGroup.node().appendChild(newSvg.node()) // stitch the glyph defs - if(_glyphDefs && _glyphDefs.node()) { - newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true), - newSvg.node().firstChild); - } - - newSvg.attr({ - 'class': svgClass, - height: _svgBBox.height, - preserveAspectRatio: 'xMinYMin meet' - }) - .style({overflow: 'visible', 'pointer-events': 'none'}); - - var fill = that.style('fill') || 'black'; - newSvg.select('g').attr({fill: fill, stroke: fill}); - - var newSvgW = getSize(newSvg, 'width'), - newSvgH = getSize(newSvg, 'height'), - newX = +that.attr('x') - newSvgW * + if (_glyphDefs && _glyphDefs.node()) { + newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true), + newSvg.node().firstChild) + } + + newSvg.attr({ + 'class': svgClass, + height: _svgBBox.height, + preserveAspectRatio: 'xMinYMin meet' + }) + .style({overflow: 'visible', 'pointer-events': 'none'}) + + var fill = that.style('fill') || 'black' + newSvg.select('g').attr({fill: fill, stroke: fill}) + + var newSvgW = getSize(newSvg, 'width'), + newSvgH = getSize(newSvg, 'height'), + newX = +that.attr('x') - newSvgW * {start: 0, middle: 0.5, end: 1}[that.attr('text-anchor') || 'start'], // font baseline is about 1/4 fontSize below centerline - textHeight = parseInt(that.style('font-size'), 10) || + textHeight = parseInt(that.style('font-size'), 10) || getSize(that, 'height'), - dy = -textHeight / 4; + dy = -textHeight / 4 - if(svgClass[0] === 'y') { - mathjaxGroup.attr({ - transform: 'rotate(' + [-90, +that.attr('x'), +that.attr('y')] + + if (svgClass[0] === 'y') { + mathjaxGroup.attr({ + transform: 'rotate(' + [-90, +that.attr('x'), +that.attr('y')] + ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')' - }); - newSvg.attr({x: +that.attr('x'), y: +that.attr('y')}); - } - else if(svgClass[0] === 'l') { - newSvg.attr({x: that.attr('x'), y: dy - (newSvgH / 2)}); - } - else if(svgClass[0] === 'a') { - newSvg.attr({x: 0, y: dy}); - } - else { - newSvg.attr({x: newX, y: (+that.attr('y') + dy - newSvgH / 2)}); - } - - if(_callback) _callback.call(that, mathjaxGroup); - resolve(mathjaxGroup); - }); - })); - } - else showText(); + }) + newSvg.attr({x: +that.attr('x'), y: +that.attr('y')}) + } else if (svgClass[0] === 'l') { + newSvg.attr({x: that.attr('x'), y: dy - (newSvgH / 2)}) + } else if (svgClass[0] === 'a') { + newSvg.attr({x: 0, y: dy}) + } else { + newSvg.attr({x: newX, y: (+that.attr('y') + dy - newSvgH / 2)}) + } - return _context; -}; + if (_callback) _callback.call(that, mathjaxGroup) + resolve(mathjaxGroup) + }) + })) + } else showText() + return _context +} // MathJax -function cleanEscapesForTex(s) { - return s.replace(/(<|<|<)/g, '\\lt ') - .replace(/(>|>|>)/g, '\\gt '); +function cleanEscapesForTex (s) { + return s.replace(/(<|<|<)/g, '\\lt ') + .replace(/(>|>|>)/g, '\\gt ') } -function texToSVG(_texString, _config, _callback) { - var randomID = 'math-output-' + Lib.randstr([], 64); - var tmpDiv = d3.select('body').append('div') +function texToSVG (_texString, _config, _callback) { + var randomID = 'math-output-' + Lib.randstr([], 64) + var tmpDiv = d3.select('body').append('div') .attr({id: randomID}) .style({visibility: 'hidden', position: 'absolute'}) .style({'font-size': _config.fontSize + 'px'}) - .text(cleanEscapesForTex(_texString)); + .text(cleanEscapesForTex(_texString)) - MathJax.Hub.Queue(['Typeset', MathJax.Hub, tmpDiv.node()], function() { - var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs'); + MathJax.Hub.Queue(['Typeset', MathJax.Hub, tmpDiv.node()], function () { + var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs') - if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) { - Lib.log('There was an error in the tex syntax.', _texString); - _callback(); - } - else { - var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect(); - _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox); - } + if (tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) { + Lib.log('There was an error in the tex syntax.', _texString) + _callback() + } else { + var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect() + _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox) + } - tmpDiv.remove(); - }); + tmpDiv.remove() + }) } var TAG_STYLES = { // would like to use baseline-shift but FF doesn't support it yet // so we need to use dy along with the uber hacky shift-back-to // baseline below - sup: 'font-size:70%" dy="-0.6em', - sub: 'font-size:70%" dy="0.3em', - b: 'font-weight:bold', - i: 'font-style:italic', - a: '', - span: '', - br: '', - em: 'font-style:italic;font-weight:bold' -}; - -var PROTOCOLS = ['http:', 'https:', 'mailto:']; - -var STRIP_TAGS = new RegExp(']*)?/?>', 'g'); - -var ENTITY_TO_UNICODE = Object.keys(stringMappings.entityToUnicode).map(function(k) { - return { - regExp: new RegExp('&' + k + ';', 'g'), - sub: stringMappings.entityToUnicode[k] - }; -}); - -var UNICODE_TO_ENTITY = Object.keys(stringMappings.unicodeToEntity).map(function(k) { - return { - regExp: new RegExp(k, 'g'), - sub: '&' + stringMappings.unicodeToEntity[k] + ';' - }; -}); - -var NEWLINES = /(\r\n?|\n)/g; - -exports.plainText = function(_str) { + sup: 'font-size:70%" dy="-0.6em', + sub: 'font-size:70%" dy="0.3em', + b: 'font-weight:bold', + i: 'font-style:italic', + a: '', + span: '', + br: '', + em: 'font-style:italic;font-weight:bold' +} + +var PROTOCOLS = ['http:', 'https:', 'mailto:'] + +var STRIP_TAGS = new RegExp(']*)?/?>', 'g') + +var ENTITY_TO_UNICODE = Object.keys(stringMappings.entityToUnicode).map(function (k) { + return { + regExp: new RegExp('&' + k + ';', 'g'), + sub: stringMappings.entityToUnicode[k] + } +}) + +var UNICODE_TO_ENTITY = Object.keys(stringMappings.unicodeToEntity).map(function (k) { + return { + regExp: new RegExp(k, 'g'), + sub: '&' + stringMappings.unicodeToEntity[k] + ';' + } +}) + +var NEWLINES = /(\r\n?|\n)/g + +exports.plainText = function (_str) { // strip out our pseudo-html so we have a readable // version to put into text fields - return (_str || '').replace(STRIP_TAGS, ' '); -}; + return (_str || '').replace(STRIP_TAGS, ' ') +} -function replaceFromMapObject(_str, list) { - var out = _str || ''; +function replaceFromMapObject (_str, list) { + var out = _str || '' - for(var i = 0; i < list.length; i++) { - var item = list[i]; - out = out.replace(item.regExp, item.sub); - } + for (var i = 0; i < list.length; i++) { + var item = list[i] + out = out.replace(item.regExp, item.sub) + } - return out; + return out } -function convertEntities(_str) { - return replaceFromMapObject(_str, ENTITY_TO_UNICODE); +function convertEntities (_str) { + return replaceFromMapObject(_str, ENTITY_TO_UNICODE) } -function encodeForHTML(_str) { - return replaceFromMapObject(_str, UNICODE_TO_ENTITY); +function encodeForHTML (_str) { + return replaceFromMapObject(_str, UNICODE_TO_ENTITY) } -function convertToSVG(_str) { - _str = convertEntities(_str); +function convertToSVG (_str) { + _str = convertEntities(_str) // normalize behavior between IE and others wrt newlines and whitespace:pre // this combination makes IE barf https://github.com/plotly/plotly.js/issues/746 // Chrome and FF display \n, \r, or \r\n as a space in this mode. // I feel like at some point we turned these into
but currently we don't so // I'm just going to cement what we do now in Chrome and FF - _str = _str.replace(NEWLINES, ' '); + _str = _str.replace(NEWLINES, ' ') - var result = _str - .split(/(<[^<>]*>)/).map(function(d) { - var match = d.match(/<(\/?)([^ >]*)\s*(.*)>/i), - tag = match && match[2].toLowerCase(), - style = TAG_STYLES[tag]; + var result = _str + .split(/(<[^<>]*>)/).map(function (d) { + var match = d.match(/<(\/?)([^ >]*)\s*(.*)>/i), + tag = match && match[2].toLowerCase(), + style = TAG_STYLES[tag] - if(style !== undefined) { - var close = match[1], - extra = match[3], + if (style !== undefined) { + var close = match[1], + extra = match[3], /** * extraStyle: any random extra css (that's supported by svg) * use this like to change font in the middle @@ -304,232 +297,228 @@ function convertToSVG(_str) { * valid HTML anymore and we dropped it accidentally for many months, we will not * resurrect it. */ - extraStyle = extra.match(/^style\s*=\s*"([^"]+)"\s*/i); + extraStyle = extra.match(/^style\s*=\s*"([^"]+)"\s*/i) // anchor and br are the only ones that don't turn into a tspan - if(tag === 'a') { - if(close) return ''; - else if(extra.substr(0, 4).toLowerCase() !== 'href') return ''; - else { + if (tag === 'a') { + if (close) return '' + else if (extra.substr(0, 4).toLowerCase() !== 'href') return '' + else { // remove quotes, leading '=', replace '&' with '&' - var href = extra.substr(4) + var href = extra.substr(4) .replace(/["']/g, '') - .replace(/=/, ''); + .replace(/=/, '') // check protocol - var dummyAnchor = document.createElement('a'); - dummyAnchor.href = href; - if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return ''; - - return ''; - } - } - else if(tag === 'br') return '
'; - else if(close) { + var dummyAnchor = document.createElement('a') + dummyAnchor.href = href + if (PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '
' + + return '' + } + } else if (tag === 'br') return '
' + else if (close) { // closing tag // sub/sup: extra tspan with zero-width space to get back to the right baseline - if(tag === 'sup') return ''; - if(tag === 'sub') return ''; - else return ''; - } - else { - var tspanStart = '' + if (tag === 'sub') return '' + else return '' + } else { + var tspanStart = ''; - } - } - else { - return exports.xml_entity_encode(d).replace(/' } - }); - - var indices = []; - for(var index = result.indexOf('
'); index > 0; index = result.indexOf('
', index + 1)) { - indices.push(index); + } else { + return exports.xml_entity_encode(d).replace(/'); index > 0; index = result.indexOf('
', index + 1)) { + indices.push(index) + } + var count = 0 + indices.forEach(function (d) { + var brIndex = d + count + var search = result.slice(0, brIndex) + var previousOpenTag = '' + for (var i2 = search.length - 1; i2 >= 0; i2--) { + var isTag = search[i2].match(/<(\/?).*>/i) + if (isTag && search[i2] !== '
') { + if (!isTag[1]) previousOpenTag = search[i2] + break + } } - var count = 0; - indices.forEach(function(d) { - var brIndex = d + count; - var search = result.slice(0, brIndex); - var previousOpenTag = ''; - for(var i2 = search.length - 1; i2 >= 0; i2--) { - var isTag = search[i2].match(/<(\/?).*>/i); - if(isTag && search[i2] !== '
') { - if(!isTag[1]) previousOpenTag = search[i2]; - break; - } - } - if(previousOpenTag) { - result.splice(brIndex + 1, 0, previousOpenTag); - result.splice(brIndex, 0, ''); - count += 2; - } - }); + if (previousOpenTag) { + result.splice(brIndex + 1, 0, previousOpenTag) + result.splice(brIndex, 0, '') + count += 2 + } + }) - var joined = result.join(''); - var splitted = joined.split(/
/gi); - if(splitted.length > 1) { - result = splitted.map(function(d, i) { + var joined = result.join('') + var splitted = joined.split(/
/gi) + if (splitted.length > 1) { + result = splitted.map(function (d, i) { // TODO: figure out max font size of this line and alter dy // this requires either: // 1) bringing the base font size into convertToTspans, or // 2) only allowing relative percentage font sizes. // I think #2 is the way to go - return '' + d + ''; - }); - } + return '' + d + '' + }) + } - return result.join(''); + return result.join('') } -function alignHTMLWith(_base, container, options) { - var alignH = options.horizontalAlign, - alignV = options.verticalAlign || 'top', - bRect = _base.node().getBoundingClientRect(), - cRect = container.node().getBoundingClientRect(), - thisRect, - getTop, - getLeft; - - if(alignV === 'bottom') { - getTop = function() { return bRect.bottom - thisRect.height; }; - } else if(alignV === 'middle') { - getTop = function() { return bRect.top + (bRect.height - thisRect.height) / 2; }; - } else { // default: top - getTop = function() { return bRect.top; }; - } - - if(alignH === 'right') { - getLeft = function() { return bRect.right - thisRect.width; }; - } else if(alignH === 'center') { - getLeft = function() { return bRect.left + (bRect.width - thisRect.width) / 2; }; - } else { // default: left - getLeft = function() { return bRect.left; }; - } - - return function() { - thisRect = this.node().getBoundingClientRect(); - this.style({ - top: (getTop() - cRect.top) + 'px', - left: (getLeft() - cRect.left) + 'px', - 'z-index': 1000 - }); - return this; - }; +function alignHTMLWith (_base, container, options) { + var alignH = options.horizontalAlign, + alignV = options.verticalAlign || 'top', + bRect = _base.node().getBoundingClientRect(), + cRect = container.node().getBoundingClientRect(), + thisRect, + getTop, + getLeft + + if (alignV === 'bottom') { + getTop = function () { return bRect.bottom - thisRect.height } + } else if (alignV === 'middle') { + getTop = function () { return bRect.top + (bRect.height - thisRect.height) / 2 } + } else { // default: top + getTop = function () { return bRect.top } + } + + if (alignH === 'right') { + getLeft = function () { return bRect.right - thisRect.width } + } else if (alignH === 'center') { + getLeft = function () { return bRect.left + (bRect.width - thisRect.width) / 2 } + } else { // default: left + getLeft = function () { return bRect.left } + } + + return function () { + thisRect = this.node().getBoundingClientRect() + this.style({ + top: (getTop() - cRect.top) + 'px', + left: (getLeft() - cRect.left) + 'px', + 'z-index': 1000 + }) + return this + } } // Editable title -exports.makeEditable = function(context, _delegate, options) { - if(!options) options = {}; - var that = this; - var dispatch = d3.dispatch('edit', 'input', 'cancel'); - var textSelection = d3.select(this.node()) - .style({'pointer-events': 'all'}); +exports.makeEditable = function (context, _delegate, options) { + if (!options) options = {} + var that = this + var dispatch = d3.dispatch('edit', 'input', 'cancel') + var textSelection = d3.select(this.node()) + .style({'pointer-events': 'all'}) - var handlerElement = _delegate || textSelection; - if(_delegate) textSelection.style({'pointer-events': 'none'}); + var handlerElement = _delegate || textSelection + if (_delegate) textSelection.style({'pointer-events': 'none'}) - function handleClick() { - appendEditable(); - that.style({opacity: 0}); + function handleClick () { + appendEditable() + that.style({opacity: 0}) // also hide any mathjax svg - var svgClass = handlerElement.attr('class'), - mathjaxClass; - if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; - else mathjaxClass = '[class*=-math-group]'; - if(mathjaxClass) { - d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); - } - } - - function selectElementContents(_el) { - var el = _el.node(); - var range = document.createRange(); - range.selectNodeContents(el); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - el.focus(); + var svgClass = handlerElement.attr('class'), + mathjaxClass + if (svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group' + else mathjaxClass = '[class*=-math-group]' + if (mathjaxClass) { + d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}) } - - function appendEditable() { - var plotDiv = d3.select(Lib.getPlotDiv(that.node())), - container = plotDiv.select('.svg-container'), - div = container.append('div'); - div.classed('plugin-editable editable', true) + } + + function selectElementContents (_el) { + var el = _el.node() + var range = document.createRange() + range.selectNodeContents(el) + var sel = window.getSelection() + sel.removeAllRanges() + sel.addRange(range) + el.focus() + } + + function appendEditable () { + var plotDiv = d3.select(Lib.getPlotDiv(that.node())), + container = plotDiv.select('.svg-container'), + div = container.append('div') + div.classed('plugin-editable editable', true) .style({ - position: 'absolute', - 'font-family': that.style('font-family') || 'Arial', - 'font-size': that.style('font-size') || 12, - color: options.fill || that.style('fill') || 'black', - opacity: 1, - 'background-color': options.background || 'transparent', - outline: '#ffffff33 1px solid', - margin: [-parseFloat(that.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px', - padding: '0', - 'box-sizing': 'border-box' + position: 'absolute', + 'font-family': that.style('font-family') || 'Arial', + 'font-size': that.style('font-size') || 12, + color: options.fill || that.style('fill') || 'black', + opacity: 1, + 'background-color': options.background || 'transparent', + outline: '#ffffff33 1px solid', + margin: [-parseFloat(that.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px', + padding: '0', + 'box-sizing': 'border-box' }) .attr({contenteditable: true}) .text(options.text || that.attr('data-unformatted')) .call(alignHTMLWith(that, container, options)) - .on('blur', function() { - that.text(this.textContent) - .style({opacity: 1}); - var svgClass = d3.select(this).attr('class'), - mathjaxClass; - if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; - else mathjaxClass = '[class*=-math-group]'; - if(mathjaxClass) { - d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); - } - var text = this.textContent; - d3.select(this).transition().duration(0).remove(); - d3.select(document).on('mouseup', null); - dispatch.edit.call(that, text); + .on('blur', function () { + that.text(this.textContent) + .style({opacity: 1}) + var svgClass = d3.select(this).attr('class'), + mathjaxClass + if (svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group' + else mathjaxClass = '[class*=-math-group]' + if (mathjaxClass) { + d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}) + } + var text = this.textContent + d3.select(this).transition().duration(0).remove() + d3.select(document).on('mouseup', null) + dispatch.edit.call(that, text) }) - .on('focus', function() { - var context = this; - d3.select(document).on('mouseup', function() { - if(d3.event.target === context) return false; - if(document.activeElement === div.node()) div.node().blur(); - }); + .on('focus', function () { + var context = this + d3.select(document).on('mouseup', function () { + if (d3.event.target === context) return false + if (document.activeElement === div.node()) div.node().blur() + }) }) - .on('keyup', function() { - if(d3.event.which === 27) { - that.style({opacity: 1}); - d3.select(this) + .on('keyup', function () { + if (d3.event.which === 27) { + that.style({opacity: 1}) + d3.select(this) .style({opacity: 0}) - .on('blur', function() { return false; }) - .transition().remove(); - dispatch.cancel.call(that, this.textContent); - } - else { - dispatch.input.call(that, this.textContent); - d3.select(this).call(alignHTMLWith(that, container, options)); - } + .on('blur', function () { return false }) + .transition().remove() + dispatch.cancel.call(that, this.textContent) + } else { + dispatch.input.call(that, this.textContent) + d3.select(this).call(alignHTMLWith(that, container, options)) + } }) - .on('keydown', function() { - if(d3.event.which === 13) this.blur(); + .on('keydown', function () { + if (d3.event.which === 13) this.blur() }) - .call(selectElementContents); - } + .call(selectElementContents) + } - if(options.immediate) handleClick(); - else handlerElement.on('click', handleClick); + if (options.immediate) handleClick() + else handlerElement.on('click', handleClick) - return d3.rebind(this, dispatch, 'on'); -}; + return d3.rebind(this, dispatch, 'on') +} diff --git a/src/lib/topojson_utils.js b/src/lib/topojson_utils.js index bdd5d7bf196..073bd170508 100644 --- a/src/lib/topojson_utils.js +++ b/src/lib/topojson_utils.js @@ -6,29 +6,27 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var topojsonUtils = module.exports = {} -var topojsonUtils = module.exports = {}; +var locationmodeToLayer = require('../plots/geo/constants').locationmodeToLayer +var topojsonFeature = require('topojson-client').feature -var locationmodeToLayer = require('../plots/geo/constants').locationmodeToLayer; -var topojsonFeature = require('topojson-client').feature; +topojsonUtils.getTopojsonName = function (geoLayout) { + return [ + geoLayout.scope.replace(/ /g, '-'), '_', + geoLayout.resolution.toString(), 'm' + ].join('') +} +topojsonUtils.getTopojsonPath = function (topojsonURL, topojsonName) { + return topojsonURL + topojsonName + '.json' +} -topojsonUtils.getTopojsonName = function(geoLayout) { - return [ - geoLayout.scope.replace(/ /g, '-'), '_', - geoLayout.resolution.toString(), 'm' - ].join(''); -}; +topojsonUtils.getTopojsonFeatures = function (trace, topojson) { + var layer = locationmodeToLayer[trace.locationmode], + obj = topojson.objects[layer] -topojsonUtils.getTopojsonPath = function(topojsonURL, topojsonName) { - return topojsonURL + topojsonName + '.json'; -}; - -topojsonUtils.getTopojsonFeatures = function(trace, topojson) { - var layer = locationmodeToLayer[trace.locationmode], - obj = topojson.objects[layer]; - - return topojsonFeature(topojson, obj).features; -}; + return topojsonFeature(topojson, obj).features +} diff --git a/src/lib/typed_array_truncate.js b/src/lib/typed_array_truncate.js index 3bacc8ebe56..b99b3cdd062 100644 --- a/src/lib/typed_array_truncate.js +++ b/src/lib/typed_array_truncate.js @@ -6,18 +6,18 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -function truncateFloat32(arrayIn, len) { - var arrayOut = new Float32Array(len); - for(var i = 0; i < len; i++) arrayOut[i] = arrayIn[i]; - return arrayOut; +function truncateFloat32 (arrayIn, len) { + var arrayOut = new Float32Array(len) + for (var i = 0; i < len; i++) arrayOut[i] = arrayIn[i] + return arrayOut } -function truncateFloat64(arrayIn, len) { - var arrayOut = new Float64Array(len); - for(var i = 0; i < len; i++) arrayOut[i] = arrayIn[i]; - return arrayOut; +function truncateFloat64 (arrayIn, len) { + var arrayOut = new Float64Array(len) + for (var i = 0; i < len; i++) arrayOut[i] = arrayIn[i] + return arrayOut } /** @@ -25,8 +25,8 @@ function truncateFloat64(arrayIn, len) { * For some reason, ES2015 Float32Array.prototype.slice takes * 2x as long, therefore we aren't checking for its existence */ -module.exports = function truncate(arrayIn, len) { - if(arrayIn instanceof Float32Array) return truncateFloat32(arrayIn, len); - if(arrayIn instanceof Float64Array) return truncateFloat64(arrayIn, len); - throw new Error('This array type is not yet supported by `truncate`.'); -}; +module.exports = function truncate (arrayIn, len) { + if (arrayIn instanceof Float32Array) return truncateFloat32(arrayIn, len) + if (arrayIn instanceof Float64Array) return truncateFloat64(arrayIn, len) + throw new Error('This array type is not yet supported by `truncate`.') +} diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 62a4b7e38d5..6ddd58aee4a 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -6,436 +6,426 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); -var m4FromQuat = require('gl-mat4/fromQuat'); - -var Registry = require('../registry'); -var Lib = require('../lib'); -var Plots = require('../plots/plots'); -var Axes = require('../plots/cartesian/axes'); -var Color = require('../components/color'); +var isNumeric = require('fast-isnumeric') +var m4FromQuat = require('gl-mat4/fromQuat') +var Registry = require('../registry') +var Lib = require('../lib') +var Plots = require('../plots/plots') +var Axes = require('../plots/cartesian/axes') +var Color = require('../components/color') // Get the container div: we store all variables for this plot as // properties of this div // some callers send this in by DOM element, others by id (string) -exports.getGraphDiv = function(gd) { - var gdElement; - - if(typeof gd === 'string') { - gdElement = document.getElementById(gd); +exports.getGraphDiv = function (gd) { + var gdElement - if(gdElement === null) { - throw new Error('No DOM element with id \'' + gd + '\' exists on the page.'); - } + if (typeof gd === 'string') { + gdElement = document.getElementById(gd) - return gdElement; - } - else if(gd === null || gd === undefined) { - throw new Error('DOM element provided is null or undefined'); + if (gdElement === null) { + throw new Error('No DOM element with id \'' + gd + '\' exists on the page.') } - return gd; // otherwise assume that gd is a DOM element -}; + return gdElement + } else if (gd === null || gd === undefined) { + throw new Error('DOM element provided is null or undefined') + } + + return gd // otherwise assume that gd is a DOM element +} // clear the promise queue if one of them got rejected -exports.clearPromiseQueue = function(gd) { - if(Array.isArray(gd._promises) && gd._promises.length > 0) { - Lib.log('Clearing previous rejected promises from queue.'); - } +exports.clearPromiseQueue = function (gd) { + if (Array.isArray(gd._promises) && gd._promises.length > 0) { + Lib.log('Clearing previous rejected promises from queue.') + } - gd._promises = []; -}; + gd._promises = [] +} // make a few changes to the layout right away // before it gets used for anything // backward compatibility and cleanup of nonstandard options -exports.cleanLayout = function(layout) { - var i, j; +exports.cleanLayout = function (layout) { + var i, j - if(!layout) layout = {}; + if (!layout) layout = {} // cannot have (x|y)axis1, numbering goes axis, axis2, axis3... - if(layout.xaxis1) { - if(!layout.xaxis) layout.xaxis = layout.xaxis1; - delete layout.xaxis1; + if (layout.xaxis1) { + if (!layout.xaxis) layout.xaxis = layout.xaxis1 + delete layout.xaxis1 + } + if (layout.yaxis1) { + if (!layout.yaxis) layout.yaxis = layout.yaxis1 + delete layout.yaxis1 + } + + var axList = Axes.list({_fullLayout: layout}) + for (i = 0; i < axList.length; i++) { + var ax = axList[i] + if (ax.anchor && ax.anchor !== 'free') { + ax.anchor = Axes.cleanId(ax.anchor) } - if(layout.yaxis1) { - if(!layout.yaxis) layout.yaxis = layout.yaxis1; - delete layout.yaxis1; - } - - var axList = Axes.list({_fullLayout: layout}); - for(i = 0; i < axList.length; i++) { - var ax = axList[i]; - if(ax.anchor && ax.anchor !== 'free') { - ax.anchor = Axes.cleanId(ax.anchor); - } - if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying); + if (ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying) // old method of axis type - isdate and islog (before category existed) - if(!ax.type) { - if(ax.isdate) ax.type = 'date'; - else if(ax.islog) ax.type = 'log'; - else if(ax.isdate === false && ax.islog === false) ax.type = 'linear'; - } - if(ax.autorange === 'withzero' || ax.autorange === 'tozero') { - ax.autorange = true; - ax.rangemode = 'tozero'; - } - delete ax.islog; - delete ax.isdate; - delete ax.categories; // replaced by _categories + if (!ax.type) { + if (ax.isdate) ax.type = 'date' + else if (ax.islog) ax.type = 'log' + else if (ax.isdate === false && ax.islog === false) ax.type = 'linear' + } + if (ax.autorange === 'withzero' || ax.autorange === 'tozero') { + ax.autorange = true + ax.rangemode = 'tozero' + } + delete ax.islog + delete ax.isdate + delete ax.categories // replaced by _categories // prune empty domain arrays made before the new nestedProperty - if(emptyContainer(ax, 'domain')) delete ax.domain; + if (emptyContainer(ax, 'domain')) delete ax.domain // autotick -> tickmode - if(ax.autotick !== undefined) { - if(ax.tickmode === undefined) { - ax.tickmode = ax.autotick ? 'auto' : 'linear'; - } - delete ax.autotick; - } + if (ax.autotick !== undefined) { + if (ax.tickmode === undefined) { + ax.tickmode = ax.autotick ? 'auto' : 'linear' + } + delete ax.autotick } - - var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0; - for(i = 0; i < annotationsLen; i++) { - var ann = layout.annotations[i]; - - if(!Lib.isPlainObject(ann)) continue; - - if(ann.ref) { - if(ann.ref === 'paper') { - ann.xref = 'paper'; - ann.yref = 'paper'; - } - else if(ann.ref === 'data') { - ann.xref = 'x'; - ann.yref = 'y'; - } - delete ann.ref; - } - - cleanAxRef(ann, 'xref'); - cleanAxRef(ann, 'yref'); + } + + var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0 + for (i = 0; i < annotationsLen; i++) { + var ann = layout.annotations[i] + + if (!Lib.isPlainObject(ann)) continue + + if (ann.ref) { + if (ann.ref === 'paper') { + ann.xref = 'paper' + ann.yref = 'paper' + } else if (ann.ref === 'data') { + ann.xref = 'x' + ann.yref = 'y' + } + delete ann.ref } - var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0; - for(i = 0; i < shapesLen; i++) { - var shape = layout.shapes[i]; + cleanAxRef(ann, 'xref') + cleanAxRef(ann, 'yref') + } - if(!Lib.isPlainObject(shape)) continue; + var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0 + for (i = 0; i < shapesLen; i++) { + var shape = layout.shapes[i] - cleanAxRef(shape, 'xref'); - cleanAxRef(shape, 'yref'); - } + if (!Lib.isPlainObject(shape)) continue + + cleanAxRef(shape, 'xref') + cleanAxRef(shape, 'yref') + } - var legend = layout.legend; - if(legend) { + var legend = layout.legend + if (legend) { // check for old-style legend positioning (x or y is +/- 100) - if(legend.x > 3) { - legend.x = 1.02; - legend.xanchor = 'left'; - } - else if(legend.x < -2) { - legend.x = -0.02; - legend.xanchor = 'right'; - } + if (legend.x > 3) { + legend.x = 1.02 + legend.xanchor = 'left' + } else if (legend.x < -2) { + legend.x = -0.02 + legend.xanchor = 'right' + } - if(legend.y > 3) { - legend.y = 1.02; - legend.yanchor = 'bottom'; - } - else if(legend.y < -2) { - legend.y = -0.02; - legend.yanchor = 'top'; - } + if (legend.y > 3) { + legend.y = 1.02 + legend.yanchor = 'bottom' + } else if (legend.y < -2) { + legend.y = -0.02 + legend.yanchor = 'top' } + } /* * Moved from rotate -> orbit for dragmode */ - if(layout.dragmode === 'rotate') layout.dragmode = 'orbit'; + if (layout.dragmode === 'rotate') layout.dragmode = 'orbit' // cannot have scene1, numbering goes scene, scene2, scene3... - if(layout.scene1) { - if(!layout.scene) layout.scene = layout.scene1; - delete layout.scene1; - } + if (layout.scene1) { + if (!layout.scene) layout.scene = layout.scene1 + delete layout.scene1 + } /* * Clean up Scene layouts */ - var sceneIds = Plots.getSubplotIds(layout, 'gl3d'); - for(i = 0; i < sceneIds.length; i++) { - var scene = layout[sceneIds[i]]; + var sceneIds = Plots.getSubplotIds(layout, 'gl3d') + for (i = 0; i < sceneIds.length; i++) { + var scene = layout[sceneIds[i]] // clean old Camera coords - var cameraposition = scene.cameraposition; - if(Array.isArray(cameraposition) && cameraposition[0].length === 4) { - var rotation = cameraposition[0], - center = cameraposition[1], - radius = cameraposition[2], - mat = m4FromQuat([], rotation), - eye = []; - - for(j = 0; j < 3; ++j) { - eye[j] = center[i] + radius * mat[2 + 4 * j]; - } - - scene.camera = { - eye: {x: eye[0], y: eye[1], z: eye[2]}, - center: {x: center[0], y: center[1], z: center[2]}, - up: {x: mat[1], y: mat[5], z: mat[9]} - }; - - delete scene.cameraposition; - } + var cameraposition = scene.cameraposition + if (Array.isArray(cameraposition) && cameraposition[0].length === 4) { + var rotation = cameraposition[0], + center = cameraposition[1], + radius = cameraposition[2], + mat = m4FromQuat([], rotation), + eye = [] + + for (j = 0; j < 3; ++j) { + eye[j] = center[i] + radius * mat[2 + 4 * j] + } + + scene.camera = { + eye: {x: eye[0], y: eye[1], z: eye[2]}, + center: {x: center[0], y: center[1], z: center[2]}, + up: {x: mat[1], y: mat[5], z: mat[9]} + } + + delete scene.cameraposition } + } // sanitize rgb(fractions) and rgba(fractions) that old tinycolor // supported, but new tinycolor does not because they're not valid css - Color.clean(layout); + Color.clean(layout) - return layout; -}; + return layout +} -function cleanAxRef(container, attr) { - var valIn = container[attr], - axLetter = attr.charAt(0); - if(valIn && valIn !== 'paper') { - container[attr] = Axes.cleanId(valIn, axLetter); - } +function cleanAxRef (container, attr) { + var valIn = container[attr], + axLetter = attr.charAt(0) + if (valIn && valIn !== 'paper') { + container[attr] = Axes.cleanId(valIn, axLetter) + } } // Make a few changes to the data right away // before it gets used for anything -exports.cleanData = function(data, existingData) { - +exports.cleanData = function (data, existingData) { // Enforce unique IDs - var suids = [], // seen uids --- so we can weed out incoming repeats - uids = data.concat(Array.isArray(existingData) ? existingData : []) - .filter(function(trace) { return 'uid' in trace; }) - .map(function(trace) { return trace.uid; }); + var suids = [], // seen uids --- so we can weed out incoming repeats + uids = data.concat(Array.isArray(existingData) ? existingData : []) + .filter(function (trace) { return 'uid' in trace }) + .map(function (trace) { return trace.uid }) - for(var tracei = 0; tracei < data.length; tracei++) { - var trace = data[tracei]; - var i; + for (var tracei = 0; tracei < data.length; tracei++) { + var trace = data[tracei] + var i // assign uids to each trace and detect collisions. - if(!('uid' in trace) || suids.indexOf(trace.uid) !== -1) { - var newUid; - - for(i = 0; i < 100; i++) { - newUid = Lib.randstr(uids); - if(suids.indexOf(newUid) === -1) break; - } - trace.uid = Lib.randstr(uids); - uids.push(trace.uid); - } + if (!('uid' in trace) || suids.indexOf(trace.uid) !== -1) { + var newUid + + for (i = 0; i < 100; i++) { + newUid = Lib.randstr(uids) + if (suids.indexOf(newUid) === -1) break + } + trace.uid = Lib.randstr(uids) + uids.push(trace.uid) + } // keep track of already seen uids, so that if there are // doubles we force the trace with a repeat uid to // acquire a new one - suids.push(trace.uid); + suids.push(trace.uid) // BACKWARD COMPATIBILITY FIXES // use xbins to bin data in x, and ybins to bin data in y - if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) { - trace.ybins = trace.xbins; - delete trace.xbins; - } + if (trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) { + trace.ybins = trace.xbins + delete trace.xbins + } // error_y.opacity is obsolete - merge into color - if(trace.error_y && 'opacity' in trace.error_y) { - var dc = Color.defaults, - yeColor = trace.error_y.color || - (Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc.length]); - trace.error_y.color = Color.addOpacity( + if (trace.error_y && 'opacity' in trace.error_y) { + var dc = Color.defaults, + yeColor = trace.error_y.color || + (Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc.length]) + trace.error_y.color = Color.addOpacity( Color.rgb(yeColor), - Color.opacity(yeColor) * trace.error_y.opacity); - delete trace.error_y.opacity; - } + Color.opacity(yeColor) * trace.error_y.opacity) + delete trace.error_y.opacity + } // convert bardir to orientation, and put the data into // the axes it's eventually going to be used with - if('bardir' in trace) { - if(trace.bardir === 'h' && (Registry.traceIs(trace, 'bar') || + if ('bardir' in trace) { + if (trace.bardir === 'h' && (Registry.traceIs(trace, 'bar') || trace.type.substr(0, 9) === 'histogram')) { - trace.orientation = 'h'; - exports.swapXYData(trace); - } - delete trace.bardir; - } + trace.orientation = 'h' + exports.swapXYData(trace) + } + delete trace.bardir + } // now we have only one 1D histogram type, and whether // it uses x or y data depends on trace.orientation - if(trace.type === 'histogramy') exports.swapXYData(trace); - if(trace.type === 'histogramx' || trace.type === 'histogramy') { - trace.type = 'histogram'; - } + if (trace.type === 'histogramy') exports.swapXYData(trace) + if (trace.type === 'histogramx' || trace.type === 'histogramy') { + trace.type = 'histogram' + } // scl->scale, reversescl->reversescale - if('scl' in trace) { - trace.colorscale = trace.scl; - delete trace.scl; - } - if('reversescl' in trace) { - trace.reversescale = trace.reversescl; - delete trace.reversescl; - } + if ('scl' in trace) { + trace.colorscale = trace.scl + delete trace.scl + } + if ('reversescl' in trace) { + trace.reversescale = trace.reversescl + delete trace.reversescl + } // axis ids x1 -> x, y1-> y - if(trace.xaxis) trace.xaxis = Axes.cleanId(trace.xaxis, 'x'); - if(trace.yaxis) trace.yaxis = Axes.cleanId(trace.yaxis, 'y'); + if (trace.xaxis) trace.xaxis = Axes.cleanId(trace.xaxis, 'x') + if (trace.yaxis) trace.yaxis = Axes.cleanId(trace.yaxis, 'y') // scene ids scene1 -> scene - if(Registry.traceIs(trace, 'gl3d') && trace.scene) { - trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene); - } + if (Registry.traceIs(trace, 'gl3d') && trace.scene) { + trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene) + } - if(!Registry.traceIs(trace, 'pie') && !Registry.traceIs(trace, 'bar')) { - if(Array.isArray(trace.textposition)) { - trace.textposition = trace.textposition.map(cleanTextPosition); - } - else if(trace.textposition) { - trace.textposition = cleanTextPosition(trace.textposition); - } - } + if (!Registry.traceIs(trace, 'pie') && !Registry.traceIs(trace, 'bar')) { + if (Array.isArray(trace.textposition)) { + trace.textposition = trace.textposition.map(cleanTextPosition) + } else if (trace.textposition) { + trace.textposition = cleanTextPosition(trace.textposition) + } + } // fix typo in colorscale definition - if(Registry.traceIs(trace, '2dMap')) { - if(trace.colorscale === 'YIGnBu') trace.colorscale = 'YlGnBu'; - if(trace.colorscale === 'YIOrRd') trace.colorscale = 'YlOrRd'; - } - if(Registry.traceIs(trace, 'markerColorscale') && trace.marker) { - var cont = trace.marker; - if(cont.colorscale === 'YIGnBu') cont.colorscale = 'YlGnBu'; - if(cont.colorscale === 'YIOrRd') cont.colorscale = 'YlOrRd'; - } + if (Registry.traceIs(trace, '2dMap')) { + if (trace.colorscale === 'YIGnBu') trace.colorscale = 'YlGnBu' + if (trace.colorscale === 'YIOrRd') trace.colorscale = 'YlOrRd' + } + if (Registry.traceIs(trace, 'markerColorscale') && trace.marker) { + var cont = trace.marker + if (cont.colorscale === 'YIGnBu') cont.colorscale = 'YlGnBu' + if (cont.colorscale === 'YIOrRd') cont.colorscale = 'YlOrRd' + } // fix typo in surface 'highlight*' definitions - if(trace.type === 'surface' && Lib.isPlainObject(trace.contours)) { - var dims = ['x', 'y', 'z']; + if (trace.type === 'surface' && Lib.isPlainObject(trace.contours)) { + var dims = ['x', 'y', 'z'] - for(i = 0; i < dims.length; i++) { - var opts = trace.contours[dims[i]]; + for (i = 0; i < dims.length; i++) { + var opts = trace.contours[dims[i]] - if(!Lib.isPlainObject(opts)) continue; + if (!Lib.isPlainObject(opts)) continue - if(opts.highlightColor) { - opts.highlightcolor = opts.highlightColor; - delete opts.highlightColor; - } + if (opts.highlightColor) { + opts.highlightcolor = opts.highlightColor + delete opts.highlightColor + } - if(opts.highlightWidth) { - opts.highlightwidth = opts.highlightWidth; - delete opts.highlightWidth; - } - } + if (opts.highlightWidth) { + opts.highlightwidth = opts.highlightWidth + delete opts.highlightWidth } + } + } // transforms backward compatibility fixes - if(Array.isArray(trace.transforms)) { - var transforms = trace.transforms; - - for(i = 0; i < transforms.length; i++) { - var transform = transforms[i]; - - if(!Lib.isPlainObject(transform)) continue; - - if(transform.type === 'filter') { - if(transform.filtersrc) { - transform.target = transform.filtersrc; - delete transform.filtersrc; - } - - if(transform.calendar) { - if(!transform.valuecalendar) { - transform.valuecalendar = transform.calendar; - } - delete transform.calendar; - } - } + if (Array.isArray(trace.transforms)) { + var transforms = trace.transforms + + for (i = 0; i < transforms.length; i++) { + var transform = transforms[i] + + if (!Lib.isPlainObject(transform)) continue + + if (transform.type === 'filter') { + if (transform.filtersrc) { + transform.target = transform.filtersrc + delete transform.filtersrc + } + + if (transform.calendar) { + if (!transform.valuecalendar) { + transform.valuecalendar = transform.calendar } + delete transform.calendar + } } + } + } // prune empty containers made before the new nestedProperty - if(emptyContainer(trace, 'line')) delete trace.line; - if('marker' in trace) { - if(emptyContainer(trace.marker, 'line')) delete trace.marker.line; - if(emptyContainer(trace, 'marker')) delete trace.marker; - } + if (emptyContainer(trace, 'line')) delete trace.line + if ('marker' in trace) { + if (emptyContainer(trace.marker, 'line')) delete trace.marker.line + if (emptyContainer(trace, 'marker')) delete trace.marker + } // sanitize rgb(fractions) and rgba(fractions) that old tinycolor // supported, but new tinycolor does not because they're not valid css - Color.clean(trace); - } -}; + Color.clean(trace) + } +} // textposition - support partial attributes (ie just 'top') // and incorrect use of middle / center etc. -function cleanTextPosition(textposition) { - var posY = 'middle', - posX = 'center'; - if(textposition.indexOf('top') !== -1) posY = 'top'; - else if(textposition.indexOf('bottom') !== -1) posY = 'bottom'; +function cleanTextPosition (textposition) { + var posY = 'middle', + posX = 'center' + if (textposition.indexOf('top') !== -1) posY = 'top' + else if (textposition.indexOf('bottom') !== -1) posY = 'bottom' - if(textposition.indexOf('left') !== -1) posX = 'left'; - else if(textposition.indexOf('right') !== -1) posX = 'right'; + if (textposition.indexOf('left') !== -1) posX = 'left' + else if (textposition.indexOf('right') !== -1) posX = 'right' - return posY + ' ' + posX; + return posY + ' ' + posX } -function emptyContainer(outer, innerStr) { - return (innerStr in outer) && +function emptyContainer (outer, innerStr) { + return (innerStr in outer) && (typeof outer[innerStr] === 'object') && - (Object.keys(outer[innerStr]).length === 0); + (Object.keys(outer[innerStr]).length === 0) } - // swap all the data and data attributes associated with x and y -exports.swapXYData = function(trace) { - var i; - Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']); - if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) { - if(trace.transpose) delete trace.transpose; - else trace.transpose = true; - } - if(trace.error_x && trace.error_y) { - var errorY = trace.error_y, - copyYstyle = ('copy_ystyle' in errorY) ? errorY.copy_ystyle : - !(errorY.color || errorY.thickness || errorY.width); - Lib.swapAttrs(trace, ['error_?.copy_ystyle']); - if(copyYstyle) { - Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']); - } +exports.swapXYData = function (trace) { + var i + Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']) + if (Array.isArray(trace.z) && Array.isArray(trace.z[0])) { + if (trace.transpose) delete trace.transpose + else trace.transpose = true + } + if (trace.error_x && trace.error_y) { + var errorY = trace.error_y, + copyYstyle = ('copy_ystyle' in errorY) ? errorY.copy_ystyle : + !(errorY.color || errorY.thickness || errorY.width) + Lib.swapAttrs(trace, ['error_?.copy_ystyle']) + if (copyYstyle) { + Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']) } - if(trace.hoverinfo) { - var hoverInfoParts = trace.hoverinfo.split('+'); - for(i = 0; i < hoverInfoParts.length; i++) { - if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y'; - else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x'; - } - trace.hoverinfo = hoverInfoParts.join('+'); + } + if (trace.hoverinfo) { + var hoverInfoParts = trace.hoverinfo.split('+') + for (i = 0; i < hoverInfoParts.length; i++) { + if (hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y' + else if (hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x' } -}; + trace.hoverinfo = hoverInfoParts.join('+') + } +} // coerce traceIndices input to array of trace indices -exports.coerceTraceIndices = function(gd, traceIndices) { - if(isNumeric(traceIndices)) { - return [traceIndices]; - } - else if(!Array.isArray(traceIndices) || !traceIndices.length) { - return gd.data.map(function(_, i) { return i; }); - } - - return traceIndices; -}; +exports.coerceTraceIndices = function (gd, traceIndices) { + if (isNumeric(traceIndices)) { + return [traceIndices] + } else if (!Array.isArray(traceIndices) || !traceIndices.length) { + return gd.data.map(function (_, i) { return i }) + } + + return traceIndices +} /** * Manages logic around array container item creation / deletion / update @@ -449,38 +439,35 @@ exports.coerceTraceIndices = function(gd, traceIndices) { * undo hash (N.B. undoit may be mutated here). * */ -exports.manageArrayContainers = function(np, newVal, undoit) { - var obj = np.obj, - parts = np.parts, - pLength = parts.length, - pLast = parts[pLength - 1]; +exports.manageArrayContainers = function (np, newVal, undoit) { + var obj = np.obj, + parts = np.parts, + pLength = parts.length, + pLast = parts[pLength - 1] - var pLastIsNumber = isNumeric(pLast); + var pLastIsNumber = isNumeric(pLast) // delete item - if(pLastIsNumber && newVal === null) { - + if (pLastIsNumber && newVal === null) { // Clear item in array container when new value is null - var contPath = parts.slice(0, pLength - 1).join('.'), - cont = Lib.nestedProperty(obj, contPath).get(); - cont.splice(pLast, 1); + var contPath = parts.slice(0, pLength - 1).join('.'), + cont = Lib.nestedProperty(obj, contPath).get() + cont.splice(pLast, 1) // Note that nested property clears null / undefined at end of // array container, but not within them. - } + } // create item - else if(pLastIsNumber && np.get() === undefined) { - + else if (pLastIsNumber && np.get() === undefined) { // When adding a new item, make sure undo command will remove it - if(np.get() === undefined) undoit[np.astr] = null; + if (np.get() === undefined) undoit[np.astr] = null - np.set(newVal); - } + np.set(newVal) + } // update item - else { - + else { // If the last part of attribute string isn't a number, // np.set is all we need. - np.set(newVal); - } -}; + np.set(newVal) + } +} diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3f5f80444d5..dd101cb9a42 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -6,31 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') +var Plotly = require('../plotly') +var Lib = require('../lib') +var Events = require('../lib/events') +var Queue = require('../lib/queue') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Registry = require('../registry') +var Plots = require('../plots/plots') +var Fx = require('../plots/cartesian/graph_interact') +var Polar = require('../plots/polar') -var Plotly = require('../plotly'); -var Lib = require('../lib'); -var Events = require('../lib/events'); -var Queue = require('../lib/queue'); - -var Registry = require('../registry'); -var Plots = require('../plots/plots'); -var Fx = require('../plots/cartesian/graph_interact'); -var Polar = require('../plots/polar'); - -var Drawing = require('../components/drawing'); -var ErrorBars = require('../components/errorbars'); -var xmlnsNamespaces = require('../constants/xmlns_namespaces'); -var svgTextUtils = require('../lib/svg_text_utils'); - -var helpers = require('./helpers'); -var subroutines = require('./subroutines'); +var Drawing = require('../components/drawing') +var ErrorBars = require('../components/errorbars') +var xmlnsNamespaces = require('../constants/xmlns_namespaces') +var svgTextUtils = require('../lib/svg_text_utils') +var helpers = require('./helpers') +var subroutines = require('./subroutines') /** * Main plot-creation function @@ -46,467 +43,464 @@ var subroutines = require('./subroutines'); * configuration options (see ./plot_config.js for more info) * */ -Plotly.plot = function(gd, data, layout, config) { - var frames; +Plotly.plot = function (gd, data, layout, config) { + var frames - gd = helpers.getGraphDiv(gd); + gd = helpers.getGraphDiv(gd) // Events.init is idempotent and bails early if gd has already been init'd - Events.init(gd); - - if(Lib.isPlainObject(data)) { - var obj = data; - data = obj.data; - layout = obj.layout; - config = obj.config; - frames = obj.frames; - } + Events.init(gd) + + if (Lib.isPlainObject(data)) { + var obj = data + data = obj.data + layout = obj.layout + config = obj.config + frames = obj.frames + } - var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]); - if(okToPlot === false) return Promise.reject(); + var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]) + if (okToPlot === false) return Promise.reject() // if there's no data or layout, and this isn't yet a plotly plot // container, log a warning to help plotly.js users debug - if(!data && !layout && !Lib.isPlotDiv(gd)) { - Lib.warn('Calling Plotly.plot as if redrawing ' + - 'but this container doesn\'t yet have a plot.', gd); - } + if (!data && !layout && !Lib.isPlotDiv(gd)) { + Lib.warn('Calling Plotly.plot as if redrawing ' + + 'but this container doesn\'t yet have a plot.', gd) + } - function addFrames() { - if(frames) { - return Plotly.addFrames(gd, frames); - } + function addFrames () { + if (frames) { + return Plotly.addFrames(gd, frames) } + } // transfer configuration options to gd until we move over to // a more OO like model - setPlotContext(gd, config); + setPlotContext(gd, config) - if(!layout) layout = {}; + if (!layout) layout = {} // hook class for plots main container (in case of plotly.js // this won't be #embedded-graph or .js-tab-contents) - d3.select(gd).classed('js-plotly-plot', true); + d3.select(gd).classed('js-plotly-plot', true) // off-screen getBoundingClientRect testing space, // in #js-plotly-tester (and stored as gd._tester) // so we can share cached text across tabs - Drawing.makeTester(gd); + Drawing.makeTester(gd) // collect promises for any async actions during plotting // any part of the plotting code can push to gd._promises, then // before we move to the next step, we check that they're all // complete, and empty out the promise list again. - gd._promises = []; + gd._promises = [] - var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data)); + var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data)) // if there is already data on the graph, append the new data // if you only want to redraw, pass a non-array for data - if(Array.isArray(data)) { - helpers.cleanData(data, gd.data); + if (Array.isArray(data)) { + helpers.cleanData(data, gd.data) - if(graphWasEmpty) gd.data = data; - else gd.data.push.apply(gd.data, data); + if (graphWasEmpty) gd.data = data + else gd.data.push.apply(gd.data, data) // for routines outside graph_obj that want a clean tab // (rather than appending to an existing one) gd.empty // is used to determine whether to make a new tab - gd.empty = false; - } + gd.empty = false + } - if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout); + if (!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout) // if the user is trying to drag the axes, allow new data and layout // to come in but don't allow a replot. - if(gd._dragging && !gd._transitioning) { + if (gd._dragging && !gd._transitioning) { // signal to drag handler that after everything else is done // we need to replot, because something has changed - gd._replotPending = true; - return Promise.reject(); - } else { + gd._replotPending = true + return Promise.reject() + } else { // we're going ahead with a replot now - gd._replotPending = false; - } + gd._replotPending = false + } - Plots.supplyDefaults(gd); + Plots.supplyDefaults(gd) // Polar plots - if(data && data[0] && data[0].r) return plotPolar(gd, data, layout); + if (data && data[0] && data[0].r) return plotPolar(gd, data, layout) // so we don't try to re-call Plotly.plot from inside // legend and colorbar, if margins changed - gd._replotting = true; + gd._replotting = true // make or remake the framework if we need to - if(graphWasEmpty) makePlotFramework(gd); + if (graphWasEmpty) makePlotFramework(gd) // polar need a different framework - if(gd.framework !== makePlotFramework) { - gd.framework = makePlotFramework; - makePlotFramework(gd); - } + if (gd.framework !== makePlotFramework) { + gd.framework = makePlotFramework + makePlotFramework(gd) + } // save initial axis range once per graph - if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd); + if (graphWasEmpty) Plotly.Axes.saveRangeInitial(gd) - var fullLayout = gd._fullLayout; + var fullLayout = gd._fullLayout // prepare the data and find the autorange // generate calcdata, if we need to // to force redoing calcdata, just delete it before calling Plotly.plot - var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length; - if(recalc) Plots.doCalcdata(gd); + var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length + if (recalc) Plots.doCalcdata(gd) // in case it has changed, attach fullData traces to calcdata - for(var i = 0; i < gd.calcdata.length; i++) { - gd.calcdata[i][0].trace = gd._fullData[i]; - } + for (var i = 0; i < gd.calcdata.length; i++) { + gd.calcdata[i][0].trace = gd._fullData[i] + } /* * start async-friendly code - now we're actually drawing things */ - var oldmargins = JSON.stringify(fullLayout._size); + var oldmargins = JSON.stringify(fullLayout._size) // draw framework first so that margin-pushing // components can position themselves correctly - function drawFramework() { - var basePlotModules = fullLayout._basePlotModules; + function drawFramework () { + var basePlotModules = fullLayout._basePlotModules - for(var i = 0; i < basePlotModules.length; i++) { - if(basePlotModules[i].drawFramework) { - basePlotModules[i].drawFramework(gd); - } - } - - return Lib.syncOrAsync([ - subroutines.layoutStyles, - drawAxes, - Fx.init - ], gd); + for (var i = 0; i < basePlotModules.length; i++) { + if (basePlotModules[i].drawFramework) { + basePlotModules[i].drawFramework(gd) + } } + return Lib.syncOrAsync([ + subroutines.layoutStyles, + drawAxes, + Fx.init + ], gd) + } + // draw anything that can affect margins. // currently this is legend and colorbars - function marginPushers() { - var calcdata = gd.calcdata; - var i, cd, trace; - - Registry.getComponentMethod('legend', 'draw')(gd); - Registry.getComponentMethod('rangeselector', 'draw')(gd); - Registry.getComponentMethod('sliders', 'draw')(gd); - Registry.getComponentMethod('updatemenus', 'draw')(gd); - - for(i = 0; i < calcdata.length; i++) { - cd = calcdata[i]; - trace = cd[0].trace; - if(trace.visible !== true || !trace._module.colorbar) { - Plots.autoMargin(gd, 'cb' + trace.uid); - } - else trace._module.colorbar(gd, cd); - } + function marginPushers () { + var calcdata = gd.calcdata + var i, cd, trace + + Registry.getComponentMethod('legend', 'draw')(gd) + Registry.getComponentMethod('rangeselector', 'draw')(gd) + Registry.getComponentMethod('sliders', 'draw')(gd) + Registry.getComponentMethod('updatemenus', 'draw')(gd) - Plots.doAutoMargin(gd); - return Plots.previousPromises(gd); + for (i = 0; i < calcdata.length; i++) { + cd = calcdata[i] + trace = cd[0].trace + if (trace.visible !== true || !trace._module.colorbar) { + Plots.autoMargin(gd, 'cb' + trace.uid) + } else trace._module.colorbar(gd, cd) } + Plots.doAutoMargin(gd) + return Plots.previousPromises(gd) + } + // in case the margins changed, draw margin pushers again - function marginPushersAgain() { - var seq = JSON.stringify(fullLayout._size) === oldmargins ? + function marginPushersAgain () { + var seq = JSON.stringify(fullLayout._size) === oldmargins ? [] : - [marginPushers, subroutines.layoutStyles]; + [marginPushers, subroutines.layoutStyles] // re-initialize cartesian interaction, // which are sometimes cleared during marginPushers - seq = seq.concat(Fx.init); + seq = seq.concat(Fx.init) - return Lib.syncOrAsync(seq, gd); - } + return Lib.syncOrAsync(seq, gd) + } - function positionAndAutorange() { - if(!recalc) return; + function positionAndAutorange () { + if (!recalc) return - var subplots = Plots.getSubplotIds(fullLayout, 'cartesian'), - modules = fullLayout._modules; + var subplots = Plots.getSubplotIds(fullLayout, 'cartesian'), + modules = fullLayout._modules // position and range calculations for traces that // depend on each other ie bars (stacked or grouped) // and boxes (grouped) push each other out of the way - var subplotInfo, _module; + var subplotInfo, _module - for(var i = 0; i < subplots.length; i++) { - subplotInfo = fullLayout._plots[subplots[i]]; + for (var i = 0; i < subplots.length; i++) { + subplotInfo = fullLayout._plots[subplots[i]] - for(var j = 0; j < modules.length; j++) { - _module = modules[j]; - if(_module.setPositions) _module.setPositions(gd, subplotInfo); - } - } + for (var j = 0; j < modules.length; j++) { + _module = modules[j] + if (_module.setPositions) _module.setPositions(gd, subplotInfo) + } + } // calc and autorange for errorbars - ErrorBars.calc(gd); + ErrorBars.calc(gd) // TODO: autosize extra for text markers - return Lib.syncOrAsync([ - Registry.getComponentMethod('shapes', 'calcAutorange'), - Registry.getComponentMethod('annotations', 'calcAutorange'), - doAutoRange - ], gd); - } + return Lib.syncOrAsync([ + Registry.getComponentMethod('shapes', 'calcAutorange'), + Registry.getComponentMethod('annotations', 'calcAutorange'), + doAutoRange + ], gd) + } - function doAutoRange() { - if(gd._transitioning) return; + function doAutoRange () { + if (gd._transitioning) return - var axList = Plotly.Axes.list(gd, '', true); - for(var i = 0; i < axList.length; i++) { - Plotly.Axes.doAutoRange(axList[i]); - } + var axList = Plotly.Axes.list(gd, '', true) + for (var i = 0; i < axList.length; i++) { + Plotly.Axes.doAutoRange(axList[i]) } + } // draw ticks, titles, and calculate axis scaling (._b, ._m) - function drawAxes() { - return Plotly.Axes.doTicks(gd, 'redraw'); - } + function drawAxes () { + return Plotly.Axes.doTicks(gd, 'redraw') + } // Now plot the data - function drawData() { - var calcdata = gd.calcdata, - i; + function drawData () { + var calcdata = gd.calcdata, + i // in case of traces that were heatmaps or contour maps // previously, remove them and their colorbars explicitly - for(i = 0; i < calcdata.length; i++) { - var trace = calcdata[i][0].trace, - isVisible = (trace.visible === true), - uid = trace.uid; + for (i = 0; i < calcdata.length; i++) { + var trace = calcdata[i][0].trace, + isVisible = (trace.visible === true), + uid = trace.uid - if(!isVisible || !Registry.traceIs(trace, '2dMap')) { - fullLayout._paper.selectAll( + if (!isVisible || !Registry.traceIs(trace, '2dMap')) { + fullLayout._paper.selectAll( '.hm' + uid + ',.contour' + uid + ',#clip' + uid - ).remove(); - } + ).remove() + } - if(!isVisible || !trace._module.colorbar) { - fullLayout._infolayer.selectAll('.cb' + uid).remove(); - } - } + if (!isVisible || !trace._module.colorbar) { + fullLayout._infolayer.selectAll('.cb' + uid).remove() + } + } // loop over the base plot modules present on graph - var basePlotModules = fullLayout._basePlotModules; - for(i = 0; i < basePlotModules.length; i++) { - basePlotModules[i].plot(gd); - } + var basePlotModules = fullLayout._basePlotModules + for (i = 0; i < basePlotModules.length; i++) { + basePlotModules[i].plot(gd) + } // keep reference to shape layers in subplots - var layerSubplot = fullLayout._paper.selectAll('.layer-subplot'); - fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer'); - fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer'); + var layerSubplot = fullLayout._paper.selectAll('.layer-subplot') + fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer') + fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer') // styling separate from drawing - Plots.style(gd); + Plots.style(gd) // show annotations and shapes - Registry.getComponentMethod('shapes', 'draw')(gd); - Registry.getComponentMethod('annotations', 'draw')(gd); + Registry.getComponentMethod('shapes', 'draw')(gd) + Registry.getComponentMethod('annotations', 'draw')(gd) // source links - Plots.addLinks(gd); + Plots.addLinks(gd) // Mark the first render as complete - gd._replotting = false; + gd._replotting = false - return Plots.previousPromises(gd); - } + return Plots.previousPromises(gd) + } // An initial paint must be completed before these components can be // correctly sized and the whole plot re-margined. gd._replotting must // be set to false before these will work properly. - function finalDraw() { - Registry.getComponentMethod('shapes', 'draw')(gd); - Registry.getComponentMethod('images', 'draw')(gd); - Registry.getComponentMethod('annotations', 'draw')(gd); - Registry.getComponentMethod('legend', 'draw')(gd); - Registry.getComponentMethod('rangeslider', 'draw')(gd); - Registry.getComponentMethod('rangeselector', 'draw')(gd); - Registry.getComponentMethod('sliders', 'draw')(gd); - Registry.getComponentMethod('updatemenus', 'draw')(gd); - } - - Lib.syncOrAsync([ - Plots.previousPromises, - addFrames, - drawFramework, - marginPushers, - marginPushersAgain, - positionAndAutorange, - subroutines.layoutStyles, - drawAxes, - drawData, - finalDraw - ], gd); + function finalDraw () { + Registry.getComponentMethod('shapes', 'draw')(gd) + Registry.getComponentMethod('images', 'draw')(gd) + Registry.getComponentMethod('annotations', 'draw')(gd) + Registry.getComponentMethod('legend', 'draw')(gd) + Registry.getComponentMethod('rangeslider', 'draw')(gd) + Registry.getComponentMethod('rangeselector', 'draw')(gd) + Registry.getComponentMethod('sliders', 'draw')(gd) + Registry.getComponentMethod('updatemenus', 'draw')(gd) + } + + Lib.syncOrAsync([ + Plots.previousPromises, + addFrames, + drawFramework, + marginPushers, + marginPushersAgain, + positionAndAutorange, + subroutines.layoutStyles, + drawAxes, + drawData, + finalDraw + ], gd) // even if everything we did was synchronous, return a promise // so that the caller doesn't care which route we took - return Promise.all(gd._promises).then(function() { - gd.emit('plotly_afterplot'); - return gd; - }); -}; - + return Promise.all(gd._promises).then(function () { + gd.emit('plotly_afterplot') + return gd + }) +} -function opaqueSetBackground(gd, bgColor) { - gd._fullLayout._paperdiv.style('background', 'white'); - Plotly.defaultConfig.setBackground(gd, bgColor); +function opaqueSetBackground (gd, bgColor) { + gd._fullLayout._paperdiv.style('background', 'white') + Plotly.defaultConfig.setBackground(gd, bgColor) } -function setPlotContext(gd, config) { - if(!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig); - var context = gd._context; - - if(config) { - Object.keys(config).forEach(function(key) { - if(key in context) { - if(key === 'setBackground' && config[key] === 'opaque') { - context[key] = opaqueSetBackground; - } - else context[key] = config[key]; - } - }); +function setPlotContext (gd, config) { + if (!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig) + var context = gd._context + + if (config) { + Object.keys(config).forEach(function (key) { + if (key in context) { + if (key === 'setBackground' && config[key] === 'opaque') { + context[key] = opaqueSetBackground + } else context[key] = config[key] + } + }) // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility - if(config.plot3dPixelRatio && !context.plotGlPixelRatio) { - context.plotGlPixelRatio = context.plot3dPixelRatio; - } + if (config.plot3dPixelRatio && !context.plotGlPixelRatio) { + context.plotGlPixelRatio = context.plot3dPixelRatio } + } // staticPlot forces a bunch of others: - if(context.staticPlot) { - context.editable = false; - context.autosizable = false; - context.scrollZoom = false; - context.doubleClick = false; - context.showTips = false; - context.showLink = false; - context.displayModeBar = false; - } + if (context.staticPlot) { + context.editable = false + context.autosizable = false + context.scrollZoom = false + context.doubleClick = false + context.showTips = false + context.showLink = false + context.displayModeBar = false + } } -function plotPolar(gd, data, layout) { +function plotPolar (gd, data, layout) { // build or reuse the container skeleton - var plotContainer = d3.select(gd).selectAll('.plot-container') - .data([0]); - plotContainer.enter() + var plotContainer = d3.select(gd).selectAll('.plot-container') + .data([0]) + plotContainer.enter() .insert('div', ':first-child') - .classed('plot-container plotly', true); - var paperDiv = plotContainer.selectAll('.svg-container') - .data([0]); - paperDiv.enter().append('div') + .classed('plot-container plotly', true) + var paperDiv = plotContainer.selectAll('.svg-container') + .data([0]) + paperDiv.enter().append('div') .classed('svg-container', true) - .style('position', 'relative'); + .style('position', 'relative') // empty it everytime for now - paperDiv.html(''); + paperDiv.html('') // fulfill gd requirements - if(data) gd.data = data; - if(layout) gd.layout = layout; - Polar.manager.fillLayout(gd); + if (data) gd.data = data + if (layout) gd.layout = layout + Polar.manager.fillLayout(gd) // resize canvas - paperDiv.style({ - width: gd._fullLayout.width + 'px', - height: gd._fullLayout.height + 'px' - }); + paperDiv.style({ + width: gd._fullLayout.width + 'px', + height: gd._fullLayout.height + 'px' + }) // instantiate framework - gd.framework = Polar.manager.framework(gd); + gd.framework = Polar.manager.framework(gd) // plot - gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node()); + gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node()) // set undo point - gd.framework.setUndoPoint(); + gd.framework.setUndoPoint() // get the resulting svg for extending it - var polarPlotSVG = gd.framework.svg(); + var polarPlotSVG = gd.framework.svg() // editable title - var opacity = 1; - var txt = gd._fullLayout.title; - if(txt === '' || !txt) opacity = 0; - var placeholderText = 'Click to enter title'; + var opacity = 1 + var txt = gd._fullLayout.title + if (txt === '' || !txt) opacity = 0 + var placeholderText = 'Click to enter title' - var titleLayout = function() { - this.call(svgTextUtils.convertToTspans); + var titleLayout = function () { + this.call(svgTextUtils.convertToTspans) // TODO: html/mathjax // TODO: center title - }; + } - var title = polarPlotSVG.select('.title-group text') - .call(titleLayout); + var title = polarPlotSVG.select('.title-group text') + .call(titleLayout) - if(gd._context.editable) { - title.attr({'data-unformatted': txt}); - if(!txt || txt === placeholderText) { - opacity = 0.2; - title.attr({'data-unformatted': placeholderText}) + if (gd._context.editable) { + title.attr({'data-unformatted': txt}) + if (!txt || txt === placeholderText) { + opacity = 0.2 + title.attr({'data-unformatted': placeholderText}) .text(placeholderText) .style({opacity: opacity}) - .on('mouseover.opacity', function() { - d3.select(this).transition().duration(100) - .style('opacity', 1); + .on('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); - }); - } + .on('mouseout.opacity', function () { + d3.select(this).transition().duration(1000) + .style('opacity', 0) + }) + } - var setContenteditable = function() { - this.call(svgTextUtils.makeEditable) - .on('edit', function(text) { - gd.framework({layout: {title: text}}); - this.attr({'data-unformatted': text}) + var setContenteditable = function () { + this.call(svgTextUtils.makeEditable) + .on('edit', function (text) { + gd.framework({layout: {title: text}}) + this.attr({'data-unformatted': text}) .text(text) - .call(titleLayout); - this.call(setContenteditable); + .call(titleLayout) + this.call(setContenteditable) + }) + .on('cancel', function () { + var txt = this.attr('data-unformatted') + this.text(txt).call(titleLayout) }) - .on('cancel', function() { - var txt = this.attr('data-unformatted'); - this.text(txt).call(titleLayout); - }); - }; - title.call(setContenteditable); } + title.call(setContenteditable) + } - gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor); - Plots.addLinks(gd); + gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor) + Plots.addLinks(gd) - return Promise.resolve(); + return Promise.resolve() } // convenience function to force a full redraw, mostly for use by plotly.js -Plotly.redraw = function(gd) { - gd = helpers.getGraphDiv(gd); +Plotly.redraw = function (gd) { + gd = helpers.getGraphDiv(gd) - if(!Lib.isPlotDiv(gd)) { - throw new Error('This element is not a Plotly plot: ' + gd); - } + if (!Lib.isPlotDiv(gd)) { + throw new Error('This element is not a Plotly plot: ' + gd) + } - helpers.cleanData(gd.data, gd.data); - helpers.cleanLayout(gd.layout); + helpers.cleanData(gd.data, gd.data) + helpers.cleanLayout(gd.layout) - gd.calcdata = undefined; - return Plotly.plot(gd).then(function() { - gd.emit('plotly_redraw'); - return gd; - }); -}; + gd.calcdata = undefined + return Plotly.plot(gd).then(function () { + gd.emit('plotly_redraw') + return gd + }) +} /** * Convenience function to make idempotent plot option obvious to users. @@ -516,15 +510,15 @@ Plotly.redraw = function(gd) { * @param {Object} layout * @param {Object} config */ -Plotly.newPlot = function(gd, data, layout, config) { - gd = helpers.getGraphDiv(gd); +Plotly.newPlot = function (gd, data, layout, config) { + gd = helpers.getGraphDiv(gd) // remove gl contexts - Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {}); + Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {}) - Plots.purge(gd); - return Plotly.plot(gd, data, layout, config); -}; + Plots.purge(gd) + return Plotly.plot(gd, data, layout, config) +} /** * Wrap negative indicies to their positive counterparts. @@ -532,21 +526,21 @@ Plotly.newPlot = function(gd, data, layout, config) { * @param {Number[]} indices An array of indices * @param {Number} maxIndex The maximum index allowable (arr.length - 1) */ -function positivifyIndices(indices, maxIndex) { - var parentLength = maxIndex + 1, - positiveIndices = [], - i, - index; - - for(i = 0; i < indices.length; i++) { - index = indices[i]; - if(index < 0) { - positiveIndices.push(parentLength + index); - } else { - positiveIndices.push(index); - } +function positivifyIndices (indices, maxIndex) { + var parentLength = maxIndex + 1, + positiveIndices = [], + i, + index + + for (i = 0; i < indices.length; i++) { + index = indices[i] + if (index < 0) { + positiveIndices.push(parentLength + index) + } else { + positiveIndices.push(index) } - return positiveIndices; + } + return positiveIndices } /** @@ -558,30 +552,30 @@ function positivifyIndices(indices, maxIndex) { * @param indices * @param arrayName */ -function assertIndexArray(gd, indices, arrayName) { - var i, - index; +function assertIndexArray (gd, indices, arrayName) { + var i, + index - for(i = 0; i < indices.length; i++) { - index = indices[i]; + for (i = 0; i < indices.length; i++) { + index = indices[i] // validate that indices are indeed integers - if(index !== parseInt(index, 10)) { - throw new Error('all values in ' + arrayName + ' must be integers'); - } + if (index !== parseInt(index, 10)) { + throw new Error('all values in ' + arrayName + ' must be integers') + } // check that all indices are in bounds for given gd.data array length - if(index >= gd.data.length || index < -gd.data.length) { - throw new Error(arrayName + ' must be valid indices for gd.data.'); - } + if (index >= gd.data.length || index < -gd.data.length) { + throw new Error(arrayName + ' must be valid indices for gd.data.') + } // check that indices aren't repeated - if(indices.indexOf(index, i + 1) > -1 || + if (indices.indexOf(index, i + 1) > -1 || index >= 0 && indices.indexOf(-gd.data.length + index) > -1 || index < 0 && indices.indexOf(gd.data.length + index) > -1) { - throw new Error('each index in ' + arrayName + ' must be unique.'); - } + throw new Error('each index in ' + arrayName + ' must be unique.') } + } } /** @@ -591,34 +585,32 @@ function assertIndexArray(gd, indices, arrayName) { * @param currentIndices * @param newIndices */ -function checkMoveTracesArgs(gd, currentIndices, newIndices) { - +function checkMoveTracesArgs (gd, currentIndices, newIndices) { // check that gd has attribute 'data' and 'data' is array - if(!Array.isArray(gd.data)) { - throw new Error('gd.data must be an array.'); - } + if (!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array.') + } // validate currentIndices array - if(typeof currentIndices === 'undefined') { - throw new Error('currentIndices is a required argument.'); - } else if(!Array.isArray(currentIndices)) { - currentIndices = [currentIndices]; - } - assertIndexArray(gd, currentIndices, 'currentIndices'); + if (typeof currentIndices === 'undefined') { + throw new Error('currentIndices is a required argument.') + } else if (!Array.isArray(currentIndices)) { + currentIndices = [currentIndices] + } + assertIndexArray(gd, currentIndices, 'currentIndices') // validate newIndices array if it exists - if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { - newIndices = [newIndices]; - } - if(typeof newIndices !== 'undefined') { - assertIndexArray(gd, newIndices, 'newIndices'); - } + if (typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { + newIndices = [newIndices] + } + if (typeof newIndices !== 'undefined') { + assertIndexArray(gd, newIndices, 'newIndices') + } // check currentIndices and newIndices are the same length if newIdices exists - if(typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) { - throw new Error('current and new indices must be of equal length.'); - } - + if (typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) { + throw new Error('current and new indices must be of equal length.') + } } /** * A private function to reduce the type checking clutter in addTraces. @@ -627,41 +619,41 @@ function checkMoveTracesArgs(gd, currentIndices, newIndices) { * @param traces * @param newIndices */ -function checkAddTracesArgs(gd, traces, newIndices) { - var i, value; +function checkAddTracesArgs (gd, traces, newIndices) { + var i, value // check that gd has attribute 'data' and 'data' is array - if(!Array.isArray(gd.data)) { - throw new Error('gd.data must be an array.'); - } + if (!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array.') + } // make sure traces exists - if(typeof traces === 'undefined') { - throw new Error('traces must be defined.'); - } + if (typeof traces === 'undefined') { + throw new Error('traces must be defined.') + } // make sure traces is an array - if(!Array.isArray(traces)) { - traces = [traces]; - } + if (!Array.isArray(traces)) { + traces = [traces] + } // make sure each value in traces is an object - for(i = 0; i < traces.length; i++) { - value = traces[i]; - if(typeof value !== 'object' || (Array.isArray(value) || value === null)) { - throw new Error('all values in traces array must be non-array objects'); - } + for (i = 0; i < traces.length; i++) { + value = traces[i] + if (typeof value !== 'object' || (Array.isArray(value) || value === null)) { + throw new Error('all values in traces array must be non-array objects') } + } // make sure we have an index for each trace - if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { - newIndices = [newIndices]; - } - if(typeof newIndices !== 'undefined' && newIndices.length !== traces.length) { - throw new Error( + if (typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) { + newIndices = [newIndices] + } + if (typeof newIndices !== 'undefined' && newIndices.length !== traces.length) { + throw new Error( 'if indices is specified, traces.length must equal indices.length' - ); - } + ) + } } /** @@ -674,43 +666,41 @@ function checkAddTracesArgs(gd, traces, newIndices) { * @param indices * @param maxPoints */ -function assertExtendTracesArgs(gd, update, indices, maxPoints) { +function assertExtendTracesArgs (gd, update, indices, maxPoints) { + var maxPointsIsObject = Lib.isPlainObject(maxPoints) - var maxPointsIsObject = Lib.isPlainObject(maxPoints); + if (!Array.isArray(gd.data)) { + throw new Error('gd.data must be an array') + } + if (!Lib.isPlainObject(update)) { + throw new Error('update must be a key:value object') + } - if(!Array.isArray(gd.data)) { - throw new Error('gd.data must be an array'); - } - if(!Lib.isPlainObject(update)) { - throw new Error('update must be a key:value object'); - } - - if(typeof indices === 'undefined') { - throw new Error('indices must be an integer or array of integers'); - } + if (typeof indices === 'undefined') { + throw new Error('indices must be an integer or array of integers') + } - assertIndexArray(gd, indices, 'indices'); - - for(var key in update) { + assertIndexArray(gd, indices, 'indices') + for (var key in update) { /* * Verify that the attribute to be updated contains as many trace updates * as indices. Failure must result in throw and no-op */ - if(!Array.isArray(update[key]) || update[key].length !== indices.length) { - throw new Error('attribute ' + key + ' must be an array of length equal to indices array length'); - } + if (!Array.isArray(update[key]) || update[key].length !== indices.length) { + throw new Error('attribute ' + key + ' must be an array of length equal to indices array length') + } /* * if maxPoints is an object it must match keys and array lengths of 'update' 1:1 */ - if(maxPointsIsObject && + if (maxPointsIsObject && (!(key in maxPoints) || !Array.isArray(maxPoints[key]) || maxPoints[key].length !== update[key].length)) { - throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' + - 'corrispondence with the keys and number of traces in the update object'); - } + throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' + + 'corrispondence with the keys and number of traces in the update object') } + } } /** @@ -722,69 +712,66 @@ function assertExtendTracesArgs(gd, update, indices, maxPoints) { * @param {Number||Object} maxPoints * @return {Object[]} */ -function getExtendProperties(gd, update, indices, maxPoints) { - - var maxPointsIsObject = Lib.isPlainObject(maxPoints), - updateProps = []; - var trace, target, prop, insert, maxp; +function getExtendProperties (gd, update, indices, maxPoints) { + var maxPointsIsObject = Lib.isPlainObject(maxPoints), + updateProps = [] + var trace, target, prop, insert, maxp // allow scalar index to represent a single trace position - if(!Array.isArray(indices)) indices = [indices]; + if (!Array.isArray(indices)) indices = [indices] // negative indices are wrapped around to their positive value. Equivalent to python indexing. - indices = positivifyIndices(indices, gd.data.length - 1); + indices = positivifyIndices(indices, gd.data.length - 1) // loop through all update keys and traces and harvest validated data. - for(var key in update) { - - for(var j = 0; j < indices.length; j++) { - + for (var key in update) { + for (var j = 0; j < indices.length; j++) { /* * Choose the trace indexed by the indices map argument and get the prop setter-getter * instance that references the key and value for this particular trace. */ - trace = gd.data[indices[j]]; - prop = Lib.nestedProperty(trace, key); + trace = gd.data[indices[j]] + prop = Lib.nestedProperty(trace, key) /* * Target is the existing gd.data.trace.dataArray value like "x" or "marker.size" * Target must exist as an Array to allow the extend operation to be performed. */ - target = prop.get(); - insert = update[key][j]; + target = prop.get() + insert = update[key][j] - if(!Array.isArray(insert)) { - throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array'); - } - if(!Array.isArray(target)) { - throw new Error('cannot extend missing or non-array attribute: ' + key); - } + if (!Array.isArray(insert)) { + throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array') + } + if (!Array.isArray(target)) { + throw new Error('cannot extend missing or non-array attribute: ' + key) + } /* * maxPoints may be an object map or a scalar. If object select the key:value, else * Use the scalar maxPoints for all key and trace combinations. */ - maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints; + maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints // could have chosen null here, -1 just tells us to not take a window - if(!isNumeric(maxp)) maxp = -1; + if (!isNumeric(maxp)) maxp = -1 /* * Wrap the nestedProperty in an object containing required data * for lengthening and windowing this particular trace - key combination. * Flooring maxp mirrors the behaviour of floats in the Array.slice JSnative function. */ - updateProps.push({ - prop: prop, - target: target, - insert: insert, - maxp: Math.floor(maxp) - }); - } + updateProps.push({ + prop: prop, + target: target, + insert: insert, + maxp: Math.floor(maxp) + }) } + } // all target and insertion data now validated - return updateProps; + return updateProps } /** @@ -798,58 +785,56 @@ function getExtendProperties(gd, update, indices, maxPoints) { * @param {Function} spliceArray * @return {Object} */ -function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray) { +function spliceTraces (gd, update, indices, maxPoints, lengthenArray, spliceArray) { + assertExtendTracesArgs(gd, update, indices, maxPoints) - assertExtendTracesArgs(gd, update, indices, maxPoints); - - var updateProps = getExtendProperties(gd, update, indices, maxPoints), - remainder = [], - undoUpdate = {}, - undoPoints = {}; - var target, prop, maxp; - - for(var i = 0; i < updateProps.length; i++) { + var updateProps = getExtendProperties(gd, update, indices, maxPoints), + remainder = [], + undoUpdate = {}, + undoPoints = {} + var target, prop, maxp + for (var i = 0; i < updateProps.length; i++) { /* * prop is the object returned by Lib.nestedProperties */ - prop = updateProps[i].prop; - maxp = updateProps[i].maxp; + prop = updateProps[i].prop + maxp = updateProps[i].maxp - target = lengthenArray(updateProps[i].target, updateProps[i].insert); + target = lengthenArray(updateProps[i].target, updateProps[i].insert) /* * If maxp is set within post-extension trace.length, splice to maxp length. * Otherwise skip function call as splice op will have no effect anyway. */ - if(maxp >= 0 && maxp < target.length) remainder = spliceArray(target, maxp); + if (maxp >= 0 && maxp < target.length) remainder = spliceArray(target, maxp) /* * to reverse this operation we need the size of the original trace as the reverse * operation will need to window out any lengthening operation performed in this pass. */ - maxp = updateProps[i].target.length; + maxp = updateProps[i].target.length /* * Magic happens here! update gd.data.trace[key] with new array data. */ - prop.set(target); + prop.set(target) - if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = []; - if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = []; + if (!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = [] + if (!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = [] /* * build the inverse update object for the undo operation */ - undoUpdate[prop.astr].push(remainder); + undoUpdate[prop.astr].push(remainder) /* * build the matching maxPoints undo object containing original trace lengths. */ - undoPoints[prop.astr].push(maxp); - } + undoPoints[prop.astr].push(maxp) + } - return {update: undoUpdate, maxPoints: undoPoints}; + return {update: undoUpdate, maxPoints: undoPoints} } /** @@ -869,59 +854,59 @@ function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray * @param {Number|Object} [maxPoints] Number of points for trace window after lengthening. * */ -Plotly.extendTraces = function extendTraces(gd, update, indices, maxPoints) { - gd = helpers.getGraphDiv(gd); +Plotly.extendTraces = function extendTraces (gd, update, indices, maxPoints) { + gd = helpers.getGraphDiv(gd) - var undo = spliceTraces(gd, update, indices, maxPoints, + var undo = spliceTraces(gd, update, indices, maxPoints, /* * The Lengthen operation extends trace from end with insert */ - function(target, insert) { - return target.concat(insert); + function (target, insert) { + return target.concat(insert) }, /* * Window the trace keeping maxPoints, counting back from the end */ - function(target, maxPoints) { - return target.splice(0, target.length - maxPoints); - }); + function (target, maxPoints) { + return target.splice(0, target.length - maxPoints) + }) - var promise = Plotly.redraw(gd); + var promise = Plotly.redraw(gd) - var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); + var undoArgs = [gd, undo.update, indices, undo.maxPoints] + Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments) - return promise; -}; + return promise +} -Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) { - gd = helpers.getGraphDiv(gd); +Plotly.prependTraces = function prependTraces (gd, update, indices, maxPoints) { + gd = helpers.getGraphDiv(gd) - var undo = spliceTraces(gd, update, indices, maxPoints, + var undo = spliceTraces(gd, update, indices, maxPoints, /* * The Lengthen operation extends trace by appending insert to start */ - function(target, insert) { - return insert.concat(target); + function (target, insert) { + return insert.concat(target) }, /* * Window the trace keeping maxPoints, counting forward from the start */ - function(target, maxPoints) { - return target.splice(maxPoints, target.length); - }); + function (target, maxPoints) { + return target.splice(maxPoints, target.length) + }) - var promise = Plotly.redraw(gd); + var promise = Plotly.redraw(gd) - var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); + var undoArgs = [gd, undo.update, indices, undo.maxPoints] + Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments) - return promise; -}; + return promise +} /** * Add data traces to an existing graph div. @@ -932,75 +917,72 @@ Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) { * @param {Number[]|Number} [newIndices=[gd.data.length]] Locations to add traces * */ -Plotly.addTraces = function addTraces(gd, traces, newIndices) { - gd = helpers.getGraphDiv(gd); +Plotly.addTraces = function addTraces (gd, traces, newIndices) { + gd = helpers.getGraphDiv(gd) - var currentIndices = [], - undoFunc = Plotly.deleteTraces, - redoFunc = addTraces, - undoArgs = [gd, currentIndices], - redoArgs = [gd, traces], // no newIndices here - i, - promise; + var currentIndices = [], + undoFunc = Plotly.deleteTraces, + redoFunc = addTraces, + undoArgs = [gd, currentIndices], + redoArgs = [gd, traces], // no newIndices here + i, + promise // all validation is done elsewhere to remove clutter here - checkAddTracesArgs(gd, traces, newIndices); + checkAddTracesArgs(gd, traces, newIndices) // make sure traces is an array - if(!Array.isArray(traces)) { - traces = [traces]; - } + if (!Array.isArray(traces)) { + traces = [traces] + } // make sure traces do not repeat existing ones - traces = traces.map(function(trace) { - return Lib.extendFlat({}, trace); - }); + traces = traces.map(function (trace) { + return Lib.extendFlat({}, trace) + }) - helpers.cleanData(traces, gd.data); + helpers.cleanData(traces, gd.data) // add the traces to gd.data (no redrawing yet!) - for(i = 0; i < traces.length; i++) { - gd.data.push(traces[i]); - } + for (i = 0; i < traces.length; i++) { + gd.data.push(traces[i]) + } // to continue, we need to call moveTraces which requires currentIndices - for(i = 0; i < traces.length; i++) { - currentIndices.push(-traces.length + i); - } + for (i = 0; i < traces.length; i++) { + currentIndices.push(-traces.length + i) + } // if the user didn't define newIndices, they just want the traces appended // i.e., we can simply redraw and be done - if(typeof newIndices === 'undefined') { - promise = Plotly.redraw(gd); - Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); - return promise; - } + if (typeof newIndices === 'undefined') { + promise = Plotly.redraw(gd) + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) + return promise + } // make sure indices is property defined - if(!Array.isArray(newIndices)) { - newIndices = [newIndices]; - } - - try { + if (!Array.isArray(newIndices)) { + newIndices = [newIndices] + } + try { // this is redundant, but necessary to not catch later possible errors! - checkMoveTracesArgs(gd, currentIndices, newIndices); - } - catch(error) { - + checkMoveTracesArgs(gd, currentIndices, newIndices) + } catch (error) { // something went wrong, reset gd to be safe and rethrow error - gd.data.splice(gd.data.length - traces.length, traces.length); - throw error; - } + gd.data.splice(gd.data.length - traces.length, traces.length) + throw error + } // if we're here, the user has defined specific places to place the new traces // this requires some extra work that moveTraces will do - Queue.startSequence(gd); - Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); - promise = Plotly.moveTraces(gd, currentIndices, newIndices); - Queue.stopSequence(gd); - return promise; -}; + Queue.startSequence(gd) + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) + promise = Plotly.moveTraces(gd, currentIndices, newIndices) + Queue.stopSequence(gd) + return promise +} /** * Delete traces at `indices` from gd.data array. @@ -1009,40 +991,40 @@ Plotly.addTraces = function addTraces(gd, traces, newIndices) { * @param {Object[]} gd.data The array of traces we're removing from * @param {Number|Number[]} indices The indices */ -Plotly.deleteTraces = function deleteTraces(gd, indices) { - gd = helpers.getGraphDiv(gd); +Plotly.deleteTraces = function deleteTraces (gd, indices) { + gd = helpers.getGraphDiv(gd) - var traces = [], - undoFunc = Plotly.addTraces, - redoFunc = deleteTraces, - undoArgs = [gd, traces, indices], - redoArgs = [gd, indices], - i, - deletedTrace; + var traces = [], + undoFunc = Plotly.addTraces, + redoFunc = deleteTraces, + undoArgs = [gd, traces, indices], + redoArgs = [gd, indices], + i, + deletedTrace // make sure indices are defined - if(typeof indices === 'undefined') { - throw new Error('indices must be an integer or array of integers.'); - } else if(!Array.isArray(indices)) { - indices = [indices]; - } - assertIndexArray(gd, indices, 'indices'); + if (typeof indices === 'undefined') { + throw new Error('indices must be an integer or array of integers.') + } else if (!Array.isArray(indices)) { + indices = [indices] + } + assertIndexArray(gd, indices, 'indices') // convert negative indices to positive indices - indices = positivifyIndices(indices, gd.data.length - 1); + indices = positivifyIndices(indices, gd.data.length - 1) // we want descending here so that splicing later doesn't affect indexing - indices.sort(Lib.sorterDes); - for(i = 0; i < indices.length; i += 1) { - deletedTrace = gd.data.splice(indices[i], 1)[0]; - traces.push(deletedTrace); - } + indices.sort(Lib.sorterDes) + for (i = 0; i < indices.length; i += 1) { + deletedTrace = gd.data.splice(indices[i], 1)[0] + traces.push(deletedTrace) + } - var promise = Plotly.redraw(gd); - Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + var promise = Plotly.redraw(gd) + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) - return promise; -}; + return promise +} /** * Move traces at currentIndices array to locations in newIndices array. @@ -1075,72 +1057,71 @@ Plotly.deleteTraces = function deleteTraces(gd, indices) { * // reorder all traces (assume there are 5--a, b, c, d, e) * Plotly.moveTraces(gd, [b, d, e, a, c]) // same as 'move to end' */ -Plotly.moveTraces = function moveTraces(gd, currentIndices, newIndices) { - gd = helpers.getGraphDiv(gd); +Plotly.moveTraces = function moveTraces (gd, currentIndices, newIndices) { + gd = helpers.getGraphDiv(gd) - var newData = [], - movingTraceMap = [], - undoFunc = moveTraces, - redoFunc = moveTraces, - undoArgs = [gd, newIndices, currentIndices], - redoArgs = [gd, currentIndices, newIndices], - i; + var newData = [], + movingTraceMap = [], + undoFunc = moveTraces, + redoFunc = moveTraces, + undoArgs = [gd, newIndices, currentIndices], + redoArgs = [gd, currentIndices, newIndices], + i // to reduce complexity here, check args elsewhere // this throws errors where appropriate - checkMoveTracesArgs(gd, currentIndices, newIndices); + checkMoveTracesArgs(gd, currentIndices, newIndices) // make sure currentIndices is an array - currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices]; + currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices] // if undefined, define newIndices to point to the end of gd.data array - if(typeof newIndices === 'undefined') { - newIndices = []; - for(i = 0; i < currentIndices.length; i++) { - newIndices.push(-currentIndices.length + i); - } + if (typeof newIndices === 'undefined') { + newIndices = [] + for (i = 0; i < currentIndices.length; i++) { + newIndices.push(-currentIndices.length + i) } + } // make sure newIndices is an array if it's user-defined - newIndices = Array.isArray(newIndices) ? newIndices : [newIndices]; + newIndices = Array.isArray(newIndices) ? newIndices : [newIndices] // convert negative indices to positive indices (they're the same length) - currentIndices = positivifyIndices(currentIndices, gd.data.length - 1); - newIndices = positivifyIndices(newIndices, gd.data.length - 1); + currentIndices = positivifyIndices(currentIndices, gd.data.length - 1) + newIndices = positivifyIndices(newIndices, gd.data.length - 1) // at this point, we've coerced the index arrays into predictable forms // get the traces that aren't being moved around - for(i = 0; i < gd.data.length; i++) { - + for (i = 0; i < gd.data.length; i++) { // if index isn't in currentIndices, include it in ignored! - if(currentIndices.indexOf(i) === -1) { - newData.push(gd.data[i]); - } + if (currentIndices.indexOf(i) === -1) { + newData.push(gd.data[i]) } + } // get a mapping of indices to moving traces - for(i = 0; i < currentIndices.length; i++) { - movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]}); - } + for (i = 0; i < currentIndices.length; i++) { + movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]}) + } // reorder this mapping by newIndex, ascending - movingTraceMap.sort(function(a, b) { - return a.newIndex - b.newIndex; - }); + movingTraceMap.sort(function (a, b) { + return a.newIndex - b.newIndex + }) // now, add the moving traces back in, in order! - for(i = 0; i < movingTraceMap.length; i += 1) { - newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace); - } + for (i = 0; i < movingTraceMap.length; i += 1) { + newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace) + } - gd.data = newData; + gd.data = newData - var promise = Plotly.redraw(gd); - Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + var promise = Plotly.redraw(gd) + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) - return promise; -}; + return promise +} /** * restyle: update trace attributes of an existing plot @@ -1172,498 +1153,477 @@ Plotly.moveTraces = function moveTraces(gd, currentIndices, newIndices) { * If the array is too short, it will wrap around (useful for * style files that want to specify cyclical default values). */ -Plotly.restyle = function restyle(gd, astr, val, traces) { - gd = helpers.getGraphDiv(gd); - helpers.clearPromiseQueue(gd); +Plotly.restyle = function restyle (gd, astr, val, traces) { + gd = helpers.getGraphDiv(gd) + helpers.clearPromiseQueue(gd) - var aobj = {}; - if(typeof astr === 'string') aobj[astr] = val; - else if(Lib.isPlainObject(astr)) { + var aobj = {} + if (typeof astr === 'string') aobj[astr] = val + else if (Lib.isPlainObject(astr)) { // the 3-arg form - aobj = astr; - if(traces === undefined) traces = val; - } - else { - Lib.warn('Restyle fail.', astr, val, traces); - return Promise.reject(); - } + aobj = astr + if (traces === undefined) traces = val + } else { + Lib.warn('Restyle fail.', astr, val, traces) + return Promise.reject() + } - if(Object.keys(aobj).length) gd.changed = true; + if (Object.keys(aobj).length) gd.changed = true - var specs = _restyle(gd, aobj, traces), - flags = specs.flags; + var specs = _restyle(gd, aobj, traces), + flags = specs.flags // clear calcdata if required - if(flags.clearCalc) gd.calcdata = undefined; + if (flags.clearCalc) gd.calcdata = undefined // fill in redraw sequence - var seq = []; + var seq = [] - if(flags.fullReplot) { - seq.push(Plotly.plot); - } - else { - seq.push(Plots.previousPromises); + if (flags.fullReplot) { + seq.push(Plotly.plot) + } else { + seq.push(Plots.previousPromises) - Plots.supplyDefaults(gd); + Plots.supplyDefaults(gd) - if(flags.dostyle) seq.push(subroutines.doTraceStyle); - if(flags.docolorbars) seq.push(subroutines.doColorBars); - } + if (flags.dostyle) seq.push(subroutines.doTraceStyle) + if (flags.docolorbars) seq.push(subroutines.doColorBars) + } - Queue.add(gd, + Queue.add(gd, restyle, [gd, specs.undoit, specs.traces], restyle, [gd, specs.redoit, specs.traces] - ); + ) - var plotDone = Lib.syncOrAsync(seq, gd); - if(!plotDone || !plotDone.then) plotDone = Promise.resolve(); + var plotDone = Lib.syncOrAsync(seq, gd) + if (!plotDone || !plotDone.then) plotDone = Promise.resolve() - return plotDone.then(function() { - gd.emit('plotly_restyle', specs.eventData); - return gd; - }); -}; + return plotDone.then(function () { + gd.emit('plotly_restyle', specs.eventData) + return gd + }) +} -function _restyle(gd, aobj, _traces) { - var fullLayout = gd._fullLayout, - fullData = gd._fullData, - data = gd.data, - i; +function _restyle (gd, aobj, _traces) { + var fullLayout = gd._fullLayout, + fullData = gd._fullData, + data = gd.data, + i - var traces = helpers.coerceTraceIndices(gd, _traces); + var traces = helpers.coerceTraceIndices(gd, _traces) // initialize flags - var flags = { - docalc: false, - docalcAutorange: false, - doplot: false, - dostyle: false, - docolorbars: false, - autorangeOn: false, - clearCalc: false, - fullReplot: false - }; + var flags = { + docalc: false, + docalcAutorange: false, + doplot: false, + dostyle: false, + docolorbars: false, + autorangeOn: false, + clearCalc: false, + fullReplot: false + } // copies of the change (and previous values of anything affected) // for the undo / redo queue - var redoit = {}, - undoit = {}, - axlist, - flagAxForDelete = {}; + var redoit = {}, + undoit = {}, + axlist, + flagAxForDelete = {} // recalcAttrs attributes need a full regeneration of calcdata // as well as a replot, because the right objects may not exist, // or autorange may need recalculating // in principle we generally shouldn't need to redo ALL traces... that's // harder though. - var recalcAttrs = [ - 'mode', 'visible', 'type', 'orientation', 'fill', - 'histfunc', 'histnorm', 'text', - 'x', 'y', 'z', - 'a', 'b', 'c', - 'open', 'high', 'low', 'close', - 'base', 'width', 'offset', - 'xtype', 'x0', 'dx', 'ytype', 'y0', 'dy', 'xaxis', 'yaxis', - 'line.width', - 'connectgaps', 'transpose', 'zsmooth', - 'showscale', 'marker.showscale', - 'zauto', 'marker.cauto', - 'autocolorscale', 'marker.autocolorscale', - 'colorscale', 'marker.colorscale', - 'reversescale', 'marker.reversescale', - 'autobinx', 'nbinsx', 'xbins', 'xbins.start', 'xbins.end', 'xbins.size', - 'autobiny', 'nbinsy', 'ybins', 'ybins.start', 'ybins.end', 'ybins.size', - 'autocontour', 'ncontours', 'contours', 'contours.coloring', - 'error_y', 'error_y.visible', 'error_y.value', 'error_y.type', - 'error_y.traceref', 'error_y.array', 'error_y.symmetric', - 'error_y.arrayminus', 'error_y.valueminus', 'error_y.tracerefminus', - 'error_x', 'error_x.visible', 'error_x.value', 'error_x.type', - 'error_x.traceref', 'error_x.array', 'error_x.symmetric', - 'error_x.arrayminus', 'error_x.valueminus', 'error_x.tracerefminus', - 'swapxy', 'swapxyaxes', 'orientationaxes', - 'marker.colors', 'values', 'labels', 'label0', 'dlabel', 'sort', - 'textinfo', 'textposition', 'textfont.size', 'textfont.family', 'textfont.color', - 'insidetextfont.size', 'insidetextfont.family', 'insidetextfont.color', - 'outsidetextfont.size', 'outsidetextfont.family', 'outsidetextfont.color', - 'hole', 'scalegroup', 'domain', 'domain.x', 'domain.y', - 'domain.x[0]', 'domain.x[1]', 'domain.y[0]', 'domain.y[1]', - 'tilt', 'tiltaxis', 'depth', 'direction', 'rotation', 'pull', - 'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale', - 'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale', - 'xcalendar', 'ycalendar', - 'cumulative', 'cumulative.enabled', 'cumulative.direction', 'cumulative.currentbin' - ]; - - for(i = 0; i < traces.length; i++) { - if(Registry.traceIs(fullData[traces[i]], 'box')) { - recalcAttrs.push('name'); - break; - } - } + var recalcAttrs = [ + 'mode', 'visible', 'type', 'orientation', 'fill', + 'histfunc', 'histnorm', 'text', + 'x', 'y', 'z', + 'a', 'b', 'c', + 'open', 'high', 'low', 'close', + 'base', 'width', 'offset', + 'xtype', 'x0', 'dx', 'ytype', 'y0', 'dy', 'xaxis', 'yaxis', + 'line.width', + 'connectgaps', 'transpose', 'zsmooth', + 'showscale', 'marker.showscale', + 'zauto', 'marker.cauto', + 'autocolorscale', 'marker.autocolorscale', + 'colorscale', 'marker.colorscale', + 'reversescale', 'marker.reversescale', + 'autobinx', 'nbinsx', 'xbins', 'xbins.start', 'xbins.end', 'xbins.size', + 'autobiny', 'nbinsy', 'ybins', 'ybins.start', 'ybins.end', 'ybins.size', + 'autocontour', 'ncontours', 'contours', 'contours.coloring', + 'error_y', 'error_y.visible', 'error_y.value', 'error_y.type', + 'error_y.traceref', 'error_y.array', 'error_y.symmetric', + 'error_y.arrayminus', 'error_y.valueminus', 'error_y.tracerefminus', + 'error_x', 'error_x.visible', 'error_x.value', 'error_x.type', + 'error_x.traceref', 'error_x.array', 'error_x.symmetric', + 'error_x.arrayminus', 'error_x.valueminus', 'error_x.tracerefminus', + 'swapxy', 'swapxyaxes', 'orientationaxes', + 'marker.colors', 'values', 'labels', 'label0', 'dlabel', 'sort', + 'textinfo', 'textposition', 'textfont.size', 'textfont.family', 'textfont.color', + 'insidetextfont.size', 'insidetextfont.family', 'insidetextfont.color', + 'outsidetextfont.size', 'outsidetextfont.family', 'outsidetextfont.color', + 'hole', 'scalegroup', 'domain', 'domain.x', 'domain.y', + 'domain.x[0]', 'domain.x[1]', 'domain.y[0]', 'domain.y[1]', + 'tilt', 'tiltaxis', 'depth', 'direction', 'rotation', 'pull', + 'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale', + 'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale', + 'xcalendar', 'ycalendar', + 'cumulative', 'cumulative.enabled', 'cumulative.direction', 'cumulative.currentbin' + ] + + for (i = 0; i < traces.length; i++) { + if (Registry.traceIs(fullData[traces[i]], 'box')) { + recalcAttrs.push('name') + break + } + } // autorangeAttrs attributes need a full redo of calcdata // only if an axis is autoranged, // because .calc() is where the autorange gets determined // TODO: could we break this out as well? - var autorangeAttrs = [ - 'marker', 'marker.size', 'textfont', - 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean', - 'tickwidth' - ]; + var autorangeAttrs = [ + 'marker', 'marker.size', 'textfont', + 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean', + 'tickwidth' + ] // replotAttrs attributes need a replot (because different // objects need to be made) but not a recalc - var replotAttrs = [ - 'zmin', 'zmax', 'zauto', - 'xgap', 'ygap', - 'marker.cmin', 'marker.cmax', 'marker.cauto', - 'line.cmin', 'line.cmax', - 'marker.line.cmin', 'marker.line.cmax', - 'contours.start', 'contours.end', 'contours.size', - 'contours.showlines', - 'line', 'line.smoothing', 'line.shape', - 'error_y.width', 'error_x.width', 'error_x.copy_ystyle', - 'marker.maxdisplayed' - ]; + var replotAttrs = [ + 'zmin', 'zmax', 'zauto', + 'xgap', 'ygap', + 'marker.cmin', 'marker.cmax', 'marker.cauto', + 'line.cmin', 'line.cmax', + 'marker.line.cmin', 'marker.line.cmax', + 'contours.start', 'contours.end', 'contours.size', + 'contours.showlines', + 'line', 'line.smoothing', 'line.shape', + 'error_y.width', 'error_x.width', 'error_x.copy_ystyle', + 'marker.maxdisplayed' + ] // these ones may alter the axis type // (at least if the first trace is involved) - var axtypeAttrs = [ - 'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis' - ]; + var axtypeAttrs = [ + 'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis' + ] - var zscl = ['zmin', 'zmax'], - xbins = ['xbins.start', 'xbins.end', 'xbins.size'], - ybins = ['ybins.start', 'ybins.end', 'ybins.size'], - contourAttrs = ['contours.start', 'contours.end', 'contours.size']; + var zscl = ['zmin', 'zmax'], + xbins = ['xbins.start', 'xbins.end', 'xbins.size'], + ybins = ['ybins.start', 'ybins.end', 'ybins.size'], + contourAttrs = ['contours.start', 'contours.end', 'contours.size'] // At the moment, only cartesian, pie and ternary plot types can afford // to not go through a full replot - var doPlotWhiteList = ['cartesian', 'pie', 'ternary']; - fullLayout._basePlotModules.forEach(function(_module) { - if(doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true; - }); + var doPlotWhiteList = ['cartesian', 'pie', 'ternary'] + fullLayout._basePlotModules.forEach(function (_module) { + if (doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true + }) // make a new empty vals array for undoit - function a0() { return traces.map(function() { return undefined; }); } + function a0 () { return traces.map(function () { return undefined }) } // for autoranging multiple axes - function addToAxlist(axid) { - var axName = Plotly.Axes.id2name(axid); - if(axlist.indexOf(axName) === -1) axlist.push(axName); - } + function addToAxlist (axid) { + var axName = Plotly.Axes.id2name(axid) + if (axlist.indexOf(axName) === -1) axlist.push(axName) + } - function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; } + function autorangeAttr (axName) { return 'LAYOUT' + axName + '.autorange' } - function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; } + function rangeAttr (axName) { return 'LAYOUT' + axName + '.range' } // for attrs that interact (like scales & autoscales), save the // old vals before making the change // val=undefined will not set a value, just record what the value was. // val=null will delete the attribute // attr can be an array to set several at once (all to the same val) - function doextra(attr, val, i) { - if(Array.isArray(attr)) { - attr.forEach(function(a) { doextra(a, val, i); }); - return; - } + function doextra (attr, val, i) { + if (Array.isArray(attr)) { + attr.forEach(function (a) { doextra(a, val, i) }) + return + } // quit if explicitly setting this elsewhere - if(attr in aobj) return; + if (attr in aobj) return - var extraparam; - if(attr.substr(0, 6) === 'LAYOUT') { - extraparam = Lib.nestedProperty(gd.layout, attr.replace('LAYOUT', '')); - } else { - extraparam = Lib.nestedProperty(data[traces[i]], attr); - } + var extraparam + if (attr.substr(0, 6) === 'LAYOUT') { + extraparam = Lib.nestedProperty(gd.layout, attr.replace('LAYOUT', '')) + } else { + extraparam = Lib.nestedProperty(data[traces[i]], attr) + } - if(!(attr in undoit)) { - undoit[attr] = a0(); - } - if(undoit[attr][i] === undefined) { - undoit[attr][i] = extraparam.get(); - } - if(val !== undefined) { - extraparam.set(val); - } + if (!(attr in undoit)) { + undoit[attr] = a0() + } + if (undoit[attr][i] === undefined) { + undoit[attr][i] = extraparam.get() + } + if (val !== undefined) { + extraparam.set(val) } + } // now make the changes to gd.data (and occasionally gd.layout) // and figure out what kind of graphics update we need to do - for(var ai in aobj) { - var vi = aobj[ai], - cont, - contFull, - param, - oldVal, - newVal; - - redoit[ai] = vi; - - if(ai.substr(0, 6) === 'LAYOUT') { - param = Lib.nestedProperty(gd.layout, ai.replace('LAYOUT', '')); - undoit[ai] = [param.get()]; + for (var ai in aobj) { + var vi = aobj[ai], + cont, + contFull, + param, + oldVal, + newVal + + redoit[ai] = vi + + if (ai.substr(0, 6) === 'LAYOUT') { + param = Lib.nestedProperty(gd.layout, ai.replace('LAYOUT', '')) + undoit[ai] = [param.get()] // since we're allowing val to be an array, allow it here too, // even though that's meaningless - param.set(Array.isArray(vi) ? vi[0] : vi); + param.set(Array.isArray(vi) ? vi[0] : vi) // ironically, the layout attrs in restyle only require replot, // not relayout - flags.docalc = true; - continue; - } + flags.docalc = true + continue + } // set attribute in gd.data - undoit[ai] = a0(); - for(i = 0; i < traces.length; i++) { - cont = data[traces[i]]; - contFull = fullData[traces[i]]; - param = Lib.nestedProperty(cont, ai); - oldVal = param.get(); - newVal = Array.isArray(vi) ? vi[i % vi.length] : vi; + undoit[ai] = a0() + for (i = 0; i < traces.length; i++) { + cont = data[traces[i]] + contFull = fullData[traces[i]] + param = Lib.nestedProperty(cont, ai) + oldVal = param.get() + newVal = Array.isArray(vi) ? vi[i % vi.length] : vi - if(newVal === undefined) continue; + if (newVal === undefined) continue // setting bin or z settings should turn off auto // and setting auto should save bin or z settings - if(zscl.indexOf(ai) !== -1) { - doextra('zauto', false, i); - } - else if(ai === 'colorscale') { - doextra('autocolorscale', false, i); - } - else if(ai === 'autocolorscale') { - doextra('colorscale', undefined, i); - } - else if(ai === 'marker.colorscale') { - doextra('marker.autocolorscale', false, i); - } - else if(ai === 'marker.autocolorscale') { - doextra('marker.colorscale', undefined, i); - } - else if(ai === 'zauto') { - doextra(zscl, undefined, i); - } - else if(xbins.indexOf(ai) !== -1) { - doextra('autobinx', false, i); - } - else if(ai === 'autobinx') { - doextra(xbins, undefined, i); - } - else if(ybins.indexOf(ai) !== -1) { - doextra('autobiny', false, i); - } - else if(ai === 'autobiny') { - doextra(ybins, undefined, i); - } - else if(contourAttrs.indexOf(ai) !== -1) { - doextra('autocontour', false, i); - } - else if(ai === 'autocontour') { - doextra(contourAttrs, undefined, i); - } + if (zscl.indexOf(ai) !== -1) { + doextra('zauto', false, i) + } else if (ai === 'colorscale') { + doextra('autocolorscale', false, i) + } else if (ai === 'autocolorscale') { + doextra('colorscale', undefined, i) + } else if (ai === 'marker.colorscale') { + doextra('marker.autocolorscale', false, i) + } else if (ai === 'marker.autocolorscale') { + doextra('marker.colorscale', undefined, i) + } else if (ai === 'zauto') { + doextra(zscl, undefined, i) + } else if (xbins.indexOf(ai) !== -1) { + doextra('autobinx', false, i) + } else if (ai === 'autobinx') { + doextra(xbins, undefined, i) + } else if (ybins.indexOf(ai) !== -1) { + doextra('autobiny', false, i) + } else if (ai === 'autobiny') { + doextra(ybins, undefined, i) + } else if (contourAttrs.indexOf(ai) !== -1) { + doextra('autocontour', false, i) + } else if (ai === 'autocontour') { + doextra(contourAttrs, undefined, i) + } // heatmaps: setting x0 or dx, y0 or dy, // should turn xtype/ytype to 'scaled' if 'array' - else if(['x0', 'dx'].indexOf(ai) !== -1 && + else if (['x0', 'dx'].indexOf(ai) !== -1 && contFull.x && contFull.xtype !== 'scaled') { - doextra('xtype', 'scaled', i); - } - else if(['y0', 'dy'].indexOf(ai) !== -1 && + doextra('xtype', 'scaled', i) + } else if (['y0', 'dy'].indexOf(ai) !== -1 && contFull.y && contFull.ytype !== 'scaled') { - doextra('ytype', 'scaled', i); - } + doextra('ytype', 'scaled', i) + } // changing colorbar size modes, // make the resulting size not change // note that colorbar fractional sizing is based on the // original plot size, before anything (like a colorbar) // increases the margins - else if(ai === 'colorbar.thicknessmode' && param.get() !== newVal && + else if (ai === 'colorbar.thicknessmode' && param.get() !== newVal && ['fraction', 'pixels'].indexOf(newVal) !== -1 && contFull.colorbar) { - var thicknorm = + var thicknorm = ['top', 'bottom'].indexOf(contFull.colorbar.orient) !== -1 ? (fullLayout.height - fullLayout.margin.t - fullLayout.margin.b) : - (fullLayout.width - fullLayout.margin.l - fullLayout.margin.r); - doextra('colorbar.thickness', contFull.colorbar.thickness * - (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i); - } - else if(ai === 'colorbar.lenmode' && param.get() !== newVal && + (fullLayout.width - fullLayout.margin.l - fullLayout.margin.r) + doextra('colorbar.thickness', contFull.colorbar.thickness * + (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i) + } else if (ai === 'colorbar.lenmode' && param.get() !== newVal && ['fraction', 'pixels'].indexOf(newVal) !== -1 && contFull.colorbar) { - var lennorm = + var lennorm = ['top', 'bottom'].indexOf(contFull.colorbar.orient) !== -1 ? (fullLayout.width - fullLayout.margin.l - fullLayout.margin.r) : - (fullLayout.height - fullLayout.margin.t - fullLayout.margin.b); - doextra('colorbar.len', contFull.colorbar.len * - (newVal === 'fraction' ? 1 / lennorm : lennorm), i); - } - else if(ai === 'colorbar.tick0' || ai === 'colorbar.dtick') { - doextra('colorbar.tickmode', 'linear', i); - } - else if(ai === 'colorbar.tickmode') { - doextra(['colorbar.tick0', 'colorbar.dtick'], undefined, i); - } - - - if(ai === 'type' && (newVal === 'pie') !== (oldVal === 'pie')) { - var labelsTo = 'x', - valuesTo = 'y'; - if((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') { - labelsTo = 'y'; - valuesTo = 'x'; - } - Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo); - Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo); - Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo); - - if(oldVal === 'pie') { - Lib.nestedProperty(cont, 'marker.color') - .set(Lib.nestedProperty(cont, 'marker.colors').get()); + (fullLayout.height - fullLayout.margin.t - fullLayout.margin.b) + doextra('colorbar.len', contFull.colorbar.len * + (newVal === 'fraction' ? 1 / lennorm : lennorm), i) + } else if (ai === 'colorbar.tick0' || ai === 'colorbar.dtick') { + doextra('colorbar.tickmode', 'linear', i) + } else if (ai === 'colorbar.tickmode') { + doextra(['colorbar.tick0', 'colorbar.dtick'], undefined, i) + } + + if (ai === 'type' && (newVal === 'pie') !== (oldVal === 'pie')) { + var labelsTo = 'x', + valuesTo = 'y' + if ((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') { + labelsTo = 'y' + valuesTo = 'x' + } + Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo) + Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo) + Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo) + + if (oldVal === 'pie') { + Lib.nestedProperty(cont, 'marker.color') + .set(Lib.nestedProperty(cont, 'marker.colors').get()) // super kludgy - but if all pies are gone we won't remove them otherwise - fullLayout._pielayer.selectAll('g.trace').remove(); - } else if(Registry.traceIs(cont, 'cartesian')) { - Lib.nestedProperty(cont, 'marker.colors') - .set(Lib.nestedProperty(cont, 'marker.color').get()); + fullLayout._pielayer.selectAll('g.trace').remove() + } else if (Registry.traceIs(cont, 'cartesian')) { + Lib.nestedProperty(cont, 'marker.colors') + .set(Lib.nestedProperty(cont, 'marker.color').get()) // look for axes that are no longer in use and delete them - flagAxForDelete[cont.xaxis || 'x'] = true; - flagAxForDelete[cont.yaxis || 'y'] = true; - } - } + flagAxForDelete[cont.xaxis || 'x'] = true + flagAxForDelete[cont.yaxis || 'y'] = true + } + } - undoit[ai][i] = oldVal; + undoit[ai][i] = oldVal // set the new value - if val is an array, it's one el per trace // first check for attributes that get more complex alterations - var swapAttrs = [ - 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes' - ]; - if(swapAttrs.indexOf(ai) !== -1) { + var swapAttrs = [ + 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes' + ] + if (swapAttrs.indexOf(ai) !== -1) { // setting an orientation: make sure it's changing // before we swap everything else - if(ai === 'orientation') { - param.set(newVal); - if(param.get() === undoit[ai][i]) continue; - } + if (ai === 'orientation') { + param.set(newVal) + if (param.get() === undoit[ai][i]) continue + } // orientationaxes has no value, // it flips everything and the axes - else if(ai === 'orientationaxes') { - cont.orientation = - {v: 'h', h: 'v'}[contFull.orientation]; - } - helpers.swapXYData(cont); - } - else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) { - helpers.manageArrayContainers(param, newVal, undoit); - flags.docalc = true; - } + else if (ai === 'orientationaxes') { + cont.orientation = + {v: 'h', h: 'v'}[contFull.orientation] + } + helpers.swapXYData(cont) + } else if (Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) { + helpers.manageArrayContainers(param, newVal, undoit) + flags.docalc = true + } // all the other ones, just modify that one attribute - else param.set(newVal); - - } + else param.set(newVal) + } // swap the data attributes of the relevant x and y axes? - if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) { - Plotly.Axes.swap(gd, traces); - } + if (['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) { + Plotly.Axes.swap(gd, traces) + } // swap hovermode if set to "compare x/y data" - if(ai === 'orientationaxes') { - var hovermode = Lib.nestedProperty(gd.layout, 'hovermode'); - if(hovermode.get() === 'x') { - hovermode.set('y'); - } else if(hovermode.get() === 'y') { - hovermode.set('x'); - } - } + if (ai === 'orientationaxes') { + var hovermode = Lib.nestedProperty(gd.layout, 'hovermode') + if (hovermode.get() === 'x') { + hovermode.set('y') + } else if (hovermode.get() === 'y') { + hovermode.set('x') + } + } // check if we need to call axis type - if((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) { - Plotly.Axes.clearTypes(gd, traces); - flags.docalc = true; - } + if ((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) { + Plotly.Axes.clearTypes(gd, traces) + flags.docalc = true + } // switching from auto to manual binning or z scaling doesn't // actually do anything but change what you see in the styling // box. everything else at least needs to apply styles - if((['autobinx', 'autobiny', 'zauto'].indexOf(ai) === -1) || + if ((['autobinx', 'autobiny', 'zauto'].indexOf(ai) === -1) || newVal !== false) { - flags.dostyle = true; - } - if(['colorbar', 'line'].indexOf(param.parts[0]) !== -1 || + flags.dostyle = true + } + if (['colorbar', 'line'].indexOf(param.parts[0]) !== -1 || param.parts[0] === 'marker' && param.parts[1] === 'colorbar') { - flags.docolorbars = true; - } + flags.docolorbars = true + } - var aiArrayStart = ai.indexOf('['), - aiAboveArray = aiArrayStart === -1 ? ai : ai.substr(0, aiArrayStart); + var aiArrayStart = ai.indexOf('['), + aiAboveArray = aiArrayStart === -1 ? ai : ai.substr(0, aiArrayStart) - if(recalcAttrs.indexOf(aiAboveArray) !== -1) { + if (recalcAttrs.indexOf(aiAboveArray) !== -1) { // major enough changes deserve autoscale, autobin, and // non-reversed axes so people don't get confused - if(['orientation', 'type'].indexOf(ai) !== -1) { - axlist = []; - for(i = 0; i < traces.length; i++) { - var trace = data[traces[i]]; - - if(Registry.traceIs(trace, 'cartesian')) { - addToAxlist(trace.xaxis || 'x'); - addToAxlist(trace.yaxis || 'y'); - - if(ai === 'type') { - doextra(['autobinx', 'autobiny'], true, i); - } - } - } - - doextra(axlist.map(autorangeAttr), true, 0); - doextra(axlist.map(rangeAttr), [0, 1], 0); + if (['orientation', 'type'].indexOf(ai) !== -1) { + axlist = [] + for (i = 0; i < traces.length; i++) { + var trace = data[traces[i]] + + if (Registry.traceIs(trace, 'cartesian')) { + addToAxlist(trace.xaxis || 'x') + addToAxlist(trace.yaxis || 'y') + + if (ai === 'type') { + doextra(['autobinx', 'autobiny'], true, i) } - flags.docalc = true; + } } - else if(replotAttrs.indexOf(aiAboveArray) !== -1) flags.doplot = true; - else if(autorangeAttrs.indexOf(aiAboveArray) !== -1) flags.docalcAutorange = true; - } + + doextra(axlist.map(autorangeAttr), true, 0) + doextra(axlist.map(rangeAttr), [0, 1], 0) + } + flags.docalc = true + } else if (replotAttrs.indexOf(aiAboveArray) !== -1) flags.doplot = true + else if (autorangeAttrs.indexOf(aiAboveArray) !== -1) flags.docalcAutorange = true + } // do we need to force a recalc? - Plotly.Axes.list(gd).forEach(function(ax) { - if(ax.autorange) flags.autorangeOn = true; - }); + Plotly.Axes.list(gd).forEach(function (ax) { + if (ax.autorange) flags.autorangeOn = true + }) // check axes we've flagged for possible deletion // flagAxForDelete is a hash so we can make sure we only get each axis once - var axListForDelete = Object.keys(flagAxForDelete); - axisLoop: - for(i = 0; i < axListForDelete.length; i++) { - var axId = axListForDelete[i], - axLetter = axId.charAt(0), - axAttr = axLetter + 'axis'; - - for(var j = 0; j < data.length; j++) { - if(Registry.traceIs(data[j], 'cartesian') && + var axListForDelete = Object.keys(flagAxForDelete) + axisLoop: + for (i = 0; i < axListForDelete.length; i++) { + var axId = axListForDelete[i], + axLetter = axId.charAt(0), + axAttr = axLetter + 'axis' + + for (var j = 0; j < data.length; j++) { + if (Registry.traceIs(data[j], 'cartesian') && (data[j][axAttr] || axLetter) === axId) { - continue axisLoop; - } + continue axisLoop } + } // no data on this axis - delete it. - doextra('LAYOUT' + Plotly.Axes.id2name(axId), null, 0); + doextra('LAYOUT' + Plotly.Axes.id2name(axId), null, 0) } // combine a few flags together; - if(flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) { - flags.clearCalc = true; - } - if(flags.docalc || flags.doplot || flags.docalcAutorange) { - flags.fullReplot = true; - } - - return { - flags: flags, - undoit: undoit, - redoit: redoit, - traces: traces, - eventData: Lib.extendDeepNoArrays([], [redoit, traces]) - }; + if (flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) { + flags.clearCalc = true + } + if (flags.docalc || flags.doplot || flags.docalcAutorange) { + flags.fullReplot = true + } + + return { + flags: flags, + undoit: undoit, + redoit: redoit, + traces: traces, + eventData: Lib.extendDeepNoArrays([], [redoit, traces]) + } } /** @@ -1686,374 +1646,351 @@ function _restyle(gd, aobj, _traces) { * attribute object `{astr1: val1, astr2: val2 ...}` * allows setting multiple attributes simultaneously */ -Plotly.relayout = function relayout(gd, astr, val) { - gd = helpers.getGraphDiv(gd); - helpers.clearPromiseQueue(gd); +Plotly.relayout = function relayout (gd, astr, val) { + gd = helpers.getGraphDiv(gd) + helpers.clearPromiseQueue(gd) - if(gd.framework && gd.framework.isPolar) { - return Promise.resolve(gd); - } + if (gd.framework && gd.framework.isPolar) { + return Promise.resolve(gd) + } - var aobj = {}; - if(typeof astr === 'string') aobj[astr] = val; - else if(Lib.isPlainObject(astr)) aobj = astr; - else { - Lib.warn('Relayout fail.', astr, val); - return Promise.reject(); - } + var aobj = {} + if (typeof astr === 'string') aobj[astr] = val + else if (Lib.isPlainObject(astr)) aobj = astr + else { + Lib.warn('Relayout fail.', astr, val) + return Promise.reject() + } - if(Object.keys(aobj).length) gd.changed = true; + if (Object.keys(aobj).length) gd.changed = true - var specs = _relayout(gd, aobj), - flags = specs.flags; + var specs = _relayout(gd, aobj), + flags = specs.flags // clear calcdata if required - if(flags.docalc) gd.calcdata = undefined; + if (flags.docalc) gd.calcdata = undefined // fill in redraw sequence - var seq = []; - - if(flags.layoutReplot) { - seq.push(subroutines.layoutReplot); - } - else if(Object.keys(aobj).length) { - seq.push(Plots.previousPromises); - Plots.supplyDefaults(gd); - - if(flags.dolegend) seq.push(subroutines.doLegend); - if(flags.dolayoutstyle) seq.push(subroutines.layoutStyles); - if(flags.doticks) seq.push(subroutines.doTicksRelayout); - if(flags.domodebar) seq.push(subroutines.doModeBar); - if(flags.docamera) seq.push(subroutines.doCamera); - } - - Queue.add(gd, + var seq = [] + + if (flags.layoutReplot) { + seq.push(subroutines.layoutReplot) + } else if (Object.keys(aobj).length) { + seq.push(Plots.previousPromises) + Plots.supplyDefaults(gd) + + if (flags.dolegend) seq.push(subroutines.doLegend) + if (flags.dolayoutstyle) seq.push(subroutines.layoutStyles) + if (flags.doticks) seq.push(subroutines.doTicksRelayout) + if (flags.domodebar) seq.push(subroutines.doModeBar) + if (flags.docamera) seq.push(subroutines.doCamera) + } + + Queue.add(gd, relayout, [gd, specs.undoit], relayout, [gd, specs.redoit] - ); + ) - var plotDone = Lib.syncOrAsync(seq, gd); - if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); + var plotDone = Lib.syncOrAsync(seq, gd) + if (!plotDone || !plotDone.then) plotDone = Promise.resolve(gd) - return plotDone.then(function() { - gd.emit('plotly_relayout', specs.eventData); - return gd; - }); -}; + return plotDone.then(function () { + gd.emit('plotly_relayout', specs.eventData) + return gd + }) +} -function _relayout(gd, aobj) { - var layout = gd.layout, - fullLayout = gd._fullLayout, - keys = Object.keys(aobj), - axes = Plotly.Axes.list(gd), - i; +function _relayout (gd, aobj) { + var layout = gd.layout, + fullLayout = gd._fullLayout, + keys = Object.keys(aobj), + axes = Plotly.Axes.list(gd), + i // look for 'allaxes', split out into all axes // in case of 3D the axis are nested within a scene which is held in _id - for(i = 0; i < keys.length; i++) { - if(keys[i].indexOf('allaxes') === 0) { - for(var j = 0; j < axes.length; j++) { - var scene = axes[j]._id.substr(1), - axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '', - newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name); - - if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]]; - } + for (i = 0; i < keys.length; i++) { + if (keys[i].indexOf('allaxes') === 0) { + for (var j = 0; j < axes.length; j++) { + var scene = axes[j]._id.substr(1), + axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '', + newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name) - delete aobj[keys[i]]; - } + if (!aobj[newkey]) aobj[newkey] = aobj[keys[i]] + } + + delete aobj[keys[i]] } + } // initialize flags - var flags = { - dolegend: false, - doticks: false, - dolayoutstyle: false, - doplot: false, - docalc: false, - domodebar: false, - docamera: false, - layoutReplot: false - }; + var flags = { + dolegend: false, + doticks: false, + dolayoutstyle: false, + doplot: false, + docalc: false, + domodebar: false, + docamera: false, + layoutReplot: false + } // copies of the change (and previous values of anything affected) // for the undo / redo queue - var redoit = {}, - undoit = {}; + var redoit = {}, + undoit = {} // for attrs that interact (like scales & autoscales), save the // old vals before making the change // val=undefined will not set a value, just record what the value was. // attr can be an array to set several at once (all to the same val) - function doextra(attr, val) { - if(Array.isArray(attr)) { - attr.forEach(function(a) { doextra(a, val); }); - return; - } + function doextra (attr, val) { + if (Array.isArray(attr)) { + attr.forEach(function (a) { doextra(a, val) }) + return + } // quit if explicitly setting this elsewhere - if(attr in aobj) return; + if (attr in aobj) return - var p = Lib.nestedProperty(layout, attr); - if(!(attr in undoit)) undoit[attr] = p.get(); - if(val !== undefined) p.set(val); - } + var p = Lib.nestedProperty(layout, attr) + if (!(attr in undoit)) undoit[attr] = p.get() + if (val !== undefined) p.set(val) + } // for editing annotations or shapes - is it on autoscaled axes? - function refAutorange(obj, axletter) { - var axName = Plotly.Axes.id2name(obj[axletter + 'ref'] || axletter); - return (fullLayout[axName] || {}).autorange; - } + function refAutorange (obj, axletter) { + var axName = Plotly.Axes.id2name(obj[axletter + 'ref'] || axletter) + return (fullLayout[axName] || {}).autorange + } // alter gd.layout - for(var ai in aobj) { - var p = Lib.nestedProperty(layout, ai), - vi = aobj[ai], - plen = p.parts.length, + for (var ai in aobj) { + var p = Lib.nestedProperty(layout, ai), + vi = aobj[ai], + plen = p.parts.length, // p.parts may end with an index integer if the property is an array - pend = typeof p.parts[plen - 1] === 'string' ? (plen - 1) : (plen - 2), + pend = typeof p.parts[plen - 1] === 'string' ? (plen - 1) : (plen - 2), // last property in chain (leaf node) - pleaf = p.parts[pend], + pleaf = p.parts[pend], // leaf plus immediate parent - pleafPlus = p.parts[pend - 1] + '.' + pleaf, + pleafPlus = p.parts[pend - 1] + '.' + pleaf, // trunk nodes (everything except the leaf) - ptrunk = p.parts.slice(0, pend).join('.'), - parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(), - parentFull = Lib.nestedProperty(fullLayout, ptrunk).get(); + ptrunk = p.parts.slice(0, pend).join('.'), + parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(), + parentFull = Lib.nestedProperty(fullLayout, ptrunk).get() - if(vi === undefined) continue; + if (vi === undefined) continue - redoit[ai] = vi; + redoit[ai] = vi // axis reverse is special - it is its own inverse // op and has no flag. - undoit[ai] = (pleaf === 'reverse') ? vi : p.get(); + undoit[ai] = (pleaf === 'reverse') ? vi : p.get() // Setting width or height to null must reset the graph's width / height // back to its initial value as computed during the first pass in Plots.plotAutoSize. // // To do so, we must manually set them back here using the _initialAutoSize cache. - if(['width', 'height'].indexOf(ai) !== -1 && vi === null) { - gd._fullLayout[ai] = gd._initialAutoSize[ai]; - } + if (['width', 'height'].indexOf(ai) !== -1 && vi === null) { + gd._fullLayout[ai] = gd._initialAutoSize[ai] + } // check autorange vs range - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) { - doextra(ptrunk + '.autorange', false); - } - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) { - doextra([ptrunk + '.range[0]', ptrunk + '.range[1]'], - undefined); - } - else if(pleafPlus.match(/^aspectratio\.[xyz]$/)) { - doextra(p.parts[0] + '.aspectmode', 'manual'); - } - else if(pleafPlus.match(/^aspectmode$/)) { - doextra([ptrunk + '.x', ptrunk + '.y', ptrunk + '.z'], undefined); - } - else if(pleaf === 'tick0' || pleaf === 'dtick') { - doextra(ptrunk + '.tickmode', 'linear'); - } - else if(pleaf === 'tickmode') { - doextra([ptrunk + '.tick0', ptrunk + '.dtick'], undefined); - } - else if(/[xy]axis[0-9]*?$/.test(pleaf) && !Object.keys(vi || {}).length) { - flags.docalc = true; - } - else if(/[xy]axis[0-9]*\.categoryorder$/.test(pleafPlus)) { - flags.docalc = true; - } - else if(/[xy]axis[0-9]*\.categoryarray/.test(pleafPlus)) { - flags.docalc = true; - } - - if(pleafPlus.indexOf('rangeslider') !== -1) { - flags.docalc = true; - } + else if (pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) { + doextra(ptrunk + '.autorange', false) + } else if (pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) { + doextra([ptrunk + '.range[0]', ptrunk + '.range[1]'], + undefined) + } else if (pleafPlus.match(/^aspectratio\.[xyz]$/)) { + doextra(p.parts[0] + '.aspectmode', 'manual') + } else if (pleafPlus.match(/^aspectmode$/)) { + doextra([ptrunk + '.x', ptrunk + '.y', ptrunk + '.z'], undefined) + } else if (pleaf === 'tick0' || pleaf === 'dtick') { + doextra(ptrunk + '.tickmode', 'linear') + } else if (pleaf === 'tickmode') { + doextra([ptrunk + '.tick0', ptrunk + '.dtick'], undefined) + } else if (/[xy]axis[0-9]*?$/.test(pleaf) && !Object.keys(vi || {}).length) { + flags.docalc = true + } else if (/[xy]axis[0-9]*\.categoryorder$/.test(pleafPlus)) { + flags.docalc = true + } else if (/[xy]axis[0-9]*\.categoryarray/.test(pleafPlus)) { + flags.docalc = true + } + + if (pleafPlus.indexOf('rangeslider') !== -1) { + flags.docalc = true + } // toggling log without autorange: need to also recalculate ranges // logical XOR (ie are we toggling log) - if(pleaf === 'type' && ((parentFull.type === 'log') !== (vi === 'log'))) { - var ax = parentIn; - - if(!ax || !ax.range) { - doextra(ptrunk + '.autorange', true); - } - else if(!parentFull.autorange) { - var r0 = ax.range[0], - r1 = ax.range[1]; - if(vi === 'log') { + if (pleaf === 'type' && ((parentFull.type === 'log') !== (vi === 'log'))) { + var ax = parentIn + + if (!ax || !ax.range) { + doextra(ptrunk + '.autorange', true) + } else if (!parentFull.autorange) { + var r0 = ax.range[0], + r1 = ax.range[1] + if (vi === 'log') { // if both limits are negative, autorange - if(r0 <= 0 && r1 <= 0) { - doextra(ptrunk + '.autorange', true); - } + if (r0 <= 0 && r1 <= 0) { + doextra(ptrunk + '.autorange', true) + } // if one is negative, set it 6 orders below the other. - if(r0 <= 0) r0 = r1 / 1e6; - else if(r1 <= 0) r1 = r0 / 1e6; + if (r0 <= 0) r0 = r1 / 1e6 + else if (r1 <= 0) r1 = r0 / 1e6 // now set the range values as appropriate - doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10); - doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10); - } - else { - doextra(ptrunk + '.range[0]', Math.pow(10, r0)); - doextra(ptrunk + '.range[1]', Math.pow(10, r1)); - } - } - else if(vi === 'log') { + doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10) + doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10) + } else { + doextra(ptrunk + '.range[0]', Math.pow(10, r0)) + doextra(ptrunk + '.range[1]', Math.pow(10, r1)) + } + } else if (vi === 'log') { // just make sure the range is positive and in the right // order, it'll get recalculated later - ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1]; - } - } + ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1] + } + } // handle axis reversal explicitly, as there's no 'reverse' flag - if(pleaf === 'reverse') { - if(parentIn.range) parentIn.range.reverse(); - else { - doextra(ptrunk + '.autorange', true); - parentIn.range = [1, 0]; - } + if (pleaf === 'reverse') { + if (parentIn.range) parentIn.range.reverse() + else { + doextra(ptrunk + '.autorange', true) + parentIn.range = [1, 0] + } - if(parentFull.autorange) flags.docalc = true; - else flags.doplot = true; - } + if (parentFull.autorange) flags.docalc = true + else flags.doplot = true + } // send annotation and shape mods one-by-one through Annotations.draw(), // don't set via nestedProperty // that's because add and remove are special - else if(p.parts[0] === 'annotations' || p.parts[0] === 'shapes') { - var objNum = p.parts[1], - objType = p.parts[0], - objList = layout[objType] || [], - obji = objList[objNum] || {}; + else if (p.parts[0] === 'annotations' || p.parts[0] === 'shapes') { + var objNum = p.parts[1], + objType = p.parts[0], + objList = layout[objType] || [], + obji = objList[objNum] || {} // if p.parts is just an annotation number, and val is either // 'add' or an entire annotation to add, the undo is 'remove' // if val is 'remove' then undo is the whole annotation object - if(p.parts.length === 2) { - + if (p.parts.length === 2) { // new API, remove annotation / shape with `null` - if(vi === null) aobj[ai] = 'remove'; - - if(aobj[ai] === 'add' || Lib.isPlainObject(aobj[ai])) { - undoit[ai] = 'remove'; - } - else if(aobj[ai] === 'remove') { - if(objNum === -1) { - undoit[objType] = objList; - delete undoit[ai]; - } - else undoit[ai] = obji; - } - else Lib.log('???', aobj); - } - - if((refAutorange(obji, 'x') || refAutorange(obji, 'y')) && + if (vi === null) aobj[ai] = 'remove' + + if (aobj[ai] === 'add' || Lib.isPlainObject(aobj[ai])) { + undoit[ai] = 'remove' + } else if (aobj[ai] === 'remove') { + if (objNum === -1) { + undoit[objType] = objList + delete undoit[ai] + } else undoit[ai] = obji + } else Lib.log('???', aobj) + } + + if ((refAutorange(obji, 'x') || refAutorange(obji, 'y')) && !Lib.containsAny(ai, ['color', 'opacity', 'align', 'dash'])) { - flags.docalc = true; - } + flags.docalc = true + } // TODO: combine all edits to a given annotation / shape into one call // as it is we get separate calls for x and y (or ax and ay) on move - var drawOne = Registry.getComponentMethod(objType, 'drawOne'); - drawOne(gd, objNum, p.parts.slice(2).join('.'), aobj[ai]); - delete aobj[ai]; - } - else if( + var drawOne = Registry.getComponentMethod(objType, 'drawOne') + drawOne(gd, objNum, p.parts.slice(2).join('.'), aobj[ai]) + delete aobj[ai] + } else if ( Plots.layoutArrayContainers.indexOf(p.parts[0]) !== -1 || (p.parts[0] === 'mapbox' && p.parts[1] === 'layers') ) { - helpers.manageArrayContainers(p, vi, undoit); - flags.doplot = true; - } + helpers.manageArrayContainers(p, vi, undoit) + flags.doplot = true + } // alter gd.layout - else { - var pp1 = String(p.parts[1] || ''); + else { + var pp1 = String(p.parts[1] || '') // check whether we can short-circuit a full redraw // 3d or geo at this point just needs to redraw. - if(p.parts[0].indexOf('scene') === 0) { - if(p.parts[1] === 'camera') flags.docamera = true; - else flags.doplot = true; - } - else if(p.parts[0].indexOf('geo') === 0) flags.doplot = true; - else if(p.parts[0].indexOf('ternary') === 0) flags.doplot = true; - else if(ai === 'paper_bgcolor') flags.doplot = true; - else if(fullLayout._has('gl2d') && + if (p.parts[0].indexOf('scene') === 0) { + if (p.parts[1] === 'camera') flags.docamera = true + else flags.doplot = true + } else if (p.parts[0].indexOf('geo') === 0) flags.doplot = true + else if (p.parts[0].indexOf('ternary') === 0) flags.doplot = true + else if (ai === 'paper_bgcolor') flags.doplot = true + else if (fullLayout._has('gl2d') && (ai.indexOf('axis') !== -1 || p.parts[0] === 'plot_bgcolor') - ) flags.doplot = true; - else if(ai === 'hiddenlabels') flags.docalc = true; - else if(p.parts[0].indexOf('legend') !== -1) flags.dolegend = true; - else if(ai.indexOf('title') !== -1) flags.doticks = true; - else if(p.parts[0].indexOf('bgcolor') !== -1) flags.dolayoutstyle = true; - else if(p.parts.length > 1 && + ) flags.doplot = true + else if (ai === 'hiddenlabels') flags.docalc = true + else if (p.parts[0].indexOf('legend') !== -1) flags.dolegend = true + else if (ai.indexOf('title') !== -1) flags.doticks = true + else if (p.parts[0].indexOf('bgcolor') !== -1) flags.dolayoutstyle = true + else if (p.parts.length > 1 && Lib.containsAny(pp1, ['tick', 'exponent', 'grid', 'zeroline'])) { - flags.doticks = true; - } - else if(ai.indexOf('.linewidth') !== -1 && + flags.doticks = true + } else if (ai.indexOf('.linewidth') !== -1 && ai.indexOf('axis') !== -1) { - flags.doticks = flags.dolayoutstyle = true; - } - else if(p.parts.length > 1 && pp1.indexOf('line') !== -1) { - flags.dolayoutstyle = true; - } - else if(p.parts.length > 1 && pp1 === 'mirror') { - flags.doticks = flags.dolayoutstyle = true; - } - else if(ai === 'margin.pad') { - flags.doticks = flags.dolayoutstyle = true; - } - else if(p.parts[0] === 'margin' || + flags.doticks = flags.dolayoutstyle = true + } else if (p.parts.length > 1 && pp1.indexOf('line') !== -1) { + flags.dolayoutstyle = true + } else if (p.parts.length > 1 && pp1 === 'mirror') { + flags.doticks = flags.dolayoutstyle = true + } else if (ai === 'margin.pad') { + flags.doticks = flags.dolayoutstyle = true + } else if (p.parts[0] === 'margin' || p.parts[1] === 'autorange' || p.parts[1] === 'rangemode' || p.parts[1] === 'type' || p.parts[1] === 'domain' || ai.indexOf('calendar') !== -1 || ai.match(/^(bar|box|font)/)) { - flags.docalc = true; - } + flags.docalc = true + } /* * hovermode and dragmode don't need any redrawing, since they just * affect reaction to user input, everything else, assume full replot. * height, width, autosize get dealt with below. Except for the case of * of subplots - scenes - which require scene.updateFx to be called. */ - else if(['hovermode', 'dragmode'].indexOf(ai) !== -1) flags.domodebar = true; - else if(['hovermode', 'dragmode', 'height', - 'width', 'autosize'].indexOf(ai) === -1) { - flags.doplot = true; - } + else if (['hovermode', 'dragmode'].indexOf(ai) !== -1) flags.domodebar = true + else if (['hovermode', 'dragmode', 'height', + 'width', 'autosize'].indexOf(ai) === -1) { + flags.doplot = true + } - p.set(vi); - } + p.set(vi) } + } - var oldWidth = gd._fullLayout.width, - oldHeight = gd._fullLayout.height; + var oldWidth = gd._fullLayout.width, + oldHeight = gd._fullLayout.height // coerce the updated layout - Plots.supplyDefaults(gd); + Plots.supplyDefaults(gd) // calculate autosizing - if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, gd._fullLayout); + if (gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, gd._fullLayout) // avoid unnecessary redraws - var hasSizechanged = aobj.height || aobj.width || + var hasSizechanged = aobj.height || aobj.width || (gd._fullLayout.width !== oldWidth) || - (gd._fullLayout.height !== oldHeight); + (gd._fullLayout.height !== oldHeight) - if(hasSizechanged) flags.docalc = true; + if (hasSizechanged) flags.docalc = true - if(flags.doplot || flags.docalc) { - flags.layoutReplot = true; - } + if (flags.doplot || flags.docalc) { + flags.layoutReplot = true + } // now all attribute mods are done, as are // redo and undo so we can save them - return { - flags: flags, - undoit: undoit, - redoit: redoit, - eventData: Lib.extendDeep({}, redoit) - }; + return { + flags: flags, + undoit: undoit, + redoit: redoit, + eventData: Lib.extendDeep({}, redoit) + } } /** @@ -2071,79 +2008,76 @@ function _relayout(gd, aobj) { * integer or array of integers for the traces to alter (all if omitted) * */ -Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) { - gd = helpers.getGraphDiv(gd); - helpers.clearPromiseQueue(gd); +Plotly.update = function update (gd, traceUpdate, layoutUpdate, traces) { + gd = helpers.getGraphDiv(gd) + helpers.clearPromiseQueue(gd) - if(gd.framework && gd.framework.isPolar) { - return Promise.resolve(gd); - } + if (gd.framework && gd.framework.isPolar) { + return Promise.resolve(gd) + } - if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {}; - if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {}; + if (!Lib.isPlainObject(traceUpdate)) traceUpdate = {} + if (!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {} - if(Object.keys(traceUpdate).length) gd.changed = true; - if(Object.keys(layoutUpdate).length) gd.changed = true; + if (Object.keys(traceUpdate).length) gd.changed = true + if (Object.keys(layoutUpdate).length) gd.changed = true - var restyleSpecs = _restyle(gd, traceUpdate, traces), - restyleFlags = restyleSpecs.flags; + var restyleSpecs = _restyle(gd, traceUpdate, traces), + restyleFlags = restyleSpecs.flags - var relayoutSpecs = _relayout(gd, layoutUpdate), - relayoutFlags = relayoutSpecs.flags; + var relayoutSpecs = _relayout(gd, layoutUpdate), + relayoutFlags = relayoutSpecs.flags // clear calcdata if required - if(restyleFlags.clearCalc || relayoutFlags.docalc) gd.calcdata = undefined; + if (restyleFlags.clearCalc || relayoutFlags.docalc) gd.calcdata = undefined // fill in redraw sequence - var seq = []; + var seq = [] - if(restyleFlags.fullReplot && relayoutFlags.layoutReplot) { - var data = gd.data, - layout = gd.layout; + if (restyleFlags.fullReplot && relayoutFlags.layoutReplot) { + var data = gd.data, + layout = gd.layout // clear existing data/layout on gd // so that Plotly.plot doesn't try to extend them - gd.data = undefined; - gd.layout = undefined; - - seq.push(function() { return Plotly.plot(gd, data, layout); }); - } - else if(restyleFlags.fullReplot) { - seq.push(Plotly.plot); - } - else if(relayoutFlags.layoutReplot) { - seq.push(subroutines.layoutReplot); - } - else { - seq.push(Plots.previousPromises); - Plots.supplyDefaults(gd); - - if(restyleFlags.dostyle) seq.push(subroutines.doTraceStyle); - if(restyleFlags.docolorbars) seq.push(subroutines.doColorBars); - if(relayoutFlags.dolegend) seq.push(subroutines.doLegend); - if(relayoutFlags.dolayoutstyle) seq.push(subroutines.layoutStyles); - if(relayoutFlags.doticks) seq.push(subroutines.doTicksRelayout); - if(relayoutFlags.domodebar) seq.push(subroutines.doModeBar); - if(relayoutFlags.doCamera) seq.push(subroutines.doCamera); - } - - Queue.add(gd, + gd.data = undefined + gd.layout = undefined + + seq.push(function () { return Plotly.plot(gd, data, layout) }) + } else if (restyleFlags.fullReplot) { + seq.push(Plotly.plot) + } else if (relayoutFlags.layoutReplot) { + seq.push(subroutines.layoutReplot) + } else { + seq.push(Plots.previousPromises) + Plots.supplyDefaults(gd) + + if (restyleFlags.dostyle) seq.push(subroutines.doTraceStyle) + if (restyleFlags.docolorbars) seq.push(subroutines.doColorBars) + if (relayoutFlags.dolegend) seq.push(subroutines.doLegend) + if (relayoutFlags.dolayoutstyle) seq.push(subroutines.layoutStyles) + if (relayoutFlags.doticks) seq.push(subroutines.doTicksRelayout) + if (relayoutFlags.domodebar) seq.push(subroutines.doModeBar) + if (relayoutFlags.doCamera) seq.push(subroutines.doCamera) + } + + Queue.add(gd, update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces], update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces] - ); + ) - var plotDone = Lib.syncOrAsync(seq, gd); - if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); + var plotDone = Lib.syncOrAsync(seq, gd) + if (!plotDone || !plotDone.then) plotDone = Promise.resolve(gd) - return plotDone.then(function() { - gd.emit('plotly_update', { - data: restyleSpecs.eventData, - layout: relayoutSpecs.eventData - }); + return plotDone.then(function () { + gd.emit('plotly_update', { + data: restyleSpecs.eventData, + layout: relayoutSpecs.eventData + }) - return gd; - }); -}; + return gd + }) +} /** * Animate to a frame, sequence of frame, frame group, or frame definition @@ -2172,350 +2106,349 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) { * @param {object} animationOpts * configuration for the animation */ -Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { - gd = helpers.getGraphDiv(gd); +Plotly.animate = function (gd, frameOrGroupNameOrFrameList, animationOpts) { + gd = helpers.getGraphDiv(gd) - if(!Lib.isPlotDiv(gd)) { - throw new Error( + if (!Lib.isPlotDiv(gd)) { + throw new Error( 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + 'to create a plot before animating it. For more details, see ' + 'https://plot.ly/javascript/animations/' - ); - } + ) + } - var trans = gd._transitionData; + var trans = gd._transitionData // This is the queue of frames that will be animated as soon as possible. They // are popped immediately upon the *start* of a transition: - if(!trans._frameQueue) { - trans._frameQueue = []; - } + if (!trans._frameQueue) { + trans._frameQueue = [] + } - animationOpts = Plots.supplyAnimationDefaults(animationOpts); - var transitionOpts = animationOpts.transition; - var frameOpts = animationOpts.frame; + animationOpts = Plots.supplyAnimationDefaults(animationOpts) + var transitionOpts = animationOpts.transition + var frameOpts = animationOpts.frame // Since frames are popped immediately, an empty queue only means all frames have // *started* to transition, not that the animation is complete. To solve that, // track a separate counter that increments at the same time as frames are added // to the queue, but decrements only when the transition is complete. - if(trans._frameWaitingCnt === undefined) { - trans._frameWaitingCnt = 0; - } - - function getTransitionOpts(i) { - if(Array.isArray(transitionOpts)) { - if(i >= transitionOpts.length) { - return transitionOpts[0]; - } else { - return transitionOpts[i]; - } - } else { - return transitionOpts; - } + if (trans._frameWaitingCnt === undefined) { + trans._frameWaitingCnt = 0 + } + + function getTransitionOpts (i) { + if (Array.isArray(transitionOpts)) { + if (i >= transitionOpts.length) { + return transitionOpts[0] + } else { + return transitionOpts[i] + } + } else { + return transitionOpts } + } - function getFrameOpts(i) { - if(Array.isArray(frameOpts)) { - if(i >= frameOpts.length) { - return frameOpts[0]; - } else { - return frameOpts[i]; - } - } else { - return frameOpts; - } + function getFrameOpts (i) { + if (Array.isArray(frameOpts)) { + if (i >= frameOpts.length) { + return frameOpts[0] + } else { + return frameOpts[i] + } + } else { + return frameOpts } + } // Execute a callback after the wrapper function has been called n times. // This is used to defer the resolution until a transition has resovled *and* // the frame has completed. If it's not done this way, then we get a race // condition in which the animation might resolve before a transition is complete // or vice versa. - function callbackOnNthTime(cb, n) { - var cnt = 0; - return function() { - if(cb && ++cnt === n) { - return cb(); - } - }; + function callbackOnNthTime (cb, n) { + var cnt = 0 + return function () { + if (cb && ++cnt === n) { + return cb() + } } + } - return new Promise(function(resolve, reject) { - function discardExistingFrames() { - if(trans._frameQueue.length === 0) { - return; - } + return new Promise(function (resolve, reject) { + function discardExistingFrames () { + if (trans._frameQueue.length === 0) { + return + } - while(trans._frameQueue.length) { - var next = trans._frameQueue.pop(); - if(next.onInterrupt) { - next.onInterrupt(); - } - } - - gd.emit('plotly_animationinterrupted', []); + while (trans._frameQueue.length) { + var next = trans._frameQueue.pop() + if (next.onInterrupt) { + next.onInterrupt() } + } - function queueFrames(frameList) { - if(frameList.length === 0) return; + gd.emit('plotly_animationinterrupted', []) + } - for(var i = 0; i < frameList.length; i++) { - var computedFrame; + function queueFrames (frameList) { + if (frameList.length === 0) return - if(frameList[i].type === 'byname') { + for (var i = 0; i < frameList.length; i++) { + var computedFrame + + if (frameList[i].type === 'byname') { // If it's a named frame, compute it: - computedFrame = Plots.computeFrame(gd, frameList[i].name); - } else { + computedFrame = Plots.computeFrame(gd, frameList[i].name) + } else { // Otherwise we must have been given a simple object, so treat // the input itself as the computed frame. - computedFrame = frameList[i].data; - } + computedFrame = frameList[i].data + } - var frameOpts = getFrameOpts(i); - var transitionOpts = getTransitionOpts(i); + var frameOpts = getFrameOpts(i) + var transitionOpts = getTransitionOpts(i) // It doesn't make much sense for the transition duration to be greater than // the frame duration, so limit it: - transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration); - - var nextFrame = { - frame: computedFrame, - name: frameList[i].name, - frameOpts: frameOpts, - transitionOpts: transitionOpts, - }; - if(i === frameList.length - 1) { + transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration) + + var nextFrame = { + frame: computedFrame, + name: frameList[i].name, + frameOpts: frameOpts, + transitionOpts: transitionOpts + } + if (i === frameList.length - 1) { // The last frame in this .animate call stores the promise resolve // and reject callbacks. This is how we ensure that the animation // loop (which may exist as a result of a *different* .animate call) // still resolves or rejecdts this .animate call's promise. once it's // complete. - nextFrame.onComplete = callbackOnNthTime(resolve, 2); - nextFrame.onInterrupt = reject; - } + nextFrame.onComplete = callbackOnNthTime(resolve, 2) + nextFrame.onInterrupt = reject + } - trans._frameQueue.push(nextFrame); - } + trans._frameQueue.push(nextFrame) + } // Set it as never having transitioned to a frame. This will cause the animation // loop to immediately transition to the next frame (which, for immediate mode, // is the first frame in the list since all others would have been discarded // below) - if(animationOpts.mode === 'immediate') { - trans._lastFrameAt = -Infinity; - } + if (animationOpts.mode === 'immediate') { + trans._lastFrameAt = -Infinity + } // Only it's not already running, start a RAF loop. This could be avoided in the // case that there's only one frame, but it significantly complicated the logic // and only sped things up by about 5% or so for a lorenz attractor simulation. // It would be a fine thing to implement, but the benefit of that optimization // doesn't seem worth the extra complexity. - if(!trans._animationRaf) { - beginAnimationLoop(); - } - } + if (!trans._animationRaf) { + beginAnimationLoop() + } + } - function stopAnimationLoop() { - gd.emit('plotly_animated'); + function stopAnimationLoop () { + gd.emit('plotly_animated') // Be sure to unset also since it's how we know whether a loop is already running: - window.cancelAnimationFrame(trans._animationRaf); - trans._animationRaf = null; - } + window.cancelAnimationFrame(trans._animationRaf) + trans._animationRaf = null + } - function nextFrame() { - if(trans._currentFrame && trans._currentFrame.onComplete) { + function nextFrame () { + if (trans._currentFrame && trans._currentFrame.onComplete) { // Execute the callback and unset it to ensure it doesn't // accidentally get called twice - trans._currentFrame.onComplete(); - } + trans._currentFrame.onComplete() + } - var newFrame = trans._currentFrame = trans._frameQueue.shift(); + var newFrame = trans._currentFrame = trans._frameQueue.shift() - if(newFrame) { + if (newFrame) { // Since it's sometimes necessary to do deep digging into frame data, // we'll consider it not 100% impossible for nulls or numbers to sneak through, // so check when casting the name, just to be absolutely certain: - var stringName = newFrame.name ? newFrame.name.toString() : null; - gd._fullLayout._currentFrame = stringName; + var stringName = newFrame.name ? newFrame.name.toString() : null + gd._fullLayout._currentFrame = stringName - trans._lastFrameAt = Date.now(); - trans._timeToNext = newFrame.frameOpts.duration; + trans._lastFrameAt = Date.now() + trans._timeToNext = newFrame.frameOpts.duration // This is simply called and it's left to .transition to decide how to manage // interrupting current transitions. That means we don't need to worry about // how it resolves or what happens after this: - Plots.transition(gd, + Plots.transition(gd, newFrame.frame.data, newFrame.frame.layout, helpers.coerceTraceIndices(gd, newFrame.frame.traces), newFrame.frameOpts, newFrame.transitionOpts - ).then(function() { - if(newFrame.onComplete) { - newFrame.onComplete(); - } - - }); - - gd.emit('plotly_animatingframe', { - name: stringName, - frame: newFrame.frame, - animation: { - frame: newFrame.frameOpts, - transition: newFrame.transitionOpts, - } - }); - } else { + ).then(function () { + if (newFrame.onComplete) { + newFrame.onComplete() + } + }) + + gd.emit('plotly_animatingframe', { + name: stringName, + frame: newFrame.frame, + animation: { + frame: newFrame.frameOpts, + transition: newFrame.transitionOpts + } + }) + } else { // If there are no more frames, then stop the RAF loop: - stopAnimationLoop(); - } - } + stopAnimationLoop() + } + } - function beginAnimationLoop() { - gd.emit('plotly_animating'); + function beginAnimationLoop () { + gd.emit('plotly_animating') // If no timer is running, then set last frame = long ago so that the next // frame is immediately transitioned: - trans._lastFrameAt = -Infinity; - trans._timeToNext = 0; - trans._runningTransitions = 0; - trans._currentFrame = null; + trans._lastFrameAt = -Infinity + trans._timeToNext = 0 + trans._runningTransitions = 0 + trans._currentFrame = null - var doFrame = function() { + var doFrame = function () { // This *must* be requested before nextFrame since nextFrame may decide // to cancel it if there's nothing more to animated: - trans._animationRaf = window.requestAnimationFrame(doFrame); + trans._animationRaf = window.requestAnimationFrame(doFrame) // Check if we're ready for a new frame: - if(Date.now() - trans._lastFrameAt > trans._timeToNext) { - nextFrame(); - } - }; - - doFrame(); + if (Date.now() - trans._lastFrameAt > trans._timeToNext) { + nextFrame() } + } + + doFrame() + } // This is an animate-local counter that helps match up option input list // items with the particular frame. - var configCounter = 0; - function setTransitionConfig(frame) { - if(Array.isArray(transitionOpts)) { - if(configCounter >= transitionOpts.length) { - frame.transitionOpts = transitionOpts[configCounter]; - } else { - frame.transitionOpts = transitionOpts[0]; - } - } else { - frame.transitionOpts = transitionOpts; - } - configCounter++; - return frame; + var configCounter = 0 + function setTransitionConfig (frame) { + if (Array.isArray(transitionOpts)) { + if (configCounter >= transitionOpts.length) { + frame.transitionOpts = transitionOpts[configCounter] + } else { + frame.transitionOpts = transitionOpts[0] } + } else { + frame.transitionOpts = transitionOpts + } + configCounter++ + return frame + } // Disambiguate what's sort of frames have been received - var i, frame; - var frameList = []; - var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null; - var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList); - var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList); + var i, frame + var frameList = [] + var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null + var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList) + var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList) - if(isSingleFrame) { + if (isSingleFrame) { // In this case, a simple object has been passed to animate. - frameList.push({ - type: 'object', - data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList)) - }); - } else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) { + frameList.push({ + type: 'object', + data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList)) + }) + } else if (allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) { // In this case, null or undefined has been passed so that we want to // animate *all* currently defined frames - for(i = 0; i < trans._frames.length; i++) { - frame = trans._frames[i]; - - if(!frame) continue; - - if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) { - frameList.push({ - type: 'byname', - name: String(frame.name), - data: setTransitionConfig({name: frame.name}) - }); - } - } - } else if(isFrameArray) { - for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) { - var frameOrName = frameOrGroupNameOrFrameList[i]; - if(['number', 'string'].indexOf(typeof frameOrName) !== -1) { - frameOrName = String(frameOrName); + for (i = 0; i < trans._frames.length; i++) { + frame = trans._frames[i] + + if (!frame) continue + + if (allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) { + frameList.push({ + type: 'byname', + name: String(frame.name), + data: setTransitionConfig({name: frame.name}) + }) + } + } + } else if (isFrameArray) { + for (i = 0; i < frameOrGroupNameOrFrameList.length; i++) { + var frameOrName = frameOrGroupNameOrFrameList[i] + if (['number', 'string'].indexOf(typeof frameOrName) !== -1) { + frameOrName = String(frameOrName) // In this case, there's an array and this frame is a string name: - frameList.push({ - type: 'byname', - name: frameOrName, - data: setTransitionConfig({name: frameOrName}) - }); - } else if(Lib.isPlainObject(frameOrName)) { - frameList.push({ - type: 'object', - data: setTransitionConfig(Lib.extendFlat({}, frameOrName)) - }); - } - } + frameList.push({ + type: 'byname', + name: frameOrName, + data: setTransitionConfig({name: frameOrName}) + }) + } else if (Lib.isPlainObject(frameOrName)) { + frameList.push({ + type: 'object', + data: setTransitionConfig(Lib.extendFlat({}, frameOrName)) + }) } + } + } // Verify that all of these frames actually exist; return and reject if not: - for(i = 0; i < frameList.length; i++) { - frame = frameList[i]; - if(frame.type === 'byname' && !trans._frameHash[frame.data.name]) { - Lib.warn('animate failure: frame not found: "' + frame.data.name + '"'); - reject(); - return; - } - } + for (i = 0; i < frameList.length; i++) { + frame = frameList[i] + if (frame.type === 'byname' && !trans._frameHash[frame.data.name]) { + Lib.warn('animate failure: frame not found: "' + frame.data.name + '"') + reject() + return + } + } // If the mode is either next or immediate, then all currently queued frames must // be dumped and the corresponding .animate promises rejected. - if(['next', 'immediate'].indexOf(animationOpts.mode) !== -1) { - discardExistingFrames(); - } + if (['next', 'immediate'].indexOf(animationOpts.mode) !== -1) { + discardExistingFrames() + } - if(animationOpts.direction === 'reverse') { - frameList.reverse(); - } + if (animationOpts.direction === 'reverse') { + frameList.reverse() + } - var currentFrame = gd._fullLayout._currentFrame; - if(currentFrame && animationOpts.fromcurrent) { - var idx = -1; - for(i = 0; i < frameList.length; i++) { - frame = frameList[i]; - if(frame.type === 'byname' && frame.name === currentFrame) { - idx = i; - break; - } - } + var currentFrame = gd._fullLayout._currentFrame + if (currentFrame && animationOpts.fromcurrent) { + var idx = -1 + for (i = 0; i < frameList.length; i++) { + frame = frameList[i] + if (frame.type === 'byname' && frame.name === currentFrame) { + idx = i + break + } + } - if(idx > 0 && idx < frameList.length - 1) { - var filteredFrameList = []; - for(i = 0; i < frameList.length; i++) { - frame = frameList[i]; - if(frameList[i].type !== 'byname' || i > idx) { - filteredFrameList.push(frame); - } - } - frameList = filteredFrameList; - } + if (idx > 0 && idx < frameList.length - 1) { + var filteredFrameList = [] + for (i = 0; i < frameList.length; i++) { + frame = frameList[i] + if (frameList[i].type !== 'byname' || i > idx) { + filteredFrameList.push(frame) + } } + frameList = filteredFrameList + } + } - if(frameList.length > 0) { - queueFrames(frameList); - } else { + if (frameList.length > 0) { + queueFrames(frameList) + } else { // This is the case where there were simply no frames. It's a little strange // since there's not much to do: - gd.emit('plotly_animated'); - resolve(); - } - }); -}; + gd.emit('plotly_animated') + resolve() + } + }) +} /** * Register new frames @@ -2536,120 +2469,118 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { * provided, an index will be provided in serial order. If already used, the frame * will be overwritten. */ -Plotly.addFrames = function(gd, frameList, indices) { - gd = helpers.getGraphDiv(gd); +Plotly.addFrames = function (gd, frameList, indices) { + gd = helpers.getGraphDiv(gd) - var numericNameWarningCount = 0; + var numericNameWarningCount = 0 - if(frameList === null || frameList === undefined) { - return Promise.resolve(); - } + if (frameList === null || frameList === undefined) { + return Promise.resolve() + } - if(!Lib.isPlotDiv(gd)) { - throw new Error( + if (!Lib.isPlotDiv(gd)) { + throw new Error( 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + 'to create a plot before adding frames. For more details, see ' + 'https://plot.ly/javascript/animations/' - ); - } - - var i, frame, j, idx; - var _frames = gd._transitionData._frames; - var _hash = gd._transitionData._frameHash; + ) + } + var i, frame, j, idx + var _frames = gd._transitionData._frames + var _hash = gd._transitionData._frameHash - if(!Array.isArray(frameList)) { - throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList); - } + if (!Array.isArray(frameList)) { + throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList) + } // Create a sorted list of insertions since we run into lots of problems if these // aren't in ascending order of index: // // Strictly for sorting. Make sure this is guaranteed to never collide with any // already-exisisting indices: - var bigIndex = _frames.length + frameList.length * 2; + var bigIndex = _frames.length + frameList.length * 2 - var insertions = []; - for(i = frameList.length - 1; i >= 0; i--) { - if(!Lib.isPlainObject(frameList[i])) continue; + var insertions = [] + for (i = frameList.length - 1; i >= 0; i--) { + if (!Lib.isPlainObject(frameList[i])) continue - var name = (_hash[frameList[i].name] || {}).name; - var newName = frameList[i].name; + var name = (_hash[frameList[i].name] || {}).name + var newName = frameList[i].name - if(name && newName && typeof newName === 'number' && _hash[name]) { - numericNameWarningCount++; + if (name && newName && typeof newName === 'number' && _hash[name]) { + numericNameWarningCount++ - Lib.warn('addFrames: overwriting frame "' + _hash[name].name + + Lib.warn('addFrames: overwriting frame "' + _hash[name].name + '" with a frame whose name of type "number" also equates to "' + name + '". This is valid but may potentially lead to unexpected ' + 'behavior since all plotly.js frame names are stored internally ' + - 'as strings.'); + 'as strings.') - if(numericNameWarningCount > 5) { - Lib.warn('addFrames: This API call has yielded too many warnings. ' + + if (numericNameWarningCount > 5) { + Lib.warn('addFrames: This API call has yielded too many warnings. ' + 'For the rest of this call, further warnings about numeric frame ' + - 'names will be suppressed.'); - } - } - - insertions.push({ - frame: Plots.supplyFrameDefaults(frameList[i]), - index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i - }); + 'names will be suppressed.') + } } - // Sort this, taking note that undefined insertions end up at the end: - insertions.sort(function(a, b) { - if(a.index > b.index) return -1; - if(a.index < b.index) return 1; - return 0; - }); + insertions.push({ + frame: Plots.supplyFrameDefaults(frameList[i]), + index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i + }) + } - var ops = []; - var revops = []; - var frameCount = _frames.length; + // Sort this, taking note that undefined insertions end up at the end: + insertions.sort(function (a, b) { + if (a.index > b.index) return -1 + if (a.index < b.index) return 1 + return 0 + }) - for(i = insertions.length - 1; i >= 0; i--) { - frame = insertions[i].frame; + var ops = [] + var revops = [] + var frameCount = _frames.length - if(typeof frame.name === 'number') { - Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' + - 'implicitly cast to strings'); + for (i = insertions.length - 1; i >= 0; i--) { + frame = insertions[i].frame - } + if (typeof frame.name === 'number') { + Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' + + 'implicitly cast to strings') + } - if(!frame.name) { + if (!frame.name) { // Repeatedly assign a default name, incrementing the counter each time until // we get a name that's not in the hashed lookup table: - while(_hash[(frame.name = 'frame ' + gd._transitionData._counter++)]); - } + while (_hash[(frame.name = 'frame ' + gd._transitionData._counter++)]); + } - if(_hash[frame.name]) { + if (_hash[frame.name]) { // If frame is present, overwrite its definition: - for(j = 0; j < _frames.length; j++) { - if((_frames[j] || {}).name === frame.name) break; - } - ops.push({type: 'replace', index: j, value: frame}); - revops.unshift({type: 'replace', index: j, value: _frames[j]}); - } else { + for (j = 0; j < _frames.length; j++) { + if ((_frames[j] || {}).name === frame.name) break + } + ops.push({type: 'replace', index: j, value: frame}) + revops.unshift({type: 'replace', index: j, value: _frames[j]}) + } else { // Otherwise insert it at the end of the list: - idx = Math.max(0, Math.min(insertions[i].index, frameCount)); + idx = Math.max(0, Math.min(insertions[i].index, frameCount)) - ops.push({type: 'insert', index: idx, value: frame}); - revops.unshift({type: 'delete', index: idx}); - frameCount++; - } + ops.push({type: 'insert', index: idx, value: frame}) + revops.unshift({type: 'delete', index: idx}) + frameCount++ } + } - var undoFunc = Plots.modifyFrames, - redoFunc = Plots.modifyFrames, - undoArgs = [gd, revops], - redoArgs = [gd, ops]; + var undoFunc = Plots.modifyFrames, + redoFunc = Plots.modifyFrames, + undoArgs = [gd, revops], + redoArgs = [gd, ops] - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + if (Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) - return Plots.modifyFrames(gd, ops); -}; + return Plots.modifyFrames(gd, ops) +} /** * Delete frame @@ -2660,36 +2591,36 @@ Plotly.addFrames = function(gd, frameList, indices) { * @param {array of integers} frameList * list of integer indices of frames to be deleted */ -Plotly.deleteFrames = function(gd, frameList) { - gd = helpers.getGraphDiv(gd); +Plotly.deleteFrames = function (gd, frameList) { + gd = helpers.getGraphDiv(gd) - if(!Lib.isPlotDiv(gd)) { - throw new Error('This element is not a Plotly plot: ' + gd); - } + if (!Lib.isPlotDiv(gd)) { + throw new Error('This element is not a Plotly plot: ' + gd) + } - var i, idx; - var _frames = gd._transitionData._frames; - var ops = []; - var revops = []; + var i, idx + var _frames = gd._transitionData._frames + var ops = [] + var revops = [] - frameList = frameList.slice(0); - frameList.sort(); + frameList = frameList.slice(0) + frameList.sort() - for(i = frameList.length - 1; i >= 0; i--) { - idx = frameList[i]; - ops.push({type: 'delete', index: idx}); - revops.unshift({type: 'insert', index: idx, value: _frames[idx]}); - } + for (i = frameList.length - 1; i >= 0; i--) { + idx = frameList[i] + ops.push({type: 'delete', index: idx}) + revops.unshift({type: 'insert', index: idx, value: _frames[idx]}) + } - var undoFunc = Plots.modifyFrames, - redoFunc = Plots.modifyFrames, - undoArgs = [gd, revops], - redoArgs = [gd, ops]; + var undoFunc = Plots.modifyFrames, + redoFunc = Plots.modifyFrames, + undoArgs = [gd, revops], + redoArgs = [gd, ops] - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + if (Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs) - return Plots.modifyFrames(gd, ops); -}; + return Plots.modifyFrames(gd, ops) +} /** * Purge a graph container div back to its initial pre-Plotly.plot state @@ -2697,132 +2628,132 @@ Plotly.deleteFrames = function(gd, frameList) { * @param {string id or DOM element} gd * the id or DOM element of the graph container div */ -Plotly.purge = function purge(gd) { - gd = helpers.getGraphDiv(gd); +Plotly.purge = function purge (gd) { + gd = helpers.getGraphDiv(gd) - var fullLayout = gd._fullLayout || {}, - fullData = gd._fullData || []; + var fullLayout = gd._fullLayout || {}, + fullData = gd._fullData || [] // remove gl contexts - Plots.cleanPlot([], {}, fullData, fullLayout); + Plots.cleanPlot([], {}, fullData, fullLayout) // purge properties - Plots.purge(gd); + Plots.purge(gd) // purge event emitter methods - Events.purge(gd); + Events.purge(gd) // remove plot container - if(fullLayout._container) fullLayout._container.remove(); + if (fullLayout._container) fullLayout._container.remove() - delete gd._context; - delete gd._replotPending; - delete gd._mouseDownTime; - delete gd._hmpixcount; - delete gd._hmlumcount; + delete gd._context + delete gd._replotPending + delete gd._mouseDownTime + delete gd._hmpixcount + delete gd._hmlumcount - return gd; -}; + return gd +} // ------------------------------------------------------- // makePlotFramework: Create the plot container and axes // ------------------------------------------------------- -function makePlotFramework(gd) { - var gd3 = d3.select(gd), - fullLayout = gd._fullLayout; +function makePlotFramework (gd) { + var gd3 = d3.select(gd), + fullLayout = gd._fullLayout // Plot container - fullLayout._container = gd3.selectAll('.plot-container').data([0]); - fullLayout._container.enter().insert('div', ':first-child') + fullLayout._container = gd3.selectAll('.plot-container').data([0]) + fullLayout._container.enter().insert('div', ':first-child') .classed('plot-container', true) - .classed('plotly', true); + .classed('plotly', true) // Make the svg container - fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]); - fullLayout._paperdiv.enter().append('div') + fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]) + fullLayout._paperdiv.enter().append('div') .classed('svg-container', true) - .style('position', 'relative'); + .style('position', 'relative') // Make the graph containers // start fresh each time we get here, so we know the order comes out // right, rather than enter/exit which can muck up the order // TODO: sort out all the ordering so we don't have to // explicitly delete anything - fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') - .data([0]); - fullLayout._glcontainer.enter().append('div') - .classed('gl-container', true); - - fullLayout._geocontainer = fullLayout._paperdiv.selectAll('.geo-container') - .data([0]); - fullLayout._geocontainer.enter().append('div') - .classed('geo-container', true); - - fullLayout._paperdiv.selectAll('.main-svg').remove(); - - fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') - .classed('main-svg', true); - - fullLayout._toppaper = fullLayout._paperdiv.append('svg') - .classed('main-svg', true); - - if(!fullLayout._uid) { - var otherUids = []; - d3.selectAll('defs').each(function() { - if(this.id) otherUids.push(this.id.split('-')[1]); - }); - fullLayout._uid = Lib.randstr(otherUids); - } + fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') + .data([0]) + fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true) + + fullLayout._geocontainer = fullLayout._paperdiv.selectAll('.geo-container') + .data([0]) + fullLayout._geocontainer.enter().append('div') + .classed('geo-container', true) + + fullLayout._paperdiv.selectAll('.main-svg').remove() + + fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') + .classed('main-svg', true) + + fullLayout._toppaper = fullLayout._paperdiv.append('svg') + .classed('main-svg', true) + + if (!fullLayout._uid) { + var otherUids = [] + d3.selectAll('defs').each(function () { + if (this.id) otherUids.push(this.id.split('-')[1]) + }) + fullLayout._uid = Lib.randstr(otherUids) + } - fullLayout._paperdiv.selectAll('.main-svg') - .attr(xmlnsNamespaces.svgAttrs); + fullLayout._paperdiv.selectAll('.main-svg') + .attr(xmlnsNamespaces.svgAttrs) - fullLayout._defs = fullLayout._paper.append('defs') - .attr('id', 'defs-' + fullLayout._uid); + fullLayout._defs = fullLayout._paper.append('defs') + .attr('id', 'defs-' + fullLayout._uid) - fullLayout._topdefs = fullLayout._toppaper.append('defs') - .attr('id', 'topdefs-' + fullLayout._uid); + fullLayout._topdefs = fullLayout._toppaper.append('defs') + .attr('id', 'topdefs-' + fullLayout._uid) - fullLayout._draggers = fullLayout._paper.append('g') - .classed('draglayer', true); + fullLayout._draggers = fullLayout._paper.append('g') + .classed('draglayer', true) // lower shape layer // (only for shapes to be drawn below the whole plot) - var layerBelow = fullLayout._paper.append('g') - .classed('layer-below', true); - fullLayout._imageLowerLayer = layerBelow.append('g') - .classed('imagelayer', true); - fullLayout._shapeLowerLayer = layerBelow.append('g') - .classed('shapelayer', true); + var layerBelow = fullLayout._paper.append('g') + .classed('layer-below', true) + fullLayout._imageLowerLayer = layerBelow.append('g') + .classed('imagelayer', true) + fullLayout._shapeLowerLayer = layerBelow.append('g') + .classed('shapelayer', true) // single cartesian layer for the whole plot - fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true); + fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true) // single ternary layer for the whole plot - fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true); + fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true) // upper shape layer // (only for shapes to be drawn above the whole plot, including subplots) - var layerAbove = fullLayout._paper.append('g') - .classed('layer-above', true); - fullLayout._imageUpperLayer = layerAbove.append('g') - .classed('imagelayer', true); - fullLayout._shapeUpperLayer = layerAbove.append('g') - .classed('shapelayer', true); + var layerAbove = fullLayout._paper.append('g') + .classed('layer-above', true) + fullLayout._imageUpperLayer = layerAbove.append('g') + .classed('imagelayer', true) + fullLayout._shapeUpperLayer = layerAbove.append('g') + .classed('shapelayer', true) // single pie layer for the whole plot - fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); + fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true) // fill in image server scrape-svg - fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true); - fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true); + fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true) + fullLayout._geoimages = fullLayout._paper.append('g').classed('geoimages', true) // lastly info (legend, annotations) and hover layers go on top // these are in a different svg element normally, but get collapsed into a single // svg when exporting (after inserting 3D) - fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true); - fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true); - fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true); + fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true) + fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true) + fullLayout._hoverlayer = fullLayout._toppaper.append('g').classed('hoverlayer', true) - gd.emit('plotly_framework'); + gd.emit('plotly_framework') } diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index d54dd1a05a6..213d1489d1e 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /* eslint-disable no-console */ @@ -21,98 +21,97 @@ module.exports = { // no interactivity, for export or image generation - staticPlot: false, + staticPlot: false, // we can edit titles, move annotations, etc - editable: false, + editable: false, // DO autosize once regardless of layout.autosize // (use default width or height values otherwise) - autosizable: false, + autosizable: false, // set the length of the undo/redo queue - queueLength: 0, + queueLength: 0, // if we DO autosize, do we fill the container or the screen? - fillFrame: false, + fillFrame: false, // if we DO autosize, set the frame margins in percents of plot size - frameMargins: 0, + frameMargins: 0, // mousewheel or two-finger scroll zooms the plot - scrollZoom: false, + scrollZoom: false, // double click interaction (false, 'reset', 'autosize' or 'reset+autosize') - doubleClick: 'reset+autosize', + doubleClick: 'reset+autosize', // new users see some hints about interactivity - showTips: true, + showTips: true, // link to open this plot in plotly - showLink: false, + showLink: false, // if we show a link, does it contain data or just link to a plotly file? - sendData: true, + sendData: true, // text appearing in the sendData link - linkText: 'Edit chart', + linkText: 'Edit chart', // false or function adding source(s) to linkText - showSources: false, + showSources: false, // display the mode bar (true, false, or 'hover') - displayModeBar: 'hover', + displayModeBar: 'hover', // remove mode bar button by name // (see ./components/modebar/buttons.js for the list of names) - modeBarButtonsToRemove: [], + modeBarButtonsToRemove: [], // add mode bar button using config objects // (see ./components/modebar/buttons.js for list of arguments) - modeBarButtonsToAdd: [], + modeBarButtonsToAdd: [], // fully custom mode bar buttons as nested array, // where the outer arrays represents button groups, and // the inner arrays have buttons config objects or names of default buttons // (see ./components/modebar/buttons.js for more info) - modeBarButtons: false, + modeBarButtons: false, // add the plotly logo on the end of the mode bar - displaylogo: true, + displaylogo: true, // increase the pixel ratio for Gl plot images - plotGlPixelRatio: 2, + plotGlPixelRatio: 2, // function to add the background color to a different container // or 'opaque' to ensure there's white behind it - setBackground: defaultSetBackground, + setBackground: defaultSetBackground, // URL to topojson files used in geo charts - topojsonURL: 'https://cdn.plot.ly/', + topojsonURL: 'https://cdn.plot.ly/', // Mapbox access token (required to plot mapbox trace types) // If using an Mapbox Atlas server, set this option to '', // so that plotly.js won't attempt to authenticate to the public Mapbox server. - mapboxAccessToken: null, + mapboxAccessToken: null, // Turn all console logging on or off (errors will be thrown) // This should ONLY be set via Plotly.setPlotConfig - logging: false, + logging: false, // Set global transform to be applied to all traces with no // specification needed - globalTransforms: [] -}; + globalTransforms: [] +} // where and how the background gets set can be overridden by context // so we define the default (plotly.js) behavior here -function defaultSetBackground(gd, bgColor) { - try { - gd._fullLayout._paper.style('background', bgColor); - } - catch(e) { - if(module.exports.logging > 0) { - console.error(e); - } +function defaultSetBackground (gd, bgColor) { + try { + gd._fullLayout._paper.style('background', bgColor) + } catch (e) { + if (module.exports.logging > 0) { + console.error(e) } + } } diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 2075674108a..ad663a83a08 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -6,33 +6,32 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../registry') +var Lib = require('../lib') -var Registry = require('../registry'); -var Lib = require('../lib'); - -var baseAttributes = require('../plots/attributes'); -var baseLayoutAttributes = require('../plots/layout_attributes'); -var frameAttributes = require('../plots/frame_attributes'); -var animationAttributes = require('../plots/animation_attributes'); +var baseAttributes = require('../plots/attributes') +var baseLayoutAttributes = require('../plots/layout_attributes') +var frameAttributes = require('../plots/frame_attributes') +var animationAttributes = require('../plots/animation_attributes') // polar attributes are not part of the Registry yet -var polarAreaAttrs = require('../plots/polar/area_attributes'); -var polarAxisAttrs = require('../plots/polar/axis_attributes'); +var polarAreaAttrs = require('../plots/polar/area_attributes') +var polarAxisAttrs = require('../plots/polar/axis_attributes') -var extendFlat = Lib.extendFlat; -var extendDeep = Lib.extendDeep; +var extendFlat = Lib.extendFlat +var extendDeep = Lib.extendDeep -var IS_SUBPLOT_OBJ = '_isSubplotObj'; -var IS_LINKED_TO_ARRAY = '_isLinkedToArray'; -var DEPRECATED = '_deprecated'; -var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, DEPRECATED]; +var IS_SUBPLOT_OBJ = '_isSubplotObj' +var IS_LINKED_TO_ARRAY = '_isLinkedToArray' +var DEPRECATED = '_deprecated' +var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, DEPRECATED] -exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ; -exports.IS_LINKED_TO_ARRAY = IS_LINKED_TO_ARRAY; -exports.DEPRECATED = DEPRECATED; -exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS; +exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ +exports.IS_LINKED_TO_ARRAY = IS_LINKED_TO_ARRAY +exports.DEPRECATED = DEPRECATED +exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS /** Outputs the full plotly.js plot schema * @@ -45,34 +44,34 @@ exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS; * - animations * - config (coming soon ...) */ -exports.get = function() { - var traces = {}; +exports.get = function () { + var traces = {} - Registry.allTypes.concat('area').forEach(function(type) { - traces[type] = getTraceAttributes(type); - }); + Registry.allTypes.concat('area').forEach(function (type) { + traces[type] = getTraceAttributes(type) + }) - var transforms = {}; + var transforms = {} - Object.keys(Registry.transformsRegistry).forEach(function(type) { - transforms[type] = getTransformAttributes(type); - }); + Object.keys(Registry.transformsRegistry).forEach(function (type) { + transforms[type] = getTransformAttributes(type) + }) - return { - defs: { - valObjects: Lib.valObjects, - metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']) - }, + return { + defs: { + valObjects: Lib.valObjects, + metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']) + }, - traces: traces, - layout: getLayoutAttributes(), + traces: traces, + layout: getLayoutAttributes(), - transforms: transforms, + transforms: transforms, - frames: getFramesAttributes(), - animation: formatAttributes(animationAttributes) - }; -}; + frames: getFramesAttributes(), + animation: formatAttributes(animationAttributes) + } +} /** * Crawl the attribute tree, recursively calling a callback function @@ -97,21 +96,21 @@ exports.get = function() { * @return {object} transformOut * copy of transformIn that contains attribute defaults */ -exports.crawl = function(attrs, callback, specifiedLevel) { - var level = specifiedLevel || 0; +exports.crawl = function (attrs, callback, specifiedLevel) { + var level = specifiedLevel || 0 - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; + Object.keys(attrs).forEach(function (attrName) { + var attr = attrs[attrName] - if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; + if (UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return - callback(attr, attrName, attrs, level); + callback(attr, attrName, attrs, level) - if(exports.isValObject(attr)) return; + if (exports.isValObject(attr)) return - if(Lib.isPlainObject(attr)) exports.crawl(attr, callback, level + 1); - }); -}; + if (Lib.isPlainObject(attr)) exports.crawl(attr, callback, level + 1) + }) +} /** Is object a value object (or a container object)? * @@ -120,9 +119,9 @@ exports.crawl = function(attrs, callback, specifiedLevel) { * returns true for a valid value object and * false for tree nodes in the attribute hierarchy */ -exports.isValObject = function(obj) { - return obj && obj.valType !== undefined; -}; +exports.isValObject = function (obj) { + return obj && obj.valType !== undefined +} /** * Find all data array attributes in a given trace object - including @@ -134,40 +133,40 @@ exports.isValObject = function(obj) { * @return {array} arrayAttributes * list of array attributes for the given trace */ -exports.findArrayAttributes = function(trace) { - var arrayAttributes = [], - stack = []; +exports.findArrayAttributes = function (trace) { + var arrayAttributes = [], + stack = [] - function callback(attr, attrName, attrs, level) { - stack = stack.slice(0, level).concat([attrName]); + function callback (attr, attrName, attrs, level) { + stack = stack.slice(0, level).concat([attrName]) - var splittableAttr = attr && (attr.valType === 'data_array' || attr.arrayOk === true); - if(!splittableAttr) return; + var splittableAttr = attr && (attr.valType === 'data_array' || attr.arrayOk === true) + if (!splittableAttr) return - var astr = toAttrString(stack); - var val = Lib.nestedProperty(trace, astr).get(); - if(!Array.isArray(val)) return; + var astr = toAttrString(stack) + var val = Lib.nestedProperty(trace, astr).get() + if (!Array.isArray(val)) return - arrayAttributes.push(astr); - } + arrayAttributes.push(astr) + } - function toAttrString(stack) { - return stack.join('.'); - } + function toAttrString (stack) { + return stack.join('.') + } - exports.crawl(trace._module.attributes, callback); + exports.crawl(trace._module.attributes, callback) - if(trace.transforms) { - var transforms = trace.transforms; + if (trace.transforms) { + var transforms = trace.transforms - for(var i = 0; i < transforms.length; i++) { - var transform = transforms[i]; + for (var i = 0; i < transforms.length; i++) { + var transform = transforms[i] - stack = ['transforms[' + i + ']']; + stack = ['transforms[' + i + ']'] - exports.crawl(transform._module.attributes, callback, 1); - } + exports.crawl(transform._module.attributes, callback, 1) } + } // Look into the fullInput module attributes for array attributes // to make sure that 'custom' array attributes are detected. @@ -175,232 +174,225 @@ exports.findArrayAttributes = function(trace) { // At the moment, we need this block to make sure that // ohlc and candlestick 'open', 'high', 'low', 'close' can be // used with filter ang groupby transforms. - if(trace._fullInput) { - exports.crawl(trace._fullInput._module.attributes, callback); + if (trace._fullInput) { + exports.crawl(trace._fullInput._module.attributes, callback) - arrayAttributes = Lib.filterUnique(arrayAttributes); - } + arrayAttributes = Lib.filterUnique(arrayAttributes) + } - return arrayAttributes; -}; + return arrayAttributes +} -function getTraceAttributes(type) { - var _module, basePlotModule; +function getTraceAttributes (type) { + var _module, basePlotModule - if(type === 'area') { - _module = { attributes: polarAreaAttrs }; - basePlotModule = {}; - } - else { - _module = Registry.modules[type]._module, - basePlotModule = _module.basePlotModule; - } + if (type === 'area') { + _module = { attributes: polarAreaAttrs } + basePlotModule = {} + } else { + _module = Registry.modules[type]._module, + basePlotModule = _module.basePlotModule + } - var attributes = {}; + var attributes = {} // make 'type' the first attribute in the object - attributes.type = null; + attributes.type = null // base attributes (same for all trace types) - extendDeep(attributes, baseAttributes); + extendDeep(attributes, baseAttributes) // module attributes - extendDeep(attributes, _module.attributes); + extendDeep(attributes, _module.attributes) // subplot attributes - if(basePlotModule.attributes) { - extendDeep(attributes, basePlotModule.attributes); - } + if (basePlotModule.attributes) { + extendDeep(attributes, basePlotModule.attributes) + } // add registered components trace attributes - Object.keys(Registry.componentsRegistry).forEach(function(k) { - var _module = Registry.componentsRegistry[k]; + Object.keys(Registry.componentsRegistry).forEach(function (k) { + var _module = Registry.componentsRegistry[k] - if(_module.schema && _module.schema.traces && _module.schema.traces[type]) { - Object.keys(_module.schema.traces[type]).forEach(function(v) { - insertAttrs(attributes, _module.schema.traces[type][v], v); - }); - } - }); + if (_module.schema && _module.schema.traces && _module.schema.traces[type]) { + Object.keys(_module.schema.traces[type]).forEach(function (v) { + insertAttrs(attributes, _module.schema.traces[type][v], v) + }) + } + }) // 'type' gets overwritten by baseAttributes; reset it here - attributes.type = type; + attributes.type = type - var out = { - meta: _module.meta || {}, - attributes: formatAttributes(attributes), - }; + var out = { + meta: _module.meta || {}, + attributes: formatAttributes(attributes) + } // trace-specific layout attributes - if(_module.layoutAttributes) { - var layoutAttributes = {}; + if (_module.layoutAttributes) { + var layoutAttributes = {} - extendDeep(layoutAttributes, _module.layoutAttributes); - out.layoutAttributes = formatAttributes(layoutAttributes); - } + extendDeep(layoutAttributes, _module.layoutAttributes) + out.layoutAttributes = formatAttributes(layoutAttributes) + } - return out; + return out } -function getLayoutAttributes() { - var layoutAttributes = {}; +function getLayoutAttributes () { + var layoutAttributes = {} // global layout attributes - extendDeep(layoutAttributes, baseLayoutAttributes); + extendDeep(layoutAttributes, baseLayoutAttributes) // add base plot module layout attributes - Object.keys(Registry.subplotsRegistry).forEach(function(k) { - var _module = Registry.subplotsRegistry[k]; + Object.keys(Registry.subplotsRegistry).forEach(function (k) { + var _module = Registry.subplotsRegistry[k] - if(!_module.layoutAttributes) return; + if (!_module.layoutAttributes) return - if(_module.name === 'cartesian') { - handleBasePlotModule(layoutAttributes, _module, 'xaxis'); - handleBasePlotModule(layoutAttributes, _module, 'yaxis'); - } - else { - var astr = _module.attr === 'subplot' ? _module.name : _module.attr; + if (_module.name === 'cartesian') { + handleBasePlotModule(layoutAttributes, _module, 'xaxis') + handleBasePlotModule(layoutAttributes, _module, 'yaxis') + } else { + var astr = _module.attr === 'subplot' ? _module.name : _module.attr - handleBasePlotModule(layoutAttributes, _module, astr); - } - }); + handleBasePlotModule(layoutAttributes, _module, astr) + } + }) // polar layout attributes - layoutAttributes = assignPolarLayoutAttrs(layoutAttributes); + layoutAttributes = assignPolarLayoutAttrs(layoutAttributes) // add registered components layout attributes - Object.keys(Registry.componentsRegistry).forEach(function(k) { - var _module = Registry.componentsRegistry[k]; + Object.keys(Registry.componentsRegistry).forEach(function (k) { + var _module = Registry.componentsRegistry[k] - if(!_module.layoutAttributes) return; + if (!_module.layoutAttributes) return - if(_module.schema && _module.schema.layout) { - Object.keys(_module.schema.layout).forEach(function(v) { - insertAttrs(layoutAttributes, _module.schema.layout[v], v); - }); - } - else { - insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name); - } - }); + if (_module.schema && _module.schema.layout) { + Object.keys(_module.schema.layout).forEach(function (v) { + insertAttrs(layoutAttributes, _module.schema.layout[v], v) + }) + } else { + insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name) + } + }) - return { - layoutAttributes: formatAttributes(layoutAttributes) - }; + return { + layoutAttributes: formatAttributes(layoutAttributes) + } } -function getTransformAttributes(type) { - var _module = Registry.transformsRegistry[type]; - var attributes = extendDeep({}, _module.attributes); +function getTransformAttributes (type) { + var _module = Registry.transformsRegistry[type] + var attributes = extendDeep({}, _module.attributes) // add registered components transform attributes - Object.keys(Registry.componentsRegistry).forEach(function(k) { - var _module = Registry.componentsRegistry[k]; + Object.keys(Registry.componentsRegistry).forEach(function (k) { + var _module = Registry.componentsRegistry[k] - if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) { - Object.keys(_module.schema.transforms[type]).forEach(function(v) { - insertAttrs(attributes, _module.schema.transforms[type][v], v); - }); - } - }); + if (_module.schema && _module.schema.transforms && _module.schema.transforms[type]) { + Object.keys(_module.schema.transforms[type]).forEach(function (v) { + insertAttrs(attributes, _module.schema.transforms[type][v], v) + }) + } + }) - return { - attributes: formatAttributes(attributes) - }; + return { + attributes: formatAttributes(attributes) + } } -function getFramesAttributes() { - var attrs = { - frames: Lib.extendDeep({}, frameAttributes) - }; +function getFramesAttributes () { + var attrs = { + frames: Lib.extendDeep({}, frameAttributes) + } - formatAttributes(attrs); + formatAttributes(attrs) - return attrs.frames; + return attrs.frames } -function formatAttributes(attrs) { - mergeValTypeAndRole(attrs); - formatArrayContainers(attrs); +function formatAttributes (attrs) { + mergeValTypeAndRole(attrs) + formatArrayContainers(attrs) - return attrs; + return attrs } -function mergeValTypeAndRole(attrs) { - - function makeSrcAttr(attrName) { - return { - valType: 'string', - role: 'info', - description: [ - 'Sets the source reference on plot.ly for ', - attrName, '.' - ].join(' ') - }; +function mergeValTypeAndRole (attrs) { + function makeSrcAttr (attrName) { + return { + valType: 'string', + role: 'info', + description: [ + 'Sets the source reference on plot.ly for ', + attrName, '.' + ].join(' ') } + } - function callback(attr, attrName, attrs) { - if(exports.isValObject(attr)) { - if(attr.valType === 'data_array') { + function callback (attr, attrName, attrs) { + if (exports.isValObject(attr)) { + if (attr.valType === 'data_array') { // all 'data_array' attrs have role 'data' - attr.role = 'data'; + attr.role = 'data' // all 'data_array' attrs have a corresponding 'src' attr - attrs[attrName + 'src'] = makeSrcAttr(attrName); - } - else if(attr.arrayOk === true) { + attrs[attrName + 'src'] = makeSrcAttr(attrName) + } else if (attr.arrayOk === true) { // all 'arrayOk' attrs have a corresponding 'src' attr - attrs[attrName + 'src'] = makeSrcAttr(attrName); - } - } - else if(Lib.isPlainObject(attr)) { + attrs[attrName + 'src'] = makeSrcAttr(attrName) + } + } else if (Lib.isPlainObject(attr)) { // all attrs container objects get role 'object' - attr.role = 'object'; - } + attr.role = 'object' } + } - exports.crawl(attrs, callback); + exports.crawl(attrs, callback) } -function formatArrayContainers(attrs) { +function formatArrayContainers (attrs) { + function callback (attr, attrName, attrs) { + if (!attr) return - function callback(attr, attrName, attrs) { - if(!attr) return; + var itemName = attr[IS_LINKED_TO_ARRAY] - var itemName = attr[IS_LINKED_TO_ARRAY]; + if (!itemName) return - if(!itemName) return; + delete attr[IS_LINKED_TO_ARRAY] - delete attr[IS_LINKED_TO_ARRAY]; - - attrs[attrName] = { items: {} }; - attrs[attrName].items[itemName] = attr; - attrs[attrName].role = 'object'; - } + attrs[attrName] = { items: {} } + attrs[attrName].items[itemName] = attr + attrs[attrName].role = 'object' + } - exports.crawl(attrs, callback); + exports.crawl(attrs, callback) } -function assignPolarLayoutAttrs(layoutAttributes) { - extendFlat(layoutAttributes, { - radialaxis: polarAxisAttrs.radialaxis, - angularaxis: polarAxisAttrs.angularaxis - }); +function assignPolarLayoutAttrs (layoutAttributes) { + extendFlat(layoutAttributes, { + radialaxis: polarAxisAttrs.radialaxis, + angularaxis: polarAxisAttrs.angularaxis + }) - extendFlat(layoutAttributes, polarAxisAttrs.layout); + extendFlat(layoutAttributes, polarAxisAttrs.layout) - return layoutAttributes; + return layoutAttributes } -function handleBasePlotModule(layoutAttributes, _module, astr) { - var np = Lib.nestedProperty(layoutAttributes, astr), - attrs = extendDeep({}, _module.layoutAttributes); +function handleBasePlotModule (layoutAttributes, _module, astr) { + var np = Lib.nestedProperty(layoutAttributes, astr), + attrs = extendDeep({}, _module.layoutAttributes) - attrs[IS_SUBPLOT_OBJ] = true; - np.set(attrs); + attrs[IS_SUBPLOT_OBJ] = true + np.set(attrs) } -function insertAttrs(baseAttrs, newAttrs, astr) { - var np = Lib.nestedProperty(baseAttrs, astr); +function insertAttrs (baseAttrs, newAttrs, astr) { + var np = Lib.nestedProperty(baseAttrs, astr) - np.set(extendDeep(np.get() || {}, newAttrs)); + np.set(extendDeep(np.get() || {}, newAttrs)) } diff --git a/src/plot_api/register.js b/src/plot_api/register.js index 87dae2e49b2..c3c26161db7 100644 --- a/src/plot_api/register.js +++ b/src/plot_api/register.js @@ -6,92 +6,89 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../registry'); -var Lib = require('../lib'); +var Registry = require('../registry') +var Lib = require('../lib') +module.exports = function register (_modules) { + if (!_modules) { + throw new Error('No argument passed to Plotly.register.') + } else if (_modules && !Array.isArray(_modules)) { + _modules = [_modules] + } -module.exports = function register(_modules) { - if(!_modules) { - throw new Error('No argument passed to Plotly.register.'); - } - else if(_modules && !Array.isArray(_modules)) { - _modules = [_modules]; - } + for (var i = 0; i < _modules.length; i++) { + var newModule = _modules[i] - for(var i = 0; i < _modules.length; i++) { - var newModule = _modules[i]; - - if(!newModule) { - throw new Error('Invalid module was attempted to be registered!'); - } + if (!newModule) { + throw new Error('Invalid module was attempted to be registered!') + } - switch(newModule.moduleType) { - case 'trace': - registerTraceModule(newModule); - break; + switch (newModule.moduleType) { + case 'trace': + registerTraceModule(newModule) + break - case 'transform': - registerTransformModule(newModule); - break; + case 'transform': + registerTransformModule(newModule) + break - case 'component': - registerComponentModule(newModule); - break; + case 'component': + registerComponentModule(newModule) + break - default: - throw new Error('Invalid module was attempted to be registered!'); - } + default: + throw new Error('Invalid module was attempted to be registered!') } -}; + } +} -function registerTraceModule(newModule) { - Registry.register(newModule, newModule.name, newModule.categories, newModule.meta); +function registerTraceModule (newModule) { + Registry.register(newModule, newModule.name, newModule.categories, newModule.meta) - if(!Registry.subplotsRegistry[newModule.basePlotModule.name]) { - Registry.registerSubplot(newModule.basePlotModule); - } + if (!Registry.subplotsRegistry[newModule.basePlotModule.name]) { + Registry.registerSubplot(newModule.basePlotModule) + } } -function registerTransformModule(newModule) { - if(typeof newModule.name !== 'string') { - throw new Error('Transform module *name* must be a string.'); - } +function registerTransformModule (newModule) { + if (typeof newModule.name !== 'string') { + throw new Error('Transform module *name* must be a string.') + } - var prefix = 'Transform module ' + newModule.name; + var prefix = 'Transform module ' + newModule.name - var hasTransform = typeof newModule.transform === 'function', - hasCalcTransform = typeof newModule.calcTransform === 'function'; + var hasTransform = typeof newModule.transform === 'function', + hasCalcTransform = typeof newModule.calcTransform === 'function' + if (!hasTransform && !hasCalcTransform) { + throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.') + } - if(!hasTransform && !hasCalcTransform) { - throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.'); - } - - if(hasTransform && hasCalcTransform) { - Lib.log([ - prefix + ' has both a *transform* and *calcTransform* methods.', - 'Please note that all *transform* methods are executed', - 'before all *calcTransform* methods.' - ].join(' ')); - } + if (hasTransform && hasCalcTransform) { + Lib.log([ + prefix + ' has both a *transform* and *calcTransform* methods.', + 'Please note that all *transform* methods are executed', + 'before all *calcTransform* methods.' + ].join(' ')) + } - if(!Lib.isPlainObject(newModule.attributes)) { - Lib.log(prefix + ' registered without an *attributes* object.'); - } + if (!Lib.isPlainObject(newModule.attributes)) { + Lib.log(prefix + ' registered without an *attributes* object.') + } - if(typeof newModule.supplyDefaults !== 'function') { - Lib.log(prefix + ' registered without a *supplyDefaults* method.'); - } + if (typeof newModule.supplyDefaults !== 'function') { + Lib.log(prefix + ' registered without a *supplyDefaults* method.') + } - Registry.transformsRegistry[newModule.name] = newModule; + Registry.transformsRegistry[newModule.name] = newModule } -function registerComponentModule(newModule) { - if(typeof newModule.name !== 'string') { - throw new Error('Component module *name* must be a string.'); - } +function registerComponentModule (newModule) { + if (typeof newModule.name !== 'string') { + throw new Error('Component module *name* must be a string.') + } - Registry.registerComponent(newModule); + Registry.registerComponent(newModule) } diff --git a/src/plot_api/set_plot_config.js b/src/plot_api/set_plot_config.js index e4f9e058ca4..ad58280e8eb 100644 --- a/src/plot_api/set_plot_config.js +++ b/src/plot_api/set_plot_config.js @@ -6,11 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Plotly = require('../plotly'); -var Lib = require('../lib'); +var Plotly = require('../plotly') +var Lib = require('../lib') /** * Extends the plot config @@ -19,6 +18,6 @@ var Lib = require('../lib'); * to extend the current plot configuration. * */ -module.exports = function setPlotConfig(configObj) { - return Lib.extendFlat(Plotly.defaultConfig, configObj); -}; +module.exports = function setPlotConfig (configObj) { + return Lib.extendFlat(Plotly.defaultConfig, configObj) +} diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index ceb2a4dbf64..f52dd8c3c82 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -6,172 +6,167 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Plotly = require('../plotly') +var Registry = require('../registry') +var Plots = require('../plots/plots') +var Lib = require('../lib') -var Plotly = require('../plotly'); -var Registry = require('../registry'); -var Plots = require('../plots/plots'); -var Lib = require('../lib'); +var Color = require('../components/color') +var Drawing = require('../components/drawing') +var Titles = require('../components/titles') +var ModeBar = require('../components/modebar') -var Color = require('../components/color'); -var Drawing = require('../components/drawing'); -var Titles = require('../components/titles'); -var ModeBar = require('../components/modebar'); +exports.layoutStyles = function (gd) { + return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd) +} - -exports.layoutStyles = function(gd) { - return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd); -}; - -exports.lsInner = function(gd) { - var fullLayout = gd._fullLayout, - gs = fullLayout._size, - axList = Plotly.Axes.list(gd), - i; +exports.lsInner = function (gd) { + var fullLayout = gd._fullLayout, + gs = fullLayout._size, + axList = Plotly.Axes.list(gd), + i // clear axis line positions, to be set in the subplot loop below - for(i = 0; i < axList.length; i++) axList[i]._linepositions = {}; + for (i = 0; i < axList.length; i++) axList[i]._linepositions = {} - fullLayout._paperdiv + fullLayout._paperdiv .style({ - width: fullLayout.width + 'px', - height: fullLayout.height + 'px' + width: fullLayout.width + 'px', + height: fullLayout.height + 'px' }) .selectAll('.main-svg') - .call(Drawing.setSize, fullLayout.width, fullLayout.height); + .call(Drawing.setSize, fullLayout.width, fullLayout.height) - gd._context.setBackground(gd, fullLayout.paper_bgcolor); + gd._context.setBackground(gd, fullLayout.paper_bgcolor) - var freefinished = []; - fullLayout._paper.selectAll('g.subplot').each(function(subplot) { - var plotinfo = fullLayout._plots[subplot], - xa = Plotly.Axes.getFromId(gd, subplot, 'x'), - ya = Plotly.Axes.getFromId(gd, subplot, 'y'); + var freefinished = [] + fullLayout._paper.selectAll('g.subplot').each(function (subplot) { + var plotinfo = fullLayout._plots[subplot], + xa = Plotly.Axes.getFromId(gd, subplot, 'x'), + ya = Plotly.Axes.getFromId(gd, subplot, 'y') - xa.setScale(); // this may already be done... not sure - ya.setScale(); + xa.setScale() // this may already be done... not sure + ya.setScale() - if(plotinfo.bg) { - plotinfo.bg + if (plotinfo.bg) { + plotinfo.bg .call(Drawing.setRect, xa._offset - gs.p, ya._offset - gs.p, xa._length + 2 * gs.p, ya._length + 2 * gs.p) - .call(Color.fill, fullLayout.plot_bgcolor); - } + .call(Color.fill, fullLayout.plot_bgcolor) + } // Clip so that data only shows up on the plot area. - plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; + plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot' - var plotClip = fullLayout._defs.selectAll('g.clips') + var plotClip = fullLayout._defs.selectAll('g.clips') .selectAll('#' + plotinfo.clipId) - .data([0]); + .data([0]) - plotClip.enter().append('clipPath') + plotClip.enter().append('clipPath') .attr({ - 'class': 'plotclip', - 'id': plotinfo.clipId + 'class': 'plotclip', + 'id': plotinfo.clipId }) - .append('rect'); + .append('rect') - plotClip.selectAll('rect') + plotClip.selectAll('rect') .attr({ - 'width': xa._length, - 'height': ya._length - }); - + 'width': xa._length, + 'height': ya._length + }) - plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset); - plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId); + plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset) + plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId) - var xlw = Drawing.crispRound(gd, xa.linewidth, 1), - ylw = Drawing.crispRound(gd, ya.linewidth, 1), - xp = gs.p + ylw, - xpathPrefix = 'M' + (-xp) + ',', - xpathSuffix = 'h' + (xa._length + 2 * xp), - showfreex = xa.anchor === 'free' && + var xlw = Drawing.crispRound(gd, xa.linewidth, 1), + ylw = Drawing.crispRound(gd, ya.linewidth, 1), + xp = gs.p + ylw, + xpathPrefix = 'M' + (-xp) + ',', + xpathSuffix = 'h' + (xa._length + 2 * xp), + showfreex = xa.anchor === 'free' && freefinished.indexOf(xa._id) === -1, - freeposx = gs.h * (1 - (xa.position||0)) + ((xlw / 2) % 1), - showbottom = + freeposx = gs.h * (1 - (xa.position || 0)) + ((xlw / 2) % 1), + showbottom = (xa.anchor === ya._id && (xa.mirror || xa.side !== 'top')) || xa.mirror === 'all' || xa.mirror === 'allticks' || (xa.mirrors && xa.mirrors[ya._id + 'bottom']), - bottompos = ya._length + gs.p + xlw / 2, - showtop = + bottompos = ya._length + gs.p + xlw / 2, + showtop = (xa.anchor === ya._id && (xa.mirror || xa.side === 'top')) || xa.mirror === 'all' || xa.mirror === 'allticks' || (xa.mirrors && xa.mirrors[ya._id + 'top']), - toppos = -gs.p - xlw / 2, + toppos = -gs.p - xlw / 2, // shorten y axis lines so they don't overlap x axis lines - yp = gs.p, + yp = gs.p, // except where there's no x line // TODO: this gets more complicated with multiple x and y axes - ypbottom = showbottom ? 0 : xlw, - yptop = showtop ? 0 : xlw, - ypathSuffix = ',' + (-yp - yptop) + + ypbottom = showbottom ? 0 : xlw, + yptop = showtop ? 0 : xlw, + ypathSuffix = ',' + (-yp - yptop) + 'v' + (ya._length + 2 * yp + yptop + ypbottom), - showfreey = ya.anchor === 'free' && + showfreey = ya.anchor === 'free' && freefinished.indexOf(ya._id) === -1, - freeposy = gs.w * (ya.position||0) + ((ylw / 2) % 1), - showleft = + freeposy = gs.w * (ya.position || 0) + ((ylw / 2) % 1), + showleft = (ya.anchor === xa._id && (ya.mirror || ya.side !== 'right')) || ya.mirror === 'all' || ya.mirror === 'allticks' || (ya.mirrors && ya.mirrors[xa._id + 'left']), - leftpos = -gs.p - ylw / 2, - showright = + leftpos = -gs.p - ylw / 2, + showright = (ya.anchor === xa._id && (ya.mirror || ya.side === 'right')) || ya.mirror === 'all' || ya.mirror === 'allticks' || (ya.mirrors && ya.mirrors[xa._id + 'right']), - rightpos = xa._length + gs.p + ylw / 2; + rightpos = xa._length + gs.p + ylw / 2 // save axis line positions for ticks, draggers, etc to reference // each subplot gets an entry: // [left or bottom, right or top, free, main] // main is the position at which to draw labels and draggers, if any - xa._linepositions[subplot] = [ - showbottom ? bottompos : undefined, - showtop ? toppos : undefined, - showfreex ? freeposx : undefined - ]; - if(xa.anchor === ya._id) { - xa._linepositions[subplot][3] = xa.side === 'top' ? - toppos : bottompos; - } - else if(showfreex) { - xa._linepositions[subplot][3] = freeposx; - } - - ya._linepositions[subplot] = [ - showleft ? leftpos : undefined, - showright ? rightpos : undefined, - showfreey ? freeposy : undefined - ]; - if(ya.anchor === xa._id) { - ya._linepositions[subplot][3] = ya.side === 'right' ? - rightpos : leftpos; - } - else if(showfreey) { - ya._linepositions[subplot][3] = freeposy; - } + xa._linepositions[subplot] = [ + showbottom ? bottompos : undefined, + showtop ? toppos : undefined, + showfreex ? freeposx : undefined + ] + if (xa.anchor === ya._id) { + xa._linepositions[subplot][3] = xa.side === 'top' ? + toppos : bottompos + } else if (showfreex) { + xa._linepositions[subplot][3] = freeposx + } + + ya._linepositions[subplot] = [ + showleft ? leftpos : undefined, + showright ? rightpos : undefined, + showfreey ? freeposy : undefined + ] + if (ya.anchor === xa._id) { + ya._linepositions[subplot][3] = ya.side === 'right' ? + rightpos : leftpos + } else if (showfreey) { + ya._linepositions[subplot][3] = freeposy + } // translate all the extra stuff to have the // same origin as the plot area or axes - var origin = 'translate(' + xa._offset + ',' + ya._offset + ')', - originx = origin, - originy = origin; - if(showfreex) { - originx = 'translate(' + xa._offset + ',' + gs.t + ')'; - toppos += ya._offset - gs.t; - bottompos += ya._offset - gs.t; - } - if(showfreey) { - originy = 'translate(' + gs.l + ',' + ya._offset + ')'; - leftpos += xa._offset - gs.l; - rightpos += xa._offset - gs.l; - } - - plotinfo.xlines + var origin = 'translate(' + xa._offset + ',' + ya._offset + ')', + originx = origin, + originy = origin + if (showfreex) { + originx = 'translate(' + xa._offset + ',' + gs.t + ')' + toppos += ya._offset - gs.t + bottompos += ya._offset - gs.t + } + if (showfreey) { + originy = 'translate(' + gs.l + ',' + ya._offset + ')' + leftpos += xa._offset - gs.l + rightpos += xa._offset - gs.l + } + + plotinfo.xlines .attr('transform', originx) .attr('d', ( (showbottom ? (xpathPrefix + bottompos + xpathSuffix) : '') + @@ -181,8 +176,8 @@ exports.lsInner = function(gd) { 'M0,0') .style('stroke-width', xlw + 'px') .call(Color.stroke, xa.showline ? - xa.linecolor : 'rgba(0,0,0,0)'); - plotinfo.ylines + xa.linecolor : 'rgba(0,0,0,0)') + plotinfo.ylines .attr('transform', originy) .attr('d', ( (showleft ? ('M' + leftpos + ypathSuffix) : '') + @@ -191,138 +186,137 @@ exports.lsInner = function(gd) { 'M0,0') .attr('stroke-width', ylw + 'px') .call(Color.stroke, ya.showline ? - ya.linecolor : 'rgba(0,0,0,0)'); + ya.linecolor : 'rgba(0,0,0,0)') - plotinfo.xaxislayer.attr('transform', originx); - plotinfo.yaxislayer.attr('transform', originy); - plotinfo.gridlayer.attr('transform', origin); - plotinfo.zerolinelayer.attr('transform', origin); - plotinfo.draglayer.attr('transform', origin); + plotinfo.xaxislayer.attr('transform', originx) + plotinfo.yaxislayer.attr('transform', originy) + plotinfo.gridlayer.attr('transform', origin) + plotinfo.zerolinelayer.attr('transform', origin) + plotinfo.draglayer.attr('transform', origin) // mark free axes as displayed, so we don't draw them again - if(showfreex) { freefinished.push(xa._id); } - if(showfreey) { freefinished.push(ya._id); } - }); - - Plotly.Axes.makeClipPaths(gd); - exports.drawMainTitle(gd); - ModeBar.manage(gd); - - return gd._promises.length && Promise.all(gd._promises); -}; - -exports.drawMainTitle = function(gd) { - var fullLayout = gd._fullLayout; - - Titles.draw(gd, 'gtitle', { - propContainer: fullLayout, - propName: 'title', - dfltName: 'Plot', - attributes: { - x: fullLayout.width / 2, - y: fullLayout._size.t / 2, - 'text-anchor': 'middle' - } - }); -}; + if (showfreex) { freefinished.push(xa._id) } + if (showfreey) { freefinished.push(ya._id) } + }) + + Plotly.Axes.makeClipPaths(gd) + exports.drawMainTitle(gd) + ModeBar.manage(gd) + + return gd._promises.length && Promise.all(gd._promises) +} + +exports.drawMainTitle = function (gd) { + var fullLayout = gd._fullLayout + + Titles.draw(gd, 'gtitle', { + propContainer: fullLayout, + propName: 'title', + dfltName: 'Plot', + attributes: { + x: fullLayout.width / 2, + y: fullLayout._size.t / 2, + 'text-anchor': 'middle' + } + }) +} // First, see if we need to do arraysToCalcdata // call it regardless of what change we made, in case // supplyDefaults brought in an array that was already // in gd.data but not in gd._fullData previously -exports.doTraceStyle = function(gd) { - for(var i = 0; i < gd.calcdata.length; i++) { - var cdi = gd.calcdata[i], - _module = ((cdi[0] || {}).trace || {})._module || {}, - arraysToCalcdata = _module.arraysToCalcdata; +exports.doTraceStyle = function (gd) { + for (var i = 0; i < gd.calcdata.length; i++) { + var cdi = gd.calcdata[i], + _module = ((cdi[0] || {}).trace || {})._module || {}, + arraysToCalcdata = _module.arraysToCalcdata - if(arraysToCalcdata) arraysToCalcdata(cdi, cdi[0].trace); - } + if (arraysToCalcdata) arraysToCalcdata(cdi, cdi[0].trace) + } - Plots.style(gd); - Registry.getComponentMethod('legend', 'draw')(gd); + Plots.style(gd) + Registry.getComponentMethod('legend', 'draw')(gd) - return Plots.previousPromises(gd); -}; + return Plots.previousPromises(gd) +} -exports.doColorBars = function(gd) { - for(var i = 0; i < gd.calcdata.length; i++) { - var cdi0 = gd.calcdata[i][0]; +exports.doColorBars = function (gd) { + for (var i = 0; i < gd.calcdata.length; i++) { + var cdi0 = gd.calcdata[i][0] - if((cdi0.t || {}).cb) { - var trace = cdi0.trace, - cb = cdi0.t.cb; + if ((cdi0.t || {}).cb) { + var trace = cdi0.trace, + cb = cdi0.t.cb - if(Registry.traceIs(trace, 'contour')) { - cb.line({ - width: trace.contours.showlines !== false ? + if (Registry.traceIs(trace, 'contour')) { + cb.line({ + width: trace.contours.showlines !== false ? trace.line.width : 0, - dash: trace.line.dash, - color: trace.contours.coloring === 'line' ? + dash: trace.line.dash, + color: trace.contours.coloring === 'line' ? cb._opts.line.color : trace.line.color - }); - } - if(Registry.traceIs(trace, 'markerColorscale')) { - cb.options(trace.marker.colorbar)(); - } - else cb.options(trace.colorbar)(); - } + }) + } + if (Registry.traceIs(trace, 'markerColorscale')) { + cb.options(trace.marker.colorbar)() + } else cb.options(trace.colorbar)() } + } - return Plots.previousPromises(gd); -}; + return Plots.previousPromises(gd) +} // force plot() to redo the layout and replot with the modified layout -exports.layoutReplot = function(gd) { - var layout = gd.layout; - gd.layout = undefined; - return Plotly.plot(gd, '', layout); -}; - -exports.doLegend = function(gd) { - Registry.getComponentMethod('legend', 'draw')(gd); - return Plots.previousPromises(gd); -}; - -exports.doTicksRelayout = function(gd) { - Plotly.Axes.doTicks(gd, 'redraw'); - exports.drawMainTitle(gd); - return Plots.previousPromises(gd); -}; - -exports.doModeBar = function(gd) { - var fullLayout = gd._fullLayout; - var subplotIds, i; - - ModeBar.manage(gd); - Plotly.Fx.init(gd); - - subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d'); - for(i = 0; i < subplotIds.length; i++) { - var scene = fullLayout[subplotIds[i]]._scene; - scene.updateFx(fullLayout.dragmode, fullLayout.hovermode); - } +exports.layoutReplot = function (gd) { + var layout = gd.layout + gd.layout = undefined + return Plotly.plot(gd, '', layout) +} + +exports.doLegend = function (gd) { + Registry.getComponentMethod('legend', 'draw')(gd) + return Plots.previousPromises(gd) +} + +exports.doTicksRelayout = function (gd) { + Plotly.Axes.doTicks(gd, 'redraw') + exports.drawMainTitle(gd) + return Plots.previousPromises(gd) +} + +exports.doModeBar = function (gd) { + var fullLayout = gd._fullLayout + var subplotIds, i + + ModeBar.manage(gd) + Plotly.Fx.init(gd) + + subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d') + for (i = 0; i < subplotIds.length; i++) { + var scene = fullLayout[subplotIds[i]]._scene + scene.updateFx(fullLayout.dragmode, fullLayout.hovermode) + } // no need to do this for gl2d subplots, // Plots.linkSubplots takes care of it all. - subplotIds = Plots.getSubplotIds(fullLayout, 'geo'); - for(i = 0; i < subplotIds.length; i++) { - var geo = fullLayout[subplotIds[i]]._subplot; - geo.updateFx(fullLayout.hovermode); - } + subplotIds = Plots.getSubplotIds(fullLayout, 'geo') + for (i = 0; i < subplotIds.length; i++) { + var geo = fullLayout[subplotIds[i]]._subplot + geo.updateFx(fullLayout.hovermode) + } - return Plots.previousPromises(gd); -}; + return Plots.previousPromises(gd) +} -exports.doCamera = function(gd) { - var fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'); +exports.doCamera = function (gd) { + var fullLayout = gd._fullLayout, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d') - for(var i = 0; i < sceneIds.length; i++) { - var sceneLayout = fullLayout[sceneIds[i]], - scene = sceneLayout._scene; + for (var i = 0; i < sceneIds.length; i++) { + var sceneLayout = fullLayout[sceneIds[i]], + scene = sceneLayout._scene - scene.setCamera(sceneLayout.camera); - } -}; + scene.setCamera(sceneLayout.camera) + } +} diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index 6ebcf75b367..216625e4ab1 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -6,17 +6,17 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var isNumeric = require('fast-isnumeric'); +var isNumeric = require('fast-isnumeric') -var Plotly = require('../plotly'); -var Lib = require('../lib'); +var Plotly = require('../plotly') +var Lib = require('../lib') -var helpers = require('../snapshot/helpers'); -var clonePlot = require('../snapshot/cloneplot'); -var toSVG = require('../snapshot/tosvg'); -var svgToImg = require('../snapshot/svgtoimg'); +var helpers = require('../snapshot/helpers') +var clonePlot = require('../snapshot/cloneplot') +var toSVG = require('../snapshot/tosvg') +var svgToImg = require('../snapshot/svgtoimg') /** * @param {object} gd figure Object @@ -25,84 +25,82 @@ var svgToImg = require('../snapshot/svgtoimg'); * @param opts.width width of snapshot in px * @param opts.height height of snapshot in px */ -function toImage(gd, opts) { - - var promise = new Promise(function(resolve, reject) { +function toImage (gd, opts) { + var promise = new Promise(function (resolve, reject) { // check for undefined opts - opts = opts || {}; + opts = opts || {} // default to png - opts.format = opts.format || 'png'; + opts.format = opts.format || 'png' - var isSizeGood = function(size) { + var isSizeGood = function (size) { // undefined and null are valid options - if(size === undefined || size === null) { - return true; - } + if (size === undefined || size === null) { + return true + } - if(isNumeric(size) && size > 1) { - return true; - } + if (isNumeric(size) && size > 1) { + return true + } - return false; - }; + return false + } - if(!isSizeGood(opts.width) || !isSizeGood(opts.height)) { - reject(new Error('Height and width should be pixel values.')); - } + if (!isSizeGood(opts.width) || !isSizeGood(opts.height)) { + reject(new Error('Height and width should be pixel values.')) + } // first clone the GD so we can operate in a clean environment - var clone = clonePlot(gd, {format: 'png', height: opts.height, width: opts.width}); - var clonedGd = clone.gd; + var clone = clonePlot(gd, {format: 'png', height: opts.height, width: opts.width}) + var clonedGd = clone.gd // put the cloned div somewhere off screen before attaching to DOM - clonedGd.style.position = 'absolute'; - clonedGd.style.left = '-5000px'; - document.body.appendChild(clonedGd); - - function wait() { - var delay = helpers.getDelay(clonedGd._fullLayout); - - return new Promise(function(resolve, reject) { - setTimeout(function() { - var svg = toSVG(clonedGd); - - var canvas = document.createElement('canvas'); - canvas.id = Lib.randstr(); - - svgToImg({ - format: opts.format, - width: clonedGd._fullLayout.width, - height: clonedGd._fullLayout.height, - canvas: canvas, - svg: svg, + clonedGd.style.position = 'absolute' + clonedGd.style.left = '-5000px' + document.body.appendChild(clonedGd) + + function wait () { + var delay = helpers.getDelay(clonedGd._fullLayout) + + return new Promise(function (resolve, reject) { + setTimeout(function () { + var svg = toSVG(clonedGd) + + var canvas = document.createElement('canvas') + canvas.id = Lib.randstr() + + svgToImg({ + format: opts.format, + width: clonedGd._fullLayout.width, + height: clonedGd._fullLayout.height, + canvas: canvas, + svg: svg, // ask svgToImg to return a Promise // rather than EventEmitter // leave EventEmitter for backward // compatibility - promise: true - }).then(function(url) { - if(clonedGd) document.body.removeChild(clonedGd); - resolve(url); - }).catch(function(err) { - reject(err); - }); - - }, delay); - }); - } - - var redrawFunc = helpers.getRedrawFunc(clonedGd); - - Plotly.plot(clonedGd, clone.data, clone.layout, clone.config) + promise: true + }).then(function (url) { + if (clonedGd) document.body.removeChild(clonedGd) + resolve(url) + }).catch(function (err) { + reject(err) + }) + }, delay) + }) + } + + var redrawFunc = helpers.getRedrawFunc(clonedGd) + + Plotly.plot(clonedGd, clone.data, clone.layout, clone.config) .then(redrawFunc) .then(wait) - .then(function(url) { resolve(url); }) - .catch(function(err) { - reject(err); - }); - }); + .then(function (url) { resolve(url) }) + .catch(function (err) { + reject(err) + }) + }) - return promise; + return promise } -module.exports = toImage; +module.exports = toImage diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js index b06e5506f4d..ff139319d29 100644 --- a/src/plot_api/validate.js +++ b/src/plot_api/validate.js @@ -6,17 +6,14 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - - -var Lib = require('../lib'); -var Plots = require('../plots/plots'); -var PlotSchema = require('./plot_schema'); - -var isPlainObject = Lib.isPlainObject; -var isArray = Array.isArray; +var Lib = require('../lib') +var Plots = require('../plots/plots') +var PlotSchema = require('./plot_schema') +var isPlainObject = Lib.isPlainObject +var isArray = Array.isArray /** * Validate a data array and layout object. @@ -39,330 +36,318 @@ var isArray = Array.isArray; * - {string} msg * error message (shown in console in logger config argument is enable) */ -module.exports = function valiate(data, layout) { - var schema = PlotSchema.get(), - errorList = [], - gd = {}; - - var dataIn, layoutIn; - - if(isArray(data)) { - gd.data = Lib.extendDeep([], data); - dataIn = data; - } - else { - gd.data = []; - dataIn = []; - errorList.push(format('array', 'data')); - } - - if(isPlainObject(layout)) { - gd.layout = Lib.extendDeep({}, layout); - layoutIn = layout; - } - else { - gd.layout = {}; - layoutIn = {}; - if(arguments.length > 1) { - errorList.push(format('object', 'layout')); - } +module.exports = function valiate (data, layout) { + var schema = PlotSchema.get(), + errorList = [], + gd = {} + + var dataIn, layoutIn + + if (isArray(data)) { + gd.data = Lib.extendDeep([], data) + dataIn = data + } else { + gd.data = [] + dataIn = [] + errorList.push(format('array', 'data')) + } + + if (isPlainObject(layout)) { + gd.layout = Lib.extendDeep({}, layout) + layoutIn = layout + } else { + gd.layout = {} + layoutIn = {} + if (arguments.length > 1) { + errorList.push(format('object', 'layout')) } + } // N.B. dataIn and layoutIn are in general not the same as // gd.data and gd.layout after supplyDefaults as some attributes // in gd.data and gd.layout (still) get mutated during this step. - Plots.supplyDefaults(gd); + Plots.supplyDefaults(gd) - var dataOut = gd._fullData, - len = dataIn.length; + var dataOut = gd._fullData, + len = dataIn.length - for(var i = 0; i < len; i++) { - var traceIn = dataIn[i], - base = ['data', i]; + for (var i = 0; i < len; i++) { + var traceIn = dataIn[i], + base = ['data', i] - if(!isPlainObject(traceIn)) { - errorList.push(format('object', base)); - continue; - } + if (!isPlainObject(traceIn)) { + errorList.push(format('object', base)) + continue + } - var traceOut = dataOut[i], - traceType = traceOut.type, - traceSchema = schema.traces[traceType].attributes; + var traceOut = dataOut[i], + traceType = traceOut.type, + traceSchema = schema.traces[traceType].attributes // PlotSchema does something fancy with trace 'type', reset it here // to make the trace schema compatible with Lib.validate. - traceSchema.type = { - valType: 'enumerated', - values: [traceType] - }; + traceSchema.type = { + valType: 'enumerated', + values: [traceType] + } - if(traceOut.visible === false && traceIn.visible !== false) { - errorList.push(format('invisible', base)); - } + if (traceOut.visible === false && traceIn.visible !== false) { + errorList.push(format('invisible', base)) + } - crawl(traceIn, traceOut, traceSchema, errorList, base); + crawl(traceIn, traceOut, traceSchema, errorList, base) - var transformsIn = traceIn.transforms, - transformsOut = traceOut.transforms; + var transformsIn = traceIn.transforms, + transformsOut = traceOut.transforms - if(transformsIn) { - if(!isArray(transformsIn)) { - errorList.push(format('array', base, ['transforms'])); - } + if (transformsIn) { + if (!isArray(transformsIn)) { + errorList.push(format('array', base, ['transforms'])) + } - base.push('transforms'); + base.push('transforms') - for(var j = 0; j < transformsIn.length; j++) { - var path = ['transforms', j], - transformType = transformsIn[j].type; + for (var j = 0; j < transformsIn.length; j++) { + var path = ['transforms', j], + transformType = transformsIn[j].type - if(!isPlainObject(transformsIn[j])) { - errorList.push(format('object', base, path)); - continue; - } + if (!isPlainObject(transformsIn[j])) { + errorList.push(format('object', base, path)) + continue + } - var transformSchema = schema.transforms[transformType] ? + var transformSchema = schema.transforms[transformType] ? schema.transforms[transformType].attributes : - {}; + {} // add 'type' to transform schema to validate the transform type - transformSchema.type = { - valType: 'enumerated', - values: Object.keys(schema.transforms) - }; - - crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path); - } + transformSchema.type = { + valType: 'enumerated', + values: Object.keys(schema.transforms) } + + crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path) + } } + } - var layoutOut = gd._fullLayout, - layoutSchema = fillLayoutSchema(schema, dataOut); + var layoutOut = gd._fullLayout, + layoutSchema = fillLayoutSchema(schema, dataOut) - crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout'); + crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout') // return undefined if no validation errors were found - return (errorList.length === 0) ? void(0) : errorList; -}; + return (errorList.length === 0) ? void (0) : errorList +} -function crawl(objIn, objOut, schema, list, base, path) { - path = path || []; +function crawl (objIn, objOut, schema, list, base, path) { + path = path || [] - var keys = Object.keys(objIn); + var keys = Object.keys(objIn) - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; + for (var i = 0; i < keys.length; i++) { + var k = keys[i] // transforms are handled separately - if(k === 'transforms') continue; + if (k === 'transforms') continue - var p = path.slice(); - p.push(k); + var p = path.slice() + p.push(k) - var valIn = objIn[k], - valOut = objOut[k]; + var valIn = objIn[k], + valOut = objOut[k] - var nestedSchema = getNestedSchema(schema, k), - isInfoArray = (nestedSchema || {}).valType === 'info_array'; + var nestedSchema = getNestedSchema(schema, k), + isInfoArray = (nestedSchema || {}).valType === 'info_array' - if(!isInSchema(schema, k)) { - list.push(format('schema', base, p)); - } - else if(isPlainObject(valIn) && isPlainObject(valOut)) { - crawl(valIn, valOut, nestedSchema, list, base, p); - } - else if(nestedSchema.items && !isInfoArray && isArray(valIn)) { - var items = nestedSchema.items, - _nestedSchema = items[Object.keys(items)[0]], - indexList = []; + if (!isInSchema(schema, k)) { + list.push(format('schema', base, p)) + } else if (isPlainObject(valIn) && isPlainObject(valOut)) { + crawl(valIn, valOut, nestedSchema, list, base, p) + } else if (nestedSchema.items && !isInfoArray && isArray(valIn)) { + var items = nestedSchema.items, + _nestedSchema = items[Object.keys(items)[0]], + indexList = [] - var j, _p; + var j, _p // loop over valOut items while keeping track of their // corresponding input container index (given by _index) - for(j = 0; j < valOut.length; j++) { - var _index = valOut[j]._index || j; + for (j = 0; j < valOut.length; j++) { + var _index = valOut[j]._index || j - _p = p.slice(); - _p.push(_index); + _p = p.slice() + _p.push(_index) - if(isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) { - indexList.push(_index); - crawl(valIn[_index], valOut[j], _nestedSchema, list, base, _p); - } - } + if (isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) { + indexList.push(_index) + crawl(valIn[_index], valOut[j], _nestedSchema, list, base, _p) + } + } // loop over valIn to determine where it went wrong for some items - for(j = 0; j < valIn.length; j++) { - _p = p.slice(); - _p.push(j); - - if(!isPlainObject(valIn[j])) { - list.push(format('object', base, _p, valIn[j])); - } - else if(indexList.indexOf(j) === -1) { - list.push(format('unused', base, _p)); - } - } - } - else if(!isPlainObject(valIn) && isPlainObject(valOut)) { - list.push(format('object', base, p, valIn)); - } - else if(!isArray(valIn) && isArray(valOut) && !isInfoArray) { - list.push(format('array', base, p, valIn)); - } - else if(!(k in objOut)) { - list.push(format('unused', base, p, valIn)); - } - else if(!Lib.validate(valIn, nestedSchema)) { - list.push(format('value', base, p, valIn)); + for (j = 0; j < valIn.length; j++) { + _p = p.slice() + _p.push(j) + + if (!isPlainObject(valIn[j])) { + list.push(format('object', base, _p, valIn[j])) + } else if (indexList.indexOf(j) === -1) { + list.push(format('unused', base, _p)) } + } + } else if (!isPlainObject(valIn) && isPlainObject(valOut)) { + list.push(format('object', base, p, valIn)) + } else if (!isArray(valIn) && isArray(valOut) && !isInfoArray) { + list.push(format('array', base, p, valIn)) + } else if (!(k in objOut)) { + list.push(format('unused', base, p, valIn)) + } else if (!Lib.validate(valIn, nestedSchema)) { + list.push(format('value', base, p, valIn)) } + } - return list; + return list } // the 'full' layout schema depends on the traces types presents -function fillLayoutSchema(schema, dataOut) { - for(var i = 0; i < dataOut.length; i++) { - var traceType = dataOut[i].type, - traceLayoutAttr = schema.traces[traceType].layoutAttributes; +function fillLayoutSchema (schema, dataOut) { + for (var i = 0; i < dataOut.length; i++) { + var traceType = dataOut[i].type, + traceLayoutAttr = schema.traces[traceType].layoutAttributes - if(traceLayoutAttr) { - Lib.extendFlat(schema.layout.layoutAttributes, traceLayoutAttr); - } + if (traceLayoutAttr) { + Lib.extendFlat(schema.layout.layoutAttributes, traceLayoutAttr) } + } - return schema.layout.layoutAttributes; + return schema.layout.layoutAttributes } // validation error codes var code2msgFunc = { - object: function(base, astr) { - var prefix; - - if(base === 'layout' && astr === '') prefix = 'The layout argument'; - else if(base[0] === 'data' && astr === '') { - prefix = 'Trace ' + base[1] + ' in the data argument'; - } - else prefix = inBase(base) + 'key ' + astr; - - return prefix + ' must be linked to an object container'; - }, - array: function(base, astr) { - var prefix; - - if(base === 'data') prefix = 'The data argument'; - else prefix = inBase(base) + 'key ' + astr; - - return prefix + ' must be linked to an array container'; - }, - schema: function(base, astr) { - return inBase(base) + 'key ' + astr + ' is not part of the schema'; - }, - unused: function(base, astr, valIn) { - var target = isPlainObject(valIn) ? 'container' : 'key'; - - return inBase(base) + target + ' ' + astr + ' did not get coerced'; - }, - invisible: function(base) { - return 'Trace ' + base[1] + ' got defaulted to be not visible'; - }, - value: function(base, astr, valIn) { - return [ - inBase(base) + 'key ' + astr, - 'is set to an invalid value (' + valIn + ')' - ].join(' '); - } -}; + object: function (base, astr) { + var prefix + + if (base === 'layout' && astr === '') prefix = 'The layout argument' + else if (base[0] === 'data' && astr === '') { + prefix = 'Trace ' + base[1] + ' in the data argument' + } else prefix = inBase(base) + 'key ' + astr + + return prefix + ' must be linked to an object container' + }, + array: function (base, astr) { + var prefix + + if (base === 'data') prefix = 'The data argument' + else prefix = inBase(base) + 'key ' + astr + + return prefix + ' must be linked to an array container' + }, + schema: function (base, astr) { + return inBase(base) + 'key ' + astr + ' is not part of the schema' + }, + unused: function (base, astr, valIn) { + var target = isPlainObject(valIn) ? 'container' : 'key' + + return inBase(base) + target + ' ' + astr + ' did not get coerced' + }, + invisible: function (base) { + return 'Trace ' + base[1] + ' got defaulted to be not visible' + }, + value: function (base, astr, valIn) { + return [ + inBase(base) + 'key ' + astr, + 'is set to an invalid value (' + valIn + ')' + ].join(' ') + } +} -function inBase(base) { - if(isArray(base)) return 'In data trace ' + base[1] + ', '; +function inBase (base) { + if (isArray(base)) return 'In data trace ' + base[1] + ', ' - return 'In ' + base + ', '; + return 'In ' + base + ', ' } -function format(code, base, path, valIn) { - path = path || ''; +function format (code, base, path, valIn) { + path = path || '' - var container, trace; + var container, trace // container is either 'data' or 'layout // trace is the trace index if 'data', null otherwise - if(isArray(base)) { - container = base[0]; - trace = base[1]; - } - else { - container = base; - trace = null; - } + if (isArray(base)) { + container = base[0] + trace = base[1] + } else { + container = base + trace = null + } - var astr = convertPathToAttributeString(path), - msg = code2msgFunc[code](base, astr, valIn); + var astr = convertPathToAttributeString(path), + msg = code2msgFunc[code](base, astr, valIn) // log to console if logger config option is enabled - Lib.log(msg); - - return { - code: code, - container: container, - trace: trace, - path: path, - astr: astr, - msg: msg - }; + Lib.log(msg) + + return { + code: code, + container: container, + trace: trace, + path: path, + astr: astr, + msg: msg + } } -function isInSchema(schema, key) { - var parts = splitKey(key), - keyMinusId = parts.keyMinusId, - id = parts.id; +function isInSchema (schema, key) { + var parts = splitKey(key), + keyMinusId = parts.keyMinusId, + id = parts.id - if((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) { - return true; - } + if ((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) { + return true + } - return (key in schema); + return (key in schema) } -function getNestedSchema(schema, key) { - var parts = splitKey(key); +function getNestedSchema (schema, key) { + var parts = splitKey(key) - return schema[parts.keyMinusId]; + return schema[parts.keyMinusId] } -function splitKey(key) { - var idRegex = /([2-9]|[1-9][0-9]+)$/; +function splitKey (key) { + var idRegex = /([2-9]|[1-9][0-9]+)$/ - var keyMinusId = key.split(idRegex)[0], - id = key.substr(keyMinusId.length, key.length); + var keyMinusId = key.split(idRegex)[0], + id = key.substr(keyMinusId.length, key.length) - return { - keyMinusId: keyMinusId, - id: id - }; + return { + keyMinusId: keyMinusId, + id: id + } } -function convertPathToAttributeString(path) { - if(!isArray(path)) return String(path); +function convertPathToAttributeString (path) { + if (!isArray(path)) return String(path) - var astr = ''; + var astr = '' - for(var i = 0; i < path.length; i++) { - var p = path[i]; + for (var i = 0; i < path.length; i++) { + var p = path[i] - if(typeof p === 'number') { - astr = astr.substr(0, astr.length - 1) + '[' + p + ']'; - } - else { - astr += p; - } - - if(i < path.length - 1) astr += '.'; + if (typeof p === 'number') { + astr = astr.substr(0, astr.length - 1) + '[' + p + ']' + } else { + astr += p } - return astr; + if (i < path.length - 1) astr += '.' + } + + return astr } diff --git a/src/plotly.js b/src/plotly.js index 042166b05bc..ec2c10b6af4 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' /* * Pack internal modules unto an object. @@ -19,13 +19,13 @@ */ // configuration -exports.defaultConfig = require('./plot_api/plot_config'); +exports.defaultConfig = require('./plot_api/plot_config') // plots -exports.Plots = require('./plots/plots'); -exports.Axes = require('./plots/cartesian/axes'); -exports.Fx = require('./plots/cartesian/graph_interact'); -exports.ModeBar = require('./components/modebar'); +exports.Plots = require('./plots/plots') +exports.Axes = require('./plots/cartesian/axes') +exports.Fx = require('./plots/cartesian/graph_interact') +exports.ModeBar = require('./components/modebar') // plot api -require('./plot_api/plot_api'); +require('./plot_api/plot_api') diff --git a/src/plots/animation_attributes.js b/src/plots/animation_attributes.js index 8b7316270cc..cb7e3da74d9 100644 --- a/src/plots/animation_attributes.js +++ b/src/plots/animation_attributes.js @@ -6,117 +6,117 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - mode: { - valType: 'enumerated', - dflt: 'afterall', - role: 'info', - values: ['immediate', 'next', 'afterall'], - description: [ - 'Describes how a new animate call interacts with currently-running', - 'animations. If `immediate`, current animations are interrupted and', - 'the new animation is started. If `next`, the current frame is allowed', - 'to complete, after which the new animation is started. If `afterall`', - 'all existing frames are animated to completion before the new animation', - 'is started.' - ].join(' ') + mode: { + valType: 'enumerated', + dflt: 'afterall', + role: 'info', + values: ['immediate', 'next', 'afterall'], + description: [ + 'Describes how a new animate call interacts with currently-running', + 'animations. If `immediate`, current animations are interrupted and', + 'the new animation is started. If `next`, the current frame is allowed', + 'to complete, after which the new animation is started. If `afterall`', + 'all existing frames are animated to completion before the new animation', + 'is started.' + ].join(' ') + }, + direction: { + valType: 'enumerated', + role: 'info', + values: ['forward', 'reverse'], + dflt: 'forward', + description: [ + 'The direction in which to play the frames triggered by the animation call' + ].join(' ') + }, + fromcurrent: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Play frames starting at the current frame instead of the beginning.' + ].join(' ') + }, + frame: { + duration: { + valType: 'number', + role: 'info', + min: 0, + dflt: 500, + description: [ + 'The duration in milliseconds of each frame. If greater than the frame', + 'duration, it will be limited to the frame duration.' + ].join(' ') }, - direction: { - valType: 'enumerated', - role: 'info', - values: ['forward', 'reverse'], - dflt: 'forward', - description: [ - 'The direction in which to play the frames triggered by the animation call' - ].join(' ') - }, - fromcurrent: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Play frames starting at the current frame instead of the beginning.' - ].join(' ') - }, - frame: { - duration: { - valType: 'number', - role: 'info', - min: 0, - dflt: 500, - description: [ - 'The duration in milliseconds of each frame. If greater than the frame', - 'duration, it will be limited to the frame duration.' - ].join(' ') - }, - redraw: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Redraw the plot at completion of the transition. This is desirable', - 'for transitions that include properties that cannot be transitioned,', - 'but may significantly slow down updates that do not require a full', - 'redraw of the plot' - ].join(' ') - }, + redraw: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Redraw the plot at completion of the transition. This is desirable', + 'for transitions that include properties that cannot be transitioned,', + 'but may significantly slow down updates that do not require a full', + 'redraw of the plot' + ].join(' ') + } + }, + transition: { + duration: { + valType: 'number', + role: 'info', + min: 0, + dflt: 500, + description: [ + 'The duration of the transition, in milliseconds. If equal to zero,', + 'updates are synchronous.' + ].join(' ') }, - transition: { - duration: { - valType: 'number', - role: 'info', - min: 0, - dflt: 500, - description: [ - 'The duration of the transition, in milliseconds. If equal to zero,', - 'updates are synchronous.' - ].join(' ') - }, - easing: { - valType: 'enumerated', - dflt: 'cubic-in-out', - values: [ - 'linear', - 'quad', - 'cubic', - 'sin', - 'exp', - 'circle', - 'elastic', - 'back', - 'bounce', - 'linear-in', - 'quad-in', - 'cubic-in', - 'sin-in', - 'exp-in', - 'circle-in', - 'elastic-in', - 'back-in', - 'bounce-in', - 'linear-out', - 'quad-out', - 'cubic-out', - 'sin-out', - 'exp-out', - 'circle-out', - 'elastic-out', - 'back-out', - 'bounce-out', - 'linear-in-out', - 'quad-in-out', - 'cubic-in-out', - 'sin-in-out', - 'exp-in-out', - 'circle-in-out', - 'elastic-in-out', - 'back-in-out', - 'bounce-in-out' - ], - role: 'info', - description: 'The easing function used for the transition' - }, + easing: { + valType: 'enumerated', + dflt: 'cubic-in-out', + values: [ + 'linear', + 'quad', + 'cubic', + 'sin', + 'exp', + 'circle', + 'elastic', + 'back', + 'bounce', + 'linear-in', + 'quad-in', + 'cubic-in', + 'sin-in', + 'exp-in', + 'circle-in', + 'elastic-in', + 'back-in', + 'bounce-in', + 'linear-out', + 'quad-out', + 'cubic-out', + 'sin-out', + 'exp-out', + 'circle-out', + 'elastic-out', + 'back-out', + 'bounce-out', + 'linear-in-out', + 'quad-in-out', + 'cubic-in-out', + 'sin-in-out', + 'exp-in-out', + 'circle-in-out', + 'elastic-in-out', + 'back-in-out', + 'bounce-in-out' + ], + role: 'info', + description: 'The easing function used for the transition' } -}; + } +} diff --git a/src/plots/array_container_defaults.js b/src/plots/array_container_defaults.js index 2754ed613c2..decdf19a904 100644 --- a/src/plots/array_container_defaults.js +++ b/src/plots/array_container_defaults.js @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var Lib = require('../lib'); +'use strict' +var Lib = require('../lib') /** Convenience wrapper for making array container logic DRY and consistent * @@ -41,27 +40,27 @@ var Lib = require('../lib'); * links to supplementary data (e.g. fullData for layout components) * */ -module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) { - var name = opts.name; +module.exports = function handleArrayContainerDefaults (parentObjIn, parentObjOut, opts) { + var name = opts.name - var contIn = Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [], - contOut = parentObjOut[name] = []; + var contIn = Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [], + contOut = parentObjOut[name] = [] - for(var i = 0; i < contIn.length; i++) { - var itemIn = contIn[i], - itemOut = {}, - itemOpts = {}; + for (var i = 0; i < contIn.length; i++) { + var itemIn = contIn[i], + itemOut = {}, + itemOpts = {} - if(!Lib.isPlainObject(itemIn)) { - itemOpts.itemIsNotPlainObject = true; - itemIn = {}; - } + if (!Lib.isPlainObject(itemIn)) { + itemOpts.itemIsNotPlainObject = true + itemIn = {} + } - opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts); + opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts) - itemOut._input = itemIn; - itemOut._index = i; + itemOut._input = itemIn + itemOut._index = i - contOut.push(itemOut); - } -}; + contOut.push(itemOut) + } +} diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 594fba13a6d..33f6e23a0ee 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -6,103 +6,102 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - type: { - valType: 'enumerated', - role: 'info', - values: [], // listed dynamically - dflt: 'scatter' - }, - visible: { - valType: 'enumerated', - values: [true, false, 'legendonly'], - role: 'info', - dflt: true, - description: [ - 'Determines whether or not this trace is visible.', - 'If *legendonly*, the trace is not drawn,', - 'but can appear as a legend item', - '(provided that the legend itself is visible).' - ].join(' ') - }, - showlegend: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not an item corresponding to this', - 'trace is shown in the legend.' - ].join(' ') - }, - legendgroup: { - valType: 'string', - role: 'info', - dflt: '', - description: [ - 'Sets the legend group for this trace.', - 'Traces part of the same legend group hide/show at the same time', - 'when toggling legend items.' - ].join(' ') - }, - opacity: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the opacity of the trace.' - }, - name: { - valType: 'string', - role: 'info', - description: [ - 'Sets the trace name.', - 'The trace name appear as the legend item and on hover.' - ].join(' ') - }, - uid: { - valType: 'string', - role: 'info', - dflt: '' - }, - hoverinfo: { - valType: 'flaglist', - role: 'info', - flags: ['x', 'y', 'z', 'text', 'name'], - extras: ['all', 'none', 'skip'], - dflt: 'all', - description: [ - 'Determines which trace information appear on hover.', - 'If `none` or `skip` are set, no information is displayed upon hovering.', - 'But, if `none` is set, click and hover events are still fired.' - ].join(' ') + type: { + valType: 'enumerated', + role: 'info', + values: [], // listed dynamically + dflt: 'scatter' + }, + visible: { + valType: 'enumerated', + values: [true, false, 'legendonly'], + role: 'info', + dflt: true, + description: [ + 'Determines whether or not this trace is visible.', + 'If *legendonly*, the trace is not drawn,', + 'but can appear as a legend item', + '(provided that the legend itself is visible).' + ].join(' ') + }, + showlegend: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not an item corresponding to this', + 'trace is shown in the legend.' + ].join(' ') + }, + legendgroup: { + valType: 'string', + role: 'info', + dflt: '', + description: [ + 'Sets the legend group for this trace.', + 'Traces part of the same legend group hide/show at the same time', + 'when toggling legend items.' + ].join(' ') + }, + opacity: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of the trace.' + }, + name: { + valType: 'string', + role: 'info', + description: [ + 'Sets the trace name.', + 'The trace name appear as the legend item and on hover.' + ].join(' ') + }, + uid: { + valType: 'string', + role: 'info', + dflt: '' + }, + hoverinfo: { + valType: 'flaglist', + role: 'info', + flags: ['x', 'y', 'z', 'text', 'name'], + extras: ['all', 'none', 'skip'], + dflt: 'all', + description: [ + 'Determines which trace information appear on hover.', + 'If `none` or `skip` are set, no information is displayed upon hovering.', + 'But, if `none` is set, click and hover events are still fired.' + ].join(' ') + }, + stream: { + token: { + valType: 'string', + noBlank: true, + strict: true, + role: 'info', + description: [ + 'The stream id number links a data trace on a plot with a stream.', + 'See https://plot.ly/settings for more details.' + ].join(' ') }, - stream: { - token: { - valType: 'string', - noBlank: true, - strict: true, - role: 'info', - description: [ - 'The stream id number links a data trace on a plot with a stream.', - 'See https://plot.ly/settings for more details.' - ].join(' ') - }, - maxpoints: { - valType: 'number', - min: 0, - max: 10000, - dflt: 500, - role: 'info', - description: [ - 'Sets the maximum number of points to keep on the plots from an', - 'incoming stream.', - 'If `maxpoints` is set to *50*, only the newest 50 points will', - 'be displayed on the plot.' - ].join(' ') - } + maxpoints: { + valType: 'number', + min: 0, + max: 10000, + dflt: 500, + role: 'info', + description: [ + 'Sets the maximum number of points to keep on the plots from an', + 'incoming stream.', + 'If `maxpoints` is set to *50*, only the newest 50 points will', + 'be displayed on the plot.' + ].join(' ') } -}; + } +} diff --git a/src/plots/cartesian/attributes.js b/src/plots/cartesian/attributes.js index a472892edc6..0840435c4b4 100644 --- a/src/plots/cartesian/attributes.js +++ b/src/plots/cartesian/attributes.js @@ -6,32 +6,31 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - xaxis: { - valType: 'subplotid', - role: 'info', - dflt: 'x', - description: [ - 'Sets a reference between this trace\'s x coordinates and', - 'a 2D cartesian x axis.', - 'If *x* (the default value), the x coordinates refer to', - '`layout.xaxis`.', - 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - }, - yaxis: { - valType: 'subplotid', - role: 'info', - dflt: 'y', - description: [ - 'Sets a reference between this trace\'s y coordinates and', - 'a 2D cartesian y axis.', - 'If *y* (the default value), the y coordinates refer to', - '`layout.yaxis`.', - 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' - ].join(' ') - } -}; + xaxis: { + valType: 'subplotid', + role: 'info', + dflt: 'x', + description: [ + 'Sets a reference between this trace\'s x coordinates and', + 'a 2D cartesian x axis.', + 'If *x* (the default value), the x coordinates refer to', + '`layout.xaxis`.', + 'If *x2*, the x coordinates refer to `layout.xaxis2`, and so on.' + ].join(' ') + }, + yaxis: { + valType: 'subplotid', + role: 'info', + dflt: 'y', + description: [ + 'Sets a reference between this trace\'s y coordinates and', + 'a 2D cartesian y axis.', + 'If *y* (the default value), the y coordinates refer to', + '`layout.yaxis`.', + 'If *y2*, the y coordinates refer to `layout.xaxis2`, and so on.' + ].join(' ') + } +} diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 603a933d548..fb76eb0cbf6 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -6,45 +6,42 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); - -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var Titles = require('../../components/titles'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); - -var constants = require('../../constants/numerical'); -var FP_SAFE = constants.FP_SAFE; -var ONEAVGYEAR = constants.ONEAVGYEAR; -var ONEAVGMONTH = constants.ONEAVGMONTH; -var ONEDAY = constants.ONEDAY; -var ONEHOUR = constants.ONEHOUR; -var ONEMIN = constants.ONEMIN; -var ONESEC = constants.ONESEC; -var BADNUM = constants.BADNUM; - - -var axes = module.exports = {}; - -axes.layoutAttributes = require('./layout_attributes'); -axes.supplyLayoutDefaults = require('./layout_defaults'); - -axes.setConvert = require('./set_convert'); - -var axisIds = require('./axis_ids'); -axes.id2name = axisIds.id2name; -axes.cleanId = axisIds.cleanId; -axes.list = axisIds.list; -axes.listIds = axisIds.listIds; -axes.getFromId = axisIds.getFromId; -axes.getFromTrace = axisIds.getFromTrace; - +'use strict' + +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') + +var Registry = require('../../registry') +var Lib = require('../../lib') +var svgTextUtils = require('../../lib/svg_text_utils') +var Titles = require('../../components/titles') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') + +var constants = require('../../constants/numerical') +var FP_SAFE = constants.FP_SAFE +var ONEAVGYEAR = constants.ONEAVGYEAR +var ONEAVGMONTH = constants.ONEAVGMONTH +var ONEDAY = constants.ONEDAY +var ONEHOUR = constants.ONEHOUR +var ONEMIN = constants.ONEMIN +var ONESEC = constants.ONESEC +var BADNUM = constants.BADNUM + +var axes = module.exports = {} + +axes.layoutAttributes = require('./layout_attributes') +axes.supplyLayoutDefaults = require('./layout_defaults') + +axes.setConvert = require('./set_convert') + +var axisIds = require('./axis_ids') +axes.id2name = axisIds.id2name +axes.cleanId = axisIds.cleanId +axes.list = axisIds.list +axes.listIds = axisIds.listIds +axes.getFromId = axisIds.getFromId +axes.getFromTrace = axisIds.getFromTrace /* * find the list of possible axes to reference with an xref or yref attribute @@ -57,26 +54,26 @@ axes.getFromTrace = axisIds.getFromTrace; * extraOption: aside from existing axes with this letter, what non-axis value is allowed? * Only required if it's different from `dflt` */ -axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) { - var axLetter = attr.charAt(attr.length - 1), - axlist = axes.listIds(gd, axLetter), - refAttr = attr + 'ref', - attrDef = {}; +axes.coerceRef = function (containerIn, containerOut, gd, attr, dflt, extraOption) { + var axLetter = attr.charAt(attr.length - 1), + axlist = axes.listIds(gd, axLetter), + refAttr = attr + 'ref', + attrDef = {} - if(!dflt) dflt = axlist[0] || extraOption; - if(!extraOption) extraOption = dflt; + if (!dflt) dflt = axlist[0] || extraOption + if (!extraOption) extraOption = dflt // data-ref annotations are not supported in gl2d yet - attrDef[refAttr] = { - valType: 'enumerated', - values: axlist.concat(extraOption ? [extraOption] : []), - dflt: dflt - }; + attrDef[refAttr] = { + valType: 'enumerated', + values: axlist.concat(extraOption ? [extraOption] : []), + dflt: dflt + } // xref, yref - return Lib.coerce(containerIn, containerOut, attrDef, refAttr); -}; + return Lib.coerce(containerIn, containerOut, attrDef, refAttr) +} /* * coerce position attributes (range-type) that can be either on axes or absolute @@ -100,92 +97,89 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption * - for date axes: JS Dates or milliseconds, and convert to date strings * - for other types: coerce them to numbers */ -axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) { - var pos, - newPos; +axes.coercePosition = function (containerOut, gd, coerce, axRef, attr, dflt) { + var pos, + newPos - if(axRef === 'paper' || axRef === 'pixel') { - pos = coerce(attr, dflt); - } - else { - var ax = axes.getFromId(gd, axRef); + if (axRef === 'paper' || axRef === 'pixel') { + pos = coerce(attr, dflt) + } else { + var ax = axes.getFromId(gd, axRef) - dflt = ax.fraction2r(dflt); - pos = coerce(attr, dflt); + dflt = ax.fraction2r(dflt) + pos = coerce(attr, dflt) - if(ax.type === 'category') { + if (ax.type === 'category') { // if position is given as a category name, convert it to a number - if(typeof pos === 'string' && (ax._categories || []).length) { - newPos = ax._categories.indexOf(pos); - containerOut[attr] = (newPos === -1) ? dflt : newPos; - return; - } - } - else if(ax.type === 'date') { - containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar); - return; - } - } + if (typeof pos === 'string' && (ax._categories || []).length) { + newPos = ax._categories.indexOf(pos) + containerOut[attr] = (newPos === -1) ? dflt : newPos + return + } + } else if (ax.type === 'date') { + containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar) + return + } + } // finally make sure we have a number (unless date type already returned a string) - containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt; -}; + containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt +} // empty out types for all axes containing these traces // so we auto-set them again -axes.clearTypes = function(gd, traces) { - if(!Array.isArray(traces) || !traces.length) { - traces = (gd._fullData).map(function(d, i) { return i; }); - } - traces.forEach(function(tracenum) { - var trace = gd.data[tracenum]; - delete (axes.getFromId(gd, trace.xaxis) || {}).type; - delete (axes.getFromId(gd, trace.yaxis) || {}).type; - }); -}; +axes.clearTypes = function (gd, traces) { + if (!Array.isArray(traces) || !traces.length) { + traces = (gd._fullData).map(function (d, i) { return i }) + } + traces.forEach(function (tracenum) { + var trace = gd.data[tracenum] + delete (axes.getFromId(gd, trace.xaxis) || {}).type + delete (axes.getFromId(gd, trace.yaxis) || {}).type + }) +} // get counteraxis letter for this axis (name or id) // this can also be used as the id for default counter axis -axes.counterLetter = function(id) { - var axLetter = id.charAt(0); - if(axLetter === 'x') return 'y'; - if(axLetter === 'y') return 'x'; -}; +axes.counterLetter = function (id) { + var axLetter = id.charAt(0) + if (axLetter === 'x') return 'y' + if (axLetter === 'y') return 'x' +} // incorporate a new minimum difference and first tick into // forced // note that _forceTick0 is linearized, so needs to be turned into // a range value for setting tick0 -axes.minDtick = function(ax, newDiff, newFirst, allow) { +axes.minDtick = function (ax, newDiff, newFirst, allow) { // doesn't make sense to do forced min dTick on log or category axes, // and the plot itself may decide to cancel (ie non-grouped bars) - if(['log', 'category'].indexOf(ax.type) !== -1 || !allow) { - ax._minDtick = 0; - } + if (['log', 'category'].indexOf(ax.type) !== -1 || !allow) { + ax._minDtick = 0 + } // undefined means there's nothing there yet - else if(ax._minDtick === undefined) { - ax._minDtick = newDiff; - ax._forceTick0 = newFirst; - } - else if(ax._minDtick) { + else if (ax._minDtick === undefined) { + ax._minDtick = newDiff + ax._forceTick0 = newFirst + } else if (ax._minDtick) { // existing minDtick is an integer multiple of newDiff // (within rounding err) // and forceTick0 can be shifted to newFirst - if((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 && + if ((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 && (((newFirst - ax._forceTick0) / newDiff % 1) + 1.000001) % 1 < 2e-6) { - ax._minDtick = newDiff; - ax._forceTick0 = newFirst; - } + ax._minDtick = newDiff + ax._forceTick0 = newFirst + } // if the converse is true (newDiff is a multiple of minDtick and // newFirst can be shifted to forceTick0) then do nothing - same // forcing stands. Otherwise, cancel forced minimum - else if((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 || + else if ((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 || (((newFirst - ax._forceTick0) / ax._minDtick % 1) + 1.000001) % 1 > 2e-6) { - ax._minDtick = 0; - } + ax._minDtick = 0 } -}; + } +} // Find the autorange for this axis // @@ -200,170 +194,162 @@ axes.minDtick = function(ax, newDiff, newFirst, allow) { // maintaining backward compatibility. category will always have to use calcdata // though, because otherwise values between categories (or outside all categories) // would be impossible. -axes.getAutoRange = function(ax) { - var newRange = []; - - var minmin = ax._min[0].val, - maxmax = ax._max[0].val, - i; - - for(i = 1; i < ax._min.length; i++) { - if(minmin !== maxmax) break; - minmin = Math.min(minmin, ax._min[i].val); - } - for(i = 1; i < ax._max.length; i++) { - if(minmin !== maxmax) break; - maxmax = Math.max(maxmax, ax._max[i].val); - } - - var j, minpt, maxpt, minbest, maxbest, dp, dv, - mbest = 0, - axReverse = false; - - if(ax.range) { - var rng = Lib.simpleMap(ax.range, ax.r2l); - axReverse = rng[1] < rng[0]; - } +axes.getAutoRange = function (ax) { + var newRange = [] + + var minmin = ax._min[0].val, + maxmax = ax._max[0].val, + i + + for (i = 1; i < ax._min.length; i++) { + if (minmin !== maxmax) break + minmin = Math.min(minmin, ax._min[i].val) + } + for (i = 1; i < ax._max.length; i++) { + if (minmin !== maxmax) break + maxmax = Math.max(maxmax, ax._max[i].val) + } + + var j, minpt, maxpt, minbest, maxbest, dp, dv, + mbest = 0, + axReverse = false + + if (ax.range) { + var rng = Lib.simpleMap(ax.range, ax.r2l) + axReverse = rng[1] < rng[0] + } // one-time setting to easily reverse the axis // when plotting from code - if(ax.autorange === 'reversed') { - axReverse = true; - ax.autorange = true; - } - - for(i = 0; i < ax._min.length; i++) { - minpt = ax._min[i]; - for(j = 0; j < ax._max.length; j++) { - maxpt = ax._max[j]; - dv = maxpt.val - minpt.val; - dp = ax._length - minpt.pad - maxpt.pad; - if(dv > 0 && dp > 0 && dv / dp > mbest) { - minbest = minpt; - maxbest = maxpt; - mbest = dv / dp; - } - } - } - - if(minmin === maxmax) { - var lower = minmin - 1; - var upper = minmin + 1; - if(ax.rangemode === 'tozero') { - newRange = minmin < 0 ? [lower, 0] : [0, upper]; - } - else if(ax.rangemode === 'nonnegative') { - newRange = [Math.max(0, lower), Math.max(0, upper)]; - } - else { - newRange = [lower, upper]; - } - } - else if(mbest) { - if(ax.type === 'linear' || ax.type === '-') { - if(ax.rangemode === 'tozero') { - if(minbest.val >= 0) { - minbest = {val: 0, pad: 0}; - } - if(maxbest.val <= 0) { - maxbest = {val: 0, pad: 0}; - } - } - else if(ax.rangemode === 'nonnegative') { - if(minbest.val - mbest * minbest.pad < 0) { - minbest = {val: 0, pad: 0}; - } - if(maxbest.val < 0) { - maxbest = {val: 1, pad: 0}; - } - } + if (ax.autorange === 'reversed') { + axReverse = true + ax.autorange = true + } + + for (i = 0; i < ax._min.length; i++) { + minpt = ax._min[i] + for (j = 0; j < ax._max.length; j++) { + maxpt = ax._max[j] + dv = maxpt.val - minpt.val + dp = ax._length - minpt.pad - maxpt.pad + if (dv > 0 && dp > 0 && dv / dp > mbest) { + minbest = minpt + maxbest = maxpt + mbest = dv / dp + } + } + } + + if (minmin === maxmax) { + var lower = minmin - 1 + var upper = minmin + 1 + if (ax.rangemode === 'tozero') { + newRange = minmin < 0 ? [lower, 0] : [0, upper] + } else if (ax.rangemode === 'nonnegative') { + newRange = [Math.max(0, lower), Math.max(0, upper)] + } else { + newRange = [lower, upper] + } + } else if (mbest) { + if (ax.type === 'linear' || ax.type === '-') { + if (ax.rangemode === 'tozero') { + if (minbest.val >= 0) { + minbest = {val: 0, pad: 0} + } + if (maxbest.val <= 0) { + maxbest = {val: 0, pad: 0} + } + } else if (ax.rangemode === 'nonnegative') { + if (minbest.val - mbest * minbest.pad < 0) { + minbest = {val: 0, pad: 0} + } + if (maxbest.val < 0) { + maxbest = {val: 1, pad: 0} + } + } // in case it changed again... - mbest = (maxbest.val - minbest.val) / - (ax._length - minbest.pad - maxbest.pad); - - } - - newRange = [ - minbest.val - mbest * minbest.pad, - maxbest.val + mbest * maxbest.pad - ]; + mbest = (maxbest.val - minbest.val) / + (ax._length - minbest.pad - maxbest.pad) } + newRange = [ + minbest.val - mbest * minbest.pad, + maxbest.val + mbest * maxbest.pad + ] + } + // don't let axis have zero size, while still respecting tozero and nonnegative - if(newRange[0] === newRange[1]) { - if(ax.rangemode === 'tozero') { - if(newRange[0] < 0) { - newRange = [newRange[0], 0]; - } - else if(newRange[0] > 0) { - newRange = [0, newRange[0]]; - } - else { - newRange = [0, 1]; - } - } - else { - newRange = [newRange[0] - 1, newRange[0] + 1]; - if(ax.rangemode === 'nonnegative') { - newRange[0] = Math.max(0, newRange[0]); - } - } - } + if (newRange[0] === newRange[1]) { + if (ax.rangemode === 'tozero') { + if (newRange[0] < 0) { + newRange = [newRange[0], 0] + } else if (newRange[0] > 0) { + newRange = [0, newRange[0]] + } else { + newRange = [0, 1] + } + } else { + newRange = [newRange[0] - 1, newRange[0] + 1] + if (ax.rangemode === 'nonnegative') { + newRange[0] = Math.max(0, newRange[0]) + } + } + } // maintain reversal - if(axReverse) newRange.reverse(); + if (axReverse) newRange.reverse() - return Lib.simpleMap(newRange, ax.l2r || Number); -}; + return Lib.simpleMap(newRange, ax.l2r || Number) +} -axes.doAutoRange = function(ax) { - if(!ax._length) ax.setScale(); +axes.doAutoRange = function (ax) { + if (!ax._length) ax.setScale() // TODO do we really need this? - var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length); + var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length) - if(ax.autorange && hasDeps) { - ax.range = axes.getAutoRange(ax); + if (ax.autorange && hasDeps) { + ax.range = axes.getAutoRange(ax) // doAutoRange will get called on fullLayout, // but we want to report its results back to layout - var axIn = ax._gd.layout[ax._name]; + var axIn = ax._gd.layout[ax._name] - if(!axIn) ax._gd.layout[ax._name] = axIn = {}; + if (!axIn) ax._gd.layout[ax._name] = axIn = {} - if(axIn !== ax) { - axIn.range = ax.range.slice(); - axIn.autorange = ax.autorange; - } + if (axIn !== ax) { + axIn.range = ax.range.slice() + axIn.autorange = ax.autorange } -}; + } +} // save a copy of the initial axis ranges in fullLayout // use them in mode bar and dblclick events -axes.saveRangeInitial = function(gd, overwrite) { - var axList = axes.list(gd, '', true), - hasOneAxisChanged = false; +axes.saveRangeInitial = function (gd, overwrite) { + var axList = axes.list(gd, '', true), + hasOneAxisChanged = false - for(var i = 0; i < axList.length; i++) { - var ax = axList[i]; + for (var i = 0; i < axList.length; i++) { + var ax = axList[i] - var isNew = (ax._rangeInitial === undefined); - var hasChanged = ( + var isNew = (ax._rangeInitial === undefined) + var hasChanged = ( isNew || !( ax.range[0] === ax._rangeInitial[0] && ax.range[1] === ax._rangeInitial[1] ) - ); + ) - if((isNew && ax.autorange === false) || (overwrite && hasChanged)) { - ax._rangeInitial = ax.range.slice(); - hasOneAxisChanged = true; - } + if ((isNew && ax.autorange === false) || (overwrite && hasChanged)) { + ax._rangeInitial = ax.range.slice() + hasOneAxisChanged = true } + } - return hasOneAxisChanged; -}; + return hasOneAxisChanged +} // axes.expand: if autoranging, include new data in the outer limits // for this axis @@ -377,290 +363,279 @@ axes.saveRangeInitial = function(gd, overwrite) { // (unless one end is overridden by tozero) // tozero: (boolean) make sure to include zero if axis is linear, // and make it a tight bound if possible -axes.expand = function(ax, data, options) { - if(!(ax.autorange || ax._needsExpand) || !data) return; - if(!ax._min) ax._min = []; - if(!ax._max) ax._max = []; - if(!options) options = {}; - if(!ax._m) ax.setScale(); - - var len = data.length, - extrappad = options.padded ? ax._length * 0.05 : 0, - tozero = options.tozero && (ax.type === 'linear' || ax.type === '-'), - i, j, v, di, dmin, dmax, - ppadiplus, ppadiminus, includeThis, vmin, vmax; - - function getPad(item) { - if(Array.isArray(item)) { - return function(i) { return Math.max(Number(item[i]||0), 0); }; - } - else { - var v = Math.max(Number(item||0), 0); - return function() { return v; }; - } - } - var ppadplus = getPad((ax._m > 0 ? +axes.expand = function (ax, data, options) { + if (!(ax.autorange || ax._needsExpand) || !data) return + if (!ax._min) ax._min = [] + if (!ax._max) ax._max = [] + if (!options) options = {} + if (!ax._m) ax.setScale() + + var len = data.length, + extrappad = options.padded ? ax._length * 0.05 : 0, + tozero = options.tozero && (ax.type === 'linear' || ax.type === '-'), + i, j, v, di, dmin, dmax, + ppadiplus, ppadiminus, includeThis, vmin, vmax + + function getPad (item) { + if (Array.isArray(item)) { + return function (i) { return Math.max(Number(item[i] || 0), 0) } + } else { + var v = Math.max(Number(item || 0), 0) + return function () { return v } + } + } + var ppadplus = getPad((ax._m > 0 ? options.ppadplus : options.ppadminus) || options.ppad || 0), - ppadminus = getPad((ax._m > 0 ? + ppadminus = getPad((ax._m > 0 ? options.ppadminus : options.ppadplus) || options.ppad || 0), - vpadplus = getPad(options.vpadplus || options.vpad), - vpadminus = getPad(options.vpadminus || options.vpad); - - function addItem(i) { - di = data[i]; - if(!isNumeric(di)) return; - ppadiplus = ppadplus(i) + extrappad; - ppadiminus = ppadminus(i) + extrappad; - vmin = di - vpadminus(i); - vmax = di + vpadplus(i); + vpadplus = getPad(options.vpadplus || options.vpad), + vpadminus = getPad(options.vpadminus || options.vpad) + + function addItem (i) { + di = data[i] + if (!isNumeric(di)) return + ppadiplus = ppadplus(i) + extrappad + ppadiminus = ppadminus(i) + extrappad + vmin = di - vpadminus(i) + vmax = di + vpadplus(i) // special case for log axes: if vpad makes this object span // more than an order of mag, clip it to one order. This is so // we don't have non-positive errors or absurdly large lower // range due to rounding errors - if(ax.type === 'log' && vmin < vmax / 10) { vmin = vmax / 10; } + if (ax.type === 'log' && vmin < vmax / 10) { vmin = vmax / 10 } - dmin = ax.c2l(vmin); - dmax = ax.c2l(vmax); + dmin = ax.c2l(vmin) + dmax = ax.c2l(vmax) - if(tozero) { - dmin = Math.min(0, dmin); - dmax = Math.max(0, dmax); - } + if (tozero) { + dmin = Math.min(0, dmin) + dmax = Math.max(0, dmax) + } // In order to stop overflow errors, don't consider points // too close to the limits of js floating point - function goodNumber(v) { - return isNumeric(v) && Math.abs(v) < FP_SAFE; - } + function goodNumber (v) { + return isNumeric(v) && Math.abs(v) < FP_SAFE + } - if(goodNumber(dmin)) { - includeThis = true; + if (goodNumber(dmin)) { + includeThis = true // take items v from ax._min and compare them to the // presently active point: // - if the item supercedes the new point, set includethis false // - if the new pt supercedes the item, delete it from ax._min - for(j = 0; j < ax._min.length && includeThis; j++) { - v = ax._min[j]; - if(v.val <= dmin && v.pad >= ppadiminus) { - includeThis = false; - } - else if(v.val >= dmin && v.pad <= ppadiminus) { - ax._min.splice(j, 1); - j--; - } - } - if(includeThis) { - ax._min.push({ - val: dmin, - pad: (tozero && dmin === 0) ? 0 : ppadiminus - }); - } - } - - if(goodNumber(dmax)) { - includeThis = true; - for(j = 0; j < ax._max.length && includeThis; j++) { - v = ax._max[j]; - if(v.val >= dmax && v.pad >= ppadiplus) { - includeThis = false; - } - else if(v.val <= dmax && v.pad <= ppadiplus) { - ax._max.splice(j, 1); - j--; - } - } - if(includeThis) { - ax._max.push({ - val: dmax, - pad: (tozero && dmax === 0) ? 0 : ppadiplus - }); - } - } - } + for (j = 0; j < ax._min.length && includeThis; j++) { + v = ax._min[j] + if (v.val <= dmin && v.pad >= ppadiminus) { + includeThis = false + } else if (v.val >= dmin && v.pad <= ppadiminus) { + ax._min.splice(j, 1) + j-- + } + } + if (includeThis) { + ax._min.push({ + val: dmin, + pad: (tozero && dmin === 0) ? 0 : ppadiminus + }) + } + } + + if (goodNumber(dmax)) { + includeThis = true + for (j = 0; j < ax._max.length && includeThis; j++) { + v = ax._max[j] + if (v.val >= dmax && v.pad >= ppadiplus) { + includeThis = false + } else if (v.val <= dmax && v.pad <= ppadiplus) { + ax._max.splice(j, 1) + j-- + } + } + if (includeThis) { + ax._max.push({ + val: dmax, + pad: (tozero && dmax === 0) ? 0 : ppadiplus + }) + } + } + } // For efficiency covering monotonic or near-monotonic data, // check a few points at both ends first and then sweep // through the middle - for(i = 0; i < 6; i++) addItem(i); - for(i = len - 1; i > 5; i--) addItem(i); - -}; + for (i = 0; i < 6; i++) addItem(i) + for (i = len - 1; i > 5; i--) addItem(i) +} -axes.autoBin = function(data, ax, nbins, is2d, calendar) { - var dataMin = Lib.aggNums(Math.min, null, data), - dataMax = Lib.aggNums(Math.max, null, data); +axes.autoBin = function (data, ax, nbins, is2d, calendar) { + var dataMin = Lib.aggNums(Math.min, null, data), + dataMax = Lib.aggNums(Math.max, null, data) - if(!calendar) calendar = ax.calendar; + if (!calendar) calendar = ax.calendar - if(ax.type === 'category') { - return { - start: dataMin - 0.5, - end: dataMax + 0.5, - size: 1 - }; + if (ax.type === 'category') { + return { + start: dataMin - 0.5, + end: dataMax + 0.5, + size: 1 } + } - var size0; - if(nbins) size0 = ((dataMax - dataMin) / nbins); - else { + var size0 + if (nbins) size0 = ((dataMax - dataMin) / nbins) + else { // totally auto: scale off std deviation so the highest bin is // somewhat taller than the total number of bins, but don't let // the size get smaller than the 'nice' rounded down minimum // difference between values - var distinctData = Lib.distinctVals(data), - msexp = Math.pow(10, Math.floor( + var distinctData = Lib.distinctVals(data), + msexp = Math.pow(10, Math.floor( Math.log(distinctData.minDiff) / Math.LN10)), - minSize = msexp * Lib.roundUp( - distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true); - size0 = Math.max(minSize, 2 * Lib.stdev(data) / - Math.pow(data.length, is2d ? 0.25 : 0.4)); + minSize = msexp * Lib.roundUp( + distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true) + size0 = Math.max(minSize, 2 * Lib.stdev(data) / + Math.pow(data.length, is2d ? 0.25 : 0.4)) // fallback if ax.d2c output BADNUMs // e.g. when user try to plot categorical bins // on a layout.xaxis.type: 'linear' - if(!isNumeric(size0)) size0 = 1; - } + if (!isNumeric(size0)) size0 = 1 + } // piggyback off autotick code to make "nice" bin sizes - var dummyAx; - if(ax.type === 'log') { - dummyAx = { - type: 'linear', - range: [dataMin, dataMax] - }; - } - else { - dummyAx = { - type: ax.type, - range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar), - calendar: calendar - }; - } - axes.setConvert(dummyAx); - - axes.autoTicks(dummyAx, size0); - var binStart = axes.tickIncrement( + var dummyAx + if (ax.type === 'log') { + dummyAx = { + type: 'linear', + range: [dataMin, dataMax] + } + } else { + dummyAx = { + type: ax.type, + range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar), + calendar: calendar + } + } + axes.setConvert(dummyAx) + + axes.autoTicks(dummyAx, size0) + var binStart = axes.tickIncrement( axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar), - binEnd; + binEnd // check for too many data points right at the edges of bins // (>50% within 1% of bin edges) or all data points integral // and offset the bins accordingly - if(typeof dummyAx.dtick === 'number') { - binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax); + if (typeof dummyAx.dtick === 'number') { + binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax) - var bincount = 1 + Math.floor((dataMax - binStart) / dummyAx.dtick); - binEnd = binStart + bincount * dummyAx.dtick; - } - else { + var bincount = 1 + Math.floor((dataMax - binStart) / dummyAx.dtick) + binEnd = binStart + bincount * dummyAx.dtick + } else { // month ticks - should be the only nonlinear kind we have at this point. // dtick (as supplied by axes.autoTick) only has nonlinear values on // date and log axes, but even if you display a histogram on a log axis // we bin it on a linear axis (which one could argue against, but that's // a separate issue) - if(dummyAx.dtick.charAt(0) === 'M') { - binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin, calendar); - } + if (dummyAx.dtick.charAt(0) === 'M') { + binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin, calendar) + } // calculate the endpoint for nonlinear ticks - you have to // just increment until you're done - binEnd = binStart; - while(binEnd <= dataMax) { - binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar); - } + binEnd = binStart + while (binEnd <= dataMax) { + binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar) } + } - return { - start: ax.c2r(binStart, 0, calendar), - end: ax.c2r(binEnd, 0, calendar), - size: dummyAx.dtick - }; -}; - + return { + start: ax.c2r(binStart, 0, calendar), + end: ax.c2r(binEnd, 0, calendar), + size: dummyAx.dtick + } +} -function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) { - var edgecount = 0, - midcount = 0, - intcount = 0, - blankCount = 0; +function autoShiftNumericBins (binStart, data, ax, dataMin, dataMax) { + var edgecount = 0, + midcount = 0, + intcount = 0, + blankCount = 0 - function nearEdge(v) { + function nearEdge (v) { // is a value within 1% of a bin edge? - return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2; - } + return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2 + } - for(var i = 0; i < data.length; i++) { - if(data[i] % 1 === 0) intcount++; - else if(!isNumeric(data[i])) blankCount++; + for (var i = 0; i < data.length; i++) { + if (data[i] % 1 === 0) intcount++ + else if (!isNumeric(data[i])) blankCount++ - if(nearEdge(data[i])) edgecount++; - if(nearEdge(data[i] + ax.dtick / 2)) midcount++; - } - var dataCount = data.length - blankCount; + if (nearEdge(data[i])) edgecount++ + if (nearEdge(data[i] + ax.dtick / 2)) midcount++ + } + var dataCount = data.length - blankCount - if(intcount === dataCount && ax.type !== 'date') { + if (intcount === dataCount && ax.type !== 'date') { // all integers: if bin size is <1, it's because // that was specifically requested (large nbins) // so respect that... but center the bins containing // integers on those integers - if(ax.dtick < 1) { - binStart = dataMin - 0.5 * ax.dtick; - } + if (ax.dtick < 1) { + binStart = dataMin - 0.5 * ax.dtick + } // otherwise start half an integer down regardless of // the bin size, just enough to clear up endpoint // ambiguity about which integers are in which bins. - else { - binStart -= 0.5; - if(binStart + ax.dtick < dataMin) binStart += ax.dtick; - } + else { + binStart -= 0.5 + if (binStart + ax.dtick < dataMin) binStart += ax.dtick } - else if(midcount < dataCount * 0.1) { - if(edgecount > dataCount * 0.3 || + } else if (midcount < dataCount * 0.1) { + if (edgecount > dataCount * 0.3 || nearEdge(dataMin) || nearEdge(dataMax)) { // lots of points at the edge, not many in the middle // shift half a bin - var binshift = ax.dtick / 2; - binStart += (binStart + binshift < dataMin) ? binshift : -binshift; - } + var binshift = ax.dtick / 2 + binStart += (binStart + binshift < dataMin) ? binshift : -binshift } - return binStart; + } + return binStart } - -function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { - var stats = Lib.findExactDates(data, calendar); +function autoShiftMonthBins (binStart, data, dtick, dataMin, calendar) { + var stats = Lib.findExactDates(data, calendar) // number of data points that needs to be an exact value // to shift that increment to (near) the bin center - var threshold = 0.8; + var threshold = 0.8 - if(stats.exactDays > threshold) { - var numMonths = Number(dtick.substr(1)); + if (stats.exactDays > threshold) { + var numMonths = Number(dtick.substr(1)) - if((stats.exactYears > threshold) && (numMonths % 12 === 0)) { + if ((stats.exactYears > threshold) && (numMonths % 12 === 0)) { // The exact middle of a non-leap-year is 1.5 days into July // so if we start the bins here, all but leap years will // get hover-labeled as exact years. - binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5; - } - else if(stats.exactMonths > threshold) { + binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5 + } else if (stats.exactMonths > threshold) { // Months are not as clean, but if we shift half the *longest* // month (31/2 days) then 31-day months will get labeled exactly // and shorter months will get labeled with the correct month // but shifted 12-36 hours into it. - binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5; - } - else { + binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5 + } else { // Shifting half a day is exact, but since these are month bins it // will always give a somewhat odd-looking label, until we do something // smarter like showing the bin boundaries (or the bounds of the actual // data in each bin) - binStart -= ONEDAY / 2; - } - var nextBinStart = axes.tickIncrement(binStart, dtick); - - if(nextBinStart <= dataMin) return nextBinStart; + binStart -= ONEDAY / 2 } - return binStart; + var nextBinStart = axes.tickIncrement(binStart, dtick) + + if (nextBinStart <= dataMin) return nextBinStart + } + return binStart } // ---------------------------------------------------- @@ -671,142 +646,141 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { // if ticks are set to automatic, determine the right values (tick0,dtick) // in any case, set tickround to # of digits to round tick labels to, // or codes to this effect for log and date scales -axes.calcTicks = function calcTicks(ax) { - var rng = Lib.simpleMap(ax.range, ax.r2l); +axes.calcTicks = function calcTicks (ax) { + var rng = Lib.simpleMap(ax.range, ax.r2l) // calculate max number of (auto) ticks to display based on plot size - if(ax.tickmode === 'auto' || !ax.dtick) { - var nt = ax.nticks, - minPx; - if(!nt) { - if(ax.type === 'category') { - minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15; - nt = ax._length / minPx; - } - else { - minPx = ax._id.charAt(0) === 'y' ? 40 : 80; - nt = Lib.constrain(ax._length / minPx, 4, 9) + 1; - } - } + if (ax.tickmode === 'auto' || !ax.dtick) { + var nt = ax.nticks, + minPx + if (!nt) { + if (ax.type === 'category') { + minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15 + nt = ax._length / minPx + } else { + minPx = ax._id.charAt(0) === 'y' ? 40 : 80 + nt = Lib.constrain(ax._length / minPx, 4, 9) + 1 + } + } // add a couple of extra digits for filling in ticks when we // have explicit tickvals without tick text - if(ax.tickmode === 'array') nt *= 100; + if (ax.tickmode === 'array') nt *= 100 - axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt); + axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt) // check for a forced minimum dtick - if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) { - ax.dtick = ax._minDtick; - ax.tick0 = ax.l2r(ax._forceTick0); - } + if (ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) { + ax.dtick = ax._minDtick + ax.tick0 = ax.l2r(ax._forceTick0) } + } // check for missing tick0 - if(!ax.tick0) { - ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0; - } + if (!ax.tick0) { + ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0 + } // now figure out rounding of tick values - autoTickRound(ax); + autoTickRound(ax) // now that we've figured out the auto values for formatting // in case we're missing some ticktext, we can break out for array ticks - if(ax.tickmode === 'array') return arrayTicks(ax); + if (ax.tickmode === 'array') return arrayTicks(ax) // find the first tick - ax._tmin = axes.tickFirst(ax); + ax._tmin = axes.tickFirst(ax) // check for reversed axis - var axrev = (rng[1] < rng[0]); + var axrev = (rng[1] < rng[0]) // return the full set of tick vals - var vals = [], + var vals = [], // add a tiny bit so we get ticks which may have rounded out - endtick = rng[1] * 1.0001 - rng[0] * 0.0001; - if(ax.type === 'category') { - endtick = (axrev) ? Math.max(-0.5, endtick) : - Math.min(ax._categories.length - 0.5, endtick); - } - for(var x = ax._tmin; + endtick = rng[1] * 1.0001 - rng[0] * 0.0001 + if (ax.type === 'category') { + endtick = (axrev) ? Math.max(-0.5, endtick) : + Math.min(ax._categories.length - 0.5, endtick) + } + for (var x = ax._tmin; (axrev) ? (x >= endtick) : (x <= endtick); x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) { - vals.push(x); + vals.push(x) // prevent infinite loops - if(vals.length > 1000) break; - } + if (vals.length > 1000) break + } // save the last tick as well as first, so we can // show the exponent only on the last one - ax._tmax = vals[vals.length - 1]; + ax._tmax = vals[vals.length - 1] // for showing the rest of a date when the main tick label is only the // latter part: ax._prevDateHead holds what we showed most recently. // Start with it cleared and mark that we're in calcTicks (ie calculating a // whole string of these so we should care what the previous date head was!) - ax._prevDateHead = ''; - ax._inCalcTicks = true; - - var ticksOut = new Array(vals.length); - for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]); - - ax._inCalcTicks = false; - - return ticksOut; -}; - -function arrayTicks(ax) { - var vals = ax.tickvals, - text = ax.ticktext, - ticksOut = new Array(vals.length), - rng = Lib.simpleMap(ax.range, ax.r2l), - r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001, - r1expanded = rng[1] * 1.0001 - rng[0] * 0.0001, - tickMin = Math.min(r0expanded, r1expanded), - tickMax = Math.max(r0expanded, r1expanded), - vali, - i, - j = 0; + ax._prevDateHead = '' + ax._inCalcTicks = true + + var ticksOut = new Array(vals.length) + for (var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]) + + ax._inCalcTicks = false + + return ticksOut +} + +function arrayTicks (ax) { + var vals = ax.tickvals, + text = ax.ticktext, + ticksOut = new Array(vals.length), + rng = Lib.simpleMap(ax.range, ax.r2l), + r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001, + r1expanded = rng[1] * 1.0001 - rng[0] * 0.0001, + tickMin = Math.min(r0expanded, r1expanded), + tickMax = Math.max(r0expanded, r1expanded), + vali, + i, + j = 0 // without a text array, just format the given values as any other ticks // except with more precision to the numbers - if(!Array.isArray(text)) text = []; + if (!Array.isArray(text)) text = [] // make sure showing ticks doesn't accidentally add new categories - var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; + var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l // array ticks on log axes always show the full number // (if no explicit ticktext overrides it) - if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') { - ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1); - } + if (ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') { + ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1) + } - for(i = 0; i < vals.length; i++) { - vali = tickVal2l(vals[i]); - if(vali > tickMin && vali < tickMax) { - if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali); - else ticksOut[j] = tickTextObj(ax, vali, String(text[i])); - j++; - } + for (i = 0; i < vals.length; i++) { + vali = tickVal2l(vals[i]) + if (vali > tickMin && vali < tickMax) { + if (text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali) + else ticksOut[j] = tickTextObj(ax, vali, String(text[i])) + j++ } + } - if(j < vals.length) ticksOut.splice(j, vals.length - j); + if (j < vals.length) ticksOut.splice(j, vals.length - j) - return ticksOut; + return ticksOut } var roundBase10 = [2, 5, 10], - roundBase24 = [1, 2, 3, 6, 12], - roundBase60 = [1, 2, 5, 10, 15, 30], + roundBase24 = [1, 2, 3, 6, 12], + roundBase60 = [1, 2, 5, 10, 15, 30], // 2&3 day ticks are weird, but need something btwn 1&7 - roundDays = [1, 2, 3, 7, 14], + roundDays = [1, 2, 3, 7, 14], // approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2) // these don't have to be exact, just close enough to round to the right value - roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1], - roundLog2 = [-0.301, 0, 0.301, 0.699, 1]; + roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1], + roundLog2 = [-0.301, 0, 0.301, 0.699, 1] -function roundDTick(roughDTick, base, roundingSet) { - return base * Lib.roundUp(roughDTick / base, roundingSet); +function roundDTick (roughDTick, base, roundingSet) { + return base * Lib.roundUp(roughDTick / base, roundingSet) } // autoTicks: calculate best guess at pleasant ticks for this axis @@ -825,154 +799,140 @@ function roundDTick(roughDTick, base, roundingSet) { // log with linear ticks: L# where # is the linear tick spacing // log showing powers plus some intermediates: // D1 shows all digits, D2 shows 2 and 5 -axes.autoTicks = function(ax, roughDTick) { - var base; +axes.autoTicks = function (ax, roughDTick) { + var base - if(ax.type === 'date') { - ax.tick0 = Lib.dateTick0(ax.calendar); + if (ax.type === 'date') { + ax.tick0 = Lib.dateTick0(ax.calendar) // the criteria below are all based on the rough spacing we calculate // being > half of the final unit - so precalculate twice the rough val - var roughX2 = 2 * roughDTick; - - if(roughX2 > ONEAVGYEAR) { - roughDTick /= ONEAVGYEAR; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)); - } - else if(roughX2 > ONEAVGMONTH) { - roughDTick /= ONEAVGMONTH; - ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24); - } - else if(roughX2 > ONEDAY) { - ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays); + var roughX2 = 2 * roughDTick + + if (roughX2 > ONEAVGYEAR) { + roughDTick /= ONEAVGYEAR + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)) + ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)) + } else if (roughX2 > ONEAVGMONTH) { + roughDTick /= ONEAVGMONTH + ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24) + } else if (roughX2 > ONEDAY) { + ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays) // get week ticks on sunday // this will also move the base tick off 2000-01-01 if dtick is // 2 or 3 days... but that's a weird enough case that we'll ignore it. - ax.tick0 = Lib.dateTick0(ax.calendar, true); - } - else if(roughX2 > ONEHOUR) { - ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24); - } - else if(roughX2 > ONEMIN) { - ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60); - } - else if(roughX2 > ONESEC) { - ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60); - } - else { + ax.tick0 = Lib.dateTick0(ax.calendar, true) + } else if (roughX2 > ONEHOUR) { + ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24) + } else if (roughX2 > ONEMIN) { + ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60) + } else if (roughX2 > ONESEC) { + ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60) + } else { // milliseconds - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = roundDTick(roughDTick, base, roundBase10); - } + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)) + ax.dtick = roundDTick(roughDTick, base, roundBase10) } - else if(ax.type === 'log') { - ax.tick0 = 0; - var rng = Lib.simpleMap(ax.range, ax.r2l); + } else if (ax.type === 'log') { + ax.tick0 = 0 + var rng = Lib.simpleMap(ax.range, ax.r2l) - if(roughDTick > 0.7) { + if (roughDTick > 0.7) { // only show powers of 10 - ax.dtick = Math.ceil(roughDTick); - } - else if(Math.abs(rng[1] - rng[0]) < 1) { + ax.dtick = Math.ceil(roughDTick) + } else if (Math.abs(rng[1] - rng[0]) < 1) { // span is less than one power of 10 - var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick); + var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick) // ticks on a linear scale, labeled fully - roughDTick = Math.abs(Math.pow(10, rng[1]) - - Math.pow(10, rng[0])) / nt; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10); - } - else { + roughDTick = Math.abs(Math.pow(10, rng[1]) - + Math.pow(10, rng[0])) / nt + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)) + ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10) + } else { // include intermediates between powers of 10, // labeled with small digits // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits) - ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1'; - } + ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1' } - else if(ax.type === 'category') { - ax.tick0 = 0; - ax.dtick = Math.ceil(Math.max(roughDTick, 1)); - } - else { + } else if (ax.type === 'category') { + ax.tick0 = 0 + ax.dtick = Math.ceil(Math.max(roughDTick, 1)) + } else { // auto ticks always start at 0 - ax.tick0 = 0; - base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); - ax.dtick = roundDTick(roughDTick, base, roundBase10); - } + ax.tick0 = 0 + base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)) + ax.dtick = roundDTick(roughDTick, base, roundBase10) + } // prevent infinite loops - if(ax.dtick === 0) ax.dtick = 1; + if (ax.dtick === 0) ax.dtick = 1 // TODO: this is from log axis histograms with autorange off - if(!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') { - var olddtick = ax.dtick; - ax.dtick = 1; - throw 'ax.dtick error: ' + String(olddtick); - } -}; + if (!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') { + var olddtick = ax.dtick + ax.dtick = 1 + throw 'ax.dtick error: ' + String(olddtick) + } +} // after dtick is already known, find tickround = precision // to display in tick labels // for numeric ticks, integer # digits after . to round to // for date ticks, the last date part to show (y,m,d,H,M,S) // or an integer # digits past seconds -function autoTickRound(ax) { - var dtick = ax.dtick; - - ax._tickexponent = 0; - if(!isNumeric(dtick) && typeof dtick !== 'string') { - dtick = 1; - } - - if(ax.type === 'category') { - ax._tickround = null; - } - if(ax.type === 'date') { +function autoTickRound (ax) { + var dtick = ax.dtick + + ax._tickexponent = 0 + if (!isNumeric(dtick) && typeof dtick !== 'string') { + dtick = 1 + } + + if (ax.type === 'category') { + ax._tickround = null + } + if (ax.type === 'date') { // If tick0 is unusual, give tickround a bit more information // not necessarily *all* the information in tick0 though, if it's really odd // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19 // take off a leading minus (year < 0) and i (intercalary month) so length is consistent - var tick0ms = ax.r2l(ax.tick0), - tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''), - tick0len = tick0str.length; + var tick0ms = ax.r2l(ax.tick0), + tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''), + tick0len = tick0str.length - if(String(dtick).charAt(0) === 'M') { + if (String(dtick).charAt(0) === 'M') { // any tick0 more specific than a year: alway show the full date - if(tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd'; + if (tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd' // show the month unless ticks are full multiples of a year - else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm'; - } - else if((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd'; - else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M'; - else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S'; - else { + else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm' + } else if ((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd' + else if ((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M' + else if ((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S' + else { // tickround is a number of digits of fractional seconds // of any two adjacent ticks, at least one will have the maximum fractional digits // of all possible ticks - so take the max. length of tick0 and the next one - var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length; - ax._tickround = Math.max(tick0len, tick1len) - 20; - } + var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length + ax._tickround = Math.max(tick0len, tick1len) - 20 } - else if(isNumeric(dtick) || dtick.charAt(0) === 'L') { + } else if (isNumeric(dtick) || dtick.charAt(0) === 'L') { // linear or log (except D1, D2) - var rng = ax.range.map(ax.r2d || Number); - if(!isNumeric(dtick)) dtick = Number(dtick.substr(1)); + var rng = ax.range.map(ax.r2d || Number) + if (!isNumeric(dtick)) dtick = Number(dtick.substr(1)) // 2 digits past largest digit of dtick - ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01); + ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01) - var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[1])); + var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[1])) - var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01); - if(Math.abs(rangeexp) > 3) { - if(ax.exponentformat === 'SI' || ax.exponentformat === 'B') { - ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3); - } - else ax._tickexponent = rangeexp; - } + var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01) + if (Math.abs(rangeexp) > 3) { + if (ax.exponentformat === 'SI' || ax.exponentformat === 'B') { + ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3) + } else ax._tickexponent = rangeexp } + } // D1 or D2 (log) - else ax._tickround = null; + else ax._tickround = null } // months and years don't have constant millisecond values @@ -981,100 +941,97 @@ function autoTickRound(ax) { // for pure powers of 10 // numeric ticks always have constant differences, other datetime ticks // can all be calculated as constant number of milliseconds -axes.tickIncrement = function(x, dtick, axrev, calendar) { - var axSign = axrev ? -1 : 1; +axes.tickIncrement = function (x, dtick, axrev, calendar) { + var axSign = axrev ? -1 : 1 // includes linear, all dates smaller than month, and pure 10^n in log - if(isNumeric(dtick)) return x + axSign * dtick; + if (isNumeric(dtick)) return x + axSign * dtick // everything else is a string, one character plus a number - var tType = dtick.charAt(0), - dtSigned = axSign * Number(dtick.substr(1)); + var tType = dtick.charAt(0), + dtSigned = axSign * Number(dtick.substr(1)) // Dates: months (or years - see Lib.incrementMonth) - if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar); + if (tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar) // Log scales: Linear, Digits - else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10; + else if (tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10 // log10 of 2,5,10, or all digits (logs just have to be // close enough to round) - else if(tType === 'D') { - var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, - x2 = x + axSign * 0.01, - frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev); - - return Math.floor(x2) + - Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; - } - else throw 'unrecognized dtick ' + String(dtick); -}; + else if (tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, + x2 = x + axSign * 0.01, + frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev) + + return Math.floor(x2) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10 + } else throw 'unrecognized dtick ' + String(dtick) +} // calculate the first tick on an axis -axes.tickFirst = function(ax) { - var r2l = ax.r2l || Number, - rng = Lib.simpleMap(ax.range, r2l), - axrev = rng[1] < rng[0], - sRound = axrev ? Math.floor : Math.ceil, +axes.tickFirst = function (ax) { + var r2l = ax.r2l || Number, + rng = Lib.simpleMap(ax.range, r2l), + axrev = rng[1] < rng[0], + sRound = axrev ? Math.floor : Math.ceil, // add a tiny extra bit to make sure we get ticks // that may have been rounded out - r0 = rng[0] * 1.0001 - rng[1] * 0.0001, - dtick = ax.dtick, - tick0 = r2l(ax.tick0); + r0 = rng[0] * 1.0001 - rng[1] * 0.0001, + dtick = ax.dtick, + tick0 = r2l(ax.tick0) - if(isNumeric(dtick)) { - var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0; + if (isNumeric(dtick)) { + var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0 // make sure no ticks outside the category list - if(ax.type === 'category') { - tmin = Lib.constrain(tmin, 0, ax._categories.length - 1); - } - return tmin; + if (ax.type === 'category') { + tmin = Lib.constrain(tmin, 0, ax._categories.length - 1) } + return tmin + } - var tType = dtick.charAt(0), - dtNum = Number(dtick.substr(1)); + var tType = dtick.charAt(0), + dtNum = Number(dtick.substr(1)) // Dates: months (or years) - if(tType === 'M') { - var cnt = 0, - t0 = tick0, - t1, - mult, - newDTick; + if (tType === 'M') { + var cnt = 0, + t0 = tick0, + t1, + mult, + newDTick // This algorithm should work for *any* nonlinear (but close to linear!) // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3. - while(cnt < 10) { - t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar); - if((t1 - r0) * (t0 - r0) <= 0) { + while (cnt < 10) { + t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar) + if ((t1 - r0) * (t0 - r0) <= 0) { // t1 and t0 are on opposite sides of r0! we've succeeded! - if(axrev) return Math.min(t0, t1); - return Math.max(t0, t1); - } - mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0); - newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum); - t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar); - cnt++; - } - Lib.error('tickFirst did not converge', ax); - return t0; - } + if (axrev) return Math.min(t0, t1) + return Math.max(t0, t1) + } + mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0) + newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum) + t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar) + cnt++ + } + Lib.error('tickFirst did not converge', ax) + return t0 + } // Log scales: Linear, Digits - else if(tType === 'L') { - return Math.log(sRound( - (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10; - } - else if(tType === 'D') { - var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, - frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev); - - return Math.floor(r0) + - Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; - } - else throw 'unrecognized dtick ' + String(dtick); -}; + else if (tType === 'L') { + return Math.log(sRound( + (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10 + } else if (tType === 'D') { + var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, + frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev) + + return Math.floor(r0) + + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10 + } else throw 'unrecognized dtick ' + String(dtick) +} // draw the text for one tick. // px,py are the location on gd.paper @@ -1082,89 +1039,89 @@ axes.tickFirst = function(ax) { // ax is the axis layout, x is the tick value // hover is a (truthy) flag for whether to show numbers with a bit // more precision for hovertext -axes.tickText = function(ax, x, hover) { - var out = tickTextObj(ax, x), - hideexp, - arrayMode = ax.tickmode === 'array', - extraPrecision = hover || arrayMode, - i, - tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; - - if(arrayMode && Array.isArray(ax.ticktext)) { - var rng = Lib.simpleMap(ax.range, ax.r2l), - minDiff = Math.abs(rng[1] - rng[0]) / 10000; - for(i = 0; i < ax.ticktext.length; i++) { - if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break; - } - if(i < ax.ticktext.length) { - out.text = String(ax.ticktext[i]); - return out; - } +axes.tickText = function (ax, x, hover) { + var out = tickTextObj(ax, x), + hideexp, + arrayMode = ax.tickmode === 'array', + extraPrecision = hover || arrayMode, + i, + tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l + + if (arrayMode && Array.isArray(ax.ticktext)) { + var rng = Lib.simpleMap(ax.range, ax.r2l), + minDiff = Math.abs(rng[1] - rng[0]) / 10000 + for (i = 0; i < ax.ticktext.length; i++) { + if (Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break + } + if (i < ax.ticktext.length) { + out.text = String(ax.ticktext[i]) + return out } + } - function isHidden(showAttr) { - var first_or_last; + function isHidden (showAttr) { + var first_or_last - if(showAttr === undefined) return true; - if(hover) return showAttr === 'none'; + if (showAttr === undefined) return true + if (hover) return showAttr === 'none' - first_or_last = { - first: ax._tmin, - last: ax._tmax - }[showAttr]; + first_or_last = { + first: ax._tmin, + last: ax._tmax + }[showAttr] - return showAttr !== 'all' && x !== first_or_last; - } + return showAttr !== 'all' && x !== first_or_last + } - hideexp = ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : ''; + hideexp = ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : '' - if(ax.type === 'date') formatDate(ax, out, hover, extraPrecision); - else if(ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp); - else if(ax.type === 'category') formatCategory(ax, out); - else formatLinear(ax, out, hover, extraPrecision, hideexp); + if (ax.type === 'date') formatDate(ax, out, hover, extraPrecision) + else if (ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp) + else if (ax.type === 'category') formatCategory(ax, out) + else formatLinear(ax, out, hover, extraPrecision, hideexp) // add prefix and suffix - if(ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text; - if(ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix; - - return out; -}; + if (ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text + if (ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix -function tickTextObj(ax, x, text) { - var tf = ax.tickfont || ax._gd._fullLayout.font; + return out +} - return { - x: x, - dx: 0, - dy: 0, - text: text || '', - fontSize: tf.size, - font: tf.family, - fontColor: tf.color - }; +function tickTextObj (ax, x, text) { + var tf = ax.tickfont || ax._gd._fullLayout.font + + return { + x: x, + dx: 0, + dy: 0, + text: text || '', + fontSize: tf.size, + font: tf.family, + fontColor: tf.color + } } -function formatDate(ax, out, hover, extraPrecision) { - var tr = ax._tickround, - fmt = (hover && ax.hoverformat) || ax.tickformat; +function formatDate (ax, out, hover, extraPrecision) { + var tr = ax._tickround, + fmt = (hover && ax.hoverformat) || ax.tickformat - if(extraPrecision) { + if (extraPrecision) { // second or sub-second precision: extra always shows max digits. // for other fields, extra precision just adds one field. - if(isNumeric(tr)) tr = 4; - else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr]; - } + if (isNumeric(tr)) tr = 4 + else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr] + } - var dateStr = Lib.formatDate(out.x, fmt, tr, ax.calendar), - headStr; + var dateStr = Lib.formatDate(out.x, fmt, tr, ax.calendar), + headStr - var splitIndex = dateStr.indexOf('\n'); - if(splitIndex !== -1) { - headStr = dateStr.substr(splitIndex + 1); - dateStr = dateStr.substr(0, splitIndex); - } + var splitIndex = dateStr.indexOf('\n') + if (splitIndex !== -1) { + headStr = dateStr.substr(splitIndex + 1) + dateStr = dateStr.substr(0, splitIndex) + } - if(extraPrecision) { + if (extraPrecision) { // if extraPrecision led to trailing zeros, strip them off // actually, this can lead to removing even more zeros than // in the original rounding, but that's fine because in these @@ -1172,135 +1129,128 @@ function formatDate(ax, out, hover, extraPrecision) { // anything to be uniform with!) // can we remove the whole time part? - if(dateStr === '00:00:00' || dateStr === '00:00') { - dateStr = headStr; - headStr = ''; - } - else if(dateStr.length === 8) { + if (dateStr === '00:00:00' || dateStr === '00:00') { + dateStr = headStr + headStr = '' + } else if (dateStr.length === 8) { // strip off seconds if they're zero (zero fractional seconds // are already omitted) // but we never remove minutes and leave just hours - dateStr = dateStr.replace(/:00$/, ''); - } + dateStr = dateStr.replace(/:00$/, '') } + } - if(headStr) { - if(hover) { + if (headStr) { + if (hover) { // hover puts it all on one line, so headPart works best up front // except for year headPart: turn this into "Jan 1, 2000" etc. - if(tr === 'd') dateStr += ', ' + headStr; - else dateStr = headStr + (dateStr ? ', ' + dateStr : ''); - } - else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) { - dateStr += '
' + headStr; - ax._prevDateHead = headStr; - } + if (tr === 'd') dateStr += ', ' + headStr + else dateStr = headStr + (dateStr ? ', ' + dateStr : '') + } else if (!ax._inCalcTicks || (headStr !== ax._prevDateHead)) { + dateStr += '
' + headStr + ax._prevDateHead = headStr } + } - out.text = dateStr; + out.text = dateStr } -function formatLog(ax, out, hover, extraPrecision, hideexp) { - var dtick = ax.dtick, - x = out.x; - if(extraPrecision && ((typeof dtick !== 'string') || dtick.charAt(0) !== 'L')) dtick = 'L3'; - - if(ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) { - out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision); - } - else if(isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) { - if(['e', 'E', 'power'].indexOf(ax.exponentformat) !== -1) { - var p = Math.round(x); - if(p === 0) out.text = 1; - else if(p === 1) out.text = '10'; - else if(p > 1) out.text = '10' + p + ''; - else out.text = '10\u2212' + -p + ''; - - out.fontSize *= 1.25; - } - else { - out.text = numFormat(Math.pow(10, x), ax, '', 'fakehover'); - if(dtick === 'D1' && ax._id.charAt(0) === 'y') { - out.dy -= out.fontSize / 6; - } - } - } - else if(dtick.charAt(0) === 'D') { - out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1)))); - out.fontSize *= 0.75; - } - else throw 'unrecognized dtick ' + String(dtick); +function formatLog (ax, out, hover, extraPrecision, hideexp) { + var dtick = ax.dtick, + x = out.x + if (extraPrecision && ((typeof dtick !== 'string') || dtick.charAt(0) !== 'L')) dtick = 'L3' + + if (ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) { + out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision) + } else if (isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) { + if (['e', 'E', 'power'].indexOf(ax.exponentformat) !== -1) { + var p = Math.round(x) + if (p === 0) out.text = 1 + else if (p === 1) out.text = '10' + else if (p > 1) out.text = '10' + p + '' + else out.text = '10\u2212' + -p + '' + + out.fontSize *= 1.25 + } else { + out.text = numFormat(Math.pow(10, x), ax, '', 'fakehover') + if (dtick === 'D1' && ax._id.charAt(0) === 'y') { + out.dy -= out.fontSize / 6 + } + } + } else if (dtick.charAt(0) === 'D') { + out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1)))) + out.fontSize *= 0.75 + } else throw 'unrecognized dtick ' + String(dtick) // if 9's are printed on log scale, move the 10's away a bit - if(ax.dtick === 'D1') { - var firstChar = String(out.text).charAt(0); - if(firstChar === '0' || firstChar === '1') { - if(ax._id.charAt(0) === 'y') { - out.dx -= out.fontSize / 4; - } - else { - out.dy += out.fontSize / 2; - out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) * - out.fontSize * (x < 0 ? 0.5 : 0.25); - } - } - } + if (ax.dtick === 'D1') { + var firstChar = String(out.text).charAt(0) + if (firstChar === '0' || firstChar === '1') { + if (ax._id.charAt(0) === 'y') { + out.dx -= out.fontSize / 4 + } else { + out.dy += out.fontSize / 2 + out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) * + out.fontSize * (x < 0 ? 0.5 : 0.25) + } + } + } } -function formatCategory(ax, out) { - var tt = ax._categories[Math.round(out.x)]; - if(tt === undefined) tt = ''; - out.text = String(tt); +function formatCategory (ax, out) { + var tt = ax._categories[Math.round(out.x)] + if (tt === undefined) tt = '' + out.text = String(tt) } -function formatLinear(ax, out, hover, extraPrecision, hideexp) { +function formatLinear (ax, out, hover, extraPrecision, hideexp) { // don't add an exponent to zero if we're showing all exponents // so the only reason you'd show an exponent on zero is if it's the // ONLY tick to get an exponent (first or last) - if(ax.showexponent === 'all' && Math.abs(out.x / ax.dtick) < 1e-6) { - hideexp = 'hide'; - } - out.text = numFormat(out.x, ax, hideexp, extraPrecision); + if (ax.showexponent === 'all' && Math.abs(out.x / ax.dtick) < 1e-6) { + hideexp = 'hide' + } + out.text = numFormat(out.x, ax, hideexp, extraPrecision) } // format a number (tick value) according to the axis settings // new, more reliable procedure than d3.round or similar: // add half the rounding increment, then stringify and truncate // also automatically switch to sci. notation -var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T']; +var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T'] -function numFormat(v, ax, fmtoverride, hover) { +function numFormat (v, ax, fmtoverride, hover) { // negative? - var isNeg = v < 0, + var isNeg = v < 0, // max number of digits past decimal point to show - tickRound = ax._tickround, - exponentFormat = fmtoverride || ax.exponentformat || 'B', - exponent = ax._tickexponent, - tickformat = ax.tickformat, - separatethousands = ax.separatethousands; + tickRound = ax._tickround, + exponentFormat = fmtoverride || ax.exponentformat || 'B', + exponent = ax._tickexponent, + tickformat = ax.tickformat, + separatethousands = ax.separatethousands // special case for hover: set exponent just for this value, and // add a couple more digits of precision over tick labels - if(hover) { + if (hover) { // make a dummy axis obj to get the auto rounding and exponent - var ah = { - exponentformat: ax.exponentformat, - dtick: ax.showexponent === 'none' ? ax.dtick : + var ah = { + exponentformat: ax.exponentformat, + dtick: ax.showexponent === 'none' ? ax.dtick : (isNumeric(v) ? Math.abs(v) || 1 : 1), // if not showing any exponents, don't change the exponent // from what we calculate - range: ax.showexponent === 'none' ? ax.range.map(ax.r2d) : [0, v || 1] - }; - autoTickRound(ah); - tickRound = (Number(ah._tickround) || 0) + 4; - exponent = ah._tickexponent; - if(ax.hoverformat) tickformat = ax.hoverformat; + range: ax.showexponent === 'none' ? ax.range.map(ax.r2d) : [0, v || 1] } + autoTickRound(ah) + tickRound = (Number(ah._tickround) || 0) + 4 + exponent = ah._tickexponent + if (ax.hoverformat) tickformat = ax.hoverformat + } - if(tickformat) return d3.format(tickformat)(v).replace(/-/g, '\u2212'); + if (tickformat) return d3.format(tickformat)(v).replace(/-/g, '\u2212') // 'epsilon' - rounding increment - var e = Math.pow(10, -tickRound) / 2; + var e = Math.pow(10, -tickRound) / 2 // exponentFormat codes: // 'e' (1.2e+6, default) @@ -1311,74 +1261,67 @@ function numFormat(v, ax, fmtoverride, hover) { // 'power' (1.2x10^6) // 'hide' (1.2, use 3rd argument=='hide' to eg // only show exponent on last tick) - if(exponentFormat === 'none') exponent = 0; + if (exponentFormat === 'none') exponent = 0 // take the sign out, put it back manually at the end // - makes cases easier - v = Math.abs(v); - if(v < e) { + v = Math.abs(v) + if (v < e) { // 0 is just 0, but may get exponent if it's the last tick - v = '0'; - isNeg = false; - } - else { - v += e; + v = '0' + isNeg = false + } else { + v += e // take out a common exponent, if any - if(exponent) { - v *= Math.pow(10, -exponent); - tickRound += exponent; - } + if (exponent) { + v *= Math.pow(10, -exponent) + tickRound += exponent + } // round the mantissa - if(tickRound === 0) v = String(Math.floor(v)); - else if(tickRound < 0) { - v = String(Math.round(v)); - v = v.substr(0, v.length + tickRound); - for(var i = tickRound; i < 0; i++) v += '0'; - } - else { - v = String(v); - var dp = v.indexOf('.') + 1; - if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, ''); - } - // insert appropriate decimal point and thousands separator - v = Lib.numSeparate(v, ax._gd._fullLayout.separators, separatethousands); + if (tickRound === 0) v = String(Math.floor(v)) + else if (tickRound < 0) { + v = String(Math.round(v)) + v = v.substr(0, v.length + tickRound) + for (var i = tickRound; i < 0; i++) v += '0' + } else { + v = String(v) + var dp = v.indexOf('.') + 1 + if (dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, '') } + // insert appropriate decimal point and thousands separator + v = Lib.numSeparate(v, ax._gd._fullLayout.separators, separatethousands) + } // add exponent - if(exponent && exponentFormat !== 'hide') { - var signedExponent; - if(exponent < 0) signedExponent = '\u2212' + -exponent; - else if(exponentFormat !== 'power') signedExponent = '+' + exponent; - else signedExponent = String(exponent); + if (exponent && exponentFormat !== 'hide') { + var signedExponent + if (exponent < 0) signedExponent = '\u2212' + -exponent + else if (exponentFormat !== 'power') signedExponent = '+' + exponent + else signedExponent = String(exponent) - if(exponentFormat === 'e' || + if (exponentFormat === 'e' || ((exponentFormat === 'SI' || exponentFormat === 'B') && (exponent > 12 || exponent < -15))) { - v += 'e' + signedExponent; - } - else if(exponentFormat === 'E') { - v += 'E' + signedExponent; - } - else if(exponentFormat === 'power') { - v += '×10' + signedExponent + ''; - } - else if(exponentFormat === 'B' && exponent === 9) { - v += 'B'; - } - else if(exponentFormat === 'SI' || exponentFormat === 'B') { - v += SIPREFIXES[exponent / 3 + 5]; - } - } + v += 'e' + signedExponent + } else if (exponentFormat === 'E') { + v += 'E' + signedExponent + } else if (exponentFormat === 'power') { + v += '×10' + signedExponent + '' + } else if (exponentFormat === 'B' && exponent === 9) { + v += 'B' + } else if (exponentFormat === 'SI' || exponentFormat === 'B') { + v += SIPREFIXES[exponent / 3 + 5] + } + } // put sign back in and return // replace standard minus character (which is technically a hyphen) // with a true minus sign - if(isNeg) return '\u2212' + v; - return v; + if (isNeg) return '\u2212' + v + return v } - -axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/; +axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/ // getSubplots - extract all combinations of axes we need to make plots for // as an array of items like 'xy', 'x2y', 'x2y2'... @@ -1386,153 +1329,152 @@ axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/; // optionally restrict to only subplots containing axis object ax // looks both for combinations of x and y found in the data // and at axes and their anchors -axes.getSubplots = function(gd, ax) { - var subplots = []; - var i, j, sp; +axes.getSubplots = function (gd, ax) { + var subplots = [] + var i, j, sp // look for subplots in the data - var data = gd._fullData || gd.data || []; + var data = gd._fullData || gd.data || [] - for(i = 0; i < data.length; i++) { - var trace = data[i]; + for (i = 0; i < data.length; i++) { + var trace = data[i] - if(trace.visible === false || trace.visible === 'legendonly' || + if (trace.visible === false || trace.visible === 'legendonly' || !(Registry.traceIs(trace, 'cartesian') || Registry.traceIs(trace, 'gl2d')) - ) continue; + ) continue - var xId = trace.xaxis || 'x', - yId = trace.yaxis || 'y'; - sp = xId + yId; + var xId = trace.xaxis || 'x', + yId = trace.yaxis || 'y' + sp = xId + yId - if(subplots.indexOf(sp) === -1) subplots.push(sp); - } + if (subplots.indexOf(sp) === -1) subplots.push(sp) + } // look for subplots in the axes/anchors, so that we at least draw all axes - var axesList = axes.list(gd, '', true); + var axesList = axes.list(gd, '', true) - function hasAx2(sp, ax2) { - return sp.indexOf(ax2._id) !== -1; - } + function hasAx2 (sp, ax2) { + return sp.indexOf(ax2._id) !== -1 + } - for(i = 0; i < axesList.length; i++) { - var ax2 = axesList[i], - ax2Letter = ax2._id.charAt(0), - ax3Id = (ax2.anchor === 'free') ? + for (i = 0; i < axesList.length; i++) { + var ax2 = axesList[i], + ax2Letter = ax2._id.charAt(0), + ax3Id = (ax2.anchor === 'free') ? ((ax2Letter === 'x') ? 'y' : 'x') : ax2.anchor, - ax3 = axes.getFromId(gd, ax3Id); + ax3 = axes.getFromId(gd, ax3Id) // look if ax2 is already represented in the data - var foundAx2 = false; - for(j = 0; j < subplots.length; j++) { - if(hasAx2(subplots[j], ax2)) { - foundAx2 = true; - break; - } - } + var foundAx2 = false + for (j = 0; j < subplots.length; j++) { + if (hasAx2(subplots[j], ax2)) { + foundAx2 = true + break + } + } // ignore free axes that already represented in the data - if(ax2.anchor === 'free' && foundAx2) continue; + if (ax2.anchor === 'free' && foundAx2) continue // ignore anchor-less axes - if(!ax3) continue; + if (!ax3) continue - sp = (ax2Letter === 'x') ? + sp = (ax2Letter === 'x') ? ax2._id + ax3._id : - ax3._id + ax2._id; + ax3._id + ax2._id - if(subplots.indexOf(sp) === -1) subplots.push(sp); - } + if (subplots.indexOf(sp) === -1) subplots.push(sp) + } // filter invalid subplots - var spMatch = axes.subplotMatch, - allSubplots = []; + var spMatch = axes.subplotMatch, + allSubplots = [] - for(i = 0; i < subplots.length; i++) { - sp = subplots[i]; - if(spMatch.test(sp)) allSubplots.push(sp); - } + for (i = 0; i < subplots.length; i++) { + sp = subplots[i] + if (spMatch.test(sp)) allSubplots.push(sp) + } // sort the subplot ids - allSubplots.sort(function(a, b) { - var aMatch = a.match(spMatch), - bMatch = b.match(spMatch); + allSubplots.sort(function (a, b) { + var aMatch = a.match(spMatch), + bMatch = b.match(spMatch) - if(aMatch[1] === bMatch[1]) { - return +(aMatch[2] || 1) - (bMatch[2] || 1); - } + if (aMatch[1] === bMatch[1]) { + return +(aMatch[2] || 1) - (bMatch[2] || 1) + } - return +(aMatch[1]||0) - (bMatch[1]||0); - }); + return +(aMatch[1] || 0) - (bMatch[1] || 0) + }) - if(ax) return axes.findSubplotsWithAxis(allSubplots, ax); - return allSubplots; -}; + if (ax) return axes.findSubplotsWithAxis(allSubplots, ax) + return allSubplots +} // find all subplots with axis 'ax' -axes.findSubplotsWithAxis = function(subplots, ax) { - var axMatch = new RegExp( +axes.findSubplotsWithAxis = function (subplots, ax) { + var axMatch = new RegExp( (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') - ); - var subplotsWithAxis = []; + ) + var subplotsWithAxis = [] - for(var i = 0; i < subplots.length; i++) { - var sp = subplots[i]; - if(axMatch.test(sp)) subplotsWithAxis.push(sp); - } + for (var i = 0; i < subplots.length; i++) { + var sp = subplots[i] + if (axMatch.test(sp)) subplotsWithAxis.push(sp) + } - return subplotsWithAxis; -}; + return subplotsWithAxis +} // makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings -axes.makeClipPaths = function(gd) { - var fullLayout = gd._fullLayout, - defs = fullLayout._defs, - fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}, - fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}, - xaList = axes.list(gd, 'x', true), - yaList = axes.list(gd, 'y', true), - clipList = [], - i, - j; - - for(i = 0; i < xaList.length; i++) { - clipList.push({x: xaList[i], y: fullHeight}); - for(j = 0; j < yaList.length; j++) { - if(i === 0) clipList.push({x: fullWidth, y: yaList[j]}); - clipList.push({x: xaList[i], y: yaList[j]}); - } - } - - var defGroup = defs.selectAll('g.clips') - .data([0]); - - defGroup.enter().append('g') - .classed('clips', true); +axes.makeClipPaths = function (gd) { + var fullLayout = gd._fullLayout, + defs = fullLayout._defs, + fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}, + fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}, + xaList = axes.list(gd, 'x', true), + yaList = axes.list(gd, 'y', true), + clipList = [], + i, + j + + for (i = 0; i < xaList.length; i++) { + clipList.push({x: xaList[i], y: fullHeight}) + for (j = 0; j < yaList.length; j++) { + if (i === 0) clipList.push({x: fullWidth, y: yaList[j]}) + clipList.push({x: xaList[i], y: yaList[j]}) + } + } + + var defGroup = defs.selectAll('g.clips') + .data([0]) + + defGroup.enter().append('g') + .classed('clips', true) // selectors don't work right with camelCase tags, // have to use class instead // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I - var axClips = defGroup.selectAll('.axesclip') - .data(clipList, function(d) { return d.x._id + d.y._id; }); + var axClips = defGroup.selectAll('.axesclip') + .data(clipList, function (d) { return d.x._id + d.y._id }) - axClips.enter().append('clipPath') + axClips.enter().append('clipPath') .classed('axesclip', true) - .attr('id', function(d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id; }) - .append('rect'); - - axClips.exit().remove(); - - axClips.each(function(d) { - d3.select(this).select('rect').attr({ - x: d.x._offset || 0, - y: d.y._offset || 0, - width: d.x._length || 1, - height: d.y._length || 1 - }); - }); -}; - + .attr('id', function (d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id }) + .append('rect') + + axClips.exit().remove() + + axClips.each(function (d) { + d3.select(this).select('rect').attr({ + x: d.x._offset || 0, + y: d.y._offset || 0, + width: d.x._length || 1, + height: d.y._length || 1 + }) + }) +} // doTicks: draw ticks, grids, and tick labels // axid: 'x', 'y', 'x2' etc, @@ -1541,664 +1483,651 @@ axes.makeClipPaths = function(gd) { // ax._r (stored range for use by zoom/pan) // ax._rl (stored linearized range for use by zoom/pan) // or can pass in an axis object directly -axes.doTicks = function(gd, axid, skipTitle) { - var fullLayout = gd._fullLayout, - ax, - independent = false; +axes.doTicks = function (gd, axid, skipTitle) { + var fullLayout = gd._fullLayout, + ax, + independent = false // allow passing an independent axis object instead of id - if(typeof axid === 'object') { - ax = axid; - axid = ax._id; - independent = true; - } - else { - ax = axes.getFromId(gd, axid); - - if(axid === 'redraw') { - fullLayout._paper.selectAll('g.subplot').each(function(subplot) { - var plotinfo = fullLayout._plots[subplot], - xa = plotinfo.xaxis, - ya = plotinfo.yaxis; - - plotinfo.xaxislayer - .selectAll('.' + xa._id + 'tick').remove(); - plotinfo.yaxislayer - .selectAll('.' + ya._id + 'tick').remove(); - plotinfo.gridlayer - .selectAll('path').remove(); - plotinfo.zerolinelayer - .selectAll('path').remove(); - }); - } - - if(!axid || axid === 'redraw') { - return Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) { - return function() { - if(!ax._id) return; - var axDone = axes.doTicks(gd, ax._id); - if(axid === 'redraw') { - ax._r = ax.range.slice(); - ax._rl = Lib.simpleMap(ax._r, ax.r2l); - } - return axDone; - }; - })); - } - } + if (typeof axid === 'object') { + ax = axid + axid = ax._id + independent = true + } else { + ax = axes.getFromId(gd, axid) + + if (axid === 'redraw') { + fullLayout._paper.selectAll('g.subplot').each(function (subplot) { + var plotinfo = fullLayout._plots[subplot], + xa = plotinfo.xaxis, + ya = plotinfo.yaxis + + plotinfo.xaxislayer + .selectAll('.' + xa._id + 'tick').remove() + plotinfo.yaxislayer + .selectAll('.' + ya._id + 'tick').remove() + plotinfo.gridlayer + .selectAll('path').remove() + plotinfo.zerolinelayer + .selectAll('path').remove() + }) + } + + if (!axid || axid === 'redraw') { + return Lib.syncOrAsync(axes.list(gd, '', true).map(function (ax) { + return function () { + if (!ax._id) return + var axDone = axes.doTicks(gd, ax._id) + if (axid === 'redraw') { + ax._r = ax.range.slice() + ax._rl = Lib.simpleMap(ax._r, ax.r2l) + } + return axDone + } + })) + } + } // make sure we only have allowed options for exponents // (others can make confusing errors) - if(!ax.tickformat) { - if(['none', 'e', 'E', 'power', 'SI', 'B'].indexOf(ax.exponentformat) === -1) { - ax.exponentformat = 'e'; - } - if(['all', 'first', 'last', 'none'].indexOf(ax.showexponent) === -1) { - ax.showexponent = 'all'; - } + if (!ax.tickformat) { + if (['none', 'e', 'E', 'power', 'SI', 'B'].indexOf(ax.exponentformat) === -1) { + ax.exponentformat = 'e' } + if (['all', 'first', 'last', 'none'].indexOf(ax.showexponent) === -1) { + ax.showexponent = 'all' + } + } // set scaling to pixels - ax.setScale(); - - var axletter = axid.charAt(0), - counterLetter = axes.counterLetter(axid), - vals = axes.calcTicks(ax), - datafn = function(d) { return [d.text, d.x, ax.mirror].join('_'); }, - tcls = axid + 'tick', - gcls = axid + 'grid', - zcls = axid + 'zl', - pad = (ax.linewidth || 1) / 2, - labelStandoff = + ax.setScale() + + var axletter = axid.charAt(0), + counterLetter = axes.counterLetter(axid), + vals = axes.calcTicks(ax), + datafn = function (d) { return [d.text, d.x, ax.mirror].join('_') }, + tcls = axid + 'tick', + gcls = axid + 'grid', + zcls = axid + 'zl', + pad = (ax.linewidth || 1) / 2, + labelStandoff = (ax.ticks === 'outside' ? ax.ticklen : 1) + (ax.linewidth || 0), - labelShift = 0, - gridWidth = Drawing.crispRound(gd, ax.gridwidth, 1), - zeroLineWidth = Drawing.crispRound(gd, ax.zerolinewidth, gridWidth), - tickWidth = Drawing.crispRound(gd, ax.tickwidth, 1), - sides, transfn, tickpathfn, - i; - - if(ax._counterangle && ax.ticks === 'outside') { - var caRad = ax._counterangle * Math.PI / 180; - labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0); - labelShift = ax.ticklen * Math.sin(caRad); - } + labelShift = 0, + gridWidth = Drawing.crispRound(gd, ax.gridwidth, 1), + zeroLineWidth = Drawing.crispRound(gd, ax.zerolinewidth, gridWidth), + tickWidth = Drawing.crispRound(gd, ax.tickwidth, 1), + sides, transfn, tickpathfn, + i + + if (ax._counterangle && ax.ticks === 'outside') { + var caRad = ax._counterangle * Math.PI / 180 + labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0) + labelShift = ax.ticklen * Math.sin(caRad) + } // positioning arguments for x vs y axes - if(axletter === 'x') { - sides = ['bottom', 'top']; - transfn = function(d) { - return 'translate(' + ax.l2p(d.x) + ',0)'; - }; - tickpathfn = function(shift, len) { - if(ax._counterangle) { - var caRad = ax._counterangle * Math.PI / 180; - return 'M0,' + shift + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len); - } - else return 'M0,' + shift + 'v' + len; - }; - } - else if(axletter === 'y') { - sides = ['left', 'right']; - transfn = function(d) { - return 'translate(0,' + ax.l2p(d.x) + ')'; - }; - tickpathfn = function(shift, len) { - if(ax._counterangle) { - var caRad = ax._counterangle * Math.PI / 180; - return 'M' + shift + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len); - } - else return 'M' + shift + ',0h' + len; - }; - } - else { - Lib.warn('Unrecognized doTicks axis:', axid); - return; - } - var axside = ax.side || sides[0], + if (axletter === 'x') { + sides = ['bottom', 'top'] + transfn = function (d) { + return 'translate(' + ax.l2p(d.x) + ',0)' + } + tickpathfn = function (shift, len) { + if (ax._counterangle) { + var caRad = ax._counterangle * Math.PI / 180 + return 'M0,' + shift + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len) + } else return 'M0,' + shift + 'v' + len + } + } else if (axletter === 'y') { + sides = ['left', 'right'] + transfn = function (d) { + return 'translate(0,' + ax.l2p(d.x) + ')' + } + tickpathfn = function (shift, len) { + if (ax._counterangle) { + var caRad = ax._counterangle * Math.PI / 180 + return 'M' + shift + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len) + } else return 'M' + shift + ',0h' + len + } + } else { + Lib.warn('Unrecognized doTicks axis:', axid) + return + } + var axside = ax.side || sides[0], // which direction do the side[0], side[1], and free ticks go? // then we flip if outside XOR y axis - ticksign = [-1, 1, axside === sides[1] ? 1 : -1]; - if((ax.ticks !== 'inside') === (axletter === 'x')) { - ticksign = ticksign.map(function(v) { return -v; }); - } + ticksign = [-1, 1, axside === sides[1] ? 1 : -1] + if ((ax.ticks !== 'inside') === (axletter === 'x')) { + ticksign = ticksign.map(function (v) { return -v }) + } // remove zero lines, grid lines, and inside ticks if they're within // 1 pixel of the end // The key case here is removing zero lines when the axis bound is zero. - function clipEnds(d) { - var p = ax.l2p(d.x); - return (p > 1 && p < ax._length - 1); - } - var valsClipped = vals.filter(clipEnds); - - function drawTicks(container, tickpath) { - var ticks = container.selectAll('path.' + tcls) - .data(ax.ticks === 'inside' ? valsClipped : vals, datafn); - if(tickpath && ax.ticks) { - ticks.enter().append('path').classed(tcls, 1).classed('ticks', 1) + function clipEnds (d) { + var p = ax.l2p(d.x) + return (p > 1 && p < ax._length - 1) + } + var valsClipped = vals.filter(clipEnds) + + function drawTicks (container, tickpath) { + var ticks = container.selectAll('path.' + tcls) + .data(ax.ticks === 'inside' ? valsClipped : vals, datafn) + if (tickpath && ax.ticks) { + ticks.enter().append('path').classed(tcls, 1).classed('ticks', 1) .classed('crisp', 1) .call(Color.stroke, ax.tickcolor) .style('stroke-width', tickWidth + 'px') - .attr('d', tickpath); - ticks.attr('transform', transfn); - ticks.exit().remove(); - } - else ticks.remove(); - } + .attr('d', tickpath) + ticks.attr('transform', transfn) + ticks.exit().remove() + } else ticks.remove() + } - function drawLabels(container, position) { + function drawLabels (container, position) { // tick labels - for now just the main labels. // TODO: mirror labels, esp for subplots - var tickLabels = container.selectAll('g.' + tcls).data(vals, datafn); - if(!ax.showticklabels || !isNumeric(position)) { - tickLabels.remove(); - drawAxTitle(axid); - return; - } - - var labelx, labely, labelanchor, labelpos0, flipit; - if(axletter === 'x') { - flipit = (axside === 'bottom') ? 1 : -1; - labelx = function(d) { return d.dx + labelShift * flipit; }; - labelpos0 = position + (labelStandoff + pad) * flipit; - labely = function(d) { - return d.dy + labelpos0 + d.fontSize * - ((axside === 'bottom') ? 1 : -0.5); - }; - labelanchor = function(angle) { - if(!isNumeric(angle) || angle === 0 || angle === 180) { - return 'middle'; - } - return (angle * flipit < 0) ? 'end' : 'start'; - }; - } - else { - flipit = (axside === 'right') ? 1 : -1; - labely = function(d) { return d.dy + d.fontSize / 2 - labelShift * flipit; }; - labelx = function(d) { - return d.dx + position + (labelStandoff + pad + - ((Math.abs(ax.tickangle) === 90) ? d.fontSize / 2 : 0)) * flipit; - }; - labelanchor = function(angle) { - if(isNumeric(angle) && Math.abs(angle) === 90) { - return 'middle'; - } - return axside === 'right' ? 'start' : 'end'; - }; - } - var maxFontSize = 0, - autoangle = 0, - labelsReady = []; - tickLabels.enter().append('g').classed(tcls, 1) + var tickLabels = container.selectAll('g.' + tcls).data(vals, datafn) + if (!ax.showticklabels || !isNumeric(position)) { + tickLabels.remove() + drawAxTitle(axid) + return + } + + var labelx, labely, labelanchor, labelpos0, flipit + if (axletter === 'x') { + flipit = (axside === 'bottom') ? 1 : -1 + labelx = function (d) { return d.dx + labelShift * flipit } + labelpos0 = position + (labelStandoff + pad) * flipit + labely = function (d) { + return d.dy + labelpos0 + d.fontSize * + ((axside === 'bottom') ? 1 : -0.5) + } + labelanchor = function (angle) { + if (!isNumeric(angle) || angle === 0 || angle === 180) { + return 'middle' + } + return (angle * flipit < 0) ? 'end' : 'start' + } + } else { + flipit = (axside === 'right') ? 1 : -1 + labely = function (d) { return d.dy + d.fontSize / 2 - labelShift * flipit } + labelx = function (d) { + return d.dx + position + (labelStandoff + pad + + ((Math.abs(ax.tickangle) === 90) ? d.fontSize / 2 : 0)) * flipit + } + labelanchor = function (angle) { + if (isNumeric(angle) && Math.abs(angle) === 90) { + return 'middle' + } + return axside === 'right' ? 'start' : 'end' + } + } + var maxFontSize = 0, + autoangle = 0, + labelsReady = [] + tickLabels.enter().append('g').classed(tcls, 1) .append('text') // only so tex has predictable alignment that we can // alter later .attr('text-anchor', 'middle') - .each(function(d) { - var thisLabel = d3.select(this), - newPromise = gd._promises.length; - thisLabel + .each(function (d) { + var thisLabel = d3.select(this), + newPromise = gd._promises.length + thisLabel .call(Drawing.setPosition, labelx(d), labely(d)) .call(Drawing.font, d.font, d.fontSize, d.fontColor) .text(d.text) - .call(svgTextUtils.convertToTspans); - newPromise = gd._promises[newPromise]; - if(newPromise) { + .call(svgTextUtils.convertToTspans) + newPromise = gd._promises[newPromise] + if (newPromise) { // if we have an async label, we'll deal with that // all here so take it out of gd._promises and // instead position the label and promise this in // labelsReady - labelsReady.push(gd._promises.pop().then(function() { - positionLabels(thisLabel, ax.tickangle); - })); - } - else { + labelsReady.push(gd._promises.pop().then(function () { + positionLabels(thisLabel, ax.tickangle) + })) + } else { // sync label: just position it now. - positionLabels(thisLabel, ax.tickangle); - } - }); - tickLabels.exit().remove(); - - tickLabels.each(function(d) { - maxFontSize = Math.max(maxFontSize, d.fontSize); - }); - - function positionLabels(s, angle) { - s.each(function(d) { - var anchor = labelanchor(angle); - var thisLabel = d3.select(this), - mathjaxGroup = thisLabel.select('.text-math-group'), - transform = transfn(d) + + positionLabels(thisLabel, ax.tickangle) + } + }) + tickLabels.exit().remove() + + tickLabels.each(function (d) { + maxFontSize = Math.max(maxFontSize, d.fontSize) + }) + + function positionLabels (s, angle) { + s.each(function (d) { + var anchor = labelanchor(angle) + var thisLabel = d3.select(this), + mathjaxGroup = thisLabel.select('.text-math-group'), + transform = transfn(d) + ((isNumeric(angle) && +angle !== 0) ? (' rotate(' + angle + ',' + labelx(d) + ',' + (labely(d) - d.fontSize / 2) + ')') : - ''); - if(mathjaxGroup.empty()) { - var txt = thisLabel.select('text').attr({ - transform: transform, - 'text-anchor': anchor - }); - - if(!txt.empty()) { - txt.selectAll('tspan.line').attr({ - x: txt.attr('x'), - y: txt.attr('y') - }); - } - } - else { - var mjShift = + '') + if (mathjaxGroup.empty()) { + var txt = thisLabel.select('text').attr({ + transform: transform, + 'text-anchor': anchor + }) + + if (!txt.empty()) { + txt.selectAll('tspan.line').attr({ + x: txt.attr('x'), + y: txt.attr('y') + }) + } + } else { + var mjShift = Drawing.bBox(mathjaxGroup.node()).width * - {end: -0.5, start: 0.5}[anchor]; - mathjaxGroup.attr('transform', transform + - (mjShift ? 'translate(' + mjShift + ',0)' : '')); - } - }); + {end: -0.5, start: 0.5}[anchor] + mathjaxGroup.attr('transform', transform + + (mjShift ? 'translate(' + mjShift + ',0)' : '')) } + }) + } // make sure all labels are correctly positioned at their base angle // the positionLabels call above is only for newly drawn labels. // do this without waiting, using the last calculated angle to // minimize flicker, then do it again when we know all labels are // there, putting back the prescribed angle to check for overlaps. - positionLabels(tickLabels, ax._lastangle || ax.tickangle); + positionLabels(tickLabels, ax._lastangle || ax.tickangle) - function allLabelsReady() { - return labelsReady.length && Promise.all(labelsReady); - } + function allLabelsReady () { + return labelsReady.length && Promise.all(labelsReady) + } - function fixLabelOverlaps() { - positionLabels(tickLabels, ax.tickangle); + function fixLabelOverlaps () { + positionLabels(tickLabels, ax.tickangle) // check for auto-angling if x labels overlap // don't auto-angle at all for log axes with // base and digit format - if(axletter === 'x' && !isNumeric(ax.tickangle) && + if (axletter === 'x' && !isNumeric(ax.tickangle) && (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')) { - var lbbArray = []; - tickLabels.each(function(d) { - var s = d3.select(this), - thisLabel = s.select('.text-math-group'), - x = ax.l2p(d.x); - if(thisLabel.empty()) thisLabel = s.select('text'); + var lbbArray = [] + tickLabels.each(function (d) { + var s = d3.select(this), + thisLabel = s.select('.text-math-group'), + x = ax.l2p(d.x) + if (thisLabel.empty()) thisLabel = s.select('text') - var bb = Drawing.bBox(thisLabel.node()); + var bb = Drawing.bBox(thisLabel.node()) - lbbArray.push({ + lbbArray.push({ // ignore about y, just deal with x overlaps - top: 0, - bottom: 10, - height: 10, - left: x - bb.width / 2, + top: 0, + bottom: 10, + height: 10, + left: x - bb.width / 2, // impose a 2px gap - right: x + bb.width / 2 + 2, - width: bb.width + 2 - }); - }); - for(i = 0; i < lbbArray.length - 1; i++) { - if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { + right: x + bb.width / 2 + 2, + width: bb.width + 2 + }) + }) + for (i = 0; i < lbbArray.length - 1; i++) { + if (Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) { // any overlap at all - set 30 degrees - autoangle = 30; - break; - } - } - if(autoangle) { - var tickspacing = Math.abs( + autoangle = 30 + break + } + } + if (autoangle) { + var tickspacing = Math.abs( (vals[vals.length - 1].x - vals[0].x) * ax._m - ) / (vals.length - 1); - if(tickspacing < maxFontSize * 2.5) { - autoangle = 90; - } - positionLabels(tickLabels, autoangle); - } - ax._lastangle = autoangle; - } + ) / (vals.length - 1) + if (tickspacing < maxFontSize * 2.5) { + autoangle = 90 + } + positionLabels(tickLabels, autoangle) + } + ax._lastangle = autoangle + } // update the axis title // (so it can move out of the way if needed) // TODO: separate out scoot so we don't need to do // a full redraw of the title (mostly relevant for MathJax) - drawAxTitle(axid); - return axid + ' done'; - } - - function calcBoundingBox() { - ax._boundingBox = container.node().getBoundingClientRect(); - } + drawAxTitle(axid) + return axid + ' done' + } - var done = Lib.syncOrAsync([ - allLabelsReady, - fixLabelOverlaps, - calcBoundingBox - ]); - if(done && done.then) gd._promises.push(done); - return done; + function calcBoundingBox () { + ax._boundingBox = container.node().getBoundingClientRect() } - function drawAxTitle(axid) { - if(skipTitle) return; + var done = Lib.syncOrAsync([ + allLabelsReady, + fixLabelOverlaps, + calcBoundingBox + ]) + if (done && done.then) gd._promises.push(done) + return done + } + + function drawAxTitle (axid) { + if (skipTitle) return // now this only applies to regular cartesian axes; colorbars and // others ALWAYS call doTicks with skipTitle=true so they can // configure their own titles. - var ax = axisIds.getFromId(gd, axid), - avoidSelection = d3.select(gd).selectAll('g.' + axid + 'tick'), - avoid = { - selection: avoidSelection, - side: ax.side - }, - axLetter = axid.charAt(0), - gs = gd._fullLayout._size, - offsetBase = 1.5, - fontSize = ax.titlefont.size, - transform, - counterAxis, - x, - y; - if(avoidSelection.size()) { - var translation = Drawing.getTranslate(avoidSelection.node().parentNode); - avoid.offsetLeft = translation.x; - avoid.offsetTop = translation.y; - } - - if(axLetter === 'x') { - counterAxis = (ax.anchor === 'free') ? + var ax = axisIds.getFromId(gd, axid), + avoidSelection = d3.select(gd).selectAll('g.' + axid + 'tick'), + avoid = { + selection: avoidSelection, + side: ax.side + }, + axLetter = axid.charAt(0), + gs = gd._fullLayout._size, + offsetBase = 1.5, + fontSize = ax.titlefont.size, + transform, + counterAxis, + x, + y + if (avoidSelection.size()) { + var translation = Drawing.getTranslate(avoidSelection.node().parentNode) + avoid.offsetLeft = translation.x + avoid.offsetTop = translation.y + } + + if (axLetter === 'x') { + counterAxis = (ax.anchor === 'free') ? {_offset: gs.t + (1 - (ax.position || 0)) * gs.h, _length: 0} : - axisIds.getFromId(gd, ax.anchor); + axisIds.getFromId(gd, ax.anchor) - x = ax._offset + ax._length / 2; - y = counterAxis._offset + ((ax.side === 'top') ? + x = ax._offset + ax._length / 2 + y = counterAxis._offset + ((ax.side === 'top') ? -10 - fontSize * (offsetBase + (ax.showticklabels ? 1 : 0)) : counterAxis._length + 10 + - fontSize * (offsetBase + (ax.showticklabels ? 1.5 : 0.5))); + fontSize * (offsetBase + (ax.showticklabels ? 1.5 : 0.5))) - if(ax.rangeslider && ax.rangeslider.visible && ax._boundingBox) { - y += (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * - ax.rangeslider.thickness + ax._boundingBox.height; - } + if (ax.rangeslider && ax.rangeslider.visible && ax._boundingBox) { + y += (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * + ax.rangeslider.thickness + ax._boundingBox.height + } - if(!avoid.side) avoid.side = 'bottom'; - } - else { - counterAxis = (ax.anchor === 'free') ? + if (!avoid.side) avoid.side = 'bottom' + } else { + counterAxis = (ax.anchor === 'free') ? {_offset: gs.l + (ax.position || 0) * gs.w, _length: 0} : - axisIds.getFromId(gd, ax.anchor); + axisIds.getFromId(gd, ax.anchor) - y = ax._offset + ax._length / 2; - x = counterAxis._offset + ((ax.side === 'right') ? + y = ax._offset + ax._length / 2 + x = counterAxis._offset + ((ax.side === 'right') ? counterAxis._length + 10 + fontSize * (offsetBase + (ax.showticklabels ? 1 : 0.5)) : - -10 - fontSize * (offsetBase + (ax.showticklabels ? 0.5 : 0))); - - transform = {rotate: '-90', offset: 0}; - if(!avoid.side) avoid.side = 'left'; - } - - Titles.draw(gd, axid + 'title', { - propContainer: ax, - propName: ax._name + '.title', - dfltName: axLetter.toUpperCase() + ' axis', - avoid: avoid, - transform: transform, - attributes: {x: x, y: y, 'text-anchor': 'middle'} - }); - } - - function traceHasBarsOrFill(trace, subplot) { - if(trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) return false; - if(Registry.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axletter]) return true; - return trace.fill && trace.fill.charAt(trace.fill.length - 1) === axletter; - } - - function drawGrid(plotinfo, counteraxis, subplot) { - var gridcontainer = plotinfo.gridlayer, - zlcontainer = plotinfo.zerolinelayer, - gridvals = plotinfo['hidegrid' + axletter] ? [] : valsClipped, - gridpath = ax._gridpath || + -10 - fontSize * (offsetBase + (ax.showticklabels ? 0.5 : 0))) + + transform = {rotate: '-90', offset: 0} + if (!avoid.side) avoid.side = 'left' + } + + Titles.draw(gd, axid + 'title', { + propContainer: ax, + propName: ax._name + '.title', + dfltName: axLetter.toUpperCase() + ' axis', + avoid: avoid, + transform: transform, + attributes: {x: x, y: y, 'text-anchor': 'middle'} + }) + } + + function traceHasBarsOrFill (trace, subplot) { + if (trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) return false + if (Registry.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axletter]) return true + return trace.fill && trace.fill.charAt(trace.fill.length - 1) === axletter + } + + function drawGrid (plotinfo, counteraxis, subplot) { + var gridcontainer = plotinfo.gridlayer, + zlcontainer = plotinfo.zerolinelayer, + gridvals = plotinfo['hidegrid' + axletter] ? [] : valsClipped, + gridpath = ax._gridpath || 'M0,0' + ((axletter === 'x') ? 'v' : 'h') + counteraxis._length, - grid = gridcontainer.selectAll('path.' + gcls) - .data((ax.showgrid === false) ? [] : gridvals, datafn); - grid.enter().append('path').classed(gcls, 1) + grid = gridcontainer.selectAll('path.' + gcls) + .data((ax.showgrid === false) ? [] : gridvals, datafn) + grid.enter().append('path').classed(gcls, 1) .classed('crisp', 1) .attr('d', gridpath) - .each(function(d) { - if(ax.zeroline && (ax.type === 'linear' || ax.type === '-') && + .each(function (d) { + if (ax.zeroline && (ax.type === 'linear' || ax.type === '-') && Math.abs(d.x) < ax.dtick / 100) { - d3.select(this).remove(); - } - }); - grid.attr('transform', transfn) + d3.select(this).remove() + } + }) + grid.attr('transform', transfn) .call(Color.stroke, ax.gridcolor || '#ddd') - .style('stroke-width', gridWidth + 'px'); - grid.exit().remove(); + .style('stroke-width', gridWidth + 'px') + grid.exit().remove() // zero line - if(zlcontainer) { - var hasBarsOrFill = false; - for(var i = 0; i < gd._fullData.length; i++) { - if(traceHasBarsOrFill(gd._fullData[i], subplot)) { - hasBarsOrFill = true; - break; - } - } - var rng = Lib.simpleMap(ax.range, ax.r2l), - showZl = (rng[0] * rng[1] <= 0) && ax.zeroline && + if (zlcontainer) { + var hasBarsOrFill = false + for (var i = 0; i < gd._fullData.length; i++) { + if (traceHasBarsOrFill(gd._fullData[i], subplot)) { + hasBarsOrFill = true + break + } + } + var rng = Lib.simpleMap(ax.range, ax.r2l), + showZl = (rng[0] * rng[1] <= 0) && ax.zeroline && (ax.type === 'linear' || ax.type === '-') && gridvals.length && - (hasBarsOrFill || clipEnds({x: 0}) || !ax.showline); - var zl = zlcontainer.selectAll('path.' + zcls) - .data(showZl ? [{x: 0}] : []); - zl.enter().append('path').classed(zcls, 1).classed('zl', 1) + (hasBarsOrFill || clipEnds({x: 0}) || !ax.showline) + var zl = zlcontainer.selectAll('path.' + zcls) + .data(showZl ? [{x: 0}] : []) + zl.enter().append('path').classed(zcls, 1).classed('zl', 1) .classed('crisp', 1) - .attr('d', gridpath); - zl.attr('transform', transfn) + .attr('d', gridpath) + zl.attr('transform', transfn) .call(Color.stroke, ax.zerolinecolor || Color.defaultLine) - .style('stroke-width', zeroLineWidth + 'px'); - zl.exit().remove(); - } + .style('stroke-width', zeroLineWidth + 'px') + zl.exit().remove() } + } - if(independent) { - drawTicks(ax._axislayer, tickpathfn(ax._pos + pad * ticksign[2], ticksign[2] * ax.ticklen)); - if(ax._counteraxis) { - var fictionalPlotinfo = { - gridlayer: ax._gridlayer, - zerolinelayer: ax._zerolinelayer - }; - drawGrid(fictionalPlotinfo, ax._counteraxis); - } - return drawLabels(ax._axislayer, ax._pos); + if (independent) { + drawTicks(ax._axislayer, tickpathfn(ax._pos + pad * ticksign[2], ticksign[2] * ax.ticklen)) + if (ax._counteraxis) { + var fictionalPlotinfo = { + gridlayer: ax._gridlayer, + zerolinelayer: ax._zerolinelayer + } + drawGrid(fictionalPlotinfo, ax._counteraxis) } - else { - var alldone = axes.getSubplots(gd, ax).map(function(subplot) { - var plotinfo = fullLayout._plots[subplot]; + return drawLabels(ax._axislayer, ax._pos) + } else { + var alldone = axes.getSubplots(gd, ax).map(function (subplot) { + var plotinfo = fullLayout._plots[subplot] - if(!fullLayout._has('cartesian')) return; + if (!fullLayout._has('cartesian')) return - var container = plotinfo[axletter + 'axislayer'], + var container = plotinfo[axletter + 'axislayer'], // [bottom or left, top or right, free, main] - linepositions = ax._linepositions[subplot] || [], - counteraxis = plotinfo[counterLetter + 'axis'], - mainSubplot = counteraxis._id === ax.anchor, - ticksides = [false, false, false], - tickpath = ''; + linepositions = ax._linepositions[subplot] || [], + counteraxis = plotinfo[counterLetter + 'axis'], + mainSubplot = counteraxis._id === ax.anchor, + ticksides = [false, false, false], + tickpath = '' // ticks - if(ax.mirror === 'allticks') ticksides = [true, true, false]; - else if(mainSubplot) { - if(ax.mirror === 'ticks') ticksides = [true, true, false]; - else ticksides[sides.indexOf(axside)] = true; - } - if(ax.mirrors) { - for(i = 0; i < 2; i++) { - var thisMirror = ax.mirrors[counteraxis._id + sides[i]]; - if(thisMirror === 'ticks' || thisMirror === 'labels') { - ticksides[i] = true; - } - } - } + if (ax.mirror === 'allticks') ticksides = [true, true, false] + else if (mainSubplot) { + if (ax.mirror === 'ticks') ticksides = [true, true, false] + else ticksides[sides.indexOf(axside)] = true + } + if (ax.mirrors) { + for (i = 0; i < 2; i++) { + var thisMirror = ax.mirrors[counteraxis._id + sides[i]] + if (thisMirror === 'ticks' || thisMirror === 'labels') { + ticksides[i] = true + } + } + } // free axis ticks - if(linepositions[2] !== undefined) ticksides[2] = true; + if (linepositions[2] !== undefined) ticksides[2] = true - ticksides.forEach(function(showside, sidei) { - var pos = linepositions[sidei], - tsign = ticksign[sidei]; - if(showside && isNumeric(pos)) { - tickpath += tickpathfn(pos + pad * tsign, tsign * ax.ticklen); - } - }); + ticksides.forEach(function (showside, sidei) { + var pos = linepositions[sidei], + tsign = ticksign[sidei] + if (showside && isNumeric(pos)) { + tickpath += tickpathfn(pos + pad * tsign, tsign * ax.ticklen) + } + }) - drawTicks(container, tickpath); - drawGrid(plotinfo, counteraxis, subplot); - return drawLabels(container, linepositions[3]); - }).filter(function(onedone) { return onedone && onedone.then; }); + drawTicks(container, tickpath) + drawGrid(plotinfo, counteraxis, subplot) + return drawLabels(container, linepositions[3]) + }).filter(function (onedone) { return onedone && onedone.then }) - return alldone.length ? Promise.all(alldone) : 0; - } -}; + return alldone.length ? Promise.all(alldone) : 0 + } +} // swap all the presentation attributes of the axes showing these traces -axes.swap = function(gd, traces) { - var axGroups = makeAxisGroups(gd, traces); +axes.swap = function (gd, traces) { + var axGroups = makeAxisGroups(gd, traces) - for(var i = 0; i < axGroups.length; i++) { - swapAxisGroup(gd, axGroups[i].x, axGroups[i].y); - } -}; + for (var i = 0; i < axGroups.length; i++) { + swapAxisGroup(gd, axGroups[i].x, axGroups[i].y) + } +} -function makeAxisGroups(gd, traces) { - var groups = [], - i, - j; +function makeAxisGroups (gd, traces) { + var groups = [], + i, + j - for(i = 0; i < traces.length; i++) { - var groupsi = [], - xi = gd._fullData[traces[i]].xaxis, - yi = gd._fullData[traces[i]].yaxis; - if(!xi || !yi) continue; // not a 2D cartesian trace? + for (i = 0; i < traces.length; i++) { + var groupsi = [], + xi = gd._fullData[traces[i]].xaxis, + yi = gd._fullData[traces[i]].yaxis + if (!xi || !yi) continue // not a 2D cartesian trace? - for(j = 0; j < groups.length; j++) { - if(groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) { - groupsi.push(j); - } - } + for (j = 0; j < groups.length; j++) { + if (groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) { + groupsi.push(j) + } + } - if(!groupsi.length) { - groups.push({x: [xi], y: [yi]}); - continue; - } + if (!groupsi.length) { + groups.push({x: [xi], y: [yi]}) + continue + } - var group0 = groups[groupsi[0]], - groupj; + var group0 = groups[groupsi[0]], + groupj - if(groupsi.length > 1) { - for(j = 1; j < groupsi.length; j++) { - groupj = groups[groupsi[j]]; - mergeAxisGroups(group0.x, groupj.x); - mergeAxisGroups(group0.y, groupj.y); - } - } - mergeAxisGroups(group0.x, [xi]); - mergeAxisGroups(group0.y, [yi]); + if (groupsi.length > 1) { + for (j = 1; j < groupsi.length; j++) { + groupj = groups[groupsi[j]] + mergeAxisGroups(group0.x, groupj.x) + mergeAxisGroups(group0.y, groupj.y) + } } + mergeAxisGroups(group0.x, [xi]) + mergeAxisGroups(group0.y, [yi]) + } - return groups; + return groups } -function mergeAxisGroups(intoSet, fromSet) { - for(var i = 0; i < fromSet.length; i++) { - if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]); - } +function mergeAxisGroups (intoSet, fromSet) { + for (var i = 0; i < fromSet.length; i++) { + if (intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]) + } } -function swapAxisGroup(gd, xIds, yIds) { - var i, - j, - xFullAxes = [], - yFullAxes = [], - layout = gd.layout; - - for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i])); - for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i])); - - var allAxKeys = Object.keys(xFullAxes[0]), - noSwapAttrs = [ - 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle' - ], - numericTypes = ['linear', 'log']; - - for(i = 0; i < allAxKeys.length; i++) { - var keyi = allAxKeys[i], - xVal = xFullAxes[0][keyi], - yVal = yFullAxes[0][keyi], - allEqual = true, - coerceLinearX = false, - coerceLinearY = false; - if(keyi.charAt(0) === '_' || typeof xVal === 'function' || +function swapAxisGroup (gd, xIds, yIds) { + var i, + j, + xFullAxes = [], + yFullAxes = [], + layout = gd.layout + + for (i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i])) + for (i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i])) + + var allAxKeys = Object.keys(xFullAxes[0]), + noSwapAttrs = [ + 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle' + ], + numericTypes = ['linear', 'log'] + + for (i = 0; i < allAxKeys.length; i++) { + var keyi = allAxKeys[i], + xVal = xFullAxes[0][keyi], + yVal = yFullAxes[0][keyi], + allEqual = true, + coerceLinearX = false, + coerceLinearY = false + if (keyi.charAt(0) === '_' || typeof xVal === 'function' || noSwapAttrs.indexOf(keyi) !== -1) { - continue; - } - for(j = 1; j < xFullAxes.length && allEqual; j++) { - var xVali = xFullAxes[j][keyi]; - if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 && + continue + } + for (j = 1; j < xFullAxes.length && allEqual; j++) { + var xVali = xFullAxes[j][keyi] + if (keyi === 'type' && numericTypes.indexOf(xVal) !== -1 && numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) { // type is special - if we find a mixture of linear and log, // coerce them all to linear on flipping - coerceLinearX = true; - } - else if(xVali !== xVal) allEqual = false; - } - for(j = 1; j < yFullAxes.length && allEqual; j++) { - var yVali = yFullAxes[j][keyi]; - if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 && + coerceLinearX = true + } else if (xVali !== xVal) allEqual = false + } + for (j = 1; j < yFullAxes.length && allEqual; j++) { + var yVali = yFullAxes[j][keyi] + if (keyi === 'type' && numericTypes.indexOf(yVal) !== -1 && numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) { // type is special - if we find a mixture of linear and log, // coerce them all to linear on flipping - coerceLinearY = true; - } - else if(yFullAxes[j][keyi] !== yVal) allEqual = false; - } - if(allEqual) { - if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear'; - if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear'; - swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes); - } + coerceLinearY = true + } else if (yFullAxes[j][keyi] !== yVal) allEqual = false + } + if (allEqual) { + if (coerceLinearX) layout[xFullAxes[0]._name].type = 'linear' + if (coerceLinearY) layout[yFullAxes[0]._name].type = 'linear' + swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes) } + } // now swap x&y for any annotations anchored to these x & y - for(i = 0; i < gd._fullLayout.annotations.length; i++) { - var ann = gd._fullLayout.annotations[i]; - if(xIds.indexOf(ann.xref) !== -1 && + for (i = 0; i < gd._fullLayout.annotations.length; i++) { + var ann = gd._fullLayout.annotations[i] + if (xIds.indexOf(ann.xref) !== -1 && yIds.indexOf(ann.yref) !== -1) { - Lib.swapAttrs(layout.annotations[i], ['?']); - } + Lib.swapAttrs(layout.annotations[i], ['?']) } + } } -function swapAxisAttrs(layout, key, xFullAxes, yFullAxes) { +function swapAxisAttrs (layout, key, xFullAxes, yFullAxes) { // in case the value is the default for either axis, // look at the first axis in each list and see if // this key's value is undefined - var np = Lib.nestedProperty, - xVal = np(layout[xFullAxes[0]._name], key).get(), - yVal = np(layout[yFullAxes[0]._name], key).get(), - i; - if(key === 'title') { + var np = Lib.nestedProperty, + xVal = np(layout[xFullAxes[0]._name], key).get(), + yVal = np(layout[yFullAxes[0]._name], key).get(), + i + if (key === 'title') { // special handling of placeholder titles - if(xVal === 'Click to enter X axis title') { - xVal = 'Click to enter Y axis title'; - } - if(yVal === 'Click to enter Y axis title') { - yVal = 'Click to enter X axis title'; - } - } - - for(i = 0; i < xFullAxes.length; i++) { - np(layout, xFullAxes[i]._name + '.' + key).set(yVal); + if (xVal === 'Click to enter X axis title') { + xVal = 'Click to enter Y axis title' } - for(i = 0; i < yFullAxes.length; i++) { - np(layout, yFullAxes[i]._name + '.' + key).set(xVal); + if (yVal === 'Click to enter Y axis title') { + yVal = 'Click to enter X axis title' } + } + + for (i = 0; i < xFullAxes.length; i++) { + np(layout, xFullAxes[i]._name + '.' + key).set(yVal) + } + for (i = 0; i < yFullAxes.length; i++) { + np(layout, yFullAxes[i]._name + '.' + key).set(xVal) + } } diff --git a/src/plots/cartesian/axis_autotype.js b/src/plots/cartesian/axis_autotype.js index 476c4ec4bff..d6a2254af09 100644 --- a/src/plots/cartesian/axis_autotype.js +++ b/src/plots/cartesian/axis_autotype.js @@ -6,31 +6,30 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var BADNUM = require('../../constants/numerical').BADNUM -var Lib = require('../../lib'); -var BADNUM = require('../../constants/numerical').BADNUM; - -module.exports = function autoType(array, calendar) { - if(moreDates(array, calendar)) return 'date'; - if(category(array)) return 'category'; - if(linearOK(array)) return 'linear'; - else return '-'; -}; +module.exports = function autoType (array, calendar) { + if (moreDates(array, calendar)) return 'date' + if (category(array)) return 'category' + if (linearOK(array)) return 'linear' + else return '-' +} // is there at least one number in array? If not, we should leave // ax.type empty so it can be autoset later -function linearOK(array) { - if(!array) return false; +function linearOK (array) { + if (!array) return false - for(var i = 0; i < array.length; i++) { - if(isNumeric(array[i])) return true; - } + for (var i = 0; i < array.length; i++) { + if (isNumeric(array[i])) return true + } - return false; + return false } // does the array a have mostly dates rather than numbers? @@ -38,36 +37,36 @@ function linearOK(array) { // 2- or 4-digit integers can be both, so require twice as many // dates as non-dates, to exclude cases with mostly 2 & 4 digit // numbers and a few dates -function moreDates(a, calendar) { - var dcnt = 0, - ncnt = 0, +function moreDates (a, calendar) { + var dcnt = 0, + ncnt = 0, // test at most 1000 points, evenly spaced - inc = Math.max(1, (a.length - 1) / 1000), - ai; + inc = Math.max(1, (a.length - 1) / 1000), + ai - for(var i = 0; i < a.length; i += inc) { - ai = a[Math.round(i)]; - if(Lib.isDateTime(ai, calendar)) dcnt += 1; - if(isNumeric(ai)) ncnt += 1; - } + for (var i = 0; i < a.length; i += inc) { + ai = a[Math.round(i)] + if (Lib.isDateTime(ai, calendar)) dcnt += 1 + if (isNumeric(ai)) ncnt += 1 + } - return (dcnt > ncnt * 2); + return (dcnt > ncnt * 2) } // are the (x,y)-values in gd.data mostly text? // require twice as many categories as numbers -function category(a) { +function category (a) { // test at most 1000 points - var inc = Math.max(1, (a.length - 1) / 1000), - curvenums = 0, - curvecats = 0, - ai; + var inc = Math.max(1, (a.length - 1) / 1000), + curvenums = 0, + curvecats = 0, + ai - for(var i = 0; i < a.length; i += inc) { - ai = a[Math.round(i)]; - if(Lib.cleanNumber(ai) !== BADNUM) curvenums++; - else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++; - } + for (var i = 0; i < a.length; i += inc) { + ai = a[Math.round(i)] + if (Lib.cleanNumber(ai) !== BADNUM) curvenums++ + else if (typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++ + } - return curvecats > curvenums * 2; + return curvecats > curvenums * 2 } diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index e4e99bf8294..2d2bb4c815c 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') +var colorMix = require('tinycolor2').mix -var isNumeric = require('fast-isnumeric'); -var colorMix = require('tinycolor2').mix; - -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var lightFraction = require('../../components/color/attributes').lightFraction; - -var layoutAttributes = require('./layout_attributes'); -var handleTickValueDefaults = require('./tick_value_defaults'); -var handleTickMarkDefaults = require('./tick_mark_defaults'); -var handleTickLabelDefaults = require('./tick_label_defaults'); -var handleCategoryOrderDefaults = require('./category_order_defaults'); -var setConvert = require('./set_convert'); -var orderedCategories = require('./ordered_categories'); -var axisIds = require('./axis_ids'); -var autoType = require('./axis_autotype'); +var Registry = require('../../registry') +var Lib = require('../../lib') +var lightFraction = require('../../components/color/attributes').lightFraction +var layoutAttributes = require('./layout_attributes') +var handleTickValueDefaults = require('./tick_value_defaults') +var handleTickMarkDefaults = require('./tick_mark_defaults') +var handleTickLabelDefaults = require('./tick_label_defaults') +var handleCategoryOrderDefaults = require('./category_order_defaults') +var setConvert = require('./set_convert') +var orderedCategories = require('./ordered_categories') +var axisIds = require('./axis_ids') +var autoType = require('./axis_autotype') /** * options: object containing: @@ -40,193 +38,190 @@ var autoType = require('./axis_autotype'); * data: the plot data to use in choosing auto type * bgColor: the plot background color, to calculate default gridline colors */ -module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options) { - var letter = options.letter, - font = options.font || {}, - defaultTitle = 'Click to enter ' + +module.exports = function handleAxisDefaults (containerIn, containerOut, coerce, options) { + var letter = options.letter, + font = options.font || {}, + defaultTitle = 'Click to enter ' + (options.title || (letter.toUpperCase() + ' axis')) + - ' title'; + ' title' - function coerce2(attr, dflt) { - return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt); - } + function coerce2 (attr, dflt) { + return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt) + } // set up some private properties - if(options.name) { - containerOut._name = options.name; - containerOut._id = axisIds.name2id(options.name); - } + if (options.name) { + containerOut._name = options.name + containerOut._id = axisIds.name2id(options.name) + } // now figure out type and do some more initialization - var axType = coerce('type'); - if(axType === '-') { - setAutoType(containerOut, options.data); - - if(containerOut.type === '-') { - containerOut.type = 'linear'; - } - else { + var axType = coerce('type') + if (axType === '-') { + setAutoType(containerOut, options.data) + + if (containerOut.type === '-') { + containerOut.type = 'linear' + } else { // copy autoType back to input axis // note that if this object didn't exist // in the input layout, we have to put it in // this happens in the main supplyDefaults function - axType = containerIn.type = containerOut.type; - } + axType = containerIn.type = containerOut.type } + } - if(axType === 'date') { - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); - handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar); - } + if (axType === 'date') { + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults') + handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar) + } - setConvert(containerOut); + setConvert(containerOut) - var dfltColor = coerce('color'); + var dfltColor = coerce('color') // if axis.color was provided, use it for fonts too; otherwise, // inherit from global font color in case that was provided. - var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : font.color; + var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : font.color - coerce('title', defaultTitle); - Lib.coerceFont(coerce, 'titlefont', { - family: font.family, - size: Math.round(font.size * 1.2), - color: dfltFontColor - }); + coerce('title', defaultTitle) + Lib.coerceFont(coerce, 'titlefont', { + family: font.family, + size: Math.round(font.size * 1.2), + color: dfltFontColor + }) - var validRange = ( + var validRange = ( (containerIn.range || []).length === 2 && isNumeric(containerOut.r2l(containerIn.range[0])) && isNumeric(containerOut.r2l(containerIn.range[1])) - ); - var autoRange = coerce('autorange', !validRange); + ) + var autoRange = coerce('autorange', !validRange) - if(autoRange) coerce('rangemode'); + if (autoRange) coerce('rangemode') - coerce('range'); - containerOut.cleanRange(); + coerce('range') + containerOut.cleanRange() - handleTickValueDefaults(containerIn, containerOut, coerce, axType); - handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options); - handleTickMarkDefaults(containerIn, containerOut, coerce, options); - handleCategoryOrderDefaults(containerIn, containerOut, coerce); + handleTickValueDefaults(containerIn, containerOut, coerce, axType) + handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options) + handleTickMarkDefaults(containerIn, containerOut, coerce, options) + handleCategoryOrderDefaults(containerIn, containerOut, coerce) - var lineColor = coerce2('linecolor', dfltColor), - lineWidth = coerce2('linewidth'), - showLine = coerce('showline', !!lineColor || !!lineWidth); + var lineColor = coerce2('linecolor', dfltColor), + lineWidth = coerce2('linewidth'), + showLine = coerce('showline', !!lineColor || !!lineWidth) - if(!showLine) { - delete containerOut.linecolor; - delete containerOut.linewidth; - } + if (!showLine) { + delete containerOut.linecolor + delete containerOut.linewidth + } - if(showLine || containerOut.ticks) coerce('mirror'); + if (showLine || containerOut.ticks) coerce('mirror') - var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, lightFraction).toRgbString()), - gridWidth = coerce2('gridwidth'), - showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); + var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, lightFraction).toRgbString()), + gridWidth = coerce2('gridwidth'), + showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth) - if(!showGridLines) { - delete containerOut.gridcolor; - delete containerOut.gridwidth; - } + if (!showGridLines) { + delete containerOut.gridcolor + delete containerOut.gridwidth + } - var zeroLineColor = coerce2('zerolinecolor', dfltColor), - zeroLineWidth = coerce2('zerolinewidth'), - showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth); + var zeroLineColor = coerce2('zerolinecolor', dfltColor), + zeroLineWidth = coerce2('zerolinewidth'), + showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth) - if(!showZeroLine) { - delete containerOut.zerolinecolor; - delete containerOut.zerolinewidth; - } + if (!showZeroLine) { + delete containerOut.zerolinecolor + delete containerOut.zerolinewidth + } // fill in categories - containerOut._initialCategories = axType === 'category' ? + containerOut._initialCategories = axType === 'category' ? orderedCategories(letter, containerOut.categoryorder, containerOut.categoryarray, options.data) : - []; + [] - return containerOut; -}; + return containerOut +} -function setAutoType(ax, data) { +function setAutoType (ax, data) { // new logic: let people specify any type they want, // only autotype if type is '-' - if(ax.type !== '-') return; + if (ax.type !== '-') return - var id = ax._id, - axLetter = id.charAt(0); + var id = ax._id, + axLetter = id.charAt(0) // support 3d - if(id.indexOf('scene') !== -1) id = axLetter; + if (id.indexOf('scene') !== -1) id = axLetter - var d0 = getFirstNonEmptyTrace(data, id, axLetter); - if(!d0) return; + var d0 = getFirstNonEmptyTrace(data, id, axLetter) + if (!d0) return // first check for histograms, as the count direction // should always default to a linear axis - if(d0.type === 'histogram' && + if (d0.type === 'histogram' && axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']) { - ax.type = 'linear'; - return; - } + ax.type = 'linear' + return + } - var calAttr = axLetter + 'calendar', - calendar = d0[calAttr]; + var calAttr = axLetter + 'calendar', + calendar = d0[calAttr] // check all boxes on this x axis to see // if they're dates, numbers, or categories - if(isBoxWithoutPositionCoords(d0, axLetter)) { - var posLetter = getBoxPosLetter(d0), - boxPositions = [], - trace; - - for(var i = 0; i < data.length; i++) { - trace = data[i]; - if(!Registry.traceIs(trace, 'box') || - (trace[axLetter + 'axis'] || axLetter) !== id) continue; + if (isBoxWithoutPositionCoords(d0, axLetter)) { + var posLetter = getBoxPosLetter(d0), + boxPositions = [], + trace - if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]); - else if(trace.name !== undefined) boxPositions.push(trace.name); - else boxPositions.push('text'); + for (var i = 0; i < data.length; i++) { + trace = data[i] + if (!Registry.traceIs(trace, 'box') || + (trace[axLetter + 'axis'] || axLetter) !== id) continue - if(trace[calAttr] !== calendar) calendar = undefined; - } + if (trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]) + else if (trace.name !== undefined) boxPositions.push(trace.name) + else boxPositions.push('text') - ax.type = autoType(boxPositions, calendar); - } - else { - ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar); + if (trace[calAttr] !== calendar) calendar = undefined } + + ax.type = autoType(boxPositions, calendar) + } else { + ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar) + } } -function getBoxPosLetter(trace) { - return {v: 'x', h: 'y'}[trace.orientation || 'v']; +function getBoxPosLetter (trace) { + return {v: 'x', h: 'y'}[trace.orientation || 'v'] } -function isBoxWithoutPositionCoords(trace, axLetter) { - var posLetter = getBoxPosLetter(trace), - isBox = Registry.traceIs(trace, 'box'), - isCandlestick = Registry.traceIs(trace._fullInput || {}, 'candlestick'); +function isBoxWithoutPositionCoords (trace, axLetter) { + var posLetter = getBoxPosLetter(trace), + isBox = Registry.traceIs(trace, 'box'), + isCandlestick = Registry.traceIs(trace._fullInput || {}, 'candlestick') - return ( + return ( isBox && !isCandlestick && axLetter === posLetter && trace[posLetter] === undefined && trace[posLetter + '0'] === undefined - ); + ) } -function getFirstNonEmptyTrace(data, id, axLetter) { - for(var i = 0; i < data.length; i++) { - var trace = data[i]; - - if((trace[axLetter + 'axis'] || axLetter) === id) { - if(isBoxWithoutPositionCoords(trace, axLetter)) { - return trace; - } - else if((trace[axLetter] || []).length || trace[axLetter + '0']) { - return trace; - } - } +function getFirstNonEmptyTrace (data, id, axLetter) { + for (var i = 0; i < data.length; i++) { + var trace = data[i] + + if ((trace[axLetter + 'axis'] || axLetter) === id) { + if (isBoxWithoutPositionCoords(trace, axLetter)) { + return trace + } else if ((trace[axLetter] || []).length || trace[axLetter + '0']) { + return trace + } } + } } diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js index 63b9fae6e77..7e7a91887d4 100644 --- a/src/plots/cartesian/axis_ids.js +++ b/src/plots/cartesian/axis_ids.js @@ -6,115 +6,113 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../../registry'); -var Plots = require('../plots'); -var Lib = require('../../lib'); - -var constants = require('./constants'); +var Registry = require('../../registry') +var Plots = require('../plots') +var Lib = require('../../lib') +var constants = require('./constants') // convert between axis names (xaxis, xaxis2, etc, elements of gd.layout) // and axis id's (x, x2, etc). Would probably have ditched 'xaxis' // completely in favor of just 'x' if it weren't ingrained in the API etc. -exports.id2name = function id2name(id) { - if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; - var axNum = id.substr(1); - if(axNum === '1') axNum = ''; - return id.charAt(0) + 'axis' + axNum; -}; - -exports.name2id = function name2id(name) { - if(!name.match(constants.AX_NAME_PATTERN)) return; - var axNum = name.substr(5); - if(axNum === '1') axNum = ''; - return name.charAt(0) + axNum; -}; - -exports.cleanId = function cleanId(id, axLetter) { - if(!id.match(constants.AX_ID_PATTERN)) return; - if(axLetter && id.charAt(0) !== axLetter) return; - - var axNum = id.substr(1).replace(/^0+/, ''); - if(axNum === '1') axNum = ''; - return id.charAt(0) + axNum; -}; +exports.id2name = function id2name (id) { + if (typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return + var axNum = id.substr(1) + if (axNum === '1') axNum = '' + return id.charAt(0) + 'axis' + axNum +} + +exports.name2id = function name2id (name) { + if (!name.match(constants.AX_NAME_PATTERN)) return + var axNum = name.substr(5) + if (axNum === '1') axNum = '' + return name.charAt(0) + axNum +} + +exports.cleanId = function cleanId (id, axLetter) { + if (!id.match(constants.AX_ID_PATTERN)) return + if (axLetter && id.charAt(0) !== axLetter) return + + var axNum = id.substr(1).replace(/^0+/, '') + if (axNum === '1') axNum = '' + return id.charAt(0) + axNum +} // get all axis object names // optionally restricted to only x or y or z by string axLetter // and optionally 2D axes only, not those inside 3D scenes -function listNames(gd, axLetter, only2d) { - var fullLayout = gd._fullLayout; - if(!fullLayout) return []; - - function filterAxis(obj, extra) { - var keys = Object.keys(obj), - axMatch = /^[xyz]axis[0-9]*/, - out = []; - - for(var i = 0; i < keys.length; i++) { - var k = keys[i]; - if(axLetter && k.charAt(0) !== axLetter) continue; - if(axMatch.test(k)) out.push(extra + k); - } - - return out.sort(); +function listNames (gd, axLetter, only2d) { + var fullLayout = gd._fullLayout + if (!fullLayout) return [] + + function filterAxis (obj, extra) { + var keys = Object.keys(obj), + axMatch = /^[xyz]axis[0-9]*/, + out = [] + + for (var i = 0; i < keys.length; i++) { + var k = keys[i] + if (axLetter && k.charAt(0) !== axLetter) continue + if (axMatch.test(k)) out.push(extra + k) } - var names = filterAxis(fullLayout, ''); - if(only2d) return names; + return out.sort() + } - var sceneIds3D = Plots.getSubplotIds(fullLayout, 'gl3d') || []; - for(var i = 0; i < sceneIds3D.length; i++) { - var sceneId = sceneIds3D[i]; - names = names.concat( + var names = filterAxis(fullLayout, '') + if (only2d) return names + + var sceneIds3D = Plots.getSubplotIds(fullLayout, 'gl3d') || [] + for (var i = 0; i < sceneIds3D.length; i++) { + var sceneId = sceneIds3D[i] + names = names.concat( filterAxis(fullLayout[sceneId], sceneId + '.') - ); - } + ) + } - return names; + return names } // get all axis objects, as restricted in listNames -exports.list = function(gd, axletter, only2d) { - return listNames(gd, axletter, only2d) - .map(function(axName) { - return Lib.nestedProperty(gd._fullLayout, axName).get(); - }); -}; +exports.list = function (gd, axletter, only2d) { + return listNames(gd, axletter, only2d) + .map(function (axName) { + return Lib.nestedProperty(gd._fullLayout, axName).get() + }) +} // get all axis ids, optionally restricted by letter // this only makes sense for 2d axes -exports.listIds = function(gd, axletter) { - return listNames(gd, axletter, true).map(exports.name2id); -}; +exports.listIds = function (gd, axletter) { + return listNames(gd, axletter, true).map(exports.name2id) +} // get an axis object from its id 'x','x2' etc // optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it -exports.getFromId = function(gd, id, type) { - var fullLayout = gd._fullLayout; +exports.getFromId = function (gd, id, type) { + var fullLayout = gd._fullLayout - if(type === 'x') id = id.replace(/y[0-9]*/, ''); - else if(type === 'y') id = id.replace(/x[0-9]*/, ''); + if (type === 'x') id = id.replace(/y[0-9]*/, '') + else if (type === 'y') id = id.replace(/x[0-9]*/, '') - return fullLayout[exports.id2name(id)]; -}; + return fullLayout[exports.id2name(id)] +} // get an axis object of specified type from the containing trace -exports.getFromTrace = function(gd, fullTrace, type) { - var fullLayout = gd._fullLayout; - var ax = null; - - if(Registry.traceIs(fullTrace, 'gl3d')) { - var scene = fullTrace.scene; - if(scene.substr(0, 5) === 'scene') { - ax = fullLayout[scene][type + 'axis']; - } - } - else { - ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type); +exports.getFromTrace = function (gd, fullTrace, type) { + var fullLayout = gd._fullLayout + var ax = null + + if (Registry.traceIs(fullTrace, 'gl3d')) { + var scene = fullTrace.scene + if (scene.substr(0, 5) === 'scene') { + ax = fullLayout[scene][type + 'axis'] } + } else { + ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type) + } - return ax; -}; + return ax +} diff --git a/src/plots/cartesian/category_order_defaults.js b/src/plots/cartesian/category_order_defaults.js index c115dd28c23..7810c06497d 100644 --- a/src/plots/cartesian/category_order_defaults.js +++ b/src/plots/cartesian/category_order_defaults.js @@ -6,27 +6,26 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' +module.exports = function handleCategoryOrderDefaults (containerIn, containerOut, coerce) { + if (containerOut.type !== 'category') return -module.exports = function handleCategoryOrderDefaults(containerIn, containerOut, coerce) { - if(containerOut.type !== 'category') return; + var arrayIn = containerIn.categoryarray, + orderDefault - var arrayIn = containerIn.categoryarray, - orderDefault; - - var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0); + var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0) // override default 'categoryorder' value when non-empty array is supplied - if(isValidArray) orderDefault = 'array'; + if (isValidArray) orderDefault = 'array' - var order = coerce('categoryorder', orderDefault); + var order = coerce('categoryorder', orderDefault) // coerce 'categoryarray' only in array order case - if(order === 'array') coerce('categoryarray'); + if (order === 'array') coerce('categoryarray') // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray' - if(!isValidArray && order === 'array') { - containerOut.categoryorder = 'trace'; - } -}; + if (!isValidArray && order === 'array') { + containerOut.categoryorder = 'trace' + } +} diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index 8d7ed20df45..e63bf40ef6b 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -6,67 +6,66 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - idRegex: { - x: /^x([2-9]|[1-9][0-9]+)?$/, - y: /^y([2-9]|[1-9][0-9]+)?$/ - }, + idRegex: { + x: /^x([2-9]|[1-9][0-9]+)?$/, + y: /^y([2-9]|[1-9][0-9]+)?$/ + }, - attrRegex: { - x: /^xaxis([2-9]|[1-9][0-9]+)?$/, - y: /^yaxis([2-9]|[1-9][0-9]+)?$/ - }, + attrRegex: { + x: /^xaxis([2-9]|[1-9][0-9]+)?$/, + y: /^yaxis([2-9]|[1-9][0-9]+)?$/ + }, // axis match regular expression - xAxisMatch: /^xaxis[0-9]*$/, - yAxisMatch: /^yaxis[0-9]*$/, + xAxisMatch: /^xaxis[0-9]*$/, + yAxisMatch: /^yaxis[0-9]*$/, // pattern matching axis ids and names - AX_ID_PATTERN: /^[xyz][0-9]*$/, - AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, + AX_ID_PATTERN: /^[xyz][0-9]*$/, + AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, // ms between first mousedown and 2nd mouseup to constitute dblclick... // we don't seem to have access to the system setting - DBLCLICKDELAY: 300, + DBLCLICKDELAY: 300, // pixels to move mouse before you stop clamping to starting point - MINDRAG: 8, + MINDRAG: 8, // smallest dimension allowed for a select box - MINSELECT: 12, + MINSELECT: 12, // smallest dimension allowed for a zoombox - MINZOOM: 20, + MINZOOM: 20, // width of axis drag regions - DRAGGERSIZE: 20, + DRAGGERSIZE: 20, // max pixels away from mouse to allow a point to highlight - MAXDIST: 20, + MAXDIST: 20, // hover labels for multiple horizontal bars get tilted by this angle - YANGLE: 60, + YANGLE: 60, // size and display constants for hover text - HOVERARROWSIZE: 6, // pixel size of hover arrows - HOVERTEXTPAD: 3, // pixels padding around text - HOVERFONTSIZE: 13, - HOVERFONT: 'Arial, sans-serif', + HOVERARROWSIZE: 6, // pixel size of hover arrows + HOVERTEXTPAD: 3, // pixels padding around text + HOVERFONTSIZE: 13, + HOVERFONT: 'Arial, sans-serif', // minimum time (msec) between hover calls - HOVERMINTIME: 50, + HOVERMINTIME: 50, // max pixels off straight before a lasso select line counts as bent - BENDPX: 1.5, + BENDPX: 1.5, // delay before a redraw (relayout) after smooth panning and zooming - REDRAWDELAY: 50, + REDRAWDELAY: 50, // last resort axis ranges for x and y axes if we have no data - DFLTRANGEX: [-1, 6], - DFLTRANGEY: [-1, 4] -}; + DFLTRANGEX: [-1, 6], + DFLTRANGEY: [-1, 4] +} diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 22a253f287e..ea3046efe40 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -6,28 +6,26 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var tinycolor = require('tinycolor2') -var d3 = require('d3'); -var tinycolor = require('tinycolor2'); - -var Plotly = require('../../plotly'); -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var setCursor = require('../../lib/setcursor'); -var dragElement = require('../../components/dragelement'); - -var Axes = require('./axes'); -var prepSelect = require('./select'); -var constants = require('./constants'); +var Plotly = require('../../plotly') +var Registry = require('../../registry') +var Lib = require('../../lib') +var svgTextUtils = require('../../lib/svg_text_utils') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var setCursor = require('../../lib/setcursor') +var dragElement = require('../../components/dragelement') +var Axes = require('./axes') +var prepSelect = require('./select') +var constants = require('./constants') // flag for showing "doubleclick to zoom out" only at the beginning -var SHOWZOOMOUTTIP = true; +var SHOWZOOMOUTTIP = true // dragBox: create an element to drag one or more axis ends // inputs: @@ -38,234 +36,229 @@ var SHOWZOOMOUTTIP = true; // 's' - bottom only // 'ns' - top and bottom together, difference unchanged // ew - same for horizontal axis -module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { +module.exports = function dragBox (gd, plotinfo, x, y, w, h, ns, ew) { // mouseDown stores ms of first mousedown event in the last // DBLCLICKDELAY ms on the drag bars // numClicks stores how many mousedowns have been seen // within DBLCLICKDELAY so we can check for click or doubleclick events // dragged stores whether a drag has occurred, so we don't have to // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px - var fullLayout = gd._fullLayout, + var fullLayout = gd._fullLayout, // if we're dragging two axes at once, also drag overlays - subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []), - xa = [plotinfo.xaxis], - ya = [plotinfo.yaxis], - pw = xa[0]._length, - ph = ya[0]._length, - MINDRAG = constants.MINDRAG, - MINZOOM = constants.MINZOOM, - isMainDrag = (ns + ew === 'nsew'); - - for(var i = 1; i < subplots.length; i++) { - var subplotXa = subplots[i].xaxis, - subplotYa = subplots[i].yaxis; - if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa); - if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa); - } - - function isDirectionActive(axList, activeVal) { - for(var i = 0; i < axList.length; i++) { - if(!axList[i].fixedrange) return activeVal; - } - return ''; + subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []), + xa = [plotinfo.xaxis], + ya = [plotinfo.yaxis], + pw = xa[0]._length, + ph = ya[0]._length, + MINDRAG = constants.MINDRAG, + MINZOOM = constants.MINZOOM, + isMainDrag = (ns + ew === 'nsew') + + for (var i = 1; i < subplots.length; i++) { + var subplotXa = subplots[i].xaxis, + subplotYa = subplots[i].yaxis + if (xa.indexOf(subplotXa) === -1) xa.push(subplotXa) + if (ya.indexOf(subplotYa) === -1) ya.push(subplotYa) + } + + function isDirectionActive (axList, activeVal) { + for (var i = 0; i < axList.length; i++) { + if (!axList[i].fixedrange) return activeVal } + return '' + } - var allaxes = xa.concat(ya), - xActive = isDirectionActive(xa, ew), - yActive = isDirectionActive(ya, ns), - cursor = getDragCursor(yActive + xActive, fullLayout.dragmode), - dragClass = ns + ew + 'drag'; + var allaxes = xa.concat(ya), + xActive = isDirectionActive(xa, ew), + yActive = isDirectionActive(ya, ns), + cursor = getDragCursor(yActive + xActive, fullLayout.dragmode), + dragClass = ns + ew + 'drag' - var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]); + var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]) - dragger3.enter().append('rect') + dragger3.enter().append('rect') .classed('drag', true) .classed(dragClass, true) .style({fill: 'transparent', 'stroke-width': 0}) - .attr('data-subplot', plotinfo.id); + .attr('data-subplot', plotinfo.id) - dragger3.call(Drawing.setRect, x, y, w, h) - .call(setCursor, cursor); + dragger3.call(Drawing.setRect, x, y, w, h) + .call(setCursor, cursor) - var dragger = dragger3.node(); + var dragger = dragger3.node() // still need to make the element if the axes are disabled // but nuke its events (except for maindrag which needs them for hover) // and stop there - if(!yActive && !xActive && !isSelectOrLasso(fullLayout.dragmode)) { - dragger.onmousedown = null; - dragger.style.pointerEvents = isMainDrag ? 'all' : 'none'; - return dragger; - } - - var dragOptions = { - element: dragger, - gd: gd, - plotinfo: plotinfo, - xaxes: xa, - yaxes: ya, - doubleclick: doubleClick, - prepFn: function(e, startX, startY) { - var dragModeNow = gd._fullLayout.dragmode; - - if(isMainDrag) { + if (!yActive && !xActive && !isSelectOrLasso(fullLayout.dragmode)) { + dragger.onmousedown = null + dragger.style.pointerEvents = isMainDrag ? 'all' : 'none' + return dragger + } + + var dragOptions = { + element: dragger, + gd: gd, + plotinfo: plotinfo, + xaxes: xa, + yaxes: ya, + doubleclick: doubleClick, + prepFn: function (e, startX, startY) { + var dragModeNow = gd._fullLayout.dragmode + + if (isMainDrag) { // main dragger handles all drag modes, and changes // to pan (or to zoom if it already is pan) on shift - if(e.shiftKey) { - if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else dragModeNow = 'pan'; - } - } - // all other draggers just pan - else dragModeNow = 'pan'; - - if(dragModeNow === 'lasso') dragOptions.minDrag = 1; - else dragOptions.minDrag = undefined; - - if(dragModeNow === 'zoom') { - dragOptions.moveFn = zoomMove; - dragOptions.doneFn = zoomDone; - zoomPrep(e, startX, startY); - } - else if(dragModeNow === 'pan') { - dragOptions.moveFn = plotDrag; - dragOptions.doneFn = dragDone; - clearSelect(); - } - else if(isSelectOrLasso(dragModeNow)) { - prepSelect(e, startX, startY, dragOptions, dragModeNow); - } + if (e.shiftKey) { + if (dragModeNow === 'pan') dragModeNow = 'zoom' + else dragModeNow = 'pan' } - }; - - dragElement.init(dragOptions); - - var zoomlayer = gd._fullLayout._zoomlayer, - xs = plotinfo.xaxis._offset, - ys = plotinfo.yaxis._offset, - x0, - y0, - box, - lum, - path0, - dimmed, - zoomMode, - zb, - corners; - - function recomputeAxisLists() { - xa = [plotinfo.xaxis]; - ya = [plotinfo.yaxis]; - pw = xa[0]._length; - ph = ya[0]._length; - - for(var i = 1; i < subplots.length; i++) { - var subplotXa = subplots[i].xaxis, - subplotYa = subplots[i].yaxis; - if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa); - if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa); - } - allaxes = xa.concat(ya); - xActive = isDirectionActive(xa, ew); - yActive = isDirectionActive(ya, ns); - cursor = getDragCursor(yActive + xActive, fullLayout.dragmode); - xs = plotinfo.xaxis._offset; - ys = plotinfo.yaxis._offset; - dragOptions.xa = xa; - dragOptions.ya = ya; + } + // all other draggers just pan + else dragModeNow = 'pan' + + if (dragModeNow === 'lasso') dragOptions.minDrag = 1 + else dragOptions.minDrag = undefined + + if (dragModeNow === 'zoom') { + dragOptions.moveFn = zoomMove + dragOptions.doneFn = zoomDone + zoomPrep(e, startX, startY) + } else if (dragModeNow === 'pan') { + dragOptions.moveFn = plotDrag + dragOptions.doneFn = dragDone + clearSelect() + } else if (isSelectOrLasso(dragModeNow)) { + prepSelect(e, startX, startY, dragOptions, dragModeNow) + } } - - function zoomPrep(e, startX, startY) { - var dragBBox = dragger.getBoundingClientRect(); - x0 = startX - dragBBox.left; - y0 = startY - dragBBox.top; - box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; - lum = gd._hmpixcount ? + } + + dragElement.init(dragOptions) + + var zoomlayer = gd._fullLayout._zoomlayer, + xs = plotinfo.xaxis._offset, + ys = plotinfo.yaxis._offset, + x0, + y0, + box, + lum, + path0, + dimmed, + zoomMode, + zb, + corners + + function recomputeAxisLists () { + xa = [plotinfo.xaxis] + ya = [plotinfo.yaxis] + pw = xa[0]._length + ph = ya[0]._length + + for (var i = 1; i < subplots.length; i++) { + var subplotXa = subplots[i].xaxis, + subplotYa = subplots[i].yaxis + if (xa.indexOf(subplotXa) === -1) xa.push(subplotXa) + if (ya.indexOf(subplotYa) === -1) ya.push(subplotYa) + } + allaxes = xa.concat(ya) + xActive = isDirectionActive(xa, ew) + yActive = isDirectionActive(ya, ns) + cursor = getDragCursor(yActive + xActive, fullLayout.dragmode) + xs = plotinfo.xaxis._offset + ys = plotinfo.yaxis._offset + dragOptions.xa = xa + dragOptions.ya = ya + } + + function zoomPrep (e, startX, startY) { + var dragBBox = dragger.getBoundingClientRect() + x0 = startX - dragBBox.left + y0 = startY - dragBBox.top + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0} + lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : - tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); - path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0'; - dimmed = false; - zoomMode = 'xy'; + tinycolor(gd._fullLayout.plot_bgcolor).getLuminance() + path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0' + dimmed = false + zoomMode = 'xy' - zb = zoomlayer.append('path') + zb = zoomlayer.append('path') .attr('class', 'zoombox') .style({ - 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', - 'stroke-width': 0 + 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 }) .attr('transform', 'translate(' + xs + ', ' + ys + ')') - .attr('d', path0 + 'Z'); + .attr('d', path0 + 'Z') - corners = zoomlayer.append('path') + corners = zoomlayer.append('path') .attr('class', 'zoombox-corners') .style({ - fill: Color.background, - stroke: Color.defaultLine, - 'stroke-width': 1, - opacity: 0 + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': 1, + opacity: 0 }) .attr('transform', 'translate(' + xs + ', ' + ys + ')') - .attr('d', 'M0,0Z'); + .attr('d', 'M0,0Z') - clearSelect(); - } + clearSelect() + } - function clearSelect() { + function clearSelect () { // until we get around to persistent selections, remove the outline // here. The selection itself will be removed when the plot redraws // at the end. - zoomlayer.selectAll('.select-outline').remove(); - } + zoomlayer.selectAll('.select-outline').remove() + } - function zoomMove(dx0, dy0) { - if(gd._transitioningWithDuration) { - return false; - } + function zoomMove (dx0, dy0) { + if (gd._transitioningWithDuration) { + return false + } - var x1 = Math.max(0, Math.min(pw, dx0 + x0)), - y1 = Math.max(0, Math.min(ph, dy0 + y0)), - dx = Math.abs(x1 - x0), - dy = Math.abs(y1 - y0), - clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2); + var x1 = Math.max(0, Math.min(pw, dx0 + x0)), + y1 = Math.max(0, Math.min(ph, dy0 + y0)), + dx = Math.abs(x1 - x0), + dy = Math.abs(y1 - y0), + clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2) - box.l = Math.min(x0, x1); - box.r = Math.max(x0, x1); - box.t = Math.min(y0, y1); - box.b = Math.max(y0, y1); + box.l = Math.min(x0, x1) + box.r = Math.max(x0, x1) + box.t = Math.min(y0, y1) + box.b = Math.max(y0, y1) // look for small drags in one direction or the other, // and only drag the other axis - if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) { - if(dx < MINDRAG) { - zoomMode = ''; - box.r = box.l; - box.t = box.b; - corners.attr('d', 'M0,0Z'); - } - else { - box.t = 0; - box.b = ph; - zoomMode = 'x'; - corners.attr('d', + if (!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) { + if (dx < MINDRAG) { + zoomMode = '' + box.r = box.l + box.t = box.b + corners.attr('d', 'M0,0Z') + } else { + box.t = 0 + box.b = ph + zoomMode = 'x' + corners.attr('d', 'M' + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + - 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'); - } - } - else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { - box.l = 0; - box.r = pw; - zoomMode = 'y'; - corners.attr('d', + 'h3v' + (2 * MINZOOM + 1) + 'h-3Z') + } + } else if (!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { + box.l = 0 + box.r = pw + zoomMode = 'y' + corners.attr('d', 'M' + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + - 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'); - } - else { - zoomMode = 'xy'; - corners.attr('d', + 'v3h' + (2 * MINZOOM + 1) + 'v-3Z') + } else { + zoomMode = 'xy' + corners.attr('d', 'M' + (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) + 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' + (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) + @@ -273,439 +266,433 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen + 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' + (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen + - 'h' + clen + 'v3h-' + (clen + 3) + 'Z'); - } - box.w = box.r - box.l; - box.h = box.b - box.t; + 'h' + clen + 'v3h-' + (clen + 3) + 'Z') + } + box.w = box.r - box.l + box.h = box.b - box.t // Not sure about the addition of window.scrollX/Y... // seems to work but doesn't seem robust. - zb.attr('d', + zb.attr('d', path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + - 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z'); - if(!dimmed) { - zb.transition() + 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z') + if (!dimmed) { + zb.transition() .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' : 'rgba(255,255,255,0.3)') - .duration(200); - corners.transition() + .duration(200) + corners.transition() .style('opacity', 1) - .duration(200); - dimmed = true; - } + .duration(200) + dimmed = true } - - function zoomAxRanges(axList, r0Fraction, r1Fraction) { - var i, - axi, - axRangeLinear0, - axRangeLinearSpan; - - for(i = 0; i < axList.length; i++) { - axi = axList[i]; - if(axi.fixedrange) continue; - - axRangeLinear0 = axi._rl[0]; - axRangeLinearSpan = axi._rl[1] - axRangeLinear0; - axi.range = [ - axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction), - axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction) - ]; - } + } + + function zoomAxRanges (axList, r0Fraction, r1Fraction) { + var i, + axi, + axRangeLinear0, + axRangeLinearSpan + + for (i = 0; i < axList.length; i++) { + axi = axList[i] + if (axi.fixedrange) continue + + axRangeLinear0 = axi._rl[0] + axRangeLinearSpan = axi._rl[1] - axRangeLinear0 + axi.range = [ + axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction), + axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction) + ] } + } - function zoomDone(dragged, numClicks) { - if(Math.min(box.h, box.w) < MINDRAG * 2) { - if(numClicks === 2) doubleClick(); + function zoomDone (dragged, numClicks) { + if (Math.min(box.h, box.w) < MINDRAG * 2) { + if (numClicks === 2) doubleClick() - return removeZoombox(gd); - } + return removeZoombox(gd) + } - if(zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw); - if(zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph); + if (zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw) + if (zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph) - removeZoombox(gd); - dragTail(zoomMode); + removeZoombox(gd) + dragTail(zoomMode) - if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { - Lib.notifier('Double-click to
zoom back out', 'long'); - SHOWZOOMOUTTIP = false; - } + if (SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Lib.notifier('Double-click to
zoom back out', 'long') + SHOWZOOMOUTTIP = false } - - function dragDone(dragged, numClicks) { - var singleEnd = (ns + ew).length === 1; - if(dragged) dragTail(); - else if(numClicks === 2 && !singleEnd) doubleClick(); - else if(numClicks === 1 && singleEnd) { - var ax = ns ? ya[0] : xa[0], - end = (ns === 's' || ew === 'w') ? 0 : 1, - attrStr = ax._name + '.range[' + end + ']', - initialText = getEndText(ax, end), - hAlign = 'left', - vAlign = 'middle'; - - if(ax.fixedrange) return; - - if(ns) { - vAlign = (ns === 'n') ? 'top' : 'bottom'; - if(ax.side === 'right') hAlign = 'right'; - } - else if(ew === 'e') hAlign = 'right'; - - dragger3 + } + + function dragDone (dragged, numClicks) { + var singleEnd = (ns + ew).length === 1 + if (dragged) dragTail() + else if (numClicks === 2 && !singleEnd) doubleClick() + else if (numClicks === 1 && singleEnd) { + var ax = ns ? ya[0] : xa[0], + end = (ns === 's' || ew === 'w') ? 0 : 1, + attrStr = ax._name + '.range[' + end + ']', + initialText = getEndText(ax, end), + hAlign = 'left', + vAlign = 'middle' + + if (ax.fixedrange) return + + if (ns) { + vAlign = (ns === 'n') ? 'top' : 'bottom' + if (ax.side === 'right') hAlign = 'right' + } else if (ew === 'e') hAlign = 'right' + + dragger3 .call(svgTextUtils.makeEditable, null, { - immediate: true, - background: fullLayout.paper_bgcolor, - text: String(initialText), - fill: ax.tickfont ? ax.tickfont.color : '#444', - horizontalAlign: hAlign, - verticalAlign: vAlign + immediate: true, + background: fullLayout.paper_bgcolor, + text: String(initialText), + fill: ax.tickfont ? ax.tickfont.color : '#444', + horizontalAlign: hAlign, + verticalAlign: vAlign + }) + .on('edit', function (text) { + var v = ax.d2r(text) + if (v !== undefined) { + Plotly.relayout(gd, attrStr, v) + } }) - .on('edit', function(text) { - var v = ax.d2r(text); - if(v !== undefined) { - Plotly.relayout(gd, attrStr, v); - } - }); - } } + } // scroll zoom, on all draggers except corners - var scrollViewBox = [0, 0, pw, ph], + var scrollViewBox = [0, 0, pw, ph], // wait a little after scrolling before redrawing - redrawTimer = null, - REDRAWDELAY = constants.REDRAWDELAY, - mainplot = plotinfo.mainplot ? - fullLayout._plots[plotinfo.mainplot] : plotinfo; + redrawTimer = null, + REDRAWDELAY = constants.REDRAWDELAY, + mainplot = plotinfo.mainplot ? + fullLayout._plots[plotinfo.mainplot] : plotinfo - function zoomWheel(e) { + function zoomWheel (e) { // deactivate mousewheel scrolling on embedded graphs // devs can override this with layout._enablescrollzoom, // but _ ensures this setting won't leave their page - if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) { - return; - } + if (!gd._context.scrollZoom && !fullLayout._enablescrollzoom) { + return + } // If a transition is in progress, then disable any behavior: - if(gd._transitioningWithDuration) { - return Lib.pauseEvent(e); - } + if (gd._transitioningWithDuration) { + return Lib.pauseEvent(e) + } - var pc = gd.querySelector('.plotly'); + var pc = gd.querySelector('.plotly') - recomputeAxisLists(); + recomputeAxisLists() // if the plot has scrollbars (more than a tiny excess) // disable scrollzoom too. - if(pc.scrollHeight - pc.clientHeight > 10 || + if (pc.scrollHeight - pc.clientHeight > 10 || pc.scrollWidth - pc.clientWidth > 10) { - return; - } + return + } - clearTimeout(redrawTimer); + clearTimeout(redrawTimer) - var wheelDelta = -e.deltaY; - if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10; - if(!isFinite(wheelDelta)) { - Lib.log('Did not find wheel motion attributes: ', e); - return; - } + var wheelDelta = -e.deltaY + if (!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10 + if (!isFinite(wheelDelta)) { + Lib.log('Did not find wheel motion attributes: ', e) + return + } - var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 100), - gbb = mainplot.draglayer.select('.nsewdrag') + var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 100), + gbb = mainplot.draglayer.select('.nsewdrag') .node().getBoundingClientRect(), - xfrac = (e.clientX - gbb.left) / gbb.width, - vbx0 = scrollViewBox[0] + scrollViewBox[2] * xfrac, - yfrac = (gbb.bottom - e.clientY) / gbb.height, - vby0 = scrollViewBox[1] + scrollViewBox[3] * (1 - yfrac), - i; - - function zoomWheelOneAxis(ax, centerFraction, zoom) { - if(ax.fixedrange) return; - - var axRange = Lib.simpleMap(ax.range, ax.r2l), - v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction; - function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); } - ax.range = axRange.map(doZoom); - } + xfrac = (e.clientX - gbb.left) / gbb.width, + vbx0 = scrollViewBox[0] + scrollViewBox[2] * xfrac, + yfrac = (gbb.bottom - e.clientY) / gbb.height, + vby0 = scrollViewBox[1] + scrollViewBox[3] * (1 - yfrac), + i + + function zoomWheelOneAxis (ax, centerFraction, zoom) { + if (ax.fixedrange) return + + var axRange = Lib.simpleMap(ax.range, ax.r2l), + v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction + function doZoom (v) { return ax.l2r(v0 + (v - v0) * zoom) } + ax.range = axRange.map(doZoom) + } - if(ew) { - for(i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom); - scrollViewBox[2] *= zoom; - scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac; - } - if(ns) { - for(i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom); - scrollViewBox[3] *= zoom; - scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac); - } + if (ew) { + for (i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom) + scrollViewBox[2] *= zoom + scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac + } + if (ns) { + for (i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom) + scrollViewBox[3] *= zoom + scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac) + } // viewbox redraw at first - updateSubplots(scrollViewBox); - ticksAndAnnotations(ns, ew); + updateSubplots(scrollViewBox) + ticksAndAnnotations(ns, ew) // then replot after a delay to make sure // no more scrolling is coming - redrawTimer = setTimeout(function() { - scrollViewBox = [0, 0, pw, ph]; - dragTail(); - }, REDRAWDELAY); + redrawTimer = setTimeout(function () { + scrollViewBox = [0, 0, pw, ph] + dragTail() + }, REDRAWDELAY) - return Lib.pauseEvent(e); - } + return Lib.pauseEvent(e) + } // everything but the corners gets wheel zoom - if(ns.length * ew.length !== 1) { + if (ns.length * ew.length !== 1) { // still seems to be some confusion about onwheel vs onmousewheel... - if(dragger.onwheel !== undefined) dragger.onwheel = zoomWheel; - else if(dragger.onmousewheel !== undefined) dragger.onmousewheel = zoomWheel; - } + if (dragger.onwheel !== undefined) dragger.onwheel = zoomWheel + else if (dragger.onmousewheel !== undefined) dragger.onmousewheel = zoomWheel + } // plotDrag: move the plot in response to a drag - function plotDrag(dx, dy) { + function plotDrag (dx, dy) { // If a transition is in progress, then disable any behavior: - if(gd._transitioningWithDuration) { - return; - } + if (gd._transitioningWithDuration) { + return + } - recomputeAxisLists(); - - function dragAxList(axList, pix) { - for(var i = 0; i < axList.length; i++) { - var axi = axList[i]; - if(!axi.fixedrange) { - axi.range = [ - axi.l2r(axi._rl[0] - pix / axi._m), - axi.l2r(axi._rl[1] - pix / axi._m) - ]; - } - } - } + recomputeAxisLists() - if(xActive === 'ew' || yActive === 'ns') { - if(xActive) dragAxList(xa, dx); - if(yActive) dragAxList(ya, dy); - updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); - ticksAndAnnotations(yActive, xActive); - return; + function dragAxList (axList, pix) { + for (var i = 0; i < axList.length; i++) { + var axi = axList[i] + if (!axi.fixedrange) { + axi.range = [ + axi.l2r(axi._rl[0] - pix / axi._m), + axi.l2r(axi._rl[1] - pix / axi._m) + ] } + } + } + + if (xActive === 'ew' || yActive === 'ns') { + if (xActive) dragAxList(xa, dx) + if (yActive) dragAxList(ya, dy) + updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]) + ticksAndAnnotations(yActive, xActive) + return + } // common transform for dragging one end of an axis // d>0 is compressing scale (cursor is over the plot, // the axis end should move with the cursor) // d<0 is expanding (cursor is off the plot, axis end moves // nonlinearly so you can expand far) - function dZoom(d) { - return 1 - ((d >= 0) ? Math.min(d, 0.9) : - 1 / (1 / Math.max(d, -0.3) + 3.222)); - } + function dZoom (d) { + return 1 - ((d >= 0) ? Math.min(d, 0.9) : + 1 / (1 / Math.max(d, -0.3) + 3.222)) + } // dz: set a new value for one end (0 or 1) of an axis array axArray, // and return a pixel shift for that end for the viewbox // based on pixel drag distance d // TODO: this makes (generally non-fatal) errors when you get // near floating point limits - function dz(axArray, end, d) { - var otherEnd = 1 - end, - movedAx, - newLinearizedEnd; - for(var i = 0; i < axArray.length; i++) { - var axi = axArray[i]; - if(axi.fixedrange) continue; - movedAx = axi; - newLinearizedEnd = axi._rl[otherEnd] + - (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length); - var newEnd = axi.l2r(newLinearizedEnd); + function dz (axArray, end, d) { + var otherEnd = 1 - end, + movedAx, + newLinearizedEnd + for (var i = 0; i < axArray.length; i++) { + var axi = axArray[i] + if (axi.fixedrange) continue + movedAx = axi + newLinearizedEnd = axi._rl[otherEnd] + + (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length) + var newEnd = axi.l2r(newLinearizedEnd) // if l2r comes back false or undefined, it means we've dragged off // the end of valid ranges - so stop. - if(newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd; - } - return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) / - (movedAx._rl[end] - movedAx._rl[otherEnd]); - } - - if(xActive === 'w') dx = dz(xa, 0, dx); - else if(xActive === 'e') dx = dz(xa, 1, -dx); - else if(!xActive) dx = 0; - - if(yActive === 'n') dy = dz(ya, 1, dy); - else if(yActive === 's') dy = dz(ya, 0, -dy); - else if(!yActive) dy = 0; - - updateSubplots([ - (xActive === 'w') ? dx : 0, - (yActive === 'n') ? dy : 0, - pw - dx, - ph - dy - ]); - ticksAndAnnotations(yActive, xActive); + if (newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd + } + return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) / + (movedAx._rl[end] - movedAx._rl[otherEnd]) } - function ticksAndAnnotations(ns, ew) { - var activeAxIds = [], - i; - - function pushActiveAxIds(axList) { - for(i = 0; i < axList.length; i++) { - if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id); - } - } + if (xActive === 'w') dx = dz(xa, 0, dx) + else if (xActive === 'e') dx = dz(xa, 1, -dx) + else if (!xActive) dx = 0 + + if (yActive === 'n') dy = dz(ya, 1, dy) + else if (yActive === 's') dy = dz(ya, 0, -dy) + else if (!yActive) dy = 0 + + updateSubplots([ + (xActive === 'w') ? dx : 0, + (yActive === 'n') ? dy : 0, + pw - dx, + ph - dy + ]) + ticksAndAnnotations(yActive, xActive) + } + + function ticksAndAnnotations (ns, ew) { + var activeAxIds = [], + i + + function pushActiveAxIds (axList) { + for (i = 0; i < axList.length; i++) { + if (!axList[i].fixedrange) activeAxIds.push(axList[i]._id) + } + } - if(ew) pushActiveAxIds(xa); - if(ns) pushActiveAxIds(ya); + if (ew) pushActiveAxIds(xa) + if (ns) pushActiveAxIds(ya) - for(i = 0; i < activeAxIds.length; i++) { - Axes.doTicks(gd, activeAxIds[i], true); - } + for (i = 0; i < activeAxIds.length; i++) { + Axes.doTicks(gd, activeAxIds[i], true) + } - function redrawObjs(objArray, method) { - for(i = 0; i < objArray.length; i++) { - var obji = objArray[i]; + function redrawObjs (objArray, method) { + for (i = 0; i < objArray.length; i++) { + var obji = objArray[i] - if((ew && activeAxIds.indexOf(obji.xref) !== -1) || + if ((ew && activeAxIds.indexOf(obji.xref) !== -1) || (ns && activeAxIds.indexOf(obji.yref) !== -1)) { - method(gd, i); - } - } + method(gd, i) } + } + } // annotations and shapes 'draw' method is slow, // use the finer-grained 'drawOne' method instead - redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')); - redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')); - redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw')); - } - - function doubleClick() { - if(gd._transitioningWithDuration) return; - - var doubleClickConfig = gd._context.doubleClick, - axList = (xActive ? xa : []).concat(yActive ? ya : []), - attrs = {}; - - var ax, i, rangeInitial; - - if(doubleClickConfig === 'autosize') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true; - } - } - else if(doubleClickConfig === 'reset') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - - if(!ax._rangeInitial) { - attrs[ax._name + '.autorange'] = true; - } - else { - rangeInitial = ax._rangeInitial.slice(); - attrs[ax._name + '.range[0]'] = rangeInitial[0]; - attrs[ax._name + '.range[1]'] = rangeInitial[1]; - } - } - } - else if(doubleClickConfig === 'reset+autosize') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - - if(ax.fixedrange) continue; - if(ax._rangeInitial === undefined || + redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')) + redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')) + redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw')) + } + + function doubleClick () { + if (gd._transitioningWithDuration) return + + var doubleClickConfig = gd._context.doubleClick, + axList = (xActive ? xa : []).concat(yActive ? ya : []), + attrs = {} + + var ax, i, rangeInitial + + if (doubleClickConfig === 'autosize') { + for (i = 0; i < axList.length; i++) { + ax = axList[i] + if (!ax.fixedrange) attrs[ax._name + '.autorange'] = true + } + } else if (doubleClickConfig === 'reset') { + for (i = 0; i < axList.length; i++) { + ax = axList[i] + + if (!ax._rangeInitial) { + attrs[ax._name + '.autorange'] = true + } else { + rangeInitial = ax._rangeInitial.slice() + attrs[ax._name + '.range[0]'] = rangeInitial[0] + attrs[ax._name + '.range[1]'] = rangeInitial[1] + } + } + } else if (doubleClickConfig === 'reset+autosize') { + for (i = 0; i < axList.length; i++) { + ax = axList[i] + + if (ax.fixedrange) continue + if (ax._rangeInitial === undefined || ax.range[0] === ax._rangeInitial[0] && ax.range[1] === ax._rangeInitial[1] ) { - attrs[ax._name + '.autorange'] = true; - } - else { - rangeInitial = ax._rangeInitial.slice(); - attrs[ax._name + '.range[0]'] = rangeInitial[0]; - attrs[ax._name + '.range[1]'] = rangeInitial[1]; - } - } + attrs[ax._name + '.autorange'] = true + } else { + rangeInitial = ax._rangeInitial.slice() + attrs[ax._name + '.range[0]'] = rangeInitial[0] + attrs[ax._name + '.range[1]'] = rangeInitial[1] } - - gd.emit('plotly_doubleclick', null); - Plotly.relayout(gd, attrs); + } } + gd.emit('plotly_doubleclick', null) + Plotly.relayout(gd, attrs) + } + // dragTail - finish a drag event with a redraw - function dragTail(zoommode) { - var attrs = {}; + function dragTail (zoommode) { + var attrs = {} // revert to the previous axis settings, then apply the new ones // through relayout - this lets relayout manage undo/redo - for(var i = 0; i < allaxes.length; i++) { - var axi = allaxes[i]; - if(zoommode && zoommode.indexOf(axi._id.charAt(0)) === -1) { - continue; - } - if(axi._r[0] !== axi.range[0]) attrs[axi._name + '.range[0]'] = axi.range[0]; - if(axi._r[1] !== axi.range[1]) attrs[axi._name + '.range[1]'] = axi.range[1]; - - axi.range = axi._r.slice(); - } - - updateSubplots([0, 0, pw, ph]); - Plotly.relayout(gd, attrs); + for (var i = 0; i < allaxes.length; i++) { + var axi = allaxes[i] + if (zoommode && zoommode.indexOf(axi._id.charAt(0)) === -1) { + continue + } + if (axi._r[0] !== axi.range[0]) attrs[axi._name + '.range[0]'] = axi.range[0] + if (axi._r[1] !== axi.range[1]) attrs[axi._name + '.range[1]'] = axi.range[1] + + axi.range = axi._r.slice() } + updateSubplots([0, 0, pw, ph]) + Plotly.relayout(gd, attrs) + } + // updateSubplots - find all plot viewboxes that should be // affected by this drag, and update them. look for all plots // sharing an affected axis (including the one being dragged) - function updateSubplots(viewBox) { - var j; - var plotinfos = fullLayout._plots, - subplots = Object.keys(plotinfos); - - for(var i = 0; i < subplots.length; i++) { - - var subplot = plotinfos[subplots[i]], - xa2 = subplot.xaxis, - ya2 = subplot.yaxis, - editX = ew && !xa2.fixedrange, - editY = ns && !ya2.fixedrange; - - if(editX) { - var isInX = false; - for(j = 0; j < xa.length; j++) { - if(xa[j]._id === xa2._id) { - isInX = true; - break; - } - } - editX = editX && isInX; - } - - if(editY) { - var isInY = false; - for(j = 0; j < ya.length; j++) { - if(ya[j]._id === ya2._id) { - isInY = true; - break; - } - } - editY = editY && isInY; - } - - var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, - yScaleFactor = editY ? ya2._length / viewBox[3] : 1; - - var clipDx = editX ? viewBox[0] : 0, - clipDy = editY ? viewBox[1] : 0; - - var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, - fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; - - var plotDx = xa2._offset - fracDx, - plotDy = ya2._offset - fracDy; - - fullLayout._defs.selectAll('#' + subplot.clipId) + function updateSubplots (viewBox) { + var j + var plotinfos = fullLayout._plots, + subplots = Object.keys(plotinfos) + + for (var i = 0; i < subplots.length; i++) { + var subplot = plotinfos[subplots[i]], + xa2 = subplot.xaxis, + ya2 = subplot.yaxis, + editX = ew && !xa2.fixedrange, + editY = ns && !ya2.fixedrange + + if (editX) { + var isInX = false + for (j = 0; j < xa.length; j++) { + if (xa[j]._id === xa2._id) { + isInX = true + break + } + } + editX = editX && isInX + } + + if (editY) { + var isInY = false + for (j = 0; j < ya.length; j++) { + if (ya[j]._id === ya2._id) { + isInY = true + break + } + } + editY = editY && isInY + } + + var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, + yScaleFactor = editY ? ya2._length / viewBox[3] : 1 + + var clipDx = editX ? viewBox[0] : 0, + clipDy = editY ? viewBox[1] : 0 + + var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, + fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0 + + var plotDx = xa2._offset - fracDx, + plotDy = ya2._offset - fracDy + + fullLayout._defs.selectAll('#' + subplot.clipId) .call(Drawing.setTranslate, clipDx, clipDy) - .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor) - subplot.plot + subplot.plot .call(Drawing.setTranslate, plotDx, plotDy) .call(Drawing.setScale, xScaleFactor, yScaleFactor) @@ -713,51 +700,49 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // scale to individual points to counteract the scale of the trace // as a whole: .select('.scatterlayer').selectAll('.points').selectAll('.point') - .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); - } + .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor) } + } - return dragger; -}; + return dragger +} -function getEndText(ax, end) { - var initialVal = ax.range[end], - diff = Math.abs(initialVal - ax.range[1 - end]), - dig; +function getEndText (ax, end) { + var initialVal = ax.range[end], + diff = Math.abs(initialVal - ax.range[1 - end]), + dig // TODO: this should basically be ax.r2d but we're doing extra // rounding here... can we clean up at all? - if(ax.type === 'date') { - return initialVal; - } - else if(ax.type === 'log') { - dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3; - return d3.format('.' + dig + 'g')(Math.pow(10, initialVal)); - } - else { // linear numeric (or category... but just show numbers here) - dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) - - Math.floor(Math.log(diff) / Math.LN10) + 4; - return d3.format('.' + String(dig) + 'g')(initialVal); - } + if (ax.type === 'date') { + return initialVal + } else if (ax.type === 'log') { + dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3 + return d3.format('.' + dig + 'g')(Math.pow(10, initialVal)) + } else { // linear numeric (or category... but just show numbers here) + dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) - + Math.floor(Math.log(diff) / Math.LN10) + 4 + return d3.format('.' + String(dig) + 'g')(initialVal) + } } -function getDragCursor(nsew, dragmode) { - if(!nsew) return 'pointer'; - if(nsew === 'nsew') { - if(dragmode === 'pan') return 'move'; - return 'crosshair'; - } - return nsew.toLowerCase() + '-resize'; +function getDragCursor (nsew, dragmode) { + if (!nsew) return 'pointer' + if (nsew === 'nsew') { + if (dragmode === 'pan') return 'move' + return 'crosshair' + } + return nsew.toLowerCase() + '-resize' } -function removeZoombox(gd) { - d3.select(gd) +function removeZoombox (gd) { + d3.select(gd) .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') - .remove(); + .remove() } -function isSelectOrLasso(dragmode) { - var modes = ['lasso', 'select']; +function isSelectOrLasso (dragmode) { + var modes = ['lasso', 'select'] - return modes.indexOf(dragmode) !== -1; + return modes.indexOf(dragmode) !== -1 } diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 7fea9cb893e..4cad919f8a8 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -6,118 +6,114 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var tinycolor = require('tinycolor2') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var tinycolor = require('tinycolor2'); -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Events = require('../../lib/events') +var svgTextUtils = require('../../lib/svg_text_utils') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var dragElement = require('../../components/dragelement') +var overrideCursor = require('../../lib/override_cursor') +var Registry = require('../../registry') -var Lib = require('../../lib'); -var Events = require('../../lib/events'); -var svgTextUtils = require('../../lib/svg_text_utils'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var dragElement = require('../../components/dragelement'); -var overrideCursor = require('../../lib/override_cursor'); -var Registry = require('../../registry'); +var Axes = require('./axes') +var constants = require('./constants') +var dragBox = require('./dragbox') +var layoutAttributes = require('../layout_attributes') -var Axes = require('./axes'); -var constants = require('./constants'); -var dragBox = require('./dragbox'); -var layoutAttributes = require('../layout_attributes'); - - -var fx = module.exports = {}; +var fx = module.exports = {} // TODO remove this in version 2.0 // copy on Fx for backward compatible -fx.unhover = dragElement.unhover; +fx.unhover = dragElement.unhover -fx.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { +fx.supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { + function coerce (attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); - } + coerce('dragmode') - coerce('dragmode'); - - var hovermodeDflt; - if(layoutOut._has('cartesian')) { + var hovermodeDflt + if (layoutOut._has('cartesian')) { // flag for 'horizontal' plots: // determines the state of the mode bar 'compare' hovermode button - var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData); - hovermodeDflt = isHoriz ? 'y' : 'x'; - } - else hovermodeDflt = 'closest'; + var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData) + hovermodeDflt = isHoriz ? 'y' : 'x' + } else hovermodeDflt = 'closest' - coerce('hovermode', hovermodeDflt); -}; + coerce('hovermode', hovermodeDflt) +} -fx.isHoriz = function(fullData) { - var isHoriz = true; +fx.isHoriz = function (fullData) { + var isHoriz = true - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; + for (var i = 0; i < fullData.length; i++) { + var trace = fullData[i] - if(trace.orientation !== 'h') { - isHoriz = false; - break; - } + if (trace.orientation !== 'h') { + isHoriz = false + break } + } - return isHoriz; -}; + return isHoriz +} -fx.init = function(gd) { - var fullLayout = gd._fullLayout; +fx.init = function (gd) { + var fullLayout = gd._fullLayout - if(!fullLayout._has('cartesian') || gd._context.staticPlot) return; + if (!fullLayout._has('cartesian') || gd._context.staticPlot) return - var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) { + var subplots = Object.keys(fullLayout._plots || {}).sort(function (a, b) { // sort overlays last, then by x axis number, then y axis number - if((fullLayout._plots[a].mainplot && true) === + if ((fullLayout._plots[a].mainplot && true) === (fullLayout._plots[b].mainplot && true)) { - var aParts = a.split('y'), - bParts = b.split('y'); - return (aParts[0] === bParts[0]) ? + var aParts = a.split('y'), + bParts = b.split('y') + return (aParts[0] === bParts[0]) ? (Number(aParts[1] || 1) - Number(bParts[1] || 1)) : - (Number(aParts[0] || 1) - Number(bParts[0] || 1)); - } - return fullLayout._plots[a].mainplot ? 1 : -1; - }); + (Number(aParts[0] || 1) - Number(bParts[0] || 1)) + } + return fullLayout._plots[a].mainplot ? 1 : -1 + }) - subplots.forEach(function(subplot) { - var plotinfo = fullLayout._plots[subplot]; + subplots.forEach(function (subplot) { + var plotinfo = fullLayout._plots[subplot] - if(!fullLayout._has('cartesian')) return; + if (!fullLayout._has('cartesian')) return - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis, + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis, // the y position of the main x axis line - y0 = (xa._linepositions[subplot] || [])[3], + y0 = (xa._linepositions[subplot] || [])[3], // the x position of the main y axis line - x0 = (ya._linepositions[subplot] || [])[3]; + x0 = (ya._linepositions[subplot] || [])[3] - var DRAGGERSIZE = constants.DRAGGERSIZE; - if(isNumeric(y0) && xa.side === 'top') y0 -= DRAGGERSIZE; - if(isNumeric(x0) && ya.side !== 'right') x0 -= DRAGGERSIZE; + var DRAGGERSIZE = constants.DRAGGERSIZE + if (isNumeric(y0) && xa.side === 'top') y0 -= DRAGGERSIZE + if (isNumeric(x0) && ya.side !== 'right') x0 -= DRAGGERSIZE // main and corner draggers need not be repeated for // overlaid subplots - these draggers drag them all - if(!plotinfo.mainplot) { + if (!plotinfo.mainplot) { // main dragger goes over the grids and data, so we use its // mousemove events for all data hover effects - var maindrag = dragBox(gd, plotinfo, 0, 0, - xa._length, ya._length, 'ns', 'ew'); + var maindrag = dragBox(gd, plotinfo, 0, 0, + xa._length, ya._length, 'ns', 'ew') - maindrag.onmousemove = function(evt) { - fx.hover(gd, evt, subplot); - fullLayout._lasthover = maindrag; - fullLayout._hoversubplot = subplot; - }; + maindrag.onmousemove = function (evt) { + fx.hover(gd, evt, subplot) + fullLayout._lasthover = maindrag + fullLayout._hoversubplot = subplot + } /* * IMPORTANT: @@ -126,110 +122,110 @@ fx.init = function(gd) { * maindrag before each 'click' event, which has the effect * of clearing the hoverdata; thus, cancelling the click event. */ - maindrag.onmouseout = function(evt) { - if(gd._dragging) return; + maindrag.onmouseout = function (evt) { + if (gd._dragging) return - dragElement.unhover(gd, evt); - }; + dragElement.unhover(gd, evt) + } - maindrag.onclick = function(evt) { - fx.click(gd, evt); - }; + maindrag.onclick = function (evt) { + fx.click(gd, evt) + } // corner draggers - dragBox(gd, plotinfo, -DRAGGERSIZE, -DRAGGERSIZE, - DRAGGERSIZE, DRAGGERSIZE, 'n', 'w'); - dragBox(gd, plotinfo, xa._length, -DRAGGERSIZE, - DRAGGERSIZE, DRAGGERSIZE, 'n', 'e'); - dragBox(gd, plotinfo, -DRAGGERSIZE, ya._length, - DRAGGERSIZE, DRAGGERSIZE, 's', 'w'); - dragBox(gd, plotinfo, xa._length, ya._length, - DRAGGERSIZE, DRAGGERSIZE, 's', 'e'); - } + dragBox(gd, plotinfo, -DRAGGERSIZE, -DRAGGERSIZE, + DRAGGERSIZE, DRAGGERSIZE, 'n', 'w') + dragBox(gd, plotinfo, xa._length, -DRAGGERSIZE, + DRAGGERSIZE, DRAGGERSIZE, 'n', 'e') + dragBox(gd, plotinfo, -DRAGGERSIZE, ya._length, + DRAGGERSIZE, DRAGGERSIZE, 's', 'w') + dragBox(gd, plotinfo, xa._length, ya._length, + DRAGGERSIZE, DRAGGERSIZE, 's', 'e') + } // x axis draggers - if you have overlaid plots, // these drag each axis separately - if(isNumeric(y0)) { - if(xa.anchor === 'free') y0 -= fullLayout._size.h * (1 - ya.domain[1]); - dragBox(gd, plotinfo, xa._length * 0.1, y0, - xa._length * 0.8, DRAGGERSIZE, '', 'ew'); - dragBox(gd, plotinfo, 0, y0, - xa._length * 0.1, DRAGGERSIZE, '', 'w'); - dragBox(gd, plotinfo, xa._length * 0.9, y0, - xa._length * 0.1, DRAGGERSIZE, '', 'e'); - } + if (isNumeric(y0)) { + if (xa.anchor === 'free') y0 -= fullLayout._size.h * (1 - ya.domain[1]) + dragBox(gd, plotinfo, xa._length * 0.1, y0, + xa._length * 0.8, DRAGGERSIZE, '', 'ew') + dragBox(gd, plotinfo, 0, y0, + xa._length * 0.1, DRAGGERSIZE, '', 'w') + dragBox(gd, plotinfo, xa._length * 0.9, y0, + xa._length * 0.1, DRAGGERSIZE, '', 'e') + } // y axis draggers - if(isNumeric(x0)) { - if(ya.anchor === 'free') x0 -= fullLayout._size.w * xa.domain[0]; - dragBox(gd, plotinfo, x0, ya._length * 0.1, - DRAGGERSIZE, ya._length * 0.8, 'ns', ''); - dragBox(gd, plotinfo, x0, ya._length * 0.9, - DRAGGERSIZE, ya._length * 0.1, 's', ''); - dragBox(gd, plotinfo, x0, 0, - DRAGGERSIZE, ya._length * 0.1, 'n', ''); - } - }); + if (isNumeric(x0)) { + if (ya.anchor === 'free') x0 -= fullLayout._size.w * xa.domain[0] + dragBox(gd, plotinfo, x0, ya._length * 0.1, + DRAGGERSIZE, ya._length * 0.8, 'ns', '') + dragBox(gd, plotinfo, x0, ya._length * 0.9, + DRAGGERSIZE, ya._length * 0.1, 's', '') + dragBox(gd, plotinfo, x0, 0, + DRAGGERSIZE, ya._length * 0.1, 'n', '') + } + }) // In case you mousemove over some hovertext, send it to fx.hover too // we do this so that we can put the hover text in front of everything, // but still be able to interact with everything as if it isn't there - var hoverLayer = fullLayout._hoverlayer.node(); + var hoverLayer = fullLayout._hoverlayer.node() - hoverLayer.onmousemove = function(evt) { - evt.target = fullLayout._lasthover; - fx.hover(gd, evt, fullLayout._hoversubplot); - }; + hoverLayer.onmousemove = function (evt) { + evt.target = fullLayout._lasthover + fx.hover(gd, evt, fullLayout._hoversubplot) + } - hoverLayer.onclick = function(evt) { - evt.target = fullLayout._lasthover; - fx.click(gd, evt); - }; + hoverLayer.onclick = function (evt) { + evt.target = fullLayout._lasthover + fx.click(gd, evt) + } // also delegate mousedowns... TODO: does this actually work? - hoverLayer.onmousedown = function(evt) { - fullLayout._lasthover.onmousedown(evt); - }; -}; + hoverLayer.onmousedown = function (evt) { + fullLayout._lasthover.onmousedown(evt) + } +} // hover labels for multiple horizontal bars get tilted by some angle, // then need to be offset differently if they overlap var YANGLE = constants.YANGLE, - YA_RADIANS = Math.PI * YANGLE / 180, + YA_RADIANS = Math.PI * YANGLE / 180, // expansion of projected height - YFACTOR = 1 / Math.sin(YA_RADIANS), + YFACTOR = 1 / Math.sin(YA_RADIANS), // to make the appropriate post-rotation x offset, // you need both x and y offsets - YSHIFTX = Math.cos(YA_RADIANS), - YSHIFTY = Math.sin(YA_RADIANS); + YSHIFTX = Math.cos(YA_RADIANS), + YSHIFTY = Math.sin(YA_RADIANS) // convenience functions for mapping all relevant axes -function flat(subplots, v) { - var out = []; - for(var i = subplots.length; i > 0; i--) out.push(v); - return out; +function flat (subplots, v) { + var out = [] + for (var i = subplots.length; i > 0; i--) out.push(v) + return out } -function p2c(axArray, v) { - var out = []; - for(var i = 0; i < axArray.length; i++) out.push(axArray[i].p2c(v)); - return out; +function p2c (axArray, v) { + var out = [] + for (var i = 0; i < axArray.length; i++) out.push(axArray[i].p2c(v)) + return out } -function quadrature(dx, dy) { - return function(di) { - var x = dx(di), - y = dy(di); - return Math.sqrt(x * x + y * y); - }; +function quadrature (dx, dy) { + return function (di) { + var x = dx(di), + y = dy(di) + return Math.sqrt(x * x + y * y) + } } // size and display constants for hover text var HOVERARROWSIZE = constants.HOVERARROWSIZE, - HOVERTEXTPAD = constants.HOVERTEXTPAD, - HOVERFONTSIZE = constants.HOVERFONTSIZE, - HOVERFONT = constants.HOVERFONT; + HOVERTEXTPAD = constants.HOVERTEXTPAD, + HOVERFONTSIZE = constants.HOVERFONTSIZE, + HOVERFONT = constants.HOVERFONT // fx.hover: highlight data on hover // evt can be a mousemove event, or an object with data about what points @@ -257,483 +253,472 @@ var HOVERARROWSIZE = constants.HOVERARROWSIZE, // The actual rendering is done by private functions // hover() and unhover(). -fx.hover = function(gd, evt, subplot) { - if(typeof gd === 'string') gd = document.getElementById(gd); - if(gd._lastHoverTime === undefined) gd._lastHoverTime = 0; +fx.hover = function (gd, evt, subplot) { + if (typeof gd === 'string') gd = document.getElementById(gd) + if (gd._lastHoverTime === undefined) gd._lastHoverTime = 0 // If we have an update queued, discard it now - if(gd._hoverTimer !== undefined) { - clearTimeout(gd._hoverTimer); - gd._hoverTimer = undefined; - } + if (gd._hoverTimer !== undefined) { + clearTimeout(gd._hoverTimer) + gd._hoverTimer = undefined + } // Is it more than 100ms since the last update? If so, force // an update now (synchronously) and exit - if(Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) { - hover(gd, evt, subplot); - gd._lastHoverTime = Date.now(); - return; - } + if (Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) { + hover(gd, evt, subplot) + gd._lastHoverTime = Date.now() + return + } // Queue up the next hover for 100ms from now (if no further events) - gd._hoverTimer = setTimeout(function() { - hover(gd, evt, subplot); - gd._lastHoverTime = Date.now(); - gd._hoverTimer = undefined; - }, constants.HOVERMINTIME); -}; + gd._hoverTimer = setTimeout(function () { + hover(gd, evt, subplot) + gd._lastHoverTime = Date.now() + gd._hoverTimer = undefined + }, constants.HOVERMINTIME) +} // The actual implementation is here: -function hover(gd, evt, subplot) { - if(subplot === 'pie') { - gd.emit('plotly_hover', { - points: [evt] - }); - return; - } +function hover (gd, evt, subplot) { + if (subplot === 'pie') { + gd.emit('plotly_hover', { + points: [evt] + }) + return + } - if(!subplot) subplot = 'xy'; + if (!subplot) subplot = 'xy' // if the user passed in an array of subplots, // use those instead of finding overlayed plots - var subplots = Array.isArray(subplot) ? subplot : [subplot]; + var subplots = Array.isArray(subplot) ? subplot : [subplot] - var fullLayout = gd._fullLayout, - plots = fullLayout._plots || [], - plotinfo = plots[subplot]; + var fullLayout = gd._fullLayout, + plots = fullLayout._plots || [], + plotinfo = plots[subplot] // list of all overlaid subplots to look at - if(plotinfo) { - var overlayedSubplots = plotinfo.overlays.map(function(pi) { - return pi.id; - }); + if (plotinfo) { + var overlayedSubplots = plotinfo.overlays.map(function (pi) { + return pi.id + }) - subplots = subplots.concat(overlayedSubplots); - } + subplots = subplots.concat(overlayedSubplots) + } - var len = subplots.length, - xaArray = new Array(len), - yaArray = new Array(len); + var len = subplots.length, + xaArray = new Array(len), + yaArray = new Array(len) - for(var i = 0; i < len; i++) { - var spId = subplots[i]; + for (var i = 0; i < len; i++) { + var spId = subplots[i] // 'cartesian' case - var plotObj = plots[spId]; - if(plotObj) { - + var plotObj = plots[spId] + if (plotObj) { // TODO make sure that fullLayout_plots axis refs // get updated properly so that we don't have // to use Axes.getFromId in general. - xaArray[i] = Axes.getFromId(gd, plotObj.xaxis._id); - yaArray[i] = Axes.getFromId(gd, plotObj.yaxis._id); - continue; - } + xaArray[i] = Axes.getFromId(gd, plotObj.xaxis._id) + yaArray[i] = Axes.getFromId(gd, plotObj.yaxis._id) + continue + } // other subplot types - var _subplot = fullLayout[spId]._subplot; - xaArray[i] = _subplot.xaxis; - yaArray[i] = _subplot.yaxis; - } + var _subplot = fullLayout[spId]._subplot + xaArray[i] = _subplot.xaxis + yaArray[i] = _subplot.yaxis + } - var hovermode = evt.hovermode || fullLayout.hovermode; + var hovermode = evt.hovermode || fullLayout.hovermode - if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata || + if (['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata || gd.querySelector('.zoombox') || gd._dragging) { - return dragElement.unhoverRaw(gd, evt); - } + return dragElement.unhoverRaw(gd, evt) + } // hoverData: the set of candidate points we've found to highlight - var hoverData = [], + var hoverData = [], // searchData: the data to search in. Mostly this is just a copy of // gd.calcdata, filtered to the subplot and overlays we're on // but if a point array is supplied it will be a mapping // of indicated curves - searchData = [], + searchData = [], // [x|y]valArray: the axis values of the hover event // mapped onto each of the currently selected overlaid subplots - xvalArray, - yvalArray, + xvalArray, + yvalArray, // used in loops - itemnum, - curvenum, - cd, - trace, - subplotId, - subploti, - mode, - xval, - yval, - pointData, - closedataPreviousLength; + itemnum, + curvenum, + cd, + trace, + subplotId, + subploti, + mode, + xval, + yval, + pointData, + closedataPreviousLength // Figure out what we're hovering on: // mouse location or user-supplied data - if(Array.isArray(evt)) { + if (Array.isArray(evt)) { // user specified an array of points to highlight - hovermode = 'array'; - for(itemnum = 0; itemnum < evt.length; itemnum++) { - cd = gd.calcdata[evt[itemnum].curveNumber||0]; - if(cd[0].trace.hoverinfo !== 'skip') { - searchData.push(cd); - } - } + hovermode = 'array' + for (itemnum = 0; itemnum < evt.length; itemnum++) { + cd = gd.calcdata[evt[itemnum].curveNumber || 0] + if (cd[0].trace.hoverinfo !== 'skip') { + searchData.push(cd) + } + } + } else { + for (curvenum = 0; curvenum < gd.calcdata.length; curvenum++) { + cd = gd.calcdata[curvenum] + trace = cd[0].trace + if (trace.hoverinfo !== 'skip' && subplots.indexOf(getSubplot(trace)) !== -1) { + searchData.push(cd) + } } - else { - for(curvenum = 0; curvenum < gd.calcdata.length; curvenum++) { - cd = gd.calcdata[curvenum]; - trace = cd[0].trace; - if(trace.hoverinfo !== 'skip' && subplots.indexOf(getSubplot(trace)) !== -1) { - searchData.push(cd); - } - } // [x|y]px: the pixels (from top left) of the mouse location // on the currently selected plot area - var xpx, ypx; + var xpx, ypx // mouse event? ie is there a target element with // clientX and clientY values? - if(evt.target && ('clientX' in evt) && ('clientY' in evt)) { - + if (evt.target && ('clientX' in evt) && ('clientY' in evt)) { // fire the beforehover event and quit if it returns false // note that we're only calling this on real mouse events, so // manual calls to fx.hover will always run. - if(Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { - return; - } + if (Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { + return + } - var dbb = evt.target.getBoundingClientRect(); + var dbb = evt.target.getBoundingClientRect() - xpx = evt.clientX - dbb.left; - ypx = evt.clientY - dbb.top; + xpx = evt.clientX - dbb.left + ypx = evt.clientY - dbb.top // in case hover was called from mouseout into hovertext, // it's possible you're not actually over the plot anymore - if(xpx < 0 || xpx > dbb.width || ypx < 0 || ypx > dbb.height) { - return dragElement.unhoverRaw(gd, evt); - } - } - else { - if('xpx' in evt) xpx = evt.xpx; - else xpx = xaArray[0]._length / 2; - - if('ypx' in evt) ypx = evt.ypx; - else ypx = yaArray[0]._length / 2; - } + if (xpx < 0 || xpx > dbb.width || ypx < 0 || ypx > dbb.height) { + return dragElement.unhoverRaw(gd, evt) + } + } else { + if ('xpx' in evt) xpx = evt.xpx + else xpx = xaArray[0]._length / 2 + + if ('ypx' in evt) ypx = evt.ypx + else ypx = yaArray[0]._length / 2 + } - if('xval' in evt) xvalArray = flat(subplots, evt.xval); - else xvalArray = p2c(xaArray, xpx); + if ('xval' in evt) xvalArray = flat(subplots, evt.xval) + else xvalArray = p2c(xaArray, xpx) - if('yval' in evt) yvalArray = flat(subplots, evt.yval); - else yvalArray = p2c(yaArray, ypx); + if ('yval' in evt) yvalArray = flat(subplots, evt.yval) + else yvalArray = p2c(yaArray, ypx) - if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { - Lib.warn('Fx.hover failed', evt, gd); - return dragElement.unhoverRaw(gd, evt); - } + if (!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { + Lib.warn('Fx.hover failed', evt, gd) + return dragElement.unhoverRaw(gd, evt) } + } // the pixel distance to beat as a matching point // in 'x' or 'y' mode this resets for each trace - var distance = Infinity; + var distance = Infinity // find the closest point in each trace // this is minimum dx and/or dy, depending on mode // and the pixel position for the label (labelXpx, labelYpx) - for(curvenum = 0; curvenum < searchData.length; curvenum++) { - cd = searchData[curvenum]; + for (curvenum = 0; curvenum < searchData.length; curvenum++) { + cd = searchData[curvenum] // filter out invisible or broken data - if(!cd || !cd[0] || !cd[0].trace || cd[0].trace.visible !== true) continue; + if (!cd || !cd[0] || !cd[0].trace || cd[0].trace.visible !== true) continue - trace = cd[0].trace; - subplotId = getSubplot(trace); - subploti = subplots.indexOf(subplotId); + trace = cd[0].trace + subplotId = getSubplot(trace) + subploti = subplots.indexOf(subplotId) // within one trace mode can sometimes be overridden - mode = hovermode; + mode = hovermode // container for new point, also used to pass info into module.hoverPoints - pointData = { + pointData = { // trace properties - cd: cd, - trace: trace, - xa: xaArray[subploti], - ya: yaArray[subploti], - name: (gd.data.length > 1 || trace.hoverinfo.indexOf('name') !== -1) ? trace.name : undefined, + cd: cd, + trace: trace, + xa: xaArray[subploti], + ya: yaArray[subploti], + name: (gd.data.length > 1 || trace.hoverinfo.indexOf('name') !== -1) ? trace.name : undefined, // point properties - override all of these - index: false, // point index in trace - only used by plotly.js hoverdata consumers - distance: Math.min(distance, constants.MAXDIST), // pixel distance or pseudo-distance - color: Color.defaultLine, // trace color - x0: undefined, - x1: undefined, - y0: undefined, - y1: undefined, - xLabelVal: undefined, - yLabelVal: undefined, - zLabelVal: undefined, - text: undefined - }; + index: false, // point index in trace - only used by plotly.js hoverdata consumers + distance: Math.min(distance, constants.MAXDIST), // pixel distance or pseudo-distance + color: Color.defaultLine, // trace color + x0: undefined, + x1: undefined, + y0: undefined, + y1: undefined, + xLabelVal: undefined, + yLabelVal: undefined, + zLabelVal: undefined, + text: undefined + } // add ref to subplot object (non-cartesian case) - if(fullLayout[subplotId]) { - pointData.subplot = fullLayout[subplotId]._subplot; - } + if (fullLayout[subplotId]) { + pointData.subplot = fullLayout[subplotId]._subplot + } - closedataPreviousLength = hoverData.length; + closedataPreviousLength = hoverData.length // for a highlighting array, figure out what // we're searching for with this element - if(mode === 'array') { - var selection = evt[curvenum]; - if('pointNumber' in selection) { - pointData.index = selection.pointNumber; - mode = 'closest'; - } - else { - mode = ''; - if('xval' in selection) { - xval = selection.xval; - mode = 'x'; - } - if('yval' in selection) { - yval = selection.yval; - mode = mode ? 'closest' : 'y'; - } - } + if (mode === 'array') { + var selection = evt[curvenum] + if ('pointNumber' in selection) { + pointData.index = selection.pointNumber + mode = 'closest' + } else { + mode = '' + if ('xval' in selection) { + xval = selection.xval + mode = 'x' } - else { - xval = xvalArray[subploti]; - yval = yvalArray[subploti]; + if ('yval' in selection) { + yval = selection.yval + mode = mode ? 'closest' : 'y' } + } + } else { + xval = xvalArray[subploti] + yval = yvalArray[subploti] + } // Now find the points. - if(trace._module && trace._module.hoverPoints) { - var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode); - if(newPoints) { - var newPoint; - for(var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) { - newPoint = newPoints[newPointNum]; - if(isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) { - hoverData.push(cleanPoint(newPoint, hovermode)); - } - } - } - } - else { - Lib.log('Unrecognized trace type in hover:', trace); + if (trace._module && trace._module.hoverPoints) { + var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode) + if (newPoints) { + var newPoint + for (var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) { + newPoint = newPoints[newPointNum] + if (isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) { + hoverData.push(cleanPoint(newPoint, hovermode)) + } } + } + } else { + Lib.log('Unrecognized trace type in hover:', trace) + } // in closest mode, remove any existing (farther) points // and don't look any farther than this latest point (or points, if boxes) - if(hovermode === 'closest' && hoverData.length > closedataPreviousLength) { - hoverData.splice(0, closedataPreviousLength); - distance = hoverData[0].distance; - } + if (hovermode === 'closest' && hoverData.length > closedataPreviousLength) { + hoverData.splice(0, closedataPreviousLength) + distance = hoverData[0].distance } + } // nothing left: remove all labels and quit - if(hoverData.length === 0) return dragElement.unhoverRaw(gd, evt); + if (hoverData.length === 0) return dragElement.unhoverRaw(gd, evt) // if there's more than one horz bar trace, // rotate the labels so they don't overlap - var rotateLabels = hovermode === 'y' && searchData.length > 1; + var rotateLabels = hovermode === 'y' && searchData.length > 1 - hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); + hoverData.sort(function (d1, d2) { return d1.distance - d2.distance }) - var bgColor = Color.combine( + var bgColor = Color.combine( fullLayout.plot_bgcolor || Color.background, fullLayout.paper_bgcolor - ); + ) - var labelOpts = { - hovermode: hovermode, - rotateLabels: rotateLabels, - bgColor: bgColor, - container: fullLayout._hoverlayer, - outerContainer: fullLayout._paperdiv - }; - var hoverLabels = createHoverText(hoverData, labelOpts); + var labelOpts = { + hovermode: hovermode, + rotateLabels: rotateLabels, + bgColor: bgColor, + container: fullLayout._hoverlayer, + outerContainer: fullLayout._paperdiv + } + var hoverLabels = createHoverText(hoverData, labelOpts) - hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya'); + hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya') - alignHoverText(hoverLabels, rotateLabels); + alignHoverText(hoverLabels, rotateLabels) // lastly, emit custom hover/unhover events - var oldhoverdata = gd._hoverdata, - newhoverdata = []; + var oldhoverdata = gd._hoverdata, + newhoverdata = [] // pull out just the data that's useful to // other people and send it to the event - for(itemnum = 0; itemnum < hoverData.length; itemnum++) { - var pt = hoverData[itemnum]; - - var out = { - data: pt.trace._input, - fullData: pt.trace, - curveNumber: pt.trace.index, - pointNumber: pt.index - }; - - if(pt.trace._module.eventData) out = pt.trace._module.eventData(out, pt); - else { - out.x = pt.xVal; - out.y = pt.yVal; - out.xaxis = pt.xa; - out.yaxis = pt.ya; - - if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal; - } - - newhoverdata.push(out); + for (itemnum = 0; itemnum < hoverData.length; itemnum++) { + var pt = hoverData[itemnum] + + var out = { + data: pt.trace._input, + fullData: pt.trace, + curveNumber: pt.trace.index, + pointNumber: pt.index } - gd._hoverdata = newhoverdata; + if (pt.trace._module.eventData) out = pt.trace._module.eventData(out, pt) + else { + out.x = pt.xVal + out.y = pt.yVal + out.xaxis = pt.xa + out.yaxis = pt.ya - // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true - // we should improve the "fx" API so other plots can use it without these hack. - if(evt.target && evt.target.tagName) { - var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata); - overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : ''); + if (pt.zLabelVal !== undefined) out.z = pt.zLabelVal } - if(!hoverChanged(gd, evt, oldhoverdata)) return; + newhoverdata.push(out) + } - if(oldhoverdata) { - gd.emit('plotly_unhover', { points: oldhoverdata }); - } + gd._hoverdata = newhoverdata - gd.emit('plotly_hover', { - points: gd._hoverdata, - xaxes: xaArray, - yaxes: yaArray, - xvals: xvalArray, - yvals: yvalArray - }); + // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true + // we should improve the "fx" API so other plots can use it without these hack. + if (evt.target && evt.target.tagName) { + var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata) + overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : '') + } + + if (!hoverChanged(gd, evt, oldhoverdata)) return + + if (oldhoverdata) { + gd.emit('plotly_unhover', { points: oldhoverdata }) + } + + gd.emit('plotly_hover', { + points: gd._hoverdata, + xaxes: xaArray, + yaxes: yaArray, + xvals: xvalArray, + yvals: yvalArray + }) } // look for either .subplot (currently just ternary) // or xaxis and yaxis attributes -function getSubplot(trace) { - return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo; +function getSubplot (trace) { + return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo } -fx.getDistanceFunction = function(mode, dx, dy, dxy) { - if(mode === 'closest') return dxy || quadrature(dx, dy); - return mode === 'x' ? dx : dy; -}; +fx.getDistanceFunction = function (mode, dx, dy, dxy) { + if (mode === 'closest') return dxy || quadrature(dx, dy) + return mode === 'x' ? dx : dy +} -fx.getClosest = function(cd, distfn, pointData) { +fx.getClosest = function (cd, distfn, pointData) { // do we already have a point number? (array mode only) - if(pointData.index !== false) { - if(pointData.index >= 0 && pointData.index < cd.length) { - pointData.distance = 0; - } - else pointData.index = false; - } - else { + if (pointData.index !== false) { + if (pointData.index >= 0 && pointData.index < cd.length) { + pointData.distance = 0 + } else pointData.index = false + } else { // apply the distance function to each data point // this is the longest loop... if this bogs down, we may need // to create pre-sorted data (by x or y), not sure how to // do this for 'closest' - for(var i = 0; i < cd.length; i++) { - var newDistance = distfn(cd[i]); - if(newDistance <= pointData.distance) { - pointData.index = i; - pointData.distance = newDistance; - } - } + for (var i = 0; i < cd.length; i++) { + var newDistance = distfn(cd[i]) + if (newDistance <= pointData.distance) { + pointData.index = i + pointData.distance = newDistance + } } - return pointData; -}; + } + return pointData +} -function cleanPoint(d, hovermode) { - d.posref = hovermode === 'y' ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2; +function cleanPoint (d, hovermode) { + d.posref = hovermode === 'y' ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2 // then constrain all the positions to be on the plot - d.x0 = Lib.constrain(d.x0, 0, d.xa._length); - d.x1 = Lib.constrain(d.x1, 0, d.xa._length); - d.y0 = Lib.constrain(d.y0, 0, d.ya._length); - d.y1 = Lib.constrain(d.y1, 0, d.ya._length); + d.x0 = Lib.constrain(d.x0, 0, d.xa._length) + d.x1 = Lib.constrain(d.x1, 0, d.xa._length) + d.y0 = Lib.constrain(d.y0, 0, d.ya._length) + d.y1 = Lib.constrain(d.y1, 0, d.ya._length) // and convert the x and y label values into objects // formatted as text, with font info - var logOffScale; - if(d.xLabelVal !== undefined) { - logOffScale = (d.xa.type === 'log' && d.xLabelVal <= 0); - var xLabelObj = Axes.tickText(d.xa, - d.xa.c2l(logOffScale ? -d.xLabelVal : d.xLabelVal), 'hover'); - if(logOffScale) { - if(d.xLabelVal === 0) d.xLabel = '0'; - else d.xLabel = '-' + xLabelObj.text; - } + var logOffScale + if (d.xLabelVal !== undefined) { + logOffScale = (d.xa.type === 'log' && d.xLabelVal <= 0) + var xLabelObj = Axes.tickText(d.xa, + d.xa.c2l(logOffScale ? -d.xLabelVal : d.xLabelVal), 'hover') + if (logOffScale) { + if (d.xLabelVal === 0) d.xLabel = '0' + else d.xLabel = '-' + xLabelObj.text + } // TODO: should we do something special if the axis calendar and // the data calendar are different? Somehow display both dates with // their system names? Right now it will just display in the axis calendar // but users could add the other one as text. - else d.xLabel = xLabelObj.text; - d.xVal = d.xa.c2d(d.xLabelVal); + else d.xLabel = xLabelObj.text + d.xVal = d.xa.c2d(d.xLabelVal) + } + + if (d.yLabelVal !== undefined) { + logOffScale = (d.ya.type === 'log' && d.yLabelVal <= 0) + var yLabelObj = Axes.tickText(d.ya, + d.ya.c2l(logOffScale ? -d.yLabelVal : d.yLabelVal), 'hover') + if (logOffScale) { + if (d.yLabelVal === 0) d.yLabel = '0' + else d.yLabel = '-' + yLabelObj.text } - - if(d.yLabelVal !== undefined) { - logOffScale = (d.ya.type === 'log' && d.yLabelVal <= 0); - var yLabelObj = Axes.tickText(d.ya, - d.ya.c2l(logOffScale ? -d.yLabelVal : d.yLabelVal), 'hover'); - if(logOffScale) { - if(d.yLabelVal === 0) d.yLabel = '0'; - else d.yLabel = '-' + yLabelObj.text; - } // TODO: see above TODO - else d.yLabel = yLabelObj.text; - d.yVal = d.ya.c2d(d.yLabelVal); - } + else d.yLabel = yLabelObj.text + d.yVal = d.ya.c2d(d.yLabelVal) + } - if(d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal); + if (d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal) // for box means and error bars, add the range to the label - if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) { - var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text; - if(d.xerrneg !== undefined) { - d.xLabel += ' +' + xeText + ' / -' + - Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text; - } - else d.xLabel += ' ± ' + xeText; + if (!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) { + var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text + if (d.xerrneg !== undefined) { + d.xLabel += ' +' + xeText + ' / -' + + Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text + } else d.xLabel += ' ± ' + xeText // small distance penalty for error bars, so that if there are // traces with errors and some without, the error bar label will // hoist up to the point - if(hovermode === 'x') d.distance += 1; - } - if(!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) { - var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text; - if(d.yerrneg !== undefined) { - d.yLabel += ' +' + yeText + ' / -' + - Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text; - } - else d.yLabel += ' ± ' + yeText; - - if(hovermode === 'y') d.distance += 1; - } - - var infomode = d.trace.hoverinfo; - if(infomode !== 'all') { - infomode = infomode.split('+'); - if(infomode.indexOf('x') === -1) d.xLabel = undefined; - if(infomode.indexOf('y') === -1) d.yLabel = undefined; - if(infomode.indexOf('z') === -1) d.zLabel = undefined; - if(infomode.indexOf('text') === -1) d.text = undefined; - if(infomode.indexOf('name') === -1) d.name = undefined; - } - - return d; + if (hovermode === 'x') d.distance += 1 + } + if (!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) { + var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text + if (d.yerrneg !== undefined) { + d.yLabel += ' +' + yeText + ' / -' + + Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text + } else d.yLabel += ' ± ' + yeText + + if (hovermode === 'y') d.distance += 1 + } + + var infomode = d.trace.hoverinfo + if (infomode !== 'all') { + infomode = infomode.split('+') + if (infomode.indexOf('x') === -1) d.xLabel = undefined + if (infomode.indexOf('y') === -1) d.yLabel = undefined + if (infomode.indexOf('z') === -1) d.zLabel = undefined + if (infomode.indexOf('text') === -1) d.text = undefined + if (infomode.indexOf('name') === -1) d.name = undefined + } + + return d } -fx.loneHover = function(hoverItem, opts) { +fx.loneHover = function (hoverItem, opts) { // draw a single hover item in a pre-existing svg container somewhere // hoverItem should have keys: // - x and y (or x0, x1, y0, and y1): @@ -750,336 +735,329 @@ fx.loneHover = function(hoverItem, opts) { // - container: // a dom element - must be big enough to contain the whole // hover label - var pointData = { - color: hoverItem.color || Color.defaultLine, - x0: hoverItem.x0 || hoverItem.x || 0, - x1: hoverItem.x1 || hoverItem.x || 0, - y0: hoverItem.y0 || hoverItem.y || 0, - y1: hoverItem.y1 || hoverItem.y || 0, - xLabel: hoverItem.xLabel, - yLabel: hoverItem.yLabel, - zLabel: hoverItem.zLabel, - text: hoverItem.text, - name: hoverItem.name, - idealAlign: hoverItem.idealAlign, + var pointData = { + color: hoverItem.color || Color.defaultLine, + x0: hoverItem.x0 || hoverItem.x || 0, + x1: hoverItem.x1 || hoverItem.x || 0, + y0: hoverItem.y0 || hoverItem.y || 0, + y1: hoverItem.y1 || hoverItem.y || 0, + xLabel: hoverItem.xLabel, + yLabel: hoverItem.yLabel, + zLabel: hoverItem.zLabel, + text: hoverItem.text, + name: hoverItem.name, + idealAlign: hoverItem.idealAlign, // filler to make createHoverText happy - trace: { - index: 0, - hoverinfo: '' - }, - xa: {_offset: 0}, - ya: {_offset: 0}, - index: 0 - }; - - var container3 = d3.select(opts.container), - outerContainer3 = opts.outerContainer ? - d3.select(opts.outerContainer) : container3; - - var fullOpts = { - hovermode: 'closest', - rotateLabels: false, - bgColor: opts.bgColor || Color.background, - container: container3, - outerContainer: outerContainer3 - }; - - var hoverLabel = createHoverText([pointData], fullOpts); - alignHoverText(hoverLabel, fullOpts.rotateLabels); - - return hoverLabel.node(); -}; - -fx.loneUnhover = function(containerOrSelection) { + trace: { + index: 0, + hoverinfo: '' + }, + xa: {_offset: 0}, + ya: {_offset: 0}, + index: 0 + } + + var container3 = d3.select(opts.container), + outerContainer3 = opts.outerContainer ? + d3.select(opts.outerContainer) : container3 + + var fullOpts = { + hovermode: 'closest', + rotateLabels: false, + bgColor: opts.bgColor || Color.background, + container: container3, + outerContainer: outerContainer3 + } + + var hoverLabel = createHoverText([pointData], fullOpts) + alignHoverText(hoverLabel, fullOpts.rotateLabels) + + return hoverLabel.node() +} + +fx.loneUnhover = function (containerOrSelection) { // duck type whether the arg is a d3 selection because ie9 doesn't // handle instanceof like modern browsers do. - var selection = Lib.isD3Selection(containerOrSelection) ? + var selection = Lib.isD3Selection(containerOrSelection) ? containerOrSelection : - d3.select(containerOrSelection); - - selection.selectAll('g.hovertext').remove(); -}; - -function createHoverText(hoverData, opts) { - var hovermode = opts.hovermode, - rotateLabels = opts.rotateLabels, - bgColor = opts.bgColor, - container = opts.container, - outerContainer = opts.outerContainer, - - c0 = hoverData[0], - xa = c0.xa, - ya = c0.ya, - commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel', - t0 = c0[commonAttr], - t00 = (String(t0) || '').split(' ')[0], - outerContainerBB = outerContainer.node().getBoundingClientRect(), - outerTop = outerContainerBB.top, - outerWidth = outerContainerBB.width, - outerHeight = outerContainerBB.height; + d3.select(containerOrSelection) + + selection.selectAll('g.hovertext').remove() +} + +function createHoverText (hoverData, opts) { + var hovermode = opts.hovermode, + rotateLabels = opts.rotateLabels, + bgColor = opts.bgColor, + container = opts.container, + outerContainer = opts.outerContainer, + + c0 = hoverData[0], + xa = c0.xa, + ya = c0.ya, + commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel', + t0 = c0[commonAttr], + t00 = (String(t0) || '').split(' ')[0], + outerContainerBB = outerContainer.node().getBoundingClientRect(), + outerTop = outerContainerBB.top, + outerWidth = outerContainerBB.width, + outerHeight = outerContainerBB.height // show the common label, if any, on the axis // never show a common label in array mode, // even if sometimes there could be one - var showCommonLabel = c0.distance <= constants.MAXDIST && - (hovermode === 'x' || hovermode === 'y'); + var showCommonLabel = c0.distance <= constants.MAXDIST && + (hovermode === 'x' || hovermode === 'y') // all hover traces hoverinfo must contain the hovermode // to have common labels - var i, traceHoverinfo; - for(i = 0; i < hoverData.length; i++) { - traceHoverinfo = hoverData[i].trace.hoverinfo; - var parts = traceHoverinfo.split('+'); - if(parts.indexOf('all') === -1 && + var i, traceHoverinfo + for (i = 0; i < hoverData.length; i++) { + traceHoverinfo = hoverData[i].trace.hoverinfo + var parts = traceHoverinfo.split('+') + if (parts.indexOf('all') === -1 && parts.indexOf(hovermode) === -1) { - showCommonLabel = false; - break; - } + showCommonLabel = false + break } - - var commonLabel = container.selectAll('g.axistext') - .data(showCommonLabel ? [0] : []); - commonLabel.enter().append('g') - .classed('axistext', true); - commonLabel.exit().remove(); - - commonLabel.each(function() { - var label = d3.select(this), - lpath = label.selectAll('path').data([0]), - ltext = label.selectAll('text').data([0]); - - lpath.enter().append('path') - .style({fill: Color.defaultLine, 'stroke-width': '1px', stroke: Color.background}); - ltext.enter().append('text') + } + + var commonLabel = container.selectAll('g.axistext') + .data(showCommonLabel ? [0] : []) + commonLabel.enter().append('g') + .classed('axistext', true) + commonLabel.exit().remove() + + commonLabel.each(function () { + var label = d3.select(this), + lpath = label.selectAll('path').data([0]), + ltext = label.selectAll('text').data([0]) + + lpath.enter().append('path') + .style({fill: Color.defaultLine, 'stroke-width': '1px', stroke: Color.background}) + ltext.enter().append('text') .call(Drawing.font, HOVERFONT, HOVERFONTSIZE, Color.background) // prohibit tex interpretation until we can handle // tex and regular text together - .attr('data-notex', 1); + .attr('data-notex', 1) - ltext.text(t0) + ltext.text(t0) .call(svgTextUtils.convertToTspans) .call(Drawing.setPosition, 0, 0) .selectAll('tspan.line') - .call(Drawing.setPosition, 0, 0); - label.attr('transform', ''); + .call(Drawing.setPosition, 0, 0) + label.attr('transform', '') - var tbb = ltext.node().getBoundingClientRect(); - if(hovermode === 'x') { - ltext.attr('text-anchor', 'middle') + var tbb = ltext.node().getBoundingClientRect() + if (hovermode === 'x') { + ltext.attr('text-anchor', 'middle') .call(Drawing.setPosition, 0, (xa.side === 'top' ? (outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD) : (outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD))) .selectAll('tspan.line') .attr({ - x: ltext.attr('x'), - y: ltext.attr('y') - }); + x: ltext.attr('x'), + y: ltext.attr('y') + }) - var topsign = xa.side === 'top' ? '-' : ''; - lpath.attr('d', 'M0,0' + + var topsign = xa.side === 'top' ? '-' : '' + lpath.attr('d', 'M0,0' + 'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE + 'H' + (HOVERTEXTPAD + tbb.width / 2) + 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) + 'H-' + (HOVERTEXTPAD + tbb.width / 2) + - 'V' + topsign + HOVERARROWSIZE + 'H-' + HOVERARROWSIZE + 'Z'); + 'V' + topsign + HOVERARROWSIZE + 'H-' + HOVERARROWSIZE + 'Z') - label.attr('transform', 'translate(' + + label.attr('transform', 'translate(' + (xa._offset + (c0.x0 + c0.x1) / 2) + ',' + - (ya._offset + (xa.side === 'top' ? 0 : ya._length)) + ')'); - } - else { - ltext.attr('text-anchor', ya.side === 'right' ? 'start' : 'end') + (ya._offset + (xa.side === 'top' ? 0 : ya._length)) + ')') + } else { + ltext.attr('text-anchor', ya.side === 'right' ? 'start' : 'end') .call(Drawing.setPosition, (ya.side === 'right' ? 1 : -1) * (HOVERTEXTPAD + HOVERARROWSIZE), outerTop - tbb.top - tbb.height / 2) .selectAll('tspan.line') .attr({ - x: ltext.attr('x'), - y: ltext.attr('y') - }); + x: ltext.attr('x'), + y: ltext.attr('y') + }) - var leftsign = ya.side === 'right' ? '' : '-'; - lpath.attr('d', 'M0,0' + + var leftsign = ya.side === 'right' ? '' : '-' + lpath.attr('d', 'M0,0' + 'L' + leftsign + HOVERARROWSIZE + ',' + HOVERARROWSIZE + 'V' + (HOVERTEXTPAD + tbb.height / 2) + 'h' + leftsign + (HOVERTEXTPAD * 2 + tbb.width) + 'V-' + (HOVERTEXTPAD + tbb.height / 2) + - 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z'); + 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z') - label.attr('transform', 'translate(' + + label.attr('transform', 'translate(' + (xa._offset + (ya.side === 'right' ? xa._length : 0)) + ',' + - (ya._offset + (c0.y0 + c0.y1) / 2) + ')'); - } + (ya._offset + (c0.y0 + c0.y1) / 2) + ')') + } // remove the "close but not quite" points // because of error bars, only take up to a space - hoverData = hoverData.filter(function(d) { - return (d.zLabelVal !== undefined) || - (d[commonAttr] || '').split(' ')[0] === t00; - }); - }); + hoverData = hoverData.filter(function (d) { + return (d.zLabelVal !== undefined) || + (d[commonAttr] || '').split(' ')[0] === t00 + }) + }) // show all the individual labels // first create the objects - var hoverLabels = container.selectAll('g.hovertext') - .data(hoverData, function(d) { - return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(','); - }); - hoverLabels.enter().append('g') + var hoverLabels = container.selectAll('g.hovertext') + .data(hoverData, function (d) { + return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',') + }) + hoverLabels.enter().append('g') .classed('hovertext', true) - .each(function() { - var g = d3.select(this); + .each(function () { + var g = d3.select(this) // trace name label (rect and text.name) - g.append('rect') - .call(Color.fill, Color.addOpacity(bgColor, 0.8)); - g.append('text').classed('name', true) - .call(Drawing.font, HOVERFONT, HOVERFONTSIZE); + g.append('rect') + .call(Color.fill, Color.addOpacity(bgColor, 0.8)) + g.append('text').classed('name', true) + .call(Drawing.font, HOVERFONT, HOVERFONTSIZE) // trace data label (path and text.nums) - g.append('path') - .style('stroke-width', '1px'); - g.append('text').classed('nums', true) - .call(Drawing.font, HOVERFONT, HOVERFONTSIZE); - }); - hoverLabels.exit().remove(); + g.append('path') + .style('stroke-width', '1px') + g.append('text').classed('nums', true) + .call(Drawing.font, HOVERFONT, HOVERFONTSIZE) + }) + hoverLabels.exit().remove() // then put the text in, position the pointer to the data, // and figure out sizes - hoverLabels.each(function(d) { - var g = d3.select(this).attr('transform', ''), - name = '', - text = '', + hoverLabels.each(function (d) { + var g = d3.select(this).attr('transform', ''), + name = '', + text = '', // combine possible non-opaque trace color with bgColor - baseColor = Color.opacity(d.color) ? + baseColor = Color.opacity(d.color) ? d.color : Color.defaultLine, - traceColor = Color.combine(baseColor, bgColor), + traceColor = Color.combine(baseColor, bgColor), // find a contrasting color for border and text - contrastColor = tinycolor(traceColor).getBrightness() > 128 ? - '#000' : Color.background; - + contrastColor = tinycolor(traceColor).getBrightness() > 128 ? + '#000' : Color.background - if(d.name && d.zLabelVal === undefined) { + if (d.name && d.zLabelVal === undefined) { // strip out our pseudo-html elements from d.name (if it exists at all) - name = svgTextUtils.plainText(d.name || ''); + name = svgTextUtils.plainText(d.name || '') - if(name.length > 15) name = name.substr(0, 12) + '...'; - } + if (name.length > 15) name = name.substr(0, 12) + '...' + } // used by other modules (initially just ternary) that // manage their own hoverinfo independent of cleanPoint // the rest of this will still apply, so such modules // can still put things in (x|y|z)Label, text, and name // and hoverinfo will still determine their visibility - if(d.extraText !== undefined) text += d.extraText; + if (d.extraText !== undefined) text += d.extraText - if(d.zLabel !== undefined) { - if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; - if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; - text += (text ? 'z: ' : '') + d.zLabel; - } - else if(showCommonLabel && d[hovermode + 'Label'] === t0) { - text = d[(hovermode === 'x' ? 'y' : 'x') + 'Label'] || ''; - } - else if(d.xLabel === undefined) { - if(d.yLabel !== undefined) text = d.yLabel; - } - else if(d.yLabel === undefined) text = d.xLabel; - else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; + if (d.zLabel !== undefined) { + if (d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
' + if (d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
' + text += (text ? 'z: ' : '') + d.zLabel + } else if (showCommonLabel && d[hovermode + 'Label'] === t0) { + text = d[(hovermode === 'x' ? 'y' : 'x') + 'Label'] || '' + } else if (d.xLabel === undefined) { + if (d.yLabel !== undefined) text = d.yLabel + } else if (d.yLabel === undefined) text = d.xLabel + else text = '(' + d.xLabel + ', ' + d.yLabel + ')' - if(d.text && !Array.isArray(d.text)) text += (text ? '
' : '') + d.text; + if (d.text && !Array.isArray(d.text)) text += (text ? '
' : '') + d.text // if 'text' is empty at this point, // put 'name' in main label and don't show secondary label - if(text === '') { + if (text === '') { // if 'name' is also empty, remove entire label - if(name === '') g.remove(); - text = name; - } + if (name === '') g.remove() + text = name + } // main label - var tx = g.select('text.nums') + var tx = g.select('text.nums') .style('fill', contrastColor) .call(Drawing.setPosition, 0, 0) .text(text) .attr('data-notex', 1) - .call(svgTextUtils.convertToTspans); - tx.selectAll('tspan.line') - .call(Drawing.setPosition, 0, 0); + .call(svgTextUtils.convertToTspans) + tx.selectAll('tspan.line') + .call(Drawing.setPosition, 0, 0) - var tx2 = g.select('text.name'), - tx2width = 0; + var tx2 = g.select('text.name'), + tx2width = 0 // secondary label for non-empty 'name' - if(name && name !== text) { - tx2.style('fill', traceColor) + if (name && name !== text) { + tx2.style('fill', traceColor) .text(name) .call(Drawing.setPosition, 0, 0) .attr('data-notex', 1) - .call(svgTextUtils.convertToTspans); - tx2.selectAll('tspan.line') - .call(Drawing.setPosition, 0, 0); - tx2width = tx2.node().getBoundingClientRect().width + 2 * HOVERTEXTPAD; - } - else { - tx2.remove(); - g.select('rect').remove(); - } + .call(svgTextUtils.convertToTspans) + tx2.selectAll('tspan.line') + .call(Drawing.setPosition, 0, 0) + tx2width = tx2.node().getBoundingClientRect().width + 2 * HOVERTEXTPAD + } else { + tx2.remove() + g.select('rect').remove() + } - g.select('path') + g.select('path') .style({ - fill: traceColor, - stroke: contrastColor - }); - var tbb = tx.node().getBoundingClientRect(), - htx = d.xa._offset + (d.x0 + d.x1) / 2, - hty = d.ya._offset + (d.y0 + d.y1) / 2, - dx = Math.abs(d.x1 - d.x0), - dy = Math.abs(d.y1 - d.y0), - txTotalWidth = tbb.width + HOVERARROWSIZE + HOVERTEXTPAD + tx2width, - anchorStartOK, - anchorEndOK; - - d.ty0 = outerTop - tbb.top; - d.bx = tbb.width + 2 * HOVERTEXTPAD; - d.by = tbb.height + 2 * HOVERTEXTPAD; - d.anchor = 'start'; - d.txwidth = tbb.width; - d.tx2width = tx2width; - d.offset = 0; - - if(rotateLabels) { - d.pos = htx; - anchorStartOK = hty + dy / 2 + txTotalWidth <= outerHeight; - anchorEndOK = hty - dy / 2 - txTotalWidth >= 0; - if((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) { - hty -= dy / 2; - d.anchor = 'end'; - } else if(anchorStartOK) { - hty += dy / 2; - d.anchor = 'start'; - } else d.anchor = 'middle'; - } - else { - d.pos = hty; - anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth; - anchorEndOK = htx - dx / 2 - txTotalWidth >= 0; - if((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) { - htx -= dx / 2; - d.anchor = 'end'; - } else if(anchorStartOK) { - htx += dx / 2; - d.anchor = 'start'; - } else d.anchor = 'middle'; - } + fill: traceColor, + stroke: contrastColor + }) + var tbb = tx.node().getBoundingClientRect(), + htx = d.xa._offset + (d.x0 + d.x1) / 2, + hty = d.ya._offset + (d.y0 + d.y1) / 2, + dx = Math.abs(d.x1 - d.x0), + dy = Math.abs(d.y1 - d.y0), + txTotalWidth = tbb.width + HOVERARROWSIZE + HOVERTEXTPAD + tx2width, + anchorStartOK, + anchorEndOK + + d.ty0 = outerTop - tbb.top + d.bx = tbb.width + 2 * HOVERTEXTPAD + d.by = tbb.height + 2 * HOVERTEXTPAD + d.anchor = 'start' + d.txwidth = tbb.width + d.tx2width = tx2width + d.offset = 0 + + if (rotateLabels) { + d.pos = htx + anchorStartOK = hty + dy / 2 + txTotalWidth <= outerHeight + anchorEndOK = hty - dy / 2 - txTotalWidth >= 0 + if ((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) { + hty -= dy / 2 + d.anchor = 'end' + } else if (anchorStartOK) { + hty += dy / 2 + d.anchor = 'start' + } else d.anchor = 'middle' + } else { + d.pos = hty + anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth + anchorEndOK = htx - dx / 2 - txTotalWidth >= 0 + if ((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) { + htx -= dx / 2 + d.anchor = 'end' + } else if (anchorStartOK) { + htx += dx / 2 + d.anchor = 'start' + } else d.anchor = 'middle' + } - tx.attr('text-anchor', d.anchor); - if(tx2width) tx2.attr('text-anchor', d.anchor); - g.attr('transform', 'translate(' + htx + ',' + hty + ')' + - (rotateLabels ? 'rotate(' + YANGLE + ')' : '')); - }); + tx.attr('text-anchor', d.anchor) + if (tx2width) tx2.attr('text-anchor', d.anchor) + g.attr('transform', 'translate(' + htx + ',' + hty + ')' + + (rotateLabels ? 'rotate(' + YANGLE + ')' : '')) + }) - return hoverLabels; + return hoverLabels } // Make groups of touching points, and within each group @@ -1094,187 +1072,186 @@ function createHoverText(hoverData, opts) { // know what happens if the group spans all the way from one edge to // the other, though it hardly matters - there's just too much // information then. -function hoverAvoidOverlaps(hoverData, ax) { - var nummoves = 0, +function hoverAvoidOverlaps (hoverData, ax) { + var nummoves = 0, // make groups of touching points - pointgroups = hoverData - .map(function(d, i) { - var axis = d[ax]; - return [{ - i: i, - dp: 0, - pos: d.pos, - posref: d.posref, - size: d.by * (axis._id.charAt(0) === 'x' ? YFACTOR : 1) / 2, - pmin: axis._offset, - pmax: axis._offset + axis._length - }]; + pointgroups = hoverData + .map(function (d, i) { + var axis = d[ax] + return [{ + i: i, + dp: 0, + pos: d.pos, + posref: d.posref, + size: d.by * (axis._id.charAt(0) === 'x' ? YFACTOR : 1) / 2, + pmin: axis._offset, + pmax: axis._offset + axis._length + }] }) - .sort(function(a, b) { return a[0].posref - b[0].posref; }), - donepositioning, - topOverlap, - bottomOverlap, - i, j, - pti, - sumdp; - - function constrainGroup(grp) { - var minPt = grp[0], - maxPt = grp[grp.length - 1]; + .sort(function (a, b) { return a[0].posref - b[0].posref }), + donepositioning, + topOverlap, + bottomOverlap, + i, j, + pti, + sumdp + + function constrainGroup (grp) { + var minPt = grp[0], + maxPt = grp[grp.length - 1] // overlap with the top - positive vals are overlaps - topOverlap = minPt.pmin - minPt.pos - minPt.dp + minPt.size; + topOverlap = minPt.pmin - minPt.pos - minPt.dp + minPt.size // overlap with the bottom - positive vals are overlaps - bottomOverlap = maxPt.pos + maxPt.dp + maxPt.size - minPt.pmax; + bottomOverlap = maxPt.pos + maxPt.dp + maxPt.size - minPt.pmax // check for min overlap first, so that we always // see the largest labels // allow for .01px overlap, so we don't get an // infinite loop from rounding errors - if(topOverlap > 0.01) { - for(j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap; - donepositioning = false; - } - if(bottomOverlap < 0.01) return; - if(topOverlap < -0.01) { + if (topOverlap > 0.01) { + for (j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap + donepositioning = false + } + if (bottomOverlap < 0.01) return + if (topOverlap < -0.01) { // make sure we're not pushing back and forth - for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; - donepositioning = false; - } - if(!donepositioning) return; + for (j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap + donepositioning = false + } + if (!donepositioning) return // no room to fix positioning, delete off-screen points // first see how many points we need to delete - var deleteCount = 0; - for(i = 0; i < grp.length; i++) { - pti = grp[i]; - if(pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++; - } + var deleteCount = 0 + for (i = 0; i < grp.length; i++) { + pti = grp[i] + if (pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++ + } // start by deleting points whose data is off screen - for(i = grp.length - 1; i >= 0; i--) { - if(deleteCount <= 0) break; - pti = grp[i]; + for (i = grp.length - 1; i >= 0; i--) { + if (deleteCount <= 0) break + pti = grp[i] // pos has already been constrained to [pmin,pmax] // so look for points close to that to delete - if(pti.pos > minPt.pmax - 1) { - pti.del = true; - deleteCount--; - } - } - for(i = 0; i < grp.length; i++) { - if(deleteCount <= 0) break; - pti = grp[i]; + if (pti.pos > minPt.pmax - 1) { + pti.del = true + deleteCount-- + } + } + for (i = 0; i < grp.length; i++) { + if (deleteCount <= 0) break + pti = grp[i] // pos has already been constrained to [pmin,pmax] // so look for points close to that to delete - if(pti.pos < minPt.pmin + 1) { - pti.del = true; - deleteCount--; + if (pti.pos < minPt.pmin + 1) { + pti.del = true + deleteCount-- // shift the whole group minus into this new space - bottomOverlap = pti.size * 2; - for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; - } - } + bottomOverlap = pti.size * 2 + for (j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap + } + } // then delete points that go off the bottom - for(i = grp.length - 1; i >= 0; i--) { - if(deleteCount <= 0) break; - pti = grp[i]; - if(pti.pos + pti.dp + pti.size > minPt.pmax) { - pti.del = true; - deleteCount--; - } - } + for (i = grp.length - 1; i >= 0; i--) { + if (deleteCount <= 0) break + pti = grp[i] + if (pti.pos + pti.dp + pti.size > minPt.pmax) { + pti.del = true + deleteCount-- + } } + } // loop through groups, combining them if they overlap, // until nothing moves - while(!donepositioning && nummoves <= hoverData.length) { + while (!donepositioning && nummoves <= hoverData.length) { // to avoid infinite loops, don't move more times // than there are traces - nummoves++; + nummoves++ // assume nothing will move in this iteration, // reverse this if it does - donepositioning = true; - i = 0; - while(i < pointgroups.length - 1) { + donepositioning = true + i = 0 + while (i < pointgroups.length - 1) { // the higher (g0) and lower (g1) point group - var g0 = pointgroups[i], - g1 = pointgroups[i + 1], + var g0 = pointgroups[i], + g1 = pointgroups[i + 1], // the lowest point in the higher group (p0) // the highest point in the lower group (p1) - p0 = g0[g0.length - 1], - p1 = g1[0]; - topOverlap = p0.pos + p0.dp + p0.size - p1.pos - p1.dp + p1.size; + p0 = g0[g0.length - 1], + p1 = g1[0] + topOverlap = p0.pos + p0.dp + p0.size - p1.pos - p1.dp + p1.size // Only group points that lie on the same axes - if(topOverlap > 0.01 && (p0.pmin === p1.pmin) && (p0.pmax === p1.pmax)) { + if (topOverlap > 0.01 && (p0.pmin === p1.pmin) && (p0.pmax === p1.pmax)) { // push the new point(s) added to this group out of the way - for(j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap; + for (j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap // add them to the group - g0.push.apply(g0, g1); - pointgroups.splice(i + 1, 1); + g0.push.apply(g0, g1) + pointgroups.splice(i + 1, 1) // adjust for minimum average movement - sumdp = 0; - for(j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp; - bottomOverlap = sumdp / g0.length; - for(j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap; - donepositioning = false; - } - else i++; - } + sumdp = 0 + for (j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp + bottomOverlap = sumdp / g0.length + for (j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap + donepositioning = false + } else i++ + } // check if we're going off the plot on either side and fix - pointgroups.forEach(constrainGroup); - } + pointgroups.forEach(constrainGroup) + } // now put these offsets into hoverData - for(i = pointgroups.length - 1; i >= 0; i--) { - var grp = pointgroups[i]; - for(j = grp.length - 1; j >= 0; j--) { - var pt = grp[j], - hoverPt = hoverData[pt.i]; - hoverPt.offset = pt.dp; - hoverPt.del = pt.del; - } + for (i = pointgroups.length - 1; i >= 0; i--) { + var grp = pointgroups[i] + for (j = grp.length - 1; j >= 0; j--) { + var pt = grp[j], + hoverPt = hoverData[pt.i] + hoverPt.offset = pt.dp + hoverPt.del = pt.del } + } } -function alignHoverText(hoverLabels, rotateLabels) { +function alignHoverText (hoverLabels, rotateLabels) { // finally set the text positioning relative to the data and draw the // box around it - hoverLabels.each(function(d) { - var g = d3.select(this); - if(d.del) { - g.remove(); - return; - } - var horzSign = d.anchor === 'end' ? -1 : 1, - tx = g.select('text.nums'), - alignShift = {start: 1, end: -1, middle: 0}[d.anchor], - txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD), - tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD), - offsetX = 0, - offsetY = d.offset; - if(d.anchor === 'middle') { - txx -= d.tx2width / 2; - tx2x -= d.tx2width / 2; - } - if(rotateLabels) { - offsetY *= -YSHIFTY; - offsetX = d.offset * YSHIFTX; - } + hoverLabels.each(function (d) { + var g = d3.select(this) + if (d.del) { + g.remove() + return + } + var horzSign = d.anchor === 'end' ? -1 : 1, + tx = g.select('text.nums'), + alignShift = {start: 1, end: -1, middle: 0}[d.anchor], + txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD), + tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD), + offsetX = 0, + offsetY = d.offset + if (d.anchor === 'middle') { + txx -= d.tx2width / 2 + tx2x -= d.tx2width / 2 + } + if (rotateLabels) { + offsetY *= -YSHIFTY + offsetX = d.offset * YSHIFTX + } - g.select('path').attr('d', d.anchor === 'middle' ? + g.select('path').attr('d', d.anchor === 'middle' ? // middle aligned: rect centered on data ('M-' + (d.bx / 2) + ',-' + (d.by / 2) + 'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') : // left or right aligned: side rect with arrow to data @@ -1284,64 +1261,62 @@ function alignHoverText(hoverLabels, rotateLabels) { 'v-' + d.by + 'H' + (horzSign * HOVERARROWSIZE + offsetX) + 'V' + (offsetY - HOVERARROWSIZE) + - 'Z')); + 'Z')) - tx.call(Drawing.setPosition, + tx.call(Drawing.setPosition, txx + offsetX, offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD) .selectAll('tspan.line') .attr({ - x: tx.attr('x'), - y: tx.attr('y') - }); + x: tx.attr('x'), + y: tx.attr('y') + }) - if(d.tx2width) { - g.select('text.name, text.name tspan.line') + if (d.tx2width) { + g.select('text.name, text.name tspan.line') .call(Drawing.setPosition, tx2x + alignShift * HOVERTEXTPAD + offsetX, - offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD); - g.select('rect') + offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD) + g.select('rect') .call(Drawing.setRect, tx2x + (alignShift - 1) * d.tx2width / 2 + offsetX, offsetY - d.by / 2 - 1, - d.tx2width, d.by + 2); - } - }); + d.tx2width, d.by + 2) + } + }) } -function hoverChanged(gd, evt, oldhoverdata) { +function hoverChanged (gd, evt, oldhoverdata) { // don't emit any events if nothing changed or // if fx.hover was called manually - if(!evt.target) return false; - if(!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true; + if (!evt.target) return false + if (!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true - for(var i = oldhoverdata.length - 1; i >= 0; i--) { - var oldPt = oldhoverdata[i], - newPt = gd._hoverdata[i]; - if(oldPt.curveNumber !== newPt.curveNumber || + for (var i = oldhoverdata.length - 1; i >= 0; i--) { + var oldPt = oldhoverdata[i], + newPt = gd._hoverdata[i] + if (oldPt.curveNumber !== newPt.curveNumber || String(oldPt.pointNumber) !== String(newPt.pointNumber)) { - return true; - } + return true } - return false; + } + return false } // on click -fx.click = function(gd, evt) { - var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata); +fx.click = function (gd, evt) { + var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata) - function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata}); } + function emitClick () { gd.emit('plotly_click', {points: gd._hoverdata}) } - if(gd._hoverdata && evt && evt.target) { - if(annotationsDone && annotationsDone.then) { - annotationsDone.then(emitClick); - } - else emitClick(); + if (gd._hoverdata && evt && evt.target) { + if (annotationsDone && annotationsDone.then) { + annotationsDone.then(emitClick) + } else emitClick() // why do we get a double event without this??? - if(evt.stopImmediatePropagation) evt.stopImmediatePropagation(); - } -}; - + if (evt.stopImmediatePropagation) evt.stopImmediatePropagation() + } +} // for bar charts and others with finite-size objects: you must be inside // it to see its hover info, so distance is infinite outside. @@ -1351,9 +1326,9 @@ fx.click = function(gd, evt) { // note that for closest mode, two inbox's will get added in quadrature // args are (signed) difference from the two opposite edges // count one edge as in, so that over continuous ranges you never get a gap -fx.inbox = function(v0, v1) { - if(v0 * v1 < 0 || v0 === 0) { - return constants.MAXDIST * (0.6 - 0.3 / Math.max(3, Math.abs(v0 - v1))); - } - return Infinity; -}; +fx.inbox = function (v0, v1) { + if (v0 * v1 < 0 || v0 === 0) { + return constants.MAXDIST * (0.6 - 0.3 / Math.max(3, Math.abs(v0 - v1))) + } + return Infinity +} diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index d8a78abd677..59c944a3bd1 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -6,93 +6,92 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var Lib = require('../../lib') +var Plots = require('../plots') +var Axes = require('./axes') +var constants = require('./constants') -var d3 = require('d3'); -var Lib = require('../../lib'); -var Plots = require('../plots'); -var Axes = require('./axes'); -var constants = require('./constants'); +exports.name = 'cartesian' -exports.name = 'cartesian'; +exports.attr = ['xaxis', 'yaxis'] -exports.attr = ['xaxis', 'yaxis']; +exports.idRoot = ['x', 'y'] -exports.idRoot = ['x', 'y']; +exports.idRegex = constants.idRegex -exports.idRegex = constants.idRegex; +exports.attrRegex = constants.attrRegex -exports.attrRegex = constants.attrRegex; +exports.attributes = require('./attributes') -exports.attributes = require('./attributes'); +exports.layoutAttributes = require('./layout_attributes') -exports.layoutAttributes = require('./layout_attributes'); +exports.transitionAxes = require('./transition_axes') -exports.transitionAxes = require('./transition_axes'); - -exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { - var fullLayout = gd._fullLayout, - subplots = Plots.getSubplotIds(fullLayout, 'cartesian'), - calcdata = gd.calcdata, - i; +exports.plot = function (gd, traces, transitionOpts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout, + subplots = Plots.getSubplotIds(fullLayout, 'cartesian'), + calcdata = gd.calcdata, + i // If traces is not provided, then it's a complete replot and missing // traces are removed - if(!Array.isArray(traces)) { - traces = []; + if (!Array.isArray(traces)) { + traces = [] - for(i = 0; i < calcdata.length; i++) { - traces.push(i); - } + for (i = 0; i < calcdata.length; i++) { + traces.push(i) } + } - for(i = 0; i < subplots.length; i++) { - var subplot = subplots[i], - subplotInfo = fullLayout._plots[subplot]; + for (i = 0; i < subplots.length; i++) { + var subplot = subplots[i], + subplotInfo = fullLayout._plots[subplot] // Get all calcdata for this subplot: - var cdSubplot = []; - var pcd; + var cdSubplot = [] + var pcd - for(var j = 0; j < calcdata.length; j++) { - var cd = calcdata[j], - trace = cd[0].trace; + for (var j = 0; j < calcdata.length; j++) { + var cd = calcdata[j], + trace = cd[0].trace // Skip trace if whitelist provided and it's not whitelisted: // if (Array.isArray(traces) && traces.indexOf(i) === -1) continue; - if(trace.xaxis + trace.yaxis === subplot) { + if (trace.xaxis + trace.yaxis === subplot) { // If this trace is specifically requested, add it to the list: - if(traces.indexOf(trace.index) !== -1) { + if (traces.indexOf(trace.index) !== -1) { // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill // is outdated. So this retroactively adds the previous trace if the // traces are interdependent. - if( + if ( pcd && pcd[0].trace.xaxis + pcd[0].trace.yaxis === subplot && ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && cdSubplot.indexOf(pcd) === -1 ) { - cdSubplot.push(pcd); - } + cdSubplot.push(pcd) + } - cdSubplot.push(cd); - } + cdSubplot.push(cd) + } // Track the previous trace on this subplot for the retroactive-add step // above: - pcd = cd; - } - } - - plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback); + pcd = cd + } } -}; -function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) { - var fullLayout = gd._fullLayout, - modules = fullLayout._modules; + plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback) + } +} + +function plotOne (gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout, + modules = fullLayout._modules // remove old traces, then redraw everything // @@ -100,159 +99,159 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback // to update instead of fully removing and redrawing every time. The // remaining plot traces should also be able to do this. Once implemented, // we won't need this - which should sometimes be a big speedup. - if(plotinfo.plot) { - plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove(); - } + if (plotinfo.plot) { + plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove() + } // plot all traces for each module at once - for(var j = 0; j < modules.length; j++) { - var _module = modules[j]; + for (var j = 0; j < modules.length; j++) { + var _module = modules[j] // skip over non-cartesian trace modules - if(_module.basePlotModule.name !== 'cartesian') continue; + if (_module.basePlotModule.name !== 'cartesian') continue // plot all traces of this type on this subplot at once - var cdModule = []; - for(var k = 0; k < cdSubplot.length; k++) { - var cd = cdSubplot[k], - trace = cd[0].trace; - - if((trace._module === _module) && (trace.visible === true)) { - cdModule.push(cd); - } - } - - _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback); + var cdModule = [] + for (var k = 0; k < cdSubplot.length; k++) { + var cd = cdSubplot[k], + trace = cd[0].trace + + if ((trace._module === _module) && (trace.visible === true)) { + cdModule.push(cd) + } } + + _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback) + } } -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldModules = oldFullLayout._modules || [], - newModules = newFullLayout._modules || []; +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldModules = oldFullLayout._modules || [], + newModules = newFullLayout._modules || [] - var hadScatter, hasScatter, i; + var hadScatter, hasScatter, i - for(i = 0; i < oldModules.length; i++) { - if(oldModules[i].name === 'scatter') { - hadScatter = true; - break; - } + for (i = 0; i < oldModules.length; i++) { + if (oldModules[i].name === 'scatter') { + hadScatter = true + break } + } - for(i = 0; i < newModules.length; i++) { - if(newModules[i].name === 'scatter') { - hasScatter = true; - break; - } + for (i = 0; i < newModules.length; i++) { + if (newModules[i].name === 'scatter') { + hasScatter = true + break } + } - if(hadScatter && !hasScatter) { - var oldPlots = oldFullLayout._plots, - ids = Object.keys(oldPlots || {}); + if (hadScatter && !hasScatter) { + var oldPlots = oldFullLayout._plots, + ids = Object.keys(oldPlots || {}) - for(i = 0; i < ids.length; i++) { - var subplotInfo = oldPlots[ids[i]]; + for (i = 0; i < ids.length; i++) { + var subplotInfo = oldPlots[ids[i]] - if(subplotInfo.plot) { - subplotInfo.plot.select('g.scatterlayer') + if (subplotInfo.plot) { + subplotInfo.plot.select('g.scatterlayer') .selectAll('g.trace') - .remove(); - } - } + .remove() + } } + } - var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian')); - var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian')); + var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian')) + var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian')) - if(hadCartesian && !hasCartesian) { - var subplotLayers = oldFullLayout._cartesianlayer.selectAll('.subplot'); + if (hadCartesian && !hasCartesian) { + var subplotLayers = oldFullLayout._cartesianlayer.selectAll('.subplot') - subplotLayers.call(purgeSubplotLayers, oldFullLayout); - oldFullLayout._defs.selectAll('.axesclip').remove(); - } -}; + subplotLayers.call(purgeSubplotLayers, oldFullLayout) + oldFullLayout._defs.selectAll('.axesclip').remove() + } +} -exports.drawFramework = function(gd) { - var fullLayout = gd._fullLayout, - subplotData = makeSubplotData(gd); +exports.drawFramework = function (gd) { + var fullLayout = gd._fullLayout, + subplotData = makeSubplotData(gd) - var subplotLayers = fullLayout._cartesianlayer.selectAll('.subplot') - .data(subplotData, Lib.identity); + var subplotLayers = fullLayout._cartesianlayer.selectAll('.subplot') + .data(subplotData, Lib.identity) - subplotLayers.enter().append('g') - .attr('class', function(name) { return 'subplot ' + name; }); + subplotLayers.enter().append('g') + .attr('class', function (name) { return 'subplot ' + name }) - subplotLayers.order(); + subplotLayers.order() - subplotLayers.exit() - .call(purgeSubplotLayers, fullLayout); + subplotLayers.exit() + .call(purgeSubplotLayers, fullLayout) - subplotLayers.each(function(name) { - var plotinfo = fullLayout._plots[name]; + subplotLayers.each(function (name) { + var plotinfo = fullLayout._plots[name] // keep ref to plot group - plotinfo.plotgroup = d3.select(this); + plotinfo.plotgroup = d3.select(this) // initialize list of overlay subplots - plotinfo.overlays = []; + plotinfo.overlays = [] - makeSubplotLayer(plotinfo); + makeSubplotLayer(plotinfo) // fill in list of overlay subplots - if(plotinfo.mainplot) { - var mainplot = fullLayout._plots[plotinfo.mainplot]; - mainplot.overlays.push(plotinfo); - } + if (plotinfo.mainplot) { + var mainplot = fullLayout._plots[plotinfo.mainplot] + mainplot.overlays.push(plotinfo) + } // make separate drag layers for each subplot, // but append them to paper rather than the plot groups, // so they end up on top of the rest - plotinfo.draglayer = joinLayer(fullLayout._draggers, 'g', name); - }); -}; + plotinfo.draglayer = joinLayer(fullLayout._draggers, 'g', name) + }) +} -exports.rangePlot = function(gd, plotinfo, cdSubplot) { - makeSubplotLayer(plotinfo); - plotOne(gd, plotinfo, cdSubplot); - Plots.style(gd); -}; +exports.rangePlot = function (gd, plotinfo, cdSubplot) { + makeSubplotLayer(plotinfo) + plotOne(gd, plotinfo, cdSubplot) + Plots.style(gd) +} -function makeSubplotData(gd) { - var fullLayout = gd._fullLayout, - subplots = Object.keys(fullLayout._plots); +function makeSubplotData (gd) { + var fullLayout = gd._fullLayout, + subplots = Object.keys(fullLayout._plots) - var subplotData = [], - overlays = []; + var subplotData = [], + overlays = [] - for(var i = 0; i < subplots.length; i++) { - var subplot = subplots[i], - plotinfo = fullLayout._plots[subplot]; + for (var i = 0; i < subplots.length; i++) { + var subplot = subplots[i], + plotinfo = fullLayout._plots[subplot] - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis; + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis // is this subplot overlaid on another? // ax.overlaying is the id of another axis of the same // dimension that this one overlays to be an overlaid subplot, // the main plot must exist make sure we're not trying to // overlay on an axis that's already overlaying another - var xa2 = Axes.getFromId(gd, xa.overlaying) || xa; - if(xa2 !== xa && xa2.overlaying) { - xa2 = xa; - xa.overlaying = false; - } + var xa2 = Axes.getFromId(gd, xa.overlaying) || xa + if (xa2 !== xa && xa2.overlaying) { + xa2 = xa + xa.overlaying = false + } - var ya2 = Axes.getFromId(gd, ya.overlaying) || ya; - if(ya2 !== ya && ya2.overlaying) { - ya2 = ya; - ya.overlaying = false; - } + var ya2 = Axes.getFromId(gd, ya.overlaying) || ya + if (ya2 !== ya && ya2.overlaying) { + ya2 = ya + ya.overlaying = false + } - var mainplot = xa2._id + ya2._id; - if(mainplot !== subplot && subplots.indexOf(mainplot) !== -1) { - plotinfo.mainplot = mainplot; - plotinfo.mainplotinfo = fullLayout._plots[mainplot]; - overlays.push(subplot); + var mainplot = xa2._id + ya2._id + if (mainplot !== subplot && subplots.indexOf(mainplot) !== -1) { + plotinfo.mainplot = mainplot + plotinfo.mainplotinfo = fullLayout._plots[mainplot] + overlays.push(subplot) // for now force overlays to overlay completely... so they // can drag together correctly and share backgrounds. @@ -260,23 +259,22 @@ function makeSubplotData(gd) { // tick/line domain or something, so they can still share // the (possibly larger) dragger and background but don't // have to both be drawn over that whole domain - xa.domain = xa2.domain.slice(); - ya.domain = ya2.domain.slice(); - } - else { - subplotData.push(subplot); - } + xa.domain = xa2.domain.slice() + ya.domain = ya2.domain.slice() + } else { + subplotData.push(subplot) } + } // main subplots before overlays - subplotData = subplotData.concat(overlays); + subplotData = subplotData.concat(overlays) - return subplotData; + return subplotData } -function makeSubplotLayer(plotinfo) { - var plotgroup = plotinfo.plotgroup, - id = plotinfo.id; +function makeSubplotLayer (plotinfo) { + var plotgroup = plotinfo.plotgroup, + id = plotinfo.id // Layers to keep plot types in the right order. // from back to front: @@ -285,91 +283,90 @@ function makeSubplotLayer(plotinfo) { // 3. errorbars for bars and scatter // 4. scatter // 5. box plots - function joinPlotLayers(parent) { - joinLayer(parent, 'g', 'imagelayer'); - joinLayer(parent, 'g', 'maplayer'); - joinLayer(parent, 'g', 'barlayer'); - joinLayer(parent, 'g', 'boxlayer'); - joinLayer(parent, 'g', 'scatterlayer'); - } + function joinPlotLayers (parent) { + joinLayer(parent, 'g', 'imagelayer') + joinLayer(parent, 'g', 'maplayer') + joinLayer(parent, 'g', 'barlayer') + joinLayer(parent, 'g', 'boxlayer') + joinLayer(parent, 'g', 'scatterlayer') + } - if(!plotinfo.mainplot) { - plotinfo.bg = joinLayer(plotgroup, 'rect', 'bg'); - plotinfo.bg.style('stroke-width', 0); + if (!plotinfo.mainplot) { + plotinfo.bg = joinLayer(plotgroup, 'rect', 'bg') + plotinfo.bg.style('stroke-width', 0) - var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot'); - plotinfo.shapelayer = joinLayer(backLayer, 'g', 'shapelayer'); - plotinfo.imagelayer = joinLayer(backLayer, 'g', 'imagelayer'); + var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot') + plotinfo.shapelayer = joinLayer(backLayer, 'g', 'shapelayer') + plotinfo.imagelayer = joinLayer(backLayer, 'g', 'imagelayer') - plotinfo.gridlayer = joinLayer(plotgroup, 'g', 'gridlayer'); - plotinfo.overgrid = joinLayer(plotgroup, 'g', 'overgrid'); + plotinfo.gridlayer = joinLayer(plotgroup, 'g', 'gridlayer') + plotinfo.overgrid = joinLayer(plotgroup, 'g', 'overgrid') - plotinfo.zerolinelayer = joinLayer(plotgroup, 'g', 'zerolinelayer'); - plotinfo.overzero = joinLayer(plotgroup, 'g', 'overzero'); + plotinfo.zerolinelayer = joinLayer(plotgroup, 'g', 'zerolinelayer') + plotinfo.overzero = joinLayer(plotgroup, 'g', 'overzero') - plotinfo.plot = joinLayer(plotgroup, 'g', 'plot'); - plotinfo.overplot = joinLayer(plotgroup, 'g', 'overplot'); + plotinfo.plot = joinLayer(plotgroup, 'g', 'plot') + plotinfo.overplot = joinLayer(plotgroup, 'g', 'overplot') - plotinfo.xlines = joinLayer(plotgroup, 'path', 'xlines'); - plotinfo.ylines = joinLayer(plotgroup, 'path', 'ylines'); - plotinfo.overlines = joinLayer(plotgroup, 'g', 'overlines'); + plotinfo.xlines = joinLayer(plotgroup, 'path', 'xlines') + plotinfo.ylines = joinLayer(plotgroup, 'path', 'ylines') + plotinfo.overlines = joinLayer(plotgroup, 'g', 'overlines') - plotinfo.xaxislayer = joinLayer(plotgroup, 'g', 'xaxislayer'); - plotinfo.yaxislayer = joinLayer(plotgroup, 'g', 'yaxislayer'); - plotinfo.overaxes = joinLayer(plotgroup, 'g', 'overaxes'); - } - else { - var mainplotinfo = plotinfo.mainplotinfo; + plotinfo.xaxislayer = joinLayer(plotgroup, 'g', 'xaxislayer') + plotinfo.yaxislayer = joinLayer(plotgroup, 'g', 'yaxislayer') + plotinfo.overaxes = joinLayer(plotgroup, 'g', 'overaxes') + } else { + var mainplotinfo = plotinfo.mainplotinfo // now make the components of overlaid subplots // overlays don't have backgrounds, and append all // their other components to the corresponding // extra groups of their main plots. - plotinfo.gridlayer = joinLayer(mainplotinfo.overgrid, 'g', id); - plotinfo.zerolinelayer = joinLayer(mainplotinfo.overzero, 'g', id); + plotinfo.gridlayer = joinLayer(mainplotinfo.overgrid, 'g', id) + plotinfo.zerolinelayer = joinLayer(mainplotinfo.overzero, 'g', id) - plotinfo.plot = joinLayer(mainplotinfo.overplot, 'g', id); - plotinfo.xlines = joinLayer(mainplotinfo.overlines, 'path', id); - plotinfo.ylines = joinLayer(mainplotinfo.overlines, 'path', id); - plotinfo.xaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id); - plotinfo.yaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id); - } + plotinfo.plot = joinLayer(mainplotinfo.overplot, 'g', id) + plotinfo.xlines = joinLayer(mainplotinfo.overlines, 'path', id) + plotinfo.ylines = joinLayer(mainplotinfo.overlines, 'path', id) + plotinfo.xaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id) + plotinfo.yaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id) + } // common attributes for all subplots, overlays or not - plotinfo.plot.call(joinPlotLayers); + plotinfo.plot.call(joinPlotLayers) - plotinfo.xlines + plotinfo.xlines .style('fill', 'none') - .classed('crisp', true); + .classed('crisp', true) - plotinfo.ylines + plotinfo.ylines .style('fill', 'none') - .classed('crisp', true); + .classed('crisp', true) } -function purgeSubplotLayers(layers, fullLayout) { - if(!layers) return; +function purgeSubplotLayers (layers, fullLayout) { + if (!layers) return - layers.each(function(subplot) { - var plotgroup = d3.select(this), - clipId = 'clip' + fullLayout._uid + subplot + 'plot'; + layers.each(function (subplot) { + var plotgroup = d3.select(this), + clipId = 'clip' + fullLayout._uid + subplot + 'plot' - plotgroup.remove(); - fullLayout._draggers.selectAll('g.' + subplot).remove(); - fullLayout._defs.select('#' + clipId).remove(); + plotgroup.remove() + fullLayout._draggers.selectAll('g.' + subplot).remove() + fullLayout._defs.select('#' + clipId).remove() // do not remove individual axis s here // as other subplots may need them - }); + }) } -function joinLayer(parent, nodeType, className) { - var layer = parent.selectAll('.' + className) - .data([0]); +function joinLayer (parent, nodeType, className) { + var layer = parent.selectAll('.' + className) + .data([0]) - layer.enter().append(nodeType) - .classed(className, true); + layer.enter().append(nodeType) + .classed(className, true) - return layer; + return layer } diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index b917c4cf8e1..2454a36d41a 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -6,527 +6,526 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var fontAttrs = require('../font_attributes'); -var colorAttrs = require('../../components/color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; - -var constants = require('./constants'); +var fontAttrs = require('../font_attributes') +var colorAttrs = require('../../components/color/attributes') +var extendFlat = require('../../lib/extend').extendFlat +var constants = require('./constants') module.exports = { - color: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: [ - 'Sets default for all colors associated with this axis', - 'all at once: line, font, tick, and grid colors.', - 'Grid color is lightened by blending this with the plot background', - 'Individual pieces can override this.' - ].join(' ') - }, - title: { - valType: 'string', - role: 'info', - description: 'Sets the title of this axis.' - }, - titlefont: extendFlat({}, fontAttrs, { - description: [ - 'Sets this axis\' title font.' - ].join(' ') - }), - type: { - valType: 'enumerated', + color: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: [ + 'Sets default for all colors associated with this axis', + 'all at once: line, font, tick, and grid colors.', + 'Grid color is lightened by blending this with the plot background', + 'Individual pieces can override this.' + ].join(' ') + }, + title: { + valType: 'string', + role: 'info', + description: 'Sets the title of this axis.' + }, + titlefont: extendFlat({}, fontAttrs, { + description: [ + 'Sets this axis\' title font.' + ].join(' ') + }), + type: { + valType: 'enumerated', // '-' means we haven't yet run autotype or couldn't find any data // it gets turned into linear in gd._fullLayout but not copied back // to gd.data like the others are. - values: ['-', 'linear', 'log', 'date', 'category'], - dflt: '-', - role: 'info', - description: [ - 'Sets the axis type.', - 'By default, plotly attempts to determined the axis type', - 'by looking into the data of the traces that referenced', - 'the axis in question.' - ].join(' ') - }, - autorange: { - valType: 'enumerated', - values: [true, false, 'reversed'], - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the range of this axis is', - 'computed in relation to the input data.', - 'See `rangemode` for more info.', - 'If `range` is provided, then `autorange` is set to *false*.' - ].join(' ') - }, - rangemode: { - valType: 'enumerated', - values: ['normal', 'tozero', 'nonnegative'], - dflt: 'normal', - role: 'style', - description: [ - 'If *normal*, the range is computed in relation to the extrema', - 'of the input data.', - 'If *tozero*`, the range extends to 0,', - 'regardless of the input data', - 'If *nonnegative*, the range is non-negative,', - 'regardless of the input data.' - ].join(' ') - }, - range: { - valType: 'info_array', - role: 'info', - items: [ + values: ['-', 'linear', 'log', 'date', 'category'], + dflt: '-', + role: 'info', + description: [ + 'Sets the axis type.', + 'By default, plotly attempts to determined the axis type', + 'by looking into the data of the traces that referenced', + 'the axis in question.' + ].join(' ') + }, + autorange: { + valType: 'enumerated', + values: [true, false, 'reversed'], + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the range of this axis is', + 'computed in relation to the input data.', + 'See `rangemode` for more info.', + 'If `range` is provided, then `autorange` is set to *false*.' + ].join(' ') + }, + rangemode: { + valType: 'enumerated', + values: ['normal', 'tozero', 'nonnegative'], + dflt: 'normal', + role: 'style', + description: [ + 'If *normal*, the range is computed in relation to the extrema', + 'of the input data.', + 'If *tozero*`, the range extends to 0,', + 'regardless of the input data', + 'If *nonnegative*, the range is non-negative,', + 'regardless of the input data.' + ].join(' ') + }, + range: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'any'}, {valType: 'any'} - ], - description: [ - 'Sets the range of this axis.', - 'If the axis `type` is *log*, then you must take the log of your', - 'desired range (e.g. to set the range from 1 to 100,', - 'set the range from 0 to 2).', - 'If the axis `type` is *date*, it should be date strings,', - 'like date data, though Date objects and unix milliseconds', - 'will be accepted and converted to strings.', - 'If the axis `type` is *category*, it should be numbers,', - 'using the scale where each category is assigned a serial', - 'number from zero in the order it appears.' - ].join(' ') - }, + ], + description: [ + 'Sets the range of this axis.', + 'If the axis `type` is *log*, then you must take the log of your', + 'desired range (e.g. to set the range from 1 to 100,', + 'set the range from 0 to 2).', + 'If the axis `type` is *date*, it should be date strings,', + 'like date data, though Date objects and unix milliseconds', + 'will be accepted and converted to strings.', + 'If the axis `type` is *category*, it should be numbers,', + 'using the scale where each category is assigned a serial', + 'number from zero in the order it appears.' + ].join(' ') + }, - fixedrange: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not this axis is zoom-able.', - 'If true, then zoom is disabled.' - ].join(' ') - }, + fixedrange: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not this axis is zoom-able.', + 'If true, then zoom is disabled.' + ].join(' ') + }, // ticks - tickmode: { - valType: 'enumerated', - values: ['auto', 'linear', 'array'], - role: 'info', - description: [ - 'Sets the tick mode for this axis.', - 'If *auto*, the number of ticks is set via `nticks`.', - 'If *linear*, the placement of the ticks is determined by', - 'a starting position `tick0` and a tick step `dtick`', - '(*linear* is the default value if `tick0` and `dtick` are provided).', - 'If *array*, the placement of the ticks is set via `tickvals`', - 'and the tick text is `ticktext`.', - '(*array* is the default value if `tickvals` is provided).' - ].join(' ') - }, - nticks: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Specifies the maximum number of ticks for the particular axis.', - 'The actual number of ticks will be chosen automatically to be', - 'less than or equal to `nticks`.', - 'Has an effect only if `tickmode` is set to *auto*.' - ].join(' ') - }, - tick0: { - valType: 'any', - role: 'style', - description: [ - 'Sets the placement of the first tick on this axis.', - 'Use with `dtick`.', - 'If the axis `type` is *log*, then you must take the log of your starting tick', - '(e.g. to set the starting tick to 100, set the `tick0` to 2)', - 'except when `dtick`=*L* (see `dtick` for more info).', - 'If the axis `type` is *date*, it should be a date string, like date data.', - 'If the axis `type` is *category*, it should be a number, using the scale where', - 'each category is assigned a serial number from zero in the order it appears.' - ].join(' ') - }, - dtick: { - valType: 'any', - role: 'style', - description: [ - 'Sets the step in-between ticks on this axis. Use with `tick0`.', - 'Must be a positive number, or special strings available to *log* and *date* axes.', - 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', - 'is the tick number. For example,', - 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', - 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', - 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', - '*log* has several special values; *L*, where `f` is a positive number,', - 'gives ticks linearly spaced in value (but not position).', - 'For example `tick0` = 0.1, `dtick` = *L0.5* will put ticks at 0.1, 0.6, 1.1, 1.6 etc.', - 'To show powers of 10 plus small digits between, use *D1* (all digits) or *D2* (only 2 and 5).', - '`tick0` is ignored for *D1* and *D2*.', - 'If the axis `type` is *date*, then you must convert the time to milliseconds.', - 'For example, to set the interval between ticks to one day,', - 'set `dtick` to 86400000.0.', - '*date* also has special values *M* gives ticks spaced by a number of months.', - '`n` must be a positive integer.', - 'To set ticks on the 15th of every third month, set `tick0` to *2000-01-15* and `dtick` to *M3*.', - 'To set ticks every 4 years, set `dtick` to *M48*' - ].join(' ') - }, - tickvals: { - valType: 'data_array', - description: [ - 'Sets the values at which ticks on this axis appear.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `ticktext`.' - ].join(' ') - }, - ticktext: { - valType: 'data_array', - description: [ - 'Sets the text displayed at the ticks position via `tickvals`.', - 'Only has an effect if `tickmode` is set to *array*.', - 'Used with `tickvals`.' - ].join(' ') - }, - ticks: { - valType: 'enumerated', - values: ['outside', 'inside', ''], - role: 'style', - description: [ - 'Determines whether ticks are drawn or not.', - 'If **, this axis\' ticks are not drawn.', - 'If *outside* (*inside*), this axis\' are drawn outside (inside)', - 'the axis lines.' - ].join(' ') - }, - mirror: { - valType: 'enumerated', - values: [true, 'ticks', false, 'all', 'allticks'], - dflt: false, - role: 'style', - description: [ - 'Determines if the axis lines or/and ticks are mirrored to', - 'the opposite side of the plotting area.', - 'If *true*, the axis lines are mirrored.', - 'If *ticks*, the axis lines and ticks are mirrored.', - 'If *false*, mirroring is disable.', - 'If *all*, axis lines are mirrored on all shared-axes subplots.', - 'If *allticks*, axis lines and ticks are mirrored', - 'on all shared-axes subplots.' - ].join(' ') - }, - ticklen: { - valType: 'number', - min: 0, - dflt: 5, - role: 'style', - description: 'Sets the tick length (in px).' - }, - tickwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the tick width (in px).' - }, - tickcolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the tick color.' - }, - showticklabels: { - valType: 'boolean', - dflt: true, - role: 'style', - description: 'Determines whether or not the tick labels are drawn.' - }, - tickfont: extendFlat({}, fontAttrs, { - description: 'Sets the tick font.' - }), - tickangle: { - valType: 'angle', - dflt: 'auto', - role: 'style', - description: [ - 'Sets the angle of the tick labels with respect to the horizontal.', - 'For example, a `tickangle` of -90 draws the tick labels', - 'vertically.' - ].join(' ') - }, - tickprefix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label prefix.' - }, - showtickprefix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all tick labels are displayed with a prefix.', - 'If *first*, only the first tick is displayed with a prefix.', - 'If *last*, only the last tick is displayed with a suffix.', - 'If *none*, tick prefixes are hidden.' - ].join(' ') - }, - ticksuffix: { - valType: 'string', - dflt: '', - role: 'style', - description: 'Sets a tick label suffix.' - }, - showticksuffix: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: 'Same as `showtickprefix` but for tick suffixes.' - }, - showexponent: { - valType: 'enumerated', - values: ['all', 'first', 'last', 'none'], - dflt: 'all', - role: 'style', - description: [ - 'If *all*, all exponents are shown besides their significands.', - 'If *first*, only the exponent of the first tick is shown.', - 'If *last*, only the exponent of the last tick is shown.', - 'If *none*, no exponents appear.' - ].join(' ') - }, - exponentformat: { - valType: 'enumerated', - values: ['none', 'e', 'E', 'power', 'SI', 'B'], - dflt: 'B', - role: 'style', - description: [ - 'Determines a formatting rule for the tick exponents.', - 'For example, consider the number 1,000,000,000.', - 'If *none*, it appears as 1,000,000,000.', - 'If *e*, 1e+9.', - 'If *E*, 1E+9.', - 'If *power*, 1x10^9 (with 9 in a super script).', - 'If *SI*, 1G.', - 'If *B*, 1B.' - ].join(' ') - }, - separatethousands: { - valType: 'boolean', - dflt: false, - role: 'style', - description: [ - 'If "true", even 4-digit integers are separated' - ].join(' ') - }, - tickformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the tick label formatting rule using d3 formatting mini-languages', - 'which are very similar to those in Python. For numbers, see:', - 'https://github.com/d3/d3-format/blob/master/README.md#locale_format', - 'And for dates see:', - 'https://github.com/d3/d3-time-format/blob/master/README.md#locale_format', - 'We add one item to d3\'s date formatter: *%{n}f* for fractional seconds', - 'with n digits. For example, *2016-10-13 09:15:23.456* with tickformat', - '*%H~%M~%S.%2f* would display *09~15~23.46*' - ].join(' ') - }, - hoverformat: { - valType: 'string', - dflt: '', - role: 'style', - description: [ - 'Sets the hover text formatting rule using d3 formatting mini-languages', - 'which are very similar to those in Python. For numbers, see:', - 'https://github.com/d3/d3-format/blob/master/README.md#locale_format', - 'And for dates see:', - 'https://github.com/d3/d3-time-format/blob/master/README.md#locale_format', - 'We add one item to d3\'s date formatter: *%{n}f* for fractional seconds', - 'with n digits. For example, *2016-10-13 09:15:23.456* with tickformat', - '*%H~%M~%S.%2f* would display *09~15~23.46*' - ].join(' ') - }, + tickmode: { + valType: 'enumerated', + values: ['auto', 'linear', 'array'], + role: 'info', + description: [ + 'Sets the tick mode for this axis.', + 'If *auto*, the number of ticks is set via `nticks`.', + 'If *linear*, the placement of the ticks is determined by', + 'a starting position `tick0` and a tick step `dtick`', + '(*linear* is the default value if `tick0` and `dtick` are provided).', + 'If *array*, the placement of the ticks is set via `tickvals`', + 'and the tick text is `ticktext`.', + '(*array* is the default value if `tickvals` is provided).' + ].join(' ') + }, + nticks: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Specifies the maximum number of ticks for the particular axis.', + 'The actual number of ticks will be chosen automatically to be', + 'less than or equal to `nticks`.', + 'Has an effect only if `tickmode` is set to *auto*.' + ].join(' ') + }, + tick0: { + valType: 'any', + role: 'style', + description: [ + 'Sets the placement of the first tick on this axis.', + 'Use with `dtick`.', + 'If the axis `type` is *log*, then you must take the log of your starting tick', + '(e.g. to set the starting tick to 100, set the `tick0` to 2)', + 'except when `dtick`=*L* (see `dtick` for more info).', + 'If the axis `type` is *date*, it should be a date string, like date data.', + 'If the axis `type` is *category*, it should be a number, using the scale where', + 'each category is assigned a serial number from zero in the order it appears.' + ].join(' ') + }, + dtick: { + valType: 'any', + role: 'style', + description: [ + 'Sets the step in-between ticks on this axis. Use with `tick0`.', + 'Must be a positive number, or special strings available to *log* and *date* axes.', + 'If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n', + 'is the tick number. For example,', + 'to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1.', + 'To set tick marks at 1, 100, 10000, ... set dtick to 2.', + 'To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433.', + '*log* has several special values; *L*, where `f` is a positive number,', + 'gives ticks linearly spaced in value (but not position).', + 'For example `tick0` = 0.1, `dtick` = *L0.5* will put ticks at 0.1, 0.6, 1.1, 1.6 etc.', + 'To show powers of 10 plus small digits between, use *D1* (all digits) or *D2* (only 2 and 5).', + '`tick0` is ignored for *D1* and *D2*.', + 'If the axis `type` is *date*, then you must convert the time to milliseconds.', + 'For example, to set the interval between ticks to one day,', + 'set `dtick` to 86400000.0.', + '*date* also has special values *M* gives ticks spaced by a number of months.', + '`n` must be a positive integer.', + 'To set ticks on the 15th of every third month, set `tick0` to *2000-01-15* and `dtick` to *M3*.', + 'To set ticks every 4 years, set `dtick` to *M48*' + ].join(' ') + }, + tickvals: { + valType: 'data_array', + description: [ + 'Sets the values at which ticks on this axis appear.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `ticktext`.' + ].join(' ') + }, + ticktext: { + valType: 'data_array', + description: [ + 'Sets the text displayed at the ticks position via `tickvals`.', + 'Only has an effect if `tickmode` is set to *array*.', + 'Used with `tickvals`.' + ].join(' ') + }, + ticks: { + valType: 'enumerated', + values: ['outside', 'inside', ''], + role: 'style', + description: [ + 'Determines whether ticks are drawn or not.', + 'If **, this axis\' ticks are not drawn.', + 'If *outside* (*inside*), this axis\' are drawn outside (inside)', + 'the axis lines.' + ].join(' ') + }, + mirror: { + valType: 'enumerated', + values: [true, 'ticks', false, 'all', 'allticks'], + dflt: false, + role: 'style', + description: [ + 'Determines if the axis lines or/and ticks are mirrored to', + 'the opposite side of the plotting area.', + 'If *true*, the axis lines are mirrored.', + 'If *ticks*, the axis lines and ticks are mirrored.', + 'If *false*, mirroring is disable.', + 'If *all*, axis lines are mirrored on all shared-axes subplots.', + 'If *allticks*, axis lines and ticks are mirrored', + 'on all shared-axes subplots.' + ].join(' ') + }, + ticklen: { + valType: 'number', + min: 0, + dflt: 5, + role: 'style', + description: 'Sets the tick length (in px).' + }, + tickwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the tick width (in px).' + }, + tickcolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the tick color.' + }, + showticklabels: { + valType: 'boolean', + dflt: true, + role: 'style', + description: 'Determines whether or not the tick labels are drawn.' + }, + tickfont: extendFlat({}, fontAttrs, { + description: 'Sets the tick font.' + }), + tickangle: { + valType: 'angle', + dflt: 'auto', + role: 'style', + description: [ + 'Sets the angle of the tick labels with respect to the horizontal.', + 'For example, a `tickangle` of -90 draws the tick labels', + 'vertically.' + ].join(' ') + }, + tickprefix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label prefix.' + }, + showtickprefix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all tick labels are displayed with a prefix.', + 'If *first*, only the first tick is displayed with a prefix.', + 'If *last*, only the last tick is displayed with a suffix.', + 'If *none*, tick prefixes are hidden.' + ].join(' ') + }, + ticksuffix: { + valType: 'string', + dflt: '', + role: 'style', + description: 'Sets a tick label suffix.' + }, + showticksuffix: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: 'Same as `showtickprefix` but for tick suffixes.' + }, + showexponent: { + valType: 'enumerated', + values: ['all', 'first', 'last', 'none'], + dflt: 'all', + role: 'style', + description: [ + 'If *all*, all exponents are shown besides their significands.', + 'If *first*, only the exponent of the first tick is shown.', + 'If *last*, only the exponent of the last tick is shown.', + 'If *none*, no exponents appear.' + ].join(' ') + }, + exponentformat: { + valType: 'enumerated', + values: ['none', 'e', 'E', 'power', 'SI', 'B'], + dflt: 'B', + role: 'style', + description: [ + 'Determines a formatting rule for the tick exponents.', + 'For example, consider the number 1,000,000,000.', + 'If *none*, it appears as 1,000,000,000.', + 'If *e*, 1e+9.', + 'If *E*, 1E+9.', + 'If *power*, 1x10^9 (with 9 in a super script).', + 'If *SI*, 1G.', + 'If *B*, 1B.' + ].join(' ') + }, + separatethousands: { + valType: 'boolean', + dflt: false, + role: 'style', + description: [ + 'If "true", even 4-digit integers are separated' + ].join(' ') + }, + tickformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the tick label formatting rule using d3 formatting mini-languages', + 'which are very similar to those in Python. For numbers, see:', + 'https://github.com/d3/d3-format/blob/master/README.md#locale_format', + 'And for dates see:', + 'https://github.com/d3/d3-time-format/blob/master/README.md#locale_format', + 'We add one item to d3\'s date formatter: *%{n}f* for fractional seconds', + 'with n digits. For example, *2016-10-13 09:15:23.456* with tickformat', + '*%H~%M~%S.%2f* would display *09~15~23.46*' + ].join(' ') + }, + hoverformat: { + valType: 'string', + dflt: '', + role: 'style', + description: [ + 'Sets the hover text formatting rule using d3 formatting mini-languages', + 'which are very similar to those in Python. For numbers, see:', + 'https://github.com/d3/d3-format/blob/master/README.md#locale_format', + 'And for dates see:', + 'https://github.com/d3/d3-time-format/blob/master/README.md#locale_format', + 'We add one item to d3\'s date formatter: *%{n}f* for fractional seconds', + 'with n digits. For example, *2016-10-13 09:15:23.456* with tickformat', + '*%H~%M~%S.%2f* would display *09~15~23.46*' + ].join(' ') + }, // lines and grids - showline: { - valType: 'boolean', - dflt: false, - role: 'style', - description: [ - 'Determines whether or not a line bounding this axis is drawn.' - ].join(' ') - }, - linecolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the axis line color.' - }, - linewidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the axis line.' - }, - showgrid: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not grid lines are drawn.', - 'If *true*, the grid lines are drawn at every tick mark.' - ].join(' ') - }, - gridcolor: { - valType: 'color', - dflt: colorAttrs.lightLine, - role: 'style', - description: 'Sets the color of the grid lines.' - }, - gridwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the grid lines.' - }, - zeroline: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not a line is drawn at along the 0 value', - 'of this axis.', - 'If *true*, the zero line is drawn on top of the grid lines.' - ].join(' ') - }, - zerolinecolor: { - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: 'Sets the line color of the zero line.' - }, - zerolinewidth: { - valType: 'number', - dflt: 1, - role: 'style', - description: 'Sets the width (in px) of the zero line.' - }, + showline: { + valType: 'boolean', + dflt: false, + role: 'style', + description: [ + 'Determines whether or not a line bounding this axis is drawn.' + ].join(' ') + }, + linecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the axis line color.' + }, + linewidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the axis line.' + }, + showgrid: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not grid lines are drawn.', + 'If *true*, the grid lines are drawn at every tick mark.' + ].join(' ') + }, + gridcolor: { + valType: 'color', + dflt: colorAttrs.lightLine, + role: 'style', + description: 'Sets the color of the grid lines.' + }, + gridwidth: { + valType: 'number', + min: 0, + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the grid lines.' + }, + zeroline: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not a line is drawn at along the 0 value', + 'of this axis.', + 'If *true*, the zero line is drawn on top of the grid lines.' + ].join(' ') + }, + zerolinecolor: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: 'Sets the line color of the zero line.' + }, + zerolinewidth: { + valType: 'number', + dflt: 1, + role: 'style', + description: 'Sets the width (in px) of the zero line.' + }, // positioning attributes // anchor: not used directly, just put here for reference // values are any opposite-letter axis id - anchor: { - valType: 'enumerated', - values: [ - 'free', - constants.idRegex.x.toString(), - constants.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', - 'the corresponding opposite-letter axis.', - 'If set to *free*, this axis\' position is determined by `position`.' - ].join(' ') - }, + anchor: { + valType: 'enumerated', + values: [ + 'free', + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set to an opposite-letter axis id (e.g. `xaxis2`, `yaxis`), this axis is bound to', + 'the corresponding opposite-letter axis.', + 'If set to *free*, this axis\' position is determined by `position`.' + ].join(' ') + }, // side: not used directly, as values depend on direction // values are top, bottom for x axes, and left, right for y - side: { - valType: 'enumerated', - values: ['top', 'bottom', 'left', 'right'], - role: 'info', - description: [ - 'Determines whether a x (y) axis is positioned', - 'at the *bottom* (*left*) or *top* (*right*)', - 'of the plotting area.' - ].join(' ') - }, + side: { + valType: 'enumerated', + values: ['top', 'bottom', 'left', 'right'], + role: 'info', + description: [ + 'Determines whether a x (y) axis is positioned', + 'at the *bottom* (*left*) or *top* (*right*)', + 'of the plotting area.' + ].join(' ') + }, // overlaying: not used directly, just put here for reference // values are false and any other same-letter axis id that's not // itself overlaying anything - overlaying: { - valType: 'enumerated', - values: [ - 'free', - constants.idRegex.x.toString(), - constants.idRegex.y.toString() - ], - role: 'info', - description: [ - 'If set a same-letter axis id, this axis is overlaid on top of', - 'the corresponding same-letter axis.', - 'If *false*, this axis does not overlay any same-letter axes.' - ].join(' ') - }, - domain: { - valType: 'info_array', - role: 'info', - items: [ + overlaying: { + valType: 'enumerated', + values: [ + 'free', + constants.idRegex.x.toString(), + constants.idRegex.y.toString() + ], + role: 'info', + description: [ + 'If set a same-letter axis id, this axis is overlaid on top of', + 'the corresponding same-letter axis.', + 'If *false*, this axis does not overlay any same-letter axes.' + ].join(' ') + }, + domain: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the domain of this axis (in plot fraction).' - ].join(' ') - }, - position: { - valType: 'number', - min: 0, - max: 1, - dflt: 0, - role: 'style', - description: [ - 'Sets the position of this axis in the plotting space', - '(in normalized coordinates).', - 'Only has an effect if `anchor` is set to *free*.' - ].join(' ') - }, - categoryorder: { - valType: 'enumerated', - values: [ - 'trace', 'category ascending', 'category descending', 'array' - /* , 'value ascending', 'value descending'*/ // value ascending / descending to be implemented later - ], - dflt: 'trace', - role: 'info', - description: [ - 'Specifies the ordering logic for the case of categorical variables.', - 'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.', - 'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by', - 'the alphanumerical order of the category names.', + ], + dflt: [0, 1], + description: [ + 'Sets the domain of this axis (in plot fraction).' + ].join(' ') + }, + position: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + role: 'style', + description: [ + 'Sets the position of this axis in the plotting space', + '(in normalized coordinates).', + 'Only has an effect if `anchor` is set to *free*.' + ].join(' ') + }, + categoryorder: { + valType: 'enumerated', + values: [ + 'trace', 'category ascending', 'category descending', 'array' + /* , 'value ascending', 'value descending' */ // value ascending / descending to be implemented later + ], + dflt: 'trace', + role: 'info', + description: [ + 'Specifies the ordering logic for the case of categorical variables.', + 'By default, plotly uses *trace*, which specifies the order that is present in the data supplied.', + 'Set `categoryorder` to *category ascending* or *category descending* if order should be determined by', + 'the alphanumerical order of the category names.', /* 'Set `categoryorder` to *value ascending* or *value descending* if order should be determined by the', - 'numerical order of the values.',*/ // // value ascending / descending to be implemented later - 'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category', - 'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to', - 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.' - ].join(' ') - }, - categoryarray: { - valType: 'data_array', - role: 'info', - description: [ - 'Sets the order in which categories on this axis appear.', - 'Only has an effect if `categoryorder` is set to *array*.', - 'Used with `categoryorder`.' - ].join(' ') - }, + 'numerical order of the values.', */ // // value ascending / descending to be implemented later + 'Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category', + 'is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to', + 'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.' + ].join(' ') + }, + categoryarray: { + valType: 'data_array', + role: 'info', + description: [ + 'Sets the order in which categories on this axis appear.', + 'Only has an effect if `categoryorder` is set to *array*.', + 'Used with `categoryorder`.' + ].join(' ') + }, - _deprecated: { - autotick: { - valType: 'boolean', - role: 'info', - description: [ - 'Obsolete.', - 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', - 'Set `tickmode` to *linear* for `autotick` *false*.' - ].join(' ') - } + _deprecated: { + autotick: { + valType: 'boolean', + role: 'info', + description: [ + 'Obsolete.', + 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', + 'Set `tickmode` to *linear* for `autotick` *false*.' + ].join(' ') } -}; + } +} diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 3594499ffdc..b24730c4b30 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -6,195 +6,189 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var Registry = require('../../registry') +var Lib = require('../../lib') +var Color = require('../../components/color') +var basePlotLayoutAttributes = require('../layout_attributes') + +var constants = require('./constants') +var layoutAttributes = require('./layout_attributes') +var handleAxisDefaults = require('./axis_defaults') +var handlePositionDefaults = require('./position_defaults') +var axisIds = require('./axis_ids') + +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + var layoutKeys = Object.keys(layoutIn), + xaListCartesian = [], + yaListCartesian = [], + xaListGl2d = [], + yaListGl2d = [], + outerTicks = {}, + noGrids = {}, + i -'use strict'; - -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var Color = require('../../components/color'); -var basePlotLayoutAttributes = require('../layout_attributes'); - -var constants = require('./constants'); -var layoutAttributes = require('./layout_attributes'); -var handleAxisDefaults = require('./axis_defaults'); -var handlePositionDefaults = require('./position_defaults'); -var axisIds = require('./axis_ids'); - + // look for axes in the data + for (i = 0; i < fullData.length; i++) { + var trace = fullData[i] + var listX, listY -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - var layoutKeys = Object.keys(layoutIn), - xaListCartesian = [], - yaListCartesian = [], - xaListGl2d = [], - yaListGl2d = [], - outerTicks = {}, - noGrids = {}, - i; + if (Registry.traceIs(trace, 'cartesian')) { + listX = xaListCartesian + listY = yaListCartesian + } else if (Registry.traceIs(trace, 'gl2d')) { + listX = xaListGl2d + listY = yaListGl2d + } else continue - // look for axes in the data - for(i = 0; i < fullData.length; i++) { - var trace = fullData[i]; - var listX, listY; - - if(Registry.traceIs(trace, 'cartesian')) { - listX = xaListCartesian; - listY = yaListCartesian; - } - else if(Registry.traceIs(trace, 'gl2d')) { - listX = xaListGl2d; - listY = yaListGl2d; - } - else continue; - - var xaName = axisIds.id2name(trace.xaxis), - yaName = axisIds.id2name(trace.yaxis); + var xaName = axisIds.id2name(trace.xaxis), + yaName = axisIds.id2name(trace.yaxis) // add axes implied by traces - if(xaName && listX.indexOf(xaName) === -1) listX.push(xaName); - if(yaName && listY.indexOf(yaName) === -1) listY.push(yaName); + if (xaName && listX.indexOf(xaName) === -1) listX.push(xaName) + if (yaName && listY.indexOf(yaName) === -1) listY.push(yaName) // check for default formatting tweaks - if(Registry.traceIs(trace, '2dMap')) { - outerTicks[xaName] = true; - outerTicks[yaName] = true; - } - - if(Registry.traceIs(trace, 'oriented')) { - var positionAxis = trace.orientation === 'h' ? yaName : xaName; - noGrids[positionAxis] = true; - } + if (Registry.traceIs(trace, '2dMap')) { + outerTicks[xaName] = true + outerTicks[yaName] = true } + if (Registry.traceIs(trace, 'oriented')) { + var positionAxis = trace.orientation === 'h' ? yaName : xaName + noGrids[positionAxis] = true + } + } + // N.B. Ignore orphan axes (i.e. axes that have no data attached to them) // if gl3d or geo is present on graph. This is retain backward compatible. // // TODO drop this in version 2.0 - var ignoreOrphan = (layoutOut._has('gl3d') || layoutOut._has('geo')); + var ignoreOrphan = (layoutOut._has('gl3d') || layoutOut._has('geo')) - if(!ignoreOrphan) { - for(i = 0; i < layoutKeys.length; i++) { - var key = layoutKeys[i]; + if (!ignoreOrphan) { + for (i = 0; i < layoutKeys.length; i++) { + var key = layoutKeys[i] // orphan layout axes are considered cartesian subplots - if(xaListGl2d.indexOf(key) === -1 && + if (xaListGl2d.indexOf(key) === -1 && xaListCartesian.indexOf(key) === -1 && constants.xAxisMatch.test(key)) { - xaListCartesian.push(key); - } - else if(yaListGl2d.indexOf(key) === -1 && + xaListCartesian.push(key) + } else if (yaListGl2d.indexOf(key) === -1 && yaListCartesian.indexOf(key) === -1 && constants.yAxisMatch.test(key)) { - yaListCartesian.push(key); - } - } + yaListCartesian.push(key) + } } + } // make sure that plots with orphan cartesian axes // are considered 'cartesian' - if(xaListCartesian.length && yaListCartesian.length) { - Lib.pushUnique(layoutOut._basePlotModules, Registry.subplotsRegistry.cartesian); - } + if (xaListCartesian.length && yaListCartesian.length) { + Lib.pushUnique(layoutOut._basePlotModules, Registry.subplotsRegistry.cartesian) + } - function axSort(a, b) { - var aNum = Number(a.substr(5) || 1), - bNum = Number(b.substr(5) || 1); - return aNum - bNum; - } + function axSort (a, b) { + var aNum = Number(a.substr(5) || 1), + bNum = Number(b.substr(5) || 1) + return aNum - bNum + } - var xaList = xaListCartesian.concat(xaListGl2d).sort(axSort), - yaList = yaListCartesian.concat(yaListGl2d).sort(axSort), - axesList = xaList.concat(yaList); + var xaList = xaListCartesian.concat(xaListGl2d).sort(axSort), + yaList = yaListCartesian.concat(yaListGl2d).sort(axSort), + axesList = xaList.concat(yaList) // plot_bgcolor only makes sense if there's a (2D) plot! // TODO: bgcolor for each subplot, to inherit from the main one - var plot_bgcolor = Color.background; - if(xaList.length && yaList.length) { - plot_bgcolor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor'); + var plot_bgcolor = Color.background + if (xaList.length && yaList.length) { + plot_bgcolor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor') + } + + var bgColor = Color.combine(plot_bgcolor, layoutOut.paper_bgcolor) + + var axLayoutIn, axLayoutOut + + function coerce (attr, dflt) { + return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt) + } + + axesList.forEach(function (axName) { + var axLetter = axName.charAt(0) + + axLayoutIn = layoutIn[axName] || {} + axLayoutOut = {} + + var defaultOptions = { + letter: axLetter, + font: layoutOut.font, + outerTicks: outerTicks[axName], + showGrid: !noGrids[axName], + name: axName, + data: fullData, + bgColor: bgColor, + calendar: layoutOut.calendar } - var bgColor = Color.combine(plot_bgcolor, layoutOut.paper_bgcolor); + handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut) - var axLayoutIn, axLayoutOut; - - function coerce(attr, dflt) { - return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); + var positioningOptions = { + letter: axLetter, + counterAxes: {x: yaList, y: xaList}[axLetter].map(axisIds.name2id), + overlayableAxes: {x: xaList, y: yaList}[axLetter].filter(function (axName2) { + return axName2 !== axName && !(layoutIn[axName2] || {}).overlaying + }).map(axisIds.name2id) } - axesList.forEach(function(axName) { - var axLetter = axName.charAt(0); - - axLayoutIn = layoutIn[axName] || {}; - axLayoutOut = {}; - - var defaultOptions = { - letter: axLetter, - font: layoutOut.font, - outerTicks: outerTicks[axName], - showGrid: !noGrids[axName], - name: axName, - data: fullData, - bgColor: bgColor, - calendar: layoutOut.calendar - }; - - handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); - - var positioningOptions = { - letter: axLetter, - counterAxes: {x: yaList, y: xaList}[axLetter].map(axisIds.name2id), - overlayableAxes: {x: xaList, y: yaList}[axLetter].filter(function(axName2) { - return axName2 !== axName && !(layoutIn[axName2] || {}).overlaying; - }).map(axisIds.name2id) - }; + handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions) - handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions); - - layoutOut[axName] = axLayoutOut; + layoutOut[axName] = axLayoutOut // so we don't have to repeat autotype unnecessarily, // copy an autotype back to layoutIn - if(!layoutIn[axName] && axLayoutIn.type !== '-') { - layoutIn[axName] = {type: axLayoutIn.type}; - } - - }); + if (!layoutIn[axName] && axLayoutIn.type !== '-') { + layoutIn[axName] = {type: axLayoutIn.type} + } + }) // quick second pass for range slider and selector defaults - var rangeSliderDefaults = Registry.getComponentMethod('rangeslider', 'handleDefaults'), - rangeSelectorDefaults = Registry.getComponentMethod('rangeselector', 'handleDefaults'); + var rangeSliderDefaults = Registry.getComponentMethod('rangeslider', 'handleDefaults'), + rangeSelectorDefaults = Registry.getComponentMethod('rangeselector', 'handleDefaults') - xaList.forEach(function(axName) { - axLayoutIn = layoutIn[axName]; - axLayoutOut = layoutOut[axName]; + xaList.forEach(function (axName) { + axLayoutIn = layoutIn[axName] + axLayoutOut = layoutOut[axName] - rangeSliderDefaults(layoutIn, layoutOut, axName); + rangeSliderDefaults(layoutIn, layoutOut, axName) - if(axLayoutOut.type === 'date') { - rangeSelectorDefaults( + if (axLayoutOut.type === 'date') { + rangeSelectorDefaults( axLayoutIn, axLayoutOut, layoutOut, yaList, axLayoutOut.calendar - ); - } + ) + } - coerce('fixedrange'); - }); + coerce('fixedrange') + }) - yaList.forEach(function(axName) { - axLayoutIn = layoutIn[axName]; - axLayoutOut = layoutOut[axName]; + yaList.forEach(function (axName) { + axLayoutIn = layoutIn[axName] + axLayoutOut = layoutOut[axName] - var anchoredAxis = layoutOut[axisIds.id2name(axLayoutOut.anchor)]; + var anchoredAxis = layoutOut[axisIds.id2name(axLayoutOut.anchor)] - var fixedRangeDflt = ( + var fixedRangeDflt = ( anchoredAxis && anchoredAxis.rangeslider && anchoredAxis.rangeslider.visible - ); + ) - coerce('fixedrange', fixedRangeDflt); - }); -}; + coerce('fixedrange', fixedRangeDflt) + }) +} diff --git a/src/plots/cartesian/ordered_categories.js b/src/plots/cartesian/ordered_categories.js index 722e8570963..c4fe15829dc 100644 --- a/src/plots/cartesian/ordered_categories.js +++ b/src/plots/cartesian/ordered_categories.js @@ -6,52 +6,47 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var d3 = require('d3'); +var d3 = require('d3') // flattenUniqueSort :: String -> Function -> [[String]] -> [String] -function flattenUniqueSort(axisLetter, sortFunction, data) { - +function flattenUniqueSort (axisLetter, sortFunction, data) { // Bisection based insertion sort of distinct values for logarithmic time complexity. // Can't use a hashmap, which is O(1), because ES5 maps coerce keys to strings. If it ever becomes a bottleneck, // code can be separated: a hashmap (JS object) based version if all values encountered are strings; and // downgrading to this O(log(n)) array on the first encounter of a non-string value. - var categoryArray = []; - - var traceLines = data.map(function(d) {return d[axisLetter];}); - - var i, j, tracePoints, category, insertionIndex; + var categoryArray = [] - var bisector = d3.bisector(sortFunction).left; + var traceLines = data.map(function (d) { return d[axisLetter] }) - for(i = 0; i < traceLines.length; i++) { + var i, j, tracePoints, category, insertionIndex - tracePoints = traceLines[i]; + var bisector = d3.bisector(sortFunction).left - for(j = 0; j < tracePoints.length; j++) { + for (i = 0; i < traceLines.length; i++) { + tracePoints = traceLines[i] - category = tracePoints[j]; + for (j = 0; j < tracePoints.length; j++) { + category = tracePoints[j] // skip loop: ignore null and undefined categories - if(category === null || category === undefined) continue; + if (category === null || category === undefined) continue - insertionIndex = bisector(categoryArray, category); + insertionIndex = bisector(categoryArray, category) // skip loop on already encountered values - if(insertionIndex < categoryArray.length && categoryArray[insertionIndex] === category) continue; + if (insertionIndex < categoryArray.length && categoryArray[insertionIndex] === category) continue // insert value - categoryArray.splice(insertionIndex, 0, category); - } + categoryArray.splice(insertionIndex, 0, category) } + } - return categoryArray; + return categoryArray } - /** * This pure function returns the ordered categories for specified axisLetter, categoryorder, categoryarray and data. * @@ -65,13 +60,12 @@ function flattenUniqueSort(axisLetter, sortFunction, data) { */ // orderedCategories :: String -> String -> [String] -> [[String]] -> [String] -module.exports = function orderedCategories(axisLetter, categoryorder, categoryarray, data) { - - switch(categoryorder) { - case 'array': return Array.isArray(categoryarray) ? categoryarray.slice() : []; - case 'category ascending': return flattenUniqueSort(axisLetter, d3.ascending, data); - case 'category descending': return flattenUniqueSort(axisLetter, d3.descending, data); - case 'trace': return []; - default: return []; - } -}; +module.exports = function orderedCategories (axisLetter, categoryorder, categoryarray, data) { + switch (categoryorder) { + case 'array': return Array.isArray(categoryarray) ? categoryarray.slice() : [] + case 'category ascending': return flattenUniqueSort(axisLetter, d3.ascending, data) + case 'category descending': return flattenUniqueSort(axisLetter, d3.descending, data) + case 'trace': return [] + default: return [] + } +} diff --git a/src/plots/cartesian/position_defaults.js b/src/plots/cartesian/position_defaults.js index 94ea5ed4e73..18fa8fced57 100644 --- a/src/plots/cartesian/position_defaults.js +++ b/src/plots/cartesian/position_defaults.js @@ -6,58 +6,56 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') -var Lib = require('../../lib'); +module.exports = function handlePositionDefaults (containerIn, containerOut, coerce, options) { + var counterAxes = options.counterAxes || [], + overlayableAxes = options.overlayableAxes || [], + letter = options.letter - -module.exports = function handlePositionDefaults(containerIn, containerOut, coerce, options) { - var counterAxes = options.counterAxes || [], - overlayableAxes = options.overlayableAxes || [], - letter = options.letter; - - var anchor = Lib.coerce(containerIn, containerOut, { - anchor: { - valType: 'enumerated', - values: ['free'].concat(counterAxes), - dflt: isNumeric(containerIn.position) ? 'free' : + var anchor = Lib.coerce(containerIn, containerOut, { + anchor: { + valType: 'enumerated', + values: ['free'].concat(counterAxes), + dflt: isNumeric(containerIn.position) ? 'free' : (counterAxes[0] || 'free') - } - }, 'anchor'); - - if(anchor === 'free') coerce('position'); - - Lib.coerce(containerIn, containerOut, { - side: { - valType: 'enumerated', - values: letter === 'x' ? ['bottom', 'top'] : ['left', 'right'], - dflt: letter === 'x' ? 'bottom' : 'left' - } - }, 'side'); - - var overlaying = false; - if(overlayableAxes.length) { - overlaying = Lib.coerce(containerIn, containerOut, { - overlaying: { - valType: 'enumerated', - values: [false].concat(overlayableAxes), - dflt: false - } - }, 'overlaying'); } + }, 'anchor') + + if (anchor === 'free') coerce('position') - if(!overlaying) { + Lib.coerce(containerIn, containerOut, { + side: { + valType: 'enumerated', + values: letter === 'x' ? ['bottom', 'top'] : ['left', 'right'], + dflt: letter === 'x' ? 'bottom' : 'left' + } + }, 'side') + + var overlaying = false + if (overlayableAxes.length) { + overlaying = Lib.coerce(containerIn, containerOut, { + overlaying: { + valType: 'enumerated', + values: [false].concat(overlayableAxes), + dflt: false + } + }, 'overlaying') + } + + if (!overlaying) { // TODO: right now I'm copying this domain over to overlaying axes // in ax.setscale()... but this means we still need (imperfect) logic // in the axes popover to hide domain for the overlaying axis. // perhaps I should make a private version _domain that all axes get??? - var domain = coerce('domain'); - if(domain[0] > domain[1] - 0.01) containerOut.domain = [0, 1]; - Lib.noneOrAll(containerIn.domain, containerOut.domain, [0, 1]); - } + var domain = coerce('domain') + if (domain[0] > domain[1] - 0.01) containerOut.domain = [0, 1] + Lib.noneOrAll(containerIn.domain, containerOut.domain, [0, 1]) + } - return containerOut; -}; + return containerOut +} diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 65835464e3d..581ba568d6b 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -6,193 +6,184 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var polygon = require('../../lib/polygon'); -var color = require('../../components/color'); - -var axes = require('./axes'); -var constants = require('./constants'); - -var filteredPolygon = polygon.filter; -var polygonTester = polygon.tester; -var MINSELECT = constants.MINSELECT; - -function getAxId(ax) { return ax._id; } - -module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { - var plot = dragOptions.gd._fullLayout._zoomlayer, - dragBBox = dragOptions.element.getBoundingClientRect(), - xs = dragOptions.plotinfo.xaxis._offset, - ys = dragOptions.plotinfo.yaxis._offset, - x0 = startX - dragBBox.left, - y0 = startY - dragBBox.top, - x1 = x0, - y1 = y0, - path0 = 'M' + x0 + ',' + y0, - pw = dragOptions.xaxes[0]._length, - ph = dragOptions.yaxes[0]._length, - xAxisIds = dragOptions.xaxes.map(getAxId), - yAxisIds = dragOptions.yaxes.map(getAxId), - allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), - pts; - - if(mode === 'lasso') { - pts = filteredPolygon([[x0, y0]], constants.BENDPX); - } - - var outlines = plot.selectAll('path.select-outline').data([1, 2]); - - outlines.enter() +'use strict' + +var polygon = require('../../lib/polygon') +var color = require('../../components/color') + +var axes = require('./axes') +var constants = require('./constants') + +var filteredPolygon = polygon.filter +var polygonTester = polygon.tester +var MINSELECT = constants.MINSELECT + +function getAxId (ax) { return ax._id } + +module.exports = function prepSelect (e, startX, startY, dragOptions, mode) { + var plot = dragOptions.gd._fullLayout._zoomlayer, + dragBBox = dragOptions.element.getBoundingClientRect(), + xs = dragOptions.plotinfo.xaxis._offset, + ys = dragOptions.plotinfo.yaxis._offset, + x0 = startX - dragBBox.left, + y0 = startY - dragBBox.top, + x1 = x0, + y1 = y0, + path0 = 'M' + x0 + ',' + y0, + pw = dragOptions.xaxes[0]._length, + ph = dragOptions.yaxes[0]._length, + xAxisIds = dragOptions.xaxes.map(getAxId), + yAxisIds = dragOptions.yaxes.map(getAxId), + allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), + pts + + if (mode === 'lasso') { + pts = filteredPolygon([[x0, y0]], constants.BENDPX) + } + + var outlines = plot.selectAll('path.select-outline').data([1, 2]) + + outlines.enter() .append('path') - .attr('class', function(d) { return 'select-outline select-outline-' + d; }) + .attr('class', function (d) { return 'select-outline select-outline-' + d }) .attr('transform', 'translate(' + xs + ', ' + ys + ')') - .attr('d', path0 + 'Z'); + .attr('d', path0 + 'Z') - var corners = plot.append('path') + var corners = plot.append('path') .attr('class', 'zoombox-corners') .style({ - fill: color.background, - stroke: color.defaultLine, - 'stroke-width': 1 + fill: color.background, + stroke: color.defaultLine, + 'stroke-width': 1 }) .attr('transform', 'translate(' + xs + ', ' + ys + ')') - .attr('d', 'M0,0Z'); - + .attr('d', 'M0,0Z') // find the traces to search for selection points - var searchTraces = [], - gd = dragOptions.gd, - i, - cd, - trace, - searchInfo, - selection = [], - eventData; - for(i = 0; i < gd.calcdata.length; i++) { - cd = gd.calcdata[i]; - trace = cd[0].trace; - if(!trace._module || !trace._module.selectPoints) continue; - - if(dragOptions.subplot) { - if(trace.subplot !== dragOptions.subplot) continue; - - searchTraces.push({ - selectPoints: trace._module.selectPoints, - cd: cd, - xaxis: dragOptions.xaxes[0], - yaxis: dragOptions.yaxes[0] - }); - } - else { - if(xAxisIds.indexOf(trace.xaxis) === -1) continue; - if(yAxisIds.indexOf(trace.yaxis) === -1) continue; - - searchTraces.push({ - selectPoints: trace._module.selectPoints, - cd: cd, - xaxis: axes.getFromId(gd, trace.xaxis), - yaxis: axes.getFromId(gd, trace.yaxis) - }); - } + var searchTraces = [], + gd = dragOptions.gd, + i, + cd, + trace, + searchInfo, + selection = [], + eventData + for (i = 0; i < gd.calcdata.length; i++) { + cd = gd.calcdata[i] + trace = cd[0].trace + if (!trace._module || !trace._module.selectPoints) continue + + if (dragOptions.subplot) { + if (trace.subplot !== dragOptions.subplot) continue + + searchTraces.push({ + selectPoints: trace._module.selectPoints, + cd: cd, + xaxis: dragOptions.xaxes[0], + yaxis: dragOptions.yaxes[0] + }) + } else { + if (xAxisIds.indexOf(trace.xaxis) === -1) continue + if (yAxisIds.indexOf(trace.yaxis) === -1) continue + + searchTraces.push({ + selectPoints: trace._module.selectPoints, + cd: cd, + xaxis: axes.getFromId(gd, trace.xaxis), + yaxis: axes.getFromId(gd, trace.yaxis) + }) } + } - function axValue(ax) { - var index = (ax._id.charAt(0) === 'y') ? 1 : 0; - return function(v) { return ax.p2d(v[index]); }; - } + function axValue (ax) { + var index = (ax._id.charAt(0) === 'y') ? 1 : 0 + return function (v) { return ax.p2d(v[index]) } + } - function ascending(a, b) { return a - b; } + function ascending (a, b) { return a - b } - dragOptions.moveFn = function(dx0, dy0) { - var poly, - ax; - x1 = Math.max(0, Math.min(pw, dx0 + x0)); - y1 = Math.max(0, Math.min(ph, dy0 + y0)); + dragOptions.moveFn = function (dx0, dy0) { + var poly, + ax + x1 = Math.max(0, Math.min(pw, dx0 + x0)) + y1 = Math.max(0, Math.min(ph, dy0 + y0)) - var dx = Math.abs(x1 - x0), - dy = Math.abs(y1 - y0); + var dx = Math.abs(x1 - x0), + dy = Math.abs(y1 - y0) - if(mode === 'select') { - if(dy < Math.min(dx * 0.6, MINSELECT)) { + if (mode === 'select') { + if (dy < Math.min(dx * 0.6, MINSELECT)) { // horizontal motion: make a vertical box - poly = polygonTester([[x0, 0], [x0, ph], [x1, ph], [x1, 0]]); + poly = polygonTester([[x0, 0], [x0, ph], [x1, ph], [x1, 0]]) // extras to guide users in keeping a straight selection - corners.attr('d', 'M' + poly.xmin + ',' + (y0 - MINSELECT) + + corners.attr('d', 'M' + poly.xmin + ',' + (y0 - MINSELECT) + 'h-4v' + (2 * MINSELECT) + 'h4Z' + 'M' + (poly.xmax - 1) + ',' + (y0 - MINSELECT) + - 'h4v' + (2 * MINSELECT) + 'h-4Z'); - - } - else if(dx < Math.min(dy * 0.6, MINSELECT)) { + 'h4v' + (2 * MINSELECT) + 'h-4Z') + } else if (dx < Math.min(dy * 0.6, MINSELECT)) { // vertical motion: make a horizontal box - poly = polygonTester([[0, y0], [0, y1], [pw, y1], [pw, y0]]); - corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + poly.ymin + + poly = polygonTester([[0, y0], [0, y1], [pw, y1], [pw, y0]]) + corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + poly.ymin + 'v-4h' + (2 * MINSELECT) + 'v4Z' + 'M' + (x0 - MINSELECT) + ',' + (poly.ymax - 1) + - 'v4h' + (2 * MINSELECT) + 'v-4Z'); - } - else { + 'v4h' + (2 * MINSELECT) + 'v-4Z') + } else { // diagonal motion - poly = polygonTester([[x0, y0], [x0, y1], [x1, y1], [x1, y0]]); - corners.attr('d', 'M0,0Z'); - } - outlines.attr('d', 'M' + poly.xmin + ',' + poly.ymin + + poly = polygonTester([[x0, y0], [x0, y1], [x1, y1], [x1, y0]]) + corners.attr('d', 'M0,0Z') + } + outlines.attr('d', 'M' + poly.xmin + ',' + poly.ymin + 'H' + (poly.xmax - 1) + 'V' + (poly.ymax - 1) + - 'H' + poly.xmin + 'Z'); - } - else if(mode === 'lasso') { - pts.addPt([x1, y1]); - poly = polygonTester(pts.filtered); - outlines.attr('d', 'M' + pts.filtered.join('L') + 'Z'); - } - - selection = []; - for(i = 0; i < searchTraces.length; i++) { - searchInfo = searchTraces[i]; - [].push.apply(selection, searchInfo.selectPoints(searchInfo, poly)); - } - - eventData = {points: selection}; - - if(mode === 'select') { - var ranges = eventData.range = {}, - axLetter; - - for(i = 0; i < allAxes.length; i++) { - ax = allAxes[i]; - axLetter = ax._id.charAt(0); - ranges[ax._id] = [ - ax.p2d(poly[axLetter + 'min']), - ax.p2d(poly[axLetter + 'max'])].sort(ascending); - } - } - else { - var dataPts = eventData.lassoPoints = {}; - - for(i = 0; i < allAxes.length; i++) { - ax = allAxes[i]; - dataPts[ax._id] = pts.filtered.map(axValue(ax)); - } - } - dragOptions.gd.emit('plotly_selecting', eventData); - }; - - dragOptions.doneFn = function(dragged, numclicks) { - corners.remove(); - if(!dragged && numclicks === 2) { + 'H' + poly.xmin + 'Z') + } else if (mode === 'lasso') { + pts.addPt([x1, y1]) + poly = polygonTester(pts.filtered) + outlines.attr('d', 'M' + pts.filtered.join('L') + 'Z') + } + + selection = [] + for (i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + [].push.apply(selection, searchInfo.selectPoints(searchInfo, poly)) + } + + eventData = {points: selection} + + if (mode === 'select') { + var ranges = eventData.range = {}, + axLetter + + for (i = 0; i < allAxes.length; i++) { + ax = allAxes[i] + axLetter = ax._id.charAt(0) + ranges[ax._id] = [ + ax.p2d(poly[axLetter + 'min']), + ax.p2d(poly[axLetter + 'max'])].sort(ascending) + } + } else { + var dataPts = eventData.lassoPoints = {} + + for (i = 0; i < allAxes.length; i++) { + ax = allAxes[i] + dataPts[ax._id] = pts.filtered.map(axValue(ax)) + } + } + dragOptions.gd.emit('plotly_selecting', eventData) + } + + dragOptions.doneFn = function (dragged, numclicks) { + corners.remove() + if (!dragged && numclicks === 2) { // clear selection on doubleclick - outlines.remove(); - for(i = 0; i < searchTraces.length; i++) { - searchInfo = searchTraces[i]; - searchInfo.selectPoints(searchInfo, false); - } - - gd.emit('plotly_deselect', null); - } - else { - dragOptions.gd.emit('plotly_selected', eventData); - } - }; -}; + outlines.remove() + for (i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i] + searchInfo.selectPoints(searchInfo, false) + } + + gd.emit('plotly_deselect', null) + } else { + dragOptions.gd.emit('plotly_selected', eventData) + } + } +} diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 4c757bff288..1eb8f982639 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -6,33 +6,32 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var cleanNumber = Lib.cleanNumber +var ms2DateTime = Lib.ms2DateTime +var dateTime2ms = Lib.dateTime2ms -var Lib = require('../../lib'); -var cleanNumber = Lib.cleanNumber; -var ms2DateTime = Lib.ms2DateTime; -var dateTime2ms = Lib.dateTime2ms; +var numConstants = require('../../constants/numerical') +var FP_SAFE = numConstants.FP_SAFE +var BADNUM = numConstants.BADNUM -var numConstants = require('../../constants/numerical'); -var FP_SAFE = numConstants.FP_SAFE; -var BADNUM = numConstants.BADNUM; +var constants = require('./constants') +var axisIds = require('./axis_ids') -var constants = require('./constants'); -var axisIds = require('./axis_ids'); - -function fromLog(v) { - return Math.pow(10, v); +function fromLog (v) { + return Math.pow(10, v) } -function num(v) { - if(!isNumeric(v)) return BADNUM; - v = Number(v); - if(v < -FP_SAFE || v > FP_SAFE) return BADNUM; - return isNumeric(v) ? Number(v) : BADNUM; +function num (v) { + if (!isNumeric(v)) return BADNUM + v = Number(v) + if (v < -FP_SAFE || v > FP_SAFE) return BADNUM + return isNumeric(v) ? Number(v) : BADNUM } /** @@ -61,26 +60,23 @@ function num(v) { * also clears the autorange bounds ._min and ._max * and the autotick constraints ._minDtick, ._forceTick0 */ -module.exports = function setConvert(ax) { - +module.exports = function setConvert (ax) { // clipMult: how many axis lengths past the edge do we render? // for panning, 1-2 would suffice, but for zooming more is nice. // also, clipping can affect the direction of lines off the edge... - var clipMult = 10; + var clipMult = 10 - function toLog(v, clip) { - if(v > 0) return Math.log(v) / Math.LN10; + function toLog (v, clip) { + if (v > 0) return Math.log(v) / Math.LN10 - else if(v <= 0 && clip && ax.range && ax.range.length === 2) { + else if (v <= 0 && clip && ax.range && ax.range.length === 2) { // clip NaN (ie past negative infinity) to clipMult axis // length past the negative edge - var r0 = ax.range[0], - r1 = ax.range[1]; - return 0.5 * (r0 + r1 - 3 * clipMult * Math.abs(r0 - r1)); - } - - else return BADNUM; - } + var r0 = ax.range[0], + r1 = ax.range[1] + return 0.5 * (r0 + r1 - 3 * clipMult * Math.abs(r0 - r1)) + } else return BADNUM + } /* * wrapped dateTime2ms that: @@ -88,27 +84,27 @@ module.exports = function setConvert(ax) { * - inserts a dummy arg so calendar is the 3rd arg (see notes below). * - defaults to ax.calendar */ - function dt2ms(v, _, calendar) { + function dt2ms (v, _, calendar) { // NOTE: Changed this behavior: previously we took any numeric value // to be a ms, even if it was a string that could be a bare year. // Now we convert it as a date if at all possible, and only try // as (local) ms if that fails. - var ms = dateTime2ms(v, calendar || ax.calendar); - if(ms === BADNUM) { - if(isNumeric(v)) ms = dateTime2ms(new Date(+v)); - else return BADNUM; - } - return ms; + var ms = dateTime2ms(v, calendar || ax.calendar) + if (ms === BADNUM) { + if (isNumeric(v)) ms = dateTime2ms(new Date(+v)) + else return BADNUM } + return ms + } // wrapped ms2DateTime to insert default ax.calendar - function ms2dt(v, r, calendar) { - return ms2DateTime(v, r, calendar || ax.calendar); - } + function ms2dt (v, r, calendar) { + return ms2DateTime(v, r, calendar || ax.calendar) + } - function getCategoryName(v) { - return ax._categories[Math.round(v)]; - } + function getCategoryName (v) { + return ax._categories[Math.round(v)] + } /* * setCategoryIndex: return the index of category v, @@ -123,76 +119,74 @@ module.exports = function setConvert(ax) { * already sorted category order; otherwise there would be * a disconnect between the array and the index returned */ - function setCategoryIndex(v) { - if(v !== null && v !== undefined) { - var c = ax._categories.indexOf(v); - if(c === -1) { - ax._categories.push(v); - return ax._categories.length - 1; - } - return c; - } - return BADNUM; + function setCategoryIndex (v) { + if (v !== null && v !== undefined) { + var c = ax._categories.indexOf(v) + if (c === -1) { + ax._categories.push(v) + return ax._categories.length - 1 + } + return c } + return BADNUM + } - function getCategoryIndex(v) { + function getCategoryIndex (v) { // d2l/d2c variant that that won't add categories but will also // allow numbers to be mapped to the linearized axis positions - var index = ax._categories.indexOf(v); - if(index !== -1) return index; - if(typeof v === 'number') return v; - } + var index = ax._categories.indexOf(v) + if (index !== -1) return index + if (typeof v === 'number') return v + } - function l2p(v) { - if(!isNumeric(v)) return BADNUM; + function l2p (v) { + if (!isNumeric(v)) return BADNUM // include 2 fractional digits on pixel, for PDF zooming etc - return d3.round(ax._b + ax._m * v, 2); - } + return d3.round(ax._b + ax._m * v, 2) + } - function p2l(px) { return (px - ax._b) / ax._m; } + function p2l (px) { return (px - ax._b) / ax._m } // conversions among c/l/p are fairly simple - do them together for all axis types - ax.c2l = (ax.type === 'log') ? toLog : num; - ax.l2c = (ax.type === 'log') ? fromLog : num; + ax.c2l = (ax.type === 'log') ? toLog : num + ax.l2c = (ax.type === 'log') ? fromLog : num - ax.l2p = l2p; - ax.p2l = p2l; + ax.l2p = l2p + ax.p2l = p2l - ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p; - ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l; + ax.c2p = (ax.type === 'log') ? function (v, clip) { return l2p(toLog(v, clip)) } : l2p + ax.p2c = (ax.type === 'log') ? function (px) { return fromLog(p2l(px)) } : p2l /* * now type-specific conversions for **ALL** other combinations * they're all written out, instead of being combinations of each other, for * both clarity and speed. */ - if(['linear', '-'].indexOf(ax.type) !== -1) { + if (['linear', '-'].indexOf(ax.type) !== -1) { // all are data vals, but d and r need cleaning - ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber; - ax.c2d = ax.c2r = ax.l2d = ax.l2r = num; + ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber + ax.c2d = ax.c2r = ax.l2d = ax.l2r = num - ax.d2p = ax.r2p = function(v) { return l2p(cleanNumber(v)); }; - ax.p2d = ax.p2r = p2l; - } - else if(ax.type === 'log') { + ax.d2p = ax.r2p = function (v) { return l2p(cleanNumber(v)) } + ax.p2d = ax.p2r = p2l + } else if (ax.type === 'log') { // d and c are data vals, r and l are logged (but d and r need cleaning) - ax.d2r = ax.d2l = function(v, clip) { return toLog(cleanNumber(v), clip); }; - ax.r2d = ax.r2c = function(v) { return fromLog(cleanNumber(v)); }; + ax.d2r = ax.d2l = function (v, clip) { return toLog(cleanNumber(v), clip) } + ax.r2d = ax.r2c = function (v) { return fromLog(cleanNumber(v)) } - ax.d2c = ax.r2l = cleanNumber; - ax.c2d = ax.l2r = num; + ax.d2c = ax.r2l = cleanNumber + ax.c2d = ax.l2r = num - ax.c2r = toLog; - ax.l2d = fromLog; + ax.c2r = toLog + ax.l2d = fromLog - ax.d2p = function(v, clip) { return l2p(ax.d2r(v, clip)); }; - ax.p2d = function(px) { return fromLog(p2l(px)); }; + ax.d2p = function (v, clip) { return l2p(ax.d2r(v, clip)) } + ax.p2d = function (px) { return fromLog(p2l(px)) } - ax.r2p = function(v) { return l2p(cleanNumber(v)); }; - ax.p2r = p2l; - } - else if(ax.type === 'date') { + ax.r2p = function (v) { return l2p(cleanNumber(v)) } + ax.p2r = p2l + } else if (ax.type === 'date') { // r and d are date strings, l and c are ms /* @@ -203,46 +197,45 @@ module.exports = function setConvert(ax) { * uses this to limit precision, toLog uses true to clip negatives * to offscreen low rather than undefined), it's safe to pass 0. */ - ax.d2r = ax.r2d = Lib.identity; + ax.d2r = ax.r2d = Lib.identity - ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms; - ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt; + ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms + ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt - ax.d2p = ax.r2p = function(v, _, calendar) { return l2p(dt2ms(v, 0, calendar)); }; - ax.p2d = ax.p2r = function(px, r, calendar) { return ms2dt(p2l(px), r, calendar); }; - } - else if(ax.type === 'category') { + ax.d2p = ax.r2p = function (v, _, calendar) { return l2p(dt2ms(v, 0, calendar)) } + ax.p2d = ax.p2r = function (px, r, calendar) { return ms2dt(p2l(px), r, calendar) } + } else if (ax.type === 'category') { // d is categories; r, c, and l are indices // TODO: should r accept category names too? // ie r2c and r2l would be getCategoryIndex (and r2p would change) - ax.d2r = ax.d2c = ax.d2l = setCategoryIndex; - ax.r2d = ax.c2d = ax.l2d = getCategoryName; + ax.d2r = ax.d2c = ax.d2l = setCategoryIndex + ax.r2d = ax.c2d = ax.l2d = getCategoryName // special d2l variant that won't add categories - ax.d2l_noadd = getCategoryIndex; + ax.d2l_noadd = getCategoryIndex - ax.r2l = ax.l2r = ax.r2c = ax.c2r = num; + ax.r2l = ax.l2r = ax.r2c = ax.c2r = num - ax.d2p = function(v) { return l2p(getCategoryIndex(v)); }; - ax.p2d = function(px) { return getCategoryName(p2l(px)); }; - ax.r2p = l2p; - ax.p2r = p2l; - } + ax.d2p = function (v) { return l2p(getCategoryIndex(v)) } + ax.p2d = function (px) { return getCategoryName(p2l(px)) } + ax.r2p = l2p + ax.p2r = p2l + } // find the range value at the specified (linear) fraction of the axis - ax.fraction2r = function(v) { - var rl0 = ax.r2l(ax.range[0]), - rl1 = ax.r2l(ax.range[1]); - return ax.l2r(rl0 + v * (rl1 - rl0)); - }; + ax.fraction2r = function (v) { + var rl0 = ax.r2l(ax.range[0]), + rl1 = ax.r2l(ax.range[1]) + return ax.l2r(rl0 + v * (rl1 - rl0)) + } // find the fraction of the range at the specified range value - ax.r2fraction = function(v) { - var rl0 = ax.r2l(ax.range[0]), - rl1 = ax.r2l(ax.range[1]); - return (ax.r2l(v) - rl0) / (rl1 - rl0); - }; + ax.r2fraction = function (v) { + var rl0 = ax.r2l(ax.range[0]), + rl1 = ax.r2l(ax.range[1]) + return (ax.r2l(v) - rl0) / (rl1 - rl0) + } /* * cleanRange: make sure range is a couplet of valid & distinct values @@ -252,118 +245,115 @@ module.exports = function setConvert(ax) { * optional param rangeAttr: operate on a different attribute, like * ax._r, rather than ax.range */ - ax.cleanRange = function(rangeAttr) { - if(!rangeAttr) rangeAttr = 'range'; - var range = ax[rangeAttr], - axLetter = (ax._id || 'x').charAt(0), - i, dflt; + ax.cleanRange = function (rangeAttr) { + if (!rangeAttr) rangeAttr = 'range' + var range = ax[rangeAttr], + axLetter = (ax._id || 'x').charAt(0), + i, dflt - if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar); - else if(axLetter === 'y') dflt = constants.DFLTRANGEY; - else dflt = constants.DFLTRANGEX; + if (ax.type === 'date') dflt = Lib.dfltRange(ax.calendar) + else if (axLetter === 'y') dflt = constants.DFLTRANGEY + else dflt = constants.DFLTRANGEX // make sure we don't later mutate the defaults - dflt = dflt.slice(); + dflt = dflt.slice() - if(!range || range.length !== 2) { - ax[rangeAttr] = dflt; - return; - } + if (!range || range.length !== 2) { + ax[rangeAttr] = dflt + return + } - if(ax.type === 'date') { + if (ax.type === 'date') { // check if milliseconds or js date objects are provided for range // and convert to date strings - range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar); - range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar); - } + range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar) + range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar) + } - for(i = 0; i < 2; i++) { - if(ax.type === 'date') { - if(!Lib.isDateTime(range[i], ax.calendar)) { - ax[rangeAttr] = dflt; - break; - } + for (i = 0; i < 2; i++) { + if (ax.type === 'date') { + if (!Lib.isDateTime(range[i], ax.calendar)) { + ax[rangeAttr] = dflt + break + } - if(ax.r2l(range[0]) === ax.r2l(range[1])) { + if (ax.r2l(range[0]) === ax.r2l(range[1])) { // split by +/- 1 second - var linCenter = Lib.constrain(ax.r2l(range[0]), - Lib.MIN_MS + 1000, Lib.MAX_MS - 1000); - range[0] = ax.l2r(linCenter - 1000); - range[1] = ax.l2r(linCenter + 1000); - break; - } - } - else { - if(!isNumeric(range[i])) { - if(isNumeric(range[1 - i])) { - range[i] = range[1 - i] * (i ? 10 : 0.1); - } - else { - ax[rangeAttr] = dflt; - break; - } - } - - if(range[i] < -FP_SAFE) range[i] = -FP_SAFE; - else if(range[i] > FP_SAFE) range[i] = FP_SAFE; - - if(range[0] === range[1]) { + var linCenter = Lib.constrain(ax.r2l(range[0]), + Lib.MIN_MS + 1000, Lib.MAX_MS - 1000) + range[0] = ax.l2r(linCenter - 1000) + range[1] = ax.l2r(linCenter + 1000) + break + } + } else { + if (!isNumeric(range[i])) { + if (isNumeric(range[1 - i])) { + range[i] = range[1 - i] * (i ? 10 : 0.1) + } else { + ax[rangeAttr] = dflt + break + } + } + + if (range[i] < -FP_SAFE) range[i] = -FP_SAFE + else if (range[i] > FP_SAFE) range[i] = FP_SAFE + + if (range[0] === range[1]) { // somewhat arbitrary: split by 1 or 1ppm, whichever is bigger - var inc = Math.max(1, Math.abs(range[0] * 1e-6)); - range[0] -= inc; - range[1] += inc; - } - } + var inc = Math.max(1, Math.abs(range[0] * 1e-6)) + range[0] -= inc + range[1] += inc } - }; + } + } + } // set scaling to pixels - ax.setScale = function(usePrivateRange) { - var gs = ax._gd._fullLayout._size, - axLetter = ax._id.charAt(0); + ax.setScale = function (usePrivateRange) { + var gs = ax._gd._fullLayout._size, + axLetter = ax._id.charAt(0) // TODO cleaner way to handle this case - if(!ax._categories) ax._categories = []; + if (!ax._categories) ax._categories = [] // make sure we have a domain (pull it in from the axis // this one is overlaying if necessary) - if(ax.overlaying) { - var ax2 = axisIds.getFromId(ax._gd, ax.overlaying); - ax.domain = ax2.domain; - } + if (ax.overlaying) { + var ax2 = axisIds.getFromId(ax._gd, ax.overlaying) + ax.domain = ax2.domain + } // While transitions are occuring, occurring, we get a double-transform // issue if we transform the drawn layer *and* use the new axis range to // draw the data. This allows us to construct setConvert using the pre- // interaction values of the range: - var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range', - calendar = ax.calendar; - ax.cleanRange(rangeAttr); - - var rl0 = ax.r2l(ax[rangeAttr][0], calendar), - rl1 = ax.r2l(ax[rangeAttr][1], calendar); - - if(axLetter === 'y') { - ax._offset = gs.t + (1 - ax.domain[1]) * gs.h; - ax._length = gs.h * (ax.domain[1] - ax.domain[0]); - ax._m = ax._length / (rl0 - rl1); - ax._b = -ax._m * rl1; - } - else { - ax._offset = gs.l + ax.domain[0] * gs.w; - ax._length = gs.w * (ax.domain[1] - ax.domain[0]); - ax._m = ax._length / (rl1 - rl0); - ax._b = -ax._m * rl0; - } + var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range', + calendar = ax.calendar + ax.cleanRange(rangeAttr) + + var rl0 = ax.r2l(ax[rangeAttr][0], calendar), + rl1 = ax.r2l(ax[rangeAttr][1], calendar) + + if (axLetter === 'y') { + ax._offset = gs.t + (1 - ax.domain[1]) * gs.h + ax._length = gs.h * (ax.domain[1] - ax.domain[0]) + ax._m = ax._length / (rl0 - rl1) + ax._b = -ax._m * rl1 + } else { + ax._offset = gs.l + ax.domain[0] * gs.w + ax._length = gs.w * (ax.domain[1] - ax.domain[0]) + ax._m = ax._length / (rl1 - rl0) + ax._b = -ax._m * rl0 + } - if(!isFinite(ax._m) || !isFinite(ax._b)) { - Lib.notifier( + if (!isFinite(ax._m) || !isFinite(ax._b)) { + Lib.notifier( 'Something went wrong with axis scaling', - 'long'); - ax._gd._replotting = false; - throw new Error('axis scaling'); - } - }; + 'long') + ax._gd._replotting = false + throw new Error('axis scaling') + } + } // makeCalcdata: takes an x or y array and converts it // to a position on the axis object "ax" @@ -374,41 +364,40 @@ module.exports = function setConvert(ax) { // the first letter of ax._id?) // in case the expected data isn't there, make a list of // integers based on the opposite data - ax.makeCalcdata = function(trace, axLetter) { - var arrayIn, arrayOut, i; + ax.makeCalcdata = function (trace, axLetter) { + var arrayIn, arrayOut, i - var cal = ax.type === 'date' && trace[axLetter + 'calendar']; + var cal = ax.type === 'date' && trace[axLetter + 'calendar'] - if(axLetter in trace) { - arrayIn = trace[axLetter]; - arrayOut = new Array(arrayIn.length); + if (axLetter in trace) { + arrayIn = trace[axLetter] + arrayOut = new Array(arrayIn.length) - for(i = 0; i < arrayIn.length; i++) { - arrayOut[i] = ax.d2c(arrayIn[i], 0, cal); - } - } - else { - var v0 = ((axLetter + '0') in trace) ? + for (i = 0; i < arrayIn.length; i++) { + arrayOut[i] = ax.d2c(arrayIn[i], 0, cal) + } + } else { + var v0 = ((axLetter + '0') in trace) ? ax.d2c(trace[axLetter + '0'], 0, cal) : 0, - dv = (trace['d' + axLetter]) ? - Number(trace['d' + axLetter]) : 1; + dv = (trace['d' + axLetter]) ? + Number(trace['d' + axLetter]) : 1 // the opposing data, for size if we have x and dx etc - arrayIn = trace[{x: 'y', y: 'x'}[axLetter]]; - arrayOut = new Array(arrayIn.length); + arrayIn = trace[{x: 'y', y: 'x'}[axLetter]] + arrayOut = new Array(arrayIn.length) - for(i = 0; i < arrayIn.length; i++) arrayOut[i] = v0 + i * dv; - } - return arrayOut; - }; + for (i = 0; i < arrayIn.length; i++) arrayOut[i] = v0 + i * dv + } + return arrayOut + } // for autoranging: arrays of objects: // {val: axis value, pad: pixel padding} // on the low and high sides - ax._min = []; - ax._max = []; + ax._min = [] + ax._max = [] // and for bar charts and box plots: reset forced minimum tick spacing - delete ax._minDtick; - delete ax._forceTick0; -}; + delete ax._minDtick + delete ax._forceTick0 +} diff --git a/src/plots/cartesian/tick_label_defaults.js b/src/plots/cartesian/tick_label_defaults.js index 5f37680d331..bda302edb23 100644 --- a/src/plots/cartesian/tick_label_defaults.js +++ b/src/plots/cartesian/tick_label_defaults.js @@ -6,50 +6,48 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - +var Lib = require('../../lib') /** * options: inherits font, outerTicks, noHover from axes.handleAxisDefaults */ -module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options) { - var showAttrDflt = getShowAttrDflt(containerIn); +module.exports = function handleTickLabelDefaults (containerIn, containerOut, coerce, axType, options) { + var showAttrDflt = getShowAttrDflt(containerIn) - var tickPrefix = coerce('tickprefix'); - if(tickPrefix) coerce('showtickprefix', showAttrDflt); + var tickPrefix = coerce('tickprefix') + if (tickPrefix) coerce('showtickprefix', showAttrDflt) - var tickSuffix = coerce('ticksuffix'); - if(tickSuffix) coerce('showticksuffix', showAttrDflt); + var tickSuffix = coerce('ticksuffix') + if (tickSuffix) coerce('showticksuffix', showAttrDflt) - var showTickLabels = coerce('showticklabels'); - if(showTickLabels) { - var font = options.font || {}; + var showTickLabels = coerce('showticklabels') + if (showTickLabels) { + var font = options.font || {} // as with titlefont.color, inherit axis.color only if one was // explicitly provided - var dfltFontColor = (containerOut.color === containerIn.color) ? - containerOut.color : font.color; - Lib.coerceFont(coerce, 'tickfont', { - family: font.family, - size: font.size, - color: dfltFontColor - }); - coerce('tickangle'); + var dfltFontColor = (containerOut.color === containerIn.color) ? + containerOut.color : font.color + Lib.coerceFont(coerce, 'tickfont', { + family: font.family, + size: font.size, + color: dfltFontColor + }) + coerce('tickangle') - if(axType !== 'category') { - var tickFormat = coerce('tickformat'); - if(!tickFormat && axType !== 'date') { - coerce('showexponent', showAttrDflt); - coerce('exponentformat'); - coerce('separatethousands'); - } - } + if (axType !== 'category') { + var tickFormat = coerce('tickformat') + if (!tickFormat && axType !== 'date') { + coerce('showexponent', showAttrDflt) + coerce('exponentformat') + coerce('separatethousands') + } } + } - if(axType !== 'category' && !options.noHover) coerce('hoverformat'); -}; + if (axType !== 'category' && !options.noHover) coerce('hoverformat') +} /* * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix' @@ -65,18 +63,18 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe * the remaining is set to its dflt value. * */ -function getShowAttrDflt(containerIn) { - var showAttrsAll = ['showexponent', - 'showtickprefix', - 'showticksuffix'], - showAttrs = showAttrsAll.filter(function(a) { - return containerIn[a] !== undefined; - }), - sameVal = function(a) { - return containerIn[a] === containerIn[showAttrs[0]]; - }; - - if(showAttrs.every(sameVal) || showAttrs.length === 1) { - return containerIn[showAttrs[0]]; +function getShowAttrDflt (containerIn) { + var showAttrsAll = ['showexponent', + 'showtickprefix', + 'showticksuffix'], + showAttrs = showAttrsAll.filter(function (a) { + return containerIn[a] !== undefined + }), + sameVal = function (a) { + return containerIn[a] === containerIn[showAttrs[0]] } + + if (showAttrs.every(sameVal) || showAttrs.length === 1) { + return containerIn[showAttrs[0]] + } } diff --git a/src/plots/cartesian/tick_mark_defaults.js b/src/plots/cartesian/tick_mark_defaults.js index def1ecdff4c..cd97a8701ad 100644 --- a/src/plots/cartesian/tick_mark_defaults.js +++ b/src/plots/cartesian/tick_mark_defaults.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - -var layoutAttributes = require('./layout_attributes'); +var Lib = require('../../lib') +var layoutAttributes = require('./layout_attributes') /** * options: inherits outerTicks from axes.handleAxisDefaults */ -module.exports = function handleTickDefaults(containerIn, containerOut, coerce, options) { - var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'), - tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'), - tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color), - showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); +module.exports = function handleTickDefaults (containerIn, containerOut, coerce, options) { + var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'), + tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'), + tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color), + showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : '') - if(!showTicks) { - delete containerOut.ticklen; - delete containerOut.tickwidth; - delete containerOut.tickcolor; - } -}; + if (!showTicks) { + delete containerOut.ticklen + delete containerOut.tickwidth + delete containerOut.tickcolor + } +} diff --git a/src/plots/cartesian/tick_value_defaults.js b/src/plots/cartesian/tick_value_defaults.js index e0f4bffc2d4..eff707f8e62 100644 --- a/src/plots/cartesian/tick_value_defaults.js +++ b/src/plots/cartesian/tick_value_defaults.js @@ -6,48 +6,44 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') +var Lib = require('../../lib') +var ONEDAY = require('../../constants/numerical').ONEDAY -var isNumeric = require('fast-isnumeric'); -var Lib = require('../../lib'); -var ONEDAY = require('../../constants/numerical').ONEDAY; +module.exports = function handleTickValueDefaults (containerIn, containerOut, coerce, axType) { + var tickmodeDefault = 'auto' - -module.exports = function handleTickValueDefaults(containerIn, containerOut, coerce, axType) { - var tickmodeDefault = 'auto'; - - if(containerIn.tickmode === 'array' && + if (containerIn.tickmode === 'array' && (axType === 'log' || axType === 'date')) { - containerIn.tickmode = 'auto'; - } + containerIn.tickmode = 'auto' + } - if(Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array'; - else if(containerIn.dtick) { - tickmodeDefault = 'linear'; - } - var tickmode = coerce('tickmode', tickmodeDefault); + if (Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array' + else if (containerIn.dtick) { + tickmodeDefault = 'linear' + } + var tickmode = coerce('tickmode', tickmodeDefault) - if(tickmode === 'auto') coerce('nticks'); - else if(tickmode === 'linear') { + if (tickmode === 'auto') coerce('nticks') + else if (tickmode === 'linear') { // dtick is usually a positive number, but there are some // special strings available for log or date axes // default is 1 day for dates, otherwise 1 - var dtickDflt = (axType === 'date') ? ONEDAY : 1; - var dtick = coerce('dtick', dtickDflt); - if(isNumeric(dtick)) { - containerOut.dtick = (dtick > 0) ? Number(dtick) : dtickDflt; - } - else if(typeof dtick !== 'string') { - containerOut.dtick = dtickDflt; - } - else { + var dtickDflt = (axType === 'date') ? ONEDAY : 1 + var dtick = coerce('dtick', dtickDflt) + if (isNumeric(dtick)) { + containerOut.dtick = (dtick > 0) ? Number(dtick) : dtickDflt + } else if (typeof dtick !== 'string') { + containerOut.dtick = dtickDflt + } else { // date and log special cases are all one character plus a number - var prefix = dtick.charAt(0), - dtickNum = dtick.substr(1); + var prefix = dtick.charAt(0), + dtickNum = dtick.substr(1) - dtickNum = isNumeric(dtickNum) ? Number(dtickNum) : 0; - if((dtickNum <= 0) || !( + dtickNum = isNumeric(dtickNum) ? Number(dtickNum) : 0 + if ((dtickNum <= 0) || !( // "M" gives ticks every (integer) n months (axType === 'date' && prefix === 'M' && dtickNum === Math.round(dtickNum)) || // "L" gives ticks linearly spaced in data (not in position) every (float) f @@ -55,28 +51,26 @@ module.exports = function handleTickValueDefaults(containerIn, containerOut, coe // "D1" gives powers of 10 with all small digits between, "D2" gives only 2 and 5 (axType === 'log' && prefix === 'D' && (dtickNum === 1 || dtickNum === 2)) )) { - containerOut.dtick = dtickDflt; - } - } + containerOut.dtick = dtickDflt + } + } // tick0 can have different valType for different axis types, so // validate that now. Also for dates, change milliseconds to date strings - var tick0Dflt = (axType === 'date') ? Lib.dateTick0(containerOut.calendar) : 0; - var tick0 = coerce('tick0', tick0Dflt); - if(axType === 'date') { - containerOut.tick0 = Lib.cleanDate(tick0, tick0Dflt); - } - // Aside from date axes, dtick must be numeric; D1 and D2 modes ignore tick0 entirely - else if(isNumeric(tick0) && dtick !== 'D1' && dtick !== 'D2') { - containerOut.tick0 = Number(tick0); - } - else { - containerOut.tick0 = tick0Dflt; - } + var tick0Dflt = (axType === 'date') ? Lib.dateTick0(containerOut.calendar) : 0 + var tick0 = coerce('tick0', tick0Dflt) + if (axType === 'date') { + containerOut.tick0 = Lib.cleanDate(tick0, tick0Dflt) } - else { - var tickvals = coerce('tickvals'); - if(tickvals === undefined) containerOut.tickmode = 'auto'; - else coerce('ticktext'); + // Aside from date axes, dtick must be numeric; D1 and D2 modes ignore tick0 entirely + else if (isNumeric(tick0) && dtick !== 'D1' && dtick !== 'D2') { + containerOut.tick0 = Number(tick0) + } else { + containerOut.tick0 = tick0Dflt } -}; + } else { + var tickvals = coerce('tickvals') + if (tickvals === undefined) containerOut.tickmode = 'auto' + else coerce('ticktext') + } +} diff --git a/src/plots/cartesian/transition_axes.js b/src/plots/cartesian/transition_axes.js index b41c50b8cc3..af0ad929a80 100644 --- a/src/plots/cartesian/transition_axes.js +++ b/src/plots/cartesian/transition_axes.js @@ -6,142 +6,141 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var d3 = require('d3'); - -var Plotly = require('../../plotly'); -var Registry = require('../../registry'); -var Drawing = require('../../components/drawing'); -var Axes = require('./axes'); -var axisRegex = /((x|y)([2-9]|[1-9][0-9]+)?)axis$/; - -module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCompleteCallback) { - var fullLayout = gd._fullLayout; - var axes = []; - - function computeUpdates(layout) { - var ai, attrList, match, axis, update; - var updates = {}; - - for(ai in layout) { - attrList = ai.split('.'); - match = attrList[0].match(axisRegex); - if(match) { - var axisLetter = match[1]; - var axisName = axisLetter + 'axis'; - axis = fullLayout[axisName]; - update = {}; - - if(Array.isArray(layout[ai])) { - update.to = layout[ai].slice(0); - } else { - if(Array.isArray(layout[ai].range)) { - update.to = layout[ai].range.slice(0); - } - } - if(!update.to) continue; - - update.axisName = axisName; - update.length = axis._length; - - axes.push(axisLetter); - - updates[axisLetter] = update; - } +'use strict' + +var d3 = require('d3') + +var Plotly = require('../../plotly') +var Registry = require('../../registry') +var Drawing = require('../../components/drawing') +var Axes = require('./axes') +var axisRegex = /((x|y)([2-9]|[1-9][0-9]+)?)axis$/ + +module.exports = function transitionAxes (gd, newLayout, transitionOpts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout + var axes = [] + + function computeUpdates (layout) { + var ai, attrList, match, axis, update + var updates = {} + + for (ai in layout) { + attrList = ai.split('.') + match = attrList[0].match(axisRegex) + if (match) { + var axisLetter = match[1] + var axisName = axisLetter + 'axis' + axis = fullLayout[axisName] + update = {} + + if (Array.isArray(layout[ai])) { + update.to = layout[ai].slice(0) + } else { + if (Array.isArray(layout[ai].range)) { + update.to = layout[ai].range.slice(0) + } } + if (!update.to) continue + + update.axisName = axisName + update.length = axis._length - return updates; + axes.push(axisLetter) + + updates[axisLetter] = update + } } - function computeAffectedSubplots(fullLayout, updatedAxisIds, updates) { - var plotName; - var plotinfos = fullLayout._plots; - var affectedSubplots = []; - var toX, toY; + return updates + } - for(plotName in plotinfos) { - var plotinfo = plotinfos[plotName]; + function computeAffectedSubplots (fullLayout, updatedAxisIds, updates) { + var plotName + var plotinfos = fullLayout._plots + var affectedSubplots = [] + var toX, toY - if(affectedSubplots.indexOf(plotinfo) !== -1) continue; + for (plotName in plotinfos) { + var plotinfo = plotinfos[plotName] - var x = plotinfo.xaxis._id; - var y = plotinfo.yaxis._id; - var fromX = plotinfo.xaxis.range; - var fromY = plotinfo.yaxis.range; + if (affectedSubplots.indexOf(plotinfo) !== -1) continue - // Store the initial range at the beginning of this transition: - plotinfo.xaxis._r = plotinfo.xaxis.range.slice(); - plotinfo.yaxis._r = plotinfo.yaxis.range.slice(); - - if(updates[x]) { - toX = updates[x].to; - } else { - toX = fromX; - } - if(updates[y]) { - toY = updates[y].to; - } else { - toY = fromY; - } - - if(fromX[0] === toX[0] && fromX[1] === toX[1] && fromY[0] === toY[0] && fromY[1] === toY[1]) continue; - - if(updatedAxisIds.indexOf(x) !== -1 || updatedAxisIds.indexOf(y) !== -1) { - affectedSubplots.push(plotinfo); - } - } + var x = plotinfo.xaxis._id + var y = plotinfo.yaxis._id + var fromX = plotinfo.xaxis.range + var fromY = plotinfo.yaxis.range - return affectedSubplots; + // Store the initial range at the beginning of this transition: + plotinfo.xaxis._r = plotinfo.xaxis.range.slice() + plotinfo.yaxis._r = plotinfo.yaxis.range.slice() + + if (updates[x]) { + toX = updates[x].to + } else { + toX = fromX + } + if (updates[y]) { + toY = updates[y].to + } else { + toY = fromY + } + + if (fromX[0] === toX[0] && fromX[1] === toX[1] && fromY[0] === toY[0] && fromY[1] === toY[1]) continue + + if (updatedAxisIds.indexOf(x) !== -1 || updatedAxisIds.indexOf(y) !== -1) { + affectedSubplots.push(plotinfo) + } } - var updates = computeUpdates(newLayout); - var updatedAxisIds = Object.keys(updates); - var affectedSubplots = computeAffectedSubplots(fullLayout, updatedAxisIds, updates); + return affectedSubplots + } - if(!affectedSubplots.length) { - return false; - } + var updates = computeUpdates(newLayout) + var updatedAxisIds = Object.keys(updates) + var affectedSubplots = computeAffectedSubplots(fullLayout, updatedAxisIds, updates) - function ticksAndAnnotations(xa, ya) { - var activeAxIds = [], - i; + if (!affectedSubplots.length) { + return false + } - activeAxIds = [xa._id, ya._id]; + function ticksAndAnnotations (xa, ya) { + var activeAxIds = [], + i - for(i = 0; i < activeAxIds.length; i++) { - Axes.doTicks(gd, activeAxIds[i], true); - } + activeAxIds = [xa._id, ya._id] + + for (i = 0; i < activeAxIds.length; i++) { + Axes.doTicks(gd, activeAxIds[i], true) + } - function redrawObjs(objArray, method) { - for(i = 0; i < objArray.length; i++) { - var obji = objArray[i]; + function redrawObjs (objArray, method) { + for (i = 0; i < objArray.length; i++) { + var obji = objArray[i] - if((activeAxIds.indexOf(obji.xref) !== -1) || + if ((activeAxIds.indexOf(obji.xref) !== -1) || (activeAxIds.indexOf(obji.yref) !== -1)) { - method(gd, i); - } - } + method(gd, i) } + } + } // annotations and shapes 'draw' method is slow, // use the finer-grained 'drawOne' method instead - redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')); - redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')); - redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw')); - } + redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne')) + redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne')) + redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw')) + } - function unsetSubplotTransform(subplot) { - var xa2 = subplot.xaxis; - var ya2 = subplot.yaxis; + function unsetSubplotTransform (subplot) { + var xa2 = subplot.xaxis + var ya2 = subplot.yaxis - fullLayout._defs.selectAll('#' + subplot.clipId) + fullLayout._defs.selectAll('#' + subplot.clipId) .call(Drawing.setTranslate, 0, 0) - .call(Drawing.setScale, 1, 1); + .call(Drawing.setScale, 1, 1) - subplot.plot + subplot.plot .call(Drawing.setTranslate, xa2._offset, ya2._offset) .call(Drawing.setScale, 1, 1) @@ -149,77 +148,75 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo // scale to individual points to counteract the scale of the trace // as a whole: .selectAll('.points').selectAll('.point') - .call(Drawing.setPointGroupScale, 1, 1); - + .call(Drawing.setPointGroupScale, 1, 1) + } + + function updateSubplot (subplot, progress) { + var axis, r0, r1 + var xUpdate = updates[subplot.xaxis._id] + var yUpdate = updates[subplot.yaxis._id] + + var viewBox = [] + + if (xUpdate) { + axis = gd._fullLayout[xUpdate.axisName] + r0 = axis._r + r1 = xUpdate.to + viewBox[0] = (r0[0] * (1 - progress) + progress * r1[0] - r0[0]) / (r0[1] - r0[0]) * subplot.xaxis._length + var dx1 = r0[1] - r0[0] + var dx2 = r1[1] - r1[0] + + axis.range[0] = r0[0] * (1 - progress) + progress * r1[0] + axis.range[1] = r0[1] * (1 - progress) + progress * r1[1] + + viewBox[2] = subplot.xaxis._length * ((1 - progress) + progress * dx2 / dx1) + } else { + viewBox[0] = 0 + viewBox[2] = subplot.xaxis._length } - function updateSubplot(subplot, progress) { - var axis, r0, r1; - var xUpdate = updates[subplot.xaxis._id]; - var yUpdate = updates[subplot.yaxis._id]; - - var viewBox = []; - - if(xUpdate) { - axis = gd._fullLayout[xUpdate.axisName]; - r0 = axis._r; - r1 = xUpdate.to; - viewBox[0] = (r0[0] * (1 - progress) + progress * r1[0] - r0[0]) / (r0[1] - r0[0]) * subplot.xaxis._length; - var dx1 = r0[1] - r0[0]; - var dx2 = r1[1] - r1[0]; - - axis.range[0] = r0[0] * (1 - progress) + progress * r1[0]; - axis.range[1] = r0[1] * (1 - progress) + progress * r1[1]; - - viewBox[2] = subplot.xaxis._length * ((1 - progress) + progress * dx2 / dx1); - } else { - viewBox[0] = 0; - viewBox[2] = subplot.xaxis._length; - } - - if(yUpdate) { - axis = gd._fullLayout[yUpdate.axisName]; - r0 = axis._r; - r1 = yUpdate.to; - viewBox[1] = (r0[1] * (1 - progress) + progress * r1[1] - r0[1]) / (r0[0] - r0[1]) * subplot.yaxis._length; - var dy1 = r0[1] - r0[0]; - var dy2 = r1[1] - r1[0]; - - axis.range[0] = r0[0] * (1 - progress) + progress * r1[0]; - axis.range[1] = r0[1] * (1 - progress) + progress * r1[1]; - - viewBox[3] = subplot.yaxis._length * ((1 - progress) + progress * dy2 / dy1); - } else { - viewBox[1] = 0; - viewBox[3] = subplot.yaxis._length; - } - - ticksAndAnnotations(subplot.xaxis, subplot.yaxis); + if (yUpdate) { + axis = gd._fullLayout[yUpdate.axisName] + r0 = axis._r + r1 = yUpdate.to + viewBox[1] = (r0[1] * (1 - progress) + progress * r1[1] - r0[1]) / (r0[0] - r0[1]) * subplot.yaxis._length + var dy1 = r0[1] - r0[0] + var dy2 = r1[1] - r1[0] + + axis.range[0] = r0[0] * (1 - progress) + progress * r1[0] + axis.range[1] = r0[1] * (1 - progress) + progress * r1[1] + + viewBox[3] = subplot.yaxis._length * ((1 - progress) + progress * dy2 / dy1) + } else { + viewBox[1] = 0 + viewBox[3] = subplot.yaxis._length + } + ticksAndAnnotations(subplot.xaxis, subplot.yaxis) - var xa2 = subplot.xaxis; - var ya2 = subplot.yaxis; + var xa2 = subplot.xaxis + var ya2 = subplot.yaxis - var editX = !!xUpdate; - var editY = !!yUpdate; + var editX = !!xUpdate + var editY = !!yUpdate - var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, - yScaleFactor = editY ? ya2._length / viewBox[3] : 1; + var xScaleFactor = editX ? xa2._length / viewBox[2] : 1, + yScaleFactor = editY ? ya2._length / viewBox[3] : 1 - var clipDx = editX ? viewBox[0] : 0, - clipDy = editY ? viewBox[1] : 0; + var clipDx = editX ? viewBox[0] : 0, + clipDy = editY ? viewBox[1] : 0 - var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, - fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0; + var fracDx = editX ? (viewBox[0] / viewBox[2] * xa2._length) : 0, + fracDy = editY ? (viewBox[1] / viewBox[3] * ya2._length) : 0 - var plotDx = xa2._offset - fracDx, - plotDy = ya2._offset - fracDy; + var plotDx = xa2._offset - fracDx, + plotDy = ya2._offset - fracDy - fullLayout._defs.selectAll('#' + subplot.clipId) + fullLayout._defs.selectAll('#' + subplot.clipId) .call(Drawing.setTranslate, clipDx, clipDy) - .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor) - subplot.plot + subplot.plot .call(Drawing.setTranslate, plotDx, plotDy) .call(Drawing.setScale, xScaleFactor, yScaleFactor) @@ -227,84 +224,83 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo // scale to individual points to counteract the scale of the trace // as a whole: .selectAll('.points').selectAll('.point') - .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); - - } + .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor) + } - var onComplete; - if(makeOnCompleteCallback) { + var onComplete + if (makeOnCompleteCallback) { // This module makes the choice whether or not it notifies Plotly.transition // about completion: - onComplete = makeOnCompleteCallback(); + onComplete = makeOnCompleteCallback() + } + + function transitionComplete () { + var aobj = {} + for (var i = 0; i < updatedAxisIds.length; i++) { + var axi = gd._fullLayout[updates[updatedAxisIds[i]].axisName] + var to = updates[updatedAxisIds[i]].to + aobj[axi._name + '.range[0]'] = to[0] + aobj[axi._name + '.range[1]'] = to[1] + + axi.range = to.slice() } - function transitionComplete() { - var aobj = {}; - for(var i = 0; i < updatedAxisIds.length; i++) { - var axi = gd._fullLayout[updates[updatedAxisIds[i]].axisName]; - var to = updates[updatedAxisIds[i]].to; - aobj[axi._name + '.range[0]'] = to[0]; - aobj[axi._name + '.range[1]'] = to[1]; - - axi.range = to.slice(); - } - // Signal that this transition has completed: - onComplete && onComplete(); - - return Plotly.relayout(gd, aobj).then(function() { - for(var i = 0; i < affectedSubplots.length; i++) { - unsetSubplotTransform(affectedSubplots[i]); - } - }); + onComplete && onComplete() + + return Plotly.relayout(gd, aobj).then(function () { + for (var i = 0; i < affectedSubplots.length; i++) { + unsetSubplotTransform(affectedSubplots[i]) + } + }) + } + + function transitionInterrupt () { + var aobj = {} + for (var i = 0; i < updatedAxisIds.length; i++) { + var axi = gd._fullLayout[updatedAxisIds[i] + 'axis'] + aobj[axi._name + '.range[0]'] = axi.range[0] + aobj[axi._name + '.range[1]'] = axi.range[1] + + axi.range = axi._r.slice() } - function transitionInterrupt() { - var aobj = {}; - for(var i = 0; i < updatedAxisIds.length; i++) { - var axi = gd._fullLayout[updatedAxisIds[i] + 'axis']; - aobj[axi._name + '.range[0]'] = axi.range[0]; - aobj[axi._name + '.range[1]'] = axi.range[1]; - - axi.range = axi._r.slice(); - } - - return Plotly.relayout(gd, aobj).then(function() { - for(var i = 0; i < affectedSubplots.length; i++) { - unsetSubplotTransform(affectedSubplots[i]); - } - }); - } + return Plotly.relayout(gd, aobj).then(function () { + for (var i = 0; i < affectedSubplots.length; i++) { + unsetSubplotTransform(affectedSubplots[i]) + } + }) + } - var t1, t2, raf; - var easeFn = d3.ease(transitionOpts.easing); + var t1, t2, raf + var easeFn = d3.ease(transitionOpts.easing) - gd._transitionData._interruptCallbacks.push(function() { - window.cancelAnimationFrame(raf); - raf = null; - return transitionInterrupt(); - }); + gd._transitionData._interruptCallbacks.push(function () { + window.cancelAnimationFrame(raf) + raf = null + return transitionInterrupt() + }) - function doFrame() { - t2 = Date.now(); + function doFrame () { + t2 = Date.now() - var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration); - var progress = easeFn(tInterp); + var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration) + var progress = easeFn(tInterp) - for(var i = 0; i < affectedSubplots.length; i++) { - updateSubplot(affectedSubplots[i], progress); - } + for (var i = 0; i < affectedSubplots.length; i++) { + updateSubplot(affectedSubplots[i], progress) + } - if(t2 - t1 > transitionOpts.duration) { - transitionComplete(); - raf = window.cancelAnimationFrame(doFrame); - } else { - raf = window.requestAnimationFrame(doFrame); - } + if (t2 - t1 > transitionOpts.duration) { + transitionComplete() + raf = window.cancelAnimationFrame(doFrame) + } else { + raf = window.requestAnimationFrame(doFrame) } + } - t1 = Date.now(); - raf = window.requestAnimationFrame(doFrame); + t1 = Date.now() + raf = window.requestAnimationFrame(doFrame) - return Promise.resolve(); -}; + return Promise.resolve() +} diff --git a/src/plots/command.js b/src/plots/command.js index 62c060edd5e..3fd3e78a02a 100644 --- a/src/plots/command.js +++ b/src/plots/command.js @@ -6,11 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Plotly = require('../plotly'); -var Lib = require('../lib'); +var Plotly = require('../plotly') +var Lib = require('../lib') /* * Create or update an observer. This function is designed to be @@ -28,112 +27,110 @@ var Lib = require('../lib'); * A listener called when the value is changed. Receives data object * with information about the new state. */ -exports.manageCommandObserver = function(gd, container, commandList, onchange) { - var ret = {}; - var enabled = true; +exports.manageCommandObserver = function (gd, container, commandList, onchange) { + var ret = {} + var enabled = true - if(container && container._commandObserver) { - ret = container._commandObserver; - } + if (container && container._commandObserver) { + ret = container._commandObserver + } - if(!ret.cache) { - ret.cache = {}; - } + if (!ret.cache) { + ret.cache = {} + } // Either create or just recompute this: - ret.lookupTable = {}; + ret.lookupTable = {} - var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable); + var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable) - if(container && container._commandObserver) { - if(!binding) { + if (container && container._commandObserver) { + if (!binding) { // If container exists and there are no longer any bindings, // remove existing: - if(container._commandObserver.remove) { - container._commandObserver.remove(); - container._commandObserver = null; - return ret; - } - } else { + if (container._commandObserver.remove) { + container._commandObserver.remove() + container._commandObserver = null + return ret + } + } else { // If container exists and there *are* bindings, then the lookup // table should have been updated and check is already attached, // so there's nothing to be done: - return ret; - - - } + return ret } + } // Determine whether there's anything to do for this binding: - if(binding) { + if (binding) { // Build the cache: - bindingValueHasChanged(gd, binding, ret.cache); + bindingValueHasChanged(gd, binding, ret.cache) - ret.check = function check() { - if(!enabled) return; + ret.check = function check () { + if (!enabled) return - var update = bindingValueHasChanged(gd, binding, ret.cache); + var update = bindingValueHasChanged(gd, binding, ret.cache) - if(update.changed && onchange) { + if (update.changed && onchange) { // Disable checks for the duration of this command in order to avoid // infinite loops: - if(ret.lookupTable[update.value] !== undefined) { - ret.disable(); - Promise.resolve(onchange({ - value: update.value, - type: binding.type, - prop: binding.prop, - traces: binding.traces, - index: ret.lookupTable[update.value] - })).then(ret.enable, ret.enable); - } - } - - return update.changed; - }; + if (ret.lookupTable[update.value] !== undefined) { + ret.disable() + Promise.resolve(onchange({ + value: update.value, + type: binding.type, + prop: binding.prop, + traces: binding.traces, + index: ret.lookupTable[update.value] + })).then(ret.enable, ret.enable) + } + } - var checkEvents = [ - 'plotly_relayout', - 'plotly_redraw', - 'plotly_restyle', - 'plotly_update', - 'plotly_animatingframe', - 'plotly_afterplot' - ]; + return update.changed + } - for(var i = 0; i < checkEvents.length; i++) { - gd._internalOn(checkEvents[i], ret.check); - } + var checkEvents = [ + 'plotly_relayout', + 'plotly_redraw', + 'plotly_restyle', + 'plotly_update', + 'plotly_animatingframe', + 'plotly_afterplot' + ] + + for (var i = 0; i < checkEvents.length; i++) { + gd._internalOn(checkEvents[i], ret.check) + } - ret.remove = function() { - for(var i = 0; i < checkEvents.length; i++) { - gd._removeInternalListener(checkEvents[i], ret.check); - } - }; - } else { + ret.remove = function () { + for (var i = 0; i < checkEvents.length; i++) { + gd._removeInternalListener(checkEvents[i], ret.check) + } + } + } else { // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning // is a start - Lib.warn('Unable to automatically bind plot updates to API command'); + Lib.warn('Unable to automatically bind plot updates to API command') - ret.lookupTable = {}; - ret.remove = function() {}; - } + ret.lookupTable = {} + ret.remove = function () {} + } - ret.disable = function disable() { - enabled = false; - }; + ret.disable = function disable () { + enabled = false + } - ret.enable = function enable() { - enabled = true; - }; + ret.enable = function enable () { + enabled = true + } - if(container) { - container._commandObserver = ret; - } + if (container) { + container._commandObserver = ret + } - return ret; -}; + return ret +} /* * This function checks to see if an array of objects containing @@ -144,108 +141,108 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) { * 2. only one property may be affected * 3. the same property must be affected by all commands */ -exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue) { - var i; - var n = commandList.length; +exports.hasSimpleAPICommandBindings = function (gd, commandList, bindingsByValue) { + var i + var n = commandList.length - var refBinding; + var refBinding - for(i = 0; i < n; i++) { - var binding; - var command = commandList[i]; - var method = command.method; - var args = command.args; + for (i = 0; i < n; i++) { + var binding + var command = commandList[i] + var method = command.method + var args = command.args // If any command has no method, refuse to bind: - if(!method) { - return false; - } - var bindings = exports.computeAPICommandBindings(gd, method, args); + if (!method) { + return false + } + var bindings = exports.computeAPICommandBindings(gd, method, args) // Right now, handle one and *only* one property being set: - if(bindings.length !== 1) { - return false; - } + if (bindings.length !== 1) { + return false + } - if(!refBinding) { - refBinding = bindings[0]; - if(Array.isArray(refBinding.traces)) { - refBinding.traces.sort(); + if (!refBinding) { + refBinding = bindings[0] + if (Array.isArray(refBinding.traces)) { + refBinding.traces.sort() + } + } else { + binding = bindings[0] + if (binding.type !== refBinding.type) { + return false + } + if (binding.prop !== refBinding.prop) { + return false + } + if (Array.isArray(refBinding.traces)) { + if (Array.isArray(binding.traces)) { + binding.traces.sort() + for (var j = 0; j < refBinding.traces.length; j++) { + if (refBinding.traces[j] !== binding.traces[j]) { + return false } + } } else { - binding = bindings[0]; - if(binding.type !== refBinding.type) { - return false; - } - if(binding.prop !== refBinding.prop) { - return false; - } - if(Array.isArray(refBinding.traces)) { - if(Array.isArray(binding.traces)) { - binding.traces.sort(); - for(var j = 0; j < refBinding.traces.length; j++) { - if(refBinding.traces[j] !== binding.traces[j]) { - return false; - } - } - } else { - return false; - } - } else { - if(binding.prop !== refBinding.prop) { - return false; - } - } - } - - binding = bindings[0]; - var value = binding.value; - if(Array.isArray(value)) { - if(value.length === 1) { - value = value[0]; - } else { - return false; - } + return false } - if(bindingsByValue) { - bindingsByValue[value] = i; + } else { + if (binding.prop !== refBinding.prop) { + return false } + } } - return refBinding; -}; + binding = bindings[0] + var value = binding.value + if (Array.isArray(value)) { + if (value.length === 1) { + value = value[0] + } else { + return false + } + } + if (bindingsByValue) { + bindingsByValue[value] = i + } + } + + return refBinding +} -function bindingValueHasChanged(gd, binding, cache) { - var container, value, obj; - var changed = false; +function bindingValueHasChanged (gd, binding, cache) { + var container, value, obj + var changed = false - if(binding.type === 'data') { + if (binding.type === 'data') { // If it's data, we need to get a trace. Based on the limited scope // of what we cover, we can just take the first trace from the list, // or otherwise just the first trace: - container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0]; - } else if(binding.type === 'layout') { - container = gd._fullLayout; - } else { - return false; - } + container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0] + } else if (binding.type === 'layout') { + container = gd._fullLayout + } else { + return false + } - value = Lib.nestedProperty(container, binding.prop).get(); + value = Lib.nestedProperty(container, binding.prop).get() - obj = cache[binding.type] = cache[binding.type] || {}; + obj = cache[binding.type] = cache[binding.type] || {} - if(obj.hasOwnProperty(binding.prop)) { - if(obj[binding.prop] !== value) { - changed = true; - } + if (obj.hasOwnProperty(binding.prop)) { + if (obj[binding.prop] !== value) { + changed = true } + } - obj[binding.prop] = value; + obj[binding.prop] = value - return { - changed: changed, - value: value - }; + return { + changed: changed, + value: value + } } /* @@ -259,157 +256,157 @@ function bindingValueHasChanged(gd, binding, cache) { * @param {array} args * A list of arguments passed to the API command */ -exports.executeAPICommand = function(gd, method, args) { - var apiMethod = Plotly[method]; - - var allArgs = [gd]; - for(var i = 0; i < args.length; i++) { - allArgs.push(args[i]); - } +exports.executeAPICommand = function (gd, method, args) { + var apiMethod = Plotly[method] + + var allArgs = [gd] + for (var i = 0; i < args.length; i++) { + allArgs.push(args[i]) + } + + return apiMethod.apply(null, allArgs).catch(function (err) { + Lib.warn('API call to Plotly.' + method + ' rejected.', err) + return Promise.reject(err) + }) +} - return apiMethod.apply(null, allArgs).catch(function(err) { - Lib.warn('API call to Plotly.' + method + ' rejected.', err); - return Promise.reject(err); - }); -}; - -exports.computeAPICommandBindings = function(gd, method, args) { - var bindings; - switch(method) { - case 'restyle': - bindings = computeDataBindings(gd, args); - break; - case 'relayout': - bindings = computeLayoutBindings(gd, args); - break; - case 'update': - bindings = computeDataBindings(gd, [args[0], args[2]]) - .concat(computeLayoutBindings(gd, [args[1]])); - break; - case 'animate': - bindings = computeAnimateBindings(gd, args); - break; - default: +exports.computeAPICommandBindings = function (gd, method, args) { + var bindings + switch (method) { + case 'restyle': + bindings = computeDataBindings(gd, args) + break + case 'relayout': + bindings = computeLayoutBindings(gd, args) + break + case 'update': + bindings = computeDataBindings(gd, [args[0], args[2]]) + .concat(computeLayoutBindings(gd, [args[1]])) + break + case 'animate': + bindings = computeAnimateBindings(gd, args) + break + default: // This is the case where intelligent logic about what affects // this command is not implemented. It causes no ill effects. // For example, addFrames simply won't bind to a control component. - bindings = []; - } - return bindings; -}; + bindings = [] + } + return bindings +} -function computeAnimateBindings(gd, args) { +function computeAnimateBindings (gd, args) { // We'll assume that the only relevant modification an animation // makes that's meaningfully tracked is the frame: - if(Array.isArray(args[0]) && args[0].length === 1 && ['string', 'number'].indexOf(typeof args[0][0]) !== -1) { - return [{type: 'layout', prop: '_currentFrame', value: args[0][0].toString()}]; - } else { - return []; - } + if (Array.isArray(args[0]) && args[0].length === 1 && ['string', 'number'].indexOf(typeof args[0][0]) !== -1) { + return [{type: 'layout', prop: '_currentFrame', value: args[0][0].toString()}] + } else { + return [] + } } -function computeLayoutBindings(gd, args) { - var bindings = []; +function computeLayoutBindings (gd, args) { + var bindings = [] - var astr = args[0]; - var aobj = {}; - if(typeof astr === 'string') { - aobj[astr] = args[1]; - } else if(Lib.isPlainObject(astr)) { - aobj = astr; - } else { - return bindings; - } + var astr = args[0] + var aobj = {} + if (typeof astr === 'string') { + aobj[astr] = args[1] + } else if (Lib.isPlainObject(astr)) { + aobj = astr + } else { + return bindings + } - crawl(aobj, function(path, attrName, attr) { - bindings.push({type: 'layout', prop: path, value: attr}); - }, '', 0); + crawl(aobj, function (path, attrName, attr) { + bindings.push({type: 'layout', prop: path, value: attr}) + }, '', 0) - return bindings; + return bindings } -function computeDataBindings(gd, args) { - var traces, astr, val, aobj; - var bindings = []; +function computeDataBindings (gd, args) { + var traces, astr, val, aobj + var bindings = [] // Logic copied from Plotly.restyle: - astr = args[0]; - val = args[1]; - traces = args[2]; - aobj = {}; - if(typeof astr === 'string') { - aobj[astr] = val; - } else if(Lib.isPlainObject(astr)) { + astr = args[0] + val = args[1] + traces = args[2] + aobj = {} + if (typeof astr === 'string') { + aobj[astr] = val + } else if (Lib.isPlainObject(astr)) { // the 3-arg form - aobj = astr; + aobj = astr - if(traces === undefined) { - traces = val; - } - } else { - return bindings; + if (traces === undefined) { + traces = val } + } else { + return bindings + } - if(traces === undefined) { + if (traces === undefined) { // Explicitly assign this to null instead of undefined: - traces = null; + traces = null + } + + crawl(aobj, function (path, attrName, attr) { + var thisTraces + if (Array.isArray(attr)) { + var nAttr = Math.min(attr.length, gd.data.length) + if (traces) { + nAttr = Math.min(nAttr, traces.length) + } + thisTraces = [] + for (var j = 0; j < nAttr; j++) { + thisTraces[j] = traces ? traces[j] : j + } + } else { + thisTraces = traces ? traces.slice(0) : null } - crawl(aobj, function(path, attrName, attr) { - var thisTraces; - if(Array.isArray(attr)) { - var nAttr = Math.min(attr.length, gd.data.length); - if(traces) { - nAttr = Math.min(nAttr, traces.length); - } - thisTraces = []; - for(var j = 0; j < nAttr; j++) { - thisTraces[j] = traces ? traces[j] : j; - } - } else { - thisTraces = traces ? traces.slice(0) : null; - } - // Convert [7] to just 7 when traces is null: - if(thisTraces === null) { - if(Array.isArray(attr)) { - attr = attr[0]; - } - } else if(Array.isArray(thisTraces)) { - if(!Array.isArray(attr)) { - var tmp = attr; - attr = []; - for(var i = 0; i < thisTraces.length; i++) { - attr[i] = tmp; - } - } - attr.length = Math.min(thisTraces.length, attr.length); + if (thisTraces === null) { + if (Array.isArray(attr)) { + attr = attr[0] + } + } else if (Array.isArray(thisTraces)) { + if (!Array.isArray(attr)) { + var tmp = attr + attr = [] + for (var i = 0; i < thisTraces.length; i++) { + attr[i] = tmp } + } + attr.length = Math.min(thisTraces.length, attr.length) + } - bindings.push({ - type: 'data', - prop: path, - traces: thisTraces, - value: attr - }); - }, '', 0); + bindings.push({ + type: 'data', + prop: path, + traces: thisTraces, + value: attr + }) + }, '', 0) - return bindings; + return bindings } -function crawl(attrs, callback, path, depth) { - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; +function crawl (attrs, callback, path, depth) { + Object.keys(attrs).forEach(function (attrName) { + var attr = attrs[attrName] - if(attrName[0] === '_') return; + if (attrName[0] === '_') return - var thisPath = path + (depth > 0 ? '.' : '') + attrName; + var thisPath = path + (depth > 0 ? '.' : '') + attrName - if(Lib.isPlainObject(attr)) { - crawl(attr, callback, thisPath, depth + 1); - } else { + if (Lib.isPlainObject(attr)) { + crawl(attr, callback, thisPath, depth + 1) + } else { // Only execute the callback on leaf nodes: - callback(thisPath, attrName, attr); - } - }); + callback(thisPath, attrName, attr) + } + }) } diff --git a/src/plots/font_attributes.js b/src/plots/font_attributes.js index daf2490c563..d3ad9364ba7 100644 --- a/src/plots/font_attributes.js +++ b/src/plots/font_attributes.js @@ -6,35 +6,34 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - family: { - valType: 'string', - role: 'style', - noBlank: true, - strict: true, - description: [ - 'HTML font family - the typeface that will be applied by the web browser.', - 'The web browser will only be able to apply a font if it is available on the system', - 'which it operates. Provide multiple font families, separated by commas, to indicate', - 'the preference in which to apply fonts if they aren\'t available on the system.', - 'The plotly service (at https://plot.ly or on-premise) generates images on a server,', - 'where only a select number of', - 'fonts are installed and supported.', - 'These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*,', - '*Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*,', - '*PT Sans Narrow*, *Raleway*, *Times New Roman*.' - ].join(' ') - }, - size: { - valType: 'number', - role: 'style', - min: 1 - }, - color: { - valType: 'color', - role: 'style' - } -}; + family: { + valType: 'string', + role: 'style', + noBlank: true, + strict: true, + description: [ + 'HTML font family - the typeface that will be applied by the web browser.', + 'The web browser will only be able to apply a font if it is available on the system', + 'which it operates. Provide multiple font families, separated by commas, to indicate', + 'the preference in which to apply fonts if they aren\'t available on the system.', + 'The plotly service (at https://plot.ly or on-premise) generates images on a server,', + 'where only a select number of', + 'fonts are installed and supported.', + 'These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*,', + '*Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*,', + '*PT Sans Narrow*, *Raleway*, *Times New Roman*.' + ].join(' ') + }, + size: { + valType: 'number', + role: 'style', + min: 1 + }, + color: { + valType: 'color', + role: 'style' + } +} diff --git a/src/plots/frame_attributes.js b/src/plots/frame_attributes.js index cfb57e1b689..c06d2b1a4f0 100644 --- a/src/plots/frame_attributes.js +++ b/src/plots/frame_attributes.js @@ -6,55 +6,55 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - _isLinkedToArray: 'frames_entry', + _isLinkedToArray: 'frames_entry', - group: { - valType: 'string', - role: 'info', - description: [ - 'An identifier that specifies the group to which the frame belongs,', - 'used by animate to select a subset of frames.' - ].join(' ') - }, - name: { - valType: 'string', - role: 'info', - description: 'A label by which to identify the frame' - }, - traces: { - valType: 'any', - role: 'info', - description: [ - 'A list of trace indices that identify the respective traces in the', - 'data attribute' - ].join(' ') - }, - baseframe: { - valType: 'string', - role: 'info', - description: [ - 'The name of the frame into which this frame\'s properties are merged', - 'before applying. This is used to unify properties and avoid needing', - 'to specify the same values for the same properties in multiple frames.' - ].join(' ') - }, - data: { - valType: 'any', - role: 'object', - description: [ - 'A list of traces this frame modifies. The format is identical to the', - 'normal trace definition.' - ].join(' ') - }, - layout: { - valType: 'any', - role: 'object', - description: [ - 'Layout properties which this frame modifies. The format is identical', - 'to the normal layout definition.' - ].join(' ') - } -}; + group: { + valType: 'string', + role: 'info', + description: [ + 'An identifier that specifies the group to which the frame belongs,', + 'used by animate to select a subset of frames.' + ].join(' ') + }, + name: { + valType: 'string', + role: 'info', + description: 'A label by which to identify the frame' + }, + traces: { + valType: 'any', + role: 'info', + description: [ + 'A list of trace indices that identify the respective traces in the', + 'data attribute' + ].join(' ') + }, + baseframe: { + valType: 'string', + role: 'info', + description: [ + 'The name of the frame into which this frame\'s properties are merged', + 'before applying. This is used to unify properties and avoid needing', + 'to specify the same values for the same properties in multiple frames.' + ].join(' ') + }, + data: { + valType: 'any', + role: 'object', + description: [ + 'A list of traces this frame modifies. The format is identical to the', + 'normal trace definition.' + ].join(' ') + }, + layout: { + valType: 'any', + role: 'object', + description: [ + 'Layout properties which this frame modifies. The format is identical', + 'to the normal layout definition.' + ].join(' ') + } +} diff --git a/src/plots/geo/constants.js b/src/plots/geo/constants.js index 92564b4e485..bd74a8744c8 100644 --- a/src/plots/geo/constants.js +++ b/src/plots/geo/constants.js @@ -6,151 +6,150 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var params = module.exports = {}; +var params = module.exports = {} // projection names to d3 function name params.projNames = { // d3.geo.projection - 'equirectangular': 'equirectangular', - 'mercator': 'mercator', - 'orthographic': 'orthographic', - 'natural earth': 'naturalEarth', - 'kavrayskiy7': 'kavrayskiy7', - 'miller': 'miller', - 'robinson': 'robinson', - 'eckert4': 'eckert4', - 'azimuthal equal area': 'azimuthalEqualArea', - 'azimuthal equidistant': 'azimuthalEquidistant', - 'conic equal area': 'conicEqualArea', - 'conic conformal': 'conicConformal', - 'conic equidistant': 'conicEquidistant', - 'gnomonic': 'gnomonic', - 'stereographic': 'stereographic', - 'mollweide': 'mollweide', - 'hammer': 'hammer', - 'transverse mercator': 'transverseMercator', - 'albers usa': 'albersUsa', - 'winkel tripel': 'winkel3' -}; + 'equirectangular': 'equirectangular', + 'mercator': 'mercator', + 'orthographic': 'orthographic', + 'natural earth': 'naturalEarth', + 'kavrayskiy7': 'kavrayskiy7', + 'miller': 'miller', + 'robinson': 'robinson', + 'eckert4': 'eckert4', + 'azimuthal equal area': 'azimuthalEqualArea', + 'azimuthal equidistant': 'azimuthalEquidistant', + 'conic equal area': 'conicEqualArea', + 'conic conformal': 'conicConformal', + 'conic equidistant': 'conicEquidistant', + 'gnomonic': 'gnomonic', + 'stereographic': 'stereographic', + 'mollweide': 'mollweide', + 'hammer': 'hammer', + 'transverse mercator': 'transverseMercator', + 'albers usa': 'albersUsa', + 'winkel tripel': 'winkel3' +} // name of the axes -params.axesNames = ['lonaxis', 'lataxis']; +params.axesNames = ['lonaxis', 'lataxis'] // max longitudinal angular span (EXPERIMENTAL) params.lonaxisSpan = { - 'orthographic': 180, - 'azimuthal equal area': 360, - 'azimuthal equidistant': 360, - 'conic conformal': 180, - 'gnomonic': 160, - 'stereographic': 180, - 'transverse mercator': 180, - '*': 360 -}; + 'orthographic': 180, + 'azimuthal equal area': 360, + 'azimuthal equidistant': 360, + 'conic conformal': 180, + 'gnomonic': 160, + 'stereographic': 180, + 'transverse mercator': 180, + '*': 360 +} // max latitudinal angular span (EXPERIMENTAL) params.lataxisSpan = { - 'conic conformal': 150, - 'stereographic': 179.5, - '*': 180 -}; + 'conic conformal': 150, + 'stereographic': 179.5, + '*': 180 +} // defaults for each scope params.scopeDefaults = { - world: { - lonaxisRange: [-180, 180], - lataxisRange: [-90, 90], - projType: 'equirectangular', - projRotate: [0, 0, 0] - }, - usa: { - lonaxisRange: [-180, -50], - lataxisRange: [15, 80], - projType: 'albers usa' - }, - europe: { - lonaxisRange: [-30, 60], - lataxisRange: [30, 80], - projType: 'conic conformal', - projRotate: [15, 0, 0], - projParallels: [0, 60] - }, - asia: { - lonaxisRange: [22, 160], - lataxisRange: [-15, 55], - projType: 'mercator', - projRotate: [0, 0, 0] - }, - africa: { - lonaxisRange: [-30, 60], - lataxisRange: [-40, 40], - projType: 'mercator', - projRotate: [0, 0, 0] - }, - 'north america': { - lonaxisRange: [-180, -45], - lataxisRange: [5, 85], - projType: 'conic conformal', - projRotate: [-100, 0, 0], - projParallels: [29.5, 45.5] - }, - 'south america': { - lonaxisRange: [-100, -30], - lataxisRange: [-60, 15], - projType: 'mercator', - projRotate: [0, 0, 0] - } -}; + world: { + lonaxisRange: [-180, 180], + lataxisRange: [-90, 90], + projType: 'equirectangular', + projRotate: [0, 0, 0] + }, + usa: { + lonaxisRange: [-180, -50], + lataxisRange: [15, 80], + projType: 'albers usa' + }, + europe: { + lonaxisRange: [-30, 60], + lataxisRange: [30, 80], + projType: 'conic conformal', + projRotate: [15, 0, 0], + projParallels: [0, 60] + }, + asia: { + lonaxisRange: [22, 160], + lataxisRange: [-15, 55], + projType: 'mercator', + projRotate: [0, 0, 0] + }, + africa: { + lonaxisRange: [-30, 60], + lataxisRange: [-40, 40], + projType: 'mercator', + projRotate: [0, 0, 0] + }, + 'north america': { + lonaxisRange: [-180, -45], + lataxisRange: [5, 85], + projType: 'conic conformal', + projRotate: [-100, 0, 0], + projParallels: [29.5, 45.5] + }, + 'south america': { + lonaxisRange: [-100, -30], + lataxisRange: [-60, 15], + projType: 'mercator', + projRotate: [0, 0, 0] + } +} // angular pad to avoid rounding error around clip angles -params.clipPad = 1e-3; +params.clipPad = 1e-3 // map projection precision -params.precision = 0.1; +params.precision = 0.1 // default land and water fill colors -params.landColor = '#F0DC82'; -params.waterColor = '#3399FF'; +params.landColor = '#F0DC82' +params.waterColor = '#3399FF' // locationmode to layer name params.locationmodeToLayer = { - 'ISO-3': 'countries', - 'USA-states': 'subunits', - 'country names': 'countries' -}; + 'ISO-3': 'countries', + 'USA-states': 'subunits', + 'country names': 'countries' +} // SVG element for a sphere (use to frame maps) -params.sphereSVG = {type: 'Sphere'}; +params.sphereSVG = {type: 'Sphere'} // N.B. base layer names must be the same as in the topojson files // base layer with a fill color -params.fillLayers = ['ocean', 'land', 'lakes']; +params.fillLayers = ['ocean', 'land', 'lakes'] // base layer with a only a line color -params.lineLayers = ['subunits', 'countries', 'coastlines', 'rivers', 'frame']; +params.lineLayers = ['subunits', 'countries', 'coastlines', 'rivers', 'frame'] // all base layers - in order params.baseLayers = [ - 'ocean', 'land', 'lakes', - 'subunits', 'countries', 'coastlines', 'rivers', - 'lataxis', 'lonaxis', - 'frame' -]; + 'ocean', 'land', 'lakes', + 'subunits', 'countries', 'coastlines', 'rivers', + 'lataxis', 'lonaxis', + 'frame' +] params.layerNameToAdjective = { - ocean: 'ocean', - land: 'land', - lakes: 'lake', - subunits: 'subunit', - countries: 'country', - coastlines: 'coastline', - rivers: 'river', - frame: 'frame' -}; + ocean: 'ocean', + land: 'land', + lakes: 'lake', + subunits: 'subunit', + countries: 'country', + coastlines: 'coastline', + rivers: 'river', + frame: 'frame' +} // base layers drawn over choropleth -params.baseLayersOverChoropleth = ['rivers', 'lakes']; +params.baseLayersOverChoropleth = ['rivers', 'lakes'] diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 75bd8e37261..e52f1c14ea8 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -6,231 +6,224 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /* global PlotlyGeoAssets:false */ -var d3 = require('d3'); +var d3 = require('d3') -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var Plots = require('../plots'); -var Axes = require('../cartesian/axes'); -var Fx = require('../cartesian/graph_interact'); +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var Plots = require('../plots') +var Axes = require('../cartesian/axes') +var Fx = require('../cartesian/graph_interact') -var addProjectionsToD3 = require('./projections'); -var createGeoScale = require('./set_scale'); -var createGeoZoom = require('./zoom'); -var createGeoZoomReset = require('./zoom_reset'); -var constants = require('./constants'); +var addProjectionsToD3 = require('./projections') +var createGeoScale = require('./set_scale') +var createGeoZoom = require('./zoom') +var createGeoZoomReset = require('./zoom_reset') +var constants = require('./constants') -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); -var topojsonUtils = require('../../lib/topojson_utils'); -var topojsonFeature = require('topojson-client').feature; +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') +var topojsonUtils = require('../../lib/topojson_utils') +var topojsonFeature = require('topojson-client').feature // add a few projection types to d3.geo -addProjectionsToD3(d3); - +addProjectionsToD3(d3) -function Geo(options, fullLayout) { - this.id = options.id; - this.graphDiv = options.graphDiv; - this.container = options.container; - this.topojsonURL = options.topojsonURL; +function Geo (options, fullLayout) { + this.id = options.id + this.graphDiv = options.graphDiv + this.container = options.container + this.topojsonURL = options.topojsonURL - this.hoverContainer = null; + this.hoverContainer = null - this.topojsonName = null; - this.topojson = null; + this.topojsonName = null + this.topojson = null - this.projectionType = null; - this.projection = null; + this.projectionType = null + this.projection = null - this.clipAngle = null; - this.setScale = null; - this.path = null; + this.clipAngle = null + this.setScale = null + this.path = null - this.zoom = null; - this.zoomReset = null; + this.zoom = null + this.zoomReset = null - this.xaxis = null; - this.yaxis = null; + this.xaxis = null + this.yaxis = null - this.makeFramework(); - this.updateFx(fullLayout.hovermode); + this.makeFramework() + this.updateFx(fullLayout.hovermode) - this.traceHash = {}; + this.traceHash = {} } -module.exports = Geo; +module.exports = Geo -var proto = Geo.prototype; +var proto = Geo.prototype -proto.plot = function(geoCalcData, fullLayout, promises) { - var _this = this, - geoLayout = fullLayout[_this.id], - graphSize = fullLayout._size; +proto.plot = function (geoCalcData, fullLayout, promises) { + var _this = this, + geoLayout = fullLayout[_this.id], + graphSize = fullLayout._size - var topojsonNameNew, topojsonPath; + var topojsonNameNew, topojsonPath // N.B. 'geoLayout' is unambiguous, no need for 'user' geo layout here // TODO don't reset projection on all graph edits - _this.projection = null; + _this.projection = null - _this.setScale = createGeoScale(geoLayout, graphSize); - _this.makeProjection(geoLayout); - _this.makePath(); - _this.adjustLayout(geoLayout, graphSize); + _this.setScale = createGeoScale(geoLayout, graphSize) + _this.makeProjection(geoLayout) + _this.makePath() + _this.adjustLayout(geoLayout, graphSize) - _this.zoom = createGeoZoom(_this, geoLayout); - _this.zoomReset = createGeoZoomReset(_this, geoLayout); - _this.mockAxis = createMockAxis(fullLayout); + _this.zoom = createGeoZoom(_this, geoLayout) + _this.zoomReset = createGeoZoomReset(_this, geoLayout) + _this.mockAxis = createMockAxis(fullLayout) - _this.framework + _this.framework .call(_this.zoom) - .on('dblclick.zoom', _this.zoomReset); + .on('dblclick.zoom', _this.zoomReset) - _this.framework.on('mousemove', function() { - var mouse = d3.mouse(this), - lonlat = _this.projection.invert(mouse); + _this.framework.on('mousemove', function () { + var mouse = d3.mouse(this), + lonlat = _this.projection.invert(mouse) - if(isNaN(lonlat[0]) || isNaN(lonlat[1])) return; + if (isNaN(lonlat[0]) || isNaN(lonlat[1])) return - var evt = { - target: true, - xpx: mouse[0], - ypx: mouse[1] - }; + var evt = { + target: true, + xpx: mouse[0], + ypx: mouse[1] + } - _this.xaxis.c2p = function() { return mouse[0]; }; - _this.xaxis.p2c = function() { return lonlat[0]; }; - _this.yaxis.c2p = function() { return mouse[1]; }; - _this.yaxis.p2c = function() { return lonlat[1]; }; + _this.xaxis.c2p = function () { return mouse[0] } + _this.xaxis.p2c = function () { return lonlat[0] } + _this.yaxis.c2p = function () { return mouse[1] } + _this.yaxis.p2c = function () { return lonlat[1] } - Fx.hover(_this.graphDiv, evt, _this.id); - }); + Fx.hover(_this.graphDiv, evt, _this.id) + }) - _this.framework.on('mouseout', function() { - Fx.loneUnhover(fullLayout._toppaper); - }); + _this.framework.on('mouseout', function () { + Fx.loneUnhover(fullLayout._toppaper) + }) - _this.framework.on('click', function() { - Fx.click(_this.graphDiv, { target: true }); - }); + _this.framework.on('click', function () { + Fx.click(_this.graphDiv, { target: true }) + }) - topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout); + topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout) - if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) { - _this.topojsonName = topojsonNameNew; + if (_this.topojson === null || topojsonNameNew !== _this.topojsonName) { + _this.topojsonName = topojsonNameNew - if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) { - _this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName]; - _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); - } - else { - topojsonPath = topojsonUtils.getTopojsonPath( + if (PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) { + _this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName] + _this.onceTopojsonIsLoaded(geoCalcData, geoLayout) + } else { + topojsonPath = topojsonUtils.getTopojsonPath( _this.topojsonURL, _this.topojsonName - ); - - promises.push(new Promise(function(resolve, reject) { - d3.json(topojsonPath, function(error, topojson) { - if(error) { - if(error.status === 404) { - reject(new Error([ - 'plotly.js could not find topojson file at', - topojsonPath, '.', - 'Make sure the *topojsonURL* plot config option', - 'is set properly.' - ].join(' '))); - } - else { - reject(new Error([ - 'unexpected error while fetching topojson file at', - topojsonPath - ].join(' '))); - } - return; - } - - _this.topojson = topojson; - PlotlyGeoAssets.topojson[_this.topojsonName] = topojson; - - _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); - resolve(); - }); - })); - } + ) + + promises.push(new Promise(function (resolve, reject) { + d3.json(topojsonPath, function (error, topojson) { + if (error) { + if (error.status === 404) { + reject(new Error([ + 'plotly.js could not find topojson file at', + topojsonPath, '.', + 'Make sure the *topojsonURL* plot config option', + 'is set properly.' + ].join(' '))) + } else { + reject(new Error([ + 'unexpected error while fetching topojson file at', + topojsonPath + ].join(' '))) + } + return + } + + _this.topojson = topojson + PlotlyGeoAssets.topojson[_this.topojsonName] = topojson + + _this.onceTopojsonIsLoaded(geoCalcData, geoLayout) + resolve() + }) + })) } - else _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); + } else _this.onceTopojsonIsLoaded(geoCalcData, geoLayout) // TODO handle topojson-is-loading case // to avoid making multiple request while streaming -}; +} -proto.onceTopojsonIsLoaded = function(geoCalcData, geoLayout) { - this.drawLayout(geoLayout); +proto.onceTopojsonIsLoaded = function (geoCalcData, geoLayout) { + this.drawLayout(geoLayout) - Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout); + Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout) - this.render(); -}; + this.render() +} -proto.updateFx = function(hovermode) { - this.showHover = (hovermode !== false); +proto.updateFx = function (hovermode) { + this.showHover = (hovermode !== false) // TODO should more strict, any layout.hovermode other // then false will make all geo subplot display hover text. // Instead each geo should have its own geo.hovermode // to control hover visibility independently of other subplots. -}; +} -proto.makeProjection = function(geoLayout) { - var projLayout = geoLayout.projection, - projType = projLayout.type, - isNew = this.projection === null || projType !== this.projectionType, - projection; +proto.makeProjection = function (geoLayout) { + var projLayout = geoLayout.projection, + projType = projLayout.type, + isNew = this.projection === null || projType !== this.projectionType, + projection - if(isNew) { - this.projectionType = projType; - projection = this.projection = d3.geo[constants.projNames[projType]](); - } - else projection = this.projection; + if (isNew) { + this.projectionType = projType + projection = this.projection = d3.geo[constants.projNames[projType]]() + } else projection = this.projection - projection + projection .translate(projLayout._translate0) - .precision(constants.precision); + .precision(constants.precision) - if(!geoLayout._isAlbersUsa) { - projection + if (!geoLayout._isAlbersUsa) { + projection .rotate(projLayout._rotate) - .center(projLayout._center); - } + .center(projLayout._center) + } - if(geoLayout._clipAngle) { - this.clipAngle = geoLayout._clipAngle; // needed in proto.render - projection - .clipAngle(geoLayout._clipAngle - constants.clipPad); - } - else this.clipAngle = null; // for graph edits + if (geoLayout._clipAngle) { + this.clipAngle = geoLayout._clipAngle // needed in proto.render + projection + .clipAngle(geoLayout._clipAngle - constants.clipPad) + } else this.clipAngle = null // for graph edits - if(projLayout.parallels) { - projection - .parallels(projLayout.parallels); - } + if (projLayout.parallels) { + projection + .parallels(projLayout.parallels) + } - if(isNew) this.setScale(projection); + if (isNew) this.setScale(projection) - projection + projection .translate(projLayout._translate) - .scale(projLayout._scale); -}; + .scale(projLayout._scale) +} -proto.makePath = function() { - this.path = d3.geo.path().projection(this.projection); -}; +proto.makePath = function () { + this.path = d3.geo.path().projection(this.projection) +} /* *
@@ -238,271 +231,267 @@ proto.makePath = function() { * * */ -proto.makeFramework = function() { - var geoDiv = this.geoDiv = d3.select(this.container).append('div'); - geoDiv +proto.makeFramework = function () { + var geoDiv = this.geoDiv = d3.select(this.container).append('div') + geoDiv .attr('id', this.id) - .style('position', 'absolute'); + .style('position', 'absolute') // only choropleth traces use this, // scattergeo traces use Fx.hover and fullLayout._hoverlayer - var hoverContainer = this.hoverContainer = geoDiv.append('svg'); - hoverContainer + var hoverContainer = this.hoverContainer = geoDiv.append('svg') + hoverContainer .attr(xmlnsNamespaces.svgAttrs) .style({ - 'position': 'absolute', - 'z-index': 20, - 'pointer-events': 'none' - }); + 'position': 'absolute', + 'z-index': 20, + 'pointer-events': 'none' + }) - var framework = this.framework = geoDiv.append('svg'); - framework + var framework = this.framework = geoDiv.append('svg') + framework .attr(xmlnsNamespaces.svgAttrs) .attr({ - 'position': 'absolute', - 'preserveAspectRatio': 'none' - }); + 'position': 'absolute', + 'preserveAspectRatio': 'none' + }) - framework.append('g').attr('class', 'bglayer') - .append('rect'); + framework.append('g').attr('class', 'bglayer') + .append('rect') - framework.append('g').attr('class', 'baselayer'); - framework.append('g').attr('class', 'choroplethlayer'); - framework.append('g').attr('class', 'baselayeroverchoropleth'); - framework.append('g').attr('class', 'scattergeolayer'); + framework.append('g').attr('class', 'baselayer') + framework.append('g').attr('class', 'choroplethlayer') + framework.append('g').attr('class', 'baselayeroverchoropleth') + framework.append('g').attr('class', 'scattergeolayer') // N.B. disable dblclick zoom default - framework.on('dblclick.zoom', null); + framework.on('dblclick.zoom', null) // TODO use clip paths instead of nested SVG - this.xaxis = { _id: 'x' }; - this.yaxis = { _id: 'y' }; -}; + this.xaxis = { _id: 'x' } + this.yaxis = { _id: 'y' } +} -proto.adjustLayout = function(geoLayout, graphSize) { - var domain = geoLayout.domain; +proto.adjustLayout = function (geoLayout, graphSize) { + var domain = geoLayout.domain - var left = graphSize.l + graphSize.w * domain.x[0] + geoLayout._marginX, - top = graphSize.t + graphSize.h * (1 - domain.y[1]) + geoLayout._marginY; + var left = graphSize.l + graphSize.w * domain.x[0] + geoLayout._marginX, + top = graphSize.t + graphSize.h * (1 - domain.y[1]) + geoLayout._marginY - this.geoDiv.style({ - left: left + 'px', - top: top + 'px', - width: geoLayout._width + 'px', - height: geoLayout._height + 'px' - }); + this.geoDiv.style({ + left: left + 'px', + top: top + 'px', + width: geoLayout._width + 'px', + height: geoLayout._height + 'px' + }) - this.hoverContainer.attr({ - width: geoLayout._width, - height: geoLayout._height - }); + this.hoverContainer.attr({ + width: geoLayout._width, + height: geoLayout._height + }) - this.framework.attr({ - width: geoLayout._width, - height: geoLayout._height - }); + this.framework.attr({ + width: geoLayout._width, + height: geoLayout._height + }) - this.framework.select('.bglayer').select('rect') + this.framework.select('.bglayer').select('rect') .attr({ - width: geoLayout._width, - height: geoLayout._height + width: geoLayout._width, + height: geoLayout._height }) - .call(Color.fill, geoLayout.bgcolor); + .call(Color.fill, geoLayout.bgcolor) - this.xaxis._offset = left; - this.xaxis._length = geoLayout._width; + this.xaxis._offset = left + this.xaxis._length = geoLayout._width - this.yaxis._offset = top; - this.yaxis._length = geoLayout._height; -}; + this.yaxis._offset = top + this.yaxis._length = geoLayout._height +} -proto.drawTopo = function(selection, layerName, geoLayout) { - if(geoLayout['show' + layerName] !== true) return; +proto.drawTopo = function (selection, layerName, geoLayout) { + if (geoLayout['show' + layerName] !== true) return - var topojson = this.topojson, - datum = layerName === 'frame' ? + var topojson = this.topojson, + datum = layerName === 'frame' ? constants.sphereSVG : - topojsonFeature(topojson, topojson.objects[layerName]); + topojsonFeature(topojson, topojson.objects[layerName]) - selection.append('g') + selection.append('g') .datum(datum) .attr('class', layerName) .append('path') - .attr('class', 'basepath'); -}; + .attr('class', 'basepath') +} -function makeGraticule(lonaxisRange, lataxisRange, step) { - return d3.geo.graticule() +function makeGraticule (lonaxisRange, lataxisRange, step) { + return d3.geo.graticule() .extent([ [lonaxisRange[0], lataxisRange[0]], [lonaxisRange[1], lataxisRange[1]] ]) - .step(step); + .step(step) } -proto.drawGraticule = function(selection, axisName, geoLayout) { - var axisLayout = geoLayout[axisName]; +proto.drawGraticule = function (selection, axisName, geoLayout) { + var axisLayout = geoLayout[axisName] - if(axisLayout.showgrid !== true) return; + if (axisLayout.showgrid !== true) return - var scopeDefaults = constants.scopeDefaults[geoLayout.scope], - lonaxisRange = scopeDefaults.lonaxisRange, - lataxisRange = scopeDefaults.lataxisRange, - step = axisName === 'lonaxis' ? + var scopeDefaults = constants.scopeDefaults[geoLayout.scope], + lonaxisRange = scopeDefaults.lonaxisRange, + lataxisRange = scopeDefaults.lataxisRange, + step = axisName === 'lonaxis' ? [axisLayout.dtick] : [0, axisLayout.dtick], - graticule = makeGraticule(lonaxisRange, lataxisRange, step); + graticule = makeGraticule(lonaxisRange, lataxisRange, step) - selection.append('g') + selection.append('g') .datum(graticule) .attr('class', axisName + 'graticule') .append('path') - .attr('class', 'graticulepath'); -}; + .attr('class', 'graticulepath') +} -proto.drawLayout = function(geoLayout) { - var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = constants.baseLayers, - axesNames = constants.axesNames, - layerName; +proto.drawLayout = function (geoLayout) { + var gBaseLayer = this.framework.select('g.baselayer'), + baseLayers = constants.baseLayers, + axesNames = constants.axesNames, + layerName // TODO move to more d3-idiomatic pattern (that's work on replot) // N.B. html('') does not work in IE11 - gBaseLayer.selectAll('*').remove(); + gBaseLayer.selectAll('*').remove() - for(var i = 0; i < baseLayers.length; i++) { - layerName = baseLayers[i]; + for (var i = 0; i < baseLayers.length; i++) { + layerName = baseLayers[i] - if(axesNames.indexOf(layerName) !== -1) { - this.drawGraticule(gBaseLayer, layerName, geoLayout); - } - else this.drawTopo(gBaseLayer, layerName, geoLayout); - } + if (axesNames.indexOf(layerName) !== -1) { + this.drawGraticule(gBaseLayer, layerName, geoLayout) + } else this.drawTopo(gBaseLayer, layerName, geoLayout) + } - this.styleLayout(geoLayout); -}; + this.styleLayout(geoLayout) +} -function styleFillLayer(selection, layerName, geoLayout) { - var layerAdj = constants.layerNameToAdjective[layerName]; +function styleFillLayer (selection, layerName, geoLayout) { + var layerAdj = constants.layerNameToAdjective[layerName] - selection.select('.' + layerName) + selection.select('.' + layerName) .selectAll('path') .attr('stroke', 'none') - .call(Color.fill, geoLayout[layerAdj + 'color']); + .call(Color.fill, geoLayout[layerAdj + 'color']) } -function styleLineLayer(selection, layerName, geoLayout) { - var layerAdj = constants.layerNameToAdjective[layerName]; +function styleLineLayer (selection, layerName, geoLayout) { + var layerAdj = constants.layerNameToAdjective[layerName] - selection.select('.' + layerName) + selection.select('.' + layerName) .selectAll('path') .attr('fill', 'none') .call(Color.stroke, geoLayout[layerAdj + 'color']) - .call(Drawing.dashLine, '', geoLayout[layerAdj + 'width']); + .call(Drawing.dashLine, '', geoLayout[layerAdj + 'width']) } -function styleGraticule(selection, axisName, geoLayout) { - selection.select('.' + axisName + 'graticule') +function styleGraticule (selection, axisName, geoLayout) { + selection.select('.' + axisName + 'graticule') .selectAll('path') .attr('fill', 'none') .call(Color.stroke, geoLayout[axisName].gridcolor) - .call(Drawing.dashLine, '', geoLayout[axisName].gridwidth); + .call(Drawing.dashLine, '', geoLayout[axisName].gridwidth) } -proto.styleLayer = function(selection, layerName, geoLayout) { - var fillLayers = constants.fillLayers, - lineLayers = constants.lineLayers; +proto.styleLayer = function (selection, layerName, geoLayout) { + var fillLayers = constants.fillLayers, + lineLayers = constants.lineLayers - if(fillLayers.indexOf(layerName) !== -1) { - styleFillLayer(selection, layerName, geoLayout); - } - else if(lineLayers.indexOf(layerName) !== -1) { - styleLineLayer(selection, layerName, geoLayout); - } -}; + if (fillLayers.indexOf(layerName) !== -1) { + styleFillLayer(selection, layerName, geoLayout) + } else if (lineLayers.indexOf(layerName) !== -1) { + styleLineLayer(selection, layerName, geoLayout) + } +} -proto.styleLayout = function(geoLayout) { - var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = constants.baseLayers, - axesNames = constants.axesNames, - layerName; +proto.styleLayout = function (geoLayout) { + var gBaseLayer = this.framework.select('g.baselayer'), + baseLayers = constants.baseLayers, + axesNames = constants.axesNames, + layerName - for(var i = 0; i < baseLayers.length; i++) { - layerName = baseLayers[i]; + for (var i = 0; i < baseLayers.length; i++) { + layerName = baseLayers[i] - if(axesNames.indexOf(layerName) !== -1) { - styleGraticule(gBaseLayer, layerName, geoLayout); - } - else this.styleLayer(gBaseLayer, layerName, geoLayout); - } -}; + if (axesNames.indexOf(layerName) !== -1) { + styleGraticule(gBaseLayer, layerName, geoLayout) + } else this.styleLayer(gBaseLayer, layerName, geoLayout) + } +} -proto.isLonLatOverEdges = function(lonlat) { - var clipAngle = this.clipAngle; +proto.isLonLatOverEdges = function (lonlat) { + var clipAngle = this.clipAngle - if(clipAngle === null) return false; + if (clipAngle === null) return false - var p = this.projection.rotate(), - angle = d3.geo.distance(lonlat, [-p[0], -p[1]]), - maxAngle = clipAngle * Math.PI / 180; + var p = this.projection.rotate(), + angle = d3.geo.distance(lonlat, [-p[0], -p[1]]), + maxAngle = clipAngle * Math.PI / 180 - return angle > maxAngle; -}; + return angle > maxAngle +} // [hot code path] (re)draw all paths which depend on the projection -proto.render = function() { - var _this = this, - framework = _this.framework, - gChoropleth = framework.select('g.choroplethlayer'), - gScatterGeo = framework.select('g.scattergeolayer'), - path = _this.path; - - function translatePoints(d) { - var lonlatPx = _this.projection(d.lonlat); - if(!lonlatPx) return null; - - return 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')'; - } +proto.render = function () { + var _this = this, + framework = _this.framework, + gChoropleth = framework.select('g.choroplethlayer'), + gScatterGeo = framework.select('g.scattergeolayer'), + path = _this.path + + function translatePoints (d) { + var lonlatPx = _this.projection(d.lonlat) + if (!lonlatPx) return null + + return 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')' + } // hide paths over edges of clipped projections - function hideShowPoints(d) { - return _this.isLonLatOverEdges(d.lonlat) ? '0' : '1.0'; - } + function hideShowPoints (d) { + return _this.isLonLatOverEdges(d.lonlat) ? '0' : '1.0' + } - framework.selectAll('path.basepath').attr('d', path); - framework.selectAll('path.graticulepath').attr('d', path); + framework.selectAll('path.basepath').attr('d', path) + framework.selectAll('path.graticulepath').attr('d', path) - gChoropleth.selectAll('path.choroplethlocation').attr('d', path); - gChoropleth.selectAll('path.basepath').attr('d', path); + gChoropleth.selectAll('path.choroplethlocation').attr('d', path) + gChoropleth.selectAll('path.basepath').attr('d', path) - gScatterGeo.selectAll('path.js-line').attr('d', path); + gScatterGeo.selectAll('path.js-line').attr('d', path) - if(_this.clipAngle !== null) { - gScatterGeo.selectAll('path.point') + if (_this.clipAngle !== null) { + gScatterGeo.selectAll('path.point') .style('opacity', hideShowPoints) - .attr('transform', translatePoints); - gScatterGeo.selectAll('text') + .attr('transform', translatePoints) + gScatterGeo.selectAll('text') .style('opacity', hideShowPoints) - .attr('transform', translatePoints); - } - else { - gScatterGeo.selectAll('path.point') - .attr('transform', translatePoints); - gScatterGeo.selectAll('text') - .attr('transform', translatePoints); - } -}; + .attr('transform', translatePoints) + } else { + gScatterGeo.selectAll('path.point') + .attr('transform', translatePoints) + gScatterGeo.selectAll('text') + .attr('transform', translatePoints) + } +} // create a mock axis used to format hover text -function createMockAxis(fullLayout) { - var mockAxis = { - type: 'linear', - showexponent: 'all', - exponentformat: Axes.layoutAttributes.exponentformat.dflt, - _gd: { _fullLayout: fullLayout } - }; - - Axes.setConvert(mockAxis); - return mockAxis; +function createMockAxis (fullLayout) { + var mockAxis = { + type: 'linear', + showexponent: 'all', + exponentformat: Axes.layoutAttributes.exponentformat.dflt, + _gd: { _fullLayout: fullLayout } + } + + Axes.setConvert(mockAxis) + return mockAxis } diff --git a/src/plots/geo/index.js b/src/plots/geo/index.js index 03cfbdf3393..48c7caa5adc 100644 --- a/src/plots/geo/index.js +++ b/src/plots/geo/index.js @@ -6,99 +6,97 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Geo = require('./geo') -var Geo = require('./geo'); +var Plots = require('../../plots/plots') -var Plots = require('../../plots/plots'); +exports.name = 'geo' +exports.attr = 'geo' -exports.name = 'geo'; +exports.idRoot = 'geo' -exports.attr = 'geo'; +exports.idRegex = /^geo([2-9]|[1-9][0-9]+)?$/ -exports.idRoot = 'geo'; +exports.attrRegex = /^geo([2-9]|[1-9][0-9]+)?$/ -exports.idRegex = /^geo([2-9]|[1-9][0-9]+)?$/; +exports.attributes = require('./layout/attributes') -exports.attrRegex = /^geo([2-9]|[1-9][0-9]+)?$/; +exports.layoutAttributes = require('./layout/layout_attributes') -exports.attributes = require('./layout/attributes'); +exports.supplyLayoutDefaults = require('./layout/defaults') -exports.layoutAttributes = require('./layout/layout_attributes'); - -exports.supplyLayoutDefaults = require('./layout/defaults'); - -exports.plot = function plotGeo(gd) { - var fullLayout = gd._fullLayout, - calcData = gd.calcdata, - geoIds = Plots.getSubplotIds(fullLayout, 'geo'); +exports.plot = function plotGeo (gd) { + var fullLayout = gd._fullLayout, + calcData = gd.calcdata, + geoIds = Plots.getSubplotIds(fullLayout, 'geo') /** * If 'plotly-geo-assets.js' is not included, * initialize object to keep reference to every loaded topojson */ - if(window.PlotlyGeoAssets === undefined) { - window.PlotlyGeoAssets = { topojson: {} }; - } + if (window.PlotlyGeoAssets === undefined) { + window.PlotlyGeoAssets = { topojson: {} } + } - for(var i = 0; i < geoIds.length; i++) { - var geoId = geoIds[i], - geoCalcData = Plots.getSubplotCalcData(calcData, 'geo', geoId), - geo = fullLayout[geoId]._subplot; + for (var i = 0; i < geoIds.length; i++) { + var geoId = geoIds[i], + geoCalcData = Plots.getSubplotCalcData(calcData, 'geo', geoId), + geo = fullLayout[geoId]._subplot // If geo is not instantiated, create one! - if(!geo) { - geo = new Geo({ - id: geoId, - graphDiv: gd, - container: fullLayout._geocontainer.node(), - topojsonURL: gd._context.topojsonURL - }, + if (!geo) { + geo = new Geo({ + id: geoId, + graphDiv: gd, + container: fullLayout._geocontainer.node(), + topojsonURL: gd._context.topojsonURL + }, fullLayout - ); + ) - fullLayout[geoId]._subplot = geo; - } - - geo.plot(geoCalcData, fullLayout, gd._promises); + fullLayout[geoId]._subplot = geo } -}; -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldGeoKeys = Plots.getSubplotIds(oldFullLayout, 'geo'); + geo.plot(geoCalcData, fullLayout, gd._promises) + } +} + +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldGeoKeys = Plots.getSubplotIds(oldFullLayout, 'geo') - for(var i = 0; i < oldGeoKeys.length; i++) { - var oldGeoKey = oldGeoKeys[i]; - var oldGeo = oldFullLayout[oldGeoKey]._subplot; + for (var i = 0; i < oldGeoKeys.length; i++) { + var oldGeoKey = oldGeoKeys[i] + var oldGeo = oldFullLayout[oldGeoKey]._subplot - if(!newFullLayout[oldGeoKey] && !!oldGeo) { - oldGeo.geoDiv.remove(); - } + if (!newFullLayout[oldGeoKey] && !!oldGeo) { + oldGeo.geoDiv.remove() } -}; + } +} -exports.toSVG = function(gd) { - var fullLayout = gd._fullLayout, - geoIds = Plots.getSubplotIds(fullLayout, 'geo'), - size = fullLayout._size; +exports.toSVG = function (gd) { + var fullLayout = gd._fullLayout, + geoIds = Plots.getSubplotIds(fullLayout, 'geo'), + size = fullLayout._size - for(var i = 0; i < geoIds.length; i++) { - var geoLayout = fullLayout[geoIds[i]], - domain = geoLayout.domain, - geoFramework = geoLayout._subplot.framework; + for (var i = 0; i < geoIds.length; i++) { + var geoLayout = fullLayout[geoIds[i]], + domain = geoLayout.domain, + geoFramework = geoLayout._subplot.framework - geoFramework.attr('style', null); - geoFramework + geoFramework.attr('style', null) + geoFramework .attr({ - x: size.l + size.w * domain.x[0] + geoLayout._marginX, - y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY, - width: geoLayout._width, - height: geoLayout._height - }); - - fullLayout._geoimages.node() - .appendChild(geoFramework.node()); - } -}; + x: size.l + size.w * domain.x[0] + geoLayout._marginX, + y: size.t + size.h * (1 - domain.y[1]) + geoLayout._marginY, + width: geoLayout._width, + height: geoLayout._height + }) + + fullLayout._geoimages.node() + .appendChild(geoFramework.node()) + } +} diff --git a/src/plots/geo/layout/attributes.js b/src/plots/geo/layout/attributes.js index e721fb6b0c5..d222adad0fd 100644 --- a/src/plots/geo/layout/attributes.js +++ b/src/plots/geo/layout/attributes.js @@ -6,21 +6,20 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - geo: { - valType: 'subplotid', - role: 'info', - dflt: 'geo', - description: [ - 'Sets a reference between this trace\'s geospatial coordinates and', - 'a geographic map.', - 'If *geo* (the default value), the geospatial coordinates refer to', - '`layout.geo`.', - 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', - 'and so on.' - ].join(' ') - } -}; + geo: { + valType: 'subplotid', + role: 'info', + dflt: 'geo', + description: [ + 'Sets a reference between this trace\'s geospatial coordinates and', + 'a geographic map.', + 'If *geo* (the default value), the geospatial coordinates refer to', + '`layout.geo`.', + 'If *geo2*, the geospatial coordinates refer to `layout.geo2`,', + 'and so on.' + ].join(' ') + } +} diff --git a/src/plots/geo/layout/axis_attributes.js b/src/plots/geo/layout/axis_attributes.js index a03ea496f05..7272022f44c 100644 --- a/src/plots/geo/layout/axis_attributes.js +++ b/src/plots/geo/layout/axis_attributes.js @@ -6,56 +6,55 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var colorAttrs = require('../../../components/color/attributes'); +'use strict' +var colorAttrs = require('../../../components/color/attributes') module.exports = { - range: { - valType: 'info_array', - role: 'info', - items: [ + range: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number'}, {valType: 'number'} - ], - description: 'Sets the range of this axis (in degrees).' - }, - showgrid: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not graticule are shown on the map.' - }, - tick0: { - valType: 'number', - role: 'info', - description: [ - 'Sets the graticule\'s starting tick longitude/latitude.' - ].join(' ') - }, - dtick: { - valType: 'number', - role: 'info', - description: [ - 'Sets the graticule\'s longitude/latitude tick step.' - ].join(' ') - }, - gridcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.lightLine, - description: [ - 'Sets the graticule\'s stroke color.' - ].join(' ') - }, - gridwidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: [ - 'Sets the graticule\'s stroke width (in px).' - ].join(' ') - } -}; + ], + description: 'Sets the range of this axis (in degrees).' + }, + showgrid: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not graticule are shown on the map.' + }, + tick0: { + valType: 'number', + role: 'info', + description: [ + 'Sets the graticule\'s starting tick longitude/latitude.' + ].join(' ') + }, + dtick: { + valType: 'number', + role: 'info', + description: [ + 'Sets the graticule\'s longitude/latitude tick step.' + ].join(' ') + }, + gridcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.lightLine, + description: [ + 'Sets the graticule\'s stroke color.' + ].join(' ') + }, + gridwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: [ + 'Sets the graticule\'s stroke width (in px).' + ].join(' ') + } +} diff --git a/src/plots/geo/layout/axis_defaults.js b/src/plots/geo/layout/axis_defaults.js index f3ccf86f885..105d2a329e2 100644 --- a/src/plots/geo/layout/axis_defaults.js +++ b/src/plots/geo/layout/axis_defaults.js @@ -6,67 +6,64 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../../lib') +var constants = require('../constants') +var axisAttributes = require('./axis_attributes') -var Lib = require('../../../lib'); -var constants = require('../constants'); -var axisAttributes = require('./axis_attributes'); +module.exports = function supplyGeoAxisLayoutDefaults (geoLayoutIn, geoLayoutOut) { + var axesNames = constants.axesNames + var axisIn, axisOut -module.exports = function supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut) { - var axesNames = constants.axesNames; + function coerce (attr, dflt) { + return Lib.coerce(axisIn, axisOut, axisAttributes, attr, dflt) + } - var axisIn, axisOut; + function getRangeDflt (axisName) { + var scope = geoLayoutOut.scope - function coerce(attr, dflt) { - return Lib.coerce(axisIn, axisOut, axisAttributes, attr, dflt); - } - - function getRangeDflt(axisName) { - var scope = geoLayoutOut.scope; - - var projLayout, projType, projRotation, rotateAngle, dfltSpans, halfSpan; + var projLayout, projType, projRotation, rotateAngle, dfltSpans, halfSpan - if(scope === 'world') { - projLayout = geoLayoutOut.projection; - projType = projLayout.type; - projRotation = projLayout.rotation; - dfltSpans = constants[axisName + 'Span']; + if (scope === 'world') { + projLayout = geoLayoutOut.projection + projType = projLayout.type + projRotation = projLayout.rotation + dfltSpans = constants[axisName + 'Span'] - halfSpan = dfltSpans[projType] !== undefined ? + halfSpan = dfltSpans[projType] !== undefined ? dfltSpans[projType] / 2 : - dfltSpans['*'] / 2; - rotateAngle = axisName === 'lonaxis' ? + dfltSpans['*'] / 2 + rotateAngle = axisName === 'lonaxis' ? projRotation.lon : - projRotation.lat; + projRotation.lat - return [rotateAngle - halfSpan, rotateAngle + halfSpan]; - } - else return constants.scopeDefaults[scope][axisName + 'Range']; - } - - for(var i = 0; i < axesNames.length; i++) { - var axisName = axesNames[i]; - axisIn = geoLayoutIn[axisName] || {}; - axisOut = {}; + return [rotateAngle - halfSpan, rotateAngle + halfSpan] + } else return constants.scopeDefaults[scope][axisName + 'Range'] + } - var rangeDflt = getRangeDflt(axisName); + for (var i = 0; i < axesNames.length; i++) { + var axisName = axesNames[i] + axisIn = geoLayoutIn[axisName] || {} + axisOut = {} - var range = coerce('range', rangeDflt); + var rangeDflt = getRangeDflt(axisName) - Lib.noneOrAll(axisIn.range, axisOut.range, [0, 1]); + var range = coerce('range', rangeDflt) - coerce('tick0', range[0]); - coerce('dtick', axisName === 'lonaxis' ? 30 : 10); + Lib.noneOrAll(axisIn.range, axisOut.range, [0, 1]) - var show = coerce('showgrid'); - if(show) { - coerce('gridcolor'); - coerce('gridwidth'); - } + coerce('tick0', range[0]) + coerce('dtick', axisName === 'lonaxis' ? 30 : 10) - geoLayoutOut[axisName] = axisOut; - geoLayoutOut[axisName]._fullRange = rangeDflt; + var show = coerce('showgrid') + if (show) { + coerce('gridcolor') + coerce('gridwidth') } -}; + + geoLayoutOut[axisName] = axisOut + geoLayoutOut[axisName]._fullRange = rangeDflt + } +} diff --git a/src/plots/geo/layout/defaults.js b/src/plots/geo/layout/defaults.js index 8fa7af85365..b901d77e849 100644 --- a/src/plots/geo/layout/defaults.js +++ b/src/plots/geo/layout/defaults.js @@ -6,112 +6,109 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var handleSubplotDefaults = require('../../subplot_defaults') +var constants = require('../constants') +var layoutAttributes = require('./layout_attributes') +var supplyGeoAxisLayoutDefaults = require('./axis_defaults') + +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'geo', + attributes: layoutAttributes, + handleDefaults: handleGeoDefaults, + partition: 'y' + }) +} -'use strict'; - -var handleSubplotDefaults = require('../../subplot_defaults'); -var constants = require('../constants'); -var layoutAttributes = require('./layout_attributes'); -var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); - +function handleGeoDefaults (geoLayoutIn, geoLayoutOut, coerce) { + var show -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - handleSubplotDefaults(layoutIn, layoutOut, fullData, { - type: 'geo', - attributes: layoutAttributes, - handleDefaults: handleGeoDefaults, - partition: 'y' - }); -}; + var scope = coerce('scope') + var isScoped = (scope !== 'world') + var scopeParams = constants.scopeDefaults[scope] -function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { - var show; + var resolution = coerce('resolution') - var scope = coerce('scope'); - var isScoped = (scope !== 'world'); - var scopeParams = constants.scopeDefaults[scope]; + var projType = coerce('projection.type', scopeParams.projType) + var isAlbersUsa = projType === 'albers usa' + var isConic = projType.indexOf('conic') !== -1 - var resolution = coerce('resolution'); + if (isConic) { + var dfltProjParallels = scopeParams.projParallels || [0, 60] + coerce('projection.parallels', dfltProjParallels) + } - var projType = coerce('projection.type', scopeParams.projType); - var isAlbersUsa = projType === 'albers usa'; - var isConic = projType.indexOf('conic') !== -1; + if (!isAlbersUsa) { + var dfltProjRotate = scopeParams.projRotate || [0, 0, 0] + coerce('projection.rotation.lon', dfltProjRotate[0]) + coerce('projection.rotation.lat', dfltProjRotate[1]) + coerce('projection.rotation.roll', dfltProjRotate[2]) - if(isConic) { - var dfltProjParallels = scopeParams.projParallels || [0, 60]; - coerce('projection.parallels', dfltProjParallels); + show = coerce('showcoastlines', !isScoped) + if (show) { + coerce('coastlinecolor') + coerce('coastlinewidth') } - if(!isAlbersUsa) { - var dfltProjRotate = scopeParams.projRotate || [0, 0, 0]; - coerce('projection.rotation.lon', dfltProjRotate[0]); - coerce('projection.rotation.lat', dfltProjRotate[1]); - coerce('projection.rotation.roll', dfltProjRotate[2]); + show = coerce('showocean') + if (show) coerce('oceancolor') + } else geoLayoutOut.scope = 'usa' - show = coerce('showcoastlines', !isScoped); - if(show) { - coerce('coastlinecolor'); - coerce('coastlinewidth'); - } + coerce('projection.scale') - show = coerce('showocean'); - if(show) coerce('oceancolor'); - } - else geoLayoutOut.scope = 'usa'; + show = coerce('showland') + if (show) coerce('landcolor') - coerce('projection.scale'); + show = coerce('showlakes') + if (show) coerce('lakecolor') - show = coerce('showland'); - if(show) coerce('landcolor'); + show = coerce('showrivers') + if (show) { + coerce('rivercolor') + coerce('riverwidth') + } - show = coerce('showlakes'); - if(show) coerce('lakecolor'); + show = coerce('showcountries', isScoped && scope !== 'usa') + if (show) { + coerce('countrycolor') + coerce('countrywidth') + } - show = coerce('showrivers'); - if(show) { - coerce('rivercolor'); - coerce('riverwidth'); - } - - show = coerce('showcountries', isScoped && scope !== 'usa'); - if(show) { - coerce('countrycolor'); - coerce('countrywidth'); - } - - if(scope === 'usa' || (scope === 'north america' && resolution === 50)) { + if (scope === 'usa' || (scope === 'north america' && resolution === 50)) { // Only works for: // USA states at 110m // USA states + Canada provinces at 50m - coerce('showsubunits', true); - coerce('subunitcolor'); - coerce('subunitwidth'); - } + coerce('showsubunits', true) + coerce('subunitcolor') + coerce('subunitwidth') + } - if(!isScoped) { + if (!isScoped) { // Does not work in non-world scopes - show = coerce('showframe', true); - if(show) { - coerce('framecolor'); - coerce('framewidth'); - } + show = coerce('showframe', true) + if (show) { + coerce('framecolor') + coerce('framewidth') } + } - coerce('bgcolor'); + coerce('bgcolor') - supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut); + supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut) // bind a few helper variables - geoLayoutOut._isHighRes = resolution === 50; - geoLayoutOut._clipAngle = constants.lonaxisSpan[projType] / 2; - geoLayoutOut._isAlbersUsa = isAlbersUsa; - geoLayoutOut._isConic = isConic; - geoLayoutOut._isScoped = isScoped; - - var rotation = geoLayoutOut.projection.rotation || {}; - geoLayoutOut.projection._rotate = [ - -rotation.lon || 0, - -rotation.lat || 0, - rotation.roll || 0 - ]; + geoLayoutOut._isHighRes = resolution === 50 + geoLayoutOut._clipAngle = constants.lonaxisSpan[projType] / 2 + geoLayoutOut._isAlbersUsa = isAlbersUsa + geoLayoutOut._isConic = isConic + geoLayoutOut._isScoped = isScoped + + var rotation = geoLayoutOut.projection.rotation || {} + geoLayoutOut.projection._rotate = [ + -rotation.lon || 0, + -rotation.lat || 0, + rotation.roll || 0 + ] } diff --git a/src/plots/geo/layout/layout_attributes.js b/src/plots/geo/layout/layout_attributes.js index 0a6b9e26140..ce28a0c5b79 100644 --- a/src/plots/geo/layout/layout_attributes.js +++ b/src/plots/geo/layout/layout_attributes.js @@ -6,252 +6,251 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var colorAttrs = require('../../../components/color/attributes'); -var constants = require('../constants'); -var geoAxesAttrs = require('./axis_attributes'); +'use strict' +var colorAttrs = require('../../../components/color/attributes') +var constants = require('../constants') +var geoAxesAttrs = require('./axis_attributes') module.exports = { - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this map', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this map', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this map', - '(in plot fraction).' - ].join(' ') - } + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this map', + '(in plot fraction).' + ].join(' ') + } + }, + resolution: { + valType: 'enumerated', + values: [110, 50], + role: 'info', + dflt: 110, + coerceNumber: true, + description: [ + 'Sets the resolution of the base layers.', + 'The values have units of km/mm', + 'e.g. 110 corresponds to a scale ratio of 1:110,000,000.' + ].join(' ') + }, + scope: { + valType: 'enumerated', + role: 'info', + values: Object.keys(constants.scopeDefaults), + dflt: 'world', + description: 'Set the scope of the map.' + }, + projection: { + type: { + valType: 'enumerated', + role: 'info', + values: Object.keys(constants.projNames), + description: 'Sets the projection type.' }, - resolution: { - valType: 'enumerated', - values: [110, 50], + rotation: { + lon: { + valType: 'number', role: 'info', - dflt: 110, - coerceNumber: true, description: [ - 'Sets the resolution of the base layers.', - 'The values have units of km/mm', - 'e.g. 110 corresponds to a scale ratio of 1:110,000,000.' + 'Rotates the map along parallels', + '(in degrees East).' ].join(' ') - }, - scope: { - valType: 'enumerated', - role: 'info', - values: Object.keys(constants.scopeDefaults), - dflt: 'world', - description: 'Set the scope of the map.' - }, - projection: { - type: { - valType: 'enumerated', - role: 'info', - values: Object.keys(constants.projNames), - description: 'Sets the projection type.' - }, - rotation: { - lon: { - valType: 'number', - role: 'info', - description: [ - 'Rotates the map along parallels', - '(in degrees East).' - ].join(' ') - }, - lat: { - valType: 'number', - role: 'info', - description: [ - 'Rotates the map along meridians', - '(in degrees North).' - ].join(' ') - }, - roll: { - valType: 'number', - role: 'info', - description: [ - 'Roll the map (in degrees)', - 'For example, a roll of *180* makes the map appear upside down.' - ].join(' ') - } - }, - parallels: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number'}, - {valType: 'number'} - ], - description: [ - 'For conic projection types only.', - 'Sets the parallels (tangent, secant)', - 'where the cone intersects the sphere.' - ].join(' ') - }, - scale: { - valType: 'number', - role: 'info', - min: 0, - max: 10, - dflt: 1, - description: 'Zooms in or out on the map view.' - } - }, - showcoastlines: { - valType: 'boolean', - role: 'info', - description: 'Sets whether or not the coastlines are drawn.' - }, - coastlinecolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the coastline color.' - }, - coastlinewidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the coastline stroke width (in px).' - }, - showland: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not land masses are filled in color.' - }, - landcolor: { - valType: 'color', - role: 'style', - dflt: constants.landColor, - description: 'Sets the land mass color.' - }, - showocean: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not oceans are filled in color.' - }, - oceancolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets the ocean color' - }, - showlakes: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not lakes are drawn.' - }, - lakecolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets the color of the lakes.' - }, - showrivers: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not rivers are drawn.' - }, - rivercolor: { - valType: 'color', - role: 'style', - dflt: constants.waterColor, - description: 'Sets color of the rivers.' - }, - riverwidth: { + }, + lat: { valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the rivers.' - }, - showcountries: { - valType: 'boolean', - role: 'info', - description: 'Sets whether or not country boundaries are drawn.' - }, - countrycolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets line color of the country boundaries.' - }, - countrywidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets line width (in px) of the country boundaries.' - }, - showsubunits: { - valType: 'boolean', role: 'info', description: [ - 'Sets whether or not boundaries of subunits within countries', - '(e.g. states, provinces) are drawn.' + 'Rotates the map along meridians', + '(in degrees North).' ].join(' ') - }, - subunitcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the color of the subunits boundaries.' - }, - subunitwidth: { + }, + roll: { valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the subunits boundaries.' - }, - showframe: { - valType: 'boolean', role: 'info', - description: 'Sets whether or not a frame is drawn around the map.' - }, - framecolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - description: 'Sets the color the frame.' - }, - framewidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: 'Sets the stroke width (in px) of the frame.' + description: [ + 'Roll the map (in degrees)', + 'For example, a roll of *180* makes the map appear upside down.' + ].join(' ') + } }, - bgcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.background, - description: 'Set the background color of the map' + parallels: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number'}, + {valType: 'number'} + ], + description: [ + 'For conic projection types only.', + 'Sets the parallels (tangent, secant)', + 'where the cone intersects the sphere.' + ].join(' ') }, - lonaxis: geoAxesAttrs, - lataxis: geoAxesAttrs -}; + scale: { + valType: 'number', + role: 'info', + min: 0, + max: 10, + dflt: 1, + description: 'Zooms in or out on the map view.' + } + }, + showcoastlines: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not the coastlines are drawn.' + }, + coastlinecolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the coastline color.' + }, + coastlinewidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the coastline stroke width (in px).' + }, + showland: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not land masses are filled in color.' + }, + landcolor: { + valType: 'color', + role: 'style', + dflt: constants.landColor, + description: 'Sets the land mass color.' + }, + showocean: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not oceans are filled in color.' + }, + oceancolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets the ocean color' + }, + showlakes: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not lakes are drawn.' + }, + lakecolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets the color of the lakes.' + }, + showrivers: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not rivers are drawn.' + }, + rivercolor: { + valType: 'color', + role: 'style', + dflt: constants.waterColor, + description: 'Sets color of the rivers.' + }, + riverwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the rivers.' + }, + showcountries: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not country boundaries are drawn.' + }, + countrycolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets line color of the country boundaries.' + }, + countrywidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets line width (in px) of the country boundaries.' + }, + showsubunits: { + valType: 'boolean', + role: 'info', + description: [ + 'Sets whether or not boundaries of subunits within countries', + '(e.g. states, provinces) are drawn.' + ].join(' ') + }, + subunitcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the color of the subunits boundaries.' + }, + subunitwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the subunits boundaries.' + }, + showframe: { + valType: 'boolean', + role: 'info', + description: 'Sets whether or not a frame is drawn around the map.' + }, + framecolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + description: 'Sets the color the frame.' + }, + framewidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: 'Sets the stroke width (in px) of the frame.' + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: 'Set the background color of the map' + }, + lonaxis: geoAxesAttrs, + lataxis: geoAxesAttrs +} diff --git a/src/plots/geo/projections.js b/src/plots/geo/projections.js index e0f1efc3fc1..e8a511e5f13 100644 --- a/src/plots/geo/projections.js +++ b/src/plots/geo/projections.js @@ -14,7 +14,7 @@ * into a CommonJS require-able module. */ -'use strict'; +'use strict' /* eslint-disable */ diff --git a/src/plots/geo/set_scale.js b/src/plots/geo/set_scale.js index 66ef39f0983..ff365fe6f3e 100644 --- a/src/plots/geo/set_scale.js +++ b/src/plots/geo/set_scale.js @@ -6,128 +6,126 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var clipPad = require('./constants').clipPad -var clipPad = require('./constants').clipPad; - -function createGeoScale(geoLayout, graphSize) { - var projLayout = geoLayout.projection, - lonaxisLayout = geoLayout.lonaxis, - lataxisLayout = geoLayout.lataxis, - geoDomain = geoLayout.domain, - frameWidth = geoLayout.framewidth || 0; +function createGeoScale (geoLayout, graphSize) { + var projLayout = geoLayout.projection, + lonaxisLayout = geoLayout.lonaxis, + lataxisLayout = geoLayout.lataxis, + geoDomain = geoLayout.domain, + frameWidth = geoLayout.framewidth || 0 // width & height the geo div - var geoWidth = graphSize.w * (geoDomain.x[1] - geoDomain.x[0]), - geoHeight = graphSize.h * (geoDomain.y[1] - geoDomain.y[0]); + var geoWidth = graphSize.w * (geoDomain.x[1] - geoDomain.x[0]), + geoHeight = graphSize.h * (geoDomain.y[1] - geoDomain.y[0]) // add padding around range to avoid aliasing - var lon0 = lonaxisLayout.range[0] + clipPad, - lon1 = lonaxisLayout.range[1] - clipPad, - lat0 = lataxisLayout.range[0] + clipPad, - lat1 = lataxisLayout.range[1] - clipPad, - lonfull0 = lonaxisLayout._fullRange[0] + clipPad, - lonfull1 = lonaxisLayout._fullRange[1] - clipPad, - latfull0 = lataxisLayout._fullRange[0] + clipPad, - latfull1 = lataxisLayout._fullRange[1] - clipPad; + var lon0 = lonaxisLayout.range[0] + clipPad, + lon1 = lonaxisLayout.range[1] - clipPad, + lat0 = lataxisLayout.range[0] + clipPad, + lat1 = lataxisLayout.range[1] - clipPad, + lonfull0 = lonaxisLayout._fullRange[0] + clipPad, + lonfull1 = lonaxisLayout._fullRange[1] - clipPad, + latfull0 = lataxisLayout._fullRange[0] + clipPad, + latfull1 = lataxisLayout._fullRange[1] - clipPad // initial translation (makes the math easier) - projLayout._translate0 = [ - graphSize.l + geoWidth / 2, graphSize.t + geoHeight / 2 - ]; - + projLayout._translate0 = [ + graphSize.l + geoWidth / 2, graphSize.t + geoHeight / 2 + ] // center of the projection is given by // the lon/lat ranges and the rotate angle - var dlon = lon1 - lon0, - dlat = lat1 - lat0, - c0 = [lon0 + dlon / 2, lat0 + dlat / 2], - r = projLayout._rotate; + var dlon = lon1 - lon0, + dlat = lat1 - lat0, + c0 = [lon0 + dlon / 2, lat0 + dlat / 2], + r = projLayout._rotate - projLayout._center = [c0[0] + r[0], c0[1] + r[1]]; + projLayout._center = [c0[0] + r[0], c0[1] + r[1]] // needs a initial projection; it is called from makeProjection - var setScale = function(projection) { - var scale0 = projection.scale(), - translate0 = projLayout._translate0, - rangeBox = makeRangeBox(lon0, lat0, lon1, lat1), - fullRangeBox = makeRangeBox(lonfull0, latfull0, lonfull1, latfull1); + var setScale = function (projection) { + var scale0 = projection.scale(), + translate0 = projLayout._translate0, + rangeBox = makeRangeBox(lon0, lat0, lon1, lat1), + fullRangeBox = makeRangeBox(lonfull0, latfull0, lonfull1, latfull1) - var scale, translate, bounds, fullBounds; + var scale, translate, bounds, fullBounds // Inspired by: http://stackoverflow.com/a/14654988/4068492 // using the path determine the bounds of the current map and use // these to determine better values for the scale and translation - function getScale(bounds) { - return Math.min( + function getScale (bounds) { + return Math.min( scale0 * geoWidth / (bounds[1][0] - bounds[0][0]), scale0 * geoHeight / (bounds[1][1] - bounds[0][1]) - ); - } + ) + } // scale projection given how range box get deformed // by the projection - bounds = getBounds(projection, rangeBox); - scale = getScale(bounds); + bounds = getBounds(projection, rangeBox) + scale = getScale(bounds) // similarly, get scale at full range - fullBounds = getBounds(projection, fullRangeBox); - projLayout._fullScale = getScale(fullBounds); + fullBounds = getBounds(projection, fullRangeBox) + projLayout._fullScale = getScale(fullBounds) - projection.scale(scale); + projection.scale(scale) // translate the projection so that the top-left corner // of the range box is at the top-left corner of the viewbox - bounds = getBounds(projection, rangeBox); - translate = [ - translate0[0] - bounds[0][0] + frameWidth, - translate0[1] - bounds[0][1] + frameWidth - ]; - projLayout._translate = translate; - projection.translate(translate); + bounds = getBounds(projection, rangeBox) + translate = [ + translate0[0] - bounds[0][0] + frameWidth, + translate0[1] - bounds[0][1] + frameWidth + ] + projLayout._translate = translate + projection.translate(translate) // clip regions out of the range box // (these are clipping along horizontal/vertical lines) - bounds = getBounds(projection, rangeBox); - if(!geoLayout._isAlbersUsa) projection.clipExtent(bounds); + bounds = getBounds(projection, rangeBox) + if (!geoLayout._isAlbersUsa) projection.clipExtent(bounds) // adjust scale one more time with the 'scale' attribute - scale = projLayout.scale * scale; + scale = projLayout.scale * scale // set projection scale and save it - projLayout._scale = scale; + projLayout._scale = scale // save the effective width & height of the geo framework - geoLayout._width = Math.round(bounds[1][0]) + frameWidth; - geoLayout._height = Math.round(bounds[1][1]) + frameWidth; + geoLayout._width = Math.round(bounds[1][0]) + frameWidth + geoLayout._height = Math.round(bounds[1][1]) + frameWidth // save the margin length induced by the map scaling - geoLayout._marginX = (geoWidth - Math.round(bounds[1][0])) / 2; - geoLayout._marginY = (geoHeight - Math.round(bounds[1][1])) / 2; - }; + geoLayout._marginX = (geoWidth - Math.round(bounds[1][0])) / 2 + geoLayout._marginY = (geoHeight - Math.round(bounds[1][1])) / 2 + } - return setScale; + return setScale } -module.exports = createGeoScale; +module.exports = createGeoScale // polygon GeoJSON corresponding to lon/lat range box // with well-defined direction -function makeRangeBox(lon0, lat0, lon1, lat1) { - var dlon4 = (lon1 - lon0) / 4; +function makeRangeBox (lon0, lat0, lon1, lat1) { + var dlon4 = (lon1 - lon0) / 4 // TODO is this enough to handle ALL cases? // -- this makes scaling less precise than using d3.geo.graticule // as great circles can overshoot the boundary // (that's not a big deal I think) - return { - type: 'Polygon', - coordinates: [ - [ [lon0, lat0], + return { + type: 'Polygon', + coordinates: [ + [ [lon0, lat0], [lon0, lat1], [lon0 + dlon4, lat1], [lon0 + 2 * dlon4, lat1], @@ -138,12 +136,12 @@ function makeRangeBox(lon0, lat0, lon1, lat1) { [lon1 - 2 * dlon4, lat0], [lon1 - 3 * dlon4, lat0], [lon0, lat0] ] - ] - }; + ] + } } // bounds array [[top, left], [bottom, right]] // of the lon/lat range box -function getBounds(projection, rangeBox) { - return d3.geo.path().projection(projection).bounds(rangeBox); +function getBounds (projection, rangeBox) { + return d3.geo.path().projection(projection).bounds(rangeBox) } diff --git a/src/plots/geo/zoom.js b/src/plots/geo/zoom.js index dd26c33bae8..604754149fe 100644 --- a/src/plots/geo/zoom.js +++ b/src/plots/geo/zoom.js @@ -6,272 +6,269 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var d3 = require('d3'); +var d3 = require('d3') var radians = Math.PI / 180, - degrees = 180 / Math.PI, - zoomstartStyle = { cursor: 'pointer' }, - zoomendStyle = { cursor: 'auto' }; - + degrees = 180 / Math.PI, + zoomstartStyle = { cursor: 'pointer' }, + zoomendStyle = { cursor: 'auto' } -function createGeoZoom(geo, geoLayout) { - var zoomConstructor; +function createGeoZoom (geo, geoLayout) { + var zoomConstructor - if(geoLayout._isScoped) zoomConstructor = zoomScoped; - else if(geoLayout._clipAngle) zoomConstructor = zoomClipped; - else zoomConstructor = zoomNonClipped; + if (geoLayout._isScoped) zoomConstructor = zoomScoped + else if (geoLayout._clipAngle) zoomConstructor = zoomClipped + else zoomConstructor = zoomNonClipped // TODO add a conic-specific zoom - return zoomConstructor(geo, geoLayout.projection); + return zoomConstructor(geo, geoLayout.projection) } -module.exports = createGeoZoom; +module.exports = createGeoZoom // common to all zoom types -function initZoom(projection, projLayout) { - var fullScale = projLayout._fullScale; +function initZoom (projection, projLayout) { + var fullScale = projLayout._fullScale - return d3.behavior.zoom() + return d3.behavior.zoom() .translate(projection.translate()) .scale(projection.scale()) - .scaleExtent([0.5 * fullScale, 100 * fullScale]); + .scaleExtent([0.5 * fullScale, 100 * fullScale]) } // zoom for scoped projections -function zoomScoped(geo, projLayout) { - var projection = geo.projection, - zoom = initZoom(projection, projLayout); +function zoomScoped (geo, projLayout) { + var projection = geo.projection, + zoom = initZoom(projection, projLayout) - function handleZoomstart() { - d3.select(this).style(zoomstartStyle); - } + function handleZoomstart () { + d3.select(this).style(zoomstartStyle) + } - function handleZoom() { - projection + function handleZoom () { + projection .scale(d3.event.scale) - .translate(d3.event.translate); + .translate(d3.event.translate) - geo.render(); - } + geo.render() + } - function handleZoomend() { - d3.select(this).style(zoomendStyle); - } + function handleZoomend () { + d3.select(this).style(zoomendStyle) + } - zoom + zoom .on('zoomstart', handleZoomstart) .on('zoom', handleZoom) - .on('zoomend', handleZoomend); + .on('zoomend', handleZoomend) - return zoom; + return zoom } // zoom for non-clipped projections -function zoomNonClipped(geo, projLayout) { - var projection = geo.projection, - zoom = initZoom(projection, projLayout); +function zoomNonClipped (geo, projLayout) { + var projection = geo.projection, + zoom = initZoom(projection, projLayout) - var INSIDETOLORANCEPXS = 2; + var INSIDETOLORANCEPXS = 2 - var mouse0, rotate0, translate0, lastRotate, zoomPoint, - mouse1, rotate1, point1; + var mouse0, rotate0, translate0, lastRotate, zoomPoint, + mouse1, rotate1, point1 - function position(x) { return projection.invert(x); } + function position (x) { return projection.invert(x) } - function outside(x) { - var pt = projection(position(x)); - return (Math.abs(pt[0] - x[0]) > INSIDETOLORANCEPXS || - Math.abs(pt[1] - x[1]) > INSIDETOLORANCEPXS); - } + function outside (x) { + var pt = projection(position(x)) + return (Math.abs(pt[0] - x[0]) > INSIDETOLORANCEPXS || + Math.abs(pt[1] - x[1]) > INSIDETOLORANCEPXS) + } - function handleZoomstart() { - d3.select(this).style(zoomstartStyle); + function handleZoomstart () { + d3.select(this).style(zoomstartStyle) - mouse0 = d3.mouse(this); - rotate0 = projection.rotate(); - translate0 = projection.translate(); - lastRotate = rotate0; - zoomPoint = position(mouse0); - } - - function handleZoom() { - mouse1 = d3.mouse(this); + mouse0 = d3.mouse(this) + rotate0 = projection.rotate() + translate0 = projection.translate() + lastRotate = rotate0 + zoomPoint = position(mouse0) + } - if(outside(mouse0)) { - zoom.scale(projection.scale()); - zoom.translate(projection.translate()); - return; - } + function handleZoom () { + mouse1 = d3.mouse(this) - projection.scale(d3.event.scale); + if (outside(mouse0)) { + zoom.scale(projection.scale()) + zoom.translate(projection.translate()) + return + } - projection.translate([translate0[0], d3.event.translate[1]]); + projection.scale(d3.event.scale) - if(!zoomPoint) { - mouse0 = mouse1; - zoomPoint = position(mouse0); - } - else if(position(mouse1)) { - point1 = position(mouse1); - rotate1 = [lastRotate[0] + (point1[0] - zoomPoint[0]), rotate0[1], rotate0[2]]; - projection.rotate(rotate1); - lastRotate = rotate1; - } + projection.translate([translate0[0], d3.event.translate[1]]) - geo.render(); + if (!zoomPoint) { + mouse0 = mouse1 + zoomPoint = position(mouse0) + } else if (position(mouse1)) { + point1 = position(mouse1) + rotate1 = [lastRotate[0] + (point1[0] - zoomPoint[0]), rotate0[1], rotate0[2]] + projection.rotate(rotate1) + lastRotate = rotate1 } - function handleZoomend() { - d3.select(this).style(zoomendStyle); + geo.render() + } + + function handleZoomend () { + d3.select(this).style(zoomendStyle) // or something like // http://www.jasondavies.com/maps/gilbert/ // ... a little harder with multiple base layers - } + } - zoom + zoom .on('zoomstart', handleZoomstart) .on('zoom', handleZoom) - .on('zoomend', handleZoomend); + .on('zoomend', handleZoomend) - return zoom; + return zoom } // zoom for clipped projections // inspired by https://www.jasondavies.com/maps/d3.geo.zoom.js -function zoomClipped(geo, projLayout) { - var projection = geo.projection, - view = {r: projection.rotate(), k: projection.scale()}, - zoom = initZoom(projection, projLayout), - event = d3_eventDispatch(zoom, 'zoomstart', 'zoom', 'zoomend'), - zooming = 0, - zoomOn = zoom.on; +function zoomClipped (geo, projLayout) { + var projection = geo.projection, + view = {r: projection.rotate(), k: projection.scale()}, + zoom = initZoom(projection, projLayout), + event = d3_eventDispatch(zoom, 'zoomstart', 'zoom', 'zoomend'), + zooming = 0, + zoomOn = zoom.on - var zoomPoint; + var zoomPoint - zoom.on('zoomstart', function() { - d3.select(this).style(zoomstartStyle); + zoom.on('zoomstart', function () { + d3.select(this).style(zoomstartStyle) - var mouse0 = d3.mouse(this), - rotate0 = projection.rotate(), - lastRotate = rotate0, - translate0 = projection.translate(), - q = quaternionFromEuler(rotate0); + var mouse0 = d3.mouse(this), + rotate0 = projection.rotate(), + lastRotate = rotate0, + translate0 = projection.translate(), + q = quaternionFromEuler(rotate0) - zoomPoint = position(projection, mouse0); + zoomPoint = position(projection, mouse0) - zoomOn.call(zoom, 'zoom', function() { - var mouse1 = d3.mouse(this); + zoomOn.call(zoom, 'zoom', function () { + var mouse1 = d3.mouse(this) - projection.scale(view.k = d3.event.scale); + projection.scale(view.k = d3.event.scale) - if(!zoomPoint) { + if (!zoomPoint) { // if no zoomPoint, the mouse wasn't over the actual geography yet // maybe this point is the start... we'll find out next time! - mouse0 = mouse1; - zoomPoint = position(projection, mouse0); - } + mouse0 = mouse1 + zoomPoint = position(projection, mouse0) + } // check if the point is on the map // if not, don't do anything new but scale // if it is, then we can assume between will exist below // so we don't need the 'bank' function, whatever that is. // TODO: is this right? - else if(position(projection, mouse1)) { + else if (position(projection, mouse1)) { // go back to original projection temporarily // except for scale... that's kind of independent? - projection + projection .rotate(rotate0) - .translate(translate0); + .translate(translate0) // calculate the new params - var point1 = position(projection, mouse1), - between = rotateBetween(zoomPoint, point1), - newEuler = eulerFromQuaternion(multiply(q, between)), - rotateAngles = view.r = unRoll(newEuler, zoomPoint, lastRotate); + var point1 = position(projection, mouse1), + between = rotateBetween(zoomPoint, point1), + newEuler = eulerFromQuaternion(multiply(q, between)), + rotateAngles = view.r = unRoll(newEuler, zoomPoint, lastRotate) - if(!isFinite(rotateAngles[0]) || !isFinite(rotateAngles[1]) || + if (!isFinite(rotateAngles[0]) || !isFinite(rotateAngles[1]) || !isFinite(rotateAngles[2])) { - rotateAngles = lastRotate; - } + rotateAngles = lastRotate + } // update the projection - projection.rotate(rotateAngles); - lastRotate = rotateAngles; - } + projection.rotate(rotateAngles) + lastRotate = rotateAngles + } - zoomed(event.of(this, arguments)); - }); + zoomed(event.of(this, arguments)) + }) - zoomstarted(event.of(this, arguments)); + zoomstarted(event.of(this, arguments)) + }) + .on('zoomend', function () { + d3.select(this).style(zoomendStyle) + zoomOn.call(zoom, 'zoom', null) + zoomended(event.of(this, arguments)) }) - .on('zoomend', function() { - d3.select(this).style(zoomendStyle); - zoomOn.call(zoom, 'zoom', null); - zoomended(event.of(this, arguments)); + .on('zoom.redraw', function () { + geo.render() }) - .on('zoom.redraw', function() { - geo.render(); - }); - function zoomstarted(dispatch) { - if(!zooming++) dispatch({type: 'zoomstart'}); - } + function zoomstarted (dispatch) { + if (!zooming++) dispatch({type: 'zoomstart'}) + } - function zoomed(dispatch) { - dispatch({type: 'zoom'}); - } + function zoomed (dispatch) { + dispatch({type: 'zoom'}) + } - function zoomended(dispatch) { - if(!--zooming) dispatch({type: 'zoomend'}); - } + function zoomended (dispatch) { + if (!--zooming) dispatch({type: 'zoomend'}) + } - return d3.rebind(zoom, event, 'on'); + return d3.rebind(zoom, event, 'on') } // -- helper functions for zoomClipped -function position(projection, point) { - var spherical = projection.invert(point); - return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical); +function position (projection, point) { + var spherical = projection.invert(point) + return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical) } -function quaternionFromEuler(euler) { - var lambda = 0.5 * euler[0] * radians, - phi = 0.5 * euler[1] * radians, - gamma = 0.5 * euler[2] * radians, - sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda), - sinPhi = Math.sin(phi), cosPhi = Math.cos(phi), - sinGamma = Math.sin(gamma), cosGamma = Math.cos(gamma); - return [ - cosLambda * cosPhi * cosGamma + sinLambda * sinPhi * sinGamma, - sinLambda * cosPhi * cosGamma - cosLambda * sinPhi * sinGamma, - cosLambda * sinPhi * cosGamma + sinLambda * cosPhi * sinGamma, - cosLambda * cosPhi * sinGamma - sinLambda * sinPhi * cosGamma - ]; +function quaternionFromEuler (euler) { + var lambda = 0.5 * euler[0] * radians, + phi = 0.5 * euler[1] * radians, + gamma = 0.5 * euler[2] * radians, + sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda), + sinPhi = Math.sin(phi), cosPhi = Math.cos(phi), + sinGamma = Math.sin(gamma), cosGamma = Math.cos(gamma) + return [ + cosLambda * cosPhi * cosGamma + sinLambda * sinPhi * sinGamma, + sinLambda * cosPhi * cosGamma - cosLambda * sinPhi * sinGamma, + cosLambda * sinPhi * cosGamma + sinLambda * cosPhi * sinGamma, + cosLambda * cosPhi * sinGamma - sinLambda * sinPhi * cosGamma + ] } -function multiply(a, b) { - var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], - b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; - return [ - a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3, - a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2, - a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1, - a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0 - ]; +function multiply (a, b) { + var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], + b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3] + return [ + a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3, + a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2, + a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1, + a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0 + ] } -function rotateBetween(a, b) { - if(!a || !b) return; - var axis = cross(a, b), - norm = Math.sqrt(dot(axis, axis)), - halfgamma = 0.5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))), - k = Math.sin(halfgamma) / norm; - return norm && [Math.cos(halfgamma), axis[2] * k, -axis[1] * k, axis[0] * k]; +function rotateBetween (a, b) { + if (!a || !b) return + var axis = cross(a, b), + norm = Math.sqrt(dot(axis, axis)), + halfgamma = 0.5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))), + k = Math.sin(halfgamma) / norm + return norm && [Math.cos(halfgamma), axis[2] * k, -axis[1] * k, axis[0] * k] } // input: @@ -283,120 +280,120 @@ function rotateBetween(a, b) { // but set roll (output[2]) equal to roll0 // note that this doesn't depend on the particular projection, // just on the rotation angles -function unRoll(rotateAngles, pt, lastRotate) { +function unRoll (rotateAngles, pt, lastRotate) { // calculate the fixed point transformed by these Euler angles // but with the desired roll undone - var ptRotated = rotateCartesian(pt, 2, rotateAngles[0]); - ptRotated = rotateCartesian(ptRotated, 1, rotateAngles[1]); - ptRotated = rotateCartesian(ptRotated, 0, rotateAngles[2] - lastRotate[2]); + var ptRotated = rotateCartesian(pt, 2, rotateAngles[0]) + ptRotated = rotateCartesian(ptRotated, 1, rotateAngles[1]) + ptRotated = rotateCartesian(ptRotated, 0, rotateAngles[2] - lastRotate[2]) - var x = pt[0], - y = pt[1], - z = pt[2], - f = ptRotated[0], - g = ptRotated[1], - h = ptRotated[2], + var x = pt[0], + y = pt[1], + z = pt[2], + f = ptRotated[0], + g = ptRotated[1], + h = ptRotated[2], // the following essentially solves: // ptRotated = rotateCartesian(rotateCartesian(pt, 2, newYaw), 1, newPitch) // for newYaw and newPitch, as best it can - theta = Math.atan2(y, x) * degrees, - a = Math.sqrt(x * x + y * y), - b, - newYaw1; - - if(Math.abs(g) > a) { - newYaw1 = (g > 0 ? 90 : -90) - theta; - b = 0; - } else { - newYaw1 = Math.asin(g / a) * degrees - theta; - b = Math.sqrt(a * a - g * g); - } - - var newYaw2 = 180 - newYaw1 - 2 * theta, - newPitch1 = (Math.atan2(h, f) - Math.atan2(z, b)) * degrees, - newPitch2 = (Math.atan2(h, f) - Math.atan2(z, -b)) * degrees; + theta = Math.atan2(y, x) * degrees, + a = Math.sqrt(x * x + y * y), + b, + newYaw1 + + if (Math.abs(g) > a) { + newYaw1 = (g > 0 ? 90 : -90) - theta + b = 0 + } else { + newYaw1 = Math.asin(g / a) * degrees - theta + b = Math.sqrt(a * a - g * g) + } + + var newYaw2 = 180 - newYaw1 - 2 * theta, + newPitch1 = (Math.atan2(h, f) - Math.atan2(z, b)) * degrees, + newPitch2 = (Math.atan2(h, f) - Math.atan2(z, -b)) * degrees // which is closest to lastRotate[0,1]: newYaw/Pitch or newYaw2/Pitch2? - var dist1 = angleDistance(lastRotate[0], lastRotate[1], newYaw1, newPitch1), - dist2 = angleDistance(lastRotate[0], lastRotate[1], newYaw2, newPitch2); + var dist1 = angleDistance(lastRotate[0], lastRotate[1], newYaw1, newPitch1), + dist2 = angleDistance(lastRotate[0], lastRotate[1], newYaw2, newPitch2) - if(dist1 <= dist2) return [newYaw1, newPitch1, lastRotate[2]]; - else return [newYaw2, newPitch2, lastRotate[2]]; + if (dist1 <= dist2) return [newYaw1, newPitch1, lastRotate[2]] + else return [newYaw2, newPitch2, lastRotate[2]] } -function angleDistance(yaw0, pitch0, yaw1, pitch1) { - var dYaw = angleMod(yaw1 - yaw0), - dPitch = angleMod(pitch1 - pitch0); - return Math.sqrt(dYaw * dYaw + dPitch * dPitch); +function angleDistance (yaw0, pitch0, yaw1, pitch1) { + var dYaw = angleMod(yaw1 - yaw0), + dPitch = angleMod(pitch1 - pitch0) + return Math.sqrt(dYaw * dYaw + dPitch * dPitch) } // reduce an angle in degrees to [-180,180] -function angleMod(angle) { - return (angle % 360 + 540) % 360 - 180; +function angleMod (angle) { + return (angle % 360 + 540) % 360 - 180 } // rotate a cartesian vector // axis is 0 (x), 1 (y), or 2 (z) // angle is in degrees -function rotateCartesian(vector, axis, angle) { - var angleRads = angle * radians, - vectorOut = vector.slice(), - ax1 = (axis === 0) ? 1 : 0, - ax2 = (axis === 2) ? 1 : 2, - cosa = Math.cos(angleRads), - sina = Math.sin(angleRads); - - vectorOut[ax1] = vector[ax1] * cosa - vector[ax2] * sina; - vectorOut[ax2] = vector[ax2] * cosa + vector[ax1] * sina; - - return vectorOut; +function rotateCartesian (vector, axis, angle) { + var angleRads = angle * radians, + vectorOut = vector.slice(), + ax1 = (axis === 0) ? 1 : 0, + ax2 = (axis === 2) ? 1 : 2, + cosa = Math.cos(angleRads), + sina = Math.sin(angleRads) + + vectorOut[ax1] = vector[ax1] * cosa - vector[ax2] * sina + vectorOut[ax2] = vector[ax2] * cosa + vector[ax1] * sina + + return vectorOut } -function eulerFromQuaternion(q) { - return [ - Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees, - Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees, - Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees - ]; +function eulerFromQuaternion (q) { + return [ + Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees, + Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees, + Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees + ] } -function cartesian(spherical) { - var lambda = spherical[0] * radians, - phi = spherical[1] * radians, - cosPhi = Math.cos(phi); - return [ - cosPhi * Math.cos(lambda), - cosPhi * Math.sin(lambda), - Math.sin(phi) - ]; +function cartesian (spherical) { + var lambda = spherical[0] * radians, + phi = spherical[1] * radians, + cosPhi = Math.cos(phi) + return [ + cosPhi * Math.cos(lambda), + cosPhi * Math.sin(lambda), + Math.sin(phi) + ] } -function dot(a, b) { - var s = 0; - for(var i = 0, n = a.length; i < n; ++i) s += a[i] * b[i]; - return s; +function dot (a, b) { + var s = 0 + for (var i = 0, n = a.length; i < n; ++i) s += a[i] * b[i] + return s } -function cross(a, b) { - return [ - a[1] * b[2] - a[2] * b[1], - a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0] - ]; +function cross (a, b) { + return [ + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0] + ] } // Like d3.dispatch, but for custom events abstracting native UI events. These // events have a target component (such as a brush), a target element (such as // the svg:g element containing the brush) and the standard arguments `d` (the // target element's data) and `i` (the selection index of the target element). -function d3_eventDispatch(target) { - var i = 0, - n = arguments.length, - argumentz = []; +function d3_eventDispatch (target) { + var i = 0, + n = arguments.length, + argumentz = [] - while(++i < n) argumentz.push(arguments[i]); + while (++i < n) argumentz.push(arguments[i]) - var dispatch = d3.dispatch.apply(null, argumentz); + var dispatch = d3.dispatch.apply(null, argumentz) // Creates a dispatch context for the specified `thiz` (typically, the target // DOM element that received the source event) and `argumentz` (typically, the @@ -407,19 +404,19 @@ function d3_eventDispatch(target) { // constructor. This context will automatically populate the "sourceEvent" and // "target" attributes of the event, as well as setting the `d3.event` global // for the duration of the notification. - dispatch.of = function(thiz, argumentz) { - return function(e1) { - var e0; - try { - e0 = e1.sourceEvent = d3.event; - e1.target = target; - d3.event = e1; - dispatch[e1.type].apply(thiz, argumentz); - } finally { - d3.event = e0; - } - }; - }; - - return dispatch; + dispatch.of = function (thiz, argumentz) { + return function (e1) { + var e0 + try { + e0 = e1.sourceEvent = d3.event + e1.target = target + d3.event = e1 + dispatch[e1.type].apply(thiz, argumentz) + } finally { + d3.event = e0 + } + } + } + + return dispatch } diff --git a/src/plots/geo/zoom_reset.js b/src/plots/geo/zoom_reset.js index f022197219e..0b9beb125c6 100644 --- a/src/plots/geo/zoom_reset.js +++ b/src/plots/geo/zoom_reset.js @@ -6,28 +6,27 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Fx = require('../cartesian/graph_interact') -var Fx = require('../cartesian/graph_interact'); +function createGeoZoomReset (geo, geoLayout) { + var projection = geo.projection, + zoom = geo.zoom -function createGeoZoomReset(geo, geoLayout) { - var projection = geo.projection, - zoom = geo.zoom; + var zoomReset = function () { + geo.makeProjection(geoLayout) + geo.makePath() - var zoomReset = function() { - geo.makeProjection(geoLayout); - geo.makePath(); + zoom.scale(projection.scale()) + zoom.translate(projection.translate()) - zoom.scale(projection.scale()); - zoom.translate(projection.translate()); + Fx.loneUnhover(geo.hoverContainer) - Fx.loneUnhover(geo.hoverContainer); + geo.render() + } - geo.render(); - }; - - return zoomReset; + return zoomReset } -module.exports = createGeoZoomReset; +module.exports = createGeoZoomReset diff --git a/src/plots/gl2d/camera.js b/src/plots/gl2d/camera.js index 405795b6b57..59e28ea4872 100644 --- a/src/plots/gl2d/camera.js +++ b/src/plots/gl2d/camera.js @@ -6,166 +6,161 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var mouseChange = require('mouse-change'); -var mouseWheel = require('mouse-wheel'); - -module.exports = createCamera; - -function Camera2D(element, plot) { - this.element = element; - this.plot = plot; - this.mouseListener = null; - this.wheelListener = null; - this.lastInputTime = Date.now(); - this.lastPos = [0, 0]; - this.boxEnabled = false; - this.boxStart = [0, 0]; - this.boxEnd = [0, 0]; +'use strict' + +var mouseChange = require('mouse-change') +var mouseWheel = require('mouse-wheel') + +module.exports = createCamera + +function Camera2D (element, plot) { + this.element = element + this.plot = plot + this.mouseListener = null + this.wheelListener = null + this.lastInputTime = Date.now() + this.lastPos = [0, 0] + this.boxEnabled = false + this.boxStart = [0, 0] + this.boxEnd = [0, 0] } +function createCamera (scene) { + var element = scene.mouseContainer, + plot = scene.glplot, + result = new Camera2D(element, plot) -function createCamera(scene) { - var element = scene.mouseContainer, - plot = scene.glplot, - result = new Camera2D(element, plot); - - function unSetAutoRange() { - scene.xaxis.autorange = false; - scene.yaxis.autorange = false; - } + function unSetAutoRange () { + scene.xaxis.autorange = false + scene.yaxis.autorange = false + } - result.mouseListener = mouseChange(element, function(buttons, x, y) { - var dataBox = scene.calcDataBox(), - viewBox = plot.viewBox; + result.mouseListener = mouseChange(element, function (buttons, x, y) { + var dataBox = scene.calcDataBox(), + viewBox = plot.viewBox - var lastX = result.lastPos[0], - lastY = result.lastPos[1]; + var lastX = result.lastPos[0], + lastY = result.lastPos[1] - x *= plot.pixelRatio; - y *= plot.pixelRatio; + x *= plot.pixelRatio + y *= plot.pixelRatio // mouseChange gives y about top; convert to about bottom - y = (viewBox[3] - viewBox[1]) - y; - - function updateRange(i0, start, end) { - var range0 = Math.min(start, end), - range1 = Math.max(start, end); - - if(range0 !== range1) { - dataBox[i0] = range0; - dataBox[i0 + 2] = range1; - result.dataBox = dataBox; - scene.setRanges(dataBox); - } - else { - scene.selectBox.selectBox = [0, 0, 1, 1]; - scene.glplot.setDirty(); - } - } + y = (viewBox[3] - viewBox[1]) - y + + function updateRange (i0, start, end) { + var range0 = Math.min(start, end), + range1 = Math.max(start, end) + + if (range0 !== range1) { + dataBox[i0] = range0 + dataBox[i0 + 2] = range1 + result.dataBox = dataBox + scene.setRanges(dataBox) + } else { + scene.selectBox.selectBox = [0, 0, 1, 1] + scene.glplot.setDirty() + } + } - switch(scene.fullLayout.dragmode) { - case 'zoom': - if(buttons) { - var dataX = x / + switch (scene.fullLayout.dragmode) { + case 'zoom': + if (buttons) { + var dataX = x / (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) + - dataBox[0]; - var dataY = y / + dataBox[0] + var dataY = y / (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) + - dataBox[1]; - - if(!result.boxEnabled) { - result.boxStart[0] = dataX; - result.boxStart[1] = dataY; - } - - result.boxEnd[0] = dataX; - result.boxEnd[1] = dataY; - - result.boxEnabled = true; - } - else if(result.boxEnabled) { - updateRange(0, result.boxStart[0], result.boxEnd[0]); - updateRange(1, result.boxStart[1], result.boxEnd[1]); - unSetAutoRange(); - result.boxEnabled = false; - scene.relayoutCallback(); - } - break; - - case 'pan': - result.boxEnabled = false; - - if(buttons) { - var dx = (lastX - x) * (dataBox[2] - dataBox[0]) / - (plot.viewBox[2] - plot.viewBox[0]); - var dy = (lastY - y) * (dataBox[3] - dataBox[1]) / - (plot.viewBox[3] - plot.viewBox[1]); - - dataBox[0] += dx; - dataBox[2] += dx; - dataBox[1] += dy; - dataBox[3] += dy; - - scene.setRanges(dataBox); - - result.panning = true; - result.lastInputTime = Date.now(); - unSetAutoRange(); - scene.cameraChanged(); - scene.handleAnnotations(); - } - else if(result.panning) { - result.panning = false; - scene.relayoutCallback(); - } - break; + dataBox[1] + + if (!result.boxEnabled) { + result.boxStart[0] = dataX + result.boxStart[1] = dataY + } + + result.boxEnd[0] = dataX + result.boxEnd[1] = dataY + + result.boxEnabled = true + } else if (result.boxEnabled) { + updateRange(0, result.boxStart[0], result.boxEnd[0]) + updateRange(1, result.boxStart[1], result.boxEnd[1]) + unSetAutoRange() + result.boxEnabled = false + scene.relayoutCallback() + } + break + + case 'pan': + result.boxEnabled = false + + if (buttons) { + var dx = (lastX - x) * (dataBox[2] - dataBox[0]) / + (plot.viewBox[2] - plot.viewBox[0]) + var dy = (lastY - y) * (dataBox[3] - dataBox[1]) / + (plot.viewBox[3] - plot.viewBox[1]) + + dataBox[0] += dx + dataBox[2] += dx + dataBox[1] += dy + dataBox[3] += dy + + scene.setRanges(dataBox) + + result.panning = true + result.lastInputTime = Date.now() + unSetAutoRange() + scene.cameraChanged() + scene.handleAnnotations() + } else if (result.panning) { + result.panning = false + scene.relayoutCallback() } + break + } - result.lastPos[0] = x; - result.lastPos[1] = y; - }); + result.lastPos[0] = x + result.lastPos[1] = y + }) - result.wheelListener = mouseWheel(element, function(dx, dy) { - var dataBox = scene.calcDataBox(), - viewBox = plot.viewBox; + result.wheelListener = mouseWheel(element, function (dx, dy) { + var dataBox = scene.calcDataBox(), + viewBox = plot.viewBox - var lastX = result.lastPos[0], - lastY = result.lastPos[1]; + var lastX = result.lastPos[0], + lastY = result.lastPos[1] - switch(scene.fullLayout.dragmode) { - case 'zoom': - break; + switch (scene.fullLayout.dragmode) { + case 'zoom': + break - case 'pan': - var scale = Math.exp(0.1 * dy / (viewBox[3] - viewBox[1])); + case 'pan': + var scale = Math.exp(0.1 * dy / (viewBox[3] - viewBox[1])) - var cx = lastX / + var cx = lastX / (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) + - dataBox[0]; - var cy = lastY / + dataBox[0] + var cy = lastY / (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) + - dataBox[1]; + dataBox[1] - dataBox[0] = (dataBox[0] - cx) * scale + cx; - dataBox[2] = (dataBox[2] - cx) * scale + cx; - dataBox[1] = (dataBox[1] - cy) * scale + cy; - dataBox[3] = (dataBox[3] - cy) * scale + cy; + dataBox[0] = (dataBox[0] - cx) * scale + cx + dataBox[2] = (dataBox[2] - cx) * scale + cx + dataBox[1] = (dataBox[1] - cy) * scale + cy + dataBox[3] = (dataBox[3] - cy) * scale + cy - scene.setRanges(dataBox); + scene.setRanges(dataBox) - result.lastInputTime = Date.now(); - unSetAutoRange(); - scene.cameraChanged(); - scene.handleAnnotations(); - scene.relayoutCallback(); - break; - } + result.lastInputTime = Date.now() + unSetAutoRange() + scene.cameraChanged() + scene.handleAnnotations() + scene.relayoutCallback() + break + } - return true; - }); + return true + }) - return result; + return result } diff --git a/src/plots/gl2d/convert.js b/src/plots/gl2d/convert.js index 78784294fe9..e04f3108cd6 100644 --- a/src/plots/gl2d/convert.js +++ b/src/plots/gl2d/convert.js @@ -6,239 +6,235 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Plots = require('../plots') +var Axes = require('../cartesian/axes') -var Plots = require('../plots'); -var Axes = require('../cartesian/axes'); +var convertHTMLToUnicode = require('../../lib/html2unicode') +var str2RGBArray = require('../../lib/str2rgbarray') -var convertHTMLToUnicode = require('../../lib/html2unicode'); -var str2RGBArray = require('../../lib/str2rgbarray'); +function Axes2DOptions (scene) { + this.scene = scene + this.gl = scene.gl + this.pixelRatio = scene.pixelRatio -function Axes2DOptions(scene) { - this.scene = scene; - this.gl = scene.gl; - this.pixelRatio = scene.pixelRatio; + this.screenBox = [0, 0, 1, 1] + this.viewBox = [0, 0, 1, 1] + this.dataBox = [-1, -1, 1, 1] - this.screenBox = [0, 0, 1, 1]; - this.viewBox = [0, 0, 1, 1]; - this.dataBox = [-1, -1, 1, 1]; - - this.borderLineEnable = [false, false, false, false]; - this.borderLineWidth = [1, 1, 1, 1]; - this.borderLineColor = [ + this.borderLineEnable = [false, false, false, false] + this.borderLineWidth = [1, 1, 1, 1] + this.borderLineColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] - ]; + ] - this.ticks = [[], []]; - this.tickEnable = [true, true, false, false]; - this.tickPad = [15, 15, 15, 15]; - this.tickAngle = [0, 0, 0, 0]; - this.tickColor = [ + this.ticks = [[], []] + this.tickEnable = [true, true, false, false] + this.tickPad = [15, 15, 15, 15] + this.tickAngle = [0, 0, 0, 0] + this.tickColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] - ]; - this.tickMarkLength = [0, 0, 0, 0]; - this.tickMarkWidth = [0, 0, 0, 0]; - this.tickMarkColor = [ + ] + this.tickMarkLength = [0, 0, 0, 0] + this.tickMarkWidth = [0, 0, 0, 0] + this.tickMarkColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] - ]; - - this.labels = ['x', 'y']; - this.labelEnable = [true, true, false, false]; - this.labelAngle = [0, Math.PI / 2, 0, 3.0 * Math.PI / 2]; - this.labelPad = [15, 15, 15, 15]; - this.labelSize = [12, 12]; - this.labelFont = ['sans-serif', 'sans-serif']; - this.labelColor = [ + ] + + this.labels = ['x', 'y'] + this.labelEnable = [true, true, false, false] + this.labelAngle = [0, Math.PI / 2, 0, 3.0 * Math.PI / 2] + this.labelPad = [15, 15, 15, 15] + this.labelSize = [12, 12] + this.labelFont = ['sans-serif', 'sans-serif'] + this.labelColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] - ]; - - this.title = ''; - this.titleEnable = true; - this.titleCenter = [0, 0, 0, 0]; - this.titleAngle = 0; - this.titleColor = [0, 0, 0, 1]; - this.titleFont = 'sans-serif'; - this.titleSize = 18; - - this.gridLineEnable = [true, true]; - this.gridLineColor = [ + ] + + this.title = '' + this.titleEnable = true + this.titleCenter = [0, 0, 0, 0] + this.titleAngle = 0 + this.titleColor = [0, 0, 0, 1] + this.titleFont = 'sans-serif' + this.titleSize = 18 + + this.gridLineEnable = [true, true] + this.gridLineColor = [ [0, 0, 0, 0.5], [0, 0, 0, 0.5] - ]; - this.gridLineWidth = [1, 1]; + ] + this.gridLineWidth = [1, 1] - this.zeroLineEnable = [true, true]; - this.zeroLineWidth = [1, 1]; - this.zeroLineColor = [ + this.zeroLineEnable = [true, true] + this.zeroLineWidth = [1, 1] + this.zeroLineColor = [ [0, 0, 0, 1], [0, 0, 0, 1] - ]; + ] - this.borderColor = [0, 0, 0, 0]; - this.backgroundColor = [0, 0, 0, 0]; + this.borderColor = [0, 0, 0, 0] + this.backgroundColor = [0, 0, 0, 0] - this.static = this.scene.staticPlot; + this.static = this.scene.staticPlot } -var proto = Axes2DOptions.prototype; - -var AXES = ['xaxis', 'yaxis']; +var proto = Axes2DOptions.prototype -proto.merge = function(options) { +var AXES = ['xaxis', 'yaxis'] +proto.merge = function (options) { // titles are rendered in SVG - this.titleEnable = false; - this.backgroundColor = str2RGBArray(options.plot_bgcolor); + this.titleEnable = false + this.backgroundColor = str2RGBArray(options.plot_bgcolor) - var axisName, ax, axTitle, axMirror; - var hasAxisInDfltPos, hasAxisInAltrPos, hasSharedAxis, mirrorLines, mirrorTicks; - var i, j; + var axisName, ax, axTitle, axMirror + var hasAxisInDfltPos, hasAxisInAltrPos, hasSharedAxis, mirrorLines, mirrorTicks + var i, j - for(i = 0; i < 2; ++i) { - axisName = AXES[i]; + for (i = 0; i < 2; ++i) { + axisName = AXES[i] // get options relevant to this subplot, // '_name' is e.g. xaxis, xaxis2, yaxis, yaxis4 ... - ax = options[this.scene[axisName]._name]; + ax = options[this.scene[axisName]._name] - axTitle = /Click to enter .+ title/.test(ax.title) ? '' : ax.title; + axTitle = /Click to enter .+ title/.test(ax.title) ? '' : ax.title - for(j = 0; j <= 2; j += 2) { - this.labelEnable[i + j] = false; - this.labels[i + j] = convertHTMLToUnicode(axTitle); - this.labelColor[i + j] = str2RGBArray(ax.titlefont.color); - this.labelFont[i + j] = ax.titlefont.family; - this.labelSize[i + j] = ax.titlefont.size; - this.labelPad[i + j] = this.getLabelPad(axisName, ax); + for (j = 0; j <= 2; j += 2) { + this.labelEnable[i + j] = false + this.labels[i + j] = convertHTMLToUnicode(axTitle) + this.labelColor[i + j] = str2RGBArray(ax.titlefont.color) + this.labelFont[i + j] = ax.titlefont.family + this.labelSize[i + j] = ax.titlefont.size + this.labelPad[i + j] = this.getLabelPad(axisName, ax) - this.tickEnable[i + j] = false; - this.tickColor[i + j] = str2RGBArray((ax.tickfont || {}).color); - this.tickAngle[i + j] = (ax.tickangle === 'auto') ? + this.tickEnable[i + j] = false + this.tickColor[i + j] = str2RGBArray((ax.tickfont || {}).color) + this.tickAngle[i + j] = (ax.tickangle === 'auto') ? 0 : - Math.PI * -ax.tickangle / 180; - this.tickPad[i + j] = this.getTickPad(ax); + Math.PI * -ax.tickangle / 180 + this.tickPad[i + j] = this.getTickPad(ax) - this.tickMarkLength[i + j] = 0; - this.tickMarkWidth[i + j] = ax.tickwidth || 0; - this.tickMarkColor[i + j] = str2RGBArray(ax.tickcolor); + this.tickMarkLength[i + j] = 0 + this.tickMarkWidth[i + j] = ax.tickwidth || 0 + this.tickMarkColor[i + j] = str2RGBArray(ax.tickcolor) - this.borderLineEnable[i + j] = false; - this.borderLineColor[i + j] = str2RGBArray(ax.linecolor); - this.borderLineWidth[i + j] = ax.linewidth || 0; - } + this.borderLineEnable[i + j] = false + this.borderLineColor[i + j] = str2RGBArray(ax.linecolor) + this.borderLineWidth[i + j] = ax.linewidth || 0 + } - hasSharedAxis = this.hasSharedAxis(ax); - hasAxisInDfltPos = this.hasAxisInDfltPos(axisName, ax) && !hasSharedAxis; - hasAxisInAltrPos = this.hasAxisInAltrPos(axisName, ax) && !hasSharedAxis; + hasSharedAxis = this.hasSharedAxis(ax) + hasAxisInDfltPos = this.hasAxisInDfltPos(axisName, ax) && !hasSharedAxis + hasAxisInAltrPos = this.hasAxisInAltrPos(axisName, ax) && !hasSharedAxis - axMirror = ax.mirror || false; - mirrorLines = hasSharedAxis ? + axMirror = ax.mirror || false + mirrorLines = hasSharedAxis ? (String(axMirror).indexOf('all') !== -1) : // 'all' or 'allticks' - !!axMirror; // all but false - mirrorTicks = hasSharedAxis ? + !!axMirror // all but false + mirrorTicks = hasSharedAxis ? (axMirror === 'allticks') : - (String(axMirror).indexOf('ticks') !== -1); // 'ticks' or 'allticks' + (String(axMirror).indexOf('ticks') !== -1) // 'ticks' or 'allticks' // Axis titles and tick labels can only appear of one side of the scene // and are never show on subplots that share existing axes. - if(hasAxisInDfltPos) this.labelEnable[i] = true; - else if(hasAxisInAltrPos) this.labelEnable[i + 2] = true; + if (hasAxisInDfltPos) this.labelEnable[i] = true + else if (hasAxisInAltrPos) this.labelEnable[i + 2] = true - if(hasAxisInDfltPos) this.tickEnable[i] = ax.showticklabels; - else if(hasAxisInAltrPos) this.tickEnable[i + 2] = ax.showticklabels; + if (hasAxisInDfltPos) this.tickEnable[i] = ax.showticklabels + else if (hasAxisInAltrPos) this.tickEnable[i + 2] = ax.showticklabels // Grid lines and ticks can appear on both sides of the scene // and can appear on subplot that share existing axes via `ax.mirror`. - if(hasAxisInDfltPos || mirrorLines) this.borderLineEnable[i] = ax.showline; - if(hasAxisInAltrPos || mirrorLines) this.borderLineEnable[i + 2] = ax.showline; + if (hasAxisInDfltPos || mirrorLines) this.borderLineEnable[i] = ax.showline + if (hasAxisInAltrPos || mirrorLines) this.borderLineEnable[i + 2] = ax.showline - if(hasAxisInDfltPos || mirrorTicks) this.tickMarkLength[i] = this.getTickMarkLength(ax); - if(hasAxisInAltrPos || mirrorTicks) this.tickMarkLength[i + 2] = this.getTickMarkLength(ax); + if (hasAxisInDfltPos || mirrorTicks) this.tickMarkLength[i] = this.getTickMarkLength(ax) + if (hasAxisInAltrPos || mirrorTicks) this.tickMarkLength[i + 2] = this.getTickMarkLength(ax) - this.gridLineEnable[i] = ax.showgrid; - this.gridLineColor[i] = str2RGBArray(ax.gridcolor); - this.gridLineWidth[i] = ax.gridwidth; + this.gridLineEnable[i] = ax.showgrid + this.gridLineColor[i] = str2RGBArray(ax.gridcolor) + this.gridLineWidth[i] = ax.gridwidth - this.zeroLineEnable[i] = ax.zeroline; - this.zeroLineColor[i] = str2RGBArray(ax.zerolinecolor); - this.zeroLineWidth[i] = ax.zerolinewidth; - } -}; + this.zeroLineEnable[i] = ax.zeroline + this.zeroLineColor[i] = str2RGBArray(ax.zerolinecolor) + this.zeroLineWidth[i] = ax.zerolinewidth + } +} // is an axis shared with an already-drawn subplot ? -proto.hasSharedAxis = function(ax) { - var scene = this.scene, - subplotIds = Plots.getSubplotIds(scene.fullLayout, 'gl2d'), - list = Axes.findSubplotsWithAxis(subplotIds, ax); +proto.hasSharedAxis = function (ax) { + var scene = this.scene, + subplotIds = Plots.getSubplotIds(scene.fullLayout, 'gl2d'), + list = Axes.findSubplotsWithAxis(subplotIds, ax) // if index === 0, then the subplot is already drawn as subplots // are drawn in order. - return (list.indexOf(scene.id) !== 0); -}; + return (list.indexOf(scene.id) !== 0) +} // has an axis in default position (i.e. bottom/left) ? -proto.hasAxisInDfltPos = function(axisName, ax) { - var axSide = ax.side; +proto.hasAxisInDfltPos = function (axisName, ax) { + var axSide = ax.side - if(axisName === 'xaxis') return (axSide === 'bottom'); - else if(axisName === 'yaxis') return (axSide === 'left'); -}; + if (axisName === 'xaxis') return (axSide === 'bottom') + else if (axisName === 'yaxis') return (axSide === 'left') +} // has an axis in alternate position (i.e. top/right) ? -proto.hasAxisInAltrPos = function(axisName, ax) { - var axSide = ax.side; +proto.hasAxisInAltrPos = function (axisName, ax) { + var axSide = ax.side - if(axisName === 'xaxis') return (axSide === 'top'); - else if(axisName === 'yaxis') return (axSide === 'right'); -}; + if (axisName === 'xaxis') return (axSide === 'top') + else if (axisName === 'yaxis') return (axSide === 'right') +} -proto.getLabelPad = function(axisName, ax) { - var offsetBase = 1.5, - fontSize = ax.titlefont.size, - showticklabels = ax.showticklabels; +proto.getLabelPad = function (axisName, ax) { + var offsetBase = 1.5, + fontSize = ax.titlefont.size, + showticklabels = ax.showticklabels - if(axisName === 'xaxis') { - return (ax.side === 'top') ? + if (axisName === 'xaxis') { + return (ax.side === 'top') ? -10 + fontSize * (offsetBase + (showticklabels ? 1 : 0)) : - -10 + fontSize * (offsetBase + (showticklabels ? 0.5 : 0)); - } - else if(axisName === 'yaxis') { - return (ax.side === 'right') ? + -10 + fontSize * (offsetBase + (showticklabels ? 0.5 : 0)) + } else if (axisName === 'yaxis') { + return (ax.side === 'right') ? 10 + fontSize * (offsetBase + (showticklabels ? 1 : 0.5)) : - 10 + fontSize * (offsetBase + (showticklabels ? 0.5 : 0)); - } -}; - -proto.getTickPad = function(ax) { - return (ax.ticks === 'outside') ? 10 + ax.ticklen : 15; -}; + 10 + fontSize * (offsetBase + (showticklabels ? 0.5 : 0)) + } +} -proto.getTickMarkLength = function(ax) { - if(!ax.ticks) return 0; +proto.getTickPad = function (ax) { + return (ax.ticks === 'outside') ? 10 + ax.ticklen : 15 +} - var ticklen = ax.ticklen; +proto.getTickMarkLength = function (ax) { + if (!ax.ticks) return 0 - return (ax.ticks === 'inside') ? -ticklen : ticklen; -}; + var ticklen = ax.ticklen + return (ax.ticks === 'inside') ? -ticklen : ticklen +} -function createAxes2D(scene) { - return new Axes2DOptions(scene); +function createAxes2D (scene) { + return new Axes2DOptions(scene) } -module.exports = createAxes2D; +module.exports = createAxes2D diff --git a/src/plots/gl2d/index.js b/src/plots/gl2d/index.js index 2d4f2f7f099..af8c94fb56c 100644 --- a/src/plots/gl2d/index.js +++ b/src/plots/gl2d/index.js @@ -6,105 +6,103 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Scene2D = require('./scene2d') +var Plots = require('../plots') +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') -var Scene2D = require('./scene2d'); -var Plots = require('../plots'); -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); +exports.name = 'gl2d' +exports.attr = ['xaxis', 'yaxis'] -exports.name = 'gl2d'; - -exports.attr = ['xaxis', 'yaxis']; - -exports.idRoot = ['x', 'y']; +exports.idRoot = ['x', 'y'] exports.idRegex = { - x: /^x([2-9]|[1-9][0-9]+)?$/, - y: /^y([2-9]|[1-9][0-9]+)?$/ -}; + x: /^x([2-9]|[1-9][0-9]+)?$/, + y: /^y([2-9]|[1-9][0-9]+)?$/ +} exports.attrRegex = { - x: /^xaxis([2-9]|[1-9][0-9]+)?$/, - y: /^yaxis([2-9]|[1-9][0-9]+)?$/ -}; + x: /^xaxis([2-9]|[1-9][0-9]+)?$/, + y: /^yaxis([2-9]|[1-9][0-9]+)?$/ +} -exports.attributes = require('../cartesian/attributes'); +exports.attributes = require('../cartesian/attributes') -exports.plot = function plotGl2d(gd) { - var fullLayout = gd._fullLayout, - fullData = gd._fullData, - subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'); +exports.plot = function plotGl2d (gd) { + var fullLayout = gd._fullLayout, + fullData = gd._fullData, + subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d') - for(var i = 0; i < subplotIds.length; i++) { - var subplotId = subplotIds[i], - subplotObj = fullLayout._plots[subplotId], - fullSubplotData = Plots.getSubplotData(fullData, 'gl2d', subplotId); + for (var i = 0; i < subplotIds.length; i++) { + var subplotId = subplotIds[i], + subplotObj = fullLayout._plots[subplotId], + fullSubplotData = Plots.getSubplotData(fullData, 'gl2d', subplotId) // ref. to corresp. Scene instance - var scene = subplotObj._scene2d; + var scene = subplotObj._scene2d // If Scene is not instantiated, create one! - if(scene === undefined) { - scene = new Scene2D({ - id: subplotId, - graphDiv: gd, - container: gd.querySelector('.gl-container'), - staticPlot: gd._context.staticPlot, - plotGlPixelRatio: gd._context.plotGlPixelRatio - }, + if (scene === undefined) { + scene = new Scene2D({ + id: subplotId, + graphDiv: gd, + container: gd.querySelector('.gl-container'), + staticPlot: gd._context.staticPlot, + plotGlPixelRatio: gd._context.plotGlPixelRatio + }, fullLayout - ); + ) // set ref to Scene instance - subplotObj._scene2d = scene; - } - - scene.plot(fullSubplotData, gd.calcdata, fullLayout, gd.layout); + subplotObj._scene2d = scene } -}; -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl2d'); + scene.plot(fullSubplotData, gd.calcdata, fullLayout, gd.layout) + } +} - for(var i = 0; i < oldSceneKeys.length; i++) { - var id = oldSceneKeys[i], - oldSubplot = oldFullLayout._plots[id]; +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl2d') + + for (var i = 0; i < oldSceneKeys.length; i++) { + var id = oldSceneKeys[i], + oldSubplot = oldFullLayout._plots[id] // old subplot wasn't gl2d; nothing to do - if(!oldSubplot._scene2d) continue; + if (!oldSubplot._scene2d) continue // if no traces are present, delete gl2d subplot - var subplotData = Plots.getSubplotData(newFullData, 'gl2d', id); - if(subplotData.length === 0) { - oldSubplot._scene2d.destroy(); - delete oldFullLayout._plots[id]; - } - } -}; - -exports.toSVG = function(gd) { - var fullLayout = gd._fullLayout, - subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'); - - for(var i = 0; i < subplotIds.length; i++) { - var subplot = fullLayout._plots[subplotIds[i]], - scene = subplot._scene2d; - - var imageData = scene.toImage('png'); - var image = fullLayout._glimages.append('svg:image'); - - image.attr({ - xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData, - x: 0, - y: 0, - width: '100%', - height: '100%', - preserveAspectRatio: 'none' - }); - - scene.destroy(); + var subplotData = Plots.getSubplotData(newFullData, 'gl2d', id) + if (subplotData.length === 0) { + oldSubplot._scene2d.destroy() + delete oldFullLayout._plots[id] } -}; + } +} + +exports.toSVG = function (gd) { + var fullLayout = gd._fullLayout, + subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d') + + for (var i = 0; i < subplotIds.length; i++) { + var subplot = fullLayout._plots[subplotIds[i]], + scene = subplot._scene2d + + var imageData = scene.toImage('png') + var image = fullLayout._glimages.append('svg:image') + + image.attr({ + xmlns: xmlnsNamespaces.svg, + 'xlink:href': imageData, + x: 0, + y: 0, + width: '100%', + height: '100%', + preserveAspectRatio: 'none' + }) + + scene.destroy() + } +} diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index d86303c29f9..070fa379b86 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -6,631 +6,623 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') +var Fx = require('../../plots/cartesian/graph_interact') -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); -var Fx = require('../../plots/cartesian/graph_interact'); +var createPlot2D = require('gl-plot2d') +var createSpikes = require('gl-spikes2d') +var createSelectBox = require('gl-select-box') +var getContext = require('webgl-context') -var createPlot2D = require('gl-plot2d'); -var createSpikes = require('gl-spikes2d'); -var createSelectBox = require('gl-select-box'); -var getContext = require('webgl-context'); +var createOptions = require('./convert') +var createCamera = require('./camera') +var convertHTMLToUnicode = require('../../lib/html2unicode') +var showNoWebGlMsg = require('../../lib/show_no_webgl_msg') -var createOptions = require('./convert'); -var createCamera = require('./camera'); -var convertHTMLToUnicode = require('../../lib/html2unicode'); -var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); +var AXES = ['xaxis', 'yaxis'] +var STATIC_CANVAS, STATIC_CONTEXT -var AXES = ['xaxis', 'yaxis']; -var STATIC_CANVAS, STATIC_CONTEXT; +function Scene2D (options, fullLayout) { + this.container = options.container + this.graphDiv = options.graphDiv + this.pixelRatio = options.plotGlPixelRatio || window.devicePixelRatio + this.id = options.id + this.staticPlot = !!options.staticPlot + this.fullData = null + this.updateRefs(fullLayout) -function Scene2D(options, fullLayout) { - this.container = options.container; - this.graphDiv = options.graphDiv; - this.pixelRatio = options.plotGlPixelRatio || window.devicePixelRatio; - this.id = options.id; - this.staticPlot = !!options.staticPlot; - - this.fullData = null; - this.updateRefs(fullLayout); - - this.makeFramework(); + this.makeFramework() // update options - this.glplotOptions = createOptions(this); - this.glplotOptions.merge(fullLayout); + this.glplotOptions = createOptions(this) + this.glplotOptions.merge(fullLayout) // create the plot - this.glplot = createPlot2D(this.glplotOptions); + this.glplot = createPlot2D(this.glplotOptions) // create camera - this.camera = createCamera(this); + this.camera = createCamera(this) // trace set - this.traces = {}; - this._inputs = {}; + this.traces = {} + this._inputs = {} // create axes spikes - this.spikes = createSpikes(this.glplot); + this.spikes = createSpikes(this.glplot) - this.selectBox = createSelectBox(this.glplot, { - innerFill: false, - outerFill: true - }); + this.selectBox = createSelectBox(this.glplot, { + innerFill: false, + outerFill: true + }) // last button state - this.lastButtonState = 0; + this.lastButtonState = 0 // last pick result - this.pickResult = null; + this.pickResult = null - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; + this.bounds = [Infinity, Infinity, -Infinity, -Infinity] // flag to stop render loop - this.stopped = false; + this.stopped = false // redraw the plot - this.redraw = this.draw.bind(this); - this.redraw(); + this.redraw = this.draw.bind(this) + this.redraw() } -module.exports = Scene2D; +module.exports = Scene2D -var proto = Scene2D.prototype; - -proto.makeFramework = function() { +var proto = Scene2D.prototype +proto.makeFramework = function () { // create canvas and gl context - if(this.staticPlot) { - if(!STATIC_CONTEXT) { - STATIC_CANVAS = document.createElement('canvas'); - - STATIC_CONTEXT = getContext({ - canvas: STATIC_CANVAS, - preserveDrawingBuffer: false, - premultipliedAlpha: true, - antialias: true - }); - - if(!STATIC_CONTEXT) { - throw new Error('Error creating static canvas/context for image server'); - } - } - - this.canvas = STATIC_CANVAS; - this.gl = STATIC_CONTEXT; + if (this.staticPlot) { + if (!STATIC_CONTEXT) { + STATIC_CANVAS = document.createElement('canvas') + + STATIC_CONTEXT = getContext({ + canvas: STATIC_CANVAS, + preserveDrawingBuffer: false, + premultipliedAlpha: true, + antialias: true + }) + + if (!STATIC_CONTEXT) { + throw new Error('Error creating static canvas/context for image server') + } } - else { - var liveCanvas = document.createElement('canvas'); - var gl = getContext({ - canvas: liveCanvas, - premultipliedAlpha: true - }); + this.canvas = STATIC_CANVAS + this.gl = STATIC_CONTEXT + } else { + var liveCanvas = document.createElement('canvas') - if(!gl) showNoWebGlMsg(this); + var gl = getContext({ + canvas: liveCanvas, + premultipliedAlpha: true + }) - this.canvas = liveCanvas; - this.gl = gl; - } + if (!gl) showNoWebGlMsg(this) + + this.canvas = liveCanvas + this.gl = gl + } // position the canvas - var canvas = this.canvas; + var canvas = this.canvas - canvas.style.width = '100%'; - canvas.style.height = '100%'; - canvas.style.position = 'absolute'; - canvas.style.top = '0px'; - canvas.style.left = '0px'; - canvas.style['pointer-events'] = 'none'; + canvas.style.width = '100%' + canvas.style.height = '100%' + canvas.style.position = 'absolute' + canvas.style.top = '0px' + canvas.style.left = '0px' + canvas.style['pointer-events'] = 'none' - this.updateSize(canvas); + this.updateSize(canvas) // disabling user select on the canvas // sanitizes double-clicks interactions // ref: https://github.com/plotly/plotly.js/issues/744 - canvas.className += 'user-select-none'; + canvas.className += 'user-select-none' // create SVG container for hover text - var svgContainer = this.svgContainer = document.createElementNS( + var svgContainer = this.svgContainer = document.createElementNS( 'http://www.w3.org/2000/svg', - 'svg'); - svgContainer.style.position = 'absolute'; - svgContainer.style.top = svgContainer.style.left = '0px'; - svgContainer.style.width = svgContainer.style.height = '100%'; - svgContainer.style['z-index'] = 20; - svgContainer.style['pointer-events'] = 'none'; + 'svg') + svgContainer.style.position = 'absolute' + svgContainer.style.top = svgContainer.style.left = '0px' + svgContainer.style.width = svgContainer.style.height = '100%' + svgContainer.style['z-index'] = 20 + svgContainer.style['pointer-events'] = 'none' // create div to catch the mouse event - var mouseContainer = this.mouseContainer = document.createElement('div'); - mouseContainer.style.position = 'absolute'; + var mouseContainer = this.mouseContainer = document.createElement('div') + mouseContainer.style.position = 'absolute' // append canvas, hover svg and mouse div to container - var container = this.container; - container.appendChild(canvas); - container.appendChild(svgContainer); - container.appendChild(mouseContainer); -}; + var container = this.container + container.appendChild(canvas) + container.appendChild(svgContainer) + container.appendChild(mouseContainer) +} -proto.toImage = function(format) { - if(!format) format = 'png'; +proto.toImage = function (format) { + if (!format) format = 'png' - this.stopped = true; - if(this.staticPlot) this.container.appendChild(STATIC_CANVAS); + this.stopped = true + if (this.staticPlot) this.container.appendChild(STATIC_CANVAS) // update canvas size - this.updateSize(this.canvas); + this.updateSize(this.canvas) // force redraw - this.glplot.setDirty(); - this.glplot.draw(); + this.glplot.setDirty() + this.glplot.draw() // grab context and yank out pixels - var gl = this.glplot.gl, - w = gl.drawingBufferWidth, - h = gl.drawingBufferHeight; + var gl = this.glplot.gl, + w = gl.drawingBufferWidth, + h = gl.drawingBufferHeight - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null) - var pixels = new Uint8Array(w * h * 4); - gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + var pixels = new Uint8Array(w * h * 4) + gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels) // flip pixels - for(var j = 0, k = h - 1; j < k; ++j, --k) { - for(var i = 0; i < w; ++i) { - for(var l = 0; l < 4; ++l) { - var tmp = pixels[4 * (w * j + i) + l]; - pixels[4 * (w * j + i) + l] = pixels[4 * (w * k + i) + l]; - pixels[4 * (w * k + i) + l] = tmp; - } - } + for (var j = 0, k = h - 1; j < k; ++j, --k) { + for (var i = 0; i < w; ++i) { + for (var l = 0; l < 4; ++l) { + var tmp = pixels[4 * (w * j + i) + l] + pixels[4 * (w * j + i) + l] = pixels[4 * (w * k + i) + l] + pixels[4 * (w * k + i) + l] = tmp + } } + } - var canvas = document.createElement('canvas'); - canvas.width = w; - canvas.height = h; - - var context = canvas.getContext('2d'); - var imageData = context.createImageData(w, h); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); - - var dataURL; - - switch(format) { - case 'jpeg': - dataURL = canvas.toDataURL('image/jpeg'); - break; - case 'webp': - dataURL = canvas.toDataURL('image/webp'); - break; - default: - dataURL = canvas.toDataURL('image/png'); - } + var canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h + + var context = canvas.getContext('2d') + var imageData = context.createImageData(w, h) + imageData.data.set(pixels) + context.putImageData(imageData, 0, 0) - if(this.staticPlot) this.container.removeChild(STATIC_CANVAS); + var dataURL - return dataURL; -}; + switch (format) { + case 'jpeg': + dataURL = canvas.toDataURL('image/jpeg') + break + case 'webp': + dataURL = canvas.toDataURL('image/webp') + break + default: + dataURL = canvas.toDataURL('image/png') + } + + if (this.staticPlot) this.container.removeChild(STATIC_CANVAS) + + return dataURL +} -proto.updateSize = function(canvas) { - if(!canvas) canvas = this.canvas; +proto.updateSize = function (canvas) { + if (!canvas) canvas = this.canvas - var pixelRatio = this.pixelRatio, - fullLayout = this.fullLayout; + var pixelRatio = this.pixelRatio, + fullLayout = this.fullLayout - var width = fullLayout.width, - height = fullLayout.height, - pixelWidth = Math.ceil(pixelRatio * width) |0, - pixelHeight = Math.ceil(pixelRatio * height) |0; + var width = fullLayout.width, + height = fullLayout.height, + pixelWidth = Math.ceil(pixelRatio * width) | 0, + pixelHeight = Math.ceil(pixelRatio * height) | 0 // check for resize - if(canvas.width !== pixelWidth || canvas.height !== pixelHeight) { - canvas.width = pixelWidth; - canvas.height = pixelHeight; - } + if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) { + canvas.width = pixelWidth + canvas.height = pixelHeight + } - return canvas; -}; + return canvas +} -proto.computeTickMarks = function() { - this.xaxis.setScale(); - this.yaxis.setScale(); +proto.computeTickMarks = function () { + this.xaxis.setScale() + this.yaxis.setScale() // override _length from backward compatibility // even though setScale 'should' give the correct result - this.xaxis._length = - this.glplot.viewBox[2] - this.glplot.viewBox[0]; - this.yaxis._length = - this.glplot.viewBox[3] - this.glplot.viewBox[1]; - - var nextTicks = [ - Axes.calcTicks(this.xaxis), - Axes.calcTicks(this.yaxis) - ]; - - for(var j = 0; j < 2; ++j) { - for(var i = 0; i < nextTicks[j].length; ++i) { + this.xaxis._length = + this.glplot.viewBox[2] - this.glplot.viewBox[0] + this.yaxis._length = + this.glplot.viewBox[3] - this.glplot.viewBox[1] + + var nextTicks = [ + Axes.calcTicks(this.xaxis), + Axes.calcTicks(this.yaxis) + ] + + for (var j = 0; j < 2; ++j) { + for (var i = 0; i < nextTicks[j].length; ++i) { // coercing tick value (may not be a string) to a string - nextTicks[j][i].text = convertHTMLToUnicode(nextTicks[j][i].text + ''); - } + nextTicks[j][i].text = convertHTMLToUnicode(nextTicks[j][i].text + '') } + } - return nextTicks; -}; + return nextTicks +} -function compareTicks(a, b) { - for(var i = 0; i < 2; ++i) { - var aticks = a[i], - bticks = b[i]; +function compareTicks (a, b) { + for (var i = 0; i < 2; ++i) { + var aticks = a[i], + bticks = b[i] - if(aticks.length !== bticks.length) return true; + if (aticks.length !== bticks.length) return true - for(var j = 0; j < aticks.length; ++j) { - if(aticks[j].x !== bticks[j].x) return true; - } + for (var j = 0; j < aticks.length; ++j) { + if (aticks[j].x !== bticks[j].x) return true } + } - return false; + return false } -proto.updateRefs = function(newFullLayout) { - this.fullLayout = newFullLayout; +proto.updateRefs = function (newFullLayout) { + this.fullLayout = newFullLayout - var spmatch = Axes.subplotMatch, - xaxisName = 'xaxis' + this.id.match(spmatch)[1], - yaxisName = 'yaxis' + this.id.match(spmatch)[2]; + var spmatch = Axes.subplotMatch, + xaxisName = 'xaxis' + this.id.match(spmatch)[1], + yaxisName = 'yaxis' + this.id.match(spmatch)[2] - this.xaxis = this.fullLayout[xaxisName]; - this.yaxis = this.fullLayout[yaxisName]; -}; + this.xaxis = this.fullLayout[xaxisName] + this.yaxis = this.fullLayout[yaxisName] +} -proto.relayoutCallback = function() { - var graphDiv = this.graphDiv, - xaxis = this.xaxis, - yaxis = this.yaxis, - layout = graphDiv.layout; +proto.relayoutCallback = function () { + var graphDiv = this.graphDiv, + xaxis = this.xaxis, + yaxis = this.yaxis, + layout = graphDiv.layout // update user layout - layout.xaxis.autorange = xaxis.autorange; - layout.xaxis.range = xaxis.range.slice(0); - layout.yaxis.autorange = yaxis.autorange; - layout.yaxis.range = yaxis.range.slice(0); + layout.xaxis.autorange = xaxis.autorange + layout.xaxis.range = xaxis.range.slice(0) + layout.yaxis.autorange = yaxis.autorange + layout.yaxis.range = yaxis.range.slice(0) // make a meaningful value to be passed on to the possible 'plotly_relayout' subscriber(s) // scene.camera has no many useful projection or scale information // helps determine which one is the latest input (if async) - var update = { - lastInputTime: this.camera.lastInputTime - }; + var update = { + lastInputTime: this.camera.lastInputTime + } - update[xaxis._name] = xaxis.range.slice(0); - update[yaxis._name] = yaxis.range.slice(0); + update[xaxis._name] = xaxis.range.slice(0) + update[yaxis._name] = yaxis.range.slice(0) - graphDiv.emit('plotly_relayout', update); -}; + graphDiv.emit('plotly_relayout', update) +} -proto.cameraChanged = function() { - var camera = this.camera; +proto.cameraChanged = function () { + var camera = this.camera - this.glplot.setDataBox(this.calcDataBox()); + this.glplot.setDataBox(this.calcDataBox()) - var nextTicks = this.computeTickMarks(); - var curTicks = this.glplotOptions.ticks; + var nextTicks = this.computeTickMarks() + var curTicks = this.glplotOptions.ticks - if(compareTicks(nextTicks, curTicks)) { - this.glplotOptions.ticks = nextTicks; - this.glplotOptions.dataBox = camera.dataBox; - this.glplot.update(this.glplotOptions); - this.handleAnnotations(); - } -}; + if (compareTicks(nextTicks, curTicks)) { + this.glplotOptions.ticks = nextTicks + this.glplotOptions.dataBox = camera.dataBox + this.glplot.update(this.glplotOptions) + this.handleAnnotations() + } +} -proto.handleAnnotations = function() { - var gd = this.graphDiv, - annotations = this.fullLayout.annotations; +proto.handleAnnotations = function () { + var gd = this.graphDiv, + annotations = this.fullLayout.annotations - for(var i = 0; i < annotations.length; i++) { - var ann = annotations[i]; + for (var i = 0; i < annotations.length; i++) { + var ann = annotations[i] - if(ann.xref === this.xaxis._id && ann.yref === this.yaxis._id) { - Registry.getComponentMethod('annotations', 'drawOne')(gd, i); - } + if (ann.xref === this.xaxis._id && ann.yref === this.yaxis._id) { + Registry.getComponentMethod('annotations', 'drawOne')(gd, i) } -}; + } +} -proto.destroy = function() { - var traces = this.traces; +proto.destroy = function () { + var traces = this.traces - if(traces) { - Object.keys(traces).map(function(key) { - traces[key].dispose(); - delete traces[key]; - }); - } + if (traces) { + Object.keys(traces).map(function (key) { + traces[key].dispose() + delete traces[key] + }) + } - this.glplot.dispose(); + this.glplot.dispose() - if(!this.staticPlot) this.container.removeChild(this.canvas); - this.container.removeChild(this.svgContainer); - this.container.removeChild(this.mouseContainer); + if (!this.staticPlot) this.container.removeChild(this.canvas) + this.container.removeChild(this.svgContainer) + this.container.removeChild(this.mouseContainer) - this.fullData = null; - this._inputs = null; - this.glplot = null; - this.stopped = true; -}; + this.fullData = null + this._inputs = null + this.glplot = null + this.stopped = true +} -proto.plot = function(fullData, calcData, fullLayout) { - var glplot = this.glplot; +proto.plot = function (fullData, calcData, fullLayout) { + var glplot = this.glplot - this.updateRefs(fullLayout); - this.updateTraces(fullData, calcData); + this.updateRefs(fullLayout) + this.updateTraces(fullData, calcData) - var width = fullLayout.width, - height = fullLayout.height; + var width = fullLayout.width, + height = fullLayout.height - this.updateSize(this.canvas); + this.updateSize(this.canvas) - var options = this.glplotOptions; - options.merge(fullLayout); - options.screenBox = [0, 0, width, height]; + var options = this.glplotOptions + options.merge(fullLayout) + options.screenBox = [0, 0, width, height] - var size = fullLayout._size, - domainX = this.xaxis.domain, - domainY = this.yaxis.domain; - - options.viewBox = [ - size.l + domainX[0] * size.w, - size.b + domainY[0] * size.h, - (width - size.r) - (1 - domainX[1]) * size.w, - (height - size.t) - (1 - domainY[1]) * size.h - ]; - - this.mouseContainer.style.width = size.w * (domainX[1] - domainX[0]) + 'px'; - this.mouseContainer.style.height = size.h * (domainY[1] - domainY[0]) + 'px'; - this.mouseContainer.height = size.h * (domainY[1] - domainY[0]); - this.mouseContainer.style.left = size.l + domainX[0] * size.w + 'px'; - this.mouseContainer.style.top = size.t + (1 - domainY[1]) * size.h + 'px'; - - var bounds = this.bounds; - bounds[0] = bounds[1] = Infinity; - bounds[2] = bounds[3] = -Infinity; - - var traceIds = Object.keys(this.traces); - var ax, i; - - for(i = 0; i < traceIds.length; ++i) { - var traceObj = this.traces[traceIds[i]]; - - for(var k = 0; k < 2; ++k) { - bounds[k] = Math.min(bounds[k], traceObj.bounds[k]); - bounds[k + 2] = Math.max(bounds[k + 2], traceObj.bounds[k + 2]); - } - } + var size = fullLayout._size, + domainX = this.xaxis.domain, + domainY = this.yaxis.domain - for(i = 0; i < 2; ++i) { - if(bounds[i] > bounds[i + 2]) { - bounds[i] = -1; - bounds[i + 2] = 1; - } + options.viewBox = [ + size.l + domainX[0] * size.w, + size.b + domainY[0] * size.h, + (width - size.r) - (1 - domainX[1]) * size.w, + (height - size.t) - (1 - domainY[1]) * size.h + ] + + this.mouseContainer.style.width = size.w * (domainX[1] - domainX[0]) + 'px' + this.mouseContainer.style.height = size.h * (domainY[1] - domainY[0]) + 'px' + this.mouseContainer.height = size.h * (domainY[1] - domainY[0]) + this.mouseContainer.style.left = size.l + domainX[0] * size.w + 'px' + this.mouseContainer.style.top = size.t + (1 - domainY[1]) * size.h + 'px' + + var bounds = this.bounds + bounds[0] = bounds[1] = Infinity + bounds[2] = bounds[3] = -Infinity - ax = this[AXES[i]]; - ax._length = options.viewBox[i + 2] - options.viewBox[i]; + var traceIds = Object.keys(this.traces) + var ax, i - Axes.doAutoRange(ax); - ax.setScale(); + for (i = 0; i < traceIds.length; ++i) { + var traceObj = this.traces[traceIds[i]] + + for (var k = 0; k < 2; ++k) { + bounds[k] = Math.min(bounds[k], traceObj.bounds[k]) + bounds[k + 2] = Math.max(bounds[k + 2], traceObj.bounds[k + 2]) + } + } + + for (i = 0; i < 2; ++i) { + if (bounds[i] > bounds[i + 2]) { + bounds[i] = -1 + bounds[i + 2] = 1 } - options.ticks = this.computeTickMarks(); + ax = this[AXES[i]] + ax._length = options.viewBox[i + 2] - options.viewBox[i] + + Axes.doAutoRange(ax) + ax.setScale() + } - options.dataBox = this.calcDataBox(); + options.ticks = this.computeTickMarks() - options.merge(fullLayout); - glplot.update(options); + options.dataBox = this.calcDataBox() + + options.merge(fullLayout) + glplot.update(options) // force redraw so that promise is returned when rendering is completed - this.glplot.draw(); -}; + this.glplot.draw() +} -proto.calcDataBox = function() { - var xaxis = this.xaxis, - yaxis = this.yaxis, - xrange = xaxis.range, - yrange = yaxis.range, - xr2l = xaxis.r2l, - yr2l = yaxis.r2l; +proto.calcDataBox = function () { + var xaxis = this.xaxis, + yaxis = this.yaxis, + xrange = xaxis.range, + yrange = yaxis.range, + xr2l = xaxis.r2l, + yr2l = yaxis.r2l - return [xr2l(xrange[0]), yr2l(yrange[0]), xr2l(xrange[1]), yr2l(yrange[1])]; -}; + return [xr2l(xrange[0]), yr2l(yrange[0]), xr2l(xrange[1]), yr2l(yrange[1])] +} -proto.setRanges = function(dataBox) { - var xaxis = this.xaxis, - yaxis = this.yaxis, - xl2r = xaxis.l2r, - yl2r = yaxis.l2r; +proto.setRanges = function (dataBox) { + var xaxis = this.xaxis, + yaxis = this.yaxis, + xl2r = xaxis.l2r, + yl2r = yaxis.l2r - xaxis.range = [xl2r(dataBox[0]), xl2r(dataBox[2])]; - yaxis.range = [yl2r(dataBox[1]), yl2r(dataBox[3])]; -}; + xaxis.range = [xl2r(dataBox[0]), xl2r(dataBox[2])] + yaxis.range = [yl2r(dataBox[1]), yl2r(dataBox[3])] +} -proto.updateTraces = function(fullData, calcData) { - var traceIds = Object.keys(this.traces); - var i, j, fullTrace; +proto.updateTraces = function (fullData, calcData) { + var traceIds = Object.keys(this.traces) + var i, j, fullTrace - this.fullData = fullData; + this.fullData = fullData // remove empty traces - trace_id_loop: - for(i = 0; i < traceIds.length; i++) { - var oldUid = traceIds[i], - oldTrace = this.traces[oldUid]; + trace_id_loop: + for (i = 0; i < traceIds.length; i++) { + var oldUid = traceIds[i], + oldTrace = this.traces[oldUid] - for(j = 0; j < fullData.length; j++) { - fullTrace = fullData[j]; + for (j = 0; j < fullData.length; j++) { + fullTrace = fullData[j] - if(fullTrace.uid === oldUid && fullTrace.type === oldTrace.type) { - continue trace_id_loop; - } + if (fullTrace.uid === oldUid && fullTrace.type === oldTrace.type) { + continue trace_id_loop } + } - oldTrace.dispose(); - delete this.traces[oldUid]; + oldTrace.dispose() + delete this.traces[oldUid] } // update / create trace objects - for(i = 0; i < fullData.length; i++) { - fullTrace = fullData[i]; - this._inputs[fullTrace.uid] = i; - var calcTrace = calcData[i], - traceObj = this.traces[fullTrace.uid]; - - if(traceObj) traceObj.update(fullTrace, calcTrace); - else { - traceObj = fullTrace._module.plot(this, fullTrace, calcTrace); - this.traces[fullTrace.uid] = traceObj; - } - } -}; - -proto.emitPointAction = function(nextSelection, eventType) { + for (i = 0; i < fullData.length; i++) { + fullTrace = fullData[i] + this._inputs[fullTrace.uid] = i + var calcTrace = calcData[i], + traceObj = this.traces[fullTrace.uid] - var curveIndex = this._inputs[nextSelection.trace.uid]; + if (traceObj) traceObj.update(fullTrace, calcTrace) + else { + traceObj = fullTrace._module.plot(this, fullTrace, calcTrace) + this.traces[fullTrace.uid] = traceObj + } + } +} - this.graphDiv.emit(eventType, { - points: [{ - x: nextSelection.traceCoord[0], - y: nextSelection.traceCoord[1], - curveNumber: curveIndex, - pointNumber: nextSelection.pointIndex, - data: this.fullData[curveIndex]._input, - fullData: this.fullData, - xaxis: this.xaxis, - yaxis: this.yaxis - }] - }); -}; +proto.emitPointAction = function (nextSelection, eventType) { + var curveIndex = this._inputs[nextSelection.trace.uid] + + this.graphDiv.emit(eventType, { + points: [{ + x: nextSelection.traceCoord[0], + y: nextSelection.traceCoord[1], + curveNumber: curveIndex, + pointNumber: nextSelection.pointIndex, + data: this.fullData[curveIndex]._input, + fullData: this.fullData, + xaxis: this.xaxis, + yaxis: this.yaxis + }] + }) +} -proto.draw = function() { - if(this.stopped) return; +proto.draw = function () { + if (this.stopped) return - requestAnimationFrame(this.redraw); + requestAnimationFrame(this.redraw) - var glplot = this.glplot, - camera = this.camera, - mouseListener = camera.mouseListener, - mouseUp = this.lastButtonState === 1 && mouseListener.buttons === 0, - fullLayout = this.fullLayout; + var glplot = this.glplot, + camera = this.camera, + mouseListener = camera.mouseListener, + mouseUp = this.lastButtonState === 1 && mouseListener.buttons === 0, + fullLayout = this.fullLayout - this.lastButtonState = mouseListener.buttons; + this.lastButtonState = mouseListener.buttons - this.cameraChanged(); + this.cameraChanged() - var x = mouseListener.x * glplot.pixelRatio; - var y = this.canvas.height - glplot.pixelRatio * mouseListener.y; + var x = mouseListener.x * glplot.pixelRatio + var y = this.canvas.height - glplot.pixelRatio * mouseListener.y - if(camera.boxEnabled && fullLayout.dragmode === 'zoom') { - this.selectBox.enabled = true; + if (camera.boxEnabled && fullLayout.dragmode === 'zoom') { + this.selectBox.enabled = true - this.selectBox.selectBox = [ - Math.min(camera.boxStart[0], camera.boxEnd[0]), - Math.min(camera.boxStart[1], camera.boxEnd[1]), - Math.max(camera.boxStart[0], camera.boxEnd[0]), - Math.max(camera.boxStart[1], camera.boxEnd[1]) - ]; + this.selectBox.selectBox = [ + Math.min(camera.boxStart[0], camera.boxEnd[0]), + Math.min(camera.boxStart[1], camera.boxEnd[1]), + Math.max(camera.boxStart[0], camera.boxEnd[0]), + Math.max(camera.boxStart[1], camera.boxEnd[1]) + ] - glplot.setDirty(); - } - else { - this.selectBox.enabled = false; + glplot.setDirty() + } else { + this.selectBox.enabled = false - var size = fullLayout._size, - domainX = this.xaxis.domain, - domainY = this.yaxis.domain; + var size = fullLayout._size, + domainX = this.xaxis.domain, + domainY = this.yaxis.domain - var result = glplot.pick( + var result = glplot.pick( (x / glplot.pixelRatio) + size.l + domainX[0] * size.w, (y / glplot.pixelRatio) - (size.t + (1 - domainY[1]) * size.h) - ); + ) - var nextSelection = result && result.object._trace.handlePick(result); + var nextSelection = result && result.object._trace.handlePick(result) - if(nextSelection && mouseUp) { - this.emitPointAction(nextSelection, 'plotly_click'); - } - - if(result && result.object._trace.hoverinfo !== 'skip' && fullLayout.hovermode) { + if (nextSelection && mouseUp) { + this.emitPointAction(nextSelection, 'plotly_click') + } - if(nextSelection && ( + if (result && result.object._trace.hoverinfo !== 'skip' && fullLayout.hovermode) { + if (nextSelection && ( !this.lastPickResult || this.lastPickResult.traceUid !== nextSelection.trace.uid || this.lastPickResult.dataCoord[0] !== nextSelection.dataCoord[0] || this.lastPickResult.dataCoord[1] !== nextSelection.dataCoord[1]) ) { - var selection = nextSelection; + var selection = nextSelection - this.lastPickResult = { - traceUid: nextSelection.trace ? nextSelection.trace.uid : null, - dataCoord: nextSelection.dataCoord.slice() - }; - this.spikes.update({ center: result.dataCoord }); + this.lastPickResult = { + traceUid: nextSelection.trace ? nextSelection.trace.uid : null, + dataCoord: nextSelection.dataCoord.slice() + } + this.spikes.update({ center: result.dataCoord }) - selection.screenCoord = [ - ((glplot.viewBox[2] - glplot.viewBox[0]) * + selection.screenCoord = [ + ((glplot.viewBox[2] - glplot.viewBox[0]) * (result.dataCoord[0] - glplot.dataBox[0]) / (glplot.dataBox[2] - glplot.dataBox[0]) + glplot.viewBox[0]) / glplot.pixelRatio, - (this.canvas.height - (glplot.viewBox[3] - glplot.viewBox[1]) * + (this.canvas.height - (glplot.viewBox[3] - glplot.viewBox[1]) * (result.dataCoord[1] - glplot.dataBox[1]) / (glplot.dataBox[3] - glplot.dataBox[1]) - glplot.viewBox[1]) / glplot.pixelRatio - ]; + ] // this needs to happen before the next block that deletes traceCoord data // also it's important to copy, otherwise data is lost by the time event data is read - this.emitPointAction(nextSelection, 'plotly_hover'); - - var hoverinfo = selection.hoverinfo; - if(hoverinfo !== 'all') { - var parts = hoverinfo.split('+'); - if(parts.indexOf('x') === -1) selection.traceCoord[0] = undefined; - if(parts.indexOf('y') === -1) selection.traceCoord[1] = undefined; - if(parts.indexOf('z') === -1) selection.traceCoord[2] = undefined; - if(parts.indexOf('text') === -1) selection.textLabel = undefined; - if(parts.indexOf('name') === -1) selection.name = undefined; - } - - Fx.loneHover({ - x: selection.screenCoord[0], - y: selection.screenCoord[1], - xLabel: this.hoverFormatter('xaxis', selection.traceCoord[0]), - yLabel: this.hoverFormatter('yaxis', selection.traceCoord[1]), - zLabel: selection.traceCoord[2], - text: selection.textLabel, - name: selection.name, - color: selection.color - }, { - container: this.svgContainer - }); - } - } - else if(!result && this.lastPickResult) { - this.spikes.update({}); - this.lastPickResult = null; - this.graphDiv.emit('plotly_unhover'); - Fx.loneUnhover(this.svgContainer); + this.emitPointAction(nextSelection, 'plotly_hover') + + var hoverinfo = selection.hoverinfo + if (hoverinfo !== 'all') { + var parts = hoverinfo.split('+') + if (parts.indexOf('x') === -1) selection.traceCoord[0] = undefined + if (parts.indexOf('y') === -1) selection.traceCoord[1] = undefined + if (parts.indexOf('z') === -1) selection.traceCoord[2] = undefined + if (parts.indexOf('text') === -1) selection.textLabel = undefined + if (parts.indexOf('name') === -1) selection.name = undefined } + + Fx.loneHover({ + x: selection.screenCoord[0], + y: selection.screenCoord[1], + xLabel: this.hoverFormatter('xaxis', selection.traceCoord[0]), + yLabel: this.hoverFormatter('yaxis', selection.traceCoord[1]), + zLabel: selection.traceCoord[2], + text: selection.textLabel, + name: selection.name, + color: selection.color + }, { + container: this.svgContainer + }) + } + } else if (!result && this.lastPickResult) { + this.spikes.update({}) + this.lastPickResult = null + this.graphDiv.emit('plotly_unhover') + Fx.loneUnhover(this.svgContainer) } + } - glplot.draw(); -}; + glplot.draw() +} -proto.hoverFormatter = function(axisName, val) { - if(val === undefined) return undefined; +proto.hoverFormatter = function (axisName, val) { + if (val === undefined) return undefined - var axis = this[axisName]; - return Axes.tickText(axis, axis.c2l(val), 'hover').text; -}; + var axis = this[axisName] + return Axes.tickText(axis, axis.c2l(val), 'hover').text +} diff --git a/src/plots/gl3d/camera.js b/src/plots/gl3d/camera.js index 70f93ecb2e5..6207920b6ee 100644 --- a/src/plots/gl3d/camera.js +++ b/src/plots/gl3d/camera.js @@ -6,236 +6,236 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -module.exports = createCamera; - -var now = require('right-now'); -var createView = require('3d-view'); -var mouseChange = require('mouse-change'); -var mouseWheel = require('mouse-wheel'); - -function createCamera(element, options) { - element = element || document.body; - options = options || {}; - - var limits = [ 0.01, Infinity ]; - if('distanceLimits' in options) { - limits[0] = options.distanceLimits[0]; - limits[1] = options.distanceLimits[1]; - } - if('zoomMin' in options) { - limits[0] = options.zoomMin; - } - if('zoomMax' in options) { - limits[1] = options.zoomMax; - } - - var view = createView({ - center: options.center || [0, 0, 0], - up: options.up || [0, 1, 0], - eye: options.eye || [0, 0, 10], - mode: options.mode || 'orbit', - distanceLimits: limits - }); - - var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - var distance = 0.0; - var width = element.clientWidth; - var height = element.clientHeight; - - var camera = { - keyBindingMode: 'rotate', - view: view, - element: element, - delay: options.delay || 16, - rotateSpeed: options.rotateSpeed || 1, - zoomSpeed: options.zoomSpeed || 1, - translateSpeed: options.translateSpeed || 1, - flipX: !!options.flipX, - flipY: !!options.flipY, - modes: view.modes, - tick: function() { - var t = now(); - var delay = this.delay; - var ctime = t - 2 * delay; - view.idle(t - delay); - view.recalcMatrix(ctime); - view.flush(t - (100 + delay * 2)); - var allEqual = true; - var matrix = view.computedMatrix; - for(var i = 0; i < 16; ++i) { - allEqual = allEqual && (pmatrix[i] === matrix[i]); - pmatrix[i] = matrix[i]; - } - var sizeChanged = +'use strict' + +module.exports = createCamera + +var now = require('right-now') +var createView = require('3d-view') +var mouseChange = require('mouse-change') +var mouseWheel = require('mouse-wheel') + +function createCamera (element, options) { + element = element || document.body + options = options || {} + + var limits = [ 0.01, Infinity ] + if ('distanceLimits' in options) { + limits[0] = options.distanceLimits[0] + limits[1] = options.distanceLimits[1] + } + if ('zoomMin' in options) { + limits[0] = options.zoomMin + } + if ('zoomMax' in options) { + limits[1] = options.zoomMax + } + + var view = createView({ + center: options.center || [0, 0, 0], + up: options.up || [0, 1, 0], + eye: options.eye || [0, 0, 10], + mode: options.mode || 'orbit', + distanceLimits: limits + }) + + var pmatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + var distance = 0.0 + var width = element.clientWidth + var height = element.clientHeight + + var camera = { + keyBindingMode: 'rotate', + view: view, + element: element, + delay: options.delay || 16, + rotateSpeed: options.rotateSpeed || 1, + zoomSpeed: options.zoomSpeed || 1, + translateSpeed: options.translateSpeed || 1, + flipX: !!options.flipX, + flipY: !!options.flipY, + modes: view.modes, + tick: function () { + var t = now() + var delay = this.delay + var ctime = t - 2 * delay + view.idle(t - delay) + view.recalcMatrix(ctime) + view.flush(t - (100 + delay * 2)) + var allEqual = true + var matrix = view.computedMatrix + for (var i = 0; i < 16; ++i) { + allEqual = allEqual && (pmatrix[i] === matrix[i]) + pmatrix[i] = matrix[i] + } + var sizeChanged = element.clientWidth === width && - element.clientHeight === height; - width = element.clientWidth; - height = element.clientHeight; - if(allEqual) return !sizeChanged; - distance = Math.exp(view.computedRadius[0]); - return true; - }, - lookAt: function(center, eye, up) { - view.lookAt(view.lastT(), center, eye, up); - }, - rotate: function(pitch, yaw, roll) { - view.rotate(view.lastT(), pitch, yaw, roll); - }, - pan: function(dx, dy, dz) { - view.pan(view.lastT(), dx, dy, dz); - }, - translate: function(dx, dy, dz) { - view.translate(view.lastT(), dx, dy, dz); - } - }; - - Object.defineProperties(camera, { - matrix: { - get: function() { - return view.computedMatrix; - }, - set: function(mat) { - view.setMatrix(view.lastT(), mat); - return view.computedMatrix; - }, - enumerable: true - }, - mode: { - get: function() { - return view.getMode(); - }, - set: function(mode) { - var curUp = view.computedUp.slice(); - var curEye = view.computedEye.slice(); - var curCenter = view.computedCenter.slice(); - view.setMode(mode); - if(mode === 'turntable') { + element.clientHeight === height + width = element.clientWidth + height = element.clientHeight + if (allEqual) return !sizeChanged + distance = Math.exp(view.computedRadius[0]) + return true + }, + lookAt: function (center, eye, up) { + view.lookAt(view.lastT(), center, eye, up) + }, + rotate: function (pitch, yaw, roll) { + view.rotate(view.lastT(), pitch, yaw, roll) + }, + pan: function (dx, dy, dz) { + view.pan(view.lastT(), dx, dy, dz) + }, + translate: function (dx, dy, dz) { + view.translate(view.lastT(), dx, dy, dz) + } + } + + Object.defineProperties(camera, { + matrix: { + get: function () { + return view.computedMatrix + }, + set: function (mat) { + view.setMatrix(view.lastT(), mat) + return view.computedMatrix + }, + enumerable: true + }, + mode: { + get: function () { + return view.getMode() + }, + set: function (mode) { + var curUp = view.computedUp.slice() + var curEye = view.computedEye.slice() + var curCenter = view.computedCenter.slice() + view.setMode(mode) + if (mode === 'turntable') { // Hacky time warping stuff to generate smooth animation - var t0 = now(); - view._active.lookAt(t0, curEye, curCenter, curUp); - view._active.lookAt(t0 + 500, curEye, curCenter, [0, 0, 1]); - view._active.flush(t0); - } - return view.getMode(); - }, - enumerable: true - }, - center: { - get: function() { - return view.computedCenter; - }, - set: function(ncenter) { - view.lookAt(view.lastT(), null, ncenter); - return view.computedCenter; - }, - enumerable: true - }, - eye: { - get: function() { - return view.computedEye; - }, - set: function(neye) { - view.lookAt(view.lastT(), neye); - return view.computedEye; - }, - enumerable: true - }, - up: { - get: function() { - return view.computedUp; - }, - set: function(nup) { - view.lookAt(view.lastT(), null, null, nup); - return view.computedUp; - }, - enumerable: true - }, - distance: { - get: function() { - return distance; - }, - set: function(d) { - view.setDistance(view.lastT(), d); - return d; - }, - enumerable: true - }, - distanceLimits: { - get: function() { - return view.getDistanceLimits(limits); - }, - set: function(v) { - view.setDistanceLimits(v); - return v; - }, - enumerable: true + var t0 = now() + view._active.lookAt(t0, curEye, curCenter, curUp) + view._active.lookAt(t0 + 500, curEye, curCenter, [0, 0, 1]) + view._active.flush(t0) } - }); + return view.getMode() + }, + enumerable: true + }, + center: { + get: function () { + return view.computedCenter + }, + set: function (ncenter) { + view.lookAt(view.lastT(), null, ncenter) + return view.computedCenter + }, + enumerable: true + }, + eye: { + get: function () { + return view.computedEye + }, + set: function (neye) { + view.lookAt(view.lastT(), neye) + return view.computedEye + }, + enumerable: true + }, + up: { + get: function () { + return view.computedUp + }, + set: function (nup) { + view.lookAt(view.lastT(), null, null, nup) + return view.computedUp + }, + enumerable: true + }, + distance: { + get: function () { + return distance + }, + set: function (d) { + view.setDistance(view.lastT(), d) + return d + }, + enumerable: true + }, + distanceLimits: { + get: function () { + return view.getDistanceLimits(limits) + }, + set: function (v) { + view.setDistanceLimits(v) + return v + }, + enumerable: true + } + }) - element.addEventListener('contextmenu', function(ev) { - ev.preventDefault(); - return false; - }); + element.addEventListener('contextmenu', function (ev) { + ev.preventDefault() + return false + }) - var lastX = 0, lastY = 0; - mouseChange(element, function(buttons, x, y, mods) { - var rotate = camera.keyBindingMode === 'rotate'; - var pan = camera.keyBindingMode === 'pan'; - var zoom = camera.keyBindingMode === 'zoom'; + var lastX = 0, lastY = 0 + mouseChange(element, function (buttons, x, y, mods) { + var rotate = camera.keyBindingMode === 'rotate' + var pan = camera.keyBindingMode === 'pan' + var zoom = camera.keyBindingMode === 'zoom' - var ctrl = !!mods.control; - var alt = !!mods.alt; - var shift = !!mods.shift; - var left = !!(buttons & 1); - var right = !!(buttons & 2); - var middle = !!(buttons & 4); + var ctrl = !!mods.control + var alt = !!mods.alt + var shift = !!mods.shift + var left = !!(buttons & 1) + var right = !!(buttons & 2) + var middle = !!(buttons & 4) - var scale = 1.0 / element.clientHeight; - var dx = scale * (x - lastX); - var dy = scale * (y - lastY); + var scale = 1.0 / element.clientHeight + var dx = scale * (x - lastX) + var dy = scale * (y - lastY) - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; + var flipX = camera.flipX ? 1 : -1 + var flipY = camera.flipY ? 1 : -1 - var t = now(); + var t = now() - var drot = Math.PI * camera.rotateSpeed; + var drot = Math.PI * camera.rotateSpeed - if((rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { + if ((rotate && left && !ctrl && !alt && !shift) || (left && !ctrl && !alt && shift)) { // Rotate - view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0); - } + view.rotate(t, flipX * drot * dx, -flipY * drot * dy, 0) + } - if((pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { + if ((pan && left && !ctrl && !alt && !shift) || right || (left && ctrl && !alt && !shift)) { // Pan - view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0); - } + view.pan(t, -camera.translateSpeed * dx * distance, camera.translateSpeed * dy * distance, 0) + } - if((zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { + if ((zoom && left && !ctrl && !alt && !shift) || middle || (left && !ctrl && alt && !shift)) { // Zoom - var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } + var kzoom = -camera.zoomSpeed * dy / window.innerHeight * (t - view.lastT()) * 100 + view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)) + } - lastX = x; - lastY = y; - - return true; - }); - - mouseWheel(element, function(dx, dy) { - var flipX = camera.flipX ? 1 : -1; - var flipY = camera.flipY ? 1 : -1; - var t = now(); - if(Math.abs(dx) > Math.abs(dy)) { - view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth); - } else { - var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 100.0; - view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)); - } - }, true); + lastX = x + lastY = y + + return true + }) + + mouseWheel(element, function (dx, dy) { + var flipX = camera.flipX ? 1 : -1 + var flipY = camera.flipY ? 1 : -1 + var t = now() + if (Math.abs(dx) > Math.abs(dy)) { + view.rotate(t, 0, 0, -dx * flipX * Math.PI * camera.rotateSpeed / window.innerWidth) + } else { + var kzoom = -camera.zoomSpeed * flipY * dy / window.innerHeight * (t - view.lastT()) / 100.0 + view.pan(t, 0, 0, distance * (Math.exp(kzoom) - 1)) + } + }, true) - return camera; + return camera } diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 6e42343abc0..e4a9421f231 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -6,125 +6,123 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Scene = require('./scene') +var Plots = require('../plots') +var Lib = require('../../lib') +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') -var Scene = require('./scene'); -var Plots = require('../plots'); -var Lib = require('../../lib'); -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); +var axesNames = ['xaxis', 'yaxis', 'zaxis'] -var axesNames = ['xaxis', 'yaxis', 'zaxis']; +exports.name = 'gl3d' +exports.attr = 'scene' -exports.name = 'gl3d'; +exports.idRoot = 'scene' -exports.attr = 'scene'; +exports.idRegex = /^scene([2-9]|[1-9][0-9]+)?$/ -exports.idRoot = 'scene'; +exports.attrRegex = /^scene([2-9]|[1-9][0-9]+)?$/ -exports.idRegex = /^scene([2-9]|[1-9][0-9]+)?$/; +exports.attributes = require('./layout/attributes') -exports.attrRegex = /^scene([2-9]|[1-9][0-9]+)?$/; +exports.layoutAttributes = require('./layout/layout_attributes') -exports.attributes = require('./layout/attributes'); +exports.supplyLayoutDefaults = require('./layout/defaults') -exports.layoutAttributes = require('./layout/layout_attributes'); +exports.plot = function plotGl3d (gd) { + var fullLayout = gd._fullLayout, + fullData = gd._fullData, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d') -exports.supplyLayoutDefaults = require('./layout/defaults'); + for (var i = 0; i < sceneIds.length; i++) { + var sceneId = sceneIds[i], + fullSceneData = Plots.getSubplotData(fullData, 'gl3d', sceneId), + sceneLayout = fullLayout[sceneId], + scene = sceneLayout._scene -exports.plot = function plotGl3d(gd) { - var fullLayout = gd._fullLayout, - fullData = gd._fullData, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'); + if (!scene) { + initAxes(gd, sceneLayout) - for(var i = 0; i < sceneIds.length; i++) { - var sceneId = sceneIds[i], - fullSceneData = Plots.getSubplotData(fullData, 'gl3d', sceneId), - sceneLayout = fullLayout[sceneId], - scene = sceneLayout._scene; - - if(!scene) { - initAxes(gd, sceneLayout); - - scene = new Scene({ - id: sceneId, - graphDiv: gd, - container: gd.querySelector('.gl-container'), - staticPlot: gd._context.staticPlot, - plotGlPixelRatio: gd._context.plotGlPixelRatio - }, + scene = new Scene({ + id: sceneId, + graphDiv: gd, + container: gd.querySelector('.gl-container'), + staticPlot: gd._context.staticPlot, + plotGlPixelRatio: gd._context.plotGlPixelRatio + }, fullLayout - ); + ) // set ref to Scene instance - sceneLayout._scene = scene; - } + sceneLayout._scene = scene + } // save 'initial' camera settings for modebar button - if(!scene.cameraInitial) { - scene.cameraInitial = Lib.extendDeep({}, sceneLayout.camera); - } - - scene.plot(fullSceneData, fullLayout, gd.layout); + if (!scene.cameraInitial) { + scene.cameraInitial = Lib.extendDeep({}, sceneLayout.camera) } -}; -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl3d'); + scene.plot(fullSceneData, fullLayout, gd.layout) + } +} - for(var i = 0; i < oldSceneKeys.length; i++) { - var oldSceneKey = oldSceneKeys[i]; +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl3d') - if(!newFullLayout[oldSceneKey] && !!oldFullLayout[oldSceneKey]._scene) { - oldFullLayout[oldSceneKey]._scene.destroy(); - } - } -}; - -exports.toSVG = function(gd) { - var fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), - size = fullLayout._size; - - for(var i = 0; i < sceneIds.length; i++) { - var sceneLayout = fullLayout[sceneIds[i]], - domain = sceneLayout.domain, - scene = sceneLayout._scene; - - var imageData = scene.toImage('png'); - var image = fullLayout._glimages.append('svg:image'); - - image.attr({ - xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData, - x: size.l + size.w * domain.x[0], - y: size.t + size.h * (1 - domain.y[1]), - width: size.w * (domain.x[1] - domain.x[0]), - height: size.h * (domain.y[1] - domain.y[0]), - preserveAspectRatio: 'none' - }); - - scene.destroy(); + for (var i = 0; i < oldSceneKeys.length; i++) { + var oldSceneKey = oldSceneKeys[i] + + if (!newFullLayout[oldSceneKey] && !!oldFullLayout[oldSceneKey]._scene) { + oldFullLayout[oldSceneKey]._scene.destroy() } -}; + } +} + +exports.toSVG = function (gd) { + var fullLayout = gd._fullLayout, + sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), + size = fullLayout._size + + for (var i = 0; i < sceneIds.length; i++) { + var sceneLayout = fullLayout[sceneIds[i]], + domain = sceneLayout.domain, + scene = sceneLayout._scene + + var imageData = scene.toImage('png') + var image = fullLayout._glimages.append('svg:image') + + image.attr({ + xmlns: xmlnsNamespaces.svg, + 'xlink:href': imageData, + x: size.l + size.w * domain.x[0], + y: size.t + size.h * (1 - domain.y[1]), + width: size.w * (domain.x[1] - domain.x[0]), + height: size.h * (domain.y[1] - domain.y[0]), + preserveAspectRatio: 'none' + }) + + scene.destroy() + } +} // clean scene ids, 'scene1' -> 'scene' -exports.cleanId = function cleanId(id) { - if(!id.match(/^scene[0-9]*$/)) return; +exports.cleanId = function cleanId (id) { + if (!id.match(/^scene[0-9]*$/)) return - var sceneNum = id.substr(5); - if(sceneNum === '1') sceneNum = ''; + var sceneNum = id.substr(5) + if (sceneNum === '1') sceneNum = '' - return 'scene' + sceneNum; -}; + return 'scene' + sceneNum +} -exports.setConvert = require('./set_convert'); +exports.setConvert = require('./set_convert') -function initAxes(gd, sceneLayout) { - for(var j = 0; j < 3; ++j) { - var axisName = axesNames[j]; +function initAxes (gd, sceneLayout) { + for (var j = 0; j < 3; ++j) { + var axisName = axesNames[j] - sceneLayout[axisName]._gd = gd; - } + sceneLayout[axisName]._gd = gd + } } diff --git a/src/plots/gl3d/layout/attributes.js b/src/plots/gl3d/layout/attributes.js index 4c8c714766a..3699a86d090 100644 --- a/src/plots/gl3d/layout/attributes.js +++ b/src/plots/gl3d/layout/attributes.js @@ -6,21 +6,20 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - scene: { - valType: 'subplotid', - role: 'info', - dflt: 'scene', - description: [ - 'Sets a reference between this trace\'s 3D coordinate system and', - 'a 3D scene.', - 'If *scene* (the default value), the (x,y,z) coordinates refer to', - '`layout.scene`.', - 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', - 'and so on.' - ].join(' ') - } -}; + scene: { + valType: 'subplotid', + role: 'info', + dflt: 'scene', + description: [ + 'Sets a reference between this trace\'s 3D coordinate system and', + 'a 3D scene.', + 'If *scene* (the default value), the (x,y,z) coordinates refer to', + '`layout.scene`.', + 'If *scene2*, the (x,y,z) coordinates refer to `layout.scene2`,', + 'and so on.' + ].join(' ') + } +} diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index fe1f672b7ba..81da5d54d05 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -6,109 +6,108 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var Color = require('../../../components/color'); -var axesAttrs = require('../../cartesian/layout_attributes'); -var extendFlat = require('../../../lib/extend').extendFlat; +'use strict' +var Color = require('../../../components/color') +var axesAttrs = require('../../cartesian/layout_attributes') +var extendFlat = require('../../../lib/extend').extendFlat module.exports = { - showspikes: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Sets whether or not spikes starting from', - 'data points to this axis\' wall are shown on hover.' - ].join(' ') - }, - spikesides: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Sets whether or not spikes extending from the', - 'projection data points to this axis\' wall boundaries', - 'are shown on hover.' - ].join(' ') - }, - spikethickness: { - valType: 'number', - role: 'style', - min: 0, - dflt: 2, - description: 'Sets the thickness (in px) of the spikes.' - }, - spikecolor: { - valType: 'color', - role: 'style', - dflt: Color.defaultLine, - description: 'Sets the color of the spikes.' - }, - showbackground: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Sets whether or not this axis\' wall', - 'has a background color.' - ].join(' ') - }, - backgroundcolor: { - valType: 'color', - role: 'style', - dflt: 'rgba(204, 204, 204, 0.5)', - description: 'Sets the background color of this axis\' wall.' - }, - showaxeslabels: { - valType: 'boolean', - role: 'info', - dflt: true, - description: 'Sets whether or not this axis is labeled' - }, - color: axesAttrs.color, - categoryorder: axesAttrs.categoryorder, - categoryarray: axesAttrs.categoryarray, - title: axesAttrs.title, - titlefont: axesAttrs.titlefont, - type: axesAttrs.type, - autorange: axesAttrs.autorange, - rangemode: axesAttrs.rangemode, - range: axesAttrs.range, + showspikes: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Sets whether or not spikes starting from', + 'data points to this axis\' wall are shown on hover.' + ].join(' ') + }, + spikesides: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Sets whether or not spikes extending from the', + 'projection data points to this axis\' wall boundaries', + 'are shown on hover.' + ].join(' ') + }, + spikethickness: { + valType: 'number', + role: 'style', + min: 0, + dflt: 2, + description: 'Sets the thickness (in px) of the spikes.' + }, + spikecolor: { + valType: 'color', + role: 'style', + dflt: Color.defaultLine, + description: 'Sets the color of the spikes.' + }, + showbackground: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Sets whether or not this axis\' wall', + 'has a background color.' + ].join(' ') + }, + backgroundcolor: { + valType: 'color', + role: 'style', + dflt: 'rgba(204, 204, 204, 0.5)', + description: 'Sets the background color of this axis\' wall.' + }, + showaxeslabels: { + valType: 'boolean', + role: 'info', + dflt: true, + description: 'Sets whether or not this axis is labeled' + }, + color: axesAttrs.color, + categoryorder: axesAttrs.categoryorder, + categoryarray: axesAttrs.categoryarray, + title: axesAttrs.title, + titlefont: axesAttrs.titlefont, + type: axesAttrs.type, + autorange: axesAttrs.autorange, + rangemode: axesAttrs.rangemode, + range: axesAttrs.range, // ticks - tickmode: axesAttrs.tickmode, - nticks: axesAttrs.nticks, - tick0: axesAttrs.tick0, - dtick: axesAttrs.dtick, - tickvals: axesAttrs.tickvals, - ticktext: axesAttrs.ticktext, - ticks: axesAttrs.ticks, - mirror: axesAttrs.mirror, - ticklen: axesAttrs.ticklen, - tickwidth: axesAttrs.tickwidth, - tickcolor: axesAttrs.tickcolor, - showticklabels: axesAttrs.showticklabels, - tickfont: axesAttrs.tickfont, - tickangle: axesAttrs.tickangle, - tickprefix: axesAttrs.tickprefix, - showtickprefix: axesAttrs.showtickprefix, - ticksuffix: axesAttrs.ticksuffix, - showticksuffix: axesAttrs.showticksuffix, - showexponent: axesAttrs.showexponent, - exponentformat: axesAttrs.exponentformat, - separatethousands: axesAttrs.separatethousands, - tickformat: axesAttrs.tickformat, - hoverformat: axesAttrs.hoverformat, + tickmode: axesAttrs.tickmode, + nticks: axesAttrs.nticks, + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: axesAttrs.ticks, + mirror: axesAttrs.mirror, + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + tickprefix: axesAttrs.tickprefix, + showtickprefix: axesAttrs.showtickprefix, + ticksuffix: axesAttrs.ticksuffix, + showticksuffix: axesAttrs.showticksuffix, + showexponent: axesAttrs.showexponent, + exponentformat: axesAttrs.exponentformat, + separatethousands: axesAttrs.separatethousands, + tickformat: axesAttrs.tickformat, + hoverformat: axesAttrs.hoverformat, // lines and grids - showline: axesAttrs.showline, - linecolor: axesAttrs.linecolor, - linewidth: axesAttrs.linewidth, - showgrid: axesAttrs.showgrid, - gridcolor: extendFlat({}, axesAttrs.gridcolor, // shouldn't this be on-par with 2D? + showline: axesAttrs.showline, + linecolor: axesAttrs.linecolor, + linewidth: axesAttrs.linewidth, + showgrid: axesAttrs.showgrid, + gridcolor: extendFlat({}, axesAttrs.gridcolor, // shouldn't this be on-par with 2D? {dflt: 'rgb(204, 204, 204)'}), - gridwidth: axesAttrs.gridwidth, - zeroline: axesAttrs.zeroline, - zerolinecolor: axesAttrs.zerolinecolor, - zerolinewidth: axesAttrs.zerolinewidth -}; + gridwidth: axesAttrs.gridwidth, + zeroline: axesAttrs.zeroline, + zerolinecolor: axesAttrs.zerolinecolor, + zerolinewidth: axesAttrs.zerolinewidth +} diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index 353d03eb379..4157221aa1b 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -6,62 +6,61 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var colorMix = require('tinycolor2').mix -var colorMix = require('tinycolor2').mix; +var Lib = require('../../../lib') -var Lib = require('../../../lib'); +var layoutAttributes = require('./axis_attributes') +var handleAxisDefaults = require('../../cartesian/axis_defaults') -var layoutAttributes = require('./axis_attributes'); -var handleAxisDefaults = require('../../cartesian/axis_defaults'); - -var axesNames = ['xaxis', 'yaxis', 'zaxis']; +var axesNames = ['xaxis', 'yaxis', 'zaxis'] // TODO: hard-coded lightness fraction based on gridline default colors // that differ from other subplot types. -var gridLightness = 100 * (204 - 0x44) / (255 - 0x44); +var gridLightness = 100 * (204 - 0x44) / (255 - 0x44) -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { - var containerIn, containerOut; +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, options) { + var containerIn, containerOut - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt) + } - for(var j = 0; j < axesNames.length; j++) { - var axName = axesNames[j]; - containerIn = layoutIn[axName] || {}; + for (var j = 0; j < axesNames.length; j++) { + var axName = axesNames[j] + containerIn = layoutIn[axName] || {} - containerOut = { - _id: axName[0] + options.scene, - _name: axName - }; + containerOut = { + _id: axName[0] + options.scene, + _name: axName + } - layoutOut[axName] = containerOut = handleAxisDefaults( + layoutOut[axName] = containerOut = handleAxisDefaults( containerIn, containerOut, coerce, { - font: options.font, - letter: axName[0], - data: options.data, - showGrid: true, - bgColor: options.bgColor, - calendar: options.calendar - }); - - coerce('gridcolor', colorMix(containerOut.color, options.bgColor, gridLightness).toRgbString()); - coerce('title', axName[0]); // shouldn't this be on-par with 2D? - - containerOut.setScale = Lib.noop; - - if(coerce('showspikes')) { - coerce('spikesides'); - coerce('spikethickness'); - coerce('spikecolor', containerOut.color); - } - - coerce('showaxeslabels'); - if(coerce('showbackground')) coerce('backgroundcolor'); + font: options.font, + letter: axName[0], + data: options.data, + showGrid: true, + bgColor: options.bgColor, + calendar: options.calendar + }) + + coerce('gridcolor', colorMix(containerOut.color, options.bgColor, gridLightness).toRgbString()) + coerce('title', axName[0]) // shouldn't this be on-par with 2D? + + containerOut.setScale = Lib.noop + + if (coerce('showspikes')) { + coerce('spikesides') + coerce('spikethickness') + coerce('spikecolor', containerOut.color) } -}; + + coerce('showaxeslabels') + if (coerce('showbackground')) coerce('backgroundcolor') + } +} diff --git a/src/plots/gl3d/layout/convert.js b/src/plots/gl3d/layout/convert.js index 1bba5caebbe..dd0f6c8063c 100644 --- a/src/plots/gl3d/layout/convert.js +++ b/src/plots/gl3d/layout/convert.js @@ -6,148 +6,146 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var arrtools = require('arraytools') +var convertHTMLToUnicode = require('../../../lib/html2unicode') +var str2RgbaArray = require('../../../lib/str2rgbarray') -var arrtools = require('arraytools'); -var convertHTMLToUnicode = require('../../../lib/html2unicode'); -var str2RgbaArray = require('../../../lib/str2rgbarray'); +var arrayCopy1D = arrtools.copy1D -var arrayCopy1D = arrtools.copy1D; +var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis'] -var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; - -function AxesOptions() { - this.bounds = [ +function AxesOptions () { + this.bounds = [ [-10, -10, -10], [10, 10, 10] - ]; - - this.ticks = [ [], [], [] ]; - this.tickEnable = [ true, true, true ]; - this.tickFont = [ 'sans-serif', 'sans-serif', 'sans-serif' ]; - this.tickSize = [ 12, 12, 12 ]; - this.tickAngle = [ 0, 0, 0 ]; - this.tickColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - this.tickPad = [ 18, 18, 18 ]; - - this.labels = [ 'x', 'y', 'z' ]; - this.labelEnable = [ true, true, true ]; - this.labelFont = ['Open Sans', 'Open Sans', 'Open Sans']; - this.labelSize = [ 20, 20, 20 ]; - this.labelAngle = [ 0, 0, 0 ]; - this.labelColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - this.labelPad = [ 30, 30, 30 ]; - - this.lineEnable = [ true, true, true ]; - this.lineMirror = [ false, false, false ]; - this.lineWidth = [ 1, 1, 1 ]; - this.lineColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - - this.lineTickEnable = [ true, true, true ]; - this.lineTickMirror = [ false, false, false ]; - this.lineTickLength = [ 10, 10, 10 ]; - this.lineTickWidth = [ 1, 1, 1 ]; - this.lineTickColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - - this.gridEnable = [ true, true, true ]; - this.gridWidth = [ 1, 1, 1 ]; - this.gridColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - - this.zeroEnable = [ true, true, true ]; - this.zeroLineColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ]; - this.zeroLineWidth = [ 2, 2, 2 ]; - - this.backgroundEnable = [ true, true, true ]; - this.backgroundColor = [ [0.8, 0.8, 0.8, 0.5], + ] + + this.ticks = [ [], [], [] ] + this.tickEnable = [ true, true, true ] + this.tickFont = [ 'sans-serif', 'sans-serif', 'sans-serif' ] + this.tickSize = [ 12, 12, 12 ] + this.tickAngle = [ 0, 0, 0 ] + this.tickColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + this.tickPad = [ 18, 18, 18 ] + + this.labels = [ 'x', 'y', 'z' ] + this.labelEnable = [ true, true, true ] + this.labelFont = ['Open Sans', 'Open Sans', 'Open Sans'] + this.labelSize = [ 20, 20, 20 ] + this.labelAngle = [ 0, 0, 0 ] + this.labelColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + this.labelPad = [ 30, 30, 30 ] + + this.lineEnable = [ true, true, true ] + this.lineMirror = [ false, false, false ] + this.lineWidth = [ 1, 1, 1 ] + this.lineColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + + this.lineTickEnable = [ true, true, true ] + this.lineTickMirror = [ false, false, false ] + this.lineTickLength = [ 10, 10, 10 ] + this.lineTickWidth = [ 1, 1, 1 ] + this.lineTickColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + + this.gridEnable = [ true, true, true ] + this.gridWidth = [ 1, 1, 1 ] + this.gridColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + + this.zeroEnable = [ true, true, true ] + this.zeroLineColor = [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1] ] + this.zeroLineWidth = [ 2, 2, 2 ] + + this.backgroundEnable = [ true, true, true ] + this.backgroundColor = [ [0.8, 0.8, 0.8, 0.5], [0.8, 0.8, 0.8, 0.5], - [0.8, 0.8, 0.8, 0.5] ]; + [0.8, 0.8, 0.8, 0.5] ] // some default values are stored for applying model transforms - this._defaultTickPad = arrayCopy1D(this.tickPad); - this._defaultLabelPad = arrayCopy1D(this.labelPad); - this._defaultLineTickLength = arrayCopy1D(this.lineTickLength); + this._defaultTickPad = arrayCopy1D(this.tickPad) + this._defaultLabelPad = arrayCopy1D(this.labelPad) + this._defaultLineTickLength = arrayCopy1D(this.lineTickLength) } -var proto = AxesOptions.prototype; +var proto = AxesOptions.prototype -proto.merge = function(sceneLayout) { - var opts = this; - for(var i = 0; i < 3; ++i) { - var axes = sceneLayout[AXES_NAMES[i]]; +proto.merge = function (sceneLayout) { + var opts = this + for (var i = 0; i < 3; ++i) { + var axes = sceneLayout[AXES_NAMES[i]] // Axes labels - opts.labels[i] = convertHTMLToUnicode(axes.title); - if('titlefont' in axes) { - if(axes.titlefont.color) opts.labelColor[i] = str2RgbaArray(axes.titlefont.color); - if(axes.titlefont.family) opts.labelFont[i] = axes.titlefont.family; - if(axes.titlefont.size) opts.labelSize[i] = axes.titlefont.size; - } + opts.labels[i] = convertHTMLToUnicode(axes.title) + if ('titlefont' in axes) { + if (axes.titlefont.color) opts.labelColor[i] = str2RgbaArray(axes.titlefont.color) + if (axes.titlefont.family) opts.labelFont[i] = axes.titlefont.family + if (axes.titlefont.size) opts.labelSize[i] = axes.titlefont.size + } // Lines - if('showline' in axes) opts.lineEnable[i] = axes.showline; - if('linecolor' in axes) opts.lineColor[i] = str2RgbaArray(axes.linecolor); - if('linewidth' in axes) opts.lineWidth[i] = axes.linewidth; + if ('showline' in axes) opts.lineEnable[i] = axes.showline + if ('linecolor' in axes) opts.lineColor[i] = str2RgbaArray(axes.linecolor) + if ('linewidth' in axes) opts.lineWidth[i] = axes.linewidth - if('showgrid' in axes) opts.gridEnable[i] = axes.showgrid; - if('gridcolor' in axes) opts.gridColor[i] = str2RgbaArray(axes.gridcolor); - if('gridwidth' in axes) opts.gridWidth[i] = axes.gridwidth; + if ('showgrid' in axes) opts.gridEnable[i] = axes.showgrid + if ('gridcolor' in axes) opts.gridColor[i] = str2RgbaArray(axes.gridcolor) + if ('gridwidth' in axes) opts.gridWidth[i] = axes.gridwidth // Remove zeroline if axis type is log // otherwise the zeroline is incorrectly drawn at 1 on log axes - if(axes.type === 'log') opts.zeroEnable[i] = false; - else if('zeroline' in axes) opts.zeroEnable[i] = axes.zeroline; - if('zerolinecolor' in axes) opts.zeroLineColor[i] = str2RgbaArray(axes.zerolinecolor); - if('zerolinewidth' in axes) opts.zeroLineWidth[i] = axes.zerolinewidth; + if (axes.type === 'log') opts.zeroEnable[i] = false + else if ('zeroline' in axes) opts.zeroEnable[i] = axes.zeroline + if ('zerolinecolor' in axes) opts.zeroLineColor[i] = str2RgbaArray(axes.zerolinecolor) + if ('zerolinewidth' in axes) opts.zeroLineWidth[i] = axes.zerolinewidth // tick lines - if('ticks' in axes && !!axes.ticks) opts.lineTickEnable[i] = true; - else opts.lineTickEnable[i] = false; - - if('ticklen' in axes) { - opts.lineTickLength[i] = opts._defaultLineTickLength[i] = axes.ticklen; - } - if('tickcolor' in axes) opts.lineTickColor[i] = str2RgbaArray(axes.tickcolor); - if('tickwidth' in axes) opts.lineTickWidth[i] = axes.tickwidth; - if('tickangle' in axes) { - opts.tickAngle[i] = (axes.tickangle === 'auto') ? + if ('ticks' in axes && !!axes.ticks) opts.lineTickEnable[i] = true + else opts.lineTickEnable[i] = false + + if ('ticklen' in axes) { + opts.lineTickLength[i] = opts._defaultLineTickLength[i] = axes.ticklen + } + if ('tickcolor' in axes) opts.lineTickColor[i] = str2RgbaArray(axes.tickcolor) + if ('tickwidth' in axes) opts.lineTickWidth[i] = axes.tickwidth + if ('tickangle' in axes) { + opts.tickAngle[i] = (axes.tickangle === 'auto') ? 0 : - Math.PI * -axes.tickangle / 180; - } + Math.PI * -axes.tickangle / 180 + } // tick labels - if('showticklabels' in axes) opts.tickEnable[i] = axes.showticklabels; - if('tickfont' in axes) { - if(axes.tickfont.color) opts.tickColor[i] = str2RgbaArray(axes.tickfont.color); - if(axes.tickfont.family) opts.tickFont[i] = axes.tickfont.family; - if(axes.tickfont.size) opts.tickSize[i] = axes.tickfont.size; - } - - if('mirror' in axes) { - if(['ticks', 'all', 'allticks'].indexOf(axes.mirror) !== -1) { - opts.lineTickMirror[i] = true; - opts.lineMirror[i] = true; - } else if(axes.mirror === true) { - opts.lineTickMirror[i] = false; - opts.lineMirror[i] = true; - } else { - opts.lineTickMirror[i] = false; - opts.lineMirror[i] = false; - } - } else opts.lineMirror[i] = false; - - // grid background - if('showbackground' in axes && axes.showbackground !== false) { - opts.backgroundEnable[i] = true; - opts.backgroundColor[i] = str2RgbaArray(axes.backgroundcolor); - } else opts.backgroundEnable[i] = false; + if ('showticklabels' in axes) opts.tickEnable[i] = axes.showticklabels + if ('tickfont' in axes) { + if (axes.tickfont.color) opts.tickColor[i] = str2RgbaArray(axes.tickfont.color) + if (axes.tickfont.family) opts.tickFont[i] = axes.tickfont.family + if (axes.tickfont.size) opts.tickSize[i] = axes.tickfont.size } -}; + if ('mirror' in axes) { + if (['ticks', 'all', 'allticks'].indexOf(axes.mirror) !== -1) { + opts.lineTickMirror[i] = true + opts.lineMirror[i] = true + } else if (axes.mirror === true) { + opts.lineTickMirror[i] = false + opts.lineMirror[i] = true + } else { + opts.lineTickMirror[i] = false + opts.lineMirror[i] = false + } + } else opts.lineMirror[i] = false + + // grid background + if ('showbackground' in axes && axes.showbackground !== false) { + opts.backgroundEnable[i] = true + opts.backgroundColor[i] = str2RgbaArray(axes.backgroundcolor) + } else opts.backgroundEnable[i] = false + } +} -function createAxesOptions(plotlyOptions) { - var result = new AxesOptions(); - result.merge(plotlyOptions); - return result; +function createAxesOptions (plotlyOptions) { + var result = new AxesOptions() + result.merge(plotlyOptions) + return result } -module.exports = createAxesOptions; +module.exports = createAxesOptions diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index 285da8b2920..66227f5765f 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -6,47 +6,45 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../../components/color') -var Color = require('../../../components/color'); +var handleSubplotDefaults = require('../../subplot_defaults') +var layoutAttributes = require('./layout_attributes') +var supplyGl3dAxisLayoutDefaults = require('./axis_defaults') -var handleSubplotDefaults = require('../../subplot_defaults'); -var layoutAttributes = require('./layout_attributes'); -var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); - - -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - var hasNon3D = ( +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + var hasNon3D = ( layoutOut._has('cartesian') || layoutOut._has('geo') || layoutOut._has('gl2d') || layoutOut._has('pie') || layoutOut._has('ternary') - ); + ) // some layout-wide attribute are used in all scenes // if 3D is the only visible plot type - function getDfltFromLayout(attr) { - if(hasNon3D) return; - - var isValid = layoutAttributes[attr].values.indexOf(layoutIn[attr]) !== -1; - if(isValid) return layoutIn[attr]; - } - - handleSubplotDefaults(layoutIn, layoutOut, fullData, { - type: 'gl3d', - attributes: layoutAttributes, - handleDefaults: handleGl3dDefaults, - font: layoutOut.font, - fullData: fullData, - getDfltFromLayout: getDfltFromLayout, - paper_bgcolor: layoutOut.paper_bgcolor, - calendar: layoutOut.calendar - }); -}; - -function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { + function getDfltFromLayout (attr) { + if (hasNon3D) return + + var isValid = layoutAttributes[attr].values.indexOf(layoutIn[attr]) !== -1 + if (isValid) return layoutIn[attr] + } + + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'gl3d', + attributes: layoutAttributes, + handleDefaults: handleGl3dDefaults, + font: layoutOut.font, + fullData: fullData, + getDfltFromLayout: getDfltFromLayout, + paper_bgcolor: layoutOut.paper_bgcolor, + calendar: layoutOut.calendar + }) +} + +function handleGl3dDefaults (sceneLayoutIn, sceneLayoutOut, coerce, opts) { /* * Scene numbering proceeds as follows * scene @@ -59,27 +57,27 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { * attributes like aspectratio can be written back dynamically. */ - var bgcolor = coerce('bgcolor'), - bgColorCombined = Color.combine(bgcolor, opts.paper_bgcolor); + var bgcolor = coerce('bgcolor'), + bgColorCombined = Color.combine(bgcolor, opts.paper_bgcolor) - var cameraKeys = Object.keys(layoutAttributes.camera); + var cameraKeys = Object.keys(layoutAttributes.camera) - for(var j = 0; j < cameraKeys.length; j++) { - coerce('camera.' + cameraKeys[j] + '.x'); - coerce('camera.' + cameraKeys[j] + '.y'); - coerce('camera.' + cameraKeys[j] + '.z'); - } + for (var j = 0; j < cameraKeys.length; j++) { + coerce('camera.' + cameraKeys[j] + '.x') + coerce('camera.' + cameraKeys[j] + '.y') + coerce('camera.' + cameraKeys[j] + '.z') + } /* * coerce to positive number (min 0) but also do not accept 0 (>0 not >=0) * note that 0's go false with the !! call */ - var hasAspect = !!coerce('aspectratio.x') && + var hasAspect = !!coerce('aspectratio.x') && !!coerce('aspectratio.y') && - !!coerce('aspectratio.z'); + !!coerce('aspectratio.z') - var defaultAspectMode = hasAspect ? 'manual' : 'auto'; - var aspectMode = coerce('aspectmode', defaultAspectMode); + var defaultAspectMode = hasAspect ? 'manual' : 'auto' + var aspectMode = coerce('aspectmode', defaultAspectMode) /* * We need aspectratio object in all the Layouts as it is dynamically set @@ -88,20 +86,20 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { * for the mode. In this case we must force change it here as the default coerce * misses it above. */ - if(!hasAspect) { - sceneLayoutIn.aspectratio = sceneLayoutOut.aspectratio = {x: 1, y: 1, z: 1}; - - if(aspectMode === 'manual') sceneLayoutOut.aspectmode = 'auto'; - } - - supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { - font: opts.font, - scene: opts.id, - data: opts.fullData, - bgColor: bgColorCombined, - calendar: opts.calendar - }); - - coerce('dragmode', opts.getDfltFromLayout('dragmode')); - coerce('hovermode', opts.getDfltFromLayout('hovermode')); + if (!hasAspect) { + sceneLayoutIn.aspectratio = sceneLayoutOut.aspectratio = {x: 1, y: 1, z: 1} + + if (aspectMode === 'manual') sceneLayoutOut.aspectmode = 'auto' + } + + supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { + font: opts.font, + scene: opts.id, + data: opts.fullData, + bgColor: bgColorCombined, + calendar: opts.calendar + }) + + coerce('dragmode', opts.getDfltFromLayout('dragmode')) + coerce('hovermode', opts.getDfltFromLayout('hovermode')) } diff --git a/src/plots/gl3d/layout/layout_attributes.js b/src/plots/gl3d/layout/layout_attributes.js index a708ea7f1e7..9bd5bfd7fe1 100644 --- a/src/plots/gl3d/layout/layout_attributes.js +++ b/src/plots/gl3d/layout/layout_attributes.js @@ -6,163 +6,162 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var gl3dAxisAttrs = require('./axis_attributes') +var extendFlat = require('../../../lib/extend').extendFlat -var gl3dAxisAttrs = require('./axis_attributes'); -var extendFlat = require('../../../lib/extend').extendFlat; - -function makeVector(x, y, z) { - return { - x: { - valType: 'number', - role: 'info', - dflt: x - }, - y: { - valType: 'number', - role: 'info', - dflt: y - }, - z: { - valType: 'number', - role: 'info', - dflt: z - } - }; +function makeVector (x, y, z) { + return { + x: { + valType: 'number', + role: 'info', + dflt: x + }, + y: { + valType: 'number', + role: 'info', + dflt: y + }, + z: { + valType: 'number', + role: 'info', + dflt: z + } + } } module.exports = { - bgcolor: { - valType: 'color', - role: 'style', - dflt: 'rgba(0,0,0,0)' - }, - camera: { - up: extendFlat(makeVector(0, 0, 1), { - description: [ - 'Sets the (x,y,z) components of the \'up\' camera vector.', - 'This vector determines the up direction of this scene', - 'with respect to the page.', - 'The default is *{x: 0, y: 0, z: 1}* which means that', - 'the z axis points up.' - ].join(' ') - }), - center: extendFlat(makeVector(0, 0, 0), { - description: [ - 'Sets the (x,y,z) components of the \'center\' camera vector', - 'This vector determines the translation (x,y,z) space', - 'about the center of this scene.', - 'By default, there is no such translation.' - ].join(' ') - }), - eye: extendFlat(makeVector(1.25, 1.25, 1.25), { - description: [ - 'Sets the (x,y,z) components of the \'eye\' camera vector.', - 'This vector determines the view point about the origin', - 'of this scene.' - ].join(' ') - }) - }, - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ + bgcolor: { + valType: 'color', + role: 'style', + dflt: 'rgba(0,0,0,0)' + }, + camera: { + up: extendFlat(makeVector(0, 0, 1), { + description: [ + 'Sets the (x,y,z) components of the \'up\' camera vector.', + 'This vector determines the up direction of this scene', + 'with respect to the page.', + 'The default is *{x: 0, y: 0, z: 1}* which means that', + 'the z axis points up.' + ].join(' ') + }), + center: extendFlat(makeVector(0, 0, 0), { + description: [ + 'Sets the (x,y,z) components of the \'center\' camera vector', + 'This vector determines the translation (x,y,z) space', + 'about the center of this scene.', + 'By default, there is no such translation.' + ].join(' ') + }), + eye: extendFlat(makeVector(1.25, 1.25, 1.25), { + description: [ + 'Sets the (x,y,z) components of the \'eye\' camera vector.', + 'This vector determines the view point about the origin', + 'of this scene.' + ].join(' ') + }) + }, + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this scene', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this scene', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this scene', - '(in plot fraction).' - ].join(' ') - } - }, - aspectmode: { - valType: 'enumerated', - role: 'info', - values: ['auto', 'cube', 'data', 'manual'], - dflt: 'auto', - description: [ - 'If *cube*, this scene\'s axes are drawn as a cube,', - 'regardless of the axes\' ranges.', + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this scene', + '(in plot fraction).' + ].join(' ') + } + }, + aspectmode: { + valType: 'enumerated', + role: 'info', + values: ['auto', 'cube', 'data', 'manual'], + dflt: 'auto', + description: [ + 'If *cube*, this scene\'s axes are drawn as a cube,', + 'regardless of the axes\' ranges.', - 'If *data*, this scene\'s axes are drawn', - 'in proportion with the axes\' ranges.', + 'If *data*, this scene\'s axes are drawn', + 'in proportion with the axes\' ranges.', - 'If *manual*, this scene\'s axes are drawn', - 'in proportion with the input of *aspectratio*', - '(the default behavior if *aspectratio* is provided).', + 'If *manual*, this scene\'s axes are drawn', + 'in proportion with the input of *aspectratio*', + '(the default behavior if *aspectratio* is provided).', - 'If *auto*, this scene\'s axes are drawn', - 'using the results of *data* except when one axis', - 'is more than four times the size of the two others,', - 'where in that case the results of *cube* are used.' - ].join(' ') + 'If *auto*, this scene\'s axes are drawn', + 'using the results of *data* except when one axis', + 'is more than four times the size of the two others,', + 'where in that case the results of *cube* are used.' + ].join(' ') + }, + aspectratio: { // must be positive (0's are coerced to 1) + x: { + valType: 'number', + role: 'info', + min: 0 + }, + y: { + valType: 'number', + role: 'info', + min: 0 }, - aspectratio: { // must be positive (0's are coerced to 1) - x: { - valType: 'number', - role: 'info', - min: 0 - }, - y: { - valType: 'number', - role: 'info', - min: 0 - }, - z: { - valType: 'number', - role: 'info', - min: 0 - }, - description: [ - 'Sets this scene\'s axis aspectratio.' - ].join(' ') + z: { + valType: 'number', + role: 'info', + min: 0 }, + description: [ + 'Sets this scene\'s axis aspectratio.' + ].join(' ') + }, - xaxis: gl3dAxisAttrs, - yaxis: gl3dAxisAttrs, - zaxis: gl3dAxisAttrs, + xaxis: gl3dAxisAttrs, + yaxis: gl3dAxisAttrs, + zaxis: gl3dAxisAttrs, - dragmode: { - valType: 'enumerated', - role: 'info', - values: ['orbit', 'turntable', 'zoom', 'pan'], - dflt: 'turntable', - description: [ - 'Determines the mode of drag interactions for this scene.' - ].join(' ') - }, - hovermode: { - valType: 'enumerated', - role: 'info', - values: ['closest', false], - dflt: 'closest', - description: [ - 'Determines the mode of hover interactions for this scene.' - ].join(' ') - }, + dragmode: { + valType: 'enumerated', + role: 'info', + values: ['orbit', 'turntable', 'zoom', 'pan'], + dflt: 'turntable', + description: [ + 'Determines the mode of drag interactions for this scene.' + ].join(' ') + }, + hovermode: { + valType: 'enumerated', + role: 'info', + values: ['closest', false], + dflt: 'closest', + description: [ + 'Determines the mode of hover interactions for this scene.' + ].join(' ') + }, - _deprecated: { - cameraposition: { - valType: 'info_array', - role: 'info', - description: 'Obsolete. Use `camera` instead.' - } + _deprecated: { + cameraposition: { + valType: 'info_array', + role: 'info', + description: 'Obsolete. Use `camera` instead.' } -}; + } +} diff --git a/src/plots/gl3d/layout/spikes.js b/src/plots/gl3d/layout/spikes.js index b835642f7cb..5ca36e8d19e 100644 --- a/src/plots/gl3d/layout/spikes.js +++ b/src/plots/gl3d/layout/spikes.js @@ -6,39 +6,38 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var str2RGBArray = require('../../../lib/str2rgbarray') -var str2RGBArray = require('../../../lib/str2rgbarray'); +var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis'] -var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; - -function SpikeOptions() { - this.enabled = [true, true, true]; - this.colors = [[0, 0, 0, 1], +function SpikeOptions () { + this.enabled = [true, true, true] + this.colors = [[0, 0, 0, 1], [0, 0, 0, 1], - [0, 0, 0, 1]]; - this.drawSides = [true, true, true]; - this.lineWidth = [1, 1, 1]; + [0, 0, 0, 1]] + this.drawSides = [true, true, true] + this.lineWidth = [1, 1, 1] } -var proto = SpikeOptions.prototype; +var proto = SpikeOptions.prototype -proto.merge = function(sceneLayout) { - for(var i = 0; i < 3; ++i) { - var axes = sceneLayout[AXES_NAMES[i]]; +proto.merge = function (sceneLayout) { + for (var i = 0; i < 3; ++i) { + var axes = sceneLayout[AXES_NAMES[i]] - this.enabled[i] = axes.showspikes; - this.colors[i] = str2RGBArray(axes.spikecolor); - this.drawSides[i] = axes.spikesides; - this.lineWidth[i] = axes.spikethickness; - } -}; + this.enabled[i] = axes.showspikes + this.colors[i] = str2RGBArray(axes.spikecolor) + this.drawSides[i] = axes.spikesides + this.lineWidth[i] = axes.spikethickness + } +} -function createSpikeOptions(layout) { - var result = new SpikeOptions(); - result.merge(layout); - return result; +function createSpikeOptions (layout) { + var result = new SpikeOptions() + result.merge(layout) + return result } -module.exports = createSpikeOptions; +module.exports = createSpikeOptions diff --git a/src/plots/gl3d/layout/tick_marks.js b/src/plots/gl3d/layout/tick_marks.js index af98e6d4e45..e7d2c14b7d4 100644 --- a/src/plots/gl3d/layout/tick_marks.js +++ b/src/plots/gl3d/layout/tick_marks.js @@ -6,89 +6,88 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint block-scoped-var: 0*/ -/* eslint no-redeclare: 0*/ +/* eslint block-scoped-var: 0 */ +/* eslint no-redeclare: 0 */ -'use strict'; +'use strict' -module.exports = computeTickMarks; +module.exports = computeTickMarks -var Axes = require('../../cartesian/axes'); -var Lib = require('../../../lib'); -var convertHTMLToUnicode = require('../../../lib/html2unicode'); +var Axes = require('../../cartesian/axes') +var Lib = require('../../../lib') +var convertHTMLToUnicode = require('../../../lib/html2unicode') -var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis']; +var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis'] -var centerPoint = [0, 0, 0]; +var centerPoint = [0, 0, 0] -function contourLevelsFromTicks(ticks) { - var result = new Array(3); - for(var i = 0; i < 3; ++i) { - var tlevel = ticks[i]; - var clevel = new Array(tlevel.length); - for(var j = 0; j < tlevel.length; ++j) { - clevel[j] = tlevel[j].x; - } - result[i] = clevel; +function contourLevelsFromTicks (ticks) { + var result = new Array(3) + for (var i = 0; i < 3; ++i) { + var tlevel = ticks[i] + var clevel = new Array(tlevel.length) + for (var j = 0; j < tlevel.length; ++j) { + clevel[j] = tlevel[j].x } - return result; + result[i] = clevel + } + return result } -function computeTickMarks(scene) { - var axesOptions = scene.axesOptions; - var glRange = scene.glplot.axesPixels; - var sceneLayout = scene.fullSceneLayout; +function computeTickMarks (scene) { + var axesOptions = scene.axesOptions + var glRange = scene.glplot.axesPixels + var sceneLayout = scene.fullSceneLayout - var ticks = [[], [], []]; + var ticks = [[], [], []] - for(var i = 0; i < 3; ++i) { - var axes = sceneLayout[AXES_NAMES[i]]; + for (var i = 0; i < 3; ++i) { + var axes = sceneLayout[AXES_NAMES[i]] - axes._length = (glRange[i].hi - glRange[i].lo) * - glRange[i].pixelsPerDataUnit / scene.dataScale[i]; + axes._length = (glRange[i].hi - glRange[i].lo) * + glRange[i].pixelsPerDataUnit / scene.dataScale[i] - if(Math.abs(axes._length) === Infinity) { - ticks[i] = []; - } else { - axes.range[0] = (glRange[i].lo) / scene.dataScale[i]; - axes.range[1] = (glRange[i].hi) / scene.dataScale[i]; - axes._m = 1.0 / (scene.dataScale[i] * glRange[i].pixelsPerDataUnit); + if (Math.abs(axes._length) === Infinity) { + ticks[i] = [] + } else { + axes.range[0] = (glRange[i].lo) / scene.dataScale[i] + axes.range[1] = (glRange[i].hi) / scene.dataScale[i] + axes._m = 1.0 / (scene.dataScale[i] * glRange[i].pixelsPerDataUnit) - if(axes.range[0] === axes.range[1]) { - axes.range[0] -= 1; - axes.range[1] += 1; - } + if (axes.range[0] === axes.range[1]) { + axes.range[0] -= 1 + axes.range[1] += 1 + } // this is necessary to short-circuit the 'y' handling // in autotick part of calcTicks... Treating all axes as 'y' in this case // running the autoticks here, then setting // autoticks to false to get around the 2D handling in calcTicks. - var tickModeCached = axes.tickmode; - if(axes.tickmode === 'auto') { - axes.tickmode = 'linear'; - var nticks = axes.nticks || Lib.constrain((axes._length / 40), 4, 9); - Axes.autoTicks(axes, Math.abs(axes.range[1] - axes.range[0]) / nticks); - } - var dataTicks = Axes.calcTicks(axes); - for(var j = 0; j < dataTicks.length; ++j) { - dataTicks[j].x = dataTicks[j].x * scene.dataScale[i]; - dataTicks[j].text = convertHTMLToUnicode(dataTicks[j].text); - } - ticks[i] = dataTicks; - - - axes.tickmode = tickModeCached; - } + var tickModeCached = axes.tickmode + if (axes.tickmode === 'auto') { + axes.tickmode = 'linear' + var nticks = axes.nticks || Lib.constrain((axes._length / 40), 4, 9) + Axes.autoTicks(axes, Math.abs(axes.range[1] - axes.range[0]) / nticks) + } + var dataTicks = Axes.calcTicks(axes) + for (var j = 0; j < dataTicks.length; ++j) { + dataTicks[j].x = dataTicks[j].x * scene.dataScale[i] + dataTicks[j].text = convertHTMLToUnicode(dataTicks[j].text) + } + ticks[i] = dataTicks + + axes.tickmode = tickModeCached } + } - axesOptions.ticks = ticks; + axesOptions.ticks = ticks // Calculate tick lengths dynamically - for(var i = 0; i < 3; ++i) { - centerPoint[i] = 0.5 * (scene.glplot.bounds[0][i] + scene.glplot.bounds[1][i]); - for(var j = 0; j < 2; ++j) { - axesOptions.bounds[j][i] = scene.glplot.bounds[j][i]; - } + for (var i = 0; i < 3; ++i) { + centerPoint[i] = 0.5 * (scene.glplot.bounds[0][i] + scene.glplot.bounds[1][i]) + for (var j = 0; j < 2; ++j) { + axesOptions.bounds[j][i] = scene.glplot.bounds[j][i] } + } - scene.contourLevels = contourLevelsFromTicks(ticks); + scene.contourLevels = contourLevelsFromTicks(ticks) } diff --git a/src/plots/gl3d/project.js b/src/plots/gl3d/project.js index 2fe6c437e14..094b7e5b042 100644 --- a/src/plots/gl3d/project.js +++ b/src/plots/gl3d/project.js @@ -6,27 +6,26 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +function xformMatrix (m, v) { + var out = [0, 0, 0, 0] + var i, j -function xformMatrix(m, v) { - var out = [0, 0, 0, 0]; - var i, j; - - for(i = 0; i < 4; ++i) { - for(j = 0; j < 4; ++j) { - out[j] += m[4 * i + j] * v[i]; - } + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + out[j] += m[4 * i + j] * v[i] } + } - return out; + return out } -function project(camera, v) { - var p = xformMatrix(camera.projection, +function project (camera, v) { + var p = xformMatrix(camera.projection, xformMatrix(camera.view, - xformMatrix(camera.model, [v[0], v[1], v[2], 1]))); - return p; + xformMatrix(camera.model, [v[0], v[1], v[2], 1]))) + return p } -module.exports = project; +module.exports = project diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index f287a5c3aa9..b32d51a9de5 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -6,705 +6,683 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var createPlot = require('gl-plot3d') +var getContext = require('webgl-context') -var createPlot = require('gl-plot3d'); -var getContext = require('webgl-context'); +var Lib = require('../../lib') -var Lib = require('../../lib'); +var Axes = require('../../plots/cartesian/axes') +var Fx = require('../../plots/cartesian/graph_interact') -var Axes = require('../../plots/cartesian/axes'); -var Fx = require('../../plots/cartesian/graph_interact'); +var str2RGBAarray = require('../../lib/str2rgbarray') +var showNoWebGlMsg = require('../../lib/show_no_webgl_msg') -var str2RGBAarray = require('../../lib/str2rgbarray'); -var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); +var createCamera = require('./camera') +var project = require('./project') +var setConvert = require('./set_convert') +var createAxesOptions = require('./layout/convert') +var createSpikeOptions = require('./layout/spikes') +var computeTickMarks = require('./layout/tick_marks') -var createCamera = require('./camera'); -var project = require('./project'); -var setConvert = require('./set_convert'); -var createAxesOptions = require('./layout/convert'); -var createSpikeOptions = require('./layout/spikes'); -var computeTickMarks = require('./layout/tick_marks'); +var STATIC_CANVAS, STATIC_CONTEXT -var STATIC_CANVAS, STATIC_CONTEXT; - -function render(scene) { - - var trace; +function render (scene) { + var trace // update size of svg container - var svgContainer = scene.svgContainer; - var clientRect = scene.container.getBoundingClientRect(); - var width = clientRect.width, height = clientRect.height; - svgContainer.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height); - svgContainer.setAttributeNS(null, 'width', width); - svgContainer.setAttributeNS(null, 'height', height); + var svgContainer = scene.svgContainer + var clientRect = scene.container.getBoundingClientRect() + var width = clientRect.width, height = clientRect.height + svgContainer.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height) + svgContainer.setAttributeNS(null, 'width', width) + svgContainer.setAttributeNS(null, 'height', height) - computeTickMarks(scene); - scene.glplot.axes.update(scene.axesOptions); + computeTickMarks(scene) + scene.glplot.axes.update(scene.axesOptions) // check if pick has changed - var keys = Object.keys(scene.traces); - var lastPicked = null; - var selection = scene.glplot.selection; - for(var i = 0; i < keys.length; ++i) { - trace = scene.traces[keys[i]]; - if(trace.data.hoverinfo !== 'skip' && trace.handlePick(selection)) { - lastPicked = trace; - } - - if(trace.setContourLevels) trace.setContourLevels(); + var keys = Object.keys(scene.traces) + var lastPicked = null + var selection = scene.glplot.selection + for (var i = 0; i < keys.length; ++i) { + trace = scene.traces[keys[i]] + if (trace.data.hoverinfo !== 'skip' && trace.handlePick(selection)) { + lastPicked = trace } - function formatter(axisName, val) { - if(typeof val === 'string') return val; + if (trace.setContourLevels) trace.setContourLevels() + } - var axis = scene.fullSceneLayout[axisName]; - return Axes.tickText(axis, axis.c2l(val), 'hover').text; - } + function formatter (axisName, val) { + if (typeof val === 'string') return val - var oldEventData; + var axis = scene.fullSceneLayout[axisName] + return Axes.tickText(axis, axis.c2l(val), 'hover').text + } - if(lastPicked !== null) { - var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate); - trace = lastPicked.data; - var hoverinfo = trace.hoverinfo; + var oldEventData - var xVal = formatter('xaxis', selection.traceCoordinate[0]), - yVal = formatter('yaxis', selection.traceCoordinate[1]), - zVal = formatter('zaxis', selection.traceCoordinate[2]); + if (lastPicked !== null) { + var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate) + trace = lastPicked.data + var hoverinfo = trace.hoverinfo - if(hoverinfo !== 'all') { - var hoverinfoParts = hoverinfo.split('+'); - if(hoverinfoParts.indexOf('x') === -1) xVal = undefined; - if(hoverinfoParts.indexOf('y') === -1) yVal = undefined; - if(hoverinfoParts.indexOf('z') === -1) zVal = undefined; - if(hoverinfoParts.indexOf('text') === -1) selection.textLabel = undefined; - if(hoverinfoParts.indexOf('name') === -1) lastPicked.name = undefined; - } + var xVal = formatter('xaxis', selection.traceCoordinate[0]), + yVal = formatter('yaxis', selection.traceCoordinate[1]), + zVal = formatter('zaxis', selection.traceCoordinate[2]) - if(scene.fullSceneLayout.hovermode) { - Fx.loneHover({ - x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width, - y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height, - xLabel: xVal, - yLabel: yVal, - zLabel: zVal, - text: selection.textLabel, - name: lastPicked.name, - color: lastPicked.color - }, { - container: svgContainer - }); - } + if (hoverinfo !== 'all') { + var hoverinfoParts = hoverinfo.split('+') + if (hoverinfoParts.indexOf('x') === -1) xVal = undefined + if (hoverinfoParts.indexOf('y') === -1) yVal = undefined + if (hoverinfoParts.indexOf('z') === -1) zVal = undefined + if (hoverinfoParts.indexOf('text') === -1) selection.textLabel = undefined + if (hoverinfoParts.indexOf('name') === -1) lastPicked.name = undefined + } - var eventData = { - points: [{ - x: xVal, - y: yVal, - z: zVal, - data: trace._input, - fullData: trace, - curveNumber: trace.index, - pointNumber: selection.data.index - }] - }; - - if(selection.buttons && selection.distance < 5) { - scene.graphDiv.emit('plotly_click', eventData); - } - else { - scene.graphDiv.emit('plotly_hover', eventData); - } + if (scene.fullSceneLayout.hovermode) { + Fx.loneHover({ + x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width, + y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height, + xLabel: xVal, + yLabel: yVal, + zLabel: zVal, + text: selection.textLabel, + name: lastPicked.name, + color: lastPicked.color + }, { + container: svgContainer + }) + } - oldEventData = eventData; + var eventData = { + points: [{ + x: xVal, + y: yVal, + z: zVal, + data: trace._input, + fullData: trace, + curveNumber: trace.index, + pointNumber: selection.data.index + }] } - else { - Fx.loneUnhover(svgContainer); - scene.graphDiv.emit('plotly_unhover', oldEventData); + + if (selection.buttons && selection.distance < 5) { + scene.graphDiv.emit('plotly_click', eventData) + } else { + scene.graphDiv.emit('plotly_hover', eventData) } + + oldEventData = eventData + } else { + Fx.loneUnhover(svgContainer) + scene.graphDiv.emit('plotly_unhover', oldEventData) + } } -function initializeGLPlot(scene, fullLayout, canvas, gl) { - var glplotOptions = { - canvas: canvas, - gl: gl, - container: scene.container, - axes: scene.axesOptions, - spikes: scene.spikeOptions, - pickRadius: 10, - snapToData: true, - autoScale: true, - autoBounds: false - }; +function initializeGLPlot (scene, fullLayout, canvas, gl) { + var glplotOptions = { + canvas: canvas, + gl: gl, + container: scene.container, + axes: scene.axesOptions, + spikes: scene.spikeOptions, + pickRadius: 10, + snapToData: true, + autoScale: true, + autoBounds: false + } // for static plots, we reuse the WebGL context // as WebKit doesn't collect them reliably - if(scene.staticMode) { - if(!STATIC_CONTEXT) { - STATIC_CANVAS = document.createElement('canvas'); - STATIC_CONTEXT = getContext({ - canvas: STATIC_CANVAS, - preserveDrawingBuffer: true, - premultipliedAlpha: true, - antialias: true - }); - if(!STATIC_CONTEXT) { - throw new Error('error creating static canvas/context for image server'); - } - } - glplotOptions.pixelRatio = scene.pixelRatio; - glplotOptions.gl = STATIC_CONTEXT; - glplotOptions.canvas = STATIC_CANVAS; - } - - try { - scene.glplot = createPlot(glplotOptions); + if (scene.staticMode) { + if (!STATIC_CONTEXT) { + STATIC_CANVAS = document.createElement('canvas') + STATIC_CONTEXT = getContext({ + canvas: STATIC_CANVAS, + preserveDrawingBuffer: true, + premultipliedAlpha: true, + antialias: true + }) + if (!STATIC_CONTEXT) { + throw new Error('error creating static canvas/context for image server') + } } - catch(e) { + glplotOptions.pixelRatio = scene.pixelRatio + glplotOptions.gl = STATIC_CONTEXT + glplotOptions.canvas = STATIC_CANVAS + } + + try { + scene.glplot = createPlot(glplotOptions) + } catch (e) { /* * createPlot will throw when webgl is not enabled in the client. * Lets return an instance of the module with all functions noop'd. * The destroy method - which will remove the container from the DOM * is overridden with a function that removes the container only. */ - showNoWebGlMsg(scene); - } - - var relayoutCallback = function(scene) { - var update = {}; - update[scene.id] = getLayoutCamera(scene.camera); - scene.saveCamera(scene.graphDiv.layout); - scene.graphDiv.emit('plotly_relayout', update); - }; - - scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene)); - scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene)); - - if(!scene.staticMode) { - scene.glplot.canvas.addEventListener('webglcontextlost', function(ev) { - Lib.warn('Lost WebGL context.'); - ev.preventDefault(); - }); - } - - if(!scene.camera) { - var cameraData = scene.fullSceneLayout.camera; - scene.camera = createCamera(scene.container, { - center: [cameraData.center.x, cameraData.center.y, cameraData.center.z], - eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z], - up: [cameraData.up.x, cameraData.up.y, cameraData.up.z], - zoomMin: 0.1, - zoomMax: 100, - mode: 'orbit' - }); - } - - scene.glplot.camera = scene.camera; - - scene.glplot.oncontextloss = function() { - scene.recoverContext(); - }; - - scene.glplot.onrender = render.bind(null, scene); + showNoWebGlMsg(scene) + } + + var relayoutCallback = function (scene) { + var update = {} + update[scene.id] = getLayoutCamera(scene.camera) + scene.saveCamera(scene.graphDiv.layout) + scene.graphDiv.emit('plotly_relayout', update) + } + + scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene)) + scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene)) + + if (!scene.staticMode) { + scene.glplot.canvas.addEventListener('webglcontextlost', function (ev) { + Lib.warn('Lost WebGL context.') + ev.preventDefault() + }) + } + + if (!scene.camera) { + var cameraData = scene.fullSceneLayout.camera + scene.camera = createCamera(scene.container, { + center: [cameraData.center.x, cameraData.center.y, cameraData.center.z], + eye: [cameraData.eye.x, cameraData.eye.y, cameraData.eye.z], + up: [cameraData.up.x, cameraData.up.y, cameraData.up.z], + zoomMin: 0.1, + zoomMax: 100, + mode: 'orbit' + }) + } + + scene.glplot.camera = scene.camera + + scene.glplot.oncontextloss = function () { + scene.recoverContext() + } + + scene.glplot.onrender = render.bind(null, scene) // List of scene objects - scene.traces = {}; + scene.traces = {} - return true; + return true } -function Scene(options, fullLayout) { - +function Scene (options, fullLayout) { // create sub container for plot - var sceneContainer = document.createElement('div'); - var plotContainer = options.container; + var sceneContainer = document.createElement('div') + var plotContainer = options.container // keep a ref to the graph div to fire hover+click events - this.graphDiv = options.graphDiv; + this.graphDiv = options.graphDiv // create SVG container for hover text - var svgContainer = document.createElementNS( + var svgContainer = document.createElementNS( 'http://www.w3.org/2000/svg', - 'svg'); - svgContainer.style.position = 'absolute'; - svgContainer.style.top = svgContainer.style.left = '0px'; - svgContainer.style.width = svgContainer.style.height = '100%'; - svgContainer.style['z-index'] = 20; - svgContainer.style['pointer-events'] = 'none'; - sceneContainer.appendChild(svgContainer); - this.svgContainer = svgContainer; + 'svg') + svgContainer.style.position = 'absolute' + svgContainer.style.top = svgContainer.style.left = '0px' + svgContainer.style.width = svgContainer.style.height = '100%' + svgContainer.style['z-index'] = 20 + svgContainer.style['pointer-events'] = 'none' + sceneContainer.appendChild(svgContainer) + this.svgContainer = svgContainer // Tag the container with the sceneID - sceneContainer.id = options.id; - sceneContainer.style.position = 'absolute'; - sceneContainer.style.top = sceneContainer.style.left = '0px'; - sceneContainer.style.width = sceneContainer.style.height = '100%'; - plotContainer.appendChild(sceneContainer); + sceneContainer.id = options.id + sceneContainer.style.position = 'absolute' + sceneContainer.style.top = sceneContainer.style.left = '0px' + sceneContainer.style.width = sceneContainer.style.height = '100%' + plotContainer.appendChild(sceneContainer) - this.fullLayout = fullLayout; - this.id = options.id || 'scene'; - this.fullSceneLayout = fullLayout[this.id]; + this.fullLayout = fullLayout + this.id = options.id || 'scene' + this.fullSceneLayout = fullLayout[this.id] // Saved from last call to plot() - this.plotArgs = [ [], {}, {} ]; + this.plotArgs = [ [], {}, {} ] /* * Move this to calc step? Why does it work here? */ - this.axesOptions = createAxesOptions(fullLayout[this.id]); - this.spikeOptions = createSpikeOptions(fullLayout[this.id]); - this.container = sceneContainer; - this.staticMode = !!options.staticPlot; - this.pixelRatio = options.plotGlPixelRatio || 2; + this.axesOptions = createAxesOptions(fullLayout[this.id]) + this.spikeOptions = createSpikeOptions(fullLayout[this.id]) + this.container = sceneContainer + this.staticMode = !!options.staticPlot + this.pixelRatio = options.plotGlPixelRatio || 2 // Coordinate rescaling - this.dataScale = [1, 1, 1]; + this.dataScale = [1, 1, 1] - this.contourLevels = [ [], [], [] ]; + this.contourLevels = [ [], [], [] ] - if(!initializeGLPlot(this, fullLayout)) return; // todo check the necessity for this line + if (!initializeGLPlot(this, fullLayout)) return // todo check the necessity for this line } -var proto = Scene.prototype; +var proto = Scene.prototype -proto.recoverContext = function() { - var scene = this; - var gl = this.glplot.gl; - var canvas = this.glplot.canvas; - this.glplot.dispose(); +proto.recoverContext = function () { + var scene = this + var gl = this.glplot.gl + var canvas = this.glplot.canvas + this.glplot.dispose() - function tryRecover() { - if(gl.isContextLost()) { - requestAnimationFrame(tryRecover); - return; - } - if(!initializeGLPlot(scene, scene.fullLayout, canvas, gl)) { - Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.'); - return; - } - scene.plot.apply(scene, scene.plotArgs); + function tryRecover () { + if (gl.isContextLost()) { + requestAnimationFrame(tryRecover) + return } - requestAnimationFrame(tryRecover); -}; - -var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ]; - -function coordinateBound(axis, coord, d, bounds, calendar) { - var x; - for(var i = 0; i < coord.length; ++i) { - if(Array.isArray(coord[i])) { - for(var j = 0; j < coord[i].length; ++j) { - x = axis.d2l(coord[i][j], 0, calendar); - if(!isNaN(x) && isFinite(x)) { - bounds[0][d] = Math.min(bounds[0][d], x); - bounds[1][d] = Math.max(bounds[1][d], x); - } - } - } - else { - x = axis.d2l(coord[i], 0, calendar); - if(!isNaN(x) && isFinite(x)) { - bounds[0][d] = Math.min(bounds[0][d], x); - bounds[1][d] = Math.max(bounds[1][d], x); - } - } + if (!initializeGLPlot(scene, scene.fullLayout, canvas, gl)) { + Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.') + return } + scene.plot.apply(scene, scene.plotArgs) + } + requestAnimationFrame(tryRecover) } -function computeTraceBounds(scene, trace, bounds) { - var sceneLayout = scene.fullSceneLayout; - coordinateBound(sceneLayout.xaxis, trace.x, 0, bounds, trace.xcalendar); - coordinateBound(sceneLayout.yaxis, trace.y, 1, bounds, trace.ycalendar); - coordinateBound(sceneLayout.zaxis, trace.z, 2, bounds, trace.zcalendar); +var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ] + +function coordinateBound (axis, coord, d, bounds, calendar) { + var x + for (var i = 0; i < coord.length; ++i) { + if (Array.isArray(coord[i])) { + for (var j = 0; j < coord[i].length; ++j) { + x = axis.d2l(coord[i][j], 0, calendar) + if (!isNaN(x) && isFinite(x)) { + bounds[0][d] = Math.min(bounds[0][d], x) + bounds[1][d] = Math.max(bounds[1][d], x) + } + } + } else { + x = axis.d2l(coord[i], 0, calendar) + if (!isNaN(x) && isFinite(x)) { + bounds[0][d] = Math.min(bounds[0][d], x) + bounds[1][d] = Math.max(bounds[1][d], x) + } + } + } } -proto.plot = function(sceneData, fullLayout, layout) { +function computeTraceBounds (scene, trace, bounds) { + var sceneLayout = scene.fullSceneLayout + coordinateBound(sceneLayout.xaxis, trace.x, 0, bounds, trace.xcalendar) + coordinateBound(sceneLayout.yaxis, trace.y, 1, bounds, trace.ycalendar) + coordinateBound(sceneLayout.zaxis, trace.z, 2, bounds, trace.zcalendar) +} +proto.plot = function (sceneData, fullLayout, layout) { // Save parameters - this.plotArgs = [sceneData, fullLayout, layout]; + this.plotArgs = [sceneData, fullLayout, layout] - if(this.glplot.contextLost) return; + if (this.glplot.contextLost) return - var data, trace; - var i, j, axis, axisType; - var fullSceneLayout = fullLayout[this.id]; - var sceneLayout = layout[this.id]; + var data, trace + var i, j, axis, axisType + var fullSceneLayout = fullLayout[this.id] + var sceneLayout = layout[this.id] - if(fullSceneLayout.bgcolor) this.glplot.clearColor = str2RGBAarray(fullSceneLayout.bgcolor); - else this.glplot.clearColor = [0, 0, 0, 0]; + if (fullSceneLayout.bgcolor) this.glplot.clearColor = str2RGBAarray(fullSceneLayout.bgcolor) + else this.glplot.clearColor = [0, 0, 0, 0] - this.glplot.snapToData = true; + this.glplot.snapToData = true // Update layout - this.fullSceneLayout = fullSceneLayout; + this.fullSceneLayout = fullSceneLayout - this.glplotLayout = fullSceneLayout; - this.axesOptions.merge(fullSceneLayout); - this.spikeOptions.merge(fullSceneLayout); + this.glplotLayout = fullSceneLayout + this.axesOptions.merge(fullSceneLayout) + this.spikeOptions.merge(fullSceneLayout) // Update camera and camera mode - this.setCamera(fullSceneLayout.camera); - this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode); + this.setCamera(fullSceneLayout.camera) + this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode) // Update scene - this.glplot.update({}); + this.glplot.update({}) // Update axes functions BEFORE updating traces - for(i = 0; i < 3; ++i) { - axis = fullSceneLayout[axisProperties[i]]; - setConvert(axis); - } + for (i = 0; i < 3; ++i) { + axis = fullSceneLayout[axisProperties[i]] + setConvert(axis) + } // Convert scene data - if(!sceneData) sceneData = []; - else if(!Array.isArray(sceneData)) sceneData = [sceneData]; + if (!sceneData) sceneData = [] + else if (!Array.isArray(sceneData)) sceneData = [sceneData] // Compute trace bounding box - var dataBounds = [ + var dataBounds = [ [Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity] - ]; - for(i = 0; i < sceneData.length; ++i) { - data = sceneData[i]; - if(data.visible !== true) continue; - - computeTraceBounds(this, data, dataBounds); - } - var dataScale = [1, 1, 1]; - for(j = 0; j < 3; ++j) { - if(dataBounds[0][j] > dataBounds[1][j]) { - dataScale[j] = 1.0; - } - else { - if(dataBounds[1][j] === dataBounds[0][j]) { - dataScale[j] = 1.0; - } - else { - dataScale[j] = 1.0 / (dataBounds[1][j] - dataBounds[0][j]); - } - } + ] + for (i = 0; i < sceneData.length; ++i) { + data = sceneData[i] + if (data.visible !== true) continue + + computeTraceBounds(this, data, dataBounds) + } + var dataScale = [1, 1, 1] + for (j = 0; j < 3; ++j) { + if (dataBounds[0][j] > dataBounds[1][j]) { + dataScale[j] = 1.0 + } else { + if (dataBounds[1][j] === dataBounds[0][j]) { + dataScale[j] = 1.0 + } else { + dataScale[j] = 1.0 / (dataBounds[1][j] - dataBounds[0][j]) + } } + } // Save scale - this.dataScale = dataScale; + this.dataScale = dataScale // Update traces - for(i = 0; i < sceneData.length; ++i) { - data = sceneData[i]; - if(data.visible !== true) { - continue; - } - trace = this.traces[data.uid]; - if(trace) { - trace.update(data); - } else { - trace = data._module.plot(this, data); - this.traces[data.uid] = trace; - } - trace.name = data.name; + for (i = 0; i < sceneData.length; ++i) { + data = sceneData[i] + if (data.visible !== true) { + continue + } + trace = this.traces[data.uid] + if (trace) { + trace.update(data) + } else { + trace = data._module.plot(this, data) + this.traces[data.uid] = trace } + trace.name = data.name + } // Remove empty traces - var traceIds = Object.keys(this.traces); - - trace_id_loop: - for(i = 0; i < traceIds.length; ++i) { - for(j = 0; j < sceneData.length; ++j) { - if(sceneData[j].uid === traceIds[i] && sceneData[j].visible === true) { - continue trace_id_loop; - } + var traceIds = Object.keys(this.traces) + + trace_id_loop: + for (i = 0; i < traceIds.length; ++i) { + for (j = 0; j < sceneData.length; ++j) { + if (sceneData[j].uid === traceIds[i] && sceneData[j].visible === true) { + continue trace_id_loop } - trace = this.traces[traceIds[i]]; - trace.dispose(); - delete this.traces[traceIds[i]]; + } + trace = this.traces[traceIds[i]] + trace.dispose() + delete this.traces[traceIds[i]] } // Update ranges (needs to be called *after* objects are added due to updates) - var sceneBounds = [[0, 0, 0], [0, 0, 0]], - axisDataRange = [], - axisTypeRatios = {}; + var sceneBounds = [[0, 0, 0], [0, 0, 0]], + axisDataRange = [], + axisTypeRatios = {} - for(i = 0; i < 3; ++i) { - axis = fullSceneLayout[axisProperties[i]]; - axisType = axis.type; + for (i = 0; i < 3; ++i) { + axis = fullSceneLayout[axisProperties[i]] + axisType = axis.type - if(axisType in axisTypeRatios) { - axisTypeRatios[axisType].acc *= dataScale[i]; - axisTypeRatios[axisType].count += 1; - } - else { - axisTypeRatios[axisType] = { - acc: dataScale[i], - count: 1 - }; - } + if (axisType in axisTypeRatios) { + axisTypeRatios[axisType].acc *= dataScale[i] + axisTypeRatios[axisType].count += 1 + } else { + axisTypeRatios[axisType] = { + acc: dataScale[i], + count: 1 + } + } - if(axis.autorange) { - sceneBounds[0][i] = Infinity; - sceneBounds[1][i] = -Infinity; - for(j = 0; j < this.glplot.objects.length; ++j) { - var objBounds = this.glplot.objects[j].bounds; - sceneBounds[0][i] = Math.min(sceneBounds[0][i], - objBounds[0][i] / dataScale[i]); - sceneBounds[1][i] = Math.max(sceneBounds[1][i], - objBounds[1][i] / dataScale[i]); - } - if('rangemode' in axis && axis.rangemode === 'tozero') { - sceneBounds[0][i] = Math.min(sceneBounds[0][i], 0); - sceneBounds[1][i] = Math.max(sceneBounds[1][i], 0); - } - if(sceneBounds[0][i] > sceneBounds[1][i]) { - sceneBounds[0][i] = -1; - sceneBounds[1][i] = 1; - } else { - var d = sceneBounds[1][i] - sceneBounds[0][i]; - sceneBounds[0][i] -= d / 32.0; - sceneBounds[1][i] += d / 32.0; - } - } else { - var range = fullSceneLayout[axisProperties[i]].range; - sceneBounds[0][i] = range[0]; - sceneBounds[1][i] = range[1]; - } - if(sceneBounds[0][i] === sceneBounds[1][i]) { - sceneBounds[0][i] -= 1; - sceneBounds[1][i] += 1; - } - axisDataRange[i] = sceneBounds[1][i] - sceneBounds[0][i]; + if (axis.autorange) { + sceneBounds[0][i] = Infinity + sceneBounds[1][i] = -Infinity + for (j = 0; j < this.glplot.objects.length; ++j) { + var objBounds = this.glplot.objects[j].bounds + sceneBounds[0][i] = Math.min(sceneBounds[0][i], + objBounds[0][i] / dataScale[i]) + sceneBounds[1][i] = Math.max(sceneBounds[1][i], + objBounds[1][i] / dataScale[i]) + } + if ('rangemode' in axis && axis.rangemode === 'tozero') { + sceneBounds[0][i] = Math.min(sceneBounds[0][i], 0) + sceneBounds[1][i] = Math.max(sceneBounds[1][i], 0) + } + if (sceneBounds[0][i] > sceneBounds[1][i]) { + sceneBounds[0][i] = -1 + sceneBounds[1][i] = 1 + } else { + var d = sceneBounds[1][i] - sceneBounds[0][i] + sceneBounds[0][i] -= d / 32.0 + sceneBounds[1][i] += d / 32.0 + } + } else { + var range = fullSceneLayout[axisProperties[i]].range + sceneBounds[0][i] = range[0] + sceneBounds[1][i] = range[1] + } + if (sceneBounds[0][i] === sceneBounds[1][i]) { + sceneBounds[0][i] -= 1 + sceneBounds[1][i] += 1 + } + axisDataRange[i] = sceneBounds[1][i] - sceneBounds[0][i] // Update plot bounds - this.glplot.bounds[0][i] = sceneBounds[0][i] * dataScale[i]; - this.glplot.bounds[1][i] = sceneBounds[1][i] * dataScale[i]; - } + this.glplot.bounds[0][i] = sceneBounds[0][i] * dataScale[i] + this.glplot.bounds[1][i] = sceneBounds[1][i] * dataScale[i] + } - var axesScaleRatio = [1, 1, 1]; + var axesScaleRatio = [1, 1, 1] // Compute axis scale per category - for(i = 0; i < 3; ++i) { - axis = fullSceneLayout[axisProperties[i]]; - axisType = axis.type; - var axisRatio = axisTypeRatios[axisType]; - axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i]; - } + for (i = 0; i < 3; ++i) { + axis = fullSceneLayout[axisProperties[i]] + axisType = axis.type + var axisRatio = axisTypeRatios[axisType] + axesScaleRatio[i] = Math.pow(axisRatio.acc, 1.0 / axisRatio.count) / dataScale[i] + } /* * Dynamically set the aspect ratio depending on the users aspect settings */ - var axisAutoScaleFactor = 4; - var aspectRatio; - - if(fullSceneLayout.aspectmode === 'auto') { - - if(Math.max.apply(null, axesScaleRatio) / Math.min.apply(null, axesScaleRatio) <= axisAutoScaleFactor) { + var axisAutoScaleFactor = 4 + var aspectRatio + if (fullSceneLayout.aspectmode === 'auto') { + if (Math.max.apply(null, axesScaleRatio) / Math.min.apply(null, axesScaleRatio) <= axisAutoScaleFactor) { /* * USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL */ - aspectRatio = axesScaleRatio; - } else { - + aspectRatio = axesScaleRatio + } else { /* * USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL */ - aspectRatio = [1, 1, 1]; - } - - } else if(fullSceneLayout.aspectmode === 'cube') { - aspectRatio = [1, 1, 1]; - - } else if(fullSceneLayout.aspectmode === 'data') { - aspectRatio = axesScaleRatio; - - } else if(fullSceneLayout.aspectmode === 'manual') { - var userRatio = fullSceneLayout.aspectratio; - aspectRatio = [userRatio.x, userRatio.y, userRatio.z]; - - } else { - throw new Error('scene.js aspectRatio was not one of the enumerated types'); + aspectRatio = [1, 1, 1] } + } else if (fullSceneLayout.aspectmode === 'cube') { + aspectRatio = [1, 1, 1] + } else if (fullSceneLayout.aspectmode === 'data') { + aspectRatio = axesScaleRatio + } else if (fullSceneLayout.aspectmode === 'manual') { + var userRatio = fullSceneLayout.aspectratio + aspectRatio = [userRatio.x, userRatio.y, userRatio.z] + } else { + throw new Error('scene.js aspectRatio was not one of the enumerated types') + } /* * Write aspect Ratio back to user data and fullLayout so that it is modifies as user * manipulates the aspectmode settings and the fullLayout is up-to-date. */ - fullSceneLayout.aspectratio.x = sceneLayout.aspectratio.x = aspectRatio[0]; - fullSceneLayout.aspectratio.y = sceneLayout.aspectratio.y = aspectRatio[1]; - fullSceneLayout.aspectratio.z = sceneLayout.aspectratio.z = aspectRatio[2]; + fullSceneLayout.aspectratio.x = sceneLayout.aspectratio.x = aspectRatio[0] + fullSceneLayout.aspectratio.y = sceneLayout.aspectratio.y = aspectRatio[1] + fullSceneLayout.aspectratio.z = sceneLayout.aspectratio.z = aspectRatio[2] /* * Finally assign the computed aspecratio to the glplot module. This will have an effect * on the next render cycle. */ - this.glplot.aspect = aspectRatio; - + this.glplot.aspect = aspectRatio // Update frame position for multi plots - var domain = fullSceneLayout.domain || null, - size = fullLayout._size || null; - - if(domain && size) { - var containerStyle = this.container.style; - containerStyle.position = 'absolute'; - containerStyle.left = (size.l + domain.x[0] * size.w) + 'px'; - containerStyle.top = (size.t + (1 - domain.y[1]) * size.h) + 'px'; - containerStyle.width = (size.w * (domain.x[1] - domain.x[0])) + 'px'; - containerStyle.height = (size.h * (domain.y[1] - domain.y[0])) + 'px'; - } + var domain = fullSceneLayout.domain || null, + size = fullLayout._size || null + + if (domain && size) { + var containerStyle = this.container.style + containerStyle.position = 'absolute' + containerStyle.left = (size.l + domain.x[0] * size.w) + 'px' + containerStyle.top = (size.t + (1 - domain.y[1]) * size.h) + 'px' + containerStyle.width = (size.w * (domain.x[1] - domain.x[0])) + 'px' + containerStyle.height = (size.h * (domain.y[1] - domain.y[0])) + 'px' + } // force redraw so that promise is returned when rendering is completed - this.glplot.redraw(); -}; + this.glplot.redraw() +} -proto.destroy = function() { - this.glplot.dispose(); - this.container.parentNode.removeChild(this.container); +proto.destroy = function () { + this.glplot.dispose() + this.container.parentNode.removeChild(this.container) // Remove reference to glplot - this.glplot = null; -}; + this.glplot = null +} // getOrbitCamera :: plotly_coords -> orbit_camera_coords // inverse of getLayoutCamera -function getOrbitCamera(camera) { - return [ +function getOrbitCamera (camera) { + return [ [camera.eye.x, camera.eye.y, camera.eye.z], [camera.center.x, camera.center.y, camera.center.z], [camera.up.x, camera.up.y, camera.up.z] - ]; + ] } // getLayoutCamera :: orbit_camera_coords -> plotly_coords // inverse of getOrbitCamera -function getLayoutCamera(camera) { - return { - up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]}, - center: {x: camera.center[0], y: camera.center[1], z: camera.center[2]}, - eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]} - }; +function getLayoutCamera (camera) { + return { + up: {x: camera.up[0], y: camera.up[1], z: camera.up[2]}, + center: {x: camera.center[0], y: camera.center[1], z: camera.center[2]}, + eye: {x: camera.eye[0], y: camera.eye[1], z: camera.eye[2]} + } } // get camera position in plotly coords from 'orbit-camera' coords -proto.getCamera = function getCamera() { - this.glplot.camera.view.recalcMatrix(this.camera.view.lastT()); - return getLayoutCamera(this.glplot.camera); -}; +proto.getCamera = function getCamera () { + this.glplot.camera.view.recalcMatrix(this.camera.view.lastT()) + return getLayoutCamera(this.glplot.camera) +} // set camera position with a set of plotly coords -proto.setCamera = function setCamera(cameraData) { - this.glplot.camera.lookAt.apply(this, getOrbitCamera(cameraData)); -}; +proto.setCamera = function setCamera (cameraData) { + this.glplot.camera.lookAt.apply(this, getOrbitCamera(cameraData)) +} // save camera to user layout (i.e. gd.layout) -proto.saveCamera = function saveCamera(layout) { - var cameraData = this.getCamera(), - cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'), - cameraDataLastSave = cameraNestedProp.get(), - hasChanged = false; - - function same(x, y, i, j) { - var vectors = ['up', 'center', 'eye'], - components = ['x', 'y', 'z']; - return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]); - } - - if(cameraDataLastSave === undefined) hasChanged = true; - else { - for(var i = 0; i < 3; i++) { - for(var j = 0; j < 3; j++) { - if(!same(cameraData, cameraDataLastSave, i, j)) { - hasChanged = true; - break; - } - } +proto.saveCamera = function saveCamera (layout) { + var cameraData = this.getCamera(), + cameraNestedProp = Lib.nestedProperty(layout, this.id + '.camera'), + cameraDataLastSave = cameraNestedProp.get(), + hasChanged = false + + function same (x, y, i, j) { + var vectors = ['up', 'center', 'eye'], + components = ['x', 'y', 'z'] + return y[vectors[i]] && (x[vectors[i]][components[j]] === y[vectors[i]][components[j]]) + } + + if (cameraDataLastSave === undefined) hasChanged = true + else { + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + if (!same(cameraData, cameraDataLastSave, i, j)) { + hasChanged = true + break } + } } + } - if(hasChanged) cameraNestedProp.set(cameraData); + if (hasChanged) cameraNestedProp.set(cameraData) - return hasChanged; -}; + return hasChanged +} -proto.updateFx = function(dragmode, hovermode) { - var camera = this.camera; +proto.updateFx = function (dragmode, hovermode) { + var camera = this.camera - if(camera) { + if (camera) { // rotate and orbital are synonymous - if(dragmode === 'orbit') { - camera.mode = 'orbit'; - camera.keyBindingMode = 'rotate'; - - } else if(dragmode === 'turntable') { - camera.up = [0, 0, 1]; - camera.mode = 'turntable'; - camera.keyBindingMode = 'rotate'; - - } else { - + if (dragmode === 'orbit') { + camera.mode = 'orbit' + camera.keyBindingMode = 'rotate' + } else if (dragmode === 'turntable') { + camera.up = [0, 0, 1] + camera.mode = 'turntable' + camera.keyBindingMode = 'rotate' + } else { // none rotation modes [pan or zoom] - camera.keyBindingMode = dragmode; - } + camera.keyBindingMode = dragmode } + } // to put dragmode and hovermode on the same grounds from relayout - this.fullSceneLayout.hovermode = hovermode; -}; + this.fullSceneLayout.hovermode = hovermode +} -proto.toImage = function(format) { - if(!format) format = 'png'; +proto.toImage = function (format) { + if (!format) format = 'png' - if(this.staticMode) this.container.appendChild(STATIC_CANVAS); + if (this.staticMode) this.container.appendChild(STATIC_CANVAS) // Force redraw - this.glplot.redraw(); + this.glplot.redraw() // Grab context and yank out pixels - var gl = this.glplot.gl; - var w = gl.drawingBufferWidth; - var h = gl.drawingBufferHeight; + var gl = this.glplot.gl + var w = gl.drawingBufferWidth + var h = gl.drawingBufferHeight - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null) - var pixels = new Uint8Array(w * h * 4); - gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + var pixels = new Uint8Array(w * h * 4) + gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels) // Flip pixels - for(var j = 0, k = h - 1; j < k; ++j, --k) { - for(var i = 0; i < w; ++i) { - for(var l = 0; l < 4; ++l) { - var tmp = pixels[4 * (w * j + i) + l]; - pixels[4 * (w * j + i) + l] = pixels[4 * (w * k + i) + l]; - pixels[4 * (w * k + i) + l] = tmp; - } - } + for (var j = 0, k = h - 1; j < k; ++j, --k) { + for (var i = 0; i < w; ++i) { + for (var l = 0; l < 4; ++l) { + var tmp = pixels[4 * (w * j + i) + l] + pixels[4 * (w * j + i) + l] = pixels[4 * (w * k + i) + l] + pixels[4 * (w * k + i) + l] = tmp + } } + } + + var canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h + var context = canvas.getContext('2d') + var imageData = context.createImageData(w, h) + imageData.data.set(pixels) + context.putImageData(imageData, 0, 0) + + var dataURL + + switch (format) { + case 'jpeg': + dataURL = canvas.toDataURL('image/jpeg') + break + case 'webp': + dataURL = canvas.toDataURL('image/webp') + break + default: + dataURL = canvas.toDataURL('image/png') + } + + if (this.staticMode) this.container.removeChild(STATIC_CANVAS) + + return dataURL +} - var canvas = document.createElement('canvas'); - canvas.width = w; - canvas.height = h; - var context = canvas.getContext('2d'); - var imageData = context.createImageData(w, h); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); - - var dataURL; - - switch(format) { - case 'jpeg': - dataURL = canvas.toDataURL('image/jpeg'); - break; - case 'webp': - dataURL = canvas.toDataURL('image/webp'); - break; - default: - dataURL = canvas.toDataURL('image/png'); - } - - if(this.staticMode) this.container.removeChild(STATIC_CANVAS); - - return dataURL; -}; - -module.exports = Scene; +module.exports = Scene diff --git a/src/plots/gl3d/set_convert.js b/src/plots/gl3d/set_convert.js index d19caa8f713..ad7ecd2e0c9 100644 --- a/src/plots/gl3d/set_convert.js +++ b/src/plots/gl3d/set_convert.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Axes = require('../cartesian/axes') -var Lib = require('../../lib'); -var Axes = require('../cartesian/axes'); - - -module.exports = function setConvert(containerOut) { - Axes.setConvert(containerOut); - containerOut.setScale = Lib.noop; -}; +module.exports = function setConvert (containerOut) { + Axes.setConvert(containerOut) + containerOut.setScale = Lib.noop +} diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index c2c033d8b0d..f986c8773d4 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -6,186 +6,186 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../lib'); -var extendFlat = Lib.extendFlat; +var Lib = require('../lib') +var extendFlat = Lib.extendFlat -var fontAttrs = require('./font_attributes'); -var colorAttrs = require('../components/color/attributes'); +var fontAttrs = require('./font_attributes') +var colorAttrs = require('../components/color/attributes') module.exports = { - font: { - family: extendFlat({}, fontAttrs.family, { - dflt: '"Open Sans", verdana, arial, sans-serif' - }), - size: extendFlat({}, fontAttrs.size, { - dflt: 12 - }), - color: extendFlat({}, fontAttrs.color, { - dflt: colorAttrs.defaultLine - }), - description: [ - 'Sets the global font.', - 'Note that fonts used in traces and other', - 'layout components inherit from the global font.' - ].join(' ') - }, - title: { - valType: 'string', - role: 'info', - dflt: 'Click to enter Plot title', - description: [ - 'Sets the plot\'s title.' - ].join(' ') - }, - titlefont: extendFlat({}, fontAttrs, { - description: 'Sets the title font.' + font: { + family: extendFlat({}, fontAttrs.family, { + dflt: '"Open Sans", verdana, arial, sans-serif' + }), + size: extendFlat({}, fontAttrs.size, { + dflt: 12 }), - autosize: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not a layout width or height', - 'that has been left undefined by the user', - 'is initialized on each relayout.', + color: extendFlat({}, fontAttrs.color, { + dflt: colorAttrs.defaultLine + }), + description: [ + 'Sets the global font.', + 'Note that fonts used in traces and other', + 'layout components inherit from the global font.' + ].join(' ') + }, + title: { + valType: 'string', + role: 'info', + dflt: 'Click to enter Plot title', + description: [ + 'Sets the plot\'s title.' + ].join(' ') + }, + titlefont: extendFlat({}, fontAttrs, { + description: 'Sets the title font.' + }), + autosize: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not a layout width or height', + 'that has been left undefined by the user', + 'is initialized on each relayout.', - 'Note that, regardless of this attribute,', - 'an undefined layout width or height', - 'is always initialized on the first call to plot.' - ].join(' ') + 'Note that, regardless of this attribute,', + 'an undefined layout width or height', + 'is always initialized on the first call to plot.' + ].join(' ') + }, + width: { + valType: 'number', + role: 'info', + min: 10, + dflt: 700, + description: [ + 'Sets the plot\'s width (in px).' + ].join(' ') + }, + height: { + valType: 'number', + role: 'info', + min: 10, + dflt: 450, + description: [ + 'Sets the plot\'s height (in px).' + ].join(' ') + }, + margin: { + l: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the left margin (in px).' }, - width: { - valType: 'number', - role: 'info', - min: 10, - dflt: 700, - description: [ - 'Sets the plot\'s width (in px).' - ].join(' ') + r: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the right margin (in px).' }, - height: { - valType: 'number', - role: 'info', - min: 10, - dflt: 450, - description: [ - 'Sets the plot\'s height (in px).' - ].join(' ') + t: { + valType: 'number', + role: 'info', + min: 0, + dflt: 100, + description: 'Sets the top margin (in px).' }, - margin: { - l: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the left margin (in px).' - }, - r: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the right margin (in px).' - }, - t: { - valType: 'number', - role: 'info', - min: 0, - dflt: 100, - description: 'Sets the top margin (in px).' - }, - b: { - valType: 'number', - role: 'info', - min: 0, - dflt: 80, - description: 'Sets the bottom margin (in px).' - }, - pad: { - valType: 'number', - role: 'info', - min: 0, - dflt: 0, - description: [ - 'Sets the amount of padding (in px)', - 'between the plotting area and the axis lines' - ].join(' ') - }, - autoexpand: { - valType: 'boolean', - role: 'info', - dflt: true - } + b: { + valType: 'number', + role: 'info', + min: 0, + dflt: 80, + description: 'Sets the bottom margin (in px).' }, - paper_bgcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.background, - description: 'Sets the color of paper where the graph is drawn.' + pad: { + valType: 'number', + role: 'info', + min: 0, + dflt: 0, + description: [ + 'Sets the amount of padding (in px)', + 'between the plotting area and the axis lines' + ].join(' ') }, - plot_bgcolor: { + autoexpand: { + valType: 'boolean', + role: 'info', + dflt: true + } + }, + paper_bgcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: 'Sets the color of paper where the graph is drawn.' + }, + plot_bgcolor: { // defined here, but set in Axes.supplyLayoutDefaults // because it needs to know if there are (2D) axes or not - valType: 'color', - role: 'style', - dflt: colorAttrs.background, - description: [ - 'Sets the color of plotting area in-between x and y axes.' - ].join(' ') - }, - separators: { - valType: 'string', - role: 'style', - dflt: '.,', - description: [ - 'Sets the decimal and thousand separators.', - 'For example, *. * puts a \'.\' before decimals and', - 'a space between thousands.' - ].join(' ') - }, - hidesources: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not a text link citing the data source is', - 'placed at the bottom-right cored of the figure.', - 'Has only an effect only on graphs that have been generated via', - 'forked graphs from the plotly service (at https://plot.ly or on-premise).' - ].join(' ') - }, - smith: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: [ + 'Sets the color of plotting area in-between x and y axes.' + ].join(' ') + }, + separators: { + valType: 'string', + role: 'style', + dflt: '.,', + description: [ + 'Sets the decimal and thousand separators.', + 'For example, *. * puts a \'.\' before decimals and', + 'a space between thousands.' + ].join(' ') + }, + hidesources: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not a text link citing the data source is', + 'placed at the bottom-right cored of the figure.', + 'Has only an effect only on graphs that have been generated via', + 'forked graphs from the plotly service (at https://plot.ly or on-premise).' + ].join(' ') + }, + smith: { // will become a boolean if/when we implement this - valType: 'enumerated', - role: 'info', - values: [false], - dflt: false - }, - showlegend: { + valType: 'enumerated', + role: 'info', + values: [false], + dflt: false + }, + showlegend: { // handled in legend.supplyLayoutDefaults // but included here because it's not in the legend object - valType: 'boolean', - role: 'info', - description: 'Determines whether or not a legend is drawn.' - }, - dragmode: { - valType: 'enumerated', - role: 'info', - values: ['zoom', 'pan', 'select', 'lasso', 'orbit', 'turntable'], - dflt: 'zoom', - description: [ - 'Determines the mode of drag interactions.', - '*select* and *lasso* apply only to scatter traces with', - 'markers or text. *orbit* and *turntable* apply only to', - '3D scenes.' - ].join(' ') - }, - hovermode: { - valType: 'enumerated', - role: 'info', - values: ['x', 'y', 'closest', false], - description: 'Determines the mode of hover interactions.' - } -}; + valType: 'boolean', + role: 'info', + description: 'Determines whether or not a legend is drawn.' + }, + dragmode: { + valType: 'enumerated', + role: 'info', + values: ['zoom', 'pan', 'select', 'lasso', 'orbit', 'turntable'], + dflt: 'zoom', + description: [ + 'Determines the mode of drag interactions.', + '*select* and *lasso* apply only to scatter traces with', + 'markers or text. *orbit* and *turntable* apply only to', + '3D scenes.' + ].join(' ') + }, + hovermode: { + valType: 'enumerated', + role: 'info', + values: ['x', 'y', 'closest', false], + description: 'Determines the mode of hover interactions.' + } +} diff --git a/src/plots/mapbox/constants.js b/src/plots/mapbox/constants.js index f15fdffaf2c..310275c1cca 100644 --- a/src/plots/mapbox/constants.js +++ b/src/plots/mapbox/constants.js @@ -6,23 +6,21 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { - styleUrlPrefix: 'mapbox://styles/mapbox/', - styleUrlSuffix: 'v9', + styleUrlPrefix: 'mapbox://styles/mapbox/', + styleUrlSuffix: 'v9', - controlContainerClassName: 'mapboxgl-control-container', + controlContainerClassName: 'mapboxgl-control-container', - noAccessTokenErrorMsg: [ - 'Missing Mapbox access token.', - 'Mapbox trace type require a Mapbox access token to be registered.', - 'For example:', - ' Plotly.plot(gd, data, layout, { mapboxAccessToken: \'my-access-token\' });', - 'More info here: https://www.mapbox.com/help/define-access-token/' - ].join('\n'), + noAccessTokenErrorMsg: [ + 'Missing Mapbox access token.', + 'Mapbox trace type require a Mapbox access token to be registered.', + 'For example:', + ' Plotly.plot(gd, data, layout, { mapboxAccessToken: \'my-access-token\' });', + 'More info here: https://www.mapbox.com/help/define-access-token/' + ].join('\n'), - mapOnErrorMsg: 'Mapbox error.' -}; + mapOnErrorMsg: 'Mapbox error.' +} diff --git a/src/plots/mapbox/convert_text_opts.js b/src/plots/mapbox/convert_text_opts.js index dcded05fe04..82f662d0ab9 100644 --- a/src/plots/mapbox/convert_text_opts.js +++ b/src/plots/mapbox/convert_text_opts.js @@ -6,11 +6,9 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - +var Lib = require('../../lib') /** * Convert plotly.js 'textposition' to mapbox-gl 'anchor' and 'offset' @@ -23,50 +21,50 @@ var Lib = require('../../lib'); * - anchor * - offset */ -module.exports = function convertTextOpts(textposition, iconSize) { - var parts = textposition.split(' '), - vPos = parts[0], - hPos = parts[1]; +module.exports = function convertTextOpts (textposition, iconSize) { + var parts = textposition.split(' '), + vPos = parts[0], + hPos = parts[1] // ballpack values - var factor = Array.isArray(iconSize) ? Lib.mean(iconSize) : iconSize, - xInc = 0.5 + (factor / 100), - yInc = 1.5 + (factor / 100); + var factor = Array.isArray(iconSize) ? Lib.mean(iconSize) : iconSize, + xInc = 0.5 + (factor / 100), + yInc = 1.5 + (factor / 100) - var anchorVals = ['', ''], - offset = [0, 0]; + var anchorVals = ['', ''], + offset = [0, 0] - switch(vPos) { - case 'top': - anchorVals[0] = 'top'; - offset[1] = -yInc; - break; - case 'bottom': - anchorVals[0] = 'bottom'; - offset[1] = yInc; - break; - } + switch (vPos) { + case 'top': + anchorVals[0] = 'top' + offset[1] = -yInc + break + case 'bottom': + anchorVals[0] = 'bottom' + offset[1] = yInc + break + } - switch(hPos) { - case 'left': - anchorVals[1] = 'right'; - offset[0] = -xInc; - break; - case 'right': - anchorVals[1] = 'left'; - offset[0] = xInc; - break; - } + switch (hPos) { + case 'left': + anchorVals[1] = 'right' + offset[0] = -xInc + break + case 'right': + anchorVals[1] = 'left' + offset[0] = xInc + break + } // Mapbox text-anchor must be one of: // center, left, right, top, bottom, // top-left, top-right, bottom-left, bottom-right - var anchor; - if(anchorVals[0] && anchorVals[1]) anchor = anchorVals.join('-'); - else if(anchorVals[0]) anchor = anchorVals[0]; - else if(anchorVals[1]) anchor = anchorVals[1]; - else anchor = 'center'; + var anchor + if (anchorVals[0] && anchorVals[1]) anchor = anchorVals.join('-') + else if (anchorVals[0]) anchor = anchorVals[0] + else if (anchorVals[1]) anchor = anchorVals[1] + else anchor = 'center' - return { anchor: anchor, offset: offset }; -}; + return { anchor: anchor, offset: offset } +} diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index 25d5ef7dafc..0a810f603b3 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -6,141 +6,139 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var mapboxgl = require('mapbox-gl') -var mapboxgl = require('mapbox-gl'); +var Plots = require('../plots') +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') -var Plots = require('../plots'); -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); +var createMapbox = require('./mapbox') +var constants = require('./constants') -var createMapbox = require('./mapbox'); -var constants = require('./constants'); +exports.name = 'mapbox' +exports.attr = 'subplot' -exports.name = 'mapbox'; +exports.idRoot = 'mapbox' -exports.attr = 'subplot'; +exports.idRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/ -exports.idRoot = 'mapbox'; - -exports.idRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/; - -exports.attrRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/; +exports.attrRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/ exports.attributes = { - subplot: { - valType: 'subplotid', - role: 'info', - dflt: 'mapbox', - description: [ - 'Sets a reference between this trace\'s data coordinates and', - 'a mapbox subplot.', - 'If *mapbox* (the default value), the data refer to `layout.mapbox`.', - 'If *mapbox2*, the data refer to `layout.mapbox2`, and so on.' - ].join(' ') - } -}; + subplot: { + valType: 'subplotid', + role: 'info', + dflt: 'mapbox', + description: [ + 'Sets a reference between this trace\'s data coordinates and', + 'a mapbox subplot.', + 'If *mapbox* (the default value), the data refer to `layout.mapbox`.', + 'If *mapbox2*, the data refer to `layout.mapbox2`, and so on.' + ].join(' ') + } +} -exports.layoutAttributes = require('./layout_attributes'); +exports.layoutAttributes = require('./layout_attributes') -exports.supplyLayoutDefaults = require('./layout_defaults'); +exports.supplyLayoutDefaults = require('./layout_defaults') -exports.plot = function plotMapbox(gd) { - var fullLayout = gd._fullLayout, - calcData = gd.calcdata, - mapboxIds = Plots.getSubplotIds(fullLayout, 'mapbox'); +exports.plot = function plotMapbox (gd) { + var fullLayout = gd._fullLayout, + calcData = gd.calcdata, + mapboxIds = Plots.getSubplotIds(fullLayout, 'mapbox') - var accessToken = findAccessToken(gd, mapboxIds); - mapboxgl.accessToken = accessToken; + var accessToken = findAccessToken(gd, mapboxIds) + mapboxgl.accessToken = accessToken - for(var i = 0; i < mapboxIds.length; i++) { - var id = mapboxIds[i], - subplotCalcData = Plots.getSubplotCalcData(calcData, 'mapbox', id), - opts = fullLayout[id], - mapbox = opts._subplot; + for (var i = 0; i < mapboxIds.length; i++) { + var id = mapboxIds[i], + subplotCalcData = Plots.getSubplotCalcData(calcData, 'mapbox', id), + opts = fullLayout[id], + mapbox = opts._subplot // copy access token to fullLayout (to handle the context case) - opts.accesstoken = accessToken; - - if(!mapbox) { - mapbox = createMapbox({ - gd: gd, - container: fullLayout._glcontainer.node(), - id: id, - fullLayout: fullLayout, - staticPlot: gd._context.staticPlot - }); - - fullLayout[id]._subplot = mapbox; - } - - mapbox.plot(subplotCalcData, fullLayout, gd._promises); + opts.accesstoken = accessToken + + if (!mapbox) { + mapbox = createMapbox({ + gd: gd, + container: fullLayout._glcontainer.node(), + id: id, + fullLayout: fullLayout, + staticPlot: gd._context.staticPlot + }) + + fullLayout[id]._subplot = mapbox } -}; -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldMapboxKeys = Plots.getSubplotIds(oldFullLayout, 'mapbox'); + mapbox.plot(subplotCalcData, fullLayout, gd._promises) + } +} - for(var i = 0; i < oldMapboxKeys.length; i++) { - var oldMapboxKey = oldMapboxKeys[i]; +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldMapboxKeys = Plots.getSubplotIds(oldFullLayout, 'mapbox') - if(!newFullLayout[oldMapboxKey] && !!oldFullLayout[oldMapboxKey]._subplot) { - oldFullLayout[oldMapboxKey]._subplot.destroy(); - } - } -}; - -exports.toSVG = function(gd) { - var fullLayout = gd._fullLayout, - subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'), - size = fullLayout._size; - - for(var i = 0; i < subplotIds.length; i++) { - var opts = fullLayout[subplotIds[i]], - domain = opts.domain, - mapbox = opts._subplot; - - var imageData = mapbox.toImage('png'); - var image = fullLayout._glimages.append('svg:image'); - - image.attr({ - xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData, - x: size.l + size.w * domain.x[0], - y: size.t + size.h * (1 - domain.y[1]), - width: size.w * (domain.x[1] - domain.x[0]), - height: size.h * (domain.y[1] - domain.y[0]), - preserveAspectRatio: 'none' - }); - - mapbox.destroy(); + for (var i = 0; i < oldMapboxKeys.length; i++) { + var oldMapboxKey = oldMapboxKeys[i] + + if (!newFullLayout[oldMapboxKey] && !!oldFullLayout[oldMapboxKey]._subplot) { + oldFullLayout[oldMapboxKey]._subplot.destroy() } -}; + } +} -function findAccessToken(gd, mapboxIds) { - var fullLayout = gd._fullLayout, - context = gd._context; +exports.toSVG = function (gd) { + var fullLayout = gd._fullLayout, + subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'), + size = fullLayout._size + + for (var i = 0; i < subplotIds.length; i++) { + var opts = fullLayout[subplotIds[i]], + domain = opts.domain, + mapbox = opts._subplot + + var imageData = mapbox.toImage('png') + var image = fullLayout._glimages.append('svg:image') + + image.attr({ + xmlns: xmlnsNamespaces.svg, + 'xlink:href': imageData, + x: size.l + size.w * domain.x[0], + y: size.t + size.h * (1 - domain.y[1]), + width: size.w * (domain.x[1] - domain.x[0]), + height: size.h * (domain.y[1] - domain.y[0]), + preserveAspectRatio: 'none' + }) + + mapbox.destroy() + } +} + +function findAccessToken (gd, mapboxIds) { + var fullLayout = gd._fullLayout, + context = gd._context // special case for Mapbox Atlas users - if(context.mapboxAccessToken === '') return ''; + if (context.mapboxAccessToken === '') return '' // first look for access token in context - var accessToken = context.mapboxAccessToken; + var accessToken = context.mapboxAccessToken // allow mapbox layout options to override it - for(var i = 0; i < mapboxIds.length; i++) { - var opts = fullLayout[mapboxIds[i]]; + for (var i = 0; i < mapboxIds.length; i++) { + var opts = fullLayout[mapboxIds[i]] - if(opts.accesstoken) { - accessToken = opts.accesstoken; - break; - } + if (opts.accesstoken) { + accessToken = opts.accesstoken + break } + } - if(!accessToken) { - throw new Error(constants.noAccessTokenErrorMsg); - } + if (!accessToken) { + throw new Error(constants.noAccessTokenErrorMsg) + } - return accessToken; + return accessToken } diff --git a/src/plots/mapbox/layers.js b/src/plots/mapbox/layers.js index d964ad39973..340f4725d41 100644 --- a/src/plots/mapbox/layers.js +++ b/src/plots/mapbox/layers.js @@ -6,218 +6,211 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var convertTextOpts = require('./convert_text_opts') -var Lib = require('../../lib'); -var convertTextOpts = require('./convert_text_opts'); +function MapboxLayer (mapbox, index) { + this.mapbox = mapbox + this.map = mapbox.map + this.uid = mapbox.uid + '-' + 'layer' + index -function MapboxLayer(mapbox, index) { - this.mapbox = mapbox; - this.map = mapbox.map; - - this.uid = mapbox.uid + '-' + 'layer' + index; - - this.idSource = this.uid + '-source'; - this.idLayer = this.uid + '-layer'; + this.idSource = this.uid + '-source' + this.idLayer = this.uid + '-layer' // some state variable to check if a remove/add step is needed - this.sourceType = null; - this.source = null; - this.layerType = null; - this.below = null; + this.sourceType = null + this.source = null + this.layerType = null + this.below = null // is layer currently visible - this.visible = false; + this.visible = false } -var proto = MapboxLayer.prototype; - -proto.update = function update(opts) { - if(!this.visible) { +var proto = MapboxLayer.prototype +proto.update = function update (opts) { + if (!this.visible) { // IMPORTANT: must create source before layer to not cause errors - this.updateSource(opts); - this.updateLayer(opts); - } - else if(this.needsNewSource(opts)) { - + this.updateSource(opts) + this.updateLayer(opts) + } else if (this.needsNewSource(opts)) { // IMPORTANT: must delete layer before source to not cause errors - this.updateLayer(opts); - this.updateSource(opts); - } - else if(this.needsNewLayer(opts)) { - this.updateLayer(opts); - } + this.updateLayer(opts) + this.updateSource(opts) + } else if (this.needsNewLayer(opts)) { + this.updateLayer(opts) + } - this.updateStyle(opts); + this.updateStyle(opts) - this.visible = isVisible(opts); -}; - -proto.needsNewSource = function(opts) { + this.visible = isVisible(opts) +} +proto.needsNewSource = function (opts) { // for some reason changing layer to 'fill' or 'symbol' // w/o changing the source throws an exception in mapbox-gl 0.18 ; // stay safe and make new source on type changes - return ( + return ( this.sourceType !== opts.sourcetype || this.source !== opts.source || this.layerType !== opts.type - ); -}; + ) +} -proto.needsNewLayer = function(opts) { - return ( +proto.needsNewLayer = function (opts) { + return ( this.layerType !== opts.type || this.below !== opts.below - ); -}; + ) +} -proto.updateSource = function(opts) { - var map = this.map; +proto.updateSource = function (opts) { + var map = this.map - if(map.getSource(this.idSource)) map.removeSource(this.idSource); + if (map.getSource(this.idSource)) map.removeSource(this.idSource) - this.sourceType = opts.sourcetype; - this.source = opts.source; + this.sourceType = opts.sourcetype + this.source = opts.source - if(!isVisible(opts)) return; + if (!isVisible(opts)) return - var sourceOpts = convertSourceOpts(opts); + var sourceOpts = convertSourceOpts(opts) - map.addSource(this.idSource, sourceOpts); -}; + map.addSource(this.idSource, sourceOpts) +} -proto.updateLayer = function(opts) { - var map = this.map; +proto.updateLayer = function (opts) { + var map = this.map - if(map.getLayer(this.idLayer)) map.removeLayer(this.idLayer); + if (map.getLayer(this.idLayer)) map.removeLayer(this.idLayer) - this.layerType = opts.type; + this.layerType = opts.type - if(!isVisible(opts)) return; + if (!isVisible(opts)) return - map.addLayer({ - id: this.idLayer, - source: this.idSource, - 'source-layer': opts.sourcelayer || '', - type: opts.type - }, opts.below); + map.addLayer({ + id: this.idLayer, + source: this.idSource, + 'source-layer': opts.sourcelayer || '', + type: opts.type + }, opts.below) // the only way to make a layer invisible is to remove it - var layoutOpts = { visibility: 'visible' }; - this.mapbox.setOptions(this.idLayer, 'setLayoutProperty', layoutOpts); -}; + var layoutOpts = { visibility: 'visible' } + this.mapbox.setOptions(this.idLayer, 'setLayoutProperty', layoutOpts) +} -proto.updateStyle = function(opts) { - var convertedOpts = convertOpts(opts); +proto.updateStyle = function (opts) { + var convertedOpts = convertOpts(opts) - if(isVisible(opts)) { - this.mapbox.setOptions(this.idLayer, 'setLayoutProperty', convertedOpts.layout); - this.mapbox.setOptions(this.idLayer, 'setPaintProperty', convertedOpts.paint); - } -}; + if (isVisible(opts)) { + this.mapbox.setOptions(this.idLayer, 'setLayoutProperty', convertedOpts.layout) + this.mapbox.setOptions(this.idLayer, 'setPaintProperty', convertedOpts.paint) + } +} -proto.dispose = function dispose() { - var map = this.map; +proto.dispose = function dispose () { + var map = this.map - map.removeLayer(this.idLayer); - map.removeSource(this.idSource); -}; + map.removeLayer(this.idLayer) + map.removeSource(this.idSource) +} -function isVisible(opts) { - var source = opts.source; +function isVisible (opts) { + var source = opts.source - return ( + return ( Lib.isPlainObject(source) || (typeof source === 'string' && source.length > 0) - ); + ) } -function convertOpts(opts) { - var layout = {}, - paint = {}; - - switch(opts.type) { - - case 'circle': - Lib.extendFlat(paint, { - 'circle-radius': opts.circle.radius, - 'circle-color': opts.color, - 'circle-opacity': opts.opacity - }); - break; - - case 'line': - Lib.extendFlat(paint, { - 'line-width': opts.line.width, - 'line-color': opts.color, - 'line-opacity': opts.opacity - }); - break; - - case 'fill': - Lib.extendFlat(paint, { - 'fill-color': opts.color, - 'fill-outline-color': opts.fill.outlinecolor, - 'fill-opacity': opts.opacity +function convertOpts (opts) { + var layout = {}, + paint = {} + + switch (opts.type) { + + case 'circle': + Lib.extendFlat(paint, { + 'circle-radius': opts.circle.radius, + 'circle-color': opts.color, + 'circle-opacity': opts.opacity + }) + break + + case 'line': + Lib.extendFlat(paint, { + 'line-width': opts.line.width, + 'line-color': opts.color, + 'line-opacity': opts.opacity + }) + break + + case 'fill': + Lib.extendFlat(paint, { + 'fill-color': opts.color, + 'fill-outline-color': opts.fill.outlinecolor, + 'fill-opacity': opts.opacity // no way to pass specify outline width at the moment - }); - break; + }) + break - case 'symbol': - var symbol = opts.symbol, - textOpts = convertTextOpts(symbol.textposition, symbol.iconsize); + case 'symbol': + var symbol = opts.symbol, + textOpts = convertTextOpts(symbol.textposition, symbol.iconsize) - Lib.extendFlat(layout, { - 'icon-image': symbol.icon + '-15', - 'icon-size': symbol.iconsize / 10, + Lib.extendFlat(layout, { + 'icon-image': symbol.icon + '-15', + 'icon-size': symbol.iconsize / 10, - 'text-field': symbol.text, - 'text-size': symbol.textfont.size, - 'text-anchor': textOpts.anchor, - 'text-offset': textOpts.offset + 'text-field': symbol.text, + 'text-size': symbol.textfont.size, + 'text-anchor': textOpts.anchor, + 'text-offset': textOpts.offset // TODO font family // 'text-font': symbol.textfont.family.split(', '), - }); + }) - Lib.extendFlat(paint, { - 'icon-color': opts.color, - 'text-color': symbol.textfont.color, - 'text-opacity': opts.opacity - }); - break; - } + Lib.extendFlat(paint, { + 'icon-color': opts.color, + 'text-color': symbol.textfont.color, + 'text-opacity': opts.opacity + }) + break + } - return { layout: layout, paint: paint }; + return { layout: layout, paint: paint } } -function convertSourceOpts(opts) { - var sourceType = opts.sourcetype, - source = opts.source, - sourceOpts = { type: sourceType }, - isSourceAString = (typeof source === 'string'), - field; +function convertSourceOpts (opts) { + var sourceType = opts.sourcetype, + source = opts.source, + sourceOpts = { type: sourceType }, + isSourceAString = (typeof source === 'string'), + field - if(sourceType === 'geojson') field = 'data'; - else if(sourceType === 'vector') { - field = isSourceAString ? 'url' : 'tiles'; - } + if (sourceType === 'geojson') field = 'data' + else if (sourceType === 'vector') { + field = isSourceAString ? 'url' : 'tiles' + } - sourceOpts[field] = source; + sourceOpts[field] = source - return sourceOpts; + return sourceOpts } -module.exports = function createMapboxLayer(mapbox, index, opts) { - var mapboxLayer = new MapboxLayer(mapbox, index); +module.exports = function createMapboxLayer (mapbox, index, opts) { + var mapboxLayer = new MapboxLayer(mapbox, index) - mapboxLayer.update(opts); + mapboxLayer.update(opts) - return mapboxLayer; -}; + return mapboxLayer +} diff --git a/src/plots/mapbox/layout_attributes.js b/src/plots/mapbox/layout_attributes.js index 4df5146acde..91bfde8af32 100644 --- a/src/plots/mapbox/layout_attributes.js +++ b/src/plots/mapbox/layout_attributes.js @@ -6,260 +6,258 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); -var defaultLine = require('../../components/color').defaultLine; -var fontAttrs = require('../font_attributes'); -var textposition = require('../../traces/scatter/attributes').textposition; - +var Lib = require('../../lib') +var defaultLine = require('../../components/color').defaultLine +var fontAttrs = require('../font_attributes') +var textposition = require('../../traces/scatter/attributes').textposition module.exports = { - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this subplot', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this subplot', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this subplot', - '(in plot fraction).' - ].join(' ') - } - }, + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this subplot', + '(in plot fraction).' + ].join(' ') + } + }, - accesstoken: { - valType: 'string', - noBlank: true, - strict: true, - role: 'info', - description: [ - 'Sets the mapbox access token to be used for this mapbox map.', - 'Alternatively, the mapbox access token can be set in the', - 'configuration options under `mapboxAccessToken`.' - ].join(' ') - }, - style: { - valType: 'any', - values: ['basic', 'streets', 'outdoors', 'light', 'dark', 'satellite', 'satellite-streets'], - dflt: 'basic', - role: 'style', - description: [ - 'Sets the Mapbox map style.', - 'Either input one of the default Mapbox style names or the URL to a custom style', - 'or a valid Mapbox style JSON.' - ].join(' ') - }, + accesstoken: { + valType: 'string', + noBlank: true, + strict: true, + role: 'info', + description: [ + 'Sets the mapbox access token to be used for this mapbox map.', + 'Alternatively, the mapbox access token can be set in the', + 'configuration options under `mapboxAccessToken`.' + ].join(' ') + }, + style: { + valType: 'any', + values: ['basic', 'streets', 'outdoors', 'light', 'dark', 'satellite', 'satellite-streets'], + dflt: 'basic', + role: 'style', + description: [ + 'Sets the Mapbox map style.', + 'Either input one of the default Mapbox style names or the URL to a custom style', + 'or a valid Mapbox style JSON.' + ].join(' ') + }, - center: { - lon: { - valType: 'number', - dflt: 0, - role: 'info', - description: 'Sets the longitude of the center of the map (in degrees East).' - }, - lat: { - valType: 'number', - dflt: 0, - role: 'info', - description: 'Sets the latitude of the center of the map (in degrees North).' - } - }, - zoom: { - valType: 'number', - dflt: 1, - role: 'info', - description: 'Sets the zoom level of the map.' - }, - bearing: { - valType: 'number', - dflt: 0, - role: 'info', - description: 'Sets the bearing angle of the map (in degrees counter-clockwise from North).' - }, - pitch: { - valType: 'number', - dflt: 0, - role: 'info', - description: [ - 'Sets the pitch angle of the map', - '(in degrees, where *0* means perpendicular to the surface of the map).' - ].join(' ') + center: { + lon: { + valType: 'number', + dflt: 0, + role: 'info', + description: 'Sets the longitude of the center of the map (in degrees East).' }, + lat: { + valType: 'number', + dflt: 0, + role: 'info', + description: 'Sets the latitude of the center of the map (in degrees North).' + } + }, + zoom: { + valType: 'number', + dflt: 1, + role: 'info', + description: 'Sets the zoom level of the map.' + }, + bearing: { + valType: 'number', + dflt: 0, + role: 'info', + description: 'Sets the bearing angle of the map (in degrees counter-clockwise from North).' + }, + pitch: { + valType: 'number', + dflt: 0, + role: 'info', + description: [ + 'Sets the pitch angle of the map', + '(in degrees, where *0* means perpendicular to the surface of the map).' + ].join(' ') + }, - layers: { - _isLinkedToArray: 'layer', + layers: { + _isLinkedToArray: 'layer', - sourcetype: { - valType: 'enumerated', - values: ['geojson', 'vector'], - dflt: 'geojson', - role: 'info', - description: [ - 'Sets the source type for this layer.', - 'Support for *raster*, *image* and *video* source types is coming soon.' - ].join(' ') - }, + sourcetype: { + valType: 'enumerated', + values: ['geojson', 'vector'], + dflt: 'geojson', + role: 'info', + description: [ + 'Sets the source type for this layer.', + 'Support for *raster*, *image* and *video* source types is coming soon.' + ].join(' ') + }, - source: { - valType: 'any', - role: 'info', - description: [ - 'Sets the source data for this layer.', - 'Source can be either a URL,', - 'a geojson object (with `sourcetype` set to *geojson*)', - 'or an array of tile URLS (with `sourcetype` set to *vector*).' - ].join(' ') - }, + source: { + valType: 'any', + role: 'info', + description: [ + 'Sets the source data for this layer.', + 'Source can be either a URL,', + 'a geojson object (with `sourcetype` set to *geojson*)', + 'or an array of tile URLS (with `sourcetype` set to *vector*).' + ].join(' ') + }, - sourcelayer: { - valType: 'string', - dflt: '', - role: 'info', - description: [ - 'Specifies the layer to use from a vector tile source.', - 'Required for *vector* source type that supports multiple layers.' - ].join(' ') - }, + sourcelayer: { + valType: 'string', + dflt: '', + role: 'info', + description: [ + 'Specifies the layer to use from a vector tile source.', + 'Required for *vector* source type that supports multiple layers.' + ].join(' ') + }, - type: { - valType: 'enumerated', - values: ['circle', 'line', 'fill', 'symbol'], - dflt: 'circle', - role: 'info', - description: [ - 'Sets the layer type.', - 'Support for *raster*, *background* types is coming soon.', - 'Note that *line* and *fill* are not compatible with Point', - 'GeoJSON geometries.' - ].join(' ') - }, + type: { + valType: 'enumerated', + values: ['circle', 'line', 'fill', 'symbol'], + dflt: 'circle', + role: 'info', + description: [ + 'Sets the layer type.', + 'Support for *raster*, *background* types is coming soon.', + 'Note that *line* and *fill* are not compatible with Point', + 'GeoJSON geometries.' + ].join(' ') + }, // attributes shared between all types - below: { - valType: 'string', - dflt: '', - role: 'info', - description: [ - 'Determines if the layer will be inserted', - 'before the layer with the specified ID.', - 'If omitted or set to \'\',', - 'the layer will be inserted above every existing layer.' - ].join(' ') - }, - color: { - valType: 'color', - dflt: defaultLine, - role: 'style', - description: [ - 'Sets the primary layer color.', - 'If `type` is *circle*, color corresponds to the circle color', - 'If `type` is *line*, color corresponds to the line color', - 'If `type` is *fill*, color corresponds to the fill color', - 'If `type` is *symbol*, color corresponds to the icon color' - ].join(' ') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - role: 'info', - description: 'Sets the opacity of the layer.' - }, + below: { + valType: 'string', + dflt: '', + role: 'info', + description: [ + 'Determines if the layer will be inserted', + 'before the layer with the specified ID.', + 'If omitted or set to \'\',', + 'the layer will be inserted above every existing layer.' + ].join(' ') + }, + color: { + valType: 'color', + dflt: defaultLine, + role: 'style', + description: [ + 'Sets the primary layer color.', + 'If `type` is *circle*, color corresponds to the circle color', + 'If `type` is *line*, color corresponds to the line color', + 'If `type` is *fill*, color corresponds to the fill color', + 'If `type` is *symbol*, color corresponds to the icon color' + ].join(' ') + }, + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + role: 'info', + description: 'Sets the opacity of the layer.' + }, // type-specific style attributes - circle: { - radius: { - valType: 'number', - dflt: 15, - role: 'style', - description: [ - 'Sets the circle radius.', - 'Has an effect only when `type` is set to *circle*.' - ].join(' ') - } - }, + circle: { + radius: { + valType: 'number', + dflt: 15, + role: 'style', + description: [ + 'Sets the circle radius.', + 'Has an effect only when `type` is set to *circle*.' + ].join(' ') + } + }, - line: { - width: { - valType: 'number', - dflt: 2, - role: 'style', - description: [ - 'Sets the line width.', - 'Has an effect only when `type` is set to *line*.' - ].join(' ') - } - }, + line: { + width: { + valType: 'number', + dflt: 2, + role: 'style', + description: [ + 'Sets the line width.', + 'Has an effect only when `type` is set to *line*.' + ].join(' ') + } + }, - fill: { - outlinecolor: { - valType: 'color', - dflt: defaultLine, - role: 'style', - description: [ - 'Sets the fill outline color.', - 'Has an effect only when `type` is set to *fill*.' - ].join(' ') - } - }, + fill: { + outlinecolor: { + valType: 'color', + dflt: defaultLine, + role: 'style', + description: [ + 'Sets the fill outline color.', + 'Has an effect only when `type` is set to *fill*.' + ].join(' ') + } + }, - symbol: { - icon: { - valType: 'string', - dflt: 'marker', - role: 'style', - description: [ - 'Sets the symbol icon image.', - 'Full list: https://www.mapbox.com/maki-icons/' - ].join(' ') - }, - iconsize: { - valType: 'number', - dflt: 10, - role: 'style', - description: [ - 'Sets the symbol icon size.', - 'Has an effect only when `type` is set to *symbol*.' - ].join(' ') - }, - text: { - valType: 'string', - dflt: '', - role: 'info', - description: [ - 'Sets the symbol text.' - ].join(' ') - }, - textfont: Lib.extendDeep({}, fontAttrs, { - description: [ - 'Sets the icon text font.', - 'Has an effect only when `type` is set to *symbol*.' - ].join(' '), - family: { - dflt: 'Open Sans Regular, Arial Unicode MS Regular' - } - }), - textposition: Lib.extendFlat({}, textposition, { arrayOk: false }) + symbol: { + icon: { + valType: 'string', + dflt: 'marker', + role: 'style', + description: [ + 'Sets the symbol icon image.', + 'Full list: https://www.mapbox.com/maki-icons/' + ].join(' ') + }, + iconsize: { + valType: 'number', + dflt: 10, + role: 'style', + description: [ + 'Sets the symbol icon size.', + 'Has an effect only when `type` is set to *symbol*.' + ].join(' ') + }, + text: { + valType: 'string', + dflt: '', + role: 'info', + description: [ + 'Sets the symbol text.' + ].join(' ') + }, + textfont: Lib.extendDeep({}, fontAttrs, { + description: [ + 'Sets the icon text font.', + 'Has an effect only when `type` is set to *symbol*.' + ].join(' '), + family: { + dflt: 'Open Sans Regular, Arial Unicode MS Regular' } + }), + textposition: Lib.extendFlat({}, textposition, { arrayOk: false }) } + } -}; +} diff --git a/src/plots/mapbox/layout_defaults.js b/src/plots/mapbox/layout_defaults.js index 911278dec23..3dee5fa0eb9 100644 --- a/src/plots/mapbox/layout_defaults.js +++ b/src/plots/mapbox/layout_defaults.js @@ -6,89 +6,87 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var handleSubplotDefaults = require('../subplot_defaults') +var layoutAttributes = require('./layout_attributes') -var handleSubplotDefaults = require('../subplot_defaults'); -var layoutAttributes = require('./layout_attributes'); - - -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - handleSubplotDefaults(layoutIn, layoutOut, fullData, { - type: 'mapbox', - attributes: layoutAttributes, - handleDefaults: handleDefaults, - partition: 'y' - }); -}; +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'mapbox', + attributes: layoutAttributes, + handleDefaults: handleDefaults, + partition: 'y' + }) +} -function handleDefaults(containerIn, containerOut, coerce) { - coerce('accesstoken'); - coerce('style'); - coerce('center.lon'); - coerce('center.lat'); - coerce('zoom'); - coerce('bearing'); - coerce('pitch'); +function handleDefaults (containerIn, containerOut, coerce) { + coerce('accesstoken') + coerce('style') + coerce('center.lon') + coerce('center.lat') + coerce('zoom') + coerce('bearing') + coerce('pitch') - handleLayerDefaults(containerIn, containerOut); + handleLayerDefaults(containerIn, containerOut) // copy ref to input container to update 'center' and 'zoom' on map move - containerOut._input = containerIn; + containerOut._input = containerIn } -function handleLayerDefaults(containerIn, containerOut) { - var layersIn = containerIn.layers || [], - layersOut = containerOut.layers = []; +function handleLayerDefaults (containerIn, containerOut) { + var layersIn = containerIn.layers || [], + layersOut = containerOut.layers = [] - var layerIn, layerOut; + var layerIn, layerOut - function coerce(attr, dflt) { - return Lib.coerce(layerIn, layerOut, layoutAttributes.layers, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(layerIn, layerOut, layoutAttributes.layers, attr, dflt) + } - for(var i = 0; i < layersIn.length; i++) { - layerIn = layersIn[i]; - layerOut = {}; + for (var i = 0; i < layersIn.length; i++) { + layerIn = layersIn[i] + layerOut = {} - if(!Lib.isPlainObject(layerIn)) continue; + if (!Lib.isPlainObject(layerIn)) continue - var sourceType = coerce('sourcetype'); - coerce('source'); + var sourceType = coerce('sourcetype') + coerce('source') - if(sourceType === 'vector') coerce('sourcelayer'); + if (sourceType === 'vector') coerce('sourcelayer') // maybe add smart default based off GeoJSON geometry? - var type = coerce('type'); - - coerce('below'); - coerce('color'); - coerce('opacity'); + var type = coerce('type') - if(type === 'circle') { - coerce('circle.radius'); - } + coerce('below') + coerce('color') + coerce('opacity') - if(type === 'line') { - coerce('line.width'); - } + if (type === 'circle') { + coerce('circle.radius') + } - if(type === 'fill') { - coerce('fill.outlinecolor'); - } + if (type === 'line') { + coerce('line.width') + } - if(type === 'symbol') { - coerce('symbol.icon'); - coerce('symbol.iconsize'); + if (type === 'fill') { + coerce('fill.outlinecolor') + } - coerce('symbol.text'); - Lib.coerceFont(coerce, 'symbol.textfont'); - coerce('symbol.textposition'); - } + if (type === 'symbol') { + coerce('symbol.icon') + coerce('symbol.iconsize') - layerOut._index = i; - layersOut.push(layerOut); + coerce('symbol.text') + Lib.coerceFont(coerce, 'symbol.textfont') + coerce('symbol.textposition') } + + layerOut._index = i + layersOut.push(layerOut) + } } diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 522cf66b8a6..23b583bc4f4 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -6,134 +6,131 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var mapboxgl = require('mapbox-gl') -var mapboxgl = require('mapbox-gl'); +var Fx = require('../cartesian/graph_interact') +var Lib = require('../../lib') +var constants = require('./constants') +var layoutAttributes = require('./layout_attributes') +var createMapboxLayer = require('./layers') -var Fx = require('../cartesian/graph_interact'); -var Lib = require('../../lib'); -var constants = require('./constants'); -var layoutAttributes = require('./layout_attributes'); -var createMapboxLayer = require('./layers'); +function Mapbox (opts) { + this.id = opts.id + this.gd = opts.gd + this.container = opts.container + this.isStatic = opts.staticPlot - -function Mapbox(opts) { - this.id = opts.id; - this.gd = opts.gd; - this.container = opts.container; - this.isStatic = opts.staticPlot; - - var fullLayout = opts.fullLayout; + var fullLayout = opts.fullLayout // unique id for this Mapbox instance - this.uid = fullLayout._uid + '-' + this.id; + this.uid = fullLayout._uid + '-' + this.id // full mapbox options (N.B. needs to be updated on every updates) - this.opts = fullLayout[this.id]; + this.opts = fullLayout[this.id] // create framework on instantiation for a smoother first plot call - this.div = null; - this.xaxis = null; - this.yaxis = null; - this.createFramework(fullLayout); + this.div = null + this.xaxis = null + this.yaxis = null + this.createFramework(fullLayout) // state variables used to infer how and what to update - this.map = null; - this.accessToken = null; - this.styleObj = null; - this.traceHash = {}; - this.layerList = []; + this.map = null + this.accessToken = null + this.styleObj = null + this.traceHash = {} + this.layerList = [] } -var proto = Mapbox.prototype; +var proto = Mapbox.prototype -module.exports = function createMapbox(opts) { - var mapbox = new Mapbox(opts); +module.exports = function createMapbox (opts) { + var mapbox = new Mapbox(opts) - return mapbox; -}; + return mapbox +} -proto.plot = function(calcData, fullLayout, promises) { - var self = this; +proto.plot = function (calcData, fullLayout, promises) { + var self = this // feed in new mapbox options - var opts = self.opts = fullLayout[this.id]; + var opts = self.opts = fullLayout[this.id] // remove map and create a new map if access token has change - if(self.map && (opts.accesstoken !== self.accessToken)) { - self.map.remove(); - self.map = null; - self.styleObj = null; - self.traceHash = []; - self.layerList = {}; - } - - var promise; - - if(!self.map) { - promise = new Promise(function(resolve, reject) { - self.createMap(calcData, fullLayout, resolve, reject); - }); - } - else { - promise = new Promise(function(resolve, reject) { - self.updateMap(calcData, fullLayout, resolve, reject); - }); - } - - promises.push(promise); -}; + if (self.map && (opts.accesstoken !== self.accessToken)) { + self.map.remove() + self.map = null + self.styleObj = null + self.traceHash = [] + self.layerList = {} + } + + var promise + + if (!self.map) { + promise = new Promise(function (resolve, reject) { + self.createMap(calcData, fullLayout, resolve, reject) + }) + } else { + promise = new Promise(function (resolve, reject) { + self.updateMap(calcData, fullLayout, resolve, reject) + }) + } + + promises.push(promise) +} -proto.createMap = function(calcData, fullLayout, resolve, reject) { - var self = this, - gd = self.gd, - opts = self.opts; +proto.createMap = function (calcData, fullLayout, resolve, reject) { + var self = this, + gd = self.gd, + opts = self.opts // store style id and URL or object - var styleObj = self.styleObj = getStyleObj(opts.style); + var styleObj = self.styleObj = getStyleObj(opts.style) // store access token associated with this map - self.accessToken = opts.accesstoken; + self.accessToken = opts.accesstoken // create the map! - var map = self.map = new mapboxgl.Map({ - container: self.div, + var map = self.map = new mapboxgl.Map({ + container: self.div, - style: styleObj.style, - center: convertCenter(opts.center), - zoom: opts.zoom, - bearing: opts.bearing, - pitch: opts.pitch, + style: styleObj.style, + center: convertCenter(opts.center), + zoom: opts.zoom, + bearing: opts.bearing, + pitch: opts.pitch, - interactive: !self.isStatic, - preserveDrawingBuffer: self.isStatic - }); + interactive: !self.isStatic, + preserveDrawingBuffer: self.isStatic + }) // clear navigation container - var className = constants.controlContainerClassName, - controlContainer = self.div.getElementsByClassName(className)[0]; - self.div.removeChild(controlContainer); + var className = constants.controlContainerClassName, + controlContainer = self.div.getElementsByClassName(className)[0] + self.div.removeChild(controlContainer) - self.rejectOnError(reject); + self.rejectOnError(reject) - map.once('load', function() { - self.updateData(calcData); - self.updateLayout(fullLayout); + map.once('load', function () { + self.updateData(calcData) + self.updateLayout(fullLayout) - self.resolveOnRender(resolve); - }); + self.resolveOnRender(resolve) + }) // keep track of pan / zoom in user layout and emit relayout event - map.on('moveend', function(eventData) { - if(!self.map) return; + map.on('moveend', function (eventData) { + if (!self.map) return - var view = self.getView(); + var view = self.getView() - opts._input.center = opts.center = view.center; - opts._input.zoom = opts.zoom = view.zoom; - opts._input.bearing = opts.bearing = view.bearing; - opts._input.pitch = opts.pitch = view.pitch; + opts._input.center = opts.center = view.center + opts._input.zoom = opts.zoom = view.zoom + opts._input.bearing = opts.bearing = view.bearing + opts._input.pitch = opts.pitch = view.pitch // 'moveend' gets triggered by map.setCenter, map.setZoom, // map.setBearing and map.setPitch. @@ -143,317 +140,311 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { // mouse target (filtering out API calls) to not // duplicate 'plotly_relayout' events. - if(eventData.originalEvent) { - var update = {}; - update[self.id] = Lib.extendFlat({}, view); - gd.emit('plotly_relayout', update); - } - }); + if (eventData.originalEvent) { + var update = {} + update[self.id] = Lib.extendFlat({}, view) + gd.emit('plotly_relayout', update) + } + }) - map.on('mousemove', function(evt) { - var bb = self.div.getBoundingClientRect(); + map.on('mousemove', function (evt) { + var bb = self.div.getBoundingClientRect() // some hackery to get Fx.hover to work - evt.clientX = evt.point.x + bb.left; - evt.clientY = evt.point.y + bb.top; - - evt.target.getBoundingClientRect = function() { return bb; }; + evt.clientX = evt.point.x + bb.left + evt.clientY = evt.point.y + bb.top - self.xaxis.p2c = function() { return evt.lngLat.lng; }; - self.yaxis.p2c = function() { return evt.lngLat.lat; }; + evt.target.getBoundingClientRect = function () { return bb } - Fx.hover(gd, evt, self.id); - }); + self.xaxis.p2c = function () { return evt.lngLat.lng } + self.yaxis.p2c = function () { return evt.lngLat.lat } - map.on('click', function() { - Fx.click(gd, { target: true }); - }); + Fx.hover(gd, evt, self.id) + }) - function unhover() { - Fx.loneUnhover(fullLayout._toppaper); - } - - map.on('dragstart', unhover); - map.on('zoomstart', unhover); + map.on('click', function () { + Fx.click(gd, { target: true }) + }) -}; + function unhover () { + Fx.loneUnhover(fullLayout._toppaper) + } -proto.updateMap = function(calcData, fullLayout, resolve, reject) { - var self = this, - map = self.map; + map.on('dragstart', unhover) + map.on('zoomstart', unhover) +} - self.rejectOnError(reject); +proto.updateMap = function (calcData, fullLayout, resolve, reject) { + var self = this, + map = self.map - var styleObj = getStyleObj(self.opts.style); + self.rejectOnError(reject) - if(self.styleObj.id !== styleObj.id) { - self.styleObj = styleObj; - map.setStyle(styleObj.style); + var styleObj = getStyleObj(self.opts.style) - map.style.once('load', function() { + if (self.styleObj.id !== styleObj.id) { + self.styleObj = styleObj + map.setStyle(styleObj.style) + map.style.once('load', function () { // need to rebuild trace layers on reload // to avoid 'lost event' errors - self.traceHash = {}; + self.traceHash = {} - self.updateData(calcData); - self.updateLayout(fullLayout); + self.updateData(calcData) + self.updateLayout(fullLayout) - self.resolveOnRender(resolve); - }); - } - else { - self.updateData(calcData); - self.updateLayout(fullLayout); + self.resolveOnRender(resolve) + }) + } else { + self.updateData(calcData) + self.updateLayout(fullLayout) - self.resolveOnRender(resolve); - } -}; + self.resolveOnRender(resolve) + } +} -proto.updateData = function(calcData) { - var traceHash = this.traceHash; +proto.updateData = function (calcData) { + var traceHash = this.traceHash - var traceObj, trace, i, j; + var traceObj, trace, i, j // update or create trace objects - for(i = 0; i < calcData.length; i++) { - var calcTrace = calcData[i]; + for (i = 0; i < calcData.length; i++) { + var calcTrace = calcData[i] - trace = calcTrace[0].trace; - traceObj = traceHash[trace.uid]; + trace = calcTrace[0].trace + traceObj = traceHash[trace.uid] - if(traceObj) traceObj.update(calcTrace); - else if(trace._module) { - traceHash[trace.uid] = trace._module.plot(this, calcTrace); - } + if (traceObj) traceObj.update(calcTrace) + else if (trace._module) { + traceHash[trace.uid] = trace._module.plot(this, calcTrace) } + } // remove empty trace objects - var ids = Object.keys(traceHash); - id_loop: - for(i = 0; i < ids.length; i++) { - var id = ids[i]; + var ids = Object.keys(traceHash) + id_loop: + for (i = 0; i < ids.length; i++) { + var id = ids[i] - for(j = 0; j < calcData.length; j++) { - trace = calcData[j][0].trace; + for (j = 0; j < calcData.length; j++) { + trace = calcData[j][0].trace - if(id === trace.uid) continue id_loop; - } + if (id === trace.uid) continue id_loop + } - traceObj = traceHash[id]; - traceObj.dispose(); - delete traceHash[id]; + traceObj = traceHash[id] + traceObj.dispose() + delete traceHash[id] } -}; - -proto.updateLayout = function(fullLayout) { - var map = this.map, - opts = this.opts; - - map.setCenter(convertCenter(opts.center)); - map.setZoom(opts.zoom); - map.setBearing(opts.bearing); - map.setPitch(opts.pitch); - - this.updateLayers(); - this.updateFramework(fullLayout); - this.map.resize(); -}; - -proto.resolveOnRender = function(resolve) { - var map = this.map; - - map.on('render', function onRender() { - if(map.loaded()) { - map.off('render', onRender); - resolve(); - } - }); -}; - -proto.rejectOnError = function(reject) { - var map = this.map; - - function handler() { - reject(new Error(constants.mapOnErrorMsg)); +} + +proto.updateLayout = function (fullLayout) { + var map = this.map, + opts = this.opts + + map.setCenter(convertCenter(opts.center)) + map.setZoom(opts.zoom) + map.setBearing(opts.bearing) + map.setPitch(opts.pitch) + + this.updateLayers() + this.updateFramework(fullLayout) + this.map.resize() +} + +proto.resolveOnRender = function (resolve) { + var map = this.map + + map.on('render', function onRender () { + if (map.loaded()) { + map.off('render', onRender) + resolve() } + }) +} + +proto.rejectOnError = function (reject) { + var map = this.map - map.once('error', handler); - map.once('style.error', handler); - map.once('source.error', handler); - map.once('tile.error', handler); - map.once('layer.error', handler); -}; + function handler () { + reject(new Error(constants.mapOnErrorMsg)) + } -proto.createFramework = function(fullLayout) { - var self = this; + map.once('error', handler) + map.once('style.error', handler) + map.once('source.error', handler) + map.once('tile.error', handler) + map.once('layer.error', handler) +} + +proto.createFramework = function (fullLayout) { + var self = this - var div = self.div = document.createElement('div'); + var div = self.div = document.createElement('div') - div.id = self.uid; - div.style.position = 'absolute'; + div.id = self.uid + div.style.position = 'absolute' - self.container.appendChild(div); + self.container.appendChild(div) // create mock x/y axes for hover routine - self.xaxis = { - _id: 'x', - c2p: function(v) { return self.project(v).x; } - }; + self.xaxis = { + _id: 'x', + c2p: function (v) { return self.project(v).x } + } - self.yaxis = { - _id: 'y', - c2p: function(v) { return self.project(v).y; } - }; + self.yaxis = { + _id: 'y', + c2p: function (v) { return self.project(v).y } + } - self.updateFramework(fullLayout); -}; + self.updateFramework(fullLayout) +} -proto.updateFramework = function(fullLayout) { - var domain = fullLayout[this.id].domain, - size = fullLayout._size; +proto.updateFramework = function (fullLayout) { + var domain = fullLayout[this.id].domain, + size = fullLayout._size - var style = this.div.style; + var style = this.div.style // TODO Is this correct? It seems to get the map zoom level wrong? - style.width = size.w * (domain.x[1] - domain.x[0]) + 'px'; - style.height = size.h * (domain.y[1] - domain.y[0]) + 'px'; - style.left = size.l + domain.x[0] * size.w + 'px'; - style.top = size.t + (1 - domain.y[1]) * size.h + 'px'; + style.width = size.w * (domain.x[1] - domain.x[0]) + 'px' + style.height = size.h * (domain.y[1] - domain.y[0]) + 'px' + style.left = size.l + domain.x[0] * size.w + 'px' + style.top = size.t + (1 - domain.y[1]) * size.h + 'px' - this.xaxis._offset = size.l + domain.x[0] * size.w; - this.xaxis._length = size.w * (domain.x[1] - domain.x[0]); + this.xaxis._offset = size.l + domain.x[0] * size.w + this.xaxis._length = size.w * (domain.x[1] - domain.x[0]) - this.yaxis._offset = size.t + (1 - domain.y[1]) * size.h; - this.yaxis._length = size.h * (domain.y[1] - domain.y[0]); -}; + this.yaxis._offset = size.t + (1 - domain.y[1]) * size.h + this.yaxis._length = size.h * (domain.y[1] - domain.y[0]) +} -proto.updateLayers = function() { - var opts = this.opts, - layers = opts.layers, - layerList = this.layerList, - i; +proto.updateLayers = function () { + var opts = this.opts, + layers = opts.layers, + layerList = this.layerList, + i // if the layer arrays don't match, // don't try to be smart, // delete them all, and start all over. - if(layers.length !== layerList.length) { - for(i = 0; i < layerList.length; i++) { - layerList[i].dispose(); - } + if (layers.length !== layerList.length) { + for (i = 0; i < layerList.length; i++) { + layerList[i].dispose() + } - layerList = this.layerList = []; + layerList = this.layerList = [] - for(i = 0; i < layers.length; i++) { - layerList.push(createMapboxLayer(this, i, layers[i])); - } + for (i = 0; i < layers.length; i++) { + layerList.push(createMapboxLayer(this, i, layers[i])) } - else { - for(i = 0; i < layers.length; i++) { - layerList[i].update(layers[i]); - } + } else { + for (i = 0; i < layers.length; i++) { + layerList[i].update(layers[i]) } -}; + } +} -proto.destroy = function() { - if(this.map) { - this.map.remove(); - this.map = null; - } - this.container.removeChild(this.div); -}; +proto.destroy = function () { + if (this.map) { + this.map.remove() + this.map = null + } + this.container.removeChild(this.div) +} -proto.toImage = function() { - return this.map.getCanvas().toDataURL(); -}; +proto.toImage = function () { + return this.map.getCanvas().toDataURL() +} // convenience wrapper to create blank GeoJSON sources // and avoid 'invalid GeoJSON' errors -proto.initSource = function(idSource) { - var blank = { - type: 'geojson', - data: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [] - } - } - }; - - return this.map.addSource(idSource, blank); -}; +proto.initSource = function (idSource) { + var blank = { + type: 'geojson', + data: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [] + } + } + } + + return this.map.addSource(idSource, blank) +} // convenience wrapper to set data of GeoJSON sources -proto.setSourceData = function(idSource, data) { - this.map.getSource(idSource).setData(data); -}; +proto.setSourceData = function (idSource, data) { + this.map.getSource(idSource).setData(data) +} // convenience wrapper to create set multiple layer // 'layout' or 'paint options at once. -proto.setOptions = function(id, methodName, opts) { - var map = this.map, - keys = Object.keys(opts); +proto.setOptions = function (id, methodName, opts) { + var map = this.map, + keys = Object.keys(opts) - for(var i = 0; i < keys.length; i++) { - var key = keys[i]; + for (var i = 0; i < keys.length; i++) { + var key = keys[i] - map[methodName](id, key, opts[key]); - } -}; + map[methodName](id, key, opts[key]) + } +} // convenience method to project a [lon, lat] array to pixel coords -proto.project = function(v) { - return this.map.project(new mapboxgl.LngLat(v[0], v[1])); -}; +proto.project = function (v) { + return this.map.project(new mapboxgl.LngLat(v[0], v[1])) +} // get map's current view values in plotly.js notation -proto.getView = function() { - var map = this.map; - - var mapCenter = map.getCenter(), - center = { lon: mapCenter.lng, lat: mapCenter.lat }; - - return { - center: center, - zoom: map.getZoom(), - bearing: map.getBearing(), - pitch: map.getPitch() - }; -}; - -function getStyleObj(val) { - var styleValues = layoutAttributes.style.values, - styleDflt = layoutAttributes.style.dflt, - styleObj = {}; - - if(Lib.isPlainObject(val)) { - styleObj.id = val.id; - styleObj.style = val; - } - else if(typeof val === 'string') { - styleObj.id = val; - styleObj.style = (styleValues.indexOf(val) !== -1) ? +proto.getView = function () { + var map = this.map + + var mapCenter = map.getCenter(), + center = { lon: mapCenter.lng, lat: mapCenter.lat } + + return { + center: center, + zoom: map.getZoom(), + bearing: map.getBearing(), + pitch: map.getPitch() + } +} + +function getStyleObj (val) { + var styleValues = layoutAttributes.style.values, + styleDflt = layoutAttributes.style.dflt, + styleObj = {} + + if (Lib.isPlainObject(val)) { + styleObj.id = val.id + styleObj.style = val + } else if (typeof val === 'string') { + styleObj.id = val + styleObj.style = (styleValues.indexOf(val) !== -1) ? convertStyleVal(val) : - val; - } - else { - styleObj.id = styleDflt; - styleObj.style = convertStyleVal(styleDflt); - } + val + } else { + styleObj.id = styleDflt + styleObj.style = convertStyleVal(styleDflt) + } - return styleObj; + return styleObj } // if style is part of the 'official' mapbox values, add URL prefix and suffix -function convertStyleVal(val) { - return constants.styleUrlPrefix + val + '-' + constants.styleUrlSuffix; +function convertStyleVal (val) { + return constants.styleUrlPrefix + val + '-' + constants.styleUrlSuffix } -function convertCenter(center) { - return [center.lon, center.lat]; +function convertCenter (center) { + return [center.lon, center.lat] } diff --git a/src/plots/pad_attributes.js b/src/plots/pad_attributes.js index f5c92700b5d..035ffa9ac41 100644 --- a/src/plots/pad_attributes.js +++ b/src/plots/pad_attributes.js @@ -6,31 +6,31 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { - t: { - valType: 'number', - dflt: 0, - role: 'style', - description: 'The amount of padding (in px) along the top of the component.' - }, - r: { - valType: 'number', - dflt: 0, - role: 'style', - description: 'The amount of padding (in px) on the right side of the component.' - }, - b: { - valType: 'number', - dflt: 0, - role: 'style', - description: 'The amount of padding (in px) along the bottom of the component.' - }, - l: { - valType: 'number', - dflt: 0, - role: 'style', - description: 'The amount of padding (in px) on the left side of the component.' - } -}; + t: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) along the top of the component.' + }, + r: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) on the right side of the component.' + }, + b: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) along the bottom of the component.' + }, + l: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) on the left side of the component.' + } +} diff --git a/src/plots/plots.js b/src/plots/plots.js index 3253aba48f7..99a1ed03864 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -6,43 +6,42 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); +var Plotly = require('../plotly') +var Registry = require('../registry') +var Lib = require('../lib') +var Color = require('../components/color') -var Plotly = require('../plotly'); -var Registry = require('../registry'); -var Lib = require('../lib'); -var Color = require('../components/color'); +var plots = module.exports = {} -var plots = module.exports = {}; - -var animationAttrs = require('./animation_attributes'); -var frameAttrs = require('./frame_attributes'); +var animationAttrs = require('./animation_attributes') +var frameAttrs = require('./frame_attributes') // Expose registry methods on Plots for backward-compatibility -Lib.extendFlat(plots, Registry); +Lib.extendFlat(plots, Registry) -plots.attributes = require('./attributes'); -plots.attributes.type.values = plots.allTypes; -plots.fontAttrs = require('./font_attributes'); -plots.layoutAttributes = require('./layout_attributes'); +plots.attributes = require('./attributes') +plots.attributes.type.values = plots.allTypes +plots.fontAttrs = require('./font_attributes') +plots.layoutAttributes = require('./layout_attributes') // TODO make this a plot attribute? -plots.fontWeight = 'normal'; +plots.fontWeight = 'normal' -var subplotsRegistry = plots.subplotsRegistry; -var transformsRegistry = plots.transformsRegistry; +var subplotsRegistry = plots.subplotsRegistry +var transformsRegistry = plots.transformsRegistry -var ErrorBars = require('../components/errorbars'); +var ErrorBars = require('../components/errorbars') -var commandModule = require('./command'); -plots.executeAPICommand = commandModule.executeAPICommand; -plots.computeAPICommandBindings = commandModule.computeAPICommandBindings; -plots.manageCommandObserver = commandModule.manageCommandObserver; -plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings; +var commandModule = require('./command') +plots.executeAPICommand = commandModule.executeAPICommand +plots.computeAPICommandBindings = commandModule.computeAPICommandBindings +plots.manageCommandObserver = commandModule.manageCommandObserver +plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings /** * Find subplot ids in data. @@ -60,23 +59,23 @@ plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings; * * TODO incorporate cartesian/gl2d axis finders in this paradigm. */ -plots.findSubplotIds = function findSubplotIds(data, type) { - var subplotIds = []; +plots.findSubplotIds = function findSubplotIds (data, type) { + var subplotIds = [] - if(!plots.subplotsRegistry[type]) return subplotIds; + if (!plots.subplotsRegistry[type]) return subplotIds - var attr = plots.subplotsRegistry[type].attr; + var attr = plots.subplotsRegistry[type].attr - for(var i = 0; i < data.length; i++) { - var trace = data[i]; + for (var i = 0; i < data.length; i++) { + var trace = data[i] - if(plots.traceIs(trace, type) && subplotIds.indexOf(trace[attr]) === -1) { - subplotIds.push(trace[attr]); - } + if (plots.traceIs(trace, type) && subplotIds.indexOf(trace[attr]) === -1) { + subplotIds.push(trace[attr]) } + } - return subplotIds; -}; + return subplotIds +} /** * Get the ids of the current subplots. @@ -87,38 +86,38 @@ plots.findSubplotIds = function findSubplotIds(data, type) { * @return {array} list of ordered subplot ids (strings). * */ -plots.getSubplotIds = function getSubplotIds(layout, type) { - var _module = plots.subplotsRegistry[type]; +plots.getSubplotIds = function getSubplotIds (layout, type) { + var _module = plots.subplotsRegistry[type] - if(!_module) return []; + if (!_module) return [] // layout must be 'fullLayout' here - if(type === 'cartesian' && (!layout._has || !layout._has('cartesian'))) return []; - if(type === 'gl2d' && (!layout._has || !layout._has('gl2d'))) return []; - if(type === 'cartesian' || type === 'gl2d') { - return Object.keys(layout._plots || {}); - } + if (type === 'cartesian' && (!layout._has || !layout._has('cartesian'))) return [] + if (type === 'gl2d' && (!layout._has || !layout._has('gl2d'))) return [] + if (type === 'cartesian' || type === 'gl2d') { + return Object.keys(layout._plots || {}) + } - var idRegex = _module.idRegex, - layoutKeys = Object.keys(layout), - subplotIds = []; + var idRegex = _module.idRegex, + layoutKeys = Object.keys(layout), + subplotIds = [] - for(var i = 0; i < layoutKeys.length; i++) { - var layoutKey = layoutKeys[i]; + for (var i = 0; i < layoutKeys.length; i++) { + var layoutKey = layoutKeys[i] - if(idRegex.test(layoutKey)) subplotIds.push(layoutKey); - } + if (idRegex.test(layoutKey)) subplotIds.push(layoutKey) + } // order the ids - var idLen = _module.idRoot.length; - subplotIds.sort(function(a, b) { - var aNum = +(a.substr(idLen) || 1), - bNum = +(b.substr(idLen) || 1); - return aNum - bNum; - }); - - return subplotIds; -}; + var idLen = _module.idRoot.length + subplotIds.sort(function (a, b) { + var aNum = +(a.substr(idLen) || 1), + bNum = +(b.substr(idLen) || 1) + return aNum - bNum + }) + + return subplotIds +} /** * Get the data trace(s) associated with a given subplot. @@ -130,32 +129,31 @@ plots.getSubplotIds = function getSubplotIds(layout, type) { * @return {array} list of trace objects. * */ -plots.getSubplotData = function getSubplotData(data, type, subplotId) { - if(!plots.subplotsRegistry[type]) return []; +plots.getSubplotData = function getSubplotData (data, type, subplotId) { + if (!plots.subplotsRegistry[type]) return [] - var attr = plots.subplotsRegistry[type].attr, - subplotData = [], - trace; + var attr = plots.subplotsRegistry[type].attr, + subplotData = [], + trace - for(var i = 0; i < data.length; i++) { - trace = data[i]; + for (var i = 0; i < data.length; i++) { + trace = data[i] - if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { - var spmatch = Plotly.Axes.subplotMatch, - subplotX = 'x' + subplotId.match(spmatch)[1], - subplotY = 'y' + subplotId.match(spmatch)[2]; + if (type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { + var spmatch = Plotly.Axes.subplotMatch, + subplotX = 'x' + subplotId.match(spmatch)[1], + subplotY = 'y' + subplotId.match(spmatch)[2] - if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { - subplotData.push(trace); - } - } - else { - if(trace[attr] === subplotId) subplotData.push(trace); - } + if (trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { + subplotData.push(trace) + } + } else { + if (trace[attr] === subplotId) subplotData.push(trace) } + } - return subplotData; -}; + return subplotData +} /** * Get calcdata traces(s) associated with a given subplot @@ -166,87 +164,84 @@ plots.getSubplotData = function getSubplotData(data, type, subplotId) { * * @return {array} array of calcdata traces */ -plots.getSubplotCalcData = function(calcData, type, subplotId) { - if(!plots.subplotsRegistry[type]) return []; +plots.getSubplotCalcData = function (calcData, type, subplotId) { + if (!plots.subplotsRegistry[type]) return [] - var attr = plots.subplotsRegistry[type].attr; - var subplotCalcData = []; + var attr = plots.subplotsRegistry[type].attr + var subplotCalcData = [] - for(var i = 0; i < calcData.length; i++) { - var calcTrace = calcData[i], - trace = calcTrace[0].trace; + for (var i = 0; i < calcData.length; i++) { + var calcTrace = calcData[i], + trace = calcTrace[0].trace - if(trace[attr] === subplotId) subplotCalcData.push(calcTrace); - } + if (trace[attr] === subplotId) subplotCalcData.push(calcTrace) + } - return subplotCalcData; -}; + return subplotCalcData +} // in some cases the browser doesn't seem to know how big // the text is at first, so it needs to draw it, // then wait a little, then draw it again -plots.redrawText = function(gd) { - +plots.redrawText = function (gd) { // do not work if polar is present - if((gd.data && gd.data[0] && gd.data[0].r)) return; + if ((gd.data && gd.data[0] && gd.data[0].r)) return - return new Promise(function(resolve) { - setTimeout(function() { - Registry.getComponentMethod('annotations', 'draw')(gd); - Registry.getComponentMethod('legend', 'draw')(gd); + return new Promise(function (resolve) { + setTimeout(function () { + Registry.getComponentMethod('annotations', 'draw')(gd) + Registry.getComponentMethod('legend', 'draw')(gd); - (gd.calcdata || []).forEach(function(d) { - if(d[0] && d[0].t && d[0].t.cb) d[0].t.cb(); - }); + (gd.calcdata || []).forEach(function (d) { + if (d[0] && d[0].t && d[0].t.cb) d[0].t.cb() + }) - resolve(plots.previousPromises(gd)); - }, 300); - }); -}; + resolve(plots.previousPromises(gd)) + }, 300) + }) +} // resize plot about the container size -plots.resize = function(gd) { - return new Promise(function(resolve, reject) { - - if(!gd || d3.select(gd).style('display') === 'none') { - reject(new Error('Resize must be passed a plot div element.')); - } +plots.resize = function (gd) { + return new Promise(function (resolve, reject) { + if (!gd || d3.select(gd).style('display') === 'none') { + reject(new Error('Resize must be passed a plot div element.')) + } - if(gd._redrawTimer) clearTimeout(gd._redrawTimer); + if (gd._redrawTimer) clearTimeout(gd._redrawTimer) - gd._redrawTimer = setTimeout(function() { + gd._redrawTimer = setTimeout(function () { // return if there is nothing to resize - if(gd.layout.width && gd.layout.height) { - resolve(gd); - return; - } + if (gd.layout.width && gd.layout.height) { + resolve(gd) + return + } - delete gd.layout.width; - delete gd.layout.height; + delete gd.layout.width + delete gd.layout.height // autosizing doesn't count as a change that needs saving - var oldchanged = gd.changed; + var oldchanged = gd.changed // nor should it be included in the undo queue - gd.autoplay = true; - - Plotly.relayout(gd, { autosize: true }).then(function() { - gd.changed = oldchanged; - resolve(gd); - }); - }, 100); - }); -}; - + gd.autoplay = true + + Plotly.relayout(gd, { autosize: true }).then(function () { + gd.changed = oldchanged + resolve(gd) + }) + }, 100) + }) +} // for use in Lib.syncOrAsync, check if there are any // pending promises in this plot and wait for them -plots.previousPromises = function(gd) { - if((gd._promises || []).length) { - return Promise.all(gd._promises) - .then(function() { gd._promises = []; }); - } -}; +plots.previousPromises = function (gd) { + if ((gd._promises || []).length) { + return Promise.all(gd._promises) + .then(function () { gd._promises = [] }) + } +} /** * Adds the 'Edit chart' link. @@ -254,123 +249,121 @@ plots.previousPromises = function(gd) { * * Add source links to your graph inside the 'showSources' config argument. */ -plots.addLinks = function(gd) { - var fullLayout = gd._fullLayout; +plots.addLinks = function (gd) { + var fullLayout = gd._fullLayout - var linkContainer = fullLayout._paper - .selectAll('text.js-plot-link-container').data([0]); + var linkContainer = fullLayout._paper + .selectAll('text.js-plot-link-container').data([0]) - linkContainer.enter().append('text') + linkContainer.enter().append('text') .classed('js-plot-link-container', true) .style({ - 'font-family': '"Open Sans", Arial, sans-serif', - 'font-size': '12px', - 'fill': Color.defaultLine, - 'pointer-events': 'all' + 'font-family': '"Open Sans", Arial, sans-serif', + 'font-size': '12px', + 'fill': Color.defaultLine, + 'pointer-events': 'all' + }) + .each(function () { + var links = d3.select(this) + links.append('tspan').classed('js-link-to-tool', true) + links.append('tspan').classed('js-link-spacer', true) + links.append('tspan').classed('js-sourcelinks', true) }) - .each(function() { - var links = d3.select(this); - links.append('tspan').classed('js-link-to-tool', true); - links.append('tspan').classed('js-link-spacer', true); - links.append('tspan').classed('js-sourcelinks', true); - }); // The text node inside svg - var text = linkContainer.node(), - attrs = { - y: fullLayout._paper.attr('height') - 9 - }; + var text = linkContainer.node(), + attrs = { + y: fullLayout._paper.attr('height') - 9 + } // If text's width is bigger than the layout // Check that text is a child node or document.body // because otherwise IE/Edge might throw an exception // when calling getComputedTextLength(). // Apparently offsetParent is null for invisibles. - if(document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) { + if (document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) { // Align the text at the left - attrs['text-anchor'] = 'start'; - attrs.x = 5; - } - else { + attrs['text-anchor'] = 'start' + attrs.x = 5 + } else { // Align the text at the right - attrs['text-anchor'] = 'end'; - attrs.x = fullLayout._paper.attr('width') - 7; - } + attrs['text-anchor'] = 'end' + attrs.x = fullLayout._paper.attr('width') - 7 + } - linkContainer.attr(attrs); + linkContainer.attr(attrs) - var toolspan = linkContainer.select('.js-link-to-tool'), - spacespan = linkContainer.select('.js-link-spacer'), - sourcespan = linkContainer.select('.js-sourcelinks'); + var toolspan = linkContainer.select('.js-link-to-tool'), + spacespan = linkContainer.select('.js-link-spacer'), + sourcespan = linkContainer.select('.js-sourcelinks') - if(gd._context.showSources) gd._context.showSources(gd); + if (gd._context.showSources) gd._context.showSources(gd) // 'view in plotly' link for embedded plots - if(gd._context.showLink) positionPlayWithData(gd, toolspan); + if (gd._context.showLink) positionPlayWithData(gd, toolspan) // separator if we have both sources and tool link - spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : ''); -}; + spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : '') +} // note that now this function is only adding the brand in // iframes and 3rd-party apps -function positionPlayWithData(gd, container) { - container.text(''); - var link = container.append('a') +function positionPlayWithData (gd, container) { + container.text('') + var link = container.append('a') .attr({ - 'xlink:xlink:href': '#', - 'class': 'link--impt link--embedview', - 'font-weight': 'bold' + 'xlink:xlink:href': '#', + 'class': 'link--impt link--embedview', + 'font-weight': 'bold' }) - .text(gd._context.linkText + ' ' + String.fromCharCode(187)); - - if(gd._context.sendData) { - link.on('click', function() { - plots.sendDataToCloud(gd); - }); - } - else { - var path = window.location.pathname.split('/'); - var query = window.location.search; - link.attr({ - 'xlink:xlink:show': 'new', - 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query - }); - } + .text(gd._context.linkText + ' ' + String.fromCharCode(187)) + + if (gd._context.sendData) { + link.on('click', function () { + plots.sendDataToCloud(gd) + }) + } else { + var path = window.location.pathname.split('/') + var query = window.location.search + link.attr({ + 'xlink:xlink:show': 'new', + 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query + }) + } } -plots.sendDataToCloud = function(gd) { - gd.emit('plotly_beforeexport'); +plots.sendDataToCloud = function (gd) { + gd.emit('plotly_beforeexport') - var baseUrl = (window.PLOTLYENV && window.PLOTLYENV.BASE_URL) || 'https://plot.ly'; + var baseUrl = (window.PLOTLYENV && window.PLOTLYENV.BASE_URL) || 'https://plot.ly' - var hiddenformDiv = d3.select(gd) + var hiddenformDiv = d3.select(gd) .append('div') .attr('id', 'hiddenform') - .style('display', 'none'); + .style('display', 'none') - var hiddenform = hiddenformDiv + var hiddenform = hiddenformDiv .append('form') .attr({ - action: baseUrl + '/external', - method: 'post', - target: '_blank' - }); + action: baseUrl + '/external', + method: 'post', + target: '_blank' + }) - var hiddenformInput = hiddenform + var hiddenformInput = hiddenform .append('input') .attr({ - type: 'text', - name: 'data' - }); + type: 'text', + name: 'data' + }) - hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata'); - hiddenform.node().submit(); - hiddenformDiv.remove(); + hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata') + hiddenform.node().submit() + hiddenformDiv.remove() - gd.emit('plotly_afterexport'); - return false; -}; + gd.emit('plotly_afterexport') + return false +} // Fill in default values: // @@ -392,205 +385,202 @@ plots.sendDataToCloud = function(gd) { // gd._fullLayout._transformModules // is a list of all the transform modules invoked. // -plots.supplyDefaults = function(gd) { - var oldFullLayout = gd._fullLayout || {}, - newFullLayout = gd._fullLayout = {}, - newLayout = gd.layout || {}; +plots.supplyDefaults = function (gd) { + var oldFullLayout = gd._fullLayout || {}, + newFullLayout = gd._fullLayout = {}, + newLayout = gd.layout || {} - var oldFullData = gd._fullData || [], - newFullData = gd._fullData = [], - newData = gd.data || []; + var oldFullData = gd._fullData || [], + newFullData = gd._fullData = [], + newData = gd.data || [] - var i; + var i // Create all the storage space for frames, but only if doesn't already exist - if(!gd._transitionData) plots.createTransitionData(gd); + if (!gd._transitionData) plots.createTransitionData(gd) // first fill in what we can of layout without looking at data // because fullData needs a few things from layout - if(oldFullLayout._initialAutoSizeIsDone) { - + if (oldFullLayout._initialAutoSizeIsDone) { // coerce the updated layout while preserving width and height - var oldWidth = oldFullLayout.width, - oldHeight = oldFullLayout.height; - - plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); + var oldWidth = oldFullLayout.width, + oldHeight = oldFullLayout.height - if(!newLayout.width) newFullLayout.width = oldWidth; - if(!newLayout.height) newFullLayout.height = oldHeight; - } - else { + plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout) + if (!newLayout.width) newFullLayout.width = oldWidth + if (!newLayout.height) newFullLayout.height = oldHeight + } else { // coerce the updated layout and autosize if needed - plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); + plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout) - var missingWidthOrHeight = (!newLayout.width || !newLayout.height), - autosize = newFullLayout.autosize, - autosizable = gd._context && gd._context.autosizable, - initialAutoSize = missingWidthOrHeight && (autosize || autosizable); + var missingWidthOrHeight = (!newLayout.width || !newLayout.height), + autosize = newFullLayout.autosize, + autosizable = gd._context && gd._context.autosizable, + initialAutoSize = missingWidthOrHeight && (autosize || autosizable) - if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout); - else if(missingWidthOrHeight) plots.sanitizeMargins(gd); + if (initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout) + else if (missingWidthOrHeight) plots.sanitizeMargins(gd) // for backwards-compatibility with Plotly v1.x.x - if(!autosize && missingWidthOrHeight) { - newLayout.width = newFullLayout.width; - newLayout.height = newFullLayout.height; - } + if (!autosize && missingWidthOrHeight) { + newLayout.width = newFullLayout.width + newLayout.height = newFullLayout.height } + } - newFullLayout._initialAutoSizeIsDone = true; + newFullLayout._initialAutoSizeIsDone = true // keep track of how many traces are inputted - newFullLayout._dataLength = newData.length; + newFullLayout._dataLength = newData.length // then do the data - newFullLayout._globalTransforms = (gd._context || {}).globalTransforms; - plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout); + newFullLayout._globalTransforms = (gd._context || {}).globalTransforms + plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout) // attach helper method to check whether a plot type is present on graph - newFullLayout._has = plots._hasPlotType.bind(newFullLayout); + newFullLayout._has = plots._hasPlotType.bind(newFullLayout) // special cases that introduce interactions between traces - var _modules = newFullLayout._modules; - for(i = 0; i < _modules.length; i++) { - var _module = _modules[i]; - if(_module.cleanData) _module.cleanData(newFullData); - } + var _modules = newFullLayout._modules + for (i = 0; i < _modules.length; i++) { + var _module = _modules[i] + if (_module.cleanData) _module.cleanData(newFullData) + } - if(oldFullData.length === newData.length) { - for(i = 0; i < newFullData.length; i++) { - relinkPrivateKeys(newFullData[i], oldFullData[i]); - } + if (oldFullData.length === newData.length) { + for (i = 0; i < newFullData.length; i++) { + relinkPrivateKeys(newFullData[i], oldFullData[i]) } + } // finally, fill in the pieces of layout that may need to look at data - plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData); + plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData) // TODO remove in v2.0.0 // add has-plot-type refs to fullLayout for backward compatibility - newFullLayout._hasCartesian = newFullLayout._has('cartesian'); - newFullLayout._hasGeo = newFullLayout._has('geo'); - newFullLayout._hasGL3D = newFullLayout._has('gl3d'); - newFullLayout._hasGL2D = newFullLayout._has('gl2d'); - newFullLayout._hasTernary = newFullLayout._has('ternary'); - newFullLayout._hasPie = newFullLayout._has('pie'); + newFullLayout._hasCartesian = newFullLayout._has('cartesian') + newFullLayout._hasGeo = newFullLayout._has('geo') + newFullLayout._hasGL3D = newFullLayout._has('gl3d') + newFullLayout._hasGL2D = newFullLayout._has('gl2d') + newFullLayout._hasTernary = newFullLayout._has('ternary') + newFullLayout._hasPie = newFullLayout._has('pie') // clean subplots and other artifacts from previous plot calls - plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); + plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout) // relink / initialize subplot axis objects - plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout); + plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout) // relink functions and _ attributes to promote consistency between plots - relinkPrivateKeys(newFullLayout, oldFullLayout); + relinkPrivateKeys(newFullLayout, oldFullLayout) // TODO may return a promise - plots.doAutoMargin(gd); + plots.doAutoMargin(gd) // can't quite figure out how to get rid of this... each axis needs // a reference back to the DOM object for just a few purposes - var axList = Plotly.Axes.list(gd); - for(i = 0; i < axList.length; i++) { - var ax = axList[i]; - ax._gd = gd; - ax.setScale(); - } + var axList = Plotly.Axes.list(gd) + for (i = 0; i < axList.length; i++) { + var ax = axList[i] + ax._gd = gd + ax.setScale() + } // update object references in calcdata - if((gd.calcdata || []).length === newFullData.length) { - for(i = 0; i < newFullData.length; i++) { - var trace = newFullData[i]; - (gd.calcdata[i][0] || {}).trace = trace; - } + if ((gd.calcdata || []).length === newFullData.length) { + for (i = 0; i < newFullData.length; i++) { + var trace = newFullData[i]; + (gd.calcdata[i][0] || {}).trace = trace } -}; + } +} // Create storage for all of the data related to frames and transitions: -plots.createTransitionData = function(gd) { +plots.createTransitionData = function (gd) { // Set up the default keyframe if it doesn't exist: - if(!gd._transitionData) { - gd._transitionData = {}; - } + if (!gd._transitionData) { + gd._transitionData = {} + } - if(!gd._transitionData._frames) { - gd._transitionData._frames = []; - } + if (!gd._transitionData._frames) { + gd._transitionData._frames = [] + } - if(!gd._transitionData._frameHash) { - gd._transitionData._frameHash = {}; - } + if (!gd._transitionData._frameHash) { + gd._transitionData._frameHash = {} + } - if(!gd._transitionData._counter) { - gd._transitionData._counter = 0; - } + if (!gd._transitionData._counter) { + gd._transitionData._counter = 0 + } - if(!gd._transitionData._interruptCallbacks) { - gd._transitionData._interruptCallbacks = []; - } -}; + if (!gd._transitionData._interruptCallbacks) { + gd._transitionData._interruptCallbacks = [] + } +} // helper function to be bound to fullLayout to check // whether a certain plot type is present on plot -plots._hasPlotType = function(category) { - var basePlotModules = this._basePlotModules || []; +plots._hasPlotType = function (category) { + var basePlotModules = this._basePlotModules || [] - for(var i = 0; i < basePlotModules.length; i++) { - var _module = basePlotModules[i]; + for (var i = 0; i < basePlotModules.length; i++) { + var _module = basePlotModules[i] - if(_module.name === category) return true; - } + if (_module.name === category) return true + } - return false; -}; + return false +} -plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var i, j; +plots.cleanPlot = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var i, j - var basePlotModules = oldFullLayout._basePlotModules || []; - for(i = 0; i < basePlotModules.length; i++) { - var _module = basePlotModules[i]; + var basePlotModules = oldFullLayout._basePlotModules || [] + for (i = 0; i < basePlotModules.length; i++) { + var _module = basePlotModules[i] - if(_module.clean) { - _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout); - } + if (_module.clean) { + _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout) } + } - var hasPaper = !!oldFullLayout._paper; - var hasInfoLayer = !!oldFullLayout._infolayer; + var hasPaper = !!oldFullLayout._paper + var hasInfoLayer = !!oldFullLayout._infolayer - oldLoop: - for(i = 0; i < oldFullData.length; i++) { - var oldTrace = oldFullData[i], - oldUid = oldTrace.uid; + oldLoop: + for (i = 0; i < oldFullData.length; i++) { + var oldTrace = oldFullData[i], + oldUid = oldTrace.uid - for(j = 0; j < newFullData.length; j++) { - var newTrace = newFullData[j]; + for (j = 0; j < newFullData.length; j++) { + var newTrace = newFullData[j] - if(oldUid === newTrace.uid) continue oldLoop; - } + if (oldUid === newTrace.uid) continue oldLoop + } // clean old heatmap, contour, and scatter traces // // Note: This is also how scatter traces (cartesian and scatterternary) get // removed since otherwise the scatter module is not called (and so the join // doesn't register the removal) if scatter traces disappear entirely. - if(hasPaper) { - oldFullLayout._paper.selectAll( + if (hasPaper) { + oldFullLayout._paper.selectAll( '.hm' + oldUid + ',.contour' + oldUid + ',#clip' + oldUid + ',.trace' + oldUid - ).remove(); - } + ).remove() + } // clean old colorbars - if(hasInfoLayer) { - oldFullLayout._infolayer.selectAll('.cb' + oldUid).remove(); - } + if (hasInfoLayer) { + oldFullLayout._infolayer.selectAll('.cb' + oldUid).remove() + } } -}; +} /** * Relink private _keys and keys with a function value from one container @@ -599,349 +589,340 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou * if object is pass-by-ref. * This prevents deepCopying massive structures like a webgl context. */ -function relinkPrivateKeys(toContainer, fromContainer) { - var isPlainObject = Lib.isPlainObject, - isArray = Array.isArray; +function relinkPrivateKeys (toContainer, fromContainer) { + var isPlainObject = Lib.isPlainObject, + isArray = Array.isArray - var keys = Object.keys(fromContainer || {}); + var keys = Object.keys(fromContainer || {}) - for(var i = 0; i < keys.length; i++) { - var k = keys[i], - fromVal = fromContainer[k], - toVal = toContainer[k]; - - if(k.charAt(0) === '_' || typeof fromVal === 'function') { + for (var i = 0; i < keys.length; i++) { + var k = keys[i], + fromVal = fromContainer[k], + toVal = toContainer[k] + if (k.charAt(0) === '_' || typeof fromVal === 'function') { // if it already exists at this point, it's something // that we recreate each time around, so ignore it - if(k in toContainer) continue; - - toContainer[k] = fromVal; - } - else if(isArray(fromVal) && isArray(toVal) && isPlainObject(fromVal[0])) { + if (k in toContainer) continue + toContainer[k] = fromVal + } else if (isArray(fromVal) && isArray(toVal) && isPlainObject(fromVal[0])) { // recurse into arrays containers - for(var j = 0; j < fromVal.length; j++) { - if(isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { - relinkPrivateKeys(toVal[j], fromVal[j]); - } - } + for (var j = 0; j < fromVal.length; j++) { + if (isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { + relinkPrivateKeys(toVal[j], fromVal[j]) } - else if(isPlainObject(fromVal) && isPlainObject(toVal)) { - + } + } else if (isPlainObject(fromVal) && isPlainObject(toVal)) { // recurse into objects, but only if they still exist - relinkPrivateKeys(toVal, fromVal); + relinkPrivateKeys(toVal, fromVal) - if(!Object.keys(toVal).length) delete toContainer[k]; - } + if (!Object.keys(toVal).length) delete toContainer[k] } + } } -plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldSubplots = oldFullLayout._plots || {}, - newSubplots = newFullLayout._plots = {}; +plots.linkSubplots = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldSubplots = oldFullLayout._plots || {}, + newSubplots = newFullLayout._plots = {} - var mockGd = { - _fullData: newFullData, - _fullLayout: newFullLayout - }; + var mockGd = { + _fullData: newFullData, + _fullLayout: newFullLayout + } - var ids = Plotly.Axes.getSubplots(mockGd); + var ids = Plotly.Axes.getSubplots(mockGd) - for(var i = 0; i < ids.length; i++) { - var id = ids[i], - oldSubplot = oldSubplots[id], - plotinfo; + for (var i = 0; i < ids.length; i++) { + var id = ids[i], + oldSubplot = oldSubplots[id], + plotinfo - if(oldSubplot) { - plotinfo = newSubplots[id] = oldSubplot; - - if(plotinfo._scene2d) { - plotinfo._scene2d.updateRefs(newFullLayout); - } - } - else { - plotinfo = newSubplots[id] = {}; - plotinfo.id = id; - } + if (oldSubplot) { + plotinfo = newSubplots[id] = oldSubplot - plotinfo.xaxis = Plotly.Axes.getFromId(mockGd, id, 'x'); - plotinfo.yaxis = Plotly.Axes.getFromId(mockGd, id, 'y'); + if (plotinfo._scene2d) { + plotinfo._scene2d.updateRefs(newFullLayout) + } + } else { + plotinfo = newSubplots[id] = {} + plotinfo.id = id } -}; -plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { - var modules = fullLayout._modules = [], - basePlotModules = fullLayout._basePlotModules = [], - cnt = 0; + plotinfo.xaxis = Plotly.Axes.getFromId(mockGd, id, 'x') + plotinfo.yaxis = Plotly.Axes.getFromId(mockGd, id, 'y') + } +} - fullLayout._transformModules = []; +plots.supplyDataDefaults = function (dataIn, dataOut, layout, fullLayout) { + var modules = fullLayout._modules = [], + basePlotModules = fullLayout._basePlotModules = [], + cnt = 0 - function pushModule(fullTrace) { - dataOut.push(fullTrace); + fullLayout._transformModules = [] - var _module = fullTrace._module; - if(!_module) return; + function pushModule (fullTrace) { + dataOut.push(fullTrace) - Lib.pushUnique(modules, _module); - Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule); + var _module = fullTrace._module + if (!_module) return - cnt++; - } + Lib.pushUnique(modules, _module) + Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule) - for(var i = 0; i < dataIn.length; i++) { - var trace = dataIn[i], - fullTrace = plots.supplyTraceDefaults(trace, cnt, fullLayout, i); + cnt++ + } - fullTrace.index = i; - fullTrace._input = trace; - fullTrace._expandedIndex = cnt; + for (var i = 0; i < dataIn.length; i++) { + var trace = dataIn[i], + fullTrace = plots.supplyTraceDefaults(trace, cnt, fullLayout, i) - if(fullTrace.transforms && fullTrace.transforms.length) { - var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout); + fullTrace.index = i + fullTrace._input = trace + fullTrace._expandedIndex = cnt - for(var j = 0; j < expandedTraces.length; j++) { - var expandedTrace = expandedTraces[j], - fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout, i); + if (fullTrace.transforms && fullTrace.transforms.length) { + var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout) + + for (var j = 0; j < expandedTraces.length; j++) { + var expandedTrace = expandedTraces[j], + fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout, i) // mutate uid here using parent uid and expanded index // to promote consistency between update calls - expandedTrace.uid = fullExpandedTrace.uid = fullTrace.uid + j; + expandedTrace.uid = fullExpandedTrace.uid = fullTrace.uid + j // add info about parent data trace - fullExpandedTrace.index = i; - fullExpandedTrace._input = trace; - fullExpandedTrace._fullInput = fullTrace; + fullExpandedTrace.index = i + fullExpandedTrace._input = trace + fullExpandedTrace._fullInput = fullTrace // add info about the expanded data - fullExpandedTrace._expandedIndex = cnt; - fullExpandedTrace._expandedInput = expandedTrace; - - pushModule(fullExpandedTrace); - } - } - else { + fullExpandedTrace._expandedIndex = cnt + fullExpandedTrace._expandedInput = expandedTrace + pushModule(fullExpandedTrace) + } + } else { // add identify refs for consistency with transformed traces - fullTrace._fullInput = fullTrace; - fullTrace._expandedInput = fullTrace; + fullTrace._fullInput = fullTrace + fullTrace._expandedInput = fullTrace - pushModule(fullTrace); - } + pushModule(fullTrace) } -}; + } +} -plots.supplyAnimationDefaults = function(opts) { - opts = opts || {}; - var i; - var optsOut = {}; +plots.supplyAnimationDefaults = function (opts) { + opts = opts || {} + var i + var optsOut = {} - function coerce(attr, dflt) { - return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt) + } - coerce('mode'); - coerce('direction'); - coerce('fromcurrent'); + coerce('mode') + coerce('direction') + coerce('fromcurrent') - if(Array.isArray(opts.frame)) { - optsOut.frame = []; - for(i = 0; i < opts.frame.length; i++) { - optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {}); - } - } else { - optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {}); + if (Array.isArray(opts.frame)) { + optsOut.frame = [] + for (i = 0; i < opts.frame.length; i++) { + optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {}) } + } else { + optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {}) + } - if(Array.isArray(opts.transition)) { - optsOut.transition = []; - for(i = 0; i < opts.transition.length; i++) { - optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {}); - } - } else { - optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {}); + if (Array.isArray(opts.transition)) { + optsOut.transition = [] + for (i = 0; i < opts.transition.length; i++) { + optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {}) } + } else { + optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {}) + } - return optsOut; -}; + return optsOut +} -plots.supplyAnimationFrameDefaults = function(opts) { - var optsOut = {}; +plots.supplyAnimationFrameDefaults = function (opts) { + var optsOut = {} - function coerce(attr, dflt) { - return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt) + } - coerce('duration'); - coerce('redraw'); + coerce('duration') + coerce('redraw') - return optsOut; -}; + return optsOut +} -plots.supplyAnimationTransitionDefaults = function(opts) { - var optsOut = {}; +plots.supplyAnimationTransitionDefaults = function (opts) { + var optsOut = {} - function coerce(attr, dflt) { - return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt) + } - coerce('duration'); - coerce('easing'); + coerce('duration') + coerce('easing') - return optsOut; -}; + return optsOut +} -plots.supplyFrameDefaults = function(frameIn) { - var frameOut = {}; +plots.supplyFrameDefaults = function (frameIn) { + var frameOut = {} - function coerce(attr, dflt) { - return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt) + } - coerce('group'); - coerce('name'); - coerce('traces'); - coerce('baseframe'); - coerce('data'); - coerce('layout'); + coerce('group') + coerce('name') + coerce('traces') + coerce('baseframe') + coerce('data') + coerce('layout') - return frameOut; -}; + return frameOut +} -plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInIndex) { - var traceOut = {}, - defaultColor = Color.defaults[traceOutIndex % Color.defaults.length]; +plots.supplyTraceDefaults = function (traceIn, traceOutIndex, layout, traceInIndex) { + var traceOut = {}, + defaultColor = Color.defaults[traceOutIndex % Color.defaults.length] - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt) + } - function coerceSubplotAttr(subplotType, subplotAttr) { - if(!plots.traceIs(traceOut, subplotType)) return; + function coerceSubplotAttr (subplotType, subplotAttr) { + if (!plots.traceIs(traceOut, subplotType)) return - return Lib.coerce(traceIn, traceOut, - plots.subplotsRegistry[subplotType].attributes, subplotAttr); - } + return Lib.coerce(traceIn, traceOut, + plots.subplotsRegistry[subplotType].attributes, subplotAttr) + } - var visible = coerce('visible'); + var visible = coerce('visible') - coerce('type'); - coerce('uid'); - coerce('name', 'trace ' + traceInIndex); + coerce('type') + coerce('uid') + coerce('name', 'trace ' + traceInIndex) // coerce subplot attributes of all registered subplot types - var subplotTypes = Object.keys(subplotsRegistry); - for(var i = 0; i < subplotTypes.length; i++) { - var subplotType = subplotTypes[i]; + var subplotTypes = Object.keys(subplotsRegistry) + for (var i = 0; i < subplotTypes.length; i++) { + var subplotType = subplotTypes[i] // done below (only when visible is true) // TODO unified this pattern - if(['cartesian', 'gl2d'].indexOf(subplotType) !== -1) continue; + if (['cartesian', 'gl2d'].indexOf(subplotType) !== -1) continue - var attr = subplotsRegistry[subplotType].attr; + var attr = subplotsRegistry[subplotType].attr - if(attr) coerceSubplotAttr(subplotType, attr); - } + if (attr) coerceSubplotAttr(subplotType, attr) + } - if(visible) { - var _module = plots.getModule(traceOut); - traceOut._module = _module; + if (visible) { + var _module = plots.getModule(traceOut) + traceOut._module = _module // gets overwritten in pie, geo and ternary modules - coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); + coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined) // TODO add per-base-plot-module trace defaults step - if(_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); + if (_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout) - if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity'); + if (!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity') - coerceSubplotAttr('cartesian', 'xaxis'); - coerceSubplotAttr('cartesian', 'yaxis'); + coerceSubplotAttr('cartesian', 'xaxis') + coerceSubplotAttr('cartesian', 'yaxis') - coerceSubplotAttr('gl2d', 'xaxis'); - coerceSubplotAttr('gl2d', 'yaxis'); - - if(plots.traceIs(traceOut, 'showLegend')) { - coerce('showlegend'); - coerce('legendgroup'); - } + coerceSubplotAttr('gl2d', 'xaxis') + coerceSubplotAttr('gl2d', 'yaxis') - supplyTransformDefaults(traceIn, traceOut, layout); + if (plots.traceIs(traceOut, 'showLegend')) { + coerce('showlegend') + coerce('legendgroup') } - return traceOut; -}; + supplyTransformDefaults(traceIn, traceOut, layout) + } -function supplyTransformDefaults(traceIn, traceOut, layout) { - var globalTransforms = layout._globalTransforms || []; + return traceOut +} - if(!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return; +function supplyTransformDefaults (traceIn, traceOut, layout) { + var globalTransforms = layout._globalTransforms || [] - var containerIn = traceIn.transforms || [], - transformList = globalTransforms.concat(containerIn), - containerOut = traceOut.transforms = []; + if (!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return - for(var i = 0; i < transformList.length; i++) { - var transformIn = transformList[i], - type = transformIn.type, - _module = transformsRegistry[type], - transformOut; + var containerIn = traceIn.transforms || [], + transformList = globalTransforms.concat(containerIn), + containerOut = traceOut.transforms = [] - if(!_module) Lib.warn('Unrecognized transform type ' + type + '.'); + for (var i = 0; i < transformList.length; i++) { + var transformIn = transformList[i], + type = transformIn.type, + _module = transformsRegistry[type], + transformOut - if(_module && _module.supplyDefaults) { - transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn); - transformOut.type = type; - transformOut._module = _module; + if (!_module) Lib.warn('Unrecognized transform type ' + type + '.') - Lib.pushUnique(layout._transformModules, _module); - } - else { - transformOut = Lib.extendFlat({}, transformIn); - } + if (_module && _module.supplyDefaults) { + transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn) + transformOut.type = type + transformOut._module = _module - containerOut.push(transformOut); + Lib.pushUnique(layout._transformModules, _module) + } else { + transformOut = Lib.extendFlat({}, transformIn) } + + containerOut.push(transformOut) + } } -function applyTransforms(fullTrace, fullData, layout, fullLayout) { - var container = fullTrace.transforms, - dataOut = [fullTrace]; - - for(var i = 0; i < container.length; i++) { - var transform = container[i], - _module = transformsRegistry[transform.type]; - - if(_module && _module.transform) { - dataOut = _module.transform(dataOut, { - transform: transform, - fullTrace: fullTrace, - fullData: fullData, - layout: layout, - fullLayout: fullLayout, - transformIndex: i - }); - } +function applyTransforms (fullTrace, fullData, layout, fullLayout) { + var container = fullTrace.transforms, + dataOut = [fullTrace] + + for (var i = 0; i < container.length; i++) { + var transform = container[i], + _module = transformsRegistry[transform.type] + + if (_module && _module.transform) { + dataOut = _module.transform(dataOut, { + transform: transform, + fullTrace: fullTrace, + fullData: fullData, + layout: layout, + fullLayout: fullLayout, + transformIndex: i + }) } + } - return dataOut; + return dataOut } -plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { - function coerce(attr, dflt) { - return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt); - } +plots.supplyLayoutGlobalDefaults = function (layoutIn, layoutOut) { + function coerce (attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt) + } - var globalFont = Lib.coerceFont(coerce, 'font'); + var globalFont = Lib.coerceFont(coerce, 'font') - coerce('title'); + coerce('title') - Lib.coerceFont(coerce, 'titlefont', { - family: globalFont.family, - size: Math.round(globalFont.size * 1.4), - color: globalFont.color - }); + Lib.coerceFont(coerce, 'titlefont', { + family: globalFont.family, + size: Math.round(globalFont.size * 1.4), + color: globalFont.color + }) // Make sure that autosize is defaulted to *true* // on layouts with no set width and height for backward compatibly, @@ -952,98 +933,96 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { // to pass through the autosize routine // // This behavior is subject to change in v2. - coerce('autosize', !(layoutIn.width && layoutIn.height)); + coerce('autosize', !(layoutIn.width && layoutIn.height)) - coerce('width'); - coerce('height'); - coerce('margin.l'); - coerce('margin.r'); - coerce('margin.t'); - coerce('margin.b'); - coerce('margin.pad'); - coerce('margin.autoexpand'); + coerce('width') + coerce('height') + coerce('margin.l') + coerce('margin.r') + coerce('margin.t') + coerce('margin.b') + coerce('margin.pad') + coerce('margin.autoexpand') - if(layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut); + if (layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut) - coerce('paper_bgcolor'); + coerce('paper_bgcolor') - coerce('separators'); - coerce('hidesources'); - coerce('smith'); + coerce('separators') + coerce('hidesources') + coerce('smith') - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); - handleCalendarDefaults(layoutIn, layoutOut, 'calendar'); -}; + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults') + handleCalendarDefaults(layoutIn, layoutOut, 'calendar') +} -plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) { - var context = gd._context || {}, - frameMargins = context.frameMargins, - newWidth, - newHeight; +plots.plotAutoSize = function plotAutoSize (gd, layout, fullLayout) { + var context = gd._context || {}, + frameMargins = context.frameMargins, + newWidth, + newHeight - var isPlotDiv = Lib.isPlotDiv(gd); + var isPlotDiv = Lib.isPlotDiv(gd) - if(isPlotDiv) gd.emit('plotly_autosize'); + if (isPlotDiv) gd.emit('plotly_autosize') // embedded in an iframe - just take the full iframe size // if we get to this point, with no aspect ratio restrictions - if(context.fillFrame) { - newWidth = window.innerWidth; - newHeight = window.innerHeight; + if (context.fillFrame) { + newWidth = window.innerWidth + newHeight = window.innerHeight // somehow we get a few extra px height sometimes... // just hide it - document.body.style.overflow = 'hidden'; - } - else if(isNumeric(frameMargins) && frameMargins > 0) { - var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins), - reservedWidth = reservedMargins.left + reservedMargins.right, - reservedHeight = reservedMargins.bottom + reservedMargins.top, - factor = 1 - 2 * frameMargins; - - var gdBB = fullLayout._container && fullLayout._container.node ? + document.body.style.overflow = 'hidden' + } else if (isNumeric(frameMargins) && frameMargins > 0) { + var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins), + reservedWidth = reservedMargins.left + reservedMargins.right, + reservedHeight = reservedMargins.bottom + reservedMargins.top, + factor = 1 - 2 * frameMargins + + var gdBB = fullLayout._container && fullLayout._container.node ? fullLayout._container.node().getBoundingClientRect() : { - width: fullLayout.width, - height: fullLayout.height - }; + width: fullLayout.width, + height: fullLayout.height + } - newWidth = Math.round(factor * (gdBB.width - reservedWidth)); - newHeight = Math.round(factor * (gdBB.height - reservedHeight)); - } - else { + newWidth = Math.round(factor * (gdBB.width - reservedWidth)) + newHeight = Math.round(factor * (gdBB.height - reservedHeight)) + } else { // plotly.js - let the developers do what they want, either // provide height and width for the container div, // specify size in layout, or take the defaults, // but don't enforce any ratio restrictions - var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {}; + var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {} - newWidth = parseFloat(computedStyle.width) || fullLayout.width; - newHeight = parseFloat(computedStyle.height) || fullLayout.height; - } + newWidth = parseFloat(computedStyle.width) || fullLayout.width + newHeight = parseFloat(computedStyle.height) || fullLayout.height + } - var minWidth = plots.layoutAttributes.width.min, - minHeight = plots.layoutAttributes.height.min; - if(newWidth < minWidth) newWidth = minWidth; - if(newHeight < minHeight) newHeight = minHeight; + var minWidth = plots.layoutAttributes.width.min, + minHeight = plots.layoutAttributes.height.min + if (newWidth < minWidth) newWidth = minWidth + if (newHeight < minHeight) newHeight = minHeight - var widthHasChanged = !layout.width && + var widthHasChanged = !layout.width && (Math.abs(fullLayout.width - newWidth) > 1), - heightHasChanged = !layout.height && - (Math.abs(fullLayout.height - newHeight) > 1); + heightHasChanged = !layout.height && + (Math.abs(fullLayout.height - newHeight) > 1) - if(heightHasChanged || widthHasChanged) { - if(widthHasChanged) fullLayout.width = newWidth; - if(heightHasChanged) fullLayout.height = newHeight; - } + if (heightHasChanged || widthHasChanged) { + if (widthHasChanged) fullLayout.width = newWidth + if (heightHasChanged) fullLayout.height = newHeight + } // cache initial autosize value, used in relayout when // width or height values are set to null - if(!gd._initialAutoSize) { - gd._initialAutoSize = { width: newWidth, height: newHeight }; - } + if (!gd._initialAutoSize) { + gd._initialAutoSize = { width: newWidth, height: newHeight } + } - plots.sanitizeMargins(fullLayout); -}; + plots.sanitizeMargins(fullLayout) +} /** * Reduce all reserved margin objects to a single required margin reservation. @@ -1051,291 +1030,290 @@ plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) { * @param {Object} margins * @returns {{left: number, right: number, bottom: number, top: number}} */ -function calculateReservedMargins(margins) { - var resultingMargin = {left: 0, right: 0, bottom: 0, top: 0}, - marginName; - - if(margins) { - for(marginName in margins) { - if(margins.hasOwnProperty(marginName)) { - resultingMargin.left += margins[marginName].left || 0; - resultingMargin.right += margins[marginName].right || 0; - resultingMargin.bottom += margins[marginName].bottom || 0; - resultingMargin.top += margins[marginName].top || 0; - } - } - } - return resultingMargin; +function calculateReservedMargins (margins) { + var resultingMargin = {left: 0, right: 0, bottom: 0, top: 0}, + marginName + + if (margins) { + for (marginName in margins) { + if (margins.hasOwnProperty(marginName)) { + resultingMargin.left += margins[marginName].left || 0 + resultingMargin.right += margins[marginName].right || 0 + resultingMargin.bottom += margins[marginName].bottom || 0 + resultingMargin.top += margins[marginName].top || 0 + } + } + } + return resultingMargin } -plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) { - var i, _module; +plots.supplyLayoutModuleDefaults = function (layoutIn, layoutOut, fullData, transitionData) { + var i, _module // can't be be part of basePlotModules loop // in order to handle the orphan axes case - Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData); + Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData) // base plot module layout defaults - var basePlotModules = layoutOut._basePlotModules; - for(i = 0; i < basePlotModules.length; i++) { - _module = basePlotModules[i]; + var basePlotModules = layoutOut._basePlotModules + for (i = 0; i < basePlotModules.length; i++) { + _module = basePlotModules[i] // done above already - if(_module.name === 'cartesian') continue; + if (_module.name === 'cartesian') continue // e.g. gl2d does not have a layout-defaults step - if(_module.supplyLayoutDefaults) { - _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); - } + if (_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData) } + } // trace module layout defaults - var modules = layoutOut._modules; - for(i = 0; i < modules.length; i++) { - _module = modules[i]; + var modules = layoutOut._modules + for (i = 0; i < modules.length; i++) { + _module = modules[i] - if(_module.supplyLayoutDefaults) { - _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); - } + if (_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData) } + } // transform module layout defaults - var transformModules = layoutOut._transformModules; - for(i = 0; i < transformModules.length; i++) { - _module = transformModules[i]; + var transformModules = layoutOut._transformModules + for (i = 0; i < transformModules.length; i++) { + _module = transformModules[i] - if(_module.supplyLayoutDefaults) { - _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData); - } + if (_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData) } + } // should FX be a component? - Plotly.Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData); + Plotly.Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData) - var components = Object.keys(Registry.componentsRegistry); - for(i = 0; i < components.length; i++) { - _module = Registry.componentsRegistry[components[i]]; + var components = Object.keys(Registry.componentsRegistry) + for (i = 0; i < components.length; i++) { + _module = Registry.componentsRegistry[components[i]] - if(_module.supplyLayoutDefaults) { - _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); - } + if (_module.supplyLayoutDefaults) { + _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData) } -}; + } +} // Remove all plotly attributes from a div so it can be replotted fresh // TODO: these really need to be encapsulated into a much smaller set... -plots.purge = function(gd) { - +plots.purge = function (gd) { // note: we DO NOT remove _context because it doesn't change when we insert // a new plot, and may have been set outside of our scope. - var fullLayout = gd._fullLayout || {}; - if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove(); - if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove(); + var fullLayout = gd._fullLayout || {} + if (fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove() + if (fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove() // remove modebar - if(fullLayout._modeBar) fullLayout._modeBar.destroy(); + if (fullLayout._modeBar) fullLayout._modeBar.destroy() - if(gd._transitionData) { + if (gd._transitionData) { // Ensure any dangling callbacks are simply dropped if the plot is purged. // This is more or less only actually important for testing. - if(gd._transitionData._interruptCallbacks) { - gd._transitionData._interruptCallbacks.length = 0; - } + if (gd._transitionData._interruptCallbacks) { + gd._transitionData._interruptCallbacks.length = 0 + } - if(gd._transitionData._animationRaf) { - window.cancelAnimationFrame(gd._transitionData._animationRaf); - } + if (gd._transitionData._animationRaf) { + window.cancelAnimationFrame(gd._transitionData._animationRaf) } + } // data and layout - delete gd.data; - delete gd.layout; - delete gd._fullData; - delete gd._fullLayout; - delete gd.calcdata; - delete gd.framework; - delete gd.empty; + delete gd.data + delete gd.layout + delete gd._fullData + delete gd._fullLayout + delete gd.calcdata + delete gd.framework + delete gd.empty - delete gd.fid; + delete gd.fid - delete gd.undoqueue; // action queue - delete gd.undonum; - delete gd.autoplay; // are we doing an action that doesn't go in undo queue? - delete gd.changed; + delete gd.undoqueue // action queue + delete gd.undonum + delete gd.autoplay // are we doing an action that doesn't go in undo queue? + delete gd.changed // these get recreated on Plotly.plot anyway, but just to be safe // (and to have a record of them...) - delete gd._tester; - delete gd._testref; - delete gd._promises; - delete gd._redrawTimer; - delete gd._replotting; - delete gd.firstscatter; - delete gd.hmlumcount; - delete gd.hmpixcount; - delete gd.numboxes; - delete gd._hoverTimer; - delete gd._lastHoverTime; - delete gd._transitionData; - delete gd._transitioning; - delete gd._initialAutoSize; + delete gd._tester + delete gd._testref + delete gd._promises + delete gd._redrawTimer + delete gd._replotting + delete gd.firstscatter + delete gd.hmlumcount + delete gd.hmpixcount + delete gd.numboxes + delete gd._hoverTimer + delete gd._lastHoverTime + delete gd._transitionData + delete gd._transitioning + delete gd._initialAutoSize // remove all event listeners - if(gd.removeAllListeners) gd.removeAllListeners(); -}; + if (gd.removeAllListeners) gd.removeAllListeners() +} -plots.style = function(gd) { - var _modules = gd._fullLayout._modules; +plots.style = function (gd) { + var _modules = gd._fullLayout._modules - for(var i = 0; i < _modules.length; i++) { - var _module = _modules[i]; + for (var i = 0; i < _modules.length; i++) { + var _module = _modules[i] - if(_module.style) _module.style(gd); - } -}; + if (_module.style) _module.style(gd) + } +} -plots.sanitizeMargins = function(fullLayout) { +plots.sanitizeMargins = function (fullLayout) { // polar doesn't do margins... - if(!fullLayout || !fullLayout.margin) return; + if (!fullLayout || !fullLayout.margin) return - var width = fullLayout.width, - height = fullLayout.height, - margin = fullLayout.margin, - plotWidth = width - (margin.l + margin.r), - plotHeight = height - (margin.t + margin.b), - correction; + var width = fullLayout.width, + height = fullLayout.height, + margin = fullLayout.margin, + plotWidth = width - (margin.l + margin.r), + plotHeight = height - (margin.t + margin.b), + correction // if margin.l + margin.r = 0 then plotWidth > 0 // as width >= 10 by supplyDefaults // similarly for margin.t + margin.b - if(plotWidth < 0) { - correction = (width - 1) / (margin.l + margin.r); - margin.l = Math.floor(correction * margin.l); - margin.r = Math.floor(correction * margin.r); - } - - if(plotHeight < 0) { - correction = (height - 1) / (margin.t + margin.b); - margin.t = Math.floor(correction * margin.t); - margin.b = Math.floor(correction * margin.b); - } -}; + if (plotWidth < 0) { + correction = (width - 1) / (margin.l + margin.r) + margin.l = Math.floor(correction * margin.l) + margin.r = Math.floor(correction * margin.r) + } + + if (plotHeight < 0) { + correction = (height - 1) / (margin.t + margin.b) + margin.t = Math.floor(correction * margin.t) + margin.b = Math.floor(correction * margin.b) + } +} // called by legend and colorbar routines to see if we need to // expand the margins to show them // o is {x,l,r,y,t,b} where x and y are plot fractions, // the rest are pixels in each direction // or leave o out to delete this entry (like if it's hidden) -plots.autoMargin = function(gd, id, o) { - var fullLayout = gd._fullLayout; +plots.autoMargin = function (gd, id, o) { + var fullLayout = gd._fullLayout - if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; + if (!fullLayout._pushmargin) fullLayout._pushmargin = {} - if(fullLayout.margin.autoexpand !== false) { - if(!o) delete fullLayout._pushmargin[id]; - else { - var pad = o.pad === undefined ? 12 : o.pad; + if (fullLayout.margin.autoexpand !== false) { + if (!o) delete fullLayout._pushmargin[id] + else { + var pad = o.pad === undefined ? 12 : o.pad // if the item is too big, just give it enough automargin to // make sure you can still grab it and bring it back - if(o.l + o.r > fullLayout.width * 0.5) o.l = o.r = 0; - if(o.b + o.t > fullLayout.height * 0.5) o.b = o.t = 0; - - fullLayout._pushmargin[id] = { - l: {val: o.x, size: o.l + pad}, - r: {val: o.x, size: o.r + pad}, - b: {val: o.y, size: o.b + pad}, - t: {val: o.y, size: o.t + pad} - }; - } + if (o.l + o.r > fullLayout.width * 0.5) o.l = o.r = 0 + if (o.b + o.t > fullLayout.height * 0.5) o.b = o.t = 0 - if(!gd._replotting) plots.doAutoMargin(gd); + fullLayout._pushmargin[id] = { + l: {val: o.x, size: o.l + pad}, + r: {val: o.x, size: o.r + pad}, + b: {val: o.y, size: o.b + pad}, + t: {val: o.y, size: o.t + pad} + } } -}; -plots.doAutoMargin = function(gd) { - var fullLayout = gd._fullLayout; - if(!fullLayout._size) fullLayout._size = {}; - if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; + if (!gd._replotting) plots.doAutoMargin(gd) + } +} + +plots.doAutoMargin = function (gd) { + var fullLayout = gd._fullLayout + if (!fullLayout._size) fullLayout._size = {} + if (!fullLayout._pushmargin) fullLayout._pushmargin = {} - var gs = fullLayout._size, - oldmargins = JSON.stringify(gs); + var gs = fullLayout._size, + oldmargins = JSON.stringify(gs) // adjust margins for outside legends and colorbars // fullLayout.margin is the requested margin, // fullLayout._size has margins and plotsize after adjustment - var ml = Math.max(fullLayout.margin.l || 0, 0), - mr = Math.max(fullLayout.margin.r || 0, 0), - mt = Math.max(fullLayout.margin.t || 0, 0), - mb = Math.max(fullLayout.margin.b || 0, 0), - pm = fullLayout._pushmargin; + var ml = Math.max(fullLayout.margin.l || 0, 0), + mr = Math.max(fullLayout.margin.r || 0, 0), + mt = Math.max(fullLayout.margin.t || 0, 0), + mb = Math.max(fullLayout.margin.b || 0, 0), + pm = fullLayout._pushmargin - if(fullLayout.margin.autoexpand !== false) { + if (fullLayout.margin.autoexpand !== false) { // fill in the requested margins - pm.base = { - l: {val: 0, size: ml}, - r: {val: 1, size: mr}, - t: {val: 1, size: mt}, - b: {val: 0, size: mb} - }; + pm.base = { + l: {val: 0, size: ml}, + r: {val: 1, size: mr}, + t: {val: 1, size: mt}, + b: {val: 0, size: mb} + } // now cycle through all the combinations of l and r // (and t and b) to find the required margins - Object.keys(pm).forEach(function(k1) { - var pushleft = pm[k1].l || {}, - pushbottom = pm[k1].b || {}, - fl = pushleft.val, - pl = pushleft.size, - fb = pushbottom.val, - pb = pushbottom.size; - Object.keys(pm).forEach(function(k2) { - if(isNumeric(pl) && pm[k2].r) { - var fr = pm[k2].r.val, - pr = pm[k2].r.size; - if(fr > fl) { - var newl = (pl * fr + + Object.keys(pm).forEach(function (k1) { + var pushleft = pm[k1].l || {}, + pushbottom = pm[k1].b || {}, + fl = pushleft.val, + pl = pushleft.size, + fb = pushbottom.val, + pb = pushbottom.size + Object.keys(pm).forEach(function (k2) { + if (isNumeric(pl) && pm[k2].r) { + var fr = pm[k2].r.val, + pr = pm[k2].r.size + if (fr > fl) { + var newl = (pl * fr + (pr - fullLayout.width) * fl) / (fr - fl), - newr = (pr * (1 - fl) + - (pl - fullLayout.width) * (1 - fr)) / (fr - fl); - if(newl >= 0 && newr >= 0 && newl + newr > ml + mr) { - ml = newl; - mr = newr; - } - } - } - if(isNumeric(pb) && pm[k2].t) { - var ft = pm[k2].t.val, - pt = pm[k2].t.size; - if(ft > fb) { - var newb = (pb * ft + + newr = (pr * (1 - fl) + + (pl - fullLayout.width) * (1 - fr)) / (fr - fl) + if (newl >= 0 && newr >= 0 && newl + newr > ml + mr) { + ml = newl + mr = newr + } + } + } + if (isNumeric(pb) && pm[k2].t) { + var ft = pm[k2].t.val, + pt = pm[k2].t.size + if (ft > fb) { + var newb = (pb * ft + (pt - fullLayout.height) * fb) / (ft - fb), - newt = (pt * (1 - fb) + - (pb - fullLayout.height) * (1 - ft)) / (ft - fb); - if(newb >= 0 && newt >= 0 && newb + newt > mb + mt) { - mb = newb; - mt = newt; - } - } - } - }); - }); - } - - gs.l = Math.round(ml); - gs.r = Math.round(mr); - gs.t = Math.round(mt); - gs.b = Math.round(mb); - gs.p = Math.round(fullLayout.margin.pad); - gs.w = Math.round(fullLayout.width) - gs.l - gs.r; - gs.h = Math.round(fullLayout.height) - gs.t - gs.b; + newt = (pt * (1 - fb) + + (pb - fullLayout.height) * (1 - ft)) / (ft - fb) + if (newb >= 0 && newt >= 0 && newb + newt > mb + mt) { + mb = newb + mt = newt + } + } + } + }) + }) + } + + gs.l = Math.round(ml) + gs.r = Math.round(mr) + gs.t = Math.round(mt) + gs.b = Math.round(mb) + gs.p = Math.round(fullLayout.margin.pad) + gs.w = Math.round(fullLayout.width) - gs.l - gs.r + gs.h = Math.round(fullLayout.height) - gs.t - gs.b // if things changed and we're not already redrawing, trigger a redraw - if(!gd._replotting && oldmargins !== '{}' && + if (!gd._replotting && oldmargins !== '{}' && oldmargins !== JSON.stringify(fullLayout._size)) { - return Plotly.plot(gd); - } -}; + return Plotly.plot(gd) + } +} /** * JSONify the graph data and layout @@ -1359,92 +1337,90 @@ plots.doAutoMargin = function(gd) { * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData * @returns {Object|String} */ -plots.graphJson = function(gd, dataonly, mode, output, useDefaults) { +plots.graphJson = function (gd, dataonly, mode, output, useDefaults) { // if the defaults aren't supplied yet, we need to do that... - if((useDefaults && dataonly && !gd._fullData) || + if ((useDefaults && dataonly && !gd._fullData) || (useDefaults && !dataonly && !gd._fullLayout)) { - plots.supplyDefaults(gd); - } + plots.supplyDefaults(gd) + } - var data = (useDefaults) ? gd._fullData : gd.data, - layout = (useDefaults) ? gd._fullLayout : gd.layout, - frames = (gd._transitionData || {})._frames; + var data = (useDefaults) ? gd._fullData : gd.data, + layout = (useDefaults) ? gd._fullLayout : gd.layout, + frames = (gd._transitionData || {})._frames - function stripObj(d) { - if(typeof d === 'function') { - return null; - } - if(Lib.isPlainObject(d)) { - var o = {}, v, src; - for(v in d) { + function stripObj (d) { + if (typeof d === 'function') { + return null + } + if (Lib.isPlainObject(d)) { + var o = {}, v, src + for (v in d) { // remove private elements and functions // _ is for private, [ is a mistake ie [object Object] - if(typeof d[v] === 'function' || + if (typeof d[v] === 'function' || ['_', '['].indexOf(v.charAt(0)) !== -1) { - continue; - } + continue + } // look for src/data matches and remove the appropriate one - if(mode === 'keepdata') { + if (mode === 'keepdata') { // keepdata: remove all ...src tags - if(v.substr(v.length - 3) === 'src') { - continue; - } - } - else if(mode === 'keepstream') { + if (v.substr(v.length - 3) === 'src') { + continue + } + } else if (mode === 'keepstream') { // keep sourced data if it's being streamed. // similar to keepref, but if the 'stream' object exists // in a trace, we will keep the data array. - src = d[v + 'src']; - if(typeof src === 'string' && src.indexOf(':') > 0) { - if(!Lib.isPlainObject(d.stream)) { - continue; - } - } - } - else if(mode !== 'keepall') { + src = d[v + 'src'] + if (typeof src === 'string' && src.indexOf(':') > 0) { + if (!Lib.isPlainObject(d.stream)) { + continue + } + } + } else if (mode !== 'keepall') { // keepref: remove sourced data but only // if the source tag is well-formed - src = d[v + 'src']; - if(typeof src === 'string' && src.indexOf(':') > 0) { - continue; - } - } + src = d[v + 'src'] + if (typeof src === 'string' && src.indexOf(':') > 0) { + continue + } + } // OK, we're including this... recurse into it - o[v] = stripObj(d[v]); - } - return o; - } + o[v] = stripObj(d[v]) + } + return o + } - if(Array.isArray(d)) { - return d.map(stripObj); - } + if (Array.isArray(d)) { + return d.map(stripObj) + } // convert native dates to date strings... // mostly for external users exporting to plotly - if(Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d); + if (Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d) - return d; - } + return d + } - var obj = { - data: (data || []).map(function(v) { - var d = stripObj(v); + var obj = { + data: (data || []).map(function (v) { + var d = stripObj(v) // fit has some little arrays in it that don't contain data, // just fit params and meta - if(dataonly) { delete d.fit; } - return d; - }) - }; - if(!dataonly) { obj.layout = stripObj(layout); } + if (dataonly) { delete d.fit } + return d + }) + } + if (!dataonly) { obj.layout = stripObj(layout) } - if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig(); + if (gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig() - if(frames) obj.frames = stripObj(frames); + if (frames) obj.frames = stripObj(frames) - return (output === 'object') ? obj : JSON.stringify(obj); -}; + return (output === 'object') ? obj : JSON.stringify(obj) +} /** * Modify a keyframe using a list of operations: @@ -1452,51 +1428,51 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) { * @param {array of objects} operations * Sequence of operations to be performed on the keyframes */ -plots.modifyFrames = function(gd, operations) { - var i, op, frame; - var _frames = gd._transitionData._frames; - var _hash = gd._transitionData._frameHash; +plots.modifyFrames = function (gd, operations) { + var i, op, frame + var _frames = gd._transitionData._frames + var _hash = gd._transitionData._frameHash - for(i = 0; i < operations.length; i++) { - op = operations[i]; + for (i = 0; i < operations.length; i++) { + op = operations[i] - switch(op.type) { + switch (op.type) { // No reason this couldn't exist, but is currently unused/untested: /* case 'rename': frame = _frames[op.index]; delete _hash[frame.name]; _hash[op.name] = frame; frame.name = op.name; - break;*/ - case 'replace': - frame = op.value; - var oldName = (_frames[op.index] || {}).name; - var newName = frame.name; - _frames[op.index] = _hash[newName] = frame; - - if(newName !== oldName) { + break; */ + case 'replace': + frame = op.value + var oldName = (_frames[op.index] || {}).name + var newName = frame.name + _frames[op.index] = _hash[newName] = frame + + if (newName !== oldName) { // If name has changed in addition to replacement, then update // the lookup table: - delete _hash[oldName]; - _hash[newName] = frame; - } - - break; - case 'insert': - frame = op.value; - _hash[frame.name] = frame; - _frames.splice(op.index, 0, frame); - break; - case 'delete': - frame = _frames[op.index]; - delete _hash[frame.name]; - _frames.splice(op.index, 1); - break; + delete _hash[oldName] + _hash[newName] = frame } - } - return Promise.resolve(); -}; + break + case 'insert': + frame = op.value + _hash[frame.name] = frame + _frames.splice(op.index, 0, frame) + break + case 'delete': + frame = _frames[op.index] + delete _hash[frame.name] + _frames.splice(op.index, 1) + break + } + } + + return Promise.resolve() +} /* * Compute a keyframe. Merge a keyframe into its base frame(s) and @@ -1509,9 +1485,9 @@ plots.modifyFrames = function(gd, operations) { * * Returns: a new object with the merged content */ -plots.computeFrame = function(gd, frameName) { - var frameLookup = gd._transitionData._frameHash; - var i, traceIndices, traceIndex, destIndex; +plots.computeFrame = function (gd, frameName) { + var frameLookup = gd._transitionData._frameHash + var i, traceIndices, traceIndex, destIndex // Null or undefined will fail on .toString(). We'll allow numbers since we // make it clear frames must be given string names, but we'll allow numbers @@ -1519,77 +1495,77 @@ plots.computeFrame = function(gd, frameName) { // properly cast to strings. We really just want to ensure here that this // 1) doesn't fail, and // 2) doens't give an incorrect answer (which String(frameName) would) - if(!frameName) { - throw new Error('computeFrame must be given a string frame name'); - } + if (!frameName) { + throw new Error('computeFrame must be given a string frame name') + } - var framePtr = frameLookup[frameName.toString()]; + var framePtr = frameLookup[frameName.toString()] // Return false if the name is invalid: - if(!framePtr) { - return false; - } + if (!framePtr) { + return false + } - var frameStack = [framePtr]; - var frameNameStack = [framePtr.name]; + var frameStack = [framePtr] + var frameNameStack = [framePtr.name] // Follow frame pointers: - while(framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) { + while (framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) { // Avoid infinite loops: - if(frameNameStack.indexOf(framePtr.name) !== -1) break; + if (frameNameStack.indexOf(framePtr.name) !== -1) break - frameStack.push(framePtr); - frameNameStack.push(framePtr.name); - } + frameStack.push(framePtr) + frameNameStack.push(framePtr.name) + } // A new object for the merged result: - var result = {}; + var result = {} // Merge, starting with the last and ending with the desired frame: - while((framePtr = frameStack.pop())) { - if(framePtr.layout) { - result.layout = plots.extendLayout(result.layout, framePtr.layout); - } + while ((framePtr = frameStack.pop())) { + if (framePtr.layout) { + result.layout = plots.extendLayout(result.layout, framePtr.layout) + } - if(framePtr.data) { - if(!result.data) { - result.data = []; - } - traceIndices = framePtr.traces; + if (framePtr.data) { + if (!result.data) { + result.data = [] + } + traceIndices = framePtr.traces - if(!traceIndices) { + if (!traceIndices) { // If not defined, assume serial order starting at zero - traceIndices = []; - for(i = 0; i < framePtr.data.length; i++) { - traceIndices[i] = i; - } - } + traceIndices = [] + for (i = 0; i < framePtr.data.length; i++) { + traceIndices[i] = i + } + } - if(!result.traces) { - result.traces = []; - } + if (!result.traces) { + result.traces = [] + } - for(i = 0; i < framePtr.data.length; i++) { + for (i = 0; i < framePtr.data.length; i++) { // Loop through this frames data, find out where it should go, // and merge it! - traceIndex = traceIndices[i]; - if(traceIndex === undefined || traceIndex === null) { - continue; - } - - destIndex = result.traces.indexOf(traceIndex); - if(destIndex === -1) { - destIndex = result.data.length; - result.traces[destIndex] = traceIndex; - } - - result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]); - } + traceIndex = traceIndices[i] + if (traceIndex === undefined || traceIndex === null) { + continue } + + destIndex = result.traces.indexOf(traceIndex) + if (destIndex === -1) { + destIndex = result.data.length + result.traces[destIndex] = traceIndex + } + + result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]) + } } + } - return result; -}; + return result +} /* * Recompute the lookup table that maps frame name -> frame object. addFrames/ @@ -1597,16 +1573,16 @@ plots.computeFrame = function(gd, frameName) { * is necessary is if you poke around manually in `gd._transitionData._frames` * and create and haven't updated the lookup table. */ -plots.recomputeFrameHash = function(gd) { - var hash = gd._transitionData._frameHash = {}; - var frames = gd._transitionData._frames; - for(var i = 0; i < frames.length; i++) { - var frame = frames[i]; - if(frame && frame.name) { - hash[frame.name] = frame; - } - } -}; +plots.recomputeFrameHash = function (gd) { + var hash = gd._transitionData._frameHash = {} + var frames = gd._transitionData._frames + for (var i = 0; i < frames.length; i++) { + var frame = frames[i] + if (frame && frame.name) { + hash[frame.name] = frame + } + } +} /** * Extend an object, treating container arrays very differently by extracting @@ -1618,65 +1594,64 @@ plots.recomputeFrameHash = function(gd) { * * See extendTrace and extendLayout below for usage. */ -plots.extendObjectWithContainers = function(dest, src, containerPaths) { - var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer; - var copy = Lib.extendDeepNoArrays({}, src || {}); - var expandedObj = Lib.expandObjectPaths(copy); - var containerObj = {}; +plots.extendObjectWithContainers = function (dest, src, containerPaths) { + var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer + var copy = Lib.extendDeepNoArrays({}, src || {}) + var expandedObj = Lib.expandObjectPaths(copy) + var containerObj = {} // Step through and extract any container properties. Otherwise extendDeepNoArrays // will clobber any existing properties with an empty array and then supplyDefaults // will reset everything to defaults. - if(containerPaths && containerPaths.length) { - for(i = 0; i < containerPaths.length; i++) { - containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]); - containerVal = containerProp.get(); + if (containerPaths && containerPaths.length) { + for (i = 0; i < containerPaths.length; i++) { + containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]) + containerVal = containerProp.get() - if(containerVal === undefined) { - Lib.nestedProperty(containerObj, containerPaths[i]).set(null); - } - else { - containerProp.set(null); - Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); - } - } + if (containerVal === undefined) { + Lib.nestedProperty(containerObj, containerPaths[i]).set(null) + } else { + containerProp.set(null) + Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal) + } } + } - dest = Lib.extendDeepNoArrays(dest || {}, expandedObj); + dest = Lib.extendDeepNoArrays(dest || {}, expandedObj) - if(containerPaths && containerPaths.length) { - for(i = 0; i < containerPaths.length; i++) { - srcProp = Lib.nestedProperty(containerObj, containerPaths[i]); - srcContainer = srcProp.get(); + if (containerPaths && containerPaths.length) { + for (i = 0; i < containerPaths.length; i++) { + srcProp = Lib.nestedProperty(containerObj, containerPaths[i]) + srcContainer = srcProp.get() - if(!srcContainer) continue; + if (!srcContainer) continue - destProp = Lib.nestedProperty(dest, containerPaths[i]); - destContainer = destProp.get(); - - if(!Array.isArray(destContainer)) { - destContainer = []; - destProp.set(destContainer); - } + destProp = Lib.nestedProperty(dest, containerPaths[i]) + destContainer = destProp.get() - for(j = 0; j < srcContainer.length; j++) { - var srcObj = srcContainer[j]; + if (!Array.isArray(destContainer)) { + destContainer = [] + destProp.set(destContainer) + } - if(srcObj === null) destContainer[j] = null; - else { - destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj); - } - } + for (j = 0; j < srcContainer.length; j++) { + var srcObj = srcContainer[j] - destProp.set(destContainer); + if (srcObj === null) destContainer[j] = null + else { + destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj) } + } + + destProp.set(destContainer) } + } - return dest; -}; + return dest +} -plots.dataArrayContainers = ['transforms']; -plots.layoutArrayContainers = Registry.layoutArrayContainers; +plots.dataArrayContainers = ['transforms'] +plots.layoutArrayContainers = Registry.layoutArrayContainers /* * Extend a trace definition. This method: @@ -1686,9 +1661,9 @@ plots.layoutArrayContainers = Registry.layoutArrayContainers; * * The result is the original object reference with the new contents merged in. */ -plots.extendTrace = function(destTrace, srcTrace) { - return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers); -}; +plots.extendTrace = function (destTrace, srcTrace) { + return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers) +} /* * Extend a layout definition. This method: @@ -1699,9 +1674,9 @@ plots.extendTrace = function(destTrace, srcTrace) { * * The result is the original object reference with the new contents merged in. */ -plots.extendLayout = function(destLayout, srcLayout) { - return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers); -}; +plots.extendLayout = function (destLayout, srcLayout) { + return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers) +} /** * Transition to a set of new data and layout properties @@ -1719,322 +1694,319 @@ plots.extendLayout = function(destLayout, srcLayout) { * @param {Object} transitionOpts * options for the transition */ -plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) { - var i, traceIdx; +plots.transition = function (gd, data, layout, traces, frameOpts, transitionOpts) { + var i, traceIdx - var dataLength = Array.isArray(data) ? data.length : 0; - var traceIndices = traces.slice(0, dataLength); + var dataLength = Array.isArray(data) ? data.length : 0 + var traceIndices = traces.slice(0, dataLength) - var transitionedTraces = []; + var transitionedTraces = [] - function prepareTransitions() { - var i; + function prepareTransitions () { + var i - for(i = 0; i < traceIndices.length; i++) { - var traceIdx = traceIndices[i]; - var trace = gd._fullData[traceIdx]; - var module = trace._module; + for (i = 0; i < traceIndices.length; i++) { + var traceIdx = traceIndices[i] + var trace = gd._fullData[traceIdx] + var module = trace._module // There's nothing to do if this module is not defined: - if(!module) continue; + if (!module) continue // Don't register the trace as transitioned if it doens't know what to do. // If it *is* registered, it will receive a callback that it's responsible // for calling in order to register the transition as having completed. - if(module.animatable) { - transitionedTraces.push(traceIdx); - } + if (module.animatable) { + transitionedTraces.push(traceIdx) + } - gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]); - } + gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]) + } // Follow the same procedure. Clone it so we don't mangle the input, then // expand any object paths so we can merge deep into gd.layout: - var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout)); + var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout)) // Before merging though, we need to modify the incoming layout. We only // know how to *transition* layout ranges, so it's imperative that a new // range not be sent to the layout before the transition has started. So // we must remove the things we can transition: - var axisAttrRe = /^[xy]axis[0-9]*$/; - for(var attr in layoutUpdate) { - if(!axisAttrRe.test(attr)) continue; - delete layoutUpdate[attr].range; - } + var axisAttrRe = /^[xy]axis[0-9]*$/ + for (var attr in layoutUpdate) { + if (!axisAttrRe.test(attr)) continue + delete layoutUpdate[attr].range + } - plots.extendLayout(gd.layout, layoutUpdate); + plots.extendLayout(gd.layout, layoutUpdate) // Supply defaults after applying the incoming properties. Note that any attempt // to simplify this step and reduce the amount of work resulted in the reconstruction // of essentially the whole supplyDefaults step, so that it seems sensible to just use // supplyDefaults even though it's heavier than would otherwise be desired for // transitions: - plots.supplyDefaults(gd); + plots.supplyDefaults(gd) - plots.doCalcdata(gd); + plots.doCalcdata(gd) - ErrorBars.calc(gd); + ErrorBars.calc(gd) - return Promise.resolve(); - } + return Promise.resolve() + } - function executeCallbacks(list) { - var p = Promise.resolve(); - if(!list) return p; - while(list.length) { - p = p.then((list.shift())); - } - return p; + function executeCallbacks (list) { + var p = Promise.resolve() + if (!list) return p + while (list.length) { + p = p.then((list.shift())) } + return p + } - function flushCallbacks(list) { - if(!list) return; - while(list.length) { - list.shift(); - } + function flushCallbacks (list) { + if (!list) return + while (list.length) { + list.shift() } + } - var aborted = false; + var aborted = false - function executeTransitions() { + function executeTransitions () { + gd.emit('plotly_transitioning', []) - gd.emit('plotly_transitioning', []); - - return new Promise(function(resolve) { + return new Promise(function (resolve) { // This flag is used to disabled things like autorange: - gd._transitioning = true; + gd._transitioning = true // When instantaneous updates are coming through quickly, it's too much to simply disable // all interaction, so store this flag so we can disambiguate whether mouse interactions // should be fully disabled or not: - if(transitionOpts.duration > 0) { - gd._transitioningWithDuration = true; - } - + if (transitionOpts.duration > 0) { + gd._transitioningWithDuration = true + } // If another transition is triggered, this callback will be executed simply because it's // in the interruptCallbacks queue. If this transition completes, it will instead flush // that queue and forget about this callback. - gd._transitionData._interruptCallbacks.push(function() { - aborted = true; - }); - - if(frameOpts.redraw) { - gd._transitionData._interruptCallbacks.push(function() { - return Plotly.redraw(gd); - }); - } + gd._transitionData._interruptCallbacks.push(function () { + aborted = true + }) + + if (frameOpts.redraw) { + gd._transitionData._interruptCallbacks.push(function () { + return Plotly.redraw(gd) + }) + } // Emit this and make sure it happens last: - gd._transitionData._interruptCallbacks.push(function() { - gd.emit('plotly_transitioninterrupted', []); - }); + gd._transitionData._interruptCallbacks.push(function () { + gd.emit('plotly_transitioninterrupted', []) + }) // Construct callbacks that are executed on transition end. This ensures the d3 transitions // are *complete* before anything else is done. - var numCallbacks = 0; - var numCompleted = 0; - function makeCallback() { - numCallbacks++; - return function() { - numCompleted++; + var numCallbacks = 0 + var numCompleted = 0 + function makeCallback () { + numCallbacks++ + return function () { + numCompleted++ // When all are complete, perform a redraw: - if(!aborted && numCompleted === numCallbacks) { - completeTransition(resolve); - } - }; - } - - var traceTransitionOpts; - var j; - var basePlotModules = gd._fullLayout._basePlotModules; - var hasAxisTransition = false; - - if(layout) { - for(j = 0; j < basePlotModules.length; j++) { - if(basePlotModules[j].transitionAxes) { - var newLayout = Lib.expandObjectPaths(layout); - hasAxisTransition = basePlotModules[j].transitionAxes(gd, newLayout, transitionOpts, makeCallback) || hasAxisTransition; - } - } - } + if (!aborted && numCompleted === numCallbacks) { + completeTransition(resolve) + } + } + } + + var traceTransitionOpts + var j + var basePlotModules = gd._fullLayout._basePlotModules + var hasAxisTransition = false + + if (layout) { + for (j = 0; j < basePlotModules.length; j++) { + if (basePlotModules[j].transitionAxes) { + var newLayout = Lib.expandObjectPaths(layout) + hasAxisTransition = basePlotModules[j].transitionAxes(gd, newLayout, transitionOpts, makeCallback) || hasAxisTransition + } + } + } // Here handle the exception that we refuse to animate scales and axes at the same // time. In other words, if there's an axis transition, then set the data transition // to instantaneous. - if(hasAxisTransition) { - traceTransitionOpts = Lib.extendFlat({}, transitionOpts); - traceTransitionOpts.duration = 0; - } else { - traceTransitionOpts = transitionOpts; - } - - for(j = 0; j < basePlotModules.length; j++) { + if (hasAxisTransition) { + traceTransitionOpts = Lib.extendFlat({}, transitionOpts) + traceTransitionOpts.duration = 0 + } else { + traceTransitionOpts = transitionOpts + } + + for (j = 0; j < basePlotModules.length; j++) { // Note that we pass a callback to *create* the callback that must be invoked on completion. // This is since not all traces know about transitions, so it greatly simplifies matters if // the trace is responsible for creating a callback, if needed, and then executing it when // the time is right. - basePlotModules[j].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback); - } + basePlotModules[j].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback) + } // If nothing else creates a callback, then this will trigger the completion in the next tick: - setTimeout(makeCallback()); - - }); - } + setTimeout(makeCallback()) + }) + } - function completeTransition(callback) { + function completeTransition (callback) { // This a simple workaround for tests which purge the graph before animations // have completed. That's not a very common case, so this is the simplest // fix. - if(!gd._transitionData) return; + if (!gd._transitionData) return - flushCallbacks(gd._transitionData._interruptCallbacks); + flushCallbacks(gd._transitionData._interruptCallbacks) - return Promise.resolve().then(function() { - if(frameOpts.redraw) { - return Plotly.redraw(gd); - } - }).then(function() { + return Promise.resolve().then(function () { + if (frameOpts.redraw) { + return Plotly.redraw(gd) + } + }).then(function () { // Set transitioning false again once the redraw has occurred. This is used, for example, // to prevent the trailing redraw from autoranging: - gd._transitioning = false; - gd._transitioningWithDuration = false; + gd._transitioning = false + gd._transitioningWithDuration = false - gd.emit('plotly_transitioned', []); - }).then(callback); - } + gd.emit('plotly_transitioned', []) + }).then(callback) + } - function interruptPreviousTransitions() { + function interruptPreviousTransitions () { // Fail-safe against purged plot: - if(!gd._transitionData) return; + if (!gd._transitionData) return // If a transition is interrupted, set this to false. At the moment, the only thing that would // interrupt a transition is another transition, so that it will momentarily be set to true // again, but this determines whether autorange or dragbox work, so it's for the sake of // cleanliness: - gd._transitioning = false; + gd._transitioning = false - return executeCallbacks(gd._transitionData._interruptCallbacks); - } + return executeCallbacks(gd._transitionData._interruptCallbacks) + } - for(i = 0; i < traceIndices.length; i++) { - traceIdx = traceIndices[i]; - var contFull = gd._fullData[traceIdx]; - var module = contFull._module; + for (i = 0; i < traceIndices.length; i++) { + traceIdx = traceIndices[i] + var contFull = gd._fullData[traceIdx] + var module = contFull._module - if(!module) continue; + if (!module) continue - if(!module.animatable) { - var thisUpdate = {}; + if (!module.animatable) { + var thisUpdate = {} - for(var ai in data[i]) { - thisUpdate[ai] = [data[i][ai]]; - } - } + for (var ai in data[i]) { + thisUpdate[ai] = [data[i][ai]] + } } + } - var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, executeTransitions]; + var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, executeTransitions] - var transitionStarting = Lib.syncOrAsync(seq, gd); + var transitionStarting = Lib.syncOrAsync(seq, gd) - if(!transitionStarting || !transitionStarting.then) { - transitionStarting = Promise.resolve(); - } + if (!transitionStarting || !transitionStarting.then) { + transitionStarting = Promise.resolve() + } - return transitionStarting.then(function() { - return gd; - }); -}; + return transitionStarting.then(function () { + return gd + }) +} -plots.doCalcdata = function(gd, traces) { - var axList = Plotly.Axes.list(gd), - fullData = gd._fullData, - fullLayout = gd._fullLayout; +plots.doCalcdata = function (gd, traces) { + var axList = Plotly.Axes.list(gd), + fullData = gd._fullData, + fullLayout = gd._fullLayout - var trace, _module, i, j; + var trace, _module, i, j // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without // *all* needing doCalcdata: - var calcdata = new Array(fullData.length); - var oldCalcdata = (gd.calcdata || []).slice(0); - gd.calcdata = calcdata; + var calcdata = new Array(fullData.length) + var oldCalcdata = (gd.calcdata || []).slice(0) + gd.calcdata = calcdata // extra helper variables // firstscatter: fill-to-next on the first trace goes to zero - gd.firstscatter = true; + gd.firstscatter = true // how many box plots do we have (in case they're grouped) - gd.numboxes = 0; + gd.numboxes = 0 // for calculating avg luminosity of heatmaps - gd._hmpixcount = 0; - gd._hmlumcount = 0; + gd._hmpixcount = 0 + gd._hmlumcount = 0 // for sharing colors across pies (and for legend) - fullLayout._piecolormap = {}; - fullLayout._piedefaultcolorcount = 0; + fullLayout._piecolormap = {} + fullLayout._piedefaultcolorcount = 0 // initialize the category list, if there is one, so we start over // to be filled in later by ax.d2c - for(i = 0; i < axList.length; i++) { - axList[i]._categories = axList[i]._initialCategories.slice(); - } + for (i = 0; i < axList.length; i++) { + axList[i]._categories = axList[i]._initialCategories.slice() + } // If traces were specified and this trace was not included, // then transfer it over from the old calcdata: - for(i = 0; i < fullData.length; i++) { - if(Array.isArray(traces) && traces.indexOf(i) === -1) { - calcdata[i] = oldCalcdata[i]; - continue; - } + for (i = 0; i < fullData.length; i++) { + if (Array.isArray(traces) && traces.indexOf(i) === -1) { + calcdata[i] = oldCalcdata[i] + continue } + } - var hasCalcTransform = false; + var hasCalcTransform = false // transform loop - for(i = 0; i < fullData.length; i++) { - trace = fullData[i]; + for (i = 0; i < fullData.length; i++) { + trace = fullData[i] - if(trace.visible === true && trace.transforms) { - _module = trace._module; + if (trace.visible === true && trace.transforms) { + _module = trace._module // we need one round of trace module calc before // the calc transform to 'fill in' the categories list // used for example in the data-to-coordinate method - if(_module && _module.calc) _module.calc(gd, trace); + if (_module && _module.calc) _module.calc(gd, trace) - for(j = 0; j < trace.transforms.length; j++) { - var transform = trace.transforms[j]; + for (j = 0; j < trace.transforms.length; j++) { + var transform = trace.transforms[j] - _module = transformsRegistry[transform.type]; - if(_module && _module.calcTransform) { - hasCalcTransform = true; - _module.calcTransform(gd, trace, transform); - } - } + _module = transformsRegistry[transform.type] + if (_module && _module.calcTransform) { + hasCalcTransform = true + _module.calcTransform(gd, trace, transform) } + } } + } // clear stuff that should recomputed in 'regular' loop - if(hasCalcTransform) { - for(i = 0; i < axList.length; i++) { - axList[i]._min = []; - axList[i]._max = []; - axList[i]._categories = []; - } + if (hasCalcTransform) { + for (i = 0; i < axList.length; i++) { + axList[i]._min = [] + axList[i]._max = [] + axList[i]._categories = [] } + } // 'regular' loop - for(i = 0; i < fullData.length; i++) { - var cd = []; + for (i = 0; i < fullData.length; i++) { + var cd = [] - trace = fullData[i]; + trace = fullData[i] - if(trace.visible === true) { - _module = trace._module; - if(_module && _module.calc) cd = _module.calc(gd, trace); - } + if (trace.visible === true) { + _module = trace._module + if (_module && _module.calc) cd = _module.calc(gd, trace) + } // Make sure there is a first point. // @@ -2043,80 +2015,80 @@ plots.doCalcdata = function(gd, traces) { // // Tag this artificial calc point with 'placeholder: true', // to make it easier to skip over them in during the plot and hover step. - if(!Array.isArray(cd) || !cd[0]) { - cd = [{x: false, y: false, placeholder: true}]; - } + if (!Array.isArray(cd) || !cd[0]) { + cd = [{x: false, y: false, placeholder: true}] + } // add the trace-wide properties to the first point, // per point properties to every point // t is the holder for trace-wide properties - if(!cd[0].t) cd[0].t = {}; - cd[0].trace = trace; - - calcdata[i] = cd; - } -}; + if (!cd[0].t) cd[0].t = {} + cd[0].trace = trace -plots.generalUpdatePerTraceModule = function(subplot, subplotCalcData, subplotLayout) { - var traceHashOld = subplot.traceHash, - traceHash = {}, - i; + calcdata[i] = cd + } +} - function filterVisible(calcDataIn) { - var calcDataOut = []; +plots.generalUpdatePerTraceModule = function (subplot, subplotCalcData, subplotLayout) { + var traceHashOld = subplot.traceHash, + traceHash = {}, + i - for(var i = 0; i < calcDataIn.length; i++) { - var calcTrace = calcDataIn[i], - trace = calcTrace[0].trace; + function filterVisible (calcDataIn) { + var calcDataOut = [] - if(trace.visible === true) calcDataOut.push(calcTrace); - } + for (var i = 0; i < calcDataIn.length; i++) { + var calcTrace = calcDataIn[i], + trace = calcTrace[0].trace - return calcDataOut; + if (trace.visible === true) calcDataOut.push(calcTrace) } + return calcDataOut + } + // build up moduleName -> calcData hash - for(i = 0; i < subplotCalcData.length; i++) { - var calcTraces = subplotCalcData[i], - trace = calcTraces[0].trace; + for (i = 0; i < subplotCalcData.length; i++) { + var calcTraces = subplotCalcData[i], + trace = calcTraces[0].trace // skip over visible === false traces // as they don't have `_module` ref - if(trace.visible) { - traceHash[trace.type] = traceHash[trace.type] || []; - traceHash[trace.type].push(calcTraces); - } + if (trace.visible) { + traceHash[trace.type] = traceHash[trace.type] || [] + traceHash[trace.type].push(calcTraces) } + } - var moduleNamesOld = Object.keys(traceHashOld); - var moduleNames = Object.keys(traceHash); + var moduleNamesOld = Object.keys(traceHashOld) + var moduleNames = Object.keys(traceHash) // when a trace gets deleted, make sure that its module's // plot method is called so that it is properly // removed from the DOM. - for(i = 0; i < moduleNamesOld.length; i++) { - var moduleName = moduleNamesOld[i]; + for (i = 0; i < moduleNamesOld.length; i++) { + var moduleName = moduleNamesOld[i] - if(moduleNames.indexOf(moduleName) === -1) { - var fakeCalcTrace = traceHashOld[moduleName][0], - fakeTrace = fakeCalcTrace[0].trace; + if (moduleNames.indexOf(moduleName) === -1) { + var fakeCalcTrace = traceHashOld[moduleName][0], + fakeTrace = fakeCalcTrace[0].trace - fakeTrace.visible = false; - traceHash[moduleName] = [fakeCalcTrace]; - } + fakeTrace.visible = false + traceHash[moduleName] = [fakeCalcTrace] } + } // update list of module names to include 'fake' traces added above - moduleNames = Object.keys(traceHash); + moduleNames = Object.keys(traceHash) // call module plot method - for(i = 0; i < moduleNames.length; i++) { - var moduleCalcData = traceHash[moduleNames[i]], - _module = moduleCalcData[0][0].trace._module; + for (i = 0; i < moduleNames.length; i++) { + var moduleCalcData = traceHash[moduleNames[i]], + _module = moduleCalcData[0][0].trace._module - _module.plot(subplot, filterVisible(moduleCalcData), subplotLayout); - } + _module.plot(subplot, filterVisible(moduleCalcData), subplotLayout) + } // update moduleName -> calcData hash - subplot.traceHash = traceHash; -}; + subplot.traceHash = traceHash +} diff --git a/src/plots/polar/area_attributes.js b/src/plots/polar/area_attributes.js index 4ae8b7edae9..45da3f0aab4 100644 --- a/src/plots/polar/area_attributes.js +++ b/src/plots/polar/area_attributes.js @@ -6,18 +6,18 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../../traces/scatter/attributes'); -var scatterMarkerAttrs = scatterAttrs.marker; +var scatterAttrs = require('../../traces/scatter/attributes') +var scatterMarkerAttrs = scatterAttrs.marker module.exports = { - r: scatterAttrs.r, - t: scatterAttrs.t, - marker: { - color: scatterMarkerAttrs.color, - size: scatterMarkerAttrs.size, - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity - } -}; + r: scatterAttrs.r, + t: scatterAttrs.t, + marker: { + color: scatterMarkerAttrs.color, + size: scatterMarkerAttrs.size, + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity + } +} diff --git a/src/plots/polar/axis_attributes.js b/src/plots/polar/axis_attributes.js index 681763d90b6..76063809554 100644 --- a/src/plots/polar/axis_attributes.js +++ b/src/plots/polar/axis_attributes.js @@ -6,143 +6,142 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var axesAttrs = require('../cartesian/layout_attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +var axesAttrs = require('../cartesian/layout_attributes') +var extendFlat = require('../../lib/extend').extendFlat var domainAttr = extendFlat({}, axesAttrs.domain, { - description: [ - 'Polar chart subplots are not supported yet.', - 'This key has currently no effect.' - ].join(' ') -}); + description: [ + 'Polar chart subplots are not supported yet.', + 'This key has currently no effect.' + ].join(' ') +}) -function mergeAttrs(axisName, nonCommonAttrs) { - var commonAttrs = { - showline: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not the line bounding this', - axisName, 'axis', - 'will be shown on the figure.' - ].join(' ') - }, - showticklabels: { - valType: 'boolean', - role: 'style', - description: [ - 'Determines whether or not the', - axisName, 'axis ticks', - 'will feature tick labels.' - ].join(' ') - }, - tickorientation: { - valType: 'enumerated', - values: ['horizontal', 'vertical'], - role: 'style', - description: [ - 'Sets the orientation (from the paper perspective)', - 'of the', axisName, 'axis tick labels.' - ].join(' ') - }, - ticklen: { - valType: 'number', - min: 0, - role: 'style', - description: [ - 'Sets the length of the tick lines on this', axisName, 'axis.' - ].join(' ') - }, - tickcolor: { - valType: 'color', - role: 'style', - description: [ - 'Sets the color of the tick lines on this', axisName, 'axis.' - ].join(' ') - }, - ticksuffix: { - valType: 'string', - role: 'style', - description: [ - 'Sets the length of the tick lines on this', axisName, 'axis.' - ].join(' ') - }, - endpadding: { - valType: 'number', - role: 'style' - }, - visible: { - valType: 'boolean', - role: 'info', - description: [ - 'Determines whether or not this axis will be visible.' - ].join(' ') - } - }; +function mergeAttrs (axisName, nonCommonAttrs) { + var commonAttrs = { + showline: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not the line bounding this', + axisName, 'axis', + 'will be shown on the figure.' + ].join(' ') + }, + showticklabels: { + valType: 'boolean', + role: 'style', + description: [ + 'Determines whether or not the', + axisName, 'axis ticks', + 'will feature tick labels.' + ].join(' ') + }, + tickorientation: { + valType: 'enumerated', + values: ['horizontal', 'vertical'], + role: 'style', + description: [ + 'Sets the orientation (from the paper perspective)', + 'of the', axisName, 'axis tick labels.' + ].join(' ') + }, + ticklen: { + valType: 'number', + min: 0, + role: 'style', + description: [ + 'Sets the length of the tick lines on this', axisName, 'axis.' + ].join(' ') + }, + tickcolor: { + valType: 'color', + role: 'style', + description: [ + 'Sets the color of the tick lines on this', axisName, 'axis.' + ].join(' ') + }, + ticksuffix: { + valType: 'string', + role: 'style', + description: [ + 'Sets the length of the tick lines on this', axisName, 'axis.' + ].join(' ') + }, + endpadding: { + valType: 'number', + role: 'style' + }, + visible: { + valType: 'boolean', + role: 'info', + description: [ + 'Determines whether or not this axis will be visible.' + ].join(' ') + } + } - return extendFlat({}, nonCommonAttrs, commonAttrs); + return extendFlat({}, nonCommonAttrs, commonAttrs) } module.exports = { - radialaxis: mergeAttrs('radial', { - range: { - valType: 'info_array', - role: 'info', - items: [ + radialaxis: mergeAttrs('radial', { + range: { + valType: 'info_array', + role: 'info', + items: [ { valType: 'number' }, { valType: 'number' } - ], - description: [ - 'Defines the start and end point of this radial axis.' - ].join(' ') - }, - domain: domainAttr, - orientation: { - valType: 'number', - role: 'style', - description: [ - 'Sets the orientation (an angle with respect to the origin)', - 'of the radial axis.' - ].join(' ') - } - }), + ], + description: [ + 'Defines the start and end point of this radial axis.' + ].join(' ') + }, + domain: domainAttr, + orientation: { + valType: 'number', + role: 'style', + description: [ + 'Sets the orientation (an angle with respect to the origin)', + 'of the radial axis.' + ].join(' ') + } + }), - angularaxis: mergeAttrs('angular', { - range: { - valType: 'info_array', - role: 'info', - items: [ + angularaxis: mergeAttrs('angular', { + range: { + valType: 'info_array', + role: 'info', + items: [ { valType: 'number', dflt: 0 }, { valType: 'number', dflt: 360 } - ], - description: [ - 'Defines the start and end point of this angular axis.' - ].join(' ') - }, - domain: domainAttr - }), + ], + description: [ + 'Defines the start and end point of this angular axis.' + ].join(' ') + }, + domain: domainAttr + }), // attributes that appear at layout root - layout: { - direction: { - valType: 'enumerated', - values: ['clockwise', 'counterclockwise'], - role: 'info', - description: [ - 'For polar plots only.', - 'Sets the direction corresponding to positive angles.' - ].join(' ') - }, - orientation: { - valType: 'angle', - role: 'info', - description: [ - 'For polar plots only.', - 'Rotates the entire polar by the given angle.' - ].join(' ') - } + layout: { + direction: { + valType: 'enumerated', + values: ['clockwise', 'counterclockwise'], + role: 'info', + description: [ + 'For polar plots only.', + 'Sets the direction corresponding to positive angles.' + ].join(' ') + }, + orientation: { + valType: 'angle', + role: 'info', + description: [ + 'For polar plots only.', + 'Rotates the entire polar by the given angle.' + ].join(' ') } -}; + } +} diff --git a/src/plots/polar/index.js b/src/plots/polar/index.js index 75ce1c99adb..c7672b1a407 100644 --- a/src/plots/polar/index.js +++ b/src/plots/polar/index.js @@ -6,8 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Polar = module.exports = require('./micropolar'); +var Polar = module.exports = require('./micropolar') -Polar.manager = require('./micropolar_manager'); +Polar.manager = require('./micropolar_manager') diff --git a/src/plots/polar/micropolar.js b/src/plots/polar/micropolar.js index 208da6995b5..1d1f371f27c 100644 --- a/src/plots/polar/micropolar.js +++ b/src/plots/polar/micropolar.js @@ -6,1276 +6,1286 @@ * LICENSE file in the root directory of this source tree. */ -var d3 = require('d3'); -var Lib = require('../../lib'); -var extendDeepAll = Lib.extendDeepAll; +var d3 = require('d3') +var Lib = require('../../lib') +var extendDeepAll = Lib.extendDeepAll -var µ = module.exports = { version: '0.2.2' }; +var µ = module.exports = { version: '0.2.2' } -µ.Axis = function module() { - var config = { - data: [], - layout: {} - }, inputConfig = {}, liveConfig = {}; - var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale; - var exports = {}; - function render(_container) { - container = _container || container; - var data = config.data; - var axisConfig = config.layout; - if (typeof container == 'string' || container.nodeName) container = d3.select(container); - container.datum(data).each(function(_data, _index) { - var dataOriginal = _data.slice(); - liveConfig = { - data: µ.util.cloneJson(dataOriginal), - layout: µ.util.cloneJson(axisConfig) - }; - var colorIndex = 0; - dataOriginal.forEach(function(d, i) { - if (!d.color) { - d.color = axisConfig.defaultColorRange[colorIndex]; - colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length; - } - if (!d.strokeColor) { - d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString(); - } - liveConfig.data[i].color = d.color; - liveConfig.data[i].strokeColor = d.strokeColor; - liveConfig.data[i].strokeDash = d.strokeDash; - liveConfig.data[i].strokeSize = d.strokeSize; - }); - var data = dataOriginal.filter(function(d, i) { - var visible = d.visible; - return typeof visible === 'undefined' || visible === true; - }); - var isStacked = false; - var dataWithGroupId = data.map(function(d, i) { - isStacked = isStacked || typeof d.groupId !== 'undefined'; - return d; - }); - if (isStacked) { - var grouped = d3.nest().key(function(d, i) { - return typeof d.groupId != 'undefined' ? d.groupId : 'unstacked'; - }).entries(dataWithGroupId); - var dataYStack = []; - var stacked = grouped.map(function(d, i) { - if (d.key === 'unstacked') return d.values; else { - var prevArray = d.values[0].r.map(function(d, i) { - return 0; - }); - d.values.forEach(function(d, i, a) { - d.yStack = [ prevArray ]; - dataYStack.push(prevArray); - prevArray = µ.util.sumArrays(d.r, prevArray); - }); - return d.values; - } - }); - data = d3.merge(stacked); - } - data.forEach(function(d, i) { - d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ]; - d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ]; - }); - var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2; - radius = Math.max(10, radius); - var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ]; - var extent; - if (isStacked) { - var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack))); - extent = [ 0, highestStackedValue ]; - } else extent = d3.extent(µ.util.flattenArray(data.map(function(d, i) { - return d.r; - }))); - if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0; - radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]); - liveConfig.layout.radialAxis.domain = radialScale.domain(); - var angularDataMerged = µ.util.flattenArray(data.map(function(d, i) { - return d.t; - })); - var isOrdinal = typeof angularDataMerged[0] === 'string'; - var ticks; - if (isOrdinal) { - angularDataMerged = µ.util.deduplicate(angularDataMerged); - ticks = angularDataMerged.slice(); - angularDataMerged = d3.range(angularDataMerged.length); - data = data.map(function(d, i) { - var result = d; - d.t = [ angularDataMerged ]; - if (isStacked) result.yStack = d.yStack; - return result; - }); - } - var hasOnlyLineOrDotPlot = data.filter(function(d, i) { - return d.geometry === 'LinePlot' || d.geometry === 'DotPlot'; - }).length === data.length; - var needsEndSpacing = axisConfig.needsEndSpacing === null ? isOrdinal || !hasOnlyLineOrDotPlot : axisConfig.needsEndSpacing; - var useProvidedDomain = axisConfig.angularAxis.domain && axisConfig.angularAxis.domain != µ.DATAEXTENT && !isOrdinal && axisConfig.angularAxis.domain[0] >= 0; - var angularDomain = useProvidedDomain ? axisConfig.angularAxis.domain : d3.extent(angularDataMerged); - var angularDomainStep = Math.abs(angularDataMerged[1] - angularDataMerged[0]); - if (hasOnlyLineOrDotPlot && !isOrdinal) angularDomainStep = 0; - var angularDomainWithPadding = angularDomain.slice(); - if (needsEndSpacing && isOrdinal) angularDomainWithPadding[1] += angularDomainStep; - var tickCount = axisConfig.angularAxis.ticksCount || 4; - if (tickCount > 8) tickCount = tickCount / (tickCount / 8) + tickCount % 8; - if (axisConfig.angularAxis.ticksStep) { - tickCount = (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / tickCount; - } - var angularTicksStep = axisConfig.angularAxis.ticksStep || (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / (tickCount * (axisConfig.minorTicks + 1)); - if (ticks) angularTicksStep = Math.max(Math.round(angularTicksStep), 1); - if (!angularDomainWithPadding[2]) angularDomainWithPadding[2] = angularTicksStep; - var angularAxisRange = d3.range.apply(this, angularDomainWithPadding); - angularAxisRange = angularAxisRange.map(function(d, i) { - return parseFloat(d.toPrecision(12)); - }); - angularScale = d3.scale.linear().domain(angularDomainWithPadding.slice(0, 2)).range(axisConfig.direction === 'clockwise' ? [ 0, 360 ] : [ 360, 0 ]); - liveConfig.layout.angularAxis.domain = angularScale.domain(); - liveConfig.layout.angularAxis.endPadding = needsEndSpacing ? angularDomainStep : 0; - svg = d3.select(this).select('svg.chart-root'); - if (typeof svg === 'undefined' || svg.empty()) { - var skeleton = "' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '"; - var doc = new DOMParser().parseFromString(skeleton, 'application/xml'); - var newSvg = this.appendChild(this.ownerDocument.importNode(doc.documentElement, true)); - svg = d3.select(newSvg); - } - svg.select('.guides-group').style({ - 'pointer-events': 'none' - }); - svg.select('.angular.axis-group').style({ - 'pointer-events': 'none' - }); - svg.select('.radial.axis-group').style({ - 'pointer-events': 'none' - }); - var chartGroup = svg.select('.chart-group'); - var lineStyle = { - fill: 'none', - stroke: axisConfig.tickColor - }; - var fontStyle = { - 'font-size': axisConfig.font.size, - 'font-family': axisConfig.font.family, - fill: axisConfig.font.color, - 'text-shadow': [ '-1px 0px', '1px -1px', '-1px 1px', '1px 1px' ].map(function(d, i) { - return ' ' + d + ' 0 ' + axisConfig.font.outlineColor; - }).join(',') - }; - var legendContainer; - if (axisConfig.showLegend) { - legendContainer = svg.select('.legend-group').attr({ - transform: 'translate(' + [ radius, axisConfig.margin.top ] + ')' - }).style({ - display: 'block' - }); - var elements = data.map(function(d, i) { - var datumClone = µ.util.cloneJson(d); - datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot' ? 'square' : 'line'; - datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend; - datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color; - return datumClone; - }); +µ.Axis = function module () { + var config = { + data: [], + layout: {} + }, inputConfig = {}, liveConfig = {} + var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale + var exports = {} + function render (_container) { + container = _container || container + var data = config.data + var axisConfig = config.layout + if (typeof container === 'string' || container.nodeName) container = d3.select(container) + container.datum(data).each(function (_data, _index) { + var dataOriginal = _data.slice() + liveConfig = { + data: µ.util.cloneJson(dataOriginal), + layout: µ.util.cloneJson(axisConfig) + } + var colorIndex = 0 + dataOriginal.forEach(function (d, i) { + if (!d.color) { + d.color = axisConfig.defaultColorRange[colorIndex] + colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length + } + if (!d.strokeColor) { + d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString() + } + liveConfig.data[i].color = d.color + liveConfig.data[i].strokeColor = d.strokeColor + liveConfig.data[i].strokeDash = d.strokeDash + liveConfig.data[i].strokeSize = d.strokeSize + }) + var data = dataOriginal.filter(function (d, i) { + var visible = d.visible + return typeof visible === 'undefined' || visible === true + }) + var isStacked = false + var dataWithGroupId = data.map(function (d, i) { + isStacked = isStacked || typeof d.groupId !== 'undefined' + return d + }) + if (isStacked) { + var grouped = d3.nest().key(function (d, i) { + return typeof d.groupId !== 'undefined' ? d.groupId : 'unstacked' + }).entries(dataWithGroupId) + var dataYStack = [] + var stacked = grouped.map(function (d, i) { + if (d.key === 'unstacked') return d.values; else { + var prevArray = d.values[0].r.map(function (d, i) { + return 0 + }) + d.values.forEach(function (d, i, a) { + d.yStack = [ prevArray ] + dataYStack.push(prevArray) + prevArray = µ.util.sumArrays(d.r, prevArray) + }) + return d.values + } + }) + data = d3.merge(stacked) + } + data.forEach(function (d, i) { + d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ] + d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ] + }) + var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2 + radius = Math.max(10, radius) + var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ] + var extent + if (isStacked) { + var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack))) + extent = [ 0, highestStackedValue ] + } else { + extent = d3.extent(µ.util.flattenArray(data.map(function (d, i) { + return d.r + }))) + } + if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0 + radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]) + liveConfig.layout.radialAxis.domain = radialScale.domain() + var angularDataMerged = µ.util.flattenArray(data.map(function (d, i) { + return d.t + })) + var isOrdinal = typeof angularDataMerged[0] === 'string' + var ticks + if (isOrdinal) { + angularDataMerged = µ.util.deduplicate(angularDataMerged) + ticks = angularDataMerged.slice() + angularDataMerged = d3.range(angularDataMerged.length) + data = data.map(function (d, i) { + var result = d + d.t = [ angularDataMerged ] + if (isStacked) result.yStack = d.yStack + return result + }) + } + var hasOnlyLineOrDotPlot = data.filter(function (d, i) { + return d.geometry === 'LinePlot' || d.geometry === 'DotPlot' + }).length === data.length + var needsEndSpacing = axisConfig.needsEndSpacing === null ? isOrdinal || !hasOnlyLineOrDotPlot : axisConfig.needsEndSpacing + var useProvidedDomain = axisConfig.angularAxis.domain && axisConfig.angularAxis.domain != µ.DATAEXTENT && !isOrdinal && axisConfig.angularAxis.domain[0] >= 0 + var angularDomain = useProvidedDomain ? axisConfig.angularAxis.domain : d3.extent(angularDataMerged) + var angularDomainStep = Math.abs(angularDataMerged[1] - angularDataMerged[0]) + if (hasOnlyLineOrDotPlot && !isOrdinal) angularDomainStep = 0 + var angularDomainWithPadding = angularDomain.slice() + if (needsEndSpacing && isOrdinal) angularDomainWithPadding[1] += angularDomainStep + var tickCount = axisConfig.angularAxis.ticksCount || 4 + if (tickCount > 8) tickCount = tickCount / (tickCount / 8) + tickCount % 8 + if (axisConfig.angularAxis.ticksStep) { + tickCount = (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / tickCount + } + var angularTicksStep = axisConfig.angularAxis.ticksStep || (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / (tickCount * (axisConfig.minorTicks + 1)) + if (ticks) angularTicksStep = Math.max(Math.round(angularTicksStep), 1) + if (!angularDomainWithPadding[2]) angularDomainWithPadding[2] = angularTicksStep + var angularAxisRange = d3.range.apply(this, angularDomainWithPadding) + angularAxisRange = angularAxisRange.map(function (d, i) { + return parseFloat(d.toPrecision(12)) + }) + angularScale = d3.scale.linear().domain(angularDomainWithPadding.slice(0, 2)).range(axisConfig.direction === 'clockwise' ? [ 0, 360 ] : [ 360, 0 ]) + liveConfig.layout.angularAxis.domain = angularScale.domain() + liveConfig.layout.angularAxis.endPadding = needsEndSpacing ? angularDomainStep : 0 + svg = d3.select(this).select('svg.chart-root') + if (typeof svg === 'undefined' || svg.empty()) { + var skeleton = "' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '" + var doc = new DOMParser().parseFromString(skeleton, 'application/xml') + var newSvg = this.appendChild(this.ownerDocument.importNode(doc.documentElement, true)) + svg = d3.select(newSvg) + } + svg.select('.guides-group').style({ + 'pointer-events': 'none' + }) + svg.select('.angular.axis-group').style({ + 'pointer-events': 'none' + }) + svg.select('.radial.axis-group').style({ + 'pointer-events': 'none' + }) + var chartGroup = svg.select('.chart-group') + var lineStyle = { + fill: 'none', + stroke: axisConfig.tickColor + } + var fontStyle = { + 'font-size': axisConfig.font.size, + 'font-family': axisConfig.font.family, + fill: axisConfig.font.color, + 'text-shadow': [ '-1px 0px', '1px -1px', '-1px 1px', '1px 1px' ].map(function (d, i) { + return ' ' + d + ' 0 ' + axisConfig.font.outlineColor + }).join(',') + } + var legendContainer + if (axisConfig.showLegend) { + legendContainer = svg.select('.legend-group').attr({ + transform: 'translate(' + [ radius, axisConfig.margin.top ] + ')' + }).style({ + display: 'block' + }) + var elements = data.map(function (d, i) { + var datumClone = µ.util.cloneJson(d) + datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot' ? 'square' : 'line' + datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend + datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color + return datumClone + }) - µ.Legend().config({ - data: data.map(function(d, i) { - return d.name || 'Element' + i; - }), - legendConfig: extendDeepAll({}, + µ.Legend().config({ + data: data.map(function (d, i) { + return d.name || 'Element' + i + }), + legendConfig: extendDeepAll({}, µ.Legend.defaultConfig().legendConfig, - { - container: legendContainer, - elements: elements, - reverseOrder: axisConfig.legend.reverseOrder - } - ) - })(); - - var legendBBox = legendContainer.node().getBBox(); - radius = Math.min(axisConfig.width - legendBBox.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2; - radius = Math.max(10, radius); - chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ]; - radialScale.range([ 0, radius ]); - liveConfig.layout.radialAxis.domain = radialScale.domain(); - legendContainer.attr('transform', 'translate(' + [ chartCenter[0] + radius, chartCenter[1] - radius ] + ')'); - } else { - legendContainer = svg.select('.legend-group').style({ - display: 'none' - }); - } - svg.attr({ - width: axisConfig.width, - height: axisConfig.height - }).style({ - opacity: axisConfig.opacity - }); - chartGroup.attr('transform', 'translate(' + chartCenter + ')').style({ - cursor: 'crosshair' - }); - var centeringOffset = [ (axisConfig.width - (axisConfig.margin.left + axisConfig.margin.right + radius * 2 + (legendBBox ? legendBBox.width : 0))) / 2, (axisConfig.height - (axisConfig.margin.top + axisConfig.margin.bottom + radius * 2)) / 2 ]; - centeringOffset[0] = Math.max(0, centeringOffset[0]); - centeringOffset[1] = Math.max(0, centeringOffset[1]); - svg.select('.outer-group').attr('transform', 'translate(' + centeringOffset + ')'); - if (axisConfig.title) { - var title = svg.select('g.title-group text').style(fontStyle).text(axisConfig.title); - var titleBBox = title.node().getBBox(); - title.attr({ - x: chartCenter[0] - titleBBox.width / 2, - y: chartCenter[1] - radius - 20 - }); + { + container: legendContainer, + elements: elements, + reverseOrder: axisConfig.legend.reverseOrder } - var radialAxis = svg.select('.radial.axis-group'); - if (axisConfig.radialAxis.gridLinesVisible) { - var gridCircles = radialAxis.selectAll('circle.grid-circle').data(radialScale.ticks(5)); - gridCircles.enter().append('circle').attr({ - 'class': 'grid-circle' - }).style(lineStyle); - gridCircles.attr('r', radialScale); - gridCircles.exit().remove(); - } - radialAxis.select('circle.outside-circle').attr({ - r: radius - }).style(lineStyle); - var backgroundCircle = svg.select('circle.background-circle').attr({ - r: radius - }).style({ - fill: axisConfig.backgroundColor, - stroke: axisConfig.stroke - }); - function currentAngle(d, i) { - return angularScale(d) % 360 + axisConfig.orientation; - } - if (axisConfig.radialAxis.visible) { - var axis = d3.svg.axis().scale(radialScale).ticks(5).tickSize(5); - radialAxis.call(axis).attr({ - transform: 'rotate(' + axisConfig.radialAxis.orientation + ')' - }); - radialAxis.selectAll('.domain').style(lineStyle); - radialAxis.selectAll('g>text').text(function(d, i) { - return this.textContent + axisConfig.radialAxis.ticksSuffix; - }).style(fontStyle).style({ - 'text-anchor': 'start' - }).attr({ - x: 0, - y: 0, - dx: 0, - dy: 0, - transform: function(d, i) { - if (axisConfig.radialAxis.tickOrientation === 'horizontal') { - return 'rotate(' + -axisConfig.radialAxis.orientation + ') translate(' + [ 0, fontStyle['font-size'] ] + ')'; - } else return 'translate(' + [ 0, fontStyle['font-size'] ] + ')'; - } - }); - radialAxis.selectAll('g>line').style({ - stroke: 'black' - }); - } - var angularAxis = svg.select('.angular.axis-group').selectAll('g.angular-tick').data(angularAxisRange); - var angularAxisEnter = angularAxis.enter().append('g').classed('angular-tick', true); - angularAxis.attr({ - transform: function(d, i) { - return 'rotate(' + currentAngle(d, i) + ')'; - } - }).style({ - display: axisConfig.angularAxis.visible ? 'block' : 'none' - }); - angularAxis.exit().remove(); - angularAxisEnter.append('line').classed('grid-line', true).classed('major', function(d, i) { - return i % (axisConfig.minorTicks + 1) == 0; - }).classed('minor', function(d, i) { - return !(i % (axisConfig.minorTicks + 1) == 0); - }).style(lineStyle); - angularAxisEnter.selectAll('.minor').style({ - stroke: axisConfig.minorTickColor - }); - angularAxis.select('line.grid-line').attr({ - x1: axisConfig.tickLength ? radius - axisConfig.tickLength : 0, - x2: radius - }).style({ - display: axisConfig.angularAxis.gridLinesVisible ? 'block' : 'none' - }); - angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle); - var ticksText = angularAxis.select('text.axis-text').attr({ - x: radius + axisConfig.labelOffset, - dy: '.35em', - transform: function(d, i) { - var angle = currentAngle(d, i); - var rad = radius + axisConfig.labelOffset; - var orient = axisConfig.angularAxis.tickOrientation; - if (orient == 'horizontal') return 'rotate(' + -angle + ' ' + rad + ' 0)'; else if (orient == 'radial') return angle < 270 && angle > 90 ? 'rotate(180 ' + rad + ' 0)' : null; else return 'rotate(' + (angle <= 180 && angle > 0 ? -90 : 90) + ' ' + rad + ' 0)'; - } - }).style({ - 'text-anchor': 'middle', - display: axisConfig.angularAxis.labelsVisible ? 'block' : 'none' - }).text(function(d, i) { - if (i % (axisConfig.minorTicks + 1) != 0) return ''; - if (ticks) { - return ticks[d] + axisConfig.angularAxis.ticksSuffix; - } else return d + axisConfig.angularAxis.ticksSuffix; - }).style(fontStyle); - if (axisConfig.angularAxis.rewriteTicks) ticksText.text(function(d, i) { - if (i % (axisConfig.minorTicks + 1) != 0) return ''; - return axisConfig.angularAxis.rewriteTicks(this.textContent, i); - }); - var rightmostTickEndX = d3.max(chartGroup.selectAll('.angular-tick text')[0].map(function(d, i) { - return d.getCTM().e + d.getBBox().width; - })); - legendContainer.attr({ - transform: 'translate(' + [ radius + rightmostTickEndX, axisConfig.margin.top ] + ')' - }); - var hasGeometry = svg.select('g.geometry-group').selectAll('g').size() > 0; - var geometryContainer = svg.select('g.geometry-group').selectAll('g.geometry').data(data); - geometryContainer.enter().append('g').attr({ - 'class': function(d, i) { - return 'geometry geometry' + i; - } - }); - geometryContainer.exit().remove(); - if (data[0] || hasGeometry) { - var geometryConfigs = []; - data.forEach(function(d, i) { - var geometryConfig = {}; - geometryConfig.radialScale = radialScale; - geometryConfig.angularScale = angularScale; - geometryConfig.container = geometryContainer.filter(function(dB, iB) { - return iB == i; - }); - geometryConfig.geometry = d.geometry; - geometryConfig.orientation = axisConfig.orientation; - geometryConfig.direction = axisConfig.direction; - geometryConfig.index = i; - geometryConfigs.push({ - data: d, - geometryConfig: geometryConfig - }); - }); - var geometryConfigsGrouped = d3.nest().key(function(d, i) { - return typeof d.data.groupId != 'undefined' || 'unstacked'; - }).entries(geometryConfigs); - var geometryConfigsGrouped2 = []; - geometryConfigsGrouped.forEach(function(d, i) { - if (d.key === 'unstacked') geometryConfigsGrouped2 = geometryConfigsGrouped2.concat(d.values.map(function(d, i) { - return [ d ]; - })); else geometryConfigsGrouped2.push(d.values); - }); - geometryConfigsGrouped2.forEach(function(d, i) { - var geometry; - if (Array.isArray(d)) geometry = d[0].geometryConfig.geometry; else geometry = d.geometryConfig.geometry; - var finalGeometryConfig = d.map(function(dB, iB) { - return extendDeepAll(µ[geometry].defaultConfig(), dB); - }); - µ[geometry]().config(finalGeometryConfig)(); - }); - } - var guides = svg.select('.guides-group'); - var tooltipContainer = svg.select('.tooltips-group'); - var angularTooltip = µ.tooltipPanel().config({ - container: tooltipContainer, - fontSize: 8 - })(); - var radialTooltip = µ.tooltipPanel().config({ - container: tooltipContainer, - fontSize: 8 - })(); - var geometryTooltip = µ.tooltipPanel().config({ - container: tooltipContainer, - hasTick: true - })(); - var angularValue, radialValue; - if (!isOrdinal) { - var angularGuideLine = guides.select('line').attr({ - x1: 0, - y1: 0, - y2: 0 - }).style({ - stroke: 'grey', - 'pointer-events': 'none' - }); - chartGroup.on('mousemove.angular-guide', function(d, i) { - var mouseAngle = µ.util.getMousePos(backgroundCircle).angle; - angularGuideLine.attr({ - x2: -radius, - transform: 'rotate(' + mouseAngle + ')' - }).style({ - opacity: .5 - }); - var angleWithOriginOffset = (mouseAngle + 180 + 360 - axisConfig.orientation) % 360; - angularValue = angularScale.invert(angleWithOriginOffset); - var pos = µ.util.convertToCartesian(radius + 12, mouseAngle + 180); - angularTooltip.text(µ.util.round(angularValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]); - }).on('mouseout.angular-guide', function(d, i) { - guides.select('line').style({ - opacity: 0 - }); - }); - } - var angularGuideCircle = guides.select('circle').style({ - stroke: 'grey', - fill: 'none' - }); - chartGroup.on('mousemove.radial-guide', function(d, i) { - var r = µ.util.getMousePos(backgroundCircle).radius; - angularGuideCircle.attr({ - r: r - }).style({ - opacity: .5 - }); - radialValue = radialScale.invert(µ.util.getMousePos(backgroundCircle).radius); - var pos = µ.util.convertToCartesian(r, axisConfig.radialAxis.orientation); - radialTooltip.text(µ.util.round(radialValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]); - }).on('mouseout.radial-guide', function(d, i) { - angularGuideCircle.style({ - opacity: 0 - }); - geometryTooltip.hide(); - angularTooltip.hide(); - radialTooltip.hide(); - }); - svg.selectAll('.geometry-group .mark').on('mouseover.tooltip', function(d, i) { - var el = d3.select(this); - var color = el.style('fill'); - var newColor = 'black'; - var opacity = el.style('opacity') || 1; - el.attr({ - 'data-opacity': opacity - }); - if (color != 'none') { - el.attr({ - 'data-fill': color - }); - newColor = d3.hsl(color).darker().toString(); - el.style({ - fill: newColor, - opacity: 1 - }); - var textData = { - t: µ.util.round(d[0]), - r: µ.util.round(d[1]) - }; - if (isOrdinal) textData.t = ticks[d[0]]; - var text = 't: ' + textData.t + ', r: ' + textData.r; - var bbox = this.getBoundingClientRect(); - var svgBBox = svg.node().getBoundingClientRect(); - var pos = [ bbox.left + bbox.width / 2 - centeringOffset[0] - svgBBox.left, bbox.top + bbox.height / 2 - centeringOffset[1] - svgBBox.top ]; - geometryTooltip.config({ - color: newColor - }).text(text); - geometryTooltip.move(pos); - } else { - color = el.style('stroke'); - el.attr({ - 'data-stroke': color - }); - newColor = d3.hsl(color).darker().toString(); - el.style({ - stroke: newColor, - opacity: 1 - }); - } - }).on('mousemove.tooltip', function(d, i) { - if (d3.event.which != 0) return false; - if (d3.select(this).attr('data-fill')) geometryTooltip.show(); - }).on('mouseout.tooltip', function(d, i) { - geometryTooltip.hide(); - var el = d3.select(this); - var fillColor = el.attr('data-fill'); - if (fillColor) el.style({ - fill: fillColor, - opacity: el.attr('data-opacity') - }); else el.style({ - stroke: el.attr('data-stroke'), - opacity: el.attr('data-opacity') - }); - }); - }); - return exports; - } - exports.render = function(_container) { - render(_container); - return this; - }; - exports.config = function(_x) { - if (!arguments.length) return config; - var xClone = µ.util.cloneJson(_x); - xClone.data.forEach(function(d, i) { - if (!config.data[i]) config.data[i] = {}; - extendDeepAll(config.data[i], µ.Axis.defaultConfig().data[0]); - extendDeepAll(config.data[i], d); - }); - extendDeepAll(config.layout, µ.Axis.defaultConfig().layout); - extendDeepAll(config.layout, xClone.layout); - return this; - }; - exports.getLiveConfig = function() { - return liveConfig; - }; - exports.getinputConfig = function() { - return inputConfig; - }; - exports.radialScale = function(_x) { - return radialScale; - }; - exports.angularScale = function(_x) { - return angularScale; - }; - exports.svg = function() { - return svg; - }; - d3.rebind(exports, dispatch, 'on'); - return exports; -}; + ) + })() -µ.Axis.defaultConfig = function(d, i) { - var config = { - data: [ { - t: [ 1, 2, 3, 4 ], - r: [ 10, 11, 12, 13 ], - name: 'Line1', - geometry: 'LinePlot', - color: null, - strokeDash: 'solid', - strokeColor: null, - strokeSize: '1', - visibleInLegend: true, + var legendBBox = legendContainer.node().getBBox() + radius = Math.min(axisConfig.width - legendBBox.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2 + radius = Math.max(10, radius) + chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ] + radialScale.range([ 0, radius ]) + liveConfig.layout.radialAxis.domain = radialScale.domain() + legendContainer.attr('transform', 'translate(' + [ chartCenter[0] + radius, chartCenter[1] - radius ] + ')') + } else { + legendContainer = svg.select('.legend-group').style({ + display: 'none' + }) + } + svg.attr({ + width: axisConfig.width, + height: axisConfig.height + }).style({ + opacity: axisConfig.opacity + }) + chartGroup.attr('transform', 'translate(' + chartCenter + ')').style({ + cursor: 'crosshair' + }) + var centeringOffset = [ (axisConfig.width - (axisConfig.margin.left + axisConfig.margin.right + radius * 2 + (legendBBox ? legendBBox.width : 0))) / 2, (axisConfig.height - (axisConfig.margin.top + axisConfig.margin.bottom + radius * 2)) / 2 ] + centeringOffset[0] = Math.max(0, centeringOffset[0]) + centeringOffset[1] = Math.max(0, centeringOffset[1]) + svg.select('.outer-group').attr('transform', 'translate(' + centeringOffset + ')') + if (axisConfig.title) { + var title = svg.select('g.title-group text').style(fontStyle).text(axisConfig.title) + var titleBBox = title.node().getBBox() + title.attr({ + x: chartCenter[0] - titleBBox.width / 2, + y: chartCenter[1] - radius - 20 + }) + } + var radialAxis = svg.select('.radial.axis-group') + if (axisConfig.radialAxis.gridLinesVisible) { + var gridCircles = radialAxis.selectAll('circle.grid-circle').data(radialScale.ticks(5)) + gridCircles.enter().append('circle').attr({ + 'class': 'grid-circle' + }).style(lineStyle) + gridCircles.attr('r', radialScale) + gridCircles.exit().remove() + } + radialAxis.select('circle.outside-circle').attr({ + r: radius + }).style(lineStyle) + var backgroundCircle = svg.select('circle.background-circle').attr({ + r: radius + }).style({ + fill: axisConfig.backgroundColor, + stroke: axisConfig.stroke + }) + function currentAngle (d, i) { + return angularScale(d) % 360 + axisConfig.orientation + } + if (axisConfig.radialAxis.visible) { + var axis = d3.svg.axis().scale(radialScale).ticks(5).tickSize(5) + radialAxis.call(axis).attr({ + transform: 'rotate(' + axisConfig.radialAxis.orientation + ')' + }) + radialAxis.selectAll('.domain').style(lineStyle) + radialAxis.selectAll('g>text').text(function (d, i) { + return this.textContent + axisConfig.radialAxis.ticksSuffix + }).style(fontStyle).style({ + 'text-anchor': 'start' + }).attr({ + x: 0, + y: 0, + dx: 0, + dy: 0, + transform: function (d, i) { + if (axisConfig.radialAxis.tickOrientation === 'horizontal') { + return 'rotate(' + -axisConfig.radialAxis.orientation + ') translate(' + [ 0, fontStyle['font-size'] ] + ')' + } else return 'translate(' + [ 0, fontStyle['font-size'] ] + ')' + } + }) + radialAxis.selectAll('g>line').style({ + stroke: 'black' + }) + } + var angularAxis = svg.select('.angular.axis-group').selectAll('g.angular-tick').data(angularAxisRange) + var angularAxisEnter = angularAxis.enter().append('g').classed('angular-tick', true) + angularAxis.attr({ + transform: function (d, i) { + return 'rotate(' + currentAngle(d, i) + ')' + } + }).style({ + display: axisConfig.angularAxis.visible ? 'block' : 'none' + }) + angularAxis.exit().remove() + angularAxisEnter.append('line').classed('grid-line', true).classed('major', function (d, i) { + return i % (axisConfig.minorTicks + 1) == 0 + }).classed('minor', function (d, i) { + return !(i % (axisConfig.minorTicks + 1) == 0) + }).style(lineStyle) + angularAxisEnter.selectAll('.minor').style({ + stroke: axisConfig.minorTickColor + }) + angularAxis.select('line.grid-line').attr({ + x1: axisConfig.tickLength ? radius - axisConfig.tickLength : 0, + x2: radius + }).style({ + display: axisConfig.angularAxis.gridLinesVisible ? 'block' : 'none' + }) + angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle) + var ticksText = angularAxis.select('text.axis-text').attr({ + x: radius + axisConfig.labelOffset, + dy: '.35em', + transform: function (d, i) { + var angle = currentAngle(d, i) + var rad = radius + axisConfig.labelOffset + var orient = axisConfig.angularAxis.tickOrientation + if (orient == 'horizontal') return 'rotate(' + -angle + ' ' + rad + ' 0)'; else if (orient == 'radial') return angle < 270 && angle > 90 ? 'rotate(180 ' + rad + ' 0)' : null; else return 'rotate(' + (angle <= 180 && angle > 0 ? -90 : 90) + ' ' + rad + ' 0)' + } + }).style({ + 'text-anchor': 'middle', + display: axisConfig.angularAxis.labelsVisible ? 'block' : 'none' + }).text(function (d, i) { + if (i % (axisConfig.minorTicks + 1) != 0) return '' + if (ticks) { + return ticks[d] + axisConfig.angularAxis.ticksSuffix + } else return d + axisConfig.angularAxis.ticksSuffix + }).style(fontStyle) + if (axisConfig.angularAxis.rewriteTicks) { + ticksText.text(function (d, i) { + if (i % (axisConfig.minorTicks + 1) != 0) return '' + return axisConfig.angularAxis.rewriteTicks(this.textContent, i) + }) + } + var rightmostTickEndX = d3.max(chartGroup.selectAll('.angular-tick text')[0].map(function (d, i) { + return d.getCTM().e + d.getBBox().width + })) + legendContainer.attr({ + transform: 'translate(' + [ radius + rightmostTickEndX, axisConfig.margin.top ] + ')' + }) + var hasGeometry = svg.select('g.geometry-group').selectAll('g').size() > 0 + var geometryContainer = svg.select('g.geometry-group').selectAll('g.geometry').data(data) + geometryContainer.enter().append('g').attr({ + 'class': function (d, i) { + return 'geometry geometry' + i + } + }) + geometryContainer.exit().remove() + if (data[0] || hasGeometry) { + var geometryConfigs = [] + data.forEach(function (d, i) { + var geometryConfig = {} + geometryConfig.radialScale = radialScale + geometryConfig.angularScale = angularScale + geometryConfig.container = geometryContainer.filter(function (dB, iB) { + return iB == i + }) + geometryConfig.geometry = d.geometry + geometryConfig.orientation = axisConfig.orientation + geometryConfig.direction = axisConfig.direction + geometryConfig.index = i + geometryConfigs.push({ + data: d, + geometryConfig: geometryConfig + }) + }) + var geometryConfigsGrouped = d3.nest().key(function (d, i) { + return typeof d.data.groupId !== 'undefined' || 'unstacked' + }).entries(geometryConfigs) + var geometryConfigsGrouped2 = [] + geometryConfigsGrouped.forEach(function (d, i) { + if (d.key === 'unstacked') { + geometryConfigsGrouped2 = geometryConfigsGrouped2.concat(d.values.map(function (d, i) { + return [ d ] + })) + } else geometryConfigsGrouped2.push(d.values) + }) + geometryConfigsGrouped2.forEach(function (d, i) { + var geometry + if (Array.isArray(d)) geometry = d[0].geometryConfig.geometry; else geometry = d.geometryConfig.geometry + var finalGeometryConfig = d.map(function (dB, iB) { + return extendDeepAll(µ[geometry].defaultConfig(), dB) + }) + µ[geometry]().config(finalGeometryConfig)() + }) + } + var guides = svg.select('.guides-group') + var tooltipContainer = svg.select('.tooltips-group') + var angularTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + fontSize: 8 + })() + var radialTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + fontSize: 8 + })() + var geometryTooltip = µ.tooltipPanel().config({ + container: tooltipContainer, + hasTick: true + })() + var angularValue, radialValue + if (!isOrdinal) { + var angularGuideLine = guides.select('line').attr({ + x1: 0, + y1: 0, + y2: 0 + }).style({ + stroke: 'grey', + 'pointer-events': 'none' + }) + chartGroup.on('mousemove.angular-guide', function (d, i) { + var mouseAngle = µ.util.getMousePos(backgroundCircle).angle + angularGuideLine.attr({ + x2: -radius, + transform: 'rotate(' + mouseAngle + ')' + }).style({ + opacity: 0.5 + }) + var angleWithOriginOffset = (mouseAngle + 180 + 360 - axisConfig.orientation) % 360 + angularValue = angularScale.invert(angleWithOriginOffset) + var pos = µ.util.convertToCartesian(radius + 12, mouseAngle + 180) + angularTooltip.text(µ.util.round(angularValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]) + }).on('mouseout.angular-guide', function (d, i) { + guides.select('line').style({ + opacity: 0 + }) + }) + } + var angularGuideCircle = guides.select('circle').style({ + stroke: 'grey', + fill: 'none' + }) + chartGroup.on('mousemove.radial-guide', function (d, i) { + var r = µ.util.getMousePos(backgroundCircle).radius + angularGuideCircle.attr({ + r: r + }).style({ + opacity: 0.5 + }) + radialValue = radialScale.invert(µ.util.getMousePos(backgroundCircle).radius) + var pos = µ.util.convertToCartesian(r, axisConfig.radialAxis.orientation) + radialTooltip.text(µ.util.round(radialValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]) + }).on('mouseout.radial-guide', function (d, i) { + angularGuideCircle.style({ + opacity: 0 + }) + geometryTooltip.hide() + angularTooltip.hide() + radialTooltip.hide() + }) + svg.selectAll('.geometry-group .mark').on('mouseover.tooltip', function (d, i) { + var el = d3.select(this) + var color = el.style('fill') + var newColor = 'black' + var opacity = el.style('opacity') || 1 + el.attr({ + 'data-opacity': opacity + }) + if (color != 'none') { + el.attr({ + 'data-fill': color + }) + newColor = d3.hsl(color).darker().toString() + el.style({ + fill: newColor, opacity: 1 - } ], - layout: { - defaultColorRange: d3.scale.category10().range(), - title: null, - height: 450, - width: 500, - margin: { - top: 40, - right: 40, - bottom: 40, - left: 40 - }, - font: { - size: 12, - color: 'gray', - outlineColor: 'white', - family: 'Tahoma, sans-serif' - }, - direction: 'clockwise', - orientation: 0, - labelOffset: 10, - radialAxis: { - domain: null, - orientation: -45, - ticksSuffix: '', - visible: true, - gridLinesVisible: true, - tickOrientation: 'horizontal', - rewriteTicks: null - }, - angularAxis: { - domain: [ 0, 360 ], - ticksSuffix: '', - visible: true, - gridLinesVisible: true, - labelsVisible: true, - tickOrientation: 'horizontal', - rewriteTicks: null, - ticksCount: null, - ticksStep: null - }, - minorTicks: 0, - tickLength: null, - tickColor: 'silver', - minorTickColor: '#eee', - backgroundColor: 'none', - needsEndSpacing: null, - showLegend: true, - legend: { - reverseOrder: false - }, + }) + var textData = { + t: µ.util.round(d[0]), + r: µ.util.round(d[1]) + } + if (isOrdinal) textData.t = ticks[d[0]] + var text = 't: ' + textData.t + ', r: ' + textData.r + var bbox = this.getBoundingClientRect() + var svgBBox = svg.node().getBoundingClientRect() + var pos = [ bbox.left + bbox.width / 2 - centeringOffset[0] - svgBBox.left, bbox.top + bbox.height / 2 - centeringOffset[1] - svgBBox.top ] + geometryTooltip.config({ + color: newColor + }).text(text) + geometryTooltip.move(pos) + } else { + color = el.style('stroke') + el.attr({ + 'data-stroke': color + }) + newColor = d3.hsl(color).darker().toString() + el.style({ + stroke: newColor, opacity: 1 + }) } - }; - return config; -}; + }).on('mousemove.tooltip', function (d, i) { + if (d3.event.which != 0) return false + if (d3.select(this).attr('data-fill')) geometryTooltip.show() + }).on('mouseout.tooltip', function (d, i) { + geometryTooltip.hide() + var el = d3.select(this) + var fillColor = el.attr('data-fill') + if (fillColor) { + el.style({ + fill: fillColor, + opacity: el.attr('data-opacity') + }) + } else { + el.style({ + stroke: el.attr('data-stroke'), + opacity: el.attr('data-opacity') + }) + } + }) + }) + return exports + } + exports.render = function (_container) { + render(_container) + return this + } + exports.config = function (_x) { + if (!arguments.length) return config + var xClone = µ.util.cloneJson(_x) + xClone.data.forEach(function (d, i) { + if (!config.data[i]) config.data[i] = {} + extendDeepAll(config.data[i], µ.Axis.defaultConfig().data[0]) + extendDeepAll(config.data[i], d) + }) + extendDeepAll(config.layout, µ.Axis.defaultConfig().layout) + extendDeepAll(config.layout, xClone.layout) + return this + } + exports.getLiveConfig = function () { + return liveConfig + } + exports.getinputConfig = function () { + return inputConfig + } + exports.radialScale = function (_x) { + return radialScale + } + exports.angularScale = function (_x) { + return angularScale + } + exports.svg = function () { + return svg + } + d3.rebind(exports, dispatch, 'on') + return exports +} -µ.util = {}; +µ.Axis.defaultConfig = function (d, i) { + var config = { + data: [ { + t: [ 1, 2, 3, 4 ], + r: [ 10, 11, 12, 13 ], + name: 'Line1', + geometry: 'LinePlot', + color: null, + strokeDash: 'solid', + strokeColor: null, + strokeSize: '1', + visibleInLegend: true, + opacity: 1 + } ], + layout: { + defaultColorRange: d3.scale.category10().range(), + title: null, + height: 450, + width: 500, + margin: { + top: 40, + right: 40, + bottom: 40, + left: 40 + }, + font: { + size: 12, + color: 'gray', + outlineColor: 'white', + family: 'Tahoma, sans-serif' + }, + direction: 'clockwise', + orientation: 0, + labelOffset: 10, + radialAxis: { + domain: null, + orientation: -45, + ticksSuffix: '', + visible: true, + gridLinesVisible: true, + tickOrientation: 'horizontal', + rewriteTicks: null + }, + angularAxis: { + domain: [ 0, 360 ], + ticksSuffix: '', + visible: true, + gridLinesVisible: true, + labelsVisible: true, + tickOrientation: 'horizontal', + rewriteTicks: null, + ticksCount: null, + ticksStep: null + }, + minorTicks: 0, + tickLength: null, + tickColor: 'silver', + minorTickColor: '#eee', + backgroundColor: 'none', + needsEndSpacing: null, + showLegend: true, + legend: { + reverseOrder: false + }, + opacity: 1 + } + } + return config +} -µ.DATAEXTENT = 'dataExtent'; +µ.util = {} -µ.AREA = 'AreaChart'; +µ.DATAEXTENT = 'dataExtent' -µ.LINE = 'LinePlot'; +µ.AREA = 'AreaChart' -µ.DOT = 'DotPlot'; +µ.LINE = 'LinePlot' -µ.BAR = 'BarChart'; +µ.DOT = 'DotPlot' -µ.util._override = function(_objA, _objB) { - for (var x in _objA) if (x in _objB) _objB[x] = _objA[x]; -}; +µ.BAR = 'BarChart' -µ.util._extend = function(_objA, _objB) { - for (var x in _objA) _objB[x] = _objA[x]; -}; +µ.util._override = function (_objA, _objB) { + for (var x in _objA) if (x in _objB) _objB[x] = _objA[x] +} -µ.util._rndSnd = function() { - return Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1); -}; +µ.util._extend = function (_objA, _objB) { + for (var x in _objA) _objB[x] = _objA[x] +} -µ.util.dataFromEquation2 = function(_equation, _step) { - var step = _step || 6; - var data = d3.range(0, 360 + step, step).map(function(deg, index) { - var theta = deg * Math.PI / 180; - var radius = _equation(theta); - return [ deg, radius ]; - }); - return data; -}; +µ.util._rndSnd = function () { + return Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1) +} -µ.util.dataFromEquation = function(_equation, _step, _name) { - var step = _step || 6; - var t = [], r = []; - d3.range(0, 360 + step, step).forEach(function(deg, index) { - var theta = deg * Math.PI / 180; - var radius = _equation(theta); - t.push(deg); - r.push(radius); - }); - var result = { - t: t, - r: r - }; - if (_name) result.name = _name; - return result; -}; +µ.util.dataFromEquation2 = function (_equation, _step) { + var step = _step || 6 + var data = d3.range(0, 360 + step, step).map(function (deg, index) { + var theta = deg * Math.PI / 180 + var radius = _equation(theta) + return [ deg, radius ] + }) + return data +} -µ.util.ensureArray = function(_val, _count) { - if (typeof _val === 'undefined') return null; - var arr = [].concat(_val); - return d3.range(_count).map(function(d, i) { - return arr[i] || arr[0]; - }); -}; +µ.util.dataFromEquation = function (_equation, _step, _name) { + var step = _step || 6 + var t = [], r = [] + d3.range(0, 360 + step, step).forEach(function (deg, index) { + var theta = deg * Math.PI / 180 + var radius = _equation(theta) + t.push(deg) + r.push(radius) + }) + var result = { + t: t, + r: r + } + if (_name) result.name = _name + return result +} -µ.util.fillArrays = function(_obj, _valueNames, _count) { - _valueNames.forEach(function(d, i) { - _obj[d] = µ.util.ensureArray(_obj[d], _count); - }); - return _obj; -}; +µ.util.ensureArray = function (_val, _count) { + if (typeof _val === 'undefined') return null + var arr = [].concat(_val) + return d3.range(_count).map(function (d, i) { + return arr[i] || arr[0] + }) +} -µ.util.cloneJson = function(json) { - return JSON.parse(JSON.stringify(json)); -}; +µ.util.fillArrays = function (_obj, _valueNames, _count) { + _valueNames.forEach(function (d, i) { + _obj[d] = µ.util.ensureArray(_obj[d], _count) + }) + return _obj +} -µ.util.validateKeys = function(obj, keys) { - if (typeof keys === 'string') keys = keys.split('.'); - var next = keys.shift(); - return obj[next] && (!keys.length || objHasKeys(obj[next], keys)); -}; +µ.util.cloneJson = function (json) { + return JSON.parse(JSON.stringify(json)) +} -µ.util.sumArrays = function(a, b) { - return d3.zip(a, b).map(function(d, i) { - return d3.sum(d); - }); -}; +µ.util.validateKeys = function (obj, keys) { + if (typeof keys === 'string') keys = keys.split('.') + var next = keys.shift() + return obj[next] && (!keys.length || objHasKeys(obj[next], keys)) +} -µ.util.arrayLast = function(a) { - return a[a.length - 1]; -}; +µ.util.sumArrays = function (a, b) { + return d3.zip(a, b).map(function (d, i) { + return d3.sum(d) + }) +} -µ.util.arrayEqual = function(a, b) { - var i = Math.max(a.length, b.length, 1); - while (i-- >= 0 && a[i] === b[i]) ; - return i === -2; -}; +µ.util.arrayLast = function (a) { + return a[a.length - 1] +} -µ.util.flattenArray = function(arr) { - var r = []; - while (!µ.util.arrayEqual(r, arr)) { - r = arr; - arr = [].concat.apply([], arr); - } - return arr; -}; +µ.util.arrayEqual = function (a, b) { + var i = Math.max(a.length, b.length, 1) + while (i-- >= 0 && a[i] === b[i]) ; + return i === -2 +} -µ.util.deduplicate = function(arr) { - return arr.filter(function(v, i, a) { - return a.indexOf(v) == i; - }); -}; +µ.util.flattenArray = function (arr) { + var r = [] + while (!µ.util.arrayEqual(r, arr)) { + r = arr + arr = [].concat.apply([], arr) + } + return arr +} -µ.util.convertToCartesian = function(radius, theta) { - var thetaRadians = theta * Math.PI / 180; - var x = radius * Math.cos(thetaRadians); - var y = radius * Math.sin(thetaRadians); - return [ x, y ]; -}; +µ.util.deduplicate = function (arr) { + return arr.filter(function (v, i, a) { + return a.indexOf(v) == i + }) +} -µ.util.round = function(_value, _digits) { - var digits = _digits || 2; - var mult = Math.pow(10, digits); - return Math.round(_value * mult) / mult; -}; +µ.util.convertToCartesian = function (radius, theta) { + var thetaRadians = theta * Math.PI / 180 + var x = radius * Math.cos(thetaRadians) + var y = radius * Math.sin(thetaRadians) + return [ x, y ] +} -µ.util.getMousePos = function(_referenceElement) { - var mousePos = d3.mouse(_referenceElement.node()); - var mouseX = mousePos[0]; - var mouseY = mousePos[1]; - var mouse = {}; - mouse.x = mouseX; - mouse.y = mouseY; - mouse.pos = mousePos; - mouse.angle = (Math.atan2(mouseY, mouseX) + Math.PI) * 180 / Math.PI; - mouse.radius = Math.sqrt(mouseX * mouseX + mouseY * mouseY); - return mouse; -}; +µ.util.round = function (_value, _digits) { + var digits = _digits || 2 + var mult = Math.pow(10, digits) + return Math.round(_value * mult) / mult +} -µ.util.duplicatesCount = function(arr) { - var uniques = {}, val; - var dups = {}; - for (var i = 0, len = arr.length; i < len; i++) { - val = arr[i]; - if (val in uniques) { - uniques[val]++; - dups[val] = uniques[val]; - } else { - uniques[val] = 1; - } - } - return dups; -}; +µ.util.getMousePos = function (_referenceElement) { + var mousePos = d3.mouse(_referenceElement.node()) + var mouseX = mousePos[0] + var mouseY = mousePos[1] + var mouse = {} + mouse.x = mouseX + mouse.y = mouseY + mouse.pos = mousePos + mouse.angle = (Math.atan2(mouseY, mouseX) + Math.PI) * 180 / Math.PI + mouse.radius = Math.sqrt(mouseX * mouseX + mouseY * mouseY) + return mouse +} -µ.util.duplicates = function(arr) { - return Object.keys(µ.util.duplicatesCount(arr)); -}; - -µ.util.translator = function(obj, sourceBranch, targetBranch, reverse) { - if (reverse) { - var targetBranchCopy = targetBranch.slice(); - targetBranch = sourceBranch; - sourceBranch = targetBranchCopy; +µ.util.duplicatesCount = function (arr) { + var uniques = {}, val + var dups = {} + for (var i = 0, len = arr.length; i < len; i++) { + val = arr[i] + if (val in uniques) { + uniques[val]++ + dups[val] = uniques[val] + } else { + uniques[val] = 1 } - var value = sourceBranch.reduce(function(previousValue, currentValue) { - if (typeof previousValue != 'undefined') return previousValue[currentValue]; - }, obj); - if (typeof value === 'undefined') return; - sourceBranch.reduce(function(previousValue, currentValue, index) { - if (typeof previousValue == 'undefined') return; - if (index === sourceBranch.length - 1) delete previousValue[currentValue]; - return previousValue[currentValue]; - }, obj); - targetBranch.reduce(function(previousValue, currentValue, index) { - if (typeof previousValue[currentValue] === 'undefined') previousValue[currentValue] = {}; - if (index === targetBranch.length - 1) previousValue[currentValue] = value; - return previousValue[currentValue]; - }, obj); -}; + } + return dups +} -µ.PolyChart = function module() { - var config = [ µ.PolyChart.defaultConfig() ]; - var dispatch = d3.dispatch('hover'); - var dashArray = { - solid: 'none', - dash: [ 5, 2 ], - dot: [ 2, 5 ] - }; - var colorScale; - function exports() { - var geometryConfig = config[0].geometryConfig; - var container = geometryConfig.container; - if (typeof container == 'string') container = d3.select(container); - container.datum(config).each(function(_config, _index) { - var isStack = !!_config[0].data.yStack; - var data = _config.map(function(d, i) { - if (isStack) return d3.zip(d.data.t[0], d.data.r[0], d.data.yStack[0]); else return d3.zip(d.data.t[0], d.data.r[0]); - }); - var angularScale = geometryConfig.angularScale; - var domainMin = geometryConfig.radialScale.domain()[0]; - var generator = {}; - generator.bar = function(d, i, pI) { - var dataConfig = _config[pI].data; - var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0); - var stackTop = geometryConfig.radialScale(d[2] || 0); - var w = dataConfig.barWidth; - d3.select(this).attr({ - 'class': 'mark bar', - d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join('L') + 'Z', - transform: function(d, i) { - return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')'; - } - }); - }; - generator.dot = function(d, i, pI) { - var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d; - var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i); - d3.select(this).attr({ - 'class': 'mark dot', - d: symbol, - transform: function(d, i) { - var coord = convertToCartesian(getPolarCoordinates(stackedData)); - return 'translate(' + [ coord.x, coord.y ] + ')'; - } - }); - }; - var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function(d) { - return geometryConfig.radialScale(d[1]); - }).angle(function(d) { - return geometryConfig.angularScale(d[0]) * Math.PI / 180; - }); - generator.line = function(d, i, pI) { - var lineData = d[2] ? data[pI].map(function(d, i) { - return [ d[0], d[1] + d[2] ]; - }) : data[pI]; - d3.select(this).each(generator['dot']).style({ - opacity: function(dB, iB) { - return +_config[pI].data.dotVisible; - }, - fill: markStyle.stroke(d, i, pI) - }).attr({ - 'class': 'mark dot' - }); - if (i > 0) return; - var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]); - lineSelection.enter().insert('path'); - lineSelection.attr({ - 'class': 'line', - d: line(lineData), - transform: function(dB, iB) { - return 'rotate(' + (geometryConfig.orientation + 90) + ')'; - }, - 'pointer-events': 'none' - }).style({ - fill: function(dB, iB) { - return markStyle.fill(d, i, pI); - }, - 'fill-opacity': 0, - stroke: function(dB, iB) { - return markStyle.stroke(d, i, pI); - }, - 'stroke-width': function(dB, iB) { - return markStyle['stroke-width'](d, i, pI); - }, - 'stroke-dasharray': function(dB, iB) { - return markStyle['stroke-dasharray'](d, i, pI); - }, - opacity: function(dB, iB) { - return markStyle.opacity(d, i, pI); - }, - display: function(dB, iB) { - return markStyle.display(d, i, pI); - } - }); - }; - var angularRange = geometryConfig.angularScale.range(); - var triangleAngle = Math.abs(angularRange[1] - angularRange[0]) / data[0].length * Math.PI / 180; - var arc = d3.svg.arc().startAngle(function(d) { - return -triangleAngle / 2; - }).endAngle(function(d) { - return triangleAngle / 2; - }).innerRadius(function(d) { - return geometryConfig.radialScale(domainMin + (d[2] || 0)); - }).outerRadius(function(d) { - return geometryConfig.radialScale(domainMin + (d[2] || 0)) + geometryConfig.radialScale(d[1]); - }); - generator.arc = function(d, i, pI) { - d3.select(this).attr({ - 'class': 'mark arc', - d: arc, - transform: function(d, i) { - return 'rotate(' + (geometryConfig.orientation + angularScale(d[0]) + 90) + ')'; - } - }); - }; - var markStyle = { - fill: function(d, i, pI) { - return _config[pI].data.color; - }, - stroke: function(d, i, pI) { - return _config[pI].data.strokeColor; - }, - 'stroke-width': function(d, i, pI) { - return _config[pI].data.strokeSize + 'px'; - }, - 'stroke-dasharray': function(d, i, pI) { - return dashArray[_config[pI].data.strokeDash]; - }, - opacity: function(d, i, pI) { - return _config[pI].data.opacity; - }, - display: function(d, i, pI) { - return typeof _config[pI].data.visible === 'undefined' || _config[pI].data.visible ? 'block' : 'none'; - } - }; - var geometryLayer = d3.select(this).selectAll('g.layer').data(data); - geometryLayer.enter().append('g').attr({ - 'class': 'layer' - }); - var geometry = geometryLayer.selectAll('path.mark').data(function(d, i) { - return d; - }); - geometry.enter().append('path').attr({ - 'class': 'mark' - }); - geometry.style(markStyle).each(generator[geometryConfig.geometryType]); - geometry.exit().remove(); - geometryLayer.exit().remove(); - function getPolarCoordinates(d, i) { - var r = geometryConfig.radialScale(d[1]); - var t = (geometryConfig.angularScale(d[0]) + geometryConfig.orientation) * Math.PI / 180; - return { - r: r, - t: t - }; - } - function convertToCartesian(polarCoordinates) { - var x = polarCoordinates.r * Math.cos(polarCoordinates.t); - var y = polarCoordinates.r * Math.sin(polarCoordinates.t); - return { - x: x, - y: y - }; - } - }); - } - exports.config = function(_x) { - if (!arguments.length) return config; - _x.forEach(function(d, i) { - if (!config[i]) config[i] = {}; - extendDeepAll(config[i], µ.PolyChart.defaultConfig()); - extendDeepAll(config[i], d); - }); - return this; - }; - exports.getColorScale = function() { - return colorScale; - }; - d3.rebind(exports, dispatch, 'on'); - return exports; -}; +µ.util.duplicates = function (arr) { + return Object.keys(µ.util.duplicatesCount(arr)) +} -µ.PolyChart.defaultConfig = function() { - var config = { - data: { - name: 'geom1', - t: [ [ 1, 2, 3, 4 ] ], - r: [ [ 1, 2, 3, 4 ] ], - dotType: 'circle', - dotSize: 64, - dotVisible: false, - barWidth: 20, - color: '#ffa500', - strokeSize: 1, - strokeColor: 'silver', - strokeDash: 'solid', - opacity: 1, - index: 0, - visible: true, - visibleInLegend: true +µ.util.translator = function (obj, sourceBranch, targetBranch, reverse) { + if (reverse) { + var targetBranchCopy = targetBranch.slice() + targetBranch = sourceBranch + sourceBranch = targetBranchCopy + } + var value = sourceBranch.reduce(function (previousValue, currentValue) { + if (typeof previousValue !== 'undefined') return previousValue[currentValue] + }, obj) + if (typeof value === 'undefined') return + sourceBranch.reduce(function (previousValue, currentValue, index) { + if (typeof previousValue === 'undefined') return + if (index === sourceBranch.length - 1) delete previousValue[currentValue] + return previousValue[currentValue] + }, obj) + targetBranch.reduce(function (previousValue, currentValue, index) { + if (typeof previousValue[currentValue] === 'undefined') previousValue[currentValue] = {} + if (index === targetBranch.length - 1) previousValue[currentValue] = value + return previousValue[currentValue] + }, obj) +} + +µ.PolyChart = function module () { + var config = [ µ.PolyChart.defaultConfig() ] + var dispatch = d3.dispatch('hover') + var dashArray = { + solid: 'none', + dash: [ 5, 2 ], + dot: [ 2, 5 ] + } + var colorScale + function exports () { + var geometryConfig = config[0].geometryConfig + var container = geometryConfig.container + if (typeof container === 'string') container = d3.select(container) + container.datum(config).each(function (_config, _index) { + var isStack = !!_config[0].data.yStack + var data = _config.map(function (d, i) { + if (isStack) return d3.zip(d.data.t[0], d.data.r[0], d.data.yStack[0]); else return d3.zip(d.data.t[0], d.data.r[0]) + }) + var angularScale = geometryConfig.angularScale + var domainMin = geometryConfig.radialScale.domain()[0] + var generator = {} + generator.bar = function (d, i, pI) { + var dataConfig = _config[pI].data + var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0) + var stackTop = geometryConfig.radialScale(d[2] || 0) + var w = dataConfig.barWidth + d3.select(this).attr({ + 'class': 'mark bar', + d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join('L') + 'Z', + transform: function (d, i) { + return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')' + } + }) + } + generator.dot = function (d, i, pI) { + var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d + var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i) + d3.select(this).attr({ + 'class': 'mark dot', + d: symbol, + transform: function (d, i) { + var coord = convertToCartesian(getPolarCoordinates(stackedData)) + return 'translate(' + [ coord.x, coord.y ] + ')' + } + }) + } + var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function (d) { + return geometryConfig.radialScale(d[1]) + }).angle(function (d) { + return geometryConfig.angularScale(d[0]) * Math.PI / 180 + }) + generator.line = function (d, i, pI) { + var lineData = d[2] ? data[pI].map(function (d, i) { + return [ d[0], d[1] + d[2] ] + }) : data[pI] + d3.select(this).each(generator['dot']).style({ + opacity: function (dB, iB) { + return +_config[pI].data.dotVisible + }, + fill: markStyle.stroke(d, i, pI) + }).attr({ + 'class': 'mark dot' + }) + if (i > 0) return + var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]) + lineSelection.enter().insert('path') + lineSelection.attr({ + 'class': 'line', + d: line(lineData), + transform: function (dB, iB) { + return 'rotate(' + (geometryConfig.orientation + 90) + ')' + }, + 'pointer-events': 'none' + }).style({ + fill: function (dB, iB) { + return markStyle.fill(d, i, pI) + }, + 'fill-opacity': 0, + stroke: function (dB, iB) { + return markStyle.stroke(d, i, pI) + }, + 'stroke-width': function (dB, iB) { + return markStyle['stroke-width'](d, i, pI) + }, + 'stroke-dasharray': function (dB, iB) { + return markStyle['stroke-dasharray'](d, i, pI) + }, + opacity: function (dB, iB) { + return markStyle.opacity(d, i, pI) + }, + display: function (dB, iB) { + return markStyle.display(d, i, pI) + } + }) + } + var angularRange = geometryConfig.angularScale.range() + var triangleAngle = Math.abs(angularRange[1] - angularRange[0]) / data[0].length * Math.PI / 180 + var arc = d3.svg.arc().startAngle(function (d) { + return -triangleAngle / 2 + }).endAngle(function (d) { + return triangleAngle / 2 + }).innerRadius(function (d) { + return geometryConfig.radialScale(domainMin + (d[2] || 0)) + }).outerRadius(function (d) { + return geometryConfig.radialScale(domainMin + (d[2] || 0)) + geometryConfig.radialScale(d[1]) + }) + generator.arc = function (d, i, pI) { + d3.select(this).attr({ + 'class': 'mark arc', + d: arc, + transform: function (d, i) { + return 'rotate(' + (geometryConfig.orientation + angularScale(d[0]) + 90) + ')' + } + }) + } + var markStyle = { + fill: function (d, i, pI) { + return _config[pI].data.color + }, + stroke: function (d, i, pI) { + return _config[pI].data.strokeColor + }, + 'stroke-width': function (d, i, pI) { + return _config[pI].data.strokeSize + 'px' + }, + 'stroke-dasharray': function (d, i, pI) { + return dashArray[_config[pI].data.strokeDash] + }, + opacity: function (d, i, pI) { + return _config[pI].data.opacity }, - geometryConfig: { - geometry: 'LinePlot', - geometryType: 'arc', - direction: 'clockwise', - orientation: 0, - container: 'body', - radialScale: null, - angularScale: null, - colorScale: d3.scale.category20() + display: function (d, i, pI) { + return typeof _config[pI].data.visible === 'undefined' || _config[pI].data.visible ? 'block' : 'none' } - }; - return config; -}; + } + var geometryLayer = d3.select(this).selectAll('g.layer').data(data) + geometryLayer.enter().append('g').attr({ + 'class': 'layer' + }) + var geometry = geometryLayer.selectAll('path.mark').data(function (d, i) { + return d + }) + geometry.enter().append('path').attr({ + 'class': 'mark' + }) + geometry.style(markStyle).each(generator[geometryConfig.geometryType]) + geometry.exit().remove() + geometryLayer.exit().remove() + function getPolarCoordinates (d, i) { + var r = geometryConfig.radialScale(d[1]) + var t = (geometryConfig.angularScale(d[0]) + geometryConfig.orientation) * Math.PI / 180 + return { + r: r, + t: t + } + } + function convertToCartesian (polarCoordinates) { + var x = polarCoordinates.r * Math.cos(polarCoordinates.t) + var y = polarCoordinates.r * Math.sin(polarCoordinates.t) + return { + x: x, + y: y + } + } + }) + } + exports.config = function (_x) { + if (!arguments.length) return config + _x.forEach(function (d, i) { + if (!config[i]) config[i] = {} + extendDeepAll(config[i], µ.PolyChart.defaultConfig()) + extendDeepAll(config[i], d) + }) + return this + } + exports.getColorScale = function () { + return colorScale + } + d3.rebind(exports, dispatch, 'on') + return exports +} -µ.BarChart = function module() { - return µ.PolyChart(); -}; +µ.PolyChart.defaultConfig = function () { + var config = { + data: { + name: 'geom1', + t: [ [ 1, 2, 3, 4 ] ], + r: [ [ 1, 2, 3, 4 ] ], + dotType: 'circle', + dotSize: 64, + dotVisible: false, + barWidth: 20, + color: '#ffa500', + strokeSize: 1, + strokeColor: 'silver', + strokeDash: 'solid', + opacity: 1, + index: 0, + visible: true, + visibleInLegend: true + }, + geometryConfig: { + geometry: 'LinePlot', + geometryType: 'arc', + direction: 'clockwise', + orientation: 0, + container: 'body', + radialScale: null, + angularScale: null, + colorScale: d3.scale.category20() + } + } + return config +} -µ.BarChart.defaultConfig = function() { - var config = { - geometryConfig: { - geometryType: 'bar' - } - }; - return config; -}; +µ.BarChart = function module () { + return µ.PolyChart() +} -µ.AreaChart = function module() { - return µ.PolyChart(); -}; +µ.BarChart.defaultConfig = function () { + var config = { + geometryConfig: { + geometryType: 'bar' + } + } + return config +} -µ.AreaChart.defaultConfig = function() { - var config = { - geometryConfig: { - geometryType: 'arc' - } - }; - return config; -}; +µ.AreaChart = function module () { + return µ.PolyChart() +} -µ.DotPlot = function module() { - return µ.PolyChart(); -}; +µ.AreaChart.defaultConfig = function () { + var config = { + geometryConfig: { + geometryType: 'arc' + } + } + return config +} -µ.DotPlot.defaultConfig = function() { - var config = { - geometryConfig: { - geometryType: 'dot', - dotType: 'circle' - } - }; - return config; -}; +µ.DotPlot = function module () { + return µ.PolyChart() +} -µ.LinePlot = function module() { - return µ.PolyChart(); -}; +µ.DotPlot.defaultConfig = function () { + var config = { + geometryConfig: { + geometryType: 'dot', + dotType: 'circle' + } + } + return config +} -µ.LinePlot.defaultConfig = function() { - var config = { - geometryConfig: { - geometryType: 'line' - } - }; - return config; -}; +µ.LinePlot = function module () { + return µ.PolyChart() +} -µ.Legend = function module() { - var config = µ.Legend.defaultConfig(); - var dispatch = d3.dispatch('hover'); - function exports() { - var legendConfig = config.legendConfig; - var flattenData = config.data.map(function(d, i) { - return [].concat(d).map(function(dB, iB) { - var element = extendDeepAll({}, legendConfig.elements[i]); - element.name = dB; - element.color = [].concat(legendConfig.elements[i].color)[iB]; - return element; - }); - }); - var data = d3.merge(flattenData); - data = data.filter(function(d, i) { - return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined'); - }); - if (legendConfig.reverseOrder) data = data.reverse(); - var container = legendConfig.container; - if (typeof container == 'string' || container.nodeName) container = d3.select(container); - var colors = data.map(function(d, i) { - return d.color; - }); - var lineHeight = legendConfig.fontSize; - var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous; - var height = isContinuous ? legendConfig.height : lineHeight * data.length; - var legendContainerGroup = container.classed('legend-group', true); - var svg = legendContainerGroup.selectAll('svg').data([ 0 ]); - var svgEnter = svg.enter().append('svg').attr({ - width: 300, - height: height + lineHeight, - xmlns: 'http://www.w3.org/2000/svg', - 'xmlns:xlink': 'http://www.w3.org/1999/xlink', - version: '1.1' - }); - svgEnter.append('g').classed('legend-axis', true); - svgEnter.append('g').classed('legend-marks', true); - var dataNumbered = d3.range(data.length); - var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors); - var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints']([ 0, height ]); - var shapeGenerator = function(_type, _size) { - var squareSize = _size * 3; - if (_type === 'line') { - return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z'; - } else if (d3.svg.symbolTypes.indexOf(_type) != -1) return d3.svg.symbol().type(_type).size(squareSize)(); else return d3.svg.symbol().type('square').size(squareSize)(); - }; - if (isContinuous) { - var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({ - id: 'grad1', - x1: '0%', - y1: '0%', - x2: '0%', - y2: '100%' - }).selectAll('stop').data(colors); - gradient.enter().append('stop'); - gradient.attr({ - offset: function(d, i) { - return i / (colors.length - 1) * 100 + '%'; - } - }).style({ - 'stop-color': function(d, i) { - return d; - } - }); - svg.append('rect').classed('legend-mark', true).attr({ - height: legendConfig.height, - width: legendConfig.colorBandWidth, - fill: 'url(#grad1)' - }); - } else { - var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data); - legendElement.enter().append('path').classed('legend-mark', true); - legendElement.attr({ - transform: function(d, i) { - return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')'; - }, - d: function(d, i) { - var symbolType = d.symbol; - return shapeGenerator(symbolType, lineHeight); - }, - fill: function(d, i) { - return colorScale(i); - } - }); - legendElement.exit().remove(); - } - var legendAxis = d3.svg.axis().scale(dataScale).orient('right'); - var axis = svg.select('g.legend-axis').attr({ - transform: 'translate(' + [ isContinuous ? legendConfig.colorBandWidth : lineHeight, lineHeight / 2 ] + ')' - }).call(legendAxis); - axis.selectAll('.domain').style({ - fill: 'none', - stroke: 'none' - }); - axis.selectAll('line').style({ - fill: 'none', - stroke: isContinuous ? legendConfig.textColor : 'none' - }); - axis.selectAll('text').style({ - fill: legendConfig.textColor, - 'font-size': legendConfig.fontSize - }).text(function(d, i) { - return data[i].name; - }); - return exports; +µ.LinePlot.defaultConfig = function () { + var config = { + geometryConfig: { + geometryType: 'line' } - exports.config = function(_x) { - if (!arguments.length) return config; - extendDeepAll(config, _x); - return this; - }; - d3.rebind(exports, dispatch, 'on'); - return exports; -}; + } + return config +} -µ.Legend.defaultConfig = function(d, i) { - var config = { - data: [ 'a', 'b', 'c' ], - legendConfig: { - elements: [ { - symbol: 'line', - color: 'red' - }, { - symbol: 'square', - color: 'yellow' - }, { - symbol: 'diamond', - color: 'limegreen' - } ], - height: 150, - colorBandWidth: 30, - fontSize: 12, - container: 'body', - isContinuous: null, - textColor: 'grey', - reverseOrder: false +µ.Legend = function module () { + var config = µ.Legend.defaultConfig() + var dispatch = d3.dispatch('hover') + function exports () { + var legendConfig = config.legendConfig + var flattenData = config.data.map(function (d, i) { + return [].concat(d).map(function (dB, iB) { + var element = extendDeepAll({}, legendConfig.elements[i]) + element.name = dB + element.color = [].concat(legendConfig.elements[i].color)[iB] + return element + }) + }) + var data = d3.merge(flattenData) + data = data.filter(function (d, i) { + return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined') + }) + if (legendConfig.reverseOrder) data = data.reverse() + var container = legendConfig.container + if (typeof container === 'string' || container.nodeName) container = d3.select(container) + var colors = data.map(function (d, i) { + return d.color + }) + var lineHeight = legendConfig.fontSize + var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous + var height = isContinuous ? legendConfig.height : lineHeight * data.length + var legendContainerGroup = container.classed('legend-group', true) + var svg = legendContainerGroup.selectAll('svg').data([ 0 ]) + var svgEnter = svg.enter().append('svg').attr({ + width: 300, + height: height + lineHeight, + xmlns: 'http://www.w3.org/2000/svg', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + version: '1.1' + }) + svgEnter.append('g').classed('legend-axis', true) + svgEnter.append('g').classed('legend-marks', true) + var dataNumbered = d3.range(data.length) + var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors) + var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints']([ 0, height ]) + var shapeGenerator = function (_type, _size) { + var squareSize = _size * 3 + if (_type === 'line') { + return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z' + } else if (d3.svg.symbolTypes.indexOf(_type) != -1) return d3.svg.symbol().type(_type).size(squareSize)(); else return d3.svg.symbol().type('square').size(squareSize)() + } + if (isContinuous) { + var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({ + id: 'grad1', + x1: '0%', + y1: '0%', + x2: '0%', + y2: '100%' + }).selectAll('stop').data(colors) + gradient.enter().append('stop') + gradient.attr({ + offset: function (d, i) { + return i / (colors.length - 1) * 100 + '%' + } + }).style({ + 'stop-color': function (d, i) { + return d } - }; - return config; -}; + }) + svg.append('rect').classed('legend-mark', true).attr({ + height: legendConfig.height, + width: legendConfig.colorBandWidth, + fill: 'url(#grad1)' + }) + } else { + var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data) + legendElement.enter().append('path').classed('legend-mark', true) + legendElement.attr({ + transform: function (d, i) { + return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')' + }, + d: function (d, i) { + var symbolType = d.symbol + return shapeGenerator(symbolType, lineHeight) + }, + fill: function (d, i) { + return colorScale(i) + } + }) + legendElement.exit().remove() + } + var legendAxis = d3.svg.axis().scale(dataScale).orient('right') + var axis = svg.select('g.legend-axis').attr({ + transform: 'translate(' + [ isContinuous ? legendConfig.colorBandWidth : lineHeight, lineHeight / 2 ] + ')' + }).call(legendAxis) + axis.selectAll('.domain').style({ + fill: 'none', + stroke: 'none' + }) + axis.selectAll('line').style({ + fill: 'none', + stroke: isContinuous ? legendConfig.textColor : 'none' + }) + axis.selectAll('text').style({ + fill: legendConfig.textColor, + 'font-size': legendConfig.fontSize + }).text(function (d, i) { + return data[i].name + }) + return exports + } + exports.config = function (_x) { + if (!arguments.length) return config + extendDeepAll(config, _x) + return this + } + d3.rebind(exports, dispatch, 'on') + return exports +} -µ.tooltipPanel = function() { - var tooltipEl, tooltipTextEl, backgroundEl; - var config = { - container: null, - hasTick: false, - fontSize: 12, - color: 'white', - padding: 5 - }; - var id = 'tooltip-' + µ.tooltipPanel.uid++; - var tickSize = 10; - var exports = function() { - tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]); - var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({ - 'pointer-events': 'none', - display: 'none' - }); - backgroundEl = tooltipEnter.append('path').style({ - fill: 'white', - 'fill-opacity': .9 - }).attr({ - d: 'M0 0' - }); - tooltipTextEl = tooltipEnter.append('text').attr({ - dx: config.padding + tickSize, - dy: +config.fontSize * .3 - }); - return exports; - }; - exports.text = function(_text) { - var l = d3.hsl(config.color).l; - var strokeColor = l >= .5 ? '#aaa' : 'white'; - var fillColor = l >= .5 ? 'black' : 'white'; - var text = _text || ''; - tooltipTextEl.style({ - fill: fillColor, - 'font-size': config.fontSize + 'px' - }).text(text); - var padding = config.padding; - var bbox = tooltipTextEl.node().getBBox(); - var boxStyle = { - fill: config.color, - stroke: strokeColor, - 'stroke-width': '2px' - }; - var backGroundW = bbox.width + padding * 2 + tickSize; - var backGroundH = bbox.height + padding * 2; - backgroundEl.attr({ - d: 'M' + [ [ tickSize, -backGroundH / 2 ], [ tickSize, -backGroundH / 4 ], [ config.hasTick ? 0 : tickSize, 0 ], [ tickSize, backGroundH / 4 ], [ tickSize, backGroundH / 2 ], [ backGroundW, backGroundH / 2 ], [ backGroundW, -backGroundH / 2 ] ].join('L') + 'Z' - }).style(boxStyle); - tooltipEl.attr({ - transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')' - }); - tooltipEl.style({ - display: 'block' - }); - return exports; - }; - exports.move = function(_pos) { - if (!tooltipEl) return; - tooltipEl.attr({ - transform: 'translate(' + [ _pos[0], _pos[1] ] + ')' - }).style({ - display: 'block' - }); - return exports; - }; - exports.hide = function() { - if (!tooltipEl) return; - tooltipEl.style({ - display: 'none' - }); - return exports; - }; - exports.show = function() { - if (!tooltipEl) return; - tooltipEl.style({ - display: 'block' - }); - return exports; - }; - exports.config = function(_x) { - extendDeepAll(config, _x); - return exports; - }; - return exports; -}; +µ.Legend.defaultConfig = function (d, i) { + var config = { + data: [ 'a', 'b', 'c' ], + legendConfig: { + elements: [ { + symbol: 'line', + color: 'red' + }, { + symbol: 'square', + color: 'yellow' + }, { + symbol: 'diamond', + color: 'limegreen' + } ], + height: 150, + colorBandWidth: 30, + fontSize: 12, + container: 'body', + isContinuous: null, + textColor: 'grey', + reverseOrder: false + } + } + return config +} + +µ.tooltipPanel = function () { + var tooltipEl, tooltipTextEl, backgroundEl + var config = { + container: null, + hasTick: false, + fontSize: 12, + color: 'white', + padding: 5 + } + var id = 'tooltip-' + µ.tooltipPanel.uid++ + var tickSize = 10 + var exports = function () { + tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]) + var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({ + 'pointer-events': 'none', + display: 'none' + }) + backgroundEl = tooltipEnter.append('path').style({ + fill: 'white', + 'fill-opacity': 0.9 + }).attr({ + d: 'M0 0' + }) + tooltipTextEl = tooltipEnter.append('text').attr({ + dx: config.padding + tickSize, + dy: +config.fontSize * 0.3 + }) + return exports + } + exports.text = function (_text) { + var l = d3.hsl(config.color).l + var strokeColor = l >= 0.5 ? '#aaa' : 'white' + var fillColor = l >= 0.5 ? 'black' : 'white' + var text = _text || '' + tooltipTextEl.style({ + fill: fillColor, + 'font-size': config.fontSize + 'px' + }).text(text) + var padding = config.padding + var bbox = tooltipTextEl.node().getBBox() + var boxStyle = { + fill: config.color, + stroke: strokeColor, + 'stroke-width': '2px' + } + var backGroundW = bbox.width + padding * 2 + tickSize + var backGroundH = bbox.height + padding * 2 + backgroundEl.attr({ + d: 'M' + [ [ tickSize, -backGroundH / 2 ], [ tickSize, -backGroundH / 4 ], [ config.hasTick ? 0 : tickSize, 0 ], [ tickSize, backGroundH / 4 ], [ tickSize, backGroundH / 2 ], [ backGroundW, backGroundH / 2 ], [ backGroundW, -backGroundH / 2 ] ].join('L') + 'Z' + }).style(boxStyle) + tooltipEl.attr({ + transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')' + }) + tooltipEl.style({ + display: 'block' + }) + return exports + } + exports.move = function (_pos) { + if (!tooltipEl) return + tooltipEl.attr({ + transform: 'translate(' + [ _pos[0], _pos[1] ] + ')' + }).style({ + display: 'block' + }) + return exports + } + exports.hide = function () { + if (!tooltipEl) return + tooltipEl.style({ + display: 'none' + }) + return exports + } + exports.show = function () { + if (!tooltipEl) return + tooltipEl.style({ + display: 'block' + }) + return exports + } + exports.config = function (_x) { + extendDeepAll(config, _x) + return exports + } + return exports +} -µ.tooltipPanel.uid = 1; +µ.tooltipPanel.uid = 1 -µ.adapter = {}; +µ.adapter = {} -µ.adapter.plotly = function module() { - var exports = {}; - exports.convert = function(_inputConfig, reverse) { - var outputConfig = {}; - if (_inputConfig.data) { - outputConfig.data = _inputConfig.data.map(function(d, i) { - var r = extendDeepAll({}, d); - var toTranslate = [ +µ.adapter.plotly = function module () { + var exports = {} + exports.convert = function (_inputConfig, reverse) { + var outputConfig = {} + if (_inputConfig.data) { + outputConfig.data = _inputConfig.data.map(function (d, i) { + var r = extendDeepAll({}, d) + var toTranslate = [ [ r, [ 'marker', 'color' ], [ 'color' ] ], [ r, [ 'marker', 'opacity' ], [ 'opacity' ] ], [ r, [ 'marker', 'line', 'color' ], [ 'strokeColor' ] ], @@ -1286,50 +1296,50 @@ var µ = module.exports = { version: '0.2.2' }; [ r, [ 'marker', 'barWidth' ], [ 'barWidth' ] ], [ r, [ 'line', 'interpolation' ], [ 'lineInterpolation' ] ], [ r, [ 'showlegend' ], [ 'visibleInLegend' ] ] - ]; - toTranslate.forEach(function(d, i) { - µ.util.translator.apply(null, d.concat(reverse)); - }); + ] + toTranslate.forEach(function (d, i) { + µ.util.translator.apply(null, d.concat(reverse)) + }) - if (!reverse) delete r.marker; - if (reverse) delete r.groupId; - if (!reverse) { - if (r.type === 'scatter') { - if (r.mode === 'lines') r.geometry = 'LinePlot'; else if (r.mode === 'markers') r.geometry = 'DotPlot'; else if (r.mode === 'lines+markers') { - r.geometry = 'LinePlot'; - r.dotVisible = true; - } - } else if (r.type === 'area') r.geometry = 'AreaChart'; else if (r.type === 'bar') r.geometry = 'BarChart'; - delete r.mode; - delete r.type; - } else { - if (r.geometry === 'LinePlot') { - r.type = 'scatter'; - if (r.dotVisible === true) { - delete r.dotVisible; - r.mode = 'lines+markers'; - } else r.mode = 'lines'; - } else if (r.geometry === 'DotPlot') { - r.type = 'scatter'; - r.mode = 'markers'; - } else if (r.geometry === 'AreaChart') r.type = 'area'; else if (r.geometry === 'BarChart') r.type = 'bar'; - delete r.geometry; - } - return r; - }); - if (!reverse && _inputConfig.layout && _inputConfig.layout.barmode === 'stack') { - var duplicates = µ.util.duplicates(outputConfig.data.map(function(d, i) { - return d.geometry; - })); - outputConfig.data.forEach(function(d, i) { - var idx = duplicates.indexOf(d.geometry); - if (idx != -1) outputConfig.data[i].groupId = idx; - }); + if (!reverse) delete r.marker + if (reverse) delete r.groupId + if (!reverse) { + if (r.type === 'scatter') { + if (r.mode === 'lines') r.geometry = 'LinePlot'; else if (r.mode === 'markers') r.geometry = 'DotPlot'; else if (r.mode === 'lines+markers') { + r.geometry = 'LinePlot' + r.dotVisible = true } + } else if (r.type === 'area') r.geometry = 'AreaChart'; else if (r.type === 'bar') r.geometry = 'BarChart' + delete r.mode + delete r.type + } else { + if (r.geometry === 'LinePlot') { + r.type = 'scatter' + if (r.dotVisible === true) { + delete r.dotVisible + r.mode = 'lines+markers' + } else r.mode = 'lines' + } else if (r.geometry === 'DotPlot') { + r.type = 'scatter' + r.mode = 'markers' + } else if (r.geometry === 'AreaChart') r.type = 'area'; else if (r.geometry === 'BarChart') r.type = 'bar' + delete r.geometry } - if (_inputConfig.layout) { - var r = extendDeepAll({}, _inputConfig.layout); - var toTranslate = [ + return r + }) + if (!reverse && _inputConfig.layout && _inputConfig.layout.barmode === 'stack') { + var duplicates = µ.util.duplicates(outputConfig.data.map(function (d, i) { + return d.geometry + })) + outputConfig.data.forEach(function (d, i) { + var idx = duplicates.indexOf(d.geometry) + if (idx != -1) outputConfig.data[i].groupId = idx + }) + } + } + if (_inputConfig.layout) { + var r = extendDeepAll({}, _inputConfig.layout) + var toTranslate = [ [ r, [ 'plot_bgcolor' ], [ 'backgroundColor' ] ], [ r, [ 'showlegend' ], [ 'showLegend' ] ], [ r, [ 'radialaxis' ], [ 'radialAxis' ] ], @@ -1360,58 +1370,58 @@ var µ = module.exports = { version: '0.2.2' }; [ r.legend, [ 'traceorder' ], [ 'reverseOrder' ] ], [ r, [ 'labeloffset' ], [ 'labelOffset' ] ], [ r, [ 'defaultcolorrange' ], [ 'defaultColorRange' ] ] - ]; - toTranslate.forEach(function(d, i) { - µ.util.translator.apply(null, d.concat(reverse)); - }); + ] + toTranslate.forEach(function (d, i) { + µ.util.translator.apply(null, d.concat(reverse)) + }) - if (!reverse) { - if (r.angularAxis && typeof r.angularAxis.ticklen !== 'undefined') r.tickLength = r.angularAxis.ticklen; - if (r.angularAxis && typeof r.angularAxis.tickcolor !== 'undefined') r.tickColor = r.angularAxis.tickcolor; - } else { - if (typeof r.tickLength !== 'undefined') { - r.angularaxis.ticklen = r.tickLength; - delete r.tickLength; - } - if (r.tickColor) { - r.angularaxis.tickcolor = r.tickColor; - delete r.tickColor; - } - } - if (r.legend && typeof r.legend.reverseOrder != 'boolean') { - r.legend.reverseOrder = r.legend.reverseOrder != 'normal'; - } - if (r.legend && typeof r.legend.traceorder == 'boolean') { - r.legend.traceorder = r.legend.traceorder ? 'reversed' : 'normal'; - delete r.legend.reverseOrder; - } - if (r.margin && typeof r.margin.t != 'undefined') { - var source = [ 't', 'r', 'b', 'l', 'pad' ]; - var target = [ 'top', 'right', 'bottom', 'left', 'pad' ]; - var margin = {}; - d3.entries(r.margin).forEach(function(dB, iB) { - margin[target[source.indexOf(dB.key)]] = dB.value; - }); - r.margin = margin; - } - if (reverse) { - delete r.needsEndSpacing; - delete r.minorTickColor; - delete r.minorTicks; - delete r.angularaxis.ticksCount; - delete r.angularaxis.ticksCount; - delete r.angularaxis.ticksStep; - delete r.angularaxis.rewriteTicks; - delete r.angularaxis.nticks; - delete r.radialaxis.ticksCount; - delete r.radialaxis.ticksCount; - delete r.radialaxis.ticksStep; - delete r.radialaxis.rewriteTicks; - delete r.radialaxis.nticks; - } - outputConfig.layout = r; + if (!reverse) { + if (r.angularAxis && typeof r.angularAxis.ticklen !== 'undefined') r.tickLength = r.angularAxis.ticklen + if (r.angularAxis && typeof r.angularAxis.tickcolor !== 'undefined') r.tickColor = r.angularAxis.tickcolor + } else { + if (typeof r.tickLength !== 'undefined') { + r.angularaxis.ticklen = r.tickLength + delete r.tickLength } - return outputConfig; - }; - return exports; -}; + if (r.tickColor) { + r.angularaxis.tickcolor = r.tickColor + delete r.tickColor + } + } + if (r.legend && typeof r.legend.reverseOrder !== 'boolean') { + r.legend.reverseOrder = r.legend.reverseOrder != 'normal' + } + if (r.legend && typeof r.legend.traceorder === 'boolean') { + r.legend.traceorder = r.legend.traceorder ? 'reversed' : 'normal' + delete r.legend.reverseOrder + } + if (r.margin && typeof r.margin.t !== 'undefined') { + var source = [ 't', 'r', 'b', 'l', 'pad' ] + var target = [ 'top', 'right', 'bottom', 'left', 'pad' ] + var margin = {} + d3.entries(r.margin).forEach(function (dB, iB) { + margin[target[source.indexOf(dB.key)]] = dB.value + }) + r.margin = margin + } + if (reverse) { + delete r.needsEndSpacing + delete r.minorTickColor + delete r.minorTicks + delete r.angularaxis.ticksCount + delete r.angularaxis.ticksCount + delete r.angularaxis.ticksStep + delete r.angularaxis.rewriteTicks + delete r.angularaxis.nticks + delete r.radialaxis.ticksCount + delete r.radialaxis.ticksCount + delete r.radialaxis.ticksStep + delete r.radialaxis.rewriteTicks + delete r.radialaxis.nticks + } + outputConfig.layout = r + } + return outputConfig + } + return exports +} diff --git a/src/plots/polar/micropolar_manager.js b/src/plots/polar/micropolar_manager.js index b685ec5f6e4..3cc453371f8 100644 --- a/src/plots/polar/micropolar_manager.js +++ b/src/plots/polar/micropolar_manager.js @@ -8,77 +8,77 @@ /* eslint-disable new-cap */ -'use strict'; +'use strict' -var d3 = require('d3'); -var Lib = require('../../lib'); -var Color = require('../../components/color'); +var d3 = require('d3') +var Lib = require('../../lib') +var Color = require('../../components/color') -var micropolar = require('./micropolar'); -var UndoManager = require('./undo_manager'); -var extendDeepAll = Lib.extendDeepAll; +var micropolar = require('./micropolar') +var UndoManager = require('./undo_manager') +var extendDeepAll = Lib.extendDeepAll -var manager = module.exports = {}; +var manager = module.exports = {} -manager.framework = function(_gd) { - var config, previousConfigClone, plot, convertedInput, container; - var undoManager = new UndoManager(); +manager.framework = function (_gd) { + var config, previousConfigClone, plot, convertedInput, container + var undoManager = new UndoManager() - function exports(_inputConfig, _container) { - if(_container) container = _container; - d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove(); + function exports (_inputConfig, _container) { + if (_container) container = _container + d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove() - config = (!config) ? + config = (!config) ? _inputConfig : - extendDeepAll(config, _inputConfig); + extendDeepAll(config, _inputConfig) - if(!plot) plot = micropolar.Axis(); - convertedInput = micropolar.adapter.plotly().convert(config); - plot.config(convertedInput).render(container); - _gd.data = config.data; - _gd.layout = config.layout; - manager.fillLayout(_gd); - return config; - } - exports.isPolar = true; - exports.svg = function() { return plot.svg(); }; - exports.getConfig = function() { return config; }; - exports.getLiveConfig = function() { - return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true); - }; - exports.getLiveScales = function() { return {t: plot.angularScale(), r: plot.radialScale()}; }; - exports.setUndoPoint = function() { - var that = this; - var configClone = micropolar.util.cloneJson(config); - (function(_configClone, _previousConfigClone) { - undoManager.add({ - undo: function() { - if(_previousConfigClone) that(_previousConfigClone); - }, - redo: function() { - that(_configClone); - } - }); - })(configClone, previousConfigClone); - previousConfigClone = micropolar.util.cloneJson(configClone); - }; - exports.undo = function() { undoManager.undo(); }; - exports.redo = function() { undoManager.redo(); }; - return exports; -}; + if (!plot) plot = micropolar.Axis() + convertedInput = micropolar.adapter.plotly().convert(config) + plot.config(convertedInput).render(container) + _gd.data = config.data + _gd.layout = config.layout + manager.fillLayout(_gd) + return config + } + exports.isPolar = true + exports.svg = function () { return plot.svg() } + exports.getConfig = function () { return config } + exports.getLiveConfig = function () { + return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true) + } + exports.getLiveScales = function () { return {t: plot.angularScale(), r: plot.radialScale()} } + exports.setUndoPoint = function () { + var that = this + var configClone = micropolar.util.cloneJson(config); + (function (_configClone, _previousConfigClone) { + undoManager.add({ + undo: function () { + if (_previousConfigClone) that(_previousConfigClone) + }, + redo: function () { + that(_configClone) + } + }) + })(configClone, previousConfigClone) + previousConfigClone = micropolar.util.cloneJson(configClone) + } + exports.undo = function () { undoManager.undo() } + exports.redo = function () { undoManager.redo() } + return exports +} -manager.fillLayout = function(_gd) { - var container = d3.select(_gd).selectAll('.plot-container'), - paperDiv = container.selectAll('.svg-container'), - paper = _gd.framework && _gd.framework.svg && _gd.framework.svg(), - dflts = { - width: 800, - height: 600, - paper_bgcolor: Color.background, - _container: container, - _paperdiv: paperDiv, - _paper: paper - }; +manager.fillLayout = function (_gd) { + var container = d3.select(_gd).selectAll('.plot-container'), + paperDiv = container.selectAll('.svg-container'), + paper = _gd.framework && _gd.framework.svg && _gd.framework.svg(), + dflts = { + width: 800, + height: 600, + paper_bgcolor: Color.background, + _container: container, + _paperdiv: paperDiv, + _paper: paper + } - _gd._fullLayout = extendDeepAll(dflts, _gd.layout); -}; + _gd._fullLayout = extendDeepAll(dflts, _gd.layout) +} diff --git a/src/plots/polar/undo_manager.js b/src/plots/polar/undo_manager.js index fe8b58f9c27..5acc0de505b 100644 --- a/src/plots/polar/undo_manager.js +++ b/src/plots/polar/undo_manager.js @@ -6,59 +6,59 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' // Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager // Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com -module.exports = function UndoManager() { - var undoCommands = [], - index = -1, - isExecuting = false, - callback; +module.exports = function UndoManager () { + var undoCommands = [], + index = -1, + isExecuting = false, + callback - function execute(command, action) { - if(!command) return this; + function execute (command, action) { + if (!command) return this - isExecuting = true; - command[action](); - isExecuting = false; + isExecuting = true + command[action]() + isExecuting = false - return this; - } + return this + } - return { - add: function(command) { - if(isExecuting) return this; - undoCommands.splice(index + 1, undoCommands.length - index); - undoCommands.push(command); - index = undoCommands.length - 1; - return this; - }, - setCallback: function(callbackFunc) { callback = callbackFunc; }, - undo: function() { - var command = undoCommands[index]; - if(!command) return this; - execute(command, 'undo'); - index -= 1; - if(callback) callback(command.undo); - return this; - }, - redo: function() { - var command = undoCommands[index + 1]; - if(!command) return this; - execute(command, 'redo'); - index += 1; - if(callback) callback(command.redo); - return this; - }, - clear: function() { - undoCommands = []; - index = -1; - }, - hasUndo: function() { return index !== -1; }, - hasRedo: function() { return index < (undoCommands.length - 1); }, - getCommands: function() { return undoCommands; }, - getPreviousCommand: function() { return undoCommands[index - 1]; }, - getIndex: function() { return index; } - }; -}; + return { + add: function (command) { + if (isExecuting) return this + undoCommands.splice(index + 1, undoCommands.length - index) + undoCommands.push(command) + index = undoCommands.length - 1 + return this + }, + setCallback: function (callbackFunc) { callback = callbackFunc }, + undo: function () { + var command = undoCommands[index] + if (!command) return this + execute(command, 'undo') + index -= 1 + if (callback) callback(command.undo) + return this + }, + redo: function () { + var command = undoCommands[index + 1] + if (!command) return this + execute(command, 'redo') + index += 1 + if (callback) callback(command.redo) + return this + }, + clear: function () { + undoCommands = [] + index = -1 + }, + hasUndo: function () { return index !== -1 }, + hasRedo: function () { return index < (undoCommands.length - 1) }, + getCommands: function () { return undoCommands }, + getPreviousCommand: function () { return undoCommands[index - 1] }, + getIndex: function () { return index } + } +} diff --git a/src/plots/subplot_defaults.js b/src/plots/subplot_defaults.js index 1da3202973d..11e3d3e1547 100644 --- a/src/plots/subplot_defaults.js +++ b/src/plots/subplot_defaults.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../lib'); -var Plots = require('./plots'); - +var Lib = require('../lib') +var Plots = require('./plots') /** * Find and supply defaults to all subplots of a given type @@ -40,34 +38,34 @@ var Plots = require('./plots'); * additional items needed by this function here as well * } */ -module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) { - var subplotType = opts.type, - subplotAttributes = opts.attributes, - handleDefaults = opts.handleDefaults, - partition = opts.partition || 'x'; +module.exports = function handleSubplotDefaults (layoutIn, layoutOut, fullData, opts) { + var subplotType = opts.type, + subplotAttributes = opts.attributes, + handleDefaults = opts.handleDefaults, + partition = opts.partition || 'x' - var ids = Plots.findSubplotIds(fullData, subplotType), - idsLength = ids.length; + var ids = Plots.findSubplotIds(fullData, subplotType), + idsLength = ids.length - var subplotLayoutIn, subplotLayoutOut; + var subplotLayoutIn, subplotLayoutOut - function coerce(attr, dflt) { - return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt) + } - for(var i = 0; i < idsLength; i++) { - var id = ids[i]; + for (var i = 0; i < idsLength; i++) { + var id = ids[i] // ternary traces get a layout ternary for free! - if(layoutIn[id]) subplotLayoutIn = layoutIn[id]; - else subplotLayoutIn = layoutIn[id] = {}; + if (layoutIn[id]) subplotLayoutIn = layoutIn[id] + else subplotLayoutIn = layoutIn[id] = {} - layoutOut[id] = subplotLayoutOut = {}; + layoutOut[id] = subplotLayoutOut = {} - coerce('domain.' + partition, [i / idsLength, (i + 1) / idsLength]); - coerce('domain.' + {x: 'y', y: 'x'}[partition]); + coerce('domain.' + partition, [i / idsLength, (i + 1) / idsLength]) + coerce('domain.' + {x: 'y', y: 'x'}[partition]) - opts.id = id; - handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts); - } -}; + opts.id = id + handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts) + } +} diff --git a/src/plots/ternary/index.js b/src/plots/ternary/index.js index e1da80af7b1..5a879335133 100644 --- a/src/plots/ternary/index.js +++ b/src/plots/ternary/index.js @@ -6,67 +6,65 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Ternary = require('./ternary') -var Ternary = require('./ternary'); +var Plots = require('../../plots/plots') -var Plots = require('../../plots/plots'); +exports.name = 'ternary' +exports.attr = 'subplot' -exports.name = 'ternary'; +exports.idRoot = 'ternary' -exports.attr = 'subplot'; +exports.idRegex = /^ternary([2-9]|[1-9][0-9]+)?$/ -exports.idRoot = 'ternary'; +exports.attrRegex = /^ternary([2-9]|[1-9][0-9]+)?$/ -exports.idRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; +exports.attributes = require('./layout/attributes') -exports.attrRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; +exports.layoutAttributes = require('./layout/layout_attributes') -exports.attributes = require('./layout/attributes'); +exports.supplyLayoutDefaults = require('./layout/defaults') -exports.layoutAttributes = require('./layout/layout_attributes'); +exports.plot = function plotTernary (gd) { + var fullLayout = gd._fullLayout, + calcData = gd.calcdata, + ternaryIds = Plots.getSubplotIds(fullLayout, 'ternary') -exports.supplyLayoutDefaults = require('./layout/defaults'); - -exports.plot = function plotTernary(gd) { - var fullLayout = gd._fullLayout, - calcData = gd.calcdata, - ternaryIds = Plots.getSubplotIds(fullLayout, 'ternary'); - - for(var i = 0; i < ternaryIds.length; i++) { - var ternaryId = ternaryIds[i], - ternaryCalcData = Plots.getSubplotCalcData(calcData, 'ternary', ternaryId), - ternary = fullLayout[ternaryId]._subplot; + for (var i = 0; i < ternaryIds.length; i++) { + var ternaryId = ternaryIds[i], + ternaryCalcData = Plots.getSubplotCalcData(calcData, 'ternary', ternaryId), + ternary = fullLayout[ternaryId]._subplot // If ternary is not instantiated, create one! - if(!ternary) { - ternary = new Ternary({ - id: ternaryId, - graphDiv: gd, - container: fullLayout._ternarylayer.node() - }, + if (!ternary) { + ternary = new Ternary({ + id: ternaryId, + graphDiv: gd, + container: fullLayout._ternarylayer.node() + }, fullLayout - ); + ) - fullLayout[ternaryId]._subplot = ternary; - } - - ternary.plot(ternaryCalcData, fullLayout, gd._promises); + fullLayout[ternaryId]._subplot = ternary } -}; -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldTernaryKeys = Plots.getSubplotIds(oldFullLayout, 'ternary'); + ternary.plot(ternaryCalcData, fullLayout, gd._promises) + } +} + +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldTernaryKeys = Plots.getSubplotIds(oldFullLayout, 'ternary') - for(var i = 0; i < oldTernaryKeys.length; i++) { - var oldTernaryKey = oldTernaryKeys[i]; - var oldTernary = oldFullLayout[oldTernaryKey]._subplot; + for (var i = 0; i < oldTernaryKeys.length; i++) { + var oldTernaryKey = oldTernaryKeys[i] + var oldTernary = oldFullLayout[oldTernaryKey]._subplot - if(!newFullLayout[oldTernaryKey] && !!oldTernary) { - oldTernary.plotContainer.remove(); - oldTernary.clipDef.remove(); - } + if (!newFullLayout[oldTernaryKey] && !!oldTernary) { + oldTernary.plotContainer.remove() + oldTernary.clipDef.remove() } -}; + } +} diff --git a/src/plots/ternary/layout/attributes.js b/src/plots/ternary/layout/attributes.js index 0a95e1deb33..bf9116a9967 100644 --- a/src/plots/ternary/layout/attributes.js +++ b/src/plots/ternary/layout/attributes.js @@ -6,19 +6,18 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - subplot: { - valType: 'subplotid', - role: 'info', - dflt: 'ternary', - description: [ - 'Sets a reference between this trace\'s data coordinates and', - 'a ternary subplot.', - 'If *ternary* (the default value), the data refer to `layout.ternary`.', - 'If *ternary2*, the data refer to `layout.ternary2`, and so on.' - ].join(' ') - } -}; + subplot: { + valType: 'subplotid', + role: 'info', + dflt: 'ternary', + description: [ + 'Sets a reference between this trace\'s data coordinates and', + 'a ternary subplot.', + 'If *ternary* (the default value), the data refer to `layout.ternary`.', + 'If *ternary2*, the data refer to `layout.ternary2`, and so on.' + ].join(' ') + } +} diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index 05d34dfef19..2c6937ac0ed 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -6,58 +6,56 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - - -var axesAttrs = require('../../cartesian/layout_attributes'); -var extendFlat = require('../../../lib/extend').extendFlat; +'use strict' +var axesAttrs = require('../../cartesian/layout_attributes') +var extendFlat = require('../../../lib/extend').extendFlat module.exports = { - title: axesAttrs.title, - titlefont: axesAttrs.titlefont, - color: axesAttrs.color, + title: axesAttrs.title, + titlefont: axesAttrs.titlefont, + color: axesAttrs.color, // ticks - tickmode: axesAttrs.tickmode, - nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}), - tick0: axesAttrs.tick0, - dtick: axesAttrs.dtick, - tickvals: axesAttrs.tickvals, - ticktext: axesAttrs.ticktext, - ticks: axesAttrs.ticks, - ticklen: axesAttrs.ticklen, - tickwidth: axesAttrs.tickwidth, - tickcolor: axesAttrs.tickcolor, - showticklabels: axesAttrs.showticklabels, - showtickprefix: axesAttrs.showtickprefix, - tickprefix: axesAttrs.tickprefix, - showticksuffix: axesAttrs.showticksuffix, - ticksuffix: axesAttrs.ticksuffix, - showexponent: axesAttrs.showexponent, - exponentformat: axesAttrs.exponentformat, - separatethousands: axesAttrs.separatethousands, - tickfont: axesAttrs.tickfont, - tickangle: axesAttrs.tickangle, - tickformat: axesAttrs.tickformat, - hoverformat: axesAttrs.hoverformat, + tickmode: axesAttrs.tickmode, + nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}), + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, + ticks: axesAttrs.ticks, + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + showtickprefix: axesAttrs.showtickprefix, + tickprefix: axesAttrs.tickprefix, + showticksuffix: axesAttrs.showticksuffix, + ticksuffix: axesAttrs.ticksuffix, + showexponent: axesAttrs.showexponent, + exponentformat: axesAttrs.exponentformat, + separatethousands: axesAttrs.separatethousands, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + hoverformat: axesAttrs.hoverformat, // lines and grids - showline: extendFlat({}, axesAttrs.showline, {dflt: true}), - linecolor: axesAttrs.linecolor, - linewidth: axesAttrs.linewidth, - showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}), - gridcolor: axesAttrs.gridcolor, - gridwidth: axesAttrs.gridwidth, + showline: extendFlat({}, axesAttrs.showline, {dflt: true}), + linecolor: axesAttrs.linecolor, + linewidth: axesAttrs.linewidth, + showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}), + gridcolor: axesAttrs.gridcolor, + gridwidth: axesAttrs.gridwidth, // range - min: { - valType: 'number', - dflt: 0, - role: 'info', - min: 0, - description: [ - 'The minimum value visible on this axis.', - 'The maximum is determined by the sum minus the minimum', - 'values of the other two axes. The full view corresponds to', - 'all the minima set to zero.' - ].join(' ') - } -}; + min: { + valType: 'number', + dflt: 0, + role: 'info', + min: 0, + description: [ + 'The minimum value visible on this axis.', + 'The maximum is determined by the sum minus the minimum', + 'values of the other two axes. The full view corresponds to', + 'all the minima set to zero.' + ].join(' ') + } +} diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 0ed502a839e..63cb639ff7f 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -6,77 +6,74 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' +var colorMix = require('tinycolor2').mix -'use strict'; -var colorMix = require('tinycolor2').mix; +var Lib = require('../../../lib') -var Lib = require('../../../lib'); +var layoutAttributes = require('./axis_attributes') +var handleTickLabelDefaults = require('../../cartesian/tick_label_defaults') +var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults') +var handleTickValueDefaults = require('../../cartesian/tick_value_defaults') -var layoutAttributes = require('./axis_attributes'); -var handleTickLabelDefaults = require('../../cartesian/tick_label_defaults'); -var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults'); -var handleTickValueDefaults = require('../../cartesian/tick_value_defaults'); +module.exports = function supplyLayoutDefaults (containerIn, containerOut, options) { + function coerce (attr, dflt) { + return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt) + } + containerOut.type = 'linear' // no other types allowed for ternary -module.exports = function supplyLayoutDefaults(containerIn, containerOut, options) { - - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt); - } - - containerOut.type = 'linear'; // no other types allowed for ternary - - var dfltColor = coerce('color'); + var dfltColor = coerce('color') // if axis.color was provided, use it for fonts too; otherwise, // inherit from global font color in case that was provided. - var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : options.font.color; + var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : options.font.color - var axName = containerOut._name, - letterUpper = axName.charAt(0).toUpperCase(), - dfltTitle = 'Component ' + letterUpper; + var axName = containerOut._name, + letterUpper = axName.charAt(0).toUpperCase(), + dfltTitle = 'Component ' + letterUpper - var title = coerce('title', dfltTitle); - containerOut._hovertitle = title === dfltTitle ? title : letterUpper; + var title = coerce('title', dfltTitle) + containerOut._hovertitle = title === dfltTitle ? title : letterUpper - Lib.coerceFont(coerce, 'titlefont', { - family: options.font.family, - size: Math.round(options.font.size * 1.2), - color: dfltFontColor - }); + Lib.coerceFont(coerce, 'titlefont', { + family: options.font.family, + size: Math.round(options.font.size * 1.2), + color: dfltFontColor + }) // range is just set by 'min' - max is determined by the other axes mins - coerce('min'); - - handleTickValueDefaults(containerIn, containerOut, coerce, 'linear'); - handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', - { noHover: false }); - handleTickMarkDefaults(containerIn, containerOut, coerce, - { outerTicks: true }); - - var showTickLabels = coerce('showticklabels'); - if(showTickLabels) { - Lib.coerceFont(coerce, 'tickfont', { - family: options.font.family, - size: options.font.size, - color: dfltFontColor - }); - coerce('tickangle'); - coerce('tickformat'); - } - - coerce('hoverformat'); - - var showLine = coerce('showline'); - if(showLine) { - coerce('linecolor', dfltColor); - coerce('linewidth'); - } - - var showGridLines = coerce('showgrid'); - if(showGridLines) { + coerce('min') + + handleTickValueDefaults(containerIn, containerOut, coerce, 'linear') + handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', + { noHover: false }) + handleTickMarkDefaults(containerIn, containerOut, coerce, + { outerTicks: true }) + + var showTickLabels = coerce('showticklabels') + if (showTickLabels) { + Lib.coerceFont(coerce, 'tickfont', { + family: options.font.family, + size: options.font.size, + color: dfltFontColor + }) + coerce('tickangle') + coerce('tickformat') + } + + coerce('hoverformat') + + var showLine = coerce('showline') + if (showLine) { + coerce('linecolor', dfltColor) + coerce('linewidth') + } + + var showGridLines = coerce('showgrid') + if (showGridLines) { // default grid color is darker here (60%, vs cartesian default ~91%) // because the grid is not square so the eye needs heavier cues to follow - coerce('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString()); - coerce('gridwidth'); - } -}; + coerce('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString()) + coerce('gridwidth') + } +} diff --git a/src/plots/ternary/layout/defaults.js b/src/plots/ternary/layout/defaults.js index a6b3cface36..b266cccde65 100644 --- a/src/plots/ternary/layout/defaults.js +++ b/src/plots/ternary/layout/defaults.js @@ -6,56 +6,55 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../../components/color') -var Color = require('../../../components/color'); +var handleSubplotDefaults = require('../../subplot_defaults') +var layoutAttributes = require('./layout_attributes') +var handleAxisDefaults = require('./axis_defaults') -var handleSubplotDefaults = require('../../subplot_defaults'); -var layoutAttributes = require('./layout_attributes'); -var handleAxisDefaults = require('./axis_defaults'); +var axesNames = ['aaxis', 'baxis', 'caxis'] -var axesNames = ['aaxis', 'baxis', 'caxis']; - -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - handleSubplotDefaults(layoutIn, layoutOut, fullData, { - type: 'ternary', - attributes: layoutAttributes, - handleDefaults: handleTernaryDefaults, - font: layoutOut.font, - paper_bgcolor: layoutOut.paper_bgcolor - }); -}; +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'ternary', + attributes: layoutAttributes, + handleDefaults: handleTernaryDefaults, + font: layoutOut.font, + paper_bgcolor: layoutOut.paper_bgcolor + }) +} -function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) { - var bgColor = coerce('bgcolor'); - var sum = coerce('sum'); - options.bgColor = Color.combine(bgColor, options.paper_bgcolor); - var axName, containerIn, containerOut; +function handleTernaryDefaults (ternaryLayoutIn, ternaryLayoutOut, coerce, options) { + var bgColor = coerce('bgcolor') + var sum = coerce('sum') + options.bgColor = Color.combine(bgColor, options.paper_bgcolor) + var axName, containerIn, containerOut // TODO: allow most (if not all) axis attributes to be set // in the outer container and used as defaults in the individual axes? - for(var j = 0; j < axesNames.length; j++) { - axName = axesNames[j]; - containerIn = ternaryLayoutIn[axName] || {}; - containerOut = ternaryLayoutOut[axName] = {_name: axName}; + for (var j = 0; j < axesNames.length; j++) { + axName = axesNames[j] + containerIn = ternaryLayoutIn[axName] || {} + containerOut = ternaryLayoutOut[axName] = {_name: axName} - handleAxisDefaults(containerIn, containerOut, options); - } + handleAxisDefaults(containerIn, containerOut, options) + } // if the min values contradict each other, set them all to default (0) // and delete *all* the inputs so the user doesn't get confused later by // changing one and having them all change. - var aaxis = ternaryLayoutOut.aaxis, - baxis = ternaryLayoutOut.baxis, - caxis = ternaryLayoutOut.caxis; - if(aaxis.min + baxis.min + caxis.min >= sum) { - aaxis.min = 0; - baxis.min = 0; - caxis.min = 0; - if(ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min; - if(ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min; - if(ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min; - } + var aaxis = ternaryLayoutOut.aaxis, + baxis = ternaryLayoutOut.baxis, + caxis = ternaryLayoutOut.caxis + if (aaxis.min + baxis.min + caxis.min >= sum) { + aaxis.min = 0 + baxis.min = 0 + caxis.min = 0 + if (ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min + if (ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min + if (ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min + } } diff --git a/src/plots/ternary/layout/layout_attributes.js b/src/plots/ternary/layout/layout_attributes.js index 4ac0400e472..6e949fd9a94 100644 --- a/src/plots/ternary/layout/layout_attributes.js +++ b/src/plots/ternary/layout/layout_attributes.js @@ -6,58 +6,57 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var colorAttrs = require('../../../components/color/attributes'); -var ternaryAxesAttrs = require('./axis_attributes'); +'use strict' +var colorAttrs = require('../../../components/color/attributes') +var ternaryAxesAttrs = require('./axis_attributes') module.exports = { - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this subplot', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this subplot', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this subplot', - '(in plot fraction).' - ].join(' ') - } - }, - bgcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.background, - description: 'Set the background color of the subplot' - }, - sum: { - valType: 'number', - role: 'info', - dflt: 1, - min: 0, - description: [ - 'The number each triplet should sum to,', - 'and the maximum range of each axis' - ].join(' ') - }, - aaxis: ternaryAxesAttrs, - baxis: ternaryAxesAttrs, - caxis: ternaryAxesAttrs -}; + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this subplot', + '(in plot fraction).' + ].join(' ') + } + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: 'Set the background color of the subplot' + }, + sum: { + valType: 'number', + role: 'info', + dflt: 1, + min: 0, + description: [ + 'The number each triplet should sum to,', + 'and the maximum range of each axis' + ].join(' ') + }, + aaxis: ternaryAxesAttrs, + baxis: ternaryAxesAttrs, + caxis: ternaryAxesAttrs +} diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 547e427af99..d3ab9a25c16 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -6,638 +6,630 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var d3 = require('d3'); -var tinycolor = require('tinycolor2'); - -var Plotly = require('../../plotly'); -var Lib = require('../../lib'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var setConvert = require('../cartesian/set_convert'); -var extendFlat = require('../../lib/extend').extendFlat; -var Plots = require('../plots'); -var Axes = require('../cartesian/axes'); -var dragElement = require('../../components/dragelement'); -var Titles = require('../../components/titles'); -var prepSelect = require('../cartesian/select'); -var constants = require('../cartesian/constants'); -var fx = require('../cartesian/graph_interact'); - - -function Ternary(options, fullLayout) { - this.id = options.id; - this.graphDiv = options.graphDiv; - this.init(fullLayout); - this.makeFramework(); +'use strict' + +var d3 = require('d3') +var tinycolor = require('tinycolor2') + +var Plotly = require('../../plotly') +var Lib = require('../../lib') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var setConvert = require('../cartesian/set_convert') +var extendFlat = require('../../lib/extend').extendFlat +var Plots = require('../plots') +var Axes = require('../cartesian/axes') +var dragElement = require('../../components/dragelement') +var Titles = require('../../components/titles') +var prepSelect = require('../cartesian/select') +var constants = require('../cartesian/constants') +var fx = require('../cartesian/graph_interact') + +function Ternary (options, fullLayout) { + this.id = options.id + this.graphDiv = options.graphDiv + this.init(fullLayout) + this.makeFramework() } -module.exports = Ternary; +module.exports = Ternary -var proto = Ternary.prototype; +var proto = Ternary.prototype -proto.init = function(fullLayout) { - this.container = fullLayout._ternarylayer; - this.defs = fullLayout._defs; - this.layoutId = fullLayout._uid; - this.traceHash = {}; -}; +proto.init = function (fullLayout) { + this.container = fullLayout._ternarylayer + this.defs = fullLayout._defs + this.layoutId = fullLayout._uid + this.traceHash = {} +} -proto.plot = function(ternaryCalcData, fullLayout) { - var _this = this, - ternaryLayout = fullLayout[_this.id], - graphSize = fullLayout._size; +proto.plot = function (ternaryCalcData, fullLayout) { + var _this = this, + ternaryLayout = fullLayout[_this.id], + graphSize = fullLayout._size - _this.adjustLayout(ternaryLayout, graphSize); + _this.adjustLayout(ternaryLayout, graphSize) - Plots.generalUpdatePerTraceModule(_this, ternaryCalcData, ternaryLayout); + Plots.generalUpdatePerTraceModule(_this, ternaryCalcData, ternaryLayout) - _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor); -}; + _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor) +} -proto.makeFramework = function() { - var _this = this; +proto.makeFramework = function () { + var _this = this - var defGroup = _this.defs.selectAll('g.clips') - .data([0]); - defGroup.enter().append('g') - .classed('clips', true); + var defGroup = _this.defs.selectAll('g.clips') + .data([0]) + defGroup.enter().append('g') + .classed('clips', true) // clippath for this ternary subplot - var clipId = 'clip' + _this.layoutId + _this.id; - _this.clipDef = defGroup.selectAll('#' + clipId) - .data([0]); - _this.clipDef.enter().append('clipPath').attr('id', clipId) - .append('path').attr('d', 'M0,0Z'); + var clipId = 'clip' + _this.layoutId + _this.id + _this.clipDef = defGroup.selectAll('#' + clipId) + .data([0]) + _this.clipDef.enter().append('clipPath').attr('id', clipId) + .append('path').attr('d', 'M0,0Z') // container for everything in this ternary subplot - _this.plotContainer = _this.container.selectAll('g.' + _this.id) - .data([0]); - _this.plotContainer.enter().append('g') - .classed(_this.id, true); + _this.plotContainer = _this.container.selectAll('g.' + _this.id) + .data([0]) + _this.plotContainer.enter().append('g') + .classed(_this.id, true) - _this.layers = {}; + _this.layers = {} // inside that container, we have one container for the data, and // one each for the three axes around it. - var plotLayers = [ - 'draglayer', - 'plotbg', - 'backplot', - 'grids', - 'frontplot', - 'zoom', - 'aaxis', 'baxis', 'caxis', 'axlines' - ]; - var toplevel = _this.plotContainer.selectAll('g.toplevel') - .data(plotLayers); - toplevel.enter().append('g') - .attr('class', function(d) { return 'toplevel ' + d; }) - .each(function(d) { - var s = d3.select(this); - _this.layers[d] = s; + var plotLayers = [ + 'draglayer', + 'plotbg', + 'backplot', + 'grids', + 'frontplot', + 'zoom', + 'aaxis', 'baxis', 'caxis', 'axlines' + ] + var toplevel = _this.plotContainer.selectAll('g.toplevel') + .data(plotLayers) + toplevel.enter().append('g') + .attr('class', function (d) { return 'toplevel ' + d }) + .each(function (d) { + var s = d3.select(this) + _this.layers[d] = s // containers for different trace types. // NOTE - this is different from cartesian, where all traces // are in front of grids. Here I'm putting maps behind the grids // so the grids will always be visible if they're requested. // Perhaps we want that for cartesian too? - if(d === 'frontplot') s.append('g').classed('scatterlayer', true); - else if(d === 'backplot') s.append('g').classed('maplayer', true); - else if(d === 'plotbg') s.append('path').attr('d', 'M0,0Z'); - else if(d === 'axlines') { - s.selectAll('path').data(['aline', 'bline', 'cline']) - .enter().append('path').each(function(d) { - d3.select(this).classed(d, true); - }); - } - }); - - var grids = _this.plotContainer.select('.grids').selectAll('g.grid') - .data(['agrid', 'bgrid', 'cgrid']); - grids.enter().append('g') - .attr('class', function(d) { return 'grid ' + d; }) - .each(function(d) { _this.layers[d] = d3.select(this); }); - - _this.plotContainer.selectAll('.backplot,.frontplot,.grids') - .call(Drawing.setClipUrl, clipId); - - if(!_this.graphDiv._context.staticPlot) { - _this.initInteractions(); - } -}; - -var w_over_h = Math.sqrt(4 / 3); - -proto.adjustLayout = function(ternaryLayout, graphSize) { - var _this = this, - domain = ternaryLayout.domain, - xDomainCenter = (domain.x[0] + domain.x[1]) / 2, - yDomainCenter = (domain.y[0] + domain.y[1]) / 2, - xDomain = domain.x[1] - domain.x[0], - yDomain = domain.y[1] - domain.y[0], - wmax = xDomain * graphSize.w, - hmax = yDomain * graphSize.h, - sum = ternaryLayout.sum, - amin = ternaryLayout.aaxis.min, - bmin = ternaryLayout.baxis.min, - cmin = ternaryLayout.caxis.min; - - var x0, y0, w, h, xDomainFinal, yDomainFinal; - - if(wmax > w_over_h * hmax) { - h = hmax; - w = h * w_over_h; - } - else { - w = wmax; - h = w / w_over_h; - } - - xDomainFinal = xDomain * w / wmax; - yDomainFinal = yDomain * h / hmax; - - x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2; - y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2; + if (d === 'frontplot') s.append('g').classed('scatterlayer', true) + else if (d === 'backplot') s.append('g').classed('maplayer', true) + else if (d === 'plotbg') s.append('path').attr('d', 'M0,0Z') + else if (d === 'axlines') { + s.selectAll('path').data(['aline', 'bline', 'cline']) + .enter().append('path').each(function (d) { + d3.select(this).classed(d, true) + }) + } + }) + + var grids = _this.plotContainer.select('.grids').selectAll('g.grid') + .data(['agrid', 'bgrid', 'cgrid']) + grids.enter().append('g') + .attr('class', function (d) { return 'grid ' + d }) + .each(function (d) { _this.layers[d] = d3.select(this) }) + + _this.plotContainer.selectAll('.backplot,.frontplot,.grids') + .call(Drawing.setClipUrl, clipId) + + if (!_this.graphDiv._context.staticPlot) { + _this.initInteractions() + } +} - _this.x0 = x0; - _this.y0 = y0; - _this.w = w; - _this.h = h; - _this.sum = sum; +var w_over_h = Math.sqrt(4 / 3) + +proto.adjustLayout = function (ternaryLayout, graphSize) { + var _this = this, + domain = ternaryLayout.domain, + xDomainCenter = (domain.x[0] + domain.x[1]) / 2, + yDomainCenter = (domain.y[0] + domain.y[1]) / 2, + xDomain = domain.x[1] - domain.x[0], + yDomain = domain.y[1] - domain.y[0], + wmax = xDomain * graphSize.w, + hmax = yDomain * graphSize.h, + sum = ternaryLayout.sum, + amin = ternaryLayout.aaxis.min, + bmin = ternaryLayout.baxis.min, + cmin = ternaryLayout.caxis.min + + var x0, y0, w, h, xDomainFinal, yDomainFinal + + if (wmax > w_over_h * hmax) { + h = hmax + w = h * w_over_h + } else { + w = wmax + h = w / w_over_h + } + + xDomainFinal = xDomain * w / wmax + yDomainFinal = yDomain * h / hmax + + x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2 + y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2 + + _this.x0 = x0 + _this.y0 = y0 + _this.w = w + _this.h = h + _this.sum = sum // set up the x and y axis objects we'll use to lay out the points - _this.xaxis = { - type: 'linear', - range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin], - domain: [ - xDomainCenter - xDomainFinal / 2, - xDomainCenter + xDomainFinal / 2 - ], - _id: 'x', - _gd: _this.graphDiv - }; - setConvert(_this.xaxis); - _this.xaxis.setScale(); - - _this.yaxis = { - type: 'linear', - range: [amin, sum - bmin - cmin], - domain: [ - yDomainCenter - yDomainFinal / 2, - yDomainCenter + yDomainFinal / 2 - ], - _id: 'y', - _gd: _this.graphDiv - }; - setConvert(_this.yaxis); - _this.yaxis.setScale(); + _this.xaxis = { + type: 'linear', + range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin], + domain: [ + xDomainCenter - xDomainFinal / 2, + xDomainCenter + xDomainFinal / 2 + ], + _id: 'x', + _gd: _this.graphDiv + } + setConvert(_this.xaxis) + _this.xaxis.setScale() + + _this.yaxis = { + type: 'linear', + range: [amin, sum - bmin - cmin], + domain: [ + yDomainCenter - yDomainFinal / 2, + yDomainCenter + yDomainFinal / 2 + ], + _id: 'y', + _gd: _this.graphDiv + } + setConvert(_this.yaxis) + _this.yaxis.setScale() // set up the modified axes for tick drawing - var yDomain0 = _this.yaxis.domain[0]; + var yDomain0 = _this.yaxis.domain[0] // aaxis goes up the left side. Set it up as a y axis, but with // fictitious angles and domain, but then rotate and translate // it into place at the end - var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, { - range: [amin, sum - bmin - cmin], - side: 'left', - _counterangle: 30, + var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, { + range: [amin, sum - bmin - cmin], + side: 'left', + _counterangle: 30, // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here // so we can shift by 30. - tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30, - domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], - _axislayer: _this.layers.aaxis, - _gridlayer: _this.layers.agrid, - _pos: 0, // _this.xaxis.domain[0] * graphSize.w, - _gd: _this.graphDiv, - _id: 'y', - _length: w, - _gridpath: 'M0,0l' + h + ',-' + (w / 2) - }); - setConvert(aaxis); + tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30, + domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], + _axislayer: _this.layers.aaxis, + _gridlayer: _this.layers.agrid, + _pos: 0, // _this.xaxis.domain[0] * graphSize.w, + _gd: _this.graphDiv, + _id: 'y', + _length: w, + _gridpath: 'M0,0l' + h + ',-' + (w / 2) + }) + setConvert(aaxis) // baxis goes across the bottom (backward). We can set it up as an x axis // without any enclosing transformation. - var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, { - range: [sum - amin - cmin, bmin], - side: 'bottom', - _counterangle: 30, - domain: _this.xaxis.domain, - _axislayer: _this.layers.baxis, - _gridlayer: _this.layers.bgrid, - _counteraxis: _this.aaxis, - _pos: 0, // (1 - yDomain0) * graphSize.h, - _gd: _this.graphDiv, - _id: 'x', - _length: w, - _gridpath: 'M0,0l-' + (w / 2) + ',-' + h - }); - setConvert(baxis); - aaxis._counteraxis = baxis; + var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, { + range: [sum - amin - cmin, bmin], + side: 'bottom', + _counterangle: 30, + domain: _this.xaxis.domain, + _axislayer: _this.layers.baxis, + _gridlayer: _this.layers.bgrid, + _counteraxis: _this.aaxis, + _pos: 0, // (1 - yDomain0) * graphSize.h, + _gd: _this.graphDiv, + _id: 'x', + _length: w, + _gridpath: 'M0,0l-' + (w / 2) + ',-' + h + }) + setConvert(baxis) + aaxis._counteraxis = baxis // caxis goes down the right side. Set it up as a y axis, with // post-transformation similar to aaxis - var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, { - range: [sum - amin - bmin, cmin], - side: 'right', - _counterangle: 30, - tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30, - domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], - _axislayer: _this.layers.caxis, - _gridlayer: _this.layers.cgrid, - _counteraxis: _this.baxis, - _pos: 0, // _this.xaxis.domain[1] * graphSize.w, - _gd: _this.graphDiv, - _id: 'y', - _length: w, - _gridpath: 'M0,0l-' + h + ',' + (w / 2) - }); - setConvert(caxis); - - var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z'; - _this.clipDef.select('path').attr('d', triangleClip); - _this.layers.plotbg.select('path').attr('d', triangleClip); - - var plotTransform = 'translate(' + x0 + ',' + y0 + ')'; - _this.plotContainer.selectAll('.scatterlayer,.maplayer,.zoom') - .attr('transform', plotTransform); + var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, { + range: [sum - amin - bmin, cmin], + side: 'right', + _counterangle: 30, + tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30, + domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], + _axislayer: _this.layers.caxis, + _gridlayer: _this.layers.cgrid, + _counteraxis: _this.baxis, + _pos: 0, // _this.xaxis.domain[1] * graphSize.w, + _gd: _this.graphDiv, + _id: 'y', + _length: w, + _gridpath: 'M0,0l-' + h + ',' + (w / 2) + }) + setConvert(caxis) + + var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z' + _this.clipDef.select('path').attr('d', triangleClip) + _this.layers.plotbg.select('path').attr('d', triangleClip) + + var plotTransform = 'translate(' + x0 + ',' + y0 + ')' + _this.plotContainer.selectAll('.scatterlayer,.maplayer,.zoom') + .attr('transform', plotTransform) // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle - var bTransform = 'translate(' + x0 + ',' + (y0 + h) + ')'; + var bTransform = 'translate(' + x0 + ',' + (y0 + h) + ')' - _this.layers.baxis.attr('transform', bTransform); - _this.layers.bgrid.attr('transform', bTransform); + _this.layers.baxis.attr('transform', bTransform) + _this.layers.bgrid.attr('transform', bTransform) - var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(30)'; - _this.layers.aaxis.attr('transform', aTransform); - _this.layers.agrid.attr('transform', aTransform); + var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(30)' + _this.layers.aaxis.attr('transform', aTransform) + _this.layers.agrid.attr('transform', aTransform) - var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(-30)'; - _this.layers.caxis.attr('transform', cTransform); - _this.layers.cgrid.attr('transform', cTransform); + var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(-30)' + _this.layers.caxis.attr('transform', cTransform) + _this.layers.cgrid.attr('transform', cTransform) - _this.drawAxes(true); + _this.drawAxes(true) // remove crispEdges - all the off-square angles in ternary plots // make these counterproductive. - _this.plotContainer.selectAll('.crisp').classed('crisp', false); + _this.plotContainer.selectAll('.crisp').classed('crisp', false) - var axlines = _this.layers.axlines; - axlines.select('.aline') + var axlines = _this.layers.axlines + axlines.select('.aline') .attr('d', aaxis.showline ? 'M' + x0 + ',' + (y0 + h) + 'l' + (w / 2) + ',-' + h : 'M0,0') .call(Color.stroke, aaxis.linecolor || '#000') - .style('stroke-width', (aaxis.linewidth || 0) + 'px'); - axlines.select('.bline') + .style('stroke-width', (aaxis.linewidth || 0) + 'px') + axlines.select('.bline') .attr('d', baxis.showline ? 'M' + x0 + ',' + (y0 + h) + 'h' + w : 'M0,0') .call(Color.stroke, baxis.linecolor || '#000') - .style('stroke-width', (baxis.linewidth || 0) + 'px'); - axlines.select('.cline') + .style('stroke-width', (baxis.linewidth || 0) + 'px') + axlines.select('.cline') .attr('d', caxis.showline ? 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0') .call(Color.stroke, caxis.linecolor || '#000') - .style('stroke-width', (caxis.linewidth || 0) + 'px'); -}; - -proto.drawAxes = function(doTitles) { - var _this = this, - gd = _this.graphDiv, - titlesuffix = _this.id.substr(7) + 'title', - aaxis = _this.aaxis, - baxis = _this.baxis, - caxis = _this.caxis; + .style('stroke-width', (caxis.linewidth || 0) + 'px') +} + +proto.drawAxes = function (doTitles) { + var _this = this, + gd = _this.graphDiv, + titlesuffix = _this.id.substr(7) + 'title', + aaxis = _this.aaxis, + baxis = _this.baxis, + caxis = _this.caxis // 3rd arg true below skips titles, so we can configure them // correctly later on. - Axes.doTicks(gd, aaxis, true); - Axes.doTicks(gd, baxis, true); - Axes.doTicks(gd, caxis, true); + Axes.doTicks(gd, aaxis, true) + Axes.doTicks(gd, baxis, true) + Axes.doTicks(gd, caxis, true) - if(doTitles) { - var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, + if (doTitles) { + var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) + - (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0)); - Titles.draw(gd, 'a' + titlesuffix, { - propContainer: aaxis, - propName: _this.id + '.aaxis.title', - dfltName: 'Component A', - attributes: { - x: _this.x0 + _this.w / 2, - y: _this.y0 - aaxis.titlefont.size / 3 - apad, - 'text-anchor': 'middle' - } - }); - - var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + - (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; - - Titles.draw(gd, 'b' + titlesuffix, { - propContainer: baxis, - propName: _this.id + '.baxis.title', - dfltName: 'Component B', - attributes: { - x: _this.x0 - bpad, - y: _this.y0 + _this.h + baxis.titlefont.size * 0.83 + bpad, - 'text-anchor': 'middle' - } - }); - - Titles.draw(gd, 'c' + titlesuffix, { - propContainer: caxis, - propName: _this.id + '.caxis.title', - dfltName: 'Component C', - attributes: { - x: _this.x0 + _this.w + bpad, - y: _this.y0 + _this.h + caxis.titlefont.size * 0.83 + bpad, - 'text-anchor': 'middle' - } - }); - } -}; + (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0)) + Titles.draw(gd, 'a' + titlesuffix, { + propContainer: aaxis, + propName: _this.id + '.aaxis.title', + dfltName: 'Component A', + attributes: { + x: _this.x0 + _this.w / 2, + y: _this.y0 - aaxis.titlefont.size / 3 - apad, + 'text-anchor': 'middle' + } + }) + + var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + + (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3 + + Titles.draw(gd, 'b' + titlesuffix, { + propContainer: baxis, + propName: _this.id + '.baxis.title', + dfltName: 'Component B', + attributes: { + x: _this.x0 - bpad, + y: _this.y0 + _this.h + baxis.titlefont.size * 0.83 + bpad, + 'text-anchor': 'middle' + } + }) + + Titles.draw(gd, 'c' + titlesuffix, { + propContainer: caxis, + propName: _this.id + '.caxis.title', + dfltName: 'Component C', + attributes: { + x: _this.x0 + _this.w + bpad, + y: _this.y0 + _this.h + caxis.titlefont.size * 0.83 + bpad, + 'text-anchor': 'middle' + } + }) + } +} // hard coded paths for zoom corners // uses the same sizing as cartesian, length is MINZOOM/2, width is 3px -var CLEN = constants.MINZOOM / 2 + 0.87; +var CLEN = constants.MINZOOM / 2 + 0.87 var BLPATH = 'm-0.87,.5h' + CLEN + 'v3h-' + (CLEN + 5.2) + 'l' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + - 'l2.6,1.5l-' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z'; + 'l2.6,1.5l-' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z' var BRPATH = 'm0.87,.5h-' + CLEN + 'v3h' + (CLEN + 5.2) + 'l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + - 'l-2.6,1.5l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z'; + 'l-2.6,1.5l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z' var TOPPATH = 'm0,1l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'l2.6,-1.5l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + 'l-' + (CLEN / 2 + 2.6) + ',' + (CLEN * 0.87 + 4.5) + - 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z'; -var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z'; + 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z' +var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z' // I guess this could be shared with cartesian... but for now it's separate. -var SHOWZOOMOUTTIP = true; +var SHOWZOOMOUTTIP = true -proto.initInteractions = function() { - var _this = this, - dragger = _this.layers.plotbg.select('path').node(), - gd = _this.graphDiv, - zoomContainer = _this.layers.zoom; +proto.initInteractions = function () { + var _this = this, + dragger = _this.layers.plotbg.select('path').node(), + gd = _this.graphDiv, + zoomContainer = _this.layers.zoom // use plotbg for the main interactions - var dragOptions = { - element: dragger, - gd: gd, - plotinfo: {plot: zoomContainer}, - doubleclick: doubleClick, - subplot: _this.id, - prepFn: function(e, startX, startY) { + var dragOptions = { + element: dragger, + gd: gd, + plotinfo: {plot: zoomContainer}, + doubleclick: doubleClick, + subplot: _this.id, + prepFn: function (e, startX, startY) { // these aren't available yet when initInteractions // is called - dragOptions.xaxes = [_this.xaxis]; - dragOptions.yaxes = [_this.yaxis]; - var dragModeNow = gd._fullLayout.dragmode; - if(e.shiftKey) { - if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else dragModeNow = 'pan'; - } - - if(dragModeNow === 'lasso') dragOptions.minDrag = 1; - else dragOptions.minDrag = undefined; - - if(dragModeNow === 'zoom') { - dragOptions.moveFn = zoomMove; - dragOptions.doneFn = zoomDone; - zoomPrep(e, startX, startY); - } - else if(dragModeNow === 'pan') { - dragOptions.moveFn = plotDrag; - dragOptions.doneFn = dragDone; - panPrep(); - clearSelect(); - } - else if(dragModeNow === 'select' || dragModeNow === 'lasso') { - prepSelect(e, startX, startY, dragOptions, dragModeNow); - } - } - }; - - var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners; - - function zoomPrep(e, startX, startY) { - var dragBBox = dragger.getBoundingClientRect(); - x0 = startX - dragBBox.left; - y0 = startY - dragBBox.top; - mins0 = { - a: _this.aaxis.range[0], - b: _this.baxis.range[1], - c: _this.caxis.range[1] - }; - mins = mins0; - span0 = _this.aaxis.range[1] - mins0.a; - lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance(); - path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z'; - dimmed = false; - - zb = zoomContainer.append('path') + dragOptions.xaxes = [_this.xaxis] + dragOptions.yaxes = [_this.yaxis] + var dragModeNow = gd._fullLayout.dragmode + if (e.shiftKey) { + if (dragModeNow === 'pan') dragModeNow = 'zoom' + else dragModeNow = 'pan' + } + + if (dragModeNow === 'lasso') dragOptions.minDrag = 1 + else dragOptions.minDrag = undefined + + if (dragModeNow === 'zoom') { + dragOptions.moveFn = zoomMove + dragOptions.doneFn = zoomDone + zoomPrep(e, startX, startY) + } else if (dragModeNow === 'pan') { + dragOptions.moveFn = plotDrag + dragOptions.doneFn = dragDone + panPrep() + clearSelect() + } else if (dragModeNow === 'select' || dragModeNow === 'lasso') { + prepSelect(e, startX, startY, dragOptions, dragModeNow) + } + } + } + + var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners + + function zoomPrep (e, startX, startY) { + var dragBBox = dragger.getBoundingClientRect() + x0 = startX - dragBBox.left + y0 = startY - dragBBox.top + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + } + mins = mins0 + span0 = _this.aaxis.range[1] - mins0.a + lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance() + path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z' + dimmed = false + + zb = zoomContainer.append('path') .attr('class', 'zoombox') .style({ - 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', - 'stroke-width': 0 + 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 }) - .attr('d', path0); + .attr('d', path0) - corners = zoomContainer.append('path') + corners = zoomContainer.append('path') .attr('class', 'zoombox-corners') .style({ - fill: Color.background, - stroke: Color.defaultLine, - 'stroke-width': 1, - opacity: 0 + fill: Color.background, + stroke: Color.defaultLine, + 'stroke-width': 1, + opacity: 0 }) - .attr('d', 'M0,0Z'); - - clearSelect(); - } - - function getAFrac(x, y) { return 1 - (y / _this.h); } - function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); } - function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); } - - function zoomMove(dx0, dy0) { - var x1 = x0 + dx0, - y1 = y0 + dy0, - afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))), - bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))), - cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))), - xLeft = ((afrac / 2) + cfrac) * _this.w, - xRight = (1 - (afrac / 2) - bfrac) * _this.w, - xCenter = (xLeft + xRight) / 2, - xSpan = xRight - xLeft, - yBottom = (1 - afrac) * _this.h, - yTop = yBottom - xSpan / w_over_h; - - if(xSpan < constants.MINZOOM) { - mins = mins0; - zb.attr('d', path0); - corners.attr('d', 'M0,0Z'); - } - else { - mins = { - a: mins0.a + afrac * span0, - b: mins0.b + bfrac * span0, - c: mins0.c + cfrac * span0 - }; - zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + + .attr('d', 'M0,0Z') + + clearSelect() + } + + function getAFrac (x, y) { return 1 - (y / _this.h) } + function getBFrac (x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w) } + function getCFrac (x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w) } + + function zoomMove (dx0, dy0) { + var x1 = x0 + dx0, + y1 = y0 + dy0, + afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))), + bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))), + cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))), + xLeft = ((afrac / 2) + cfrac) * _this.w, + xRight = (1 - (afrac / 2) - bfrac) * _this.w, + xCenter = (xLeft + xRight) / 2, + xSpan = xRight - xLeft, + yBottom = (1 - afrac) * _this.h, + yTop = yBottom - xSpan / w_over_h + + if (xSpan < constants.MINZOOM) { + mins = mins0 + zb.attr('d', path0) + corners.attr('d', 'M0,0Z') + } else { + mins = { + a: mins0.a + afrac * span0, + b: mins0.b + bfrac * span0, + c: mins0.c + cfrac * span0 + } + zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + 'H' + xRight + 'L' + xCenter + ',' + yTop + - 'L' + xLeft + ',' + yBottom + 'Z'); - corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER + + 'L' + xLeft + ',' + yBottom + 'Z') + corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER + 'M' + xLeft + ',' + yBottom + BLPATH + 'M' + xRight + ',' + yBottom + BRPATH + - 'M' + xCenter + ',' + yTop + TOPPATH); - } + 'M' + xCenter + ',' + yTop + TOPPATH) + } - if(!dimmed) { - zb.transition() + if (!dimmed) { + zb.transition() .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' : 'rgba(255,255,255,0.3)') - .duration(200); - corners.transition() + .duration(200) + corners.transition() .style('opacity', 1) - .duration(200); - dimmed = true; - } + .duration(200) + dimmed = true } + } - function zoomDone(dragged, numClicks) { - if(mins === mins0) { - if(numClicks === 2) doubleClick(); + function zoomDone (dragged, numClicks) { + if (mins === mins0) { + if (numClicks === 2) doubleClick() - return removeZoombox(gd); - } + return removeZoombox(gd) + } - removeZoombox(gd); + removeZoombox(gd) - var attrs = {}; - attrs[_this.id + '.aaxis.min'] = mins.a; - attrs[_this.id + '.baxis.min'] = mins.b; - attrs[_this.id + '.caxis.min'] = mins.c; + var attrs = {} + attrs[_this.id + '.aaxis.min'] = mins.a + attrs[_this.id + '.baxis.min'] = mins.b + attrs[_this.id + '.caxis.min'] = mins.c - Plotly.relayout(gd, attrs); + Plotly.relayout(gd, attrs) - if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { - Lib.notifier('Double-click to
zoom back out', 'long'); - SHOWZOOMOUTTIP = false; - } + if (SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Lib.notifier('Double-click to
zoom back out', 'long') + SHOWZOOMOUTTIP = false } + } - function panPrep() { - mins0 = { - a: _this.aaxis.range[0], - b: _this.baxis.range[1], - c: _this.caxis.range[1] - }; - mins = mins0; + function panPrep () { + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + } + mins = mins0 + } + + function plotDrag (dx, dy) { + var dxScaled = dx / _this.xaxis._m, + dyScaled = dy / _this.yaxis._m + mins = { + a: mins0.a - dyScaled, + b: mins0.b + (dxScaled + dyScaled) / 2, + c: mins0.c - (dxScaled - dyScaled) / 2 + } + var minsorted = [mins.a, mins.b, mins.c].sort(), + minindices = { + a: minsorted.indexOf(mins.a), + b: minsorted.indexOf(mins.b), + c: minsorted.indexOf(mins.c) + } + if (minsorted[0] < 0) { + if (minsorted[1] + minsorted[0] / 2 < 0) { + minsorted[2] += minsorted[0] + minsorted[1] + minsorted[0] = minsorted[1] = 0 + } else { + minsorted[2] += minsorted[0] / 2 + minsorted[1] += minsorted[0] / 2 + minsorted[0] = 0 + } + mins = { + a: minsorted[minindices.a], + b: minsorted[minindices.b], + c: minsorted[minindices.c] + } + dy = (mins0.a - mins.a) * _this.yaxis._m + dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m } - - function plotDrag(dx, dy) { - var dxScaled = dx / _this.xaxis._m, - dyScaled = dy / _this.yaxis._m; - mins = { - a: mins0.a - dyScaled, - b: mins0.b + (dxScaled + dyScaled) / 2, - c: mins0.c - (dxScaled - dyScaled) / 2 - }; - var minsorted = [mins.a, mins.b, mins.c].sort(), - minindices = { - a: minsorted.indexOf(mins.a), - b: minsorted.indexOf(mins.b), - c: minsorted.indexOf(mins.c) - }; - if(minsorted[0] < 0) { - if(minsorted[1] + minsorted[0] / 2 < 0) { - minsorted[2] += minsorted[0] + minsorted[1]; - minsorted[0] = minsorted[1] = 0; - } - else { - minsorted[2] += minsorted[0] / 2; - minsorted[1] += minsorted[0] / 2; - minsorted[0] = 0; - } - mins = { - a: minsorted[minindices.a], - b: minsorted[minindices.b], - c: minsorted[minindices.c] - }; - dy = (mins0.a - mins.a) * _this.yaxis._m; - dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m; - } // move the data (translate, don't redraw) - var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')'; - _this.plotContainer.selectAll('.scatterlayer,.maplayer') - .attr('transform', plotTransform); + var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')' + _this.plotContainer.selectAll('.scatterlayer,.maplayer') + .attr('transform', plotTransform) // move the ticks - _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c]; - _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b]; - _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c]; - - _this.drawAxes(false); - _this.plotContainer.selectAll('.crisp').classed('crisp', false); - } - - function dragDone(dragged, numClicks) { - if(dragged) { - var attrs = {}; - attrs[_this.id + '.aaxis.min'] = mins.a; - attrs[_this.id + '.baxis.min'] = mins.b; - attrs[_this.id + '.caxis.min'] = mins.c; - - Plotly.relayout(gd, attrs); - } - else if(numClicks === 2) doubleClick(); - } - - function clearSelect() { + _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c] + _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b] + _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c] + + _this.drawAxes(false) + _this.plotContainer.selectAll('.crisp').classed('crisp', false) + } + + function dragDone (dragged, numClicks) { + if (dragged) { + var attrs = {} + attrs[_this.id + '.aaxis.min'] = mins.a + attrs[_this.id + '.baxis.min'] = mins.b + attrs[_this.id + '.caxis.min'] = mins.c + + Plotly.relayout(gd, attrs) + } else if (numClicks === 2) doubleClick() + } + + function clearSelect () { // until we get around to persistent selections, remove the outline // here. The selection itself will be removed when the plot redraws // at the end. - _this.plotContainer.selectAll('.select-outline').remove(); - } - - function doubleClick() { - var attrs = {}; - attrs[_this.id + '.aaxis.min'] = 0; - attrs[_this.id + '.baxis.min'] = 0; - attrs[_this.id + '.caxis.min'] = 0; - gd.emit('plotly_doubleclick', null); - Plotly.relayout(gd, attrs); - } + _this.plotContainer.selectAll('.select-outline').remove() + } + + function doubleClick () { + var attrs = {} + attrs[_this.id + '.aaxis.min'] = 0 + attrs[_this.id + '.baxis.min'] = 0 + attrs[_this.id + '.caxis.min'] = 0 + gd.emit('plotly_doubleclick', null) + Plotly.relayout(gd, attrs) + } // finally, set up hover and click // these event handlers must already be set before dragElement.init // so it can stash them and override them. - dragger.onmousemove = function(evt) { - fx.hover(gd, evt, _this.id); - gd._fullLayout._lasthover = dragger; - gd._fullLayout._hoversubplot = _this.id; - }; + dragger.onmousemove = function (evt) { + fx.hover(gd, evt, _this.id) + gd._fullLayout._lasthover = dragger + gd._fullLayout._hoversubplot = _this.id + } - dragger.onmouseout = function(evt) { - if(gd._dragging) return; + dragger.onmouseout = function (evt) { + if (gd._dragging) return - dragElement.unhover(gd, evt); - }; + dragElement.unhover(gd, evt) + } - dragger.onclick = function(evt) { - fx.click(gd, evt); - }; + dragger.onclick = function (evt) { + fx.click(gd, evt) + } - dragElement.init(dragOptions); -}; + dragElement.init(dragOptions) +} -function removeZoombox(gd) { - d3.select(gd) +function removeZoombox (gd) { + d3.select(gd) .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') - .remove(); + .remove() } diff --git a/src/registry.js b/src/registry.js index 5fb6f2256bd..c80be6bdcb4 100644 --- a/src/registry.js +++ b/src/registry.js @@ -6,19 +6,18 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('./lib') +var basePlotAttributes = require('./plots/attributes') -var Lib = require('./lib'); -var basePlotAttributes = require('./plots/attributes'); - -exports.modules = {}; -exports.allCategories = {}; -exports.allTypes = []; -exports.subplotsRegistry = {}; -exports.transformsRegistry = {}; -exports.componentsRegistry = {}; -exports.layoutArrayContainers = []; +exports.modules = {} +exports.allCategories = {} +exports.allTypes = [] +exports.subplotsRegistry = {} +exports.transformsRegistry = {} +exports.componentsRegistry = {} +exports.layoutArrayContainers = [] /** * register a module as the handler for a trace type @@ -29,29 +28,29 @@ exports.layoutArrayContainers = []; * tested by calls: traceIs(trace, oneCategory) * @param {object} meta meta information about the trace type */ -exports.register = function(_module, thisType, categoriesIn, meta) { - if(exports.modules[thisType]) { - Lib.log('Type ' + thisType + ' already registered'); - return; - } - - var categoryObj = {}; - for(var i = 0; i < categoriesIn.length; i++) { - categoryObj[categoriesIn[i]] = true; - exports.allCategories[categoriesIn[i]] = true; - } - - exports.modules[thisType] = { - _module: _module, - categories: categoryObj - }; - - if(meta && Object.keys(meta).length) { - exports.modules[thisType].meta = meta; - } - - exports.allTypes.push(thisType); -}; +exports.register = function (_module, thisType, categoriesIn, meta) { + if (exports.modules[thisType]) { + Lib.log('Type ' + thisType + ' already registered') + return + } + + var categoryObj = {} + for (var i = 0; i < categoriesIn.length; i++) { + categoryObj[categoriesIn[i]] = true + exports.allCategories[categoriesIn[i]] = true + } + + exports.modules[thisType] = { + _module: _module, + categories: categoryObj + } + + if (meta && Object.keys(meta).length) { + exports.modules[thisType].meta = meta + } + + exports.allTypes.push(thisType) +} /** * register a subplot type @@ -72,27 +71,27 @@ exports.register = function(_module, thisType, categoriesIn, meta) { * to a nested attribute objects * (the set of all valid attr names is generated below and stored in attrRegex). */ -exports.registerSubplot = function(_module) { - var plotType = _module.name; +exports.registerSubplot = function (_module) { + var plotType = _module.name - if(exports.subplotsRegistry[plotType]) { - Lib.log('Plot type ' + plotType + ' already registered.'); - return; - } + if (exports.subplotsRegistry[plotType]) { + Lib.log('Plot type ' + plotType + ' already registered.') + return + } // not sure what's best for the 'cartesian' type at this point - exports.subplotsRegistry[plotType] = _module; -}; + exports.subplotsRegistry[plotType] = _module +} -exports.registerComponent = function(_module) { - var name = _module.name; +exports.registerComponent = function (_module) { + var name = _module.name - exports.componentsRegistry[name] = _module; + exports.componentsRegistry[name] = _module - if(_module.layoutAttributes && _module.layoutAttributes._isLinkedToArray) { - Lib.pushUnique(exports.layoutArrayContainers, name); - } -}; + if (_module.layoutAttributes && _module.layoutAttributes._isLinkedToArray) { + Lib.pushUnique(exports.layoutArrayContainers, name) + } +} /** * Get registered module using trace object or trace type @@ -102,19 +101,19 @@ exports.registerComponent = function(_module) { * @return {object} * module object corresponding to trace type */ -exports.getModule = function(trace) { - if(trace.r !== undefined) { - Lib.warn('Tried to put a polar trace ' + +exports.getModule = function (trace) { + if (trace.r !== undefined) { + Lib.warn('Tried to put a polar trace ' + 'on an incompatible graph of cartesian ' + 'data. Ignoring this dataset.', trace - ); - return false; - } + ) + return false + } - var _module = exports.modules[getTraceType(trace)]; - if(!_module) return false; - return _module._module; -}; + var _module = exports.modules[getTraceType(trace)] + if (!_module) return false + return _module._module +} /** * Determine if this trace type is in a given category @@ -125,24 +124,24 @@ exports.getModule = function(trace) { * category in question * @return {boolean} */ -exports.traceIs = function(traceType, category) { - traceType = getTraceType(traceType); +exports.traceIs = function (traceType, category) { + traceType = getTraceType(traceType) // old plot.ly workspace hack, nothing to see here - if(traceType === 'various') return false; + if (traceType === 'various') return false - var _module = exports.modules[traceType]; + var _module = exports.modules[traceType] - if(!_module) { - if(traceType && traceType !== 'area') { - Lib.log('Unrecognized trace type ' + traceType + '.'); - } - - _module = exports.modules[basePlotAttributes.type.dflt]; + if (!_module) { + if (traceType && traceType !== 'area') { + Lib.log('Unrecognized trace type ' + traceType + '.') } - return !!_module.categories[category]; -}; + _module = exports.modules[basePlotAttributes.type.dflt] + } + + return !!_module.categories[category] +} /** * Retrieve component module method @@ -153,14 +152,14 @@ exports.traceIs = function(traceType, category) { * name of component module method * @return {function} */ -exports.getComponentMethod = function(name, method) { - var _module = exports.componentsRegistry[name]; +exports.getComponentMethod = function (name, method) { + var _module = exports.componentsRegistry[name] - if(!_module) return Lib.noop; - return _module[method]; -}; + if (!_module) return Lib.noop + return _module[method] +} -function getTraceType(traceType) { - if(typeof traceType === 'object') traceType = traceType.type; - return traceType; +function getTraceType (traceType) { + if (typeof traceType === 'object') traceType = traceType.type + return traceType } diff --git a/src/snapshot/cloneplot.js b/src/snapshot/cloneplot.js index bb44098caae..f9207183469 100644 --- a/src/snapshot/cloneplot.js +++ b/src/snapshot/cloneplot.js @@ -6,163 +6,159 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../lib') +var Plots = require('../plots/plots') -var Lib = require('../lib'); -var Plots = require('../plots/plots'); - -var extendFlat = Lib.extendFlat; -var extendDeep = Lib.extendDeep; +var extendFlat = Lib.extendFlat +var extendDeep = Lib.extendDeep // Put default plotTile layouts here -function cloneLayoutOverride(tileClass) { - var override; - - switch(tileClass) { - case 'themes__thumb': - override = { - autosize: true, - width: 150, - height: 150, - title: '', - showlegend: false, - margin: {l: 5, r: 5, t: 5, b: 5, pad: 0}, - annotations: [] - }; - break; - - case 'thumbnail': - override = { - title: '', - hidesources: true, - showlegend: false, - borderwidth: 0, - bordercolor: '', - margin: {l: 1, r: 1, t: 1, b: 1, pad: 0}, - annotations: [] - }; - break; - - default: - override = {}; - } - - - return override; +function cloneLayoutOverride (tileClass) { + var override + + switch (tileClass) { + case 'themes__thumb': + override = { + autosize: true, + width: 150, + height: 150, + title: '', + showlegend: false, + margin: {l: 5, r: 5, t: 5, b: 5, pad: 0}, + annotations: [] + } + break + + case 'thumbnail': + override = { + title: '', + hidesources: true, + showlegend: false, + borderwidth: 0, + bordercolor: '', + margin: {l: 1, r: 1, t: 1, b: 1, pad: 0}, + annotations: [] + } + break + + default: + override = {} + } + + return override } -function keyIsAxis(keyName) { - var types = ['xaxis', 'yaxis', 'zaxis']; - return (types.indexOf(keyName.slice(0, 5)) > -1); +function keyIsAxis (keyName) { + var types = ['xaxis', 'yaxis', 'zaxis'] + return (types.indexOf(keyName.slice(0, 5)) > -1) } - -module.exports = function clonePlot(graphObj, options) { - +module.exports = function clonePlot (graphObj, options) { // Polar plot compatibility - if(graphObj.framework && graphObj.framework.isPolar) { - graphObj = graphObj.framework.getConfig(); - } + if (graphObj.framework && graphObj.framework.isPolar) { + graphObj = graphObj.framework.getConfig() + } - var i; - var oldData = graphObj.data; - var oldLayout = graphObj.layout; - var newData = extendDeep([], oldData); - var newLayout = extendDeep({}, oldLayout, cloneLayoutOverride(options.tileClass)); + var i + var oldData = graphObj.data + var oldLayout = graphObj.layout + var newData = extendDeep([], oldData) + var newLayout = extendDeep({}, oldLayout, cloneLayoutOverride(options.tileClass)) - if(options.width) newLayout.width = options.width; - if(options.height) newLayout.height = options.height; + if (options.width) newLayout.width = options.width + if (options.height) newLayout.height = options.height - if(options.tileClass === 'thumbnail' || options.tileClass === 'themes__thumb') { + if (options.tileClass === 'thumbnail' || options.tileClass === 'themes__thumb') { // kill annotations - newLayout.annotations = []; - var keys = Object.keys(newLayout); + newLayout.annotations = [] + var keys = Object.keys(newLayout) - for(i = 0; i < keys.length; i++) { - if(keyIsAxis(keys[i])) { - newLayout[keys[i]].title = ''; - } - } + for (i = 0; i < keys.length; i++) { + if (keyIsAxis(keys[i])) { + newLayout[keys[i]].title = '' + } + } // kill colorbar and pie labels - for(i = 0; i < newData.length; i++) { - var trace = newData[i]; - trace.showscale = false; - if(trace.marker) trace.marker.showscale = false; - if(trace.type === 'pie') trace.textposition = 'none'; - } + for (i = 0; i < newData.length; i++) { + var trace = newData[i] + trace.showscale = false + if (trace.marker) trace.marker.showscale = false + if (trace.type === 'pie') trace.textposition = 'none' } + } - if(Array.isArray(options.annotations)) { - for(i = 0; i < options.annotations.length; i++) { - newLayout.annotations.push(options.annotations[i]); - } + if (Array.isArray(options.annotations)) { + for (i = 0; i < options.annotations.length; i++) { + newLayout.annotations.push(options.annotations[i]) } + } + + var sceneIds = Plots.getSubplotIds(newLayout, 'gl3d') + + if (sceneIds.length) { + var axesImageOverride = {} + if (options.tileClass === 'thumbnail') { + axesImageOverride = { + title: '', + showaxeslabels: false, + showticklabels: false, + linetickenable: false + } + } + for (i = 0; i < sceneIds.length; i++) { + var scene = newLayout[sceneIds[i]] - var sceneIds = Plots.getSubplotIds(newLayout, 'gl3d'); - - if(sceneIds.length) { - var axesImageOverride = {}; - if(options.tileClass === 'thumbnail') { - axesImageOverride = { - title: '', - showaxeslabels: false, - showticklabels: false, - linetickenable: false - }; - } - for(i = 0; i < sceneIds.length; i++) { - var scene = newLayout[sceneIds[i]]; - - if(!scene.xaxis) { - scene.xaxis = {}; - } - - if(!scene.yaxis) { - scene.yaxis = {}; - } - - if(!scene.zaxis) { - scene.zaxis = {}; - } - - extendFlat(scene.xaxis, axesImageOverride); - extendFlat(scene.yaxis, axesImageOverride); - extendFlat(scene.zaxis, axesImageOverride); + if (!scene.xaxis) { + scene.xaxis = {} + } - // TODO what does this do? - scene._scene = null; - } - } + if (!scene.yaxis) { + scene.yaxis = {} + } + + if (!scene.zaxis) { + scene.zaxis = {} + } - var gd = document.createElement('div'); - if(options.tileClass) gd.className = options.tileClass; + extendFlat(scene.xaxis, axesImageOverride) + extendFlat(scene.yaxis, axesImageOverride) + extendFlat(scene.zaxis, axesImageOverride) - var plotTile = { - gd: gd, - td: gd, // for external (image server) compatibility - layout: newLayout, - data: newData, - config: { - staticPlot: (options.staticPlot === undefined) ? + // TODO what does this do? + scene._scene = null + } + } + + var gd = document.createElement('div') + if (options.tileClass) gd.className = options.tileClass + + var plotTile = { + gd: gd, + td: gd, // for external (image server) compatibility + layout: newLayout, + data: newData, + config: { + staticPlot: (options.staticPlot === undefined) ? true : options.staticPlot, - plotGlPixelRatio: (options.plotGlPixelRatio === undefined) ? + plotGlPixelRatio: (options.plotGlPixelRatio === undefined) ? 2 : options.plotGlPixelRatio, - displaylogo: options.displaylogo || false, - showLink: options.showLink || false, - showTips: options.showTips || false - } - }; - - if(options.setBackground !== 'transparent') { - plotTile.config.setBackground = options.setBackground || 'opaque'; + displaylogo: options.displaylogo || false, + showLink: options.showLink || false, + showTips: options.showTips || false } + } + + if (options.setBackground !== 'transparent') { + plotTile.config.setBackground = options.setBackground || 'opaque' + } // attaching the default Layout the gd, so you can grab it later - plotTile.gd.defaultLayout = cloneLayoutOverride(options.tileClass); + plotTile.gd.defaultLayout = cloneLayoutOverride(options.tileClass) - return plotTile; -}; + return plotTile +} diff --git a/src/snapshot/download.js b/src/snapshot/download.js index aa37d95e450..d72f3c5f5d2 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var toImage = require('../plot_api/to_image'); -var Lib = require('../lib'); // for isIE -var fileSaver = require('./filesaver'); +var toImage = require('../plot_api/to_image') +var Lib = require('../lib') // for isIE +var fileSaver = require('./filesaver') /** * @param {object} gd figure Object @@ -21,44 +20,43 @@ var fileSaver = require('./filesaver'); * @param opts.height height of snapshot in px * @param opts.filename name of file excluding extension */ -function downloadImage(gd, opts) { - +function downloadImage (gd, opts) { // check for undefined opts - opts = opts || {}; + opts = opts || {} // default to png - opts.format = opts.format || 'png'; + opts.format = opts.format || 'png' - return new Promise(function(resolve, reject) { - if(gd._snapshotInProgress) { - reject(new Error('Snapshotting already in progress.')); - } + return new Promise(function (resolve, reject) { + if (gd._snapshotInProgress) { + reject(new Error('Snapshotting already in progress.')) + } // see comments within svgtoimg for additional // discussion of problems with IE // can now draw to canvas, but CORS tainted canvas // does not allow toDataURL // svg format will work though - if(Lib.isIE() && opts.format !== 'svg') { - reject(new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.')); - } - - gd._snapshotInProgress = true; - var promise = toImage(gd, opts); - - var filename = opts.filename || gd.fn || 'newplot'; - filename += '.' + opts.format; - - promise.then(function(result) { - gd._snapshotInProgress = false; - return fileSaver(result, filename); - }).then(function(name) { - resolve(name); - }).catch(function(err) { - gd._snapshotInProgress = false; - reject(err); - }); - }); + if (Lib.isIE() && opts.format !== 'svg') { + reject(new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.')) + } + + gd._snapshotInProgress = true + var promise = toImage(gd, opts) + + var filename = opts.filename || gd.fn || 'newplot' + filename += '.' + opts.format + + promise.then(function (result) { + gd._snapshotInProgress = false + return fileSaver(result, filename) + }).then(function (name) { + resolve(name) + }).catch(function (err) { + gd._snapshotInProgress = false + reject(err) + }) + }) } -module.exports = downloadImage; +module.exports = downloadImage diff --git a/src/snapshot/filesaver.js b/src/snapshot/filesaver.js index 88109ffe7dd..dd2c0e50985 100644 --- a/src/snapshot/filesaver.js +++ b/src/snapshot/filesaver.js @@ -19,48 +19,48 @@ * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md */ -'use strict'; +'use strict' -var fileSaver = function(url, name) { - var saveLink = document.createElement('a'); - var canUseSaveLink = 'download' in saveLink; - var isSafari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent); - var promise = new Promise(function(resolve, reject) { +var fileSaver = function (url, name) { + var saveLink = document.createElement('a') + var canUseSaveLink = 'download' in saveLink + var isSafari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent) + var promise = new Promise(function (resolve, reject) { // IE <10 is explicitly unsupported - if(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) { - reject(new Error('IE < 10 unsupported')); - } + if (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) { + reject(new Error('IE < 10 unsupported')) + } // First try a.download, then web filesystem, then object URLs - if(isSafari) { + if (isSafari) { // Safari doesn't allow downloading of blob urls - document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/)); - resolve(name); - } + document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/)) + resolve(name) + } - if(!name) { - name = 'download'; - } + if (!name) { + name = 'download' + } - if(canUseSaveLink) { - saveLink.href = url; - saveLink.download = name; - document.body.appendChild(saveLink); - saveLink.click(); - document.body.removeChild(saveLink); - resolve(name); - } + if (canUseSaveLink) { + saveLink.href = url + saveLink.download = name + document.body.appendChild(saveLink) + saveLink.click() + document.body.removeChild(saveLink) + resolve(name) + } // IE 10+ (native saveAs) - if(typeof navigator !== 'undefined' && navigator.msSaveBlob) { - navigator.msSaveBlob(new Blob([url]), name); - resolve(name); - } + if (typeof navigator !== 'undefined' && navigator.msSaveBlob) { + navigator.msSaveBlob(new Blob([url]), name) + resolve(name) + } - reject(new Error('download error')); - }); + reject(new Error('download error')) + }) - return promise; -}; + return promise +} -module.exports = fileSaver; +module.exports = fileSaver diff --git a/src/snapshot/helpers.js b/src/snapshot/helpers.js index 8af139fc9eb..ef2df494631 100644 --- a/src/snapshot/helpers.js +++ b/src/snapshot/helpers.js @@ -6,26 +6,23 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -exports.getDelay = function(fullLayout) { - +exports.getDelay = function (fullLayout) { // polar clears fullLayout._has for some reason - if(!fullLayout._has) return 0; + if (!fullLayout._has) return 0 // maybe we should add a 'gl' (and 'svg') layoutCategory ?? - return (fullLayout._has('gl3d') || fullLayout._has('gl2d')) ? 500 : 0; -}; - -exports.getRedrawFunc = function(gd) { + return (fullLayout._has('gl3d') || fullLayout._has('gl2d')) ? 500 : 0 +} +exports.getRedrawFunc = function (gd) { // do not work if polar is present - if((gd.data && gd.data[0] && gd.data[0].r)) return; - - return function() { - (gd.calcdata || []).forEach(function(d) { - if(d[0] && d[0].t && d[0].t.cb) d[0].t.cb(); - }); - }; -}; + if ((gd.data && gd.data[0] && gd.data[0].r)) return + + return function () { + (gd.calcdata || []).forEach(function (d) { + if (d[0] && d[0].t && d[0].t.cb) d[0].t.cb() + }) + } +} diff --git a/src/snapshot/index.js b/src/snapshot/index.js index a8f56fbe19f..2f62c3447dc 100644 --- a/src/snapshot/index.js +++ b/src/snapshot/index.js @@ -6,19 +6,18 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var helpers = require('./helpers'); +var helpers = require('./helpers') var Snapshot = { - getDelay: helpers.getDelay, - getRedrawFunc: helpers.getRedrawFunc, - clone: require('./cloneplot'), - toSVG: require('./tosvg'), - svgToImg: require('./svgtoimg'), - toImage: require('./toimage'), - downloadImage: require('./download') -}; + getDelay: helpers.getDelay, + getRedrawFunc: helpers.getRedrawFunc, + clone: require('./cloneplot'), + toSVG: require('./tosvg'), + svgToImg: require('./svgtoimg'), + toImage: require('./toimage'), + downloadImage: require('./download') +} -module.exports = Snapshot; +module.exports = Snapshot diff --git a/src/snapshot/svgtoimg.js b/src/snapshot/svgtoimg.js index 05eddab673e..a5c016228cd 100644 --- a/src/snapshot/svgtoimg.js +++ b/src/snapshot/svgtoimg.js @@ -6,21 +6,19 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../lib'); -var EventEmitter = require('events').EventEmitter; +var Lib = require('../lib') +var EventEmitter = require('events').EventEmitter -function svgToImg(opts) { +function svgToImg (opts) { + var ev = opts.emitter || new EventEmitter() - var ev = opts.emitter || new EventEmitter(); + var promise = new Promise(function (resolve, reject) { + var Image = window.Image - var promise = new Promise(function(resolve, reject) { - - var Image = window.Image; - - var svg = opts.svg; - var format = opts.format || 'png'; + var svg = opts.svg + var format = opts.format || 'png' // IE is very strict, so we will need to clean // svg with the following regex @@ -29,101 +27,101 @@ function svgToImg(opts) { // see https://github.com/kangax/fabric.js/issues/1957 // http://stackoverflow.com/questions/18112047/canvas-todataurl-working-in-all-browsers-except-ie10 // Leave here just in case the CORS/tainted IE issue gets resolved - if(Lib.isIE()) { + if (Lib.isIE()) { // replace double quote with single quote - svg = svg.replace(/"/gi, '\''); + svg = svg.replace(/"/gi, '\'') // url in svg are single quoted // since we changed double to single // we'll need to change these to double-quoted - svg = svg.replace(/(\('#)(.*)('\))/gi, '(\"$2\")'); + svg = svg.replace(/(\('#)(.*)('\))/gi, '(\"$2\")') // font names with spaces will be escaped single-quoted // we'll need to change these to double-quoted - svg = svg.replace(/(\\')/gi, '\"'); + svg = svg.replace(/(\\')/gi, '\"') // IE only support svg - if(format !== 'svg') { - var ieSvgError = new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'); - reject(ieSvgError); + if (format !== 'svg') { + var ieSvgError = new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.') + reject(ieSvgError) // eventually remove the ev // in favor of promises - if(!opts.promise) { - return ev.emit('error', ieSvgError); - } else { - return promise; - } - } + if (!opts.promise) { + return ev.emit('error', ieSvgError) + } else { + return promise } + } + } - var canvas = opts.canvas; + var canvas = opts.canvas - var ctx = canvas.getContext('2d'); - var img = new Image(); + var ctx = canvas.getContext('2d') + var img = new Image() // for Safari support, eliminate createObjectURL // this decision could cause problems if content // is not restricted to svg - var url = 'data:image/svg+xml,' + encodeURIComponent(svg); + var url = 'data:image/svg+xml,' + encodeURIComponent(svg) - canvas.height = opts.height || 150; - canvas.width = opts.width || 300; + canvas.height = opts.height || 150 + canvas.width = opts.width || 300 - img.onload = function() { - var imgData; + img.onload = function () { + var imgData // don't need to draw to canvas if svg // save some time and also avoid failure on IE - if(format !== 'svg') { - ctx.drawImage(img, 0, 0); - } - - switch(format) { - case 'jpeg': - imgData = canvas.toDataURL('image/jpeg'); - break; - case 'png': - imgData = canvas.toDataURL('image/png'); - break; - case 'webp': - imgData = canvas.toDataURL('image/webp'); - break; - case 'svg': - imgData = url; - break; - default: - reject(new Error('Image format is not jpeg, png or svg')); + if (format !== 'svg') { + ctx.drawImage(img, 0, 0) + } + + switch (format) { + case 'jpeg': + imgData = canvas.toDataURL('image/jpeg') + break + case 'png': + imgData = canvas.toDataURL('image/png') + break + case 'webp': + imgData = canvas.toDataURL('image/webp') + break + case 'svg': + imgData = url + break + default: + reject(new Error('Image format is not jpeg, png or svg')) // eventually remove the ev // in favor of promises - if(!opts.promise) { - return ev.emit('error', 'Image format is not jpeg, png or svg'); - } - } - resolve(imgData); + if (!opts.promise) { + return ev.emit('error', 'Image format is not jpeg, png or svg') + } + } + resolve(imgData) // eventually remove the ev // in favor of promises - if(!opts.promise) { - ev.emit('success', imgData); - } - }; + if (!opts.promise) { + ev.emit('success', imgData) + } + } - img.onerror = function(err) { - reject(err); + img.onerror = function (err) { + reject(err) // eventually remove the ev // in favor of promises - if(!opts.promise) { - return ev.emit('error', err); - } - }; + if (!opts.promise) { + return ev.emit('error', err) + } + } - img.src = url; - }); + img.src = url + }) // temporary for backward compatibility // move to only Promise in 2.0.0 // and eliminate the EventEmitter - if(opts.promise) { - return promise; - } + if (opts.promise) { + return promise + } - return ev; + return ev } -module.exports = svgToImg; +module.exports = svgToImg diff --git a/src/snapshot/toimage.js b/src/snapshot/toimage.js index db0a2a1d1ac..47177a5176b 100644 --- a/src/snapshot/toimage.js +++ b/src/snapshot/toimage.js @@ -6,73 +6,69 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var EventEmitter = require('events').EventEmitter; +var EventEmitter = require('events').EventEmitter -var Plotly = require('../plotly'); -var Lib = require('../lib'); - -var helpers = require('./helpers'); -var clonePlot = require('./cloneplot'); -var toSVG = require('./tosvg'); -var svgToImg = require('./svgtoimg'); +var Plotly = require('../plotly') +var Lib = require('../lib') +var helpers = require('./helpers') +var clonePlot = require('./cloneplot') +var toSVG = require('./tosvg') +var svgToImg = require('./svgtoimg') /** * @param {object} gd figure Object * @param {object} opts option object * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' */ -function toImage(gd, opts) { - +function toImage (gd, opts) { // first clone the GD so we can operate in a clean environment - var ev = new EventEmitter(); + var ev = new EventEmitter() - var clone = clonePlot(gd, {format: 'png'}); - var clonedGd = clone.gd; + var clone = clonePlot(gd, {format: 'png'}) + var clonedGd = clone.gd // put the cloned div somewhere off screen before attaching to DOM - clonedGd.style.position = 'absolute'; - clonedGd.style.left = '-5000px'; - document.body.appendChild(clonedGd); + clonedGd.style.position = 'absolute' + clonedGd.style.left = '-5000px' + document.body.appendChild(clonedGd) - function wait() { - var delay = helpers.getDelay(clonedGd._fullLayout); + function wait () { + var delay = helpers.getDelay(clonedGd._fullLayout) - setTimeout(function() { - var svg = toSVG(clonedGd); + setTimeout(function () { + var svg = toSVG(clonedGd) - var canvas = document.createElement('canvas'); - canvas.id = Lib.randstr(); + var canvas = document.createElement('canvas') + canvas.id = Lib.randstr() - ev = svgToImg({ - format: opts.format, - width: clonedGd._fullLayout.width, - height: clonedGd._fullLayout.height, - canvas: canvas, - emitter: ev, - svg: svg - }); + ev = svgToImg({ + format: opts.format, + width: clonedGd._fullLayout.width, + height: clonedGd._fullLayout.height, + canvas: canvas, + emitter: ev, + svg: svg + }) - ev.clean = function() { - if(clonedGd) document.body.removeChild(clonedGd); - }; + ev.clean = function () { + if (clonedGd) document.body.removeChild(clonedGd) + } + }, delay) + } - }, delay); - } + var redrawFunc = helpers.getRedrawFunc(clonedGd) - var redrawFunc = helpers.getRedrawFunc(clonedGd); - - Plotly.plot(clonedGd, clone.data, clone.layout, clone.config) + Plotly.plot(clonedGd, clone.data, clone.layout, clone.config) .then(redrawFunc) .then(wait) - .catch(function(err) { - ev.emit('error', err); - }); - + .catch(function (err) { + ev.emit('error', err) + }) - return ev; + return ev } -module.exports = toImage; +module.exports = toImage diff --git a/src/snapshot/tosvg.js b/src/snapshot/tosvg.js index 92188e7b3ff..88401cb1d49 100644 --- a/src/snapshot/tosvg.js +++ b/src/snapshot/tosvg.js @@ -6,112 +6,109 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var svgTextUtils = require('../lib/svg_text_utils') +var Drawing = require('../components/drawing') +var Color = require('../components/color') -var svgTextUtils = require('../lib/svg_text_utils'); -var Drawing = require('../components/drawing'); -var Color = require('../components/color'); +var xmlnsNamespaces = require('../constants/xmlns_namespaces') -var xmlnsNamespaces = require('../constants/xmlns_namespaces'); - - -module.exports = function toSVG(gd, format) { - var fullLayout = gd._fullLayout, - svg = fullLayout._paper, - toppaper = fullLayout._toppaper, - i; +module.exports = function toSVG (gd, format) { + var fullLayout = gd._fullLayout, + svg = fullLayout._paper, + toppaper = fullLayout._toppaper, + i // make background color a rect in the svg, then revert after scraping // all other alterations have been dealt with by properly preparing the svg // in the first place... like setting cursors with css classes so we don't // have to remove them, and providing the right namespaces in the svg to // begin with - svg.insert('rect', ':first-child') + svg.insert('rect', ':first-child') .call(Drawing.setRect, 0, 0, fullLayout.width, fullLayout.height) - .call(Color.fill, fullLayout.paper_bgcolor); + .call(Color.fill, fullLayout.paper_bgcolor) // subplot-specific to-SVG methods // which notably add the contents of the gl-container // into the main svg node - var basePlotModules = fullLayout._basePlotModules || []; - for(i = 0; i < basePlotModules.length; i++) { - var _module = basePlotModules[i]; + var basePlotModules = fullLayout._basePlotModules || [] + for (i = 0; i < basePlotModules.length; i++) { + var _module = basePlotModules[i] - if(_module.toSVG) _module.toSVG(gd); - } + if (_module.toSVG) _module.toSVG(gd) + } // add top items above them assumes everything in toppaper is either // a group or a defs, and if it's empty (like hoverlayer) we can ignore it. - if(toppaper) { - var nodes = toppaper.node().childNodes; + if (toppaper) { + var nodes = toppaper.node().childNodes // make copy of nodes as childNodes prop gets mutated in loop below - var topGroups = Array.prototype.slice.call(nodes); + var topGroups = Array.prototype.slice.call(nodes) - for(i = 0; i < topGroups.length; i++) { - var topGroup = topGroups[i]; + for (i = 0; i < topGroups.length; i++) { + var topGroup = topGroups[i] - if(topGroup.childNodes.length) svg.node().appendChild(topGroup); - } + if (topGroup.childNodes.length) svg.node().appendChild(topGroup) } + } // remove draglayer for Adobe Illustrator compatibility - if(fullLayout._draggers) { - fullLayout._draggers.remove(); - } + if (fullLayout._draggers) { + fullLayout._draggers.remove() + } // in case the svg element had an explicit background color, remove this // we want the rect to get the color so it's the right size; svg bg will // fill whatever container it's displayed in regardless of plot size. - svg.node().style.background = ''; + svg.node().style.background = '' - svg.selectAll('text') + svg.selectAll('text') .attr('data-unformatted', null) - .each(function() { - var txt = d3.select(this); + .each(function () { + var txt = d3.select(this) // hidden text is pre-formatting mathjax, // the browser ignores it but it can still confuse batik - if(txt.style('visibility') === 'hidden') { - txt.remove(); - return; - } - else { + if (txt.style('visibility') === 'hidden') { + txt.remove() + return + } else { // force other visibility value to export as visible // to not potentially confuse non-browser SVG implementations - txt.style('visibility', 'visible'); - } + txt.style('visibility', 'visible') + } // Font family styles break things because of quotation marks, // so we must remove them *after* the SVG DOM has been serialized // to a string (browsers convert singles back) - var ff = txt.style('font-family'); - if(ff && ff.indexOf('"') !== -1) { - txt.style('font-family', ff.replace(/"/g, 'TOBESTRIPPED')); - } - }); + var ff = txt.style('font-family') + if (ff && ff.indexOf('"') !== -1) { + txt.style('font-family', ff.replace(/"/g, 'TOBESTRIPPED')) + } + }) - if(format === 'pdf' || format === 'eps') { + if (format === 'pdf' || format === 'eps') { // these formats make the extra line MathJax adds around symbols look super thick in some cases // it looks better if this is removed entirely. - svg.selectAll('#MathJax_SVG_glyphs path') - .attr('stroke-width', 0); - } + svg.selectAll('#MathJax_SVG_glyphs path') + .attr('stroke-width', 0) + } // fix for IE namespacing quirk? // http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie - svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg); - svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink); + svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg) + svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink) - var s = new window.XMLSerializer().serializeToString(svg.node()); - s = svgTextUtils.html_entity_decode(s); - s = svgTextUtils.xml_entity_encode(s); + var s = new window.XMLSerializer().serializeToString(svg.node()) + s = svgTextUtils.html_entity_decode(s) + s = svgTextUtils.xml_entity_encode(s) // Fix quotations around font strings - s = s.replace(/("TOBESTRIPPED)|(TOBESTRIPPED")/g, '\''); + s = s.replace(/("TOBESTRIPPED)|(TOBESTRIPPED")/g, '\'') - return s; -}; + return s +} diff --git a/src/traces/bar/arrays_to_calcdata.js b/src/traces/bar/arrays_to_calcdata.js index 15ecc601d72..63144bd28a2 100644 --- a/src/traces/bar/arrays_to_calcdata.js +++ b/src/traces/bar/arrays_to_calcdata.js @@ -6,25 +6,23 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var mergeArray = require('../../lib').mergeArray; - +var mergeArray = require('../../lib').mergeArray // arrayOk attributes, merge them into calcdata array -module.exports = function arraysToCalcdata(cd, trace) { - mergeArray(trace.text, cd, 'tx'); +module.exports = function arraysToCalcdata (cd, trace) { + mergeArray(trace.text, cd, 'tx') - var marker = trace.marker; - if(marker) { - mergeArray(marker.opacity, cd, 'mo'); - mergeArray(marker.color, cd, 'mc'); + var marker = trace.marker + if (marker) { + mergeArray(marker.opacity, cd, 'mo') + mergeArray(marker.color, cd, 'mc') - var markerLine = marker.line; - if(markerLine) { - mergeArray(markerLine.color, cd, 'mlc'); - mergeArray(markerLine.width, cd, 'mlw'); - } + var markerLine = marker.line + if (markerLine) { + mergeArray(markerLine.color, cd, 'mlc') + mergeArray(markerLine.width, cd, 'mlw') } -}; + } +} diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 95141454624..1dc0f19f848 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -6,142 +6,141 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); -var errorBarAttrs = require('../../components/errorbars/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); -var fontAttrs = require('../../plots/font_attributes'); +var scatterAttrs = require('../scatter/attributes') +var colorAttributes = require('../../components/colorscale/color_attributes') +var errorBarAttrs = require('../../components/errorbars/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') +var fontAttrs = require('../../plots/font_attributes') -var extendFlat = require('../../lib/extend').extendFlat; -var extendDeep = require('../../lib/extend').extendDeep; +var extendFlat = require('../../lib/extend').extendFlat +var extendDeep = require('../../lib/extend').extendDeep -var textFontAttrs = extendDeep({}, fontAttrs); -textFontAttrs.family.arrayOk = true; -textFontAttrs.size.arrayOk = true; -textFontAttrs.color.arrayOk = true; +var textFontAttrs = extendDeep({}, fontAttrs) +textFontAttrs.family.arrayOk = true +textFontAttrs.size.arrayOk = true +textFontAttrs.color.arrayOk = true -var scatterMarkerAttrs = scatterAttrs.marker; -var scatterMarkerLineAttrs = scatterMarkerAttrs.line; +var scatterMarkerAttrs = scatterAttrs.marker +var scatterMarkerLineAttrs = scatterMarkerAttrs.line var markerLineWidth = extendFlat({}, - scatterMarkerLineAttrs.width, { dflt: 0 }); + scatterMarkerLineAttrs.width, { dflt: 0 }) var markerLine = extendFlat({}, { - width: markerLineWidth -}, colorAttributes('marker.line')); + width: markerLineWidth +}, colorAttributes('marker.line')) var marker = extendFlat({}, { - line: markerLine + line: markerLine }, colorAttributes('marker'), { - showscale: scatterMarkerAttrs.showscale, - colorbar: colorbarAttrs -}); - + showscale: scatterMarkerAttrs.showscale, + colorbar: colorbarAttrs +}) module.exports = { - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, - - text: scatterAttrs.text, - - textposition: { - valType: 'enumerated', - role: 'info', - values: ['inside', 'outside', 'auto', 'none'], - dflt: 'none', - arrayOk: true, - description: [ - 'Specifies the location of the `text`.', - '*inside* positions `text` inside, next to the bar end', - '(rotated and scaled if needed).', - '*outside* positions `text` outside, next to the bar end', - '(scaled if needed).', - '*auto* positions `text` inside or outside', - 'so that `text` size is maximized.' - ].join(' ') - }, - - textfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `text`.' - }), - - insidetextfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `text` lying inside the bar.' - }), - - outsidetextfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `text` lying outside the bar.' - }), - - orientation: { - valType: 'enumerated', - role: 'info', - values: ['v', 'h'], - description: [ - 'Sets the orientation of the bars.', - 'With *v* (*h*), the value of the each bar spans', - 'along the vertical (horizontal).' - ].join(' ') - }, - - base: { - valType: 'any', - dflt: null, - arrayOk: true, - role: 'info', - description: [ - 'Sets where the bar base is drawn (in position axis units).', - 'In *stack* or *relative* barmode,', - 'traces that set *base* will be excluded', - 'and drawn in *overlay* mode instead.' - ].join(' ') - }, - - offset: { - valType: 'number', - dflt: null, - arrayOk: true, - role: 'info', - description: [ - 'Shifts the position where the bar is drawn', - '(in position axis units).', - 'In *group* barmode,', - 'traces that set *offset* will be excluded', - 'and drawn in *overlay* mode instead.' - ].join(' ') - }, - - width: { - valType: 'number', - dflt: null, - min: 0, - arrayOk: true, - role: 'info', - description: [ - 'Sets the bar width (in position axis units).' - ].join(' ') - }, - - marker: marker, - - r: scatterAttrs.r, - t: scatterAttrs.t, - - error_y: errorBarAttrs, - error_x: errorBarAttrs, - - _deprecated: { - bardir: { - valType: 'enumerated', - role: 'info', - values: ['v', 'h'], - description: 'Renamed to `orientation`.' - } + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, + + text: scatterAttrs.text, + + textposition: { + valType: 'enumerated', + role: 'info', + values: ['inside', 'outside', 'auto', 'none'], + dflt: 'none', + arrayOk: true, + description: [ + 'Specifies the location of the `text`.', + '*inside* positions `text` inside, next to the bar end', + '(rotated and scaled if needed).', + '*outside* positions `text` outside, next to the bar end', + '(scaled if needed).', + '*auto* positions `text` inside or outside', + 'so that `text` size is maximized.' + ].join(' ') + }, + + textfont: extendFlat({}, textFontAttrs, { + description: 'Sets the font used for `text`.' + }), + + insidetextfont: extendFlat({}, textFontAttrs, { + description: 'Sets the font used for `text` lying inside the bar.' + }), + + outsidetextfont: extendFlat({}, textFontAttrs, { + description: 'Sets the font used for `text` lying outside the bar.' + }), + + orientation: { + valType: 'enumerated', + role: 'info', + values: ['v', 'h'], + description: [ + 'Sets the orientation of the bars.', + 'With *v* (*h*), the value of the each bar spans', + 'along the vertical (horizontal).' + ].join(' ') + }, + + base: { + valType: 'any', + dflt: null, + arrayOk: true, + role: 'info', + description: [ + 'Sets where the bar base is drawn (in position axis units).', + 'In *stack* or *relative* barmode,', + 'traces that set *base* will be excluded', + 'and drawn in *overlay* mode instead.' + ].join(' ') + }, + + offset: { + valType: 'number', + dflt: null, + arrayOk: true, + role: 'info', + description: [ + 'Shifts the position where the bar is drawn', + '(in position axis units).', + 'In *group* barmode,', + 'traces that set *offset* will be excluded', + 'and drawn in *overlay* mode instead.' + ].join(' ') + }, + + width: { + valType: 'number', + dflt: null, + min: 0, + arrayOk: true, + role: 'info', + description: [ + 'Sets the bar width (in position axis units).' + ].join(' ') + }, + + marker: marker, + + r: scatterAttrs.r, + t: scatterAttrs.t, + + error_y: errorBarAttrs, + error_x: errorBarAttrs, + + _deprecated: { + bardir: { + valType: 'enumerated', + role: 'info', + values: ['v', 'h'], + description: 'Renamed to `orientation`.' } -}; + } +} diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 24fb3352260..8abf4051825 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -6,100 +6,95 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Axes = require('../../plots/cartesian/axes') +var hasColorscale = require('../../components/colorscale/has_colorscale') +var colorscaleCalc = require('../../components/colorscale/calc') -var Axes = require('../../plots/cartesian/axes'); -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var colorscaleCalc = require('../../components/colorscale/calc'); +var arraysToCalcdata = require('./arrays_to_calcdata') -var arraysToCalcdata = require('./arrays_to_calcdata'); - - -module.exports = function calc(gd, trace) { +module.exports = function calc (gd, trace) { // depending on bar direction, set position and size axes // and data ranges // note: this logic for choosing orientation is // duplicated in graph_obj->setstyles - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'), - orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'), - sa, pos, size, i, scalendar; + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + ya = Axes.getFromId(gd, trace.yaxis || 'y'), + orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'), + sa, pos, size, i, scalendar - if(orientation === 'h') { - sa = xa; - size = xa.makeCalcdata(trace, 'x'); - pos = ya.makeCalcdata(trace, 'y'); + if (orientation === 'h') { + sa = xa + size = xa.makeCalcdata(trace, 'x') + pos = ya.makeCalcdata(trace, 'y') // not sure if it really makes sense to have dates for bar size data... // ideally if we want to make gantt charts or something we'd treat // the actual size (trace.x or y) as time delta but base as absolute // time. But included here for completeness. - scalendar = trace.xcalendar; - } - else { - sa = ya; - size = ya.makeCalcdata(trace, 'y'); - pos = xa.makeCalcdata(trace, 'x'); - scalendar = trace.ycalendar; - } + scalendar = trace.xcalendar + } else { + sa = ya + size = ya.makeCalcdata(trace, 'y') + pos = xa.makeCalcdata(trace, 'x') + scalendar = trace.ycalendar + } // create the "calculated data" to plot - var serieslen = Math.min(pos.length, size.length), - cd = []; + var serieslen = Math.min(pos.length, size.length), + cd = [] // set position - for(i = 0; i < serieslen; i++) { - + for (i = 0; i < serieslen; i++) { // add bars with non-numeric sizes to calcdata // so that ensure that traces with gaps are // plotted in the correct order - if(isNumeric(pos[i])) { - cd.push({p: pos[i]}); - } + if (isNumeric(pos[i])) { + cd.push({p: pos[i]}) } + } // set base - var base = trace.base, - b; - - if(Array.isArray(base)) { - for(i = 0; i < Math.min(base.length, cd.length); i++) { - b = sa.d2c(base[i], 0, scalendar); - cd[i].b = (isNumeric(b)) ? b : 0; - } - for(; i < cd.length; i++) { - cd[i].b = 0; - } + var base = trace.base, + b + + if (Array.isArray(base)) { + for (i = 0; i < Math.min(base.length, cd.length); i++) { + b = sa.d2c(base[i], 0, scalendar) + cd[i].b = (isNumeric(b)) ? b : 0 + } + for (; i < cd.length; i++) { + cd[i].b = 0 } - else { - b = sa.d2c(base, 0, scalendar); - b = (isNumeric(b)) ? b : 0; - for(i = 0; i < cd.length; i++) { - cd[i].b = b; - } + } else { + b = sa.d2c(base, 0, scalendar) + b = (isNumeric(b)) ? b : 0 + for (i = 0; i < cd.length; i++) { + cd[i].b = b } + } // set size - for(i = 0; i < cd.length; i++) { - if(isNumeric(size[i])) { - cd[i].s = size[i]; - } + for (i = 0; i < cd.length; i++) { + if (isNumeric(size[i])) { + cd[i].s = size[i] } + } // auto-z and autocolorscale if applicable - if(hasColorscale(trace, 'marker')) { - colorscaleCalc(trace, trace.marker.color, 'marker', 'c'); - } - if(hasColorscale(trace, 'marker.line')) { - colorscaleCalc(trace, trace.marker.line.color, 'marker.line', 'c'); - } + if (hasColorscale(trace, 'marker')) { + colorscaleCalc(trace, trace.marker.color, 'marker', 'c') + } + if (hasColorscale(trace, 'marker.line')) { + colorscaleCalc(trace, trace.marker.line.color, 'marker.line', 'c') + } - arraysToCalcdata(cd, trace); + arraysToCalcdata(cd, trace) - return cd; -}; + return cd +} diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 614176cfc68..b9479fc18ad 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -6,52 +6,50 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var Color = require('../../components/color') -var Lib = require('../../lib'); -var Color = require('../../components/color'); +var handleXYDefaults = require('../scatter/xy_defaults') +var handleStyleDefaults = require('../bar/style_defaults') +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults') +var attributes = require('./attributes') -var handleXYDefaults = require('../scatter/xy_defaults'); -var handleStyleDefaults = require('../bar/style_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var coerceFont = Lib.coerceFont -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var len = handleXYDefaults(traceIn, traceOut, layout, coerce) + if (!len) { + traceOut.visible = false + return + } - var coerceFont = Lib.coerceFont; + coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v') + coerce('base') + coerce('offset') + coerce('width') - var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { - traceOut.visible = false; - return; - } + coerce('text') - coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v'); - coerce('base'); - coerce('offset'); - coerce('width'); + var textPosition = coerce('textposition') - coerce('text'); + var hasBoth = Array.isArray(textPosition) || textPosition === 'auto', + hasInside = hasBoth || textPosition === 'inside', + hasOutside = hasBoth || textPosition === 'outside' + if (hasInside || hasOutside) { + var textFont = coerceFont(coerce, 'textfont', layout.font) + if (hasInside) coerceFont(coerce, 'insidetextfont', textFont) + if (hasOutside) coerceFont(coerce, 'outsidetextfont', textFont) + } - var textPosition = coerce('textposition'); - - var hasBoth = Array.isArray(textPosition) || textPosition === 'auto', - hasInside = hasBoth || textPosition === 'inside', - hasOutside = hasBoth || textPosition === 'outside'; - if(hasInside || hasOutside) { - var textFont = coerceFont(coerce, 'textfont', layout.font); - if(hasInside) coerceFont(coerce, 'insidetextfont', textFont); - if(hasOutside) coerceFont(coerce, 'outsidetextfont', textFont); - } - - handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); + handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) // override defaultColor for error bars with defaultLine - errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'x', inherit: 'y'}); -}; + errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'y'}) + errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'x', inherit: 'y'}) +} diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index f65f947461c..ea0b3ea906b 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -6,86 +6,82 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Fx = require('../../plots/cartesian/graph_interact'); -var ErrorBars = require('../../components/errorbars'); -var Color = require('../../components/color'); - - -module.exports = function hoverPoints(pointData, xval, yval, hovermode) { - var cd = pointData.cd, - trace = cd[0].trace, - t = cd[0].t, - xa = pointData.xa, - ya = pointData.ya, - barDelta = (hovermode === 'closest') ? +'use strict' + +var Fx = require('../../plots/cartesian/graph_interact') +var ErrorBars = require('../../components/errorbars') +var Color = require('../../components/color') + +module.exports = function hoverPoints (pointData, xval, yval, hovermode) { + var cd = pointData.cd, + trace = cd[0].trace, + t = cd[0].t, + xa = pointData.xa, + ya = pointData.ya, + barDelta = (hovermode === 'closest') ? t.barwidth / 2 : t.bargroupwidth / 2, - barPos; + barPos - if(hovermode !== 'closest') barPos = function(di) { return di.p; }; - else if(trace.orientation === 'h') barPos = function(di) { return di.y; }; - else barPos = function(di) { return di.x; }; + if (hovermode !== 'closest') barPos = function (di) { return di.p } + else if (trace.orientation === 'h') barPos = function (di) { return di.y } + else barPos = function (di) { return di.x } - var dx, dy; - if(trace.orientation === 'h') { - dx = function(di) { + var dx, dy + if (trace.orientation === 'h') { + dx = function (di) { // add a gradient so hovering near the end of a // bar makes it a little closer match - return Fx.inbox(di.b - xval, di.x - xval) + (di.x - xval) / (di.x - di.b); - }; - dy = function(di) { - var centerPos = barPos(di) - yval; - return Fx.inbox(centerPos - barDelta, centerPos + barDelta); - }; + return Fx.inbox(di.b - xval, di.x - xval) + (di.x - xval) / (di.x - di.b) } - else { - dy = function(di) { - return Fx.inbox(di.b - yval, di.y - yval) + (di.y - yval) / (di.y - di.b); - }; - dx = function(di) { - var centerPos = barPos(di) - xval; - return Fx.inbox(centerPos - barDelta, centerPos + barDelta); - }; + dy = function (di) { + var centerPos = barPos(di) - yval + return Fx.inbox(centerPos - barDelta, centerPos + barDelta) } - - var distfn = Fx.getDistanceFunction(hovermode, dx, dy); - Fx.getClosest(cd, distfn, pointData); - - // skip the rest (for this trace) if we didn't find a close point - if(pointData.index === false) return; - - // the closest data point - var di = cd[pointData.index], - mc = di.mcc || trace.marker.color, - mlc = di.mlcc || trace.marker.line.color, - mlw = di.mlw || trace.marker.line.width; - if(Color.opacity(mc)) pointData.color = mc; - else if(Color.opacity(mlc) && mlw) pointData.color = mlc; - - var size = (trace.base) ? di.b + di.s : di.s; - if(trace.orientation === 'h') { - pointData.x0 = pointData.x1 = xa.c2p(di.x, true); - pointData.xLabelVal = size; - - pointData.y0 = ya.c2p(barPos(di) - barDelta, true); - pointData.y1 = ya.c2p(barPos(di) + barDelta, true); - pointData.yLabelVal = di.p; + } else { + dy = function (di) { + return Fx.inbox(di.b - yval, di.y - yval) + (di.y - yval) / (di.y - di.b) } - else { - pointData.y0 = pointData.y1 = ya.c2p(di.y, true); - pointData.yLabelVal = size; - - pointData.x0 = xa.c2p(barPos(di) - barDelta, true); - pointData.x1 = xa.c2p(barPos(di) + barDelta, true); - pointData.xLabelVal = di.p; + dx = function (di) { + var centerPos = barPos(di) - xval + return Fx.inbox(centerPos - barDelta, centerPos + barDelta) } + } - if(di.tx) pointData.text = di.tx; + var distfn = Fx.getDistanceFunction(hovermode, dx, dy) + Fx.getClosest(cd, distfn, pointData) - ErrorBars.hoverInfo(di, trace, pointData); + // skip the rest (for this trace) if we didn't find a close point + if (pointData.index === false) return - return [pointData]; -}; + // the closest data point + var di = cd[pointData.index], + mc = di.mcc || trace.marker.color, + mlc = di.mlcc || trace.marker.line.color, + mlw = di.mlw || trace.marker.line.width + if (Color.opacity(mc)) pointData.color = mc + else if (Color.opacity(mlc) && mlw) pointData.color = mlc + + var size = (trace.base) ? di.b + di.s : di.s + if (trace.orientation === 'h') { + pointData.x0 = pointData.x1 = xa.c2p(di.x, true) + pointData.xLabelVal = size + + pointData.y0 = ya.c2p(barPos(di) - barDelta, true) + pointData.y1 = ya.c2p(barPos(di) + barDelta, true) + pointData.yLabelVal = di.p + } else { + pointData.y0 = pointData.y1 = ya.c2p(di.y, true) + pointData.yLabelVal = size + + pointData.x0 = xa.c2p(barPos(di) - barDelta, true) + pointData.x1 = xa.c2p(barPos(di) + barDelta, true) + pointData.xLabelVal = di.p + } + + if (di.tx) pointData.text = di.tx + + ErrorBars.hoverInfo(di, trace, pointData) + + return [pointData] +} diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index f890fe8b673..68fe467a4b4 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -6,34 +6,33 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Bar = {} -var Bar = {}; +Bar.attributes = require('./attributes') +Bar.layoutAttributes = require('./layout_attributes') +Bar.supplyDefaults = require('./defaults') +Bar.supplyLayoutDefaults = require('./layout_defaults') +Bar.calc = require('./calc') +Bar.setPositions = require('./set_positions') +Bar.colorbar = require('../scatter/colorbar') +Bar.arraysToCalcdata = require('./arrays_to_calcdata') +Bar.plot = require('./plot') +Bar.style = require('./style') +Bar.hoverPoints = require('./hover') -Bar.attributes = require('./attributes'); -Bar.layoutAttributes = require('./layout_attributes'); -Bar.supplyDefaults = require('./defaults'); -Bar.supplyLayoutDefaults = require('./layout_defaults'); -Bar.calc = require('./calc'); -Bar.setPositions = require('./set_positions'); -Bar.colorbar = require('../scatter/colorbar'); -Bar.arraysToCalcdata = require('./arrays_to_calcdata'); -Bar.plot = require('./plot'); -Bar.style = require('./style'); -Bar.hoverPoints = require('./hover'); - -Bar.moduleType = 'trace'; -Bar.name = 'bar'; -Bar.basePlotModule = require('../../plots/cartesian'); -Bar.categories = ['cartesian', 'bar', 'oriented', 'markerColorscale', 'errorBarsOK', 'showLegend']; +Bar.moduleType = 'trace' +Bar.name = 'bar' +Bar.basePlotModule = require('../../plots/cartesian') +Bar.categories = ['cartesian', 'bar', 'oriented', 'markerColorscale', 'errorBarsOK', 'showLegend'] Bar.meta = { - description: [ - 'The data visualized by the span of the bars is set in `y`', - 'if `orientation` is set th *v* (the default)', - 'and the labels are set in `x`.', - 'By setting `orientation` to *h*, the roles are interchanged.' - ].join(' ') -}; + description: [ + 'The data visualized by the span of the bars is set in `y`', + 'if `orientation` is set th *v* (the default)', + 'and the labels are set in `x`.', + 'By setting `orientation` to *h*, the roles are interchanged.' + ].join(' ') +} -module.exports = Bar; +module.exports = Bar diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index 5dfb7c78191..fdf98ed6d84 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -6,58 +6,57 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - barmode: { - valType: 'enumerated', - values: ['stack', 'group', 'overlay', 'relative'], - dflt: 'group', - role: 'info', - description: [ - 'Determines how bars at the same location coordinate', - 'are displayed on the graph.', - 'With *stack*, the bars are stacked on top of one another', - 'With *relative*, the bars are stacked on top of one another,', - 'with negative values below the axis, positive values above', - 'With *group*, the bars are plotted next to one another', - 'centered around the shared location.', - 'With *overlay*, the bars are plotted over one another,', - 'you might need to an *opacity* to see multiple bars.' - ].join(' ') - }, - barnorm: { - valType: 'enumerated', - values: ['', 'fraction', 'percent'], - dflt: '', - role: 'info', - description: [ - 'Sets the normalization for bar traces on the graph.', - 'With *fraction*, the value of each bar is divide by the sum of the', - 'values at the location coordinate.', - 'With *percent*, the results form *fraction* are presented in percents.' - ].join(' ') - }, - bargap: { - valType: 'number', - min: 0, - max: 1, - role: 'style', - description: [ - 'Sets the gap (in plot fraction) between bars of', - 'adjacent location coordinates.' - ].join(' ') - }, - bargroupgap: { - valType: 'number', - min: 0, - max: 1, - dflt: 0, - role: 'style', - description: [ - 'Sets the gap (in plot fraction) between bars of', - 'the same location coordinate.' - ].join(' ') - } -}; + barmode: { + valType: 'enumerated', + values: ['stack', 'group', 'overlay', 'relative'], + dflt: 'group', + role: 'info', + description: [ + 'Determines how bars at the same location coordinate', + 'are displayed on the graph.', + 'With *stack*, the bars are stacked on top of one another', + 'With *relative*, the bars are stacked on top of one another,', + 'with negative values below the axis, positive values above', + 'With *group*, the bars are plotted next to one another', + 'centered around the shared location.', + 'With *overlay*, the bars are plotted over one another,', + 'you might need to an *opacity* to see multiple bars.' + ].join(' ') + }, + barnorm: { + valType: 'enumerated', + values: ['', 'fraction', 'percent'], + dflt: '', + role: 'info', + description: [ + 'Sets the normalization for bar traces on the graph.', + 'With *fraction*, the value of each bar is divide by the sum of the', + 'values at the location coordinate.', + 'With *percent*, the results form *fraction* are presented in percents.' + ].join(' ') + }, + bargap: { + valType: 'number', + min: 0, + max: 1, + role: 'style', + description: [ + 'Sets the gap (in plot fraction) between bars of', + 'adjacent location coordinates.' + ].join(' ') + }, + bargroupgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + role: 'style', + description: [ + 'Sets the gap (in plot fraction) between bars of', + 'the same location coordinate.' + ].join(' ') + } +} diff --git a/src/traces/bar/layout_defaults.js b/src/traces/bar/layout_defaults.js index 9fc2e030fe5..32a26d88099 100644 --- a/src/traces/bar/layout_defaults.js +++ b/src/traces/bar/layout_defaults.js @@ -6,51 +6,49 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') +var Lib = require('../../lib') -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); -var Lib = require('../../lib'); +var layoutAttributes = require('./layout_attributes') -var layoutAttributes = require('./layout_attributes'); +module.exports = function (layoutIn, layoutOut, fullData) { + function coerce (attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt) + } + var hasBars = false, + shouldBeGapless = false, + gappedAnyway = false, + usedSubplots = {} -module.exports = function(layoutIn, layoutOut, fullData) { - function coerce(attr, dflt) { - return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); - } - - var hasBars = false, - shouldBeGapless = false, - gappedAnyway = false, - usedSubplots = {}; - - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; - if(Registry.traceIs(trace, 'bar')) hasBars = true; - else continue; + for (var i = 0; i < fullData.length; i++) { + var trace = fullData[i] + if (Registry.traceIs(trace, 'bar')) hasBars = true + else continue // if we have at least 2 grouped bar traces on the same subplot, // we should default to a gap anyway, even if the data is histograms - if(layoutIn.barmode !== 'overlay' && layoutIn.barmode !== 'stack') { - var subploti = trace.xaxis + trace.yaxis; - if(usedSubplots[subploti]) gappedAnyway = true; - usedSubplots[subploti] = true; - } - - if(trace.visible && trace.type === 'histogram') { - var pa = Axes.getFromId({_fullLayout: layoutOut}, - trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']); - if(pa.type !== 'category') shouldBeGapless = true; - } + if (layoutIn.barmode !== 'overlay' && layoutIn.barmode !== 'stack') { + var subploti = trace.xaxis + trace.yaxis + if (usedSubplots[subploti]) gappedAnyway = true + usedSubplots[subploti] = true + } + + if (trace.visible && trace.type === 'histogram') { + var pa = Axes.getFromId({_fullLayout: layoutOut}, + trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']) + if (pa.type !== 'category') shouldBeGapless = true } + } - if(!hasBars) return; + if (!hasBars) return - var mode = coerce('barmode'); - if(mode !== 'overlay') coerce('barnorm'); + var mode = coerce('barmode') + if (mode !== 'overlay') coerce('barnorm') - coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2); - coerce('bargroupgap'); -}; + coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2) + coerce('bargroupgap') +} diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index ff69c21f01f..bdf61747d53 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -6,515 +6,496 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var isNumeric = require('fast-isnumeric') +var tinycolor = require('tinycolor2') -var d3 = require('d3'); -var isNumeric = require('fast-isnumeric'); -var tinycolor = require('tinycolor2'); +var Lib = require('../../lib') +var svgTextUtils = require('../../lib/svg_text_utils') -var Lib = require('../../lib'); -var svgTextUtils = require('../../lib/svg_text_utils'); - -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var ErrorBars = require('../../components/errorbars'); +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var ErrorBars = require('../../components/errorbars') var attributes = require('./attributes'), - attributeText = attributes.text, - attributeTextPosition = attributes.textposition, - attributeTextFont = attributes.textfont, - attributeInsideTextFont = attributes.insidetextfont, - attributeOutsideTextFont = attributes.outsidetextfont; + attributeText = attributes.text, + attributeTextPosition = attributes.textposition, + attributeTextFont = attributes.textfont, + attributeInsideTextFont = attributes.insidetextfont, + attributeOutsideTextFont = attributes.outsidetextfont // padding in pixels around text -var TEXTPAD = 3; +var TEXTPAD = 3 -module.exports = function plot(gd, plotinfo, cdbar) { - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - fullLayout = gd._fullLayout; +module.exports = function plot (gd, plotinfo, cdbar) { + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + fullLayout = gd._fullLayout - var bartraces = plotinfo.plot.select('.barlayer') + var bartraces = plotinfo.plot.select('.barlayer') .selectAll('g.trace.bars') - .data(cdbar); + .data(cdbar) - bartraces.enter().append('g') - .attr('class', 'trace bars'); + bartraces.enter().append('g') + .attr('class', 'trace bars') - bartraces.append('g') + bartraces.append('g') .attr('class', 'points') - .each(function(d) { - var t = d[0].t, - trace = d[0].trace, - poffset = t.poffset, - poffsetIsArray = Array.isArray(poffset), - barwidth = t.barwidth, - barwidthIsArray = Array.isArray(barwidth); - - d3.select(this).selectAll('g.point') + .each(function (d) { + var t = d[0].t, + trace = d[0].trace, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth) + + d3.select(this).selectAll('g.point') .data(Lib.identity) .enter().append('g').classed('point', true) - .each(function(di, i) { + .each(function (di, i) { // now display the bar // clipped xf/yf (2nd arg true): non-positive // log values go off-screen by plotwidth // so you see them continue if you drag the plot - var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset), - p1 = p0 + ((barwidthIsArray) ? barwidth[i] : barwidth), - s0 = di.b, - s1 = s0 + di.s; - - var x0, x1, y0, y1; - if(trace.orientation === 'h') { - y0 = ya.c2p(p0, true); - y1 = ya.c2p(p1, true); - x0 = xa.c2p(s0, true); - x1 = xa.c2p(s1, true); - } - else { - x0 = xa.c2p(p0, true); - x1 = xa.c2p(p1, true); - y0 = ya.c2p(s0, true); - y1 = ya.c2p(s1, true); - } - - if(!isNumeric(x0) || !isNumeric(x1) || + var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset), + p1 = p0 + ((barwidthIsArray) ? barwidth[i] : barwidth), + s0 = di.b, + s1 = s0 + di.s + + var x0, x1, y0, y1 + if (trace.orientation === 'h') { + y0 = ya.c2p(p0, true) + y1 = ya.c2p(p1, true) + x0 = xa.c2p(s0, true) + x1 = xa.c2p(s1, true) + } else { + x0 = xa.c2p(p0, true) + x1 = xa.c2p(p1, true) + y0 = ya.c2p(s0, true) + y1 = ya.c2p(s1, true) + } + + if (!isNumeric(x0) || !isNumeric(x1) || !isNumeric(y0) || !isNumeric(y1) || x0 === x1 || y0 === y1) { - d3.select(this).remove(); - return; - } + d3.select(this).remove() + return + } - var lw = (di.mlw + 1 || trace.marker.line.width + 1 || + var lw = (di.mlw + 1 || trace.marker.line.width + 1 || (di.trace ? di.trace.marker.line.width : 0) + 1) - 1, - offset = d3.round((lw / 2) % 1, 2); + offset = d3.round((lw / 2) % 1, 2) - function roundWithLine(v) { + function roundWithLine (v) { // if there are explicit gaps, don't round, // it can make the gaps look crappy - return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ? - d3.round(Math.round(v) - offset, 2) : v; - } + return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ? + d3.round(Math.round(v) - offset, 2) : v + } - function expandToVisible(v, vc) { + function expandToVisible (v, vc) { // if it's not in danger of disappearing entirely, // round more precisely - return Math.abs(v - vc) >= 2 ? roundWithLine(v) : + return Math.abs(v - vc) >= 2 ? roundWithLine(v) : // but if it's very thin, expand it so it's // necessarily visible, even if it might overlap // its neighbor - (v > vc ? Math.ceil(v) : Math.floor(v)); - } + (v > vc ? Math.ceil(v) : Math.floor(v)) + } - if(!gd._context.staticPlot) { + if (!gd._context.staticPlot) { // if bars are not fully opaque or they have a line // around them, round to integer pixels, mainly for // safari so we prevent overlaps from its expansive // pixelation. if the bars ARE fully opaque and have // no line, expand to a full pixel to make sure we // can see them - var op = Color.opacity(di.mc || trace.marker.color), - fixpx = (op < 1 || lw > 0.01) ? - roundWithLine : expandToVisible; - x0 = fixpx(x0, x1); - x1 = fixpx(x1, x0); - y0 = fixpx(y0, y1); - y1 = fixpx(y1, y0); - } + var op = Color.opacity(di.mc || trace.marker.color), + fixpx = (op < 1 || lw > 0.01) ? + roundWithLine : expandToVisible + x0 = fixpx(x0, x1) + x1 = fixpx(x1, x0) + y0 = fixpx(y0, y1) + y1 = fixpx(y1, y0) + } // append bar path and text - var bar = d3.select(this); + var bar = d3.select(this) - bar.append('path').attr('d', - 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); + bar.append('path').attr('d', + 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z') - appendBarText(gd, bar, d, i, x0, x1, y0, y1); - }); - }); + appendBarText(gd, bar, d, i, x0, x1, y0, y1) + }) + }) // error bars are on the top - bartraces.call(ErrorBars.plot, plotinfo); - -}; + bartraces.call(ErrorBars.plot, plotinfo) +} -function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { - function appendTextNode(bar, text, textFont) { - var textSelection = bar.append('text') +function appendBarText (gd, bar, calcTrace, i, x0, x1, y0, y1) { + function appendTextNode (bar, text, textFont) { + var textSelection = bar.append('text') // prohibit tex interpretation until we can handle // tex and regular text together .attr('data-notex', 1) .text(text) .attr({ - 'class': 'bartext', - transform: '', - 'data-bb': '', - 'text-anchor': 'middle', - x: 0, - y: 0 + 'class': 'bartext', + transform: '', + 'data-bb': '', + 'text-anchor': 'middle', + x: 0, + y: 0 }) - .call(Drawing.font, textFont); + .call(Drawing.font, textFont) - textSelection.call(svgTextUtils.convertToTspans); - textSelection.selectAll('tspan.line').attr({x: 0, y: 0}); + textSelection.call(svgTextUtils.convertToTspans) + textSelection.selectAll('tspan.line').attr({x: 0, y: 0}) - return textSelection; - } + return textSelection + } // get trace attributes - var trace = calcTrace[0].trace, - orientation = trace.orientation; + var trace = calcTrace[0].trace, + orientation = trace.orientation - var text = getText(trace, i); - if(!text) return; + var text = getText(trace, i) + if (!text) return - var textPosition = getTextPosition(trace, i); - if(textPosition === 'none') return; + var textPosition = getTextPosition(trace, i) + if (textPosition === 'none') return - var textFont = getTextFont(trace, i, gd._fullLayout.font), - insideTextFont = getInsideTextFont(trace, i, textFont), - outsideTextFont = getOutsideTextFont(trace, i, textFont); + var textFont = getTextFont(trace, i, gd._fullLayout.font), + insideTextFont = getInsideTextFont(trace, i, textFont), + outsideTextFont = getOutsideTextFont(trace, i, textFont) // compute text position - var barmode = gd._fullLayout.barmode, - inStackMode = (barmode === 'stack'), - inRelativeMode = (barmode === 'relative'), - inStackOrRelativeMode = inStackMode || inRelativeMode, + var barmode = gd._fullLayout.barmode, + inStackMode = (barmode === 'stack'), + inRelativeMode = (barmode === 'relative'), + inStackOrRelativeMode = inStackMode || inRelativeMode, - calcBar = calcTrace[i], - isOutmostBar = !inStackOrRelativeMode || calcBar._outmost, + calcBar = calcTrace[i], + isOutmostBar = !inStackOrRelativeMode || calcBar._outmost, - barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, // padding excluded - barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, // padding excluded + barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, // padding excluded + barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, // padding excluded - textSelection, - textBB, - textWidth, - textHeight; + textSelection, + textBB, + textWidth, + textHeight - if(textPosition === 'outside') { - if(!isOutmostBar) textPosition = 'inside'; - } + if (textPosition === 'outside') { + if (!isOutmostBar) textPosition = 'inside' + } - if(textPosition === 'auto') { - if(isOutmostBar) { + if (textPosition === 'auto') { + if (isOutmostBar) { // draw text using insideTextFont and check if it fits inside bar - textSelection = appendTextNode(bar, text, insideTextFont); + textSelection = appendTextNode(bar, text, insideTextFont) - textBB = Drawing.bBox(textSelection.node()), + textBB = Drawing.bBox(textSelection.node()), textWidth = textBB.width, - textHeight = textBB.height; + textHeight = textBB.height - var textHasSize = (textWidth > 0 && textHeight > 0), - fitsInside = + var textHasSize = (textWidth > 0 && textHeight > 0), + fitsInside = (textWidth <= barWidth && textHeight <= barHeight), - fitsInsideIfRotated = + fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth), - fitsInsideIfShrunk = (orientation === 'h') ? + fitsInsideIfShrunk = (orientation === 'h') ? (barWidth >= textWidth * (barHeight / textHeight)) : - (barHeight >= textHeight * (barWidth / textWidth)); - if(textHasSize && + (barHeight >= textHeight * (barWidth / textWidth)) + if (textHasSize && (fitsInside || fitsInsideIfRotated || fitsInsideIfShrunk)) { - textPosition = 'inside'; - } - else { - textPosition = 'outside'; - textSelection.remove(); - textSelection = null; - } - } - else textPosition = 'inside'; - } - - if(!textSelection) { - textSelection = appendTextNode(bar, text, + textPosition = 'inside' + } else { + textPosition = 'outside' + textSelection.remove() + textSelection = null + } + } else textPosition = 'inside' + } + + if (!textSelection) { + textSelection = appendTextNode(bar, text, (textPosition === 'outside') ? - outsideTextFont : insideTextFont); + outsideTextFont : insideTextFont) - textBB = Drawing.bBox(textSelection.node()), + textBB = Drawing.bBox(textSelection.node()), textWidth = textBB.width, - textHeight = textBB.height; + textHeight = textBB.height - if(textWidth <= 0 || textHeight <= 0) { - textSelection.remove(); - return; - } + if (textWidth <= 0 || textHeight <= 0) { + textSelection.remove() + return } + } // compute text transform - var transform; - if(textPosition === 'outside') { - transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, - orientation); - } - else { - transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, - orientation); - } - - textSelection.attr('transform', transform); + var transform + if (textPosition === 'outside') { + transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, + orientation) + } else { + transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, + orientation) + } + + textSelection.attr('transform', transform) } -function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { +function getTransformToMoveInsideBar (x0, x1, y0, y1, textBB, orientation) { // compute text and target positions - var textWidth = textBB.width, - textHeight = textBB.height, - textX = (textBB.left + textBB.right) / 2, - textY = (textBB.top + textBB.bottom) / 2, - barWidth = Math.abs(x1 - x0), - barHeight = Math.abs(y1 - y0), - targetWidth, - targetHeight, - targetX, - targetY; + var textWidth = textBB.width, + textHeight = textBB.height, + textX = (textBB.left + textBB.right) / 2, + textY = (textBB.top + textBB.bottom) / 2, + barWidth = Math.abs(x1 - x0), + barHeight = Math.abs(y1 - y0), + targetWidth, + targetHeight, + targetX, + targetY // apply text padding - var textpad; - if(barWidth > (2 * TEXTPAD) && barHeight > (2 * TEXTPAD)) { - textpad = TEXTPAD; - barWidth -= 2 * textpad; - barHeight -= 2 * textpad; - } - else textpad = 0; + var textpad + if (barWidth > (2 * TEXTPAD) && barHeight > (2 * TEXTPAD)) { + textpad = TEXTPAD + barWidth -= 2 * textpad + barHeight -= 2 * textpad + } else textpad = 0 // compute rotation and scale - var rotate, - scale; + var rotate, + scale - if(textWidth <= barWidth && textHeight <= barHeight) { + if (textWidth <= barWidth && textHeight <= barHeight) { // no scale or rotation is required - rotate = false; - scale = 1; - } - else if(textWidth <= barHeight && textHeight <= barWidth) { + rotate = false + scale = 1 + } else if (textWidth <= barHeight && textHeight <= barWidth) { // only rotation is required - rotate = true; - scale = 1; - } - else if((textWidth < textHeight) === (barWidth < barHeight)) { + rotate = true + scale = 1 + } else if ((textWidth < textHeight) === (barWidth < barHeight)) { // only scale is required - rotate = false; - scale = Math.min(barWidth / textWidth, barHeight / textHeight); - } - else { + rotate = false + scale = Math.min(barWidth / textWidth, barHeight / textHeight) + } else { // both scale and rotation are required - rotate = true; - scale = Math.min(barHeight / textWidth, barWidth / textHeight); - } + rotate = true + scale = Math.min(barHeight / textWidth, barWidth / textHeight) + } - if(rotate) rotate = 90; // rotate clockwise + if (rotate) rotate = 90 // rotate clockwise // compute text and target positions - if(rotate) { - targetWidth = scale * textHeight; - targetHeight = scale * textWidth; - } - else { - targetWidth = scale * textWidth; - targetHeight = scale * textHeight; - } - - if(orientation === 'h') { - if(x1 < x0) { + if (rotate) { + targetWidth = scale * textHeight + targetHeight = scale * textWidth + } else { + targetWidth = scale * textWidth + targetHeight = scale * textHeight + } + + if (orientation === 'h') { + if (x1 < x0) { // bar end is on the left hand side - targetX = x1 + textpad + targetWidth / 2; - targetY = (y0 + y1) / 2; - } - else { - targetX = x1 - textpad - targetWidth / 2; - targetY = (y0 + y1) / 2; - } + targetX = x1 + textpad + targetWidth / 2 + targetY = (y0 + y1) / 2 + } else { + targetX = x1 - textpad - targetWidth / 2 + targetY = (y0 + y1) / 2 } - else { - if(y1 > y0) { + } else { + if (y1 > y0) { // bar end is on the bottom - targetX = (x0 + x1) / 2; - targetY = y1 - textpad - targetHeight / 2; - } - else { - targetX = (x0 + x1) / 2; - targetY = y1 + textpad + targetHeight / 2; - } + targetX = (x0 + x1) / 2 + targetY = y1 - textpad - targetHeight / 2 + } else { + targetX = (x0 + x1) / 2 + targetY = y1 + textpad + targetHeight / 2 } + } - return getTransform(textX, textY, targetX, targetY, scale, rotate); + return getTransform(textX, textY, targetX, targetY, scale, rotate) } -function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { - var barWidth = (orientation === 'h') ? +function getTransformToMoveOutsideBar (x0, x1, y0, y1, textBB, orientation) { + var barWidth = (orientation === 'h') ? Math.abs(y1 - y0) : Math.abs(x1 - x0), - textpad; + textpad // apply text padding if possible - if(barWidth > 2 * TEXTPAD) { - textpad = TEXTPAD; - barWidth -= 2 * textpad; - } + if (barWidth > 2 * TEXTPAD) { + textpad = TEXTPAD + barWidth -= 2 * textpad + } // compute rotation and scale - var rotate = false, - scale = (orientation === 'h') ? + var rotate = false, + scale = (orientation === 'h') ? Math.min(1, barWidth / textBB.height) : - Math.min(1, barWidth / textBB.width); + Math.min(1, barWidth / textBB.width) // compute text and target positions - var textX = (textBB.left + textBB.right) / 2, - textY = (textBB.top + textBB.bottom) / 2, - targetWidth, - targetHeight, - targetX, - targetY; - if(rotate) { - targetWidth = scale * textBB.height; - targetHeight = scale * textBB.width; - } - else { - targetWidth = scale * textBB.width; - targetHeight = scale * textBB.height; - } - - if(orientation === 'h') { - if(x1 < x0) { + var textX = (textBB.left + textBB.right) / 2, + textY = (textBB.top + textBB.bottom) / 2, + targetWidth, + targetHeight, + targetX, + targetY + if (rotate) { + targetWidth = scale * textBB.height + targetHeight = scale * textBB.width + } else { + targetWidth = scale * textBB.width + targetHeight = scale * textBB.height + } + + if (orientation === 'h') { + if (x1 < x0) { // bar end is on the left hand side - targetX = x1 - textpad - targetWidth / 2; - targetY = (y0 + y1) / 2; - } - else { - targetX = x1 + textpad + targetWidth / 2; - targetY = (y0 + y1) / 2; - } + targetX = x1 - textpad - targetWidth / 2 + targetY = (y0 + y1) / 2 + } else { + targetX = x1 + textpad + targetWidth / 2 + targetY = (y0 + y1) / 2 } - else { - if(y1 > y0) { + } else { + if (y1 > y0) { // bar end is on the bottom - targetX = (x0 + x1) / 2; - targetY = y1 + textpad + targetHeight / 2; - } - else { - targetX = (x0 + x1) / 2; - targetY = y1 - textpad - targetHeight / 2; - } + targetX = (x0 + x1) / 2 + targetY = y1 + textpad + targetHeight / 2 + } else { + targetX = (x0 + x1) / 2 + targetY = y1 - textpad - targetHeight / 2 } + } - return getTransform(textX, textY, targetX, targetY, scale, rotate); + return getTransform(textX, textY, targetX, targetY, scale, rotate) } -function getTransform(textX, textY, targetX, targetY, scale, rotate) { - var transformScale, - transformRotate, - transformTranslate; +function getTransform (textX, textY, targetX, targetY, scale, rotate) { + var transformScale, + transformRotate, + transformTranslate - if(scale < 1) transformScale = 'scale(' + scale + ') '; - else { - scale = 1; - transformScale = ''; - } + if (scale < 1) transformScale = 'scale(' + scale + ') ' + else { + scale = 1 + transformScale = '' + } - transformRotate = (rotate) ? - 'rotate(' + rotate + ' ' + textX + ' ' + textY + ') ' : ''; + transformRotate = (rotate) ? + 'rotate(' + rotate + ' ' + textX + ' ' + textY + ') ' : '' // Note that scaling also affects the center of the text box - var translateX = (targetX - scale * textX), - translateY = (targetY - scale * textY); - transformTranslate = 'translate(' + translateX + ' ' + translateY + ')'; + var translateX = (targetX - scale * textX), + translateY = (targetY - scale * textY) + transformTranslate = 'translate(' + translateX + ' ' + translateY + ')' - return transformTranslate + transformScale + transformRotate; + return transformTranslate + transformScale + transformRotate } -function getText(trace, index) { - var value = getValue(trace.text, index); - return coerceString(attributeText, value); +function getText (trace, index) { + var value = getValue(trace.text, index) + return coerceString(attributeText, value) } -function getTextPosition(trace, index) { - var value = getValue(trace.textposition, index); - return coerceEnumerated(attributeTextPosition, value); +function getTextPosition (trace, index) { + var value = getValue(trace.textposition, index) + return coerceEnumerated(attributeTextPosition, value) } -function getTextFont(trace, index, defaultValue) { - return getFontValue( - attributeTextFont, trace.textfont, index, defaultValue); +function getTextFont (trace, index, defaultValue) { + return getFontValue( + attributeTextFont, trace.textfont, index, defaultValue) } -function getInsideTextFont(trace, index, defaultValue) { - return getFontValue( - attributeInsideTextFont, trace.insidetextfont, index, defaultValue); +function getInsideTextFont (trace, index, defaultValue) { + return getFontValue( + attributeInsideTextFont, trace.insidetextfont, index, defaultValue) } -function getOutsideTextFont(trace, index, defaultValue) { - return getFontValue( - attributeOutsideTextFont, trace.outsidetextfont, index, defaultValue); +function getOutsideTextFont (trace, index, defaultValue) { + return getFontValue( + attributeOutsideTextFont, trace.outsidetextfont, index, defaultValue) } -function getFontValue(attributeDefinition, attributeValue, index, defaultValue) { - attributeValue = attributeValue || {}; +function getFontValue (attributeDefinition, attributeValue, index, defaultValue) { + attributeValue = attributeValue || {} - var familyValue = getValue(attributeValue.family, index), - sizeValue = getValue(attributeValue.size, index), - colorValue = getValue(attributeValue.color, index); + var familyValue = getValue(attributeValue.family, index), + sizeValue = getValue(attributeValue.size, index), + colorValue = getValue(attributeValue.color, index) - return { - family: coerceString( + return { + family: coerceString( attributeDefinition.family, familyValue, defaultValue.family), - size: coerceNumber( + size: coerceNumber( attributeDefinition.size, sizeValue, defaultValue.size), - color: coerceColor( + color: coerceColor( attributeDefinition.color, colorValue, defaultValue.color) - }; + } } -function getValue(arrayOrScalar, index) { - var value; - if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar; - else if(index < arrayOrScalar.length) value = arrayOrScalar[index]; - return value; +function getValue (arrayOrScalar, index) { + var value + if (!Array.isArray(arrayOrScalar)) value = arrayOrScalar + else if (index < arrayOrScalar.length) value = arrayOrScalar[index] + return value } -function coerceString(attributeDefinition, value, defaultValue) { - if(typeof value === 'string') { - if(value || !attributeDefinition.noBlank) return value; - } - else if(typeof value === 'number') { - if(!attributeDefinition.strict) return String(value); - } +function coerceString (attributeDefinition, value, defaultValue) { + if (typeof value === 'string') { + if (value || !attributeDefinition.noBlank) return value + } else if (typeof value === 'number') { + if (!attributeDefinition.strict) return String(value) + } - return (defaultValue !== undefined) ? + return (defaultValue !== undefined) ? defaultValue : - attributeDefinition.dflt; + attributeDefinition.dflt } -function coerceEnumerated(attributeDefinition, value, defaultValue) { - if(attributeDefinition.coerceNumber) value = +value; +function coerceEnumerated (attributeDefinition, value, defaultValue) { + if (attributeDefinition.coerceNumber) value = +value - if(attributeDefinition.values.indexOf(value) !== -1) return value; + if (attributeDefinition.values.indexOf(value) !== -1) return value - return (defaultValue !== undefined) ? + return (defaultValue !== undefined) ? defaultValue : - attributeDefinition.dflt; + attributeDefinition.dflt } -function coerceNumber(attributeDefinition, value, defaultValue) { - if(isNumeric(value)) { - value = +value; +function coerceNumber (attributeDefinition, value, defaultValue) { + if (isNumeric(value)) { + value = +value - var min = attributeDefinition.min, - max = attributeDefinition.max, - isOutOfBounds = (min !== undefined && value < min) || - (max !== undefined && value > max); + var min = attributeDefinition.min, + max = attributeDefinition.max, + isOutOfBounds = (min !== undefined && value < min) || + (max !== undefined && value > max) - if(!isOutOfBounds) return value; - } + if (!isOutOfBounds) return value + } - return (defaultValue !== undefined) ? + return (defaultValue !== undefined) ? defaultValue : - attributeDefinition.dflt; + attributeDefinition.dflt } -function coerceColor(attributeDefinition, value, defaultValue) { - if(tinycolor(value).isValid()) return value; +function coerceColor (attributeDefinition, value, defaultValue) { + if (tinycolor(value).isValid()) return value - return (defaultValue !== undefined) ? + return (defaultValue !== undefined) ? defaultValue : - attributeDefinition.dflt; + attributeDefinition.dflt } diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 79df94decc5..386c503009d 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); - -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); -var Sieve = require('./sieve.js'); +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') +var Sieve = require('./sieve.js') /* * Bar chart stacking/grouping positioning and autoscaling calculations @@ -22,392 +21,376 @@ var Sieve = require('./sieve.js'); * now doing this one subplot at a time */ -module.exports = function setPositions(gd, plotinfo) { - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis; - - var fullTraces = gd._fullData, - calcTraces = gd.calcdata, - calcTracesHorizontal = [], - calcTracesVertical = [], - i; - for(i = 0; i < fullTraces.length; i++) { - var fullTrace = fullTraces[i]; - if( +module.exports = function setPositions (gd, plotinfo) { + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis + + var fullTraces = gd._fullData, + calcTraces = gd.calcdata, + calcTracesHorizontal = [], + calcTracesVertical = [], + i + for (i = 0; i < fullTraces.length; i++) { + var fullTrace = fullTraces[i] + if ( fullTrace.visible === true && Registry.traceIs(fullTrace, 'bar') && fullTrace.xaxis === xa._id && fullTrace.yaxis === ya._id && !calcTraces[i][0].placeholder ) { - if(fullTrace.orientation === 'h') { - calcTracesHorizontal.push(calcTraces[i]); - } - else { - calcTracesVertical.push(calcTraces[i]); - } - } + if (fullTrace.orientation === 'h') { + calcTracesHorizontal.push(calcTraces[i]) + } else { + calcTracesVertical.push(calcTraces[i]) + } } + } - setGroupPositions(gd, xa, ya, calcTracesVertical); - setGroupPositions(gd, ya, xa, calcTracesHorizontal); -}; - + setGroupPositions(gd, xa, ya, calcTracesVertical) + setGroupPositions(gd, ya, xa, calcTracesHorizontal) +} -function setGroupPositions(gd, pa, sa, calcTraces) { - if(!calcTraces.length) return; +function setGroupPositions (gd, pa, sa, calcTraces) { + if (!calcTraces.length) return - var barmode = gd._fullLayout.barmode, - overlay = (barmode === 'overlay'), - group = (barmode === 'group'), - excluded, - included, - i, calcTrace, fullTrace; + var barmode = gd._fullLayout.barmode, + overlay = (barmode === 'overlay'), + group = (barmode === 'group'), + excluded, + included, + i, calcTrace, fullTrace - if(overlay) { - setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces); - } - else if(group) { + if (overlay) { + setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) + } else if (group) { // exclude from the group those traces for which the user set an offset - excluded = []; - included = []; - for(i = 0; i < calcTraces.length; i++) { - calcTrace = calcTraces[i]; - fullTrace = calcTrace[0].trace; - - if(fullTrace.offset === undefined) included.push(calcTrace); - else excluded.push(calcTrace); - } + excluded = [] + included = [] + for (i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i] + fullTrace = calcTrace[0].trace + + if (fullTrace.offset === undefined) included.push(calcTrace) + else excluded.push(calcTrace) + } - if(included.length) { - setGroupPositionsInGroupMode(gd, pa, sa, included); - } - if(excluded.length) { - setGroupPositionsInOverlayMode(gd, pa, sa, excluded); - } + if (included.length) { + setGroupPositionsInGroupMode(gd, pa, sa, included) + } + if (excluded.length) { + setGroupPositionsInOverlayMode(gd, pa, sa, excluded) } - else { + } else { // exclude from the stack those traces for which the user set a base - excluded = []; - included = []; - for(i = 0; i < calcTraces.length; i++) { - calcTrace = calcTraces[i]; - fullTrace = calcTrace[0].trace; - - if(fullTrace.base === undefined) included.push(calcTrace); - else excluded.push(calcTrace); - } + excluded = [] + included = [] + for (i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i] + fullTrace = calcTrace[0].trace + + if (fullTrace.base === undefined) included.push(calcTrace) + else excluded.push(calcTrace) + } - if(included.length) { - setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included); - } - if(excluded.length) { - setGroupPositionsInOverlayMode(gd, pa, sa, excluded); - } + if (included.length) { + setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included) + } + if (excluded.length) { + setGroupPositionsInOverlayMode(gd, pa, sa, excluded) } + } } - -function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { - var barnorm = gd._fullLayout.barnorm, - separateNegativeValues = false, - dontMergeOverlappingData = !barnorm; +function setGroupPositionsInOverlayMode (gd, pa, sa, calcTraces) { + var barnorm = gd._fullLayout.barnorm, + separateNegativeValues = false, + dontMergeOverlappingData = !barnorm // update position axis and set bar offsets and widths - for(var i = 0; i < calcTraces.length; i++) { - var calcTrace = calcTraces[i]; + for (var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i] - var sieve = new Sieve( + var sieve = new Sieve( [calcTrace], separateNegativeValues, dontMergeOverlappingData - ); + ) // set bar offsets and widths, and update position axis - setOffsetAndWidth(gd, pa, sieve); + setOffsetAndWidth(gd, pa, sieve) // set bar bases and sizes, and update size axis // // (note that `setGroupPositionsInOverlayMode` handles the case barnorm // is defined, because this function is also invoked for traces that // can't be grouped or stacked) - if(barnorm) { - sieveBars(gd, sa, sieve); - normalizeBars(gd, sa, sieve); - } - else { - setBaseAndTop(gd, sa, sieve); - } + if (barnorm) { + sieveBars(gd, sa, sieve) + normalizeBars(gd, sa, sieve) + } else { + setBaseAndTop(gd, sa, sieve) } + } } - -function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces) { - var fullLayout = gd._fullLayout, - barnorm = fullLayout.barnorm, - separateNegativeValues = false, - dontMergeOverlappingData = !barnorm, - sieve = new Sieve( +function setGroupPositionsInGroupMode (gd, pa, sa, calcTraces) { + var fullLayout = gd._fullLayout, + barnorm = fullLayout.barnorm, + separateNegativeValues = false, + dontMergeOverlappingData = !barnorm, + sieve = new Sieve( calcTraces, separateNegativeValues, dontMergeOverlappingData - ); + ) // set bar offsets and widths, and update position axis - setOffsetAndWidthInGroupMode(gd, pa, sieve); + setOffsetAndWidthInGroupMode(gd, pa, sieve) // set bar bases and sizes, and update size axis - if(barnorm) { - sieveBars(gd, sa, sieve); - normalizeBars(gd, sa, sieve); - } - else { - setBaseAndTop(gd, sa, sieve); - } + if (barnorm) { + sieveBars(gd, sa, sieve) + normalizeBars(gd, sa, sieve) + } else { + setBaseAndTop(gd, sa, sieve) + } } - -function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces) { - var fullLayout = gd._fullLayout, - barmode = fullLayout.barmode, - stack = (barmode === 'stack'), - relative = (barmode === 'relative'), - barnorm = gd._fullLayout.barnorm, - separateNegativeValues = relative, - dontMergeOverlappingData = !(barnorm || stack || relative), - sieve = new Sieve( +function setGroupPositionsInStackOrRelativeMode (gd, pa, sa, calcTraces) { + var fullLayout = gd._fullLayout, + barmode = fullLayout.barmode, + stack = (barmode === 'stack'), + relative = (barmode === 'relative'), + barnorm = gd._fullLayout.barnorm, + separateNegativeValues = relative, + dontMergeOverlappingData = !(barnorm || stack || relative), + sieve = new Sieve( calcTraces, separateNegativeValues, dontMergeOverlappingData - ); + ) // set bar offsets and widths, and update position axis - setOffsetAndWidth(gd, pa, sieve); + setOffsetAndWidth(gd, pa, sieve) // set bar bases and sizes, and update size axis - stackBars(gd, sa, sieve); + stackBars(gd, sa, sieve) // flag the outmost bar (for text display purposes) - for(var i = 0; i < calcTraces.length; i++) { - var calcTrace = calcTraces[i]; + for (var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i] - for(var j = 0; j < calcTrace.length; j++) { - var bar = calcTrace[j]; + for (var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j] - if(!isNumeric(bar.s)) continue; + if (!isNumeric(bar.s)) continue - var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s)); - if(isOutmostBar) bar._outmost = true; - } + var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s)) + if (isOutmostBar) bar._outmost = true } + } // Note that marking the outmost bars has to be done // before `normalizeBars` changes `bar.b` and `bar.s`. - if(barnorm) normalizeBars(gd, sa, sieve); + if (barnorm) normalizeBars(gd, sa, sieve) } - -function setOffsetAndWidth(gd, pa, sieve) { - var fullLayout = gd._fullLayout, - bargap = fullLayout.bargap, - bargroupgap = fullLayout.bargroupgap, - minDiff = sieve.minDiff, - calcTraces = sieve.traces, - i, calcTrace, calcTrace0, - t; +function setOffsetAndWidth (gd, pa, sieve) { + var fullLayout = gd._fullLayout, + bargap = fullLayout.bargap, + bargroupgap = fullLayout.bargroupgap, + minDiff = sieve.minDiff, + calcTraces = sieve.traces, + i, calcTrace, calcTrace0, + t // set bar offsets and widths - var barGroupWidth = minDiff * (1 - bargap), - barWidthPlusGap = barGroupWidth, - barWidth = barWidthPlusGap * (1 - bargroupgap); + var barGroupWidth = minDiff * (1 - bargap), + barWidthPlusGap = barGroupWidth, + barWidth = barWidthPlusGap * (1 - bargroupgap) // computer bar group center and bar offset - var offsetFromCenter = -barWidth / 2; + var offsetFromCenter = -barWidth / 2 - for(i = 0; i < calcTraces.length; i++) { - calcTrace = calcTraces[i]; - calcTrace0 = calcTrace[0]; + for (i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i] + calcTrace0 = calcTrace[0] // store bar width and offset for this trace - t = calcTrace0.t; - t.barwidth = barWidth; - t.poffset = offsetFromCenter; - t.bargroupwidth = barGroupWidth; - } + t = calcTrace0.t + t.barwidth = barWidth + t.poffset = offsetFromCenter + t.bargroupwidth = barGroupWidth + } // stack bars that only differ by rounding - sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + sieve.binWidth = calcTraces[0][0].t.barwidth / 100 // if defined, apply trace offset and width - applyAttributes(sieve); + applyAttributes(sieve) // store the bar center in each calcdata item - setBarCenter(gd, pa, sieve); + setBarCenter(gd, pa, sieve) // update position axes - updatePositionAxis(gd, pa, sieve); + updatePositionAxis(gd, pa, sieve) } - -function setOffsetAndWidthInGroupMode(gd, pa, sieve) { - var fullLayout = gd._fullLayout, - bargap = fullLayout.bargap, - bargroupgap = fullLayout.bargroupgap, - positions = sieve.positions, - distinctPositions = sieve.distinctPositions, - minDiff = sieve.minDiff, - calcTraces = sieve.traces, - i, calcTrace, calcTrace0, - t; +function setOffsetAndWidthInGroupMode (gd, pa, sieve) { + var fullLayout = gd._fullLayout, + bargap = fullLayout.bargap, + bargroupgap = fullLayout.bargroupgap, + positions = sieve.positions, + distinctPositions = sieve.distinctPositions, + minDiff = sieve.minDiff, + calcTraces = sieve.traces, + i, calcTrace, calcTrace0, + t // if there aren't any overlapping positions, // let them have full width even if mode is group - var overlap = (positions.length !== distinctPositions.length); + var overlap = (positions.length !== distinctPositions.length) - var nTraces = calcTraces.length, - barGroupWidth = minDiff * (1 - bargap), - barWidthPlusGap = (overlap) ? barGroupWidth / nTraces : barGroupWidth, - barWidth = barWidthPlusGap * (1 - bargroupgap); + var nTraces = calcTraces.length, + barGroupWidth = minDiff * (1 - bargap), + barWidthPlusGap = (overlap) ? barGroupWidth / nTraces : barGroupWidth, + barWidth = barWidthPlusGap * (1 - bargroupgap) - for(i = 0; i < nTraces; i++) { - calcTrace = calcTraces[i]; - calcTrace0 = calcTrace[0]; + for (i = 0; i < nTraces; i++) { + calcTrace = calcTraces[i] + calcTrace0 = calcTrace[0] // computer bar group center and bar offset - var offsetFromCenter = (overlap) ? + var offsetFromCenter = (overlap) ? ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 : - -barWidth / 2; + -barWidth / 2 // store bar width and offset for this trace - t = calcTrace0.t; - t.barwidth = barWidth; - t.poffset = offsetFromCenter; - t.bargroupwidth = barGroupWidth; - } + t = calcTrace0.t + t.barwidth = barWidth + t.poffset = offsetFromCenter + t.bargroupwidth = barGroupWidth + } // stack bars that only differ by rounding - sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + sieve.binWidth = calcTraces[0][0].t.barwidth / 100 // if defined, apply trace width - applyAttributes(sieve); + applyAttributes(sieve) // store the bar center in each calcdata item - setBarCenter(gd, pa, sieve); + setBarCenter(gd, pa, sieve) // update position axes - updatePositionAxis(gd, pa, sieve, overlap); + updatePositionAxis(gd, pa, sieve, overlap) } +function applyAttributes (sieve) { + var calcTraces = sieve.traces, + i, calcTrace, calcTrace0, fullTrace, + j, + t -function applyAttributes(sieve) { - var calcTraces = sieve.traces, - i, calcTrace, calcTrace0, fullTrace, - j, - t; - - for(i = 0; i < calcTraces.length; i++) { - calcTrace = calcTraces[i]; - calcTrace0 = calcTrace[0]; - fullTrace = calcTrace0.trace; - t = calcTrace0.t; + for (i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i] + calcTrace0 = calcTrace[0] + fullTrace = calcTrace0.trace + t = calcTrace0.t - var offset = fullTrace.offset, - initialPoffset = t.poffset, - newPoffset; + var offset = fullTrace.offset, + initialPoffset = t.poffset, + newPoffset - if(Array.isArray(offset)) { + if (Array.isArray(offset)) { // if offset is an array, then clone it into t.poffset. - newPoffset = offset.slice(0, calcTrace.length); + newPoffset = offset.slice(0, calcTrace.length) // guard against non-numeric items - for(j = 0; j < newPoffset.length; j++) { - if(!isNumeric(newPoffset[j])) { - newPoffset[j] = initialPoffset; - } - } + for (j = 0; j < newPoffset.length; j++) { + if (!isNumeric(newPoffset[j])) { + newPoffset[j] = initialPoffset + } + } // if the length of the array is too short, // then extend it with the initial value of t.poffset - for(j = newPoffset.length; j < calcTrace.length; j++) { - newPoffset.push(initialPoffset); - } + for (j = newPoffset.length; j < calcTrace.length; j++) { + newPoffset.push(initialPoffset) + } - t.poffset = newPoffset; - } - else if(offset !== undefined) { - t.poffset = offset; - } + t.poffset = newPoffset + } else if (offset !== undefined) { + t.poffset = offset + } - var width = fullTrace.width, - initialBarwidth = t.barwidth; + var width = fullTrace.width, + initialBarwidth = t.barwidth - if(Array.isArray(width)) { + if (Array.isArray(width)) { // if width is an array, then clone it into t.barwidth. - var newBarwidth = width.slice(0, calcTrace.length); + var newBarwidth = width.slice(0, calcTrace.length) // guard against non-numeric items - for(j = 0; j < newBarwidth.length; j++) { - if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth; - } + for (j = 0; j < newBarwidth.length; j++) { + if (!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth + } // if the length of the array is too short, // then extend it with the initial value of t.barwidth - for(j = newBarwidth.length; j < calcTrace.length; j++) { - newBarwidth.push(initialBarwidth); - } + for (j = newBarwidth.length; j < calcTrace.length; j++) { + newBarwidth.push(initialBarwidth) + } - t.barwidth = newBarwidth; + t.barwidth = newBarwidth // if user didn't set offset, // then correct t.poffset to ensure bars remain centered - if(offset === undefined) { - newPoffset = []; - for(j = 0; j < calcTrace.length; j++) { - newPoffset.push( + if (offset === undefined) { + newPoffset = [] + for (j = 0; j < calcTrace.length; j++) { + newPoffset.push( initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 - ); - } - t.poffset = newPoffset; - } + ) } - else if(width !== undefined) { - t.barwidth = width; + t.poffset = newPoffset + } + } else if (width !== undefined) { + t.barwidth = width // if user didn't set offset, // then correct t.poffset to ensure bars remain centered - if(offset === undefined) { - t.poffset = initialPoffset + (initialBarwidth - width) / 2; - } - } + if (offset === undefined) { + t.poffset = initialPoffset + (initialBarwidth - width) / 2 + } } + } } +function setBarCenter (gd, pa, sieve) { + var calcTraces = sieve.traces, + pLetter = getAxisLetter(pa) -function setBarCenter(gd, pa, sieve) { - var calcTraces = sieve.traces, - pLetter = getAxisLetter(pa); + for (var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i], + t = calcTrace[0].t, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth) - for(var i = 0; i < calcTraces.length; i++) { - var calcTrace = calcTraces[i], - t = calcTrace[0].t, - poffset = t.poffset, - poffsetIsArray = Array.isArray(poffset), - barwidth = t.barwidth, - barwidthIsArray = Array.isArray(barwidth); + for (var j = 0; j < calcTrace.length; j++) { + var calcBar = calcTrace[j] - for(var j = 0; j < calcTrace.length; j++) { - var calcBar = calcTrace[j]; - - calcBar[pLetter] = calcBar.p + + calcBar[pLetter] = calcBar.p + ((poffsetIsArray) ? poffset[j] : poffset) + - ((barwidthIsArray) ? barwidth[j] : barwidth) / 2; - } + ((barwidthIsArray) ? barwidth[j] : barwidth) / 2 } + } } +function updatePositionAxis (gd, pa, sieve, allowMinDtick) { + var calcTraces = sieve.traces, + distinctPositions = sieve.distinctPositions, + distinctPositions0 = distinctPositions[0], + minDiff = sieve.minDiff, + vpad = minDiff / 2 -function updatePositionAxis(gd, pa, sieve, allowMinDtick) { - var calcTraces = sieve.traces, - distinctPositions = sieve.distinctPositions, - distinctPositions0 = distinctPositions[0], - minDiff = sieve.minDiff, - vpad = minDiff / 2; - - Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick); + Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick) // If the user set the bar width or the offset, // then bars can be shifted away from their positions @@ -415,194 +398,189 @@ function updatePositionAxis(gd, pa, sieve, allowMinDtick) { // // Here, we compute pMin and pMax to expand the position axis, // so that all bars are fully within the axis range. - var pMin = Math.min.apply(Math, distinctPositions) - vpad, - pMax = Math.max.apply(Math, distinctPositions) + vpad; + var pMin = Math.min.apply(Math, distinctPositions) - vpad, + pMax = Math.max.apply(Math, distinctPositions) + vpad - for(var i = 0; i < calcTraces.length; i++) { - var calcTrace = calcTraces[i], - calcTrace0 = calcTrace[0], - fullTrace = calcTrace0.trace; + for (var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i], + calcTrace0 = calcTrace[0], + fullTrace = calcTrace0.trace - if(fullTrace.width === undefined && fullTrace.offset === undefined) { - continue; - } + if (fullTrace.width === undefined && fullTrace.offset === undefined) { + continue + } - var t = calcTrace0.t, - poffset = t.poffset, - barwidth = t.barwidth, - poffsetIsArray = Array.isArray(poffset), - barwidthIsArray = Array.isArray(barwidth); - - for(var j = 0; j < calcTrace.length; j++) { - var calcBar = calcTrace[j], - calcBarOffset = (poffsetIsArray) ? poffset[j] : poffset, - calcBarWidth = (barwidthIsArray) ? barwidth[j] : barwidth, - p = calcBar.p, - l = p + calcBarOffset, - r = l + calcBarWidth; - - pMin = Math.min(pMin, l); - pMax = Math.max(pMax, r); - } + var t = calcTrace0.t, + poffset = t.poffset, + barwidth = t.barwidth, + poffsetIsArray = Array.isArray(poffset), + barwidthIsArray = Array.isArray(barwidth) + + for (var j = 0; j < calcTrace.length; j++) { + var calcBar = calcTrace[j], + calcBarOffset = (poffsetIsArray) ? poffset[j] : poffset, + calcBarWidth = (barwidthIsArray) ? barwidth[j] : barwidth, + p = calcBar.p, + l = p + calcBarOffset, + r = l + calcBarWidth + + pMin = Math.min(pMin, l) + pMax = Math.max(pMax, r) } + } - Axes.expand(pa, [pMin, pMax], {padded: false}); + Axes.expand(pa, [pMin, pMax], {padded: false}) } - -function setBaseAndTop(gd, sa, sieve) { +function setBaseAndTop (gd, sa, sieve) { // store these bar bases and tops in calcdata // and make sure the size axis includes zero, // along with the bases and tops of each bar. - var traces = sieve.traces, - sLetter = getAxisLetter(sa), - sMax = sa.l2c(sa.c2l(0)), - sMin = sMax; - - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; - - for(var j = 0; j < trace.length; j++) { - var bar = trace[j], - barBase = bar.b, - barTop = barBase + bar.s; - - bar[sLetter] = barTop; - - if(isNumeric(sa.c2l(barTop))) { - sMax = Math.max(sMax, barTop); - sMin = Math.min(sMin, barTop); - } - if(isNumeric(sa.c2l(barBase))) { - sMax = Math.max(sMax, barBase); - sMin = Math.min(sMin, barBase); - } - } + var traces = sieve.traces, + sLetter = getAxisLetter(sa), + sMax = sa.l2c(sa.c2l(0)), + sMin = sMax + + for (var i = 0; i < traces.length; i++) { + var trace = traces[i] + + for (var j = 0; j < trace.length; j++) { + var bar = trace[j], + barBase = bar.b, + barTop = barBase + bar.s + + bar[sLetter] = barTop + + if (isNumeric(sa.c2l(barTop))) { + sMax = Math.max(sMax, barTop) + sMin = Math.min(sMin, barTop) + } + if (isNumeric(sa.c2l(barBase))) { + sMax = Math.max(sMax, barBase) + sMin = Math.min(sMin, barBase) + } } + } - Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}) } +function stackBars (gd, sa, sieve) { + var fullLayout = gd._fullLayout, + barnorm = fullLayout.barnorm, + sLetter = getAxisLetter(sa), + traces = sieve.traces, + i, trace, + j, bar -function stackBars(gd, sa, sieve) { - var fullLayout = gd._fullLayout, - barnorm = fullLayout.barnorm, - sLetter = getAxisLetter(sa), - traces = sieve.traces, - i, trace, - j, bar; + var sMax = sa.l2c(sa.c2l(0)), + sMin = sMax - var sMax = sa.l2c(sa.c2l(0)), - sMin = sMax; + for (i = 0; i < traces.length; i++) { + trace = traces[i] - for(i = 0; i < traces.length; i++) { - trace = traces[i]; + for (j = 0; j < trace.length; j++) { + bar = trace[j] - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - - if(!isNumeric(bar.s)) continue; + if (!isNumeric(bar.s)) continue // stack current bar and get previous sum - var barBase = sieve.put(bar.p, bar.b + bar.s), - barTop = barBase + bar.b + bar.s; + var barBase = sieve.put(bar.p, bar.b + bar.s), + barTop = barBase + bar.b + bar.s // store the bar base and top in each calcdata item - bar.b = barBase; - bar[sLetter] = barTop; - - if(!barnorm) { - if(isNumeric(sa.c2l(barTop))) { - sMax = Math.max(sMax, barTop); - sMin = Math.min(sMin, barTop); - } - if(isNumeric(sa.c2l(barBase))) { - sMax = Math.max(sMax, barBase); - sMin = Math.min(sMin, barBase); - } - } + bar.b = barBase + bar[sLetter] = barTop + + if (!barnorm) { + if (isNumeric(sa.c2l(barTop))) { + sMax = Math.max(sMax, barTop) + sMin = Math.min(sMin, barTop) + } + if (isNumeric(sa.c2l(barBase))) { + sMax = Math.max(sMax, barBase) + sMin = Math.min(sMin, barBase) } + } } + } // if barnorm is set, let normalizeBars update the axis range - if(!barnorm) Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); + if (!barnorm) Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}) } +function sieveBars (gd, sa, sieve) { + var traces = sieve.traces -function sieveBars(gd, sa, sieve) { - var traces = sieve.traces; + for (var i = 0; i < traces.length; i++) { + var trace = traces[i] - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; + for (var j = 0; j < trace.length; j++) { + var bar = trace[j] - for(var j = 0; j < trace.length; j++) { - var bar = trace[j]; - - if(isNumeric(bar.s)) sieve.put(bar.p, bar.b + bar.s); - } + if (isNumeric(bar.s)) sieve.put(bar.p, bar.b + bar.s) } + } } - -function normalizeBars(gd, sa, sieve) { +function normalizeBars (gd, sa, sieve) { // Note: // // normalizeBars requires that either sieveBars or stackBars has been // previously invoked. - var traces = sieve.traces, - sLetter = getAxisLetter(sa), - sTop = (gd._fullLayout.barnorm === 'fraction') ? 1 : 100, - sTiny = sTop / 1e9, // in case of rounding error in sum - sMin = 0, - sMax = (gd._fullLayout.barmode === 'stack') ? sTop : 0, - padded = false; - - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; - - for(var j = 0; j < trace.length; j++) { - var bar = trace[j]; - - if(!isNumeric(bar.s)) continue; - - var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); - bar.b *= scale; - bar.s *= scale; - - var barBase = bar.b, - barTop = barBase + bar.s; - bar[sLetter] = barTop; - - if(isNumeric(sa.c2l(barTop))) { - if(barTop < sMin - sTiny) { - padded = true; - sMin = barTop; - } - if(barTop > sMax + sTiny) { - padded = true; - sMax = barTop; - } - } - - if(isNumeric(sa.c2l(barBase))) { - if(barBase < sMin - sTiny) { - padded = true; - sMin = barBase; - } - if(barBase > sMax + sTiny) { - padded = true; - sMax = barBase; - } - } + var traces = sieve.traces, + sLetter = getAxisLetter(sa), + sTop = (gd._fullLayout.barnorm === 'fraction') ? 1 : 100, + sTiny = sTop / 1e9, // in case of rounding error in sum + sMin = 0, + sMax = (gd._fullLayout.barmode === 'stack') ? sTop : 0, + padded = false + + for (var i = 0; i < traces.length; i++) { + var trace = traces[i] + + for (var j = 0; j < trace.length; j++) { + var bar = trace[j] + + if (!isNumeric(bar.s)) continue + + var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)) + bar.b *= scale + bar.s *= scale + + var barBase = bar.b, + barTop = barBase + bar.s + bar[sLetter] = barTop + + if (isNumeric(sa.c2l(barTop))) { + if (barTop < sMin - sTiny) { + padded = true + sMin = barTop } + if (barTop > sMax + sTiny) { + padded = true + sMax = barTop + } + } + + if (isNumeric(sa.c2l(barBase))) { + if (barBase < sMin - sTiny) { + padded = true + sMin = barBase + } + if (barBase > sMax + sTiny) { + padded = true + sMax = barBase + } + } } + } // update range of size axis - Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}) } - -function getAxisLetter(ax) { - return ax._id.charAt(0); +function getAxisLetter (ax) { + return ax._id.charAt(0) } diff --git a/src/traces/bar/sieve.js b/src/traces/bar/sieve.js index 55655c743c9..ca482dd3f05 100644 --- a/src/traces/bar/sieve.js +++ b/src/traces/bar/sieve.js @@ -6,11 +6,11 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -module.exports = Sieve; +module.exports = Sieve -var Lib = require('../../lib'); +var Lib = require('../../lib') /** * Helper class to sieve data from traces into bins @@ -24,28 +24,28 @@ var Lib = require('../../lib'); * @param {boolean} [dontMergeOverlappingData] * If true, then don't merge overlapping bars into a single bar */ -function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) { - this.traces = traces; - this.separateNegativeValues = separateNegativeValues; - this.dontMergeOverlappingData = dontMergeOverlappingData; +function Sieve (traces, separateNegativeValues, dontMergeOverlappingData) { + this.traces = traces + this.separateNegativeValues = separateNegativeValues + this.dontMergeOverlappingData = dontMergeOverlappingData - var positions = []; - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; - for(var j = 0; j < trace.length; j++) { - var bar = trace[j]; - positions.push(bar.p); - } + var positions = [] + for (var i = 0; i < traces.length; i++) { + var trace = traces[i] + for (var j = 0; j < trace.length; j++) { + var bar = trace[j] + positions.push(bar.p) } - this.positions = positions; + } + this.positions = positions - var dv = Lib.distinctVals(this.positions); - this.distinctPositions = dv.vals; - this.minDiff = dv.minDiff; + var dv = Lib.distinctVals(this.positions) + this.distinctPositions = dv.vals + this.minDiff = dv.minDiff - this.binWidth = this.minDiff; + this.binWidth = this.minDiff - this.bins = {}; + this.bins = {} } /** @@ -56,14 +56,14 @@ function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) { * @param {number} value * @returns {number} Previous bin value */ -Sieve.prototype.put = function put(position, value) { - var label = this.getLabel(position, value), - oldValue = this.bins[label] || 0; +Sieve.prototype.put = function put (position, value) { + var label = this.getLabel(position, value), + oldValue = this.bins[label] || 0 - this.bins[label] = oldValue + value; + this.bins[label] = oldValue + value - return oldValue; -}; + return oldValue +} /** * Get current bin value for a given datum @@ -74,10 +74,10 @@ Sieve.prototype.put = function put(position, value) { * (required if this.separateNegativeValues is true) * @returns {number} Current bin value */ -Sieve.prototype.get = function put(position, value) { - var label = this.getLabel(position, value); - return this.bins[label] || 0; -}; +Sieve.prototype.get = function put (position, value) { + var label = this.getLabel(position, value) + return this.bins[label] || 0 +} /** * Get bin label for a given datum @@ -90,10 +90,10 @@ Sieve.prototype.get = function put(position, value) { * (prefixed with a 'v' if value is negative and this.separateNegativeValues is * true; otherwise prefixed with '^') */ -Sieve.prototype.getLabel = function getLabel(position, value) { - var prefix = (value < 0 && this.separateNegativeValues) ? 'v' : '^', - label = (this.dontMergeOverlappingData) ? +Sieve.prototype.getLabel = function getLabel (position, value) { + var prefix = (value < 0 && this.separateNegativeValues) ? 'v' : '^', + label = (this.dontMergeOverlappingData) ? position : - Math.round(position / this.binWidth); - return prefix + label; -}; + Math.round(position / this.binWidth) + return prefix + label +} diff --git a/src/traces/bar/style.js b/src/traces/bar/style.js index d0fc54e3429..c7400fe7b92 100644 --- a/src/traces/bar/style.js +++ b/src/traces/bar/style.js @@ -6,71 +6,69 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var ErrorBars = require('../../components/errorbars') -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var ErrorBars = require('../../components/errorbars'); - - -module.exports = function style(gd) { - var s = d3.select(gd).selectAll('g.trace.bars'), - barcount = s.size(), - fullLayout = gd._fullLayout; +module.exports = function style (gd) { + var s = d3.select(gd).selectAll('g.trace.bars'), + barcount = s.size(), + fullLayout = gd._fullLayout // trace styling - s.style('opacity', function(d) { return d[0].trace.opacity; }) + s.style('opacity', function (d) { return d[0].trace.opacity }) // for gapless (either stacked or neighboring grouped) bars use // crispEdges to turn off antialiasing so an artificial gap // isn't introduced. - .each(function(d) { - if((fullLayout.barmode === 'stack' && barcount > 1) || + .each(function (d) { + if ((fullLayout.barmode === 'stack' && barcount > 1) || (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0 && !d[0].trace.marker.line.width)) { - d3.select(this).attr('shape-rendering', 'crispEdges'); - } - }); + d3.select(this).attr('shape-rendering', 'crispEdges') + } + }) // then style the individual bars - s.selectAll('g.points').each(function(d) { - var trace = d[0].trace, - marker = trace.marker, - markerLine = marker.line, - markerScale = Drawing.tryColorscale(marker, ''), - lineScale = Drawing.tryColorscale(marker, 'line'); + s.selectAll('g.points').each(function (d) { + var trace = d[0].trace, + marker = trace.marker, + markerLine = marker.line, + markerScale = Drawing.tryColorscale(marker, ''), + lineScale = Drawing.tryColorscale(marker, 'line') - d3.select(this).selectAll('path').each(function(d) { + d3.select(this).selectAll('path').each(function (d) { // allow all marker and marker line colors to be scaled // by given max and min to colorscales - var fillColor, - lineColor, - lineWidth = (d.mlw + 1 || markerLine.width + 1) - 1, - p = d3.select(this); + var fillColor, + lineColor, + lineWidth = (d.mlw + 1 || markerLine.width + 1) - 1, + p = d3.select(this) - if('mc' in d) fillColor = d.mcc = markerScale(d.mc); - else if(Array.isArray(marker.color)) fillColor = Color.defaultLine; - else fillColor = marker.color; + if ('mc' in d) fillColor = d.mcc = markerScale(d.mc) + else if (Array.isArray(marker.color)) fillColor = Color.defaultLine + else fillColor = marker.color - p.style('stroke-width', lineWidth + 'px') - .call(Color.fill, fillColor); - if(lineWidth) { - if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc); + p.style('stroke-width', lineWidth + 'px') + .call(Color.fill, fillColor) + if (lineWidth) { + if ('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc) // weird case: array wasn't long enough to apply to every point - else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine; - else lineColor = markerLine.color; + else if (Array.isArray(markerLine.color)) lineColor = Color.defaultLine + else lineColor = markerLine.color - p.call(Color.stroke, lineColor); - } - }); + p.call(Color.stroke, lineColor) + } + }) // TODO: text markers on bars, either extra text or just bar values // d3.select(this).selectAll('text') // .call(Drawing.textPointStyle,d.t||d[0].t); - }); + }) - s.call(ErrorBars.style); -}; + s.call(ErrorBars.style) +} diff --git a/src/traces/bar/style_defaults.js b/src/traces/bar/style_defaults.js index 3ccd7494554..f44b2dde0a0 100644 --- a/src/traces/bar/style_defaults.js +++ b/src/traces/bar/style_defaults.js @@ -6,30 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../components/color') +var hasColorscale = require('../../components/colorscale/has_colorscale') +var colorscaleDefaults = require('../../components/colorscale/defaults') -var Color = require('../../components/color'); -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); +module.exports = function handleStyleDefaults (traceIn, traceOut, coerce, defaultColor, layout) { + coerce('marker.color', defaultColor) - -module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) { - coerce('marker.color', defaultColor); - - if(hasColorscale(traceIn, 'marker')) { - colorscaleDefaults( + if (hasColorscale(traceIn, 'marker')) { + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'} - ); - } + ) + } - coerce('marker.line.color', Color.defaultLine); + coerce('marker.line.color', Color.defaultLine) - if(hasColorscale(traceIn, 'marker.line')) { - colorscaleDefaults( + if (hasColorscale(traceIn, 'marker.line')) { + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'} - ); - } + ) + } - coerce('marker.line.width'); -}; + coerce('marker.line.width') +} diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js index f1308538480..b35a8645ca6 100644 --- a/src/traces/box/attributes.js +++ b/src/traces/box/attributes.js @@ -6,173 +6,172 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var colorAttrs = require('../../components/color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +var scatterAttrs = require('../scatter/attributes') +var colorAttrs = require('../../components/color/attributes') +var extendFlat = require('../../lib/extend').extendFlat var scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; - + scatterMarkerLineAttrs = scatterMarkerAttrs.line module.exports = { - y: { - valType: 'data_array', - description: [ - 'Sets the y sample data or coordinates.', - 'See overview for more info.' - ].join(' ') - }, - x: { - valType: 'data_array', - description: [ - 'Sets the x sample data or coordinates.', - 'See overview for more info.' - ].join(' ') - }, - x0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the x coordinate of the box.', - 'See overview for more info.' - ].join(' ') - }, - y0: { - valType: 'any', - role: 'info', - description: [ - 'Sets the y coordinate of the box.', - 'See overview for more info.' - ].join(' ') + y: { + valType: 'data_array', + description: [ + 'Sets the y sample data or coordinates.', + 'See overview for more info.' + ].join(' ') + }, + x: { + valType: 'data_array', + description: [ + 'Sets the x sample data or coordinates.', + 'See overview for more info.' + ].join(' ') + }, + x0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the x coordinate of the box.', + 'See overview for more info.' + ].join(' ') + }, + y0: { + valType: 'any', + role: 'info', + description: [ + 'Sets the y coordinate of the box.', + 'See overview for more info.' + ].join(' ') + }, + xcalendar: scatterAttrs.xcalendar, + ycalendar: scatterAttrs.ycalendar, + whiskerwidth: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.5, + role: 'style', + description: [ + 'Sets the width of the whiskers relative to', + 'the box\' width.', + 'For example, with 1, the whiskers are as wide as the box(es).' + ].join(' ') + }, + boxpoints: { + valType: 'enumerated', + values: ['all', 'outliers', 'suspectedoutliers', false], + dflt: 'outliers', + role: 'style', + description: [ + 'If *outliers*, only the sample points lying outside the whiskers', + 'are shown', + 'If *suspectedoutliers*, the outlier points are shown and', + 'points either less than 4*Q1-3*Q3 or greater than 4*Q3-3*Q1', + 'are highlighted (see `outliercolor`)', + 'If *all*, all sample points are shown', + 'If *false*, only the box(es) are shown with no sample points' + ].join(' ') + }, + boxmean: { + valType: 'enumerated', + values: [true, 'sd', false], + dflt: false, + role: 'style', + description: [ + 'If *true*, the mean of the box(es)\' underlying distribution is', + 'drawn as a dashed line inside the box(es).', + 'If *sd* the standard deviation is also drawn.' + ].join(' ') + }, + jitter: { + valType: 'number', + min: 0, + max: 1, + role: 'style', + description: [ + 'Sets the amount of jitter in the sample points drawn.', + 'If *0*, the sample points align along the distribution axis.', + 'If *1*, the sample points are drawn in a random jitter of width', + 'equal to the width of the box(es).' + ].join(' ') + }, + pointpos: { + valType: 'number', + min: -2, + max: 2, + role: 'style', + description: [ + 'Sets the position of the sample points in relation to the box(es).', + 'If *0*, the sample points are places over the center of the box(es).', + 'Positive (negative) values correspond to positions to the', + 'right (left) for vertical boxes and above (below) for horizontal boxes' + ].join(' ') + }, + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + role: 'style', + description: [ + 'Sets the orientation of the box(es).', + 'If *v* (*h*), the distribution is visualized along', + 'the vertical (horizontal).' + ].join(' ') + }, + marker: { + outliercolor: { + valType: 'color', + dflt: 'rgba(0, 0, 0, 0)', + role: 'style', + description: 'Sets the color of the outlier sample points.' }, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, - whiskerwidth: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.5, - role: 'style', - description: [ - 'Sets the width of the whiskers relative to', - 'the box\' width.', - 'For example, with 1, the whiskers are as wide as the box(es).' - ].join(' ') - }, - boxpoints: { - valType: 'enumerated', - values: ['all', 'outliers', 'suspectedoutliers', false], - dflt: 'outliers', - role: 'style', - description: [ - 'If *outliers*, only the sample points lying outside the whiskers', - 'are shown', - 'If *suspectedoutliers*, the outlier points are shown and', - 'points either less than 4*Q1-3*Q3 or greater than 4*Q3-3*Q1', - 'are highlighted (see `outliercolor`)', - 'If *all*, all sample points are shown', - 'If *false*, only the box(es) are shown with no sample points' - ].join(' ') - }, - boxmean: { - valType: 'enumerated', - values: [true, 'sd', false], - dflt: false, + symbol: extendFlat({}, scatterMarkerAttrs.symbol, + {arrayOk: false}), + opacity: extendFlat({}, scatterMarkerAttrs.opacity, + {arrayOk: false, dflt: 1}), + size: extendFlat({}, scatterMarkerAttrs.size, + {arrayOk: false}), + color: extendFlat({}, scatterMarkerAttrs.color, + {arrayOk: false}), + line: { + color: extendFlat({}, scatterMarkerLineAttrs.color, + {arrayOk: false, dflt: colorAttrs.defaultLine}), + width: extendFlat({}, scatterMarkerLineAttrs.width, + {arrayOk: false, dflt: 0}), + outliercolor: { + valType: 'color', role: 'style', description: [ - 'If *true*, the mean of the box(es)\' underlying distribution is', - 'drawn as a dashed line inside the box(es).', - 'If *sd* the standard deviation is also drawn.' + 'Sets the border line color of the outlier sample points.', + 'Defaults to marker.color' ].join(' ') - }, - jitter: { + }, + outlierwidth: { valType: 'number', min: 0, - max: 1, - role: 'style', - description: [ - 'Sets the amount of jitter in the sample points drawn.', - 'If *0*, the sample points align along the distribution axis.', - 'If *1*, the sample points are drawn in a random jitter of width', - 'equal to the width of the box(es).' - ].join(' ') - }, - pointpos: { - valType: 'number', - min: -2, - max: 2, - role: 'style', - description: [ - 'Sets the position of the sample points in relation to the box(es).', - 'If *0*, the sample points are places over the center of the box(es).', - 'Positive (negative) values correspond to positions to the', - 'right (left) for vertical boxes and above (below) for horizontal boxes' - ].join(' ') - }, - orientation: { - valType: 'enumerated', - values: ['v', 'h'], + dflt: 1, role: 'style', description: [ - 'Sets the orientation of the box(es).', - 'If *v* (*h*), the distribution is visualized along', - 'the vertical (horizontal).' + 'Sets the border line width (in px) of the outlier sample points.' ].join(' ') + } + } + }, + line: { + color: { + valType: 'color', + role: 'style', + description: 'Sets the color of line bounding the box(es).' }, - marker: { - outliercolor: { - valType: 'color', - dflt: 'rgba(0, 0, 0, 0)', - role: 'style', - description: 'Sets the color of the outlier sample points.' - }, - symbol: extendFlat({}, scatterMarkerAttrs.symbol, - {arrayOk: false}), - opacity: extendFlat({}, scatterMarkerAttrs.opacity, - {arrayOk: false, dflt: 1}), - size: extendFlat({}, scatterMarkerAttrs.size, - {arrayOk: false}), - color: extendFlat({}, scatterMarkerAttrs.color, - {arrayOk: false}), - line: { - color: extendFlat({}, scatterMarkerLineAttrs.color, - {arrayOk: false, dflt: colorAttrs.defaultLine}), - width: extendFlat({}, scatterMarkerLineAttrs.width, - {arrayOk: false, dflt: 0}), - outliercolor: { - valType: 'color', - role: 'style', - description: [ - 'Sets the border line color of the outlier sample points.', - 'Defaults to marker.color' - ].join(' ') - }, - outlierwidth: { - valType: 'number', - min: 0, - dflt: 1, - role: 'style', - description: [ - 'Sets the border line width (in px) of the outlier sample points.' - ].join(' ') - } - } - }, - line: { - color: { - valType: 'color', - role: 'style', - description: 'Sets the color of line bounding the box(es).' - }, - width: { - valType: 'number', - role: 'style', - min: 0, - dflt: 2, - description: 'Sets the width (in px) of line bounding the box(es).' - } - }, - fillcolor: scatterAttrs.fillcolor -}; + width: { + valType: 'number', + role: 'style', + min: 0, + dflt: 2, + description: 'Sets the width (in px) of line bounding the box(es).' + } + }, + fillcolor: scatterAttrs.fillcolor +} diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js index d6a7ca28c14..b6908f5cb2f 100644 --- a/src/traces/box/calc.js +++ b/src/traces/box/calc.js @@ -6,142 +6,140 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var isNumeric = require('fast-isnumeric'); - -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var isNumeric = require('fast-isnumeric') +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') // outlier definition based on http://www.physics.csbsju.edu/stats/box2.html -module.exports = function calc(gd, trace) { - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'), - orientation = trace.orientation, - cd = [], - valAxis, valLetter, val, valBinned, - posAxis, posLetter, pos, posDistinct, dPos; +module.exports = function calc (gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + ya = Axes.getFromId(gd, trace.yaxis || 'y'), + orientation = trace.orientation, + cd = [], + valAxis, valLetter, val, valBinned, + posAxis, posLetter, pos, posDistinct, dPos // Set value (val) and position (pos) keys via orientation - if(orientation === 'h') { - valAxis = xa; - valLetter = 'x'; - posAxis = ya; - posLetter = 'y'; - } else { - valAxis = ya; - valLetter = 'y'; - posAxis = xa; - posLetter = 'x'; - } - - val = valAxis.makeCalcdata(trace, valLetter); // get val + if (orientation === 'h') { + valAxis = xa + valLetter = 'x' + posAxis = ya + posLetter = 'y' + } else { + valAxis = ya + valLetter = 'y' + posAxis = xa + posLetter = 'x' + } + + val = valAxis.makeCalcdata(trace, valLetter) // get val // size autorange based on all source points // position happens afterward when we know all the pos - Axes.expand(valAxis, val, {padded: true}); + Axes.expand(valAxis, val, {padded: true}) // In vertical (horizontal) box plots: // if no x (y) data, use x0 (y0), or name // so if you want one box // per trace, set x0 (y0) to the x (y) value or category for this trace // (or set x (y) to a constant array matching y (x)) - function getPos(gd, trace, posLetter, posAxis, val) { - var pos0; - if(posLetter in trace) pos = posAxis.makeCalcdata(trace, posLetter); - else { - if(posLetter + '0' in trace) pos0 = trace[posLetter + '0']; - else if('name' in trace && ( + function getPos (gd, trace, posLetter, posAxis, val) { + var pos0 + if (posLetter in trace) pos = posAxis.makeCalcdata(trace, posLetter) + else { + if (posLetter + '0' in trace) pos0 = trace[posLetter + '0'] + else if ('name' in trace && ( posAxis.type === 'category' || (isNumeric(trace.name) && ['linear', 'log'].indexOf(posAxis.type) !== -1) || (Lib.isDateTime(trace.name) && posAxis.type === 'date') )) { - pos0 = trace.name; - } - else pos0 = gd.numboxes; - pos0 = posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']); - pos = val.map(function() { return pos0; }); - } - return pos; + pos0 = trace.name + } else pos0 = gd.numboxes + pos0 = posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']) + pos = val.map(function () { return pos0 }) } + return pos + } - pos = getPos(gd, trace, posLetter, posAxis, val); + pos = getPos(gd, trace, posLetter, posAxis, val) // get distinct positions and min difference - var dv = Lib.distinctVals(pos); - posDistinct = dv.vals; - dPos = dv.minDiff / 2; + var dv = Lib.distinctVals(pos) + posDistinct = dv.vals + dPos = dv.minDiff / 2 - function binVal(cd, val, pos, posDistinct, dPos) { - var posDistinctLength = posDistinct.length, - valLength = val.length, - valBinned = [], - bins = [], - i, p, n, v; + function binVal (cd, val, pos, posDistinct, dPos) { + var posDistinctLength = posDistinct.length, + valLength = val.length, + valBinned = [], + bins = [], + i, p, n, v // store distinct pos in cd, find bins, init. valBinned - for(i = 0; i < posDistinctLength; ++i) { - p = posDistinct[i]; - cd[i] = {pos: p}; - bins[i] = p - dPos; - valBinned[i] = []; - } - bins.push(posDistinct[posDistinctLength - 1] + dPos); + for (i = 0; i < posDistinctLength; ++i) { + p = posDistinct[i] + cd[i] = {pos: p} + bins[i] = p - dPos + valBinned[i] = [] + } + bins.push(posDistinct[posDistinctLength - 1] + dPos) // bin the values - for(i = 0; i < valLength; ++i) { - v = val[i]; - if(!isNumeric(v)) continue; - n = Lib.findBin(pos[i], bins); - if(n >= 0 && n < valLength) valBinned[n].push(v); - } - - return valBinned; + for (i = 0; i < valLength; ++i) { + v = val[i] + if (!isNumeric(v)) continue + n = Lib.findBin(pos[i], bins) + if (n >= 0 && n < valLength) valBinned[n].push(v) } - valBinned = binVal(cd, val, pos, posDistinct, dPos); + return valBinned + } + + valBinned = binVal(cd, val, pos, posDistinct, dPos) // sort the bins and calculate the stats - function calculateStats(cd, valBinned) { - var v, l, cdi, i; - - for(i = 0; i < valBinned.length; ++i) { - v = valBinned[i].sort(Lib.sorterAsc); - l = v.length; - cdi = cd[i]; - - cdi.val = v; // put all values into calcdata - cdi.min = v[0]; - cdi.max = v[l - 1]; - cdi.mean = Lib.mean(v, l); - cdi.sd = Lib.stdev(v, l, cdi.mean); - cdi.q1 = Lib.interp(v, 0.25); // first quartile - cdi.med = Lib.interp(v, 0.5); // median - cdi.q3 = Lib.interp(v, 0.75); // third quartile + function calculateStats (cd, valBinned) { + var v, l, cdi, i + + for (i = 0; i < valBinned.length; ++i) { + v = valBinned[i].sort(Lib.sorterAsc) + l = v.length + cdi = cd[i] + + cdi.val = v // put all values into calcdata + cdi.min = v[0] + cdi.max = v[l - 1] + cdi.mean = Lib.mean(v, l) + cdi.sd = Lib.stdev(v, l, cdi.mean) + cdi.q1 = Lib.interp(v, 0.25) // first quartile + cdi.med = Lib.interp(v, 0.5) // median + cdi.q3 = Lib.interp(v, 0.75) // third quartile // lower and upper fences - last point inside // 1.5 interquartile ranges from quartiles - cdi.lf = Math.min(cdi.q1, v[ - Math.min(Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, v, true) + 1, l - 1)]); - cdi.uf = Math.max(cdi.q3, v[ - Math.max(Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, v), 0)]); + cdi.lf = Math.min(cdi.q1, v[ + Math.min(Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, v, true) + 1, l - 1)]) + cdi.uf = Math.max(cdi.q3, v[ + Math.max(Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, v), 0)]) // lower and upper outliers - 3 IQR out (don't clip to max/min, // this is only for discriminating suspected & far outliers) - cdi.lo = 4 * cdi.q1 - 3 * cdi.q3; - cdi.uo = 4 * cdi.q3 - 3 * cdi.q1; - } + cdi.lo = 4 * cdi.q1 - 3 * cdi.q3 + cdi.uo = 4 * cdi.q3 - 3 * cdi.q1 } + } - calculateStats(cd, valBinned); + calculateStats(cd, valBinned) // remove empty bins - cd = cd.filter(function(cdi) { return cdi.val && cdi.val.length; }); - if(!cd.length) return [{t: {emptybox: true}}]; + cd = cd.filter(function (cdi) { return cdi.val && cdi.val.length }) + if (!cd.length) return [{t: {emptybox: true}}] // add numboxes and dPos to cd - cd[0].t = {boxnum: gd.numboxes, dPos: dPos}; - gd.numboxes++; - return cd; -}; + cd[0].t = {boxnum: gd.numboxes, dPos: dPos} + gd.numboxes++ + return cd +} diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index e913a66d912..e41a0de60a6 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -6,66 +6,66 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var Registry = require('../../registry'); -var Color = require('../../components/color'); +var Lib = require('../../lib') +var Registry = require('../../registry') +var Color = require('../../components/color') -var attributes = require('./attributes'); +var attributes = require('./attributes') -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - var y = coerce('y'), - x = coerce('x'), - defaultOrientation; + var y = coerce('y'), + x = coerce('x'), + defaultOrientation - if(y && y.length) { - defaultOrientation = 'v'; - if(!x) coerce('x0'); - } else if(x && x.length) { - defaultOrientation = 'h'; - coerce('y0'); - } else { - traceOut.visible = false; - return; - } + if (y && y.length) { + defaultOrientation = 'v' + if (!x) coerce('x0') + } else if (x && x.length) { + defaultOrientation = 'h' + coerce('y0') + } else { + traceOut.visible = false + return + } - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout) - coerce('orientation', defaultOrientation); + coerce('orientation', defaultOrientation) - coerce('line.color', (traceIn.marker || {}).color || defaultColor); - coerce('line.width', 2); - coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5)); + coerce('line.color', (traceIn.marker || {}).color || defaultColor) + coerce('line.width', 2) + coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5)) - coerce('whiskerwidth'); - coerce('boxmean'); + coerce('whiskerwidth') + coerce('boxmean') - var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor'), - lineoutliercolor = coerce('marker.line.outliercolor'), - boxpoints = outlierColorDflt || + var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor'), + lineoutliercolor = coerce('marker.line.outliercolor'), + boxpoints = outlierColorDflt || lineoutliercolor ? coerce('boxpoints', 'suspectedoutliers') : - coerce('boxpoints'); + coerce('boxpoints') - if(boxpoints) { - coerce('jitter', boxpoints === 'all' ? 0.3 : 0); - coerce('pointpos', boxpoints === 'all' ? -1.5 : 0); + if (boxpoints) { + coerce('jitter', boxpoints === 'all' ? 0.3 : 0) + coerce('pointpos', boxpoints === 'all' ? -1.5 : 0) - coerce('marker.symbol'); - coerce('marker.opacity'); - coerce('marker.size'); - coerce('marker.color', traceOut.line.color); - coerce('marker.line.color'); - coerce('marker.line.width'); + coerce('marker.symbol') + coerce('marker.opacity') + coerce('marker.size') + coerce('marker.color', traceOut.line.color) + coerce('marker.line.color') + coerce('marker.line.width') - if(boxpoints === 'suspectedoutliers') { - coerce('marker.line.outliercolor', traceOut.marker.color); - coerce('marker.line.outlierwidth'); - } + if (boxpoints === 'suspectedoutliers') { + coerce('marker.line.outliercolor', traceOut.marker.color) + coerce('marker.line.outlierwidth') } -}; + } +} diff --git a/src/traces/box/hover.js b/src/traces/box/hover.js index 76e65c5104f..12486cb3c39 100644 --- a/src/traces/box/hover.js +++ b/src/traces/box/hover.js @@ -6,102 +6,102 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Axes = require('../../plots/cartesian/axes'); -var Fx = require('../../plots/cartesian/graph_interact'); -var Lib = require('../../lib'); -var Color = require('../../components/color'); +var Axes = require('../../plots/cartesian/axes') +var Fx = require('../../plots/cartesian/graph_interact') +var Lib = require('../../lib') +var Color = require('../../components/color') -module.exports = function hoverPoints(pointData, xval, yval, hovermode) { +module.exports = function hoverPoints (pointData, xval, yval, hovermode) { // closest mode: handicap box plots a little relative to others - var cd = pointData.cd, - trace = cd[0].trace, - t = cd[0].t, - xa = pointData.xa, - ya = pointData.ya, - closeData = [], - dx, dy, distfn, boxDelta, - posLetter, posAxis, - val, valLetter, valAxis; + var cd = pointData.cd, + trace = cd[0].trace, + t = cd[0].t, + xa = pointData.xa, + ya = pointData.ya, + closeData = [], + dx, dy, distfn, boxDelta, + posLetter, posAxis, + val, valLetter, valAxis // adjust inbox w.r.t. to calculate box size - boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos; - - if(trace.orientation === 'h') { - dx = function(di) { - return Fx.inbox(di.min - xval, di.max - xval); - }; - dy = function(di) { - var pos = di.pos + t.bPos - yval; - return Fx.inbox(pos - boxDelta, pos + boxDelta); - }; - posLetter = 'y'; - posAxis = ya; - valLetter = 'x'; - valAxis = xa; - } else { - dx = function(di) { - var pos = di.pos + t.bPos - xval; - return Fx.inbox(pos - boxDelta, pos + boxDelta); - }; - dy = function(di) { - return Fx.inbox(di.min - yval, di.max - yval); - }; - posLetter = 'x'; - posAxis = xa; - valLetter = 'y'; - valAxis = ya; + boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos + + if (trace.orientation === 'h') { + dx = function (di) { + return Fx.inbox(di.min - xval, di.max - xval) + } + dy = function (di) { + var pos = di.pos + t.bPos - yval + return Fx.inbox(pos - boxDelta, pos + boxDelta) + } + posLetter = 'y' + posAxis = ya + valLetter = 'x' + valAxis = xa + } else { + dx = function (di) { + var pos = di.pos + t.bPos - xval + return Fx.inbox(pos - boxDelta, pos + boxDelta) + } + dy = function (di) { + return Fx.inbox(di.min - yval, di.max - yval) } + posLetter = 'x' + posAxis = xa + valLetter = 'y' + valAxis = ya + } - distfn = Fx.getDistanceFunction(hovermode, dx, dy); - Fx.getClosest(cd, distfn, pointData); + distfn = Fx.getDistanceFunction(hovermode, dx, dy) + Fx.getClosest(cd, distfn, pointData) // skip the rest (for this trace) if we didn't find a close point - if(pointData.index === false) return; + if (pointData.index === false) return // create the item(s) in closedata for this point // the closest data point - var di = cd[pointData.index], - lc = trace.line.color, - mc = (trace.marker || {}).color; - if(Color.opacity(lc) && trace.line.width) pointData.color = lc; - else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc; - else pointData.color = trace.fillcolor; + var di = cd[pointData.index], + lc = trace.line.color, + mc = (trace.marker || {}).color + if (Color.opacity(lc) && trace.line.width) pointData.color = lc + else if (Color.opacity(mc) && trace.boxpoints) pointData.color = mc + else pointData.color = trace.fillcolor - pointData[posLetter + '0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true); - pointData[posLetter + '1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true); + pointData[posLetter + '0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true) + pointData[posLetter + '1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true) - Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text; - pointData[posLetter + 'LabelVal'] = di.pos; + Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text + pointData[posLetter + 'LabelVal'] = di.pos // box plots: each "point" gets many labels - var usedVals = {}, - attrs = ['med', 'min', 'q1', 'q3', 'max'], - attr, - pointData2; - if(trace.boxmean) attrs.push('mean'); - if(trace.boxpoints) [].push.apply(attrs, ['lf', 'uf']); + var usedVals = {}, + attrs = ['med', 'min', 'q1', 'q3', 'max'], + attr, + pointData2 + if (trace.boxmean) attrs.push('mean') + if (trace.boxpoints) [].push.apply(attrs, ['lf', 'uf']) - for(var i = 0; i < attrs.length; i++) { - attr = attrs[i]; + for (var i = 0; i < attrs.length; i++) { + attr = attrs[i] - if(!(attr in di) || (di[attr] in usedVals)) continue; - usedVals[di[attr]] = true; + if (!(attr in di) || (di[attr] in usedVals)) continue + usedVals[di[attr]] = true // copy out to a new object for each value to label - val = valAxis.c2p(di[attr], true); - pointData2 = Lib.extendFlat({}, pointData); - pointData2[valLetter + '0'] = pointData2[valLetter + '1'] = val; - pointData2[valLetter + 'LabelVal'] = di[attr]; - pointData2.attr = attr; - - if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { - pointData2[valLetter + 'err'] = di.sd; - } - pointData.name = ''; // only keep name on the first item (median) - closeData.push(pointData2); + val = valAxis.c2p(di[attr], true) + pointData2 = Lib.extendFlat({}, pointData) + pointData2[valLetter + '0'] = pointData2[valLetter + '1'] = val + pointData2[valLetter + 'LabelVal'] = di[attr] + pointData2.attr = attr + + if (attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { + pointData2[valLetter + 'err'] = di.sd } - return closeData; -}; + pointData.name = '' // only keep name on the first item (median) + closeData.push(pointData2) + } + return closeData +} diff --git a/src/traces/box/index.js b/src/traces/box/index.js index 82ed9d23097..a5390036fce 100644 --- a/src/traces/box/index.js +++ b/src/traces/box/index.js @@ -6,39 +6,39 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Box = {}; +var Box = {} -Box.attributes = require('./attributes'); -Box.layoutAttributes = require('./layout_attributes'); -Box.supplyDefaults = require('./defaults'); -Box.supplyLayoutDefaults = require('./layout_defaults'); -Box.calc = require('./calc'); -Box.setPositions = require('./set_positions'); -Box.plot = require('./plot'); -Box.style = require('./style'); -Box.hoverPoints = require('./hover'); +Box.attributes = require('./attributes') +Box.layoutAttributes = require('./layout_attributes') +Box.supplyDefaults = require('./defaults') +Box.supplyLayoutDefaults = require('./layout_defaults') +Box.calc = require('./calc') +Box.setPositions = require('./set_positions') +Box.plot = require('./plot') +Box.style = require('./style') +Box.hoverPoints = require('./hover') -Box.moduleType = 'trace'; -Box.name = 'box'; -Box.basePlotModule = require('../../plots/cartesian'); -Box.categories = ['cartesian', 'symbols', 'oriented', 'box', 'showLegend']; +Box.moduleType = 'trace' +Box.name = 'box' +Box.basePlotModule = require('../../plots/cartesian') +Box.categories = ['cartesian', 'symbols', 'oriented', 'box', 'showLegend'] Box.meta = { - description: [ - 'In vertical (horizontal) box plots,', - 'statistics are computed using `y` (`x`) values.', - 'By supplying an `x` (`y`) array, one box per distinct x (y) value', - 'is drawn', - 'If no `x` (`y`) {array} is provided, a single box is drawn.', - 'That box position is then positioned with', - 'with `name` or with `x0` (`y0`) if provided.', - 'Each box spans from quartile 1 (Q1) to quartile 3 (Q3).', - 'The second quartile (Q2) is marked by a line inside the box.', - 'By default, the whiskers correspond to the box\' edges', - '+/- 1.5 times the interquartile range (IQR = Q3-Q1),', - 'see *boxpoints* for other options.' - ].join(' ') -}; + description: [ + 'In vertical (horizontal) box plots,', + 'statistics are computed using `y` (`x`) values.', + 'By supplying an `x` (`y`) array, one box per distinct x (y) value', + 'is drawn', + 'If no `x` (`y`) {array} is provided, a single box is drawn.', + 'That box position is then positioned with', + 'with `name` or with `x0` (`y0`) if provided.', + 'Each box spans from quartile 1 (Q1) to quartile 3 (Q3).', + 'The second quartile (Q2) is marked by a line inside the box.', + 'By default, the whiskers correspond to the box\' edges', + '+/- 1.5 times the interquartile range (IQR = Q3-Q1),', + 'see *boxpoints* for other options.' + ].join(' ') +} -module.exports = Box; +module.exports = Box diff --git a/src/traces/box/layout_attributes.js b/src/traces/box/layout_attributes.js index 7e2d9f0fc75..89578134cf7 100644 --- a/src/traces/box/layout_attributes.js +++ b/src/traces/box/layout_attributes.js @@ -6,44 +6,43 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - +'use strict' module.exports = { - boxmode: { - valType: 'enumerated', - values: ['group', 'overlay'], - dflt: 'overlay', - role: 'info', - description: [ - 'Determines how boxes at the same location coordinate', - 'are displayed on the graph.', - 'If *group*, the boxes are plotted next to one another', - 'centered around the shared location.', - 'If *overlay*, the boxes are plotted over one another,', - 'you might need to set *opacity* to see them multiple boxes.' - ].join(' ') - }, - boxgap: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.3, - role: 'style', - description: [ - 'Sets the gap (in plot fraction) between boxes of', - 'adjacent location coordinates.' - ].join(' ') - }, - boxgroupgap: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.3, - role: 'style', - description: [ - 'Sets the gap (in plot fraction) between boxes of', - 'the same location coordinate.' - ].join(' ') - } -}; + boxmode: { + valType: 'enumerated', + values: ['group', 'overlay'], + dflt: 'overlay', + role: 'info', + description: [ + 'Determines how boxes at the same location coordinate', + 'are displayed on the graph.', + 'If *group*, the boxes are plotted next to one another', + 'centered around the shared location.', + 'If *overlay*, the boxes are plotted over one another,', + 'you might need to set *opacity* to see them multiple boxes.' + ].join(' ') + }, + boxgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.3, + role: 'style', + description: [ + 'Sets the gap (in plot fraction) between boxes of', + 'adjacent location coordinates.' + ].join(' ') + }, + boxgroupgap: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.3, + role: 'style', + description: [ + 'Sets the gap (in plot fraction) between boxes of', + 'the same location coordinate.' + ].join(' ') + } +} diff --git a/src/traces/box/layout_defaults.js b/src/traces/box/layout_defaults.js index 3213f703af8..822706daa46 100644 --- a/src/traces/box/layout_defaults.js +++ b/src/traces/box/layout_defaults.js @@ -6,27 +6,27 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var layoutAttributes = require('./layout_attributes'); +var Registry = require('../../registry') +var Lib = require('../../lib') +var layoutAttributes = require('./layout_attributes') -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { - function coerce(attr, dflt) { - return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); - } +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut, fullData) { + function coerce (attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt) + } - var hasBoxes; - for(var i = 0; i < fullData.length; i++) { - if(Registry.traceIs(fullData[i], 'box')) { - hasBoxes = true; - break; - } + var hasBoxes + for (var i = 0; i < fullData.length; i++) { + if (Registry.traceIs(fullData[i], 'box')) { + hasBoxes = true + break } - if(!hasBoxes) return; + } + if (!hasBoxes) return - coerce('boxmode'); - coerce('boxgap'); - coerce('boxgroupgap'); -}; + coerce('boxmode') + coerce('boxgap') + coerce('boxgroupgap') +} diff --git a/src/traces/box/plot.js b/src/traces/box/plot.js index f7e5b58ae7c..a381f83c5e5 100644 --- a/src/traces/box/plot.js +++ b/src/traces/box/plot.js @@ -6,233 +6,229 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); - -var Lib = require('../../lib'); -var Drawing = require('../../components/drawing'); +var d3 = require('d3') +var Lib = require('../../lib') +var Drawing = require('../../components/drawing') // repeatable pseudorandom generator -var randSeed = 2000000000; +var randSeed = 2000000000 -function seed() { - randSeed = 2000000000; +function seed () { + randSeed = 2000000000 } -function rand() { - var lastVal = randSeed; - randSeed = (69069 * randSeed + 1) % 4294967296; +function rand () { + var lastVal = randSeed + randSeed = (69069 * randSeed + 1) % 4294967296 // don't let consecutive vals be too close together // gets away from really trying to be random, in favor of better local uniformity - if(Math.abs(randSeed - lastVal) < 429496729) return rand(); - return randSeed / 4294967296; + if (Math.abs(randSeed - lastVal) < 429496729) return rand() + return randSeed / 4294967296 } // constants for dynamic jitter (ie less jitter for sparser points) var JITTERCOUNT = 5, // points either side of this to include - JITTERSPREAD = 0.01; // fraction of IQR to count as "dense" - + JITTERSPREAD = 0.01 // fraction of IQR to count as "dense" -module.exports = function plot(gd, plotinfo, cdbox) { - var fullLayout = gd._fullLayout, - xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - posAxis, valAxis; +module.exports = function plot (gd, plotinfo, cdbox) { + var fullLayout = gd._fullLayout, + xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + posAxis, valAxis - var boxtraces = plotinfo.plot.select('.boxlayer') + var boxtraces = plotinfo.plot.select('.boxlayer') .selectAll('g.trace.boxes') .data(cdbox) .enter().append('g') - .attr('class', 'trace boxes'); + .attr('class', 'trace boxes') - boxtraces.each(function(d) { - var t = d[0].t, - trace = d[0].trace, - group = (fullLayout.boxmode === 'group' && gd.numboxes > 1), + boxtraces.each(function (d) { + var t = d[0].t, + trace = d[0].trace, + group = (fullLayout.boxmode === 'group' && gd.numboxes > 1), // box half width - bdPos = t.dPos * (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) / (group ? gd.numboxes : 1), + bdPos = t.dPos * (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) / (group ? gd.numboxes : 1), // box center offset - bPos = group ? 2 * t.dPos * (-0.5 + (t.boxnum + 0.5) / gd.numboxes) * (1 - fullLayout.boxgap) : 0, + bPos = group ? 2 * t.dPos * (-0.5 + (t.boxnum + 0.5) / gd.numboxes) * (1 - fullLayout.boxgap) : 0, // whisker width - wdPos = bdPos * trace.whiskerwidth; - if(trace.visible !== true || t.emptybox) { - d3.select(this).remove(); - return; - } + wdPos = bdPos * trace.whiskerwidth + if (trace.visible !== true || t.emptybox) { + d3.select(this).remove() + return + } // set axis via orientation - if(trace.orientation === 'h') { - posAxis = ya; - valAxis = xa; - } else { - posAxis = xa; - valAxis = ya; - } + if (trace.orientation === 'h') { + posAxis = ya + valAxis = xa + } else { + posAxis = xa + valAxis = ya + } // save the box size and box position for use by hover - t.bPos = bPos; - t.bdPos = bdPos; + t.bPos = bPos + t.bdPos = bdPos // repeatable pseudorandom number generator - seed(); + seed() // boxes and whiskers - d3.select(this).selectAll('path.box') + d3.select(this).selectAll('path.box') .data(Lib.identity) .enter().append('path') .attr('class', 'box') - .each(function(d) { - var posc = posAxis.c2p(d.pos + bPos, true), - pos0 = posAxis.c2p(d.pos + bPos - bdPos, true), - pos1 = posAxis.c2p(d.pos + bPos + bdPos, true), - posw0 = posAxis.c2p(d.pos + bPos - wdPos, true), - posw1 = posAxis.c2p(d.pos + bPos + wdPos, true), - q1 = valAxis.c2p(d.q1, true), - q3 = valAxis.c2p(d.q3, true), + .each(function (d) { + var posc = posAxis.c2p(d.pos + bPos, true), + pos0 = posAxis.c2p(d.pos + bPos - bdPos, true), + pos1 = posAxis.c2p(d.pos + bPos + bdPos, true), + posw0 = posAxis.c2p(d.pos + bPos - wdPos, true), + posw1 = posAxis.c2p(d.pos + bPos + wdPos, true), + q1 = valAxis.c2p(d.q1, true), + q3 = valAxis.c2p(d.q3, true), // make sure median isn't identical to either of the // quartiles, so we can see it - m = Lib.constrain(valAxis.c2p(d.med, true), + m = Lib.constrain(valAxis.c2p(d.med, true), Math.min(q1, q3) + 1, Math.max(q1, q3) - 1), - lf = valAxis.c2p(trace.boxpoints === false ? d.min : d.lf, true), - uf = valAxis.c2p(trace.boxpoints === false ? d.max : d.uf, true); - if(trace.orientation === 'h') { - d3.select(this).attr('d', + lf = valAxis.c2p(trace.boxpoints === false ? d.min : d.lf, true), + uf = valAxis.c2p(trace.boxpoints === false ? d.max : d.uf, true) + if (trace.orientation === 'h') { + d3.select(this).attr('d', 'M' + m + ',' + pos0 + 'V' + pos1 + // median line 'M' + q1 + ',' + pos0 + 'V' + pos1 + 'H' + q3 + 'V' + pos0 + 'Z' + // box 'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers ((trace.whiskerwidth === 0) ? '' : // whisker caps - 'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1)); - } else { - d3.select(this).attr('d', + 'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1)) + } else { + d3.select(this).attr('d', 'M' + pos0 + ',' + m + 'H' + pos1 + // median line 'M' + pos0 + ',' + q1 + 'H' + pos1 + 'V' + q3 + 'H' + pos0 + 'Z' + // box 'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers ((trace.whiskerwidth === 0) ? '' : // whisker caps - 'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1)); - } - }); + 'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1)) + } + }) // draw points, if desired - if(trace.boxpoints) { - d3.select(this).selectAll('g.points') + if (trace.boxpoints) { + d3.select(this).selectAll('g.points') // since box plot points get an extra level of nesting, each // box needs the trace styling info - .data(function(d) { - d.forEach(function(v) { - v.t = t; - v.trace = trace; - }); - return d; + .data(function (d) { + d.forEach(function (v) { + v.t = t + v.trace = trace + }) + return d }) .enter().append('g') .attr('class', 'points') .selectAll('path') - .data(function(d) { - var pts = (trace.boxpoints === 'all') ? d.val : - d.val.filter(function(v) { return (v < d.lf || v > d.uf); }), + .data(function (d) { + var pts = (trace.boxpoints === 'all') ? d.val : + d.val.filter(function (v) { return (v < d.lf || v > d.uf) }), // normally use IQR, but if this is 0 or too small, use max-min - typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1), - minSpread = typicalSpread * 1e-9, - spreadLimit = typicalSpread * JITTERSPREAD, - jitterFactors = [], - maxJitterFactor = 0, - i, - i0, i1, - pmin, - pmax, - jitterFactor, - newJitter; + typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1), + minSpread = typicalSpread * 1e-9, + spreadLimit = typicalSpread * JITTERSPREAD, + jitterFactors = [], + maxJitterFactor = 0, + i, + i0, i1, + pmin, + pmax, + jitterFactor, + newJitter // dynamic jitter - if(trace.jitter) { - if(typicalSpread === 0) { + if (trace.jitter) { + if (typicalSpread === 0) { // edge case of no spread at all: fall back to max jitter - maxJitterFactor = 1; - jitterFactors = new Array(pts.length); - for(i = 0; i < pts.length; i++) { - jitterFactors[i] = 1; - } + maxJitterFactor = 1 + jitterFactors = new Array(pts.length) + for (i = 0; i < pts.length; i++) { + jitterFactors[i] = 1 + } + } else { + for (i = 0; i < pts.length; i++) { + i0 = Math.max(0, i - JITTERCOUNT) + pmin = pts[i0] + i1 = Math.min(pts.length - 1, i + JITTERCOUNT) + pmax = pts[i1] + + if (trace.boxpoints !== 'all') { + if (pts[i] < d.lf) pmax = Math.min(pmax, d.lf) + else pmin = Math.max(pmin, d.uf) } - else { - for(i = 0; i < pts.length; i++) { - i0 = Math.max(0, i - JITTERCOUNT); - pmin = pts[i0]; - i1 = Math.min(pts.length - 1, i + JITTERCOUNT); - pmax = pts[i1]; - - if(trace.boxpoints !== 'all') { - if(pts[i] < d.lf) pmax = Math.min(pmax, d.lf); - else pmin = Math.max(pmin, d.uf); - } - - jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0; - jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1); - - jitterFactors.push(jitterFactor); - maxJitterFactor = Math.max(jitterFactor, maxJitterFactor); - } - } - newJitter = trace.jitter * 2 / maxJitterFactor; - } - return pts.map(function(v, i) { - var posOffset = trace.pointpos, - p; - if(trace.jitter) { - posOffset += newJitter * jitterFactors[i] * (rand() - 0.5); - } + jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0 + jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1) - if(trace.orientation === 'h') { - p = { - y: d.pos + posOffset * bdPos + bPos, - x: v - }; - } else { - p = { - x: d.pos + posOffset * bdPos + bPos, - y: v - }; - } + jitterFactors.push(jitterFactor) + maxJitterFactor = Math.max(jitterFactor, maxJitterFactor) + } + } + newJitter = trace.jitter * 2 / maxJitterFactor + } + + return pts.map(function (v, i) { + var posOffset = trace.pointpos, + p + if (trace.jitter) { + posOffset += newJitter * jitterFactors[i] * (rand() - 0.5) + } + + if (trace.orientation === 'h') { + p = { + y: d.pos + posOffset * bdPos + bPos, + x: v + } + } else { + p = { + x: d.pos + posOffset * bdPos + bPos, + y: v + } + } // tag suspected outliers - if(trace.boxpoints === 'suspectedoutliers' && v < d.uo && v > d.lo) { - p.so = true; - } - return p; - }); + if (trace.boxpoints === 'suspectedoutliers' && v < d.uo && v > d.lo) { + p.so = true + } + return p + }) }) .enter().append('path') - .call(Drawing.translatePoints, xa, ya); - } + .call(Drawing.translatePoints, xa, ya) + } // draw mean (and stdev diamond) if desired - if(trace.boxmean) { - d3.select(this).selectAll('path.mean') + if (trace.boxmean) { + d3.select(this).selectAll('path.mean') .data(Lib.identity) .enter().append('path') .attr('class', 'mean') .style('fill', 'none') - .each(function(d) { - var posc = posAxis.c2p(d.pos + bPos, true), - pos0 = posAxis.c2p(d.pos + bPos - bdPos, true), - pos1 = posAxis.c2p(d.pos + bPos + bdPos, true), - m = valAxis.c2p(d.mean, true), - sl = valAxis.c2p(d.mean - d.sd, true), - sh = valAxis.c2p(d.mean + d.sd, true); - if(trace.orientation === 'h') { - d3.select(this).attr('d', + .each(function (d) { + var posc = posAxis.c2p(d.pos + bPos, true), + pos0 = posAxis.c2p(d.pos + bPos - bdPos, true), + pos1 = posAxis.c2p(d.pos + bPos + bdPos, true), + m = valAxis.c2p(d.mean, true), + sl = valAxis.c2p(d.mean - d.sd, true), + sh = valAxis.c2p(d.mean + d.sd, true) + if (trace.orientation === 'h') { + d3.select(this).attr('d', 'M' + m + ',' + pos0 + 'V' + pos1 + ((trace.boxmean !== 'sd') ? '' : - 'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z')); - } - else { - d3.select(this).attr('d', + 'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z')) + } else { + d3.select(this).attr('d', 'M' + pos0 + ',' + m + 'H' + pos1 + ((trace.boxmean !== 'sd') ? '' : - 'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z')); - } - }); - } - }); -}; + 'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z')) + } + }) + } + }) +} diff --git a/src/traces/box/set_positions.js b/src/traces/box/set_positions.js index 30580031d4b..2e93c6d12ed 100644 --- a/src/traces/box/set_positions.js +++ b/src/traces/box/set_positions.js @@ -6,87 +6,86 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../../registry'); -var Axes = require('../../plots/cartesian/axes'); -var Lib = require('../../lib'); +var Registry = require('../../registry') +var Axes = require('../../plots/cartesian/axes') +var Lib = require('../../lib') +module.exports = function setPositions (gd, plotinfo) { + var fullLayout = gd._fullLayout, + xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + orientations = ['v', 'h'] + var posAxis, i, j, k -module.exports = function setPositions(gd, plotinfo) { - var fullLayout = gd._fullLayout, - xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - orientations = ['v', 'h']; - var posAxis, i, j, k; - - for(i = 0; i < orientations.length; ++i) { - var orientation = orientations[i], - boxlist = [], - boxpointlist = [], - minPad = 0, - maxPad = 0, - cd, - t, - trace; + for (i = 0; i < orientations.length; ++i) { + var orientation = orientations[i], + boxlist = [], + boxpointlist = [], + minPad = 0, + maxPad = 0, + cd, + t, + trace // set axis via orientation - if(orientation === 'h') posAxis = ya; - else posAxis = xa; + if (orientation === 'h') posAxis = ya + else posAxis = xa // make list of boxes - for(j = 0; j < gd.calcdata.length; ++j) { - cd = gd.calcdata[j]; - t = cd[0].t; - trace = cd[0].trace; + for (j = 0; j < gd.calcdata.length; ++j) { + cd = gd.calcdata[j] + t = cd[0].t + trace = cd[0].trace - if(trace.visible === true && Registry.traceIs(trace, 'box') && + if (trace.visible === true && Registry.traceIs(trace, 'box') && !t.emptybox && trace.orientation === orientation && trace.xaxis === xa._id && trace.yaxis === ya._id) { - boxlist.push(j); - if(trace.boxpoints !== false) { - minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1); - maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1); - } - } + boxlist.push(j) + if (trace.boxpoints !== false) { + minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1) + maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1) } + } + } // make list of box points - for(j = 0; j < boxlist.length; j++) { - cd = gd.calcdata[boxlist[j]]; - for(k = 0; k < cd.length; k++) boxpointlist.push(cd[k].pos); - } - if(!boxpointlist.length) continue; + for (j = 0; j < boxlist.length; j++) { + cd = gd.calcdata[boxlist[j]] + for (k = 0; k < cd.length; k++) boxpointlist.push(cd[k].pos) + } + if (!boxpointlist.length) continue // box plots - update dPos based on multiple traces // and then use for posAxis autorange - var boxdv = Lib.distinctVals(boxpointlist), - dPos = boxdv.minDiff / 2; + var boxdv = Lib.distinctVals(boxpointlist), + dPos = boxdv.minDiff / 2 // if there's no duplication of x points, // disable 'group' mode by setting numboxes=1 - if(boxpointlist.length === boxdv.vals.length) gd.numboxes = 1; + if (boxpointlist.length === boxdv.vals.length) gd.numboxes = 1 // check for forced minimum dtick - Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true); + Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true) // set the width of all boxes - for(i = 0; i < boxlist.length; i++) { - var boxListIndex = boxlist[i]; - gd.calcdata[boxListIndex][0].t.dPos = dPos; - } + for (i = 0; i < boxlist.length; i++) { + var boxListIndex = boxlist[i] + gd.calcdata[boxListIndex][0].t.dPos = dPos + } // autoscale the x axis - including space for points if they're off the side // TODO: this will overdo it if the outermost boxes don't have // their points as far out as the other boxes - var padfactor = (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) * - dPos / gd.numboxes; - Axes.expand(posAxis, boxdv.vals, { - vpadminus: dPos + minPad * padfactor, - vpadplus: dPos + maxPad * padfactor - }); - } -}; + var padfactor = (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) * + dPos / gd.numboxes + Axes.expand(posAxis, boxdv.vals, { + vpadminus: dPos + minPad * padfactor, + vpadplus: dPos + maxPad * padfactor + }) + } +} diff --git a/src/traces/box/style.js b/src/traces/box/style.js index cb187ebedca..502fee60ba6 100644 --- a/src/traces/box/style.js +++ b/src/traces/box/style.js @@ -6,32 +6,31 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); +var d3 = require('d3') -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +module.exports = function style (gd) { + var s = d3.select(gd).selectAll('g.trace.boxes') -module.exports = function style(gd) { - var s = d3.select(gd).selectAll('g.trace.boxes'); - - s.style('opacity', function(d) { return d[0].trace.opacity; }) - .each(function(d) { - var trace = d[0].trace, - lineWidth = trace.line.width; - d3.select(this).selectAll('path.box') + s.style('opacity', function (d) { return d[0].trace.opacity }) + .each(function (d) { + var trace = d[0].trace, + lineWidth = trace.line.width + d3.select(this).selectAll('path.box') .style('stroke-width', lineWidth + 'px') .call(Color.stroke, trace.line.color) - .call(Color.fill, trace.fillcolor); - d3.select(this).selectAll('path.mean') + .call(Color.fill, trace.fillcolor) + d3.select(this).selectAll('path.mean') .style({ - 'stroke-width': lineWidth, - 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px' + 'stroke-width': lineWidth, + 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px' }) - .call(Color.stroke, trace.line.color); - d3.select(this).selectAll('g.points path') - .call(Drawing.pointStyle, trace); - }); -}; + .call(Color.stroke, trace.line.color) + d3.select(this).selectAll('g.points path') + .call(Drawing.pointStyle, trace) + }) +} diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index c6c4e18ac3e..4fd60757575 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -6,51 +6,50 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); -var OHLCattrs = require('../ohlc/attributes'); -var boxAttrs = require('../box/attributes'); +var Lib = require('../../lib') +var OHLCattrs = require('../ohlc/attributes') +var boxAttrs = require('../box/attributes') var directionAttrs = { - name: OHLCattrs.increasing.name, - showlegend: OHLCattrs.increasing.showlegend, + name: OHLCattrs.increasing.name, + showlegend: OHLCattrs.increasing.showlegend, - line: { - color: Lib.extendFlat({}, boxAttrs.line.color), - width: Lib.extendFlat({}, boxAttrs.line.width) - }, + line: { + color: Lib.extendFlat({}, boxAttrs.line.color), + width: Lib.extendFlat({}, boxAttrs.line.width) + }, - fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), -}; + fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor) +} module.exports = { - x: OHLCattrs.x, - open: OHLCattrs.open, - high: OHLCattrs.high, - low: OHLCattrs.low, - close: OHLCattrs.close, - - line: { - width: Lib.extendFlat({}, boxAttrs.line.width, { - description: [ - boxAttrs.line.width.description, - 'Note that this style setting can also be set per', - 'direction via `increasing.line.width` and', - '`decreasing.line.width`.' - ].join(' ') - }) - }, - - increasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: OHLCattrs.increasing.line.color.dflt } } - }), - - decreasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: OHLCattrs.decreasing.line.color.dflt } } - }), - - text: OHLCattrs.text, - whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }) -}; + x: OHLCattrs.x, + open: OHLCattrs.open, + high: OHLCattrs.high, + low: OHLCattrs.low, + close: OHLCattrs.close, + + line: { + width: Lib.extendFlat({}, boxAttrs.line.width, { + description: [ + boxAttrs.line.width.description, + 'Note that this style setting can also be set per', + 'direction via `increasing.line.width` and', + '`decreasing.line.width`.' + ].join(' ') + }) + }, + + increasing: Lib.extendDeep({}, directionAttrs, { + line: { color: { dflt: OHLCattrs.increasing.line.color.dflt } } + }), + + decreasing: Lib.extendDeep({}, directionAttrs, { + line: { color: { dflt: OHLCattrs.decreasing.line.color.dflt } } + }), + + text: OHLCattrs.text, + whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }) +} diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index 66213e94794..2cd763dfd65 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -6,41 +6,40 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var handleOHLC = require('../ohlc/ohlc_defaults') +var handleDirectionDefaults = require('../ohlc/direction_defaults') +var helpers = require('../ohlc/helpers') +var attributes = require('./attributes') -var Lib = require('../../lib'); -var handleOHLC = require('../ohlc/ohlc_defaults'); -var handleDirectionDefaults = require('../ohlc/direction_defaults'); -var helpers = require('../ohlc/helpers'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + helpers.pushDummyTransformOpts(traceIn, traceOut) -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - helpers.pushDummyTransformOpts(traceIn, traceOut); + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var len = handleOHLC(traceIn, traceOut, coerce, layout) + if (len === 0) { + traceOut.visible = false + return + } - var len = handleOHLC(traceIn, traceOut, coerce, layout); - if(len === 0) { - traceOut.visible = false; - return; - } + coerce('line.width') - coerce('line.width'); + handleDirection(traceIn, traceOut, coerce, 'increasing') + handleDirection(traceIn, traceOut, coerce, 'decreasing') - handleDirection(traceIn, traceOut, coerce, 'increasing'); - handleDirection(traceIn, traceOut, coerce, 'decreasing'); - - coerce('text'); - coerce('whiskerwidth'); -}; + coerce('text') + coerce('whiskerwidth') +} -function handleDirection(traceIn, traceOut, coerce, direction) { - handleDirectionDefaults(traceIn, traceOut, coerce, direction); +function handleDirection (traceIn, traceOut, coerce, direction) { + handleDirectionDefaults(traceIn, traceOut, coerce, direction) - coerce(direction + '.line.color'); - coerce(direction + '.line.width', traceOut.line.width); - coerce(direction + '.fillcolor'); + coerce(direction + '.line.color') + coerce(direction + '.line.width', traceOut.line.width) + coerce(direction + '.fillcolor') } diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js index 13764ecbabe..96bec5aa0b8 100644 --- a/src/traces/candlestick/index.js +++ b/src/traces/candlestick/index.js @@ -6,35 +6,34 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var register = require('../../plot_api/register'); +var register = require('../../plot_api/register') module.exports = { - moduleType: 'trace', - name: 'candlestick', - basePlotModule: require('../../plots/cartesian'), - categories: ['cartesian', 'showLegend', 'candlestick'], - meta: { - description: [ - 'The candlestick is a style of financial chart describing', - 'open, high, low and close for a given `x` coordinate (most likely time).', - - 'The boxes represent the spread between the `open` and `close` values and', - 'the lines represent the spread between the `low` and `high` values', - - 'Sample points where the close value is higher (lower) then the open', - 'value are called increasing (decreasing).', - - 'By default, increasing candles are drawn in green whereas', - 'decreasing are drawn in red.' - ].join(' ') - }, - - attributes: require('./attributes'), - supplyDefaults: require('./defaults'), -}; - -register(require('../box')); -register(require('./transform')); + moduleType: 'trace', + name: 'candlestick', + basePlotModule: require('../../plots/cartesian'), + categories: ['cartesian', 'showLegend', 'candlestick'], + meta: { + description: [ + 'The candlestick is a style of financial chart describing', + 'open, high, low and close for a given `x` coordinate (most likely time).', + + 'The boxes represent the spread between the `open` and `close` values and', + 'the lines represent the spread between the `low` and `high` values', + + 'Sample points where the close value is higher (lower) then the open', + 'value are called increasing (decreasing).', + + 'By default, increasing candles are drawn in green whereas', + 'decreasing are drawn in red.' + ].join(' ') + }, + + attributes: require('./attributes'), + supplyDefaults: require('./defaults') +} + +register(require('../box')) +register(require('./transform')) diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index ce0aaeb03ad..6a558155277 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -6,121 +6,120 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var helpers = require('../ohlc/helpers') -var Lib = require('../../lib'); -var helpers = require('../ohlc/helpers'); +exports.moduleType = 'transform' -exports.moduleType = 'transform'; +exports.name = 'candlestick' -exports.name = 'candlestick'; +exports.attributes = {} -exports.attributes = {}; +exports.supplyDefaults = function (transformIn, traceOut, layout, traceIn) { + helpers.clearEphemeralTransformOpts(traceIn) + helpers.copyOHLC(transformIn, traceOut) -exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) { - helpers.clearEphemeralTransformOpts(traceIn); - helpers.copyOHLC(transformIn, traceOut); - - return transformIn; -}; + return transformIn +} -exports.transform = function transform(dataIn, state) { - var dataOut = []; +exports.transform = function transform (dataIn, state) { + var dataOut = [] - for(var i = 0; i < dataIn.length; i++) { - var traceIn = dataIn[i]; + for (var i = 0; i < dataIn.length; i++) { + var traceIn = dataIn[i] - if(traceIn.type !== 'candlestick') { - dataOut.push(traceIn); - continue; - } + if (traceIn.type !== 'candlestick') { + dataOut.push(traceIn) + continue + } - dataOut.push( + dataOut.push( makeTrace(traceIn, state, 'increasing'), makeTrace(traceIn, state, 'decreasing') - ); - } + ) + } - helpers.addRangeSlider(dataOut, state.layout); + helpers.addRangeSlider(dataOut, state.layout) - return dataOut; -}; + return dataOut +} -function makeTrace(traceIn, state, direction) { - var traceOut = { - type: 'box', - boxpoints: false, +function makeTrace (traceIn, state, direction) { + var traceOut = { + type: 'box', + boxpoints: false, - visible: traceIn.visible, - hoverinfo: traceIn.hoverinfo, - opacity: traceIn.opacity, - xaxis: traceIn.xaxis, - yaxis: traceIn.yaxis, + visible: traceIn.visible, + hoverinfo: traceIn.hoverinfo, + opacity: traceIn.opacity, + xaxis: traceIn.xaxis, + yaxis: traceIn.yaxis, - transforms: helpers.makeTransform(traceIn, state, direction) - }; + transforms: helpers.makeTransform(traceIn, state, direction) + } // the rest of below may not have been coerced - var directionOpts = traceIn[direction]; + var directionOpts = traceIn[direction] - if(directionOpts) { - Lib.extendFlat(traceOut, { + if (directionOpts) { + Lib.extendFlat(traceOut, { // to make autotype catch date axes soon!! - x: traceIn.x || [0], - xcalendar: traceIn.xcalendar, + x: traceIn.x || [0], + xcalendar: traceIn.xcalendar, // concat low and high to get correct autorange - y: [].concat(traceIn.low).concat(traceIn.high), + y: [].concat(traceIn.low).concat(traceIn.high), - whiskerwidth: traceIn.whiskerwidth, - text: traceIn.text, + whiskerwidth: traceIn.whiskerwidth, + text: traceIn.text, - name: directionOpts.name, - showlegend: directionOpts.showlegend, - line: directionOpts.line, - fillcolor: directionOpts.fillcolor - }); - } + name: directionOpts.name, + showlegend: directionOpts.showlegend, + line: directionOpts.line, + fillcolor: directionOpts.fillcolor + }) + } - return traceOut; + return traceOut } -exports.calcTransform = function calcTransform(gd, trace, opts) { - var direction = opts.direction, - filterFn = helpers.getFilterFn(direction); +exports.calcTransform = function calcTransform (gd, trace, opts) { + var direction = opts.direction, + filterFn = helpers.getFilterFn(direction) - var open = trace.open, - high = trace.high, - low = trace.low, - close = trace.close; + var open = trace.open, + high = trace.high, + low = trace.low, + close = trace.close - var len = open.length, - x = [], - y = []; + var len = open.length, + x = [], + y = [] - var appendX = trace._fullInput.x ? - function(i) { - var v = trace.x[i]; - x.push(v, v, v, v, v, v); + var appendX = trace._fullInput.x ? + function (i) { + var v = trace.x[i] + x.push(v, v, v, v, v, v) } : - function(i) { - x.push(i, i, i, i, i, i); - }; - - var appendY = function(o, h, l, c) { - y.push(l, o, c, c, c, h); - }; - - for(var i = 0; i < len; i++) { - if(filterFn(open[i], close[i])) { - appendX(i); - appendY(open[i], high[i], low[i], close[i]); + function (i) { + x.push(i, i, i, i, i, i) } + + var appendY = function (o, h, l, c) { + y.push(l, o, c, c, c, h) + } + + for (var i = 0; i < len; i++) { + if (filterFn(open[i], close[i])) { + appendX(i) + appendY(open[i], high[i], low[i], close[i]) } + } - trace.x = x; - trace.y = y; -}; + trace.x = x + trace.y = y +} diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 85523db3ed5..baa8c32d87a 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -6,44 +6,44 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var ScatterGeoAttrs = require('../scattergeo/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); -var plotAttrs = require('../../plots/attributes'); +var ScatterGeoAttrs = require('../scattergeo/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') +var plotAttrs = require('../../plots/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat -var ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line; +var ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line module.exports = extendFlat({}, { - locations: { - valType: 'data_array', - description: [ - 'Sets the coordinates via location IDs or names.', - 'See `locationmode` for more info.' - ].join(' ') - }, - locationmode: ScatterGeoAttrs.locationmode, - z: { - valType: 'data_array', - description: 'Sets the color values.' - }, - text: { - valType: 'data_array', - description: 'Sets the text elements associated with each location.' - }, - marker: { - line: { - color: ScatterGeoMarkerLineAttrs.color, - width: extendFlat({}, ScatterGeoMarkerLineAttrs.width, {dflt: 1}) - } - }, - hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['location', 'z', 'text', 'name'] - }), + locations: { + valType: 'data_array', + description: [ + 'Sets the coordinates via location IDs or names.', + 'See `locationmode` for more info.' + ].join(' ') + }, + locationmode: ScatterGeoAttrs.locationmode, + z: { + valType: 'data_array', + description: 'Sets the color values.' + }, + text: { + valType: 'data_array', + description: 'Sets the text elements associated with each location.' + }, + marker: { + line: { + color: ScatterGeoMarkerLineAttrs.color, + width: extendFlat({}, ScatterGeoMarkerLineAttrs.width, {dflt: 1}) + } + }, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['location', 'z', 'text', 'name'] + }) }, colorscaleAttrs, { colorbar: colorbarAttrs } -); +) diff --git a/src/traces/choropleth/calc.js b/src/traces/choropleth/calc.js index 5a3eacb14a4..ec02f53469b 100644 --- a/src/traces/choropleth/calc.js +++ b/src/traces/choropleth/calc.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var colorscaleCalc = require('../../components/colorscale/calc') -var colorscaleCalc = require('../../components/colorscale/calc'); - - -module.exports = function calc(gd, trace) { - colorscaleCalc(trace, trace.z, '', 'z'); -}; +module.exports = function calc (gd, trace) { + colorscaleCalc(trace, trace.z, '', 'z') +} diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index d4dbfa057d5..97c8367e58a 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -6,48 +6,46 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var colorscaleDefaults = require('../../components/colorscale/defaults') +var attributes = require('./attributes') -var colorscaleDefaults = require('../../components/colorscale/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var locations = coerce('locations') -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var len + if (locations) len = locations.length - var locations = coerce('locations'); + if (!locations || !len) { + traceOut.visible = false + return + } - var len; - if(locations) len = locations.length; + var z = coerce('z') + if (!Array.isArray(z)) { + traceOut.visible = false + return + } - if(!locations || !len) { - traceOut.visible = false; - return; - } + if (z.length > len) traceOut.z = z.slice(0, len) - var z = coerce('z'); - if(!Array.isArray(z)) { - traceOut.visible = false; - return; - } + coerce('locationmode') - if(z.length > len) traceOut.z = z.slice(0, len); + coerce('text') - coerce('locationmode'); + coerce('marker.line.color') + coerce('marker.line.width') - coerce('text'); - - coerce('marker.line.color'); - coerce('marker.line.width'); - - colorscaleDefaults( + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} - ); + ) - coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined); -}; + coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined) +} diff --git a/src/traces/choropleth/index.js b/src/traces/choropleth/index.js index 15cfae98a54..e92533205d7 100644 --- a/src/traces/choropleth/index.js +++ b/src/traces/choropleth/index.js @@ -6,31 +6,30 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Choropleth = {} -var Choropleth = {}; - -Choropleth.attributes = require('./attributes'); -Choropleth.supplyDefaults = require('./defaults'); -Choropleth.colorbar = require('../heatmap/colorbar'); -Choropleth.calc = require('./calc'); -Choropleth.plot = require('./plot').plot; +Choropleth.attributes = require('./attributes') +Choropleth.supplyDefaults = require('./defaults') +Choropleth.colorbar = require('../heatmap/colorbar') +Choropleth.calc = require('./calc') +Choropleth.plot = require('./plot').plot // add dummy hover handler to skip Fx.hover w/o warnings -Choropleth.hoverPoints = function() {}; +Choropleth.hoverPoints = function () {} -Choropleth.moduleType = 'trace'; -Choropleth.name = 'choropleth'; -Choropleth.basePlotModule = require('../../plots/geo'); -Choropleth.categories = ['geo', 'noOpacity']; +Choropleth.moduleType = 'trace' +Choropleth.name = 'choropleth' +Choropleth.basePlotModule = require('../../plots/geo') +Choropleth.categories = ['geo', 'noOpacity'] Choropleth.meta = { - 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(' ') -}; + 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(' ') +} -module.exports = Choropleth; +module.exports = Choropleth diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index 86d947a6fce..6bbbd88fd63 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -6,222 +6,219 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Axes = require('../../plots/cartesian/axes') +var Fx = require('../../plots/cartesian/graph_interact') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var Colorscale = require('../../components/colorscale') -var Axes = require('../../plots/cartesian/axes'); -var Fx = require('../../plots/cartesian/graph_interact'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var Colorscale = require('../../components/colorscale'); +var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures +var locationToFeature = require('../../lib/geo_location_utils').locationToFeature +var arrayToCalcItem = require('../../lib/array_to_calc_item') -var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; -var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; -var arrayToCalcItem = require('../../lib/array_to_calc_item'); +var constants = require('../../plots/geo/constants') +var attributes = require('./attributes') -var constants = require('../../plots/geo/constants'); -var attributes = require('./attributes'); +var plotChoropleth = module.exports = {} -var plotChoropleth = module.exports = {}; +plotChoropleth.calcGeoJSON = function (trace, topojson) { + var cdi = [], + locations = trace.locations, + len = locations.length, + features = getTopojsonFeatures(trace, topojson), + markerLine = (trace.marker || {}).line || {} + var feature -plotChoropleth.calcGeoJSON = function(trace, topojson) { - var cdi = [], - locations = trace.locations, - len = locations.length, - features = getTopojsonFeatures(trace, topojson), - markerLine = (trace.marker || {}).line || {}; + for (var i = 0; i < len; i++) { + feature = locationToFeature(trace.locationmode, locations[i], features) - var feature; - - for(var i = 0; i < len; i++) { - feature = locationToFeature(trace.locationmode, locations[i], features); - - if(!feature) continue; // filter the blank features here + if (!feature) continue // filter the blank features here // 'data_array' attributes - feature.z = trace.z[i]; - if(trace.text !== undefined) feature.tx = trace.text[i]; + feature.z = trace.z[i] + if (trace.text !== undefined) feature.tx = trace.text[i] // 'arrayOk' attributes - arrayToCalcItem(markerLine.color, feature, 'mlc', i); - arrayToCalcItem(markerLine.width, feature, 'mlw', i); - - cdi.push(feature); - } + arrayToCalcItem(markerLine.color, feature, 'mlc', i) + arrayToCalcItem(markerLine.width, feature, 'mlw', i) - if(cdi.length > 0) cdi[0].trace = trace; + cdi.push(feature) + } - return cdi; -}; + if (cdi.length > 0) cdi[0].trace = trace -plotChoropleth.plot = function(geo, calcData, geoLayout) { + return cdi +} - function keyFunc(d) { return d[0].trace.uid; } +plotChoropleth.plot = function (geo, calcData, geoLayout) { + function keyFunc (d) { return d[0].trace.uid } - var framework = geo.framework, - gChoropleth = framework.select('g.choroplethlayer'), - gBaseLayer = framework.select('g.baselayer'), - gBaseLayerOverChoropleth = framework.select('g.baselayeroverchoropleth'), - baseLayersOverChoropleth = constants.baseLayersOverChoropleth, - layerName; + var framework = geo.framework, + gChoropleth = framework.select('g.choroplethlayer'), + gBaseLayer = framework.select('g.baselayer'), + gBaseLayerOverChoropleth = framework.select('g.baselayeroverchoropleth'), + baseLayersOverChoropleth = constants.baseLayersOverChoropleth, + layerName - var gChoroplethTraces = gChoropleth + var gChoroplethTraces = gChoropleth .selectAll('g.trace.choropleth') - .data(calcData, keyFunc); + .data(calcData, keyFunc) - gChoroplethTraces.enter().append('g') - .attr('class', 'trace choropleth'); + gChoroplethTraces.enter().append('g') + .attr('class', 'trace choropleth') - gChoroplethTraces.exit().remove(); + gChoroplethTraces.exit().remove() - gChoroplethTraces.each(function(calcTrace) { - var trace = calcTrace[0].trace, - cdi = plotChoropleth.calcGeoJSON(trace, geo.topojson), - cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace), - eventDataFunc = makeEventDataFunc(trace); + gChoroplethTraces.each(function (calcTrace) { + var trace = calcTrace[0].trace, + cdi = plotChoropleth.calcGeoJSON(trace, geo.topojson), + cleanHoverLabelsFunc = makeCleanHoverLabelsFunc(geo, trace), + eventDataFunc = makeEventDataFunc(trace) // keep ref to event data in this scope for plotly_unhover - var eventData = null; + var eventData = null - function handleMouseOver(pt, ptIndex) { - if(!geo.showHover) return; + function handleMouseOver (pt, ptIndex) { + if (!geo.showHover) return - var xy = geo.projection(pt.properties.ct); - cleanHoverLabelsFunc(pt); + var xy = geo.projection(pt.properties.ct) + cleanHoverLabelsFunc(pt) - Fx.loneHover({ - x: xy[0], - y: xy[1], - name: pt.nameLabel, - text: pt.textLabel - }, { - container: geo.hoverContainer.node() - }); + Fx.loneHover({ + x: xy[0], + y: xy[1], + name: pt.nameLabel, + text: pt.textLabel + }, { + container: geo.hoverContainer.node() + }) - eventData = eventDataFunc(pt, ptIndex); + eventData = eventDataFunc(pt, ptIndex) - geo.graphDiv.emit('plotly_hover', eventData); - } + geo.graphDiv.emit('plotly_hover', eventData) + } - function handleClick(pt, ptIndex) { - geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex)); - } + function handleClick (pt, ptIndex) { + geo.graphDiv.emit('plotly_click', eventDataFunc(pt, ptIndex)) + } - var paths = d3.select(this).selectAll('path.choroplethlocation') - .data(cdi); + var paths = d3.select(this).selectAll('path.choroplethlocation') + .data(cdi) - paths.enter().append('path') + paths.enter().append('path') .classed('choroplethlocation', true) .on('mouseover', handleMouseOver) .on('click', handleClick) - .on('mouseout', function() { - Fx.loneUnhover(geo.hoverContainer); + .on('mouseout', function () { + Fx.loneUnhover(geo.hoverContainer) - geo.graphDiv.emit('plotly_unhover', eventData); + geo.graphDiv.emit('plotly_unhover', eventData) }) - .on('mousedown', function() { + .on('mousedown', function () { // to simulate the 'zoomon' event - Fx.loneUnhover(geo.hoverContainer); + Fx.loneUnhover(geo.hoverContainer) }) - .on('mouseup', handleMouseOver); // ~ 'zoomend' + .on('mouseup', handleMouseOver) // ~ 'zoomend' - paths.exit().remove(); - }); + paths.exit().remove() + }) // some baselayers are drawn over choropleth - gBaseLayerOverChoropleth.selectAll('*').remove(); + gBaseLayerOverChoropleth.selectAll('*').remove() - for(var i = 0; i < baseLayersOverChoropleth.length; i++) { - layerName = baseLayersOverChoropleth[i]; - gBaseLayer.select('g.' + layerName).remove(); - geo.drawTopo(gBaseLayerOverChoropleth, layerName, geoLayout); - geo.styleLayer(gBaseLayerOverChoropleth, layerName, geoLayout); - } + for (var i = 0; i < baseLayersOverChoropleth.length; i++) { + layerName = baseLayersOverChoropleth[i] + gBaseLayer.select('g.' + layerName).remove() + geo.drawTopo(gBaseLayerOverChoropleth, layerName, geoLayout) + geo.styleLayer(gBaseLayerOverChoropleth, layerName, geoLayout) + } - plotChoropleth.style(geo); -}; + plotChoropleth.style(geo) +} -plotChoropleth.style = function(geo) { - geo.framework.selectAll('g.trace.choropleth') - .each(function(calcTrace) { - var trace = calcTrace[0].trace, - s = d3.select(this), - marker = trace.marker || {}, - markerLine = marker.line || {}; +plotChoropleth.style = function (geo) { + geo.framework.selectAll('g.trace.choropleth') + .each(function (calcTrace) { + var trace = calcTrace[0].trace, + s = d3.select(this), + marker = trace.marker || {}, + markerLine = marker.line || {} - var sclFunc = Colorscale.makeColorScaleFunc( + var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( trace.colorscale, trace.zmin, trace.zmax ) - ); + ) - s.selectAll('path.choroplethlocation') - .each(function(pt) { - d3.select(this) - .attr('fill', function(pt) { return sclFunc(pt.z); }) + s.selectAll('path.choroplethlocation') + .each(function (pt) { + d3.select(this) + .attr('fill', function (pt) { return sclFunc(pt.z) }) .call(Color.stroke, pt.mlc || markerLine.color) - .call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0); - }); - }); -}; - -function makeCleanHoverLabelsFunc(geo, trace) { - var hoverinfo = trace.hoverinfo; - - if(hoverinfo === 'none' || hoverinfo === 'skip') { - return function cleanHoverLabelsFunc(pt) { - delete pt.nameLabel; - delete pt.textLabel; - }; + .call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0) + }) + }) +} + +function makeCleanHoverLabelsFunc (geo, trace) { + var hoverinfo = trace.hoverinfo + + if (hoverinfo === 'none' || hoverinfo === 'skip') { + return function cleanHoverLabelsFunc (pt) { + delete pt.nameLabel + delete pt.textLabel } + } - var hoverinfoParts = (hoverinfo === 'all') ? + var hoverinfoParts = (hoverinfo === 'all') ? attributes.hoverinfo.flags : - hoverinfo.split('+'); + hoverinfo.split('+') - var hasName = (hoverinfoParts.indexOf('name') !== -1), - hasLocation = (hoverinfoParts.indexOf('location') !== -1), - hasZ = (hoverinfoParts.indexOf('z') !== -1), - hasText = (hoverinfoParts.indexOf('text') !== -1), - hasIdAsNameLabel = !hasName && hasLocation; + var hasName = (hoverinfoParts.indexOf('name') !== -1), + hasLocation = (hoverinfoParts.indexOf('location') !== -1), + hasZ = (hoverinfoParts.indexOf('z') !== -1), + hasText = (hoverinfoParts.indexOf('text') !== -1), + hasIdAsNameLabel = !hasName && hasLocation - function formatter(val) { - var axis = geo.mockAxis; - return Axes.tickText(axis, axis.c2l(val), 'hover').text; - } + function formatter (val) { + var axis = geo.mockAxis + return Axes.tickText(axis, axis.c2l(val), 'hover').text + } - return function cleanHoverLabelsFunc(pt) { + return function cleanHoverLabelsFunc (pt) { // put location id in name label container // if name isn't part of hoverinfo - var thisText = []; + var thisText = [] - if(hasIdAsNameLabel) pt.nameLabel = pt.id; - else { - if(hasName) pt.nameLabel = trace.name; - if(hasLocation) thisText.push(pt.id); - } + if (hasIdAsNameLabel) pt.nameLabel = pt.id + else { + if (hasName) pt.nameLabel = trace.name + if (hasLocation) thisText.push(pt.id) + } - if(hasZ) thisText.push(formatter(pt.z)); - if(hasText) thisText.push(pt.tx); + if (hasZ) thisText.push(formatter(pt.z)) + if (hasText) thisText.push(pt.tx) - pt.textLabel = thisText.join('
'); - }; + pt.textLabel = thisText.join('
') + } } -function makeEventDataFunc(trace) { - return function(pt, ptIndex) { - return {points: [{ - data: trace._input, - fullData: trace, - curveNumber: trace.index, - pointNumber: ptIndex, - location: pt.id, - z: pt.z - }]}; - }; +function makeEventDataFunc (trace) { + return function (pt, ptIndex) { + return {points: [{ + data: trace._input, + fullData: trace, + curveNumber: trace.index, + pointNumber: ptIndex, + location: pt.id, + z: pt.z + }]} + } } diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index ee547aa5dbe..eb739cd4b6d 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -6,128 +6,128 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var heatmapAttrs = require('../heatmap/attributes'); -var scatterAttrs = require('../scatter/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +var heatmapAttrs = require('../heatmap/attributes') +var scatterAttrs = require('../scatter/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') +var extendFlat = require('../../lib/extend').extendFlat -var scatterLineAttrs = scatterAttrs.line; +var scatterLineAttrs = scatterAttrs.line module.exports = extendFlat({}, { - z: heatmapAttrs.z, - x: heatmapAttrs.x, - x0: heatmapAttrs.x0, - dx: heatmapAttrs.dx, - y: heatmapAttrs.y, - y0: heatmapAttrs.y0, - dy: heatmapAttrs.dy, - text: heatmapAttrs.text, - transpose: heatmapAttrs.transpose, - xtype: heatmapAttrs.xtype, - ytype: heatmapAttrs.ytype, + z: heatmapAttrs.z, + x: heatmapAttrs.x, + x0: heatmapAttrs.x0, + dx: heatmapAttrs.dx, + y: heatmapAttrs.y, + y0: heatmapAttrs.y0, + dy: heatmapAttrs.dy, + text: heatmapAttrs.text, + transpose: heatmapAttrs.transpose, + xtype: heatmapAttrs.xtype, + ytype: heatmapAttrs.ytype, - connectgaps: heatmapAttrs.connectgaps, + connectgaps: heatmapAttrs.connectgaps, - autocontour: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the contour level attributes are', - 'picked by an algorithm.', - 'If *true*, the number of contour levels can be set in `ncontours`.', - 'If *false*, set the contour level attributes in `contours`.' - ].join(' ') + autocontour: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the contour level attributes are', + 'picked by an algorithm.', + 'If *true*, the number of contour levels can be set in `ncontours`.', + 'If *false*, set the contour level attributes in `contours`.' + ].join(' ') + }, + ncontours: { + valType: 'integer', + dflt: 15, + min: 1, + role: 'style', + description: [ + 'Sets the maximum number of contour levels. The actual number', + 'of contours will be chosen automatically to be less than or', + 'equal to the value of `ncontours`.', + 'Has an effect only if `autocontour` is *true* or if', + '`contours.size` is missing.' + ].join(' ') + }, + + contours: { + start: { + valType: 'number', + dflt: null, + role: 'style', + description: [ + 'Sets the starting contour level value.', + 'Must be less than `contours.end`' + ].join(' ') }, - ncontours: { - valType: 'integer', - dflt: 15, - min: 1, - role: 'style', - description: [ - 'Sets the maximum number of contour levels. The actual number', - 'of contours will be chosen automatically to be less than or', - 'equal to the value of `ncontours`.', - 'Has an effect only if `autocontour` is *true* or if', - '`contours.size` is missing.' - ].join(' ') + end: { + valType: 'number', + dflt: null, + role: 'style', + description: [ + 'Sets the end contour level value.', + 'Must be more than `contours.start`' + ].join(' ') }, - - contours: { - start: { - valType: 'number', - dflt: null, - role: 'style', - description: [ - 'Sets the starting contour level value.', - 'Must be less than `contours.end`' - ].join(' ') - }, - end: { - valType: 'number', - dflt: null, - role: 'style', - description: [ - 'Sets the end contour level value.', - 'Must be more than `contours.start`' - ].join(' ') - }, - size: { - valType: 'number', - dflt: null, - min: 0, - role: 'style', - description: [ - 'Sets the step between each contour level.', - 'Must be positive.' - ].join(' ') - }, - coloring: { - valType: 'enumerated', - values: ['fill', 'heatmap', 'lines', 'none'], - dflt: 'fill', - role: 'style', - description: [ - 'Determines the coloring method showing the contour values.', - 'If *fill*, coloring is done evenly between each contour level', - 'If *heatmap*, a heatmap gradient coloring is applied', - 'between each contour level.', - 'If *lines*, coloring is done on the contour lines.', - 'If *none*, no coloring is applied on this trace.' - ].join(' ') - }, - showlines: { - valType: 'boolean', - dflt: true, - role: 'style', - description: [ - 'Determines whether or not the contour lines are drawn.', - 'Has only an effect if `contours.coloring` is set to *fill*.' - ].join(' ') - } + size: { + valType: 'number', + dflt: null, + min: 0, + role: 'style', + description: [ + 'Sets the step between each contour level.', + 'Must be positive.' + ].join(' ') }, - - line: { - color: extendFlat({}, scatterLineAttrs.color, { - description: [ - 'Sets the color of the contour level.', - 'Has no if `contours.coloring` is set to *lines*.' - ].join(' ') - }), - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - smoothing: extendFlat({}, scatterLineAttrs.smoothing, { - description: [ - 'Sets the amount of smoothing for the contour lines,', - 'where *0* corresponds to no smoothing.' - ].join(' ') - }) + coloring: { + valType: 'enumerated', + values: ['fill', 'heatmap', 'lines', 'none'], + dflt: 'fill', + role: 'style', + description: [ + 'Determines the coloring method showing the contour values.', + 'If *fill*, coloring is done evenly between each contour level', + 'If *heatmap*, a heatmap gradient coloring is applied', + 'between each contour level.', + 'If *lines*, coloring is done on the contour lines.', + 'If *none*, no coloring is applied on this trace.' + ].join(' ') + }, + showlines: { + valType: 'boolean', + dflt: true, + role: 'style', + description: [ + 'Determines whether or not the contour lines are drawn.', + 'Has only an effect if `contours.coloring` is set to *fill*.' + ].join(' ') } + }, + + line: { + color: extendFlat({}, scatterLineAttrs.color, { + description: [ + 'Sets the color of the contour level.', + 'Has no if `contours.coloring` is set to *lines*.' + ].join(' ') + }), + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + smoothing: extendFlat({}, scatterLineAttrs.smoothing, { + description: [ + 'Sets the amount of smoothing for the contour lines,', + 'where *0* corresponds to no smoothing.' + ].join(' ') + }) + } }, colorscaleAttrs, { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}) }, { colorbar: colorbarAttrs } -); +) diff --git a/src/traces/contour/calc.js b/src/traces/contour/calc.js index 07e86c2f2fc..000c59e5de9 100644 --- a/src/traces/contour/calc.js +++ b/src/traces/contour/calc.js @@ -6,75 +6,72 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Axes = require('../../plots/cartesian/axes'); -var extendFlat = require('../../lib').extendFlat; -var heatmapCalc = require('../heatmap/calc'); - +var Axes = require('../../plots/cartesian/axes') +var extendFlat = require('../../lib').extendFlat +var heatmapCalc = require('../heatmap/calc') // most is the same as heatmap calc, then adjust it // though a few things inside heatmap calc still look for // contour maps, because the makeBoundArray calls are too entangled -module.exports = function calc(gd, trace) { - var cd = heatmapCalc(gd, trace), - contours = trace.contours; +module.exports = function calc (gd, trace) { + var cd = heatmapCalc(gd, trace), + contours = trace.contours // check if we need to auto-choose contour levels - if(trace.autocontour !== false) { - var dummyAx = autoContours(trace.zmin, trace.zmax, trace.ncontours); + if (trace.autocontour !== false) { + var dummyAx = autoContours(trace.zmin, trace.zmax, trace.ncontours) - contours.size = dummyAx.dtick; + contours.size = dummyAx.dtick - contours.start = Axes.tickFirst(dummyAx); - dummyAx.range.reverse(); - contours.end = Axes.tickFirst(dummyAx); + contours.start = Axes.tickFirst(dummyAx) + dummyAx.range.reverse() + contours.end = Axes.tickFirst(dummyAx) - if(contours.start === trace.zmin) contours.start += contours.size; - if(contours.end === trace.zmax) contours.end -= contours.size; + if (contours.start === trace.zmin) contours.start += contours.size + if (contours.end === trace.zmax) contours.end -= contours.size // if you set a small ncontours, *and* the ends are exactly on zmin/zmax // there's an edge case where start > end now. Make sure there's at least // one meaningful contour, put it midway between the crossed values - if(contours.start > contours.end) { - contours.start = contours.end = (contours.start + contours.end) / 2; - } + if (contours.start > contours.end) { + contours.start = contours.end = (contours.start + contours.end) / 2 + } // copy auto-contour info back to the source data. // previously we copied the whole contours object back, but that had // other info (coloring, showlines) that should be left to supplyDefaults - if(!trace._input.contours) trace._input.contours = {}; - extendFlat(trace._input.contours, { - start: contours.start, - end: contours.end, - size: contours.size - }); - trace._input.autocontour = true; - } - else { + if (!trace._input.contours) trace._input.contours = {} + extendFlat(trace._input.contours, { + start: contours.start, + end: contours.end, + size: contours.size + }) + trace._input.autocontour = true + } else { // sanity checks on manually-supplied start/end/size - var start = contours.start, - end = contours.end, - inputContours = trace._input.contours; - - if(start > end) { - contours.start = inputContours.start = end; - end = contours.end = inputContours.end = start; - start = contours.start; - } - - if(!(contours.size > 0)) { - var sizeOut; - if(start === end) sizeOut = 1; - else sizeOut = autoContours(start, end, trace.ncontours).dtick; - - inputContours.size = contours.size = sizeOut; - } + var start = contours.start, + end = contours.end, + inputContours = trace._input.contours + + if (start > end) { + contours.start = inputContours.start = end + end = contours.end = inputContours.end = start + start = contours.start } - return cd; -}; + if (!(contours.size > 0)) { + var sizeOut + if (start === end) sizeOut = 1 + else sizeOut = autoContours(start, end, trace.ncontours).dtick + + inputContours.size = contours.size = sizeOut + } + } + + return cd +} /* * autoContours: make a dummy axis object with dtick we can use @@ -87,16 +84,16 @@ module.exports = function calc(gd, trace) { * * returns: an axis object */ -function autoContours(start, end, ncontours) { - var dummyAx = { - type: 'linear', - range: [start, end] - }; +function autoContours (start, end, ncontours) { + var dummyAx = { + type: 'linear', + range: [start, end] + } - Axes.autoTicks( + Axes.autoTicks( dummyAx, (end - start) / (ncontours || 15) - ); + ) - return dummyAx; + return dummyAx } diff --git a/src/traces/contour/colorbar.js b/src/traces/contour/colorbar.js index 68be46e0234..e409d040b40 100644 --- a/src/traces/contour/colorbar.js +++ b/src/traces/contour/colorbar.js @@ -6,55 +6,53 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Plots = require('../../plots/plots') +var drawColorbar = require('../../components/colorbar/draw') -var Plots = require('../../plots/plots'); -var drawColorbar = require('../../components/colorbar/draw'); +var makeColorMap = require('./make_color_map') +var endPlus = require('./end_plus') -var makeColorMap = require('./make_color_map'); -var endPlus = require('./end_plus'); +module.exports = function colorbar (gd, cd) { + var trace = cd[0].trace, + cbId = 'cb' + trace.uid + gd._fullLayout._infolayer.selectAll('.' + cbId).remove() -module.exports = function colorbar(gd, cd) { - var trace = cd[0].trace, - cbId = 'cb' + trace.uid; + if (trace.showscale === false) { + Plots.autoMargin(gd, cbId) + return + } - gd._fullLayout._infolayer.selectAll('.' + cbId).remove(); + var cb = drawColorbar(gd, cbId) + cd[0].t.cb = cb - if(trace.showscale === false) { - Plots.autoMargin(gd, cbId); - return; - } + var contours = trace.contours, + line = trace.line, + cs = contours.size || 1, + coloring = contours.coloring - var cb = drawColorbar(gd, cbId); - cd[0].t.cb = cb; + var colorMap = makeColorMap(trace, {isColorbar: true}) - var contours = trace.contours, - line = trace.line, - cs = contours.size || 1, - coloring = contours.coloring; + if (coloring === 'heatmap') { + cb.filllevels({ + start: trace.zmin, + end: trace.zmax, + size: (trace.zmax - trace.zmin) / 254 + }) + } - var colorMap = makeColorMap(trace, {isColorbar: true}); - - if(coloring === 'heatmap') { - cb.filllevels({ - start: trace.zmin, - end: trace.zmax, - size: (trace.zmax - trace.zmin) / 254 - }); - } - - cb.fillcolor((coloring === 'fill' || coloring === 'heatmap') ? colorMap : '') + cb.fillcolor((coloring === 'fill' || coloring === 'heatmap') ? colorMap : '') .line({ - color: coloring === 'lines' ? colorMap : line.color, - width: contours.showlines !== false ? line.width : 0, - dash: line.dash + color: coloring === 'lines' ? colorMap : line.color, + width: contours.showlines !== false ? line.width : 0, + dash: line.dash }) .levels({ - start: contours.start, - end: endPlus(contours), - size: cs + start: contours.start, + end: endPlus(contours), + size: cs }) - .options(trace.colorbar)(); -}; + .options(trace.colorbar)() +} diff --git a/src/traces/contour/constants.js b/src/traces/contour/constants.js index 406c4057804..450d82c7b02 100644 --- a/src/traces/contour/constants.js +++ b/src/traces/contour/constants.js @@ -6,33 +6,33 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' // some constants to help with marching squares algorithm // where does the path start for each index? -module.exports.BOTTOMSTART = [1, 9, 13, 104, 713]; -module.exports.TOPSTART = [4, 6, 7, 104, 713]; -module.exports.LEFTSTART = [8, 12, 14, 208, 1114]; -module.exports.RIGHTSTART = [2, 3, 11, 208, 1114]; +module.exports.BOTTOMSTART = [1, 9, 13, 104, 713] +module.exports.TOPSTART = [4, 6, 7, 104, 713] +module.exports.LEFTSTART = [8, 12, 14, 208, 1114] +module.exports.RIGHTSTART = [2, 3, 11, 208, 1114] // which way [dx,dy] do we leave a given index? // saddles are already disambiguated module.exports.NEWDELTA = [ - null, [-1, 0], [0, -1], [-1, 0], + null, [-1, 0], [0, -1], [-1, 0], [1, 0], null, [0, -1], [-1, 0], [0, 1], [0, 1], null, [0, 1], [1, 0], [1, 0], [0, -1] -]; +] // for each saddle, the first index here is used // for dx||dy<0, the second for dx||dy>0 module.exports.CHOOSESADDLE = { - 104: [4, 1], - 208: [2, 8], - 713: [7, 13], - 1114: [11, 14] -}; + 104: [4, 1], + 208: [2, 8], + 713: [7, 13], + 1114: [11, 14] +} // after one index has been used for a saddle, which do we // substitute to be used up later? -module.exports.SADDLEREMAINDER = {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11}; +module.exports.SADDLEREMAINDER = {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11} diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js index 04b9debba70..e46e07240a2 100644 --- a/src/traces/contour/defaults.js +++ b/src/traces/contour/defaults.js @@ -6,46 +6,44 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var hasColumns = require('../heatmap/has_columns') +var handleXYZDefaults = require('../heatmap/xyz_defaults') +var handleStyleDefaults = require('../contour/style_defaults') +var attributes = require('./attributes') -var hasColumns = require('../heatmap/has_columns'); -var handleXYZDefaults = require('../heatmap/xyz_defaults'); -var handleStyleDefaults = require('../contour/style_defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var len = handleXYZDefaults(traceIn, traceOut, coerce, layout) + if (!len) { + traceOut.visible = false + return + } -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + coerce('text') + coerce('connectgaps', hasColumns(traceOut)) - var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - coerce('connectgaps', hasColumns(traceOut)); - - var contourStart = Lib.coerce2(traceIn, traceOut, attributes, 'contours.start'), - contourEnd = Lib.coerce2(traceIn, traceOut, attributes, 'contours.end'), - missingEnd = (contourStart === false) || (contourEnd === false), + var contourStart = Lib.coerce2(traceIn, traceOut, attributes, 'contours.start'), + contourEnd = Lib.coerce2(traceIn, traceOut, attributes, 'contours.end'), + missingEnd = (contourStart === false) || (contourEnd === false), // normally we only need size if autocontour is off. But contour.calc // pushes its calculated contour size back to the input trace, so for // things like restyle that can call supplyDefaults without calc // after the initial draw, we can just reuse the previous calculation - contourSize = coerce('contours.size'), - autoContour; + contourSize = coerce('contours.size'), + autoContour - if(missingEnd) autoContour = traceOut.autocontour = true; - else autoContour = coerce('autocontour', false); + if (missingEnd) autoContour = traceOut.autocontour = true + else autoContour = coerce('autocontour', false) - if(autoContour || !contourSize) coerce('ncontours'); + if (autoContour || !contourSize) coerce('ncontours') - handleStyleDefaults(traceIn, traceOut, coerce, layout); -}; + handleStyleDefaults(traceIn, traceOut, coerce, layout) +} diff --git a/src/traces/contour/end_plus.js b/src/traces/contour/end_plus.js index 8b8e9dc3f65..d8ee30b2bc8 100644 --- a/src/traces/contour/end_plus.js +++ b/src/traces/contour/end_plus.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /* * tiny helper to move the end of the contours a little to prevent * losing the last contour to rounding errors */ -module.exports = function endPlus(contours) { - return contours.end + contours.size / 1e6; -}; +module.exports = function endPlus (contours) { + return contours.end + contours.size / 1e6 +} diff --git a/src/traces/contour/find_all_paths.js b/src/traces/contour/find_all_paths.js index 273368094e2..b6ca442c182 100644 --- a/src/traces/contour/find_all_paths.js +++ b/src/traces/contour/find_all_paths.js @@ -6,263 +6,255 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var constants = require('./constants'); +var Lib = require('../../lib') +var constants = require('./constants') -module.exports = function findAllPaths(pathinfo) { - var cnt, - startLoc, - i, - pi, - j; +module.exports = function findAllPaths (pathinfo) { + var cnt, + startLoc, + i, + pi, + j - for(i = 0; i < pathinfo.length; i++) { - pi = pathinfo[i]; + for (i = 0; i < pathinfo.length; i++) { + pi = pathinfo[i] - for(j = 0; j < pi.starts.length; j++) { - startLoc = pi.starts[j]; - makePath(pi, startLoc, 'edge'); - } + for (j = 0; j < pi.starts.length; j++) { + startLoc = pi.starts[j] + makePath(pi, startLoc, 'edge') + } - cnt = 0; - while(Object.keys(pi.crossings).length && cnt < 10000) { - cnt++; - startLoc = Object.keys(pi.crossings)[0].split(',').map(Number); - makePath(pi, startLoc); - } - if(cnt === 10000) Lib.log('Infinite loop in contour?'); + cnt = 0 + while (Object.keys(pi.crossings).length && cnt < 10000) { + cnt++ + startLoc = Object.keys(pi.crossings)[0].split(',').map(Number) + makePath(pi, startLoc) } -}; + if (cnt === 10000) Lib.log('Infinite loop in contour?') + } +} -function equalPts(pt1, pt2) { - return Math.abs(pt1[0] - pt2[0]) < 0.01 && - Math.abs(pt1[1] - pt2[1]) < 0.01; +function equalPts (pt1, pt2) { + return Math.abs(pt1[0] - pt2[0]) < 0.01 && + Math.abs(pt1[1] - pt2[1]) < 0.01 } -function ptDist(pt1, pt2) { - var dx = pt1[0] - pt2[0], - dy = pt1[1] - pt2[1]; - return Math.sqrt(dx * dx + dy * dy); +function ptDist (pt1, pt2) { + var dx = pt1[0] - pt2[0], + dy = pt1[1] - pt2[1] + return Math.sqrt(dx * dx + dy * dy) } -function makePath(pi, loc, edgeflag) { - var startLocStr = loc.join(','), - locStr = startLocStr, - mi = pi.crossings[locStr], - marchStep = startStep(mi, edgeflag, loc), +function makePath (pi, loc, edgeflag) { + var startLocStr = loc.join(','), + locStr = startLocStr, + mi = pi.crossings[locStr], + marchStep = startStep(mi, edgeflag, loc), // start by going backward a half step and finding the crossing point - pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])], - startStepStr = marchStep.join(','), - m = pi.z.length, - n = pi.z[0].length, - cnt; + pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])], + startStepStr = marchStep.join(','), + m = pi.z.length, + n = pi.z[0].length, + cnt // now follow the path - for(cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops - if(mi > 20) { - mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1]; - pi.crossings[locStr] = constants.SADDLEREMAINDER[mi]; - } - else { - delete pi.crossings[locStr]; - } + for (cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops + if (mi > 20) { + mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1] + pi.crossings[locStr] = constants.SADDLEREMAINDER[mi] + } else { + delete pi.crossings[locStr] + } - marchStep = constants.NEWDELTA[mi]; - if(!marchStep) { - Lib.log('Found bad marching index:', mi, loc, pi.level); - break; - } + marchStep = constants.NEWDELTA[mi] + if (!marchStep) { + Lib.log('Found bad marching index:', mi, loc, pi.level) + break + } // find the crossing a half step forward, and then take the full step - pts.push(getInterpPx(pi, loc, marchStep)); - loc[0] += marchStep[0]; - loc[1] += marchStep[1]; + pts.push(getInterpPx(pi, loc, marchStep)) + loc[0] += marchStep[0] + loc[1] += marchStep[1] // don't include the same point multiple times - if(equalPts(pts[pts.length - 1], pts[pts.length - 2])) pts.pop(); - locStr = loc.join(','); + if (equalPts(pts[pts.length - 1], pts[pts.length - 2])) pts.pop() + locStr = loc.join(',') - var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) || + var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) || (marchStep[1] && (loc[1] < 0 || loc[1] > m - 2)), - closedLoop = (locStr === startLocStr) && (marchStep.join(',') === startStepStr); + closedLoop = (locStr === startLocStr) && (marchStep.join(',') === startStepStr) // have we completed a loop, or reached an edge? - if((closedLoop) || (edgeflag && atEdge)) break; - - mi = pi.crossings[locStr]; - } - - if(cnt === 10000) { - Lib.log('Infinite loop in contour?'); - } - var closedpath = equalPts(pts[0], pts[pts.length - 1]), - totaldist = 0, - distThresholdFactor = 0.2 * pi.smoothing, - alldists = [], - cropstart = 0, - distgroup, - cnt2, - cnt3, - newpt, - ptcnt, - ptavg, - thisdist; + if ((closedLoop) || (edgeflag && atEdge)) break + + mi = pi.crossings[locStr] + } + + if (cnt === 10000) { + Lib.log('Infinite loop in contour?') + } + var closedpath = equalPts(pts[0], pts[pts.length - 1]), + totaldist = 0, + distThresholdFactor = 0.2 * pi.smoothing, + alldists = [], + cropstart = 0, + distgroup, + cnt2, + cnt3, + newpt, + ptcnt, + ptavg, + thisdist // check for points that are too close together (<1/5 the average dist, // less if less smoothed) and just take the center (or avg of center 2) // this cuts down on funny behavior when a point is very close to a contour level - for(cnt = 1; cnt < pts.length; cnt++) { - thisdist = ptDist(pts[cnt], pts[cnt - 1]); - totaldist += thisdist; - alldists.push(thisdist); - } - - var distThreshold = totaldist / alldists.length * distThresholdFactor; - - function getpt(i) { return pts[i % pts.length]; } - - for(cnt = pts.length - 2; cnt >= cropstart; cnt--) { - distgroup = alldists[cnt]; - if(distgroup < distThreshold) { - cnt3 = 0; - for(cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) { - if(distgroup + alldists[cnt2] < distThreshold) { - distgroup += alldists[cnt2]; - } - else break; - } + for (cnt = 1; cnt < pts.length; cnt++) { + thisdist = ptDist(pts[cnt], pts[cnt - 1]) + totaldist += thisdist + alldists.push(thisdist) + } + + var distThreshold = totaldist / alldists.length * distThresholdFactor + + function getpt (i) { return pts[i % pts.length] } + + for (cnt = pts.length - 2; cnt >= cropstart; cnt--) { + distgroup = alldists[cnt] + if (distgroup < distThreshold) { + cnt3 = 0 + for (cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) { + if (distgroup + alldists[cnt2] < distThreshold) { + distgroup += alldists[cnt2] + } else break + } // closed path with close points wrapping around the boundary? - if(closedpath && cnt === pts.length - 2) { - for(cnt3 = 0; cnt3 < cnt2; cnt3++) { - if(distgroup + alldists[cnt3] < distThreshold) { - distgroup += alldists[cnt3]; - } - else break; - } - } - ptcnt = cnt - cnt2 + cnt3 + 1; - ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2); + if (closedpath && cnt === pts.length - 2) { + for (cnt3 = 0; cnt3 < cnt2; cnt3++) { + if (distgroup + alldists[cnt3] < distThreshold) { + distgroup += alldists[cnt3] + } else break + } + } + ptcnt = cnt - cnt2 + cnt3 + 1 + ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2) // either endpoint included: keep the endpoint - if(!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1]; - else if(!closedpath && cnt2 === -1) newpt = pts[0]; + if (!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1] + else if (!closedpath && cnt2 === -1) newpt = pts[0] // odd # of points - just take the central one - else if(ptcnt % 2) newpt = getpt(ptavg); + else if (ptcnt % 2) newpt = getpt(ptavg) // even # of pts - average central two - else { - newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2, - (getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2]; - } - - pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt); - cnt = cnt2 + 1; - if(cnt3) cropstart = cnt3; - if(closedpath) { - if(cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1]; - else if(cnt === 0) pts[pts.length - 1] = pts[0]; - } - } + else { + newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2, + (getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2] + } + + pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt) + cnt = cnt2 + 1 + if (cnt3) cropstart = cnt3 + if (closedpath) { + if (cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1] + else if (cnt === 0) pts[pts.length - 1] = pts[0] + } } - pts.splice(0, cropstart); + } + pts.splice(0, cropstart) // don't return single-point paths (ie all points were the same // so they got deleted?) - if(pts.length < 2) return; - else if(closedpath) { - pts.pop(); - pi.paths.push(pts); + if (pts.length < 2) return + else if (closedpath) { + pts.pop() + pi.paths.push(pts) + } else { + if (!edgeflag) { + Lib.log('Unclosed interior contour?', + pi.level, startLocStr, pts.join('L')) } - else { - if(!edgeflag) { - Lib.log('Unclosed interior contour?', - pi.level, startLocStr, pts.join('L')); - } // edge path - does it start where an existing edge path ends, or vice versa? - var merged = false; - pi.edgepaths.forEach(function(edgepath, edgei) { - if(!merged && equalPts(edgepath[0], pts[pts.length - 1])) { - pts.pop(); - merged = true; + var merged = false + pi.edgepaths.forEach(function (edgepath, edgei) { + if (!merged && equalPts(edgepath[0], pts[pts.length - 1])) { + pts.pop() + merged = true // now does it ALSO meet the end of another (or the same) path? - var doublemerged = false; - pi.edgepaths.forEach(function(edgepath2, edgei2) { - if(!doublemerged && equalPts( + var doublemerged = false + pi.edgepaths.forEach(function (edgepath2, edgei2) { + if (!doublemerged && equalPts( edgepath2[edgepath2.length - 1], pts[0])) { - doublemerged = true; - pts.splice(0, 1); - pi.edgepaths.splice(edgei, 1); - if(edgei2 === edgei) { + doublemerged = true + pts.splice(0, 1) + pi.edgepaths.splice(edgei, 1) + if (edgei2 === edgei) { // the path is now closed - pi.paths.push(pts.concat(edgepath2)); - } - else { - pi.edgepaths[edgei2] = - pi.edgepaths[edgei2].concat(pts, edgepath2); - } - } - }); - if(!doublemerged) { - pi.edgepaths[edgei] = pts.concat(edgepath); - } + pi.paths.push(pts.concat(edgepath2)) + } else { + pi.edgepaths[edgei2] = + pi.edgepaths[edgei2].concat(pts, edgepath2) } - }); - pi.edgepaths.forEach(function(edgepath, edgei) { - if(!merged && equalPts(edgepath[edgepath.length - 1], pts[0])) { - pts.splice(0, 1); - pi.edgepaths[edgei] = edgepath.concat(pts); - merged = true; - } - }); - - if(!merged) pi.edgepaths.push(pts); - } + } + }) + if (!doublemerged) { + pi.edgepaths[edgei] = pts.concat(edgepath) + } + } + }) + pi.edgepaths.forEach(function (edgepath, edgei) { + if (!merged && equalPts(edgepath[edgepath.length - 1], pts[0])) { + pts.splice(0, 1) + pi.edgepaths[edgei] = edgepath.concat(pts) + merged = true + } + }) + + if (!merged) pi.edgepaths.push(pts) + } } // special function to get the marching step of the // first point in the path (leading to loc) -function startStep(mi, edgeflag, loc) { - var dx = 0, - dy = 0; - if(mi > 20 && edgeflag) { +function startStep (mi, edgeflag, loc) { + var dx = 0, + dy = 0 + if (mi > 20 && edgeflag) { // these saddles start at +/- x - if(mi === 208 || mi === 1114) { + if (mi === 208 || mi === 1114) { // if we're starting at the left side, we must be going right - dx = loc[0] === 0 ? 1 : -1; - } - else { + dx = loc[0] === 0 ? 1 : -1 + } else { // if we're starting at the bottom, we must be going up - dy = loc[1] === 0 ? 1 : -1; - } + dy = loc[1] === 0 ? 1 : -1 } - else if(constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1; - else if(constants.LEFTSTART.indexOf(mi) !== -1) dx = 1; - else if(constants.TOPSTART.indexOf(mi) !== -1) dy = -1; - else dx = -1; - return [dx, dy]; + } else if (constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1 + else if (constants.LEFTSTART.indexOf(mi) !== -1) dx = 1 + else if (constants.TOPSTART.indexOf(mi) !== -1) dy = -1 + else dx = -1 + return [dx, dy] } -function getInterpPx(pi, loc, step) { - var locx = loc[0] + Math.max(step[0], 0), - locy = loc[1] + Math.max(step[1], 0), - zxy = pi.z[locy][locx], - xa = pi.xaxis, - ya = pi.yaxis; - - if(step[1]) { - var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy); - return [xa.c2p((1 - dx) * pi.x[locx] + dx * pi.x[locx + 1], true), - ya.c2p(pi.y[locy], true)]; - } - else { - var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy); - return [xa.c2p(pi.x[locx], true), - ya.c2p((1 - dy) * pi.y[locy] + dy * pi.y[locy + 1], true)]; - } +function getInterpPx (pi, loc, step) { + var locx = loc[0] + Math.max(step[0], 0), + locy = loc[1] + Math.max(step[1], 0), + zxy = pi.z[locy][locx], + xa = pi.xaxis, + ya = pi.yaxis + + if (step[1]) { + var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy) + return [xa.c2p((1 - dx) * pi.x[locx] + dx * pi.x[locx + 1], true), + ya.c2p(pi.y[locy], true)] + } else { + var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy) + return [xa.c2p(pi.x[locx], true), + ya.c2p((1 - dy) * pi.y[locy] + dy * pi.y[locy + 1], true)] + } } diff --git a/src/traces/contour/hover.js b/src/traces/contour/hover.js index d53393d9ed8..cf508beed54 100644 --- a/src/traces/contour/hover.js +++ b/src/traces/contour/hover.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var heatmapHoverPoints = require('../heatmap/hover') -var heatmapHoverPoints = require('../heatmap/hover'); - - -module.exports = function hoverPoints(pointData, xval, yval, hovermode) { - return heatmapHoverPoints(pointData, xval, yval, hovermode, true); -}; +module.exports = function hoverPoints (pointData, xval, yval, hovermode) { + return heatmapHoverPoints(pointData, xval, yval, hovermode, true) +} diff --git a/src/traces/contour/index.js b/src/traces/contour/index.js index ee18de12422..d305de7dd3b 100644 --- a/src/traces/contour/index.js +++ b/src/traces/contour/index.js @@ -6,34 +6,33 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Contour = {} -var Contour = {}; +Contour.attributes = require('./attributes') +Contour.supplyDefaults = require('./defaults') +Contour.calc = require('./calc') +Contour.plot = require('./plot') +Contour.style = require('./style') +Contour.colorbar = require('./colorbar') +Contour.hoverPoints = require('./hover') -Contour.attributes = require('./attributes'); -Contour.supplyDefaults = require('./defaults'); -Contour.calc = require('./calc'); -Contour.plot = require('./plot'); -Contour.style = require('./style'); -Contour.colorbar = require('./colorbar'); -Contour.hoverPoints = require('./hover'); - -Contour.moduleType = 'trace'; -Contour.name = 'contour'; -Contour.basePlotModule = require('../../plots/cartesian'); -Contour.categories = ['cartesian', '2dMap', 'contour']; +Contour.moduleType = 'trace' +Contour.name = 'contour' +Contour.basePlotModule = require('../../plots/cartesian') +Contour.categories = ['cartesian', '2dMap', 'contour'] Contour.meta = { - description: [ - 'The data from which contour lines are computed is set in `z`.', - 'Data in `z` must be a {2D array} of numbers.', + description: [ + 'The data from which contour lines are computed is set in `z`.', + 'Data in `z` must be a {2D array} of numbers.', - 'Say that `z` has N rows and M columns, then by default,', - 'these N rows correspond to N y coordinates', - '(set in `y` or auto-generated) and the M columns', - 'correspond to M x coordinates (set in `x` or auto-generated).', - 'By setting `transpose` to *true*, the above behavior is flipped.' - ].join(' ') -}; + 'Say that `z` has N rows and M columns, then by default,', + 'these N rows correspond to N y coordinates', + '(set in `y` or auto-generated) and the M columns', + 'correspond to M x coordinates (set in `x` or auto-generated).', + 'By setting `transpose` to *true*, the above behavior is flipped.' + ].join(' ') +} -module.exports = Contour; +module.exports = Contour diff --git a/src/traces/contour/make_color_map.js b/src/traces/contour/make_color_map.js index fc54a155705..b1eaf521a3d 100644 --- a/src/traces/contour/make_color_map.js +++ b/src/traces/contour/make_color_map.js @@ -6,72 +6,70 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') +var Colorscale = require('../../components/colorscale') +var endPlus = require('./end_plus') -var d3 = require('d3'); -var Colorscale = require('../../components/colorscale'); -var endPlus = require('./end_plus'); +module.exports = function makeColorMap (trace) { + var contours = trace.contours, + start = contours.start, + end = endPlus(contours), + cs = contours.size || 1, + nc = Math.floor((end - start) / cs) + 1, + extra = contours.coloring === 'lines' ? 0 : 1 -module.exports = function makeColorMap(trace) { - var contours = trace.contours, - start = contours.start, - end = endPlus(contours), - cs = contours.size || 1, - nc = Math.floor((end - start) / cs) + 1, - extra = contours.coloring === 'lines' ? 0 : 1; + var scl = trace.colorscale, + len = scl.length - var scl = trace.colorscale, - len = scl.length; + var domain = new Array(len), + range = new Array(len) - var domain = new Array(len), - range = new Array(len); + var si, i - var si, i; - - if(contours.coloring === 'heatmap') { - if(trace.zauto && trace.autocontour === false) { - trace.zmin = start - cs / 2; - trace.zmax = trace.zmin + nc * cs; - } + if (contours.coloring === 'heatmap') { + if (trace.zauto && trace.autocontour === false) { + trace.zmin = start - cs / 2 + trace.zmax = trace.zmin + nc * cs + } - for(i = 0; i < len; i++) { - si = scl[i]; + for (i = 0; i < len; i++) { + si = scl[i] - domain[i] = si[0] * (trace.zmax - trace.zmin) + trace.zmin; - range[i] = si[1]; - } + domain[i] = si[0] * (trace.zmax - trace.zmin) + trace.zmin + range[i] = si[1] + } // do the contours extend beyond the colorscale? // if so, extend the colorscale with constants - var zRange = d3.extent([trace.zmin, trace.zmax, contours.start, - contours.start + cs * (nc - 1)]), - zmin = zRange[trace.zmin < trace.zmax ? 0 : 1], - zmax = zRange[trace.zmin < trace.zmax ? 1 : 0]; + var zRange = d3.extent([trace.zmin, trace.zmax, contours.start, + contours.start + cs * (nc - 1)]), + zmin = zRange[trace.zmin < trace.zmax ? 0 : 1], + zmax = zRange[trace.zmin < trace.zmax ? 1 : 0] - if(zmin !== trace.zmin) { - domain.splice(0, 0, zmin); - range.splice(0, 0, Range[0]); - } + if (zmin !== trace.zmin) { + domain.splice(0, 0, zmin) + range.splice(0, 0, Range[0]) + } - if(zmax !== trace.zmax) { - domain.push(zmax); - range.push(range[range.length - 1]); - } + if (zmax !== trace.zmax) { + domain.push(zmax) + range.push(range[range.length - 1]) } - else { - for(i = 0; i < len; i++) { - si = scl[i]; + } else { + for (i = 0; i < len; i++) { + si = scl[i] - domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start; - range[i] = si[1]; - } + domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start + range[i] = si[1] } + } - return Colorscale.makeColorScaleFunc({ - domain: domain, - range: range, - }, { - noNumericCheck: true - }); -}; + return Colorscale.makeColorScaleFunc({ + domain: domain, + range: range + }, { + noNumericCheck: true + }) +} diff --git a/src/traces/contour/make_crossings.js b/src/traces/contour/make_crossings.js index 7d24830f4cf..5efee79c682 100644 --- a/src/traces/contour/make_crossings.js +++ b/src/traces/contour/make_crossings.js @@ -6,64 +6,64 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var constants = require('./constants'); +var constants = require('./constants') // Calculate all the marching indices, for ALL levels at once. // since we want to be exhaustive we'll check for contour crossings // at every intersection, rather than just following a path // TODO: shorten the inner loop to only the relevant levels -module.exports = function makeCrossings(pathinfo) { - var z = pathinfo[0].z, - m = z.length, - n = z[0].length, // we already made sure z isn't ragged in interp2d - twoWide = m === 2 || n === 2, - xi, - yi, - startIndices, - ystartIndices, - label, - corners, - mi, - pi, - i; +module.exports = function makeCrossings (pathinfo) { + var z = pathinfo[0].z, + m = z.length, + n = z[0].length, // we already made sure z isn't ragged in interp2d + twoWide = m === 2 || n === 2, + xi, + yi, + startIndices, + ystartIndices, + label, + corners, + mi, + pi, + i - for(yi = 0; yi < m - 1; yi++) { - ystartIndices = []; - if(yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART); - if(yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART); + for (yi = 0; yi < m - 1; yi++) { + ystartIndices = [] + if (yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART) + if (yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART) - for(xi = 0; xi < n - 1; xi++) { - startIndices = ystartIndices.slice(); - if(xi === 0) startIndices = startIndices.concat(constants.LEFTSTART); - if(xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART); + for (xi = 0; xi < n - 1; xi++) { + startIndices = ystartIndices.slice() + if (xi === 0) startIndices = startIndices.concat(constants.LEFTSTART) + if (xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART) - label = xi + ',' + yi; - corners = [[z[yi][xi], z[yi][xi + 1]], - [z[yi + 1][xi], z[yi + 1][xi + 1]]]; - for(i = 0; i < pathinfo.length; i++) { - pi = pathinfo[i]; - mi = getMarchingIndex(pi.level, corners); - if(!mi) continue; + label = xi + ',' + yi + corners = [[z[yi][xi], z[yi][xi + 1]], + [z[yi + 1][xi], z[yi + 1][xi + 1]]] + for (i = 0; i < pathinfo.length; i++) { + pi = pathinfo[i] + mi = getMarchingIndex(pi.level, corners) + if (!mi) continue - pi.crossings[label] = mi; - if(startIndices.indexOf(mi) !== -1) { - pi.starts.push([xi, yi]); - if(twoWide && startIndices.indexOf(mi, + pi.crossings[label] = mi + if (startIndices.indexOf(mi) !== -1) { + pi.starts.push([xi, yi]) + if (twoWide && startIndices.indexOf(mi, startIndices.indexOf(mi) + 1) !== -1) { // the same square has starts from opposite sides // it's not possible to have starts on opposite edges // of a corner, only a start and an end... // but if the array is only two points wide (either way) // you can have starts on opposite sides. - pi.starts.push([xi, yi]); - } - } - } + pi.starts.push([xi, yi]) + } } + } } -}; + } +} // modified marching squares algorithm, // so we disambiguate the saddle points from the start @@ -73,18 +73,18 @@ module.exports = function makeCrossings(pathinfo) { // except that the saddles bifurcate and I represent them // as the decimal combination of the two appropriate // non-saddle indices -function getMarchingIndex(val, corners) { - var mi = (corners[0][0] > val ? 0 : 1) + +function getMarchingIndex (val, corners) { + var mi = (corners[0][0] > val ? 0 : 1) + (corners[0][1] > val ? 0 : 2) + (corners[1][1] > val ? 0 : 4) + - (corners[1][0] > val ? 0 : 8); - if(mi === 5 || mi === 10) { - var avg = (corners[0][0] + corners[0][1] + - corners[1][0] + corners[1][1]) / 4; + (corners[1][0] > val ? 0 : 8) + if (mi === 5 || mi === 10) { + var avg = (corners[0][0] + corners[0][1] + + corners[1][0] + corners[1][1]) / 4 // two peaks with a big valley - if(val > avg) return (mi === 5) ? 713 : 1114; + if (val > avg) return (mi === 5) ? 713 : 1114 // two valleys with a big ridge - return (mi === 5) ? 104 : 208; - } - return (mi === 15) ? 0 : mi; + return (mi === 5) ? 104 : 208 + } + return (mi === 15) ? 0 : mi } diff --git a/src/traces/contour/plot.js b/src/traces/contour/plot.js index e33beda01f1..116add87b9e 100644 --- a/src/traces/contour/plot.js +++ b/src/traces/contour/plot.js @@ -6,351 +6,346 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Lib = require('../../lib') +var Drawing = require('../../components/drawing') -var Lib = require('../../lib'); -var Drawing = require('../../components/drawing'); +var heatmapPlot = require('../heatmap/plot') +var makeCrossings = require('./make_crossings') +var findAllPaths = require('./find_all_paths') +var endPlus = require('./end_plus') -var heatmapPlot = require('../heatmap/plot'); -var makeCrossings = require('./make_crossings'); -var findAllPaths = require('./find_all_paths'); -var endPlus = require('./end_plus'); +module.exports = function plot (gd, plotinfo, cdcontours) { + for (var i = 0; i < cdcontours.length; i++) { + plotOne(gd, plotinfo, cdcontours[i]) + } +} - -module.exports = function plot(gd, plotinfo, cdcontours) { - for(var i = 0; i < cdcontours.length; i++) { - plotOne(gd, plotinfo, cdcontours[i]); - } -}; - -function plotOne(gd, plotinfo, cd) { - var trace = cd[0].trace, - x = cd[0].x, - y = cd[0].y, - contours = trace.contours, - uid = trace.uid, - xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - fullLayout = gd._fullLayout, - id = 'contour' + uid, - pathinfo = emptyPathinfo(contours, plotinfo, cd[0]); - - if(trace.visible !== true) { - fullLayout._paper.selectAll('.' + id + ',.hm' + uid).remove(); - fullLayout._infolayer.selectAll('.cb' + uid).remove(); - return; - } +function plotOne (gd, plotinfo, cd) { + var trace = cd[0].trace, + x = cd[0].x, + y = cd[0].y, + contours = trace.contours, + uid = trace.uid, + xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + fullLayout = gd._fullLayout, + id = 'contour' + uid, + pathinfo = emptyPathinfo(contours, plotinfo, cd[0]) + + if (trace.visible !== true) { + fullLayout._paper.selectAll('.' + id + ',.hm' + uid).remove() + fullLayout._infolayer.selectAll('.cb' + uid).remove() + return + } // use a heatmap to fill - draw it behind the lines - if(contours.coloring === 'heatmap') { - if(trace.zauto && (trace.autocontour === false)) { - trace._input.zmin = trace.zmin = - contours.start - contours.size / 2; - trace._input.zmax = trace.zmax = - trace.zmin + pathinfo.length * contours.size; - } - - heatmapPlot(gd, plotinfo, [cd]); + if (contours.coloring === 'heatmap') { + if (trace.zauto && (trace.autocontour === false)) { + trace._input.zmin = trace.zmin = + contours.start - contours.size / 2 + trace._input.zmax = trace.zmax = + trace.zmin + pathinfo.length * contours.size } + + heatmapPlot(gd, plotinfo, [cd]) + } // in case this used to be a heatmap (or have heatmap fill) - else fullLayout._paper.selectAll('.hm' + uid).remove(); + else fullLayout._paper.selectAll('.hm' + uid).remove() - makeCrossings(pathinfo); - findAllPaths(pathinfo); + makeCrossings(pathinfo) + findAllPaths(pathinfo) - var leftedge = xa.c2p(x[0], true), - rightedge = xa.c2p(x[x.length - 1], true), - bottomedge = ya.c2p(y[0], true), - topedge = ya.c2p(y[y.length - 1], true), - perimeter = [ + var leftedge = xa.c2p(x[0], true), + rightedge = xa.c2p(x[x.length - 1], true), + bottomedge = ya.c2p(y[0], true), + topedge = ya.c2p(y[y.length - 1], true), + perimeter = [ [leftedge, topedge], [rightedge, topedge], [rightedge, bottomedge], [leftedge, bottomedge] - ]; + ] // draw everything - var plotGroup = makeContourGroup(plotinfo, cd, id); - makeBackground(plotGroup, perimeter, contours); - makeFills(plotGroup, pathinfo, perimeter, contours); - makeLines(plotGroup, pathinfo, contours); - clipGaps(plotGroup, plotinfo, cd[0], perimeter); + var plotGroup = makeContourGroup(plotinfo, cd, id) + makeBackground(plotGroup, perimeter, contours) + makeFills(plotGroup, pathinfo, perimeter, contours) + makeLines(plotGroup, pathinfo, contours) + clipGaps(plotGroup, plotinfo, cd[0], perimeter) } -function emptyPathinfo(contours, plotinfo, cd0) { - var cs = contours.size, - pathinfo = [], - end = endPlus(contours); +function emptyPathinfo (contours, plotinfo, cd0) { + var cs = contours.size, + pathinfo = [], + end = endPlus(contours) - for(var ci = contours.start; ci < end; ci += cs) { - pathinfo.push({ - level: ci, + for (var ci = contours.start; ci < end; ci += cs) { + pathinfo.push({ + level: ci, // all the cells with nontrivial marching index - crossings: {}, + crossings: {}, // starting points on the edges of the lattice for each contour - starts: [], + starts: [], // all unclosed paths (may have less items than starts, // if a path is closed by rounding) - edgepaths: [], + edgepaths: [], // all closed paths - paths: [], + paths: [], // store axes so we can convert to px - xaxis: plotinfo.xaxis, - yaxis: plotinfo.yaxis, + xaxis: plotinfo.xaxis, + yaxis: plotinfo.yaxis, // full data arrays to use for interpolation - x: cd0.x, - y: cd0.y, - z: cd0.z, - smoothing: cd0.trace.line.smoothing - }); - - if(pathinfo.length > 1000) { - Lib.warn('Too many contours, clipping at 1000', contours); - break; - } + x: cd0.x, + y: cd0.y, + z: cd0.z, + smoothing: cd0.trace.line.smoothing + }) + + if (pathinfo.length > 1000) { + Lib.warn('Too many contours, clipping at 1000', contours) + break } - return pathinfo; + } + return pathinfo } -function makeContourGroup(plotinfo, cd, id) { - var plotgroup = plotinfo.plot.select('.maplayer') +function makeContourGroup (plotinfo, cd, id) { + var plotgroup = plotinfo.plot.select('.maplayer') .selectAll('g.contour.' + id) - .data(cd); + .data(cd) - plotgroup.enter().append('g') + plotgroup.enter().append('g') .classed('contour', true) - .classed(id, true); + .classed(id, true) - plotgroup.exit().remove(); + plotgroup.exit().remove() - return plotgroup; + return plotgroup } -function makeBackground(plotgroup, perimeter, contours) { - var bggroup = plotgroup.selectAll('g.contourbg').data([0]); - bggroup.enter().append('g').classed('contourbg', true); +function makeBackground (plotgroup, perimeter, contours) { + var bggroup = plotgroup.selectAll('g.contourbg').data([0]) + bggroup.enter().append('g').classed('contourbg', true) - var bgfill = bggroup.selectAll('path') - .data(contours.coloring === 'fill' ? [0] : []); - bgfill.enter().append('path'); - bgfill.exit().remove(); - bgfill + var bgfill = bggroup.selectAll('path') + .data(contours.coloring === 'fill' ? [0] : []) + bgfill.enter().append('path') + bgfill.exit().remove() + bgfill .attr('d', 'M' + perimeter.join('L') + 'Z') - .style('stroke', 'none'); + .style('stroke', 'none') } -function makeFills(plotgroup, pathinfo, perimeter, contours) { - var fillgroup = plotgroup.selectAll('g.contourfill') - .data([0]); - fillgroup.enter().append('g') - .classed('contourfill', true); - - var fillitems = fillgroup.selectAll('path') - .data(contours.coloring === 'fill' ? pathinfo : []); - fillitems.enter().append('path'); - fillitems.exit().remove(); - fillitems.each(function(pi) { +function makeFills (plotgroup, pathinfo, perimeter, contours) { + var fillgroup = plotgroup.selectAll('g.contourfill') + .data([0]) + fillgroup.enter().append('g') + .classed('contourfill', true) + + var fillitems = fillgroup.selectAll('path') + .data(contours.coloring === 'fill' ? pathinfo : []) + fillitems.enter().append('path') + fillitems.exit().remove() + fillitems.each(function (pi) { // join all paths for this level together into a single path // first follow clockwise around the perimeter to close any open paths // if the whole perimeter is above this level, start with a path // enclosing the whole thing. With all that, the parity should mean // that we always fill everything above the contour, nothing below - var fullpath = joinAllPaths(pi, perimeter); + var fullpath = joinAllPaths(pi, perimeter) - if(!fullpath) d3.select(this).remove(); - else d3.select(this).attr('d', fullpath).style('stroke', 'none'); - }); + if (!fullpath) d3.select(this).remove() + else d3.select(this).attr('d', fullpath).style('stroke', 'none') + }) } -function joinAllPaths(pi, perimeter) { - var edgeVal2 = Math.min(pi.z[0][0], pi.z[0][1]), - fullpath = (pi.edgepaths.length || edgeVal2 <= pi.level) ? +function joinAllPaths (pi, perimeter) { + var edgeVal2 = Math.min(pi.z[0][0], pi.z[0][1]), + fullpath = (pi.edgepaths.length || edgeVal2 <= pi.level) ? '' : ('M' + perimeter.join('L') + 'Z'), - i = 0, - startsleft = pi.edgepaths.map(function(v, i) { return i; }), - newloop = true, - endpt, - newendpt, - cnt, - nexti, - possiblei, - addpath; - - function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01; } - function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01; } - function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01; } - function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01; } - - while(startsleft.length) { - addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing); - fullpath += newloop ? addpath : addpath.replace(/^M/, 'L'); - startsleft.splice(startsleft.indexOf(i), 1); - endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1]; - nexti = -1; + i = 0, + startsleft = pi.edgepaths.map(function (v, i) { return i }), + newloop = true, + endpt, + newendpt, + cnt, + nexti, + possiblei, + addpath + + function istop (pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01 } + function isbottom (pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01 } + function isleft (pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01 } + function isright (pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01 } + + while (startsleft.length) { + addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing) + fullpath += newloop ? addpath : addpath.replace(/^M/, 'L') + startsleft.splice(startsleft.indexOf(i), 1) + endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1] + nexti = -1 // now loop through sides, moving our endpoint until we find a new start - for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops - if(!endpt) { - Lib.log('Missing end?', i, pi); - break; - } - - if(istop(endpt) && !isright(endpt)) newendpt = perimeter[1]; // right top - else if(isleft(endpt)) newendpt = perimeter[0]; // left top - else if(isbottom(endpt)) newendpt = perimeter[3]; // right bottom - else if(isright(endpt)) newendpt = perimeter[2]; // left bottom - - for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) { - var ptNew = pi.edgepaths[possiblei][0]; + for (cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops + if (!endpt) { + Lib.log('Missing end?', i, pi) + break + } + + if (istop(endpt) && !isright(endpt)) newendpt = perimeter[1] // right top + else if (isleft(endpt)) newendpt = perimeter[0] // left top + else if (isbottom(endpt)) newendpt = perimeter[3] // right bottom + else if (isright(endpt)) newendpt = perimeter[2] // left bottom + + for (possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) { + var ptNew = pi.edgepaths[possiblei][0] // is ptNew on the (horz. or vert.) segment from endpt to newendpt? - if(Math.abs(endpt[0] - newendpt[0]) < 0.01) { - if(Math.abs(endpt[0] - ptNew[0]) < 0.01 && + if (Math.abs(endpt[0] - newendpt[0]) < 0.01) { + if (Math.abs(endpt[0] - ptNew[0]) < 0.01 && (ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) { - newendpt = ptNew; - nexti = possiblei; - } - } - else if(Math.abs(endpt[1] - newendpt[1]) < 0.01) { - if(Math.abs(endpt[1] - ptNew[1]) < 0.01 && + newendpt = ptNew + nexti = possiblei + } + } else if (Math.abs(endpt[1] - newendpt[1]) < 0.01) { + if (Math.abs(endpt[1] - ptNew[1]) < 0.01 && (ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) { - newendpt = ptNew; - nexti = possiblei; - } - } - else { - Lib.log('endpt to newendpt is not vert. or horz.', - endpt, newendpt, ptNew); - } - } - - endpt = newendpt; - - if(nexti >= 0) break; - fullpath += 'L' + newendpt; + newendpt = ptNew + nexti = possiblei + } + } else { + Lib.log('endpt to newendpt is not vert. or horz.', + endpt, newendpt, ptNew) } + } - if(nexti === pi.edgepaths.length) { - Lib.log('unclosed perimeter path'); - break; - } + endpt = newendpt - i = nexti; + if (nexti >= 0) break + fullpath += 'L' + newendpt + } + + if (nexti === pi.edgepaths.length) { + Lib.log('unclosed perimeter path') + break + } + + i = nexti // if we closed back on a loop we already included, // close it and start a new loop - newloop = (startsleft.indexOf(i) === -1); - if(newloop) { - i = startsleft[0]; - fullpath += 'Z'; - } + newloop = (startsleft.indexOf(i) === -1) + if (newloop) { + i = startsleft[0] + fullpath += 'Z' } + } // finally add the interior paths - for(i = 0; i < pi.paths.length; i++) { - fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing); - } + for (i = 0; i < pi.paths.length; i++) { + fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing) + } - return fullpath; + return fullpath } -function makeLines(plotgroup, pathinfo, contours) { - var smoothing = pathinfo[0].smoothing; - - var linegroup = plotgroup.selectAll('g.contourlevel') - .data(contours.showlines === false ? [] : pathinfo); - linegroup.enter().append('g') - .classed('contourlevel', true); - linegroup.exit().remove(); - - var opencontourlines = linegroup.selectAll('path.openline') - .data(function(d) { return d.edgepaths; }); - opencontourlines.enter().append('path') - .classed('openline', true); - opencontourlines.exit().remove(); - opencontourlines - .attr('d', function(d) { - return Drawing.smoothopen(d, smoothing); +function makeLines (plotgroup, pathinfo, contours) { + var smoothing = pathinfo[0].smoothing + + var linegroup = plotgroup.selectAll('g.contourlevel') + .data(contours.showlines === false ? [] : pathinfo) + linegroup.enter().append('g') + .classed('contourlevel', true) + linegroup.exit().remove() + + var opencontourlines = linegroup.selectAll('path.openline') + .data(function (d) { return d.edgepaths }) + opencontourlines.enter().append('path') + .classed('openline', true) + opencontourlines.exit().remove() + opencontourlines + .attr('d', function (d) { + return Drawing.smoothopen(d, smoothing) }) - .style('stroke-miterlimit', 1); - - var closedcontourlines = linegroup.selectAll('path.closedline') - .data(function(d) { return d.paths; }); - closedcontourlines.enter().append('path') - .classed('closedline', true); - closedcontourlines.exit().remove(); - closedcontourlines - .attr('d', function(d) { - return Drawing.smoothclosed(d, smoothing); + .style('stroke-miterlimit', 1) + + var closedcontourlines = linegroup.selectAll('path.closedline') + .data(function (d) { return d.paths }) + closedcontourlines.enter().append('path') + .classed('closedline', true) + closedcontourlines.exit().remove() + closedcontourlines + .attr('d', function (d) { + return Drawing.smoothclosed(d, smoothing) }) - .style('stroke-miterlimit', 1); + .style('stroke-miterlimit', 1) } -function clipGaps(plotGroup, plotinfo, cd0, perimeter) { - var clipId = 'clip' + cd0.trace.uid; +function clipGaps (plotGroup, plotinfo, cd0, perimeter) { + var clipId = 'clip' + cd0.trace.uid - var defs = plotinfo.plot.selectAll('defs') - .data([0]); - defs.enter().append('defs'); + var defs = plotinfo.plot.selectAll('defs') + .data([0]) + defs.enter().append('defs') - var clipPath = defs.selectAll('#' + clipId) - .data(cd0.trace.connectgaps ? [] : [0]); - clipPath.enter().append('clipPath').attr('id', clipId); - clipPath.exit().remove(); + var clipPath = defs.selectAll('#' + clipId) + .data(cd0.trace.connectgaps ? [] : [0]) + clipPath.enter().append('clipPath').attr('id', clipId) + clipPath.exit().remove() - if(cd0.trace.connectgaps === false) { - var clipPathInfo = { + if (cd0.trace.connectgaps === false) { + var clipPathInfo = { // fraction of the way from missing to present point // to draw the boundary. // if you make this 1 (or 1-epsilon) then a point in // a sea of missing data will disappear entirely. - level: 0.9, - crossings: {}, - starts: [], - edgepaths: [], - paths: [], - xaxis: plotinfo.xaxis, - yaxis: plotinfo.yaxis, - x: cd0.x, - y: cd0.y, + level: 0.9, + crossings: {}, + starts: [], + edgepaths: [], + paths: [], + xaxis: plotinfo.xaxis, + yaxis: plotinfo.yaxis, + x: cd0.x, + y: cd0.y, // 0 = no data, 1 = data - z: makeClipMask(cd0), - smoothing: 0 - }; - - makeCrossings([clipPathInfo]); - findAllPaths([clipPathInfo]); - var fullpath = joinAllPaths(clipPathInfo, perimeter); - - var path = clipPath.selectAll('path') - .data([0]); - path.enter().append('path'); - path.attr('d', fullpath); + z: makeClipMask(cd0), + smoothing: 0 } - else clipId = null; - plotGroup.call(Drawing.setClipUrl, clipId); - plotinfo.plot.selectAll('.hm' + cd0.trace.uid) - .call(Drawing.setClipUrl, clipId); + makeCrossings([clipPathInfo]) + findAllPaths([clipPathInfo]) + var fullpath = joinAllPaths(clipPathInfo, perimeter) + + var path = clipPath.selectAll('path') + .data([0]) + path.enter().append('path') + path.attr('d', fullpath) + } else clipId = null + + plotGroup.call(Drawing.setClipUrl, clipId) + plotinfo.plot.selectAll('.hm' + cd0.trace.uid) + .call(Drawing.setClipUrl, clipId) } -function makeClipMask(cd0) { - var empties = cd0.trace._emptypoints, - z = [], - m = cd0.z.length, - n = cd0.z[0].length, - i, - row = [], - emptyPoint; - - for(i = 0; i < n; i++) row.push(1); - for(i = 0; i < m; i++) z.push(row.slice()); - for(i = 0; i < empties.length; i++) { - emptyPoint = empties[i]; - z[emptyPoint[0]][emptyPoint[1]] = 0; - } +function makeClipMask (cd0) { + var empties = cd0.trace._emptypoints, + z = [], + m = cd0.z.length, + n = cd0.z[0].length, + i, + row = [], + emptyPoint + + for (i = 0; i < n; i++) row.push(1) + for (i = 0; i < m; i++) z.push(row.slice()) + for (i = 0; i < empties.length; i++) { + emptyPoint = empties[i] + z[emptyPoint[0]][emptyPoint[1]] = 0 + } // save this mask to determine whether to show this data in hover - cd0.zmask = z; - return z; + cd0.zmask = z + return z } diff --git a/src/traces/contour/style.js b/src/traces/contour/style.js index 3ce4e56c64c..d36eedb9d81 100644 --- a/src/traces/contour/style.js +++ b/src/traces/contour/style.js @@ -6,55 +6,53 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Drawing = require('../../components/drawing') +var heatmapStyle = require('../heatmap/style') -var Drawing = require('../../components/drawing'); -var heatmapStyle = require('../heatmap/style'); +var makeColorMap = require('./make_color_map') -var makeColorMap = require('./make_color_map'); +module.exports = function style (gd) { + var contours = d3.select(gd).selectAll('g.contour') + contours.style('opacity', function (d) { + return d.trace.opacity + }) -module.exports = function style(gd) { - var contours = d3.select(gd).selectAll('g.contour'); + contours.each(function (d) { + var c = d3.select(this), + trace = d.trace, + contours = trace.contours, + line = trace.line, + cs = contours.size || 1, + start = contours.start - contours.style('opacity', function(d) { - return d.trace.opacity; - }); + var colorMap = makeColorMap(trace) - contours.each(function(d) { - var c = d3.select(this), - trace = d.trace, - contours = trace.contours, - line = trace.line, - cs = contours.size || 1, - start = contours.start; - - var colorMap = makeColorMap(trace); - - c.selectAll('g.contourlevel').each(function(d) { - d3.select(this).selectAll('path') + c.selectAll('g.contourlevel').each(function (d) { + d3.select(this).selectAll('path') .call(Drawing.lineGroupStyle, line.width, contours.coloring === 'lines' ? colorMap(d.level) : line.color, - line.dash); - }); + line.dash) + }) - var firstFill; + var firstFill - c.selectAll('g.contourfill path') - .style('fill', function(d) { - if(firstFill === undefined) firstFill = d.level; - return colorMap(d.level + 0.5 * cs); - }); + c.selectAll('g.contourfill path') + .style('fill', function (d) { + if (firstFill === undefined) firstFill = d.level + return colorMap(d.level + 0.5 * cs) + }) - if(firstFill === undefined) firstFill = start; + if (firstFill === undefined) firstFill = start - c.selectAll('g.contourbg path') - .style('fill', colorMap(firstFill - 0.5 * cs)); - }); + c.selectAll('g.contourbg path') + .style('fill', colorMap(firstFill - 0.5 * cs)) + }) - heatmapStyle(gd); -}; + heatmapStyle(gd) +} diff --git a/src/traces/contour/style_defaults.js b/src/traces/contour/style_defaults.js index cd29ec6ccbe..93904292926 100644 --- a/src/traces/contour/style_defaults.js +++ b/src/traces/contour/style_defaults.js @@ -6,29 +6,27 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var colorscaleDefaults = require('../../components/colorscale/defaults') -var colorscaleDefaults = require('../../components/colorscale/defaults'); +module.exports = function handleStyleDefaults (traceIn, traceOut, coerce, layout) { + var coloring = coerce('contours.coloring') + var showLines + if (coloring === 'fill') showLines = coerce('contours.showlines') -module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, layout) { - var coloring = coerce('contours.coloring'); + if (showLines !== false) { + if (coloring !== 'lines') coerce('line.color', '#000') + coerce('line.width', 0.5) + coerce('line.dash') + } - var showLines; - if(coloring === 'fill') showLines = coerce('contours.showlines'); + coerce('line.smoothing') - if(showLines !== false) { - if(coloring !== 'lines') coerce('line.color', '#000'); - coerce('line.width', 0.5); - coerce('line.dash'); - } - - coerce('line.smoothing'); - - if((traceOut.contours || {}).coloring !== 'none') { - colorscaleDefaults( + if ((traceOut.contours || {}).coloring !== 'none') { + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} - ); - } -}; + ) + } +} diff --git a/src/traces/contourgl/convert.js b/src/traces/contourgl/convert.js index 2c3ef01984c..1f437ebc037 100644 --- a/src/traces/contourgl/convert.js +++ b/src/traces/contourgl/convert.js @@ -6,180 +6,178 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var createContour2D = require('gl-contour2d') +var createHeatmap2D = require('gl-heatmap2d') + +var Axes = require('../../plots/cartesian/axes') +var makeColorMap = require('../contour/make_color_map') +var str2RGBArray = require('../../lib/str2rgbarray') + +function Contour (scene, uid) { + this.scene = scene + this.uid = uid + this.type = 'contourgl' + + this.name = '' + this.hoverinfo = 'all' + + this.xData = [] + this.yData = [] + this.zData = [] + this.textLabels = [] + + this.idToIndex = [] + this.bounds = [0, 0, 0, 0] + + this.contourOptions = { + z: new Float32Array(0), + x: [], + y: [], + shape: [0, 0], + levels: [0], + levelColors: [0, 0, 0, 1], + lineWidth: 1 + } + this.contour = createContour2D(scene.glplot, this.contourOptions) + this.contour._trace = this + + this.heatmapOptions = { + z: new Float32Array(0), + x: [], + y: [], + shape: [0, 0], + colorLevels: [0], + colorValues: [0, 0, 0, 0] + } + this.heatmap = createHeatmap2D(scene.glplot, this.heatmapOptions) + this.heatmap._trace = this +} -'use strict'; - -var createContour2D = require('gl-contour2d'); -var createHeatmap2D = require('gl-heatmap2d'); - -var Axes = require('../../plots/cartesian/axes'); -var makeColorMap = require('../contour/make_color_map'); -var str2RGBArray = require('../../lib/str2rgbarray'); - - -function Contour(scene, uid) { - this.scene = scene; - this.uid = uid; - this.type = 'contourgl'; - - this.name = ''; - this.hoverinfo = 'all'; - - this.xData = []; - this.yData = []; - this.zData = []; - this.textLabels = []; - - this.idToIndex = []; - this.bounds = [0, 0, 0, 0]; - - this.contourOptions = { - z: new Float32Array(0), - x: [], - y: [], - shape: [0, 0], - levels: [0], - levelColors: [0, 0, 0, 1], - lineWidth: 1 - }; - this.contour = createContour2D(scene.glplot, this.contourOptions); - this.contour._trace = this; - - this.heatmapOptions = { - z: new Float32Array(0), - x: [], - y: [], - shape: [0, 0], - colorLevels: [0], - colorValues: [0, 0, 0, 0] - }; - this.heatmap = createHeatmap2D(scene.glplot, this.heatmapOptions); - this.heatmap._trace = this; +var proto = Contour.prototype + +proto.handlePick = function (pickResult) { + var options = this.heatmapOptions, + shape = options.shape, + index = pickResult.pointId, + xIndex = index % shape[0], + yIndex = Math.floor(index / shape[0]), + zIndex = index + + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: [ + options.x[xIndex], + options.y[yIndex], + options.z[zIndex] + ], + textLabel: this.textLabels[index], + name: this.name, + pointIndex: [xIndex, yIndex], + hoverinfo: this.hoverinfo + } } -var proto = Contour.prototype; - -proto.handlePick = function(pickResult) { - var options = this.heatmapOptions, - shape = options.shape, - index = pickResult.pointId, - xIndex = index % shape[0], - yIndex = Math.floor(index / shape[0]), - zIndex = index; - - return { - trace: this, - dataCoord: pickResult.dataCoord, - traceCoord: [ - options.x[xIndex], - options.y[yIndex], - options.z[zIndex] - ], - textLabel: this.textLabels[index], - name: this.name, - pointIndex: [xIndex, yIndex], - hoverinfo: this.hoverinfo - }; -}; - -proto.update = function(fullTrace, calcTrace) { - var calcPt = calcTrace[0]; - - this.name = fullTrace.name; - this.hoverinfo = fullTrace.hoverinfo; +proto.update = function (fullTrace, calcTrace) { + var calcPt = calcTrace[0] + + this.name = fullTrace.name + this.hoverinfo = fullTrace.hoverinfo // convert z from 2D -> 1D - var z = calcPt.z, - rowLen = z[0].length, - colLen = z.length, - colorOptions; + var z = calcPt.z, + rowLen = z[0].length, + colLen = z.length, + colorOptions - this.contourOptions.z = flattenZ(z, rowLen, colLen); - this.heatmapOptions.z = [].concat.apply([], z); + this.contourOptions.z = flattenZ(z, rowLen, colLen) + this.heatmapOptions.z = [].concat.apply([], z) - this.contourOptions.shape = this.heatmapOptions.shape = [rowLen, colLen]; + this.contourOptions.shape = this.heatmapOptions.shape = [rowLen, colLen] - this.contourOptions.x = this.heatmapOptions.x = calcPt.x; - this.contourOptions.y = this.heatmapOptions.y = calcPt.y; + this.contourOptions.x = this.heatmapOptions.x = calcPt.x + this.contourOptions.y = this.heatmapOptions.y = calcPt.y // pass on fill information - if(fullTrace.contours.coloring === 'fill') { - colorOptions = convertColorScale(fullTrace, {fill: true}); - this.contourOptions.levels = colorOptions.levels.slice(1); + if (fullTrace.contours.coloring === 'fill') { + colorOptions = convertColorScale(fullTrace, {fill: true}) + this.contourOptions.levels = colorOptions.levels.slice(1) // though gl-contour2d automatically defaults to a transparent layer for the last // band color, it's set manually here in case the gl-contour2 API changes - this.contourOptions.fillColors = colorOptions.levelColors; - this.contourOptions.levelColors = [].concat.apply([], this.contourOptions.levels.map(function() { - return [0.25, 0.25, 0.25, 1.0]; - })); - } else { - colorOptions = convertColorScale(fullTrace, {fill: false}); - this.contourOptions.levels = colorOptions.levels; - this.contourOptions.levelColors = colorOptions.levelColors; - } + this.contourOptions.fillColors = colorOptions.levelColors + this.contourOptions.levelColors = [].concat.apply([], this.contourOptions.levels.map(function () { + return [0.25, 0.25, 0.25, 1.0] + })) + } else { + colorOptions = convertColorScale(fullTrace, {fill: false}) + this.contourOptions.levels = colorOptions.levels + this.contourOptions.levelColors = colorOptions.levelColors + } // convert text from 2D -> 1D - this.textLabels = [].concat.apply([], fullTrace.text); + this.textLabels = [].concat.apply([], fullTrace.text) - this.contour.update(this.contourOptions); - this.heatmap.update(this.heatmapOptions); + this.contour.update(this.contourOptions) + this.heatmap.update(this.heatmapOptions) // expand axes - Axes.expand(this.scene.xaxis, calcPt.x); - Axes.expand(this.scene.yaxis, calcPt.y); -}; - -proto.dispose = function() { - this.contour.dispose(); - this.heatmap.dispose(); -}; - -function flattenZ(zIn, rowLen, colLen) { - var zOut = new Float32Array(rowLen * colLen); - var pt = 0; - - for(var i = 0; i < rowLen; i++) { - for(var j = 0; j < colLen; j++) { - zOut[pt++] = zIn[j][i]; - } + Axes.expand(this.scene.xaxis, calcPt.x) + Axes.expand(this.scene.yaxis, calcPt.y) +} + +proto.dispose = function () { + this.contour.dispose() + this.heatmap.dispose() +} + +function flattenZ (zIn, rowLen, colLen) { + var zOut = new Float32Array(rowLen * colLen) + var pt = 0 + + for (var i = 0; i < rowLen; i++) { + for (var j = 0; j < colLen; j++) { + zOut[pt++] = zIn[j][i] } + } - return zOut; + return zOut } -function convertColorScale(fullTrace, options) { - var contours = fullTrace.contours, - start = contours.start, - end = contours.end, - cs = contours.size || 1, - fill = options.fill; +function convertColorScale (fullTrace, options) { + var contours = fullTrace.contours, + start = contours.start, + end = contours.end, + cs = contours.size || 1, + fill = options.fill - var colorMap = makeColorMap(fullTrace); + var colorMap = makeColorMap(fullTrace) - var N = Math.floor((end - start) / cs) + (fill ? 2 : 1), // for K thresholds (contour linees) there are K+1 areas - levels = new Array(N), - levelColors = new Array(4 * N); + var N = Math.floor((end - start) / cs) + (fill ? 2 : 1), // for K thresholds (contour linees) there are K+1 areas + levels = new Array(N), + levelColors = new Array(4 * N) - for(var i = 0; i < N; i++) { - var level = levels[i] = start + cs * (i) - (fill ? cs / 2 : 0); // in case of fill, use band midpoint - var color = str2RGBArray(colorMap(level)); + for (var i = 0; i < N; i++) { + var level = levels[i] = start + cs * (i) - (fill ? cs / 2 : 0) // in case of fill, use band midpoint + var color = str2RGBArray(colorMap(level)) - for(var j = 0; j < 4; j++) { - levelColors[(4 * i) + j] = color[j]; - } + for (var j = 0; j < 4; j++) { + levelColors[(4 * i) + j] = color[j] } + } - return { - levels: levels, - levelColors: levelColors - }; + return { + levels: levels, + levelColors: levelColors + } } -function createContour(scene, fullTrace, calcTrace) { - var plot = new Contour(scene, fullTrace.uid); - plot.update(fullTrace, calcTrace); +function createContour (scene, fullTrace, calcTrace) { + var plot = new Contour(scene, fullTrace.uid) + plot.update(fullTrace, calcTrace) - return plot; + return plot } -module.exports = createContour; +module.exports = createContour diff --git a/src/traces/contourgl/index.js b/src/traces/contourgl/index.js index ac4fca3b72d..b7c329dcb52 100644 --- a/src/traces/contourgl/index.js +++ b/src/traces/contourgl/index.js @@ -6,26 +6,25 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var ContourGl = {} -var ContourGl = {}; +ContourGl.attributes = require('../contour/attributes') +ContourGl.supplyDefaults = require('../contour/defaults') +ContourGl.colorbar = require('../contour/colorbar') -ContourGl.attributes = require('../contour/attributes'); -ContourGl.supplyDefaults = require('../contour/defaults'); -ContourGl.colorbar = require('../contour/colorbar'); +ContourGl.calc = require('../contour/calc') +ContourGl.plot = require('./convert') -ContourGl.calc = require('../contour/calc'); -ContourGl.plot = require('./convert'); - -ContourGl.moduleType = 'trace'; -ContourGl.name = 'contourgl'; -ContourGl.basePlotModule = require('../../plots/gl2d'); -ContourGl.categories = ['gl2d', '2dMap']; +ContourGl.moduleType = 'trace' +ContourGl.name = 'contourgl' +ContourGl.basePlotModule = require('../../plots/gl2d') +ContourGl.categories = ['gl2d', '2dMap'] ContourGl.meta = { - description: [ - 'WebGL contour (beta)' - ].join(' ') -}; + description: [ + 'WebGL contour (beta)' + ].join(' ') +} -module.exports = ContourGl; +module.exports = ContourGl diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 2b4d98ce245..5a65e181183 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -6,93 +6,93 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var scatterAttrs = require('../scatter/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat module.exports = extendFlat({}, { - z: { - valType: 'data_array', - description: 'Sets the z data.' - }, - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, + z: { + valType: 'data_array', + description: 'Sets the z data.' + }, + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, - text: { - valType: 'data_array', - description: 'Sets the text elements associated with each z value.' - }, - transpose: { - valType: 'boolean', - dflt: false, - role: 'info', - description: 'Transposes the z data.' - }, - xtype: { - valType: 'enumerated', - values: ['array', 'scaled'], - role: 'info', - description: [ - 'If *array*, the heatmap\'s x coordinates are given by *x*', - '(the default behavior when `x` is provided).', - 'If *scaled*, the heatmap\'s x coordinates are given by *x0* and *dx*', - '(the default behavior when `x` is not provided).' - ].join(' ') - }, - ytype: { - valType: 'enumerated', - values: ['array', 'scaled'], - role: 'info', - description: [ - 'If *array*, the heatmap\'s y coordinates are given by *y*', - '(the default behavior when `y` is provided)', - 'If *scaled*, the heatmap\'s y coordinates are given by *y0* and *dy*', - '(the default behavior when `y` is not provided)' - ].join(' ') - }, - zsmooth: { - valType: 'enumerated', - values: ['fast', 'best', false], - dflt: false, - role: 'style', - description: [ - 'Picks a smoothing algorithm use to smooth `z` data.' - ].join(' ') - }, - connectgaps: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data are filled in.' - ].join(' ') - }, - xgap: { - valType: 'number', - dflt: 0, - min: 0, - role: 'style', - description: 'Sets the horizontal gap (in pixels) between bricks.' - }, - ygap: { - valType: 'number', - dflt: 0, - min: 0, - role: 'style', - description: 'Sets the vertical gap (in pixels) between bricks.' - }, + text: { + valType: 'data_array', + description: 'Sets the text elements associated with each z value.' + }, + transpose: { + valType: 'boolean', + dflt: false, + role: 'info', + description: 'Transposes the z data.' + }, + xtype: { + valType: 'enumerated', + values: ['array', 'scaled'], + role: 'info', + description: [ + 'If *array*, the heatmap\'s x coordinates are given by *x*', + '(the default behavior when `x` is provided).', + 'If *scaled*, the heatmap\'s x coordinates are given by *x0* and *dx*', + '(the default behavior when `x` is not provided).' + ].join(' ') + }, + ytype: { + valType: 'enumerated', + values: ['array', 'scaled'], + role: 'info', + description: [ + 'If *array*, the heatmap\'s y coordinates are given by *y*', + '(the default behavior when `y` is provided)', + 'If *scaled*, the heatmap\'s y coordinates are given by *y0* and *dy*', + '(the default behavior when `y` is not provided)' + ].join(' ') + }, + zsmooth: { + valType: 'enumerated', + values: ['fast', 'best', false], + dflt: false, + role: 'style', + description: [ + 'Picks a smoothing algorithm use to smooth `z` data.' + ].join(' ') + }, + connectgaps: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.' + ].join(' ') + }, + xgap: { + valType: 'number', + dflt: 0, + min: 0, + role: 'style', + description: 'Sets the horizontal gap (in pixels) between bricks.' + }, + ygap: { + valType: 'number', + dflt: 0, + min: 0, + role: 'style', + description: 'Sets the vertical gap (in pixels) between bricks.' + } }, colorscaleAttrs, { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}) }, { colorbar: colorbarAttrs } -); +) diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index 7b38ba77620..96593f8507e 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -6,135 +6,131 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); - -var histogram2dCalc = require('../histogram2d/calc'); -var colorscaleCalc = require('../../components/colorscale/calc'); -var hasColumns = require('./has_columns'); -var convertColumnXYZ = require('./convert_column_xyz'); -var maxRowLength = require('./max_row_length'); -var clean2dArray = require('./clean_2d_array'); -var interp2d = require('./interp2d'); -var findEmpties = require('./find_empties'); -var makeBoundArray = require('./make_bound_array'); - - -module.exports = function calc(gd, trace) { +'use strict' + +var Registry = require('../../registry') +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') + +var histogram2dCalc = require('../histogram2d/calc') +var colorscaleCalc = require('../../components/colorscale/calc') +var hasColumns = require('./has_columns') +var convertColumnXYZ = require('./convert_column_xyz') +var maxRowLength = require('./max_row_length') +var clean2dArray = require('./clean_2d_array') +var interp2d = require('./interp2d') +var findEmpties = require('./find_empties') +var makeBoundArray = require('./make_bound_array') + +module.exports = function calc (gd, trace) { // prepare the raw data // run makeCalcdata on x and y even for heatmaps, in case of category mappings - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'), - isContour = Registry.traceIs(trace, 'contour'), - isHist = Registry.traceIs(trace, 'histogram'), - isGL2D = Registry.traceIs(trace, 'gl2d'), - zsmooth = isContour ? 'best' : trace.zsmooth, - x, - x0, - dx, - y, - y0, - dy, - z, - i; + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + ya = Axes.getFromId(gd, trace.yaxis || 'y'), + isContour = Registry.traceIs(trace, 'contour'), + isHist = Registry.traceIs(trace, 'histogram'), + isGL2D = Registry.traceIs(trace, 'gl2d'), + zsmooth = isContour ? 'best' : trace.zsmooth, + x, + x0, + dx, + y, + y0, + dy, + z, + i // cancel minimum tick spacings (only applies to bars and boxes) - xa._minDtick = 0; - ya._minDtick = 0; - - if(isHist) { - var binned = histogram2dCalc(gd, trace); - x = binned.x; - x0 = binned.x0; - dx = binned.dx; - y = binned.y; - y0 = binned.y0; - dy = binned.dy; - z = binned.z; + xa._minDtick = 0 + ya._minDtick = 0 + + if (isHist) { + var binned = histogram2dCalc(gd, trace) + x = binned.x + x0 = binned.x0 + dx = binned.dx + y = binned.y + y0 = binned.y0 + dy = binned.dy + z = binned.z + } else { + if (hasColumns(trace)) convertColumnXYZ(trace, xa, ya) + + x = trace.x ? xa.makeCalcdata(trace, 'x') : [] + y = trace.y ? ya.makeCalcdata(trace, 'y') : [] + x0 = trace.x0 || 0 + dx = trace.dx || 1 + y0 = trace.y0 || 0 + dy = trace.dy || 1 + + z = clean2dArray(trace.z, trace.transpose) + + if (isContour || trace.connectgaps) { + trace._emptypoints = findEmpties(z) + trace._interpz = interp2d(z, trace._emptypoints, trace._interpz) } - else { - if(hasColumns(trace)) convertColumnXYZ(trace, xa, ya); - - x = trace.x ? xa.makeCalcdata(trace, 'x') : []; - y = trace.y ? ya.makeCalcdata(trace, 'y') : []; - x0 = trace.x0 || 0; - dx = trace.dx || 1; - y0 = trace.y0 || 0; - dy = trace.dy || 1; + } - z = clean2dArray(trace.z, trace.transpose); - - if(isContour || trace.connectgaps) { - trace._emptypoints = findEmpties(z); - trace._interpz = interp2d(z, trace._emptypoints, trace._interpz); - } - } - - function noZsmooth(msg) { - zsmooth = trace._input.zsmooth = trace.zsmooth = false; - Lib.notifier('cannot fast-zsmooth: ' + msg); - } + function noZsmooth (msg) { + zsmooth = trace._input.zsmooth = trace.zsmooth = false + Lib.notifier('cannot fast-zsmooth: ' + msg) + } // check whether we really can smooth (ie all boxes are about the same size) - if(zsmooth === 'fast') { - if(xa.type === 'log' || ya.type === 'log') { - noZsmooth('log axis found'); + if (zsmooth === 'fast') { + if (xa.type === 'log' || ya.type === 'log') { + noZsmooth('log axis found') + } else if (!isHist) { + if (x.length) { + var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1), + maxErrX = Math.abs(avgdx / 100) + for (i = 0; i < x.length - 1; i++) { + if (Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) { + noZsmooth('x scale is not linear') + break + } } - else if(!isHist) { - if(x.length) { - var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1), - maxErrX = Math.abs(avgdx / 100); - for(i = 0; i < x.length - 1; i++) { - if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) { - noZsmooth('x scale is not linear'); - break; - } - } - } - if(y.length && zsmooth === 'fast') { - var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1), - maxErrY = Math.abs(avgdy / 100); - for(i = 0; i < y.length - 1; i++) { - if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) { - noZsmooth('y scale is not linear'); - break; - } - } - } + } + if (y.length && zsmooth === 'fast') { + var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1), + maxErrY = Math.abs(avgdy / 100) + for (i = 0; i < y.length - 1; i++) { + if (Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) { + noZsmooth('y scale is not linear') + break + } } + } } + } // create arrays of brick boundaries, to be used by autorange and heatmap.plot - var xlen = maxRowLength(z), - xIn = trace.xtype === 'scaled' ? '' : x, - xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa), - yIn = trace.ytype === 'scaled' ? '' : y, - yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya); + var xlen = maxRowLength(z), + xIn = trace.xtype === 'scaled' ? '' : x, + xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa), + yIn = trace.ytype === 'scaled' ? '' : y, + yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya) // handled in gl2d convert step - if(!isGL2D) { - Axes.expand(xa, xArray); - Axes.expand(ya, yArray); - } + if (!isGL2D) { + Axes.expand(xa, xArray) + Axes.expand(ya, yArray) + } - var cd0 = {x: xArray, y: yArray, z: z}; + var cd0 = {x: xArray, y: yArray, z: z} // auto-z and autocolorscale if applicable - colorscaleCalc(trace, z, '', 'z'); - - if(isContour && trace.contours && trace.contours.coloring === 'heatmap') { - var dummyTrace = { - type: trace.type === 'contour' ? 'heatmap' : 'histogram2d', - xcalendar: trace.xcalendar, - ycalendar: trace.ycalendar - }; - cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa); - cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya); + colorscaleCalc(trace, z, '', 'z') + + if (isContour && trace.contours && trace.contours.coloring === 'heatmap') { + var dummyTrace = { + type: trace.type === 'contour' ? 'heatmap' : 'histogram2d', + xcalendar: trace.xcalendar, + ycalendar: trace.ycalendar } + cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa) + cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya) + } - return [cd0]; -}; + return [cd0] +} diff --git a/src/traces/heatmap/clean_2d_array.js b/src/traces/heatmap/clean_2d_array.js index 91a37380094..36935b16015 100644 --- a/src/traces/heatmap/clean_2d_array.js +++ b/src/traces/heatmap/clean_2d_array.js @@ -6,38 +6,37 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var isNumeric = require('fast-isnumeric'); - -module.exports = function clean2dArray(zOld, transpose) { - var rowlen, collen, getCollen, old2new, i, j; - - function cleanZvalue(v) { - if(!isNumeric(v)) return undefined; - return +v; - } - - if(transpose) { - rowlen = 0; - for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length); - if(rowlen === 0) return false; - getCollen = function(zOld) { return zOld.length; }; - old2new = function(zOld, i, j) { return zOld[j][i]; }; - } - else { - rowlen = zOld.length; - getCollen = function(zOld, i) { return zOld[i].length; }; - old2new = function(zOld, i, j) { return zOld[i][j]; }; - } - - var zNew = new Array(rowlen); - - for(i = 0; i < rowlen; i++) { - collen = getCollen(zOld, i); - zNew[i] = new Array(collen); - for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(old2new(zOld, i, j)); - } - - return zNew; -}; +'use strict' + +var isNumeric = require('fast-isnumeric') + +module.exports = function clean2dArray (zOld, transpose) { + var rowlen, collen, getCollen, old2new, i, j + + function cleanZvalue (v) { + if (!isNumeric(v)) return undefined + return +v + } + + if (transpose) { + rowlen = 0 + for (i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length) + if (rowlen === 0) return false + getCollen = function (zOld) { return zOld.length } + old2new = function (zOld, i, j) { return zOld[j][i] } + } else { + rowlen = zOld.length + getCollen = function (zOld, i) { return zOld[i].length } + old2new = function (zOld, i, j) { return zOld[i][j] } + } + + var zNew = new Array(rowlen) + + for (i = 0; i < rowlen; i++) { + collen = getCollen(zOld, i) + zNew[i] = new Array(collen) + for (j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(old2new(zOld, i, j)) + } + + return zNew +} diff --git a/src/traces/heatmap/colorbar.js b/src/traces/heatmap/colorbar.js index 147d2672b21..7f2125a271b 100644 --- a/src/traces/heatmap/colorbar.js +++ b/src/traces/heatmap/colorbar.js @@ -6,44 +6,42 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Plots = require('../../plots/plots') +var Colorscale = require('../../components/colorscale') +var drawColorbar = require('../../components/colorbar/draw') -var Lib = require('../../lib'); -var Plots = require('../../plots/plots'); -var Colorscale = require('../../components/colorscale'); -var drawColorbar = require('../../components/colorbar/draw'); +module.exports = function colorbar (gd, cd) { + var trace = cd[0].trace, + cbId = 'cb' + trace.uid, + zmin = trace.zmin, + zmax = trace.zmax + if (!isNumeric(zmin)) zmin = Lib.aggNums(Math.min, null, trace.z) + if (!isNumeric(zmax)) zmax = Lib.aggNums(Math.max, null, trace.z) -module.exports = function colorbar(gd, cd) { - var trace = cd[0].trace, - cbId = 'cb' + trace.uid, - zmin = trace.zmin, - zmax = trace.zmax; + gd._fullLayout._infolayer.selectAll('.' + cbId).remove() - if(!isNumeric(zmin)) zmin = Lib.aggNums(Math.min, null, trace.z); - if(!isNumeric(zmax)) zmax = Lib.aggNums(Math.max, null, trace.z); + if (!trace.showscale) { + Plots.autoMargin(gd, cbId) + return + } - gd._fullLayout._infolayer.selectAll('.' + cbId).remove(); - - if(!trace.showscale) { - Plots.autoMargin(gd, cbId); - return; - } - - var cb = cd[0].t.cb = drawColorbar(gd, cbId); - var sclFunc = Colorscale.makeColorScaleFunc( + var cb = cd[0].t.cb = drawColorbar(gd, cbId) + var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( trace.colorscale, zmin, zmax ), { noNumericCheck: true } - ); + ) - cb.fillcolor(sclFunc) + cb.fillcolor(sclFunc) .filllevels({start: zmin, end: zmax, size: (zmax - zmin) / 254}) - .options(trace.colorbar)(); -}; + .options(trace.colorbar)() +} diff --git a/src/traces/heatmap/convert_column_xyz.js b/src/traces/heatmap/convert_column_xyz.js index c118de79f12..93826a4113d 100644 --- a/src/traces/heatmap/convert_column_xyz.js +++ b/src/traces/heatmap/convert_column_xyz.js @@ -6,52 +6,50 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +module.exports = function convertColumnXYZ (trace, xa, ya) { + var xCol = trace.x.slice(), + yCol = trace.y.slice(), + zCol = trace.z, + textCol = trace.text, + colLen = Math.min(xCol.length, yCol.length, zCol.length), + hasColumnText = (textCol !== undefined && !Array.isArray(textCol[0])), + xcalendar = trace.xcalendar, + ycalendar = trace.ycalendar + var i -module.exports = function convertColumnXYZ(trace, xa, ya) { - var xCol = trace.x.slice(), - yCol = trace.y.slice(), - zCol = trace.z, - textCol = trace.text, - colLen = Math.min(xCol.length, yCol.length, zCol.length), - hasColumnText = (textCol !== undefined && !Array.isArray(textCol[0])), - xcalendar = trace.xcalendar, - ycalendar = trace.ycalendar; + if (colLen < xCol.length) xCol = xCol.slice(0, colLen) + if (colLen < yCol.length) yCol = yCol.slice(0, colLen) - var i; + for (i = 0; i < colLen; i++) { + xCol[i] = xa.d2c(xCol[i], 0, xcalendar) + yCol[i] = ya.d2c(yCol[i], 0, ycalendar) + } - if(colLen < xCol.length) xCol = xCol.slice(0, colLen); - if(colLen < yCol.length) yCol = yCol.slice(0, colLen); + var xColdv = Lib.distinctVals(xCol), + x = xColdv.vals, + yColdv = Lib.distinctVals(yCol), + y = yColdv.vals, + z = Lib.init2dArray(y.length, x.length) - for(i = 0; i < colLen; i++) { - xCol[i] = xa.d2c(xCol[i], 0, xcalendar); - yCol[i] = ya.d2c(yCol[i], 0, ycalendar); - } + var ix, iy, text - var xColdv = Lib.distinctVals(xCol), - x = xColdv.vals, - yColdv = Lib.distinctVals(yCol), - y = yColdv.vals, - z = Lib.init2dArray(y.length, x.length); + if (hasColumnText) text = Lib.init2dArray(y.length, x.length) - var ix, iy, text; + for (i = 0; i < colLen; i++) { + ix = Lib.findBin(xCol[i] + xColdv.minDiff / 2, x) + iy = Lib.findBin(yCol[i] + yColdv.minDiff / 2, y) - if(hasColumnText) text = Lib.init2dArray(y.length, x.length); + z[iy][ix] = zCol[i] + if (hasColumnText) text[iy][ix] = textCol[i] + } - for(i = 0; i < colLen; i++) { - ix = Lib.findBin(xCol[i] + xColdv.minDiff / 2, x); - iy = Lib.findBin(yCol[i] + yColdv.minDiff / 2, y); - - z[iy][ix] = zCol[i]; - if(hasColumnText) text[iy][ix] = textCol[i]; - } - - trace.x = x; - trace.y = y; - trace.z = z; - if(hasColumnText) trace.text = text; -}; + trace.x = x + trace.y = y + trace.z = z + if (hasColumnText) trace.text = text +} diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js index 3dbbaa0f380..4ef911647ae 100644 --- a/src/traces/heatmap/defaults.js +++ b/src/traces/heatmap/defaults.js @@ -6,38 +6,36 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var hasColumns = require('./has_columns') +var handleXYZDefaults = require('./xyz_defaults') +var colorscaleDefaults = require('../../components/colorscale/defaults') +var attributes = require('./attributes') -var hasColumns = require('./has_columns'); -var handleXYZDefaults = require('./xyz_defaults'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var len = handleXYZDefaults(traceIn, traceOut, coerce, layout) + if (!len) { + traceOut.visible = false + return + } -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + coerce('text') - var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - - var zsmooth = coerce('zsmooth'); - if(zsmooth === false) { + var zsmooth = coerce('zsmooth') + if (zsmooth === false) { // ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect. - coerce('xgap'); - coerce('ygap'); - } + coerce('xgap') + coerce('ygap') + } - coerce('connectgaps', hasColumns(traceOut) && (traceOut.zsmooth !== false)); + coerce('connectgaps', hasColumns(traceOut) && (traceOut.zsmooth !== false)) - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); -}; + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}) +} diff --git a/src/traces/heatmap/find_empties.js b/src/traces/heatmap/find_empties.js index 243f566bc05..322a8ad5578 100644 --- a/src/traces/heatmap/find_empties.js +++ b/src/traces/heatmap/find_empties.js @@ -6,9 +6,9 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var maxRowLength = require('./max_row_length'); +var maxRowLength = require('./max_row_length') /* Return a list of empty points in 2D array z * each empty point z[i][j] gives an array [i, j, neighborCount] @@ -17,88 +17,87 @@ var maxRowLength = require('./max_row_length'); * if no neighbors exist, we iteratively look for neighbors that HAVE * neighbors, and add a fractional neighborCount */ -module.exports = function findEmpties(z) { - var empties = [], - neighborHash = {}, - noNeighborList = [], - nextRow = z[0], - row = [], - blank = [0, 0, 0], - rowLength = maxRowLength(z), - prevRow, - i, - j, - thisPt, - p, - neighborCount, - newNeighborHash, - foundNewNeighbors; +module.exports = function findEmpties (z) { + var empties = [], + neighborHash = {}, + noNeighborList = [], + nextRow = z[0], + row = [], + blank = [0, 0, 0], + rowLength = maxRowLength(z), + prevRow, + i, + j, + thisPt, + p, + neighborCount, + newNeighborHash, + foundNewNeighbors - for(i = 0; i < z.length; i++) { - prevRow = row; - row = nextRow; - nextRow = z[i + 1] || []; - for(j = 0; j < rowLength; j++) { - if(row[j] === undefined) { - neighborCount = (row[j - 1] !== undefined ? 1 : 0) + + for (i = 0; i < z.length; i++) { + prevRow = row + row = nextRow + nextRow = z[i + 1] || [] + for (j = 0; j < rowLength; j++) { + if (row[j] === undefined) { + neighborCount = (row[j - 1] !== undefined ? 1 : 0) + (row[j + 1] !== undefined ? 1 : 0) + (prevRow[j] !== undefined ? 1 : 0) + - (nextRow[j] !== undefined ? 1 : 0); + (nextRow[j] !== undefined ? 1 : 0) - if(neighborCount) { + if (neighborCount) { // for this purpose, don't count off-the-edge points // as undefined neighbors - if(i === 0) neighborCount++; - if(j === 0) neighborCount++; - if(i === z.length - 1) neighborCount++; - if(j === row.length - 1) neighborCount++; + if (i === 0) neighborCount++ + if (j === 0) neighborCount++ + if (i === z.length - 1) neighborCount++ + if (j === row.length - 1) neighborCount++ // if all neighbors that could exist do, we don't // need this for finding farther neighbors - if(neighborCount < 4) { - neighborHash[[i, j]] = [i, j, neighborCount]; - } + if (neighborCount < 4) { + neighborHash[[i, j]] = [i, j, neighborCount] + } - empties.push([i, j, neighborCount]); - } - else noNeighborList.push([i, j]); - } - } + empties.push([i, j, neighborCount]) + } else noNeighborList.push([i, j]) + } } + } - while(noNeighborList.length) { - newNeighborHash = {}; - foundNewNeighbors = false; + while (noNeighborList.length) { + newNeighborHash = {} + foundNewNeighbors = false // look for cells that now have neighbors but didn't before - for(p = noNeighborList.length - 1; p >= 0; p--) { - thisPt = noNeighborList[p]; - i = thisPt[0]; - j = thisPt[1]; + for (p = noNeighborList.length - 1; p >= 0; p--) { + thisPt = noNeighborList[p] + i = thisPt[0] + j = thisPt[1] - neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] + + neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] + (neighborHash[[i + 1, j]] || blank)[2] + (neighborHash[[i, j - 1]] || blank)[2] + - (neighborHash[[i, j + 1]] || blank)[2]) / 20; + (neighborHash[[i, j + 1]] || blank)[2]) / 20 - if(neighborCount) { - newNeighborHash[thisPt] = [i, j, neighborCount]; - noNeighborList.splice(p, 1); - foundNewNeighbors = true; - } - } + if (neighborCount) { + newNeighborHash[thisPt] = [i, j, neighborCount] + noNeighborList.splice(p, 1) + foundNewNeighbors = true + } + } - if(!foundNewNeighbors) { - throw 'findEmpties iterated with no new neighbors'; - } + if (!foundNewNeighbors) { + throw 'findEmpties iterated with no new neighbors' + } // put these new cells into the main neighbor list - for(thisPt in newNeighborHash) { - neighborHash[thisPt] = newNeighborHash[thisPt]; - empties.push(newNeighborHash[thisPt]); - } + for (thisPt in newNeighborHash) { + neighborHash[thisPt] = newNeighborHash[thisPt] + empties.push(newNeighborHash[thisPt]) } + } // sort the full list in descending order of neighbor count - return empties.sort(function(a, b) { return b[2] - a[2]; }); -}; + return empties.sort(function (a, b) { return b[2] - a[2] }) +} diff --git a/src/traces/heatmap/has_columns.js b/src/traces/heatmap/has_columns.js index f8909d1249f..d62a14f5428 100644 --- a/src/traces/heatmap/has_columns.js +++ b/src/traces/heatmap/has_columns.js @@ -6,9 +6,8 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -module.exports = function(trace) { - return !Array.isArray(trace.z[0]); -}; +module.exports = function (trace) { + return !Array.isArray(trace.z[0]) +} diff --git a/src/traces/heatmap/hover.js b/src/traces/heatmap/hover.js index 89a229a4e10..49a73ec97ca 100644 --- a/src/traces/heatmap/hover.js +++ b/src/traces/heatmap/hover.js @@ -6,111 +6,105 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Fx = require('../../plots/cartesian/graph_interact') +var Lib = require('../../lib') -var Fx = require('../../plots/cartesian/graph_interact'); -var Lib = require('../../lib'); +var MAXDIST = require('../../plots/cartesian/constants').MAXDIST -var MAXDIST = require('../../plots/cartesian/constants').MAXDIST; - - -module.exports = function hoverPoints(pointData, xval, yval, hovermode, contour) { +module.exports = function hoverPoints (pointData, xval, yval, hovermode, contour) { // never let a heatmap override another type as closest point - if(pointData.distance < MAXDIST) return; + if (pointData.distance < MAXDIST) return - var cd0 = pointData.cd[0], - trace = cd0.trace, - xa = pointData.xa, - ya = pointData.ya, - x = cd0.x, - y = cd0.y, - z = cd0.z, - zmask = cd0.zmask, - x2 = x, - y2 = y, - xl, - yl, - nx, - ny; + var cd0 = pointData.cd[0], + trace = cd0.trace, + xa = pointData.xa, + ya = pointData.ya, + x = cd0.x, + y = cd0.y, + z = cd0.z, + zmask = cd0.zmask, + x2 = x, + y2 = y, + xl, + yl, + nx, + ny - if(pointData.index !== false) { - try { - nx = Math.round(pointData.index[1]); - ny = Math.round(pointData.index[0]); - } - catch(e) { - Lib.error('Error hovering on heatmap, ' + - 'pointNumber must be [row,col], found:', pointData.index); - return; - } - if(nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) { - return; - } + if (pointData.index !== false) { + try { + nx = Math.round(pointData.index[1]) + ny = Math.round(pointData.index[0]) + } catch (e) { + Lib.error('Error hovering on heatmap, ' + + 'pointNumber must be [row,col], found:', pointData.index) + return } - else if(Fx.inbox(xval - x[0], xval - x[x.length - 1]) > MAXDIST || - Fx.inbox(yval - y[0], yval - y[y.length - 1]) > MAXDIST) { - return; + if (nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) { + return } - else { - if(contour) { - var i2; - x2 = [2 * x[0] - x[1]]; + } else if (Fx.inbox(xval - x[0], xval - x[x.length - 1]) > MAXDIST || + Fx.inbox(yval - y[0], yval - y[y.length - 1]) > MAXDIST) { + return + } else { + if (contour) { + var i2 + x2 = [2 * x[0] - x[1]] - for(i2 = 1; i2 < x.length; i2++) { - x2.push((x[i2] + x[i2 - 1]) / 2); - } - x2.push([2 * x[x.length - 1] - x[x.length - 2]]); + for (i2 = 1; i2 < x.length; i2++) { + x2.push((x[i2] + x[i2 - 1]) / 2) + } + x2.push([2 * x[x.length - 1] - x[x.length - 2]]) - y2 = [2 * y[0] - y[1]]; - for(i2 = 1; i2 < y.length; i2++) { - y2.push((y[i2] + y[i2 - 1]) / 2); - } - y2.push([2 * y[y.length - 1] - y[y.length - 2]]); - } - nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2))); - ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2))); + y2 = [2 * y[0] - y[1]] + for (i2 = 1; i2 < y.length; i2++) { + y2.push((y[i2] + y[i2 - 1]) / 2) + } + y2.push([2 * y[y.length - 1] - y[y.length - 2]]) } + nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2))) + ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2))) + } - var x0 = xa.c2p(x[nx]), - x1 = xa.c2p(x[nx + 1]), - y0 = ya.c2p(y[ny]), - y1 = ya.c2p(y[ny + 1]); + var x0 = xa.c2p(x[nx]), + x1 = xa.c2p(x[nx + 1]), + y0 = ya.c2p(y[ny]), + y1 = ya.c2p(y[ny + 1]) - if(contour) { - x1 = x0; - xl = x[nx]; - y1 = y0; - yl = y[ny]; - } - else { - xl = (x[nx] + x[nx + 1]) / 2; - yl = (y[ny] + y[ny + 1]) / 2; - if(trace.zsmooth) { - x0 = x1 = (x0 + x1) / 2; - y0 = y1 = (y0 + y1) / 2; - } + if (contour) { + x1 = x0 + xl = x[nx] + y1 = y0 + yl = y[ny] + } else { + xl = (x[nx] + x[nx + 1]) / 2 + yl = (y[ny] + y[ny + 1]) / 2 + if (trace.zsmooth) { + x0 = x1 = (x0 + x1) / 2 + y0 = y1 = (y0 + y1) / 2 } + } - var zVal = z[ny][nx]; - if(zmask && !zmask[ny][nx]) zVal = undefined; + var zVal = z[ny][nx] + if (zmask && !zmask[ny][nx]) zVal = undefined - var text; - if(Array.isArray(trace.text) && Array.isArray(trace.text[ny])) { - text = trace.text[ny][nx]; - } + var text + if (Array.isArray(trace.text) && Array.isArray(trace.text[ny])) { + text = trace.text[ny][nx] + } - return [Lib.extendFlat(pointData, { - index: [ny, nx], + return [Lib.extendFlat(pointData, { + index: [ny, nx], // never let a 2D override 1D type as closest point - distance: MAXDIST + 10, - x0: x0, - x1: x1, - y0: y0, - y1: y1, - xLabelVal: xl, - yLabelVal: yl, - zLabelVal: zVal, - text: text - })]; -}; + distance: MAXDIST + 10, + x0: x0, + x1: x1, + y0: y0, + y1: y1, + xLabelVal: xl, + yLabelVal: yl, + zLabelVal: zVal, + text: text + })] +} diff --git a/src/traces/heatmap/index.js b/src/traces/heatmap/index.js index b3a1da2269a..91b4613810c 100644 --- a/src/traces/heatmap/index.js +++ b/src/traces/heatmap/index.js @@ -6,48 +6,47 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Heatmap = {}; - -Heatmap.attributes = require('./attributes'); -Heatmap.supplyDefaults = require('./defaults'); -Heatmap.calc = require('./calc'); -Heatmap.plot = require('./plot'); -Heatmap.colorbar = require('./colorbar'); -Heatmap.style = require('./style'); -Heatmap.hoverPoints = require('./hover'); - -Heatmap.moduleType = 'trace'; -Heatmap.name = 'heatmap'; -Heatmap.basePlotModule = require('../../plots/cartesian'); -Heatmap.categories = ['cartesian', '2dMap']; +'use strict' + +var Heatmap = {} + +Heatmap.attributes = require('./attributes') +Heatmap.supplyDefaults = require('./defaults') +Heatmap.calc = require('./calc') +Heatmap.plot = require('./plot') +Heatmap.colorbar = require('./colorbar') +Heatmap.style = require('./style') +Heatmap.hoverPoints = require('./hover') + +Heatmap.moduleType = 'trace' +Heatmap.name = 'heatmap' +Heatmap.basePlotModule = require('../../plots/cartesian') +Heatmap.categories = ['cartesian', '2dMap'] Heatmap.meta = { - description: [ - 'The data that describes the heatmap value-to-color mapping', - 'is set in `z`.', - 'Data in `z` can either be a {2D array} of values (ragged or not)', - 'or a 1D array of values.', - - 'In the case where `z` is a {2D array},', - 'say that `z` has N rows and M columns.', - 'Then, by default, the resulting heatmap will have N partitions along', - 'the y axis and M partitions along the x axis.', - 'In other words, the i-th row/ j-th column cell in `z`', - 'is mapped to the i-th partition of the y axis', - '(starting from the bottom of the plot) and the j-th partition', - 'of the x-axis (starting from the left of the plot).', - 'This behavior can be flipped by using `transpose`.', - 'Moreover, `x` (`y`) can be provided with M or M+1 (N or N+1) elements.', - 'If M (N), then the coordinates correspond to the center of the', - 'heatmap cells and the cells have equal width.', - 'If M+1 (N+1), then the coordinates correspond to the edges of the', - 'heatmap cells.', - - 'In the case where `z` is a 1D {array}, the x and y coordinates must be', - 'provided in `x` and `y` respectively to form data triplets.' - ].join(' ') -}; - -module.exports = Heatmap; + description: [ + 'The data that describes the heatmap value-to-color mapping', + 'is set in `z`.', + 'Data in `z` can either be a {2D array} of values (ragged or not)', + 'or a 1D array of values.', + + 'In the case where `z` is a {2D array},', + 'say that `z` has N rows and M columns.', + 'Then, by default, the resulting heatmap will have N partitions along', + 'the y axis and M partitions along the x axis.', + 'In other words, the i-th row/ j-th column cell in `z`', + 'is mapped to the i-th partition of the y axis', + '(starting from the bottom of the plot) and the j-th partition', + 'of the x-axis (starting from the left of the plot).', + 'This behavior can be flipped by using `transpose`.', + 'Moreover, `x` (`y`) can be provided with M or M+1 (N or N+1) elements.', + 'If M (N), then the coordinates correspond to the center of the', + 'heatmap cells and the cells have equal width.', + 'If M+1 (N+1), then the coordinates correspond to the edges of the', + 'heatmap cells.', + + 'In the case where `z` is a 1D {array}, the x and y coordinates must be', + 'provided in `x` and `y` respectively to form data triplets.' + ].join(' ') +} + +module.exports = Heatmap diff --git a/src/traces/heatmap/interp2d.js b/src/traces/heatmap/interp2d.js index 3676a9a9f7b..dffe6ab5028 100644 --- a/src/traces/heatmap/interp2d.js +++ b/src/traces/heatmap/interp2d.js @@ -6,125 +6,122 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); +var Lib = require('../../lib') var INTERPTHRESHOLD = 1e-2, - NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]] -function correctionOvershoot(maxFractionalChange) { +function correctionOvershoot (maxFractionalChange) { // start with less overshoot, until we know it's converging, // then ramp up the overshoot for faster convergence - return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5); + return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5) } -module.exports = function interp2d(z, emptyPoints, savedInterpZ) { +module.exports = function interp2d (z, emptyPoints, savedInterpZ) { // fill in any missing data in 2D array z using an iterative // poisson equation solver with zero-derivative BC at edges // amazingly, this just amounts to repeatedly averaging all the existing // nearest neighbors (at least if we don't take x/y scaling into account) - var maxFractionalChange = 1, - i, - thisPt; - - if(Array.isArray(savedInterpZ)) { - for(i = 0; i < emptyPoints.length; i++) { - thisPt = emptyPoints[i]; - z[thisPt[0]][thisPt[1]] = savedInterpZ[thisPt[0]][thisPt[1]]; - } + var maxFractionalChange = 1, + i, + thisPt + + if (Array.isArray(savedInterpZ)) { + for (i = 0; i < emptyPoints.length; i++) { + thisPt = emptyPoints[i] + z[thisPt[0]][thisPt[1]] = savedInterpZ[thisPt[0]][thisPt[1]] } - else { + } else { // one pass to fill in a starting value for all the empties - iterateInterp2d(z, emptyPoints); - } + iterateInterp2d(z, emptyPoints) + } // we're don't need to iterate lone empties - remove them - for(i = 0; i < emptyPoints.length; i++) { - if(emptyPoints[i][2] < 4) break; - } + for (i = 0; i < emptyPoints.length; i++) { + if (emptyPoints[i][2] < 4) break + } // but don't remove these points from the original array, // we'll use them for masking, so make a copy. - emptyPoints = emptyPoints.slice(i); + emptyPoints = emptyPoints.slice(i) - for(i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) { - maxFractionalChange = iterateInterp2d(z, emptyPoints, - correctionOvershoot(maxFractionalChange)); - } - if(maxFractionalChange > INTERPTHRESHOLD) { - Lib.log('interp2d didn\'t converge quickly', maxFractionalChange); - } + for (i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) { + maxFractionalChange = iterateInterp2d(z, emptyPoints, + correctionOvershoot(maxFractionalChange)) + } + if (maxFractionalChange > INTERPTHRESHOLD) { + Lib.log('interp2d didn\'t converge quickly', maxFractionalChange) + } - return z; -}; - -function iterateInterp2d(z, emptyPoints, overshoot) { - var maxFractionalChange = 0, - thisPt, - i, - j, - p, - q, - neighborShift, - neighborRow, - neighborVal, - neighborCount, - neighborSum, - initialVal, - minNeighbor, - maxNeighbor; - - for(p = 0; p < emptyPoints.length; p++) { - thisPt = emptyPoints[p]; - i = thisPt[0]; - j = thisPt[1]; - initialVal = z[i][j]; - neighborSum = 0; - neighborCount = 0; - - for(q = 0; q < 4; q++) { - neighborShift = NEIGHBORSHIFTS[q]; - neighborRow = z[i + neighborShift[0]]; - if(!neighborRow) continue; - neighborVal = neighborRow[j + neighborShift[1]]; - if(neighborVal !== undefined) { - if(neighborSum === 0) { - minNeighbor = maxNeighbor = neighborVal; - } - else { - minNeighbor = Math.min(minNeighbor, neighborVal); - maxNeighbor = Math.max(maxNeighbor, neighborVal); - } - neighborCount++; - neighborSum += neighborVal; - } - } + return z +} - if(neighborCount === 0) { - throw 'iterateInterp2d order is wrong: no defined neighbors'; +function iterateInterp2d (z, emptyPoints, overshoot) { + var maxFractionalChange = 0, + thisPt, + i, + j, + p, + q, + neighborShift, + neighborRow, + neighborVal, + neighborCount, + neighborSum, + initialVal, + minNeighbor, + maxNeighbor + + for (p = 0; p < emptyPoints.length; p++) { + thisPt = emptyPoints[p] + i = thisPt[0] + j = thisPt[1] + initialVal = z[i][j] + neighborSum = 0 + neighborCount = 0 + + for (q = 0; q < 4; q++) { + neighborShift = NEIGHBORSHIFTS[q] + neighborRow = z[i + neighborShift[0]] + if (!neighborRow) continue + neighborVal = neighborRow[j + neighborShift[1]] + if (neighborVal !== undefined) { + if (neighborSum === 0) { + minNeighbor = maxNeighbor = neighborVal + } else { + minNeighbor = Math.min(minNeighbor, neighborVal) + maxNeighbor = Math.max(maxNeighbor, neighborVal) } + neighborCount++ + neighborSum += neighborVal + } + } + + if (neighborCount === 0) { + throw 'iterateInterp2d order is wrong: no defined neighbors' + } // this is the laplace equation interpolation: // each point is just the average of its neighbors // note that this ignores differential x/y scaling // which I think is the right approach, since we // don't know what that scaling means - z[i][j] = neighborSum / neighborCount; + z[i][j] = neighborSum / neighborCount - if(initialVal === undefined) { - if(neighborCount < 4) maxFractionalChange = 1; - } - else { + if (initialVal === undefined) { + if (neighborCount < 4) maxFractionalChange = 1 + } else { // we can make large empty regions converge faster // if we overshoot the change vs the previous value - z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal; + z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal - if(maxNeighbor > minNeighbor) { - maxFractionalChange = Math.max(maxFractionalChange, - Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor)); - } - } + if (maxNeighbor > minNeighbor) { + maxFractionalChange = Math.max(maxFractionalChange, + Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor)) + } } + } - return maxFractionalChange; + return maxFractionalChange } diff --git a/src/traces/heatmap/make_bound_array.js b/src/traces/heatmap/make_bound_array.js index 3617f342ca6..b0212a15ad5 100644 --- a/src/traces/heatmap/make_bound_array.js +++ b/src/traces/heatmap/make_bound_array.js @@ -6,75 +6,72 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../../registry'); +var Registry = require('../../registry') -module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { - var arrayOut = [], - isContour = Registry.traceIs(trace, 'contour'), - isHist = Registry.traceIs(trace, 'histogram'), - isGL2D = Registry.traceIs(trace, 'gl2d'), - v0, - dv, - i; +module.exports = function makeBoundArray (trace, arrayIn, v0In, dvIn, numbricks, ax) { + var arrayOut = [], + isContour = Registry.traceIs(trace, 'contour'), + isHist = Registry.traceIs(trace, 'histogram'), + isGL2D = Registry.traceIs(trace, 'gl2d'), + v0, + dv, + i - var isArrayOfTwoItemsOrMore = Array.isArray(arrayIn) && arrayIn.length > 1; + var isArrayOfTwoItemsOrMore = Array.isArray(arrayIn) && arrayIn.length > 1 - if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { - var len = arrayIn.length; + if (isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { + var len = arrayIn.length // given vals are brick centers // hopefully length === numbricks, but use this method even if too few are supplied // and extend it linearly based on the last two points - if(len <= numbricks) { + if (len <= numbricks) { // contour plots only want the centers - if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks); - else if(numbricks === 1) { - arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5]; - } - else { - arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]]; + if (isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks) + else if (numbricks === 1) { + arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5] + } else { + arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]] - for(i = 1; i < len; i++) { - arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5); - } + for (i = 1; i < len; i++) { + arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5) + } - arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]); - } + arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]) + } - if(len < numbricks) { - var lastPt = arrayOut[arrayOut.length - 1], - delta = lastPt - arrayOut[arrayOut.length - 2]; + if (len < numbricks) { + var lastPt = arrayOut[arrayOut.length - 1], + delta = lastPt - arrayOut[arrayOut.length - 2] - for(i = len; i < numbricks; i++) { - lastPt += delta; - arrayOut.push(lastPt); - } - } + for (i = len; i < numbricks; i++) { + lastPt += delta + arrayOut.push(lastPt) } - else { + } + } else { // hopefully length === numbricks+1, but do something regardless: // given vals are brick boundaries - return isContour ? + return isContour ? arrayIn.slice(0, numbricks) : // we must be strict for contours - arrayIn.slice(0, numbricks + 1); - } + arrayIn.slice(0, numbricks + 1) } - else { - dv = dvIn || 1; + } else { + dv = dvIn || 1 - var calendar = trace[ax._id.charAt(0) + 'calendar']; + var calendar = trace[ax._id.charAt(0) + 'calendar'] - if(isHist || ax.type === 'category') v0 = ax.r2c(v0In, 0, calendar) || 0; - else if(Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0]; - else if(v0In === undefined) v0 = 0; - else v0 = ax.d2c(v0In, 0, calendar); + if (isHist || ax.type === 'category') v0 = ax.r2c(v0In, 0, calendar) || 0 + else if (Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0] + else if (v0In === undefined) v0 = 0 + else v0 = ax.d2c(v0In, 0, calendar) - for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) { - arrayOut.push(v0 + dv * i); - } + for (i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) { + arrayOut.push(v0 + dv * i) } + } - return arrayOut; -}; + return arrayOut +} diff --git a/src/traces/heatmap/max_row_length.js b/src/traces/heatmap/max_row_length.js index d35412ca2b8..9e255e06401 100644 --- a/src/traces/heatmap/max_row_length.js +++ b/src/traces/heatmap/max_row_length.js @@ -6,15 +6,14 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +module.exports = function maxRowLength (z) { + var len = 0 -module.exports = function maxRowLength(z) { - var len = 0; + for (var i = 0; i < z.length; i++) { + len = Math.max(len, z[i].length) + } - for(var i = 0; i < z.length; i++) { - len = Math.max(len, z[i].length); - } - - return len; -}; + return len +} diff --git a/src/traces/heatmap/plot.js b/src/traces/heatmap/plot.js index 02fbf077ee7..666ca520c42 100644 --- a/src/traces/heatmap/plot.js +++ b/src/traces/heatmap/plot.js @@ -6,60 +6,58 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var tinycolor = require('tinycolor2') -var tinycolor = require('tinycolor2'); +var Registry = require('../../registry') +var Lib = require('../../lib') +var Colorscale = require('../../components/colorscale') +var xmlnsNamespaces = require('../../constants/xmlns_namespaces') -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var Colorscale = require('../../components/colorscale'); -var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); +var maxRowLength = require('./max_row_length') -var maxRowLength = require('./max_row_length'); - - -module.exports = function(gd, plotinfo, cdheatmaps) { - for(var i = 0; i < cdheatmaps.length; i++) { - plotOne(gd, plotinfo, cdheatmaps[i]); - } -}; +module.exports = function (gd, plotinfo, cdheatmaps) { + for (var i = 0; i < cdheatmaps.length; i++) { + plotOne(gd, plotinfo, cdheatmaps[i]) + } +} // From http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/ -function plotOne(gd, plotinfo, cd) { - var trace = cd[0].trace, - uid = trace.uid, - xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - fullLayout = gd._fullLayout, - id = 'hm' + uid; +function plotOne (gd, plotinfo, cd) { + var trace = cd[0].trace, + uid = trace.uid, + xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + fullLayout = gd._fullLayout, + id = 'hm' + uid // in case this used to be a contour map - fullLayout._paper.selectAll('.contour' + uid).remove(); + fullLayout._paper.selectAll('.contour' + uid).remove() - if(trace.visible !== true) { - fullLayout._paper.selectAll('.' + id).remove(); - fullLayout._infolayer.selectAll('.cb' + uid).remove(); - return; - } + if (trace.visible !== true) { + fullLayout._paper.selectAll('.' + id).remove() + fullLayout._infolayer.selectAll('.cb' + uid).remove() + return + } - var z = cd[0].z, - x = cd[0].x, - y = cd[0].y, - isContour = Registry.traceIs(trace, 'contour'), - zsmooth = isContour ? 'best' : trace.zsmooth, + var z = cd[0].z, + x = cd[0].x, + y = cd[0].y, + isContour = Registry.traceIs(trace, 'contour'), + zsmooth = isContour ? 'best' : trace.zsmooth, // get z dims - m = z.length, - n = maxRowLength(z), - xrev = false, - left, - right, - temp, - yrev = false, - top, - bottom, - i; + m = z.length, + n = maxRowLength(z), + xrev = false, + left, + right, + temp, + yrev = false, + top, + bottom, + i // TODO: if there are multiple overlapping categorical heatmaps, // or if we allow category sorting, then the categories may not be @@ -72,354 +70,348 @@ function plotOne(gd, plotinfo, cd) { // TODO: use low-resolution images outside the visible plot for panning // these while loops find the first and last brick bounds that are defined // (in case of log of a negative) - i = 0; - while(left === undefined && i < x.length - 1) { - left = xa.c2p(x[i]); - i++; - } - i = x.length - 1; - while(right === undefined && i > 0) { - right = xa.c2p(x[i]); - i--; - } - - if(right < left) { - temp = right; - right = left; - left = temp; - xrev = true; - } - - i = 0; - while(top === undefined && i < y.length - 1) { - top = ya.c2p(y[i]); - i++; - } - i = y.length - 1; - while(bottom === undefined && i > 0) { - bottom = ya.c2p(y[i]); - i--; - } - - if(bottom < top) { - temp = top; - top = bottom; - bottom = temp; - yrev = true; - } + i = 0 + while (left === undefined && i < x.length - 1) { + left = xa.c2p(x[i]) + i++ + } + i = x.length - 1 + while (right === undefined && i > 0) { + right = xa.c2p(x[i]) + i-- + } + + if (right < left) { + temp = right + right = left + left = temp + xrev = true + } + + i = 0 + while (top === undefined && i < y.length - 1) { + top = ya.c2p(y[i]) + i++ + } + i = y.length - 1 + while (bottom === undefined && i > 0) { + bottom = ya.c2p(y[i]) + i-- + } + + if (bottom < top) { + temp = top + top = bottom + bottom = temp + yrev = true + } // for contours with heatmap fill, we generate the boundaries based on // brick centers but then use the brick edges for drawing the bricks - if(isContour) { + if (isContour) { // TODO: for 'best' smoothing, we really should use the given brick // centers as well as brick bounds in calculating values, in case of // nonuniform brick sizes - x = cd[0].xfill; - y = cd[0].yfill; - } + x = cd[0].xfill + y = cd[0].yfill + } // make an image that goes at most half a screen off either side, to keep // time reasonable when you zoom in. if zsmooth is true/fast, don't worry // about this, because zooming doesn't increase number of pixels // if zsmooth is best, don't include anything off screen because it takes too long - if(zsmooth !== 'fast') { - var extra = zsmooth === 'best' ? 0 : 0.5; - left = Math.max(-extra * xa._length, left); - right = Math.min((1 + extra) * xa._length, right); - top = Math.max(-extra * ya._length, top); - bottom = Math.min((1 + extra) * ya._length, bottom); - } + if (zsmooth !== 'fast') { + var extra = zsmooth === 'best' ? 0 : 0.5 + left = Math.max(-extra * xa._length, left) + right = Math.min((1 + extra) * xa._length, right) + top = Math.max(-extra * ya._length, top) + bottom = Math.min((1 + extra) * ya._length, bottom) + } - var imageWidth = Math.round(right - left), - imageHeight = Math.round(bottom - top); + var imageWidth = Math.round(right - left), + imageHeight = Math.round(bottom - top) // setup image nodes // if image is entirely off-screen, don't even draw it - var isOffScreen = (imageWidth <= 0 || imageHeight <= 0); + var isOffScreen = (imageWidth <= 0 || imageHeight <= 0) - var plotgroup = plotinfo.plot.select('.imagelayer') + var plotgroup = plotinfo.plot.select('.imagelayer') .selectAll('g.hm.' + id) - .data(isOffScreen ? [] : [0]); + .data(isOffScreen ? [] : [0]) - plotgroup.enter().append('g') + plotgroup.enter().append('g') .classed('hm', true) - .classed(id, true); + .classed(id, true) - plotgroup.exit().remove(); + plotgroup.exit().remove() - if(isOffScreen) return; + if (isOffScreen) return // generate image data - var canvasW, canvasH; - if(zsmooth === 'fast') { - canvasW = n; - canvasH = m; - } else { - canvasW = imageWidth; - canvasH = imageHeight; - } - - var canvas = document.createElement('canvas'); - canvas.width = canvasW; - canvas.height = canvasH; - var context = canvas.getContext('2d'); - - var sclFunc = Colorscale.makeColorScaleFunc( + var canvasW, canvasH + if (zsmooth === 'fast') { + canvasW = n + canvasH = m + } else { + canvasW = imageWidth + canvasH = imageHeight + } + + var canvas = document.createElement('canvas') + canvas.width = canvasW + canvas.height = canvasH + var context = canvas.getContext('2d') + + var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( trace.colorscale, trace.zmin, trace.zmax ), { noNumericCheck: true, returnArray: true } - ); + ) // map brick boundaries to image pixels - var xpx, - ypx; - if(zsmooth === 'fast') { - xpx = xrev ? - function(index) { return n - 1 - index; } : - Lib.identity; - ypx = yrev ? - function(index) { return m - 1 - index; } : - Lib.identity; + var xpx, + ypx + if (zsmooth === 'fast') { + xpx = xrev ? + function (index) { return n - 1 - index } : + Lib.identity + ypx = yrev ? + function (index) { return m - 1 - index } : + Lib.identity + } else { + xpx = function (index) { + return Lib.constrain(Math.round(xa.c2p(x[index]) - left), + 0, imageWidth) } - else { - xpx = function(index) { - return Lib.constrain(Math.round(xa.c2p(x[index]) - left), - 0, imageWidth); - }; - ypx = function(index) { - return Lib.constrain(Math.round(ya.c2p(y[index]) - top), - 0, imageHeight); - }; + ypx = function (index) { + return Lib.constrain(Math.round(ya.c2p(y[index]) - top), + 0, imageHeight) } + } // get interpolated bin value. Returns {bin0:closest bin, frac:fractional dist to next, bin1:next bin} - function findInterp(pixel, pixArray) { - var maxbin = pixArray.length - 2, - bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxbin), - pix0 = pixArray[bin], - pix1 = pixArray[bin + 1], - interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxbin), - bin0 = Math.round(interp), - frac = Math.abs(interp - bin0); - - if(!interp || interp === maxbin || !frac) { - return { - bin0: bin0, - bin1: bin0, - frac: 0 - }; - } - return { - bin0: bin0, - frac: frac, - bin1: Math.round(bin0 + frac / (interp - bin0)) - }; + function findInterp (pixel, pixArray) { + var maxbin = pixArray.length - 2, + bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxbin), + pix0 = pixArray[bin], + pix1 = pixArray[bin + 1], + interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxbin), + bin0 = Math.round(interp), + frac = Math.abs(interp - bin0) + + if (!interp || interp === maxbin || !frac) { + return { + bin0: bin0, + bin1: bin0, + frac: 0 + } + } + return { + bin0: bin0, + frac: frac, + bin1: Math.round(bin0 + frac / (interp - bin0)) } + } // build the pixel map brick-by-brick // cruise through z-matrix row-by-row // build a brick at each z-matrix value - var yi = ypx(0), - yb = [yi, yi], - xbi = xrev ? 0 : 1, - ybi = yrev ? 0 : 1, + var yi = ypx(0), + yb = [yi, yi], + xbi = xrev ? 0 : 1, + ybi = yrev ? 0 : 1, // for collecting an average luminosity of the heatmap - pixcount = 0, - rcount = 0, - gcount = 0, - bcount = 0, - brickWithPadding, - xb, - j, - xi, - v, - row, - c; - - function applyBrickPadding(trace, x0, x1, y0, y1, xIndex, xLength, yIndex, yLength) { - var padding = { - x0: x0, - x1: x1, - y0: y0, - y1: y1 - }, - xEdgeGap = trace.xgap * 2 / 3, - yEdgeGap = trace.ygap * 2 / 3, - xCenterGap = trace.xgap / 3, - yCenterGap = trace.ygap / 3; - - if(yIndex === yLength - 1) { // top edge brick - padding.y1 = y1 - yEdgeGap; - } - - if(xIndex === xLength - 1) { // right edge brick - padding.x0 = x0 + xEdgeGap; - } - - if(yIndex === 0) { // bottom edge brick - padding.y0 = y0 + yEdgeGap; - } - - if(xIndex === 0) { // left edge brick - padding.x1 = x1 - xEdgeGap; - } - - if(xIndex > 0 && xIndex < xLength - 1) { // brick in the center along x - padding.x0 = x0 + xCenterGap; - padding.x1 = x1 - xCenterGap; - } + pixcount = 0, + rcount = 0, + gcount = 0, + bcount = 0, + brickWithPadding, + xb, + j, + xi, + v, + row, + c + + function applyBrickPadding (trace, x0, x1, y0, y1, xIndex, xLength, yIndex, yLength) { + var padding = { + x0: x0, + x1: x1, + y0: y0, + y1: y1 + }, + xEdgeGap = trace.xgap * 2 / 3, + yEdgeGap = trace.ygap * 2 / 3, + xCenterGap = trace.xgap / 3, + yCenterGap = trace.ygap / 3 + + if (yIndex === yLength - 1) { // top edge brick + padding.y1 = y1 - yEdgeGap + } - if(yIndex > 0 && yIndex < yLength - 1) { // brick in the center along y - padding.y0 = y0 + yCenterGap; - padding.y1 = y1 - yCenterGap; - } + if (xIndex === xLength - 1) { // right edge brick + padding.x0 = x0 + xEdgeGap + } - return padding; + if (yIndex === 0) { // bottom edge brick + padding.y0 = y0 + yEdgeGap } - function setColor(v, pixsize) { - if(v !== undefined) { - var c = sclFunc(v); - c[0] = Math.round(c[0]); - c[1] = Math.round(c[1]); - c[2] = Math.round(c[2]); - - pixcount += pixsize; - rcount += c[0] * pixsize; - gcount += c[1] * pixsize; - bcount += c[2] * pixsize; - return c; - } - return [0, 0, 0, 0]; + if (xIndex === 0) { // left edge brick + padding.x1 = x1 - xEdgeGap } - function putColor(pixels, pxIndex, c) { - pixels[pxIndex] = c[0]; - pixels[pxIndex + 1] = c[1]; - pixels[pxIndex + 2] = c[2]; - pixels[pxIndex + 3] = Math.round(c[3] * 255); + if (xIndex > 0 && xIndex < xLength - 1) { // brick in the center along x + padding.x0 = x0 + xCenterGap + padding.x1 = x1 - xCenterGap } - function interpColor(r0, r1, xinterp, yinterp) { - var z00 = r0[xinterp.bin0]; - if(z00 === undefined) return setColor(undefined, 1); + if (yIndex > 0 && yIndex < yLength - 1) { // brick in the center along y + padding.y0 = y0 + yCenterGap + padding.y1 = y1 - yCenterGap + } - var z01 = r0[xinterp.bin1], - z10 = r1[xinterp.bin0], - z11 = r1[xinterp.bin1], - dx = (z01 - z00) || 0, - dy = (z10 - z00) || 0, - dxy; + return padding + } + + function setColor (v, pixsize) { + if (v !== undefined) { + var c = sclFunc(v) + c[0] = Math.round(c[0]) + c[1] = Math.round(c[1]) + c[2] = Math.round(c[2]) + + pixcount += pixsize + rcount += c[0] * pixsize + gcount += c[1] * pixsize + bcount += c[2] * pixsize + return c + } + return [0, 0, 0, 0] + } + + function putColor (pixels, pxIndex, c) { + pixels[pxIndex] = c[0] + pixels[pxIndex + 1] = c[1] + pixels[pxIndex + 2] = c[2] + pixels[pxIndex + 3] = Math.round(c[3] * 255) + } + + function interpColor (r0, r1, xinterp, yinterp) { + var z00 = r0[xinterp.bin0] + if (z00 === undefined) return setColor(undefined, 1) + + var z01 = r0[xinterp.bin1], + z10 = r1[xinterp.bin0], + z11 = r1[xinterp.bin1], + dx = (z01 - z00) || 0, + dy = (z10 - z00) || 0, + dxy // the bilinear interpolation term needs different calculations // for all the different permutations of missing data // among the neighbors of the main point, to ensure // continuity across brick boundaries. - if(z01 === undefined) { - if(z11 === undefined) dxy = 0; - else if(z10 === undefined) dxy = 2 * (z11 - z00); - else dxy = (2 * z11 - z10 - z00) * 2 / 3; - } - else if(z11 === undefined) { - if(z10 === undefined) dxy = 0; - else dxy = (2 * z00 - z01 - z10) * 2 / 3; - } - else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3; - else dxy = (z11 + z00 - z01 - z10); - - return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy)); + if (z01 === undefined) { + if (z11 === undefined) dxy = 0 + else if (z10 === undefined) dxy = 2 * (z11 - z00) + else dxy = (2 * z11 - z10 - z00) * 2 / 3 + } else if (z11 === undefined) { + if (z10 === undefined) dxy = 0 + else dxy = (2 * z00 - z01 - z10) * 2 / 3 + } else if (z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3 + else dxy = (z11 + z00 - z01 - z10) + + return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy)) + } + + if (zsmooth) { // best or fast, works fastest with imageData + var pxIndex = 0, + pixels + + try { + pixels = new Uint8Array(imageWidth * imageHeight * 4) + } catch (e) { + pixels = new Array(imageWidth * imageHeight * 4) } - if(zsmooth) { // best or fast, works fastest with imageData - var pxIndex = 0, - pixels; - - try { - pixels = new Uint8Array(imageWidth * imageHeight * 4); - } - catch(e) { - pixels = new Array(imageWidth * imageHeight * 4); - } - - if(zsmooth === 'best') { - var xPixArray = new Array(x.length), - yPixArray = new Array(y.length), - xinterpArray = new Array(imageWidth), - yinterp, - r0, - r1; + if (zsmooth === 'best') { + var xPixArray = new Array(x.length), + yPixArray = new Array(y.length), + xinterpArray = new Array(imageWidth), + yinterp, + r0, + r1 // first make arrays of x and y pixel locations of brick boundaries - for(i = 0; i < x.length; i++) xPixArray[i] = Math.round(xa.c2p(x[i]) - left); - for(i = 0; i < y.length; i++) yPixArray[i] = Math.round(ya.c2p(y[i]) - top); + for (i = 0; i < x.length; i++) xPixArray[i] = Math.round(xa.c2p(x[i]) - left) + for (i = 0; i < y.length; i++) yPixArray[i] = Math.round(ya.c2p(y[i]) - top) // then make arrays of interpolations // (bin0=closest, bin1=next, frac=fractional dist.) - for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterp(i, xPixArray); + for (i = 0; i < imageWidth; i++) xinterpArray[i] = findInterp(i, xPixArray) // now do the interpolations and fill the png - for(j = 0; j < imageHeight; j++) { - yinterp = findInterp(j, yPixArray); - r0 = z[yinterp.bin0]; - r1 = z[yinterp.bin1]; - for(i = 0; i < imageWidth; i++, pxIndex += 4) { - c = interpColor(r0, r1, xinterpArray[i], yinterp); - putColor(pixels, pxIndex, c); - } - } + for (j = 0; j < imageHeight; j++) { + yinterp = findInterp(j, yPixArray) + r0 = z[yinterp.bin0] + r1 = z[yinterp.bin1] + for (i = 0; i < imageWidth; i++, pxIndex += 4) { + c = interpColor(r0, r1, xinterpArray[i], yinterp) + putColor(pixels, pxIndex, c) } - else { // zsmooth = fast - for(j = 0; j < m; j++) { - row = z[j]; - yb = ypx(j); - for(i = 0; i < imageWidth; i++) { - c = setColor(row[i], 1); - pxIndex = (yb * imageWidth + xpx(i)) * 4; - putColor(pixels, pxIndex, c); - } - } + } + } else { // zsmooth = fast + for (j = 0; j < m; j++) { + row = z[j] + yb = ypx(j) + for (i = 0; i < imageWidth; i++) { + c = setColor(row[i], 1) + pxIndex = (yb * imageWidth + xpx(i)) * 4 + putColor(pixels, pxIndex, c) } + } + } - var imageData = context.createImageData(imageWidth, imageHeight); - try { - imageData.data.set(pixels); - } - catch(e) { - var pxArray = imageData.data, - dlen = pxArray.length; - for(j = 0; j < dlen; j ++) { - pxArray[j] = pixels[j]; - } - } + var imageData = context.createImageData(imageWidth, imageHeight) + try { + imageData.data.set(pixels) + } catch (e) { + var pxArray = imageData.data, + dlen = pxArray.length + for (j = 0; j < dlen; j++) { + pxArray[j] = pixels[j] + } + } - context.putImageData(imageData, 0, 0); - } else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect - for(j = 0; j < m; j++) { - row = z[j]; - yb.reverse(); - yb[ybi] = ypx(j + 1); - if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) { - continue; - } - xi = xpx(0); - xb = [xi, xi]; - for(i = 0; i < n; i++) { + context.putImageData(imageData, 0, 0) + } else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect + for (j = 0; j < m; j++) { + row = z[j] + yb.reverse() + yb[ybi] = ypx(j + 1) + if (yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) { + continue + } + xi = xpx(0) + xb = [xi, xi] + for (i = 0; i < n; i++) { // build one color brick! - xb.reverse(); - xb[xbi] = xpx(i + 1); - if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) { - continue; - } - v = row[i]; - c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0])); - context.fillStyle = 'rgba(' + c.join(',') + ')'; - - brickWithPadding = applyBrickPadding(trace, + xb.reverse() + xb[xbi] = xpx(i + 1) + if (xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) { + continue + } + v = row[i] + c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0])) + context.fillStyle = 'rgba(' + c.join(',') + ')' + + brickWithPadding = applyBrickPadding(trace, xb[0], xb[1], yb[0], @@ -427,39 +419,39 @@ function plotOne(gd, plotinfo, cd) { i, n, j, - m); + m) - context.fillRect(brickWithPadding.x0, + context.fillRect(brickWithPadding.x0, brickWithPadding.y0, (brickWithPadding.x1 - brickWithPadding.x0), - (brickWithPadding.y1 - brickWithPadding.y0)); - } - } + (brickWithPadding.y1 - brickWithPadding.y0)) + } } + } - rcount = Math.round(rcount / pixcount); - gcount = Math.round(gcount / pixcount); - bcount = Math.round(bcount / pixcount); - var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')'); + rcount = Math.round(rcount / pixcount) + gcount = Math.round(gcount / pixcount) + bcount = Math.round(bcount / pixcount) + var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')') - gd._hmpixcount = (gd._hmpixcount||0) + pixcount; - gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance(); + gd._hmpixcount = (gd._hmpixcount || 0) + pixcount + gd._hmlumcount = (gd._hmlumcount || 0) + pixcount * avgColor.getLuminance() - var image3 = plotgroup.selectAll('image') - .data(cd); + var image3 = plotgroup.selectAll('image') + .data(cd) - image3.enter().append('svg:image').attr({ - xmlns: xmlnsNamespaces.svg, - preserveAspectRatio: 'none' - }); + image3.enter().append('svg:image').attr({ + xmlns: xmlnsNamespaces.svg, + preserveAspectRatio: 'none' + }) - image3.attr({ - height: imageHeight, - width: imageWidth, - x: left, - y: top, - 'xlink:href': canvas.toDataURL('image/png') - }); + image3.attr({ + height: imageHeight, + width: imageWidth, + x: left, + y: top, + 'xlink:href': canvas.toDataURL('image/png') + }) - image3.exit().remove(); + image3.exit().remove() } diff --git a/src/traces/heatmap/style.js b/src/traces/heatmap/style.js index 9eac041e073..0a0bbe8bb0a 100644 --- a/src/traces/heatmap/style.js +++ b/src/traces/heatmap/style.js @@ -6,14 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); - -module.exports = function style(gd) { - d3.select(gd).selectAll('.hm image') - .style('opacity', function(d) { - return d.trace.opacity; - }); -}; +module.exports = function style (gd) { + d3.select(gd).selectAll('.hm image') + .style('opacity', function (d) { + return d.trace.opacity + }) +} diff --git a/src/traces/heatmap/xyz_defaults.js b/src/traces/heatmap/xyz_defaults.js index 33d13eb8dd5..dc079875d66 100644 --- a/src/traces/heatmap/xyz_defaults.js +++ b/src/traces/heatmap/xyz_defaults.js @@ -6,63 +6,60 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Registry = require('../../registry') +var hasColumns = require('./has_columns') -var Registry = require('../../registry'); -var hasColumns = require('./has_columns'); +module.exports = function handleXYZDefaults (traceIn, traceOut, coerce, layout) { + var z = coerce('z') + var x, y + if (z === undefined || !z.length) return 0 -module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout) { - var z = coerce('z'); - var x, y; - - if(z === undefined || !z.length) return 0; - - if(hasColumns(traceIn)) { - x = coerce('x'); - y = coerce('y'); + if (hasColumns(traceIn)) { + x = coerce('x') + y = coerce('y') // column z must be accompanied by 'x' and 'y' arrays - if(!x || !y) return 0; - } - else { - x = coordDefaults('x', coerce); - y = coordDefaults('y', coerce); + if (!x || !y) return 0 + } else { + x = coordDefaults('x', coerce) + y = coordDefaults('y', coerce) // TODO put z validation elsewhere - if(!isValidZ(z)) return 0; + if (!isValidZ(z)) return 0 - coerce('transpose'); - } + coerce('transpose') + } - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout) - return traceOut.z.length; -}; + return traceOut.z.length +} -function coordDefaults(coordStr, coerce) { - var coord = coerce(coordStr), - coordType = coord ? +function coordDefaults (coordStr, coerce) { + var coord = coerce(coordStr), + coordType = coord ? coerce(coordStr + 'type', 'array') : - 'scaled'; + 'scaled' - if(coordType === 'scaled') { - coerce(coordStr + '0'); - coerce('d' + coordStr); - } + if (coordType === 'scaled') { + coerce(coordStr + '0') + coerce('d' + coordStr) + } - return coord; + return coord } -function isValidZ(z) { - var allRowsAreArrays = true, - oneRowIsFilled = false, - hasOneNumber = false, - zi; +function isValidZ (z) { + var allRowsAreArrays = true, + oneRowIsFilled = false, + hasOneNumber = false, + zi /* * Without this step: @@ -72,20 +69,20 @@ function isValidZ(z) { * oneRowIsFilled = false breaks both */ - for(var i = 0; i < z.length; i++) { - zi = z[i]; - if(!Array.isArray(zi)) { - allRowsAreArrays = false; - break; - } - if(zi.length > 0) oneRowIsFilled = true; - for(var j = 0; j < zi.length; j++) { - if(isNumeric(zi[j])) { - hasOneNumber = true; - break; - } - } + for (var i = 0; i < z.length; i++) { + zi = z[i] + if (!Array.isArray(zi)) { + allRowsAreArrays = false + break + } + if (zi.length > 0) oneRowIsFilled = true + for (var j = 0; j < zi.length; j++) { + if (isNumeric(zi[j])) { + hasOneNumber = true + break + } } + } - return (allRowsAreArrays && oneRowIsFilled && hasOneNumber); + return (allRowsAreArrays && oneRowIsFilled && hasOneNumber) } diff --git a/src/traces/heatmapgl/attributes.js b/src/traces/heatmapgl/attributes.js index d3dae984714..181962dcc4e 100644 --- a/src/traces/heatmapgl/attributes.js +++ b/src/traces/heatmapgl/attributes.js @@ -6,28 +6,27 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' +var heatmapAttrs = require('../heatmap/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var heatmapAttrs = require('../heatmap/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); - -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat var commonList = [ - 'z', - 'x', 'x0', 'dx', - 'y', 'y0', 'dy', - 'text', 'transpose', - 'xtype', 'ytype' -]; - -var attrs = {}; - -for(var i = 0; i < commonList.length; i++) { - var k = commonList[i]; - attrs[k] = heatmapAttrs[k]; + 'z', + 'x', 'x0', 'dx', + 'y', 'y0', 'dy', + 'text', 'transpose', + 'xtype', 'ytype' +] + +var attrs = {} + +for (var i = 0; i < commonList.length; i++) { + var k = commonList[i] + attrs[k] = heatmapAttrs[k] } extendFlat( @@ -35,6 +34,6 @@ extendFlat( colorscaleAttrs, { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}) }, { colorbar: colorbarAttrs } -); +) -module.exports = attrs; +module.exports = attrs diff --git a/src/traces/heatmapgl/convert.js b/src/traces/heatmapgl/convert.js index 5bdeed828c8..5607d827a45 100644 --- a/src/traces/heatmapgl/convert.js +++ b/src/traces/heatmapgl/convert.js @@ -6,132 +6,130 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var createHeatmap2D = require('gl-heatmap2d') +var Axes = require('../../plots/cartesian/axes') +var str2RGBArray = require('../../lib/str2rgbarray') + +function Heatmap (scene, uid) { + this.scene = scene + this.uid = uid + this.type = 'heatmapgl' + + this.name = '' + this.hoverinfo = 'all' + + this.xData = [] + this.yData = [] + this.zData = [] + this.textLabels = [] + + this.idToIndex = [] + this.bounds = [0, 0, 0, 0] + + this.options = { + z: [], + x: [], + y: [], + shape: [0, 0], + colorLevels: [0], + colorValues: [0, 0, 0, 1] + } + + this.heatmap = createHeatmap2D(scene.glplot, this.options) + this.heatmap._trace = this +} -'use strict'; - -var createHeatmap2D = require('gl-heatmap2d'); -var Axes = require('../../plots/cartesian/axes'); -var str2RGBArray = require('../../lib/str2rgbarray'); - - -function Heatmap(scene, uid) { - this.scene = scene; - this.uid = uid; - this.type = 'heatmapgl'; - - this.name = ''; - this.hoverinfo = 'all'; - - this.xData = []; - this.yData = []; - this.zData = []; - this.textLabels = []; - - this.idToIndex = []; - this.bounds = [0, 0, 0, 0]; - - this.options = { - z: [], - x: [], - y: [], - shape: [0, 0], - colorLevels: [0], - colorValues: [0, 0, 0, 1] - }; - - this.heatmap = createHeatmap2D(scene.glplot, this.options); - this.heatmap._trace = this; +var proto = Heatmap.prototype + +proto.handlePick = function (pickResult) { + var options = this.options, + shape = options.shape, + index = pickResult.pointId, + xIndex = index % shape[0], + yIndex = Math.floor(index / shape[0]), + zIndex = index + + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: [ + options.x[xIndex], + options.y[yIndex], + options.z[zIndex] + ], + textLabel: this.textLabels[index], + name: this.name, + pointIndex: [xIndex, yIndex], + hoverinfo: this.hoverinfo + } } -var proto = Heatmap.prototype; - -proto.handlePick = function(pickResult) { - var options = this.options, - shape = options.shape, - index = pickResult.pointId, - xIndex = index % shape[0], - yIndex = Math.floor(index / shape[0]), - zIndex = index; - - return { - trace: this, - dataCoord: pickResult.dataCoord, - traceCoord: [ - options.x[xIndex], - options.y[yIndex], - options.z[zIndex] - ], - textLabel: this.textLabels[index], - name: this.name, - pointIndex: [xIndex, yIndex], - hoverinfo: this.hoverinfo - }; -}; - -proto.update = function(fullTrace, calcTrace) { - var calcPt = calcTrace[0]; - - this.name = fullTrace.name; - this.hoverinfo = fullTrace.hoverinfo; +proto.update = function (fullTrace, calcTrace) { + var calcPt = calcTrace[0] + + this.name = fullTrace.name + this.hoverinfo = fullTrace.hoverinfo // convert z from 2D -> 1D - var z = calcPt.z; - this.options.z = [].concat.apply([], z); + var z = calcPt.z + this.options.z = [].concat.apply([], z) - var rowLen = z[0].length, - colLen = z.length; - this.options.shape = [rowLen, colLen]; + var rowLen = z[0].length, + colLen = z.length + this.options.shape = [rowLen, colLen] - this.options.x = calcPt.x; - this.options.y = calcPt.y; + this.options.x = calcPt.x + this.options.y = calcPt.y - var colorOptions = convertColorscale(fullTrace); - this.options.colorLevels = colorOptions.colorLevels; - this.options.colorValues = colorOptions.colorValues; + var colorOptions = convertColorscale(fullTrace) + this.options.colorLevels = colorOptions.colorLevels + this.options.colorValues = colorOptions.colorValues // convert text from 2D -> 1D - this.textLabels = [].concat.apply([], fullTrace.text); + this.textLabels = [].concat.apply([], fullTrace.text) - this.heatmap.update(this.options); + this.heatmap.update(this.options) - Axes.expand(this.scene.xaxis, calcPt.x); - Axes.expand(this.scene.yaxis, calcPt.y); -}; + Axes.expand(this.scene.xaxis, calcPt.x) + Axes.expand(this.scene.yaxis, calcPt.y) +} -proto.dispose = function() { - this.heatmap.dispose(); -}; +proto.dispose = function () { + this.heatmap.dispose() +} -function convertColorscale(fullTrace) { - var scl = fullTrace.colorscale, - zmin = fullTrace.zmin, - zmax = fullTrace.zmax; +function convertColorscale (fullTrace) { + var scl = fullTrace.colorscale, + zmin = fullTrace.zmin, + zmax = fullTrace.zmax - var N = scl.length, - domain = new Array(N), - range = new Array(4 * N); + var N = scl.length, + domain = new Array(N), + range = new Array(4 * N) - for(var i = 0; i < N; i++) { - var si = scl[i]; - var color = str2RGBArray(si[1]); + for (var i = 0; i < N; i++) { + var si = scl[i] + var color = str2RGBArray(si[1]) - domain[i] = zmin + si[0] * (zmax - zmin); + domain[i] = zmin + si[0] * (zmax - zmin) - for(var j = 0; j < 4; j++) { - range[(4 * i) + j] = color[j]; - } + for (var j = 0; j < 4; j++) { + range[(4 * i) + j] = color[j] } + } - return { - colorLevels: domain, - colorValues: range - }; + return { + colorLevels: domain, + colorValues: range + } } -function createHeatmap(scene, fullTrace, calcTrace) { - var plot = new Heatmap(scene, fullTrace.uid); - plot.update(fullTrace, calcTrace); - return plot; +function createHeatmap (scene, fullTrace, calcTrace) { + var plot = new Heatmap(scene, fullTrace.uid) + plot.update(fullTrace, calcTrace) + return plot } -module.exports = createHeatmap; +module.exports = createHeatmap diff --git a/src/traces/heatmapgl/index.js b/src/traces/heatmapgl/index.js index 19ac6fe15f4..b53fd77c6df 100644 --- a/src/traces/heatmapgl/index.js +++ b/src/traces/heatmapgl/index.js @@ -6,26 +6,25 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var HeatmapGl = {} -var HeatmapGl = {}; +HeatmapGl.attributes = require('./attributes') +HeatmapGl.supplyDefaults = require('../heatmap/defaults') +HeatmapGl.colorbar = require('../heatmap/colorbar') -HeatmapGl.attributes = require('./attributes'); -HeatmapGl.supplyDefaults = require('../heatmap/defaults'); -HeatmapGl.colorbar = require('../heatmap/colorbar'); +HeatmapGl.calc = require('../heatmap/calc') +HeatmapGl.plot = require('./convert') -HeatmapGl.calc = require('../heatmap/calc'); -HeatmapGl.plot = require('./convert'); - -HeatmapGl.moduleType = 'trace'; -HeatmapGl.name = 'heatmapgl'; -HeatmapGl.basePlotModule = require('../../plots/gl2d'); -HeatmapGl.categories = ['gl2d', '2dMap']; +HeatmapGl.moduleType = 'trace' +HeatmapGl.name = 'heatmapgl' +HeatmapGl.basePlotModule = require('../../plots/gl2d') +HeatmapGl.categories = ['gl2d', '2dMap'] HeatmapGl.meta = { - description: [ - 'WebGL version of the heatmap trace type.' - ].join(' ') -}; + description: [ + 'WebGL version of the heatmap trace type.' + ].join(' ') +} -module.exports = HeatmapGl; +module.exports = HeatmapGl diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 889dda231fc..01e13d71f47 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -6,205 +6,204 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var barAttrs = require('../bar/attributes'); +'use strict' +var barAttrs = require('../bar/attributes') module.exports = { - x: { - valType: 'data_array', - description: [ - 'Sets the sample data to be binned on the x axis.' - ].join(' ') - }, - y: { - valType: 'data_array', - description: [ - 'Sets the sample data to be binned on the y axis.' - ].join(' ') + x: { + valType: 'data_array', + description: [ + 'Sets the sample data to be binned on the x axis.' + ].join(' ') + }, + y: { + valType: 'data_array', + description: [ + 'Sets the sample data to be binned on the y axis.' + ].join(' ') + }, + + text: barAttrs.text, + orientation: barAttrs.orientation, + + histfunc: { + valType: 'enumerated', + values: ['count', 'sum', 'avg', 'min', 'max'], + role: 'style', + dflt: 'count', + description: [ + 'Specifies the binning function used for this histogram trace.', + + 'If *count*, the histogram values are computed by counting the', + 'number of values lying inside each bin.', + + 'If *sum*, *avg*, *min*, *max*,', + 'the histogram values are computed using', + 'the sum, the average, the minimum or the maximum', + 'of the values lying inside each bin respectively.' + ].join(' ') + }, + histnorm: { + valType: 'enumerated', + values: ['', 'percent', 'probability', 'density', 'probability density'], + dflt: '', + role: 'style', + description: [ + 'Specifies the type of normalization used for this histogram trace.', + + 'If **, the span of each bar corresponds to the number of', + 'occurrences (i.e. the number of data points lying inside the bins).', + + 'If *percent* / *probability*, the span of each bar corresponds to', + 'the percentage / fraction of occurrences with respect to the total', + 'number of sample points', + '(here, the sum of all bin HEIGHTS equals 100% / 1).', + + 'If *density*, the span of each bar corresponds to the number of', + 'occurrences in a bin divided by the size of the bin interval', + '(here, the sum of all bin AREAS equals the', + 'total number of sample points).', + + 'If *probability density*, the area of each bar corresponds to the', + 'probability that an event will fall into the corresponding bin', + '(here, the sum of all bin AREAS equals 1).' + ].join(' ') + }, + + cumulative: { + enabled: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'If true, display the cumulative distribution by summing the', + 'binned values. Use the `direction` and `centralbin` attributes', + 'to tune the accumulation method.', + 'Note: in this mode, the *density* `histnorm` settings behave', + 'the same as their equivalents without *density*:', + '** and *density* both rise to the number of data points, and', + '*probability* and *probability density* both rise to the', + 'number of sample points.' + ].join(' ') }, - text: barAttrs.text, - orientation: barAttrs.orientation, - - histfunc: { - valType: 'enumerated', - values: ['count', 'sum', 'avg', 'min', 'max'], - role: 'style', - dflt: 'count', - description: [ - 'Specifies the binning function used for this histogram trace.', - - 'If *count*, the histogram values are computed by counting the', - 'number of values lying inside each bin.', - - 'If *sum*, *avg*, *min*, *max*,', - 'the histogram values are computed using', - 'the sum, the average, the minimum or the maximum', - 'of the values lying inside each bin respectively.' - ].join(' ') - }, - histnorm: { - valType: 'enumerated', - values: ['', 'percent', 'probability', 'density', 'probability density'], - dflt: '', - role: 'style', - description: [ - 'Specifies the type of normalization used for this histogram trace.', - - 'If **, the span of each bar corresponds to the number of', - 'occurrences (i.e. the number of data points lying inside the bins).', - - 'If *percent* / *probability*, the span of each bar corresponds to', - 'the percentage / fraction of occurrences with respect to the total', - 'number of sample points', - '(here, the sum of all bin HEIGHTS equals 100% / 1).', - - 'If *density*, the span of each bar corresponds to the number of', - 'occurrences in a bin divided by the size of the bin interval', - '(here, the sum of all bin AREAS equals the', - 'total number of sample points).', - - 'If *probability density*, the area of each bar corresponds to the', - 'probability that an event will fall into the corresponding bin', - '(here, the sum of all bin AREAS equals 1).' - ].join(' ') + direction: { + valType: 'enumerated', + values: ['increasing', 'decreasing'], + dflt: 'increasing', + role: 'info', + description: [ + 'Only applies if cumulative is enabled.', + 'If *increasing* (default) we sum all prior bins, so the result', + 'increases from left to right. If *decreasing* we sum later bins', + 'so the result decreases from left to right.' + ].join(' ') }, - cumulative: { - enabled: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'If true, display the cumulative distribution by summing the', - 'binned values. Use the `direction` and `centralbin` attributes', - 'to tune the accumulation method.', - 'Note: in this mode, the *density* `histnorm` settings behave', - 'the same as their equivalents without *density*:', - '** and *density* both rise to the number of data points, and', - '*probability* and *probability density* both rise to the', - 'number of sample points.' - ].join(' ') - }, - - direction: { - valType: 'enumerated', - values: ['increasing', 'decreasing'], - dflt: 'increasing', - role: 'info', - description: [ - 'Only applies if cumulative is enabled.', - 'If *increasing* (default) we sum all prior bins, so the result', - 'increases from left to right. If *decreasing* we sum later bins', - 'so the result decreases from left to right.' - ].join(' ') - }, - - currentbin: { - valType: 'enumerated', - values: ['include', 'exclude', 'half'], - dflt: 'include', - role: 'info', - description: [ - 'Only applies if cumulative is enabled.', - 'Sets whether the current bin is included, excluded, or has half', - 'of its value included in the current cumulative value.', - '*include* is the default for compatibility with various other', - 'tools, however it introduces a half-bin bias to the results.', - '*exclude* makes the opposite half-bin bias, and *half* removes', - 'it.' - ].join(' ') - } - }, + currentbin: { + valType: 'enumerated', + values: ['include', 'exclude', 'half'], + dflt: 'include', + role: 'info', + description: [ + 'Only applies if cumulative is enabled.', + 'Sets whether the current bin is included, excluded, or has half', + 'of its value included in the current cumulative value.', + '*include* is the default for compatibility with various other', + 'tools, however it introduces a half-bin bias to the results.', + '*exclude* makes the opposite half-bin bias, and *half* removes', + 'it.' + ].join(' ') + } + }, + + autobinx: { + valType: 'boolean', + dflt: null, + role: 'style', + description: [ + 'Determines whether or not the x axis bin attributes are picked', + 'by an algorithm. Note that this should be set to false if you', + 'want to manually set the number of bins using the attributes in', + 'xbins.' + ].join(' ') + }, + nbinsx: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Specifies the maximum number of desired bins. This value will be used', + 'in an algorithm that will decide the optimal bin size such that the', + 'histogram best visualizes the distribution of the data.' + ].join(' ') + }, + xbins: makeBinsAttr('x'), + + autobiny: { + valType: 'boolean', + dflt: null, + role: 'style', + description: [ + 'Determines whether or not the y axis bin attributes are picked', + 'by an algorithm. Note that this should be set to false if you', + 'want to manually set the number of bins using the attributes in', + 'ybins.' + ].join(' ') + }, + nbinsy: { + valType: 'integer', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Specifies the maximum number of desired bins. This value will be used', + 'in an algorithm that will decide the optimal bin size such that the', + 'histogram best visualizes the distribution of the data.' + ].join(' ') + }, + ybins: makeBinsAttr('y'), + + marker: barAttrs.marker, + + error_y: barAttrs.error_y, + error_x: barAttrs.error_x, + + _deprecated: { + bardir: barAttrs._deprecated.bardir + } +} - autobinx: { - valType: 'boolean', - dflt: null, - role: 'style', - description: [ - 'Determines whether or not the x axis bin attributes are picked', - 'by an algorithm. Note that this should be set to false if you', - 'want to manually set the number of bins using the attributes in', - 'xbins.' - ].join(' ') +function makeBinsAttr (axLetter) { + return { + start: { + valType: 'any', // for date axes + dflt: null, + role: 'style', + description: [ + 'Sets the starting value for the', axLetter, + 'axis bins.' + ].join(' ') }, - nbinsx: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Specifies the maximum number of desired bins. This value will be used', - 'in an algorithm that will decide the optimal bin size such that the', - 'histogram best visualizes the distribution of the data.' - ].join(' ') + end: { + valType: 'any', // for date axes + dflt: null, + role: 'style', + description: [ + 'Sets the end value for the', axLetter, + 'axis bins.' + ].join(' ') }, - xbins: makeBinsAttr('x'), - - autobiny: { - valType: 'boolean', - dflt: null, - role: 'style', - description: [ - 'Determines whether or not the y axis bin attributes are picked', - 'by an algorithm. Note that this should be set to false if you', - 'want to manually set the number of bins using the attributes in', - 'ybins.' - ].join(' ') - }, - nbinsy: { - valType: 'integer', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Specifies the maximum number of desired bins. This value will be used', - 'in an algorithm that will decide the optimal bin size such that the', - 'histogram best visualizes the distribution of the data.' - ].join(' ') - }, - ybins: makeBinsAttr('y'), - - marker: barAttrs.marker, - - error_y: barAttrs.error_y, - error_x: barAttrs.error_x, - - _deprecated: { - bardir: barAttrs._deprecated.bardir + size: { + valType: 'any', // for date axes + dflt: null, + role: 'style', + description: [ + 'Sets the step in-between value each', axLetter, + 'axis bin.' + ].join(' ') } -}; - -function makeBinsAttr(axLetter) { - return { - start: { - valType: 'any', // for date axes - dflt: null, - role: 'style', - description: [ - 'Sets the starting value for the', axLetter, - 'axis bins.' - ].join(' ') - }, - end: { - valType: 'any', // for date axes - dflt: null, - role: 'style', - description: [ - 'Sets the end value for the', axLetter, - 'axis bins.' - ].join(' ') - }, - size: { - valType: 'any', // for date axes - dflt: null, - role: 'style', - description: [ - 'Sets the step in-between value each', axLetter, - 'axis bin.' - ].join(' ') - } - }; + } } diff --git a/src/traces/histogram/average.js b/src/traces/histogram/average.js index ce382e3395e..237c0d05c80 100644 --- a/src/traces/histogram/average.js +++ b/src/traces/histogram/average.js @@ -6,19 +6,16 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - - -module.exports = function doAvg(size, counts) { - var nMax = size.length, - total = 0; - for(var i = 0; i < nMax; i++) { - if(counts[i]) { - size[i] /= counts[i]; - total += size[i]; - } - else size[i] = null; - } - return total; -}; +module.exports = function doAvg (size, counts) { + var nMax = size.length, + total = 0 + for (var i = 0; i < nMax; i++) { + if (counts[i]) { + size[i] /= counts[i] + total += size[i] + } else size[i] = null + } + return total +} diff --git a/src/traces/histogram/bin_defaults.js b/src/traces/histogram/bin_defaults.js index 444668ac815..0b92468c2f6 100644 --- a/src/traces/histogram/bin_defaults.js +++ b/src/traces/histogram/bin_defaults.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +module.exports = function handleBinDefaults (traceIn, traceOut, coerce, binDirections) { + coerce('histnorm') - -module.exports = function handleBinDefaults(traceIn, traceOut, coerce, binDirections) { - coerce('histnorm'); - - binDirections.forEach(function(binDirection) { + binDirections.forEach(function (binDirection) { /* * Because date axes have string values for start and end, * and string options for size, we cannot validate these attributes * now. We will do this during calc (immediately prior to binning) * in ./clean_bins, and push the cleaned values back to _fullData. */ - coerce(binDirection + 'bins.start'); - coerce(binDirection + 'bins.end'); - coerce(binDirection + 'bins.size'); - coerce('autobin' + binDirection); - coerce('nbins' + binDirection); - }); + coerce(binDirection + 'bins.start') + coerce(binDirection + 'bins.end') + coerce(binDirection + 'bins.size') + coerce('autobin' + binDirection) + coerce('nbins' + binDirection) + }) - return traceOut; -}; + return traceOut +} diff --git a/src/traces/histogram/bin_functions.js b/src/traces/histogram/bin_functions.js index d4219667903..6cb535efd53 100644 --- a/src/traces/histogram/bin_functions.js +++ b/src/traces/histogram/bin_functions.js @@ -6,69 +6,65 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); - +var isNumeric = require('fast-isnumeric') module.exports = { - count: function(n, i, size) { - size[n]++; - return 1; - }, + count: function (n, i, size) { + size[n]++ + return 1 + }, - sum: function(n, i, size, counterData) { - var v = counterData[i]; - if(isNumeric(v)) { - v = Number(v); - size[n] += v; - return v; - } - return 0; - }, + sum: function (n, i, size, counterData) { + var v = counterData[i] + if (isNumeric(v)) { + v = Number(v) + size[n] += v + return v + } + return 0 + }, - avg: function(n, i, size, counterData, counts) { - var v = counterData[i]; - if(isNumeric(v)) { - v = Number(v); - size[n] += v; - counts[n]++; - } - return 0; - }, + avg: function (n, i, size, counterData, counts) { + var v = counterData[i] + if (isNumeric(v)) { + v = Number(v) + size[n] += v + counts[n]++ + } + return 0 + }, - min: function(n, i, size, counterData) { - var v = counterData[i]; - if(isNumeric(v)) { - v = Number(v); - if(!isNumeric(size[n])) { - size[n] = v; - return v; - } - else if(size[n] > v) { - var delta = v - size[n]; - size[n] = v; - return delta; - } - } - return 0; - }, + min: function (n, i, size, counterData) { + var v = counterData[i] + if (isNumeric(v)) { + v = Number(v) + if (!isNumeric(size[n])) { + size[n] = v + return v + } else if (size[n] > v) { + var delta = v - size[n] + size[n] = v + return delta + } + } + return 0 + }, - max: function(n, i, size, counterData) { - var v = counterData[i]; - if(isNumeric(v)) { - v = Number(v); - if(!isNumeric(size[n])) { - size[n] = v; - return v; - } - else if(size[n] < v) { - var delta = v - size[n]; - size[n] = v; - return delta; - } - } - return 0; + max: function (n, i, size, counterData) { + var v = counterData[i] + if (isNumeric(v)) { + v = Number(v) + if (!isNumeric(size[n])) { + size[n] = v + return v + } else if (size[n] < v) { + var delta = v - size[n] + size[n] = v + return delta + } } -}; + return 0 + } +} diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 9b079438b98..df08e85bf27 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -6,221 +6,212 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var binFunctions = require('./bin_functions') +var normFunctions = require('./norm_functions') +var doAvg = require('./average') +var cleanBins = require('./clean_bins') -var binFunctions = require('./bin_functions'); -var normFunctions = require('./norm_functions'); -var doAvg = require('./average'); -var cleanBins = require('./clean_bins'); - - -module.exports = function calc(gd, trace) { +module.exports = function calc (gd, trace) { // ignore as much processing as possible (and including in autorange) if bar is not visible - if(trace.visible !== true) return; + if (trace.visible !== true) return // depending on orientation, set position and size axes and data ranges // note: this logic for choosing orientation is duplicated in graph_obj->setstyles - var pos = [], - size = [], - i, - pa = Axes.getFromId(gd, + var pos = [], + size = [], + i, + pa = Axes.getFromId(gd, trace.orientation === 'h' ? (trace.yaxis || 'y') : (trace.xaxis || 'x')), - maindata = trace.orientation === 'h' ? 'y' : 'x', - counterdata = {x: 'y', y: 'x'}[maindata], - calendar = trace[maindata + 'calendar'], - cumulativeSpec = trace.cumulative; + maindata = trace.orientation === 'h' ? 'y' : 'x', + counterdata = {x: 'y', y: 'x'}[maindata], + calendar = trace[maindata + 'calendar'], + cumulativeSpec = trace.cumulative - cleanBins(trace, pa, maindata); + cleanBins(trace, pa, maindata) // prepare the raw data - var pos0 = pa.makeCalcdata(trace, maindata); + var pos0 = pa.makeCalcdata(trace, maindata) // calculate the bins - var binAttr = maindata + 'bins', - binspec; - if((trace['autobin' + maindata] !== false) || !(binAttr in trace)) { - binspec = Axes.autoBin(pos0, pa, trace['nbins' + maindata], false, calendar); + var binAttr = maindata + 'bins', + binspec + if ((trace['autobin' + maindata] !== false) || !(binAttr in trace)) { + binspec = Axes.autoBin(pos0, pa, trace['nbins' + maindata], false, calendar) // adjust for CDF edge cases - if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) { - if(cumulativeSpec.direction === 'decreasing') { - binspec.start = pa.c2r(pa.r2c(binspec.start) - binspec.size); - } - else { - binspec.end = pa.c2r(pa.r2c(binspec.end) + binspec.size); - } - } + if (cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) { + if (cumulativeSpec.direction === 'decreasing') { + binspec.start = pa.c2r(pa.r2c(binspec.start) - binspec.size) + } else { + binspec.end = pa.c2r(pa.r2c(binspec.end) + binspec.size) + } + } // copy bin info back to the source and full data. - trace._input[binAttr] = trace[binAttr] = binspec; - } - else { - binspec = trace[binAttr]; - } + trace._input[binAttr] = trace[binAttr] = binspec + } else { + binspec = trace[binAttr] + } - var nonuniformBins = typeof binspec.size === 'string', - bins = nonuniformBins ? [] : binspec, + var nonuniformBins = typeof binspec.size === 'string', + bins = nonuniformBins ? [] : binspec, // make the empty bin array - i2, - binend, - n, - inc = [], - counts = [], - total = 0, - norm = trace.histnorm, - func = trace.histfunc, - densitynorm = norm.indexOf('density') !== -1; - - if(cumulativeSpec.enabled && densitynorm) { + i2, + binend, + n, + inc = [], + counts = [], + total = 0, + norm = trace.histnorm, + func = trace.histfunc, + densitynorm = norm.indexOf('density') !== -1 + + if (cumulativeSpec.enabled && densitynorm) { // we treat "cumulative" like it means "integral" if you use a density norm, // which in the end means it's the same as without "density" - norm = norm.replace(/ ?density$/, ''); - densitynorm = false; - } - - var extremefunc = func === 'max' || func === 'min', - sizeinit = extremefunc ? null : 0, - binfunc = binFunctions.count, - normfunc = normFunctions[norm], - doavg = false, - pr2c = function(v) { return pa.r2c(v, 0, calendar); }, - rawCounterData; - - if(Array.isArray(trace[counterdata]) && func !== 'count') { - rawCounterData = trace[counterdata]; - doavg = func === 'avg'; - binfunc = binFunctions[func]; - } + norm = norm.replace(/ ?density$/, '') + densitynorm = false + } + + var extremefunc = func === 'max' || func === 'min', + sizeinit = extremefunc ? null : 0, + binfunc = binFunctions.count, + normfunc = normFunctions[norm], + doavg = false, + pr2c = function (v) { return pa.r2c(v, 0, calendar) }, + rawCounterData + + if (Array.isArray(trace[counterdata]) && func !== 'count') { + rawCounterData = trace[counterdata] + doavg = func === 'avg' + binfunc = binFunctions[func] + } // create the bins (and any extra arrays needed) // assume more than 5000 bins is an error, so we don't crash the browser - i = pr2c(binspec.start); + i = pr2c(binspec.start) // decrease end a little in case of rounding errors - binend = pr2c(binspec.end) + (i - Axes.tickIncrement(i, binspec.size, false, calendar)) / 1e6; + binend = pr2c(binspec.end) + (i - Axes.tickIncrement(i, binspec.size, false, calendar)) / 1e6 - while(i < binend && pos.length < 5000) { - i2 = Axes.tickIncrement(i, binspec.size, false, calendar); - pos.push((i + i2) / 2); - size.push(sizeinit); + while (i < binend && pos.length < 5000) { + i2 = Axes.tickIncrement(i, binspec.size, false, calendar) + pos.push((i + i2) / 2) + size.push(sizeinit) // nonuniform bins (like months) we need to search, // rather than straight calculate the bin we're in - if(nonuniformBins) bins.push(i); + if (nonuniformBins) bins.push(i) // nonuniform bins also need nonuniform normalization factors - if(densitynorm) inc.push(1 / (i2 - i)); - if(doavg) counts.push(0); - i = i2; - } + if (densitynorm) inc.push(1 / (i2 - i)) + if (doavg) counts.push(0) + i = i2 + } // for date axes we need bin bounds to be calcdata. For nonuniform bins // we already have this, but uniform with start/end/size they're still strings. - if(!nonuniformBins && pa.type === 'date') { - bins = { - start: pr2c(bins.start), - end: pr2c(bins.end), - size: bins.size - }; + if (!nonuniformBins && pa.type === 'date') { + bins = { + start: pr2c(bins.start), + end: pr2c(bins.end), + size: bins.size } + } - var nMax = size.length; + var nMax = size.length // bin the data - for(i = 0; i < pos0.length; i++) { - n = Lib.findBin(pos0[i], bins); - if(n >= 0 && n < nMax) total += binfunc(n, i, size, rawCounterData, counts); - } + for (i = 0; i < pos0.length; i++) { + n = Lib.findBin(pos0[i], bins) + if (n >= 0 && n < nMax) total += binfunc(n, i, size, rawCounterData, counts) + } // average and/or normalize the data, if needed - if(doavg) total = doAvg(size, counts); - if(normfunc) normfunc(size, total, inc); + if (doavg) total = doAvg(size, counts) + if (normfunc) normfunc(size, total, inc) // after all normalization etc, now we can accumulate if desired - if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin); - + if (cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin) - var serieslen = Math.min(pos.length, size.length), - cd = [], - firstNonzero = 0, - lastNonzero = serieslen - 1; + var serieslen = Math.min(pos.length, size.length), + cd = [], + firstNonzero = 0, + lastNonzero = serieslen - 1 // look for empty bins at the ends to remove, so autoscale omits them - for(i = 0; i < serieslen; i++) { - if(size[i]) { - firstNonzero = i; - break; - } + for (i = 0; i < serieslen; i++) { + if (size[i]) { + firstNonzero = i + break } - for(i = serieslen - 1; i > firstNonzero; i--) { - if(size[i]) { - lastNonzero = i; - break; - } + } + for (i = serieslen - 1; i > firstNonzero; i--) { + if (size[i]) { + lastNonzero = i + break } + } // create the "calculated data" to plot - for(i = firstNonzero; i <= lastNonzero; i++) { - if((isNumeric(pos[i]) && isNumeric(size[i]))) { - cd.push({p: pos[i], s: size[i], b: 0}); - } + for (i = firstNonzero; i <= lastNonzero; i++) { + if ((isNumeric(pos[i]) && isNumeric(size[i]))) { + cd.push({p: pos[i], s: size[i], b: 0}) } + } - return cd; -}; - -function cdf(size, direction, currentbin) { - var i, - vi, - prevSum; - - function firstHalfPoint(i) { - prevSum = size[i]; - size[i] /= 2; - } + return cd +} - function nextHalfPoint(i) { - vi = size[i]; - size[i] = prevSum + vi / 2; - prevSum += vi; +function cdf (size, direction, currentbin) { + var i, + vi, + prevSum + + function firstHalfPoint (i) { + prevSum = size[i] + size[i] /= 2 + } + + function nextHalfPoint (i) { + vi = size[i] + size[i] = prevSum + vi / 2 + prevSum += vi + } + + if (currentbin === 'half') { + if (direction === 'increasing') { + firstHalfPoint(0) + for (i = 1; i < size.length; i++) { + nextHalfPoint(i) + } + } else { + firstHalfPoint(size.length - 1) + for (i = size.length - 2; i >= 0; i--) { + nextHalfPoint(i) + } } - - if(currentbin === 'half') { - - if(direction === 'increasing') { - firstHalfPoint(0); - for(i = 1; i < size.length; i++) { - nextHalfPoint(i); - } - } - else { - firstHalfPoint(size.length - 1); - for(i = size.length - 2; i >= 0; i--) { - nextHalfPoint(i); - } - } + } else if (direction === 'increasing') { + for (i = 1; i < size.length; i++) { + size[i] += size[i - 1] } - else if(direction === 'increasing') { - for(i = 1; i < size.length; i++) { - size[i] += size[i - 1]; - } // 'exclude' is identical to 'include' just shifted one bin over - if(currentbin === 'exclude') { - size.unshift(0); - size.pop(); - } + if (currentbin === 'exclude') { + size.unshift(0) + size.pop() } - else { - for(i = size.length - 2; i >= 0; i--) { - size[i] += size[i + 1]; - } - - if(currentbin === 'exclude') { - size.push(0); - size.shift(); - } + } else { + for (i = size.length - 2; i >= 0; i--) { + size[i] += size[i + 1] + } + + if (currentbin === 'exclude') { + size.push(0) + size.shift() } + } } diff --git a/src/traces/histogram/clean_bins.js b/src/traces/histogram/clean_bins.js index ab53f6bd88f..2129f0b42ad 100644 --- a/src/traces/histogram/clean_bins.js +++ b/src/traces/histogram/clean_bins.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; -var isNumeric = require('fast-isnumeric'); -var cleanDate = require('../../lib').cleanDate; -var constants = require('../../constants/numerical'); -var ONEDAY = constants.ONEDAY; -var BADNUM = constants.BADNUM; +'use strict' +var isNumeric = require('fast-isnumeric') +var cleanDate = require('../../lib').cleanDate +var constants = require('../../constants/numerical') +var ONEDAY = constants.ONEDAY +var BADNUM = constants.BADNUM /* * cleanBins: validate attributes autobin[xy] and [xy]bins.(start, end, size) @@ -23,53 +22,51 @@ var BADNUM = constants.BADNUM; * after trace supplyDefaults are completed. So this gets called during the * calc step, when data are inserted into bins. */ -module.exports = function cleanBins(trace, ax, binDirection) { - var axType = ax.type, - binAttr = binDirection + 'bins', - bins = trace[binAttr]; +module.exports = function cleanBins (trace, ax, binDirection) { + var axType = ax.type, + binAttr = binDirection + 'bins', + bins = trace[binAttr] - if(!bins) bins = trace[binAttr] = {}; + if (!bins) bins = trace[binAttr] = {} - var cleanBound = (axType === 'date') ? - function(v) { return (v || v === 0) ? cleanDate(v, BADNUM, bins.calendar) : null; } : - function(v) { return isNumeric(v) ? Number(v) : null; }; + var cleanBound = (axType === 'date') ? + function (v) { return (v || v === 0) ? cleanDate(v, BADNUM, bins.calendar) : null } : + function (v) { return isNumeric(v) ? Number(v) : null } - bins.start = cleanBound(bins.start); - bins.end = cleanBound(bins.end); + bins.start = cleanBound(bins.start) + bins.end = cleanBound(bins.end) // logic for bin size is very similar to dtick (cartesian/tick_value_defaults) // but without the extra string options for log axes // ie the only strings we accept are M for months - var sizeDflt = (axType === 'date') ? ONEDAY : 1, - binSize = bins.size; + var sizeDflt = (axType === 'date') ? ONEDAY : 1, + binSize = bins.size - if(isNumeric(binSize)) { - bins.size = (binSize > 0) ? Number(binSize) : sizeDflt; - } - else if(typeof binSize !== 'string') { - bins.size = sizeDflt; - } - else { + if (isNumeric(binSize)) { + bins.size = (binSize > 0) ? Number(binSize) : sizeDflt + } else if (typeof binSize !== 'string') { + bins.size = sizeDflt + } else { // date special case: "M" gives bins every (integer) n months - var prefix = binSize.charAt(0), - sizeNum = binSize.substr(1); + var prefix = binSize.charAt(0), + sizeNum = binSize.substr(1) - sizeNum = isNumeric(sizeNum) ? Number(sizeNum) : 0; - if((sizeNum <= 0) || !( + sizeNum = isNumeric(sizeNum) ? Number(sizeNum) : 0 + if ((sizeNum <= 0) || !( axType === 'date' && prefix === 'M' && sizeNum === Math.round(sizeNum) )) { - bins.size = sizeDflt; - } + bins.size = sizeDflt } + } - var autoBinAttr = 'autobin' + binDirection; + var autoBinAttr = 'autobin' + binDirection - if(typeof trace[autoBinAttr] !== 'boolean') { - trace[autoBinAttr] = !( + if (typeof trace[autoBinAttr] !== 'boolean') { + trace[autoBinAttr] = !( (bins.start || bins.start === 0) && (bins.end || bins.end === 0) - ); - } + ) + } - if(!trace[autoBinAttr]) delete trace['nbins' + binDirection]; -}; + if (!trace[autoBinAttr]) delete trace['nbins' + binDirection] +} diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index 534bddf3f8f..f9faebc4659 100644 --- a/src/traces/histogram/defaults.js +++ b/src/traces/histogram/defaults.js @@ -6,55 +6,53 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Lib = require('../../lib') +var Color = require('../../components/color') -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var Color = require('../../components/color'); +var handleBinDefaults = require('./bin_defaults') +var handleStyleDefaults = require('../bar/style_defaults') +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults') +var attributes = require('./attributes') -var handleBinDefaults = require('./bin_defaults'); -var handleStyleDefaults = require('../bar/style_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var x = coerce('x'), + y = coerce('y') -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var cumulative = coerce('cumulative.enabled') + if (cumulative) { + coerce('cumulative.direction') + coerce('cumulative.currentbin') + } - var x = coerce('x'), - y = coerce('y'); + coerce('text') - var cumulative = coerce('cumulative.enabled'); - if(cumulative) { - coerce('cumulative.direction'); - coerce('cumulative.currentbin'); - } + var orientation = coerce('orientation', (y && !x) ? 'h' : 'v'), + sample = traceOut[orientation === 'v' ? 'x' : 'y'] - coerce('text'); + if (!(sample && sample.length)) { + traceOut.visible = false + return + } - var orientation = coerce('orientation', (y && !x) ? 'h' : 'v'), - sample = traceOut[orientation === 'v' ? 'x' : 'y']; + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout) - if(!(sample && sample.length)) { - traceOut.visible = false; - return; - } + var hasAggregationData = traceOut[orientation === 'h' ? 'x' : 'y'] + if (hasAggregationData) coerce('histfunc') - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + var binDirections = (orientation === 'h') ? ['y'] : ['x'] + handleBinDefaults(traceIn, traceOut, coerce, binDirections) - var hasAggregationData = traceOut[orientation === 'h' ? 'x' : 'y']; - if(hasAggregationData) coerce('histfunc'); - - var binDirections = (orientation === 'h') ? ['y'] : ['x']; - handleBinDefaults(traceIn, traceOut, coerce, binDirections); - - handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); + handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) // override defaultColor for error bars with defaultLine - errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'x', inherit: 'y'}); -}; + errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'y'}) + errorBarsSupplyDefaults(traceIn, traceOut, Color.defaultLine, {axis: 'x', inherit: 'y'}) +} diff --git a/src/traces/histogram/index.js b/src/traces/histogram/index.js index bf743152181..e2bfee1d0f6 100644 --- a/src/traces/histogram/index.js +++ b/src/traces/histogram/index.js @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' /** * Histogram has its own attribute, defaults and calc steps, @@ -22,32 +21,31 @@ * to allow quadrature combination of errors in summed histograms... */ - -var Histogram = {}; - -Histogram.attributes = require('./attributes'); -Histogram.layoutAttributes = require('../bar/layout_attributes'); -Histogram.supplyDefaults = require('./defaults'); -Histogram.supplyLayoutDefaults = require('../bar/layout_defaults'); -Histogram.calc = require('./calc'); -Histogram.setPositions = require('../bar/set_positions'); -Histogram.plot = require('../bar/plot'); -Histogram.style = require('../bar/style'); -Histogram.colorbar = require('../scatter/colorbar'); -Histogram.hoverPoints = require('../bar/hover'); - -Histogram.moduleType = 'trace'; -Histogram.name = 'histogram'; -Histogram.basePlotModule = require('../../plots/cartesian'); -Histogram.categories = ['cartesian', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend']; +var Histogram = {} + +Histogram.attributes = require('./attributes') +Histogram.layoutAttributes = require('../bar/layout_attributes') +Histogram.supplyDefaults = require('./defaults') +Histogram.supplyLayoutDefaults = require('../bar/layout_defaults') +Histogram.calc = require('./calc') +Histogram.setPositions = require('../bar/set_positions') +Histogram.plot = require('../bar/plot') +Histogram.style = require('../bar/style') +Histogram.colorbar = require('../scatter/colorbar') +Histogram.hoverPoints = require('../bar/hover') + +Histogram.moduleType = 'trace' +Histogram.name = 'histogram' +Histogram.basePlotModule = require('../../plots/cartesian') +Histogram.categories = ['cartesian', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend'] Histogram.meta = { - description: [ - 'The sample data from which statistics are computed is set in `x`', - 'for vertically spanning histograms and', - 'in `y` for horizontally spanning histograms.', - 'Binning options are set `xbins` and `ybins` respectively', - 'if no aggregation data is provided.' - ].join(' ') -}; - -module.exports = Histogram; + description: [ + 'The sample data from which statistics are computed is set in `x`', + 'for vertically spanning histograms and', + 'in `y` for horizontally spanning histograms.', + 'Binning options are set `xbins` and `ybins` respectively', + 'if no aggregation data is provided.' + ].join(' ') +} + +module.exports = Histogram diff --git a/src/traces/histogram/norm_functions.js b/src/traces/histogram/norm_functions.js index 81381217e68..12f57a48bd1 100644 --- a/src/traces/histogram/norm_functions.js +++ b/src/traces/histogram/norm_functions.js @@ -6,28 +6,26 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' module.exports = { - percent: function(size, total) { - var nMax = size.length, - norm = 100 / total; - for(var n = 0; n < nMax; n++) size[n] *= norm; - }, - probability: function(size, total) { - var nMax = size.length; - for(var n = 0; n < nMax; n++) size[n] /= total; - }, - density: function(size, total, inc, yinc) { - var nMax = size.length; - yinc = yinc || 1; - for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc; - }, - 'probability density': function(size, total, inc, yinc) { - var nMax = size.length; - if(yinc) total /= yinc; - for(var n = 0; n < nMax; n++) size[n] *= inc[n] / total; - } -}; + percent: function (size, total) { + var nMax = size.length, + norm = 100 / total + for (var n = 0; n < nMax; n++) size[n] *= norm + }, + probability: function (size, total) { + var nMax = size.length + for (var n = 0; n < nMax; n++) size[n] /= total + }, + density: function (size, total, inc, yinc) { + var nMax = size.length + yinc = yinc || 1 + for (var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc + }, + 'probability density': function (size, total, inc, yinc) { + var nMax = size.length + if (yinc) total /= yinc + for (var n = 0; n < nMax; n++) size[n] *= inc[n] / total + } +} diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 06d6e8eacaf..fd8a18562e0 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -6,45 +6,45 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var histogramAttrs = require('../histogram/attributes'); -var heatmapAttrs = require('../heatmap/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var histogramAttrs = require('../histogram/attributes') +var heatmapAttrs = require('../heatmap/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat module.exports = extendFlat({}, - { - x: histogramAttrs.x, - y: histogramAttrs.y, + { + x: histogramAttrs.x, + y: histogramAttrs.y, - z: { - valType: 'data_array', - description: 'Sets the aggregation data.' - }, - marker: { - color: { - valType: 'data_array', - description: 'Sets the aggregation data.' - } - }, + z: { + valType: 'data_array', + description: 'Sets the aggregation data.' + }, + marker: { + color: { + valType: 'data_array', + description: 'Sets the aggregation data.' + } + }, - histnorm: histogramAttrs.histnorm, - histfunc: histogramAttrs.histfunc, - autobinx: histogramAttrs.autobinx, - nbinsx: histogramAttrs.nbinsx, - xbins: histogramAttrs.xbins, - autobiny: histogramAttrs.autobiny, - nbinsy: histogramAttrs.nbinsy, - ybins: histogramAttrs.ybins, + histnorm: histogramAttrs.histnorm, + histfunc: histogramAttrs.histfunc, + autobinx: histogramAttrs.autobinx, + nbinsx: histogramAttrs.nbinsx, + xbins: histogramAttrs.xbins, + autobiny: histogramAttrs.autobiny, + nbinsy: histogramAttrs.nbinsy, + ybins: histogramAttrs.ybins, - xgap: heatmapAttrs.xgap, - ygap: heatmapAttrs.ygap, - zsmooth: heatmapAttrs.zsmooth - }, + xgap: heatmapAttrs.xgap, + ygap: heatmapAttrs.ygap, + zsmooth: heatmapAttrs.zsmooth + }, colorscaleAttrs, { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}) }, { colorbar: colorbarAttrs } -); +) diff --git a/src/traces/histogram2d/calc.js b/src/traces/histogram2d/calc.js index 602c5d9a545..d0bbd3965fa 100644 --- a/src/traces/histogram2d/calc.js +++ b/src/traces/histogram2d/calc.js @@ -6,196 +6,192 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); - -var binFunctions = require('../histogram/bin_functions'); -var normFunctions = require('../histogram/norm_functions'); -var doAvg = require('../histogram/average'); -var cleanBins = require('../histogram/clean_bins'); - - -module.exports = function calc(gd, trace) { - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - x = trace.x ? xa.makeCalcdata(trace, 'x') : [], - ya = Axes.getFromId(gd, trace.yaxis || 'y'), - y = trace.y ? ya.makeCalcdata(trace, 'y') : [], - xcalendar = trace.xcalendar, - ycalendar = trace.ycalendar, - xr2c = function(v) { return xa.r2c(v, 0, xcalendar); }, - yr2c = function(v) { return ya.r2c(v, 0, ycalendar); }, - xc2r = function(v) { return xa.c2r(v, 0, xcalendar); }, - yc2r = function(v) { return ya.c2r(v, 0, ycalendar); }, - x0, - dx, - y0, - dy, - z, - i; - - cleanBins(trace, xa, 'x'); - cleanBins(trace, ya, 'y'); - - var serieslen = Math.min(x.length, y.length); - if(x.length > serieslen) x.splice(serieslen, x.length - serieslen); - if(y.length > serieslen) y.splice(serieslen, y.length - serieslen); - +'use strict' + +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') + +var binFunctions = require('../histogram/bin_functions') +var normFunctions = require('../histogram/norm_functions') +var doAvg = require('../histogram/average') +var cleanBins = require('../histogram/clean_bins') + +module.exports = function calc (gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + x = trace.x ? xa.makeCalcdata(trace, 'x') : [], + ya = Axes.getFromId(gd, trace.yaxis || 'y'), + y = trace.y ? ya.makeCalcdata(trace, 'y') : [], + xcalendar = trace.xcalendar, + ycalendar = trace.ycalendar, + xr2c = function (v) { return xa.r2c(v, 0, xcalendar) }, + yr2c = function (v) { return ya.r2c(v, 0, ycalendar) }, + xc2r = function (v) { return xa.c2r(v, 0, xcalendar) }, + yc2r = function (v) { return ya.c2r(v, 0, ycalendar) }, + x0, + dx, + y0, + dy, + z, + i + + cleanBins(trace, xa, 'x') + cleanBins(trace, ya, 'y') + + var serieslen = Math.min(x.length, y.length) + if (x.length > serieslen) x.splice(serieslen, x.length - serieslen) + if (y.length > serieslen) y.splice(serieslen, y.length - serieslen) // calculate the bins - if(trace.autobinx || !('xbins' in trace)) { - trace.xbins = Axes.autoBin(x, xa, trace.nbinsx, '2d', xcalendar); - if(trace.type === 'histogram2dcontour') { + if (trace.autobinx || !('xbins' in trace)) { + trace.xbins = Axes.autoBin(x, xa, trace.nbinsx, '2d', xcalendar) + if (trace.type === 'histogram2dcontour') { // the "true" last argument reverses the tick direction (which we can't // just do with a minus sign because of month bins) - trace.xbins.start = xc2r(Axes.tickIncrement( - xr2c(trace.xbins.start), trace.xbins.size, true, xcalendar)); - trace.xbins.end = xc2r(Axes.tickIncrement( - xr2c(trace.xbins.end), trace.xbins.size, false, xcalendar)); - } + trace.xbins.start = xc2r(Axes.tickIncrement( + xr2c(trace.xbins.start), trace.xbins.size, true, xcalendar)) + trace.xbins.end = xc2r(Axes.tickIncrement( + xr2c(trace.xbins.end), trace.xbins.size, false, xcalendar)) + } // copy bin info back to the source data. - trace._input.xbins = trace.xbins; - } - if(trace.autobiny || !('ybins' in trace)) { - trace.ybins = Axes.autoBin(y, ya, trace.nbinsy, '2d', ycalendar); - if(trace.type === 'histogram2dcontour') { - trace.ybins.start = yc2r(Axes.tickIncrement( - yr2c(trace.ybins.start), trace.ybins.size, true, ycalendar)); - trace.ybins.end = yc2r(Axes.tickIncrement( - yr2c(trace.ybins.end), trace.ybins.size, false, ycalendar)); - } - trace._input.ybins = trace.ybins; + trace._input.xbins = trace.xbins + } + if (trace.autobiny || !('ybins' in trace)) { + trace.ybins = Axes.autoBin(y, ya, trace.nbinsy, '2d', ycalendar) + if (trace.type === 'histogram2dcontour') { + trace.ybins.start = yc2r(Axes.tickIncrement( + yr2c(trace.ybins.start), trace.ybins.size, true, ycalendar)) + trace.ybins.end = yc2r(Axes.tickIncrement( + yr2c(trace.ybins.end), trace.ybins.size, false, ycalendar)) } + trace._input.ybins = trace.ybins + } // make the empty bin array & scale the map - z = []; - var onecol = [], - zerocol = [], - nonuniformBinsX = (typeof(trace.xbins.size) === 'string'), - nonuniformBinsY = (typeof(trace.ybins.size) === 'string'), - xbins = nonuniformBinsX ? [] : trace.xbins, - ybins = nonuniformBinsY ? [] : trace.ybins, - total = 0, - n, - m, - counts = [], - norm = trace.histnorm, - func = trace.histfunc, - densitynorm = (norm.indexOf('density') !== -1), - extremefunc = (func === 'max' || func === 'min'), - sizeinit = (extremefunc ? null : 0), - binfunc = binFunctions.count, - normfunc = normFunctions[norm], - doavg = false, - xinc = [], - yinc = []; + z = [] + var onecol = [], + zerocol = [], + nonuniformBinsX = (typeof (trace.xbins.size) === 'string'), + nonuniformBinsY = (typeof (trace.ybins.size) === 'string'), + xbins = nonuniformBinsX ? [] : trace.xbins, + ybins = nonuniformBinsY ? [] : trace.ybins, + total = 0, + n, + m, + counts = [], + norm = trace.histnorm, + func = trace.histfunc, + densitynorm = (norm.indexOf('density') !== -1), + extremefunc = (func === 'max' || func === 'min'), + sizeinit = (extremefunc ? null : 0), + binfunc = binFunctions.count, + normfunc = normFunctions[norm], + doavg = false, + xinc = [], + yinc = [] // set a binning function other than count? // for binning functions: check first for 'z', // then 'mc' in case we had a colored scatter plot // and want to transfer these colors to the 2D histo // TODO: this is why we need a data picker in the popover... - var rawCounterData = ('z' in trace) ? + var rawCounterData = ('z' in trace) ? trace.z : (('marker' in trace && Array.isArray(trace.marker.color)) ? - trace.marker.color : ''); - if(rawCounterData && func !== 'count') { - doavg = func === 'avg'; - binfunc = binFunctions[func]; - } + trace.marker.color : '') + if (rawCounterData && func !== 'count') { + doavg = func === 'avg' + binfunc = binFunctions[func] + } // decrease end a little in case of rounding errors - var binspec = trace.xbins, - binStart = xr2c(binspec.start), - binEnd = xr2c(binspec.end) + - (binStart - Axes.tickIncrement(binStart, binspec.size, false, xcalendar)) / 1e6; - - for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, xcalendar)) { - onecol.push(sizeinit); - if(nonuniformBinsX) xbins.push(i); - if(doavg) zerocol.push(0); - } - if(nonuniformBinsX) xbins.push(i); - - var nx = onecol.length; - x0 = trace.xbins.start; - var x0c = xr2c(x0); - dx = (i - x0c) / nx; - x0 = xc2r(x0c + dx / 2); - - binspec = trace.ybins; - binStart = yr2c(binspec.start); - binEnd = yr2c(binspec.end) + - (binStart - Axes.tickIncrement(binStart, binspec.size, false, ycalendar)) / 1e6; - - for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, ycalendar)) { - z.push(onecol.concat()); - if(nonuniformBinsY) ybins.push(i); - if(doavg) counts.push(zerocol.concat()); - } - if(nonuniformBinsY) ybins.push(i); - - var ny = z.length; - y0 = trace.ybins.start; - var y0c = yr2c(y0); - dy = (i - y0c) / ny; - y0 = yc2r(y0c + dy / 2); - - if(densitynorm) { - xinc = onecol.map(function(v, i) { - if(nonuniformBinsX) return 1 / (xbins[i + 1] - xbins[i]); - return 1 / dx; - }); - yinc = z.map(function(v, i) { - if(nonuniformBinsY) return 1 / (ybins[i + 1] - ybins[i]); - return 1 / dy; - }); - } + var binspec = trace.xbins, + binStart = xr2c(binspec.start), + binEnd = xr2c(binspec.end) + + (binStart - Axes.tickIncrement(binStart, binspec.size, false, xcalendar)) / 1e6 + + for (i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, xcalendar)) { + onecol.push(sizeinit) + if (nonuniformBinsX) xbins.push(i) + if (doavg) zerocol.push(0) + } + if (nonuniformBinsX) xbins.push(i) + + var nx = onecol.length + x0 = trace.xbins.start + var x0c = xr2c(x0) + dx = (i - x0c) / nx + x0 = xc2r(x0c + dx / 2) + + binspec = trace.ybins + binStart = yr2c(binspec.start) + binEnd = yr2c(binspec.end) + + (binStart - Axes.tickIncrement(binStart, binspec.size, false, ycalendar)) / 1e6 + + for (i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, ycalendar)) { + z.push(onecol.concat()) + if (nonuniformBinsY) ybins.push(i) + if (doavg) counts.push(zerocol.concat()) + } + if (nonuniformBinsY) ybins.push(i) + + var ny = z.length + y0 = trace.ybins.start + var y0c = yr2c(y0) + dy = (i - y0c) / ny + y0 = yc2r(y0c + dy / 2) + + if (densitynorm) { + xinc = onecol.map(function (v, i) { + if (nonuniformBinsX) return 1 / (xbins[i + 1] - xbins[i]) + return 1 / dx + }) + yinc = z.map(function (v, i) { + if (nonuniformBinsY) return 1 / (ybins[i + 1] - ybins[i]) + return 1 / dy + }) + } // for date axes we need bin bounds to be calcdata. For nonuniform bins // we already have this, but uniform with start/end/size they're still strings. - if(!nonuniformBinsX && xa.type === 'date') { - xbins = { - start: xr2c(xbins.start), - end: xr2c(xbins.end), - size: xbins.size - }; + if (!nonuniformBinsX && xa.type === 'date') { + xbins = { + start: xr2c(xbins.start), + end: xr2c(xbins.end), + size: xbins.size } - if(!nonuniformBinsY && ya.type === 'date') { - ybins = { - start: yr2c(ybins.start), - end: yr2c(ybins.end), - size: ybins.size - }; + } + if (!nonuniformBinsY && ya.type === 'date') { + ybins = { + start: yr2c(ybins.start), + end: yr2c(ybins.end), + size: ybins.size } - + } // put data into bins - for(i = 0; i < serieslen; i++) { - n = Lib.findBin(x[i], xbins); - m = Lib.findBin(y[i], ybins); - if(n >= 0 && n < nx && m >= 0 && m < ny) { - total += binfunc(n, i, z[m], rawCounterData, counts[m]); - } + for (i = 0; i < serieslen; i++) { + n = Lib.findBin(x[i], xbins) + m = Lib.findBin(y[i], ybins) + if (n >= 0 && n < nx && m >= 0 && m < ny) { + total += binfunc(n, i, z[m], rawCounterData, counts[m]) } + } // normalize, if needed - if(doavg) { - for(m = 0; m < ny; m++) total += doAvg(z[m], counts[m]); - } - if(normfunc) { - for(m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]); - } - - return { - x: x, - x0: x0, - dx: dx, - y: y, - y0: y0, - dy: dy, - z: z - }; -}; + if (doavg) { + for (m = 0; m < ny; m++) total += doAvg(z[m], counts[m]) + } + if (normfunc) { + for (m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]) + } + + return { + x: x, + x0: x0, + dx: dx, + y: y, + y0: y0, + dy: dy, + z: z + } +} diff --git a/src/traces/histogram2d/defaults.js b/src/traces/histogram2d/defaults.js index 05b1c6ebbc4..b709afe9bf6 100644 --- a/src/traces/histogram2d/defaults.js +++ b/src/traces/histogram2d/defaults.js @@ -6,31 +6,29 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var handleSampleDefaults = require('./sample_defaults') +var colorscaleDefaults = require('../../components/colorscale/defaults') +var attributes = require('./attributes') -var handleSampleDefaults = require('./sample_defaults'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + handleSampleDefaults(traceIn, traceOut, coerce, layout) -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - handleSampleDefaults(traceIn, traceOut, coerce, layout); - - var zsmooth = coerce('zsmooth'); - if(zsmooth === false) { + var zsmooth = coerce('zsmooth') + if (zsmooth === false) { // ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect. - coerce('xgap'); - coerce('ygap'); - } + coerce('xgap') + coerce('ygap') + } - colorscaleDefaults( + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} - ); -}; + ) +} diff --git a/src/traces/histogram2d/index.js b/src/traces/histogram2d/index.js index fb0975bf58e..1f7c70103ed 100644 --- a/src/traces/histogram2d/index.js +++ b/src/traces/histogram2d/index.js @@ -6,33 +6,32 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Histogram2D = {} -var Histogram2D = {}; +Histogram2D.attributes = require('./attributes') +Histogram2D.supplyDefaults = require('./defaults') +Histogram2D.calc = require('../heatmap/calc') +Histogram2D.plot = require('../heatmap/plot') +Histogram2D.colorbar = require('../heatmap/colorbar') +Histogram2D.style = require('../heatmap/style') +Histogram2D.hoverPoints = require('../heatmap/hover') -Histogram2D.attributes = require('./attributes'); -Histogram2D.supplyDefaults = require('./defaults'); -Histogram2D.calc = require('../heatmap/calc'); -Histogram2D.plot = require('../heatmap/plot'); -Histogram2D.colorbar = require('../heatmap/colorbar'); -Histogram2D.style = require('../heatmap/style'); -Histogram2D.hoverPoints = require('../heatmap/hover'); - -Histogram2D.moduleType = 'trace'; -Histogram2D.name = 'histogram2d'; -Histogram2D.basePlotModule = require('../../plots/cartesian'); -Histogram2D.categories = ['cartesian', '2dMap', 'histogram']; +Histogram2D.moduleType = 'trace' +Histogram2D.name = 'histogram2d' +Histogram2D.basePlotModule = require('../../plots/cartesian') +Histogram2D.categories = ['cartesian', '2dMap', 'histogram'] Histogram2D.meta = { - hrName: 'histogram_2d', - description: [ - 'The sample data from which statistics are computed is set in `x`', - 'and `y` (where `x` and `y` represent marginal distributions,', - 'binning is set in `xbins` and `ybins` in this case)', - 'or `z` (where `z` represent the 2D distribution and binning set,', - 'binning is set by `x` and `y` in this case).', - 'The resulting distribution is visualized as a heatmap.' - ].join(' ') -}; + hrName: 'histogram_2d', + description: [ + 'The sample data from which statistics are computed is set in `x`', + 'and `y` (where `x` and `y` represent marginal distributions,', + 'binning is set in `xbins` and `ybins` in this case)', + 'or `z` (where `z` represent the 2D distribution and binning set,', + 'binning is set by `x` and `y` in this case).', + 'The resulting distribution is visualized as a heatmap.' + ].join(' ') +} -module.exports = Histogram2D; +module.exports = Histogram2D diff --git a/src/traces/histogram2d/sample_defaults.js b/src/traces/histogram2d/sample_defaults.js index c4483bb5022..f8a3193f733 100644 --- a/src/traces/histogram2d/sample_defaults.js +++ b/src/traces/histogram2d/sample_defaults.js @@ -6,33 +6,31 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var handleBinDefaults = require('../histogram/bin_defaults') -var Registry = require('../../registry'); -var handleBinDefaults = require('../histogram/bin_defaults'); - - -module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) { - var x = coerce('x'), - y = coerce('y'); +module.exports = function handleSampleDefaults (traceIn, traceOut, coerce, layout) { + var x = coerce('x'), + y = coerce('y') // we could try to accept x0 and dx, etc... // but that's a pretty weird use case. // for now require both x and y explicitly specified. - if(!(x && x.length && y && y.length)) { - traceOut.visible = false; - return; - } + if (!(x && x.length && y && y.length)) { + traceOut.visible = false + return + } - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout) // if marker.color is an array, we can use it in aggregation instead of z - var hasAggregationData = coerce('z') || coerce('marker.color'); + var hasAggregationData = coerce('z') || coerce('marker.color') - if(hasAggregationData) coerce('histfunc'); + if (hasAggregationData) coerce('histfunc') - var binDirections = ['x', 'y']; - handleBinDefaults(traceIn, traceOut, coerce, binDirections); -}; + var binDirections = ['x', 'y'] + handleBinDefaults(traceIn, traceOut, coerce, binDirections) +} diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index 0a7ba7f36c5..d8bc215def0 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -6,35 +6,35 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var histogram2dAttrs = require('../histogram2d/attributes'); -var contourAttrs = require('../contour/attributes'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var histogram2dAttrs = require('../histogram2d/attributes') +var contourAttrs = require('../contour/attributes') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat module.exports = extendFlat({}, { - x: histogram2dAttrs.x, - y: histogram2dAttrs.y, - z: histogram2dAttrs.z, - marker: histogram2dAttrs.marker, + x: histogram2dAttrs.x, + y: histogram2dAttrs.y, + z: histogram2dAttrs.z, + marker: histogram2dAttrs.marker, - histnorm: histogram2dAttrs.histnorm, - histfunc: histogram2dAttrs.histfunc, - autobinx: histogram2dAttrs.autobinx, - nbinsx: histogram2dAttrs.nbinsx, - xbins: histogram2dAttrs.xbins, - autobiny: histogram2dAttrs.autobiny, - nbinsy: histogram2dAttrs.nbinsy, - ybins: histogram2dAttrs.ybins, + histnorm: histogram2dAttrs.histnorm, + histfunc: histogram2dAttrs.histfunc, + autobinx: histogram2dAttrs.autobinx, + nbinsx: histogram2dAttrs.nbinsx, + xbins: histogram2dAttrs.xbins, + autobiny: histogram2dAttrs.autobiny, + nbinsy: histogram2dAttrs.nbinsy, + ybins: histogram2dAttrs.ybins, - autocontour: contourAttrs.autocontour, - ncontours: contourAttrs.ncontours, - contours: contourAttrs.contours, - line: contourAttrs.line + autocontour: contourAttrs.autocontour, + ncontours: contourAttrs.ncontours, + contours: contourAttrs.contours, + line: contourAttrs.line }, colorscaleAttrs, { colorbar: colorbarAttrs } -); +) diff --git a/src/traces/histogram2dcontour/defaults.js b/src/traces/histogram2dcontour/defaults.js index 21a2dbc0755..16362fd1c25 100644 --- a/src/traces/histogram2dcontour/defaults.js +++ b/src/traces/histogram2dcontour/defaults.js @@ -6,29 +6,27 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var handleSampleDefaults = require('../histogram2d/sample_defaults') +var handleStyleDefaults = require('../contour/style_defaults') +var attributes = require('./attributes') -var handleSampleDefaults = require('../histogram2d/sample_defaults'); -var handleStyleDefaults = require('../contour/style_defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + handleSampleDefaults(traceIn, traceOut, coerce, layout) -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var contourStart = Lib.coerce2(traceIn, traceOut, attributes, 'contours.start'), + contourEnd = Lib.coerce2(traceIn, traceOut, attributes, 'contours.end'), + autocontour = coerce('autocontour', !(contourStart && contourEnd)) - handleSampleDefaults(traceIn, traceOut, coerce, layout); + if (autocontour) coerce('ncontours') + else coerce('contours.size') - var contourStart = Lib.coerce2(traceIn, traceOut, attributes, 'contours.start'), - contourEnd = Lib.coerce2(traceIn, traceOut, attributes, 'contours.end'), - autocontour = coerce('autocontour', !(contourStart && contourEnd)); - - if(autocontour) coerce('ncontours'); - else coerce('contours.size'); - - handleStyleDefaults(traceIn, traceOut, coerce, layout); -}; + handleStyleDefaults(traceIn, traceOut, coerce, layout) +} diff --git a/src/traces/histogram2dcontour/index.js b/src/traces/histogram2dcontour/index.js index 1f4e8ef5d84..5c87da623ae 100644 --- a/src/traces/histogram2dcontour/index.js +++ b/src/traces/histogram2dcontour/index.js @@ -6,33 +6,32 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Histogram2dContour = {} -var Histogram2dContour = {}; +Histogram2dContour.attributes = require('./attributes') +Histogram2dContour.supplyDefaults = require('./defaults') +Histogram2dContour.calc = require('../contour/calc') +Histogram2dContour.plot = require('../contour/plot') +Histogram2dContour.style = require('../contour/style') +Histogram2dContour.colorbar = require('../contour/colorbar') +Histogram2dContour.hoverPoints = require('../contour/hover') -Histogram2dContour.attributes = require('./attributes'); -Histogram2dContour.supplyDefaults = require('./defaults'); -Histogram2dContour.calc = require('../contour/calc'); -Histogram2dContour.plot = require('../contour/plot'); -Histogram2dContour.style = require('../contour/style'); -Histogram2dContour.colorbar = require('../contour/colorbar'); -Histogram2dContour.hoverPoints = require('../contour/hover'); - -Histogram2dContour.moduleType = 'trace'; -Histogram2dContour.name = 'histogram2dcontour'; -Histogram2dContour.basePlotModule = require('../../plots/cartesian'); -Histogram2dContour.categories = ['cartesian', '2dMap', 'contour', 'histogram']; +Histogram2dContour.moduleType = 'trace' +Histogram2dContour.name = 'histogram2dcontour' +Histogram2dContour.basePlotModule = require('../../plots/cartesian') +Histogram2dContour.categories = ['cartesian', '2dMap', 'contour', 'histogram'] Histogram2dContour.meta = { - hrName: 'histogram_2d_contour', - description: [ - 'The sample data from which statistics are computed is set in `x`', - 'and `y` (where `x` and `y` represent marginal distributions,', - 'binning is set in `xbins` and `ybins` in this case)', - 'or `z` (where `z` represent the 2D distribution and binning set,', - 'binning is set by `x` and `y` in this case).', - 'The resulting distribution is visualized as a contour plot.' - ].join(' ') -}; + hrName: 'histogram_2d_contour', + description: [ + 'The sample data from which statistics are computed is set in `x`', + 'and `y` (where `x` and `y` represent marginal distributions,', + 'binning is set in `xbins` and `ybins` in this case)', + 'or `z` (where `z` represent the 2D distribution and binning set,', + 'binning is set by `x` and `y` in this case).', + 'The resulting distribution is visualized as a contour plot.' + ].join(' ') +} -module.exports = Histogram2dContour; +module.exports = Histogram2dContour diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index 3796850f51a..c7a6504263f 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -6,191 +6,190 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); -var surfaceAtts = require('../surface/attributes'); - -var extendFlat = require('../../lib/extend').extendFlat; +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') +var surfaceAtts = require('../surface/attributes') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { - x: { - valType: 'data_array', - description: [ - 'Sets the X coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - y: { - valType: 'data_array', - description: [ - 'Sets the Y coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - z: { - valType: 'data_array', - description: [ - 'Sets the Z coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - - i: { - valType: 'data_array', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *first* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', - 'together represent face m (triangle m) in the mesh, where `i[m] = n` points to the triplet', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `i` represents a', - 'point in space, which is the first vertex of a triangle.' - ].join(' ') - }, - j: { - valType: 'data_array', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *second* vertex of a triangle. For example, `{i[m], j[m], k[m]}` ', - 'together represent face m (triangle m) in the mesh, where `j[m] = n` points to the triplet', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `j` represents a', - 'point in space, which is the second vertex of a triangle.' - ].join(' ') - - }, - k: { - valType: 'data_array', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *third* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', - 'together represent face m (triangle m) in the mesh, where `k[m] = n` points to the triplet ', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `k` represents a', - 'point in space, which is the third vertex of a triangle.' - ].join(' ') - - }, - - delaunayaxis: { - valType: 'enumerated', - role: 'info', - values: [ 'x', 'y', 'z' ], - dflt: 'z', - description: [ - 'Sets the Delaunay axis, which is the axis that is perpendicular to the surface of the', - 'Delaunay triangulation.', - 'It has an effect if `i`, `j`, `k` are not provided and `alphahull` is set to indicate', - 'Delaunay triangulation.' - ].join(' ') - }, - - alphahull: { - valType: 'number', - role: 'style', - dflt: -1, - description: [ - 'Determines how the mesh surface triangles are derived from the set of', - 'vertices (points) represented by the `x`, `y` and `z` arrays, if', - 'the `i`, `j`, `k` arrays are not supplied.', - 'For general use of `mesh3d` it is preferred that `i`, `j`, `k` are', - 'supplied.', - - 'If *-1*, Delaunay triangulation is used, which is mainly suitable if the', - 'mesh is a single, more or less layer surface that is perpendicular to `delaunayaxis`.', - 'In case the `delaunayaxis` intersects the mesh surface at more than one point', - 'it will result triangles that are very long in the dimension of `delaunayaxis`.', - - 'If *>0*, the alpha-shape algorithm is used. In this case, the positive `alphahull` value', - 'signals the use of the alpha-shape algorithm, _and_ its value', - 'acts as the parameter for the mesh fitting.', - - 'If *0*, the convex-hull algorithm is used. It is suitable for convex bodies', - 'or if the intention is to enclose the `x`, `y` and `z` point set into a convex', - 'hull.' - ].join(' ') - }, - - intensity: { - valType: 'data_array', - description: [ - 'Sets the vertex intensity values,', - 'used for plotting fields on meshes' - ].join(' ') - }, + x: { + valType: 'data_array', + description: [ + 'Sets the X coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, + y: { + valType: 'data_array', + description: [ + 'Sets the Y coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, + z: { + valType: 'data_array', + description: [ + 'Sets the Z coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, + + i: { + valType: 'data_array', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *first* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', + 'together represent face m (triangle m) in the mesh, where `i[m] = n` points to the triplet', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `i` represents a', + 'point in space, which is the first vertex of a triangle.' + ].join(' ') + }, + j: { + valType: 'data_array', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *second* vertex of a triangle. For example, `{i[m], j[m], k[m]}` ', + 'together represent face m (triangle m) in the mesh, where `j[m] = n` points to the triplet', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `j` represents a', + 'point in space, which is the second vertex of a triangle.' + ].join(' ') + + }, + k: { + valType: 'data_array', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *third* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', + 'together represent face m (triangle m) in the mesh, where `k[m] = n` points to the triplet ', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `k` represents a', + 'point in space, which is the third vertex of a triangle.' + ].join(' ') + + }, + + delaunayaxis: { + valType: 'enumerated', + role: 'info', + values: [ 'x', 'y', 'z' ], + dflt: 'z', + description: [ + 'Sets the Delaunay axis, which is the axis that is perpendicular to the surface of the', + 'Delaunay triangulation.', + 'It has an effect if `i`, `j`, `k` are not provided and `alphahull` is set to indicate', + 'Delaunay triangulation.' + ].join(' ') + }, + + alphahull: { + valType: 'number', + role: 'style', + dflt: -1, + description: [ + 'Determines how the mesh surface triangles are derived from the set of', + 'vertices (points) represented by the `x`, `y` and `z` arrays, if', + 'the `i`, `j`, `k` arrays are not supplied.', + 'For general use of `mesh3d` it is preferred that `i`, `j`, `k` are', + 'supplied.', + + 'If *-1*, Delaunay triangulation is used, which is mainly suitable if the', + 'mesh is a single, more or less layer surface that is perpendicular to `delaunayaxis`.', + 'In case the `delaunayaxis` intersects the mesh surface at more than one point', + 'it will result triangles that are very long in the dimension of `delaunayaxis`.', + + 'If *>0*, the alpha-shape algorithm is used. In this case, the positive `alphahull` value', + 'signals the use of the alpha-shape algorithm, _and_ its value', + 'acts as the parameter for the mesh fitting.', + + 'If *0*, the convex-hull algorithm is used. It is suitable for convex bodies', + 'or if the intention is to enclose the `x`, `y` and `z` point set into a convex', + 'hull.' + ].join(' ') + }, + + intensity: { + valType: 'data_array', + description: [ + 'Sets the vertex intensity values,', + 'used for plotting fields on meshes' + ].join(' ') + }, // Color field - color: { - valType: 'color', - role: 'style', - description: 'Sets the color of the whole mesh' - }, - vertexcolor: { - valType: 'data_array', // FIXME: this should be a color array - role: 'style', - description: [ - 'Sets the color of each vertex', - 'Overrides *color*.' - ].join(' ') - }, - facecolor: { - valType: 'data_array', - role: 'style', - description: [ - 'Sets the color of each face', - 'Overrides *color* and *vertexcolor*.' - ].join(' ') - }, + color: { + valType: 'color', + role: 'style', + description: 'Sets the color of the whole mesh' + }, + vertexcolor: { + valType: 'data_array', // FIXME: this should be a color array + role: 'style', + description: [ + 'Sets the color of each vertex', + 'Overrides *color*.' + ].join(' ') + }, + facecolor: { + valType: 'data_array', + role: 'style', + description: [ + 'Sets the color of each face', + 'Overrides *color* and *vertexcolor*.' + ].join(' ') + }, // Opacity - opacity: extendFlat({}, surfaceAtts.opacity), + opacity: extendFlat({}, surfaceAtts.opacity), // Flat shaded mode - flatshading: { - valType: 'boolean', - role: 'style', - dflt: false, - description: [ - 'Determines whether or not normal smoothing is applied to the meshes,', - 'creating meshes with an angular, low-poly look via flat reflections.' - ].join(' ') - }, - - contour: { - show: extendFlat({}, surfaceAtts.contours.x.show, { - description: [ - 'Sets whether or not dynamic contours are shown on hover' - ].join(' ') - }), - color: extendFlat({}, surfaceAtts.contours.x.color), - width: extendFlat({}, surfaceAtts.contours.x.width) - }, - - colorscale: colorscaleAttrs.colorscale, - reversescale: colorscaleAttrs.reversescale, - showscale: colorscaleAttrs.showscale, - colorbar: colorbarAttrs, - - lightposition: { - 'x': extendFlat({}, surfaceAtts.lightposition.x, {dflt: 1e5}), - 'y': extendFlat({}, surfaceAtts.lightposition.y, {dflt: 1e5}), - 'z': extendFlat({}, surfaceAtts.lightposition.z, {dflt: 0}) + flatshading: { + valType: 'boolean', + role: 'style', + dflt: false, + description: [ + 'Determines whether or not normal smoothing is applied to the meshes,', + 'creating meshes with an angular, low-poly look via flat reflections.' + ].join(' ') + }, + + contour: { + show: extendFlat({}, surfaceAtts.contours.x.show, { + description: [ + 'Sets whether or not dynamic contours are shown on hover' + ].join(' ') + }), + color: extendFlat({}, surfaceAtts.contours.x.color), + width: extendFlat({}, surfaceAtts.contours.x.width) + }, + + colorscale: colorscaleAttrs.colorscale, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, + colorbar: colorbarAttrs, + + lightposition: { + 'x': extendFlat({}, surfaceAtts.lightposition.x, {dflt: 1e5}), + 'y': extendFlat({}, surfaceAtts.lightposition.y, {dflt: 1e5}), + 'z': extendFlat({}, surfaceAtts.lightposition.z, {dflt: 0}) + }, + lighting: extendFlat({}, { + vertexnormalsepsilon: { + valType: 'number', + role: 'style', + min: 0.00, + max: 1, + dflt: 1e-12, // otherwise finely tessellated things eg. the brain will have no specular light reflection + description: 'Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.' }, - lighting: extendFlat({}, { - vertexnormalsepsilon: { - valType: 'number', - role: 'style', - min: 0.00, - max: 1, - dflt: 1e-12, // otherwise finely tessellated things eg. the brain will have no specular light reflection - description: 'Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.' - }, - facenormalsepsilon: { - valType: 'number', - role: 'style', - min: 0.00, - max: 1, - dflt: 1e-6, // even the brain model doesn't appear to need finer than this - description: 'Epsilon for face normals calculation avoids math issues arising from degenerate geometry.' - } - }, surfaceAtts.lighting) -}; + facenormalsepsilon: { + valType: 'number', + role: 'style', + min: 0.00, + max: 1, + dflt: 1e-6, // even the brain model doesn't appear to need finer than this + description: 'Epsilon for face normals calculation avoids math issues arising from degenerate geometry.' + } + }, surfaceAtts.lighting) +} diff --git a/src/traces/mesh3d/convert.js b/src/traces/mesh3d/convert.js index 13768b0b62d..baa8eefe3b4 100644 --- a/src/traces/mesh3d/convert.js +++ b/src/traces/mesh3d/convert.js @@ -6,155 +6,147 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var createMesh = require('gl-mesh3d') +var tinycolor = require('tinycolor2') +var triangulate = require('delaunay-triangulate') +var alphaShape = require('alpha-shape') +var convexHull = require('convex-hull') + +var str2RgbaArray = require('../../lib/str2rgbarray') + +function Mesh3DTrace (scene, mesh, uid) { + this.scene = scene + this.uid = uid + this.mesh = mesh + this.name = '' + this.color = '#fff' + this.data = null + this.showContour = false +} -'use strict'; - -var createMesh = require('gl-mesh3d'); -var tinycolor = require('tinycolor2'); -var triangulate = require('delaunay-triangulate'); -var alphaShape = require('alpha-shape'); -var convexHull = require('convex-hull'); +var proto = Mesh3DTrace.prototype -var str2RgbaArray = require('../../lib/str2rgbarray'); +proto.handlePick = function (selection) { + if (selection.object === this.mesh) { + var selectIndex = selection.data.index + selection.traceCoordinate = [ + this.data.x[selectIndex], + this.data.y[selectIndex], + this.data.z[selectIndex] + ] -function Mesh3DTrace(scene, mesh, uid) { - this.scene = scene; - this.uid = uid; - this.mesh = mesh; - this.name = ''; - this.color = '#fff'; - this.data = null; - this.showContour = false; + return true + } } -var proto = Mesh3DTrace.prototype; - -proto.handlePick = function(selection) { - if(selection.object === this.mesh) { - var selectIndex = selection.data.index; - - selection.traceCoordinate = [ - this.data.x[selectIndex], - this.data.y[selectIndex], - this.data.z[selectIndex] - ]; - - return true; +function parseColorScale (colorscale) { + return colorscale.map(function (elem) { + var index = elem[0] + var color = tinycolor(elem[1]) + var rgb = color.toRgb() + return { + index: index, + rgb: [rgb.r, rgb.g, rgb.b, 1] } -}; - -function parseColorScale(colorscale) { - return colorscale.map(function(elem) { - var index = elem[0]; - var color = tinycolor(elem[1]); - var rgb = color.toRgb(); - return { - index: index, - rgb: [rgb.r, rgb.g, rgb.b, 1] - }; - }); + }) } -function parseColorArray(colors) { - return colors.map(str2RgbaArray); +function parseColorArray (colors) { + return colors.map(str2RgbaArray) } -function zip3(x, y, z) { - var result = new Array(x.length); - for(var i = 0; i < x.length; ++i) { - result[i] = [x[i], y[i], z[i]]; - } - return result; +function zip3 (x, y, z) { + var result = new Array(x.length) + for (var i = 0; i < x.length; ++i) { + result[i] = [x[i], y[i], z[i]] + } + return result } -proto.update = function(data) { - var scene = this.scene, - layout = scene.fullSceneLayout; +proto.update = function (data) { + var scene = this.scene, + layout = scene.fullSceneLayout - this.data = data; + this.data = data // Unpack position data - function toDataCoords(axis, coord, scale, calendar) { - return coord.map(function(x) { - return axis.d2l(x, 0, calendar) * scale; - }); - } + function toDataCoords (axis, coord, scale, calendar) { + return coord.map(function (x) { + return axis.d2l(x, 0, calendar) * scale + }) + } - var positions = zip3( + var positions = zip3( toDataCoords(layout.xaxis, data.x, scene.dataScale[0], data.xcalendar), toDataCoords(layout.yaxis, data.y, scene.dataScale[1], data.ycalendar), - toDataCoords(layout.zaxis, data.z, scene.dataScale[2], data.zcalendar)); + toDataCoords(layout.zaxis, data.z, scene.dataScale[2], data.zcalendar)) + + var cells + if (data.i && data.j && data.k) { + cells = zip3(data.i, data.j, data.k) + } else if (data.alphahull === 0) { + cells = convexHull(positions) + } else if (data.alphahull > 0) { + cells = alphaShape(data.alphahull, positions) + } else { + var d = ['x', 'y', 'z'].indexOf(data.delaunayaxis) + cells = triangulate(positions.map(function (c) { + return [c[(d + 1) % 3], c[(d + 2) % 3]] + })) + } + + var config = { + positions: positions, + cells: cells, + lightPosition: [data.lightposition.x, data.lightposition.y, data.lightposition.z], + ambient: data.lighting.ambient, + diffuse: data.lighting.diffuse, + specular: data.lighting.specular, + roughness: data.lighting.roughness, + fresnel: data.lighting.fresnel, + vertexNormalsEpsilon: data.lighting.vertexnormalsepsilon, + faceNormalsEpsilon: data.lighting.facenormalsepsilon, + opacity: data.opacity, + contourEnable: data.contour.show, + contourColor: str2RgbaArray(data.contour.color).slice(0, 3), + contourWidth: data.contour.width, + useFacetNormals: data.flatshading + } + + if (data.intensity) { + this.color = '#fff' + config.vertexIntensity = data.intensity + config.colormap = parseColorScale(data.colorscale) + } else if (data.vertexcolor) { + this.color = data.vertexcolors[0] + config.vertexColors = parseColorArray(data.vertexcolor) + } else if (data.facecolor) { + this.color = data.facecolor[0] + config.cellColors = parseColorArray(data.facecolor) + } else { + this.color = data.color + config.meshColor = str2RgbaArray(data.color) + } - var cells; - if(data.i && data.j && data.k) { - cells = zip3(data.i, data.j, data.k); - } - else if(data.alphahull === 0) { - cells = convexHull(positions); - } - else if(data.alphahull > 0) { - cells = alphaShape(data.alphahull, positions); - } - else { - var d = ['x', 'y', 'z'].indexOf(data.delaunayaxis); - cells = triangulate(positions.map(function(c) { - return [c[(d + 1) % 3], c[(d + 2) % 3]]; - })); - } + // Update mesh + this.mesh.update(config) +} - var config = { - positions: positions, - cells: cells, - lightPosition: [data.lightposition.x, data.lightposition.y, data.lightposition.z], - ambient: data.lighting.ambient, - diffuse: data.lighting.diffuse, - specular: data.lighting.specular, - roughness: data.lighting.roughness, - fresnel: data.lighting.fresnel, - vertexNormalsEpsilon: data.lighting.vertexnormalsepsilon, - faceNormalsEpsilon: data.lighting.facenormalsepsilon, - opacity: data.opacity, - contourEnable: data.contour.show, - contourColor: str2RgbaArray(data.contour.color).slice(0, 3), - contourWidth: data.contour.width, - useFacetNormals: data.flatshading - }; - - if(data.intensity) { - this.color = '#fff'; - config.vertexIntensity = data.intensity; - config.colormap = parseColorScale(data.colorscale); - } - else if(data.vertexcolor) { - this.color = data.vertexcolors[0]; - config.vertexColors = parseColorArray(data.vertexcolor); - } - else if(data.facecolor) { - this.color = data.facecolor[0]; - config.cellColors = parseColorArray(data.facecolor); - } - else { - this.color = data.color; - config.meshColor = str2RgbaArray(data.color); - } +proto.dispose = function () { + this.scene.glplot.remove(this.mesh) + this.mesh.dispose() +} - // Update mesh - this.mesh.update(config); -}; - -proto.dispose = function() { - this.scene.glplot.remove(this.mesh); - this.mesh.dispose(); -}; - -function createMesh3DTrace(scene, data) { - var gl = scene.glplot.gl; - var mesh = createMesh({gl: gl}); - var result = new Mesh3DTrace(scene, mesh, data.uid); - result.update(data); - scene.glplot.add(mesh); - return result; +function createMesh3DTrace (scene, data) { + var gl = scene.glplot.gl + var mesh = createMesh({gl: gl}) + var result = new Mesh3DTrace(scene, mesh, data.uid) + result.update(data) + scene.glplot.add(mesh) + return result } -module.exports = createMesh3DTrace; +module.exports = createMesh3DTrace diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index d731a077cf3..8042ea27e67 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -6,94 +6,91 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Lib = require('../../lib') +var colorbarDefaults = require('../../components/colorbar/defaults') +var attributes = require('./attributes') -var Registry = require('../../registry'); -var Lib = require('../../lib'); -var colorbarDefaults = require('../../components/colorbar/defaults'); -var attributes = require('./attributes'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } // read in face/vertex properties - function readComponents(array) { - var ret = array.map(function(attr) { - var result = coerce(attr); + function readComponents (array) { + var ret = array.map(function (attr) { + var result = coerce(attr) - if(result && Array.isArray(result)) return result; - return null; - }); + if (result && Array.isArray(result)) return result + return null + }) - return ret.every(function(x) { - return x && x.length === ret[0].length; - }) && ret; - } + 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']); + var coords = readComponents(['x', 'y', 'z']) + var indices = readComponents(['i', 'j', 'k']) - if(!coords) { - traceOut.visible = false; - return; - } + if (!coords) { + traceOut.visible = false + return + } - if(indices) { + if (indices) { // otherwise, convert all face indices to ints - indices.forEach(function(index) { - for(var i = 0; i < index.length; ++i) index[i] |= 0; - }); - } + indices.forEach(function (index) { + for (var i = 0; i < index.length; ++i) index[i] |= 0 + }) + } - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); // Coerce remaining properties - [ - 'lighting.ambient', - 'lighting.diffuse', - 'lighting.specular', - 'lighting.roughness', - 'lighting.fresnel', - 'lighting.vertexnormalsepsilon', - 'lighting.facenormalsepsilon', - 'lightposition.x', - 'lightposition.y', - 'lightposition.z', - 'contour.show', - 'contour.color', - 'contour.width', - 'colorscale', - 'reversescale', - 'flatshading', - 'alphahull', - 'delaunayaxis', - 'opacity' - ].forEach(function(x) { coerce(x); }); - - if('intensity' in traceIn) { - coerce('intensity'); - coerce('showscale', true); - } - else { - traceOut.showscale = false; - - if('vertexcolor' in traceIn) coerce('vertexcolor'); - else if('facecolor' in traceIn) coerce('facecolor'); - else coerce('color', defaultColor); - } - - if(traceOut.reversescale) { - traceOut.colorscale = traceOut.colorscale.map(function(si) { - return [1 - si[0], si[1]]; - }).reverse(); - } - - if(traceOut.showscale) { - colorbarDefaults(traceIn, traceOut, layout); - } -}; + [ + 'lighting.ambient', + 'lighting.diffuse', + 'lighting.specular', + 'lighting.roughness', + 'lighting.fresnel', + 'lighting.vertexnormalsepsilon', + 'lighting.facenormalsepsilon', + 'lightposition.x', + 'lightposition.y', + 'lightposition.z', + 'contour.show', + 'contour.color', + 'contour.width', + 'colorscale', + 'reversescale', + 'flatshading', + 'alphahull', + 'delaunayaxis', + 'opacity' + ].forEach(function (x) { coerce(x) }) + + if ('intensity' in traceIn) { + coerce('intensity') + coerce('showscale', true) + } else { + traceOut.showscale = false + + if ('vertexcolor' in traceIn) coerce('vertexcolor') + else if ('facecolor' in traceIn) coerce('facecolor') + else coerce('color', defaultColor) + } + + if (traceOut.reversescale) { + traceOut.colorscale = traceOut.colorscale.map(function (si) { + return [1 - si[0], si[1]] + }).reverse() + } + + if (traceOut.showscale) { + colorbarDefaults(traceIn, traceOut, layout) + } +} diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index 34eed6c6aa9..24278d8cab4 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -6,29 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Mesh3D = {} -var Mesh3D = {}; +Mesh3D.attributes = require('./attributes') +Mesh3D.supplyDefaults = require('./defaults') +Mesh3D.colorbar = require('../heatmap/colorbar') +Mesh3D.plot = require('./convert') -Mesh3D.attributes = require('./attributes'); -Mesh3D.supplyDefaults = require('./defaults'); -Mesh3D.colorbar = require('../heatmap/colorbar'); -Mesh3D.plot = require('./convert'); - -Mesh3D.moduleType = 'trace'; +Mesh3D.moduleType = 'trace' Mesh3D.name = 'mesh3d', -Mesh3D.basePlotModule = require('../../plots/gl3d'); -Mesh3D.categories = ['gl3d']; +Mesh3D.basePlotModule = require('../../plots/gl3d') +Mesh3D.categories = ['gl3d'] Mesh3D.meta = { - description: [ - 'Draws sets of triangles with coordinates given by', - 'three 1-dimensional arrays in `x`, `y`, `z` and', - '(1) a sets of `i`, `j`, `k` indices', - '(2) Delaunay triangulation or', - '(3) the Alpha-shape algorithm or', - '(4) the Convex-hull algorithm' - ].join(' ') -}; + description: [ + 'Draws sets of triangles with coordinates given by', + 'three 1-dimensional arrays in `x`, `y`, `z` and', + '(1) a sets of `i`, `j`, `k` indices', + '(2) Delaunay triangulation or', + '(3) the Alpha-shape algorithm or', + '(4) the Convex-hull algorithm' + ].join(' ') +} -module.exports = Mesh3D; +module.exports = Mesh3D diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 02d50c76486..5943a37e312 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -6,128 +6,127 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var scatterAttrs = require('../scatter/attributes') -var Lib = require('../../lib'); -var scatterAttrs = require('../scatter/attributes'); +var INCREASING_COLOR = '#3D9970' +var DECREASING_COLOR = '#FF4136' -var INCREASING_COLOR = '#3D9970'; -var DECREASING_COLOR = '#FF4136'; - -var lineAttrs = scatterAttrs.line; +var lineAttrs = scatterAttrs.line var directionAttrs = { - name: { - valType: 'string', - role: 'info', - description: [ - 'Sets the segment name.', - 'The segment name appear as the legend item and on hover.' - ].join(' ') - }, - - showlegend: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not an item corresponding to this', - 'segment is shown in the legend.' - ].join(' ') - }, - - line: { - color: Lib.extendFlat({}, lineAttrs.color), - width: Lib.extendFlat({}, lineAttrs.width), - dash: Lib.extendFlat({}, lineAttrs.dash), - } -}; + name: { + valType: 'string', + role: 'info', + description: [ + 'Sets the segment name.', + 'The segment name appear as the legend item and on hover.' + ].join(' ') + }, + + showlegend: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not an item corresponding to this', + 'segment is shown in the legend.' + ].join(' ') + }, + + line: { + color: Lib.extendFlat({}, lineAttrs.color), + width: Lib.extendFlat({}, lineAttrs.width), + dash: Lib.extendFlat({}, lineAttrs.dash) + } +} module.exports = { - x: { - valType: 'data_array', - description: [ - 'Sets the x coordinates.', - 'If absent, linear coordinate will be generated.' - ].join(' ') - }, - - open: { - valType: 'data_array', - dflt: [], - description: 'Sets the open values.' - }, - - high: { - valType: 'data_array', - dflt: [], - description: 'Sets the high values.' - }, - - low: { - valType: 'data_array', - dflt: [], - description: 'Sets the low values.' - }, - - close: { - valType: 'data_array', - dflt: [], - description: 'Sets the close values.' - }, - - line: { - width: Lib.extendFlat({}, lineAttrs.width, { - description: [ - lineAttrs.width, - 'Note that this style setting can also be set per', - 'direction via `increasing.line.width` and', - '`decreasing.line.width`.' - ].join(' ') - }), - dash: Lib.extendFlat({}, lineAttrs.dash, { - description: [ - lineAttrs.dash, - 'Note that this style setting can also be set per', - 'direction via `increasing.line.dash` and', - '`decreasing.line.dash`.' - ].join(' ') - }), - }, - - increasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: INCREASING_COLOR } } - }), - - decreasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: DECREASING_COLOR } } + x: { + valType: 'data_array', + description: [ + 'Sets the x coordinates.', + 'If absent, linear coordinate will be generated.' + ].join(' ') + }, + + open: { + valType: 'data_array', + dflt: [], + description: 'Sets the open values.' + }, + + high: { + valType: 'data_array', + dflt: [], + description: 'Sets the high values.' + }, + + low: { + valType: 'data_array', + dflt: [], + description: 'Sets the low values.' + }, + + close: { + valType: 'data_array', + dflt: [], + description: 'Sets the close values.' + }, + + line: { + width: Lib.extendFlat({}, lineAttrs.width, { + description: [ + lineAttrs.width, + 'Note that this style setting can also be set per', + 'direction via `increasing.line.width` and', + '`decreasing.line.width`.' + ].join(' ') }), - - text: { - valType: 'string', - role: 'info', - dflt: '', - arrayOk: true, - description: [ - 'Sets hover text elements associated with each sample point.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to', - 'this trace\'s sample points.' - ].join(' ') - }, - - tickwidth: { - valType: 'number', - min: 0, - max: 0.5, - dflt: 0.3, - role: 'style', - description: [ - 'Sets the width of the open/close tick marks', - 'relative to the *x* minimal interval.' - ].join(' ') - } -}; + dash: Lib.extendFlat({}, lineAttrs.dash, { + description: [ + lineAttrs.dash, + 'Note that this style setting can also be set per', + 'direction via `increasing.line.dash` and', + '`decreasing.line.dash`.' + ].join(' ') + }) + }, + + increasing: Lib.extendDeep({}, directionAttrs, { + line: { color: { dflt: INCREASING_COLOR } } + }), + + decreasing: Lib.extendDeep({}, directionAttrs, { + line: { color: { dflt: DECREASING_COLOR } } + }), + + text: { + valType: 'string', + role: 'info', + dflt: '', + arrayOk: true, + description: [ + 'Sets hover text elements associated with each sample point.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to', + 'this trace\'s sample points.' + ].join(' ') + }, + + tickwidth: { + valType: 'number', + min: 0, + max: 0.5, + dflt: 0.3, + role: 'style', + description: [ + 'Sets the width of the open/close tick marks', + 'relative to the *x* minimal interval.' + ].join(' ') + } +} diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js index da557610a49..485aa57196c 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -6,42 +6,41 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var handleOHLC = require('./ohlc_defaults') +var handleDirectionDefaults = require('./direction_defaults') +var attributes = require('./attributes') +var helpers = require('./helpers') -var Lib = require('../../lib'); -var handleOHLC = require('./ohlc_defaults'); -var handleDirectionDefaults = require('./direction_defaults'); -var attributes = require('./attributes'); -var helpers = require('./helpers'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + helpers.pushDummyTransformOpts(traceIn, traceOut) -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - helpers.pushDummyTransformOpts(traceIn, traceOut); + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + var len = handleOHLC(traceIn, traceOut, coerce, layout) + if (len === 0) { + traceOut.visible = false + return + } - var len = handleOHLC(traceIn, traceOut, coerce, layout); - if(len === 0) { - traceOut.visible = false; - return; - } + coerce('line.width') + coerce('line.dash') - coerce('line.width'); - coerce('line.dash'); + handleDirection(traceIn, traceOut, coerce, 'increasing') + handleDirection(traceIn, traceOut, coerce, 'decreasing') - handleDirection(traceIn, traceOut, coerce, 'increasing'); - handleDirection(traceIn, traceOut, coerce, 'decreasing'); - - coerce('text'); - coerce('tickwidth'); -}; + coerce('text') + coerce('tickwidth') +} -function handleDirection(traceIn, traceOut, coerce, direction) { - handleDirectionDefaults(traceIn, traceOut, coerce, direction); +function handleDirection (traceIn, traceOut, coerce, direction) { + handleDirectionDefaults(traceIn, traceOut, coerce, direction) - coerce(direction + '.line.color'); - coerce(direction + '.line.width', traceOut.line.width); - coerce(direction + '.line.dash', traceOut.line.dash); + coerce(direction + '.line.color') + coerce(direction + '.line.width', traceOut.line.width) + coerce(direction + '.line.dash', traceOut.line.dash) } diff --git a/src/traces/ohlc/direction_defaults.js b/src/traces/ohlc/direction_defaults.js index 801b4444319..02e2ef4f7a9 100644 --- a/src/traces/ohlc/direction_defaults.js +++ b/src/traces/ohlc/direction_defaults.js @@ -6,19 +6,17 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - - -module.exports = function handleDirectionDefaults(traceIn, traceOut, coerce, direction) { - coerce(direction + '.showlegend'); +module.exports = function handleDirectionDefaults (traceIn, traceOut, coerce, direction) { + coerce(direction + '.showlegend') // trace-wide *showlegend* overrides direction *showlegend* - if(traceIn.showlegend === false) { - traceOut[direction].showlegend = false; - } + if (traceIn.showlegend === false) { + traceOut[direction].showlegend = false + } - var nameDflt = traceOut.name + ' - ' + direction; + var nameDflt = traceOut.name + ' - ' + direction - coerce(direction + '.name', nameDflt); -}; + coerce(direction + '.name', nameDflt) +} diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js index e7fca7d0d60..5e8e0c87190 100644 --- a/src/traces/ohlc/helpers.js +++ b/src/traces/ohlc/helpers.js @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); +var Lib = require('../../lib') // This routine gets called during the trace supply-defaults step. // @@ -21,38 +20,37 @@ var Lib = require('../../lib'); // defaults (which are called after trace defaults) start // from a clear transforms container. The mutations inflicted are // cleared in exports.clearEphemeralTransformOpts. -exports.pushDummyTransformOpts = function(traceIn, traceOut) { - var transformOpts = { +exports.pushDummyTransformOpts = function (traceIn, traceOut) { + var transformOpts = { // give dummy transform the same type as trace - type: traceOut.type, + type: traceOut.type, // track ephemeral transforms in user data - _ephemeral: true - }; + _ephemeral: true + } - if(Array.isArray(traceIn.transforms)) { - traceIn.transforms.push(transformOpts); - } - else { - traceIn.transforms = [transformOpts]; - } -}; + if (Array.isArray(traceIn.transforms)) { + traceIn.transforms.push(transformOpts) + } else { + traceIn.transforms = [transformOpts] + } +} // This routine gets called during the transform supply-defaults step // where it clears ephemeral transform opts in user data // and effectively put back user date in its pre-supplyDefaults state. -exports.clearEphemeralTransformOpts = function(traceIn) { - var transformsIn = traceIn.transforms; +exports.clearEphemeralTransformOpts = function (traceIn) { + var transformsIn = traceIn.transforms - if(!Array.isArray(transformsIn)) return; + if (!Array.isArray(transformsIn)) return - for(var i = 0; i < transformsIn.length; i++) { - if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1); - } + for (var i = 0; i < transformsIn.length; i++) { + if (transformsIn[i]._ephemeral) transformsIn.splice(i, 1) + } - if(transformsIn.length === 0) delete traceIn.transforms; -}; + if (transformsIn.length === 0) delete traceIn.transforms +} // This routine gets called during the transform supply-defaults step // where it passes 'ohlc' and 'candlestick' attributes @@ -62,12 +60,12 @@ exports.clearEphemeralTransformOpts = function(traceIn) { // // Note that this routine only has an effect during the // second round of transform defaults done on generated traces -exports.copyOHLC = function(container, traceOut) { - if(container.open) traceOut.open = container.open; - if(container.high) traceOut.high = container.high; - if(container.low) traceOut.low = container.low; - if(container.close) traceOut.close = container.close; -}; +exports.copyOHLC = function (container, traceOut) { + if (container.open) traceOut.open = container.open + if (container.high) traceOut.high = container.high + if (container.low) traceOut.low = container.low + if (container.close) traceOut.close = container.close +} // This routine gets called during the applyTransform step. // @@ -77,45 +75,45 @@ exports.copyOHLC = function(container, traceOut) { // // To make sure that the attributes reach the calcTransform, // store it in the transform opts object. -exports.makeTransform = function(traceIn, state, direction) { - var out = Lib.extendFlat([], traceIn.transforms); +exports.makeTransform = function (traceIn, state, direction) { + var out = Lib.extendFlat([], traceIn.transforms) - out[state.transformIndex] = { - type: traceIn.type, - direction: direction, + out[state.transformIndex] = { + type: traceIn.type, + direction: direction, // these are copied to traceOut during exports.copyOHLC - open: traceIn.open, - high: traceIn.high, - low: traceIn.low, - close: traceIn.close - }; - - return out; -}; - -exports.getFilterFn = function(direction) { - switch(direction) { - case 'increasing': - return function(o, c) { return o <= c; }; - - case 'decreasing': - return function(o, c) { return o > c; }; + open: traceIn.open, + high: traceIn.high, + low: traceIn.low, + close: traceIn.close + } + + return out +} + +exports.getFilterFn = function (direction) { + switch (direction) { + case 'increasing': + return function (o, c) { return o <= c } + + case 'decreasing': + return function (o, c) { return o > c } + } +} + +exports.addRangeSlider = function (data, layout) { + var hasOneVisibleTrace = false + + for (var i = 0; i < data.length; i++) { + if (data[i].visible === true) { + hasOneVisibleTrace = true + break } -}; + } -exports.addRangeSlider = function(data, layout) { - var hasOneVisibleTrace = false; - - for(var i = 0; i < data.length; i++) { - if(data[i].visible === true) { - hasOneVisibleTrace = true; - break; - } - } - - if(hasOneVisibleTrace) { - if(!layout.xaxis) layout.xaxis = {}; - if(!layout.xaxis.rangeslider) layout.xaxis.rangeslider = {}; - } -}; + if (hasOneVisibleTrace) { + if (!layout.xaxis) layout.xaxis = {} + if (!layout.xaxis.rangeslider) layout.xaxis.rangeslider = {} + } +} diff --git a/src/traces/ohlc/index.js b/src/traces/ohlc/index.js index 8f03e2d2a44..3b39061d177 100644 --- a/src/traces/ohlc/index.js +++ b/src/traces/ohlc/index.js @@ -6,35 +6,34 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var register = require('../../plot_api/register'); +var register = require('../../plot_api/register') module.exports = { - moduleType: 'trace', - name: 'ohlc', - basePlotModule: require('../../plots/cartesian'), - categories: ['cartesian', 'showLegend'], - meta: { - description: [ - 'The ohlc (short for Open-High-Low-Close) is a style of financial chart describing', - 'open, high, low and close for a given `x` coordinate (most likely time).', - - 'The tip of the lines represent the `low` and `high` values and', - 'the horizontal segments represent the `open` and `close` values.', - - 'Sample points where the close value is higher (lower) then the open', - 'value are called increasing (decreasing).', - - 'By default, increasing candles are drawn in green whereas', - 'decreasing are drawn in red.' - ].join(' ') - }, - - attributes: require('./attributes'), - supplyDefaults: require('./defaults'), -}; - -register(require('../scatter')); -register(require('./transform')); + moduleType: 'trace', + name: 'ohlc', + basePlotModule: require('../../plots/cartesian'), + categories: ['cartesian', 'showLegend'], + meta: { + description: [ + 'The ohlc (short for Open-High-Low-Close) is a style of financial chart describing', + 'open, high, low and close for a given `x` coordinate (most likely time).', + + 'The tip of the lines represent the `low` and `high` values and', + 'the horizontal segments represent the `open` and `close` values.', + + 'Sample points where the close value is higher (lower) then the open', + 'value are called increasing (decreasing).', + + 'By default, increasing candles are drawn in green whereas', + 'decreasing are drawn in red.' + ].join(' ') + }, + + attributes: require('./attributes'), + supplyDefaults: require('./defaults') +} + +register(require('../scatter')) +register(require('./transform')) diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js index 392dadd0d76..32640ce52c7 100644 --- a/src/traces/ohlc/ohlc_defaults.js +++ b/src/traces/ohlc/ohlc_defaults.js @@ -6,35 +6,33 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') -var Registry = require('../../registry'); +module.exports = function handleOHLC (traceIn, traceOut, coerce, layout) { + var len + var x = coerce('x'), + open = coerce('open'), + high = coerce('high'), + low = coerce('low'), + close = coerce('close') -module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) { - var len; + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x'], layout) - var x = coerce('x'), - open = coerce('open'), - high = coerce('high'), - low = coerce('low'), - close = coerce('close'); + len = Math.min(open.length, high.length, low.length, close.length) - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x'], layout); + if (x) { + len = Math.min(len, x.length) + if (len < x.length) traceOut.x = x.slice(0, len) + } - len = Math.min(open.length, high.length, low.length, close.length); + if (len < open.length) traceOut.open = open.slice(0, len) + if (len < high.length) traceOut.high = high.slice(0, len) + if (len < low.length) traceOut.low = low.slice(0, len) + if (len < close.length) traceOut.close = close.slice(0, len) - if(x) { - len = Math.min(len, x.length); - if(len < x.length) traceOut.x = x.slice(0, len); - } - - if(len < open.length) traceOut.open = open.slice(0, len); - if(len < high.length) traceOut.high = high.slice(0, len); - if(len < low.length) traceOut.low = low.slice(0, len); - if(len < close.length) traceOut.close = close.slice(0, len); - - return len; -}; + return len +} diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 236536056ac..cb0c6ec3778 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -6,87 +6,86 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var helpers = require('./helpers') +var Axes = require('../../plots/cartesian/axes') +var axisIds = require('../../plots/cartesian/axis_ids') -var Lib = require('../../lib'); -var helpers = require('./helpers'); -var Axes = require('../../plots/cartesian/axes'); -var axisIds = require('../../plots/cartesian/axis_ids'); +exports.moduleType = 'transform' -exports.moduleType = 'transform'; +exports.name = 'ohlc' -exports.name = 'ohlc'; +exports.attributes = {} -exports.attributes = {}; +exports.supplyDefaults = function (transformIn, traceOut, layout, traceIn) { + helpers.clearEphemeralTransformOpts(traceIn) + helpers.copyOHLC(transformIn, traceOut) -exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) { - helpers.clearEphemeralTransformOpts(traceIn); - helpers.copyOHLC(transformIn, traceOut); - - return transformIn; -}; + return transformIn +} -exports.transform = function transform(dataIn, state) { - var dataOut = []; +exports.transform = function transform (dataIn, state) { + var dataOut = [] - for(var i = 0; i < dataIn.length; i++) { - var traceIn = dataIn[i]; + for (var i = 0; i < dataIn.length; i++) { + var traceIn = dataIn[i] - if(traceIn.type !== 'ohlc') { - dataOut.push(traceIn); - continue; - } + if (traceIn.type !== 'ohlc') { + dataOut.push(traceIn) + continue + } - dataOut.push( + dataOut.push( makeTrace(traceIn, state, 'increasing'), makeTrace(traceIn, state, 'decreasing') - ); - } + ) + } - helpers.addRangeSlider(dataOut, state.layout); + helpers.addRangeSlider(dataOut, state.layout) - return dataOut; -}; + return dataOut +} -function makeTrace(traceIn, state, direction) { - var traceOut = { - type: 'scatter', - mode: 'lines', - connectgaps: false, +function makeTrace (traceIn, state, direction) { + var traceOut = { + type: 'scatter', + mode: 'lines', + connectgaps: false, - visible: traceIn.visible, - opacity: traceIn.opacity, - xaxis: traceIn.xaxis, - yaxis: traceIn.yaxis, + visible: traceIn.visible, + opacity: traceIn.opacity, + xaxis: traceIn.xaxis, + yaxis: traceIn.yaxis, - hoverinfo: makeHoverInfo(traceIn), - transforms: helpers.makeTransform(traceIn, state, direction) - }; + hoverinfo: makeHoverInfo(traceIn), + transforms: helpers.makeTransform(traceIn, state, direction) + } // the rest of below may not have been coerced - var directionOpts = traceIn[direction]; + var directionOpts = traceIn[direction] - if(directionOpts) { - Lib.extendFlat(traceOut, { + if (directionOpts) { + Lib.extendFlat(traceOut, { // to make autotype catch date axes soon!! - x: traceIn.x || [0], - xcalendar: traceIn.xcalendar, + x: traceIn.x || [0], + xcalendar: traceIn.xcalendar, // concat low and high to get correct autorange - y: [].concat(traceIn.low).concat(traceIn.high), + y: [].concat(traceIn.low).concat(traceIn.high), - text: traceIn.text, + text: traceIn.text, - name: directionOpts.name, - showlegend: directionOpts.showlegend, - line: directionOpts.line - }); - } + name: directionOpts.name, + showlegend: directionOpts.showlegend, + line: directionOpts.line + }) + } - return traceOut; + return traceOut } // Let scatter hoverPoint format 'x' coordinates, if desired. @@ -98,160 +97,159 @@ function makeTrace(traceIn, state, direction) { // // A future iteration should perhaps try to add a hook for transforms in // the hoverPoints handlers. -function makeHoverInfo(traceIn) { - var hoverinfo = traceIn.hoverinfo; +function makeHoverInfo (traceIn) { + var hoverinfo = traceIn.hoverinfo - if(hoverinfo === 'all') return 'x+text+name'; + if (hoverinfo === 'all') return 'x+text+name' - var parts = hoverinfo.split('+'), - indexOfY = parts.indexOf('y'), - indexOfText = parts.indexOf('text'); + var parts = hoverinfo.split('+'), + indexOfY = parts.indexOf('y'), + indexOfText = parts.indexOf('text') - if(indexOfY !== -1) { - parts.splice(indexOfY, 1); + if (indexOfY !== -1) { + parts.splice(indexOfY, 1) - if(indexOfText === -1) parts.push('text'); - } + if (indexOfText === -1) parts.push('text') + } - return parts.join('+'); + return parts.join('+') } -exports.calcTransform = function calcTransform(gd, trace, opts) { - var direction = opts.direction, - filterFn = helpers.getFilterFn(direction); - - var xa = axisIds.getFromTrace(gd, trace, 'x'), - ya = axisIds.getFromTrace(gd, trace, 'y'), - tickWidth = convertTickWidth(gd, xa, trace); - - var open = trace.open, - high = trace.high, - low = trace.low, - close = trace.close, - textIn = trace.text; - - var len = open.length, - x = [], - y = [], - textOut = []; - - var appendX; - if(trace._fullInput.x) { - appendX = function(i) { - var xi = trace.x[i], - xcalendar = trace.xcalendar, - xcalc = xa.d2c(xi, 0, xcalendar); - - x.push( +exports.calcTransform = function calcTransform (gd, trace, opts) { + var direction = opts.direction, + filterFn = helpers.getFilterFn(direction) + + var xa = axisIds.getFromTrace(gd, trace, 'x'), + ya = axisIds.getFromTrace(gd, trace, 'y'), + tickWidth = convertTickWidth(gd, xa, trace) + + var open = trace.open, + high = trace.high, + low = trace.low, + close = trace.close, + textIn = trace.text + + var len = open.length, + x = [], + y = [], + textOut = [] + + var appendX + if (trace._fullInput.x) { + appendX = function (i) { + var xi = trace.x[i], + xcalendar = trace.xcalendar, + xcalc = xa.d2c(xi, 0, xcalendar) + + x.push( xa.c2d(xcalc - tickWidth, 0, xcalendar), xi, xi, xi, xi, xa.c2d(xcalc + tickWidth, 0, xcalendar), - null); - }; + null) } - else { - appendX = function(i) { - x.push( + } else { + appendX = function (i) { + x.push( i - tickWidth, i, i, i, i, i + tickWidth, - null); - }; + null) + } + } + + var appendY = function (o, h, l, c) { + y.push(o, o, h, l, c, c, null) + } + + var format = function (ax, val) { + return Axes.tickText(ax, ax.c2l(val), 'hover').text + } + + var hoverinfo = trace._fullInput.hoverinfo, + hoverParts = hoverinfo.split('+'), + hasAll = hoverinfo === 'all', + hasY = hasAll || hoverParts.indexOf('y') !== -1, + hasText = hasAll || hoverParts.indexOf('text') !== -1 + + var getTextItem = Array.isArray(textIn) ? + function (i) { return textIn[i] || '' } : + function () { return textIn } + + var appendText = function (i, o, h, l, c) { + var t = [] + + if (hasY) { + t.push('Open: ' + format(ya, o)) + t.push('High: ' + format(ya, h)) + t.push('Low: ' + format(ya, l)) + t.push('Close: ' + format(ya, c)) } - var appendY = function(o, h, l, c) { - y.push(o, o, h, l, c, c, null); - }; - - var format = function(ax, val) { - return Axes.tickText(ax, ax.c2l(val), 'hover').text; - }; - - var hoverinfo = trace._fullInput.hoverinfo, - hoverParts = hoverinfo.split('+'), - hasAll = hoverinfo === 'all', - hasY = hasAll || hoverParts.indexOf('y') !== -1, - hasText = hasAll || hoverParts.indexOf('text') !== -1; - - var getTextItem = Array.isArray(textIn) ? - function(i) { return textIn[i] || ''; } : - function() { return textIn; }; - - var appendText = function(i, o, h, l, c) { - var t = []; - - if(hasY) { - t.push('Open: ' + format(ya, o)); - t.push('High: ' + format(ya, h)); - t.push('Low: ' + format(ya, l)); - t.push('Close: ' + format(ya, c)); - } - - if(hasText) t.push(getTextItem(i)); + if (hasText) t.push(getTextItem(i)) - var _t = t.join('
'); + var _t = t.join('
') - textOut.push(_t, _t, _t, _t, _t, _t, null); - }; + textOut.push(_t, _t, _t, _t, _t, _t, null) + } - for(var i = 0; i < len; i++) { - if(filterFn(open[i], close[i])) { - appendX(i); - appendY(open[i], high[i], low[i], close[i]); - appendText(i, open[i], high[i], low[i], close[i]); - } + for (var i = 0; i < len; i++) { + if (filterFn(open[i], close[i])) { + appendX(i) + appendY(open[i], high[i], low[i], close[i]) + appendText(i, open[i], high[i], low[i], close[i]) } + } - trace.x = x; - trace.y = y; - trace.text = textOut; -}; + trace.x = x + trace.y = y + trace.text = textOut +} -function convertTickWidth(gd, xa, trace) { - var fullInput = trace._fullInput, - tickWidth = fullInput.tickwidth, - minDiff = fullInput._minDiff; +function convertTickWidth (gd, xa, trace) { + var fullInput = trace._fullInput, + tickWidth = fullInput.tickwidth, + minDiff = fullInput._minDiff - if(!minDiff) { - var fullData = gd._fullData, - ohlcTracesOnThisXaxis = []; + if (!minDiff) { + var fullData = gd._fullData, + ohlcTracesOnThisXaxis = [] - minDiff = Infinity; + minDiff = Infinity // find min x-coordinates difference of all traces // attached to this x-axis and stash the result - var i; + var i - for(i = 0; i < fullData.length; i++) { - var _trace = fullData[i]._fullInput; + for (i = 0; i < fullData.length; i++) { + var _trace = fullData[i]._fullInput - if(_trace.type === 'ohlc' && + if (_trace.type === 'ohlc' && _trace.visible === true && _trace.xaxis === xa._id ) { - ohlcTracesOnThisXaxis.push(_trace); + ohlcTracesOnThisXaxis.push(_trace) // - _trace.x may be undefined here, // it is filled later in calcTransform // // - handle trace of length 1 separately. - if(_trace.x && _trace.x.length > 1) { - var xcalc = Lib.simpleMap(_trace.x, xa.d2c, 0, trace.xcalendar), - _minDiff = Lib.distinctVals(xcalc).minDiff; - minDiff = Math.min(minDiff, _minDiff); - } - } + if (_trace.x && _trace.x.length > 1) { + var xcalc = Lib.simpleMap(_trace.x, xa.d2c, 0, trace.xcalendar), + _minDiff = Lib.distinctVals(xcalc).minDiff + minDiff = Math.min(minDiff, _minDiff) } + } + } // if minDiff is still Infinity here, set it to 1 - if(minDiff === Infinity) minDiff = 1; + if (minDiff === Infinity) minDiff = 1 - for(i = 0; i < ohlcTracesOnThisXaxis.length; i++) { - ohlcTracesOnThisXaxis[i]._minDiff = minDiff; - } + for (i = 0; i < ohlcTracesOnThisXaxis.length; i++) { + ohlcTracesOnThisXaxis[i]._minDiff = minDiff } + } - return minDiff * tickWidth; + return minDiff * tickWidth } diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index a5eb8aefc6e..58936f1374b 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -6,183 +6,182 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var colorAttrs = require('../../components/color/attributes'); -var fontAttrs = require('../../plots/font_attributes'); -var plotAttrs = require('../../plots/attributes'); - -var extendFlat = require('../../lib/extend').extendFlat; +var colorAttrs = require('../../components/color/attributes') +var fontAttrs = require('../../plots/font_attributes') +var plotAttrs = require('../../plots/attributes') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { - labels: { - valType: 'data_array', - description: 'Sets the sector labels.' - }, + labels: { + valType: 'data_array', + description: 'Sets the sector labels.' + }, // equivalent of x0 and dx, if label is missing - label0: { + label0: { + valType: 'number', + role: 'info', + dflt: 0, + description: [ + 'Alternate to `labels`.', + 'Builds a numeric set of labels.', + 'Use with `dlabel`', + 'where `label0` is the starting label and `dlabel` the step.' + ].join(' ') + }, + dlabel: { + valType: 'number', + role: 'info', + dflt: 1, + description: 'Sets the label step. See `label0` for more info.' + }, + + values: { + valType: 'data_array', + description: 'Sets the values of the sectors of this pie chart.' + }, + + marker: { + colors: { + valType: 'data_array', // TODO 'color_array' ? + description: [ + 'Sets the color of each sector of this pie chart.', + 'If not specified, the default trace color set is used', + 'to pick the sector colors.' + ].join(' ') + }, + + line: { + color: { + valType: 'color', + role: 'style', + dflt: colorAttrs.defaultLine, + arrayOk: true, + description: [ + 'Sets the color of the line enclosing each sector.' + ].join(' ') + }, + width: { valType: 'number', - role: 'info', + role: 'style', + min: 0, dflt: 0, + arrayOk: true, description: [ - 'Alternate to `labels`.', - 'Builds a numeric set of labels.', - 'Use with `dlabel`', - 'where `label0` is the starting label and `dlabel` the step.' + 'Sets the width (in px) of the line enclosing each sector.' ].join(' ') - }, - dlabel: { - valType: 'number', - role: 'info', - dflt: 1, - description: 'Sets the label step. See `label0` for more info.' - }, - - values: { - valType: 'data_array', - description: 'Sets the values of the sectors of this pie chart.' - }, - - marker: { - colors: { - valType: 'data_array', // TODO 'color_array' ? - description: [ - 'Sets the color of each sector of this pie chart.', - 'If not specified, the default trace color set is used', - 'to pick the sector colors.' - ].join(' ') - }, - - line: { - color: { - valType: 'color', - role: 'style', - dflt: colorAttrs.defaultLine, - arrayOk: true, - description: [ - 'Sets the color of the line enclosing each sector.' - ].join(' ') - }, - width: { - valType: 'number', - role: 'style', - min: 0, - dflt: 0, - arrayOk: true, - description: [ - 'Sets the width (in px) of the line enclosing each sector.' - ].join(' ') - } - } - }, + } + } + }, - text: { - valType: 'data_array', - description: 'Sets text elements associated with each sector.' - }, + text: { + valType: 'data_array', + description: 'Sets text elements associated with each sector.' + }, // 'see eg:' // 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif', // '(this example involves a map too - may someday be a whole trace type', // 'of its own. but the point is the size of the whole pie is important.)' - scalegroup: { - valType: 'string', - role: 'info', - dflt: '', - description: [ - 'If there are multiple pies that should be sized according to', - 'their totals, link them by providing a non-empty group id here', - 'shared by every trace in the same group.' - ].join(' ') - }, + scalegroup: { + valType: 'string', + role: 'info', + dflt: '', + description: [ + 'If there are multiple pies that should be sized according to', + 'their totals, link them by providing a non-empty group id here', + 'shared by every trace in the same group.' + ].join(' ') + }, // labels (legend is handled by plots.attributes.showlegend and layout.hiddenlabels) - textinfo: { - valType: 'flaglist', - role: 'info', - flags: ['label', 'text', 'value', 'percent'], - extras: ['none'], - description: [ - 'Determines which trace information appear on the graph.' - ].join(' ') - }, - hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['label', 'text', 'value', 'percent', 'name'] - }), - textposition: { - valType: 'enumerated', - role: 'info', - values: ['inside', 'outside', 'auto', 'none'], - dflt: 'auto', - arrayOk: true, - description: [ - 'Specifies the location of the `textinfo`.' - ].join(' ') - }, + textinfo: { + valType: 'flaglist', + role: 'info', + flags: ['label', 'text', 'value', 'percent'], + extras: ['none'], + description: [ + 'Determines which trace information appear on the graph.' + ].join(' ') + }, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['label', 'text', 'value', 'percent', 'name'] + }), + textposition: { + valType: 'enumerated', + role: 'info', + values: ['inside', 'outside', 'auto', 'none'], + dflt: 'auto', + arrayOk: true, + description: [ + 'Specifies the location of the `textinfo`.' + ].join(' ') + }, // TODO make those arrayOk? - textfont: extendFlat({}, fontAttrs, { - description: 'Sets the font used for `textinfo`.' - }), - insidetextfont: extendFlat({}, fontAttrs, { - description: 'Sets the font used for `textinfo` lying inside the pie.' - }), - outsidetextfont: extendFlat({}, fontAttrs, { - description: 'Sets the font used for `textinfo` lying outside the pie.' - }), + textfont: extendFlat({}, fontAttrs, { + description: 'Sets the font used for `textinfo`.' + }), + insidetextfont: extendFlat({}, fontAttrs, { + description: 'Sets the font used for `textinfo` lying inside the pie.' + }), + outsidetextfont: extendFlat({}, fontAttrs, { + description: 'Sets the font used for `textinfo` lying outside the pie.' + }), // position and shape - domain: { - x: { - valType: 'info_array', - role: 'info', - items: [ + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the horizontal domain of this pie trace', - '(in plot fraction).' - ].join(' ') - }, - y: { - valType: 'info_array', - role: 'info', - items: [ + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this pie trace', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ {valType: 'number', min: 0, max: 1}, {valType: 'number', min: 0, max: 1} - ], - dflt: [0, 1], - description: [ - 'Sets the vertical domain of this pie trace', - '(in plot fraction).' - ].join(' ') - } - }, - hole: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 0, - description: [ - 'Sets the fraction of the radius to cut out of the pie.', - 'Use this to make a donut chart.' - ].join(' ') - }, + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this pie trace', + '(in plot fraction).' + ].join(' ') + } + }, + hole: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 0, + description: [ + 'Sets the fraction of the radius to cut out of the pie.', + 'Use this to make a donut chart.' + ].join(' ') + }, // ordering and direction - sort: { - valType: 'boolean', - role: 'style', - dflt: true, - description: [ - 'Determines whether or not the sectors of reordered', - 'from largest to smallest.' - ].join(' ') - }, - direction: { + sort: { + valType: 'boolean', + role: 'style', + dflt: true, + description: [ + 'Determines whether or not the sectors of reordered', + 'from largest to smallest.' + ].join(' ') + }, + direction: { /** * there are two common conventions, both of which place the first * (largest, if sorted) slice with its left edge at 12 o'clock but @@ -190,39 +189,39 @@ module.exports = { * * see http://visage.co/data-visualization-101-pie-charts/ */ - valType: 'enumerated', - values: ['clockwise', 'counterclockwise'], - role: 'style', - dflt: 'counterclockwise', - description: [ - 'Specifies the direction at which succeeding sectors follow', - 'one another.' - ].join(' ') - }, - rotation: { - valType: 'number', - role: 'style', - min: -360, - max: 360, - dflt: 0, - description: [ - 'Instead of the first slice starting at 12 o\'clock,', - 'rotate to some other angle.' - ].join(' ') - }, + valType: 'enumerated', + values: ['clockwise', 'counterclockwise'], + role: 'style', + dflt: 'counterclockwise', + description: [ + 'Specifies the direction at which succeeding sectors follow', + 'one another.' + ].join(' ') + }, + rotation: { + valType: 'number', + role: 'style', + min: -360, + max: 360, + dflt: 0, + description: [ + 'Instead of the first slice starting at 12 o\'clock,', + 'rotate to some other angle.' + ].join(' ') + }, - pull: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 0, - arrayOk: true, - description: [ - 'Sets the fraction of larger radius to pull the sectors', - 'out from the center. This can be a constant', - 'to pull all slices apart from each other equally', - 'or an array to highlight one or more slices.' - ].join(' ') - } -}; + pull: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 0, + arrayOk: true, + description: [ + 'Sets the fraction of larger radius to pull the sectors', + 'out from the center. This can be a constant', + 'to pull all slices apart from each other equally', + 'or an array to highlight one or more slices.' + ].join(' ') + } +} diff --git a/src/traces/pie/base_plot.js b/src/traces/pie/base_plot.js index e907f84f858..990c9e26365 100644 --- a/src/traces/pie/base_plot.js +++ b/src/traces/pie/base_plot.js @@ -6,40 +6,39 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Registry = require('../../registry'); +var Registry = require('../../registry') +exports.name = 'pie' -exports.name = 'pie'; +exports.plot = function (gd) { + var Pie = Registry.getModule('pie') + var cdPie = getCdModule(gd.calcdata, Pie) -exports.plot = function(gd) { - var Pie = Registry.getModule('pie'); - var cdPie = getCdModule(gd.calcdata, Pie); - - if(cdPie.length) Pie.plot(gd, cdPie); -}; + if (cdPie.length) Pie.plot(gd, cdPie) +} -exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var hadPie = (oldFullLayout._has && oldFullLayout._has('pie')); - var hasPie = (newFullLayout._has && newFullLayout._has('pie')); +exports.clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { + var hadPie = (oldFullLayout._has && oldFullLayout._has('pie')) + var hasPie = (newFullLayout._has && newFullLayout._has('pie')) - if(hadPie && !hasPie) { - oldFullLayout._pielayer.selectAll('g.trace').remove(); - } -}; + if (hadPie && !hasPie) { + oldFullLayout._pielayer.selectAll('g.trace').remove() + } +} -function getCdModule(calcdata, _module) { - var cdModule = []; +function getCdModule (calcdata, _module) { + var cdModule = [] - for(var i = 0; i < calcdata.length; i++) { - var cd = calcdata[i]; - var trace = cd[0].trace; + for (var i = 0; i < calcdata.length; i++) { + var cd = calcdata[i] + var trace = cd[0].trace - if((trace._module === _module) && (trace.visible === true)) { - cdModule.push(cd); - } + if ((trace._module === _module) && (trace.visible === true)) { + cdModule.push(cd) } + } - return cdModule; + return cdModule } diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js index 7fd82028790..e00f56b5834 100644 --- a/src/traces/pie/calc.js +++ b/src/traces/pie/calc.js @@ -6,81 +6,81 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var isNumeric = require('fast-isnumeric'); -var tinycolor = require('tinycolor2'); - -var Color = require('../../components/color'); -var helpers = require('./helpers'); - -module.exports = function calc(gd, trace) { - var vals = trace.values, - labels = trace.labels, - cd = [], - fullLayout = gd._fullLayout, - colorMap = fullLayout._piecolormap, - allThisTraceLabels = {}, - needDefaults = false, - vTotal = 0, - hiddenLabels = fullLayout.hiddenlabels || [], - i, - v, - label, - color, - hidden, - pt; - - if(trace.dlabel) { - labels = new Array(vals.length); - for(i = 0; i < vals.length; i++) { - labels[i] = String(trace.label0 + i * trace.dlabel); - } +'use strict' + +var isNumeric = require('fast-isnumeric') +var tinycolor = require('tinycolor2') + +var Color = require('../../components/color') +var helpers = require('./helpers') + +module.exports = function calc (gd, trace) { + var vals = trace.values, + labels = trace.labels, + cd = [], + fullLayout = gd._fullLayout, + colorMap = fullLayout._piecolormap, + allThisTraceLabels = {}, + needDefaults = false, + vTotal = 0, + hiddenLabels = fullLayout.hiddenlabels || [], + i, + v, + label, + color, + hidden, + pt + + if (trace.dlabel) { + labels = new Array(vals.length) + for (i = 0; i < vals.length; i++) { + labels[i] = String(trace.label0 + i * trace.dlabel) } + } - for(i = 0; i < vals.length; i++) { - v = vals[i]; - if(!isNumeric(v)) continue; - v = +v; - if(v < 0) continue; + for (i = 0; i < vals.length; i++) { + v = vals[i] + if (!isNumeric(v)) continue + v = +v + if (v < 0) continue - label = labels[i]; - if(label === undefined || label === '') label = i; - label = String(label); + label = labels[i] + if (label === undefined || label === '') label = i + label = String(label) // only take the first occurrence of any given label. // TODO: perhaps (optionally?) sum values for a repeated label? - if(allThisTraceLabels[label] === undefined) allThisTraceLabels[label] = true; - else continue; - - color = tinycolor(trace.marker.colors[i]); - if(color.isValid()) { - color = Color.addOpacity(color, color.getAlpha()); - if(!colorMap[label]) { - colorMap[label] = color; - } - } + if (allThisTraceLabels[label] === undefined) allThisTraceLabels[label] = true + else continue + + color = tinycolor(trace.marker.colors[i]) + if (color.isValid()) { + color = Color.addOpacity(color, color.getAlpha()) + if (!colorMap[label]) { + colorMap[label] = color + } + } // have we seen this label and assigned a color to it in a previous trace? - else if(colorMap[label]) color = colorMap[label]; + else if (colorMap[label]) color = colorMap[label] // color needs a default - mark it false, come back after sorting - else { - color = false; - needDefaults = true; - } - - hidden = hiddenLabels.indexOf(label) !== -1; - - if(!hidden) vTotal += v; - - cd.push({ - v: v, - label: label, - color: color, - i: i, - hidden: hidden - }); + else { + color = false + needDefaults = true } - if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; }); + hidden = hiddenLabels.indexOf(label) !== -1 + + if (!hidden) vTotal += v + + cd.push({ + v: v, + label: label, + color: color, + i: i, + hidden: hidden + }) + } + + if (trace.sort) cd.sort(function (a, b) { return b.v - a.v }) /** * now go back and fill in colors we're still missing @@ -88,63 +88,63 @@ module.exports = function calc(gd, trace) { * in the order slices will be displayed */ - if(needDefaults) { - for(i = 0; i < cd.length; i++) { - pt = cd[i]; - if(pt.color === false) { - colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount); - fullLayout._piedefaultcolorcount++; - } - } + if (needDefaults) { + for (i = 0; i < cd.length; i++) { + pt = cd[i] + if (pt.color === false) { + colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount) + fullLayout._piedefaultcolorcount++ + } } + } // include the sum of all values in the first point - if(cd[0]) cd[0].vTotal = vTotal; + if (cd[0]) cd[0].vTotal = vTotal // now insert text - if(trace.textinfo && trace.textinfo !== 'none') { - var hasLabel = trace.textinfo.indexOf('label') !== -1, - hasText = trace.textinfo.indexOf('text') !== -1, - hasValue = trace.textinfo.indexOf('value') !== -1, - hasPercent = trace.textinfo.indexOf('percent') !== -1, - separators = fullLayout.separators, - thisText; - - for(i = 0; i < cd.length; i++) { - pt = cd[i]; - thisText = hasLabel ? [pt.label] : []; - if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]); - if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators)); - if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators)); - pt.text = thisText.join('
'); - } + if (trace.textinfo && trace.textinfo !== 'none') { + var hasLabel = trace.textinfo.indexOf('label') !== -1, + hasText = trace.textinfo.indexOf('text') !== -1, + hasValue = trace.textinfo.indexOf('value') !== -1, + hasPercent = trace.textinfo.indexOf('percent') !== -1, + separators = fullLayout.separators, + thisText + + for (i = 0; i < cd.length; i++) { + pt = cd[i] + thisText = hasLabel ? [pt.label] : [] + if (hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]) + if (hasValue) thisText.push(helpers.formatPieValue(pt.v, separators)) + if (hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators)) + pt.text = thisText.join('
') } + } - return cd; -}; + return cd +} /** * pick a default color from the main default set, augmented by * itself lighter then darker before repeating */ -var pieDefaultColors; +var pieDefaultColors -function nextDefaultColor(index) { - if(!pieDefaultColors) { +function nextDefaultColor (index) { + if (!pieDefaultColors) { // generate this default set on demand (but then it gets saved in the module) - var mainDefaults = Color.defaults; - pieDefaultColors = mainDefaults.slice(); + var mainDefaults = Color.defaults + pieDefaultColors = mainDefaults.slice() - var i; + var i - for(i = 0; i < mainDefaults.length; i++) { - pieDefaultColors.push(tinycolor(mainDefaults[i]).lighten(20).toHexString()); - } + for (i = 0; i < mainDefaults.length; i++) { + pieDefaultColors.push(tinycolor(mainDefaults[i]).lighten(20).toHexString()) + } - for(i = 0; i < Color.defaults.length; i++) { - pieDefaultColors.push(tinycolor(mainDefaults[i]).darken(20).toHexString()); - } + for (i = 0; i < Color.defaults.length; i++) { + pieDefaultColors.push(tinycolor(mainDefaults[i]).darken(20).toHexString()) } + } - return pieDefaultColors[index % pieDefaultColors.length]; + return pieDefaultColors[index % pieDefaultColors.length] } diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 9a21628aca9..b492dd6591f 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -6,63 +6,62 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); -var attributes = require('./attributes'); +var Lib = require('../../lib') +var attributes = require('./attributes') -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - var coerceFont = Lib.coerceFont; + var coerceFont = Lib.coerceFont - var vals = coerce('values'); - if(!Array.isArray(vals) || !vals.length) { - traceOut.visible = false; - return; - } + var vals = coerce('values') + if (!Array.isArray(vals) || !vals.length) { + traceOut.visible = false + return + } - var labels = coerce('labels'); - if(!Array.isArray(labels)) { - coerce('label0'); - coerce('dlabel'); - } + var labels = coerce('labels') + if (!Array.isArray(labels)) { + coerce('label0') + coerce('dlabel') + } - var lineWidth = coerce('marker.line.width'); - if(lineWidth) coerce('marker.line.color'); + var lineWidth = coerce('marker.line.width') + if (lineWidth) coerce('marker.line.color') - var colors = coerce('marker.colors'); - if(!Array.isArray(colors)) traceOut.marker.colors = []; // later this will get padded with default colors + var colors = coerce('marker.colors') + if (!Array.isArray(colors)) traceOut.marker.colors = [] // later this will get padded with default colors - coerce('scalegroup'); + coerce('scalegroup') // TODO: tilt, depth, and hole all need to be coerced to the same values within a scaleegroup // (ideally actually, depth would get set the same *after* scaling, ie the same absolute depth) // and if colors aren't specified we should match these up - potentially even if separate pies // are NOT in the same sharegroup + var textData = coerce('text') + var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent') - var textData = coerce('text'); - var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); - - coerce('hoverinfo', (layout._dataLength === 1) ? 'label+text+value+percent' : undefined); + coerce('hoverinfo', (layout._dataLength === 1) ? 'label+text+value+percent' : undefined) - if(textInfo && textInfo !== 'none') { - var textPosition = coerce('textposition'), - hasBoth = Array.isArray(textPosition) || textPosition === 'auto', - hasInside = hasBoth || textPosition === 'inside', - hasOutside = hasBoth || textPosition === 'outside'; + if (textInfo && textInfo !== 'none') { + var textPosition = coerce('textposition'), + hasBoth = Array.isArray(textPosition) || textPosition === 'auto', + hasInside = hasBoth || textPosition === 'inside', + hasOutside = hasBoth || textPosition === 'outside' - if(hasInside || hasOutside) { - var dfltFont = coerceFont(coerce, 'textfont', layout.font); - if(hasInside) coerceFont(coerce, 'insidetextfont', dfltFont); - if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont); - } + if (hasInside || hasOutside) { + var dfltFont = coerceFont(coerce, 'textfont', layout.font) + if (hasInside) coerceFont(coerce, 'insidetextfont', dfltFont) + if (hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont) } + } - coerce('domain.x'); - coerce('domain.y'); + coerce('domain.x') + coerce('domain.y') // 3D attributes commented out until I finish them in a later PR // var tilt = coerce('tilt'); @@ -72,11 +71,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // coerce('shading'); // } - coerce('hole'); + coerce('hole') - coerce('sort'); - coerce('direction'); - coerce('rotation'); + coerce('sort') + coerce('direction') + coerce('rotation') - coerce('pull'); -}; + coerce('pull') +} diff --git a/src/traces/pie/helpers.js b/src/traces/pie/helpers.js index ac19f6f6c1e..4b684cd697d 100644 --- a/src/traces/pie/helpers.js +++ b/src/traces/pie/helpers.js @@ -6,22 +6,22 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); +var Lib = require('../../lib') -exports.formatPiePercent = function formatPiePercent(v, separators) { - var vRounded = (v * 100).toPrecision(3); - if(vRounded.lastIndexOf('.') !== -1) { - vRounded = vRounded.replace(/[.]?0+$/, ''); - } - return Lib.numSeparate(vRounded, separators) + '%'; -}; +exports.formatPiePercent = function formatPiePercent (v, separators) { + var vRounded = (v * 100).toPrecision(3) + if (vRounded.lastIndexOf('.') !== -1) { + vRounded = vRounded.replace(/[.]?0+$/, '') + } + return Lib.numSeparate(vRounded, separators) + '%' +} -exports.formatPieValue = function formatPieValue(v, separators) { - var vRounded = v.toPrecision(10); - if(vRounded.lastIndexOf('.') !== -1) { - vRounded = vRounded.replace(/[.]?0+$/, ''); - } - return Lib.numSeparate(vRounded, separators); -}; +exports.formatPieValue = function formatPieValue (v, separators) { + var vRounded = v.toPrecision(10) + if (vRounded.lastIndexOf('.') !== -1) { + vRounded = vRounded.replace(/[.]?0+$/, '') + } + return Lib.numSeparate(vRounded, separators) +} diff --git a/src/traces/pie/index.js b/src/traces/pie/index.js index 87d85a0fbba..208bac57e75 100644 --- a/src/traces/pie/index.js +++ b/src/traces/pie/index.js @@ -6,29 +6,29 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Pie = {}; +var Pie = {} -Pie.attributes = require('./attributes'); -Pie.supplyDefaults = require('./defaults'); -Pie.supplyLayoutDefaults = require('./layout_defaults'); -Pie.layoutAttributes = require('./layout_attributes'); -Pie.calc = require('./calc'); -Pie.plot = require('./plot'); -Pie.style = require('./style'); -Pie.styleOne = require('./style_one'); +Pie.attributes = require('./attributes') +Pie.supplyDefaults = require('./defaults') +Pie.supplyLayoutDefaults = require('./layout_defaults') +Pie.layoutAttributes = require('./layout_attributes') +Pie.calc = require('./calc') +Pie.plot = require('./plot') +Pie.style = require('./style') +Pie.styleOne = require('./style_one') -Pie.moduleType = 'trace'; -Pie.name = 'pie'; -Pie.basePlotModule = require('./base_plot'); -Pie.categories = ['pie', 'showLegend']; +Pie.moduleType = 'trace' +Pie.name = 'pie' +Pie.basePlotModule = require('./base_plot') +Pie.categories = ['pie', 'showLegend'] Pie.meta = { - description: [ - 'A data visualized by the sectors of the pie is set in `values`.', - 'The sector labels are set in `labels`.', - 'The sector colors are set in `marker.colors`' - ].join(' ') -}; + description: [ + 'A data visualized by the sectors of the pie is set in `values`.', + 'The sector labels are set in `labels`.', + 'The sector colors are set in `marker.colors`' + ].join(' ') +} -module.exports = Pie; +module.exports = Pie diff --git a/src/traces/pie/layout_attributes.js b/src/traces/pie/layout_attributes.js index 29167d778c2..baf3e5fcf70 100644 --- a/src/traces/pie/layout_attributes.js +++ b/src/traces/pie/layout_attributes.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' module.exports = { /** @@ -14,5 +14,5 @@ module.exports = { * but it can contain many labels, and can hide slices * from several pies simultaneously */ - hiddenlabels: {valType: 'data_array'} -}; + hiddenlabels: {valType: 'data_array'} +} diff --git a/src/traces/pie/layout_defaults.js b/src/traces/pie/layout_defaults.js index 1f44573e03f..9e94bdf49e0 100644 --- a/src/traces/pie/layout_defaults.js +++ b/src/traces/pie/layout_defaults.js @@ -6,15 +6,15 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../../lib'); +var Lib = require('../../lib') -var layoutAttributes = require('./layout_attributes'); +var layoutAttributes = require('./layout_attributes') -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - function coerce(attr, dflt) { - return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); - } - coerce('hiddenlabels'); -}; +module.exports = function supplyLayoutDefaults (layoutIn, layoutOut) { + function coerce (attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt) + } + coerce('hiddenlabels') +} diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 1d58dd2d9b4..1723fd5a7bd 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -6,334 +6,332 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); +var d3 = require('d3') -var Fx = require('../../plots/cartesian/graph_interact'); -var Color = require('../../components/color'); -var Drawing = require('../../components/drawing'); -var svgTextUtils = require('../../lib/svg_text_utils'); +var Fx = require('../../plots/cartesian/graph_interact') +var Color = require('../../components/color') +var Drawing = require('../../components/drawing') +var svgTextUtils = require('../../lib/svg_text_utils') -var helpers = require('./helpers'); +var helpers = require('./helpers') -module.exports = function plot(gd, cdpie) { - var fullLayout = gd._fullLayout; +module.exports = function plot (gd, cdpie) { + var fullLayout = gd._fullLayout - scalePies(cdpie, fullLayout._size); + scalePies(cdpie, fullLayout._size) - var pieGroups = fullLayout._pielayer.selectAll('g.trace').data(cdpie); + var pieGroups = fullLayout._pielayer.selectAll('g.trace').data(cdpie) - pieGroups.enter().append('g') + pieGroups.enter().append('g') .attr({ - 'stroke-linejoin': 'round', // TODO: miter might look better but can sometimes cause problems + 'stroke-linejoin': 'round', // TODO: miter might look better but can sometimes cause problems // maybe miter with a small-ish stroke-miterlimit? - 'class': 'trace' - }); - pieGroups.exit().remove(); - pieGroups.order(); - - pieGroups.each(function(cd) { - var pieGroup = d3.select(this), - cd0 = cd[0], - trace = cd0.trace, - tiltRads = 0, // trace.tilt * Math.PI / 180, - depthLength = (trace.depth||0) * cd0.r * Math.sin(tiltRads) / 2, - tiltAxis = trace.tiltaxis || 0, - tiltAxisRads = tiltAxis * Math.PI / 180, - depthVector = [ - depthLength * Math.sin(tiltAxisRads), - depthLength * Math.cos(tiltAxisRads) - ], - rSmall = cd0.r * Math.cos(tiltRads); - - var pieParts = pieGroup.selectAll('g.part') - .data(trace.tilt ? ['top', 'sides'] : ['top']); - - pieParts.enter().append('g').attr('class', function(d) { - return d + ' part'; - }); - pieParts.exit().remove(); - pieParts.order(); - - setCoords(cd); - - pieGroup.selectAll('.top').each(function() { - var slices = d3.select(this).selectAll('g.slice').data(cd); - - slices.enter().append('g') - .classed('slice', true); - slices.exit().remove(); - - var quadrants = [ + 'class': 'trace' + }) + pieGroups.exit().remove() + pieGroups.order() + + pieGroups.each(function (cd) { + var pieGroup = d3.select(this), + cd0 = cd[0], + trace = cd0.trace, + tiltRads = 0, // trace.tilt * Math.PI / 180, + depthLength = (trace.depth || 0) * cd0.r * Math.sin(tiltRads) / 2, + tiltAxis = trace.tiltaxis || 0, + tiltAxisRads = tiltAxis * Math.PI / 180, + depthVector = [ + depthLength * Math.sin(tiltAxisRads), + depthLength * Math.cos(tiltAxisRads) + ], + rSmall = cd0.r * Math.cos(tiltRads) + + var pieParts = pieGroup.selectAll('g.part') + .data(trace.tilt ? ['top', 'sides'] : ['top']) + + pieParts.enter().append('g').attr('class', function (d) { + return d + ' part' + }) + pieParts.exit().remove() + pieParts.order() + + setCoords(cd) + + pieGroup.selectAll('.top').each(function () { + var slices = d3.select(this).selectAll('g.slice').data(cd) + + slices.enter().append('g') + .classed('slice', true) + slices.exit().remove() + + var quadrants = [ [[], []], // y<0: x<0, x>=0 [[], []] // y>=0: x<0, x>=0 - ], - hasOutsideText = false; + ], + hasOutsideText = false - slices.each(function(pt) { - if(pt.hidden) { - d3.select(this).selectAll('path,g').remove(); - return; - } + slices.each(function (pt) { + if (pt.hidden) { + d3.select(this).selectAll('path,g').remove() + return + } - quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt); + quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt) - var cx = cd0.cx + depthVector[0], - cy = cd0.cy + depthVector[1], - sliceTop = d3.select(this), - slicePath = sliceTop.selectAll('path.surface').data([pt]), - hasHoverData = false; + var cx = cd0.cx + depthVector[0], + cy = cd0.cy + depthVector[1], + sliceTop = d3.select(this), + slicePath = sliceTop.selectAll('path.surface').data([pt]), + hasHoverData = false - function handleMouseOver(evt) { + function handleMouseOver (evt) { // in case fullLayout or fullData has changed without a replot - var fullLayout2 = gd._fullLayout, - trace2 = gd._fullData[trace.index], - hoverinfo = trace2.hoverinfo; + var fullLayout2 = gd._fullLayout, + trace2 = gd._fullData[trace.index], + hoverinfo = trace2.hoverinfo - if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; + if (hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name' // in case we dragged over the pie from another subplot, // or if hover is turned off - if(gd._dragging || fullLayout2.hovermode === false || + if (gd._dragging || fullLayout2.hovermode === false || hoverinfo === 'none' || hoverinfo === 'skip' || !hoverinfo) { - return; - } - - var rInscribed = getInscribedRadiusFraction(pt, cd0), - hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed), - hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed), - separators = fullLayout.separators, - thisText = []; - - if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); - if(trace2.text && trace2.text[pt.i] && hoverinfo.indexOf('text') !== -1) { - thisText.push(trace2.text[pt.i]); - } - if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)); - if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); - - Fx.loneHover({ - x0: hoverCenterX - rInscribed * cd0.r, - x1: hoverCenterX + rInscribed * cd0.r, - y: hoverCenterY, - text: thisText.join('
'), - name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined, - color: pt.color, - idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right' - }, { - container: fullLayout2._hoverlayer.node(), - outerContainer: fullLayout2._paper.node() - }); - - Fx.hover(gd, evt, 'pie'); - - hasHoverData = true; - } - - function handleMouseOut(evt) { - gd.emit('plotly_unhover', { - points: [evt] - }); - - if(hasHoverData) { - Fx.loneUnhover(fullLayout._hoverlayer.node()); - hasHoverData = false; - } - } - - function handleClick() { - gd._hoverdata = [pt]; - gd._hoverdata.trace = cd.trace; - Fx.click(gd, { target: true }); - } - - slicePath.enter().append('path') + return + } + + var rInscribed = getInscribedRadiusFraction(pt, cd0), + hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed), + hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed), + separators = fullLayout.separators, + thisText = [] + + if (hoverinfo.indexOf('label') !== -1) thisText.push(pt.label) + if (trace2.text && trace2.text[pt.i] && hoverinfo.indexOf('text') !== -1) { + thisText.push(trace2.text[pt.i]) + } + if (hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)) + if (hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)) + + Fx.loneHover({ + x0: hoverCenterX - rInscribed * cd0.r, + x1: hoverCenterX + rInscribed * cd0.r, + y: hoverCenterY, + text: thisText.join('
'), + name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined, + color: pt.color, + idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right' + }, { + container: fullLayout2._hoverlayer.node(), + outerContainer: fullLayout2._paper.node() + }) + + Fx.hover(gd, evt, 'pie') + + hasHoverData = true + } + + function handleMouseOut (evt) { + gd.emit('plotly_unhover', { + points: [evt] + }) + + if (hasHoverData) { + Fx.loneUnhover(fullLayout._hoverlayer.node()) + hasHoverData = false + } + } + + function handleClick () { + gd._hoverdata = [pt] + gd._hoverdata.trace = cd.trace + Fx.click(gd, { target: true }) + } + + slicePath.enter().append('path') .classed('surface', true) - .style({'pointer-events': 'all'}); + .style({'pointer-events': 'all'}) - sliceTop.select('path.textline').remove(); + sliceTop.select('path.textline').remove() - sliceTop + sliceTop .on('mouseover', handleMouseOver) .on('mouseout', handleMouseOut) - .on('click', handleClick); - - if(trace.pull) { - var pull = +(Array.isArray(trace.pull) ? trace.pull[pt.i] : trace.pull) || 0; - if(pull > 0) { - cx += pull * pt.pxmid[0]; - cy += pull * pt.pxmid[1]; - } - } + .on('click', handleClick) + + if (trace.pull) { + var pull = +(Array.isArray(trace.pull) ? trace.pull[pt.i] : trace.pull) || 0 + if (pull > 0) { + cx += pull * pt.pxmid[0] + cy += pull * pt.pxmid[1] + } + } - pt.cxFinal = cx; - pt.cyFinal = cy; + pt.cxFinal = cx + pt.cyFinal = cy - function arc(start, finish, cw, scale) { - return 'a' + (scale * cd0.r) + ',' + (scale * rSmall) + ' ' + tiltAxis + ' ' + + function arc (start, finish, cw, scale) { + return 'a' + (scale * cd0.r) + ',' + (scale * rSmall) + ' ' + tiltAxis + ' ' + pt.largeArc + (cw ? ' 1 ' : ' 0 ') + - (scale * (finish[0] - start[0])) + ',' + (scale * (finish[1] - start[1])); - } + (scale * (finish[0] - start[0])) + ',' + (scale * (finish[1] - start[1])) + } - var hole = trace.hole; - if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical - var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) + + var hole = trace.hole + if (pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical + var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) + arc(pt.px0, pt.pxmid, true, 1) + - arc(pt.pxmid, pt.px0, true, 1) + 'Z'; - if(hole) { - slicePath.attr('d', + arc(pt.pxmid, pt.px0, true, 1) + 'Z' + if (hole) { + slicePath.attr('d', 'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) + arc(pt.px0, pt.pxmid, false, hole) + arc(pt.pxmid, pt.px0, false, hole) + - 'Z' + outerCircle); - } - else slicePath.attr('d', outerCircle); - } else { - - var outerArc = arc(pt.px0, pt.px1, true, 1); - - if(hole) { - var rim = 1 - hole; - slicePath.attr('d', + 'Z' + outerCircle) + } else slicePath.attr('d', outerCircle) + } else { + var outerArc = arc(pt.px0, pt.px1, true, 1) + + if (hole) { + var rim = 1 - hole + slicePath.attr('d', 'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) + arc(pt.px1, pt.px0, false, hole) + 'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) + outerArc + - 'Z'); - } else { - slicePath.attr('d', + 'Z') + } else { + slicePath.attr('d', 'M' + cx + ',' + cy + 'l' + pt.px0[0] + ',' + pt.px0[1] + outerArc + - 'Z'); - } - } + 'Z') + } + } // add text - var textPosition = Array.isArray(trace.textposition) ? + var textPosition = Array.isArray(trace.textposition) ? trace.textposition[pt.i] : trace.textposition, - sliceTextGroup = sliceTop.selectAll('g.slicetext') - .data(pt.text && (textPosition !== 'none') ? [0] : []); + sliceTextGroup = sliceTop.selectAll('g.slicetext') + .data(pt.text && (textPosition !== 'none') ? [0] : []) - sliceTextGroup.enter().append('g') - .classed('slicetext', true); - sliceTextGroup.exit().remove(); + sliceTextGroup.enter().append('g') + .classed('slicetext', true) + sliceTextGroup.exit().remove() - sliceTextGroup.each(function() { - var sliceText = d3.select(this).selectAll('text').data([0]); + sliceTextGroup.each(function () { + var sliceText = d3.select(this).selectAll('text').data([0]) - sliceText.enter().append('text') + sliceText.enter().append('text') // prohibit tex interpretation until we can handle // tex and regular text together - .attr('data-notex', 1); - sliceText.exit().remove(); + .attr('data-notex', 1) + sliceText.exit().remove() - sliceText.text(pt.text) + sliceText.text(pt.text) .attr({ - 'class': 'slicetext', - transform: '', - 'data-bb': '', - 'text-anchor': 'middle', - x: 0, - y: 0 + 'class': 'slicetext', + transform: '', + 'data-bb': '', + 'text-anchor': 'middle', + x: 0, + y: 0 }) .call(Drawing.font, textPosition === 'outside' ? trace.outsidetextfont : trace.insidetextfont) - .call(svgTextUtils.convertToTspans); - sliceText.selectAll('tspan.line').attr({x: 0, y: 0}); + .call(svgTextUtils.convertToTspans) + sliceText.selectAll('tspan.line').attr({x: 0, y: 0}) // position the text relative to the slice // TODO: so far this only accounts for flat - var textBB = Drawing.bBox(sliceText.node()), - transform; - - if(textPosition === 'outside') { - transform = transformOutsideText(textBB, pt); - } else { - transform = transformInsideText(textBB, pt, cd0); - if(textPosition === 'auto' && transform.scale < 1) { - sliceText.call(Drawing.font, trace.outsidetextfont); - if(trace.outsidetextfont.family !== trace.insidetextfont.family || + var textBB = Drawing.bBox(sliceText.node()), + transform + + if (textPosition === 'outside') { + transform = transformOutsideText(textBB, pt) + } else { + transform = transformInsideText(textBB, pt, cd0) + if (textPosition === 'auto' && transform.scale < 1) { + sliceText.call(Drawing.font, trace.outsidetextfont) + if (trace.outsidetextfont.family !== trace.insidetextfont.family || trace.outsidetextfont.size !== trace.insidetextfont.size) { - sliceText.attr({'data-bb': ''}); - textBB = Drawing.bBox(sliceText.node()); - } - transform = transformOutsideText(textBB, pt); - } - } + sliceText.attr({'data-bb': ''}) + textBB = Drawing.bBox(sliceText.node()) + } + transform = transformOutsideText(textBB, pt) + } + } - var translateX = cx + pt.pxmid[0] * transform.rCenter + (transform.x || 0), - translateY = cy + pt.pxmid[1] * transform.rCenter + (transform.y || 0); + var translateX = cx + pt.pxmid[0] * transform.rCenter + (transform.x || 0), + translateY = cy + pt.pxmid[1] * transform.rCenter + (transform.y || 0) // save some stuff to use later ensure no labels overlap - if(transform.outside) { - pt.yLabelMin = translateY - textBB.height / 2; - pt.yLabelMid = translateY; - pt.yLabelMax = translateY + textBB.height / 2; - pt.labelExtraX = 0; - pt.labelExtraY = 0; - hasOutsideText = true; - } - - sliceText.attr('transform', + if (transform.outside) { + pt.yLabelMin = translateY - textBB.height / 2 + pt.yLabelMid = translateY + pt.yLabelMax = translateY + textBB.height / 2 + pt.labelExtraX = 0 + pt.labelExtraY = 0 + hasOutsideText = true + } + + sliceText.attr('transform', 'translate(' + translateX + ',' + translateY + ')' + (transform.scale < 1 ? ('scale(' + transform.scale + ')') : '') + (transform.rotate ? ('rotate(' + transform.rotate + ')') : '') + 'translate(' + (-(textBB.left + textBB.right) / 2) + ',' + (-(textBB.top + textBB.bottom) / 2) + - ')'); - }); - }); + ')') + }) + }) // now make sure no labels overlap (at least within one pie) - if(hasOutsideText) scootLabels(quadrants, trace); - slices.each(function(pt) { - if(pt.labelExtraX || pt.labelExtraY) { + if (hasOutsideText) scootLabels(quadrants, trace) + slices.each(function (pt) { + if (pt.labelExtraX || pt.labelExtraY) { // first move the text to its new location - var sliceTop = d3.select(this), - sliceText = sliceTop.select('g.slicetext text'); + var sliceTop = d3.select(this), + sliceText = sliceTop.select('g.slicetext text') - sliceText.attr('transform', 'translate(' + pt.labelExtraX + ',' + pt.labelExtraY + ')' + - sliceText.attr('transform')); + sliceText.attr('transform', 'translate(' + pt.labelExtraX + ',' + pt.labelExtraY + ')' + + sliceText.attr('transform')) // then add a line to the new location - var lineStartX = pt.cxFinal + pt.pxmid[0], - lineStartY = pt.cyFinal + pt.pxmid[1], - textLinePath = 'M' + lineStartX + ',' + lineStartY, - finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4; - if(pt.labelExtraX) { - var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0], - yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]); - - if(Math.abs(yFromX) > Math.abs(yNet)) { - textLinePath += + var lineStartX = pt.cxFinal + pt.pxmid[0], + lineStartY = pt.cyFinal + pt.pxmid[1], + textLinePath = 'M' + lineStartX + ',' + lineStartY, + finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4 + if (pt.labelExtraX) { + var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0], + yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]) + + if (Math.abs(yFromX) > Math.abs(yNet)) { + textLinePath += 'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet + - 'H' + (lineStartX + pt.labelExtraX + finalX); - } else { - textLinePath += 'l' + pt.labelExtraX + ',' + yFromX + + 'H' + (lineStartX + pt.labelExtraX + finalX) + } else { + textLinePath += 'l' + pt.labelExtraX + ',' + yFromX + 'v' + (yNet - yFromX) + - 'h' + finalX; - } - } else { - textLinePath += + 'h' + finalX + } + } else { + textLinePath += 'V' + (pt.yLabelMid + pt.labelExtraY) + - 'h' + finalX; - } + 'h' + finalX + } - sliceTop.append('path') + sliceTop.append('path') .classed('textline', true) .call(Color.stroke, trace.outsidetextfont.color) .attr({ - 'stroke-width': Math.min(2, trace.outsidetextfont.size / 8), - d: textLinePath, - fill: 'none' - }); - } - }); - }); - }); + 'stroke-width': Math.min(2, trace.outsidetextfont.size / 8), + d: textLinePath, + fill: 'none' + }) + } + }) + }) + }) // This is for a bug in Chrome (as of 2015-07-22, and does not affect FF) // if insidetextfont and outsidetextfont are different sizes, sometimes the size @@ -341,353 +339,350 @@ module.exports = function plot(gd, cdpie) { // spaced wrong. You just have to tell it to try again later and it gets fixed. // I have no idea why we haven't seen this in other contexts. Also, sometimes // it gets the initial draw correct but on redraw it gets confused. - setTimeout(function() { - pieGroups.selectAll('tspan').each(function() { - var s = d3.select(this); - if(s.attr('dy')) s.attr('dy', s.attr('dy')); - }); - }, 0); -}; - - -function transformInsideText(textBB, pt, cd0) { - var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height), - textAspect = textBB.width / textBB.height, - halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5), - ring = 1 - cd0.trace.hole, - rInscribed = getInscribedRadiusFraction(pt, cd0), + setTimeout(function () { + pieGroups.selectAll('tspan').each(function () { + var s = d3.select(this) + if (s.attr('dy')) s.attr('dy', s.attr('dy')) + }) + }, 0) +} + +function transformInsideText (textBB, pt, cd0) { + var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height), + textAspect = textBB.width / textBB.height, + halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5), + ring = 1 - cd0.trace.hole, + rInscribed = getInscribedRadiusFraction(pt, cd0), // max size text can be inserted inside without rotating it // this inscribes the text rectangle in a circle, which is then inscribed // in the slice, so it will be an underestimate, which some day we may want // to improve so this case can get more use - transform = { - scale: rInscribed * cd0.r * 2 / textDiameter, + transform = { + scale: rInscribed * cd0.r * 2 / textDiameter, // and the center position and rotation in this case - rCenter: 1 - rInscribed, - rotate: 0 - }; + rCenter: 1 - rInscribed, + rotate: 0 + } - if(transform.scale >= 1) return transform; + if (transform.scale >= 1) return transform // max size if text is rotated radially - var Qr = textAspect + 1 / (2 * Math.tan(halfAngle)), - maxHalfHeightRotRadial = cd0.r * Math.min( + var Qr = textAspect + 1 / (2 * Math.tan(halfAngle)), + maxHalfHeightRotRadial = cd0.r * Math.min( 1 / (Math.sqrt(Qr * Qr + 0.5) + Qr), ring / (Math.sqrt(textAspect * textAspect + ring / 2) + textAspect) ), - radialTransform = { - scale: maxHalfHeightRotRadial * 2 / textBB.height, - rCenter: Math.cos(maxHalfHeightRotRadial / cd0.r) - + radialTransform = { + scale: maxHalfHeightRotRadial * 2 / textBB.height, + rCenter: Math.cos(maxHalfHeightRotRadial / cd0.r) - maxHalfHeightRotRadial * textAspect / cd0.r, - rotate: (180 / Math.PI * pt.midangle + 720) % 180 - 90 - }, + rotate: (180 / Math.PI * pt.midangle + 720) % 180 - 90 + }, // max size if text is rotated tangentially - aspectInv = 1 / textAspect, - Qt = aspectInv + 1 / (2 * Math.tan(halfAngle)), - maxHalfWidthTangential = cd0.r * Math.min( + aspectInv = 1 / textAspect, + Qt = aspectInv + 1 / (2 * Math.tan(halfAngle)), + maxHalfWidthTangential = cd0.r * Math.min( 1 / (Math.sqrt(Qt * Qt + 0.5) + Qt), ring / (Math.sqrt(aspectInv * aspectInv + ring / 2) + aspectInv) ), - tangentialTransform = { - scale: maxHalfWidthTangential * 2 / textBB.width, - rCenter: Math.cos(maxHalfWidthTangential / cd0.r) - + tangentialTransform = { + scale: maxHalfWidthTangential * 2 / textBB.width, + rCenter: Math.cos(maxHalfWidthTangential / cd0.r) - maxHalfWidthTangential / textAspect / cd0.r, - rotate: (180 / Math.PI * pt.midangle + 810) % 180 - 90 - }, + rotate: (180 / Math.PI * pt.midangle + 810) % 180 - 90 + }, // if we need a rotated transform, pick the biggest one // even if both are bigger than 1 - rotatedTransform = tangentialTransform.scale > radialTransform.scale ? - tangentialTransform : radialTransform; + rotatedTransform = tangentialTransform.scale > radialTransform.scale ? + tangentialTransform : radialTransform - if(transform.scale < 1 && rotatedTransform.scale > transform.scale) return rotatedTransform; - return transform; + if (transform.scale < 1 && rotatedTransform.scale > transform.scale) return rotatedTransform + return transform } -function getInscribedRadiusFraction(pt, cd0) { - if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole +function getInscribedRadiusFraction (pt, cd0) { + if (pt.v === cd0.vTotal && !cd0.trace.hole) return 1// special case of 100% with no hole - var halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5); - return Math.min(1 / (1 + 1 / Math.sin(halfAngle)), (1 - cd0.trace.hole) / 2); + var halfAngle = Math.PI * Math.min(pt.v / cd0.vTotal, 0.5) + return Math.min(1 / (1 + 1 / Math.sin(halfAngle)), (1 - cd0.trace.hole) / 2) } -function transformOutsideText(textBB, pt) { - var x = pt.pxmid[0], - y = pt.pxmid[1], - dx = textBB.width / 2, - dy = textBB.height / 2; - - if(x < 0) dx *= -1; - if(y < 0) dy *= -1; - - return { - scale: 1, - rCenter: 1, - rotate: 0, - x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2, - y: dy / (1 + x * x / (y * y)), - outside: true - }; +function transformOutsideText (textBB, pt) { + var x = pt.pxmid[0], + y = pt.pxmid[1], + dx = textBB.width / 2, + dy = textBB.height / 2 + + if (x < 0) dx *= -1 + if (y < 0) dy *= -1 + + return { + scale: 1, + rCenter: 1, + rotate: 0, + x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2, + y: dy / (1 + x * x / (y * y)), + outside: true + } } -function scootLabels(quadrants, trace) { - var xHalf, - yHalf, - equatorFirst, - farthestX, - farthestY, - xDiffSign, - yDiffSign, - thisQuad, - oppositeQuad, - wholeSide, - i, - thisQuadOutside, - firstOppositeOutsidePt; - - function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; } - function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; } - - function scootOneLabel(thisPt, prevPt) { - if(!prevPt) prevPt = {}; - - var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin), - thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax, - thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin, - thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]), - newExtraY = prevOuterY - thisInnerY, - xBuffer, - i, - otherPt, - otherOuterY, - otherOuterX, - newExtraX; +function scootLabels (quadrants, trace) { + var xHalf, + yHalf, + equatorFirst, + farthestX, + farthestY, + xDiffSign, + yDiffSign, + thisQuad, + oppositeQuad, + wholeSide, + i, + thisQuadOutside, + firstOppositeOutsidePt + + function topFirst (a, b) { return a.pxmid[1] - b.pxmid[1] } + function bottomFirst (a, b) { return b.pxmid[1] - a.pxmid[1] } + + function scootOneLabel (thisPt, prevPt) { + if (!prevPt) prevPt = {} + + var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin), + thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax, + thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin, + thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]), + newExtraY = prevOuterY - thisInnerY, + xBuffer, + i, + otherPt, + otherOuterY, + otherOuterX, + newExtraX // make sure this label doesn't overlap other labels // this *only* has us move these labels vertically - if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY; + if (newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY // make sure this label doesn't overlap any slices - if(!Array.isArray(trace.pull)) return; // this can only happen with array pulls + if (!Array.isArray(trace.pull)) return // this can only happen with array pulls - for(i = 0; i < wholeSide.length; i++) { - otherPt = wholeSide[i]; + for (i = 0; i < wholeSide.length; i++) { + otherPt = wholeSide[i] // overlap can only happen if the other point is pulled more than this one - if(otherPt === thisPt || ((trace.pull[thisPt.i] || 0) >= trace.pull[otherPt.i] || 0)) continue; + if (otherPt === thisPt || ((trace.pull[thisPt.i] || 0) >= trace.pull[otherPt.i] || 0)) continue - if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) { + if ((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) { // closer to the equator - by construction all of these happen first // move the text vertically to get away from these slices - otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]); - newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY; + otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]) + newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY - if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY; - - } else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) { + if (newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY + } else if ((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) { // farther from the equator - happens after we've done all the // vertical moving we're going to do // move horizontally to get away from these more polar slices // if we're moving horz. based on a slice that's several slices away from this one // then we need some extra space for the lines to labels between them - xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt)); + xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt)) - otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]); - newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX; + otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]) + newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX - if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX; - } - } + if (newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX + } } + } - for(yHalf = 0; yHalf < 2; yHalf++) { - equatorFirst = yHalf ? topFirst : bottomFirst; - farthestY = yHalf ? Math.max : Math.min; - yDiffSign = yHalf ? 1 : -1; + for (yHalf = 0; yHalf < 2; yHalf++) { + equatorFirst = yHalf ? topFirst : bottomFirst + farthestY = yHalf ? Math.max : Math.min + yDiffSign = yHalf ? 1 : -1 - for(xHalf = 0; xHalf < 2; xHalf++) { - farthestX = xHalf ? Math.max : Math.min; - xDiffSign = xHalf ? 1 : -1; + for (xHalf = 0; xHalf < 2; xHalf++) { + farthestX = xHalf ? Math.max : Math.min + xDiffSign = xHalf ? 1 : -1 // first sort the array // note this is a copy of cd, so cd itself doesn't get sorted // but we can still modify points in place. - thisQuad = quadrants[yHalf][xHalf]; - thisQuad.sort(equatorFirst); - - oppositeQuad = quadrants[1 - yHalf][xHalf]; - wholeSide = oppositeQuad.concat(thisQuad); - - thisQuadOutside = []; - for(i = 0; i < thisQuad.length; i++) { - if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]); - } - - firstOppositeOutsidePt = false; - for(i = 0; yHalf && i < oppositeQuad.length; i++) { - if(oppositeQuad[i].yLabelMid !== undefined) { - firstOppositeOutsidePt = oppositeQuad[i]; - break; - } - } + thisQuad = quadrants[yHalf][xHalf] + thisQuad.sort(equatorFirst) + + oppositeQuad = quadrants[1 - yHalf][xHalf] + wholeSide = oppositeQuad.concat(thisQuad) + + thisQuadOutside = [] + for (i = 0; i < thisQuad.length; i++) { + if (thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]) + } + + firstOppositeOutsidePt = false + for (i = 0; yHalf && i < oppositeQuad.length; i++) { + if (oppositeQuad[i].yLabelMid !== undefined) { + firstOppositeOutsidePt = oppositeQuad[i] + break + } + } // each needs to avoid the previous - for(i = 0; i < thisQuadOutside.length; i++) { - var prevPt = i && thisQuadOutside[i - 1]; + for (i = 0; i < thisQuadOutside.length; i++) { + var prevPt = i && thisQuadOutside[i - 1] // bottom half needs to avoid the first label of the top half // top half we still need to call scootOneLabel on the first slice // so we can avoid other slices, but we don't pass a prevPt - if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt; - scootOneLabel(thisQuadOutside[i], prevPt); - } - } + if (firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt + scootOneLabel(thisQuadOutside[i], prevPt) + } } + } } -function scalePies(cdpie, plotSize) { - var pieBoxWidth, - pieBoxHeight, - i, - j, - cd0, - trace, - tiltAxisRads, - maxPull, - scaleGroups = [], - scaleGroup, - minPxPerValUnit; +function scalePies (cdpie, plotSize) { + var pieBoxWidth, + pieBoxHeight, + i, + j, + cd0, + trace, + tiltAxisRads, + maxPull, + scaleGroups = [], + scaleGroup, + minPxPerValUnit // first figure out the center and maximum radius for each pie - for(i = 0; i < cdpie.length; i++) { - cd0 = cdpie[i][0]; - trace = cd0.trace; - pieBoxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]); - pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]); - tiltAxisRads = trace.tiltaxis * Math.PI / 180; - - maxPull = trace.pull; - if(Array.isArray(maxPull)) { - maxPull = 0; - for(j = 0; j < trace.pull.length; j++) { - if(trace.pull[j] > maxPull) maxPull = trace.pull[j]; - } - } + for (i = 0; i < cdpie.length; i++) { + cd0 = cdpie[i][0] + trace = cd0.trace + pieBoxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) + pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]) + tiltAxisRads = trace.tiltaxis * Math.PI / 180 + + maxPull = trace.pull + if (Array.isArray(maxPull)) { + maxPull = 0 + for (j = 0; j < trace.pull.length; j++) { + if (trace.pull[j] > maxPull) maxPull = trace.pull[j] + } + } - cd0.r = Math.min( + cd0.r = Math.min( pieBoxWidth / maxExtent(trace.tilt, Math.sin(tiltAxisRads), trace.depth), pieBoxHeight / maxExtent(trace.tilt, Math.cos(tiltAxisRads), trace.depth) - ) / (2 + 2 * maxPull); + ) / (2 + 2 * maxPull) - cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2; - cd0.cy = plotSize.t + plotSize.h * (2 - trace.domain.y[1] - trace.domain.y[0]) / 2; + cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2 + cd0.cy = plotSize.t + plotSize.h * (2 - trace.domain.y[1] - trace.domain.y[0]) / 2 - if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) { - scaleGroups.push(trace.scalegroup); - } + if (trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) { + scaleGroups.push(trace.scalegroup) } + } // Then scale any pies that are grouped - for(j = 0; j < scaleGroups.length; j++) { - minPxPerValUnit = Infinity; - scaleGroup = scaleGroups[j]; - - for(i = 0; i < cdpie.length; i++) { - cd0 = cdpie[i][0]; - if(cd0.trace.scalegroup === scaleGroup) { - minPxPerValUnit = Math.min(minPxPerValUnit, - cd0.r * cd0.r / cd0.vTotal); - } - } - - for(i = 0; i < cdpie.length; i++) { - cd0 = cdpie[i][0]; - if(cd0.trace.scalegroup === scaleGroup) { - cd0.r = Math.sqrt(minPxPerValUnit * cd0.vTotal); - } - } + for (j = 0; j < scaleGroups.length; j++) { + minPxPerValUnit = Infinity + scaleGroup = scaleGroups[j] + + for (i = 0; i < cdpie.length; i++) { + cd0 = cdpie[i][0] + if (cd0.trace.scalegroup === scaleGroup) { + minPxPerValUnit = Math.min(minPxPerValUnit, + cd0.r * cd0.r / cd0.vTotal) + } } + for (i = 0; i < cdpie.length; i++) { + cd0 = cdpie[i][0] + if (cd0.trace.scalegroup === scaleGroup) { + cd0.r = Math.sqrt(minPxPerValUnit * cd0.vTotal) + } + } + } } -function setCoords(cd) { - var cd0 = cd[0], - trace = cd0.trace, - tilt = trace.tilt, - tiltAxisRads, - tiltAxisSin, - tiltAxisCos, - tiltRads, - crossTilt, - inPlane, - currentAngle = trace.rotation * Math.PI / 180, - angleFactor = 2 * Math.PI / cd0.vTotal, - firstPt = 'px0', - lastPt = 'px1', - i, - cdi, - currentCoords; - - if(trace.direction === 'counterclockwise') { - for(i = 0; i < cd.length; i++) { - if(!cd[i].hidden) break; // find the first non-hidden slice - } - if(i === cd.length) return; // all slices hidden - - currentAngle += angleFactor * cd[i].v; - angleFactor *= -1; - firstPt = 'px1'; - lastPt = 'px0'; +function setCoords (cd) { + var cd0 = cd[0], + trace = cd0.trace, + tilt = trace.tilt, + tiltAxisRads, + tiltAxisSin, + tiltAxisCos, + tiltRads, + crossTilt, + inPlane, + currentAngle = trace.rotation * Math.PI / 180, + angleFactor = 2 * Math.PI / cd0.vTotal, + firstPt = 'px0', + lastPt = 'px1', + i, + cdi, + currentCoords + + if (trace.direction === 'counterclockwise') { + for (i = 0; i < cd.length; i++) { + if (!cd[i].hidden) break // find the first non-hidden slice } + if (i === cd.length) return // all slices hidden - if(tilt) { - tiltRads = tilt * Math.PI / 180; - tiltAxisRads = trace.tiltaxis * Math.PI / 180; - crossTilt = Math.sin(tiltAxisRads) * Math.cos(tiltAxisRads); - inPlane = 1 - Math.cos(tiltRads); - tiltAxisSin = Math.sin(tiltAxisRads); - tiltAxisCos = Math.cos(tiltAxisRads); - } + currentAngle += angleFactor * cd[i].v + angleFactor *= -1 + firstPt = 'px1' + lastPt = 'px0' + } - function getCoords(angle) { - var xFlat = cd0.r * Math.sin(angle), - yFlat = -cd0.r * Math.cos(angle); + if (tilt) { + tiltRads = tilt * Math.PI / 180 + tiltAxisRads = trace.tiltaxis * Math.PI / 180 + crossTilt = Math.sin(tiltAxisRads) * Math.cos(tiltAxisRads) + inPlane = 1 - Math.cos(tiltRads) + tiltAxisSin = Math.sin(tiltAxisRads) + tiltAxisCos = Math.cos(tiltAxisRads) + } - if(!tilt) return [xFlat, yFlat]; + function getCoords (angle) { + var xFlat = cd0.r * Math.sin(angle), + yFlat = -cd0.r * Math.cos(angle) - return [ - xFlat * (1 - inPlane * tiltAxisSin * tiltAxisSin) + yFlat * crossTilt * inPlane, - xFlat * crossTilt * inPlane + yFlat * (1 - inPlane * tiltAxisCos * tiltAxisCos), - Math.sin(tiltRads) * (yFlat * tiltAxisCos - xFlat * tiltAxisSin) - ]; - } + if (!tilt) return [xFlat, yFlat] - currentCoords = getCoords(currentAngle); + return [ + xFlat * (1 - inPlane * tiltAxisSin * tiltAxisSin) + yFlat * crossTilt * inPlane, + xFlat * crossTilt * inPlane + yFlat * (1 - inPlane * tiltAxisCos * tiltAxisCos), + Math.sin(tiltRads) * (yFlat * tiltAxisCos - xFlat * tiltAxisSin) + ] + } - for(i = 0; i < cd.length; i++) { - cdi = cd[i]; - if(cdi.hidden) continue; + currentCoords = getCoords(currentAngle) - cdi[firstPt] = currentCoords; + for (i = 0; i < cd.length; i++) { + cdi = cd[i] + if (cdi.hidden) continue - currentAngle += angleFactor * cdi.v / 2; - cdi.pxmid = getCoords(currentAngle); - cdi.midangle = currentAngle; + cdi[firstPt] = currentCoords - currentAngle += angleFactor * cdi.v / 2; - currentCoords = getCoords(currentAngle); + currentAngle += angleFactor * cdi.v / 2 + cdi.pxmid = getCoords(currentAngle) + cdi.midangle = currentAngle - cdi[lastPt] = currentCoords; + currentAngle += angleFactor * cdi.v / 2 + currentCoords = getCoords(currentAngle) - cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0; - } + cdi[lastPt] = currentCoords + + cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0 + } } -function maxExtent(tilt, tiltAxisFraction, depth) { - if(!tilt) return 1; - var sinTilt = Math.sin(tilt * Math.PI / 180); - return Math.max(0.01, // don't let it go crazy if you tilt the pie totally on its side +function maxExtent (tilt, tiltAxisFraction, depth) { + if (!tilt) return 1 + var sinTilt = Math.sin(tilt * Math.PI / 180) + return Math.max(0.01, // don't let it go crazy if you tilt the pie totally on its side depth * sinTilt * Math.abs(tiltAxisFraction) + - 2 * Math.sqrt(1 - sinTilt * sinTilt * tiltAxisFraction * tiltAxisFraction)); + 2 * Math.sqrt(1 - sinTilt * sinTilt * tiltAxisFraction * tiltAxisFraction)) } diff --git a/src/traces/pie/style.js b/src/traces/pie/style.js index fb02933eb00..038dadad5b5 100644 --- a/src/traces/pie/style.js +++ b/src/traces/pie/style.js @@ -6,22 +6,22 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var d3 = require('d3'); +var d3 = require('d3') -var styleOne = require('./style_one'); +var styleOne = require('./style_one') -module.exports = function style(gd) { - gd._fullLayout._pielayer.selectAll('.trace').each(function(cd) { - var cd0 = cd[0], - trace = cd0.trace, - traceSelection = d3.select(this); +module.exports = function style (gd) { + gd._fullLayout._pielayer.selectAll('.trace').each(function (cd) { + var cd0 = cd[0], + trace = cd0.trace, + traceSelection = d3.select(this) - traceSelection.style({opacity: trace.opacity}); + traceSelection.style({opacity: trace.opacity}) - traceSelection.selectAll('.top path.surface').each(function(pt) { - d3.select(this).call(styleOne, pt, trace); - }); - }); -}; + traceSelection.selectAll('.top path.surface').each(function (pt) { + d3.select(this).call(styleOne, pt, trace) + }) + }) +} diff --git a/src/traces/pie/style_one.js b/src/traces/pie/style_one.js index d6d66738082..57ab299c353 100644 --- a/src/traces/pie/style_one.js +++ b/src/traces/pie/style_one.js @@ -6,20 +6,20 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Color = require('../../components/color'); +var Color = require('../../components/color') -module.exports = function styleOne(s, pt, trace) { - var lineColor = trace.marker.line.color; - if(Array.isArray(lineColor)) lineColor = lineColor[pt.i] || Color.defaultLine; +module.exports = function styleOne (s, pt, trace) { + var lineColor = trace.marker.line.color + if (Array.isArray(lineColor)) lineColor = lineColor[pt.i] || Color.defaultLine - var lineWidth = trace.marker.line.width || 0; - if(Array.isArray(lineWidth)) lineWidth = lineWidth[pt.i] || 0; + var lineWidth = trace.marker.line.width || 0 + if (Array.isArray(lineWidth)) lineWidth = lineWidth[pt.i] || 0 - s.style({ - 'stroke-width': lineWidth, - fill: pt.color - }) - .call(Color.stroke, lineColor); -}; + s.style({ + 'stroke-width': lineWidth, + fill: pt.color + }) + .call(Color.stroke, lineColor) +} diff --git a/src/traces/pointcloud/attributes.js b/src/traces/pointcloud/attributes.js index 3c3d76277c4..b24b68ed140 100644 --- a/src/traces/pointcloud/attributes.js +++ b/src/traces/pointcloud/attributes.js @@ -6,127 +6,127 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterglAttrs = require('../scattergl/attributes'); +var scatterglAttrs = require('../scattergl/attributes') module.exports = { - x: scatterglAttrs.x, - y: scatterglAttrs.y, - xy: { - valType: 'data_array', - description: [ - 'Faster alternative to specifying `x` and `y` separately.', - 'If supplied, it must be a typed `Float32Array` array that', - 'represents points such that `xy[i * 2] = x[i]` and `xy[i * 2 + 1] = y[i]`' - ].join(' ') + x: scatterglAttrs.x, + y: scatterglAttrs.y, + xy: { + valType: 'data_array', + description: [ + 'Faster alternative to specifying `x` and `y` separately.', + 'If supplied, it must be a typed `Float32Array` array that', + 'represents points such that `xy[i * 2] = x[i]` and `xy[i * 2 + 1] = y[i]`' + ].join(' ') + }, + indices: { + valType: 'data_array', + description: [ + 'A sequential value, 0..n, supply it to avoid creating this array inside plotting.', + 'If specified, it must be a typed `Int32Array` array.', + 'Its length must be equal to or greater than the number of points.', + 'For the best performance and memory use, create one large `indices` typed array', + 'that is guaranteed to be at least as long as the largest number of points during', + 'use, and reuse it on each `Plotly.restyle()` call.' + ].join(' ') + }, + xbounds: { + valType: 'data_array', + description: [ + 'Specify `xbounds` in the shape of `[xMin, xMax] to avoid looping through', + 'the `xy` typed array. Use it in conjunction with `xy` and `ybounds` for the performance benefits.' + ].join(' ') + }, + ybounds: { + valType: 'data_array', + description: [ + 'Specify `ybounds` in the shape of `[yMin, yMax] to avoid looping through', + 'the `xy` typed array. Use it in conjunction with `xy` and `xbounds` for the performance benefits.' + ].join(' ') + }, + text: scatterglAttrs.text, + marker: { + color: { + valType: 'color', + arrayOk: false, + role: 'style', + description: [ + 'Sets the marker fill color. It accepts a specific color.', + 'If the color is not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.' + ].join('') }, - indices: { - valType: 'data_array', - description: [ - 'A sequential value, 0..n, supply it to avoid creating this array inside plotting.', - 'If specified, it must be a typed `Int32Array` array.', - 'Its length must be equal to or greater than the number of points.', - 'For the best performance and memory use, create one large `indices` typed array', - 'that is guaranteed to be at least as long as the largest number of points during', - 'use, and reuse it on each `Plotly.restyle()` call.' - ].join(' ') + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + arrayOk: false, + role: 'style', + description: [ + 'Sets the marker opacity. The default value is `1` (fully opaque).', + 'If the markers are not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.', + 'Opacity fades the color even if `blend` is left on `false` even if there', + 'is no translucency effect in that case.' + ].join(' ') + }, + blend: { + valType: 'boolean', + dflt: null, + role: 'style', + description: [ + 'Determines if colors are blended together for a translucency effect', + 'in case `opacity` is specified as a value less then `1`.', + 'Setting `blend` to `true` reduces zoom/pan', + 'speed if used with large numbers of points.' + ].join(' ') }, - xbounds: { - valType: 'data_array', + sizemin: { + valType: 'number', + min: 0.1, + max: 2, + dflt: 0.5, + role: 'style', + description: [ + 'Sets the minimum size (in px) of the rendered marker points, effective when', + 'the `pointcloud` shows a million or more points.' + ].join(' ') + }, + sizemax: { + valType: 'number', + min: 0.1, + dflt: 20, + role: 'style', + description: [ + 'Sets the maximum size (in px) of the rendered marker points.', + 'Effective when the `pointcloud` shows only few points.' + ].join(' ') + }, + border: { + color: { + valType: 'color', + arrayOk: false, + role: 'style', description: [ - 'Specify `xbounds` in the shape of `[xMin, xMax] to avoid looping through', - 'the `xy` typed array. Use it in conjunction with `xy` and `ybounds` for the performance benefits.' + 'Sets the stroke color. It accepts a specific color.', + 'If the color is not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.' ].join(' ') - }, - ybounds: { - valType: 'data_array', + }, + arearatio: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + role: 'style', description: [ - 'Specify `ybounds` in the shape of `[yMin, yMax] to avoid looping through', - 'the `xy` typed array. Use it in conjunction with `xy` and `xbounds` for the performance benefits.' + 'Specifies what fraction of the marker area is covered with the', + 'border.' ].join(' ') - }, - text: scatterglAttrs.text, - marker: { - color: { - valType: 'color', - arrayOk: false, - role: 'style', - description: [ - 'Sets the marker fill color. It accepts a specific color.', - 'If the color is not fully opaque and there are hundreds of thousands', - 'of points, it may cause slower zooming and panning.' - ].join('') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - arrayOk: false, - role: 'style', - description: [ - 'Sets the marker opacity. The default value is `1` (fully opaque).', - 'If the markers are not fully opaque and there are hundreds of thousands', - 'of points, it may cause slower zooming and panning.', - 'Opacity fades the color even if `blend` is left on `false` even if there', - 'is no translucency effect in that case.' - ].join(' ') - }, - blend: { - valType: 'boolean', - dflt: null, - role: 'style', - description: [ - 'Determines if colors are blended together for a translucency effect', - 'in case `opacity` is specified as a value less then `1`.', - 'Setting `blend` to `true` reduces zoom/pan', - 'speed if used with large numbers of points.' - ].join(' ') - }, - sizemin: { - valType: 'number', - min: 0.1, - max: 2, - dflt: 0.5, - role: 'style', - description: [ - 'Sets the minimum size (in px) of the rendered marker points, effective when', - 'the `pointcloud` shows a million or more points.' - ].join(' ') - }, - sizemax: { - valType: 'number', - min: 0.1, - dflt: 20, - role: 'style', - description: [ - 'Sets the maximum size (in px) of the rendered marker points.', - 'Effective when the `pointcloud` shows only few points.' - ].join(' ') - }, - border: { - color: { - valType: 'color', - arrayOk: false, - role: 'style', - description: [ - 'Sets the stroke color. It accepts a specific color.', - 'If the color is not fully opaque and there are hundreds of thousands', - 'of points, it may cause slower zooming and panning.' - ].join(' ') - }, - arearatio: { - valType: 'number', - min: 0, - max: 1, - dflt: 0, - role: 'style', - description: [ - 'Specifies what fraction of the marker area is covered with the', - 'border.' - ].join(' ') - } - } + } } -}; + } +} diff --git a/src/traces/pointcloud/convert.js b/src/traces/pointcloud/convert.js index 95ac5461cb4..d84d048c474 100644 --- a/src/traces/pointcloud/convert.js +++ b/src/traces/pointcloud/convert.js @@ -6,225 +6,208 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var createPointCloudRenderer = require('gl-pointcloud2d'); - -var str2RGBArray = require('../../lib/str2rgbarray'); -var getTraceColor = require('../scatter/get_trace_color'); - -var AXES = ['xaxis', 'yaxis']; - -function Pointcloud(scene, uid) { - this.scene = scene; - this.uid = uid; - this.type = 'pointcloud'; - - this.pickXData = []; - this.pickYData = []; - this.xData = []; - this.yData = []; - this.textLabels = []; - this.color = 'rgb(0, 0, 0)'; - this.name = ''; - this.hoverinfo = 'all'; - - this.idToIndex = new Int32Array(0); - this.bounds = [0, 0, 0, 0]; - - this.pointcloudOptions = { - positions: new Float32Array(0), - idToIndex: this.idToIndex, - sizemin: 0.5, - sizemax: 12, - color: [0, 0, 0, 1], - areaRatio: 1, - borderColor: [0, 0, 0, 1] - }; - this.pointcloud = createPointCloudRenderer(scene.glplot, this.pointcloudOptions); - this.pointcloud._trace = this; // scene2d requires this prop +'use strict' + +var createPointCloudRenderer = require('gl-pointcloud2d') + +var str2RGBArray = require('../../lib/str2rgbarray') +var getTraceColor = require('../scatter/get_trace_color') + +var AXES = ['xaxis', 'yaxis'] + +function Pointcloud (scene, uid) { + this.scene = scene + this.uid = uid + this.type = 'pointcloud' + + this.pickXData = [] + this.pickYData = [] + this.xData = [] + this.yData = [] + this.textLabels = [] + this.color = 'rgb(0, 0, 0)' + this.name = '' + this.hoverinfo = 'all' + + this.idToIndex = new Int32Array(0) + this.bounds = [0, 0, 0, 0] + + this.pointcloudOptions = { + positions: new Float32Array(0), + idToIndex: this.idToIndex, + sizemin: 0.5, + sizemax: 12, + color: [0, 0, 0, 1], + areaRatio: 1, + borderColor: [0, 0, 0, 1] + } + this.pointcloud = createPointCloudRenderer(scene.glplot, this.pointcloudOptions) + this.pointcloud._trace = this // scene2d requires this prop } -var proto = Pointcloud.prototype; +var proto = Pointcloud.prototype -proto.handlePick = function(pickResult) { - - var index = this.idToIndex[pickResult.pointId]; +proto.handlePick = function (pickResult) { + var index = this.idToIndex[pickResult.pointId] // prefer the readout from XY, if present - return { - trace: this, - dataCoord: pickResult.dataCoord, - traceCoord: this.pickXYData ? + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: this.pickXYData ? [this.pickXYData[index * 2], this.pickXYData[index * 2 + 1]] : [this.pickXData[index], this.pickYData[index]], - textLabel: Array.isArray(this.textLabels) ? + textLabel: Array.isArray(this.textLabels) ? this.textLabels[index] : this.textLabels, - color: this.color, - name: this.name, - pointIndex: index, - hoverinfo: this.hoverinfo - }; -}; - -proto.update = function(options) { - - this.textLabels = options.text; - this.name = options.name; - this.hoverinfo = options.hoverinfo; - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; + color: this.color, + name: this.name, + pointIndex: index, + hoverinfo: this.hoverinfo + } +} - this.updateFast(options); +proto.update = function (options) { + this.textLabels = options.text + this.name = options.name + this.hoverinfo = options.hoverinfo + this.bounds = [Infinity, Infinity, -Infinity, -Infinity] - this.color = getTraceColor(options, {}); -}; + this.updateFast(options) -proto.updateFast = function(options) { - var x = this.xData = this.pickXData = options.x; - var y = this.yData = this.pickYData = options.y; - var xy = this.pickXYData = options.xy; + this.color = getTraceColor(options, {}) +} - var userBounds = options.xbounds && options.ybounds; - var index = options.indices; +proto.updateFast = function (options) { + var x = this.xData = this.pickXData = options.x + var y = this.yData = this.pickYData = options.y + var xy = this.pickXYData = options.xy - var len, - idToIndex, - positions, - bounds = this.bounds; + var userBounds = options.xbounds && options.ybounds + var index = options.indices - var xx, yy, i; + var len, + idToIndex, + positions, + bounds = this.bounds - if(xy) { + var xx, yy, i - positions = xy; + if (xy) { + positions = xy // dividing xy.length by 2 and truncating to integer if xy.length was not even - len = xy.length >>> 1; - - if(userBounds) { - - bounds[0] = options.xbounds[0]; - bounds[2] = options.xbounds[1]; - bounds[1] = options.ybounds[0]; - bounds[3] = options.ybounds[1]; - - } else { - - for(i = 0; i < len; i++) { - - xx = positions[i * 2]; - yy = positions[i * 2 + 1]; - - if(xx < bounds[0]) bounds[0] = xx; - if(xx > bounds[2]) bounds[2] = xx; - if(yy < bounds[1]) bounds[1] = yy; - if(yy > bounds[3]) bounds[3] = yy; - } - - } - - if(index) { - - idToIndex = index; - - } else { - - idToIndex = new Int32Array(len); - - for(i = 0; i < len; i++) { - - idToIndex[i] = i; - - } - - } + len = xy.length >>> 1 + if (userBounds) { + bounds[0] = options.xbounds[0] + bounds[2] = options.xbounds[1] + bounds[1] = options.ybounds[0] + bounds[3] = options.ybounds[1] } else { + for (i = 0; i < len; i++) { + xx = positions[i * 2] + yy = positions[i * 2 + 1] + + if (xx < bounds[0]) bounds[0] = xx + if (xx > bounds[2]) bounds[2] = xx + if (yy < bounds[1]) bounds[1] = yy + if (yy > bounds[3]) bounds[3] = yy + } + } - len = x.length; + if (index) { + idToIndex = index + } else { + idToIndex = new Int32Array(len) - positions = new Float32Array(2 * len); - idToIndex = new Int32Array(len); + for (i = 0; i < len; i++) { + idToIndex[i] = i + } + } + } else { + len = x.length - for(i = 0; i < len; i++) { - xx = x[i]; - yy = y[i]; + positions = new Float32Array(2 * len) + idToIndex = new Int32Array(len) - idToIndex[i] = i; + for (i = 0; i < len; i++) { + xx = x[i] + yy = y[i] - positions[i * 2] = xx; - positions[i * 2 + 1] = yy; + idToIndex[i] = i - if(xx < bounds[0]) bounds[0] = xx; - if(xx > bounds[2]) bounds[2] = xx; - if(yy < bounds[1]) bounds[1] = yy; - if(yy > bounds[3]) bounds[3] = yy; - } + positions[i * 2] = xx + positions[i * 2 + 1] = yy + if (xx < bounds[0]) bounds[0] = xx + if (xx > bounds[2]) bounds[2] = xx + if (yy < bounds[1]) bounds[1] = yy + if (yy > bounds[3]) bounds[3] = yy } + } - this.idToIndex = idToIndex; - this.pointcloudOptions.idToIndex = idToIndex; + this.idToIndex = idToIndex + this.pointcloudOptions.idToIndex = idToIndex - this.pointcloudOptions.positions = positions; + this.pointcloudOptions.positions = positions - var markerColor = str2RGBArray(options.marker.color), - borderColor = str2RGBArray(options.marker.border.color), - opacity = options.opacity * options.marker.opacity; + var markerColor = str2RGBArray(options.marker.color), + borderColor = str2RGBArray(options.marker.border.color), + opacity = options.opacity * options.marker.opacity - markerColor[3] *= opacity; - this.pointcloudOptions.color = markerColor; + markerColor[3] *= opacity + this.pointcloudOptions.color = markerColor // detect blending from the number of points, if undefined // because large data with blending hits performance - var blend = options.marker.blend; - if(blend === null) { - var maxPoints = 100; - blend = x.length < maxPoints || y.length < maxPoints; - } - this.pointcloudOptions.blend = blend; + var blend = options.marker.blend + if (blend === null) { + var maxPoints = 100 + blend = x.length < maxPoints || y.length < maxPoints + } + this.pointcloudOptions.blend = blend - borderColor[3] *= opacity; - this.pointcloudOptions.borderColor = borderColor; + borderColor[3] *= opacity + this.pointcloudOptions.borderColor = borderColor - var markerSizeMin = options.marker.sizemin; - var markerSizeMax = Math.max(options.marker.sizemax, options.marker.sizemin); - this.pointcloudOptions.sizeMin = markerSizeMin; - this.pointcloudOptions.sizeMax = markerSizeMax; - this.pointcloudOptions.areaRatio = options.marker.border.arearatio; + var markerSizeMin = options.marker.sizemin + var markerSizeMax = Math.max(options.marker.sizemax, options.marker.sizemin) + this.pointcloudOptions.sizeMin = markerSizeMin + this.pointcloudOptions.sizeMax = markerSizeMax + this.pointcloudOptions.areaRatio = options.marker.border.arearatio - this.pointcloud.update(this.pointcloudOptions); + this.pointcloud.update(this.pointcloudOptions) // add item for autorange routine - this.expandAxesFast(bounds, markerSizeMax / 2); // avoid axis reexpand just because of the adaptive point size -}; + this.expandAxesFast(bounds, markerSizeMax / 2) // avoid axis reexpand just because of the adaptive point size +} -proto.expandAxesFast = function(bounds, markerSize) { - var pad = markerSize || 0.5; - var ax, min, max; +proto.expandAxesFast = function (bounds, markerSize) { + var pad = markerSize || 0.5 + var ax, min, max - for(var i = 0; i < 2; i++) { - ax = this.scene[AXES[i]]; + for (var i = 0; i < 2; i++) { + ax = this.scene[AXES[i]] - min = ax._min; - if(!min) min = []; - min.push({ val: bounds[i], pad: pad }); + min = ax._min + if (!min) min = [] + min.push({ val: bounds[i], pad: pad }) - max = ax._max; - if(!max) max = []; - max.push({ val: bounds[i + 2], pad: pad }); - } -}; + max = ax._max + if (!max) max = [] + max.push({ val: bounds[i + 2], pad: pad }) + } +} -proto.dispose = function() { - this.pointcloud.dispose(); -}; +proto.dispose = function () { + this.pointcloud.dispose() +} -function createPointcloud(scene, data) { - var plot = new Pointcloud(scene, data.uid); - plot.update(data); - return plot; +function createPointcloud (scene, data) { + var plot = new Pointcloud(scene, data.uid) + plot.update(data) + return plot } -module.exports = createPointcloud; +module.exports = createPointcloud diff --git a/src/traces/pointcloud/defaults.js b/src/traces/pointcloud/defaults.js index 16c40747a69..981d3d64125 100644 --- a/src/traces/pointcloud/defaults.js +++ b/src/traces/pointcloud/defaults.js @@ -6,38 +6,37 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var attributes = require('./attributes') -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + coerce('x') + coerce('y') - coerce('x'); - coerce('y'); + coerce('xbounds') + coerce('ybounds') - coerce('xbounds'); - coerce('ybounds'); + if (traceIn.xy && traceIn.xy instanceof Float32Array) { + traceOut.xy = traceIn.xy + } - if(traceIn.xy && traceIn.xy instanceof Float32Array) { - traceOut.xy = traceIn.xy; - } + if (traceIn.indices && traceIn.indices instanceof Int32Array) { + traceOut.indices = traceIn.indices + } - if(traceIn.indices && traceIn.indices instanceof Int32Array) { - traceOut.indices = traceIn.indices; - } - - coerce('text'); - coerce('marker.color', defaultColor); - coerce('marker.opacity'); - coerce('marker.blend'); - coerce('marker.sizemin'); - coerce('marker.sizemax'); - coerce('marker.border.color', defaultColor); - coerce('marker.border.arearatio'); -}; + coerce('text') + coerce('marker.color', defaultColor) + coerce('marker.opacity') + coerce('marker.blend') + coerce('marker.sizemin') + coerce('marker.sizemax') + coerce('marker.border.color', defaultColor) + coerce('marker.border.arearatio') +} diff --git a/src/traces/pointcloud/index.js b/src/traces/pointcloud/index.js index b5cef7bdd2c..28bf9dfa807 100644 --- a/src/traces/pointcloud/index.js +++ b/src/traces/pointcloud/index.js @@ -6,26 +6,26 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var pointcloud = {}; +var pointcloud = {} -pointcloud.attributes = require('./attributes'); -pointcloud.supplyDefaults = require('./defaults'); +pointcloud.attributes = require('./attributes') +pointcloud.supplyDefaults = require('./defaults') // reuse the Scatter3D 'dummy' calc step so that legends know what to do -pointcloud.calc = require('../scatter3d/calc'); -pointcloud.plot = require('./convert'); +pointcloud.calc = require('../scatter3d/calc') +pointcloud.plot = require('./convert') -pointcloud.moduleType = 'trace'; -pointcloud.name = 'pointcloud'; -pointcloud.basePlotModule = require('../../plots/gl2d'); -pointcloud.categories = ['gl2d', 'showLegend']; +pointcloud.moduleType = 'trace' +pointcloud.name = 'pointcloud' +pointcloud.basePlotModule = require('../../plots/gl2d') +pointcloud.categories = ['gl2d', 'showLegend'] pointcloud.meta = { - description: [ - 'The data visualized as a point cloud set in `x` and `y`', - 'using the WebGl plotting engine.' - ].join(' ') -}; + description: [ + 'The data visualized as a point cloud set in `x` and `y`', + 'using the WebGl plotting engine.' + ].join(' ') +} -module.exports = pointcloud; +module.exports = pointcloud diff --git a/src/traces/scatter/arrays_to_calcdata.js b/src/traces/scatter/arrays_to_calcdata.js index 7cfcf57d2a3..aa62617c6c4 100644 --- a/src/traces/scatter/arrays_to_calcdata.js +++ b/src/traces/scatter/arrays_to_calcdata.js @@ -6,34 +6,31 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - +var Lib = require('../../lib') // arrayOk attributes, merge them into calcdata array -module.exports = function arraysToCalcdata(cd, trace) { - - Lib.mergeArray(trace.text, cd, 'tx'); - Lib.mergeArray(trace.textposition, cd, 'tp'); - if(trace.textfont) { - Lib.mergeArray(trace.textfont.size, cd, 'ts'); - Lib.mergeArray(trace.textfont.color, cd, 'tc'); - Lib.mergeArray(trace.textfont.family, cd, 'tf'); - } - - var marker = trace.marker; - if(marker) { - Lib.mergeArray(marker.size, cd, 'ms'); - Lib.mergeArray(marker.opacity, cd, 'mo'); - Lib.mergeArray(marker.symbol, cd, 'mx'); - Lib.mergeArray(marker.color, cd, 'mc'); - - var markerLine = marker.line; - if(marker.line) { - Lib.mergeArray(markerLine.color, cd, 'mlc'); - Lib.mergeArray(markerLine.width, cd, 'mlw'); - } +module.exports = function arraysToCalcdata (cd, trace) { + Lib.mergeArray(trace.text, cd, 'tx') + Lib.mergeArray(trace.textposition, cd, 'tp') + if (trace.textfont) { + Lib.mergeArray(trace.textfont.size, cd, 'ts') + Lib.mergeArray(trace.textfont.color, cd, 'tc') + Lib.mergeArray(trace.textfont.family, cd, 'tf') + } + + var marker = trace.marker + if (marker) { + Lib.mergeArray(marker.size, cd, 'ms') + Lib.mergeArray(marker.opacity, cd, 'mo') + Lib.mergeArray(marker.symbol, cd, 'mx') + Lib.mergeArray(marker.color, cd, 'mc') + + var markerLine = marker.line + if (marker.line) { + Lib.mergeArray(markerLine.color, cd, 'mlc') + Lib.mergeArray(markerLine.width, cd, 'mlw') } -}; + } +} diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 0354006573d..241f92074f1 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -6,359 +6,359 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var colorAttributes = require('../../components/colorscale/color_attributes'); -var errorBarAttrs = require('../../components/errorbars/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var colorAttributes = require('../../components/colorscale/color_attributes') +var errorBarAttrs = require('../../components/errorbars/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var Drawing = require('../../components/drawing'); -var constants = require('./constants'); -var extendFlat = require('../../lib/extend').extendFlat; +var Drawing = require('../../components/drawing') +var constants = require('./constants') +var extendFlat = require('../../lib/extend').extendFlat module.exports = { - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' + x: { + valType: 'data_array', + description: 'Sets the x coordinates.' + }, + x0: { + valType: 'any', + dflt: 0, + role: 'info', + description: [ + 'Alternate to `x`.', + 'Builds a linear space of x coordinates.', + 'Use with `dx`', + 'where `x0` is the starting coordinate and `dx` the step.' + ].join(' ') + }, + dx: { + valType: 'number', + dflt: 1, + role: 'info', + description: [ + 'Sets the x coordinate step.', + 'See `x0` for more info.' + ].join(' ') + }, + y: { + valType: 'data_array', + description: 'Sets the y coordinates.' + }, + y0: { + valType: 'any', + dflt: 0, + role: 'info', + description: [ + 'Alternate to `y`.', + 'Builds a linear space of y coordinates.', + 'Use with `dy`', + 'where `y0` is the starting coordinate and `dy` the step.' + ].join(' ') + }, + dy: { + valType: 'number', + dflt: 1, + role: 'info', + description: [ + 'Sets the y coordinate step.', + 'See `y0` for more info.' + ].join(' ') + }, + ids: { + valType: 'data_array', + description: 'A list of keys for object constancy of data points during animation' + }, + text: { + valType: 'string', + role: 'info', + dflt: '', + arrayOk: true, + description: [ + 'Sets text elements associated with each (x,y) pair.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y) coordinates.' + ].join(' ') + }, + mode: { + valType: 'flaglist', + flags: ['lines', 'markers', 'text'], + extras: ['none'], + role: 'info', + description: [ + 'Determines the drawing mode for this scatter trace.', + 'If the provided `mode` includes *text* then the `text` elements', + 'appear at the coordinates. Otherwise, the `text` elements', + 'appear on hover.', + 'If there are less than ' + constants.PTS_LINESONLY + ' points,', + 'then the default is *lines+markers*. Otherwise, *lines*.' + ].join(' ') + }, + hoveron: { + valType: 'flaglist', + flags: ['points', 'fills'], + role: 'info', + description: [ + 'Do the hover effects highlight individual points (markers or', + 'line points) or do they highlight filled regions?', + 'If the fill is *toself* or *tonext* and there are no markers', + 'or text, then the default is *fills*, otherwise it is *points*.' + ].join(' ') + }, + line: { + color: { + valType: 'color', + role: 'style', + description: 'Sets the line color.' }, - x0: { - valType: 'any', - dflt: 0, - role: 'info', - description: [ - 'Alternate to `x`.', - 'Builds a linear space of x coordinates.', - 'Use with `dx`', - 'where `x0` is the starting coordinate and `dx` the step.' - ].join(' ') + width: { + valType: 'number', + min: 0, + dflt: 2, + role: 'style', + description: 'Sets the line width (in px).' }, - dx: { - valType: 'number', - dflt: 1, - role: 'info', - description: [ - 'Sets the x coordinate step.', - 'See `x0` for more info.' - ].join(' ') + shape: { + valType: 'enumerated', + values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'], + dflt: 'linear', + role: 'style', + description: [ + 'Determines the line shape.', + 'With *spline* the lines are drawn using spline interpolation.', + 'The other available values correspond to step-wise line shapes.' + ].join(' ') }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' + smoothing: { + valType: 'number', + min: 0, + max: 1.3, + dflt: 1, + role: 'style', + description: [ + 'Has an effect only if `shape` is set to *spline*', + 'Sets the amount of smoothing.', + '*0* corresponds to no smoothing (equivalent to a *linear* shape).' + ].join(' ') }, - y0: { - valType: 'any', - dflt: 0, - role: 'info', - description: [ - 'Alternate to `y`.', - 'Builds a linear space of y coordinates.', - 'Use with `dy`', - 'where `y0` is the starting coordinate and `dy` the step.' - ].join(' ') + dash: { + valType: 'string', + // string type usually doesn't take values... this one should really be + // a special type or at least a special coercion function, from the GUI + // you only get these values but elsewhere the user can supply a list of + // dash lengths in px, and it will be honored + values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'], + dflt: 'solid', + role: 'style', + description: [ + 'Sets the style of the lines. Set to a dash string type', + 'or a dash length in px.' + ].join(' ') }, - dy: { - valType: 'number', - dflt: 1, - role: 'info', - description: [ - 'Sets the y coordinate step.', - 'See `y0` for more info.' - ].join(' ') + simplify: { + valType: 'boolean', + dflt: true, + role: 'info', + description: [ + 'Simplifies lines by removing nearly-collinear points. When transitioning', + 'lines, it may be desirable to disable this so that the number of points', + 'along the resulting SVG path is unaffected.' + ].join(' ') + } + }, + connectgaps: { + valType: 'boolean', + dflt: false, + role: 'info', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the provided data arrays are connected.' + ].join(' ') + }, + fill: { + valType: 'enumerated', + values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'], + dflt: 'none', + role: 'style', + description: [ + 'Sets the area to fill with a solid color.', + 'Use with `fillcolor` if not *none*.', + '*tozerox* and *tozeroy* fill to x=0 and y=0 respectively.', + '*tonextx* and *tonexty* fill between the endpoints of this', + 'trace and the endpoints of the trace before it, connecting those', + 'endpoints with straight lines (to make a stacked area graph);', + 'if there is no trace before it, they behave like *tozerox* and', + '*tozeroy*.', + '*toself* connects the endpoints of the trace (or each segment', + 'of the trace if it has gaps) into a closed shape.', + '*tonext* fills the space between two traces if one completely', + 'encloses the other (eg consecutive contour lines), and behaves like', + '*toself* if there is no trace before it. *tonext* should not be', + 'used if one trace does not enclose the other.' + ].join(' ') + }, + fillcolor: { + valType: 'color', + role: 'style', + description: [ + 'Sets the fill color.', + 'Defaults to a half-transparent variant of the line color,', + 'marker color, or marker line color, whichever is available.' + ].join(' ') + }, + marker: extendFlat({}, { + symbol: { + valType: 'enumerated', + values: Drawing.symbolList, + dflt: 'circle', + arrayOk: true, + role: 'style', + description: [ + 'Sets the marker symbol type.', + 'Adding 100 is equivalent to appending *-open* to a symbol name.', + 'Adding 200 is equivalent to appending *-dot* to a symbol name.', + 'Adding 300 is equivalent to appending *-open-dot*', + 'or *dot-open* to a symbol name.' + ].join(' ') }, - ids: { - valType: 'data_array', - description: 'A list of keys for object constancy of data points during animation' + opacity: { + valType: 'number', + min: 0, + max: 1, + arrayOk: true, + role: 'style', + description: 'Sets the marker opacity.' }, - text: { - valType: 'string', - role: 'info', - dflt: '', - arrayOk: true, - description: [ - 'Sets text elements associated with each (x,y) pair.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' - ].join(' ') + size: { + valType: 'number', + min: 0, + dflt: 6, + arrayOk: true, + role: 'style', + description: 'Sets the marker size (in px).' }, - mode: { - valType: 'flaglist', - flags: ['lines', 'markers', 'text'], - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.', - 'If the provided `mode` includes *text* then the `text` elements', - 'appear at the coordinates. Otherwise, the `text` elements', - 'appear on hover.', - 'If there are less than ' + constants.PTS_LINESONLY + ' points,', - 'then the default is *lines+markers*. Otherwise, *lines*.' - ].join(' ') + maxdisplayed: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Sets a maximum number of points to be drawn on the graph.', + '*0* corresponds to no limit.' + ].join(' ') }, - hoveron: { - valType: 'flaglist', - flags: ['points', 'fills'], - role: 'info', - description: [ - 'Do the hover effects highlight individual points (markers or', - 'line points) or do they highlight filled regions?', - 'If the fill is *toself* or *tonext* and there are no markers', - 'or text, then the default is *fills*, otherwise it is *points*.' - ].join(' ') + sizeref: { + valType: 'number', + dflt: 1, + role: 'style', + description: [ + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the scale factor used to determine the rendered size of', + 'marker points. Use with `sizemin` and `sizemode`.' + ].join(' ') }, - line: { - color: { - valType: 'color', - role: 'style', - description: 'Sets the line color.' - }, - width: { - valType: 'number', - min: 0, - dflt: 2, - role: 'style', - description: 'Sets the line width (in px).' - }, - shape: { - valType: 'enumerated', - values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'], - dflt: 'linear', - role: 'style', - description: [ - 'Determines the line shape.', - 'With *spline* the lines are drawn using spline interpolation.', - 'The other available values correspond to step-wise line shapes.' - ].join(' ') - }, - smoothing: { - valType: 'number', - min: 0, - max: 1.3, - dflt: 1, - role: 'style', - description: [ - 'Has an effect only if `shape` is set to *spline*', - 'Sets the amount of smoothing.', - '*0* corresponds to no smoothing (equivalent to a *linear* shape).' - ].join(' ') - }, - dash: { - valType: 'string', - // string type usually doesn't take values... this one should really be - // a special type or at least a special coercion function, from the GUI - // you only get these values but elsewhere the user can supply a list of - // dash lengths in px, and it will be honored - values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'], - dflt: 'solid', - role: 'style', - description: [ - 'Sets the style of the lines. Set to a dash string type', - 'or a dash length in px.' - ].join(' ') - }, - simplify: { - valType: 'boolean', - dflt: true, - role: 'info', - description: [ - 'Simplifies lines by removing nearly-collinear points. When transitioning', - 'lines, it may be desirable to disable this so that the number of points', - 'along the resulting SVG path is unaffected.' - ].join(' ') - } + sizemin: { + valType: 'number', + min: 0, + dflt: 0, + role: 'style', + description: [ + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the minimum size (in px) of the rendered marker points.' + ].join(' ') }, - connectgaps: { - valType: 'boolean', - dflt: false, - role: 'info', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the provided data arrays are connected.' - ].join(' ') + sizemode: { + valType: 'enumerated', + values: ['diameter', 'area'], + dflt: 'diameter', + role: 'info', + description: [ + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the rule for which the data in `size` is converted', + 'to pixels.' + ].join(' ') }, - fill: { - valType: 'enumerated', - values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'], - dflt: 'none', - role: 'style', - description: [ - 'Sets the area to fill with a solid color.', - 'Use with `fillcolor` if not *none*.', - '*tozerox* and *tozeroy* fill to x=0 and y=0 respectively.', - '*tonextx* and *tonexty* fill between the endpoints of this', - 'trace and the endpoints of the trace before it, connecting those', - 'endpoints with straight lines (to make a stacked area graph);', - 'if there is no trace before it, they behave like *tozerox* and', - '*tozeroy*.', - '*toself* connects the endpoints of the trace (or each segment', - 'of the trace if it has gaps) into a closed shape.', - '*tonext* fills the space between two traces if one completely', - 'encloses the other (eg consecutive contour lines), and behaves like', - '*toself* if there is no trace before it. *tonext* should not be', - 'used if one trace does not enclose the other.' - ].join(' ') + + showscale: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Has an effect only if `marker.color` is set to a numerical array.', + 'Determines whether or not a colorbar is displayed.' + ].join(' ') }, - fillcolor: { - valType: 'color', + colorbar: colorbarAttrs, + + line: extendFlat({}, { + width: { + valType: 'number', + min: 0, + arrayOk: true, role: 'style', - description: [ - 'Sets the fill color.', - 'Defaults to a half-transparent variant of the line color,', - 'marker color, or marker line color, whichever is available.' - ].join(' ') + description: 'Sets the width (in px) of the lines bounding the marker points.' + } }, - marker: extendFlat({}, { - symbol: { - valType: 'enumerated', - values: Drawing.symbolList, - dflt: 'circle', - arrayOk: true, - role: 'style', - description: [ - 'Sets the marker symbol type.', - 'Adding 100 is equivalent to appending *-open* to a symbol name.', - 'Adding 200 is equivalent to appending *-dot* to a symbol name.', - 'Adding 300 is equivalent to appending *-open-dot*', - 'or *dot-open* to a symbol name.' - ].join(' ') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - arrayOk: true, - role: 'style', - description: 'Sets the marker opacity.' - }, - size: { - valType: 'number', - min: 0, - dflt: 6, - arrayOk: true, - role: 'style', - description: 'Sets the marker size (in px).' - }, - maxdisplayed: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Sets a maximum number of points to be drawn on the graph.', - '*0* corresponds to no limit.' - ].join(' ') - }, - sizeref: { - valType: 'number', - dflt: 1, - role: 'style', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the scale factor used to determine the rendered size of', - 'marker points. Use with `sizemin` and `sizemode`.' - ].join(' ') - }, - sizemin: { - valType: 'number', - min: 0, - dflt: 0, - role: 'style', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the minimum size (in px) of the rendered marker points.' - ].join(' ') - }, - sizemode: { - valType: 'enumerated', - values: ['diameter', 'area'], - dflt: 'diameter', - role: 'info', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the rule for which the data in `size` is converted', - 'to pixels.' - ].join(' ') - }, - - showscale: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Has an effect only if `marker.color` is set to a numerical array.', - 'Determines whether or not a colorbar is displayed.' - ].join(' ') - }, - colorbar: colorbarAttrs, - - line: extendFlat({}, { - width: { - valType: 'number', - min: 0, - arrayOk: true, - role: 'style', - description: 'Sets the width (in px) of the lines bounding the marker points.' - } - }, colorAttributes('marker.line') ) - }, + }, colorAttributes('marker') ), - textposition: { - valType: 'enumerated', - values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right' - ], - dflt: 'middle center', - arrayOk: true, - role: 'style', - description: [ - 'Sets the positions of the `text` elements', - 'with respects to the (x,y) coordinates.' - ].join(' ') + textposition: { + valType: 'enumerated', + values: [ + 'top left', 'top center', 'top right', + 'middle left', 'middle center', 'middle right', + 'bottom left', 'bottom center', 'bottom right' + ], + dflt: 'middle center', + arrayOk: true, + role: 'style', + description: [ + 'Sets the positions of the `text` elements', + 'with respects to the (x,y) coordinates.' + ].join(' ') + }, + textfont: { + family: { + valType: 'string', + role: 'style', + noBlank: true, + strict: true, + arrayOk: true }, - textfont: { - family: { - valType: 'string', - role: 'style', - noBlank: true, - strict: true, - arrayOk: true - }, - size: { - valType: 'number', - role: 'style', - min: 1, - arrayOk: true - }, - color: { - valType: 'color', - role: 'style', - arrayOk: true - }, - description: 'Sets the text font.' + size: { + valType: 'number', + role: 'style', + min: 1, + arrayOk: true }, - - r: { - valType: 'data_array', - description: [ - 'For polar chart only.', - 'Sets the radial coordinates.' - ].join('') - }, - t: { - valType: 'data_array', - description: [ - 'For polar chart only.', - 'Sets the angular coordinates.' - ].join('') + color: { + valType: 'color', + role: 'style', + arrayOk: true }, + description: 'Sets the text font.' + }, + + r: { + valType: 'data_array', + description: [ + 'For polar chart only.', + 'Sets the radial coordinates.' + ].join('') + }, + t: { + valType: 'data_array', + description: [ + 'For polar chart only.', + 'Sets the angular coordinates.' + ].join('') + }, - error_y: errorBarAttrs, - error_x: errorBarAttrs -}; + error_y: errorBarAttrs, + error_x: errorBarAttrs +} diff --git a/src/traces/scatter/calc.js b/src/traces/scatter/calc.js index 1708af0144b..595fc1b3882 100644 --- a/src/traces/scatter/calc.js +++ b/src/traces/scatter/calc.js @@ -6,123 +6,119 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Axes = require('../../plots/cartesian/axes') -var Axes = require('../../plots/cartesian/axes'); +var subTypes = require('./subtypes') +var calcColorscale = require('./colorscale_calc') +var arraysToCalcdata = require('./arrays_to_calcdata') -var subTypes = require('./subtypes'); -var calcColorscale = require('./colorscale_calc'); -var arraysToCalcdata = require('./arrays_to_calcdata'); +module.exports = function calc (gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + ya = Axes.getFromId(gd, trace.yaxis || 'y') + var x = xa.makeCalcdata(trace, 'x'), + y = ya.makeCalcdata(trace, 'y') -module.exports = function calc(gd, trace) { - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'); - - var x = xa.makeCalcdata(trace, 'x'), - y = ya.makeCalcdata(trace, 'y'); - - var serieslen = Math.min(x.length, y.length), - marker, - s, - i; + var serieslen = Math.min(x.length, y.length), + marker, + s, + i // cancel minimum tick spacings (only applies to bars and boxes) - xa._minDtick = 0; - ya._minDtick = 0; + xa._minDtick = 0 + ya._minDtick = 0 - if(x.length > serieslen) x.splice(serieslen, x.length - serieslen); - if(y.length > serieslen) y.splice(serieslen, y.length - serieslen); + if (x.length > serieslen) x.splice(serieslen, x.length - serieslen) + if (y.length > serieslen) y.splice(serieslen, y.length - serieslen) // check whether bounds should be tight, padded, extended to zero... // most cases both should be padded on both ends, so start with that. - var xOptions = {padded: true}, - yOptions = {padded: true}; - - if(subTypes.hasMarkers(trace)) { + var xOptions = {padded: true}, + yOptions = {padded: true} + if (subTypes.hasMarkers(trace)) { // Treat size like x or y arrays --- Run d2c // this needs to go before ppad computation - marker = trace.marker; - s = marker.size; + marker = trace.marker + s = marker.size - if(Array.isArray(s)) { + if (Array.isArray(s)) { // I tried auto-type but category and dates dont make much sense. - var ax = {type: 'linear'}; - Axes.setConvert(ax); - s = ax.makeCalcdata(trace.marker, 'size'); - if(s.length > serieslen) s.splice(serieslen, s.length - serieslen); - } - - var sizeref = 1.6 * (trace.marker.sizeref || 1), - markerTrans; - if(trace.marker.sizemode === 'area') { - markerTrans = function(v) { - return Math.max(Math.sqrt((v || 0) / sizeref), 3); - }; - } - else { - markerTrans = function(v) { - return Math.max((v || 0) / sizeref, 3); - }; - } - xOptions.ppad = yOptions.ppad = Array.isArray(s) ? - s.map(markerTrans) : markerTrans(s); + var ax = {type: 'linear'} + Axes.setConvert(ax) + s = ax.makeCalcdata(trace.marker, 'size') + if (s.length > serieslen) s.splice(serieslen, s.length - serieslen) + } + + var sizeref = 1.6 * (trace.marker.sizeref || 1), + markerTrans + if (trace.marker.sizemode === 'area') { + markerTrans = function (v) { + return Math.max(Math.sqrt((v || 0) / sizeref), 3) + } + } else { + markerTrans = function (v) { + return Math.max((v || 0) / sizeref, 3) + } } + xOptions.ppad = yOptions.ppad = Array.isArray(s) ? + s.map(markerTrans) : markerTrans(s) + } - calcColorscale(trace); + calcColorscale(trace) // TODO: text size // include zero (tight) and extremes (padded) if fill to zero // (unless the shape is closed, then it's just filling the shape regardless) - if(((trace.fill === 'tozerox') || + if (((trace.fill === 'tozerox') || ((trace.fill === 'tonextx') && gd.firstscatter)) && ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { - xOptions.tozero = true; - } + xOptions.tozero = true + } // if no error bars, markers or text, or fill to y=0 remove x padding - else if(!trace.error_y.visible && ( + else if (!trace.error_y.visible && ( ['tonexty', 'tozeroy'].indexOf(trace.fill) !== -1 || (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)) )) { - xOptions.padded = false; - xOptions.ppad = 0; - } + xOptions.padded = false + xOptions.ppad = 0 + } // now check for y - rather different logic, though still mostly padded both ends // include zero (tight) and extremes (padded) if fill to zero // (unless the shape is closed, then it's just filling the shape regardless) - if(((trace.fill === 'tozeroy') || ((trace.fill === 'tonexty') && gd.firstscatter)) && + if (((trace.fill === 'tozeroy') || ((trace.fill === 'tonexty') && gd.firstscatter)) && ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { - yOptions.tozero = true; - } + yOptions.tozero = true + } // tight y: any x fill - else if(['tonextx', 'tozerox'].indexOf(trace.fill) !== -1) { - yOptions.padded = false; - } + else if (['tonextx', 'tozerox'].indexOf(trace.fill) !== -1) { + yOptions.padded = false + } - Axes.expand(xa, x, xOptions); - Axes.expand(ya, y, yOptions); + Axes.expand(xa, x, xOptions) + Axes.expand(ya, y, yOptions) // create the "calculated data" to plot - var cd = new Array(serieslen); - for(i = 0; i < serieslen; i++) { - cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? - {x: x[i], y: y[i]} : {x: false, y: false}; - - if(trace.ids) { - cd[i].id = String(trace.ids[i]); - } + var cd = new Array(serieslen) + for (i = 0; i < serieslen; i++) { + cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? + {x: x[i], y: y[i]} : {x: false, y: false} + + if (trace.ids) { + cd[i].id = String(trace.ids[i]) } + } - arraysToCalcdata(cd, trace); + arraysToCalcdata(cd, trace) - gd.firstscatter = false; - return cd; -}; + gd.firstscatter = false + return cd +} diff --git a/src/traces/scatter/clean_data.js b/src/traces/scatter/clean_data.js index 8e18a13fb1e..5f313e9d4d8 100644 --- a/src/traces/scatter/clean_data.js +++ b/src/traces/scatter/clean_data.js @@ -6,32 +6,30 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' // remove opacity for any trace that has a fill or is filled to -module.exports = function cleanData(fullData) { - for(var i = 0; i < fullData.length; i++) { - var tracei = fullData[i]; - if(tracei.type !== 'scatter') continue; +module.exports = function cleanData (fullData) { + for (var i = 0; i < fullData.length; i++) { + var tracei = fullData[i] + if (tracei.type !== 'scatter') continue - var filli = tracei.fill; - if(filli === 'none' || filli === 'toself') continue; + var filli = tracei.fill + if (filli === 'none' || filli === 'toself') continue - tracei.opacity = undefined; + tracei.opacity = undefined - if(filli === 'tonexty' || filli === 'tonextx') { - for(var j = i - 1; j >= 0; j--) { - var tracej = fullData[j]; + if (filli === 'tonexty' || filli === 'tonextx') { + for (var j = i - 1; j >= 0; j--) { + var tracej = fullData[j] - if((tracej.type === 'scatter') && + if ((tracej.type === 'scatter') && (tracej.xaxis === tracei.xaxis) && (tracej.yaxis === tracei.yaxis)) { - tracej.opacity = undefined; - break; - } - } + tracej.opacity = undefined + break } + } } -}; + } +} diff --git a/src/traces/scatter/colorbar.js b/src/traces/scatter/colorbar.js index 5a6ce7b52f1..3605896cfe8 100644 --- a/src/traces/scatter/colorbar.js +++ b/src/traces/scatter/colorbar.js @@ -6,50 +6,48 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Plots = require('../../plots/plots') +var Colorscale = require('../../components/colorscale') +var drawColorbar = require('../../components/colorbar/draw') -var Lib = require('../../lib'); -var Plots = require('../../plots/plots'); -var Colorscale = require('../../components/colorscale'); -var drawColorbar = require('../../components/colorbar/draw'); +module.exports = function colorbar (gd, cd) { + var trace = cd[0].trace, + marker = trace.marker, + cbId = 'cb' + trace.uid - -module.exports = function colorbar(gd, cd) { - var trace = cd[0].trace, - marker = trace.marker, - cbId = 'cb' + trace.uid; - - gd._fullLayout._infolayer.selectAll('.' + cbId).remove(); + gd._fullLayout._infolayer.selectAll('.' + cbId).remove() // TODO unify scatter and heatmap colorbar // TODO make Colorbar.draw support multiple colorbar per trace - if((marker === undefined) || !marker.showscale) { - Plots.autoMargin(gd, cbId); - return; - } + if ((marker === undefined) || !marker.showscale) { + Plots.autoMargin(gd, cbId) + return + } - var vals = marker.color, - cmin = marker.cmin, - cmax = marker.cmax; + var vals = marker.color, + cmin = marker.cmin, + cmax = marker.cmax - if(!isNumeric(cmin)) cmin = Lib.aggNums(Math.min, null, vals); - if(!isNumeric(cmax)) cmax = Lib.aggNums(Math.max, null, vals); + if (!isNumeric(cmin)) cmin = Lib.aggNums(Math.min, null, vals) + if (!isNumeric(cmax)) cmax = Lib.aggNums(Math.max, null, vals) - var cb = cd[0].t.cb = drawColorbar(gd, cbId); - var sclFunc = Colorscale.makeColorScaleFunc( + var cb = cd[0].t.cb = drawColorbar(gd, cbId) + var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( marker.colorscale, cmin, cmax ), { noNumericCheck: true } - ); + ) - cb.fillcolor(sclFunc) + cb.fillcolor(sclFunc) .filllevels({start: cmin, end: cmax, size: (cmax - cmin) / 254}) - .options(marker.colorbar)(); -}; + .options(marker.colorbar)() +} diff --git a/src/traces/scatter/colorscale_calc.js b/src/traces/scatter/colorscale_calc.js index 27630c8f91b..f7c84469110 100644 --- a/src/traces/scatter/colorscale_calc.js +++ b/src/traces/scatter/colorscale_calc.js @@ -6,26 +6,24 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var hasColorscale = require('../../components/colorscale/has_colorscale') +var calcColorscale = require('../../components/colorscale/calc') -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var calcColorscale = require('../../components/colorscale/calc'); +var subTypes = require('./subtypes') -var subTypes = require('./subtypes'); +module.exports = function calcMarkerColorscale (trace) { + if (subTypes.hasLines(trace) && hasColorscale(trace, 'line')) { + calcColorscale(trace, trace.line.color, 'line', 'c') + } - -module.exports = function calcMarkerColorscale(trace) { - if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) { - calcColorscale(trace, trace.line.color, 'line', 'c'); + if (subTypes.hasMarkers(trace)) { + if (hasColorscale(trace, 'marker')) { + calcColorscale(trace, trace.marker.color, 'marker', 'c') } - - if(subTypes.hasMarkers(trace)) { - if(hasColorscale(trace, 'marker')) { - calcColorscale(trace, trace.marker.color, 'marker', 'c'); - } - if(hasColorscale(trace, 'marker.line')) { - calcColorscale(trace, trace.marker.line.color, 'marker.line', 'c'); - } + if (hasColorscale(trace, 'marker.line')) { + calcColorscale(trace, trace.marker.line.color, 'marker.line', 'c') } -}; + } +} diff --git a/src/traces/scatter/constants.js b/src/traces/scatter/constants.js index 66eb332f109..f7cf231748c 100644 --- a/src/traces/scatter/constants.js +++ b/src/traces/scatter/constants.js @@ -6,9 +6,8 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; +'use strict' module.exports = { - PTS_LINESONLY: 20 -}; + PTS_LINESONLY: 20 +} diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 6b71dbd4f6f..130cc8db04a 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -6,73 +6,71 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Lib = require('../../lib'); - -var attributes = require('./attributes'); -var constants = require('./constants'); -var subTypes = require('./subtypes'); -var handleXYDefaults = require('./xy_defaults'); -var handleMarkerDefaults = require('./marker_defaults'); -var handleLineDefaults = require('./line_defaults'); -var handleLineShapeDefaults = require('./line_shape_defaults'); -var handleTextDefaults = require('./text_defaults'); -var handleFillColorDefaults = require('./fillcolor_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var len = handleXYDefaults(traceIn, traceOut, layout, coerce), +'use strict' + +var Lib = require('../../lib') + +var attributes = require('./attributes') +var constants = require('./constants') +var subTypes = require('./subtypes') +var handleXYDefaults = require('./xy_defaults') +var handleMarkerDefaults = require('./marker_defaults') +var handleLineDefaults = require('./line_defaults') +var handleLineShapeDefaults = require('./line_shape_defaults') +var handleTextDefaults = require('./text_defaults') +var handleFillColorDefaults = require('./fillcolor_defaults') +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults') + +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce), // TODO: default mode by orphan points... - defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - coerce('mode', defaultMode); - coerce('ids'); - - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - handleLineShapeDefaults(traceIn, traceOut, coerce); - coerce('connectgaps'); - coerce('line.simplify'); - } - - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - if(subTypes.hasText(traceOut)) { - handleTextDefaults(traceIn, traceOut, layout, coerce); - } - - var dfltHoverOn = []; - - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { - coerce('marker.maxdisplayed'); - dfltHoverOn.push('points'); - } - - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); - } - - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { - dfltHoverOn.push('fills'); - } - coerce('hoveron', dfltHoverOn.join('+') || 'points'); - - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); -}; + defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines' + if (!len) { + traceOut.visible = false + return + } + + coerce('text') + coerce('mode', defaultMode) + coerce('ids') + + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + handleLineShapeDefaults(traceIn, traceOut, coerce) + coerce('connectgaps') + coerce('line.simplify') + } + + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } + + if (subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce) + } + + var dfltHoverOn = [] + + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + coerce('marker.maxdisplayed') + dfltHoverOn.push('points') + } + + coerce('fill') + if (traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce) + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce) + } + + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills') + } + coerce('hoveron', dfltHoverOn.join('+') || 'points') + + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}) + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}) +} diff --git a/src/traces/scatter/fillcolor_defaults.js b/src/traces/scatter/fillcolor_defaults.js index b53fcb8e93d..752305b7796 100644 --- a/src/traces/scatter/fillcolor_defaults.js +++ b/src/traces/scatter/fillcolor_defaults.js @@ -6,31 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../components/color') -var Color = require('../../components/color'); +module.exports = function fillColorDefaults (traceIn, traceOut, defaultColor, coerce) { + var inheritColorFromMarker = false - -module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) { - var inheritColorFromMarker = false; - - if(traceOut.marker) { + if (traceOut.marker) { // don't try to inherit a color array - var markerColor = traceOut.marker.color, - markerLineColor = (traceOut.marker.line || {}).color; + var markerColor = traceOut.marker.color, + markerLineColor = (traceOut.marker.line || {}).color - if(markerColor && !Array.isArray(markerColor)) { - inheritColorFromMarker = markerColor; - } - else if(markerLineColor && !Array.isArray(markerLineColor)) { - inheritColorFromMarker = markerLineColor; - } + if (markerColor && !Array.isArray(markerColor)) { + inheritColorFromMarker = markerColor + } else if (markerLineColor && !Array.isArray(markerLineColor)) { + inheritColorFromMarker = markerLineColor } + } - coerce('fillcolor', Color.addOpacity( + coerce('fillcolor', Color.addOpacity( (traceOut.line || {}).color || inheritColorFromMarker || defaultColor, 0.5 - )); -}; + )) +} diff --git a/src/traces/scatter/get_trace_color.js b/src/traces/scatter/get_trace_color.js index cbf0708217c..95263c011f0 100644 --- a/src/traces/scatter/get_trace_color.js +++ b/src/traces/scatter/get_trace_color.js @@ -6,46 +6,40 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../components/color') +var subtypes = require('./subtypes') -var Color = require('../../components/color'); -var subtypes = require('./subtypes'); - - -module.exports = function getTraceColor(trace, di) { - var lc, tc; +module.exports = function getTraceColor (trace, di) { + var lc, tc // TODO: text modes - if(trace.mode === 'lines') { - lc = trace.line.color; - return (lc && Color.opacity(lc)) ? - lc : trace.fillcolor; - } - else if(trace.mode === 'none') { - return trace.fill ? trace.fillcolor : ''; - } - else { - var mc = di.mcc || (trace.marker || {}).color, - mlc = di.mlcc || ((trace.marker || {}).line || {}).color; - - tc = (mc && Color.opacity(mc)) ? mc : + if (trace.mode === 'lines') { + lc = trace.line.color + return (lc && Color.opacity(lc)) ? + lc : trace.fillcolor + } else if (trace.mode === 'none') { + return trace.fill ? trace.fillcolor : '' + } else { + var mc = di.mcc || (trace.marker || {}).color, + mlc = di.mlcc || ((trace.marker || {}).line || {}).color + + tc = (mc && Color.opacity(mc)) ? mc : (mlc && Color.opacity(mlc) && - (di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : ''; + (di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : '' - if(tc) { + if (tc) { // make sure the points aren't TOO transparent - if(Color.opacity(tc) < 0.3) { - return Color.addOpacity(tc, 0.3); - } - else return tc; - } - else { - lc = (trace.line || {}).color; - return (lc && Color.opacity(lc) && + if (Color.opacity(tc) < 0.3) { + return Color.addOpacity(tc, 0.3) + } else return tc + } else { + lc = (trace.line || {}).color + return (lc && Color.opacity(lc) && subtypes.hasLines(trace) && trace.line.width) ? - lc : trace.fillcolor; - } + lc : trace.fillcolor } -}; + } +} diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index 2392a23bb4f..747f0ca1fec 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -6,162 +6,158 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Lib = require('../../lib'); -var Fx = require('../../plots/cartesian/graph_interact'); -var constants = require('../../plots/cartesian/constants'); -var ErrorBars = require('../../components/errorbars'); -var getTraceColor = require('./get_trace_color'); -var Color = require('../../components/color'); - - -module.exports = function hoverPoints(pointData, xval, yval, hovermode) { - var cd = pointData.cd, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya, - xpx = xa.c2p(xval), - ypx = ya.c2p(yval), - pt = [xpx, ypx]; +'use strict' + +var Lib = require('../../lib') +var Fx = require('../../plots/cartesian/graph_interact') +var constants = require('../../plots/cartesian/constants') +var ErrorBars = require('../../components/errorbars') +var getTraceColor = require('./get_trace_color') +var Color = require('../../components/color') + +module.exports = function hoverPoints (pointData, xval, yval, hovermode) { + var cd = pointData.cd, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya, + xpx = xa.c2p(xval), + ypx = ya.c2p(yval), + pt = [xpx, ypx] // look for points to hover on first, then take fills only if we // didn't find a point - if(trace.hoveron.indexOf('points') !== -1) { - var dx = function(di) { + if (trace.hoveron.indexOf('points') !== -1) { + var dx = function (di) { // scatter points: d.mrc is the calculated marker radius // adjust the distance so if you're inside the marker it // always will show up regardless of point size, but // prioritize smaller points - var rad = Math.max(3, di.mrc || 0); - return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad); - }, - dy = function(di) { - var rad = Math.max(3, di.mrc || 0); - return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad); - }, - dxy = function(di) { - var rad = Math.max(3, di.mrc || 0), - dx = xa.c2p(di.x) - xpx, - dy = ya.c2p(di.y) - ypx; - return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); - }, - distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); - - Fx.getClosest(cd, distfn, pointData); + var rad = Math.max(3, di.mrc || 0) + return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad) + }, + dy = function (di) { + var rad = Math.max(3, di.mrc || 0) + return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad) + }, + dxy = function (di) { + var rad = Math.max(3, di.mrc || 0), + dx = xa.c2p(di.x) - xpx, + dy = ya.c2p(di.y) - ypx + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad) + }, + distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy) + + Fx.getClosest(cd, distfn, pointData) // skip the rest (for this trace) if we didn't find a close point - if(pointData.index !== false) { - + if (pointData.index !== false) { // the closest data point - var di = cd[pointData.index], - xc = xa.c2p(di.x, true), - yc = ya.c2p(di.y, true), - rad = di.mrc || 1; + var di = cd[pointData.index], + xc = xa.c2p(di.x, true), + yc = ya.c2p(di.y, true), + rad = di.mrc || 1 - Lib.extendFlat(pointData, { - color: getTraceColor(trace, di), + Lib.extendFlat(pointData, { + color: getTraceColor(trace, di), - x0: xc - rad, - x1: xc + rad, - xLabelVal: di.x, + x0: xc - rad, + x1: xc + rad, + xLabelVal: di.x, - y0: yc - rad, - y1: yc + rad, - yLabelVal: di.y - }); + y0: yc - rad, + y1: yc + rad, + yLabelVal: di.y + }) - if(di.tx) pointData.text = di.tx; - else if(trace.text) pointData.text = trace.text; + if (di.tx) pointData.text = di.tx + else if (trace.text) pointData.text = trace.text - ErrorBars.hoverInfo(di, trace, pointData); + ErrorBars.hoverInfo(di, trace, pointData) - return [pointData]; - } + return [pointData] } + } // even if hoveron is 'fills', only use it if we have polygons too - if(trace.hoveron.indexOf('fills') !== -1 && trace._polygons) { - var polygons = trace._polygons, - polygonsIn = [], - inside = false, - xmin = Infinity, - xmax = -Infinity, - ymin = Infinity, - ymax = -Infinity, - i, j, polygon, pts, xCross, x0, x1, y0, y1; - - for(i = 0; i < polygons.length; i++) { - polygon = polygons[i]; + if (trace.hoveron.indexOf('fills') !== -1 && trace._polygons) { + var polygons = trace._polygons, + polygonsIn = [], + inside = false, + xmin = Infinity, + xmax = -Infinity, + ymin = Infinity, + ymax = -Infinity, + i, j, polygon, pts, xCross, x0, x1, y0, y1 + + for (i = 0; i < polygons.length; i++) { + polygon = polygons[i] // TODO: this is not going to work right for curved edges, it will // act as though they're straight. That's probably going to need // the elements themselves to capture the events. Worth it? - if(polygon.contains(pt)) { - inside = !inside; + if (polygon.contains(pt)) { + inside = !inside // TODO: need better than just the overall bounding box - polygonsIn.push(polygon); - ymin = Math.min(ymin, polygon.ymin); - ymax = Math.max(ymax, polygon.ymax); - } - } + polygonsIn.push(polygon) + ymin = Math.min(ymin, polygon.ymin) + ymax = Math.max(ymax, polygon.ymax) + } + } - if(inside) { + if (inside) { // constrain ymin/max to the visible plot, so the label goes // at the middle of the piece you can see - ymin = Math.max(ymin, 0); - ymax = Math.min(ymax, ya._length); + ymin = Math.max(ymin, 0) + ymax = Math.min(ymax, ya._length) // find the overall left-most and right-most points of the // polygon(s) we're inside at their combined vertical midpoint. // This is where we will draw the hover label. // Note that this might not be the vertical midpoint of the // whole trace, if it's disjoint. - var yAvg = (ymin + ymax) / 2; - for(i = 0; i < polygonsIn.length; i++) { - pts = polygonsIn[i].pts; - for(j = 1; j < pts.length; j++) { - y0 = pts[j - 1][1]; - y1 = pts[j][1]; - if((y0 > yAvg) !== (y1 >= yAvg)) { - x0 = pts[j - 1][0]; - x1 = pts[j][0]; - xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0); - xmin = Math.min(xmin, xCross); - xmax = Math.max(xmax, xCross); - } - } - } + var yAvg = (ymin + ymax) / 2 + for (i = 0; i < polygonsIn.length; i++) { + pts = polygonsIn[i].pts + for (j = 1; j < pts.length; j++) { + y0 = pts[j - 1][1] + y1 = pts[j][1] + if ((y0 > yAvg) !== (y1 >= yAvg)) { + x0 = pts[j - 1][0] + x1 = pts[j][0] + xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0) + xmin = Math.min(xmin, xCross) + xmax = Math.max(xmax, xCross) + } + } + } // constrain xmin/max to the visible plot now too - xmin = Math.max(xmin, 0); - xmax = Math.min(xmax, xa._length); + xmin = Math.max(xmin, 0) + xmax = Math.min(xmax, xa._length) // get only fill or line color for the hover color - var color = Color.defaultLine; - if(Color.opacity(trace.fillcolor)) color = trace.fillcolor; - else if(Color.opacity((trace.line || {}).color)) { - color = trace.line.color; - } + var color = Color.defaultLine + if (Color.opacity(trace.fillcolor)) color = trace.fillcolor + else if (Color.opacity((trace.line || {}).color)) { + color = trace.line.color + } - Lib.extendFlat(pointData, { + Lib.extendFlat(pointData, { // never let a 2D override 1D type as closest point - distance: constants.MAXDIST + 10, - x0: xmin, - x1: xmax, - y0: yAvg, - y1: yAvg, - color: color - }); - - delete pointData.index; - - if(trace.text && !Array.isArray(trace.text)) { - pointData.text = String(trace.text); - } - else pointData.text = trace.name; - - return [pointData]; - } + distance: constants.MAXDIST + 10, + x0: xmin, + x1: xmax, + y0: yAvg, + y1: yAvg, + color: color + }) + + delete pointData.index + + if (trace.text && !Array.isArray(trace.text)) { + pointData.text = String(trace.text) + } else pointData.text = trace.name + + return [pointData] } -}; + } +} diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index 197b540e8d2..b8017d857ee 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -6,43 +6,42 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Scatter = {} -var Scatter = {}; - -var subtypes = require('./subtypes'); -Scatter.hasLines = subtypes.hasLines; -Scatter.hasMarkers = subtypes.hasMarkers; -Scatter.hasText = subtypes.hasText; -Scatter.isBubble = subtypes.isBubble; +var subtypes = require('./subtypes') +Scatter.hasLines = subtypes.hasLines +Scatter.hasMarkers = subtypes.hasMarkers +Scatter.hasText = subtypes.hasText +Scatter.isBubble = subtypes.isBubble // traces with < this many points are by default shown // with points and lines, > just get lines -Scatter.attributes = require('./attributes'); -Scatter.supplyDefaults = require('./defaults'); -Scatter.cleanData = require('./clean_data'); -Scatter.calc = require('./calc'); -Scatter.arraysToCalcdata = require('./arrays_to_calcdata'); -Scatter.plot = require('./plot'); -Scatter.colorbar = require('./colorbar'); -Scatter.style = require('./style'); -Scatter.hoverPoints = require('./hover'); -Scatter.selectPoints = require('./select'); -Scatter.animatable = true; +Scatter.attributes = require('./attributes') +Scatter.supplyDefaults = require('./defaults') +Scatter.cleanData = require('./clean_data') +Scatter.calc = require('./calc') +Scatter.arraysToCalcdata = require('./arrays_to_calcdata') +Scatter.plot = require('./plot') +Scatter.colorbar = require('./colorbar') +Scatter.style = require('./style') +Scatter.hoverPoints = require('./hover') +Scatter.selectPoints = require('./select') +Scatter.animatable = true -Scatter.moduleType = 'trace'; -Scatter.name = 'scatter'; -Scatter.basePlotModule = require('../../plots/cartesian'); -Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend']; +Scatter.moduleType = 'trace' +Scatter.name = 'scatter' +Scatter.basePlotModule = require('../../plots/cartesian') +Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend'] Scatter.meta = { - description: [ - 'The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts.', - 'The data visualized as scatter point or lines is set in `x` and `y`.', - 'Text (appearing either on the chart or on hover only) is via `text`.', - 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', - 'to numerical arrays.' - ].join(' ') -}; + description: [ + 'The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts.', + 'The data visualized as scatter point or lines is set in `x` and `y`.', + 'Text (appearing either on the chart or on hover only) is via `text`.', + 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', + 'to numerical arrays.' + ].join(' ') +} -module.exports = Scatter; +module.exports = Scatter diff --git a/src/traces/scatter/line_defaults.js b/src/traces/scatter/line_defaults.js index f0fc1660492..3798764156a 100644 --- a/src/traces/scatter/line_defaults.js +++ b/src/traces/scatter/line_defaults.js @@ -6,26 +6,23 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var hasColorscale = require('../../components/colorscale/has_colorscale') +var colorscaleDefaults = require('../../components/colorscale/defaults') -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); +module.exports = function lineDefaults (traceIn, traceOut, defaultColor, layout, coerce) { + var markerColor = (traceIn.marker || {}).color + coerce('line.color', defaultColor) -module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - var markerColor = (traceIn.marker || {}).color; + if (hasColorscale(traceIn, 'line')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}) + } else { + var lineColorDflt = (Array.isArray(markerColor) ? false : markerColor) || defaultColor + coerce('line.color', lineColorDflt) + } - coerce('line.color', defaultColor); - - if(hasColorscale(traceIn, 'line')) { - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); - } - else { - var lineColorDflt = (Array.isArray(markerColor) ? false : markerColor) || defaultColor; - coerce('line.color', lineColorDflt); - } - - coerce('line.width'); - coerce('line.dash'); -}; + coerce('line.width') + coerce('line.dash') +} diff --git a/src/traces/scatter/line_points.js b/src/traces/scatter/line_points.js index 03b77be8dfa..3d46d797ea1 100644 --- a/src/traces/scatter/line_points.js +++ b/src/traces/scatter/line_points.js @@ -6,166 +6,164 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var BADNUM = require('../../constants/numerical').BADNUM; - - -module.exports = function linePoints(d, opts) { - var xa = opts.xaxis, - ya = opts.yaxis, - simplify = opts.simplify, - connectGaps = opts.connectGaps, - baseTolerance = opts.baseTolerance, - linear = opts.linear, - segments = [], - minTolerance = 0.2, // fraction of tolerance "so close we don't even consider it a new point" - pts = new Array(d.length), - pti = 0, - i, +'use strict' + +var BADNUM = require('../../constants/numerical').BADNUM + +module.exports = function linePoints (d, opts) { + var xa = opts.xaxis, + ya = opts.yaxis, + simplify = opts.simplify, + connectGaps = opts.connectGaps, + baseTolerance = opts.baseTolerance, + linear = opts.linear, + segments = [], + minTolerance = 0.2, // fraction of tolerance "so close we don't even consider it a new point" + pts = new Array(d.length), + pti = 0, + i, // pt variables are pixel coordinates [x,y] of one point - clusterStartPt, // these four are the outputs of clustering on a line - clusterEndPt, - clusterHighPt, - clusterLowPt, - thisPt, // "this" is the next point we're considering adding to the cluster - - clusterRefDist, - clusterHighFirst, // did we encounter the high point first, then a low point, or vice versa? - clusterUnitVector, // the first two points in the cluster determine its unit vector + clusterStartPt, // these four are the outputs of clustering on a line + clusterEndPt, + clusterHighPt, + clusterLowPt, + thisPt, // "this" is the next point we're considering adding to the cluster + + clusterRefDist, + clusterHighFirst, // did we encounter the high point first, then a low point, or vice versa? + clusterUnitVector, // the first two points in the cluster determine its unit vector // so the second is always in the "High" direction - thisVector, // the pixel delta from clusterStartPt + thisVector, // the pixel delta from clusterStartPt // val variables are (signed) pixel distances along the cluster vector - clusterHighVal, - clusterLowVal, - thisVal, + clusterHighVal, + clusterLowVal, + thisVal, // deviation variables are (signed) pixel distances normal to the cluster vector - clusterMinDeviation, - clusterMaxDeviation, - thisDeviation; + clusterMinDeviation, + clusterMaxDeviation, + thisDeviation - if(!simplify) { - baseTolerance = minTolerance = -1; - } + if (!simplify) { + baseTolerance = minTolerance = -1 + } // turn one calcdata point into pixel coordinates - function getPt(index) { - var x = xa.c2p(d[index].x), - y = ya.c2p(d[index].y); - if(x === BADNUM || y === BADNUM) return false; - return [x, y]; - } + function getPt (index) { + var x = xa.c2p(d[index].x), + y = ya.c2p(d[index].y) + if (x === BADNUM || y === BADNUM) return false + return [x, y] + } // if we're off-screen, increase tolerance over baseTolerance - function getTolerance(pt) { - var xFrac = pt[0] / xa._length, - yFrac = pt[1] / ya._length; - return (1 + 10 * Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1)) * baseTolerance; - } - - function ptDist(pt1, pt2) { - var dx = pt1[0] - pt2[0], - dy = pt1[1] - pt2[1]; - return Math.sqrt(dx * dx + dy * dy); - } + function getTolerance (pt) { + var xFrac = pt[0] / xa._length, + yFrac = pt[1] / ya._length + return (1 + 10 * Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1)) * baseTolerance + } + + function ptDist (pt1, pt2) { + var dx = pt1[0] - pt2[0], + dy = pt1[1] - pt2[1] + return Math.sqrt(dx * dx + dy * dy) + } // loop over ALL points in this trace - for(i = 0; i < d.length; i++) { - clusterStartPt = getPt(i); - if(!clusterStartPt) continue; + for (i = 0; i < d.length; i++) { + clusterStartPt = getPt(i) + if (!clusterStartPt) continue - pti = 0; - pts[pti++] = clusterStartPt; + pti = 0 + pts[pti++] = clusterStartPt // loop over one segment of the trace - for(i++; i < d.length; i++) { - clusterHighPt = getPt(i); - if(!clusterHighPt) { - if(connectGaps) continue; - else break; - } + for (i++; i < d.length; i++) { + clusterHighPt = getPt(i) + if (!clusterHighPt) { + if (connectGaps) continue + else break + } // can't decimate if nonlinear line shape // TODO: we *could* decimate [hv]{2,3} shapes if we restricted clusters to horz or vert again // but spline would be verrry awkward to decimate - if(!linear) { - pts[pti++] = clusterHighPt; - continue; - } + if (!linear) { + pts[pti++] = clusterHighPt + continue + } - clusterRefDist = ptDist(clusterHighPt, clusterStartPt); + clusterRefDist = ptDist(clusterHighPt, clusterStartPt) - if(clusterRefDist < getTolerance(clusterHighPt) * minTolerance) continue; + if (clusterRefDist < getTolerance(clusterHighPt) * minTolerance) continue - clusterUnitVector = [ - (clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist, - (clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist - ]; + clusterUnitVector = [ + (clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist, + (clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist + ] - clusterLowPt = clusterStartPt; - clusterHighVal = clusterRefDist; - clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0; - clusterHighFirst = false; - clusterEndPt = clusterHighPt; + clusterLowPt = clusterStartPt + clusterHighVal = clusterRefDist + clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0 + clusterHighFirst = false + clusterEndPt = clusterHighPt // loop over one cluster of points that collapse onto one line - for(i++; i < d.length; i++) { - thisPt = getPt(i); - if(!thisPt) { - if(connectGaps) continue; - else break; - } - thisVector = [ - thisPt[0] - clusterStartPt[0], - thisPt[1] - clusterStartPt[1] - ]; + for (i++; i < d.length; i++) { + thisPt = getPt(i) + if (!thisPt) { + if (connectGaps) continue + else break + } + thisVector = [ + thisPt[0] - clusterStartPt[0], + thisPt[1] - clusterStartPt[1] + ] // cross product (or dot with normal to the cluster vector) - thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0]; - clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation); - clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation); - - if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt)) break; - - clusterEndPt = thisPt; - thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1]; - - if(thisVal > clusterHighVal) { - clusterHighVal = thisVal; - clusterHighPt = thisPt; - clusterHighFirst = false; - } else if(thisVal < clusterLowVal) { - clusterLowVal = thisVal; - clusterLowPt = thisPt; - clusterHighFirst = true; - } - } + thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0] + clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation) + clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation) + + if (clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt)) break + + clusterEndPt = thisPt + thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1] + + if (thisVal > clusterHighVal) { + clusterHighVal = thisVal + clusterHighPt = thisPt + clusterHighFirst = false + } else if (thisVal < clusterLowVal) { + clusterLowVal = thisVal + clusterLowPt = thisPt + clusterHighFirst = true + } + } // insert this cluster into pts // we've already inserted the start pt, now check if we have high and low pts - if(clusterHighFirst) { - pts[pti++] = clusterHighPt; - if(clusterEndPt !== clusterLowPt) pts[pti++] = clusterLowPt; - } else { - if(clusterLowPt !== clusterStartPt) pts[pti++] = clusterLowPt; - if(clusterEndPt !== clusterHighPt) pts[pti++] = clusterHighPt; - } + if (clusterHighFirst) { + pts[pti++] = clusterHighPt + if (clusterEndPt !== clusterLowPt) pts[pti++] = clusterLowPt + } else { + if (clusterLowPt !== clusterStartPt) pts[pti++] = clusterLowPt + if (clusterEndPt !== clusterHighPt) pts[pti++] = clusterHighPt + } // and finally insert the end pt - pts[pti++] = clusterEndPt; + pts[pti++] = clusterEndPt // have we reached the end of this segment? - if(i >= d.length || !thisPt) break; + if (i >= d.length || !thisPt) break // otherwise we have an out-of-cluster point to insert as next clusterStartPt - pts[pti++] = thisPt; - clusterStartPt = thisPt; - } - - segments.push(pts.slice(0, pti)); + pts[pti++] = thisPt + clusterStartPt = thisPt } - return segments; -}; + segments.push(pts.slice(0, pti)) + } + + return segments +} diff --git a/src/traces/scatter/line_shape_defaults.js b/src/traces/scatter/line_shape_defaults.js index 76758ccce7b..d9de0e82faf 100644 --- a/src/traces/scatter/line_shape_defaults.js +++ b/src/traces/scatter/line_shape_defaults.js @@ -6,12 +6,10 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - +'use strict' // common to 'scatter' and 'scatterternary' -module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) { - var shape = coerce('line.shape'); - if(shape === 'spline') coerce('line.smoothing'); -}; +module.exports = function handleLineShapeDefaults (traceIn, traceOut, coerce) { + var shape = coerce('line.shape') + if (shape === 'spline') coerce('line.smoothing') +} diff --git a/src/traces/scatter/link_traces.js b/src/traces/scatter/link_traces.js index 61400ef4c77..1af8d951acb 100644 --- a/src/traces/scatter/link_traces.js +++ b/src/traces/scatter/link_traces.js @@ -6,34 +6,34 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -module.exports = function linkTraces(gd, plotinfo, cdscatter) { - var cd, trace; - var prevtrace = null; +module.exports = function linkTraces (gd, plotinfo, cdscatter) { + var cd, trace + var prevtrace = null - for(var i = 0; i < cdscatter.length; ++i) { - cd = cdscatter[i]; - trace = cd[0].trace; + for (var i = 0; i < cdscatter.length; ++i) { + cd = cdscatter[i] + trace = cd[0].trace // Note: The check which ensures all cdscatter here are for the same axis and // are either cartesian or scatterternary has been removed. This code assumes // the passed scattertraces have been filtered to the proper plot types and // the proper subplots. - if(trace.visible === true) { - trace._nexttrace = null; + if (trace.visible === true) { + trace._nexttrace = null - if(['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1) { - trace._prevtrace = prevtrace; + if (['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1) { + trace._prevtrace = prevtrace - if(prevtrace) { - prevtrace._nexttrace = trace; - } - } - - prevtrace = trace; - } else { - trace._prevtrace = trace._nexttrace = null; + if (prevtrace) { + prevtrace._nexttrace = trace } + } + + prevtrace = trace + } else { + trace._prevtrace = trace._nexttrace = null } -}; + } +} diff --git a/src/traces/scatter/make_bubble_size_func.js b/src/traces/scatter/make_bubble_size_func.js index 56a4c199b2c..39bc8d8b262 100644 --- a/src/traces/scatter/make_bubble_size_func.js +++ b/src/traces/scatter/make_bubble_size_func.js @@ -6,35 +6,33 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var isNumeric = require('fast-isnumeric'); - +var isNumeric = require('fast-isnumeric') // used in the drawing step for 'scatter' and 'scattegeo' and // in the convert step for 'scatter3d' -module.exports = function makeBubbleSizeFn(trace) { - var marker = trace.marker, - sizeRef = marker.sizeref || 1, - sizeMin = marker.sizemin || 0; +module.exports = function makeBubbleSizeFn (trace) { + var marker = trace.marker, + sizeRef = marker.sizeref || 1, + sizeMin = marker.sizemin || 0 // for bubble charts, allow scaling the provided value linearly // and by area or diameter. // Note this only applies to the array-value sizes - var baseFn = (marker.sizemode === 'area') ? - function(v) { return Math.sqrt(v / sizeRef); } : - function(v) { return v / sizeRef; }; + var baseFn = (marker.sizemode === 'area') ? + function (v) { return Math.sqrt(v / sizeRef) } : + function (v) { return v / sizeRef } // TODO add support for position/negative bubbles? // TODO add 'sizeoffset' attribute? - return function(v) { - var baseSize = baseFn(v / 2); + return function (v) { + var baseSize = baseFn(v / 2) // don't show non-numeric and negative sizes - return (isNumeric(baseSize) && (baseSize > 0)) ? + return (isNumeric(baseSize) && (baseSize > 0)) ? Math.max(baseSize, sizeMin) : - 0; - }; -}; + 0 + } +} diff --git a/src/traces/scatter/marker_defaults.js b/src/traces/scatter/marker_defaults.js index 3211d62426e..41b1a93959d 100644 --- a/src/traces/scatter/marker_defaults.js +++ b/src/traces/scatter/marker_defaults.js @@ -6,53 +6,50 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Color = require('../../components/color') +var hasColorscale = require('../../components/colorscale/has_colorscale') +var colorscaleDefaults = require('../../components/colorscale/defaults') -var Color = require('../../components/color'); -var hasColorscale = require('../../components/colorscale/has_colorscale'); -var colorscaleDefaults = require('../../components/colorscale/defaults'); +var subTypes = require('./subtypes') -var subTypes = require('./subtypes'); - - -module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - var isBubble = subTypes.isBubble(traceIn), - lineColor = (traceIn.line || {}).color, - defaultMLC; +module.exports = function markerDefaults (traceIn, traceOut, defaultColor, layout, coerce) { + var isBubble = subTypes.isBubble(traceIn), + lineColor = (traceIn.line || {}).color, + defaultMLC // marker.color inherit from line.color (even if line.color is an array) - if(lineColor) defaultColor = lineColor; + if (lineColor) defaultColor = lineColor - coerce('marker.symbol'); - coerce('marker.opacity', isBubble ? 0.7 : 1); - coerce('marker.size'); + coerce('marker.symbol') + coerce('marker.opacity', isBubble ? 0.7 : 1) + coerce('marker.size') - coerce('marker.color', defaultColor); - if(hasColorscale(traceIn, 'marker')) { - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}); - } + coerce('marker.color', defaultColor) + if (hasColorscale(traceIn, 'marker')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}) + } // if there's a line with a different color than the marker, use // that line color as the default marker line color // (except when it's an array) // mostly this is for transparent markers to behave nicely - if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) { - defaultMLC = lineColor; - } - else if(isBubble) defaultMLC = Color.background; - else defaultMLC = Color.defaultLine; - - coerce('marker.line.color', defaultMLC); - if(hasColorscale(traceIn, 'marker.line')) { - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}); - } - - coerce('marker.line.width', isBubble ? 1 : 0); - - if(isBubble) { - coerce('marker.sizeref'); - coerce('marker.sizemin'); - coerce('marker.sizemode'); - } -}; + if (lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) { + defaultMLC = lineColor + } else if (isBubble) defaultMLC = Color.background + else defaultMLC = Color.defaultLine + + coerce('marker.line.color', defaultMLC) + if (hasColorscale(traceIn, 'marker.line')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}) + } + + coerce('marker.line.width', isBubble ? 1 : 0) + + if (isBubble) { + coerce('marker.sizeref') + coerce('marker.sizemin') + coerce('marker.sizemode') + } +} diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js index b9dde96cd7a..2cac0491dd5 100644 --- a/src/traces/scatter/plot.js +++ b/src/traces/scatter/plot.js @@ -6,528 +6,515 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Lib = require('../../lib') +var Drawing = require('../../components/drawing') +var ErrorBars = require('../../components/errorbars') -var Lib = require('../../lib'); -var Drawing = require('../../components/drawing'); -var ErrorBars = require('../../components/errorbars'); +var subTypes = require('./subtypes') +var linePoints = require('./line_points') +var linkTraces = require('./link_traces') +var polygonTester = require('../../lib/polygon').tester -var subTypes = require('./subtypes'); -var linePoints = require('./line_points'); -var linkTraces = require('./link_traces'); -var polygonTester = require('../../lib/polygon').tester; +module.exports = function plot (gd, plotinfo, cdscatter, transitionOpts, makeOnCompleteCallback) { + var i, uids, selection, join, onComplete -module.exports = function plot(gd, plotinfo, cdscatter, transitionOpts, makeOnCompleteCallback) { - var i, uids, selection, join, onComplete; - - var scatterlayer = plotinfo.plot.select('g.scatterlayer'); + var scatterlayer = plotinfo.plot.select('g.scatterlayer') // If transition config is provided, then it is only a partial replot and traces not // updated are removed. - var isFullReplot = !transitionOpts; - var hasTransition = !!transitionOpts && transitionOpts.duration > 0; + var isFullReplot = !transitionOpts + var hasTransition = !!transitionOpts && transitionOpts.duration > 0 - selection = scatterlayer.selectAll('g.trace'); + selection = scatterlayer.selectAll('g.trace') - join = selection.data(cdscatter, function(d) { return d[0].trace.uid; }); + join = selection.data(cdscatter, function (d) { return d[0].trace.uid }) // Append new traces: - join.enter().append('g') - .attr('class', function(d) { - return 'trace scatter trace' + d[0].trace.uid; + join.enter().append('g') + .attr('class', function (d) { + return 'trace scatter trace' + d[0].trace.uid }) - .style('stroke-miterlimit', 2); + .style('stroke-miterlimit', 2) // After the elements are created but before they've been draw, we have to perform // this extra step of linking the traces. This allows appending of fill layers so that // the z-order of fill layers is correct. - linkTraces(gd, plotinfo, cdscatter); + linkTraces(gd, plotinfo, cdscatter) - createFills(gd, scatterlayer); + createFills(gd, scatterlayer) // Sort the traces, once created, so that the ordering is preserved even when traces // are shown and hidden. This is needed since we're not just wiping everything out // and recreating on every update. - for(i = 0, uids = []; i < cdscatter.length; i++) { - uids[i] = cdscatter[i][0].trace.uid; - } - - scatterlayer.selectAll('g.trace').sort(function(a, b) { - var idx1 = uids.indexOf(a[0].trace.uid); - var idx2 = uids.indexOf(b[0].trace.uid); - return idx1 > idx2 ? 1 : -1; - }); - - if(hasTransition) { - if(makeOnCompleteCallback) { + for (i = 0, uids = []; i < cdscatter.length; i++) { + uids[i] = cdscatter[i][0].trace.uid + } + + scatterlayer.selectAll('g.trace').sort(function (a, b) { + var idx1 = uids.indexOf(a[0].trace.uid) + var idx2 = uids.indexOf(b[0].trace.uid) + return idx1 > idx2 ? 1 : -1 + }) + + if (hasTransition) { + if (makeOnCompleteCallback) { // If it was passed a callback to register completion, make a callback. If // this is created, then it must be executed on completion, otherwise the // pos-transition redraw will not execute: - onComplete = makeOnCompleteCallback(); - } + onComplete = makeOnCompleteCallback() + } - var transition = d3.transition() + var transition = d3.transition() .duration(transitionOpts.duration) .ease(transitionOpts.easing) - .each('end', function() { - onComplete && onComplete(); + .each('end', function () { + onComplete && onComplete() + }) + .each('interrupt', function () { + onComplete && onComplete() }) - .each('interrupt', function() { - onComplete && onComplete(); - }); - transition.each(function() { + transition.each(function () { // Must run the selection again since otherwise enters/updates get grouped together // and these get executed out of order. Except we need them in order! - scatterlayer.selectAll('g.trace').each(function(d, i) { - plotOne(gd, i, plotinfo, d, cdscatter, this, transitionOpts); - }); - }); - } else { - scatterlayer.selectAll('g.trace').each(function(d, i) { - plotOne(gd, i, plotinfo, d, cdscatter, this, transitionOpts); - }); - } - - if(isFullReplot) { - join.exit().remove(); - } + scatterlayer.selectAll('g.trace').each(function (d, i) { + plotOne(gd, i, plotinfo, d, cdscatter, this, transitionOpts) + }) + }) + } else { + scatterlayer.selectAll('g.trace').each(function (d, i) { + plotOne(gd, i, plotinfo, d, cdscatter, this, transitionOpts) + }) + } + + if (isFullReplot) { + join.exit().remove() + } // remove paths that didn't get used - scatterlayer.selectAll('path:not([d])').remove(); -}; + scatterlayer.selectAll('path:not([d])').remove() +} -function createFills(gd, scatterlayer) { - var trace; +function createFills (gd, scatterlayer) { + var trace - scatterlayer.selectAll('g.trace').each(function(d) { - var tr = d3.select(this); + scatterlayer.selectAll('g.trace').each(function (d) { + var tr = d3.select(this) // Loop only over the traces being redrawn: - trace = d[0].trace; + trace = d[0].trace // make the fill-to-next path now for the NEXT trace, so it shows // behind both lines. - if(trace._nexttrace) { - trace._nextFill = tr.select('.js-fill.js-tonext'); - if(!trace._nextFill.size()) { - + if (trace._nexttrace) { + trace._nextFill = tr.select('.js-fill.js-tonext') + if (!trace._nextFill.size()) { // If there is an existing tozero fill, we must insert this *after* that fill: - var loc = ':first-child'; - if(tr.select('.js-fill.js-tozero').size()) { - loc += ' + *'; - } - - trace._nextFill = tr.insert('path', loc).attr('class', 'js-fill js-tonext'); - } - } else { - tr.selectAll('.js-fill.js-tonext').remove(); - trace._nextFill = null; + var loc = ':first-child' + if (tr.select('.js-fill.js-tozero').size()) { + loc += ' + *' } - if(trace.fill && (trace.fill.substr(0, 6) === 'tozero' || trace.fill === 'toself' || + trace._nextFill = tr.insert('path', loc).attr('class', 'js-fill js-tonext') + } + } else { + tr.selectAll('.js-fill.js-tonext').remove() + trace._nextFill = null + } + + if (trace.fill && (trace.fill.substr(0, 6) === 'tozero' || trace.fill === 'toself' || (trace.fill.substr(0, 2) === 'to' && !trace._prevtrace))) { - trace._ownFill = tr.select('.js-fill.js-tozero'); - if(!trace._ownFill.size()) { - trace._ownFill = tr.insert('path', ':first-child').attr('class', 'js-fill js-tozero'); - } - } else { - tr.selectAll('.js-fill.js-tozero').remove(); - trace._ownFill = null; - } - }); + trace._ownFill = tr.select('.js-fill.js-tozero') + if (!trace._ownFill.size()) { + trace._ownFill = tr.insert('path', ':first-child').attr('class', 'js-fill js-tozero') + } + } else { + tr.selectAll('.js-fill.js-tozero').remove() + trace._ownFill = null + } + }) } -function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) { - var join, i; +function plotOne (gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) { + var join, i // Since this has been reorganized and we're executing this on individual traces, // we need to pass it the full list of cdscatter as well as this trace's index (idx) // since it does an internal n^2 loop over comparisons with other traces: - selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll); + selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) - var hasTransition = !!transitionOpts && transitionOpts.duration > 0; + var hasTransition = !!transitionOpts && transitionOpts.duration > 0 - function transition(selection) { - return hasTransition ? selection.transition() : selection; - } + function transition (selection) { + return hasTransition ? selection.transition() : selection + } - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis; + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis - var trace = cdscatter[0].trace, - line = trace.line, - tr = d3.select(element); + var trace = cdscatter[0].trace, + line = trace.line, + tr = d3.select(element) // (so error bars can find them along with bars) // error bars are at the bottom - tr.call(ErrorBars.plot, plotinfo, transitionOpts); + tr.call(ErrorBars.plot, plotinfo, transitionOpts) - if(trace.visible !== true) return; + if (trace.visible !== true) return - transition(tr).style('opacity', trace.opacity); + transition(tr).style('opacity', trace.opacity) // BUILD LINES AND FILLS - var ownFillEl3, tonext; - var ownFillDir = trace.fill.charAt(trace.fill.length - 1); - if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = ''; + var ownFillEl3, tonext + var ownFillDir = trace.fill.charAt(trace.fill.length - 1) + if (ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '' // store node for tweaking by selectPoints - cdscatter[0].node3 = tr; + cdscatter[0].node3 = tr - var prevRevpath = ''; - var prevPolygons = []; - var prevtrace = trace._prevtrace; + var prevRevpath = '' + var prevPolygons = [] + var prevtrace = trace._prevtrace - if(prevtrace) { - prevRevpath = prevtrace._prevRevpath || ''; - tonext = prevtrace._nextFill; - prevPolygons = prevtrace._polygons; - } + if (prevtrace) { + prevRevpath = prevtrace._prevRevpath || '' + tonext = prevtrace._nextFill + prevPolygons = prevtrace._polygons + } - var thispath, - thisrevpath, + var thispath, + thisrevpath, // fullpath is all paths for this curve, joined together straight // across gaps, for filling - fullpath = '', + fullpath = '', // revpath is fullpath reversed, for fill-to-next - revpath = '', + revpath = '', // functions for converting a point array to a path - pathfn, revpathbase, revpathfn, + pathfn, revpathbase, revpathfn, // variables used before and after the data join - pt0, lastSegment, pt1, thisPolygons; + pt0, lastSegment, pt1, thisPolygons // initialize line join data / method - var segments = [], - lineSegments = [], - makeUpdate = Lib.noop; + var segments = [], + lineSegments = [], + makeUpdate = Lib.noop - ownFillEl3 = trace._ownFill; + ownFillEl3 = trace._ownFill - if(subTypes.hasLines(trace) || trace.fill !== 'none') { - - if(tonext) { + if (subTypes.hasLines(trace) || trace.fill !== 'none') { + if (tonext) { // This tells .style which trace to use for fill information: - tonext.datum(cdscatter); - } + tonext.datum(cdscatter) + } - if(['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) { - pathfn = Drawing.steps(line.shape); - revpathbase = Drawing.steps( + if (['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) { + pathfn = Drawing.steps(line.shape) + revpathbase = Drawing.steps( line.shape.split('').reverse().join('') - ); - } - else if(line.shape === 'spline') { - pathfn = revpathbase = function(pts) { - var pLast = pts[pts.length - 1]; - if(pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) { + ) + } else if (line.shape === 'spline') { + pathfn = revpathbase = function (pts) { + var pLast = pts[pts.length - 1] + if (pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) { // identical start and end points: treat it as a // closed curve so we don't get a kink - return Drawing.smoothclosed(pts.slice(1), line.smoothing); - } - else { - return Drawing.smoothopen(pts, line.smoothing); - } - }; - } - else { - pathfn = revpathbase = function(pts) { - return 'M' + pts.join('L'); - }; + return Drawing.smoothclosed(pts.slice(1), line.smoothing) + } else { + return Drawing.smoothopen(pts, line.smoothing) } + } + } else { + pathfn = revpathbase = function (pts) { + return 'M' + pts.join('L') + } + } - revpathfn = function(pts) { + revpathfn = function (pts) { // note: this is destructive (reverses pts in place) so can't use pts after this - return revpathbase(pts.reverse()); - }; - - segments = linePoints(cdscatter, { - xaxis: xa, - yaxis: ya, - connectGaps: trace.connectgaps, - baseTolerance: Math.max(line.width || 1, 3) / 4, - linear: line.shape === 'linear', - simplify: line.simplify - }); + return revpathbase(pts.reverse()) + } + + segments = linePoints(cdscatter, { + xaxis: xa, + yaxis: ya, + connectGaps: trace.connectgaps, + baseTolerance: Math.max(line.width || 1, 3) / 4, + linear: line.shape === 'linear', + simplify: line.simplify + }) // since we already have the pixel segments here, use them to make // polygons for hover on fill // TODO: can we skip this if hoveron!=fills? That would mean we // need to redraw when you change hoveron... - thisPolygons = trace._polygons = new Array(segments.length); - for(i = 0; i < segments.length; i++) { - trace._polygons[i] = polygonTester(segments[i]); - } + thisPolygons = trace._polygons = new Array(segments.length) + for (i = 0; i < segments.length; i++) { + trace._polygons[i] = polygonTester(segments[i]) + } + + if (segments.length) { + pt0 = segments[0][0] + lastSegment = segments[segments.length - 1] + pt1 = lastSegment[lastSegment.length - 1] + } - if(segments.length) { - pt0 = segments[0][0]; - lastSegment = segments[segments.length - 1]; - pt1 = lastSegment[lastSegment.length - 1]; + lineSegments = segments.filter(function (s) { + return s.length > 1 + }) + + makeUpdate = function (isEnter) { + return function (pts) { + thispath = pathfn(pts) + thisrevpath = revpathfn(pts) + if (!fullpath) { + fullpath = thispath + revpath = thisrevpath + } else if (ownFillDir) { + fullpath += 'L' + thispath.substr(1) + revpath = thisrevpath + ('L' + revpath.substr(1)) + } else { + fullpath += 'Z' + thispath + revpath = thisrevpath + 'Z' + revpath } - lineSegments = segments.filter(function(s) { - return s.length > 1; - }); - - makeUpdate = function(isEnter) { - return function(pts) { - thispath = pathfn(pts); - thisrevpath = revpathfn(pts); - if(!fullpath) { - fullpath = thispath; - revpath = thisrevpath; - } - else if(ownFillDir) { - fullpath += 'L' + thispath.substr(1); - revpath = thisrevpath + ('L' + revpath.substr(1)); - } - else { - fullpath += 'Z' + thispath; - revpath = thisrevpath + 'Z' + revpath; - } - - if(subTypes.hasLines(trace) && pts.length > 1) { - var el = d3.select(this); + if (subTypes.hasLines(trace) && pts.length > 1) { + var el = d3.select(this) // This makes the coloring work correctly: - el.datum(cdscatter); + el.datum(cdscatter) - if(isEnter) { - transition(el.style('opacity', 0) + if (isEnter) { + transition(el.style('opacity', 0) .attr('d', thispath) .call(Drawing.lineGroupStyle)) - .style('opacity', 1); - } else { - var sel = transition(el); - sel.attr('d', thispath); - Drawing.singleLineStyle(cdscatter, sel); - } - } - }; - }; + .style('opacity', 1) + } else { + var sel = transition(el) + sel.attr('d', thispath) + Drawing.singleLineStyle(cdscatter, sel) + } + } + } } + } - var lineJoin = tr.selectAll('.js-line').data(lineSegments); + var lineJoin = tr.selectAll('.js-line').data(lineSegments) - transition(lineJoin.exit()) + transition(lineJoin.exit()) .style('opacity', 0) - .remove(); + .remove() - lineJoin.each(makeUpdate(false)); + lineJoin.each(makeUpdate(false)) - lineJoin.enter().append('path') + lineJoin.enter().append('path') .classed('js-line', true) .style('vector-effect', 'non-scaling-stroke') .call(Drawing.lineGroupStyle) - .each(makeUpdate(true)); - - if(segments.length) { - if(ownFillEl3) { - if(pt0 && pt1) { - if(ownFillDir) { - if(ownFillDir === 'y') { - pt0[1] = pt1[1] = ya.c2p(0, true); - } - else if(ownFillDir === 'x') { - pt0[0] = pt1[0] = xa.c2p(0, true); - } + .each(makeUpdate(true)) + + if (segments.length) { + if (ownFillEl3) { + if (pt0 && pt1) { + if (ownFillDir) { + if (ownFillDir === 'y') { + pt0[1] = pt1[1] = ya.c2p(0, true) + } else if (ownFillDir === 'x') { + pt0[0] = pt1[0] = xa.c2p(0, true) + } // fill to zero: full trace path, plus extension of // the endpoints to the appropriate axis // For the sake of animations, wrap the points around so that // the points on the axes are the first two points. Otherwise // animations get a little crazy if the number of points changes. - transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1)); - } else { + transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1)) + } else { // fill to self: just join the path to itself - transition(ownFillEl3).attr('d', fullpath + 'Z'); - } - } + transition(ownFillEl3).attr('d', fullpath + 'Z') } - else if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) { + } + } else if (trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) { // fill to next: full trace path, plus the previous path reversed - if(trace.fill === 'tonext') { + if (trace.fill === 'tonext') { // tonext: for use by concentric shapes, like manually constructed // contours, we just add the two paths closed on themselves. // This makes strange results if one path is *not* entirely // inside the other, but then that is a strange usage. - transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z'); - } - else { + transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z') + } else { // tonextx/y: for now just connect endpoints with lines. This is // the correct behavior if the endpoints are at the same value of // y/x, but if they *aren't*, we should ideally do more complicated // things depending on whether the new endpoint projects onto the // existing curve or off the end of it - transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z'); - } - trace._polygons = trace._polygons.concat(prevPolygons); - } - trace._prevRevpath = revpath; - trace._prevPolygons = thisPolygons; + transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z') + } + trace._polygons = trace._polygons.concat(prevPolygons) } + trace._prevRevpath = revpath + trace._prevPolygons = thisPolygons + } + function visFilter (d) { + return d.filter(function (v) { return v.vis }) + } - function visFilter(d) { - return d.filter(function(v) { return v.vis; }); - } - - function keyFunc(d) { - return d.id; - } + function keyFunc (d) { + return d.id + } // Returns a function if the trace is keyed, otherwise returns undefined - function getKeyFunc(trace) { - if(trace.ids) { - return keyFunc; - } + function getKeyFunc (trace) { + if (trace.ids) { + return keyFunc } + } - function hideFilter() { - return false; - } + function hideFilter () { + return false + } - function makePoints(d) { - var join, selection; + function makePoints (d) { + var join, selection - var trace = d[0].trace, - s = d3.select(this), - showMarkers = subTypes.hasMarkers(trace), - showText = subTypes.hasText(trace); + var trace = d[0].trace, + s = d3.select(this), + showMarkers = subTypes.hasMarkers(trace), + showText = subTypes.hasText(trace) - var keyFunc = getKeyFunc(trace), - markerFilter = hideFilter, - textFilter = hideFilter; + var keyFunc = getKeyFunc(trace), + markerFilter = hideFilter, + textFilter = hideFilter - if(showMarkers) { - markerFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity; - } + if (showMarkers) { + markerFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity + } - if(showText) { - textFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity; - } + if (showText) { + textFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity + } // marker points - selection = s.selectAll('path.point'); + selection = s.selectAll('path.point') - join = selection.data(markerFilter, keyFunc); + join = selection.data(markerFilter, keyFunc) - var enter = join.enter().append('path') - .classed('point', true); + var enter = join.enter().append('path') + .classed('point', true) - enter.call(Drawing.pointStyle, trace) - .call(Drawing.translatePoints, xa, ya, trace); + enter.call(Drawing.pointStyle, trace) + .call(Drawing.translatePoints, xa, ya, trace) - if(hasTransition) { - enter.style('opacity', 0).transition() - .style('opacity', 1); - } + if (hasTransition) { + enter.style('opacity', 0).transition() + .style('opacity', 1) + } - join.each(function(d) { - var sel = transition(d3.select(this)); - Drawing.translatePoint(d, sel, xa, ya); - Drawing.singlePointStyle(d, sel, trace); - }); + join.each(function (d) { + var sel = transition(d3.select(this)) + Drawing.translatePoint(d, sel, xa, ya) + Drawing.singlePointStyle(d, sel, trace) + }) - if(hasTransition) { - join.exit().transition() + if (hasTransition) { + join.exit().transition() .style('opacity', 0) - .remove(); - } else { - join.exit().remove(); - } + .remove() + } else { + join.exit().remove() + } // text points - selection = s.selectAll('g'); - join = selection.data(textFilter, keyFunc); + selection = s.selectAll('g') + join = selection.data(textFilter, keyFunc) // each text needs to go in its own 'g' in case // it gets converted to mathjax - join.enter().append('g').append('text'); + join.enter().append('g').append('text') - join.each(function(d) { - var sel = transition(d3.select(this).select('text')); - Drawing.translatePoint(d, sel, xa, ya); - }); + join.each(function (d) { + var sel = transition(d3.select(this).select('text')) + Drawing.translatePoint(d, sel, xa, ya) + }) - join.selectAll('text') + join.selectAll('text') .call(Drawing.textPointStyle, trace) - .each(function(d) { - + .each(function (d) { // This just *has* to be totally custom becuase of SVG text positioning :( // It's obviously copied from translatePoint; we just can't use that // // put xp and yp into d if pixel scaling is already done - var x = d.xp || xa.c2p(d.x), - y = d.yp || ya.c2p(d.y); + var x = d.xp || xa.c2p(d.x), + y = d.yp || ya.c2p(d.y) - d3.select(this).selectAll('tspan').each(function() { - transition(d3.select(this)).attr({x: x, y: y}); - }); - }); + d3.select(this).selectAll('tspan').each(function () { + transition(d3.select(this)).attr({x: x, y: y}) + }) + }) - join.exit().remove(); - } + join.exit().remove() + } // NB: selectAll is evaluated on instantiation: - var pointSelection = tr.selectAll('.points'); + var pointSelection = tr.selectAll('.points') // Join with new data - join = pointSelection.data([cdscatter]); + join = pointSelection.data([cdscatter]) // Transition existing, but don't defer this to an async .transition since // there's no timing involved: - pointSelection.each(makePoints); + pointSelection.each(makePoints) - join.enter().append('g') + join.enter().append('g') .classed('points', true) - .each(makePoints); + .each(makePoints) - join.exit().remove(); + join.exit().remove() } -function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) { - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c)), - yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c)); +function selectMarkers (gd, idx, plotinfo, cdscatter, cdscatterAll) { + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis, + xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c)), + yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c)) - var trace = cdscatter[0].trace; - if(!subTypes.hasMarkers(trace)) return; + var trace = cdscatter[0].trace + if (!subTypes.hasMarkers(trace)) return // if marker.maxdisplayed is used, select a maximum of // mnum markers to show, from the set that are in the viewport - var mnum = trace.marker.maxdisplayed; + var mnum = trace.marker.maxdisplayed // TODO: remove some as we get away from the viewport? - if(mnum === 0) return; - - var cd = cdscatter.filter(function(v) { - return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1]; - }), - inc = Math.ceil(cd.length / mnum), - tnum = 0; - cdscatterAll.forEach(function(cdj, j) { - var tracei = cdj[0].trace; - if(subTypes.hasMarkers(tracei) && + if (mnum === 0) return + + var cd = cdscatter.filter(function (v) { + return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1] + }), + inc = Math.ceil(cd.length / mnum), + tnum = 0 + cdscatterAll.forEach(function (cdj, j) { + var tracei = cdj[0].trace + if (subTypes.hasMarkers(tracei) && tracei.marker.maxdisplayed > 0 && j < idx) { - tnum++; - } - }); + tnum++ + } + }) // if multiple traces use maxdisplayed, stagger which markers we // display this formula offsets successive traces by 1/3 of the // increment, adding an extra small amount after each triplet so // it's not quite periodic - var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1); + var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1) // for error bars: save in cd which markers to show // so we don't have to repeat this - cdscatter.forEach(function(v) { delete v.vis; }); - cd.forEach(function(v, i) { - if(Math.round((i + i0) % inc) === 0) v.vis = true; - }); + cdscatter.forEach(function (v) { delete v.vis }) + cd.forEach(function (v, i) { + if (Math.round((i + i0) % inc) === 0) v.vis = true + }) } diff --git a/src/traces/scatter/select.js b/src/traces/scatter/select.js index 8ad3fc12030..c87a0e81ed8 100644 --- a/src/traces/scatter/select.js +++ b/src/traces/scatter/select.js @@ -6,65 +6,62 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var subtypes = require('./subtypes') -var subtypes = require('./subtypes'); +var DESELECTDIM = 0.2 -var DESELECTDIM = 0.2; - -module.exports = function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd, - xa = searchInfo.xaxis, - ya = searchInfo.yaxis, - selection = [], - trace = cd[0].trace, - curveNumber = trace.index, - marker = trace.marker, - i, - di, - x, - y; +module.exports = function selectPoints (searchInfo, polygon) { + var cd = searchInfo.cd, + xa = searchInfo.xaxis, + ya = searchInfo.yaxis, + selection = [], + trace = cd[0].trace, + curveNumber = trace.index, + marker = trace.marker, + i, + di, + x, + y // TODO: include lines? that would require per-segment line properties - var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); - if(trace.visible !== true || hasOnlyLines) return; + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)) + if (trace.visible !== true || hasOnlyLines) return - var opacity = Array.isArray(marker.opacity) ? 1 : marker.opacity; + var opacity = Array.isArray(marker.opacity) ? 1 : marker.opacity - if(polygon === false) { // clear selection - for(i = 0; i < cd.length; i++) cd[i].dim = 0; - } - else { - for(i = 0; i < cd.length; i++) { - di = cd[i]; - x = xa.c2p(di.x); - y = ya.c2p(di.y); - if(polygon.contains([x, y])) { - selection.push({ - curveNumber: curveNumber, - pointNumber: i, - x: di.x, - y: di.y, - id: di.id - }); - di.dim = 0; - } - else di.dim = 1; - } + if (polygon === false) { // clear selection + for (i = 0; i < cd.length; i++) cd[i].dim = 0 + } else { + for (i = 0; i < cd.length; i++) { + di = cd[i] + x = xa.c2p(di.x) + y = ya.c2p(di.y) + if (polygon.contains([x, y])) { + selection.push({ + curveNumber: curveNumber, + pointNumber: i, + x: di.x, + y: di.y, + id: di.id + }) + di.dim = 0 + } else di.dim = 1 } + } // do the dimming here, as well as returning the selection // The logic here duplicates Drawing.pointStyle, but I don't want // d.dim in pointStyle in case something goes wrong with selection. - cd[0].node3.selectAll('path.point') - .style('opacity', function(d) { - return ((d.mo + 1 || opacity + 1) - 1) * (d.dim ? DESELECTDIM : 1); - }); - cd[0].node3.selectAll('text') - .style('opacity', function(d) { - return d.dim ? DESELECTDIM : 1; - }); + cd[0].node3.selectAll('path.point') + .style('opacity', function (d) { + return ((d.mo + 1 || opacity + 1) - 1) * (d.dim ? DESELECTDIM : 1) + }) + cd[0].node3.selectAll('text') + .style('opacity', function (d) { + return d.dim ? DESELECTDIM : 1 + }) - return selection; -}; + return selection +} diff --git a/src/traces/scatter/style.js b/src/traces/scatter/style.js index 78339d3acf7..271232079cd 100644 --- a/src/traces/scatter/style.js +++ b/src/traces/scatter/style.js @@ -6,35 +6,33 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Drawing = require('../../components/drawing') +var ErrorBars = require('../../components/errorbars') -var Drawing = require('../../components/drawing'); -var ErrorBars = require('../../components/errorbars'); +module.exports = function style (gd) { + var s = d3.select(gd).selectAll('g.trace.scatter') + s.style('opacity', function (d) { + return d[0].trace.opacity + }) -module.exports = function style(gd) { - var s = d3.select(gd).selectAll('g.trace.scatter'); + s.selectAll('g.points') + .each(function (d) { + d3.select(this).selectAll('path.point') + .call(Drawing.pointStyle, d.trace || d[0].trace) + d3.select(this).selectAll('text') + .call(Drawing.textPointStyle, d.trace || d[0].trace) + }) - s.style('opacity', function(d) { - return d[0].trace.opacity; - }); + s.selectAll('g.trace path.js-line') + .call(Drawing.lineGroupStyle) - s.selectAll('g.points') - .each(function(d) { - d3.select(this).selectAll('path.point') - .call(Drawing.pointStyle, d.trace || d[0].trace); - d3.select(this).selectAll('text') - .call(Drawing.textPointStyle, d.trace || d[0].trace); - }); + s.selectAll('g.trace path.js-fill') + .call(Drawing.fillGroupStyle) - s.selectAll('g.trace path.js-line') - .call(Drawing.lineGroupStyle); - - s.selectAll('g.trace path.js-fill') - .call(Drawing.fillGroupStyle); - - s.call(ErrorBars.style); -}; + s.call(ErrorBars.style) +} diff --git a/src/traces/scatter/subtypes.js b/src/traces/scatter/subtypes.js index 5d117eced40..e8b212c823e 100644 --- a/src/traces/scatter/subtypes.js +++ b/src/traces/scatter/subtypes.js @@ -6,29 +6,28 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); +var Lib = require('../../lib') module.exports = { - hasLines: function(trace) { - return trace.visible && trace.mode && - trace.mode.indexOf('lines') !== -1; - }, + hasLines: function (trace) { + return trace.visible && trace.mode && + trace.mode.indexOf('lines') !== -1 + }, - hasMarkers: function(trace) { - return trace.visible && trace.mode && - trace.mode.indexOf('markers') !== -1; - }, + hasMarkers: function (trace) { + return trace.visible && trace.mode && + trace.mode.indexOf('markers') !== -1 + }, - hasText: function(trace) { - return trace.visible && trace.mode && - trace.mode.indexOf('text') !== -1; - }, + hasText: function (trace) { + return trace.visible && trace.mode && + trace.mode.indexOf('text') !== -1 + }, - isBubble: function(trace) { - return Lib.isPlainObject(trace.marker) && - Array.isArray(trace.marker.size); - } -}; + isBubble: function (trace) { + return Lib.isPlainObject(trace.marker) && + Array.isArray(trace.marker.size) + } +} diff --git a/src/traces/scatter/text_defaults.js b/src/traces/scatter/text_defaults.js index 2860d127825..654c5ccc3e4 100644 --- a/src/traces/scatter/text_defaults.js +++ b/src/traces/scatter/text_defaults.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var Lib = require('../../lib'); - +var Lib = require('../../lib') // common to 'scatter', 'scatter3d' and 'scattergeo' -module.exports = function(traceIn, traceOut, layout, coerce) { - coerce('textposition'); - Lib.coerceFont(coerce, 'textfont', layout.font); -}; +module.exports = function (traceIn, traceOut, layout, coerce) { + coerce('textposition') + Lib.coerceFont(coerce, 'textfont', layout.font) +} diff --git a/src/traces/scatter/xy_defaults.js b/src/traces/scatter/xy_defaults.js index a43f04bc337..51162cb2e3e 100644 --- a/src/traces/scatter/xy_defaults.js +++ b/src/traces/scatter/xy_defaults.js @@ -6,43 +6,39 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') -var Registry = require('../../registry'); +module.exports = function handleXYDefaults (traceIn, traceOut, layout, coerce) { + var len, + x = coerce('x'), + y = coerce('y') + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout) -module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) { - var len, - x = coerce('x'), - y = coerce('y'); - - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); - - if(x) { - if(y) { - len = Math.min(x.length, y.length); + if (x) { + if (y) { + len = Math.min(x.length, y.length) // TODO: not sure we should do this here... but I think // the way it works in calc is wrong, because it'll delete data // which could be a problem eg in streaming / editing if x and y // come in at different times // so we need to revisit calc before taking this out - if(len < x.length) traceOut.x = x.slice(0, len); - if(len < y.length) traceOut.y = y.slice(0, len); - } - else { - len = x.length; - coerce('y0'); - coerce('dy'); - } - } - else { - if(!y) return 0; - - len = traceOut.y.length; - coerce('x0'); - coerce('dx'); + if (len < x.length) traceOut.x = x.slice(0, len) + if (len < y.length) traceOut.y = y.slice(0, len) + } else { + len = x.length + coerce('y0') + coerce('dy') } - return len; -}; + } else { + if (!y) return 0 + + len = traceOut.y.length + coerce('x0') + coerce('dx') + } + return len +} diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 7c5278c25c9..f1c81602c24 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -6,153 +6,153 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); -var errorBarAttrs = require('../../components/errorbars/attributes'); +var scatterAttrs = require('../scatter/attributes') +var colorAttributes = require('../../components/colorscale/color_attributes') +var errorBarAttrs = require('../../components/errorbars/attributes') -var MARKER_SYMBOLS = require('../../constants/gl_markers'); -var extendFlat = require('../../lib/extend').extendFlat; +var MARKER_SYMBOLS = require('../../constants/gl_markers') +var extendFlat = require('../../lib/extend').extendFlat var scatterLineAttrs = scatterAttrs.line, - scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; + scatterMarkerAttrs = scatterAttrs.marker, + scatterMarkerLineAttrs = scatterMarkerAttrs.line -function makeProjectionAttr(axLetter) { - return { - show: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Sets whether or not projections are shown along the', - axLetter, 'axis.' - ].join(' ') - }, - opacity: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the projection color.' - }, - scale: { - valType: 'number', - role: 'style', - min: 0, - max: 10, - dflt: 2 / 3, - description: [ - 'Sets the scale factor determining the size of the', - 'projection marker points.' - ].join(' ') - } - }; +function makeProjectionAttr (axLetter) { + return { + show: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Sets whether or not projections are shown along the', + axLetter, 'axis.' + ].join(' ') + }, + opacity: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the projection color.' + }, + scale: { + valType: 'number', + role: 'style', + min: 0, + max: 10, + dflt: 2 / 3, + description: [ + 'Sets the scale factor determining the size of the', + 'projection marker points.' + ].join(' ') + } + } } module.exports = { - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' - }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' - }, - z: { - valType: 'data_array', - description: 'Sets the z coordinates.' - }, + x: { + valType: 'data_array', + description: 'Sets the x coordinates.' + }, + y: { + valType: 'data_array', + description: 'Sets the y coordinates.' + }, + z: { + valType: 'data_array', + description: 'Sets the z coordinates.' + }, - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (x,y,z) triplet.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y,z) coordinates.' - ].join(' ') - }), - mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y,z) triplet.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y,z) coordinates.' + ].join(' ') + }), + mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? {dflt: 'lines+markers'}), - surfaceaxis: { - valType: 'enumerated', - role: 'info', - values: [-1, 0, 1, 2], - dflt: -1, - description: [ - 'If *-1*, the scatter points are not fill with a surface', - 'If *0*, *1*, *2*, the scatter points are filled with', - 'a Delaunay surface about the x, y, z respectively.' - ].join(' ') - }, - surfacecolor: { - valType: 'color', - role: 'style', - description: 'Sets the surface fill color.' - }, - projection: { - x: makeProjectionAttr('x'), - y: makeProjectionAttr('y'), - z: makeProjectionAttr('z') - }, - connectgaps: scatterAttrs.connectgaps, - line: extendFlat({}, { - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - showscale: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Has an effect only if `line.color` is set to a numerical array.', - 'Determines whether or not a colorbar is displayed.' - ].join(' ') - } - }, + surfaceaxis: { + valType: 'enumerated', + role: 'info', + values: [-1, 0, 1, 2], + dflt: -1, + description: [ + 'If *-1*, the scatter points are not fill with a surface', + 'If *0*, *1*, *2*, the scatter points are filled with', + 'a Delaunay surface about the x, y, z respectively.' + ].join(' ') + }, + surfacecolor: { + valType: 'color', + role: 'style', + description: 'Sets the surface fill color.' + }, + projection: { + x: makeProjectionAttr('x'), + y: makeProjectionAttr('y'), + z: makeProjectionAttr('z') + }, + connectgaps: scatterAttrs.connectgaps, + line: extendFlat({}, { + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + showscale: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Has an effect only if `line.color` is set to a numerical array.', + 'Determines whether or not a colorbar is displayed.' + ].join(' ') + } + }, colorAttributes('line') ), - marker: extendFlat({}, { // Parity with scatter.js? - symbol: { - valType: 'enumerated', - values: Object.keys(MARKER_SYMBOLS), - role: 'style', - dflt: 'circle', - arrayOk: true, - description: 'Sets the marker symbol type.' - }, - size: extendFlat({}, scatterMarkerAttrs.size, {dflt: 8}), - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: extendFlat({}, scatterMarkerAttrs.opacity, { - arrayOk: false, - description: [ - 'Sets the marker opacity.', - 'Note that the marker opacity for scatter3d traces', - 'must be a scalar value for performance reasons.', - 'To set a blending opacity value', - '(i.e. which is not transparent), set *marker.color*', - 'to an rgba color and use its alpha channel.' - ].join(' ') - }), - showscale: scatterMarkerAttrs.showscale, - colorbar: scatterMarkerAttrs.colorbar, + marker: extendFlat({}, { // Parity with scatter.js? + symbol: { + valType: 'enumerated', + values: Object.keys(MARKER_SYMBOLS), + role: 'style', + dflt: 'circle', + arrayOk: true, + description: 'Sets the marker symbol type.' + }, + size: extendFlat({}, scatterMarkerAttrs.size, {dflt: 8}), + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: extendFlat({}, scatterMarkerAttrs.opacity, { + arrayOk: false, + description: [ + 'Sets the marker opacity.', + 'Note that the marker opacity for scatter3d traces', + 'must be a scalar value for performance reasons.', + 'To set a blending opacity value', + '(i.e. which is not transparent), set *marker.color*', + 'to an rgba color and use its alpha channel.' + ].join(' ') + }), + showscale: scatterMarkerAttrs.showscale, + colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({}, + line: extendFlat({}, {width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false})}, colorAttributes('marker.line') ) - }, + }, colorAttributes('marker') ), - textposition: extendFlat({}, scatterAttrs.textposition, {dflt: 'top center'}), - textfont: scatterAttrs.textfont, + textposition: extendFlat({}, scatterAttrs.textposition, {dflt: 'top center'}), + textfont: scatterAttrs.textfont, - error_x: errorBarAttrs, - error_y: errorBarAttrs, - error_z: errorBarAttrs, -}; + error_x: errorBarAttrs, + error_y: errorBarAttrs, + error_z: errorBarAttrs +} diff --git a/src/traces/scatter3d/calc.js b/src/traces/scatter3d/calc.js index 59ab3fb5bf7..dba85b051f0 100644 --- a/src/traces/scatter3d/calc.js +++ b/src/traces/scatter3d/calc.js @@ -6,22 +6,21 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); -var calcColorscales = require('../scatter/colorscale_calc'); +'use strict' +var arraysToCalcdata = require('../scatter/arrays_to_calcdata') +var calcColorscales = require('../scatter/colorscale_calc') /** * This is a kludge to put the array attributes into * calcdata the way Scatter.plot does, so that legends and * popovers know what to do with them. */ -module.exports = function calc(gd, trace) { - var cd = [{x: false, y: false, trace: trace, t: {}}]; +module.exports = function calc (gd, trace) { + var cd = [{x: false, y: false, trace: trace, t: {}}] - arraysToCalcdata(cd, trace); - calcColorscales(trace); + arraysToCalcdata(cd, trace) + calcColorscales(trace) - return cd; -}; + return cd +} diff --git a/src/traces/scatter3d/calc_errors.js b/src/traces/scatter3d/calc_errors.js index 1e77154a2be..43e88ca0722 100644 --- a/src/traces/scatter3d/calc_errors.js +++ b/src/traces/scatter3d/calc_errors.js @@ -6,64 +6,62 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var makeComputeError = require('../../components/errorbars/compute_error') -var makeComputeError = require('../../components/errorbars/compute_error'); +function calculateAxisErrors (data, params, scaleFactor) { + if (!params || !params.visible) return null + var computeError = makeComputeError(params) + var result = new Array(data.length) -function calculateAxisErrors(data, params, scaleFactor) { - if(!params || !params.visible) return null; + for (var i = 0; i < data.length; i++) { + var errors = computeError(+data[i], i) - var computeError = makeComputeError(params); - var result = new Array(data.length); + result[i] = [ + -errors[0] * scaleFactor, + errors[1] * scaleFactor + ] + } - for(var i = 0; i < data.length; i++) { - var errors = computeError(+data[i], i); - - result[i] = [ - -errors[0] * scaleFactor, - errors[1] * scaleFactor - ]; - } - - return result; + return result } -function dataLength(array) { - for(var i = 0; i < array.length; i++) { - if(array[i]) return array[i].length; - } - return 0; +function dataLength (array) { + for (var i = 0; i < array.length; i++) { + if (array[i]) return array[i].length + } + return 0 } -function calculateErrors(data, scaleFactor) { - var errors = [ - calculateAxisErrors(data.x, data.error_x, scaleFactor[0]), - calculateAxisErrors(data.y, data.error_y, scaleFactor[1]), - calculateAxisErrors(data.z, data.error_z, scaleFactor[2]) - ]; +function calculateErrors (data, scaleFactor) { + var errors = [ + calculateAxisErrors(data.x, data.error_x, scaleFactor[0]), + calculateAxisErrors(data.y, data.error_y, scaleFactor[1]), + calculateAxisErrors(data.z, data.error_z, scaleFactor[2]) + ] - var n = dataLength(errors); - if(n === 0) return null; + var n = dataLength(errors) + if (n === 0) return null - var errorBounds = new Array(n); + var errorBounds = new Array(n) - for(var i = 0; i < n; i++) { - var bound = [[0, 0, 0], [0, 0, 0]]; + for (var i = 0; i < n; i++) { + var bound = [[0, 0, 0], [0, 0, 0]] - for(var j = 0; j < 3; j++) { - if(errors[j]) { - for(var k = 0; k < 2; k++) { - bound[k][j] = errors[j][i][k]; - } - } + for (var j = 0; j < 3; j++) { + if (errors[j]) { + for (var k = 0; k < 2; k++) { + bound[k][j] = errors[j][i][k] } - - errorBounds[i] = bound; + } } - return errorBounds; + errorBounds[i] = bound + } + + return errorBounds } -module.exports = calculateErrors; +module.exports = calculateErrors diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index fa1a0a8f2dd..278d64c161a 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -6,455 +6,448 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var createLinePlot = require('gl-line3d'); -var createScatterPlot = require('gl-scatter3d'); -var createErrorBars = require('gl-error3d'); -var createMesh = require('gl-mesh3d'); -var triangulate = require('delaunay-triangulate'); - -var Lib = require('../../lib'); -var str2RgbaArray = require('../../lib/str2rgbarray'); -var formatColor = require('../../lib/gl_format_color'); -var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); -var DASH_PATTERNS = require('../../constants/gl3d_dashes'); -var MARKER_SYMBOLS = require('../../constants/gl_markers'); - -var calculateError = require('./calc_errors'); - -function LineWithMarkers(scene, uid) { - this.scene = scene; - this.uid = uid; - this.linePlot = null; - this.scatterPlot = null; - this.errorBars = null; - this.textMarkers = null; - this.delaunayMesh = null; - this.color = null; - this.mode = ''; - this.dataPoints = []; - this.axesBounds = [ +'use strict' + +var createLinePlot = require('gl-line3d') +var createScatterPlot = require('gl-scatter3d') +var createErrorBars = require('gl-error3d') +var createMesh = require('gl-mesh3d') +var triangulate = require('delaunay-triangulate') + +var Lib = require('../../lib') +var str2RgbaArray = require('../../lib/str2rgbarray') +var formatColor = require('../../lib/gl_format_color') +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func') +var DASH_PATTERNS = require('../../constants/gl3d_dashes') +var MARKER_SYMBOLS = require('../../constants/gl_markers') + +var calculateError = require('./calc_errors') + +function LineWithMarkers (scene, uid) { + this.scene = scene + this.uid = uid + this.linePlot = null + this.scatterPlot = null + this.errorBars = null + this.textMarkers = null + this.delaunayMesh = null + this.color = null + this.mode = '' + this.dataPoints = [] + this.axesBounds = [ [-Infinity, -Infinity, -Infinity], [Infinity, Infinity, Infinity] - ]; - this.textLabels = null; - this.data = null; + ] + this.textLabels = null + this.data = null } -var proto = LineWithMarkers.prototype; +var proto = LineWithMarkers.prototype -proto.handlePick = function(selection) { - if(selection.object && +proto.handlePick = function (selection) { + if (selection.object && (selection.object === this.linePlot || selection.object === this.delaunayMesh || selection.object === this.textMarkers || selection.object === this.scatterPlot)) { - if(selection.object.highlight) { - selection.object.highlight(null); - } - if(this.scatterPlot) { - selection.object = this.scatterPlot; - this.scatterPlot.highlight(selection.data); - } - if(this.textLabels && this.textLabels[selection.data.index] !== undefined) { - selection.textLabel = this.textLabels[selection.data.index]; - } - else selection.textLabel = ''; - - var selectIndex = selection.data.index; - selection.traceCoordinate = [ - this.data.x[selectIndex], - this.data.y[selectIndex], - this.data.z[selectIndex] - ]; - - return true; + if (selection.object.highlight) { + selection.object.highlight(null) + } + if (this.scatterPlot) { + selection.object = this.scatterPlot + this.scatterPlot.highlight(selection.data) } -}; - -function constructDelaunay(points, color, axis) { - var u = (axis + 1) % 3; - var v = (axis + 2) % 3; - var filteredPoints = []; - var filteredIds = []; - var i; - - for(i = 0; i < points.length; ++i) { - var p = points[i]; - if(isNaN(p[u]) || !isFinite(p[u]) || + if (this.textLabels && this.textLabels[selection.data.index] !== undefined) { + selection.textLabel = this.textLabels[selection.data.index] + } else selection.textLabel = '' + + var selectIndex = selection.data.index + selection.traceCoordinate = [ + this.data.x[selectIndex], + this.data.y[selectIndex], + this.data.z[selectIndex] + ] + + return true + } +} + +function constructDelaunay (points, color, axis) { + var u = (axis + 1) % 3 + var v = (axis + 2) % 3 + var filteredPoints = [] + var filteredIds = [] + var i + + for (i = 0; i < points.length; ++i) { + var p = points[i] + if (isNaN(p[u]) || !isFinite(p[u]) || isNaN(p[v]) || !isFinite(p[v])) { - continue; - } - filteredPoints.push([p[u], p[v]]); - filteredIds.push(i); + continue } - var cells = triangulate(filteredPoints); - for(i = 0; i < cells.length; ++i) { - var c = cells[i]; - for(var j = 0; j < c.length; ++j) { - c[j] = filteredIds[c[j]]; - } + filteredPoints.push([p[u], p[v]]) + filteredIds.push(i) + } + var cells = triangulate(filteredPoints) + for (i = 0; i < cells.length; ++i) { + var c = cells[i] + for (var j = 0; j < c.length; ++j) { + c[j] = filteredIds[c[j]] } - return { - positions: points, - cells: cells, - meshColor: color - }; + } + return { + positions: points, + cells: cells, + meshColor: color + } } -function calculateErrorParams(errors) { - var capSize = [0.0, 0.0, 0.0], - color = [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - lineWidth = [0.0, 0.0, 0.0]; - - for(var i = 0; i < 3; i++) { - var e = errors[i]; +function calculateErrorParams (errors) { + var capSize = [0.0, 0.0, 0.0], + color = [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + lineWidth = [0.0, 0.0, 0.0] - if(e && e.copy_zstyle !== false) e = errors[2]; - if(!e) continue; + for (var i = 0; i < 3; i++) { + var e = errors[i] - capSize[i] = e.width / 2; // ballpark rescaling - color[i] = str2RgbaArray(e.color); - lineWidth = e.thickness; + if (e && e.copy_zstyle !== false) e = errors[2] + if (!e) continue - } + capSize[i] = e.width / 2 // ballpark rescaling + color[i] = str2RgbaArray(e.color) + lineWidth = e.thickness + } - return {capSize: capSize, color: color, lineWidth: lineWidth}; + return {capSize: capSize, color: color, lineWidth: lineWidth} } -function calculateTextOffset(tp) { +function calculateTextOffset (tp) { // Read out text properties - var textOffset = [0, 0]; - if(Array.isArray(tp)) return [0, -1]; - if(tp.indexOf('bottom') >= 0) textOffset[1] += 1; - if(tp.indexOf('top') >= 0) textOffset[1] -= 1; - if(tp.indexOf('left') >= 0) textOffset[0] -= 1; - if(tp.indexOf('right') >= 0) textOffset[0] += 1; - return textOffset; + var textOffset = [0, 0] + if (Array.isArray(tp)) return [0, -1] + if (tp.indexOf('bottom') >= 0) textOffset[1] += 1 + if (tp.indexOf('top') >= 0) textOffset[1] -= 1 + if (tp.indexOf('left') >= 0) textOffset[0] -= 1 + if (tp.indexOf('right') >= 0) textOffset[0] += 1 + return textOffset } - -function calculateSize(sizeIn, sizeFn) { +function calculateSize (sizeIn, sizeFn) { // rough parity with Plotly 2D markers - return sizeFn(sizeIn * 4); + return sizeFn(sizeIn * 4) } -function calculateSymbol(symbolIn) { - return MARKER_SYMBOLS[symbolIn]; +function calculateSymbol (symbolIn) { + return MARKER_SYMBOLS[symbolIn] } -function formatParam(paramIn, len, calculate, dflt, extraFn) { - var paramOut = null; +function formatParam (paramIn, len, calculate, dflt, extraFn) { + var paramOut = null - if(Array.isArray(paramIn)) { - paramOut = []; - - for(var i = 0; i < len; i++) { - if(paramIn[i] === undefined) paramOut[i] = dflt; - else paramOut[i] = calculate(paramIn[i], extraFn); - } + if (Array.isArray(paramIn)) { + paramOut = [] + for (var i = 0; i < len; i++) { + if (paramIn[i] === undefined) paramOut[i] = dflt + else paramOut[i] = calculate(paramIn[i], extraFn) } - else paramOut = calculate(paramIn, Lib.identity); + } else paramOut = calculate(paramIn, Lib.identity) - return paramOut; + return paramOut } - -function convertPlotlyOptions(scene, data) { - var params, i, - points = [], - sceneLayout = scene.fullSceneLayout, - scaleFactor = scene.dataScale, - xaxis = sceneLayout.xaxis, - yaxis = sceneLayout.yaxis, - zaxis = sceneLayout.zaxis, - marker = data.marker, - line = data.line, - xc, x = data.x || [], - yc, y = data.y || [], - zc, z = data.z || [], - len = x.length, - xcalendar = data.xcalendar, - ycalendar = data.ycalendar, - zcalendar = data.zcalendar, - text; +function convertPlotlyOptions (scene, data) { + var params, i, + points = [], + sceneLayout = scene.fullSceneLayout, + scaleFactor = scene.dataScale, + xaxis = sceneLayout.xaxis, + yaxis = sceneLayout.yaxis, + zaxis = sceneLayout.zaxis, + marker = data.marker, + line = data.line, + xc, x = data.x || [], + yc, y = data.y || [], + zc, z = data.z || [], + len = x.length, + xcalendar = data.xcalendar, + ycalendar = data.ycalendar, + zcalendar = data.zcalendar, + text // Convert points - for(i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // sanitize numbers and apply transforms based on axes.type - xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0]; - yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1]; - zc = zaxis.d2l(z[i], 0, zcalendar) * scaleFactor[2]; + xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0] + yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1] + zc = zaxis.d2l(z[i], 0, zcalendar) * scaleFactor[2] - points[i] = [xc, yc, zc]; - } + points[i] = [xc, yc, zc] + } // convert text - if(Array.isArray(data.text)) text = data.text; - else if(data.text !== undefined) { - text = new Array(len); - for(i = 0; i < len; i++) text[i] = data.text; - } + if (Array.isArray(data.text)) text = data.text + else if (data.text !== undefined) { + text = new Array(len) + for (i = 0; i < len; i++) text[i] = data.text + } // Build object parameters - params = { - position: points, - mode: data.mode, - text: text - }; - - if('line' in data) { - params.lineColor = formatColor(line, 1, len); - params.lineWidth = line.width; - params.lineDashes = line.dash; + params = { + position: points, + mode: data.mode, + text: text + } + + if ('line' in data) { + params.lineColor = formatColor(line, 1, len) + params.lineWidth = line.width + params.lineDashes = line.dash + } + + if ('marker' in data) { + var sizeFn = makeBubbleSizeFn(data) + + params.scatterColor = formatColor(marker, 1, len) + params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn) + params.scatterMarker = formatParam(marker.symbol, len, calculateSymbol, '●') + params.scatterLineWidth = marker.line.width // arrayOk === false + params.scatterLineColor = formatColor(marker.line, 1, len) + params.scatterAngle = 0 + } + + if ('textposition' in data) { + params.textOffset = calculateTextOffset(data.textposition) // arrayOk === false + params.textColor = formatColor(data.textfont, 1, len) + params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12) + params.textFont = data.textfont.family // arrayOk === false + params.textAngle = 0 + } + + var dims = ['x', 'y', 'z'] + params.project = [false, false, false] + params.projectScale = [1, 1, 1] + params.projectOpacity = [1, 1, 1] + for (i = 0; i < 3; ++i) { + var projection = data.projection[dims[i]] + if ((params.project[i] = projection.show)) { + params.projectOpacity[i] = projection.opacity + params.projectScale[i] = projection.scale } + } - if('marker' in data) { - var sizeFn = makeBubbleSizeFn(data); + params.errorBounds = calculateError(data, scaleFactor) - params.scatterColor = formatColor(marker, 1, len); - params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn); - params.scatterMarker = formatParam(marker.symbol, len, calculateSymbol, '●'); - params.scatterLineWidth = marker.line.width; // arrayOk === false - params.scatterLineColor = formatColor(marker.line, 1, len); - params.scatterAngle = 0; - } - - if('textposition' in data) { - params.textOffset = calculateTextOffset(data.textposition); // arrayOk === false - params.textColor = formatColor(data.textfont, 1, len); - params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12); - params.textFont = data.textfont.family; // arrayOk === false - params.textAngle = 0; - } + var errorParams = calculateErrorParams([data.error_x, data.error_y, data.error_z]) + params.errorColor = errorParams.color + params.errorLineWidth = errorParams.lineWidth + params.errorCapSize = errorParams.capSize - var dims = ['x', 'y', 'z']; - params.project = [false, false, false]; - params.projectScale = [1, 1, 1]; - params.projectOpacity = [1, 1, 1]; - for(i = 0; i < 3; ++i) { - var projection = data.projection[dims[i]]; - if((params.project[i] = projection.show)) { - params.projectOpacity[i] = projection.opacity; - params.projectScale[i] = projection.scale; - } - } + params.delaunayAxis = data.surfaceaxis + params.delaunayColor = str2RgbaArray(data.surfacecolor) - params.errorBounds = calculateError(data, scaleFactor); - - var errorParams = calculateErrorParams([data.error_x, data.error_y, data.error_z]); - params.errorColor = errorParams.color; - params.errorLineWidth = errorParams.lineWidth; - params.errorCapSize = errorParams.capSize; - - params.delaunayAxis = data.surfaceaxis; - params.delaunayColor = str2RgbaArray(data.surfacecolor); - - return params; + return params } -function arrayToColor(color) { - if(Array.isArray(color)) { - var c = color[0]; +function arrayToColor (color) { + if (Array.isArray(color)) { + var c = color[0] - if(Array.isArray(c)) color = c; + if (Array.isArray(c)) color = c - return 'rgb(' + color.slice(0, 3).map(function(x) { - return Math.round(x * 255); - }) + ')'; - } + return 'rgb(' + color.slice(0, 3).map(function (x) { + return Math.round(x * 255) + }) + ')' + } - return null; + return null } -proto.update = function(data) { - var gl = this.scene.glplot.gl, - lineOptions, - scatterOptions, - errorOptions, - textOptions, - dashPattern = DASH_PATTERNS.solid; +proto.update = function (data) { + var gl = this.scene.glplot.gl, + lineOptions, + scatterOptions, + errorOptions, + textOptions, + dashPattern = DASH_PATTERNS.solid // Save data - this.data = data; + this.data = data // Run data conversion - var options = convertPlotlyOptions(this.scene, data); - - if('mode' in options) { - this.mode = options.mode; - } - if('lineDashes' in options) { - if(options.lineDashes in DASH_PATTERNS) { - dashPattern = DASH_PATTERNS[options.lineDashes]; - } + var options = convertPlotlyOptions(this.scene, data) + + if ('mode' in options) { + this.mode = options.mode + } + if ('lineDashes' in options) { + if (options.lineDashes in DASH_PATTERNS) { + dashPattern = DASH_PATTERNS[options.lineDashes] } + } - this.color = arrayToColor(options.scatterColor) || - arrayToColor(options.lineColor); + this.color = arrayToColor(options.scatterColor) || + arrayToColor(options.lineColor) // Save data points - this.dataPoints = options.position; - - lineOptions = { - gl: gl, - position: options.position, - color: options.lineColor, - lineWidth: options.lineWidth || 1, - dashes: dashPattern[0], - dashScale: dashPattern[1], - opacity: data.opacity, - connectGaps: data.connectgaps - }; - - if(this.mode.indexOf('lines') !== -1) { - if(this.linePlot) this.linePlot.update(lineOptions); - else { - this.linePlot = createLinePlot(lineOptions); - this.scene.glplot.add(this.linePlot); - } - } else if(this.linePlot) { - this.scene.glplot.remove(this.linePlot); - this.linePlot.dispose(); - this.linePlot = null; + this.dataPoints = options.position + + lineOptions = { + gl: gl, + position: options.position, + color: options.lineColor, + lineWidth: options.lineWidth || 1, + dashes: dashPattern[0], + dashScale: dashPattern[1], + opacity: data.opacity, + connectGaps: data.connectgaps + } + + if (this.mode.indexOf('lines') !== -1) { + if (this.linePlot) this.linePlot.update(lineOptions) + else { + this.linePlot = createLinePlot(lineOptions) + this.scene.glplot.add(this.linePlot) } + } else if (this.linePlot) { + this.scene.glplot.remove(this.linePlot) + this.linePlot.dispose() + this.linePlot = null + } // N.B. marker.opacity must be a scalar for performance - var scatterOpacity = data.opacity; - if(data.marker && data.marker.opacity) scatterOpacity *= data.marker.opacity; - - scatterOptions = { - gl: gl, - position: options.position, - color: options.scatterColor, - size: options.scatterSize, - glyph: options.scatterMarker, - opacity: scatterOpacity, - orthographic: true, - lineWidth: options.scatterLineWidth, - lineColor: options.scatterLineColor, - project: options.project, - projectScale: options.projectScale, - projectOpacity: options.projectOpacity - }; - - if(this.mode.indexOf('markers') !== -1) { - if(this.scatterPlot) this.scatterPlot.update(scatterOptions); - else { - this.scatterPlot = createScatterPlot(scatterOptions); - this.scatterPlot.highlightScale = 1; - this.scene.glplot.add(this.scatterPlot); - } - } else if(this.scatterPlot) { - this.scene.glplot.remove(this.scatterPlot); - this.scatterPlot.dispose(); - this.scatterPlot = null; + var scatterOpacity = data.opacity + if (data.marker && data.marker.opacity) scatterOpacity *= data.marker.opacity + + scatterOptions = { + gl: gl, + position: options.position, + color: options.scatterColor, + size: options.scatterSize, + glyph: options.scatterMarker, + opacity: scatterOpacity, + orthographic: true, + lineWidth: options.scatterLineWidth, + lineColor: options.scatterLineColor, + project: options.project, + projectScale: options.projectScale, + projectOpacity: options.projectOpacity + } + + if (this.mode.indexOf('markers') !== -1) { + if (this.scatterPlot) this.scatterPlot.update(scatterOptions) + else { + this.scatterPlot = createScatterPlot(scatterOptions) + this.scatterPlot.highlightScale = 1 + this.scene.glplot.add(this.scatterPlot) } - - textOptions = { - gl: gl, - position: options.position, - glyph: options.text, - color: options.textColor, - size: options.textSize, - angle: options.textAngle, - alignment: options.textOffset, - font: options.textFont, - orthographic: true, - lineWidth: 0, - project: false, - opacity: data.opacity - }; - - this.textLabels = options.text; - - if(this.mode.indexOf('text') !== -1) { - if(this.textMarkers) this.textMarkers.update(textOptions); - else { - this.textMarkers = createScatterPlot(textOptions); - this.textMarkers.highlightScale = 1; - this.scene.glplot.add(this.textMarkers); - } - } else if(this.textMarkers) { - this.scene.glplot.remove(this.textMarkers); - this.textMarkers.dispose(); - this.textMarkers = null; + } else if (this.scatterPlot) { + this.scene.glplot.remove(this.scatterPlot) + this.scatterPlot.dispose() + this.scatterPlot = null + } + + textOptions = { + gl: gl, + position: options.position, + glyph: options.text, + color: options.textColor, + size: options.textSize, + angle: options.textAngle, + alignment: options.textOffset, + font: options.textFont, + orthographic: true, + lineWidth: 0, + project: false, + opacity: data.opacity + } + + this.textLabels = options.text + + if (this.mode.indexOf('text') !== -1) { + if (this.textMarkers) this.textMarkers.update(textOptions) + else { + this.textMarkers = createScatterPlot(textOptions) + this.textMarkers.highlightScale = 1 + this.scene.glplot.add(this.textMarkers) } - - errorOptions = { - gl: gl, - position: options.position, - color: options.errorColor, - error: options.errorBounds, - lineWidth: options.errorLineWidth, - capSize: options.errorCapSize, - opacity: data.opacity - }; - if(this.errorBars) { - if(options.errorBounds) { - this.errorBars.update(errorOptions); - } else { - this.scene.glplot.remove(this.errorBars); - this.errorBars.dispose(); - this.errorBars = null; - } - } else if(options.errorBounds) { - this.errorBars = createErrorBars(errorOptions); - this.scene.glplot.add(this.errorBars); + } else if (this.textMarkers) { + this.scene.glplot.remove(this.textMarkers) + this.textMarkers.dispose() + this.textMarkers = null + } + + errorOptions = { + gl: gl, + position: options.position, + color: options.errorColor, + error: options.errorBounds, + lineWidth: options.errorLineWidth, + capSize: options.errorCapSize, + opacity: data.opacity + } + if (this.errorBars) { + if (options.errorBounds) { + this.errorBars.update(errorOptions) + } else { + this.scene.glplot.remove(this.errorBars) + this.errorBars.dispose() + this.errorBars = null } + } else if (options.errorBounds) { + this.errorBars = createErrorBars(errorOptions) + this.scene.glplot.add(this.errorBars) + } - if(options.delaunayAxis >= 0) { - var delaunayOptions = constructDelaunay( + if (options.delaunayAxis >= 0) { + var delaunayOptions = constructDelaunay( options.position, options.delaunayColor, options.delaunayAxis - ); - delaunayOptions.opacity = data.opacity; - - if(this.delaunayMesh) { - this.delaunayMesh.update(delaunayOptions); - } else { - delaunayOptions.gl = gl; - this.delaunayMesh = createMesh(delaunayOptions); - this.scene.glplot.add(this.delaunayMesh); - } - } else if(this.delaunayMesh) { - this.scene.glplot.remove(this.delaunayMesh); - this.delaunayMesh.dispose(); - this.delaunayMesh = null; + ) + delaunayOptions.opacity = data.opacity + + if (this.delaunayMesh) { + this.delaunayMesh.update(delaunayOptions) + } else { + delaunayOptions.gl = gl + this.delaunayMesh = createMesh(delaunayOptions) + this.scene.glplot.add(this.delaunayMesh) } -}; + } else if (this.delaunayMesh) { + this.scene.glplot.remove(this.delaunayMesh) + this.delaunayMesh.dispose() + this.delaunayMesh = null + } +} -proto.dispose = function() { - if(this.linePlot) { - this.scene.glplot.remove(this.linePlot); - this.linePlot.dispose(); - } - if(this.scatterPlot) { - this.scene.glplot.remove(this.scatterPlot); - this.scatterPlot.dispose(); - } - if(this.errorBars) { - this.scene.glplot.remove(this.errorBars); - this.errorBars.dispose(); - } - if(this.textMarkers) { - this.scene.glplot.remove(this.textMarkers); - this.textMarkers.dispose(); - } - if(this.delaunayMesh) { - this.scene.glplot.remove(this.delaunayMesh); - this.delaunayMesh.dispose(); - } -}; +proto.dispose = function () { + if (this.linePlot) { + this.scene.glplot.remove(this.linePlot) + this.linePlot.dispose() + } + if (this.scatterPlot) { + this.scene.glplot.remove(this.scatterPlot) + this.scatterPlot.dispose() + } + if (this.errorBars) { + this.scene.glplot.remove(this.errorBars) + this.errorBars.dispose() + } + if (this.textMarkers) { + this.scene.glplot.remove(this.textMarkers) + this.textMarkers.dispose() + } + if (this.delaunayMesh) { + this.scene.glplot.remove(this.delaunayMesh) + this.delaunayMesh.dispose() + } +} -function createLineWithMarkers(scene, data) { - var plot = new LineWithMarkers(scene, data.uid); - plot.update(data); - return plot; +function createLineWithMarkers (scene, data) { + var plot = new LineWithMarkers(scene, data.uid) + plot.update(data) + return plot } -module.exports = createLineWithMarkers; +module.exports = createLineWithMarkers diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index 4f62fd3c1d8..2b6f7ef88df 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -6,82 +6,79 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Registry = require('../../registry'); -var Lib = require('../../lib'); - -var subTypes = require('../scatter/subtypes'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleTextDefaults = require('../scatter/text_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); - -var attributes = require('./attributes'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!len) { - traceOut.visible = false; - return; +'use strict' + +var Registry = require('../../registry') +var Lib = require('../../lib') + +var subTypes = require('../scatter/subtypes') +var handleMarkerDefaults = require('../scatter/marker_defaults') +var handleLineDefaults = require('../scatter/line_defaults') +var handleTextDefaults = require('../scatter/text_defaults') +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults') + +var attributes = require('./attributes') + +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + + var len = handleXYZDefaults(traceIn, traceOut, coerce, layout) + if (!len) { + traceOut.visible = false + return + } + + coerce('text') + coerce('mode') + + if (subTypes.hasLines(traceOut)) { + coerce('connectgaps') + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } + + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } + + if (subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce) + } + + var lineColor = (traceOut.line || {}).color, + markerColor = (traceOut.marker || {}).color + if (coerce('surfaceaxis') >= 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') } + } - coerce('text'); - coerce('mode'); - - if(subTypes.hasLines(traceOut)) { - coerce('connectgaps'); - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'}) + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'}) + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'}) +} - if(subTypes.hasText(traceOut)) { - handleTextDefaults(traceIn, traceOut, layout, coerce); - } +function handleXYZDefaults (traceIn, traceOut, coerce, layout) { + var len = 0, + x = coerce('x'), + y = coerce('y'), + z = coerce('z') - var lineColor = (traceOut.line || {}).color, - markerColor = (traceOut.marker || {}).color; - if(coerce('surfaceaxis') >= 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'); - } - } + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout) - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'}); -}; - -function handleXYZDefaults(traceIn, traceOut, coerce, layout) { - var len = 0, - x = coerce('x'), - y = coerce('y'), - z = coerce('z'); - - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); - - if(x && y && z) { - len = Math.min(x.length, y.length, z.length); - if(len < x.length) traceOut.x = x.slice(0, len); - if(len < y.length) traceOut.y = y.slice(0, len); - if(len < z.length) traceOut.z = z.slice(0, len); - } + if (x && y && z) { + len = Math.min(x.length, y.length, z.length) + if (len < x.length) traceOut.x = x.slice(0, len) + if (len < y.length) traceOut.y = y.slice(0, len) + if (len < z.length) traceOut.z = z.slice(0, len) + } - return len; + return len } diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js index e1399198c77..a944f13fc8c 100644 --- a/src/traces/scatter3d/index.js +++ b/src/traces/scatter3d/index.js @@ -6,31 +6,31 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Scatter3D = {}; +var Scatter3D = {} -Scatter3D.plot = require('./convert'); -Scatter3D.attributes = require('./attributes'); -Scatter3D.markerSymbols = require('../../constants/gl_markers'); -Scatter3D.supplyDefaults = require('./defaults'); -Scatter3D.colorbar = require('../scatter/colorbar'); -Scatter3D.calc = require('./calc'); +Scatter3D.plot = require('./convert') +Scatter3D.attributes = require('./attributes') +Scatter3D.markerSymbols = require('../../constants/gl_markers') +Scatter3D.supplyDefaults = require('./defaults') +Scatter3D.colorbar = require('../scatter/colorbar') +Scatter3D.calc = require('./calc') -Scatter3D.moduleType = 'trace'; -Scatter3D.name = 'scatter3d'; -Scatter3D.basePlotModule = require('../../plots/gl3d'); -Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend']; +Scatter3D.moduleType = 'trace' +Scatter3D.name = 'scatter3d' +Scatter3D.basePlotModule = require('../../plots/gl3d') +Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend'] Scatter3D.meta = { - hrName: 'scatter_3d', - description: [ - 'The data visualized as scatter point or lines in 3D dimension', - 'is set in `x`, `y`, `z`.', - 'Text (appearing either on the chart or on hover only) is via `text`.', - 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', - 'Projections are achieved via `projection`.', - 'Surface fills are achieved via `surfaceaxis`.' - ].join(' ') -}; + hrName: 'scatter_3d', + description: [ + 'The data visualized as scatter point or lines in 3D dimension', + 'is set in `x`, `y`, `z`.', + 'Text (appearing either on the chart or on hover only) is via `text`.', + 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', + 'Projections are achieved via `projection`.', + 'Surface fills are achieved via `surfaceaxis`.' + ].join(' ') +} -module.exports = Scatter3D; +module.exports = Scatter3D diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 464354e35b0..0844de3c580 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -6,101 +6,101 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var plotAttrs = require('../../plots/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); +var scatterAttrs = require('../scatter/attributes') +var plotAttrs = require('../../plots/attributes') +var colorAttributes = require('../../components/colorscale/color_attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat var scatterMarkerAttrs = scatterAttrs.marker, - scatterLineAttrs = scatterAttrs.line, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; + scatterLineAttrs = scatterAttrs.line, + scatterMarkerLineAttrs = scatterMarkerAttrs.line module.exports = { - lon: { - valType: 'data_array', - description: 'Sets the longitude coordinates (in degrees East).' - }, - lat: { - valType: 'data_array', - description: 'Sets the latitude coordinates (in degrees North).' - }, + lon: { + valType: 'data_array', + description: 'Sets the longitude coordinates (in degrees East).' + }, + lat: { + valType: 'data_array', + description: 'Sets the latitude coordinates (in degrees North).' + }, - locations: { - valType: 'data_array', - description: [ - 'Sets the coordinates via location IDs or names.', - 'Coordinates correspond to the centroid of each location given.', - 'See `locationmode` for more info.' - ].join(' ') - }, - locationmode: { - valType: 'enumerated', - values: ['ISO-3', 'USA-states', 'country names'], - role: 'info', - dflt: 'ISO-3', - description: [ - 'Determines the set of locations used to match entries in `locations`', - 'to regions on the map.' - ].join(' ') - }, + locations: { + valType: 'data_array', + description: [ + 'Sets the coordinates via location IDs or names.', + 'Coordinates correspond to the centroid of each location given.', + 'See `locationmode` for more info.' + ].join(' ') + }, + locationmode: { + valType: 'enumerated', + values: ['ISO-3', 'USA-states', 'country names'], + role: 'info', + dflt: 'ISO-3', + description: [ + 'Determines the set of locations used to match entries in `locations`', + 'to regions on the map.' + ].join(' ') + }, - mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (lon,lat) pair', - 'or item in `locations`.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) or `locations` coordinates.' - ].join(' ') - }), - textfont: scatterAttrs.textfont, - textposition: scatterAttrs.textposition, + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (lon,lat) pair', + 'or item in `locations`.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (lon,lat) or `locations` coordinates.' + ].join(' ') + }), + textfont: scatterAttrs.textfont, + textposition: scatterAttrs.textposition, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash - }, - connectgaps: scatterAttrs.connectgaps, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash + }, + connectgaps: scatterAttrs.connectgaps, - marker: extendFlat({}, { - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - showscale: scatterMarkerAttrs.showscale, - colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({}, + marker: extendFlat({}, { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + showscale: scatterMarkerAttrs.showscale, + colorbar: scatterMarkerAttrs.colorbar, + line: extendFlat({}, {width: scatterMarkerLineAttrs.width}, colorAttributes('marker.line') ) - }, + }, colorAttributes('marker') ), - fill: { - valType: 'enumerated', - values: ['none', 'toself'], - dflt: 'none', - role: 'style', - description: [ - 'Sets the area to fill with a solid color.', - 'Use with `fillcolor` if not *none*.', - '*toself* connects the endpoints of the trace (or each segment', - 'of the trace if it has gaps) into a closed shape.' - ].join(' ') - }, - fillcolor: scatterAttrs.fillcolor, + fill: { + valType: 'enumerated', + values: ['none', 'toself'], + dflt: 'none', + role: 'style', + description: [ + 'Sets the area to fill with a solid color.', + 'Use with `fillcolor` if not *none*.', + '*toself* connects the endpoints of the trace (or each segment', + 'of the trace if it has gaps) into a closed shape.' + ].join(' ') + }, + fillcolor: scatterAttrs.fillcolor, - hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['lon', 'lat', 'location', 'text', 'name'] - }) -}; + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['lon', 'lat', 'location', 'text', 'name'] + }) +} diff --git a/src/traces/scattergeo/calc.js b/src/traces/scattergeo/calc.js index e124cf9993c..243bd0790b3 100644 --- a/src/traces/scattergeo/calc.js +++ b/src/traces/scattergeo/calc.js @@ -6,50 +6,47 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var calcMarkerColorscale = require('../scatter/colorscale_calc') -var calcMarkerColorscale = require('../scatter/colorscale_calc'); +module.exports = function calc (gd, trace) { + var hasLocationData = Array.isArray(trace.locations), + len = hasLocationData ? trace.locations.length : trace.lon.length + var calcTrace = [], + cnt = 0 -module.exports = function calc(gd, trace) { - var hasLocationData = Array.isArray(trace.locations), - len = hasLocationData ? trace.locations.length : trace.lon.length; + for (var i = 0; i < len; i++) { + var calcPt = {}, + skip - var calcTrace = [], - cnt = 0; + if (hasLocationData) { + var loc = trace.locations[i] - for(var i = 0; i < len; i++) { - var calcPt = {}, - skip; + calcPt.loc = loc + skip = (typeof loc !== 'string') + } else { + var lon = trace.lon[i], + lat = trace.lat[i] - if(hasLocationData) { - var loc = trace.locations[i]; - - calcPt.loc = loc; - skip = (typeof loc !== 'string'); - } - else { - var lon = trace.lon[i], - lat = trace.lat[i]; - - calcPt.lonlat = [+lon, +lat]; - skip = (!isNumeric(lon) || !isNumeric(lat)); - } + calcPt.lonlat = [+lon, +lat] + skip = (!isNumeric(lon) || !isNumeric(lat)) + } - if(skip) { - if(cnt > 0) calcTrace[cnt - 1].gapAfter = true; - continue; - } + if (skip) { + if (cnt > 0) calcTrace[cnt - 1].gapAfter = true + continue + } - cnt++; + cnt++ - calcTrace.push(calcPt); - } + calcTrace.push(calcPt) + } - calcMarkerColorscale(trace); + calcMarkerColorscale(trace) - return calcTrace; -}; + return calcTrace +} diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index aa0bbdd5f45..c2d9d660e46 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -6,73 +6,71 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var subTypes = require('../scatter/subtypes') +var handleMarkerDefaults = require('../scatter/marker_defaults') +var handleLineDefaults = require('../scatter/line_defaults') +var handleTextDefaults = require('../scatter/text_defaults') +var handleFillColorDefaults = require('../scatter/fillcolor_defaults') -var subTypes = require('../scatter/subtypes'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleTextDefaults = require('../scatter/text_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var attributes = require('./attributes') -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + var len = handleLonLatLocDefaults(traceIn, traceOut, coerce) + if (!len) { + traceOut.visible = false + return + } -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } + coerce('text') + coerce('mode') - var len = handleLonLatLocDefaults(traceIn, traceOut, coerce); - if(!len) { - traceOut.visible = false; - return; - } + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + coerce('connectgaps') + } - coerce('text'); - coerce('mode'); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - coerce('connectgaps'); - } + if (subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce) + } - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } + coerce('fill') + if (traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce) + } - if(subTypes.hasText(traceOut)) { - handleTextDefaults(traceIn, traceOut, layout, coerce); - } - - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - } - - coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+location+text' : undefined); -}; + coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+location+text' : undefined) +} -function handleLonLatLocDefaults(traceIn, traceOut, coerce) { - var len = 0, - locations = coerce('locations'); +function handleLonLatLocDefaults (traceIn, traceOut, coerce) { + var len = 0, + locations = coerce('locations') - var lon, lat; + var lon, lat - if(locations) { - coerce('locationmode'); - len = locations.length; - return len; - } + if (locations) { + coerce('locationmode') + len = locations.length + return len + } - lon = coerce('lon') || []; - lat = coerce('lat') || []; - len = Math.min(lon.length, lat.length); + lon = coerce('lon') || [] + lat = coerce('lat') || [] + len = Math.min(lon.length, lat.length) - if(len < lon.length) traceOut.lon = lon.slice(0, len); - if(len < lat.length) traceOut.lat = lat.slice(0, len); + if (len < lon.length) traceOut.lon = lon.slice(0, len) + if (len < lat.length) traceOut.lat = lat.slice(0, len) - return len; + return len } diff --git a/src/traces/scattergeo/event_data.js b/src/traces/scattergeo/event_data.js index f43043352ec..aa232215d13 100644 --- a/src/traces/scattergeo/event_data.js +++ b/src/traces/scattergeo/event_data.js @@ -6,14 +6,12 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +module.exports = function eventData (out, pt) { + out.lon = pt.lon + out.lat = pt.lat + out.location = pt.lon ? pt.lon : null - -module.exports = function eventData(out, pt) { - out.lon = pt.lon; - out.lat = pt.lat; - out.location = pt.lon ? pt.lon : null; - - return out; -}; + return out +} diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index 3e3c54f5bd7..d539abd2d93 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -6,106 +6,103 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Fx = require('../../plots/cartesian/graph_interact') +var Axes = require('../../plots/cartesian/axes') -var Fx = require('../../plots/cartesian/graph_interact'); -var Axes = require('../../plots/cartesian/axes'); +var getTraceColor = require('../scatter/get_trace_color') +var attributes = require('./attributes') -var getTraceColor = require('../scatter/get_trace_color'); -var attributes = require('./attributes'); +module.exports = function hoverPoints (pointData) { + var cd = pointData.cd, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya, + geo = pointData.subplot + if (cd[0].placeholder) return -module.exports = function hoverPoints(pointData) { - var cd = pointData.cd, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya, - geo = pointData.subplot; + function c2p (lonlat) { + return geo.projection(lonlat) + } - if(cd[0].placeholder) return; - - function c2p(lonlat) { - return geo.projection(lonlat); - } - - function distFn(d) { - var lonlat = d.lonlat; + function distFn (d) { + var lonlat = d.lonlat // this handles the not-found location feature case - if(lonlat[0] === null || lonlat[1] === null) return Infinity; + if (lonlat[0] === null || lonlat[1] === null) return Infinity - if(geo.isLonLatOverEdges(lonlat)) return Infinity; + if (geo.isLonLatOverEdges(lonlat)) return Infinity - var pos = c2p(lonlat); + var pos = c2p(lonlat) - var xPx = xa.c2p(), - yPx = ya.c2p(); + var xPx = xa.c2p(), + yPx = ya.c2p() - var dx = Math.abs(xPx - pos[0]), - dy = Math.abs(yPx - pos[1]), - rad = Math.max(3, d.mrc || 0); + var dx = Math.abs(xPx - pos[0]), + dy = Math.abs(yPx - pos[1]), + rad = Math.max(3, d.mrc || 0) // N.B. d.mrc is the calculated marker radius // which is only set for trace with 'markers' mode. - return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); - } + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad) + } - Fx.getClosest(cd, distFn, pointData); + Fx.getClosest(cd, distFn, pointData) // skip the rest (for this trace) if we didn't find a close point - if(pointData.index === false) return; + if (pointData.index === false) return - var di = cd[pointData.index], - lonlat = di.lonlat, - pos = c2p(lonlat), - rad = di.mrc || 1; + var di = cd[pointData.index], + lonlat = di.lonlat, + pos = c2p(lonlat), + rad = di.mrc || 1 - pointData.x0 = pos[0] - rad; - pointData.x1 = pos[0] + rad; - pointData.y0 = pos[1] - rad; - pointData.y1 = pos[1] + rad; + pointData.x0 = pos[0] - rad + pointData.x1 = pos[0] + rad + pointData.y0 = pos[1] - rad + pointData.y1 = pos[1] + rad - pointData.loc = di.loc; - pointData.lat = lonlat[0]; - pointData.lon = lonlat[1]; + pointData.loc = di.loc + pointData.lat = lonlat[0] + pointData.lon = lonlat[1] - pointData.color = getTraceColor(trace, di); - pointData.extraText = getExtraText(trace, di, geo.mockAxis); + pointData.color = getTraceColor(trace, di) + pointData.extraText = getExtraText(trace, di, geo.mockAxis) - return [pointData]; -}; + return [pointData] +} -function getExtraText(trace, pt, axis) { - var hoverinfo = trace.hoverinfo; +function getExtraText (trace, pt, axis) { + var hoverinfo = trace.hoverinfo - var parts = (hoverinfo === 'all') ? + var parts = (hoverinfo === 'all') ? attributes.hoverinfo.flags : - hoverinfo.split('+'); + hoverinfo.split('+') - var hasLocation = parts.indexOf('location') !== -1 && Array.isArray(trace.locations), - hasLon = (parts.indexOf('lon') !== -1), - hasLat = (parts.indexOf('lat') !== -1), - hasText = (parts.indexOf('text') !== -1); + var hasLocation = parts.indexOf('location') !== -1 && Array.isArray(trace.locations), + hasLon = (parts.indexOf('lon') !== -1), + hasLat = (parts.indexOf('lat') !== -1), + hasText = (parts.indexOf('text') !== -1) - var text = []; + var text = [] - function format(val) { - return Axes.tickText(axis, axis.c2l(val), 'hover').text + '\u00B0'; - } + function format (val) { + return Axes.tickText(axis, axis.c2l(val), 'hover').text + '\u00B0' + } - if(hasLocation) text.push(pt.loc); - else if(hasLon && hasLat) { - text.push('(' + format(pt.lonlat[0]) + ', ' + format(pt.lonlat[1]) + ')'); - } - else if(hasLon) text.push('lon: ' + format(pt.lonlat[0])); - else if(hasLat) text.push('lat: ' + format(pt.lonlat[1])); + if (hasLocation) text.push(pt.loc) + else if (hasLon && hasLat) { + text.push('(' + format(pt.lonlat[0]) + ', ' + format(pt.lonlat[1]) + ')') + } else if (hasLon) text.push('lon: ' + format(pt.lonlat[0])) + else if (hasLat) text.push('lat: ' + format(pt.lonlat[1])) - if(hasText) { - var tx = pt.tx || trace.text; - if(!Array.isArray(tx)) text.push(tx); - } + if (hasText) { + var tx = pt.tx || trace.text + if (!Array.isArray(tx)) text.push(tx) + } - return text.join('
'); + return text.join('
') } diff --git a/src/traces/scattergeo/index.js b/src/traces/scattergeo/index.js index d48f0351330..c483d772755 100644 --- a/src/traces/scattergeo/index.js +++ b/src/traces/scattergeo/index.js @@ -6,30 +6,29 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var ScatterGeo = {} -var ScatterGeo = {}; +ScatterGeo.attributes = require('./attributes') +ScatterGeo.supplyDefaults = require('./defaults') +ScatterGeo.colorbar = require('../scatter/colorbar') +ScatterGeo.calc = require('./calc') +ScatterGeo.plot = require('./plot') +ScatterGeo.hoverPoints = require('./hover') +ScatterGeo.eventData = require('./event_data') -ScatterGeo.attributes = require('./attributes'); -ScatterGeo.supplyDefaults = require('./defaults'); -ScatterGeo.colorbar = require('../scatter/colorbar'); -ScatterGeo.calc = require('./calc'); -ScatterGeo.plot = require('./plot'); -ScatterGeo.hoverPoints = require('./hover'); -ScatterGeo.eventData = require('./event_data'); - -ScatterGeo.moduleType = 'trace'; -ScatterGeo.name = 'scattergeo'; -ScatterGeo.basePlotModule = require('../../plots/geo'); -ScatterGeo.categories = ['geo', 'symbols', 'markerColorscale', 'showLegend']; +ScatterGeo.moduleType = 'trace' +ScatterGeo.name = 'scattergeo' +ScatterGeo.basePlotModule = require('../../plots/geo') +ScatterGeo.categories = ['geo', 'symbols', 'markerColorscale', 'showLegend'] ScatterGeo.meta = { - hrName: 'scatter_geo', - description: [ - 'The data visualized as scatter point or lines on a geographic map', - 'is provided either by longitude/latitude pairs in `lon` and `lat`', - 'respectively or by geographic location IDs or names in `locations`.' - ].join(' ') -}; + hrName: 'scatter_geo', + description: [ + 'The data visualized as scatter point or lines on a geographic map', + 'is provided either by longitude/latitude pairs in `lon` and `lat`', + 'respectively or by geographic location IDs or names in `locations`.' + ].join(' ') +} -module.exports = ScatterGeo; +module.exports = ScatterGeo diff --git a/src/traces/scattergeo/plot.js b/src/traces/scattergeo/plot.js index b0c8b278dbc..89d2f0bcdf2 100644 --- a/src/traces/scattergeo/plot.js +++ b/src/traces/scattergeo/plot.js @@ -6,165 +6,161 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var d3 = require('d3') -var d3 = require('d3'); +var Drawing = require('../../components/drawing') +var Color = require('../../components/color') -var Drawing = require('../../components/drawing'); -var Color = require('../../components/color'); +var Lib = require('../../lib') +var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures +var locationToFeature = require('../../lib/geo_location_utils').locationToFeature +var geoJsonUtils = require('../../lib/geojson_utils') +var arrayToCalcItem = require('../../lib/array_to_calc_item') +var subTypes = require('../scatter/subtypes') -var Lib = require('../../lib'); -var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; -var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; -var geoJsonUtils = require('../../lib/geojson_utils'); -var arrayToCalcItem = require('../../lib/array_to_calc_item'); -var subTypes = require('../scatter/subtypes'); +module.exports = function plot (geo, calcData) { + function keyFunc (d) { return d[0].trace.uid } - -module.exports = function plot(geo, calcData) { - - function keyFunc(d) { return d[0].trace.uid; } - - var gScatterGeoTraces = geo.framework.select('.scattergeolayer') + var gScatterGeoTraces = geo.framework.select('.scattergeolayer') .selectAll('g.trace.scattergeo') - .data(calcData, keyFunc); + .data(calcData, keyFunc) - gScatterGeoTraces.enter().append('g') - .attr('class', 'trace scattergeo'); + gScatterGeoTraces.enter().append('g') + .attr('class', 'trace scattergeo') - gScatterGeoTraces.exit().remove(); + gScatterGeoTraces.exit().remove() // TODO find a way to order the inner nodes on update - gScatterGeoTraces.selectAll('*').remove(); + gScatterGeoTraces.selectAll('*').remove() - gScatterGeoTraces.each(function(calcTrace) { - var s = d3.select(this), - trace = calcTrace[0].trace, - convertToLonLatFn = makeConvertToLonLatFn(trace, geo.topojson); + gScatterGeoTraces.each(function (calcTrace) { + var s = d3.select(this), + trace = calcTrace[0].trace, + convertToLonLatFn = makeConvertToLonLatFn(trace, geo.topojson) // skip over placeholder traces - if(calcTrace[0].placeholder) s.remove(); + if (calcTrace[0].placeholder) s.remove() // just like calcTrace but w/o not-found location datum - var _calcTrace = []; + var _calcTrace = [] - for(var i = 0; i < calcTrace.length; i++) { - var _calcPt = convertToLonLatFn(calcTrace[i]); + for (var i = 0; i < calcTrace.length; i++) { + var _calcPt = convertToLonLatFn(calcTrace[i]) - if(_calcPt) { - arrayItemToCalcdata(trace, calcTrace[i], i); - _calcTrace.push(_calcPt); - } - } + if (_calcPt) { + arrayItemToCalcdata(trace, calcTrace[i], i) + _calcTrace.push(_calcPt) + } + } - if(subTypes.hasLines(trace) || trace.fill !== 'none') { - var lineCoords = geoJsonUtils.calcTraceToLineCoords(_calcTrace); + if (subTypes.hasLines(trace) || trace.fill !== 'none') { + var lineCoords = geoJsonUtils.calcTraceToLineCoords(_calcTrace) - var lineData = (trace.fill !== 'none') ? + var lineData = (trace.fill !== 'none') ? geoJsonUtils.makePolygon(lineCoords, trace) : - geoJsonUtils.makeLine(lineCoords, trace); + geoJsonUtils.makeLine(lineCoords, trace) - s.selectAll('path.js-line') + s.selectAll('path.js-line') .data([lineData]) .enter().append('path') - .classed('js-line', true); - } + .classed('js-line', true) + } - if(subTypes.hasMarkers(trace)) { - s.selectAll('path.point').data(_calcTrace) + if (subTypes.hasMarkers(trace)) { + s.selectAll('path.point').data(_calcTrace) .enter().append('path') - .classed('point', true); - } + .classed('point', true) + } - if(subTypes.hasText(trace)) { - s.selectAll('g').data(_calcTrace) + if (subTypes.hasText(trace)) { + s.selectAll('g').data(_calcTrace) .enter().append('g') - .append('text'); - } - }); + .append('text') + } + }) // call style here within topojson request callback - style(geo); -}; + style(geo) +} -function makeConvertToLonLatFn(trace, topojson) { - if(!Array.isArray(trace.locations)) return Lib.identity; +function makeConvertToLonLatFn (trace, topojson) { + if (!Array.isArray(trace.locations)) return Lib.identity - var features = getTopojsonFeatures(trace, topojson), - locationmode = trace.locationmode; + var features = getTopojsonFeatures(trace, topojson), + locationmode = trace.locationmode - return function(calcPt) { - var feature = locationToFeature(locationmode, calcPt.loc, features); + return function (calcPt) { + var feature = locationToFeature(locationmode, calcPt.loc, features) - if(feature) { - calcPt.lonlat = feature.properties.ct; - return calcPt; - } - else { + if (feature) { + calcPt.lonlat = feature.properties.ct + return calcPt + } else { // mutate gd.calcdata so that hoverPoints knows to skip this datum - calcPt.lonlat = [null, null]; - return false; - } - }; -} - -function arrayItemToCalcdata(trace, calcItem, i) { - var marker = trace.marker; - - function merge(traceAttr, calcAttr) { - arrayToCalcItem(traceAttr, calcItem, calcAttr, i); - } - - merge(trace.text, 'tx'); - merge(trace.textposition, 'tp'); - if(trace.textfont) { - merge(trace.textfont.size, 'ts'); - merge(trace.textfont.color, 'tc'); - merge(trace.textfont.family, 'tf'); + calcPt.lonlat = [null, null] + return false } + } +} - if(marker && marker.line) { - var markerLine = marker.line; - merge(marker.opacity, 'mo'); - merge(marker.symbol, 'mx'); - merge(marker.color, 'mc'); - merge(marker.size, 'ms'); - merge(markerLine.color, 'mlc'); - merge(markerLine.width, 'mlw'); - } +function arrayItemToCalcdata (trace, calcItem, i) { + var marker = trace.marker + + function merge (traceAttr, calcAttr) { + arrayToCalcItem(traceAttr, calcItem, calcAttr, i) + } + + merge(trace.text, 'tx') + merge(trace.textposition, 'tp') + if (trace.textfont) { + merge(trace.textfont.size, 'ts') + merge(trace.textfont.color, 'tc') + merge(trace.textfont.family, 'tf') + } + + if (marker && marker.line) { + var markerLine = marker.line + merge(marker.opacity, 'mo') + merge(marker.symbol, 'mx') + merge(marker.color, 'mc') + merge(marker.size, 'ms') + merge(markerLine.color, 'mlc') + merge(markerLine.width, 'mlw') + } } -function style(geo) { - var selection = geo.framework.selectAll('g.trace.scattergeo'); +function style (geo) { + var selection = geo.framework.selectAll('g.trace.scattergeo') - selection.style('opacity', function(calcTrace) { - return calcTrace[0].trace.opacity; - }); + selection.style('opacity', function (calcTrace) { + return calcTrace[0].trace.opacity + }) - selection.each(function(calcTrace) { - var trace = calcTrace[0].trace, - group = d3.select(this); + selection.each(function (calcTrace) { + var trace = calcTrace[0].trace, + group = d3.select(this) - group.selectAll('path.point') - .call(Drawing.pointStyle, trace); - group.selectAll('text') - .call(Drawing.textPointStyle, trace); - }); + group.selectAll('path.point') + .call(Drawing.pointStyle, trace) + group.selectAll('text') + .call(Drawing.textPointStyle, trace) + }) // this part is incompatible with Drawing.lineGroupStyle - selection.selectAll('path.js-line') + selection.selectAll('path.js-line') .style('fill', 'none') - .each(function(d) { - var path = d3.select(this), - trace = d.trace, - line = trace.line || {}; - - path.call(Color.stroke, line.color) - .call(Drawing.dashLine, line.dash || '', line.width || 0); - - if(trace.fill !== 'none') { - path.call(Color.fill, trace.fillcolor); - } - }); + .each(function (d) { + var path = d3.select(this), + trace = d.trace, + line = trace.line || {} + + path.call(Color.stroke, line.color) + .call(Drawing.dashLine, line.dash || '', line.width || 0) + + if (trace.fill !== 'none') { + path.call(Color.fill, trace.fillcolor) + } + }) } diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 2764714a5a7..72963794740 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -6,83 +6,83 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); +var scatterAttrs = require('../scatter/attributes') +var colorAttributes = require('../../components/colorscale/color_attributes') -var DASHES = require('../../constants/gl2d_dashes'); -var MARKERS = require('../../constants/gl_markers'); -var extendFlat = require('../../lib/extend').extendFlat; -var extendDeep = require('../../lib/extend').extendDeep; +var DASHES = require('../../constants/gl2d_dashes') +var MARKERS = require('../../constants/gl_markers') +var extendFlat = require('../../lib/extend').extendFlat +var extendDeep = require('../../lib/extend').extendDeep var scatterLineAttrs = scatterAttrs.line, - scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; + scatterMarkerAttrs = scatterAttrs.marker, + scatterMarkerLineAttrs = scatterMarkerAttrs.line module.exports = { - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (x,y) pair to appear on hover.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' - ].join(' ') - }), - mode: { - valType: 'flaglist', - flags: ['lines', 'markers'], - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.' - ].join(' ') + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y) pair to appear on hover.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y) coordinates.' + ].join(' ') + }), + mode: { + valType: 'flaglist', + flags: ['lines', 'markers'], + extras: ['none'], + role: 'info', + description: [ + 'Determines the drawing mode for this scatter trace.' + ].join(' ') + }, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: { + valType: 'enumerated', + values: Object.keys(DASHES), + dflt: 'solid', + role: 'style', + description: 'Sets the style of the lines.' + } + }, + marker: extendDeep({}, colorAttributes('marker'), { + symbol: { + valType: 'enumerated', + values: Object.keys(MARKERS), + dflt: 'circle', + arrayOk: true, + role: 'style', + description: 'Sets the marker symbol type.' }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: { - valType: 'enumerated', - values: Object.keys(DASHES), - dflt: 'solid', - role: 'style', - description: 'Sets the style of the lines.' - } - }, - marker: extendDeep({}, colorAttributes('marker'), { - symbol: { - valType: 'enumerated', - values: Object.keys(MARKERS), - dflt: 'circle', - arrayOk: true, - role: 'style', - description: 'Sets the marker symbol type.' - }, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: scatterMarkerAttrs.opacity, - showscale: scatterMarkerAttrs.showscale, - colorbar: scatterMarkerAttrs.colorbar, - line: extendDeep({}, colorAttributes('marker.line'), { - width: scatterMarkerLineAttrs.width - }) - }), - connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, { - values: ['none', 'tozeroy', 'tozerox'] - }), - fillcolor: scatterAttrs.fillcolor, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: scatterMarkerAttrs.opacity, + showscale: scatterMarkerAttrs.showscale, + colorbar: scatterMarkerAttrs.colorbar, + line: extendDeep({}, colorAttributes('marker.line'), { + width: scatterMarkerLineAttrs.width + }) + }), + connectgaps: scatterAttrs.connectgaps, + fill: extendFlat({}, scatterAttrs.fill, { + values: ['none', 'tozeroy', 'tozerox'] + }), + fillcolor: scatterAttrs.fillcolor, - error_y: scatterAttrs.error_y, - error_x: scatterAttrs.error_x -}; + error_y: scatterAttrs.error_y, + error_x: scatterAttrs.error_x +} diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index fcfa54eb6d4..9b9948896e2 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -6,224 +6,222 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var createScatter = require('gl-scatter2d'); -var createFancyScatter = require('gl-scatter2d-fancy'); -var createLine = require('gl-line2d'); -var createError = require('gl-error2d'); -var isNumeric = require('fast-isnumeric'); - -var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); -var autoType = require('../../plots/cartesian/axis_autotype'); -var ErrorBars = require('../../components/errorbars'); -var str2RGBArray = require('../../lib/str2rgbarray'); -var truncate = require('../../lib/typed_array_truncate'); -var formatColor = require('../../lib/gl_format_color'); -var subTypes = require('../scatter/subtypes'); -var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); -var getTraceColor = require('../scatter/get_trace_color'); -var MARKER_SYMBOLS = require('../../constants/gl_markers'); -var DASHES = require('../../constants/gl2d_dashes'); - -var AXES = ['xaxis', 'yaxis']; - - -function LineWithMarkers(scene, uid) { - this.scene = scene; - this.uid = uid; - this.type = 'scattergl'; - - this.pickXData = []; - this.pickYData = []; - this.xData = []; - this.yData = []; - this.textLabels = []; - this.color = 'rgb(0, 0, 0)'; - this.name = ''; - this.hoverinfo = 'all'; - this.connectgaps = true; - - this.idToIndex = []; - this.bounds = [0, 0, 0, 0]; - - this.hasLines = false; - this.lineOptions = { - positions: new Float64Array(0), - color: [0, 0, 0, 1], - width: 1, - fill: [false, false, false, false], - fillColor: [ +'use strict' + +var createScatter = require('gl-scatter2d') +var createFancyScatter = require('gl-scatter2d-fancy') +var createLine = require('gl-line2d') +var createError = require('gl-error2d') +var isNumeric = require('fast-isnumeric') + +var Lib = require('../../lib') +var Axes = require('../../plots/cartesian/axes') +var autoType = require('../../plots/cartesian/axis_autotype') +var ErrorBars = require('../../components/errorbars') +var str2RGBArray = require('../../lib/str2rgbarray') +var truncate = require('../../lib/typed_array_truncate') +var formatColor = require('../../lib/gl_format_color') +var subTypes = require('../scatter/subtypes') +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func') +var getTraceColor = require('../scatter/get_trace_color') +var MARKER_SYMBOLS = require('../../constants/gl_markers') +var DASHES = require('../../constants/gl2d_dashes') + +var AXES = ['xaxis', 'yaxis'] + +function LineWithMarkers (scene, uid) { + this.scene = scene + this.uid = uid + this.type = 'scattergl' + + this.pickXData = [] + this.pickYData = [] + this.xData = [] + this.yData = [] + this.textLabels = [] + this.color = 'rgb(0, 0, 0)' + this.name = '' + this.hoverinfo = 'all' + this.connectgaps = true + + this.idToIndex = [] + this.bounds = [0, 0, 0, 0] + + this.hasLines = false + this.lineOptions = { + positions: new Float64Array(0), + color: [0, 0, 0, 1], + width: 1, + fill: [false, false, false, false], + fillColor: [ [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1]], - dashes: [1] - }; - this.line = createLine(scene.glplot, this.lineOptions); - this.line._trace = this; - - this.hasErrorX = false; - this.errorXOptions = { - positions: new Float64Array(0), - errors: new Float64Array(0), - lineWidth: 1, - capSize: 0, - color: [0, 0, 0, 1] - }; - this.errorX = createError(scene.glplot, this.errorXOptions); - this.errorX._trace = this; - - this.hasErrorY = false; - this.errorYOptions = { - positions: new Float64Array(0), - errors: new Float64Array(0), - lineWidth: 1, - capSize: 0, - color: [0, 0, 0, 1] - }; - this.errorY = createError(scene.glplot, this.errorYOptions); - this.errorY._trace = this; - - this.hasMarkers = false; - this.scatterOptions = { - positions: new Float64Array(0), - sizes: [], - colors: [], - glyphs: [], - borderWidths: [], - borderColors: [], - size: 12, - color: [0, 0, 0, 1], - borderSize: 1, - borderColor: [0, 0, 0, 1] - }; - this.scatter = createScatter(scene.glplot, this.scatterOptions); - this.scatter._trace = this; - this.fancyScatter = createFancyScatter(scene.glplot, this.scatterOptions); - this.fancyScatter._trace = this; - - this.isVisible = false; + dashes: [1] + } + this.line = createLine(scene.glplot, this.lineOptions) + this.line._trace = this + + this.hasErrorX = false + this.errorXOptions = { + positions: new Float64Array(0), + errors: new Float64Array(0), + lineWidth: 1, + capSize: 0, + color: [0, 0, 0, 1] + } + this.errorX = createError(scene.glplot, this.errorXOptions) + this.errorX._trace = this + + this.hasErrorY = false + this.errorYOptions = { + positions: new Float64Array(0), + errors: new Float64Array(0), + lineWidth: 1, + capSize: 0, + color: [0, 0, 0, 1] + } + this.errorY = createError(scene.glplot, this.errorYOptions) + this.errorY._trace = this + + this.hasMarkers = false + this.scatterOptions = { + positions: new Float64Array(0), + sizes: [], + colors: [], + glyphs: [], + borderWidths: [], + borderColors: [], + size: 12, + color: [0, 0, 0, 1], + borderSize: 1, + borderColor: [0, 0, 0, 1] + } + this.scatter = createScatter(scene.glplot, this.scatterOptions) + this.scatter._trace = this + this.fancyScatter = createFancyScatter(scene.glplot, this.scatterOptions) + this.fancyScatter._trace = this + + this.isVisible = false } -var proto = LineWithMarkers.prototype; +var proto = LineWithMarkers.prototype -proto.handlePick = function(pickResult) { - var index = pickResult.pointId; +proto.handlePick = function (pickResult) { + var index = pickResult.pointId - if(pickResult.object !== this.line || this.connectgaps) { - index = this.idToIndex[pickResult.pointId]; - } + if (pickResult.object !== this.line || this.connectgaps) { + index = this.idToIndex[pickResult.pointId] + } - var x = this.pickXData[index]; + var x = this.pickXData[index] - return { - trace: this, - dataCoord: pickResult.dataCoord, - traceCoord: [ - isNumeric(x) || !Lib.isDateTime(x) ? x : Lib.dateTime2ms(x), - this.pickYData[index] - ], - textLabel: Array.isArray(this.textLabels) ? + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: [ + isNumeric(x) || !Lib.isDateTime(x) ? x : Lib.dateTime2ms(x), + this.pickYData[index] + ], + textLabel: Array.isArray(this.textLabels) ? this.textLabels[index] : this.textLabels, - color: Array.isArray(this.color) ? + color: Array.isArray(this.color) ? this.color[index] : this.color, - name: this.name, - pointIndex: index, - hoverinfo: this.hoverinfo - }; -}; + name: this.name, + pointIndex: index, + hoverinfo: this.hoverinfo + } +} // check if trace is fancy -proto.isFancy = function(options) { - if(this.scene.xaxis.type !== 'linear' && this.scene.xaxis.type !== 'date') return true; - if(this.scene.yaxis.type !== 'linear') return true; +proto.isFancy = function (options) { + if (this.scene.xaxis.type !== 'linear' && this.scene.xaxis.type !== 'date') return true + if (this.scene.yaxis.type !== 'linear') return true - if(!options.x || !options.y) return true; + if (!options.x || !options.y) return true - if(this.hasMarkers) { - var marker = options.marker || {}; + if (this.hasMarkers) { + var marker = options.marker || {} - if(Array.isArray(marker.symbol) || + if (Array.isArray(marker.symbol) || marker.symbol !== 'circle' || Array.isArray(marker.size) || Array.isArray(marker.color) || Array.isArray(marker.line.width) || Array.isArray(marker.line.color) || Array.isArray(marker.opacity) - ) return true; - } + ) return true + } - if(this.hasLines && !this.connectgaps) return true; + if (this.hasLines && !this.connectgaps) return true - if(this.hasErrorX) return true; - if(this.hasErrorY) return true; + if (this.hasErrorX) return true + if (this.hasErrorY) return true - return false; -}; + return false +} // handle the situation where values can be array-like or not array like -function convertArray(convert, data, count) { - if(!Array.isArray(data)) data = [data]; +function convertArray (convert, data, count) { + if (!Array.isArray(data)) data = [data] - return _convertArray(convert, data, count); + return _convertArray(convert, data, count) } -function _convertArray(convert, data, count) { - var result = new Array(count), - data0 = data[0]; +function _convertArray (convert, data, count) { + var result = new Array(count), + data0 = data[0] - for(var i = 0; i < count; ++i) { - result[i] = (i >= data.length) ? + for (var i = 0; i < count; ++i) { + result[i] = (i >= data.length) ? convert(data0) : - convert(data[i]); - } + convert(data[i]) + } - return result; + return result } -var convertNumber = convertArray.bind(null, function(x) { return +x; }); -var convertColorBase = convertArray.bind(null, str2RGBArray); -var convertSymbol = convertArray.bind(null, function(x) { - return MARKER_SYMBOLS[x] || '●'; -}); +var convertNumber = convertArray.bind(null, function (x) { return +x }) +var convertColorBase = convertArray.bind(null, str2RGBArray) +var convertSymbol = convertArray.bind(null, function (x) { + return MARKER_SYMBOLS[x] || '●' +}) -function convertColor(color, opacity, count) { - return _convertColor( +function convertColor (color, opacity, count) { + return _convertColor( convertColorBase(color, count), convertNumber(opacity, count), count - ); + ) } -function convertColorScale(containerIn, markerOpacity, traceOpacity, count) { - var colors = formatColor(containerIn, markerOpacity, count); +function convertColorScale (containerIn, markerOpacity, traceOpacity, count) { + var colors = formatColor(containerIn, markerOpacity, count) - colors = Array.isArray(colors[0]) ? + colors = Array.isArray(colors[0]) ? colors : - _convertArray(Lib.identity, [colors], count); + _convertArray(Lib.identity, [colors], count) - return _convertColor( + return _convertColor( colors, convertNumber(traceOpacity, count), count - ); + ) } -function _convertColor(colors, opacities, count) { - var result = new Array(4 * count); +function _convertColor (colors, opacities, count) { + var result = new Array(4 * count) - for(var i = 0; i < count; ++i) { - for(var j = 0; j < 3; ++j) result[4 * i + j] = colors[i][j]; + for (var i = 0; i < count; ++i) { + for (var j = 0; j < 3; ++j) result[4 * i + j] = colors[i][j] - result[4 * i + 3] = colors[i][3] * opacities[i]; - } + result[4 * i + 3] = colors[i][3] * opacities[i] + } - return result; + return result } /* Order is important here to get the correct laying: @@ -232,42 +230,39 @@ function _convertColor(colors, opacities, count) { * - errorY * - markers */ -proto.update = function(options) { - if(options.visible !== true) { - this.isVisible = false; - this.hasLines = false; - this.hasErrorX = false; - this.hasErrorY = false; - this.hasMarkers = false; - } - else { - this.isVisible = true; - this.hasLines = subTypes.hasLines(options); - this.hasErrorX = options.error_x.visible === true; - this.hasErrorY = options.error_y.visible === true; - this.hasMarkers = subTypes.hasMarkers(options); - } - - this.textLabels = options.text; - this.name = options.name; - this.hoverinfo = options.hoverinfo; - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; - this.connectgaps = !!options.connectgaps; - - if(!this.isVisible) { - this.clear(); - } - else if(this.isFancy(options)) { - this.updateFancy(options); - } - else { - this.updateFast(options); - } +proto.update = function (options) { + if (options.visible !== true) { + this.isVisible = false + this.hasLines = false + this.hasErrorX = false + this.hasErrorY = false + this.hasMarkers = false + } else { + this.isVisible = true + this.hasLines = subTypes.hasLines(options) + this.hasErrorX = options.error_x.visible === true + this.hasErrorY = options.error_y.visible === true + this.hasMarkers = subTypes.hasMarkers(options) + } + + this.textLabels = options.text + this.name = options.name + this.hoverinfo = options.hoverinfo + this.bounds = [Infinity, Infinity, -Infinity, -Infinity] + this.connectgaps = !!options.connectgaps + + if (!this.isVisible) { + this.clear() + } else if (this.isFancy(options)) { + this.updateFancy(options) + } else { + this.updateFast(options) + } // not quite on-par with 'scatter', but close enough for now // does not handle the colorscale case - this.color = getTraceColor(options, {}); -}; + this.color = getTraceColor(options, {}) +} // We'd ideally know that all values are of fast types; sampling gives no certainty but faster // (for the future, typed arrays can guarantee it, and Date values can be done with @@ -277,360 +272,354 @@ proto.update = function(options) { // Patterned from axis_defaults.js:moreDates // Code DRYing is not done to preserve the most direct compilation possible for speed; // also, there are quite a few differences -function allFastTypesLikely(a) { - var len = a.length, - inc = Math.max(1, (len - 1) / Math.min(Math.max(len, 1), 1000)), - ai; - - for(var i = 0; i < len; i += inc) { - ai = a[Math.floor(i)]; - if(!isNumeric(ai) && !(ai instanceof Date)) { - return false; - } +function allFastTypesLikely (a) { + var len = a.length, + inc = Math.max(1, (len - 1) / Math.min(Math.max(len, 1), 1000)), + ai + + for (var i = 0; i < len; i += inc) { + ai = a[Math.floor(i)] + if (!isNumeric(ai) && !(ai instanceof Date)) { + return false } + } - return true; + return true } -proto.clear = function() { - this.lineOptions.positions = new Float64Array(0); - this.line.update(this.lineOptions); +proto.clear = function () { + this.lineOptions.positions = new Float64Array(0) + this.line.update(this.lineOptions) - this.errorXOptions.positions = new Float64Array(0); - this.errorX.update(this.errorXOptions); + this.errorXOptions.positions = new Float64Array(0) + this.errorX.update(this.errorXOptions) - this.errorYOptions.positions = new Float64Array(0); - this.errorY.update(this.errorYOptions); + this.errorYOptions.positions = new Float64Array(0) + this.errorY.update(this.errorYOptions) - this.scatterOptions.positions = new Float64Array(0); - this.scatterOptions.glyphs = []; - this.scatter.update(this.scatterOptions); - this.fancyScatter.update(this.scatterOptions); -}; + this.scatterOptions.positions = new Float64Array(0) + this.scatterOptions.glyphs = [] + this.scatter.update(this.scatterOptions) + this.fancyScatter.update(this.scatterOptions) +} -proto.updateFast = function(options) { - var x = this.xData = this.pickXData = options.x; - var y = this.yData = this.pickYData = options.y; +proto.updateFast = function (options) { + var x = this.xData = this.pickXData = options.x + var y = this.yData = this.pickYData = options.y - var len = x.length, - idToIndex = new Array(len), - positions = new Float64Array(2 * len), - bounds = this.bounds, - pId = 0, - ptr = 0; + var len = x.length, + idToIndex = new Array(len), + positions = new Float64Array(2 * len), + bounds = this.bounds, + pId = 0, + ptr = 0 - var xx, yy; + var xx, yy - var xcalendar = options.xcalendar; + var xcalendar = options.xcalendar - var fastType = allFastTypesLikely(x); - var isDateTime = !fastType && autoType(x, xcalendar) === 'date'; + var fastType = allFastTypesLikely(x) + var isDateTime = !fastType && autoType(x, xcalendar) === 'date' // TODO add 'very fast' mode that bypasses this loop // TODO bypass this on modebar +/- zoom - if(fastType || isDateTime) { - - for(var i = 0; i < len; ++i) { - xx = x[i]; - yy = y[i]; - - if(isNumeric(yy)) { - - if(!fastType) { - xx = Lib.dateTime2ms(xx, xcalendar); - } + if (fastType || isDateTime) { + for (var i = 0; i < len; ++i) { + xx = x[i] + yy = y[i] + + if (isNumeric(yy)) { + if (!fastType) { + xx = Lib.dateTime2ms(xx, xcalendar) + } - idToIndex[pId++] = i; + idToIndex[pId++] = i - positions[ptr++] = xx; - positions[ptr++] = yy; + positions[ptr++] = xx + positions[ptr++] = yy - bounds[0] = Math.min(bounds[0], xx); - bounds[1] = Math.min(bounds[1], yy); - bounds[2] = Math.max(bounds[2], xx); - bounds[3] = Math.max(bounds[3], yy); - } - } + bounds[0] = Math.min(bounds[0], xx) + bounds[1] = Math.min(bounds[1], yy) + bounds[2] = Math.max(bounds[2], xx) + bounds[3] = Math.max(bounds[3], yy) + } } + } - positions = truncate(positions, ptr); - this.idToIndex = idToIndex; + positions = truncate(positions, ptr) + this.idToIndex = idToIndex - this.updateLines(options, positions); - this.updateError('X', options); - this.updateError('Y', options); + this.updateLines(options, positions) + this.updateError('X', options) + this.updateError('Y', options) - var markerSize; + var markerSize - if(this.hasMarkers) { - this.scatterOptions.positions = positions; + if (this.hasMarkers) { + this.scatterOptions.positions = positions - var markerColor = str2RGBArray(options.marker.color), - borderColor = str2RGBArray(options.marker.line.color), - opacity = (options.opacity) * (options.marker.opacity); + var markerColor = str2RGBArray(options.marker.color), + borderColor = str2RGBArray(options.marker.line.color), + opacity = (options.opacity) * (options.marker.opacity) - markerColor[3] *= opacity; - this.scatterOptions.color = markerColor; + markerColor[3] *= opacity + this.scatterOptions.color = markerColor - borderColor[3] *= opacity; - this.scatterOptions.borderColor = borderColor; + borderColor[3] *= opacity + this.scatterOptions.borderColor = borderColor - markerSize = options.marker.size; - this.scatterOptions.size = markerSize; - this.scatterOptions.borderSize = options.marker.line.width; + markerSize = options.marker.size + this.scatterOptions.size = markerSize + this.scatterOptions.borderSize = options.marker.line.width - this.scatter.update(this.scatterOptions); - } - else { - this.scatterOptions.positions = new Float64Array(0); - this.scatterOptions.glyphs = []; - this.scatter.update(this.scatterOptions); - } + this.scatter.update(this.scatterOptions) + } else { + this.scatterOptions.positions = new Float64Array(0) + this.scatterOptions.glyphs = [] + this.scatter.update(this.scatterOptions) + } // turn off fancy scatter plot - this.scatterOptions.positions = new Float64Array(0); - this.scatterOptions.glyphs = []; - this.fancyScatter.update(this.scatterOptions); + this.scatterOptions.positions = new Float64Array(0) + this.scatterOptions.glyphs = [] + this.fancyScatter.update(this.scatterOptions) // add item for autorange routine - this.expandAxesFast(bounds, markerSize); -}; + this.expandAxesFast(bounds, markerSize) +} -proto.updateFancy = function(options) { - var scene = this.scene, - xaxis = scene.xaxis, - yaxis = scene.yaxis, - bounds = this.bounds; +proto.updateFancy = function (options) { + var scene = this.scene, + xaxis = scene.xaxis, + yaxis = scene.yaxis, + bounds = this.bounds // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = this.pickXData = xaxis.makeCalcdata(options, 'x').slice(); - var y = this.pickYData = yaxis.makeCalcdata(options, 'y').slice(); + var x = this.pickXData = xaxis.makeCalcdata(options, 'x').slice() + var y = this.pickYData = yaxis.makeCalcdata(options, 'y').slice() - this.xData = x.slice(); - this.yData = y.slice(); + this.xData = x.slice() + this.yData = y.slice() // get error values - var errorVals = ErrorBars.calcFromTrace(options, scene.fullLayout); + var errorVals = ErrorBars.calcFromTrace(options, scene.fullLayout) - var len = x.length, - idToIndex = new Array(len), - positions = new Float64Array(2 * len), - errorsX = new Float64Array(4 * len), - errorsY = new Float64Array(4 * len), - pId = 0, - ptr = 0, - ptrX = 0, - ptrY = 0; + var len = x.length, + idToIndex = new Array(len), + positions = new Float64Array(2 * len), + errorsX = new Float64Array(4 * len), + errorsY = new Float64Array(4 * len), + pId = 0, + ptr = 0, + ptrX = 0, + ptrY = 0 - var getX = (xaxis.type === 'log') ? xaxis.d2l : function(x) { return x; }; - var getY = (yaxis.type === 'log') ? yaxis.d2l : function(y) { return y; }; + var getX = (xaxis.type === 'log') ? xaxis.d2l : function (x) { return x } + var getY = (yaxis.type === 'log') ? yaxis.d2l : function (y) { return y } - var i, j, xx, yy, ex0, ex1, ey0, ey1; + var i, j, xx, yy, ex0, ex1, ey0, ey1 - for(i = 0; i < len; ++i) { - this.xData[i] = xx = getX(x[i]); - this.yData[i] = yy = getY(y[i]); + for (i = 0; i < len; ++i) { + this.xData[i] = xx = getX(x[i]) + this.yData[i] = yy = getY(y[i]) - if(isNaN(xx) || isNaN(yy)) continue; + if (isNaN(xx) || isNaN(yy)) continue - idToIndex[pId++] = i; + idToIndex[pId++] = i - positions[ptr++] = xx; - positions[ptr++] = yy; + positions[ptr++] = xx + positions[ptr++] = yy - ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; - ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; + ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0 + ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0 + errorsX[ptrX++] = 0 + errorsX[ptrX++] = 0 - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; - ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; + errorsY[ptrY++] = 0 + errorsY[ptrY++] = 0 + ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0 + ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0 - bounds[0] = Math.min(bounds[0], xx - ex0); - bounds[1] = Math.min(bounds[1], yy - ey0); - bounds[2] = Math.max(bounds[2], xx + ex1); - bounds[3] = Math.max(bounds[3], yy + ey1); - } + bounds[0] = Math.min(bounds[0], xx - ex0) + bounds[1] = Math.min(bounds[1], yy - ey0) + bounds[2] = Math.max(bounds[2], xx + ex1) + bounds[3] = Math.max(bounds[3], yy + ey1) + } - positions = truncate(positions, ptr); - this.idToIndex = idToIndex; + positions = truncate(positions, ptr) + this.idToIndex = idToIndex - this.updateLines(options, positions); - this.updateError('X', options, positions, errorsX); - this.updateError('Y', options, positions, errorsY); + this.updateLines(options, positions) + this.updateError('X', options, positions, errorsX) + this.updateError('Y', options, positions, errorsY) - var sizes; + var sizes - if(this.hasMarkers) { - this.scatterOptions.positions = positions; + if (this.hasMarkers) { + this.scatterOptions.positions = positions // TODO rewrite convert function so that // we don't have to loop through the data another time - this.scatterOptions.sizes = new Array(pId); - this.scatterOptions.glyphs = new Array(pId); - this.scatterOptions.borderWidths = new Array(pId); - this.scatterOptions.colors = new Array(pId * 4); - this.scatterOptions.borderColors = new Array(pId * 4); - - var markerSizeFunc = makeBubbleSizeFn(options), - markerOpts = options.marker, - markerOpacity = markerOpts.opacity, - traceOpacity = options.opacity, - colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len), - glyphs = convertSymbol(markerOpts.symbol, len), - borderWidths = convertNumber(markerOpts.line.width, len), - borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len), - index; - - sizes = convertArray(markerSizeFunc, markerOpts.size, len); - - for(i = 0; i < pId; ++i) { - index = idToIndex[i]; - - this.scatterOptions.sizes[i] = 4.0 * sizes[index]; - this.scatterOptions.glyphs[i] = glyphs[index]; - this.scatterOptions.borderWidths[i] = 0.5 * borderWidths[index]; - - for(j = 0; j < 4; ++j) { - this.scatterOptions.colors[4 * i + j] = colors[4 * index + j]; - this.scatterOptions.borderColors[4 * i + j] = borderColors[4 * index + j]; - } - } - - this.fancyScatter.update(this.scatterOptions); - } - else { - this.scatterOptions.positions = new Float64Array(0); - this.scatterOptions.glyphs = []; - this.fancyScatter.update(this.scatterOptions); + this.scatterOptions.sizes = new Array(pId) + this.scatterOptions.glyphs = new Array(pId) + this.scatterOptions.borderWidths = new Array(pId) + this.scatterOptions.colors = new Array(pId * 4) + this.scatterOptions.borderColors = new Array(pId * 4) + + var markerSizeFunc = makeBubbleSizeFn(options), + markerOpts = options.marker, + markerOpacity = markerOpts.opacity, + traceOpacity = options.opacity, + colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len), + glyphs = convertSymbol(markerOpts.symbol, len), + borderWidths = convertNumber(markerOpts.line.width, len), + borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len), + index + + sizes = convertArray(markerSizeFunc, markerOpts.size, len) + + for (i = 0; i < pId; ++i) { + index = idToIndex[i] + + this.scatterOptions.sizes[i] = 4.0 * sizes[index] + this.scatterOptions.glyphs[i] = glyphs[index] + this.scatterOptions.borderWidths[i] = 0.5 * borderWidths[index] + + for (j = 0; j < 4; ++j) { + this.scatterOptions.colors[4 * i + j] = colors[4 * index + j] + this.scatterOptions.borderColors[4 * i + j] = borderColors[4 * index + j] + } } + this.fancyScatter.update(this.scatterOptions) + } else { + this.scatterOptions.positions = new Float64Array(0) + this.scatterOptions.glyphs = [] + this.fancyScatter.update(this.scatterOptions) + } + // turn off fast scatter plot - this.scatterOptions.positions = new Float64Array(0); - this.scatterOptions.glyphs = []; - this.scatter.update(this.scatterOptions); + this.scatterOptions.positions = new Float64Array(0) + this.scatterOptions.glyphs = [] + this.scatter.update(this.scatterOptions) // add item for autorange routine - this.expandAxesFancy(x, y, sizes); -}; - -proto.updateLines = function(options, positions) { - var i; - - if(this.hasLines) { - var linePositions = positions; + this.expandAxesFancy(x, y, sizes) +} - if(!options.connectgaps) { - var p = 0; - var x = this.xData; - var y = this.yData; - linePositions = new Float64Array(2 * x.length); +proto.updateLines = function (options, positions) { + var i - for(i = 0; i < x.length; ++i) { - linePositions[p++] = x[i]; - linePositions[p++] = y[i]; - } - } + if (this.hasLines) { + var linePositions = positions - this.lineOptions.positions = linePositions; + if (!options.connectgaps) { + var p = 0 + var x = this.xData + var y = this.yData + linePositions = new Float64Array(2 * x.length) - var lineColor = convertColor(options.line.color, options.opacity, 1), - lineWidth = Math.round(0.5 * this.lineOptions.width), - dashes = (DASHES[options.line.dash] || [1]).slice(); + for (i = 0; i < x.length; ++i) { + linePositions[p++] = x[i] + linePositions[p++] = y[i] + } + } - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + this.lineOptions.positions = linePositions - switch(options.fill) { - case 'tozeroy': - this.lineOptions.fill = [false, true, false, false]; - break; - case 'tozerox': - this.lineOptions.fill = [true, false, false, false]; - break; - default: - this.lineOptions.fill = [false, false, false, false]; - break; - } + var lineColor = convertColor(options.line.color, options.opacity, 1), + lineWidth = Math.round(0.5 * this.lineOptions.width), + dashes = (DASHES[options.line.dash] || [1]).slice() - var fillColor = str2RGBArray(options.fillcolor); + for (i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth - this.lineOptions.color = lineColor; - this.lineOptions.width = 2.0 * options.line.width; - this.lineOptions.dashes = dashes; - this.lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - } - else { - this.lineOptions.positions = new Float64Array(0); + switch (options.fill) { + case 'tozeroy': + this.lineOptions.fill = [false, true, false, false] + break + case 'tozerox': + this.lineOptions.fill = [true, false, false, false] + break + default: + this.lineOptions.fill = [false, false, false, false] + break } - this.line.update(this.lineOptions); -}; + var fillColor = str2RGBArray(options.fillcolor) -proto.updateError = function(axLetter, options, positions, errors) { - var errorObj = this['error' + axLetter], - errorOptions = options['error_' + axLetter.toLowerCase()], - errorObjOptions = this['error' + axLetter + 'Options']; + this.lineOptions.color = lineColor + this.lineOptions.width = 2.0 * options.line.width + this.lineOptions.dashes = dashes + this.lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor] + } else { + this.lineOptions.positions = new Float64Array(0) + } - if(axLetter.toLowerCase() === 'x' && errorOptions.copy_ystyle) { - errorOptions = options.error_y; - } - - if(this['hasError' + axLetter]) { - errorObjOptions.positions = positions; - errorObjOptions.errors = errors; - errorObjOptions.capSize = errorOptions.width; - errorObjOptions.lineWidth = errorOptions.thickness / 2; // ballpark rescaling - errorObjOptions.color = convertColor(errorOptions.color, 1, 1); - } - else { - errorObjOptions.positions = new Float64Array(0); - } + this.line.update(this.lineOptions) +} - errorObj.update(errorObjOptions); -}; +proto.updateError = function (axLetter, options, positions, errors) { + var errorObj = this['error' + axLetter], + errorOptions = options['error_' + axLetter.toLowerCase()], + errorObjOptions = this['error' + axLetter + 'Options'] + + if (axLetter.toLowerCase() === 'x' && errorOptions.copy_ystyle) { + errorOptions = options.error_y + } + + if (this['hasError' + axLetter]) { + errorObjOptions.positions = positions + errorObjOptions.errors = errors + errorObjOptions.capSize = errorOptions.width + errorObjOptions.lineWidth = errorOptions.thickness / 2 // ballpark rescaling + errorObjOptions.color = convertColor(errorOptions.color, 1, 1) + } else { + errorObjOptions.positions = new Float64Array(0) + } + + errorObj.update(errorObjOptions) +} -proto.expandAxesFast = function(bounds, markerSize) { - var pad = markerSize || 10; - var ax, min, max; +proto.expandAxesFast = function (bounds, markerSize) { + var pad = markerSize || 10 + var ax, min, max - for(var i = 0; i < 2; i++) { - ax = this.scene[AXES[i]]; + for (var i = 0; i < 2; i++) { + ax = this.scene[AXES[i]] - min = ax._min; - if(!min) min = []; - min.push({ val: bounds[i], pad: pad }); + min = ax._min + if (!min) min = [] + min.push({ val: bounds[i], pad: pad }) - max = ax._max; - if(!max) max = []; - max.push({ val: bounds[i + 2], pad: pad }); - } -}; + max = ax._max + if (!max) max = [] + max.push({ val: bounds[i + 2], pad: pad }) + } +} // not quite on-par with 'scatter' (scatter fill in several other expand options) // but close enough for now -proto.expandAxesFancy = function(x, y, ppad) { - var scene = this.scene, - expandOpts = { padded: true, ppad: ppad }; - - Axes.expand(scene.xaxis, x, expandOpts); - Axes.expand(scene.yaxis, y, expandOpts); -}; - -proto.dispose = function() { - this.line.dispose(); - this.errorX.dispose(); - this.errorY.dispose(); - this.scatter.dispose(); - this.fancyScatter.dispose(); -}; - -function createLineWithMarkers(scene, data) { - var plot = new LineWithMarkers(scene, data.uid); - plot.update(data); - return plot; +proto.expandAxesFancy = function (x, y, ppad) { + var scene = this.scene, + expandOpts = { padded: true, ppad: ppad } + + Axes.expand(scene.xaxis, x, expandOpts) + Axes.expand(scene.yaxis, y, expandOpts) +} + +proto.dispose = function () { + this.line.dispose() + this.errorX.dispose() + this.errorY.dispose() + this.scatter.dispose() + this.fancyScatter.dispose() +} + +function createLineWithMarkers (scene, data) { + var plot = new LineWithMarkers(scene, data.uid) + plot.update(data) + return plot } -module.exports = createLineWithMarkers; +module.exports = createLineWithMarkers diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 442363ae113..92455370c3c 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -6,50 +6,48 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var Lib = require('../../lib'); - -var constants = require('../scatter/constants'); -var subTypes = require('../scatter/subtypes'); -var handleXYDefaults = require('../scatter/xy_defaults'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); - -var attributes = require('./attributes'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); - - if(subTypes.hasLines(traceOut)) { - coerce('connectgaps'); - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - } - - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); -}; +'use strict' + +var Lib = require('../../lib') + +var constants = require('../scatter/constants') +var subTypes = require('../scatter/subtypes') +var handleXYDefaults = require('../scatter/xy_defaults') +var handleMarkerDefaults = require('../scatter/marker_defaults') +var handleLineDefaults = require('../scatter/line_defaults') +var handleFillColorDefaults = require('../scatter/fillcolor_defaults') +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults') + +var attributes = require('./attributes') + +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce) + if (!len) { + traceOut.visible = false + return + } + + coerce('text') + coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines') + + if (subTypes.hasLines(traceOut)) { + coerce('connectgaps') + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } + + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } + + coerce('fill') + if (traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce) + } + + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}) + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}) +} diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index d5e71241a46..8d0663b120a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -6,29 +6,29 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var ScatterGl = {}; +var ScatterGl = {} -ScatterGl.attributes = require('./attributes'); -ScatterGl.supplyDefaults = require('./defaults'); -ScatterGl.colorbar = require('../scatter/colorbar'); +ScatterGl.attributes = require('./attributes') +ScatterGl.supplyDefaults = require('./defaults') +ScatterGl.colorbar = require('../scatter/colorbar') // reuse the Scatter3D 'dummy' calc step so that legends know what to do -ScatterGl.calc = require('../scatter3d/calc'); -ScatterGl.plot = require('./convert'); +ScatterGl.calc = require('../scatter3d/calc') +ScatterGl.plot = require('./convert') -ScatterGl.moduleType = 'trace'; -ScatterGl.name = 'scattergl'; -ScatterGl.basePlotModule = require('../../plots/gl2d'); -ScatterGl.categories = ['gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend']; +ScatterGl.moduleType = 'trace' +ScatterGl.name = 'scattergl' +ScatterGl.basePlotModule = require('../../plots/gl2d') +ScatterGl.categories = ['gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend'] ScatterGl.meta = { - description: [ - 'The data visualized as scatter point or lines is set in `x` and `y`', - 'using the WebGl plotting engine.', - 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', - 'to a numerical arrays.' - ].join(' ') -}; + description: [ + 'The data visualized as scatter point or lines is set in `x` and `y`', + 'using the WebGl plotting engine.', + 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', + 'to a numerical arrays.' + ].join(' ') +} -module.exports = ScatterGl; +module.exports = ScatterGl diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 034f85a0b43..dd7e748c36d 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -6,101 +6,100 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterGeoAttrs = require('../scattergeo/attributes'); -var scatterAttrs = require('../scatter/attributes'); -var mapboxAttrs = require('../../plots/mapbox/layout_attributes'); -var plotAttrs = require('../../plots/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var scatterGeoAttrs = require('../scattergeo/attributes') +var scatterAttrs = require('../scatter/attributes') +var mapboxAttrs = require('../../plots/mapbox/layout_attributes') +var plotAttrs = require('../../plots/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; - -var lineAttrs = scatterGeoAttrs.line; -var markerAttrs = scatterGeoAttrs.marker; +var extendFlat = require('../../lib/extend').extendFlat +var lineAttrs = scatterGeoAttrs.line +var markerAttrs = scatterGeoAttrs.marker module.exports = { - lon: scatterGeoAttrs.lon, - lat: scatterGeoAttrs.lat, + lon: scatterGeoAttrs.lon, + lat: scatterGeoAttrs.lat, // locations // locationmode - mode: { - valType: 'flaglist', - flags: ['lines', 'markers', 'text'], - dflt: 'markers', - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.', - 'If the provided `mode` includes *text* then the `text` elements', - 'appear at the coordinates. Otherwise, the `text` elements', - 'appear on hover.' - ].join(' ') - }, - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (lon,lat) pair', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.' - ].join(' ') - }), - - line: { - color: lineAttrs.color, - width: lineAttrs.width, + mode: { + valType: 'flaglist', + flags: ['lines', 'markers', 'text'], + dflt: 'markers', + extras: ['none'], + role: 'info', + description: [ + 'Determines the drawing mode for this scatter trace.', + 'If the provided `mode` includes *text* then the `text` elements', + 'appear at the coordinates. Otherwise, the `text` elements', + 'appear on hover.' + ].join(' ') + }, + + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (lon,lat) coordinates.' + ].join(' ') + }), + + line: { + color: lineAttrs.color, + width: lineAttrs.width, // TODO - dash: lineAttrs.dash + dash: lineAttrs.dash + }, + + connectgaps: scatterAttrs.connectgaps, + + marker: { + symbol: { + valType: 'string', + dflt: 'circle', + role: 'style', + arrayOk: true, + description: [ + 'Sets the marker symbol.', + 'Full list: https://www.mapbox.com/maki-icons/', + 'Note that the array `marker.color` and `marker.size`', + 'are only available for *circle* symbols.' + ].join(' ') }, - - connectgaps: scatterAttrs.connectgaps, - - marker: { - symbol: { - valType: 'string', - dflt: 'circle', - role: 'style', - arrayOk: true, - description: [ - 'Sets the marker symbol.', - 'Full list: https://www.mapbox.com/maki-icons/', - 'Note that the array `marker.color` and `marker.size`', - 'are only available for *circle* symbols.' - ].join(' ') - }, - opacity: extendFlat({}, markerAttrs.opacity, { - arrayOk: false - }), - size: markerAttrs.size, - sizeref: markerAttrs.sizeref, - sizemin: markerAttrs.sizemin, - sizemode: markerAttrs.sizemode, - color: markerAttrs.color, - colorscale: markerAttrs.colorscale, - cauto: markerAttrs.cauto, - cmax: markerAttrs.cmax, - cmin: markerAttrs.cmin, - autocolorscale: markerAttrs.autocolorscale, - reversescale: markerAttrs.reversescale, - showscale: markerAttrs.showscale, - colorbar: colorbarAttrs + opacity: extendFlat({}, markerAttrs.opacity, { + arrayOk: false + }), + size: markerAttrs.size, + sizeref: markerAttrs.sizeref, + sizemin: markerAttrs.sizemin, + sizemode: markerAttrs.sizemode, + color: markerAttrs.color, + colorscale: markerAttrs.colorscale, + cauto: markerAttrs.cauto, + cmax: markerAttrs.cmax, + cmin: markerAttrs.cmin, + autocolorscale: markerAttrs.autocolorscale, + reversescale: markerAttrs.reversescale, + showscale: markerAttrs.showscale, + colorbar: colorbarAttrs // line - }, + }, - fill: scatterGeoAttrs.fill, - fillcolor: scatterAttrs.fillcolor, + fill: scatterGeoAttrs.fill, + fillcolor: scatterAttrs.fillcolor, - textfont: mapboxAttrs.layers.symbol.textfont, - textposition: mapboxAttrs.layers.symbol.textposition, + textfont: mapboxAttrs.layers.symbol.textfont, + textposition: mapboxAttrs.layers.symbol.textposition, - hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['lon', 'lat', 'text', 'name'] - }), -}; + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['lon', 'lat', 'text', 'name'] + }) +} diff --git a/src/traces/scattermapbox/calc.js b/src/traces/scattermapbox/calc.js index 6f6230851e8..f74fdf8b0fa 100644 --- a/src/traces/scattermapbox/calc.js +++ b/src/traces/scattermapbox/calc.js @@ -6,32 +6,30 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Colorscale = require('../../components/colorscale') -var Lib = require('../../lib'); -var Colorscale = require('../../components/colorscale'); +var subtypes = require('../scatter/subtypes') +var calcMarkerColorscale = require('../scatter/colorscale_calc') +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func') -var subtypes = require('../scatter/subtypes'); -var calcMarkerColorscale = require('../scatter/colorscale_calc'); -var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); +module.exports = function calc (gd, trace) { + var len = trace.lon.length, + marker = trace.marker + var hasMarkers = subtypes.hasMarkers(trace), + hasColorArray = (hasMarkers && Array.isArray(marker.color)), + hasSizeArray = (hasMarkers && Array.isArray(marker.size)), + hasSymbolArray = (hasMarkers && Array.isArray(marker.symbol)), + hasTextArray = Array.isArray(trace.text) -module.exports = function calc(gd, trace) { - var len = trace.lon.length, - marker = trace.marker; + calcMarkerColorscale(trace) - var hasMarkers = subtypes.hasMarkers(trace), - hasColorArray = (hasMarkers && Array.isArray(marker.color)), - hasSizeArray = (hasMarkers && Array.isArray(marker.size)), - hasSymbolArray = (hasMarkers && Array.isArray(marker.symbol)), - hasTextArray = Array.isArray(trace.text); - - calcMarkerColorscale(trace); - - var colorFn = Colorscale.hasColorscale(trace, 'marker') ? + var colorFn = Colorscale.hasColorscale(trace, 'marker') ? Colorscale.makeColorScaleFunc( Colorscale.extractScale( marker.colorscale, @@ -39,63 +37,62 @@ module.exports = function calc(gd, trace) { marker.cmax ) ) : - Lib.identity; + Lib.identity - var sizeFn = subtypes.isBubble(trace) ? + var sizeFn = subtypes.isBubble(trace) ? makeBubbleSizeFn(trace) : - Lib.identity; + Lib.identity - var calcTrace = [], - cnt = 0; + var calcTrace = [], + cnt = 0 // Different than cartesian calc step // as skip over non-numeric lon, lat pairs. // This makes the hover and convert calculations simpler. - for(var i = 0; i < len; i++) { - var lon = trace.lon[i], - lat = trace.lat[i]; + for (var i = 0; i < len; i++) { + var lon = trace.lon[i], + lat = trace.lat[i] - if(!isNumeric(lon) || !isNumeric(lat)) { - if(cnt > 0) calcTrace[cnt - 1].gapAfter = true; - continue; - } + if (!isNumeric(lon) || !isNumeric(lat)) { + if (cnt > 0) calcTrace[cnt - 1].gapAfter = true + continue + } - var calcPt = {}; - cnt++; + var calcPt = {} + cnt++ // coerce numeric strings into numbers - calcPt.lonlat = [+lon, +lat]; - - if(hasMarkers) { + calcPt.lonlat = [+lon, +lat] - if(hasColorArray) { - var mc = marker.color[i]; + if (hasMarkers) { + if (hasColorArray) { + var mc = marker.color[i] - calcPt.mc = mc; - calcPt.mcc = colorFn(mc); - } + calcPt.mc = mc + calcPt.mcc = colorFn(mc) + } - if(hasSizeArray) { - var ms = marker.size[i]; + if (hasSizeArray) { + var ms = marker.size[i] - calcPt.ms = ms; - calcPt.mrc = sizeFn(ms); - } + calcPt.ms = ms + calcPt.mrc = sizeFn(ms) + } - if(hasSymbolArray) { - var mx = marker.symbol[i]; - calcPt.mx = (typeof mx === 'string') ? mx : 'circle'; - } - } - - if(hasTextArray) { - var tx = trace.text[i]; - calcPt.tx = (typeof tx === 'string') ? tx : ''; - } + if (hasSymbolArray) { + var mx = marker.symbol[i] + calcPt.mx = (typeof mx === 'string') ? mx : 'circle' + } + } - calcTrace.push(calcPt); + if (hasTextArray) { + var tx = trace.text[i] + calcPt.tx = (typeof tx === 'string') ? tx : '' } - return calcTrace; -}; + calcTrace.push(calcPt) + } + + return calcTrace +} diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 3e46c8d8950..6e8ff58b4d9 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -6,139 +6,137 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') +var geoJsonUtils = require('../../lib/geojson_utils') -var Lib = require('../../lib'); -var geoJsonUtils = require('../../lib/geojson_utils'); +var subTypes = require('../scatter/subtypes') +var convertTextOpts = require('../../plots/mapbox/convert_text_opts') -var subTypes = require('../scatter/subtypes'); -var convertTextOpts = require('../../plots/mapbox/convert_text_opts'); +var COLOR_PROP = 'circle-color' +var SIZE_PROP = 'circle-radius' -var COLOR_PROP = 'circle-color'; -var SIZE_PROP = 'circle-radius'; +module.exports = function convert (calcTrace) { + var trace = calcTrace[0].trace + var isVisible = (trace.visible === true), + hasFill = (trace.fill !== 'none'), + hasLines = subTypes.hasLines(trace), + hasMarkers = subTypes.hasMarkers(trace), + hasText = subTypes.hasText(trace), + hasCircles = (hasMarkers && trace.marker.symbol === 'circle'), + hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle') -module.exports = function convert(calcTrace) { - var trace = calcTrace[0].trace; + var fill = initContainer(), + line = initContainer(), + circle = initContainer(), + symbol = initContainer() - var isVisible = (trace.visible === true), - hasFill = (trace.fill !== 'none'), - hasLines = subTypes.hasLines(trace), - hasMarkers = subTypes.hasMarkers(trace), - hasText = subTypes.hasText(trace), - hasCircles = (hasMarkers && trace.marker.symbol === 'circle'), - hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle'); - - var fill = initContainer(), - line = initContainer(), - circle = initContainer(), - symbol = initContainer(); - - var opts = { - fill: fill, - line: line, - circle: circle, - symbol: symbol - }; + var opts = { + fill: fill, + line: line, + circle: circle, + symbol: symbol + } // early return if not visible or placeholder - if(!isVisible || calcTrace[0].placeholder) return opts; + if (!isVisible || calcTrace[0].placeholder) return opts // fill layer and line layer use the same coords - var coords; - if(hasFill || hasLines) { - coords = geoJsonUtils.calcTraceToLineCoords(calcTrace); - } - - if(hasFill) { - fill.geojson = geoJsonUtils.makePolygon(coords); - fill.layout.visibility = 'visible'; - - Lib.extendFlat(fill.paint, { - 'fill-color': trace.fillcolor - }); - } - - if(hasLines) { - line.geojson = geoJsonUtils.makeLine(coords); - line.layout.visibility = 'visible'; - - Lib.extendFlat(line.paint, { - 'line-width': trace.line.width, - 'line-color': trace.line.color, - 'line-opacity': trace.opacity - }); + var coords + if (hasFill || hasLines) { + coords = geoJsonUtils.calcTraceToLineCoords(calcTrace) + } + + if (hasFill) { + fill.geojson = geoJsonUtils.makePolygon(coords) + fill.layout.visibility = 'visible' + + Lib.extendFlat(fill.paint, { + 'fill-color': trace.fillcolor + }) + } + + if (hasLines) { + line.geojson = geoJsonUtils.makeLine(coords) + line.layout.visibility = 'visible' + + Lib.extendFlat(line.paint, { + 'line-width': trace.line.width, + 'line-color': trace.line.color, + 'line-opacity': trace.opacity + }) // TODO convert line.dash into line-dasharray - } + } - if(hasCircles) { - var hash = {}; - hash[COLOR_PROP] = {}; - hash[SIZE_PROP] = {}; + if (hasCircles) { + var hash = {} + hash[COLOR_PROP] = {} + hash[SIZE_PROP] = {} - circle.geojson = makeCircleGeoJSON(calcTrace, hash); - circle.layout.visibility = 'visible'; + circle.geojson = makeCircleGeoJSON(calcTrace, hash) + circle.layout.visibility = 'visible' - Lib.extendFlat(circle.paint, { - 'circle-opacity': trace.opacity * trace.marker.opacity, - 'circle-color': calcCircleColor(trace, hash), - 'circle-radius': calcCircleRadius(trace, hash) - }); - } + Lib.extendFlat(circle.paint, { + 'circle-opacity': trace.opacity * trace.marker.opacity, + 'circle-color': calcCircleColor(trace, hash), + 'circle-radius': calcCircleRadius(trace, hash) + }) + } - if(hasSymbols || hasText) { - symbol.geojson = makeSymbolGeoJSON(calcTrace); + if (hasSymbols || hasText) { + symbol.geojson = makeSymbolGeoJSON(calcTrace) - Lib.extendFlat(symbol.layout, { - visibility: 'visible', - 'icon-image': '{symbol}-15', - 'text-field': '{text}' - }); + Lib.extendFlat(symbol.layout, { + visibility: 'visible', + 'icon-image': '{symbol}-15', + 'text-field': '{text}' + }) - if(hasSymbols) { - Lib.extendFlat(symbol.layout, { - 'icon-size': trace.marker.size / 10 - }); + if (hasSymbols) { + Lib.extendFlat(symbol.layout, { + 'icon-size': trace.marker.size / 10 + }) - Lib.extendFlat(symbol.paint, { - 'icon-opacity': trace.opacity * trace.marker.opacity, + Lib.extendFlat(symbol.paint, { + 'icon-opacity': trace.opacity * trace.marker.opacity, // TODO does not work ?? - 'icon-color': trace.marker.color - }); - } + 'icon-color': trace.marker.color + }) + } - if(hasText) { - var iconSize = (trace.marker || {}).size, - textOpts = convertTextOpts(trace.textposition, iconSize); + if (hasText) { + var iconSize = (trace.marker || {}).size, + textOpts = convertTextOpts(trace.textposition, iconSize) - Lib.extendFlat(symbol.layout, { - 'text-size': trace.textfont.size, - 'text-anchor': textOpts.anchor, - 'text-offset': textOpts.offset + Lib.extendFlat(symbol.layout, { + 'text-size': trace.textfont.size, + 'text-anchor': textOpts.anchor, + 'text-offset': textOpts.offset // TODO font family // 'text-font': symbol.textfont.family.split(', '), - }); + }) - Lib.extendFlat(symbol.paint, { - 'text-color': trace.textfont.color, - 'text-opacity': trace.opacity - }); - } + Lib.extendFlat(symbol.paint, { + 'text-color': trace.textfont.color, + 'text-opacity': trace.opacity + }) } + } - return opts; -}; + return opts +} -function initContainer() { - return { - geojson: geoJsonUtils.makeBlank(), - layout: { visibility: 'none' }, - paint: {} - }; +function initContainer () { + return { + geojson: geoJsonUtils.makeBlank(), + layout: { visibility: 'none' }, + paint: {} + } } // N.B. `hash` is mutated here @@ -155,153 +153,148 @@ function initContainer() { // // The solution prove to be more robust than trying to generate // `stops` arrays from scale functions. -function makeCircleGeoJSON(calcTrace, hash) { - var trace = calcTrace[0].trace; +function makeCircleGeoJSON (calcTrace, hash) { + var trace = calcTrace[0].trace - var marker = trace.marker, - hasColorArray = Array.isArray(marker.color), - hasSizeArray = Array.isArray(marker.size); + var marker = trace.marker, + hasColorArray = Array.isArray(marker.color), + hasSizeArray = Array.isArray(marker.size) // Translate vals in trace arrayOk containers // into a val-to-index hash object - function translate(props, key, val, index) { - if(hash[key][val] === undefined) hash[key][val] = index; - - props[key] = hash[key][val]; - } - - var features = []; - - for(var i = 0; i < calcTrace.length; i++) { - var calcPt = calcTrace[i]; - - var props = {}; - if(hasColorArray) translate(props, COLOR_PROP, calcPt.mcc, i); - if(hasSizeArray) translate(props, SIZE_PROP, calcPt.mrc, i); - - features.push({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: calcPt.lonlat - }, - properties: props - }); - } - - return { - type: 'FeatureCollection', - features: features - }; + function translate (props, key, val, index) { + if (hash[key][val] === undefined) hash[key][val] = index + + props[key] = hash[key][val] + } + + var features = [] + + for (var i = 0; i < calcTrace.length; i++) { + var calcPt = calcTrace[i] + + var props = {} + if (hasColorArray) translate(props, COLOR_PROP, calcPt.mcc, i) + if (hasSizeArray) translate(props, SIZE_PROP, calcPt.mrc, i) + + features.push({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: calcPt.lonlat + }, + properties: props + }) + } + + return { + type: 'FeatureCollection', + features: features + } } -function makeSymbolGeoJSON(calcTrace) { - var trace = calcTrace[0].trace; +function makeSymbolGeoJSON (calcTrace) { + var trace = calcTrace[0].trace - var marker = trace.marker || {}, - symbol = marker.symbol, - text = trace.text; + var marker = trace.marker || {}, + symbol = marker.symbol, + text = trace.text - var fillSymbol = (symbol !== 'circle') ? + var fillSymbol = (symbol !== 'circle') ? getFillFunc(symbol) : - blankFillFunc; + blankFillFunc - var fillText = subTypes.hasText(trace) ? + var fillText = subTypes.hasText(trace) ? getFillFunc(text) : - blankFillFunc; - - var features = []; - - for(var i = 0; i < calcTrace.length; i++) { - var calcPt = calcTrace[i]; - - features.push({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: calcPt.lonlat - }, - properties: { - symbol: fillSymbol(calcPt.mx), - text: fillText(calcPt.tx) - } - }); - } - - return { - type: 'FeatureCollection', - features: features - }; + blankFillFunc + + var features = [] + + for (var i = 0; i < calcTrace.length; i++) { + var calcPt = calcTrace[i] + + features.push({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: calcPt.lonlat + }, + properties: { + symbol: fillSymbol(calcPt.mx), + text: fillText(calcPt.tx) + } + }) + } + + return { + type: 'FeatureCollection', + features: features + } } -function calcCircleColor(trace, hash) { - var marker = trace.marker, - out; - - if(Array.isArray(marker.color)) { - var vals = Object.keys(hash[COLOR_PROP]), - stops = []; - - for(var i = 0; i < vals.length; i++) { - var val = vals[i]; +function calcCircleColor (trace, hash) { + var marker = trace.marker, + out - stops.push([ hash[COLOR_PROP][val], val ]); - } + if (Array.isArray(marker.color)) { + var vals = Object.keys(hash[COLOR_PROP]), + stops = [] - out = { - property: COLOR_PROP, - stops: stops - }; + for (var i = 0; i < vals.length; i++) { + var val = vals[i] + stops.push([ hash[COLOR_PROP][val], val ]) } - else { - out = marker.color; + + out = { + property: COLOR_PROP, + stops: stops } + } else { + out = marker.color + } - return out; + return out } -function calcCircleRadius(trace, hash) { - var marker = trace.marker, - out; +function calcCircleRadius (trace, hash) { + var marker = trace.marker, + out - if(Array.isArray(marker.size)) { - var vals = Object.keys(hash[SIZE_PROP]), - stops = []; + if (Array.isArray(marker.size)) { + var vals = Object.keys(hash[SIZE_PROP]), + stops = [] - for(var i = 0; i < vals.length; i++) { - var val = vals[i]; + for (var i = 0; i < vals.length; i++) { + var val = vals[i] - stops.push([ hash[SIZE_PROP][val], +val ]); - } + stops.push([ hash[SIZE_PROP][val], +val ]) + } // stops indices must be sorted - stops.sort(function(a, b) { - return a[0] - b[0]; - }); - - out = { - property: SIZE_PROP, - stops: stops - }; - } - else { - out = marker.size / 2; + stops.sort(function (a, b) { + return a[0] - b[0] + }) + + out = { + property: SIZE_PROP, + stops: stops } + } else { + out = marker.size / 2 + } - return out; + return out } -function getFillFunc(attr) { - if(Array.isArray(attr)) { - return function(v) { return v; }; - } - else if(attr) { - return function() { return attr; }; - } - else { - return blankFillFunc; - } +function getFillFunc (attr) { + if (Array.isArray(attr)) { + return function (v) { return v } + } else if (attr) { + return function () { return attr } + } else { + return blankFillFunc + } } -function blankFillFunc() { return ''; } +function blankFillFunc () { return '' } diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index 3de5641b76a..f7d09d94c01 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -6,81 +6,79 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var subTypes = require('../scatter/subtypes') +var handleMarkerDefaults = require('../scatter/marker_defaults') +var handleLineDefaults = require('../scatter/line_defaults') +var handleTextDefaults = require('../scatter/text_defaults') +var handleFillColorDefaults = require('../scatter/fillcolor_defaults') -var subTypes = require('../scatter/subtypes'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleTextDefaults = require('../scatter/text_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var attributes = require('./attributes') +var scatterAttrs = require('../scatter/attributes') -var attributes = require('./attributes'); -var scatterAttrs = require('../scatter/attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - function coerceMarker(attr, dflt) { - var attrs = (attr.indexOf('.line') === -1) ? attributes : scatterAttrs; + function coerceMarker (attr, dflt) { + var attrs = (attr.indexOf('.line') === -1) ? attributes : scatterAttrs // use 'scatter' attributes for 'marker.line.' attr, // so that we can reuse the scatter marker defaults - return Lib.coerce(traceIn, traceOut, attrs, attr, dflt); - } + return Lib.coerce(traceIn, traceOut, attrs, attr, dflt) + } - var len = handleLonLatDefaults(traceIn, traceOut, coerce); - if(!len) { - traceOut.visible = false; - return; - } + var len = handleLonLatDefaults(traceIn, traceOut, coerce) + if (!len) { + traceOut.visible = false + return + } - coerce('text'); - coerce('mode'); + coerce('text') + coerce('mode') - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - coerce('connectgaps'); - } + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + coerce('connectgaps') + } - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerceMarker); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerceMarker) // array marker.size and marker.color are only supported with circles - var marker = traceOut.marker; + var marker = traceOut.marker - if(marker.symbol !== 'circle') { - if(Array.isArray(marker.size)) marker.size = marker.size[0]; - if(Array.isArray(marker.color)) marker.color = marker.color[0]; - } + if (marker.symbol !== 'circle') { + if (Array.isArray(marker.size)) marker.size = marker.size[0] + if (Array.isArray(marker.color)) marker.color = marker.color[0] } + } - if(subTypes.hasText(traceOut)) { - handleTextDefaults(traceIn, traceOut, layout, coerce); - } + if (subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce) + } - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - } + coerce('fill') + if (traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce) + } - coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+text' : undefined); -}; + coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+text' : undefined) +} -function handleLonLatDefaults(traceIn, traceOut, coerce) { - var lon = coerce('lon') || []; - var lat = coerce('lat') || []; - var len = Math.min(lon.length, lat.length); +function handleLonLatDefaults (traceIn, traceOut, coerce) { + var lon = coerce('lon') || [] + var lat = coerce('lat') || [] + var len = Math.min(lon.length, lat.length) - if(len < lon.length) traceOut.lon = lon.slice(0, len); - if(len < lat.length) traceOut.lat = lat.slice(0, len); + if (len < lon.length) traceOut.lon = lon.slice(0, len) + if (len < lat.length) traceOut.lat = lat.slice(0, len) - return len; + return len } diff --git a/src/traces/scattermapbox/event_data.js b/src/traces/scattermapbox/event_data.js index 8581a671d78..b85b0ca72e4 100644 --- a/src/traces/scattermapbox/event_data.js +++ b/src/traces/scattermapbox/event_data.js @@ -6,13 +6,11 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +module.exports = function eventData (out, pt) { + out.lon = pt.lon + out.lat = pt.lat - -module.exports = function eventData(out, pt) { - out.lon = pt.lon; - out.lat = pt.lat; - - return out; -}; + return out +} diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index 088d35f6dc5..9fcbb331e8a 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -6,89 +6,86 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Fx = require('../../plots/cartesian/graph_interact') +var getTraceColor = require('../scatter/get_trace_color') -var Fx = require('../../plots/cartesian/graph_interact'); -var getTraceColor = require('../scatter/get_trace_color'); +module.exports = function hoverPoints (pointData, xval, yval) { + var cd = pointData.cd, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya - -module.exports = function hoverPoints(pointData, xval, yval) { - var cd = pointData.cd, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya; - - if(cd[0].placeholder) return; + if (cd[0].placeholder) return // compute winding number about [-180, 180] globe - var winding = (xval >= 0) ? + var winding = (xval >= 0) ? Math.floor((xval + 180) / 360) : - Math.ceil((xval - 180) / 360); + Math.ceil((xval - 180) / 360) // shift longitude to [-180, 180] to determine closest point - var lonShift = winding * 360; - var xval2 = xval - lonShift; + var lonShift = winding * 360 + var xval2 = xval - lonShift - function distFn(d) { - var lonlat = d.lonlat, - dx = Math.abs(xa.c2p(lonlat) - xa.c2p([xval2, lonlat[1]])), - dy = Math.abs(ya.c2p(lonlat) - ya.c2p([lonlat[0], yval])), - rad = Math.max(3, d.mrc || 0); + function distFn (d) { + var lonlat = d.lonlat, + dx = Math.abs(xa.c2p(lonlat) - xa.c2p([xval2, lonlat[1]])), + dy = Math.abs(ya.c2p(lonlat) - ya.c2p([lonlat[0], yval])), + rad = Math.max(3, d.mrc || 0) - return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); - } + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad) + } - Fx.getClosest(cd, distFn, pointData); + Fx.getClosest(cd, distFn, pointData) // skip the rest (for this trace) if we didn't find a close point - if(pointData.index === false) return; + if (pointData.index === false) return - var di = cd[pointData.index], - lonlat = di.lonlat, - lonlatShifted = [lonlat[0] + lonShift, lonlat[1]]; + var di = cd[pointData.index], + lonlat = di.lonlat, + lonlatShifted = [lonlat[0] + lonShift, lonlat[1]] // shift labels back to original winded globe - var xc = xa.c2p(lonlatShifted), - yc = ya.c2p(lonlatShifted), - rad = di.mrc || 1; + var xc = xa.c2p(lonlatShifted), + yc = ya.c2p(lonlatShifted), + rad = di.mrc || 1 - pointData.x0 = xc - rad; - pointData.x1 = xc + rad; - pointData.y0 = yc - rad; - pointData.y1 = yc + rad; + pointData.x0 = xc - rad + pointData.x1 = xc + rad + pointData.y0 = yc - rad + pointData.y1 = yc + rad - pointData.color = getTraceColor(trace, di); - pointData.extraText = getExtraText(trace, di); + pointData.color = getTraceColor(trace, di) + pointData.extraText = getExtraText(trace, di) - return [pointData]; -}; + return [pointData] +} -function getExtraText(trace, di) { - var hoverinfo = trace.hoverinfo.split('+'), - isAll = (hoverinfo.indexOf('all') !== -1), - hasLon = (hoverinfo.indexOf('lon') !== -1), - hasLat = (hoverinfo.indexOf('lat') !== -1); +function getExtraText (trace, di) { + var hoverinfo = trace.hoverinfo.split('+'), + isAll = (hoverinfo.indexOf('all') !== -1), + hasLon = (hoverinfo.indexOf('lon') !== -1), + hasLat = (hoverinfo.indexOf('lat') !== -1) - var lonlat = di.lonlat, - text = []; + var lonlat = di.lonlat, + text = [] // TODO should we use a mock axis to format hover? // If so, we'll need to make precision be zoom-level dependent - function format(v) { - return v + '\u00B0'; - } - - if(isAll || (hasLon && hasLat)) { - text.push('(' + format(lonlat[0]) + ', ' + format(lonlat[1]) + ')'); - } - else if(hasLon) text.push('lon: ' + format(lonlat[0])); - else if(hasLat) text.push('lat: ' + format(lonlat[1])); - - if(isAll || hoverinfo.indexOf('text') !== -1) { - var tx = di.tx || trace.text; - if(!Array.isArray(tx)) text.push(tx); - } - - return text.join('
'); + function format (v) { + return v + '\u00B0' + } + + if (isAll || (hasLon && hasLat)) { + text.push('(' + format(lonlat[0]) + ', ' + format(lonlat[1]) + ')') + } else if (hasLon) text.push('lon: ' + format(lonlat[0])) + else if (hasLat) text.push('lat: ' + format(lonlat[1])) + + if (isAll || hoverinfo.indexOf('text') !== -1) { + var tx = di.tx || trace.text + if (!Array.isArray(tx)) text.push(tx) + } + + return text.join('
') } diff --git a/src/traces/scattermapbox/index.js b/src/traces/scattermapbox/index.js index fa32f9847a0..1504c1f9225 100644 --- a/src/traces/scattermapbox/index.js +++ b/src/traces/scattermapbox/index.js @@ -6,30 +6,29 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' +var ScatterMapbox = {} -var ScatterMapbox = {}; +ScatterMapbox.attributes = require('./attributes') +ScatterMapbox.supplyDefaults = require('./defaults') +ScatterMapbox.colorbar = require('../scatter/colorbar') +ScatterMapbox.calc = require('./calc') +ScatterMapbox.hoverPoints = require('./hover') +ScatterMapbox.eventData = require('./event_data') +ScatterMapbox.plot = require('./plot') -ScatterMapbox.attributes = require('./attributes'); -ScatterMapbox.supplyDefaults = require('./defaults'); -ScatterMapbox.colorbar = require('../scatter/colorbar'); -ScatterMapbox.calc = require('./calc'); -ScatterMapbox.hoverPoints = require('./hover'); -ScatterMapbox.eventData = require('./event_data'); -ScatterMapbox.plot = require('./plot'); - -ScatterMapbox.moduleType = 'trace'; -ScatterMapbox.name = 'scattermapbox'; -ScatterMapbox.basePlotModule = require('../../plots/mapbox'); -ScatterMapbox.categories = ['mapbox', 'gl', 'symbols', 'markerColorscale', 'showLegend']; +ScatterMapbox.moduleType = 'trace' +ScatterMapbox.name = 'scattermapbox' +ScatterMapbox.basePlotModule = require('../../plots/mapbox') +ScatterMapbox.categories = ['mapbox', 'gl', 'symbols', 'markerColorscale', 'showLegend'] ScatterMapbox.meta = { - hrName: 'scatter_mapbox', - description: [ - 'The data visualized as scatter point, lines or marker symbols', - 'on a Mapbox GL geographic map', - 'is provided by longitude/latitude pairs in `lon` and `lat`.' - ].join(' ') -}; + hrName: 'scatter_mapbox', + description: [ + 'The data visualized as scatter point, lines or marker symbols', + 'on a Mapbox GL geographic map', + 'is provided by longitude/latitude pairs in `lon` and `lat`.' + ].join(' ') +} -module.exports = ScatterMapbox; +module.exports = ScatterMapbox diff --git a/src/traces/scattermapbox/plot.js b/src/traces/scattermapbox/plot.js index f7cc4040a2f..e5b9a7207b6 100644 --- a/src/traces/scattermapbox/plot.js +++ b/src/traces/scattermapbox/plot.js @@ -6,117 +6,115 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' + +var convert = require('./convert') + +function ScatterMapbox (mapbox, uid) { + this.mapbox = mapbox + this.map = mapbox.map + + this.uid = uid + + this.idSourceFill = uid + '-source-fill' + this.idSourceLine = uid + '-source-line' + this.idSourceCircle = uid + '-source-circle' + this.idSourceSymbol = uid + '-source-symbol' + + this.idLayerFill = uid + '-layer-fill' + this.idLayerLine = uid + '-layer-line' + this.idLayerCircle = uid + '-layer-circle' + this.idLayerSymbol = uid + '-layer-symbol' + + this.mapbox.initSource(this.idSourceFill) + this.mapbox.initSource(this.idSourceLine) + this.mapbox.initSource(this.idSourceCircle) + this.mapbox.initSource(this.idSourceSymbol) + + this.map.addLayer({ + id: this.idLayerFill, + source: this.idSourceFill, + type: 'fill' + }) + + this.map.addLayer({ + id: this.idLayerLine, + source: this.idSourceLine, + type: 'line' + }) + + this.map.addLayer({ + id: this.idLayerCircle, + source: this.idSourceCircle, + type: 'circle' + }) + + this.map.addLayer({ + id: this.idLayerSymbol, + source: this.idSourceSymbol, + type: 'symbol' + }) -'use strict'; - -var convert = require('./convert'); - + // We could merge the 'fill' source with the 'line' source and + // the 'circle' source with the 'symbol' source if ever having + // for up-to 4 sources per 'scattermapbox' traces becomes a problem. +} -function ScatterMapbox(mapbox, uid) { - this.mapbox = mapbox; - this.map = mapbox.map; +var proto = ScatterMapbox.prototype - this.uid = uid; +proto.update = function update (calcTrace) { + var mapbox = this.mapbox + var opts = convert(calcTrace) - this.idSourceFill = uid + '-source-fill'; - this.idSourceLine = uid + '-source-line'; - this.idSourceCircle = uid + '-source-circle'; - this.idSourceSymbol = uid + '-source-symbol'; + mapbox.setOptions(this.idLayerFill, 'setLayoutProperty', opts.fill.layout) + mapbox.setOptions(this.idLayerLine, 'setLayoutProperty', opts.line.layout) + mapbox.setOptions(this.idLayerCircle, 'setLayoutProperty', opts.circle.layout) + mapbox.setOptions(this.idLayerSymbol, 'setLayoutProperty', opts.symbol.layout) - this.idLayerFill = uid + '-layer-fill'; - this.idLayerLine = uid + '-layer-line'; - this.idLayerCircle = uid + '-layer-circle'; - this.idLayerSymbol = uid + '-layer-symbol'; + if (isVisible(opts.fill)) { + mapbox.setSourceData(this.idSourceFill, opts.fill.geojson) + mapbox.setOptions(this.idLayerFill, 'setPaintProperty', opts.fill.paint) + } - this.mapbox.initSource(this.idSourceFill); - this.mapbox.initSource(this.idSourceLine); - this.mapbox.initSource(this.idSourceCircle); - this.mapbox.initSource(this.idSourceSymbol); + if (isVisible(opts.line)) { + mapbox.setSourceData(this.idSourceLine, opts.line.geojson) + mapbox.setOptions(this.idLayerLine, 'setPaintProperty', opts.line.paint) + } - this.map.addLayer({ - id: this.idLayerFill, - source: this.idSourceFill, - type: 'fill' - }); + if (isVisible(opts.circle)) { + mapbox.setSourceData(this.idSourceCircle, opts.circle.geojson) + mapbox.setOptions(this.idLayerCircle, 'setPaintProperty', opts.circle.paint) + } - this.map.addLayer({ - id: this.idLayerLine, - source: this.idSourceLine, - type: 'line' - }); + if (isVisible(opts.symbol)) { + mapbox.setSourceData(this.idSourceSymbol, opts.symbol.geojson) + mapbox.setOptions(this.idLayerSymbol, 'setPaintProperty', opts.symbol.paint) + } +} - this.map.addLayer({ - id: this.idLayerCircle, - source: this.idSourceCircle, - type: 'circle' - }); +proto.dispose = function dispose () { + var map = this.map - this.map.addLayer({ - id: this.idLayerSymbol, - source: this.idSourceSymbol, - type: 'symbol' - }); + map.removeLayer(this.idLayerFill) + map.removeLayer(this.idLayerLine) + map.removeLayer(this.idLayerCircle) + map.removeLayer(this.idLayerSymbol) - // We could merge the 'fill' source with the 'line' source and - // the 'circle' source with the 'symbol' source if ever having - // for up-to 4 sources per 'scattermapbox' traces becomes a problem. + map.removeSource(this.idSourceFill) + map.removeSource(this.idSourceLine) + map.removeSource(this.idSourceCircle) + map.removeSource(this.idSourceSymbol) } -var proto = ScatterMapbox.prototype; - -proto.update = function update(calcTrace) { - var mapbox = this.mapbox; - var opts = convert(calcTrace); - - mapbox.setOptions(this.idLayerFill, 'setLayoutProperty', opts.fill.layout); - mapbox.setOptions(this.idLayerLine, 'setLayoutProperty', opts.line.layout); - mapbox.setOptions(this.idLayerCircle, 'setLayoutProperty', opts.circle.layout); - mapbox.setOptions(this.idLayerSymbol, 'setLayoutProperty', opts.symbol.layout); - - if(isVisible(opts.fill)) { - mapbox.setSourceData(this.idSourceFill, opts.fill.geojson); - mapbox.setOptions(this.idLayerFill, 'setPaintProperty', opts.fill.paint); - } - - if(isVisible(opts.line)) { - mapbox.setSourceData(this.idSourceLine, opts.line.geojson); - mapbox.setOptions(this.idLayerLine, 'setPaintProperty', opts.line.paint); - } - - if(isVisible(opts.circle)) { - mapbox.setSourceData(this.idSourceCircle, opts.circle.geojson); - mapbox.setOptions(this.idLayerCircle, 'setPaintProperty', opts.circle.paint); - } - - if(isVisible(opts.symbol)) { - mapbox.setSourceData(this.idSourceSymbol, opts.symbol.geojson); - mapbox.setOptions(this.idLayerSymbol, 'setPaintProperty', opts.symbol.paint); - } -}; - -proto.dispose = function dispose() { - var map = this.map; - - map.removeLayer(this.idLayerFill); - map.removeLayer(this.idLayerLine); - map.removeLayer(this.idLayerCircle); - map.removeLayer(this.idLayerSymbol); - - map.removeSource(this.idSourceFill); - map.removeSource(this.idSourceLine); - map.removeSource(this.idSourceCircle); - map.removeSource(this.idSourceSymbol); -}; - -function isVisible(layerOpts) { - return layerOpts.layout.visibility === 'visible'; +function isVisible (layerOpts) { + return layerOpts.layout.visibility === 'visible' } -module.exports = function createScatterMapbox(mapbox, calcTrace) { - var trace = calcTrace[0].trace; +module.exports = function createScatterMapbox (mapbox, calcTrace) { + var trace = calcTrace[0].trace - var scatterMapbox = new ScatterMapbox(mapbox, trace.uid); - scatterMapbox.update(calcTrace); + var scatterMapbox = new ScatterMapbox(mapbox, trace.uid) + scatterMapbox.update(calcTrace) - return scatterMapbox; -}; + return scatterMapbox +} diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 847cc8436fe..1ac645458ad 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -6,118 +6,118 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var scatterAttrs = require('../scatter/attributes'); -var plotAttrs = require('../../plots/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var scatterAttrs = require('../scatter/attributes') +var plotAttrs = require('../../plots/attributes') +var colorAttributes = require('../../components/colorscale/color_attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat var scatterMarkerAttrs = scatterAttrs.marker, - scatterLineAttrs = scatterAttrs.line, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; + scatterLineAttrs = scatterAttrs.line, + scatterMarkerLineAttrs = scatterMarkerAttrs.line module.exports = { - a: { - valType: 'data_array', - description: [ - 'Sets the quantity of component `a` in each data point.', - 'If `a`, `b`, and `c` are all provided, they need not be', - 'normalized, only the relative values matter. If only two', - 'arrays are provided they must be normalized to match', - '`ternary.sum`.' - ].join(' ') - }, - b: { - valType: 'data_array', - description: [ - 'Sets the quantity of component `a` in each data point.', - 'If `a`, `b`, and `c` are all provided, they need not be', - 'normalized, only the relative values matter. If only two', - 'arrays are provided they must be normalized to match', - '`ternary.sum`.' - ].join(' ') - }, - c: { - valType: 'data_array', - description: [ - 'Sets the quantity of component `a` in each data point.', - 'If `a`, `b`, and `c` are all provided, they need not be', - 'normalized, only the relative values matter. If only two', - 'arrays are provided they must be normalized to match', - '`ternary.sum`.' - ].join(' ') - }, - sum: { - valType: 'number', - role: 'info', - dflt: 0, - min: 0, - description: [ - 'The number each triplet should sum to,', - 'if only two of `a`, `b`, and `c` are provided.', - 'This overrides `ternary.sum` to normalize this specific', - 'trace, but does not affect the values displayed on the axes.', - '0 (or missing) means to use ternary.sum' - ].join(' ') - }, - mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (a,b,c) point.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of strings, the items are mapped in order to the', - 'the data points in (a,b,c).' - ].join(' ') - }), - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: scatterLineAttrs.dash, - shape: extendFlat({}, scatterLineAttrs.shape, + a: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary.sum`.' + ].join(' ') + }, + b: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary.sum`.' + ].join(' ') + }, + c: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary.sum`.' + ].join(' ') + }, + sum: { + valType: 'number', + role: 'info', + dflt: 0, + min: 0, + description: [ + 'The number each triplet should sum to,', + 'if only two of `a`, `b`, and `c` are provided.', + 'This overrides `ternary.sum` to normalize this specific', + 'trace, but does not affect the values displayed on the axes.', + '0 (or missing) means to use ternary.sum' + ].join(' ') + }, + mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (a,b,c) point.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of strings, the items are mapped in order to the', + 'the data points in (a,b,c).' + ].join(' ') + }), + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + shape: extendFlat({}, scatterLineAttrs.shape, {values: ['linear', 'spline']}), - smoothing: scatterLineAttrs.smoothing - }, - connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, { - values: ['none', 'toself', 'tonext'], - description: [ - 'Sets the area to fill with a solid color.', - 'Use with `fillcolor` if not *none*.', - 'scatterternary has a subset of the options available to scatter.', - '*toself* connects the endpoints of the trace (or each segment', - 'of the trace if it has gaps) into a closed shape.', - '*tonext* fills the space between two traces if one completely', - 'encloses the other (eg consecutive contour lines), and behaves like', - '*toself* if there is no trace before it. *tonext* should not be', - 'used if one trace does not enclose the other.' - ].join(' ') - }), - fillcolor: scatterAttrs.fillcolor, - marker: extendFlat({}, { - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity, - maxdisplayed: scatterMarkerAttrs.maxdisplayed, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - line: extendFlat({}, + smoothing: scatterLineAttrs.smoothing + }, + connectgaps: scatterAttrs.connectgaps, + fill: extendFlat({}, scatterAttrs.fill, { + values: ['none', 'toself', 'tonext'], + description: [ + 'Sets the area to fill with a solid color.', + 'Use with `fillcolor` if not *none*.', + 'scatterternary has a subset of the options available to scatter.', + '*toself* connects the endpoints of the trace (or each segment', + 'of the trace if it has gaps) into a closed shape.', + '*tonext* fills the space between two traces if one completely', + 'encloses the other (eg consecutive contour lines), and behaves like', + '*toself* if there is no trace before it. *tonext* should not be', + 'used if one trace does not enclose the other.' + ].join(' ') + }), + fillcolor: scatterAttrs.fillcolor, + marker: extendFlat({}, { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + maxdisplayed: scatterMarkerAttrs.maxdisplayed, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + line: extendFlat({}, {width: scatterMarkerLineAttrs.width}, colorAttributes('marker'.line) ) - }, colorAttributes('marker'), { - showscale: scatterMarkerAttrs.showscale, - colorbar: colorbarAttrs - }), + }, colorAttributes('marker'), { + showscale: scatterMarkerAttrs.showscale, + colorbar: colorbarAttrs + }), - textfont: scatterAttrs.textfont, - textposition: scatterAttrs.textposition, - hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['a', 'b', 'c', 'text', 'name'] - }), - hoveron: scatterAttrs.hoveron, -}; + textfont: scatterAttrs.textfont, + textposition: scatterAttrs.textposition, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['a', 'b', 'c', 'text', 'name'] + }), + hoveron: scatterAttrs.hoveron +} diff --git a/src/traces/scatterternary/calc.js b/src/traces/scatterternary/calc.js index c0a5d958212..76f9396970e 100644 --- a/src/traces/scatterternary/calc.js +++ b/src/traces/scatterternary/calc.js @@ -6,90 +6,87 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Axes = require('../../plots/cartesian/axes') -var Axes = require('../../plots/cartesian/axes'); +var subTypes = require('../scatter/subtypes') +var calcColorscale = require('../scatter/colorscale_calc') +var arraysToCalcdata = require('../scatter/arrays_to_calcdata') -var subTypes = require('../scatter/subtypes'); -var calcColorscale = require('../scatter/colorscale_calc'); -var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); +var dataArrays = ['a', 'b', 'c'] +var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']} -var dataArrays = ['a', 'b', 'c']; -var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']}; +module.exports = function calc (gd, trace) { + var ternary = gd._fullLayout[trace.subplot], + displaySum = ternary.sum, + normSum = trace.sum || displaySum - -module.exports = function calc(gd, trace) { - var ternary = gd._fullLayout[trace.subplot], - displaySum = ternary.sum, - normSum = trace.sum || displaySum; - - var i, j, dataArray, newArray, fillArray1, fillArray2; + var i, j, dataArray, newArray, fillArray1, fillArray2 // fill in one missing component - for(i = 0; i < dataArrays.length; i++) { - dataArray = dataArrays[i]; - if(trace[dataArray]) continue; - - fillArray1 = trace[arraysToFill[dataArray][0]]; - fillArray2 = trace[arraysToFill[dataArray][1]]; - newArray = new Array(fillArray1.length); - for(j = 0; j < fillArray1.length; j++) { - newArray[j] = normSum - fillArray1[j] - fillArray2[j]; - } - trace[dataArray] = newArray; + for (i = 0; i < dataArrays.length; i++) { + dataArray = dataArrays[i] + if (trace[dataArray]) continue + + fillArray1 = trace[arraysToFill[dataArray][0]] + fillArray2 = trace[arraysToFill[dataArray][1]] + newArray = new Array(fillArray1.length) + for (j = 0; j < fillArray1.length; j++) { + newArray[j] = normSum - fillArray1[j] - fillArray2[j] } + trace[dataArray] = newArray + } // make the calcdata array - var serieslen = trace.a.length; - var cd = new Array(serieslen); - var a, b, c, norm, x, y; - for(i = 0; i < serieslen; i++) { - a = trace.a[i]; - b = trace.b[i]; - c = trace.c[i]; - if(isNumeric(a) && isNumeric(b) && isNumeric(c)) { - a = +a; - b = +b; - c = +c; - norm = displaySum / (a + b + c); - if(norm !== 1) { - a *= norm; - b *= norm; - c *= norm; - } + var serieslen = trace.a.length + var cd = new Array(serieslen) + var a, b, c, norm, x, y + for (i = 0; i < serieslen; i++) { + a = trace.a[i] + b = trace.b[i] + c = trace.c[i] + if (isNumeric(a) && isNumeric(b) && isNumeric(c)) { + a = +a + b = +b + c = +c + norm = displaySum / (a + b + c) + if (norm !== 1) { + a *= norm + b *= norm + c *= norm + } // map a, b, c onto x and y where the full scale of y // is [0, sum], and x is [-sum, sum] // TODO: this makes `a` always the top, `b` the bottom left, // and `c` the bottom right. Do we want options to rearrange // these? - y = a; - x = c - b; - cd[i] = {x: x, y: y, a: a, b: b, c: c}; - } - else cd[i] = {x: false, y: false}; - } + y = a + x = c - b + cd[i] = {x: x, y: y, a: a, b: b, c: c} + } else cd[i] = {x: false, y: false} + } // fill in some extras - var marker, s; - if(subTypes.hasMarkers(trace)) { + var marker, s + if (subTypes.hasMarkers(trace)) { // Treat size like x or y arrays --- Run d2c // this needs to go before ppad computation - marker = trace.marker; - s = marker.size; - - if(Array.isArray(s)) { - var ax = {type: 'linear'}; - Axes.setConvert(ax); - s = ax.makeCalcdata(trace.marker, 'size'); - if(s.length > serieslen) s.splice(serieslen, s.length - serieslen); - } + marker = trace.marker + s = marker.size + + if (Array.isArray(s)) { + var ax = {type: 'linear'} + Axes.setConvert(ax) + s = ax.makeCalcdata(trace.marker, 'size') + if (s.length > serieslen) s.splice(serieslen, s.length - serieslen) } + } - calcColorscale(trace); - arraysToCalcdata(cd, trace); + calcColorscale(trace) + arraysToCalcdata(cd, trace) - return cd; -}; + return cd +} diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 5095345e929..53c669742d4 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -6,98 +6,94 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Lib = require('../../lib') -var Lib = require('../../lib'); +var constants = require('../scatter/constants') +var subTypes = require('../scatter/subtypes') +var handleMarkerDefaults = require('../scatter/marker_defaults') +var handleLineDefaults = require('../scatter/line_defaults') +var handleLineShapeDefaults = require('../scatter/line_shape_defaults') +var handleTextDefaults = require('../scatter/text_defaults') +var handleFillColorDefaults = require('../scatter/fillcolor_defaults') -var constants = require('../scatter/constants'); -var subTypes = require('../scatter/subtypes'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleLineShapeDefaults = require('../scatter/line_shape_defaults'); -var handleTextDefaults = require('../scatter/text_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var attributes = require('./attributes') -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var a = coerce('a'), - b = coerce('b'), - c = coerce('c'), - len; + var a = coerce('a'), + b = coerce('b'), + c = coerce('c'), + len // allow any one array to be missing, len is the minimum length of those // present. Note that after coerce data_array's are either Arrays (which // are truthy even if empty) or undefined. As in scatter, an empty array // is different from undefined, because it can signify that this data is // not known yet but expected in the future - if(a) { - len = a.length; - if(b) { - len = Math.min(len, b.length); - if(c) len = Math.min(len, c.length); - } - else if(c) len = Math.min(len, c.length); - else len = 0; - } - else if(b && c) { - len = Math.min(b.length, c.length); - } - - if(!len) { - traceOut.visible = false; - return; - } + if (a) { + len = a.length + if (b) { + len = Math.min(len, b.length) + if (c) len = Math.min(len, c.length) + } else if (c) len = Math.min(len, c.length) + else len = 0 + } else if (b && c) { + len = Math.min(b.length, c.length) + } + + if (!len) { + traceOut.visible = false + return + } // cut all data arrays down to same length - if(a && len < a.length) traceOut.a = a.slice(0, len); - if(b && len < b.length) traceOut.b = b.slice(0, len); - if(c && len < c.length) traceOut.c = c.slice(0, len); + if (a && len < a.length) traceOut.a = a.slice(0, len) + if (b && len < b.length) traceOut.b = b.slice(0, len) + if (c && len < c.length) traceOut.c = c.slice(0, len) - coerce('sum'); + coerce('sum') - coerce('text'); + coerce('text') - var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; - coerce('mode', defaultMode); + var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines' + coerce('mode', defaultMode) - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - handleLineShapeDefaults(traceIn, traceOut, coerce); - coerce('connectgaps'); - } + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) + handleLineShapeDefaults(traceIn, traceOut, coerce) + coerce('connectgaps') + } - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce) + } - if(subTypes.hasText(traceOut)) { - handleTextDefaults(traceIn, traceOut, layout, coerce); - } + if (subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce) + } - var dfltHoverOn = []; + var dfltHoverOn = [] - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { - coerce('marker.maxdisplayed'); - dfltHoverOn.push('points'); - } + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + coerce('marker.maxdisplayed') + dfltHoverOn.push('points') + } - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); - } + coerce('fill') + if (traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce) + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce) + } - coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined); + coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined) - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { - dfltHoverOn.push('fills'); - } - coerce('hoveron', dfltHoverOn.join('+') || 'points'); -}; + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills') + } + coerce('hoveron', dfltHoverOn.join('+') || 'points') +} diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js index cdf459d2609..b71dd945f8a 100644 --- a/src/traces/scatterternary/hover.js +++ b/src/traces/scatterternary/hover.js @@ -6,18 +6,16 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scatterHover = require('../scatter/hover') +var Axes = require('../../plots/cartesian/axes') -var scatterHover = require('../scatter/hover'); -var Axes = require('../../plots/cartesian/axes'); +module.exports = function hoverPoints (pointData, xval, yval, hovermode) { + var scatterPointData = scatterHover(pointData, xval, yval, hovermode) + if (!scatterPointData || scatterPointData[0].index === false) return - -module.exports = function hoverPoints(pointData, xval, yval, hovermode) { - var scatterPointData = scatterHover(pointData, xval, yval, hovermode); - if(!scatterPointData || scatterPointData[0].index === false) return; - - var newPointData = scatterPointData[0]; + var newPointData = scatterPointData[0] // if hovering on a fill, we don't show any point data so the label is // unchanged from what scatter gives us - except that it needs to @@ -29,41 +27,41 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { // is actually visible, as constrained by the diagonal axis lines, is not // so easy and anyway we lost the information we would have needed to do // this inside scatterHover. - if(newPointData.index === undefined) { - var yFracUp = 1 - (newPointData.y0 / pointData.ya._length), - xLen = pointData.xa._length, - xMin = xLen * yFracUp / 2, - xMax = xLen - xMin; - newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin); - newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin); - return scatterPointData; - } + if (newPointData.index === undefined) { + var yFracUp = 1 - (newPointData.y0 / pointData.ya._length), + xLen = pointData.xa._length, + xMin = xLen * yFracUp / 2, + xMax = xLen - xMin + newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin) + newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin) + return scatterPointData + } - var cdi = newPointData.cd[newPointData.index]; + var cdi = newPointData.cd[newPointData.index] - newPointData.a = cdi.a; - newPointData.b = cdi.b; - newPointData.c = cdi.c; + newPointData.a = cdi.a + newPointData.b = cdi.b + newPointData.c = cdi.c - newPointData.xLabelVal = undefined; - newPointData.yLabelVal = undefined; + newPointData.xLabelVal = undefined + newPointData.yLabelVal = undefined // TODO: nice formatting, and label by axis title, for a, b, and c? - var trace = newPointData.trace, - ternary = trace._ternary, - hoverinfo = trace.hoverinfo.split('+'), - text = []; + var trace = newPointData.trace, + ternary = trace._ternary, + hoverinfo = trace.hoverinfo.split('+'), + text = [] - function textPart(ax, val) { - text.push(ax._hovertitle + ': ' + Axes.tickText(ax, val, 'hover').text); - } + function textPart (ax, val) { + text.push(ax._hovertitle + ': ' + Axes.tickText(ax, val, 'hover').text) + } - if(hoverinfo.indexOf('all') !== -1) hoverinfo = ['a', 'b', 'c']; - if(hoverinfo.indexOf('a') !== -1) textPart(ternary.aaxis, cdi.a); - if(hoverinfo.indexOf('b') !== -1) textPart(ternary.baxis, cdi.b); - if(hoverinfo.indexOf('c') !== -1) textPart(ternary.caxis, cdi.c); + if (hoverinfo.indexOf('all') !== -1) hoverinfo = ['a', 'b', 'c'] + if (hoverinfo.indexOf('a') !== -1) textPart(ternary.aaxis, cdi.a) + if (hoverinfo.indexOf('b') !== -1) textPart(ternary.baxis, cdi.b) + if (hoverinfo.indexOf('c') !== -1) textPart(ternary.caxis, cdi.c) - newPointData.extraText = text.join('
'); + newPointData.extraText = text.join('
') - return scatterPointData; -}; + return scatterPointData +} diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js index e7e75c4ddfc..3aebe63b98c 100644 --- a/src/traces/scatterternary/index.js +++ b/src/traces/scatterternary/index.js @@ -6,29 +6,29 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var ScatterTernary = {}; +var ScatterTernary = {} -ScatterTernary.attributes = require('./attributes'); -ScatterTernary.supplyDefaults = require('./defaults'); -ScatterTernary.colorbar = require('../scatter/colorbar'); -ScatterTernary.calc = require('./calc'); -ScatterTernary.plot = require('./plot'); -ScatterTernary.style = require('./style'); -ScatterTernary.hoverPoints = require('./hover'); -ScatterTernary.selectPoints = require('./select'); +ScatterTernary.attributes = require('./attributes') +ScatterTernary.supplyDefaults = require('./defaults') +ScatterTernary.colorbar = require('../scatter/colorbar') +ScatterTernary.calc = require('./calc') +ScatterTernary.plot = require('./plot') +ScatterTernary.style = require('./style') +ScatterTernary.hoverPoints = require('./hover') +ScatterTernary.selectPoints = require('./select') -ScatterTernary.moduleType = 'trace'; -ScatterTernary.name = 'scatterternary'; -ScatterTernary.basePlotModule = require('../../plots/ternary'); -ScatterTernary.categories = ['ternary', 'symbols', 'markerColorscale', 'showLegend']; +ScatterTernary.moduleType = 'trace' +ScatterTernary.name = 'scatterternary' +ScatterTernary.basePlotModule = require('../../plots/ternary') +ScatterTernary.categories = ['ternary', 'symbols', 'markerColorscale', 'showLegend'] ScatterTernary.meta = { - hrName: 'scatter_ternary', - description: [ - 'Provides similar functionality to the *scatter* type but on a ternary phase diagram.', - 'The data is provided by at least two arrays out of `a`, `b`, `c` triplets.' - ].join(' ') -}; + hrName: 'scatter_ternary', + description: [ + 'Provides similar functionality to the *scatter* type but on a ternary phase diagram.', + 'The data is provided by at least two arrays out of `a`, `b`, `c` triplets.' + ].join(' ') +} -module.exports = ScatterTernary; +module.exports = ScatterTernary diff --git a/src/traces/scatterternary/plot.js b/src/traces/scatterternary/plot.js index 0ecfaa25601..be2159e97f7 100644 --- a/src/traces/scatterternary/plot.js +++ b/src/traces/scatterternary/plot.js @@ -6,29 +6,27 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scatterPlot = require('../scatter/plot') -var scatterPlot = require('../scatter/plot'); - - -module.exports = function plot(ternary, moduleCalcData) { - var plotContainer = ternary.plotContainer; +module.exports = function plot (ternary, moduleCalcData) { + var plotContainer = ternary.plotContainer // remove all nodes inside the scatter layer - plotContainer.select('.scatterlayer').selectAll('*').remove(); + plotContainer.select('.scatterlayer').selectAll('*').remove() // mimic cartesian plotinfo - var plotinfo = { - xaxis: ternary.xaxis, - yaxis: ternary.yaxis, - plot: plotContainer - }; + var plotinfo = { + xaxis: ternary.xaxis, + yaxis: ternary.yaxis, + plot: plotContainer + } // add ref to ternary subplot object in fullData traces - for(var i = 0; i < moduleCalcData.length; i++) { - moduleCalcData[i][0].trace._ternary = ternary; - } + for (var i = 0; i < moduleCalcData.length; i++) { + moduleCalcData[i][0].trace._ternary = ternary + } - scatterPlot(ternary.graphDiv, plotinfo, moduleCalcData); -}; + scatterPlot(ternary.graphDiv, plotinfo, moduleCalcData) +} diff --git a/src/traces/scatterternary/select.js b/src/traces/scatterternary/select.js index 5682b0e1669..a1d3ebd4260 100644 --- a/src/traces/scatterternary/select.js +++ b/src/traces/scatterternary/select.js @@ -6,28 +6,26 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scatterSelect = require('../scatter/select') -var scatterSelect = require('../scatter/select'); +module.exports = function selectPoints (searchInfo, polygon) { + var selection = scatterSelect(searchInfo, polygon) + if (!selection) return + var cd = searchInfo.cd, + pt, cdi, i -module.exports = function selectPoints(searchInfo, polygon) { - var selection = scatterSelect(searchInfo, polygon); - if(!selection) return; + for (i = 0; i < selection.length; i++) { + pt = selection[i] + cdi = cd[pt.pointNumber] + pt.a = cdi.a + pt.b = cdi.b + pt.c = cdi.c + delete pt.x + delete pt.y + } - var cd = searchInfo.cd, - pt, cdi, i; - - for(i = 0; i < selection.length; i++) { - pt = selection[i]; - cdi = cd[pt.pointNumber]; - pt.a = cdi.a; - pt.b = cdi.b; - pt.c = cdi.c; - delete pt.x; - delete pt.y; - } - - return selection; -}; + return selection +} diff --git a/src/traces/scatterternary/style.js b/src/traces/scatterternary/style.js index 8ead87cc97e..0c8fb1dd463 100644 --- a/src/traces/scatterternary/style.js +++ b/src/traces/scatterternary/style.js @@ -6,22 +6,20 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var scatterStyle = require('../scatter/style') -var scatterStyle = require('../scatter/style'); - - -module.exports = function style(gd) { - var modules = gd._fullLayout._modules; +module.exports = function style (gd) { + var modules = gd._fullLayout._modules // we're just going to call scatter style... if we already // called it, don't need to redo. // Later though we may want differences, or we may make style // more specific in its scope, then we can remove this. - for(var i = 0; i < modules.length; i++) { - if(modules[i].name === 'scatter') return; - } + for (var i = 0; i < modules.length; i++) { + if (modules[i].name === 'scatter') return + } - scatterStyle(gd); -}; + scatterStyle(gd) +} diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index 6e3930a0479..032dd7ceca0 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -6,241 +6,241 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Color = require('../../components/color'); -var colorscaleAttrs = require('../../components/colorscale/attributes'); -var colorbarAttrs = require('../../components/colorbar/attributes'); +var Color = require('../../components/color') +var colorscaleAttrs = require('../../components/colorscale/attributes') +var colorbarAttrs = require('../../components/colorbar/attributes') -var extendFlat = require('../../lib/extend').extendFlat; +var extendFlat = require('../../lib/extend').extendFlat -function makeContourProjAttr(axLetter) { - return { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not these contour lines are projected', - 'on the', axLetter, 'plane.', - 'If `highlight` is set to *true* (the default), the projected', - 'lines are shown on hover.', - 'If `show` is set to *true*, the projected lines are shown', - 'in permanence.' - ].join(' ') - }; +function makeContourProjAttr (axLetter) { + return { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not these contour lines are projected', + 'on the', axLetter, 'plane.', + 'If `highlight` is set to *true* (the default), the projected', + 'lines are shown on hover.', + 'If `show` is set to *true*, the projected lines are shown', + 'in permanence.' + ].join(' ') + } } -function makeContourAttr(axLetter) { - return { - show: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not contour lines about the', axLetter, - 'dimension are drawn.' - ].join(' ') - }, - project: { - x: makeContourProjAttr('x'), - y: makeContourProjAttr('y'), - z: makeContourProjAttr('z') - }, - color: { - valType: 'color', - role: 'style', - dflt: Color.defaultLine, - description: 'Sets the color of the contour lines.' - }, - usecolormap: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'An alternate to *color*.', - 'Determines whether or not the contour lines are colored using', - 'the trace *colorscale*.' - ].join(' ') - }, - width: { - valType: 'number', - role: 'style', - min: 1, - max: 16, - dflt: 2, - description: 'Sets the width of the contour lines.' - }, - highlight: { - valType: 'boolean', - role: 'info', - dflt: true, - description: [ - 'Determines whether or not contour lines about the', axLetter, - 'dimension are highlighted on hover.' - ].join(' ') - }, - highlightcolor: { - valType: 'color', - role: 'style', - dflt: Color.defaultLine, - description: 'Sets the color of the highlighted contour lines.' - }, - highlightwidth: { - valType: 'number', - role: 'style', - min: 1, - max: 16, - dflt: 2, - description: 'Sets the width of the highlighted contour lines.' - } - }; -} - -module.exports = { - z: { - valType: 'data_array', - description: 'Sets the z coordinates.' +function makeContourAttr (axLetter) { + return { + show: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not contour lines about the', axLetter, + 'dimension are drawn.' + ].join(' ') }, - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' + project: { + x: makeContourProjAttr('x'), + y: makeContourProjAttr('y'), + z: makeContourProjAttr('z') }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' + color: { + valType: 'color', + role: 'style', + dflt: Color.defaultLine, + description: 'Sets the color of the contour lines.' }, - - text: { - valType: 'data_array', - description: 'Sets the text elements associated with each z value.' + usecolormap: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'An alternate to *color*.', + 'Determines whether or not the contour lines are colored using', + 'the trace *colorscale*.' + ].join(' ') }, - surfacecolor: { - valType: 'data_array', - description: [ - 'Sets the surface color values,', - 'used for setting a color scale independent of `z`.' - ].join(' ') + width: { + valType: 'number', + role: 'style', + min: 1, + max: 16, + dflt: 2, + description: 'Sets the width of the contour lines.' }, + highlight: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'Determines whether or not contour lines about the', axLetter, + 'dimension are highlighted on hover.' + ].join(' ') + }, + highlightcolor: { + valType: 'color', + role: 'style', + dflt: Color.defaultLine, + description: 'Sets the color of the highlighted contour lines.' + }, + highlightwidth: { + valType: 'number', + role: 'style', + min: 1, + max: 16, + dflt: 2, + description: 'Sets the width of the highlighted contour lines.' + } + } +} + +module.exports = { + z: { + valType: 'data_array', + description: 'Sets the z coordinates.' + }, + x: { + valType: 'data_array', + description: 'Sets the x coordinates.' + }, + y: { + valType: 'data_array', + description: 'Sets the y coordinates.' + }, + + text: { + valType: 'data_array', + description: 'Sets the text elements associated with each z value.' + }, + surfacecolor: { + valType: 'data_array', + description: [ + 'Sets the surface color values,', + 'used for setting a color scale independent of `z`.' + ].join(' ') + }, // Todo this block has a structure of colorscale/attributes.js but with colorscale/color_attributes.js names - cauto: colorscaleAttrs.zauto, - cmin: colorscaleAttrs.zmin, - cmax: colorscaleAttrs.zmax, - colorscale: colorscaleAttrs.colorscale, - autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, + cauto: colorscaleAttrs.zauto, + cmin: colorscaleAttrs.zmin, + cmax: colorscaleAttrs.zmax, + colorscale: colorscaleAttrs.colorscale, + autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), - reversescale: colorscaleAttrs.reversescale, - showscale: colorscaleAttrs.showscale, - colorbar: colorbarAttrs, + reversescale: colorscaleAttrs.reversescale, + showscale: colorscaleAttrs.showscale, + colorbar: colorbarAttrs, + + contours: { + x: makeContourAttr('x'), + y: makeContourAttr('y'), + z: makeContourAttr('z') + }, + hidesurface: { + valType: 'boolean', + role: 'info', + dflt: false, + description: [ + 'Determines whether or not a surface is drawn.', + 'For example, set `hidesurface` to *false*', + '`contours.x.show` to *true* and', + '`contours.y.show` to *true* to draw a wire frame plot.' + ].join(' ') + }, - contours: { - x: makeContourAttr('x'), - y: makeContourAttr('y'), - z: makeContourAttr('z') + lightposition: { + x: { + valType: 'number', + role: 'style', + min: -1e5, + max: 1e5, + dflt: 10, + description: 'Numeric vector, representing the X coordinate for each vertex.' }, - hidesurface: { - valType: 'boolean', - role: 'info', - dflt: false, - description: [ - 'Determines whether or not a surface is drawn.', - 'For example, set `hidesurface` to *false*', - '`contours.x.show` to *true* and', - '`contours.y.show` to *true* to draw a wire frame plot.' - ].join(' ') + y: { + valType: 'number', + role: 'style', + min: -1e5, + max: 1e5, + dflt: 1e4, + description: 'Numeric vector, representing the Y coordinate for each vertex.' }, + z: { + valType: 'number', + role: 'style', + min: -1e5, + max: 1e5, + dflt: 0, + description: 'Numeric vector, representing the Z coordinate for each vertex.' + } + }, - lightposition: { - x: { - valType: 'number', - role: 'style', - min: -1e5, - max: 1e5, - dflt: 10, - description: 'Numeric vector, representing the X coordinate for each vertex.' - }, - y: { - valType: 'number', - role: 'style', - min: -1e5, - max: 1e5, - dflt: 1e4, - description: 'Numeric vector, representing the Y coordinate for each vertex.' - }, - z: { - valType: 'number', - role: 'style', - min: -1e5, - max: 1e5, - dflt: 0, - description: 'Numeric vector, representing the Z coordinate for each vertex.' - } + lighting: { + ambient: { + valType: 'number', + role: 'style', + min: 0.00, + max: 1.0, + dflt: 0.8, + description: 'Ambient light increases overall color visibility but can wash out the image.' }, - - lighting: { - ambient: { - valType: 'number', - role: 'style', - min: 0.00, - max: 1.0, - dflt: 0.8, - description: 'Ambient light increases overall color visibility but can wash out the image.' - }, - diffuse: { - valType: 'number', - role: 'style', - min: 0.00, - max: 1.00, - dflt: 0.8, - description: 'Represents the extent that incident rays are reflected in a range of angles.' - }, - specular: { - valType: 'number', - role: 'style', - min: 0.00, - max: 2.00, - dflt: 0.05, - description: 'Represents the level that incident rays are reflected in a single direction, causing shine.' - }, - roughness: { - valType: 'number', - role: 'style', - min: 0.00, - max: 1.00, - dflt: 0.5, - description: 'Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.' - }, - fresnel: { - valType: 'number', - role: 'style', - min: 0.00, - max: 5.00, - dflt: 0.2, - description: [ - 'Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective', - 'when viewing it from the edge of the paper (almost 90 degrees), causing shine.' - ].join(' ') - } + diffuse: { + valType: 'number', + role: 'style', + min: 0.00, + max: 1.00, + dflt: 0.8, + description: 'Represents the extent that incident rays are reflected in a range of angles.' }, - - opacity: { - valType: 'number', - role: 'style', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the opacity of the surface.' + specular: { + valType: 'number', + role: 'style', + min: 0.00, + max: 2.00, + dflt: 0.05, + description: 'Represents the level that incident rays are reflected in a single direction, causing shine.' }, - - _deprecated: { - zauto: extendFlat({}, colorscaleAttrs.zauto, { - description: 'Obsolete. Use `cauto` instead.' - }), - zmin: extendFlat({}, colorscaleAttrs.zmin, { - description: 'Obsolete. Use `cmin` instead.' - }), - zmax: extendFlat({}, colorscaleAttrs.zmax, { - description: 'Obsolete. Use `cmax` instead.' - }) + roughness: { + valType: 'number', + role: 'style', + min: 0.00, + max: 1.00, + dflt: 0.5, + description: 'Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.' + }, + fresnel: { + valType: 'number', + role: 'style', + min: 0.00, + max: 5.00, + dflt: 0.2, + description: [ + 'Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective', + 'when viewing it from the edge of the paper (almost 90 degrees), causing shine.' + ].join(' ') } -}; + }, + + opacity: { + valType: 'number', + role: 'style', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of the surface.' + }, + + _deprecated: { + zauto: extendFlat({}, colorscaleAttrs.zauto, { + description: 'Obsolete. Use `cauto` instead.' + }), + zmin: extendFlat({}, colorscaleAttrs.zmin, { + description: 'Obsolete. Use `cmin` instead.' + }), + zmax: extendFlat({}, colorscaleAttrs.zmax, { + description: 'Obsolete. Use `cmax` instead.' + }) + } +} diff --git a/src/traces/surface/calc.js b/src/traces/surface/calc.js index 5d52b2da316..4c633e432f0 100644 --- a/src/traces/surface/calc.js +++ b/src/traces/surface/calc.js @@ -6,17 +6,15 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; - -var colorscaleCalc = require('../../components/colorscale/calc'); - +var colorscaleCalc = require('../../components/colorscale/calc') // Compute auto-z and autocolorscale if applicable -module.exports = function calc(gd, trace) { - if(trace.surfacecolor) { - colorscaleCalc(trace, trace.surfacecolor, '', 'c'); - } else { - colorscaleCalc(trace, trace.z, '', 'c'); - } -}; +module.exports = function calc (gd, trace) { + if (trace.surfacecolor) { + colorscaleCalc(trace, trace.surfacecolor, '', 'c') + } else { + colorscaleCalc(trace, trace.z, '', 'c') + } +} diff --git a/src/traces/surface/colorbar.js b/src/traces/surface/colorbar.js index e483f87df3d..7acd70c9985 100644 --- a/src/traces/surface/colorbar.js +++ b/src/traces/surface/colorbar.js @@ -6,45 +6,43 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var isNumeric = require('fast-isnumeric') -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib') +var Plots = require('../../plots/plots') +var Colorscale = require('../../components/colorscale') +var drawColorbar = require('../../components/colorbar/draw') -var Lib = require('../../lib'); -var Plots = require('../../plots/plots'); -var Colorscale = require('../../components/colorscale'); -var drawColorbar = require('../../components/colorbar/draw'); +module.exports = function colorbar (gd, cd) { + var trace = cd[0].trace, + cbId = 'cb' + trace.uid, + cmin = trace.cmin, + cmax = trace.cmax, + vals = trace.surfacecolor || trace.z + if (!isNumeric(cmin)) cmin = Lib.aggNums(Math.min, null, vals) + if (!isNumeric(cmax)) cmax = Lib.aggNums(Math.max, null, vals) -module.exports = function colorbar(gd, cd) { - var trace = cd[0].trace, - cbId = 'cb' + trace.uid, - cmin = trace.cmin, - cmax = trace.cmax, - vals = trace.surfacecolor || trace.z; + gd._fullLayout._infolayer.selectAll('.' + cbId).remove() - if(!isNumeric(cmin)) cmin = Lib.aggNums(Math.min, null, vals); - if(!isNumeric(cmax)) cmax = Lib.aggNums(Math.max, null, vals); + if (!trace.showscale) { + Plots.autoMargin(gd, cbId) + return + } - gd._fullLayout._infolayer.selectAll('.' + cbId).remove(); - - if(!trace.showscale) { - Plots.autoMargin(gd, cbId); - return; - } - - var cb = cd[0].t.cb = drawColorbar(gd, cbId); - var sclFunc = Colorscale.makeColorScaleFunc( + var cb = cd[0].t.cb = drawColorbar(gd, cbId) + var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( trace.colorscale, cmin, cmax ), { noNumericCheck: true } - ); + ) - cb.fillcolor(sclFunc) + cb.fillcolor(sclFunc) .filllevels({start: cmin, end: cmax, size: (cmax - cmin) / 254}) - .options(trace.colorbar)(); -}; + .options(trace.colorbar)() +} diff --git a/src/traces/surface/convert.js b/src/traces/surface/convert.js index 31fd3685e9e..76b83302e95 100644 --- a/src/traces/surface/convert.js +++ b/src/traces/surface/convert.js @@ -6,205 +6,203 @@ * LICENSE file in the root directory of this source tree. */ - -'use strict'; - -var createSurface = require('gl-surface3d'); -var ndarray = require('ndarray'); -var homography = require('ndarray-homography'); -var fill = require('ndarray-fill'); -var ops = require('ndarray-ops'); -var tinycolor = require('tinycolor2'); - -var str2RgbaArray = require('../../lib/str2rgbarray'); - -var MIN_RESOLUTION = 128; - -function SurfaceTrace(scene, surface, uid) { - this.scene = scene; - this.uid = uid; - this.surface = surface; - this.data = null; - this.showContour = [false, false, false]; - this.dataScale = 1.0; +'use strict' + +var createSurface = require('gl-surface3d') +var ndarray = require('ndarray') +var homography = require('ndarray-homography') +var fill = require('ndarray-fill') +var ops = require('ndarray-ops') +var tinycolor = require('tinycolor2') + +var str2RgbaArray = require('../../lib/str2rgbarray') + +var MIN_RESOLUTION = 128 + +function SurfaceTrace (scene, surface, uid) { + this.scene = scene + this.uid = uid + this.surface = surface + this.data = null + this.showContour = [false, false, false] + this.dataScale = 1.0 } -var proto = SurfaceTrace.prototype; +var proto = SurfaceTrace.prototype -proto.handlePick = function(selection) { - if(selection.object === this.surface) { - var selectIndex = [ - Math.min( - Math.round(selection.data.index[0] / this.dataScale - 1)|0, +proto.handlePick = function (selection) { + if (selection.object === this.surface) { + var selectIndex = [ + Math.min( + Math.round(selection.data.index[0] / this.dataScale - 1) | 0, this.data.z[0].length - 1 ), - Math.min( - Math.round(selection.data.index[1] / this.dataScale - 1)|0, + Math.min( + Math.round(selection.data.index[1] / this.dataScale - 1) | 0, this.data.z.length - 1 ) - ]; - var traceCoordinate = [0, 0, 0]; - - if(Array.isArray(this.data.x[0])) { - traceCoordinate[0] = this.data.x[selectIndex[1]][selectIndex[0]]; - } else { - traceCoordinate[0] = this.data.x[selectIndex[0]]; - } - if(Array.isArray(this.data.y[0])) { - traceCoordinate[1] = this.data.y[selectIndex[1]][selectIndex[0]]; - } else { - traceCoordinate[1] = this.data.y[selectIndex[1]]; - } - - traceCoordinate[2] = this.data.z[selectIndex[1]][selectIndex[0]]; - selection.traceCoordinate = traceCoordinate; - - var sceneLayout = this.scene.fullSceneLayout; - selection.dataCoordinate = [ - sceneLayout.xaxis.d2l(traceCoordinate[0], 0, this.data.xcalendar) * this.scene.dataScale[0], - sceneLayout.yaxis.d2l(traceCoordinate[1], 0, this.data.ycalendar) * this.scene.dataScale[1], - sceneLayout.zaxis.d2l(traceCoordinate[2], 0, this.data.zcalendar) * this.scene.dataScale[2] - ]; - - var text = this.data.text; - if(text && text[selectIndex[1]] && text[selectIndex[1]][selectIndex[0]] !== undefined) { - selection.textLabel = text[selectIndex[1]][selectIndex[0]]; - } - else selection.textLabel = ''; - - selection.data.dataCoordinate = selection.dataCoordinate.slice(); - - this.surface.highlight(selection.data); + ] + var traceCoordinate = [0, 0, 0] + + if (Array.isArray(this.data.x[0])) { + traceCoordinate[0] = this.data.x[selectIndex[1]][selectIndex[0]] + } else { + traceCoordinate[0] = this.data.x[selectIndex[0]] + } + if (Array.isArray(this.data.y[0])) { + traceCoordinate[1] = this.data.y[selectIndex[1]][selectIndex[0]] + } else { + traceCoordinate[1] = this.data.y[selectIndex[1]] + } + + traceCoordinate[2] = this.data.z[selectIndex[1]][selectIndex[0]] + selection.traceCoordinate = traceCoordinate + + var sceneLayout = this.scene.fullSceneLayout + selection.dataCoordinate = [ + sceneLayout.xaxis.d2l(traceCoordinate[0], 0, this.data.xcalendar) * this.scene.dataScale[0], + sceneLayout.yaxis.d2l(traceCoordinate[1], 0, this.data.ycalendar) * this.scene.dataScale[1], + sceneLayout.zaxis.d2l(traceCoordinate[2], 0, this.data.zcalendar) * this.scene.dataScale[2] + ] + + var text = this.data.text + if (text && text[selectIndex[1]] && text[selectIndex[1]][selectIndex[0]] !== undefined) { + selection.textLabel = text[selectIndex[1]][selectIndex[0]] + } else selection.textLabel = '' + + selection.data.dataCoordinate = selection.dataCoordinate.slice() + + this.surface.highlight(selection.data) // Snap spikes to data coordinate - this.scene.glplot.spikes.position = selection.dataCoordinate; + this.scene.glplot.spikes.position = selection.dataCoordinate - return true; + return true + } +} + +function parseColorScale (colorscale, alpha) { + if (alpha === undefined) alpha = 1 + + return colorscale.map(function (elem) { + var index = elem[0] + var color = tinycolor(elem[1]) + var rgb = color.toRgb() + return { + index: index, + rgb: [rgb.r, rgb.g, rgb.b, alpha] } -}; - -function parseColorScale(colorscale, alpha) { - if(alpha === undefined) alpha = 1; - - return colorscale.map(function(elem) { - var index = elem[0]; - var color = tinycolor(elem[1]); - var rgb = color.toRgb(); - return { - index: index, - rgb: [rgb.r, rgb.g, rgb.b, alpha] - }; - }); + }) } -function isColormapCircular(colormap) { - var first = colormap[0].rgb, - last = colormap[colormap.length - 1].rgb; +function isColormapCircular (colormap) { + var first = colormap[0].rgb, + last = colormap[colormap.length - 1].rgb - return ( + return ( first[0] === last[0] && first[1] === last[1] && first[2] === last[2] && first[3] === last[3] - ); + ) } // Pad coords by +1 -function padField(field) { - var shape = field.shape; - var nshape = [shape[0] + 2, shape[1] + 2]; - var nfield = ndarray(new Float32Array(nshape[0] * nshape[1]), nshape); +function padField (field) { + var shape = field.shape + var nshape = [shape[0] + 2, shape[1] + 2] + var nfield = ndarray(new Float32Array(nshape[0] * nshape[1]), nshape) // Center - ops.assign(nfield.lo(1, 1).hi(shape[0], shape[1]), field); + ops.assign(nfield.lo(1, 1).hi(shape[0], shape[1]), field) // Edges - ops.assign(nfield.lo(1).hi(shape[0], 1), - field.hi(shape[0], 1)); - ops.assign(nfield.lo(1, nshape[1] - 1).hi(shape[0], 1), - field.lo(0, shape[1] - 1).hi(shape[0], 1)); - ops.assign(nfield.lo(0, 1).hi(1, shape[1]), - field.hi(1)); - ops.assign(nfield.lo(nshape[0] - 1, 1).hi(1, shape[1]), - field.lo(shape[0] - 1)); + ops.assign(nfield.lo(1).hi(shape[0], 1), + field.hi(shape[0], 1)) + ops.assign(nfield.lo(1, nshape[1] - 1).hi(shape[0], 1), + field.lo(0, shape[1] - 1).hi(shape[0], 1)) + ops.assign(nfield.lo(0, 1).hi(1, shape[1]), + field.hi(1)) + ops.assign(nfield.lo(nshape[0] - 1, 1).hi(1, shape[1]), + field.lo(shape[0] - 1)) // Corners - nfield.set(0, 0, field.get(0, 0)); - nfield.set(0, nshape[1] - 1, field.get(0, shape[1] - 1)); - nfield.set(nshape[0] - 1, 0, field.get(shape[0] - 1, 0)); - nfield.set(nshape[0] - 1, nshape[1] - 1, field.get(shape[0] - 1, shape[1] - 1)); + nfield.set(0, 0, field.get(0, 0)) + nfield.set(0, nshape[1] - 1, field.get(0, shape[1] - 1)) + nfield.set(nshape[0] - 1, 0, field.get(shape[0] - 1, 0)) + nfield.set(nshape[0] - 1, nshape[1] - 1, field.get(shape[0] - 1, shape[1] - 1)) - return nfield; + return nfield } -function refine(coords) { - var minScale = Math.max(coords[0].shape[0], coords[0].shape[1]); - - if(minScale < MIN_RESOLUTION) { - var scaleF = MIN_RESOLUTION / minScale; - var nshape = [ - Math.floor((coords[0].shape[0]) * scaleF + 1)|0, - Math.floor((coords[0].shape[1]) * scaleF + 1)|0 ]; - var nsize = nshape[0] * nshape[1]; - - for(var i = 0; i < coords.length; ++i) { - var padImg = padField(coords[i]); - var scaledImg = ndarray(new Float32Array(nsize), nshape); - homography(scaledImg, padImg, [scaleF, 0, 0, - 0, scaleF, 0, - 0, 0, 1]); - coords[i] = scaledImg; - } - - return scaleF; +function refine (coords) { + var minScale = Math.max(coords[0].shape[0], coords[0].shape[1]) + + if (minScale < MIN_RESOLUTION) { + var scaleF = MIN_RESOLUTION / minScale + var nshape = [ + Math.floor((coords[0].shape[0]) * scaleF + 1) | 0, + Math.floor((coords[0].shape[1]) * scaleF + 1) | 0 ] + var nsize = nshape[0] * nshape[1] + + for (var i = 0; i < coords.length; ++i) { + var padImg = padField(coords[i]) + var scaledImg = ndarray(new Float32Array(nsize), nshape) + homography(scaledImg, padImg, [scaleF, 0, 0, + 0, scaleF, 0, + 0, 0, 1]) + coords[i] = scaledImg } - return 1.0; + return scaleF + } + + return 1.0 } -proto.setContourLevels = function() { - var nlevels = [[], [], []]; - var needsUpdate = false; +proto.setContourLevels = function () { + var nlevels = [[], [], []] + var needsUpdate = false - for(var i = 0; i < 3; ++i) { - if(this.showContour[i]) { - needsUpdate = true; - nlevels[i] = this.scene.contourLevels[i]; - } + for (var i = 0; i < 3; ++i) { + if (this.showContour[i]) { + needsUpdate = true + nlevels[i] = this.scene.contourLevels[i] } + } - if(needsUpdate) { - this.surface.update({ levels: nlevels }); - } -}; - -proto.update = function(data) { - var i, - scene = this.scene, - sceneLayout = scene.fullSceneLayout, - surface = this.surface, - alpha = data.opacity, - colormap = parseColorScale(data.colorscale, alpha), - z = data.z, - x = data.x, - y = data.y, - xaxis = sceneLayout.xaxis, - yaxis = sceneLayout.yaxis, - zaxis = sceneLayout.zaxis, - scaleFactor = scene.dataScale, - xlen = z[0].length, - ylen = z.length, - coords = [ - ndarray(new Float32Array(xlen * ylen), [xlen, ylen]), - ndarray(new Float32Array(xlen * ylen), [xlen, ylen]), - ndarray(new Float32Array(xlen * ylen), [xlen, ylen]) - ], - xc = coords[0], - yc = coords[1], - contourLevels = scene.contourLevels; + if (needsUpdate) { + this.surface.update({ levels: nlevels }) + } +} + +proto.update = function (data) { + var i, + scene = this.scene, + sceneLayout = scene.fullSceneLayout, + surface = this.surface, + alpha = data.opacity, + colormap = parseColorScale(data.colorscale, alpha), + z = data.z, + x = data.x, + y = data.y, + xaxis = sceneLayout.xaxis, + yaxis = sceneLayout.yaxis, + zaxis = sceneLayout.zaxis, + scaleFactor = scene.dataScale, + xlen = z[0].length, + ylen = z.length, + coords = [ + ndarray(new Float32Array(xlen * ylen), [xlen, ylen]), + ndarray(new Float32Array(xlen * ylen), [xlen, ylen]), + ndarray(new Float32Array(xlen * ylen), [xlen, ylen]) + ], + xc = coords[0], + yc = coords[1], + contourLevels = scene.contourLevels // Save data - this.data = data; + this.data = data /* * Fill and transpose zdata. @@ -214,169 +212,167 @@ proto.update = function(data) { * which is the transpose of 'gl-surface-plot'. */ - var xcalendar = data.xcalendar, - ycalendar = data.ycalendar, - zcalendar = data.zcalendar; + var xcalendar = data.xcalendar, + ycalendar = data.ycalendar, + zcalendar = data.zcalendar - fill(coords[2], function(row, col) { - return zaxis.d2l(z[col][row], 0, zcalendar) * scaleFactor[2]; - }); + fill(coords[2], function (row, col) { + return zaxis.d2l(z[col][row], 0, zcalendar) * scaleFactor[2] + }) // coords x - if(Array.isArray(x[0])) { - fill(xc, function(row, col) { - return xaxis.d2l(x[col][row], 0, xcalendar) * scaleFactor[0]; - }); - } else { + if (Array.isArray(x[0])) { + fill(xc, function (row, col) { + return xaxis.d2l(x[col][row], 0, xcalendar) * scaleFactor[0] + }) + } else { // ticks x - fill(xc, function(row) { - return xaxis.d2l(x[row], 0, xcalendar) * scaleFactor[0]; - }); - } + fill(xc, function (row) { + return xaxis.d2l(x[row], 0, xcalendar) * scaleFactor[0] + }) + } // coords y - if(Array.isArray(y[0])) { - fill(yc, function(row, col) { - return yaxis.d2l(y[col][row], 0, ycalendar) * scaleFactor[1]; - }); - } else { + if (Array.isArray(y[0])) { + fill(yc, function (row, col) { + return yaxis.d2l(y[col][row], 0, ycalendar) * scaleFactor[1] + }) + } else { // ticks y - fill(yc, function(row, col) { - return yaxis.d2l(y[col], 0, ycalendar) * scaleFactor[1]; - }); - } - - var params = { - colormap: colormap, - levels: [[], [], []], - showContour: [true, true, true], - showSurface: !data.hidesurface, - contourProject: [ + fill(yc, function (row, col) { + return yaxis.d2l(y[col], 0, ycalendar) * scaleFactor[1] + }) + } + + var params = { + colormap: colormap, + levels: [[], [], []], + showContour: [true, true, true], + showSurface: !data.hidesurface, + contourProject: [ [false, false, false], [false, false, false], [false, false, false] - ], - contourWidth: [1, 1, 1], - contourColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - contourTint: [1, 1, 1], - dynamicColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - dynamicWidth: [1, 1, 1], - dynamicTint: [1, 1, 1], - opacity: 1 - }; - - params.intensityBounds = [data.cmin, data.cmax]; + ], + contourWidth: [1, 1, 1], + contourColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + contourTint: [1, 1, 1], + dynamicColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + dynamicWidth: [1, 1, 1], + dynamicTint: [1, 1, 1], + opacity: 1 + } + + params.intensityBounds = [data.cmin, data.cmax] // Refine if necessary - if(data.surfacecolor) { - var intensity = ndarray(new Float32Array(xlen * ylen), [xlen, ylen]); + if (data.surfacecolor) { + var intensity = ndarray(new Float32Array(xlen * ylen), [xlen, ylen]) - fill(intensity, function(row, col) { - return data.surfacecolor[col][row]; - }); + fill(intensity, function (row, col) { + return data.surfacecolor[col][row] + }) - coords.push(intensity); - } - else { + coords.push(intensity) + } else { // when 'z' is used as 'intensity', // we must scale its value - params.intensityBounds[0] *= scaleFactor[2]; - params.intensityBounds[1] *= scaleFactor[2]; - } + params.intensityBounds[0] *= scaleFactor[2] + params.intensityBounds[1] *= scaleFactor[2] + } - this.dataScale = refine(coords); + this.dataScale = refine(coords) - if(data.surfacecolor) { - params.intensity = coords.pop(); - } + if (data.surfacecolor) { + params.intensity = coords.pop() + } - if('opacity' in data) { - if(data.opacity < 1) { - params.opacity = 0.25 * data.opacity; - } + if ('opacity' in data) { + if (data.opacity < 1) { + params.opacity = 0.25 * data.opacity + } + } + + var highlightEnable = [true, true, true] + var axis = ['x', 'y', 'z'] + + for (i = 0; i < 3; ++i) { + var contourParams = data.contours[axis[i]] + highlightEnable[i] = contourParams.highlight + + params.showContour[i] = contourParams.show || contourParams.highlight + if (!params.showContour[i]) continue + + params.contourProject[i] = [ + contourParams.project.x, + contourParams.project.y, + contourParams.project.z + ] + + if (contourParams.show) { + this.showContour[i] = true + params.levels[i] = contourLevels[i] + surface.highlightColor[i] = params.contourColor[i] = str2RgbaArray(contourParams.color) + + if (contourParams.usecolormap) { + surface.highlightTint[i] = params.contourTint[i] = 0 + } else { + surface.highlightTint[i] = params.contourTint[i] = 1 + } + params.contourWidth[i] = contourParams.width + } else { + this.showContour[i] = false } - var highlightEnable = [true, true, true]; - var axis = ['x', 'y', 'z']; - - for(i = 0; i < 3; ++i) { - var contourParams = data.contours[axis[i]]; - highlightEnable[i] = contourParams.highlight; - - params.showContour[i] = contourParams.show || contourParams.highlight; - if(!params.showContour[i]) continue; - - params.contourProject[i] = [ - contourParams.project.x, - contourParams.project.y, - contourParams.project.z - ]; - - if(contourParams.show) { - this.showContour[i] = true; - params.levels[i] = contourLevels[i]; - surface.highlightColor[i] = params.contourColor[i] = str2RgbaArray(contourParams.color); - - if(contourParams.usecolormap) { - surface.highlightTint[i] = params.contourTint[i] = 0; - } - else { - surface.highlightTint[i] = params.contourTint[i] = 1; - } - params.contourWidth[i] = contourParams.width; - } else { - this.showContour[i] = false; - } - - if(contourParams.highlight) { - params.dynamicColor[i] = str2RgbaArray(contourParams.highlightcolor); - params.dynamicWidth[i] = contourParams.highlightwidth; - } + if (contourParams.highlight) { + params.dynamicColor[i] = str2RgbaArray(contourParams.highlightcolor) + params.dynamicWidth[i] = contourParams.highlightwidth } + } // see https://github.com/plotly/plotly.js/issues/940 - if(isColormapCircular(colormap)) { - params.vertexColor = true; - } + if (isColormapCircular(colormap)) { + params.vertexColor = true + } - params.coords = coords; + params.coords = coords - surface.update(params); + surface.update(params) - surface.visible = data.visible; - surface.enableDynamic = highlightEnable; + surface.visible = data.visible + surface.enableDynamic = highlightEnable - surface.snapToData = true; + surface.snapToData = true - if('lighting' in data) { - surface.ambientLight = data.lighting.ambient; - surface.diffuseLight = data.lighting.diffuse; - surface.specularLight = data.lighting.specular; - surface.roughness = data.lighting.roughness; - surface.fresnel = data.lighting.fresnel; - } + if ('lighting' in data) { + surface.ambientLight = data.lighting.ambient + surface.diffuseLight = data.lighting.diffuse + surface.specularLight = data.lighting.specular + surface.roughness = data.lighting.roughness + surface.fresnel = data.lighting.fresnel + } - if('lightposition' in data) { - surface.lightPosition = [data.lightposition.x, data.lightposition.y, data.lightposition.z]; - } + if ('lightposition' in data) { + surface.lightPosition = [data.lightposition.x, data.lightposition.y, data.lightposition.z] + } - if(alpha && alpha < 1) { - surface.supportsTransparency = true; - } -}; - -proto.dispose = function() { - this.scene.glplot.remove(this.surface); - this.surface.dispose(); -}; - -function createSurfaceTrace(scene, data) { - var gl = scene.glplot.gl; - var surface = createSurface({ gl: gl }); - var result = new SurfaceTrace(scene, surface, data.uid); - result.update(data); - scene.glplot.add(surface); - return result; + if (alpha && alpha < 1) { + surface.supportsTransparency = true + } +} + +proto.dispose = function () { + this.scene.glplot.remove(this.surface) + this.surface.dispose() +} + +function createSurfaceTrace (scene, data) { + var gl = scene.glplot.gl + var surface = createSurface({ gl: gl }) + var result = new SurfaceTrace(scene, surface, data.uid) + result.update(data) + scene.glplot.add(surface) + return result } -module.exports = createSurfaceTrace; +module.exports = createSurfaceTrace diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index cab5da34f98..06d4331e1ea 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -6,114 +6,111 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Registry = require('../../registry') +var Lib = require('../../lib') -var Registry = require('../../registry'); -var Lib = require('../../lib'); +var colorscaleDefaults = require('../../components/colorscale/defaults') +var attributes = require('./attributes') -var colorscaleDefaults = require('../../components/colorscale/defaults'); -var attributes = require('./attributes'); +module.exports = function supplyDefaults (traceIn, traceOut, defaultColor, layout) { + var i, j + function coerce (attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt) + } -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - var i, j; + var z = coerce('z') + if (!z) { + traceOut.visible = false + return + } - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var z = coerce('z'); - if(!z) { - traceOut.visible = false; - return; - } + var xlen = z[0].length + var ylen = z.length - var xlen = z[0].length; - var ylen = z.length; + coerce('x') + coerce('y') - coerce('x'); - coerce('y'); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults') + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout) - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); - handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); - - if(!Array.isArray(traceOut.x)) { + if (!Array.isArray(traceOut.x)) { // build a linearly scaled x - traceOut.x = []; - for(i = 0; i < xlen; ++i) { - traceOut.x[i] = i; - } + traceOut.x = [] + for (i = 0; i < xlen; ++i) { + traceOut.x[i] = i } + } - coerce('text'); - if(!Array.isArray(traceOut.y)) { - traceOut.y = []; - for(i = 0; i < ylen; ++i) { - traceOut.y[i] = i; - } + coerce('text') + if (!Array.isArray(traceOut.y)) { + traceOut.y = [] + for (i = 0; i < ylen; ++i) { + traceOut.y[i] = i } + } // Coerce remaining properties - [ - 'lighting.ambient', - 'lighting.diffuse', - 'lighting.specular', - 'lighting.roughness', - 'lighting.fresnel', - 'lightposition.x', - 'lightposition.y', - 'lightposition.z', - 'hidesurface', - 'opacity' - ].forEach(function(x) { coerce(x); }); - - var surfaceColor = coerce('surfacecolor'); - - coerce('colorscale'); - - var dims = ['x', 'y', 'z']; - for(i = 0; i < 3; ++i) { - - var contourDim = 'contours.' + dims[i]; - var show = coerce(contourDim + '.show'); - var highlight = coerce(contourDim + '.highlight'); - - if(show || highlight) { - for(j = 0; j < 3; ++j) { - coerce(contourDim + '.project.' + dims[j]); - } - } - - if(show) { - coerce(contourDim + '.color'); - coerce(contourDim + '.width'); - coerce(contourDim + '.usecolormap'); - } - - if(highlight) { - coerce(contourDim + '.highlightcolor'); - coerce(contourDim + '.highlightwidth'); - } + [ + 'lighting.ambient', + 'lighting.diffuse', + 'lighting.specular', + 'lighting.roughness', + 'lighting.fresnel', + 'lightposition.x', + 'lightposition.y', + 'lightposition.z', + 'hidesurface', + 'opacity' + ].forEach(function (x) { coerce(x) }) + + var surfaceColor = coerce('surfacecolor') + + coerce('colorscale') + + var dims = ['x', 'y', 'z'] + for (i = 0; i < 3; ++i) { + var contourDim = 'contours.' + dims[i] + var show = coerce(contourDim + '.show') + var highlight = coerce(contourDim + '.highlight') + + if (show || highlight) { + for (j = 0; j < 3; ++j) { + coerce(contourDim + '.project.' + dims[j]) + } } - // backward compatibility block - if(!surfaceColor) { - mapLegacy(traceIn, 'zmin', 'cmin'); - mapLegacy(traceIn, 'zmax', 'cmax'); - mapLegacy(traceIn, 'zauto', 'cauto'); + if (show) { + coerce(contourDim + '.color') + coerce(contourDim + '.width') + coerce(contourDim + '.usecolormap') } + if (highlight) { + coerce(contourDim + '.highlightcolor') + coerce(contourDim + '.highlightwidth') + } + } + + // backward compatibility block + if (!surfaceColor) { + mapLegacy(traceIn, 'zmin', 'cmin') + mapLegacy(traceIn, 'zmax', 'cmax') + mapLegacy(traceIn, 'zauto', 'cauto') + } + // TODO if contours.?.usecolormap are false and hidesurface is true // the colorbar shouldn't be shown by default - colorscaleDefaults( + colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'} - ); -}; + ) +} -function mapLegacy(traceIn, oldAttr, newAttr) { - if(oldAttr in traceIn && !(newAttr in traceIn)) { - traceIn[newAttr] = traceIn[oldAttr]; - } +function mapLegacy (traceIn, oldAttr, newAttr) { + if (oldAttr in traceIn && !(newAttr in traceIn)) { + traceIn[newAttr] = traceIn[oldAttr] + } } diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index 8a9d1efb156..2beaefe5205 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -6,36 +6,35 @@ * LICENSE file in the root directory of this source tree. */ +'use strict' -'use strict'; +var Surface = {} -var Surface = {}; +Surface.attributes = require('./attributes') +Surface.supplyDefaults = require('./defaults') +Surface.colorbar = require('./colorbar') +Surface.calc = require('./calc') +Surface.plot = require('./convert') -Surface.attributes = require('./attributes'); -Surface.supplyDefaults = require('./defaults'); -Surface.colorbar = require('./colorbar'); -Surface.calc = require('./calc'); -Surface.plot = require('./convert'); - -Surface.moduleType = 'trace'; -Surface.name = 'surface'; -Surface.basePlotModule = require('../../plots/gl3d'); -Surface.categories = ['gl3d', 'noOpacity']; +Surface.moduleType = 'trace' +Surface.name = 'surface' +Surface.basePlotModule = require('../../plots/gl3d') +Surface.categories = ['gl3d', 'noOpacity'] Surface.meta = { - description: [ - 'The data the describes the coordinates of the surface is set in `z`.', - 'Data in `z` should be a {2D array}.', + description: [ + 'The data the describes the coordinates of the surface is set in `z`.', + 'Data in `z` should be a {2D array}.', - 'Coordinates in `x` and `y` can either be 1D {arrays}', - 'or {2D arrays} (e.g. to graph parametric surfaces).', + 'Coordinates in `x` and `y` can either be 1D {arrays}', + 'or {2D arrays} (e.g. to graph parametric surfaces).', - 'If not provided in `x` and `y`, the x and y coordinates are assumed', - 'to be linear starting at 0 with a unit step.', + 'If not provided in `x` and `y`, the x and y coordinates are assumed', + 'to be linear starting at 0 with a unit step.', - 'The color scale corresponds to the `z` values by default.', - 'For custom color scales, use `surfacecolor` which should be a {2D array},', - 'where its bounds can be controlled using `cmin` and `cmax`.' - ].join(' ') -}; + 'The color scale corresponds to the `z` values by default.', + 'For custom color scales, use `surfacecolor` which should be a {2D array},', + 'where its bounds can be controlled using `cmin` and `cmax`.' + ].join(' ') +} -module.exports = Surface; +module.exports = Surface diff --git a/src/transforms/filter.js b/src/transforms/filter.js index f77285b5927..46039f66f56 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -6,330 +6,326 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../lib'); -var Registry = require('../registry'); -var PlotSchema = require('../plot_api/plot_schema'); -var axisIds = require('../plots/cartesian/axis_ids'); -var autoType = require('../plots/cartesian/axis_autotype'); -var setConvert = require('../plots/cartesian/set_convert'); +var Lib = require('../lib') +var Registry = require('../registry') +var PlotSchema = require('../plot_api/plot_schema') +var axisIds = require('../plots/cartesian/axis_ids') +var autoType = require('../plots/cartesian/axis_autotype') +var setConvert = require('../plots/cartesian/set_convert') -var INEQUALITY_OPS = ['=', '<', '>=', '>', '<=']; -var INTERVAL_OPS = ['[]', '()', '[)', '(]', '][', ')(', '](', ')[']; -var SET_OPS = ['{}', '}{']; +var INEQUALITY_OPS = ['=', '<', '>=', '>', '<='] +var INTERVAL_OPS = ['[]', '()', '[)', '(]', '][', ')(', '](', ')['] +var SET_OPS = ['{}', '}{'] -exports.moduleType = 'transform'; +exports.moduleType = 'transform' -exports.name = 'filter'; +exports.name = 'filter' exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - description: [ - 'Determines whether this filter transform is enabled or disabled.' - ].join(' ') - }, - target: { - valType: 'string', - strict: true, - noBlank: true, - arrayOk: true, - dflt: 'x', - description: [ - 'Sets the filter target by which the filter is applied.', - - 'If a string, *target* is assumed to be a reference to a data array', - 'in the parent trace object.', - 'To filter about nested variables, use *.* to access them.', - 'For example, set `target` to *marker.color* to filter', - 'about the marker color array.', - - 'If an array, *target* is then the data array by which the filter is applied.' - ].join(' ') - }, - operation: { - valType: 'enumerated', - values: [].concat(INEQUALITY_OPS).concat(INTERVAL_OPS).concat(SET_OPS), - dflt: '=', - description: [ - 'Sets the filter operation.', - - '*=* keeps items equal to `value`', - - '*<* keeps items less than `value`', - '*<=* keeps items less than or equal to `value`', - - '*>* keeps items greater than `value`', - '*>=* keeps items greater than or equal to `value`', - - '*[]* keeps items inside `value[0]` to value[1]` including both bounds`', - '*()* keeps items inside `value[0]` to value[1]` excluding both bounds`', - '*[)* keeps items inside `value[0]` to value[1]` including `value[0]` but excluding `value[1]', - '*(]* keeps items inside `value[0]` to value[1]` excluding `value[0]` but including `value[1]', - - '*][* keeps items outside `value[0]` to value[1]` and equal to both bounds`', - '*)(* keeps items outside `value[0]` to value[1]`', - '*](* keeps items outside `value[0]` to value[1]` and equal to `value[0]`', - '*)[* keeps items outside `value[0]` to value[1]` and equal to `value[1]`', - - '*{}* keeps items present in a set of values', - '*}{* keeps items not present in a set of values' - ].join(' ') - }, - value: { - valType: 'any', - dflt: 0, - description: [ - 'Sets the value or values by which to filter by.', - - 'Values are expected to be in the same type as the data linked', - 'to *target*.', - - 'When `operation` is set to one of the inequality values', - '(' + INEQUALITY_OPS + ')', - '*value* is expected to be a number or a string.', - - 'When `operation` is set to one of the interval value', - '(' + INTERVAL_OPS + ')', - '*value* is expected to be 2-item array where the first item', - 'is the lower bound and the second item is the upper bound.', - - 'When `operation`, is set to one of the set value', - '(' + SET_OPS + ')', - '*value* is expected to be an array with as many items as', - 'the desired set elements.' - ].join(' ') - } -}; + enabled: { + valType: 'boolean', + dflt: true, + description: [ + 'Determines whether this filter transform is enabled or disabled.' + ].join(' ') + }, + target: { + valType: 'string', + strict: true, + noBlank: true, + arrayOk: true, + dflt: 'x', + description: [ + 'Sets the filter target by which the filter is applied.', + + 'If a string, *target* is assumed to be a reference to a data array', + 'in the parent trace object.', + 'To filter about nested variables, use *.* to access them.', + 'For example, set `target` to *marker.color* to filter', + 'about the marker color array.', + + 'If an array, *target* is then the data array by which the filter is applied.' + ].join(' ') + }, + operation: { + valType: 'enumerated', + values: [].concat(INEQUALITY_OPS).concat(INTERVAL_OPS).concat(SET_OPS), + dflt: '=', + description: [ + 'Sets the filter operation.', + + '*=* keeps items equal to `value`', + + '*<* keeps items less than `value`', + '*<=* keeps items less than or equal to `value`', + + '*>* keeps items greater than `value`', + '*>=* keeps items greater than or equal to `value`', + + '*[]* keeps items inside `value[0]` to value[1]` including both bounds`', + '*()* keeps items inside `value[0]` to value[1]` excluding both bounds`', + '*[)* keeps items inside `value[0]` to value[1]` including `value[0]` but excluding `value[1]', + '*(]* keeps items inside `value[0]` to value[1]` excluding `value[0]` but including `value[1]', + + '*][* keeps items outside `value[0]` to value[1]` and equal to both bounds`', + '*)(* keeps items outside `value[0]` to value[1]`', + '*](* keeps items outside `value[0]` to value[1]` and equal to `value[0]`', + '*)[* keeps items outside `value[0]` to value[1]` and equal to `value[1]`', + + '*{}* keeps items present in a set of values', + '*}{* keeps items not present in a set of values' + ].join(' ') + }, + value: { + valType: 'any', + dflt: 0, + description: [ + 'Sets the value or values by which to filter by.', + + 'Values are expected to be in the same type as the data linked', + 'to *target*.', + + 'When `operation` is set to one of the inequality values', + '(' + INEQUALITY_OPS + ')', + '*value* is expected to be a number or a string.', + + 'When `operation` is set to one of the interval value', + '(' + INTERVAL_OPS + ')', + '*value* is expected to be 2-item array where the first item', + 'is the lower bound and the second item is the upper bound.', + + 'When `operation`, is set to one of the set value', + '(' + SET_OPS + ')', + '*value* is expected to be an array with as many items as', + 'the desired set elements.' + ].join(' ') + } +} -exports.supplyDefaults = function(transformIn) { - var transformOut = {}; +exports.supplyDefaults = function (transformIn) { + var transformOut = {} - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt) + } - var enabled = coerce('enabled'); + var enabled = coerce('enabled') - if(enabled) { - coerce('operation'); - coerce('value'); - coerce('target'); + if (enabled) { + coerce('operation') + coerce('value') + coerce('target') - var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); - handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null); - handleCalendarDefaults(transformIn, transformOut, 'targetcalendar', null); - } + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults') + handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null) + handleCalendarDefaults(transformIn, transformOut, 'targetcalendar', null) + } - return transformOut; -}; + return transformOut +} -exports.calcTransform = function(gd, trace, opts) { - if(!opts.enabled) return; +exports.calcTransform = function (gd, trace, opts) { + if (!opts.enabled) return - var target = opts.target, - filterArray = getFilterArray(trace, target), - len = filterArray.length; + var target = opts.target, + filterArray = getFilterArray(trace, target), + len = filterArray.length - if(!len) return; + if (!len) return - var targetCalendar = opts.targetcalendar; + var targetCalendar = opts.targetcalendar // even if you provide targetcalendar, if target is a string and there // is a calendar attribute matching target it will get used instead. - if(typeof target === 'string') { - var attrTargetCalendar = Lib.nestedProperty(trace, target + 'calendar').get(); - if(attrTargetCalendar) targetCalendar = attrTargetCalendar; - } + if (typeof target === 'string') { + var attrTargetCalendar = Lib.nestedProperty(trace, target + 'calendar').get() + if (attrTargetCalendar) targetCalendar = attrTargetCalendar + } // if target points to an axis, use the type we already have for that // axis to find the data type. Otherwise use the values to autotype. - var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ? - target : filterArray; + var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ? + target : filterArray - var dataToCoord = getDataToCoordFunc(gd, trace, d2cTarget), - filterFunc = getFilterFunc(opts, dataToCoord, targetCalendar), - arrayAttrs = PlotSchema.findArrayAttributes(trace), - originalArrays = {}; + var dataToCoord = getDataToCoordFunc(gd, trace, d2cTarget), + filterFunc = getFilterFunc(opts, dataToCoord, targetCalendar), + arrayAttrs = PlotSchema.findArrayAttributes(trace), + originalArrays = {} // copy all original array attribute values, // and clear arrays in trace - for(var k = 0; k < arrayAttrs.length; k++) { - var attr = arrayAttrs[k], - np = Lib.nestedProperty(trace, attr); + for (var k = 0; k < arrayAttrs.length; k++) { + var attr = arrayAttrs[k], + np = Lib.nestedProperty(trace, attr) - originalArrays[attr] = Lib.extendDeep([], np.get()); - np.set([]); - } + originalArrays[attr] = Lib.extendDeep([], np.get()) + np.set([]) + } - function fill(attr, i) { - var oldArr = originalArrays[attr], - newArr = Lib.nestedProperty(trace, attr).get(); + function fill (attr, i) { + var oldArr = originalArrays[attr], + newArr = Lib.nestedProperty(trace, attr).get() - newArr.push(oldArr[i]); - } + newArr.push(oldArr[i]) + } - for(var i = 0; i < len; i++) { - var v = filterArray[i]; + for (var i = 0; i < len; i++) { + var v = filterArray[i] - if(!filterFunc(v)) continue; + if (!filterFunc(v)) continue - for(var j = 0; j < arrayAttrs.length; j++) { - fill(arrayAttrs[j], i); - } + for (var j = 0; j < arrayAttrs.length; j++) { + fill(arrayAttrs[j], i) } -}; + } +} -function getFilterArray(trace, target) { - if(typeof target === 'string' && target) { - var array = Lib.nestedProperty(trace, target).get(); +function getFilterArray (trace, target) { + if (typeof target === 'string' && target) { + var array = Lib.nestedProperty(trace, target).get() - return Array.isArray(array) ? array : []; - } - else if(Array.isArray(target)) return target.slice(); + return Array.isArray(array) ? array : [] + } else if (Array.isArray(target)) return target.slice() - return false; + return false } -function getDataToCoordFunc(gd, trace, target) { - var ax; +function getDataToCoordFunc (gd, trace, target) { + var ax // In the case of an array target, make a mock data array // and call supplyDefaults to the data type and // setup the data-to-calc method. - if(Array.isArray(target)) { - ax = { - type: autoType(target), - _categories: [] - }; + if (Array.isArray(target)) { + ax = { + type: autoType(target), + _categories: [] + } - setConvert(ax); + setConvert(ax) - if(ax.type === 'category') { + if (ax.type === 'category') { // build up ax._categories (usually done during ax.makeCalcdata() - for(var i = 0; i < target.length; i++) { - ax.d2c(target[i]); - } - } - } - else { - ax = axisIds.getFromTrace(gd, trace, target); + for (var i = 0; i < target.length; i++) { + ax.d2c(target[i]) + } } + } else { + ax = axisIds.getFromTrace(gd, trace, target) + } // if 'target' has corresponding axis // -> use setConvert method - if(ax) return ax.d2c; + if (ax) return ax.d2c // special case for 'ids' // -> cast to String - if(target === 'ids') return function(v) { return String(v); }; + if (target === 'ids') return function (v) { return String(v) } // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size') // -> cast to Number - return function(v) { return +v; }; + return function (v) { return +v } } -function getFilterFunc(opts, d2c, targetCalendar) { - var operation = opts.operation, - value = opts.value, - hasArrayValue = Array.isArray(value); +function getFilterFunc (opts, d2c, targetCalendar) { + var operation = opts.operation, + value = opts.value, + hasArrayValue = Array.isArray(value) - function isOperationIn(array) { - return array.indexOf(operation) !== -1; - } + function isOperationIn (array) { + return array.indexOf(operation) !== -1 + } - var d2cValue = function(v) { return d2c(v, 0, opts.valuecalendar); }, - d2cTarget = function(v) { return d2c(v, 0, targetCalendar); }; + var d2cValue = function (v) { return d2c(v, 0, opts.valuecalendar) }, + d2cTarget = function (v) { return d2c(v, 0, targetCalendar) } - var coercedValue; + var coercedValue - if(isOperationIn(INEQUALITY_OPS)) { - coercedValue = hasArrayValue ? d2cValue(value[0]) : d2cValue(value); - } - else if(isOperationIn(INTERVAL_OPS)) { - coercedValue = hasArrayValue ? + if (isOperationIn(INEQUALITY_OPS)) { + coercedValue = hasArrayValue ? d2cValue(value[0]) : d2cValue(value) + } else if (isOperationIn(INTERVAL_OPS)) { + coercedValue = hasArrayValue ? [d2cValue(value[0]), d2cValue(value[1])] : - [d2cValue(value), d2cValue(value)]; - } - else if(isOperationIn(SET_OPS)) { - coercedValue = hasArrayValue ? value.map(d2cValue) : [d2cValue(value)]; - } - - switch(operation) { - - case '=': - return function(v) { return d2cTarget(v) === coercedValue; }; - - case '<': - return function(v) { return d2cTarget(v) < coercedValue; }; - - case '<=': - return function(v) { return d2cTarget(v) <= coercedValue; }; - - case '>': - return function(v) { return d2cTarget(v) > coercedValue; }; - - case '>=': - return function(v) { return d2cTarget(v) >= coercedValue; }; - - case '[]': - return function(v) { - var cv = d2cTarget(v); - return cv >= coercedValue[0] && cv <= coercedValue[1]; - }; - - case '()': - return function(v) { - var cv = d2cTarget(v); - return cv > coercedValue[0] && cv < coercedValue[1]; - }; - - case '[)': - return function(v) { - var cv = d2cTarget(v); - return cv >= coercedValue[0] && cv < coercedValue[1]; - }; - - case '(]': - return function(v) { - var cv = d2cTarget(v); - return cv > coercedValue[0] && cv <= coercedValue[1]; - }; - - case '][': - return function(v) { - var cv = d2cTarget(v); - return cv <= coercedValue[0] || cv >= coercedValue[1]; - }; - - case ')(': - return function(v) { - var cv = d2cTarget(v); - return cv < coercedValue[0] || cv > coercedValue[1]; - }; - - case '](': - return function(v) { - var cv = d2cTarget(v); - return cv <= coercedValue[0] || cv > coercedValue[1]; - }; - - case ')[': - return function(v) { - var cv = d2cTarget(v); - return cv < coercedValue[0] || cv >= coercedValue[1]; - }; - - case '{}': - return function(v) { - return coercedValue.indexOf(d2cTarget(v)) !== -1; - }; - - case '}{': - return function(v) { - return coercedValue.indexOf(d2cTarget(v)) === -1; - }; - } + [d2cValue(value), d2cValue(value)] + } else if (isOperationIn(SET_OPS)) { + coercedValue = hasArrayValue ? value.map(d2cValue) : [d2cValue(value)] + } + + switch (operation) { + + case '=': + return function (v) { return d2cTarget(v) === coercedValue } + + case '<': + return function (v) { return d2cTarget(v) < coercedValue } + + case '<=': + return function (v) { return d2cTarget(v) <= coercedValue } + + case '>': + return function (v) { return d2cTarget(v) > coercedValue } + + case '>=': + return function (v) { return d2cTarget(v) >= coercedValue } + + case '[]': + return function (v) { + var cv = d2cTarget(v) + return cv >= coercedValue[0] && cv <= coercedValue[1] + } + + case '()': + return function (v) { + var cv = d2cTarget(v) + return cv > coercedValue[0] && cv < coercedValue[1] + } + + case '[)': + return function (v) { + var cv = d2cTarget(v) + return cv >= coercedValue[0] && cv < coercedValue[1] + } + + case '(]': + return function (v) { + var cv = d2cTarget(v) + return cv > coercedValue[0] && cv <= coercedValue[1] + } + + case '][': + return function (v) { + var cv = d2cTarget(v) + return cv <= coercedValue[0] || cv >= coercedValue[1] + } + + case ')(': + return function (v) { + var cv = d2cTarget(v) + return cv < coercedValue[0] || cv > coercedValue[1] + } + + case '](': + return function (v) { + var cv = d2cTarget(v) + return cv <= coercedValue[0] || cv > coercedValue[1] + } + + case ')[': + return function (v) { + var cv = d2cTarget(v) + return cv < coercedValue[0] || cv >= coercedValue[1] + } + + case '{}': + return function (v) { + return coercedValue.indexOf(d2cTarget(v)) !== -1 + } + + case '}{': + return function (v) { + return coercedValue.indexOf(d2cTarget(v)) === -1 + } + } } diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 0cd744529ca..a679536c872 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -6,45 +6,45 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +'use strict' -var Lib = require('../lib'); -var PlotSchema = require('../plot_api/plot_schema'); +var Lib = require('../lib') +var PlotSchema = require('../plot_api/plot_schema') -exports.moduleType = 'transform'; +exports.moduleType = 'transform' -exports.name = 'groupby'; +exports.name = 'groupby' exports.attributes = { - enabled: { - valType: 'boolean', - dflt: true, - description: [ - 'Determines whether this group-by transform is enabled or disabled.' - ].join(' ') - }, - groups: { - valType: 'data_array', - dflt: [], - description: [ - 'Sets the groups in which the trace data will be split.', - 'For example, with `x` set to *[1, 2, 3, 4]* and', - '`groups` set to *[\'a\', \'b\', \'a\', \'b\']*,', - 'the groupby transform with split in one trace', - 'with `x` [1, 3] and one trace with `x` [2, 4].' - ].join(' ') - }, - style: { - valType: 'any', - dflt: {}, - description: [ - 'Sets each group style.', - 'For example, with `groups` set to *[\'a\', \'b\', \'a\', \'b\']*', - 'and `style` set to *{ a: { marker: { color: \'red\' } }}', - 'marker points in group *\'a\'* will be drawn in red.' - ].join(' ') - } -}; + enabled: { + valType: 'boolean', + dflt: true, + description: [ + 'Determines whether this group-by transform is enabled or disabled.' + ].join(' ') + }, + groups: { + valType: 'data_array', + dflt: [], + description: [ + 'Sets the groups in which the trace data will be split.', + 'For example, with `x` set to *[1, 2, 3, 4]* and', + '`groups` set to *[\'a\', \'b\', \'a\', \'b\']*,', + 'the groupby transform with split in one trace', + 'with `x` [1, 3] and one trace with `x` [2, 4].' + ].join(' ') + }, + style: { + valType: 'any', + dflt: {}, + description: [ + 'Sets each group style.', + 'For example, with `groups` set to *[\'a\', \'b\', \'a\', \'b\']*', + 'and `style` set to *{ a: { marker: { color: \'red\' } }}', + 'marker points in group *\'a\'* will be drawn in red.' + ].join(' ') + } +} /** * Supply transform attributes defaults @@ -59,22 +59,22 @@ exports.attributes = { * @return {object} transformOut * copy of transformIn that contains attribute defaults */ -exports.supplyDefaults = function(transformIn) { - var transformOut = {}; +exports.supplyDefaults = function (transformIn) { + var transformOut = {} - function coerce(attr, dflt) { - return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt); - } + function coerce (attr, dflt) { + return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt) + } - var enabled = coerce('enabled'); + var enabled = coerce('enabled') - if(!enabled) return transformOut; + if (!enabled) return transformOut - coerce('groups'); - coerce('style'); + coerce('groups') + coerce('style') - return transformOut; -}; + return transformOut +} /** * Apply transform !!! @@ -92,63 +92,63 @@ exports.supplyDefaults = function(transformIn) { * @return {object} newData * array of transformed traces */ -exports.transform = function(data, state) { - var newData = []; +exports.transform = function (data, state) { + var newData = [] - for(var i = 0; i < data.length; i++) { - newData = newData.concat(transformOne(data[i], state)); - } + for (var i = 0; i < data.length; i++) { + newData = newData.concat(transformOne(data[i], state)) + } - return newData; -}; + return newData +} -function initializeArray(newTrace, a) { - Lib.nestedProperty(newTrace, a).set([]); +function initializeArray (newTrace, a) { + Lib.nestedProperty(newTrace, a).set([]) } -function pasteArray(newTrace, trace, j, a) { - Lib.nestedProperty(newTrace, a).set( +function pasteArray (newTrace, trace, j, a) { + Lib.nestedProperty(newTrace, a).set( Lib.nestedProperty(newTrace, a).get().concat([ - Lib.nestedProperty(trace, a).get()[j] + Lib.nestedProperty(trace, a).get()[j] ]) - ); + ) } -function transformOne(trace, state) { - var opts = state.transform; - var groups = trace.transforms[state.transformIndex].groups; +function transformOne (trace, state) { + var opts = state.transform + var groups = trace.transforms[state.transformIndex].groups - if(!(Array.isArray(groups)) || groups.length === 0) { - return trace; - } + if (!(Array.isArray(groups)) || groups.length === 0) { + return trace + } - var groupNames = Lib.filterUnique(groups), - newData = new Array(groupNames.length), - len = groups.length; + var groupNames = Lib.filterUnique(groups), + newData = new Array(groupNames.length), + len = groups.length - var arrayAttrs = PlotSchema.findArrayAttributes(trace); + var arrayAttrs = PlotSchema.findArrayAttributes(trace) - var style = opts.style || {}; + var style = opts.style || {} - for(var i = 0; i < groupNames.length; i++) { - var groupName = groupNames[i]; + for (var i = 0; i < groupNames.length; i++) { + var groupName = groupNames[i] - var newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace); + var newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace) - arrayAttrs.forEach(initializeArray.bind(null, newTrace)); + arrayAttrs.forEach(initializeArray.bind(null, newTrace)) - for(var j = 0; j < len; j++) { - if(groups[j] !== groupName) continue; + for (var j = 0; j < len; j++) { + if (groups[j] !== groupName) continue - arrayAttrs.forEach(pasteArray.bind(0, newTrace, trace, j)); - } + arrayAttrs.forEach(pasteArray.bind(0, newTrace, trace, j)) + } - newTrace.name = groupName; + newTrace.name = groupName // there's no need to coerce style[groupName] here // as another round of supplyDefaults is done on the transformed traces - newTrace = Lib.extendDeepNoArrays(newTrace, style[groupName] || {}); - } + newTrace = Lib.extendDeepNoArrays(newTrace, style[groupName] || {}) + } - return newData; + return newData }