diff --git a/package.json b/package.json index 3c0c7b766ea..9389c1ba03a 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "gzip-size": "^3.0.0", "image-size": "^0.5.1", "jasmine-core": "^2.4.1", + "jsdom": "^11.2.0", "karma": "^1.5.0", "karma-browserify": "^5.1.1", "karma-chrome-launcher": "^2.0.0", diff --git a/src/components/annotations/arrow_paths.js b/src/components/annotations/arrow_paths.js index 3f27bbaf83a..172792076fe 100644 --- a/src/components/annotations/arrow_paths.js +++ b/src/components/annotations/arrow_paths.js @@ -9,14 +9,13 @@ 'use strict'; /** - * centerx is a center of scaling tuned for maximum scalability of - * the arrowhead ie throughout mag=0.3..3 the head is joined smoothly - * to the line, but the endpoint moves. - * backoff is the distance to move the arrowhead, and the end of the - * line, in order to end at the right place - * - * TODO: option to have the pointed-to point a little in front of the - * end of the line, as people tend to want a bit of a gap there... + * All paths are tuned for maximum scalability of the arrowhead, + * ie throughout arrowwidth=0.3..3 the head is joined smoothly + * to the line, with the line coming from the left and ending at (0, 0). + * `backoff` is the distance to move the arrowhead and the end of the line, + * in order that the arrowhead points to the desired place, either at + * the tip of the arrow or (in the case of circle or square) + * the center of the symbol. */ module.exports = [ diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js index 779f4a899e4..9b5426c8d69 100644 --- a/src/components/annotations/attributes.js +++ b/src/components/annotations/attributes.js @@ -11,7 +11,6 @@ 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 = { @@ -21,6 +20,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, + editType: 'calcIfAutorange', description: [ 'Determines whether or not this annotation is visible.' ].join(' ') @@ -29,6 +29,7 @@ module.exports = { text: { valType: 'string', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the text associated with this annotation.', 'Plotly uses a subset of HTML tags to do things like', @@ -41,12 +42,15 @@ module.exports = { valType: 'angle', dflt: 0, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the angle at which the `text` is drawn', 'with respect to the horizontal.' ].join(' ') }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ + editType: 'calcIfAutorange', + colorEditType: 'arraydraw', description: 'Sets the annotation text font.' }), width: { @@ -54,6 +58,7 @@ module.exports = { min: 1, dflt: null, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets an explicit width for the text box. null (default) lets the', 'text set the box width. Wider text will be clipped.', @@ -65,6 +70,7 @@ module.exports = { min: 1, dflt: null, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets an explicit height for the text box. null (default) lets the', 'text set the box height. Taller text will be clipped.' @@ -76,6 +82,7 @@ module.exports = { max: 1, dflt: 1, role: 'style', + editType: 'arraydraw', description: 'Sets the opacity of the annotation (text + arrow).' }, align: { @@ -83,6 +90,7 @@ module.exports = { values: ['left', 'center', 'right'], dflt: 'center', role: 'style', + editType: 'arraydraw', description: [ 'Sets the horizontal alignment of the `text` within the box.', 'Has an effect only if `text` spans more two or more lines', @@ -95,6 +103,7 @@ module.exports = { values: ['top', 'middle', 'bottom'], dflt: 'middle', role: 'style', + editType: 'arraydraw', description: [ 'Sets the vertical alignment of the `text` within the box.', 'Has an effect only if an explicit height is set to override', @@ -105,12 +114,14 @@ module.exports = { valType: 'color', dflt: 'rgba(0,0,0,0)', role: 'style', + editType: 'arraydraw', description: 'Sets the background color of the annotation.' }, bordercolor: { valType: 'color', dflt: 'rgba(0,0,0,0)', role: 'style', + editType: 'arraydraw', description: [ 'Sets the color of the border enclosing the annotation `text`.' ].join(' ') @@ -120,6 +131,7 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the padding (in px) between the `text`', 'and the enclosing border.' @@ -130,6 +142,7 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the width (in px) of the border enclosing', 'the annotation `text`.' @@ -140,6 +153,7 @@ module.exports = { valType: 'boolean', dflt: true, role: 'style', + editType: 'calcIfAutorange', description: [ 'Determines whether or not the annotation is drawn with an arrow.', 'If *true*, `text` is placed near the arrow\'s tail.', @@ -149,6 +163,7 @@ module.exports = { arrowcolor: { valType: 'color', role: 'style', + editType: 'arraydraw', description: 'Sets the color of the annotation arrow.' }, arrowhead: { @@ -157,6 +172,7 @@ module.exports = { max: ARROWPATHS.length, dflt: 1, role: 'style', + editType: 'arraydraw', description: 'Sets the annotation arrow head style.' }, arrowsize: { @@ -164,19 +180,25 @@ module.exports = { min: 0.3, dflt: 1, role: 'style', - description: 'Sets the size (in px) of annotation arrow head.' + editType: 'calcIfAutorange', + description: [ + 'Sets the size of the annotation arrow head, relative to `arrowwidth`.', + 'A value of 1 (default) gives a head about 3x as wide as the line.' + ].join(' ') }, arrowwidth: { valType: 'number', min: 0.1, role: 'style', - description: 'Sets the width (in px) of annotation arrow.' + editType: 'calcIfAutorange', + description: 'Sets the width (in px) of annotation arrow line.' }, standoff: { valType: 'number', min: 0, dflt: 0, role: 'style', + editType: 'calcIfAutorange', 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', @@ -188,6 +210,7 @@ module.exports = { ax: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the x component of the arrow tail about the arrow head.', 'If `axref` is `pixel`, a positive (negative) ', @@ -200,6 +223,7 @@ module.exports = { ay: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the y component of the arrow tail about the arrow head.', 'If `ayref` is `pixel`, a positive (negative) ', @@ -217,6 +241,7 @@ module.exports = { cartesianConstants.idRegex.x.toString() ], role: 'info', + editType: 'calc', description: [ 'Indicates in what terms the tail of the annotation (ax,ay) ', 'is specified. If `pixel`, `ax` is a relative offset in pixels ', @@ -234,6 +259,7 @@ module.exports = { cartesianConstants.idRegex.y.toString() ], role: 'info', + editType: 'calc', description: [ 'Indicates in what terms the tail of the annotation (ax,ay) ', 'is specified. If `pixel`, `ay` is a relative offset in pixels ', @@ -251,6 +277,7 @@ module.exports = { cartesianConstants.idRegex.x.toString() ], role: 'info', + editType: 'calc', description: [ 'Sets the annotation\'s x coordinate axis.', 'If set to an x axis id (e.g. *x* or *x2*), the `x` position', @@ -263,6 +290,7 @@ module.exports = { x: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the annotation\'s x position.', 'If the axis `type` is *log*, then you must take the', @@ -280,6 +308,7 @@ module.exports = { values: ['auto', 'left', 'center', 'right'], dflt: 'auto', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the text box\'s horizontal position anchor', 'This anchor binds the `x` position to the *left*, *center*', @@ -298,6 +327,7 @@ module.exports = { valType: 'number', dflt: 0, role: 'style', + editType: 'calcIfAutorange', description: [ 'Shifts the position of the whole annotation and arrow to the', 'right (positive) or left (negative) by this many pixels.' @@ -310,6 +340,7 @@ module.exports = { cartesianConstants.idRegex.y.toString() ], role: 'info', + editType: 'calc', description: [ 'Sets the annotation\'s y coordinate axis.', 'If set to an y axis id (e.g. *y* or *y2*), the `y` position', @@ -322,6 +353,7 @@ module.exports = { y: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the annotation\'s y position.', 'If the axis `type` is *log*, then you must take the', @@ -339,6 +371,7 @@ module.exports = { values: ['auto', 'top', 'middle', 'bottom'], dflt: 'auto', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the text box\'s vertical position anchor', 'This anchor binds the `y` position to the *top*, *middle*', @@ -357,6 +390,7 @@ module.exports = { valType: 'number', dflt: 0, role: 'style', + editType: 'calcIfAutorange', description: [ 'Shifts the position of the whole annotation and arrow up', '(positive) or down (negative) by this many pixels.' @@ -367,6 +401,7 @@ module.exports = { values: [false, 'onoff', 'onout'], dflt: false, role: 'style', + editType: 'arraydraw', description: [ 'Makes this annotation respond to clicks on the plot.', 'If you click a data point that exactly matches the `x` and `y`', @@ -385,6 +420,7 @@ module.exports = { xclick: { valType: 'any', role: 'info', + editType: 'arraydraw', description: [ 'Toggle this annotation when clicking a data point whose `x` value', 'is `xclick` rather than the annotation\'s `x` value.' @@ -393,6 +429,7 @@ module.exports = { yclick: { valType: 'any', role: 'info', + editType: 'arraydraw', description: [ 'Toggle this annotation when clicking a data point whose `y` value', 'is `yclick` rather than the annotation\'s `y` value.' @@ -401,6 +438,7 @@ module.exports = { hovertext: { valType: 'string', role: 'info', + editType: 'arraydraw', description: [ 'Sets text to appear when hovering over this annotation.', 'If omitted or blank, no hover label will appear.' @@ -410,6 +448,7 @@ module.exports = { bgcolor: { valType: 'color', role: 'style', + editType: 'arraydraw', description: [ 'Sets the background color of the hover label.', 'By default uses the annotation\'s `bgcolor` made opaque,', @@ -419,23 +458,27 @@ module.exports = { bordercolor: { valType: 'color', role: 'style', + editType: 'arraydraw', description: [ 'Sets the border color of the hover label.', 'By default uses either dark grey or white, for maximum', 'contrast with `hoverlabel.bgcolor`.' ].join(' ') }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ + editType: 'arraydraw', description: [ 'Sets the hover label text font.', 'By default uses the global hover font and size,', 'with color from `hoverlabel.bordercolor`.' ].join(' ') - }) + }), + editType: 'arraydraw' }, captureevents: { valType: 'boolean', role: 'info', + editType: 'arraydraw', description: [ 'Determines whether the annotation text box captures mouse move', 'and click events, or allows those events to pass through to data', @@ -445,11 +488,13 @@ module.exports = { 'you must explicitly enable `captureevents`.' ].join(' ') }, + editType: 'calc', _deprecated: { ref: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'Obsolete. Set `xref` and `yref` separately instead.' ].join(' ') diff --git a/src/components/annotations3d/attributes.js b/src/components/annotations3d/attributes.js index ac19539d761..12ec181862a 100644 --- a/src/components/annotations3d/attributes.js +++ b/src/components/annotations3d/attributes.js @@ -10,8 +10,9 @@ 'use strict'; var annAtts = require('../annotations/attributes'); +var overrideAll = require('../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ _isLinkedToArray: 'annotation', visible: annAtts.visible, @@ -76,7 +77,7 @@ module.exports = { standoff: annAtts.standoff, hovertext: annAtts.hovertext, hoverlabel: annAtts.hoverlabel, - captureevents: annAtts.captureevents + captureevents: annAtts.captureevents, // maybes later? // clicktoshow: annAtts.clicktoshow, @@ -89,4 +90,4 @@ module.exports = { // xref: 'x' // yref: 'y // zref: 'z' -}; +}, 'calc', 'from-root'); diff --git a/src/components/annotations3d/index.js b/src/components/annotations3d/index.js index c6a582ccd8d..4ff43fa9f56 100644 --- a/src/components/annotations3d/index.js +++ b/src/components/annotations3d/index.js @@ -13,8 +13,8 @@ module.exports = { name: 'annotations3d', schema: { - layout: { - 'scene.annotations': require('./attributes') + subplots: { + scene: {annotations: require('./attributes')} } }, diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js index eca51c1ac8a..097a46b5dc6 100644 --- a/src/components/calendars/index.js +++ b/src/components/calendars/index.js @@ -20,6 +20,7 @@ var attributes = { valType: 'enumerated', values: Object.keys(calendars.calendars), role: 'info', + editType: 'calc', dflt: 'gregorian' }; @@ -205,6 +206,7 @@ module.exports = { traces: { scatter: xyAttrs, bar: xyAttrs, + box: xyAttrs, heatmap: xyAttrs, contour: xyAttrs, histogram: xyAttrs, @@ -221,12 +223,22 @@ module.exports = { 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 + ].join(' ')) + }, + subplots: { + xaxis: {calendar: axisAttrs}, + yaxis: {calendar: axisAttrs}, + scene: { + xaxis: {calendar: axisAttrs}, + // TODO: it's actually redundant to include yaxis and zaxis here + // because in the scene attributes these are the same object so merging + // into one merges into them all. However, I left them in for parity with + // cartesian, where yaxis is unused until we Plotschema.get() when we + // use its presence or absence to determine whether to delete attributes + // from yaxis if they only apply to x (rangeselector/rangeslider) + yaxis: {calendar: axisAttrs}, + zaxis: {calendar: axisAttrs} + } }, transforms: { filter: { diff --git a/src/components/colorbar/attributes.js b/src/components/colorbar/attributes.js index 62f1e031ff8..67ce2557895 100644 --- a/src/components/colorbar/attributes.js +++ b/src/components/colorbar/attributes.js @@ -11,9 +11,10 @@ var axesAttrs = require('../../plots/cartesian/layout_attributes'); var fontAttrs = require('../../plots/font_attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ // TODO: only right is supported currently // orient: { // valType: 'enumerated', @@ -160,7 +161,9 @@ module.exports = { tickwidth: axesAttrs.tickwidth, tickcolor: axesAttrs.tickcolor, showticklabels: axesAttrs.showticklabels, - tickfont: axesAttrs.tickfont, + tickfont: fontAttrs({ + description: 'Sets the color bar\'s tick label font' + }), tickangle: axesAttrs.tickangle, tickformat: axesAttrs.tickformat, tickprefix: axesAttrs.tickprefix, @@ -176,10 +179,8 @@ module.exports = { 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(' ') + titlefont: fontAttrs({ + description: 'Sets this color bar\'s title font.' }), titleside: { valType: 'enumerated', @@ -191,4 +192,4 @@ module.exports = { 'with respect to the color bar.' ].join(' ') } -}; +}, 'colorbars', 'from-root'); diff --git a/src/components/colorscale/attributes.js b/src/components/colorscale/attributes.js index bbbfc60e9ff..ac3d107332d 100644 --- a/src/components/colorscale/attributes.js +++ b/src/components/colorscale/attributes.js @@ -13,6 +13,8 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, + editType: 'calc', + impliedEdits: {zmin: undefined, zmax: undefined}, description: [ 'Determines the whether or not the color domain is computed', 'with respect to the input data.' @@ -22,17 +24,23 @@ module.exports = { valType: 'number', role: 'info', dflt: null, + editType: 'plot', + impliedEdits: {zauto: false}, description: 'Sets the lower bound of color domain.' }, zmax: { valType: 'number', role: 'info', dflt: null, + editType: 'plot', + impliedEdits: {zauto: false}, description: 'Sets the upper bound of color domain.' }, colorscale: { valType: 'colorscale', role: 'style', + editType: 'calc', + impliedEdits: {autocolorscale: false}, description: [ 'Sets the colorscale.', 'The colorscale must be an array containing', @@ -49,6 +57,8 @@ module.exports = { valType: 'boolean', role: 'style', dflt: true, // gets overrode in 'heatmap' & 'surface' for backwards comp. + editType: 'calc', + impliedEdits: {colorscale: undefined}, description: [ 'Determines whether or not the colorscale is picked using the sign of', 'the input z values.' @@ -58,12 +68,14 @@ module.exports = { valType: 'boolean', role: 'style', dflt: false, + editType: 'calc', description: 'Reverses the colorscale.' }, showscale: { valType: 'boolean', role: 'info', dflt: true, + editType: 'calc', description: [ 'Determines whether or not a colorbar is displayed for this trace.' ].join(' ') diff --git a/src/components/colorscale/color_attributes.js b/src/components/colorscale/color_attributes.js index 9c8f6cdb065..ed8573cbe26 100644 --- a/src/components/colorscale/color_attributes.js +++ b/src/components/colorscale/color_attributes.js @@ -9,80 +9,104 @@ 'use strict'; var colorScaleAttributes = require('./attributes'); -var extendDeep = require('../../lib/extend').extendDeep; +var extendFlat = require('../../lib/extend').extendFlat; var palettes = require('./scales.js'); -module.exports = function makeColorScaleAttributes(context) { +/* + * Make all the attributes for a regular colorscale: + * color, colorscale, cauto, cmin, cmax, autocolorscale, reversescale + * + * @param {string} context: + * the container this is in (*marker*, *marker.line* etc) + * @param {optional string} editTypeOverride: + * most of these attributes already require a recalc, but the ones that do not + * have editType *style* or *plot* unless you override (presumably with *calc*) + * @param {optional bool} autoColorDflt: + * normally autocolorscale.dflt is `true`, but pass `false` to override + * + * @return {object} the finished attributes object + */ +module.exports = function makeColorScaleAttributes(context, editTypeOverride, autoColorDflt) { + var contextHead = context ? (context + '.') : ''; + return { color: { valType: 'color', arrayOk: true, role: 'style', + editType: editTypeOverride || '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('') + '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, { + colorscale: extendFlat({}, 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(', ')) + 'if `' + contextHead + '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 `' + contextHead + 'cmin` and `' + contextHead + 'cmax`.', + 'Alternatively, `colorscale` may be a palette name string', + 'of the following list:', + Object.keys(palettes).join(', ') + ].join(' ') }), - cauto: extendDeep({}, colorScaleAttributes.zauto, { + cauto: extendFlat({}, colorScaleAttributes.zauto, { + impliedEdits: {cmin: undefined, cmax: undefined}, 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('') + 'Has an effect only if `' + contextHead + '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, { + cmax: extendFlat({}, colorScaleAttributes.zmax, { + editType: editTypeOverride || colorScaleAttributes.zmax.editType, + impliedEdits: {cauto: false}, 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('') + 'Has an effect only if `' + contextHead + 'color` is set to a numerical array.', + 'Sets the upper bound of the color domain.', + 'Value should be associated to the `' + contextHead + 'color` array index,', + 'and if set, `' + contextHead + 'cmin` must be set as well.' + ].join(' ') }), - cmin: extendDeep({}, colorScaleAttributes.zmin, { + cmin: extendFlat({}, colorScaleAttributes.zmin, { + editType: editTypeOverride || colorScaleAttributes.zmin.editType, + impliedEdits: {cauto: false}, 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('') + 'Has an effect only if `' + contextHead + 'color` is set to a numerical array.', + 'Sets the lower bound of the color domain.', + 'Value should be associated to the `' + contextHead + 'color` array index,', + 'and if set, `' + contextHead + 'cmax` must be set as well.' + ].join(' ') }), - autocolorscale: extendDeep({}, colorScaleAttributes.autocolorscale, { + autocolorscale: extendFlat({}, 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('') + 'Has an effect only if `' + contextHead + 'color` is set to a numerical array.', + 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', + 'or the palette determined by `' + contextHead + '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(' '), + dflt: autoColorDflt === false ? autoColorDflt : colorScaleAttributes.autocolorscale.dflt }), - reversescale: extendDeep({}, colorScaleAttributes.reversescale, { + reversescale: extendFlat({}, 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('') + 'Has an effect only if `' + contextHead + '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/drawing/attributes.js b/src/components/drawing/attributes.js index 0ea5dbe3620..b6d83bd4590 100644 --- a/src/components/drawing/attributes.js +++ b/src/components/drawing/attributes.js @@ -18,6 +18,7 @@ exports.dash = { values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'], dflt: 'solid', role: 'style', + editType: 'style', description: [ 'Sets the dash style of lines. Set to a dash type string', '(*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*)', diff --git a/src/components/errorbars/attributes.js b/src/components/errorbars/attributes.js index be441d7b364..2997910d3e0 100644 --- a/src/components/errorbars/attributes.js +++ b/src/components/errorbars/attributes.js @@ -13,6 +13,7 @@ module.exports = { visible: { valType: 'boolean', role: 'info', + editType: 'calc', description: [ 'Determines whether or not this set of error bars is visible.' ].join(' ') @@ -21,6 +22,7 @@ module.exports = { valType: 'enumerated', values: ['percent', 'constant', 'sqrt', 'data'], role: 'info', + editType: 'calc', description: [ 'Determines the rule used to generate the error bars.', @@ -39,6 +41,7 @@ module.exports = { symmetric: { valType: 'boolean', role: 'info', + editType: 'calc', description: [ 'Determines whether or not the error bars have the same length', 'in both direction', @@ -47,6 +50,7 @@ module.exports = { }, array: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the data corresponding the length of each error bar.', 'Values are plotted relative to the underlying data.' @@ -54,6 +58,7 @@ module.exports = { }, arrayminus: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the data corresponding the length of each error bar in the', 'bottom (left) direction for vertical (horizontal) bars', @@ -65,6 +70,7 @@ module.exports = { min: 0, dflt: 10, role: 'info', + editType: 'calc', description: [ 'Sets the value of either the percentage', '(if `type` is set to *percent*) or the constant', @@ -77,6 +83,7 @@ module.exports = { min: 0, dflt: 10, role: 'info', + editType: 'calc', description: [ 'Sets the value of either the percentage', '(if `type` is set to *percent*) or the constant', @@ -89,25 +96,30 @@ module.exports = { valType: 'integer', min: 0, dflt: 0, - role: 'info' + role: 'info', + editType: 'style' }, tracerefminus: { valType: 'integer', min: 0, dflt: 0, - role: 'info' + role: 'info', + editType: 'style' }, copy_ystyle: { valType: 'boolean', - role: 'style' + role: 'style', + editType: 'plot' }, copy_zstyle: { valType: 'boolean', - role: 'style' + role: 'style', + editType: 'style' }, color: { valType: 'color', role: 'style', + editType: 'style', description: 'Sets the stoke color of the error bars.' }, thickness: { @@ -115,22 +127,26 @@ module.exports = { min: 0, dflt: 2, role: 'style', + editType: 'style', description: 'Sets the thickness (in px) of the error bars.' }, width: { valType: 'number', min: 0, role: 'style', + editType: 'plot', description: [ 'Sets the width (in px) of the cross-bar at both ends', 'of the error bars.' ].join(' ') }, + editType: 'calc', _deprecated: { opacity: { valType: 'number', role: 'style', + editType: 'style', description: [ 'Obsolete.', 'Use the alpha channel in error bar `color` to set the opacity.' diff --git a/src/components/errorbars/plot.js b/src/components/errorbars/plot.js index 1a8175f2227..e524455cd8e 100644 --- a/src/components/errorbars/plot.js +++ b/src/components/errorbars/plot.js @@ -43,13 +43,18 @@ module.exports = function plot(traces, plotinfo, transitionOpts) { trace.marker.maxdisplayed > 0 ); - if(!yObj.visible && !xObj.visible) return; + if(!yObj.visible && !xObj.visible) d = []; var errorbars = d3.select(this).selectAll('g.errorbar') .data(d, keyFunc); errorbars.exit().remove(); + if(!d.length) return; + + if(!xObj.visible) errorbars.selectAll('path.xerror').remove(); + if(!yObj.visible) errorbars.selectAll('path.yerror').remove(); + errorbars.style('opacity', 1); var enter = errorbars.enter().append('g') diff --git a/src/components/fx/attributes.js b/src/components/fx/attributes.js index 8ea95a84031..1dd31102375 100644 --- a/src/components/fx/attributes.js +++ b/src/components/fx/attributes.js @@ -8,7 +8,6 @@ 'use strict'; -var extendFlat = require('../../lib/extend').extendFlat; var fontAttrs = require('../../plots/font_attributes'); module.exports = { @@ -17,6 +16,7 @@ module.exports = { valType: 'color', role: 'style', arrayOk: true, + editType: 'none', description: [ 'Sets the background color of the hover labels for this trace' ].join(' ') @@ -25,20 +25,22 @@ module.exports = { valType: 'color', role: 'style', arrayOk: true, + editType: 'none', description: [ 'Sets the border color of the hover labels for this trace.' ].join(' ') }, - font: { - family: extendFlat({}, fontAttrs.family, { arrayOk: true }), - size: extendFlat({}, fontAttrs.size, { arrayOk: true }), - color: extendFlat({}, fontAttrs.color, { arrayOk: true }) - }, + font: fontAttrs({ + arrayOk: true, + editType: 'none', + description: 'Sets the font used in hover labels.' + }), namelength: { valType: 'integer', min: -1, arrayOk: true, role: 'style', + editType: 'none', description: [ 'Sets the length (in number of characters) of the trace name in', 'the hover labels for this trace. -1 shows the whole name', @@ -47,6 +49,7 @@ module.exports = { 'many characters, but if it is longer, will truncate to', '`namelength - 3` characters and add an ellipsis.' ].join(' ') - } + }, + editType: 'calc' } }; diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index ffc44a338ce..c9f9186009b 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -580,24 +580,27 @@ function createHoverText(hoverData, opts, gd) { ltext = label.selectAll('text').data([0]); lpath.enter().append('path') - .style({ - fill: commonLabelOpts.bgcolor || Color.defaultLine, - stroke: commonLabelOpts.bordercolor || Color.background, - 'stroke-width': '1px' - }); + .style({'stroke-width': '1px'}); + + lpath.style({ + fill: commonLabelOpts.bgcolor || Color.defaultLine, + stroke: commonLabelOpts.bordercolor || Color.background, + }); + ltext.enter().append('text') - .call(Drawing.font, - commonLabelOpts.font.family || fontFamily, - commonLabelOpts.font.size || fontSize, - commonLabelOpts.font.color || Color.background - ) // prohibit tex interpretation until we can handle // tex and regular text together .attr('data-notex', 1); ltext.text(t0) + .call(Drawing.font, + commonLabelOpts.font.family || fontFamily, + commonLabelOpts.font.size || fontSize, + commonLabelOpts.font.color || Color.background + ) .call(svgTextUtils.positionText, 0, 0) .call(svgTextUtils.convertToTspans, gd); + label.attr('transform', ''); var tbb = ltext.node().getBoundingClientRect(); diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index ef975bc79d4..25c6503a0ce 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -8,16 +8,22 @@ 'use strict'; -var extendFlat = require('../../lib/extend').extendFlat; -var fontAttrs = require('../../plots/font_attributes'); var constants = require('./constants'); +var fontAttrs = require('../../plots/font_attributes')({ + editType: 'none', + description: 'Sets the default hover label font used by all traces on the graph.' +}); +fontAttrs.family.dflt = constants.HOVERFONT; +fontAttrs.size.dflt = constants.HOVERFONTSIZE; + module.exports = { dragmode: { valType: 'enumerated', role: 'info', values: ['zoom', 'pan', 'select', 'lasso', 'orbit', 'turntable'], dflt: 'zoom', + editType: 'modebar', description: [ 'Determines the mode of drag interactions.', '*select* and *lasso* apply only to scatter traces with', @@ -29,6 +35,7 @@ module.exports = { valType: 'enumerated', role: 'info', values: ['x', 'y', 'closest', false], + editType: 'modebar', description: 'Determines the mode of hover interactions.' }, @@ -36,6 +43,7 @@ module.exports = { bgcolor: { valType: 'color', role: 'style', + editType: 'none', description: [ 'Sets the background color of all hover labels on graph' ].join(' ') @@ -43,24 +51,18 @@ module.exports = { bordercolor: { valType: 'color', role: 'style', + editType: 'none', description: [ 'Sets the border color of all hover labels on graph.' ].join(' ') }, - font: { - family: extendFlat({}, fontAttrs.family, { - dflt: constants.HOVERFONT - }), - size: extendFlat({}, fontAttrs.size, { - dflt: constants.HOVERFONTSIZE - }), - color: extendFlat({}, fontAttrs.color) - }, + font: fontAttrs, namelength: { valType: 'integer', min: -1, dflt: 15, role: 'style', + editType: 'none', description: [ 'Sets the default length (in number of characters) of the trace name in', 'the hover labels for all traces. -1 shows the whole name', @@ -69,6 +71,7 @@ module.exports = { 'many characters, but if it is longer, will truncate to', '`namelength - 3` characters and add an ellipsis.' ].join(' ') - } + }, + editType: 'none' } }; diff --git a/src/components/images/attributes.js b/src/components/images/attributes.js index 2f15c72f908..fa2d3c5455c 100644 --- a/src/components/images/attributes.js +++ b/src/components/images/attributes.js @@ -18,6 +18,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, + editType: 'arraydraw', description: [ 'Determines whether or not this image is visible.' ].join(' ') @@ -26,6 +27,7 @@ module.exports = { source: { valType: 'string', role: 'info', + editType: 'arraydraw', description: [ 'Specifies the URL of the image to be used.', 'The URL must be accessible from the domain where the', @@ -39,6 +41,7 @@ module.exports = { values: ['below', 'above'], dflt: 'above', role: 'info', + editType: 'arraydraw', description: [ 'Specifies whether images are drawn below or above traces.', 'When `xref` and `yref` are both set to `paper`,', @@ -50,6 +53,7 @@ module.exports = { valType: 'number', role: 'info', dflt: 0, + editType: 'arraydraw', description: [ 'Sets the image container size horizontally.', 'The image will be sized based on the `position` value.', @@ -62,6 +66,7 @@ module.exports = { valType: 'number', role: 'info', dflt: 0, + editType: 'arraydraw', description: [ 'Sets the image container size vertically.', 'The image will be sized based on the `position` value.', @@ -75,6 +80,7 @@ module.exports = { values: ['fill', 'contain', 'stretch'], dflt: 'contain', role: 'info', + editType: 'arraydraw', description: [ 'Specifies which dimension of the image to constrain.' ].join(' ') @@ -86,6 +92,7 @@ module.exports = { min: 0, max: 1, dflt: 1, + editType: 'arraydraw', description: 'Sets the opacity of the image.' }, @@ -93,6 +100,7 @@ module.exports = { valType: 'any', role: 'info', dflt: 0, + editType: 'arraydraw', description: [ 'Sets the image\'s x position.', 'When `xref` is set to `paper`, units are sized relative', @@ -105,6 +113,7 @@ module.exports = { valType: 'any', role: 'info', dflt: 0, + editType: 'arraydraw', description: [ 'Sets the image\'s y position.', 'When `yref` is set to `paper`, units are sized relative', @@ -118,6 +127,7 @@ module.exports = { values: ['left', 'center', 'right'], dflt: 'left', role: 'info', + editType: 'arraydraw', description: 'Sets the anchor for the x position' }, @@ -126,6 +136,7 @@ module.exports = { values: ['top', 'middle', 'bottom'], dflt: 'top', role: 'info', + editType: 'arraydraw', description: 'Sets the anchor for the y position.' }, @@ -137,6 +148,7 @@ module.exports = { ], dflt: 'paper', role: 'info', + editType: 'arraydraw', description: [ 'Sets the images\'s x coordinate axis.', 'If set to a x axis id (e.g. *x* or *x2*), the `x` position', @@ -155,6 +167,7 @@ module.exports = { ], dflt: 'paper', role: 'info', + editType: 'arraydraw', description: [ 'Sets the images\'s y coordinate axis.', 'If set to a y axis id (e.g. *y* or *y2*), the `y` position', @@ -163,5 +176,6 @@ module.exports = { 'the bottom of the plot in normalized coordinates', 'where *0* (*1*) corresponds to the bottom (top).' ].join(' ') - } + }, + editType: 'arraydraw' }; diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 8ae61ac29be..8b92707afaa 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -10,19 +10,20 @@ var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../color/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; module.exports = { bgcolor: { valType: 'color', role: 'style', + editType: 'legend', description: 'Sets the legend background color.' }, bordercolor: { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'legend', description: 'Sets the color of the border enclosing the legend.' }, borderwidth: { @@ -30,9 +31,11 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'legend', description: 'Sets the width (in px) of the border enclosing the legend.' }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ + editType: 'legend', description: 'Sets the font used to text the legend items.' }), orientation: { @@ -40,6 +43,7 @@ module.exports = { values: ['v', 'h'], dflt: 'v', role: 'info', + editType: 'legend', description: 'Sets the orientation of the legend.' }, traceorder: { @@ -47,6 +51,7 @@ module.exports = { flags: ['reversed', 'grouped'], extras: ['normal'], role: 'style', + editType: 'legend', description: [ 'Determines the order at which the legend items are displayed.', @@ -68,6 +73,7 @@ module.exports = { min: 0, dflt: 10, role: 'style', + editType: 'legend', description: [ 'Sets the amount of vertical space (in px) between legend groups.' ].join(' ') @@ -78,6 +84,7 @@ module.exports = { max: 3, dflt: 1.02, role: 'style', + editType: 'legend', description: 'Sets the x position (in normalized coordinates) of the legend.' }, xanchor: { @@ -85,6 +92,7 @@ module.exports = { values: ['auto', 'left', 'center', 'right'], dflt: 'left', role: 'info', + editType: 'legend', description: [ 'Sets the legend\'s horizontal position anchor.', 'This anchor binds the `x` position to the *left*, *center*', @@ -97,6 +105,7 @@ module.exports = { max: 3, dflt: 1, role: 'style', + editType: 'legend', description: 'Sets the y position (in normalized coordinates) of the legend.' }, yanchor: { @@ -104,10 +113,12 @@ module.exports = { values: ['auto', 'top', 'middle', 'bottom'], dflt: 'auto', role: 'info', + editType: 'legend', description: [ 'Sets the legend\'s vertical position anchor', 'This anchor binds the `y` position to the *top*, *middle*', 'or *bottom* of the legend.' ].join(' ') - } + }, + editType: 'legend' }; diff --git a/src/components/rangeselector/attributes.js b/src/components/rangeselector/attributes.js index 390afe3e200..ea1d0158af7 100644 --- a/src/components/rangeselector/attributes.js +++ b/src/components/rangeselector/attributes.js @@ -26,6 +26,7 @@ module.exports = { visible: { valType: 'boolean', role: 'info', + editType: 'plot', description: [ 'Determines whether or not this range selector is visible.', 'Note that range selectors are only available for x axes of', @@ -40,6 +41,7 @@ module.exports = { min: -2, max: 3, role: 'style', + editType: 'plot', description: 'Sets the x position (in normalized coordinates) of the range selector.' }, xanchor: { @@ -47,6 +49,7 @@ module.exports = { values: ['auto', 'left', 'center', 'right'], dflt: 'left', role: 'info', + editType: 'plot', description: [ 'Sets the range selector\'s horizontal position anchor.', 'This anchor binds the `x` position to the *left*, *center*', @@ -58,6 +61,7 @@ module.exports = { min: -2, max: 3, role: 'style', + editType: 'plot', description: 'Sets the y position (in normalized coordinates) of the range selector.' }, yanchor: { @@ -65,6 +69,7 @@ module.exports = { values: ['auto', 'top', 'middle', 'bottom'], dflt: 'bottom', role: 'info', + editType: 'plot', description: [ 'Sets the range selector\'s vertical position anchor', 'This anchor binds the `y` position to the *top*, *middle*', @@ -72,7 +77,8 @@ module.exports = { ].join(' ') }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ + editType: 'plot', description: 'Sets the font of the range selector button text.' }), @@ -80,17 +86,20 @@ module.exports = { valType: 'color', dflt: colorAttrs.lightLine, role: 'style', + editType: 'plot', description: 'Sets the background color of the range selector buttons.' }, activecolor: { valType: 'color', role: 'style', + editType: 'plot', description: 'Sets the background color of the active range selector button.' }, bordercolor: { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'plot', description: 'Sets the color of the border enclosing the range selector.' }, borderwidth: { @@ -98,6 +107,8 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'plot', description: 'Sets the width (in px) of the border enclosing the range selector.' - } + }, + editType: 'plot' }; diff --git a/src/components/rangeselector/button_attributes.js b/src/components/rangeselector/button_attributes.js index 14fd193a0d4..0852a09aa6c 100644 --- a/src/components/rangeselector/button_attributes.js +++ b/src/components/rangeselector/button_attributes.js @@ -15,6 +15,7 @@ module.exports = { role: 'info', values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'], dflt: 'month', + editType: 'plot', description: [ 'The unit of measurement that the `count` value will set the range by.' ].join(' ') @@ -24,6 +25,7 @@ module.exports = { role: 'info', values: ['backward', 'todate'], dflt: 'backward', + editType: 'plot', description: [ 'Sets the range update mode.', 'If *backward*, the range update shifts the start of range', @@ -43,6 +45,7 @@ module.exports = { role: 'info', min: 0, dflt: 1, + editType: 'plot', description: [ 'Sets the number of steps to take to update the range.', 'Use with `step` to specify the update interval.' @@ -51,6 +54,8 @@ module.exports = { label: { valType: 'string', role: 'info', + editType: 'plot', description: 'Sets the text label to appear on the button.' - } + }, + editType: 'plot' }; diff --git a/src/components/rangeselector/index.js b/src/components/rangeselector/index.js index a4e3e7cfd58..1d1e8f9ad38 100644 --- a/src/components/rangeselector/index.js +++ b/src/components/rangeselector/index.js @@ -13,8 +13,8 @@ module.exports = { name: 'rangeselector', schema: { - layout: { - 'xaxis.rangeselector': require('./attributes') + subplots: { + xaxis: {rangeselector: require('./attributes')} } }, diff --git a/src/components/rangeslider/attributes.js b/src/components/rangeslider/attributes.js index 299a471b014..b0d04e37508 100644 --- a/src/components/rangeslider/attributes.js +++ b/src/components/rangeslider/attributes.js @@ -15,12 +15,14 @@ module.exports = { valType: 'color', dflt: colorAttributes.background, role: 'style', + editType: 'calc', description: 'Sets the background color of the range slider.' }, bordercolor: { valType: 'color', dflt: colorAttributes.defaultLine, role: 'style', + editType: 'calc', description: 'Sets the border color of the range slider.' }, borderwidth: { @@ -28,12 +30,14 @@ module.exports = { dflt: 0, min: 0, role: 'style', + editType: 'calc', description: 'Sets the border color of the range slider.' }, autorange: { valType: 'boolean', dflt: true, role: 'style', + editType: 'calc', description: [ 'Determines whether or not the range slider range is', 'computed in relation to the input data.', @@ -44,9 +48,10 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'any'}, - {valType: 'any'} + {valType: 'any', editType: 'calc'}, + {valType: 'any', editType: 'calc'} ], + editType: 'calc', description: [ 'Sets the range of the range slider.', 'If not set, defaults to the full xaxis range.', @@ -66,6 +71,7 @@ module.exports = { min: 0, max: 1, role: 'style', + editType: 'calc', description: [ 'The height of the range slider as a fraction of the', 'total plot area height.' @@ -75,9 +81,11 @@ module.exports = { valType: 'boolean', dflt: true, role: 'info', + editType: 'calc', description: [ 'Determines whether or not the range slider will be visible.', 'If visible, perpendicular axes will be set to `fixedrange`' ].join(' ') - } + }, + editType: 'calc' }; diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js index 91a15ee14f3..e55204adcab 100644 --- a/src/components/rangeslider/index.js +++ b/src/components/rangeslider/index.js @@ -13,8 +13,8 @@ module.exports = { name: 'rangeslider', schema: { - layout: { - 'xaxis.rangeslider': require('./attributes') + subplots: { + xaxis: {rangeslider: require('./attributes')} } }, diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 83407b34067..4ad70c542ef 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -20,6 +20,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, + editType: 'calcIfAutorange', description: [ 'Determines whether or not this shape is visible.' ].join(' ') @@ -29,6 +30,7 @@ module.exports = { valType: 'enumerated', values: ['circle', 'rect', 'path', 'line'], role: 'info', + editType: 'calcIfAutorange', description: [ 'Specifies the shape type to be drawn.', @@ -51,6 +53,7 @@ module.exports = { values: ['below', 'above'], dflt: 'above', role: 'info', + editType: 'arraydraw', description: 'Specifies whether shapes are drawn below or above traces.' }, @@ -71,6 +74,7 @@ module.exports = { x0: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the shape\'s starting x position.', 'See `type` for more info.' @@ -79,6 +83,7 @@ module.exports = { x1: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the shape\'s end x position.', 'See `type` for more info.' @@ -98,6 +103,7 @@ module.exports = { y0: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the shape\'s starting y position.', 'See `type` for more info.' @@ -106,6 +112,7 @@ module.exports = { y1: { valType: 'any', role: 'info', + editType: 'calcIfAutorange', description: [ 'Sets the shape\'s end y position.', 'See `type` for more info.' @@ -115,6 +122,7 @@ module.exports = { path: { valType: 'string', role: 'info', + editType: 'calcIfAutorange', description: [ 'For `type` *path* - a valid SVG path but with the pixel values', 'replaced by data values. There are a few restrictions / quirks', @@ -145,20 +153,24 @@ module.exports = { max: 1, dflt: 1, role: 'info', + editType: 'arraydraw', description: 'Sets the opacity of the shape.' }, line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: dash, - role: 'info' + color: extendFlat({}, scatterLineAttrs.color, {editType: 'arraydraw'}), + width: extendFlat({}, scatterLineAttrs.width, {editType: 'calcIfAutorange'}), + dash: extendFlat({}, dash, {editType: 'arraydraw'}), + role: 'info', + editType: 'calcIfAutorange' }, fillcolor: { valType: 'color', dflt: 'rgba(0,0,0,0)', role: 'info', + editType: 'arraydraw', description: [ 'Sets the color filling the shape\'s interior.' ].join(' ') - } + }, + editType: 'arraydraw' }; diff --git a/src/components/sliders/attributes.js b/src/components/sliders/attributes.js index 8687c6728ad..1d22b4949b5 100644 --- a/src/components/sliders/attributes.js +++ b/src/components/sliders/attributes.js @@ -10,8 +10,8 @@ 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 extendDeepAll = require('../../lib/extend').extendDeepAll; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var animationAttrs = require('../../plots/animation_attributes'); var constants = require('./constants'); @@ -72,7 +72,7 @@ var stepsAttrs = { } }; -module.exports = { +module.exports = overrideAll({ _isLinkedToArray: 'slider', visible: { @@ -128,7 +128,7 @@ module.exports = { role: 'style', description: 'Sets the x position (in normalized coordinates) of the slider.' }, - pad: extendDeep({}, padAttrs, { + pad: extendDeepAll({}, padAttrs, { description: 'Set the padding of the slider component along each side.' }, {t: {dflt: 20}}), xanchor: { @@ -176,7 +176,7 @@ module.exports = { role: 'info', dflt: 'cubic-in-out', description: 'Sets the easing function of the slider transition' - }, + } }, currentvalue: { @@ -221,12 +221,12 @@ module.exports = { description: 'When currentvalue.visible is true, this sets the suffix of the label.' }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ description: 'Sets the font of the current value label text.' - }), + }) }, - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ description: 'Sets the font of the slider step labels.' }), @@ -284,5 +284,5 @@ module.exports = { dflt: constants.minorTickLength, role: 'style', description: 'Sets the length in pixels of minor step tick marks' - }, -}; + } +}, 'arraydraw', 'from-root'); diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js index 3ffffdc5296..d9fcd4a3248 100644 --- a/src/components/updatemenus/attributes.js +++ b/src/components/updatemenus/attributes.js @@ -11,6 +11,7 @@ var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../color/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var padAttrs = require('../../plots/pad_attributes'); var buttonsAttrs = { @@ -34,9 +35,9 @@ var buttonsAttrs = { role: 'info', freeLength: true, items: [ - { valType: 'any' }, - { valType: 'any' }, - { valType: 'any' } + {valType: 'any'}, + {valType: 'any'}, + {valType: 'any'} ], description: [ 'Sets the arguments values to be passed to the Plotly', @@ -63,7 +64,7 @@ var buttonsAttrs = { } }; -module.exports = { +module.exports = overrideAll({ _isLinkedToArray: 'updatemenu', _arrayAttrRegexps: [/^updatemenus\[(0|[1-9][0-9]+)\]\.buttons/], @@ -162,7 +163,7 @@ module.exports = { description: 'Sets the padding around the buttons or dropdown menu.' }), - font: extendFlat({}, fontAttrs, { + font: fontAttrs({ description: 'Sets the font of the update menu button text.' }), @@ -182,6 +183,7 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'arraydraw', description: 'Sets the width (in px) of the border enclosing the update menu.' } -}; +}, 'arraydraw', 'from-root'); diff --git a/src/lib/coerce.js b/src/lib/coerce.js index bdca900b14c..5810311ed99 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -16,10 +16,9 @@ var baseTraceAttrs = require('../plots/attributes'); var getColorscale = require('../components/colorscale/get_scale'); var colorscaleNames = Object.keys(require('../components/colorscale/scales')); var nestedProperty = require('./nested_property'); +var counterRegex = require('./regex').counter; -var ID_REGEX = /^([2-9]|[1-9][0-9]+)$/; - -exports.valObjects = { +exports.valObjectMeta = { data_array: { // You can use *dflt=[] to force said array to exist though. description: [ @@ -181,23 +180,18 @@ exports.valObjects = { 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))) { + if(typeof v === 'string' && counterRegex(dflt).test(v)) { propOut.set(v); return; } propOut.set(dflt); }, validateFunction: function(v, opts) { - var dflt = opts.dflt, - dlen = dflt.length; + var dflt = opts.dflt; if(v === dflt) return true; if(typeof v !== 'string') return false; - if(v.substr(0, dlen) === dflt && ID_REGEX.test(v.substr(dlen))) { - return true; - } + if(counterRegex(dflt).test(v)) return true; return false; } @@ -289,7 +283,7 @@ exports.valObjects = { * Ensures that container[attribute] has a valid value. * * attributes[attribute] is an object with possible keys: - * - valType: data_array, enumerated, boolean, ... as in valObjects + * - valType: data_array, enumerated, boolean, ... as in valObjectMeta * - values: (enumerated only) array of allowed vals * - min, max: (number, integer only) inclusive bounds on allowed vals * either or both may be omitted @@ -316,7 +310,7 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt return v; } - exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts); + exports.valObjectMeta[opts.valType].coerceFunction(v, propOut, dflt, opts); return propOut.get(); }; @@ -383,12 +377,12 @@ exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) { }; exports.validate = function(value, opts) { - var valObject = exports.valObjects[opts.valType]; + var valObjectDef = exports.valObjectMeta[opts.valType]; if(opts.arrayOk && Array.isArray(value)) return true; - if(valObject.validateFunction) { - return valObject.validateFunction(value, opts); + if(valObjectDef.validateFunction) { + return valObjectDef.validateFunction(value, opts); } var failed = {}, @@ -397,6 +391,6 @@ exports.validate = function(value, opts) { // 'failed' just something mutable that won't be === anything else - valObject.coerceFunction(value, propMock, failed, opts); + valObjectDef.coerceFunction(value, propMock, failed, opts); return out !== failed; }; diff --git a/src/lib/index.js b/src/lib/index.js index 522834d1813..42620398469 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -20,6 +20,7 @@ var lib = module.exports = {}; lib.nestedProperty = require('./nested_property'); lib.keyedContainer = require('./keyed_container'); +lib.relativeAttr = require('./relative_attr'); lib.isPlainObject = require('./is_plain_object'); lib.isArray = require('./is_array'); lib.mod = require('./mod'); @@ -28,7 +29,7 @@ lib.relinkPrivateKeys = require('./relink_private'); lib.ensureArray = require('./ensure_array'); var coerceModule = require('./coerce'); -lib.valObjects = coerceModule.valObjects; +lib.valObjectMeta = coerceModule.valObjectMeta; lib.coerce = coerceModule.coerce; lib.coerce2 = coerceModule.coerce2; lib.coerceFont = coerceModule.coerceFont; @@ -93,6 +94,9 @@ lib.log = loggersModule.log; lib.warn = loggersModule.warn; lib.error = loggersModule.error; +var regexModule = require('./regex'); +lib.counterRegex = regexModule.counter; + lib.notifier = require('./notifier'); lib.filterUnique = require('./filter_unique'); diff --git a/src/lib/regex.js b/src/lib/regex.js new file mode 100644 index 00000000000..8ebacb38551 --- /dev/null +++ b/src/lib/regex.js @@ -0,0 +1,23 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +// Simple helper functions +// none of these need any external deps + +/* + * make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10... + * eg: regexCounter('x') + * tail is an optional piece after the id + * eg regexCounter('scene', '.annotations') for scene2.annotations etc. + */ +exports.counter = function(head, tail, openEnded) { + return new RegExp('^' + head + '([2-9]|[1-9][0-9]+)?' + + (tail || '') + (openEnded ? '' : '$')); +}; diff --git a/src/lib/relative_attr.js b/src/lib/relative_attr.js new file mode 100644 index 00000000000..cd11562f363 --- /dev/null +++ b/src/lib/relative_attr.js @@ -0,0 +1,51 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +// ASCEND: chop off the last nesting level - either [] or . - to ascend +// the attribute tree. the remaining attrString is in match[1] +var ASCEND = /^(.*)(\.[^\.\[\]]+|\[\d\])$/; + +// SIMPLEATTR: is this an un-nested attribute? (no dots or brackets) +var SIMPLEATTR = /^[^\.\[\]]+$/; + +/* + * calculate a relative attribute string, similar to a relative path + * + * @param {string} baseAttr: + * an attribute string, such as 'annotations[3].x'. The "current location" + * is the attribute string minus the last component ('annotations[3]') + * @param {string} relativeAttr: + * a route to the desired attribute string, using '^' to ascend + * + * @return {string} attrString: + * for example: + * relativeAttr('annotations[3].x', 'y') = 'annotations[3].y' + * relativeAttr('annotations[3].x', '^[2].z') = 'annotations[2].z' + * relativeAttr('annotations[3].x', '^^margin') = 'margin' + * relativeAttr('annotations[3].x', '^^margin.r') = 'margin.r' + */ +module.exports = function(baseAttr, relativeAttr) { + while(relativeAttr) { + var match = baseAttr.match(ASCEND); + + if(match) baseAttr = match[1]; + else if(baseAttr.match(SIMPLEATTR)) baseAttr = ''; + else throw new Error('bad relativeAttr call:' + [baseAttr, relativeAttr]); + + if(relativeAttr.charAt(0) === '^') relativeAttr = relativeAttr.slice(1); + else break; + } + + if(baseAttr && relativeAttr.charAt(0) !== '[') { + return baseAttr + '.' + relativeAttr; + } + return baseAttr + relativeAttr; +}; diff --git a/src/plot_api/edit_types.js b/src/plot_api/edit_types.js index 37a69c1bdd0..6b5f199008f 100644 --- a/src/plot_api/edit_types.js +++ b/src/plot_api/edit_types.js @@ -8,51 +8,143 @@ 'use strict'; +var Lib = require('../lib'); +var extendFlat = Lib.extendFlat; +var isPlainObject = Lib.isPlainObject; + +var traceOpts = { + valType: 'flaglist', + extras: ['none'], + flags: ['calc', 'calcIfAutorange', 'clearAxisTypes', 'plot', 'style', 'colorbars'], + description: [ + 'trace attributes should include an `editType` string matching this flaglist.', + '*calc* is the most extensive: a full `Plotly.plot` starting by clearing `gd.calcdata`', + 'to force it to be regenerated', + '*calcIfAutorange* does a full `Plotly.plot`, but only clears and redoes `gd.calcdata`', + 'if there is at least one autoranged axis.', + '*clearAxisTypes* resets the types of the axes this trace is on, because new data could', + 'cause the automatic axis type detection to change. Log type will not be cleared, as that', + 'is never automatically chosen so must have been user-specified.', + '*plot* calls `Plotly.plot` but without first clearing `gd.calcdata`.', + '*style* only calls `module.style` for all trace modules and redraws the legend.', + '*colorbars* only redraws colorbars.' + ].join(' ') +}; + +var layoutOpts = { + valType: 'flaglist', + extras: ['none'], + flags: [ + 'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', + 'layoutstyle', 'modebar', 'camera', 'arraydraw' + ], + description: [ + 'layout attributes should include an `editType` string matching this flaglist.', + '*calc* is the most extensive: a full `Plotly.plot` starting by clearing `gd.calcdata`', + 'to force it to be regenerated', + '*calcIfAutorange* does a full `Plotly.plot`, but only clears and redoes `gd.calcdata`', + 'if there is at least one autoranged axis.', + '*plot* calls `Plotly.plot` but without first clearing `gd.calcdata`.', + '*legend* only redraws the legend.', + '*ticks* only redraws axis ticks, labels, and gridlines.', + '*layoutstyle* reapplies global and SVG cartesian axis styles.', + '*modebar* just updates the modebar.', + '*camera* just updates the camera settings for gl3d scenes.', + '*arraydraw* allows component arrays to invoke the redraw routines just for the', + 'component(s) that changed.' + ].join(' ') +}; + +// flags for inside restyle/relayout include a few extras +// that shouldn't be used in attributes, to deal with certain +// combinations and conditionals efficiently +var traceEditTypeFlags = traceOpts.flags.slice() + .concat(['clearCalc', 'fullReplot']); + +var layoutEditTypeFlags = layoutOpts.flags.slice() + .concat('layoutReplot'); + module.exports = { + traces: traceOpts, + layout: layoutOpts, /* * default (all false) edit flags for restyle (traces) * creates a new object each call, so the caller can mutate freely */ - traces: function() { - return { - docalc: false, - docalcAutorange: false, - doplot: false, - dostyle: false, - docolorbars: false, - autorangeOn: false, - clearCalc: false, - fullReplot: false - }; - }, + traceFlags: function() { return falseObj(traceEditTypeFlags); }, /* * default (all false) edit flags for relayout * creates a new object each call, so the caller can mutate freely */ - layout: function() { - return { - dolegend: false, - doticks: false, - dolayoutstyle: false, - doplot: false, - docalc: false, - domodebar: false, - docamera: false, - layoutReplot: false - }; - }, + layoutFlags: function() { return falseObj(layoutEditTypeFlags); }, /* * update `flags` with the `editType` values found in `attr` */ update: function(flags, attr) { var editType = attr.editType; - if(editType) { + if(editType && editType !== 'none') { var editTypeParts = editType.split('+'); for(var i = 0; i < editTypeParts.length; i++) { flags[editTypeParts[i]] = true; } } - } + }, + + overrideAll: overrideAll }; + +function falseObj(keys) { + var out = {}; + for(var i = 0; i < keys.length; i++) out[keys[i]] = false; + return out; +} + +/** + * For attributes that are largely copied from elsewhere into a plot type that doesn't + * support partial redraws - overrides the editType field of all attributes in the object + * + * @param {object} attrs: the attributes to override. Will not be mutated. + * @param {string} editTypeOverride: the new editType to use + * @param {'nested'|'from-root'} overrideContainers: + * - 'nested' will override editType for nested containers but not the root. + * - 'from-root' will also override editType of the root container. + * Containers below the absolute top level (trace or layout root) DO need an + * editType even if they are not `valObject`s themselves (eg `scatter.marker`) + * to handle the case where you edit the whole container. + * + * @return {object} a new attributes object with `editType` modified as directed + */ +function overrideAll(attrs, editTypeOverride, overrideContainers) { + var out = extendFlat({}, attrs); + for(var key in out) { + var attr = out[key]; + if(isPlainObject(attr)) { + out[key] = overrideOne(attr, editTypeOverride, overrideContainers, key); + } + } + if(overrideContainers === 'from-root') out.editType = editTypeOverride; + + return out; +} + +function overrideOne(attr, editTypeOverride, overrideContainers, key) { + if(attr.valType) { + var out = extendFlat({}, attr); + out.editType = editTypeOverride; + + if(Array.isArray(attr.items)) { + out.items = new Array(attr.items.length); + for(var i = 0; i < attr.items.length; i++) { + out.items[i] = overrideOne(attr.items[i], editTypeOverride, 'from-root'); + } + } + return out; + } + else { + // don't provide an editType for the _deprecated container + return overrideAll(attr, editTypeOverride, + (key.charAt(0) === '_') ? 'nested' : 'from-root'); + } +} diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 0da0f5051fe..322adec5f97 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -536,3 +536,36 @@ exports.hasParent = function(aobj, attr) { } return false; }; + +/** + * Empty out types for all axes containing these traces so we auto-set them again + * + * @param {object} gd + * @param {[integer]} traces: trace indices to search for axes to clear the types of + * @param {object} layoutUpdate: any update being done concurrently to the layout, + * which may supercede clearing the axis types + */ +var axLetters = ['x', 'y', 'z']; +exports.clearAxisTypes = function(gd, traces, layoutUpdate) { + for(var i = 0; i < traces.length; i++) { + var trace = gd._fullData[i]; + for(var j = 0; j < 3; j++) { + var ax = Axes.getFromTrace(gd, trace, axLetters[j]); + + // do not clear log type - that's never an auto result so must have been intentional + if(ax && ax.type !== 'log') { + var axAttr = ax._name; + var sceneName = ax._id.substr(1); + if(sceneName.substr(0, 5) === 'scene') { + if(layoutUpdate[sceneName] !== undefined) continue; + axAttr = sceneName + '.' + axAttr; + } + var typeAttr = axAttr + '.type'; + + if(layoutUpdate[axAttr] === undefined && layoutUpdate[typeAttr] === undefined) { + Lib.nestedProperty(gd.layout, typeAttr).set(null); + } + } + } + } +}; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c8619163454..58244368e0e 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -20,6 +20,7 @@ var Events = require('../lib/events'); var Queue = require('../lib/queue'); var Registry = require('../registry'); +var PlotSchema = require('./plot_schema'); var Plots = require('../plots/plots'); var Polar = require('../plots/polar'); var initInteractions = require('../plots/cartesian/graph_interact'); @@ -1260,7 +1261,7 @@ 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) { +Plotly.restyle = function restyle(gd, astr, val, _traces) { gd = helpers.getGraphDiv(gd); helpers.clearPromiseQueue(gd); @@ -1269,20 +1270,23 @@ Plotly.restyle = function restyle(gd, astr, val, traces) { else if(Lib.isPlainObject(astr)) { // the 3-arg form aobj = Lib.extendFlat({}, astr); - if(traces === undefined) traces = val; + if(_traces === undefined) _traces = val; } else { - Lib.warn('Restyle fail.', astr, val, traces); + Lib.warn('Restyle fail.', astr, val, _traces); return Promise.reject(); } if(Object.keys(aobj).length) gd.changed = true; + var traces = helpers.coerceTraceIndices(gd, _traces); + var specs = _restyle(gd, aobj, traces), flags = specs.flags; - // clear calcdata if required + // clear calcdata and/or axis types if required so they get regenerated if(flags.clearCalc) gd.calcdata = undefined; + if(flags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, {}); // fill in redraw sequence var seq = []; @@ -1294,8 +1298,8 @@ Plotly.restyle = function restyle(gd, astr, val, traces) { Plots.supplyDefaults(gd); - if(flags.dostyle) seq.push(subroutines.doTraceStyle); - if(flags.docolorbars) seq.push(subroutines.doColorBars); + if(flags.style) seq.push(subroutines.doTraceStyle); + if(flags.colorbars) seq.push(subroutines.doColorBars); } seq.push(Plots.rehover); @@ -1314,16 +1318,21 @@ Plotly.restyle = function restyle(gd, astr, val, traces) { }); }; -function _restyle(gd, aobj, _traces) { +// for undo: undefined initial vals must be turned into nulls +// so that we unset rather than ignore them +function undefinedToNull(val) { + if(val === undefined) return null; + return val; +} + +function _restyle(gd, aobj, traces) { var fullLayout = gd._fullLayout, fullData = gd._fullData, data = gd.data, i; - var traces = helpers.coerceTraceIndices(gd, _traces); - // initialize flags - var flags = editTypes.traces(); + var flags = editTypes.traceFlags(); // copies of the change (and previous values of anything affected) // for the undo / redo queue @@ -1332,119 +1341,6 @@ function _restyle(gd, aobj, _traces) { 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', - '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', - 'a0', 'da', 'b0', 'db', 'atype', 'btype', - 'cheaterslope', 'carpet', 'sum', - ]; - - var carpetAxisAttributes = [ - 'color', 'smoothing', 'title', 'titlefont', 'titlefont.size', 'titlefont.family', - 'titlefont.color', 'titleoffset', 'type', 'autorange', 'rangemode', 'range', - 'fixedrange', 'cheatertype', 'tickmode', 'nticks', 'tickvals', 'ticktext', - 'ticks', 'mirror', 'ticklen', 'tickwidth', 'tickcolor', 'showticklabels', - 'tickfont', 'tickfont.size', 'tickfont.family', 'tickfont.color', 'tickprefix', - 'showtickprefix', 'ticksuffix', 'showticksuffix', 'showexponent', 'exponentformat', - 'separatethousands', 'tickformat', 'categoryorder', 'categoryarray', 'labelpadding', - 'labelprefix', 'labelsuffix', 'labelfont', 'labelfont.family', 'labelfont.size', - 'labelfont.color', 'showline', 'linecolor', 'linewidth', 'gridcolor', 'gridwidth', - 'showgrid', 'minorgridcount', 'minorgridwidth', 'minorgridcolor', 'startline', - 'startlinecolor', 'startlinewidth', 'endline', 'endlinewidth', 'endlinecolor', - 'tick0', 'dtick', 'arraytick0', 'arraydtick', 'hoverformat', 'tickangle' - ]; - - for(i = 0; i < carpetAxisAttributes.length; i++) { - recalcAttrs.push('aaxis.' + carpetAxisAttributes[i]); - recalcAttrs.push('baxis.' + carpetAxisAttributes[i]); - } - - for(i = 0; i < traces.length; i++) { - if(Registry.traceIs(fullData[traces[i]], 'box')) { - recalcAttrs.push('name'); - break; - } - } - - // autorangeAttrs attributes need a full redo of calcdata - // only if an axis is autoranged, - // because .calc() is where the autorange gets determined - // TODO: could we break this out as well? - var autorangeAttrs = [ - 'marker', 'marker.size', 'textfont', - 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean', - 'tickwidth' - ]; - - // replotAttrs attributes need a replot (because different - // objects need to be made) but not a recalc - var replotAttrs = [ - 'zmin', 'zmax', 'zauto', - 'xgap', 'ygap', - 'marker.cmin', 'marker.cmax', 'marker.cauto', - 'line.cmin', 'line.cmax', - 'marker.line.cmin', 'marker.line.cmax', - 'line', 'line.smoothing', 'line.shape', - 'error_y.width', 'error_x.width', 'error_x.copy_ystyle', - 'marker.maxdisplayed' - ]; - - // these ones may alter the axis type - // (at least if the first trace is involved) - var axtypeAttrs = [ - 'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis' - ]; - - var zscl = ['zmin', 'zmax'], - cscl = ['cmin', 'cmax'], - xbins = ['xbins.start', 'xbins.end', 'xbins.size'], - ybins = ['ybins.start', 'ybins.end', 'ybins.size'], - contourAttrs = ['contours.start', 'contours.end', 'contours.size']; - - // At the moment, only cartesian, pie and ternary plot types can afford - // to not go through a full replot - var doPlotWhiteList = ['cartesian', 'pie', 'ternary']; - fullLayout._basePlotModules.forEach(function(_module) { - if(doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true; - }); - // make a new empty vals array for undoit function a0() { return traces.map(function() { return undefined; }); } @@ -1482,7 +1378,7 @@ function _restyle(gd, aobj, _traces) { undoit[attr] = a0(); } if(undoit[attr][i] === undefined) { - undoit[attr][i] = extraparam.get(); + undoit[attr][i] = undefinedToNull(extraparam.get()); } if(val !== undefined) { extraparam.set(val); @@ -1501,19 +1397,20 @@ function _restyle(gd, aobj, _traces) { contFull, param, oldVal, - newVal; + newVal, + valObject; redoit[ai] = vi; if(ai.substr(0, 6) === 'LAYOUT') { param = Lib.nestedProperty(gd.layout, ai.replace('LAYOUT', '')); - undoit[ai] = [param.get()]; + undoit[ai] = [undefinedToNull(param.get())]; // since we're allowing val to be an array, allow it here too, // even though that's meaningless param.set(Array.isArray(vi) ? vi[0] : vi); // ironically, the layout attrs in restyle only require replot, // not relayout - flags.docalc = true; + flags.calc = true; continue; } @@ -1528,57 +1425,14 @@ function _restyle(gd, aobj, _traces) { 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); - } - if(cscl.indexOf(ai) !== -1) { - doextra('cauto', false, i); - } - else if(ai === 'colorscale') { - doextra('autocolorscale', false, i); - } - else if(ai === 'autocolorscale') { - doextra('colorscale', undefined, i); - } - else if(ai === 'marker.colorscale') { - doextra('marker.autocolorscale', false, i); - } - else if(ai === 'marker.autocolorscale') { - doextra('marker.colorscale', undefined, i); - } - else if(ai === 'zauto') { - doextra(zscl, undefined, i); - } - else if(xbins.indexOf(ai) !== -1) { - doextra('autobinx', false, i); - } - else if(ai === 'autobinx') { - doextra(xbins, undefined, i); - } - else if(ybins.indexOf(ai) !== -1) { - doextra('autobiny', false, i); - } - else if(ai === 'autobiny') { - doextra(ybins, undefined, i); - } - else if(contourAttrs.indexOf(ai) !== -1) { - doextra('autocontour', false, i); - } - else if(ai === 'autocontour') { - doextra(contourAttrs, undefined, i); - } - // heatmaps: setting x0 or dx, y0 or dy, - // should turn xtype/ytype to 'scaled' if 'array' - else if(['x0', 'dx'].indexOf(ai) !== -1 && - contFull.x && contFull.xtype !== 'scaled') { - doextra('xtype', 'scaled', i); - } - else if(['y0', 'dy'].indexOf(ai) !== -1 && - contFull.y && contFull.ytype !== 'scaled') { - doextra('ytype', 'scaled', i); + valObject = PlotSchema.getTraceValObject(contFull, param.parts); + + if(valObject && valObject.impliedEdits && newVal !== null) { + for(var impliedKey in valObject.impliedEdits) { + doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey], i); + } } + // changing colorbar size modes, // make the resulting size not change // note that colorbar fractional sizing is based on the @@ -1607,10 +1461,6 @@ function _restyle(gd, aobj, _traces) { 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', @@ -1638,7 +1488,7 @@ function _restyle(gd, aobj, _traces) { } } - undoit[ai][i] = oldVal; + undoit[ai][i] = undefinedToNull(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 = [ @@ -1649,7 +1499,13 @@ function _restyle(gd, aobj, _traces) { // before we swap everything else if(ai === 'orientation') { param.set(newVal); - if(param.get() === undoit[ai][i]) continue; + // obnoxious that we need this level of coupling... but in order to + // properly handle setting orientation to `null` we need to mimic + // the logic inside Bars.supplyDefaults for default orientation + var defaultOrientation = (cont.x && !cont.y) ? 'h' : 'v'; + if((param.get() || defaultOrientation) === contFull.orientation) { + continue; + } } // orientationaxes has no value, // it flips everything and the axes @@ -1658,49 +1514,29 @@ function _restyle(gd, aobj, _traces) { {v: 'h', h: 'v'}[contFull.orientation]; } helpers.swapXYData(cont); + flags.calc = flags.clearAxisTypes = true; } else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) { // TODO: use manageArrays.applyContainerArrayChanges here too helpers.manageArrayContainers(param, newVal, undoit); - flags.docalc = true; + flags.calc = true; } else { - var aiHead = param.parts[0]; - var moduleAttrs = (contFull._module || {}).attributes; - var valObject = moduleAttrs && moduleAttrs[aiHead]; - if(!valObject) valObject = Plots.attributes[aiHead]; if(valObject) { - /* - * In occasional cases we can't the innermost valObject - * doesn't exist, for example `valType: 'any'` items like - * contourcarpet `contours.value` where we might set - * `contours.value[0]`. In that case, stop at the deepest - * valObject we *do* find. - */ - for(var parti = 1; parti < param.parts.length; parti++) { - var newValObject = valObject[param.parts[parti]]; - if(newValObject) valObject = newValObject; - else break; - } - - /* - * must redo calcdata when restyling: - * 1) array values of arrayOk attributes - * 2) a container object (it would be hard to tell what - * pieces changed, whether any are arrays, so to be - * safe we need to recalc) - */ - if(!valObject.valType || (valObject.arrayOk && (Array.isArray(newVal) || Array.isArray(oldVal)))) { - flags.docalc = true; + // must redo calcdata when restyling array values of arrayOk attributes + if(valObject.arrayOk && (Array.isArray(newVal) || Array.isArray(oldVal))) { + flags.calc = true; } - - // some attributes declare an 'editType' flaglist - editTypes.update(flags, valObject); + else editTypes.update(flags, valObject); } else { - // if we couldn't find valObject even at the root, - // assume a full recalc. - flags.docalc = true; + /* + * if we couldn't find valObject, assume a full recalc. + * This can happen if you're changing type and making + * some other edits too, so the modules we're + * looking at don't have these attributes in them. + */ + flags.calc = true; } // all the other ones, just modify that one attribute @@ -1723,63 +1559,37 @@ function _restyle(gd, aobj, _traces) { } } - // check if we need to call axis type - if((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) { - Plotly.Axes.clearTypes(gd, traces); - flags.docalc = true; - } - - // switching from auto to manual binning or z scaling doesn't - // actually do anything but change what you see in the styling - // box. everything else at least needs to apply styles - if((['autobinx', 'autobiny', 'zauto'].indexOf(ai) === -1) || - newVal !== false) { - flags.dostyle = true; - } - if(['colorbar', 'line'].indexOf(param.parts[0]) !== -1 || - param.parts[0] === 'marker' && param.parts[1] === 'colorbar') { - flags.docolorbars = true; - } + // 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]]; - var aiArrayStart = ai.indexOf('['), - aiAboveArray = aiArrayStart === -1 ? ai : ai.substr(0, aiArrayStart); + if(Registry.traceIs(trace, 'cartesian')) { + addToAxlist(trace.xaxis || 'x'); + addToAxlist(trace.yaxis || 'y'); - 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); - } + if(ai === 'type') { + doextra(['autobinx', 'autobiny'], true, i); } } - - doextra(axlist.map(autorangeAttr), true, 0); - doextra(axlist.map(rangeAttr), [0, 1], 0); } - flags.docalc = true; - } else if(replotAttrs.indexOf(aiAboveArray) !== -1) { - flags.doplot = true; - } else if(aiAboveArray.indexOf('aaxis') === 0 || aiAboveArray.indexOf('baxis') === 0) { - 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); } } // do we need to force a recalc? - Plotly.Axes.list(gd).forEach(function(ax) { - if(ax.autorange) flags.autorangeOn = true; - }); + var autorangeOn = false; + var axList = Plotly.Axes.list(gd); + for(i = 0; i < axList.length; i++) { + if(axList[i].autorange) { + autorangeOn = true; + break; + } + } // check axes we've flagged for possible deletion // flagAxForDelete is a hash so we can make sure we only get each axis once @@ -1802,10 +1612,10 @@ function _restyle(gd, aobj, _traces) { } // combine a few flags together; - if(flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) { + if(flags.calc || (flags.calcIfAutorange && autorangeOn)) { flags.clearCalc = true; } - if(flags.docalc || flags.doplot || flags.docalcAutorange) { + if(flags.calc || flags.plot || flags.calcIfAutorange) { flags.fullReplot = true; } @@ -1862,7 +1672,7 @@ Plotly.relayout = function relayout(gd, astr, val) { flags = specs.flags; // clear calcdata if required - if(flags.docalc) gd.calcdata = undefined; + if(flags.calc) gd.calcdata = undefined; // fill in redraw sequence @@ -1877,11 +1687,11 @@ Plotly.relayout = function relayout(gd, astr, val) { else if(Object.keys(aobj).length) { Plots.supplyDefaults(gd); - if(flags.dolegend) seq.push(subroutines.doLegend); - if(flags.dolayoutstyle) seq.push(subroutines.layoutStyles); - if(flags.doticks) seq.push(subroutines.doTicksRelayout); - if(flags.domodebar) seq.push(subroutines.doModeBar); - if(flags.docamera) seq.push(subroutines.doCamera); + if(flags.legend) seq.push(subroutines.doLegend); + if(flags.layoutstyle) seq.push(subroutines.layoutStyles); + if(flags.ticks) seq.push(subroutines.doTicksRelayout); + if(flags.modebar) seq.push(subroutines.doModeBar); + if(flags.camera) seq.push(subroutines.doCamera); } seq.push(Plots.rehover); @@ -1927,7 +1737,7 @@ function _relayout(gd, aobj) { } // initialize flags - var flags = editTypes.layout(); + var flags = editTypes.layoutFlags(); // copies of the change (and previous values of anything affected) // for the undo / redo queue @@ -1949,7 +1759,9 @@ function _relayout(gd, aobj) { if(attr in aobj || helpers.hasParent(aobj, attr)) return; var p = Lib.nestedProperty(layout, attr); - if(!(attr in undoit)) undoit[attr] = p.get(); + if(!(attr in undoit)) { + undoit[attr] = undefinedToNull(p.get()); + } if(val !== undefined) p.set(val); } @@ -1996,7 +1808,6 @@ function _relayout(gd, aobj) { // p.parts may end with an index integer if the property is an array pend = typeof p.parts[plen - 1] === 'string' ? (plen - 1) : (plen - 2), // last property in chain (leaf node) - proot = p.parts[0], pleaf = p.parts[pend], // leaf plus immediate parent pleafPlus = p.parts[pend - 1] + '.' + pleaf, @@ -2012,7 +1823,15 @@ function _relayout(gd, aobj) { // axis reverse is special - it is its own inverse // op and has no flag. - undoit[ai] = (pleaf === 'reverse') ? vi : vOld; + undoit[ai] = (pleaf === 'reverse') ? vi : undefinedToNull(vOld); + + var valObject = PlotSchema.getLayoutValObject(fullLayout, p.parts); + + if(valObject && valObject.impliedEdits && vi !== null) { + for(var impliedKey in valObject.impliedEdits) { + doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey]); + } + } // 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. @@ -2023,13 +1842,10 @@ function _relayout(gd, aobj) { } // check autorange vs range else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) { - doextra(ptrunk + '.autorange', false); recordAlteredAxis(pleafPlus); Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); } else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) { - doextra([ptrunk + '.range[0]', ptrunk + '.range[1]'], - undefined); recordAlteredAxis(pleafPlus); Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); var axFull = Lib.nestedProperty(fullLayout, ptrunk).get(); @@ -2042,34 +1858,6 @@ function _relayout(gd, aobj) { else if(pleafPlus.match(/^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/)) { Lib.nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null); } - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.constrain.*$/)) { - flags.docalc = true; - } - else if(pleafPlus.match(/^aspectratio\.[xyz]$/)) { - doextra(proot + '.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 axis type between log and linear: we need to convert // positions for components that are still using linearized values, @@ -2083,6 +1871,9 @@ function _relayout(gd, aobj) { if(toLog || fromLog) { if(!ax || !ax.range) { + // 2D never gets here, but 3D does + // I don't think this is needed, but left here in case there + // are edge cases I'm not thinking of. doextra(ptrunk + '.autorange', true); } else if(!parentFull.autorange) { @@ -2122,6 +1913,7 @@ function _relayout(gd, aobj) { // any other type changes: the range from the previous type // will not make sense, so autorange it. doextra(ptrunk + '.autorange', true); + doextra(ptrunk + '.range', null); } Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); } @@ -2147,36 +1939,39 @@ function _relayout(gd, aobj) { if(containerArrayMatch) { arrayStr = containerArrayMatch.array; i = containerArrayMatch.index; - var propStr = containerArrayMatch.property, - componentArray = Lib.nestedProperty(layout, arrayStr), - obji = (componentArray || [])[i] || {}; + var propStr = containerArrayMatch.property; + var componentArray = Lib.nestedProperty(layout, arrayStr); + var obji = (componentArray || [])[i] || {}; + var objToAutorange = obji; + + var updateValObject = valObject || {editType: 'calc'}; + var checkForAutorange = updateValObject.editType.indexOf('calcIfAutorange') !== -1; if(i === '') { - // replacing the entire array: too much going on, force recalc - if(ai.indexOf('updatemenus') === -1) flags.docalc = true; + // replacing the entire array - too many possibilities, just recalc + if(checkForAutorange) flags.calc = true; + else editTypes.update(flags, updateValObject); + checkForAutorange = false; // clear this, we're already doing a recalc } else if(propStr === '') { // special handling of undoit if we're adding or removing an element // ie 'annotations[2]' which can be {...} (add) or null (remove) - var toggledObj = vi; + objToAutorange = vi; if(manageArrays.isAddVal(vi)) { undoit[ai] = null; } else if(manageArrays.isRemoveVal(vi)) { undoit[ai] = obji; - toggledObj = obji; + objToAutorange = obji; } else Lib.warn('unrecognized full object value', aobj); - - if(refAutorange(toggledObj, 'x') || refAutorange(toggledObj, 'y') && - ai.indexOf('updatemenus') === -1) { - flags.docalc = true; - } } - else if((refAutorange(obji, 'x') || refAutorange(obji, 'y')) && - !Lib.containsAny(ai, ['color', 'opacity', 'align', 'dash', 'updatemenus'])) { - flags.docalc = true; + + if(checkForAutorange && (refAutorange(objToAutorange, 'x') || refAutorange(objToAutorange, 'y'))) { + flags.calc = true; } + else editTypes.update(flags, updateValObject); + // prepare the edits object we'll send to applyContainerArrayChanges if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {}; @@ -2186,7 +1981,7 @@ function _relayout(gd, aobj) { delete aobj[ai]; } - // handle axis reversal explicitly, as there's no 'reverse' flag + // handle axis reversal explicitly, as there's no 'reverse' attribute else if(pleaf === 'reverse') { if(parentIn.range) parentIn.range.reverse(); else { @@ -2194,77 +1989,19 @@ function _relayout(gd, aobj) { parentIn.range = [1, 0]; } - if(parentFull.autorange) flags.docalc = true; - else flags.doplot = true; + if(parentFull.autorange) flags.calc = true; + else flags.plot = true; } 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(proot.indexOf('scene') === 0) { - if(p.parts[1] === 'camera') flags.docamera = true; - else flags.doplot = true; - } - else if(proot.indexOf('geo') === 0) flags.doplot = true; - else if(proot.indexOf('ternary') === 0) flags.doplot = true; - else if(ai === 'paper_bgcolor') flags.doplot = true; - else if(proot === 'margin' || - pp1 === 'autorange' || - pp1 === 'rangemode' || - pp1 === 'type' || - pp1 === 'domain' || - pp1 === 'fixedrange' || - pp1 === 'scaleanchor' || - pp1 === 'scaleratio' || - ai.indexOf('calendar') !== -1 || - ai.match(/^(bar|box|font)/)) { - flags.docalc = true; - } - else if(fullLayout._has('gl2d') && - (ai.indexOf('axis') !== -1 || ai === 'plot_bgcolor') - ) { - flags.doplot = true; - } - else if(fullLayout._has('gl2d') && + if(fullLayout._has('gl2d') && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) ) { - flags.docalc = true; - } - else if(ai === 'hiddenlabels') flags.docalc = true; - else if(proot.indexOf('legend') !== -1) flags.dolegend = true; - else if(ai.indexOf('title') !== -1) flags.doticks = true; - else if(proot.indexOf('bgcolor') !== -1) flags.dolayoutstyle = true; - else if(plen > 1 && Lib.containsAny(pp1, ['tick', 'exponent', 'grid', 'zeroline'])) { - flags.doticks = true; - } - else if(ai.indexOf('.linewidth') !== -1 && - ai.indexOf('axis') !== -1) { - flags.doticks = flags.dolayoutstyle = true; - } - else if(plen > 1 && pp1.indexOf('line') !== -1) { - flags.dolayoutstyle = true; - } - else if(plen > 1 && pp1 === 'mirror') { - flags.doticks = flags.dolayoutstyle = true; - } - else if(ai === 'margin.pad') { - flags.doticks = flags.dolayoutstyle = true; - } - /* - * hovermode, dragmode, and spikes 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 || - ai.indexOf('spike') !== -1) { - flags.domodebar = true; - } - else if(['height', 'width', 'autosize'].indexOf(ai) === -1) { - flags.doplot = true; + flags.calc = true; } + else if(valObject) editTypes.update(flags, valObject); + else flags.calc = true; p.set(vi); } @@ -2274,7 +2011,7 @@ function _relayout(gd, aobj) { for(arrayStr in arrayEdits) { var finished = manageArrays.applyContainerArrayChanges(gd, Lib.nestedProperty(layout, arrayStr), arrayEdits[arrayStr], flags); - if(!finished) flags.doplot = true; + if(!finished) flags.plot = true; } // figure out if we need to recalculate axis constraints @@ -2288,7 +2025,7 @@ function _relayout(gd, aobj) { // specifying arbitrary ranges for all axes in the group. // this way some ranges may expand beyond what's specified, // as they do at first draw, to satisfy the constraints. - flags.docalc = true; + flags.calc = true; for(var groupAxId in group) { if(!rangesAltered[groupAxId]) { axisIds.getFromId(gd, groupAxId)._constraintShrinkable = true; @@ -2309,9 +2046,9 @@ function _relayout(gd, aobj) { (fullLayout.width !== oldWidth) || (fullLayout.height !== oldHeight); - if(hasSizechanged) flags.docalc = true; + if(hasSizechanged) flags.calc = true; - if(flags.doplot || flags.docalc) { + if(flags.plot || flags.calc) { flags.layoutReplot = true; } @@ -2341,7 +2078,7 @@ 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) { +Plotly.update = function update(gd, traceUpdate, layoutUpdate, _traces) { gd = helpers.getGraphDiv(gd); helpers.clearPromiseQueue(gd); @@ -2355,14 +2092,17 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) { if(Object.keys(traceUpdate).length) gd.changed = true; if(Object.keys(layoutUpdate).length) gd.changed = true; + var traces = helpers.coerceTraceIndices(gd, _traces); + var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces), restyleFlags = restyleSpecs.flags; var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate)), relayoutFlags = relayoutSpecs.flags; - // clear calcdata if required - if(restyleFlags.clearCalc || relayoutFlags.docalc) gd.calcdata = undefined; + // clear calcdata and/or axis types if required + if(restyleFlags.clearCalc || relayoutFlags.calc) gd.calcdata = undefined; + if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate); // fill in redraw sequence var seq = []; @@ -2388,13 +2128,13 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) { 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); + if(restyleFlags.style) seq.push(subroutines.doTraceStyle); + if(restyleFlags.colorbars) seq.push(subroutines.doColorBars); + if(relayoutFlags.legend) seq.push(subroutines.doLegend); + if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); + if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout); + if(relayoutFlags.modebar) seq.push(subroutines.doModeBar); + if(relayoutFlags.camera) seq.push(subroutines.doCamera); } seq.push(Plots.rehover); diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 27fab419cd7..74f443f9af2 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -24,7 +24,7 @@ var polarAxisAttrs = require('../plots/polar/axis_attributes'); var editTypes = require('./edit_types'); var extendFlat = Lib.extendFlat; -var extendDeep = Lib.extendDeep; +var extendDeepAll = Lib.extendDeepAll; var IS_SUBPLOT_OBJ = '_isSubplotObj'; var IS_LINKED_TO_ARRAY = '_isLinkedToArray'; @@ -63,11 +63,29 @@ exports.get = function() { return { defs: { - valObjects: Lib.valObjects, - metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']), - editTypes: { - traces: editTypes.traces(), - layout: editTypes.layout() + valObjects: Lib.valObjectMeta, + metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role', 'editType', 'impliedEdits']), + editType: { + traces: editTypes.traces, + layout: editTypes.layout + }, + impliedEdits: { + description: [ + 'Sometimes when an attribute is changed, other attributes', + 'must be altered as well in order to achieve the intended', + 'result. For example, when `range` is specified, it is', + 'important to set `autorange` to `false` or the new `range`', + 'value would be lost in the redraw. `impliedEdits` is the', + 'mechanism to do this: `impliedEdits: {autorange: false}`.', + 'Each key is a relative paths to the attribute string to', + 'change, using *^* to ascend into the parent container,', + 'for example `range[0]` has `impliedEdits: {*^autorange*: false}`.', + 'A value of `undefined` means that the attribute will not be', + 'changed, but its previous value should be recorded in case', + 'we want to reverse this change later. For example, `autorange`', + 'has `impliedEdits: {*range[0]*: undefined, *range[1]*:undefined}', + 'because the range will likely be changed by redraw.' + ].join(' ') } }, @@ -100,23 +118,31 @@ exports.get = function() { * The decision to not use callback return values for controlling tree pruning arose from * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions * precedes the callback call. + * @param {string} [attrString] + * the path to the current attribute, as an attribute string (ie 'marker.line') + * typically unsupplied, but you may supply it if you want to disambiguate which attrs tree you + * are starting from * * @return {object} transformOut * copy of transformIn that contains attribute defaults */ -exports.crawl = function(attrs, callback, specifiedLevel) { +exports.crawl = function(attrs, callback, specifiedLevel, attrString) { var level = specifiedLevel || 0; + attrString = attrString || ''; Object.keys(attrs).forEach(function(attrName) { var attr = attrs[attrName]; if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; - callback(attr, attrName, attrs, level); + var fullAttrString = (attrString ? attrString + '.' : '') + attrName; + callback(attr, attrName, attrs, level, fullAttrString); if(exports.isValObject(attr)) return; - if(Lib.isPlainObject(attr)) exports.crawl(attr, callback, level + 1); + if(Lib.isPlainObject(attr) && attrName !== 'impliedEdits') { + exports.crawl(attr, callback, level + 1, fullAttrString); + } }); }; @@ -199,7 +225,7 @@ 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. + // used with filter and groupby transforms. if(trace._fullInput && trace._fullInput._module && trace._fullInput._module.attributes) { exports.crawl(trace._fullInput._module.attributes, callback); arrayAttributes = Lib.filterUnique(arrayAttributes); @@ -208,6 +234,182 @@ exports.findArrayAttributes = function(trace) { return arrayAttributes; }; +/* + * Find the valObject for one attribute in an existing trace + * + * @param {object} trace + * full trace object that contains a reference to `_module.attributes` + * @param {object} parts + * an array of parts, like ['transforms', 1, 'value'] + * typically from nestedProperty(...).parts + * + * @return {object|false} + * the valObject for this attribute, or the last found parent + * in some cases the innermost valObject will not exist, for example + * `valType: 'any'` attributes where we might set a part of the attribute. + * In that case, stop at the deepest valObject we *do* find. + */ +exports.getTraceValObject = function(trace, parts) { + var head = parts[0]; + var i = 1; // index to start recursing from + var moduleAttrs, valObject; + + if(head === 'transforms') { + if(!Array.isArray(trace.transforms)) return false; + var tNum = parts[1]; + if(!isIndex(tNum) || tNum >= trace.transforms.length) { + return false; + } + moduleAttrs = (Registry.transformsRegistry[trace.transforms[tNum].type] || {}).attributes; + valObject = moduleAttrs && moduleAttrs[parts[2]]; + i = 3; // start recursing only inside the transform + } + else if(trace.type === 'area') { + valObject = polarAreaAttrs[head]; + } + else { + // first look in the module for this trace + // components have already merged their trace attributes in here + var _module = trace._module; + if(!_module) _module = (Registry.modules[trace.type || baseAttributes.type.dflt] || {})._module; + if(!_module) return false; + + moduleAttrs = _module.attributes; + valObject = moduleAttrs && moduleAttrs[head]; + + // then look in the subplot attributes + if(!valObject) { + var subplotModule = _module.basePlotModule; + if(subplotModule && subplotModule.attributes) { + valObject = subplotModule.attributes[head]; + } + } + + // finally look in the global attributes + if(!valObject) valObject = baseAttributes[head]; + } + + return recurseIntoValObject(valObject, parts, i); +}; + +/* + * Find the valObject for one layout attribute + * + * @param {array} parts + * an array of parts, like ['annotations', 1, 'x'] + * typically from nestedProperty(...).parts + * + * @return {object|false} + * the valObject for this attribute, or the last found parent + * in some cases the innermost valObject will not exist, for example + * `valType: 'any'` attributes where we might set a part of the attribute. + * In that case, stop at the deepest valObject we *do* find. + */ +exports.getLayoutValObject = function(fullLayout, parts) { + var valObject = layoutHeadAttr(fullLayout, parts[0]); + + return recurseIntoValObject(valObject, parts, 1); +}; + +function layoutHeadAttr(fullLayout, head) { + var i, key, _module, attributes; + + // look for attributes of the subplot types used on the plot + var basePlotModules = fullLayout._basePlotModules; + if(basePlotModules) { + var out; + for(i = 0; i < basePlotModules.length; i++) { + _module = basePlotModules[i]; + if(_module.attrRegex && _module.attrRegex.test(head)) { + // if a module defines overrides, these take precedence + // initially this is to allow gl2d different editTypes from svg cartesian + if(_module.layoutAttrOverrides) return _module.layoutAttrOverrides; + + // otherwise take the first attributes we find + if(!out && _module.layoutAttributes) out = _module.layoutAttributes; + } + + // a module can also override the behavior of base (and component) module layout attrs + // again see gl2d for initial use case + var baseOverrides = _module.baseLayoutAttrOverrides; + if(baseOverrides && head in baseOverrides) return baseOverrides[head]; + } + if(out) return out; + } + + // look for layout attributes contributed by traces on the plot + var modules = fullLayout._modules; + if(modules) { + for(i = 0; i < modules.length; i++) { + attributes = modules[i].layoutAttributes; + if(attributes && head in attributes) { + return attributes[head]; + } + } + } + + /* + * Next look in components. + * Components that define a schema have already merged this into + * base and subplot attribute defs, so ignore these. + * Others (older style) all put all their attributes + * inside a container matching the module `name` + * eg `attributes` (array) or `legend` (object) + */ + for(key in Registry.componentsRegistry) { + _module = Registry.componentsRegistry[key]; + if(!_module.schema && (head === _module.name)) { + return _module.layoutAttributes; + } + } + + if(head in baseLayoutAttributes) return baseLayoutAttributes[head]; + + // Polar doesn't populate _modules or _basePlotModules + // just fall back on these when the others fail + if(head === 'radialaxis' || head === 'angularaxis') { + return polarAxisAttrs[head]; + } + return polarAxisAttrs.layout[head] || false; +} + +function recurseIntoValObject(valObject, parts, i) { + if(!valObject) return false; + + if(valObject._isLinkedToArray) { + // skip array index, abort if we try to dive into an array without an index + if(isIndex(parts[i])) i++; + else if(i < parts.length) return false; + } + + // now recurse as far as we can. Occasionally we have an attribute + // setting an internal part below what's + for(; i < parts.length; i++) { + var newValObject = valObject[parts[i]]; + if(Lib.isPlainObject(newValObject)) valObject = newValObject; + else break; + + if(i === parts.length - 1) break; + + if(valObject._isLinkedToArray) { + i++; + if(!isIndex(parts[i])) return false; + } + else if(valObject.valType === 'info_array') { + i++; + var index = parts[i]; + if(!isIndex(index) || index >= valObject.items.length) return false; + valObject = valObject.items[index]; + } + } + + return valObject; +} + +function isIndex(val) { + return val === Math.round(val) && val >= 0; +} + function getTraceAttributes(type) { var _module, basePlotModule; @@ -226,27 +428,16 @@ function getTraceAttributes(type) { attributes.type = null; // base attributes (same for all trace types) - extendDeep(attributes, baseAttributes); + extendDeepAll(attributes, baseAttributes); // module attributes - extendDeep(attributes, _module.attributes); + extendDeepAll(attributes, _module.attributes); // subplot attributes if(basePlotModule.attributes) { - extendDeep(attributes, basePlotModule.attributes); + extendDeepAll(attributes, basePlotModule.attributes); } - // add registered components trace attributes - Object.keys(Registry.componentsRegistry).forEach(function(k) { - var _module = Registry.componentsRegistry[k]; - - if(_module.schema && _module.schema.traces && _module.schema.traces[type]) { - Object.keys(_module.schema.traces[type]).forEach(function(v) { - insertAttrs(attributes, _module.schema.traces[type][v], v); - }); - } - }); - // 'type' gets overwritten by baseAttributes; reset it here attributes.type = type; @@ -259,7 +450,7 @@ function getTraceAttributes(type) { if(_module.layoutAttributes) { var layoutAttributes = {}; - extendDeep(layoutAttributes, _module.layoutAttributes); + extendDeepAll(layoutAttributes, _module.layoutAttributes); out.layoutAttributes = formatAttributes(layoutAttributes); } @@ -268,15 +459,16 @@ function getTraceAttributes(type) { function getLayoutAttributes() { var layoutAttributes = {}; + var key, _module; // global layout attributes - extendDeep(layoutAttributes, baseLayoutAttributes); + extendDeepAll(layoutAttributes, baseLayoutAttributes); // add base plot module layout attributes - Object.keys(Registry.subplotsRegistry).forEach(function(k) { - var _module = Registry.subplotsRegistry[k]; + for(key in Registry.subplotsRegistry) { + _module = Registry.subplotsRegistry[key]; - if(!_module.layoutAttributes) return; + if(!_module.layoutAttributes) continue; if(_module.name === 'cartesian') { handleBasePlotModule(layoutAttributes, _module, 'xaxis'); @@ -287,26 +479,37 @@ function getLayoutAttributes() { handleBasePlotModule(layoutAttributes, _module, astr); } - }); + } // polar layout attributes layoutAttributes = assignPolarLayoutAttrs(layoutAttributes); // add registered components layout attributes - Object.keys(Registry.componentsRegistry).forEach(function(k) { - var _module = Registry.componentsRegistry[k]; - - 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); - }); + for(key in Registry.componentsRegistry) { + _module = Registry.componentsRegistry[key]; + var schema = _module.schema; + + /* + * Components with defined schema have already been merged in at register time + * but a few components define attributes that apply only to xaxis + * not yaxis (rangeselector, rangeslider) - delete from y schema. + * Note that the input attributes for xaxis/yaxis are the same object + * so it's not possible to only add them to xaxis from the start. + * If we ever have such asymmetry the other way, or anywhere else, + * we will need to extend both this code and mergeComponentAttrsToSubplot + * (which will not find yaxis only for example) + */ + if(schema && (schema.subplots || schema.layout)) { + var subplots = schema.subplots; + if(subplots && subplots.xaxis && !subplots.yaxis) { + for(var xkey in subplots.xaxis) delete layoutAttributes.yaxis[xkey]; + } } - else { + // older style without schema need to be explicitly merged in now + else if(_module.layoutAttributes) { insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name); } - }); + } return { layoutAttributes: formatAttributes(layoutAttributes) @@ -315,7 +518,7 @@ function getLayoutAttributes() { function getTransformAttributes(type) { var _module = Registry.transformsRegistry[type]; - var attributes = extendDeep({}, _module.attributes); + var attributes = extendDeepAll({}, _module.attributes); // add registered components transform attributes Object.keys(Registry.componentsRegistry).forEach(function(k) { @@ -335,7 +538,7 @@ function getTransformAttributes(type) { function getFramesAttributes() { var attrs = { - frames: Lib.extendDeep({}, frameAttributes) + frames: Lib.extendDeepAll({}, frameAttributes) }; formatAttributes(attrs); @@ -359,7 +562,8 @@ function mergeValTypeAndRole(attrs) { description: [ 'Sets the source reference on plot.ly for ', attrName, '.' - ].join(' ') + ].join(' '), + editType: 'none' }; } @@ -417,7 +621,7 @@ function assignPolarLayoutAttrs(layoutAttributes) { function handleBasePlotModule(layoutAttributes, _module, astr) { var np = Lib.nestedProperty(layoutAttributes, astr), - attrs = extendDeep({}, _module.layoutAttributes); + attrs = extendDeepAll({}, _module.layoutAttributes); attrs[IS_SUBPLOT_OBJ] = true; np.set(attrs); @@ -426,5 +630,5 @@ function handleBasePlotModule(layoutAttributes, _module, astr) { function insertAttrs(baseAttrs, newAttrs, astr) { var np = Lib.nestedProperty(baseAttrs, astr); - np.set(extendDeep(np.get() || {}, newAttrs)); + np.set(extendDeepAll(np.get() || {}, newAttrs)); } diff --git a/src/plot_api/register.js b/src/plot_api/register.js index 87dae2e49b2..359cfb08bfe 100644 --- a/src/plot_api/register.js +++ b/src/plot_api/register.js @@ -85,7 +85,7 @@ function registerTransformModule(newModule) { Lib.log(prefix + ' registered without a *supplyDefaults* method.'); } - Registry.transformsRegistry[newModule.name] = newModule; + Registry.registerTransform(newModule); } function registerComponentModule(newModule) { diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js index 91c0dc5d15d..95ae68780f2 100644 --- a/src/plot_api/validate.js +++ b/src/plot_api/validate.js @@ -350,15 +350,14 @@ function getNestedSchema(schema, key) { return schema[parts.keyMinusId]; } -function splitKey(key) { - var idRegex = /([2-9]|[1-9][0-9]+)$/; +var idRegex = Lib.counterRegex('([a-z]+)'); - var keyMinusId = key.split(idRegex)[0], - id = key.substr(keyMinusId.length, key.length); +function splitKey(key) { + var idMatch = key.match(idRegex); return { - keyMinusId: keyMinusId, - id: id + keyMinusId: idMatch && idMatch[1], + id: idMatch && idMatch[2] }; } diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 405623be271..40c36be371c 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -15,13 +15,15 @@ module.exports = { valType: 'enumerated', role: 'info', values: [], // listed dynamically - dflt: 'scatter' + dflt: 'scatter', + editType: 'calc+clearAxisTypes' }, visible: { valType: 'enumerated', values: [true, false, 'legendonly'], role: 'info', dflt: true, + editType: 'calc', description: [ 'Determines whether or not this trace is visible.', 'If *legendonly*, the trace is not drawn,', @@ -33,6 +35,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, + editType: 'style', description: [ 'Determines whether or not an item corresponding to this', 'trace is shown in the legend.' @@ -42,6 +45,7 @@ module.exports = { valType: 'string', role: 'info', dflt: '', + editType: 'style', description: [ 'Sets the legend group for this trace.', 'Traces part of the same legend group hide/show at the same time', @@ -54,11 +58,13 @@ module.exports = { min: 0, max: 1, dflt: 1, + editType: 'style', description: 'Sets the opacity of the trace.' }, name: { valType: 'string', role: 'info', + editType: 'style', description: [ 'Sets the trace name.', 'The trace name appear as the legend item and on hover.' @@ -67,10 +73,12 @@ module.exports = { uid: { valType: 'string', role: 'info', - dflt: '' + dflt: '', + editType: 'calc' }, ids: { valType: 'data_array', + editType: 'calc', description: [ 'Assigns id labels to each datum.', 'These ids for object constancy of data points during animation.' @@ -78,6 +86,7 @@ module.exports = { }, customdata: { valType: 'data_array', + editType: 'calc', description: [ 'Assigns extra data each datum.', 'This may be useful when listening to hover, click and selection events.', @@ -92,6 +101,7 @@ module.exports = { extras: ['all', 'none', 'skip'], arrayOk: true, dflt: 'all', + editType: 'none', description: [ 'Determines which trace information appear on hover.', 'If `none` or `skip` are set, no information is displayed upon hovering.', @@ -105,6 +115,7 @@ module.exports = { noBlank: true, strict: true, role: 'info', + editType: 'calc', description: [ 'The stream id number links a data trace on a plot with a stream.', 'See https://plot.ly/settings for more details.' @@ -116,12 +127,14 @@ module.exports = { max: 10000, dflt: 500, role: 'info', + editType: 'calc', 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(' ') - } + }, + editType: 'calc' } }; diff --git a/src/plots/cartesian/attributes.js b/src/plots/cartesian/attributes.js index a472892edc6..0b06a69b0a4 100644 --- a/src/plots/cartesian/attributes.js +++ b/src/plots/cartesian/attributes.js @@ -14,6 +14,7 @@ module.exports = { valType: 'subplotid', role: 'info', dflt: 'x', + editType: 'calc+clearAxisTypes', description: [ 'Sets a reference between this trace\'s x coordinates and', 'a 2D cartesian x axis.', @@ -26,6 +27,7 @@ module.exports = { valType: 'subplotid', role: 'info', dflt: 'y', + editType: 'calc+clearAxisTypes', description: [ 'Sets a reference between this trace\'s y coordinates and', 'a 2D cartesian y axis.', diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 5c4bc218bad..7d18cc8fd8a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -175,19 +175,6 @@ axes.getDataToCoordFunc = function(gd, trace, target, targetArray) { return getDataConversions(gd, trace, target, targetArray).d2c; }; -// 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; - }); -}; - // 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) { diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index 80c6f48af50..93468b6b5f8 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -7,25 +7,25 @@ */ 'use strict'; +var counterRegex = require('../../lib').counterRegex; module.exports = { idRegex: { - x: /^x([2-9]|[1-9][0-9]+)?$/, - y: /^y([2-9]|[1-9][0-9]+)?$/ + x: counterRegex('x'), + y: counterRegex('y') }, - attrRegex: { - x: /^xaxis([2-9]|[1-9][0-9]+)?$/, - y: /^yaxis([2-9]|[1-9][0-9]+)?$/ - }, + attrRegex: counterRegex('[xy]axis'), // axis match regular expression - xAxisMatch: /^xaxis[0-9]*$/, - yAxisMatch: /^yaxis[0-9]*$/, + xAxisMatch: counterRegex('xaxis'), + yAxisMatch: counterRegex('yaxis'), // pattern matching axis ids and names + // note that this is more permissive than counterRegex, as + // id2name, name2id, and cleanId accept "x1" etc AX_ID_PATTERN: /^[xyz][0-9]*$/, AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/, diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 90855d7452a..52571086cde 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -20,6 +20,7 @@ module.exports = { visible: { valType: 'boolean', role: 'info', + editType: 'plot', description: [ 'A single toggle to hide the axis while preserving interaction like dragging.', 'Default is true when a cheater plot is present on the axis, otherwise', @@ -30,6 +31,7 @@ module.exports = { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'ticks', description: [ 'Sets default for all colors associated with this axis', 'all at once: line, font, tick, and grid colors.', @@ -40,9 +42,11 @@ module.exports = { title: { valType: 'string', role: 'info', + editType: 'ticks', description: 'Sets the title of this axis.' }, - titlefont: extendFlat({}, fontAttrs, { + titlefont: fontAttrs({ + editType: 'ticks', description: [ 'Sets this axis\' title font.' ].join(' ') @@ -55,6 +59,7 @@ module.exports = { values: ['-', 'linear', 'log', 'date', 'category'], dflt: '-', role: 'info', + editType: 'calc', description: [ 'Sets the axis type.', 'By default, plotly attempts to determined the axis type', @@ -67,6 +72,8 @@ module.exports = { values: [true, false, 'reversed'], dflt: true, role: 'style', + editType: 'calc', + impliedEdits: {'range[0]': undefined, 'range[1]': undefined}, description: [ 'Determines whether or not the range of this axis is', 'computed in relation to the input data.', @@ -79,6 +86,7 @@ module.exports = { values: ['normal', 'tozero', 'nonnegative'], dflt: 'normal', role: 'style', + editType: 'calc', description: [ 'If *normal*, the range is computed in relation to the extrema', 'of the input data.', @@ -92,9 +100,11 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'any'}, - {valType: 'any'} + {valType: 'any', editType: 'plot', impliedEdits: {'^autorange': false}}, + {valType: 'any', editType: 'plot', impliedEdits: {'^autorange': false}} ], + editType: 'plot', + impliedEdits: {'autorange': false}, description: [ 'Sets the range of this axis.', 'If the axis `type` is *log*, then you must take the log of your', @@ -112,6 +122,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: [ 'Determines whether or not this axis is zoom-able.', 'If true, then zoom is disabled.' @@ -126,6 +137,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', + editType: 'calc', description: [ 'If set to another axis id (e.g. `x2`, `y`), the range of this axis', 'changes together with the range of the corresponding axis', @@ -147,6 +159,7 @@ module.exports = { min: 0, dflt: 1, role: 'info', + editType: 'calc', description: [ 'If this axis is linked to another by `scaleanchor`, this determines the pixel', 'to unit scale ratio. For example, if this value is 10, then every unit on', @@ -160,6 +173,7 @@ module.exports = { values: ['range', 'domain'], dflt: 'range', role: 'info', + editType: 'calc', description: [ 'If this axis needs to be compressed (either due to its own `scaleanchor` and', '`scaleratio` or those of the other axis), determines how that happens:', @@ -171,6 +185,7 @@ module.exports = { valType: 'enumerated', values: ['left', 'center', 'right', 'top', 'middle', 'bottom'], role: 'info', + editType: 'calc', description: [ 'If this axis needs to be compressed (either due to its own `scaleanchor` and', '`scaleratio` or those of the other axis), determines which direction we push', @@ -183,6 +198,8 @@ module.exports = { valType: 'enumerated', values: ['auto', 'linear', 'array'], role: 'info', + editType: 'ticks', + impliedEdits: {tick0: undefined, dtick: undefined}, description: [ 'Sets the tick mode for this axis.', 'If *auto*, the number of ticks is set via `nticks`.', @@ -199,6 +216,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'ticks', description: [ 'Specifies the maximum number of ticks for the particular axis.', 'The actual number of ticks will be chosen automatically to be', @@ -209,6 +227,8 @@ module.exports = { tick0: { valType: 'any', role: 'style', + editType: 'ticks', + impliedEdits: {tickmode: 'linear'}, description: [ 'Sets the placement of the first tick on this axis.', 'Use with `dtick`.', @@ -223,6 +243,8 @@ module.exports = { dtick: { valType: 'any', role: 'style', + editType: 'ticks', + impliedEdits: {tickmode: 'linear'}, 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.', @@ -247,6 +269,7 @@ module.exports = { }, tickvals: { valType: 'data_array', + editType: 'ticks', description: [ 'Sets the values at which ticks on this axis appear.', 'Only has an effect if `tickmode` is set to *array*.', @@ -255,6 +278,7 @@ module.exports = { }, ticktext: { valType: 'data_array', + editType: 'ticks', description: [ 'Sets the text displayed at the ticks position via `tickvals`.', 'Only has an effect if `tickmode` is set to *array*.', @@ -265,6 +289,7 @@ module.exports = { valType: 'enumerated', values: ['outside', 'inside', ''], role: 'style', + editType: 'ticks', description: [ 'Determines whether ticks are drawn or not.', 'If **, this axis\' ticks are not drawn.', @@ -277,6 +302,7 @@ module.exports = { values: [true, 'ticks', false, 'all', 'allticks'], dflt: false, role: 'style', + editType: 'ticks+layoutstyle', description: [ 'Determines if the axis lines or/and ticks are mirrored to', 'the opposite side of the plotting area.', @@ -293,6 +319,7 @@ module.exports = { min: 0, dflt: 5, role: 'style', + editType: 'ticks', description: 'Sets the tick length (in px).' }, tickwidth: { @@ -300,24 +327,28 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'ticks', description: 'Sets the tick width (in px).' }, tickcolor: { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'ticks', description: 'Sets the tick color.' }, showticklabels: { valType: 'boolean', dflt: true, role: 'style', + editType: 'ticks', description: 'Determines whether or not the tick labels are drawn.' }, showspikes: { valType: 'boolean', dflt: false, role: 'style', + editType: 'modebar', description: [ 'Determines whether or not spikes (aka droplines) are drawn for this axis.', 'Note: This only takes affect when hovermode = closest' @@ -327,20 +358,23 @@ module.exports = { valType: 'color', dflt: null, role: 'style', + editType: 'none', description: 'Sets the spike color. If undefined, will use the series color' }, spikethickness: { valType: 'number', dflt: 3, role: 'style', + editType: 'none', description: 'Sets the width (in px) of the zero line.' }, - spikedash: extendFlat({}, dash, {dflt: 'dash'}), + spikedash: extendFlat({}, dash, {dflt: 'dash', editType: 'none'}), spikemode: { valType: 'flaglist', flags: ['toaxis', 'across', 'marker'], role: 'style', dflt: 'toaxis', + editType: 'none', description: [ 'Determines the drawing mode for the spike line', 'If *toaxis*, the line is drawn from the data point to the axis the ', @@ -353,13 +387,15 @@ module.exports = { 'plotted on' ].join(' ') }, - tickfont: extendFlat({}, fontAttrs, { + tickfont: fontAttrs({ + editType: 'ticks', description: 'Sets the tick font.' }), tickangle: { valType: 'angle', dflt: 'auto', role: 'style', + editType: 'ticks', description: [ 'Sets the angle of the tick labels with respect to the horizontal.', 'For example, a `tickangle` of -90 draws the tick labels', @@ -370,6 +406,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'ticks', description: 'Sets a tick label prefix.' }, showtickprefix: { @@ -377,6 +414,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'ticks', description: [ 'If *all*, all tick labels are displayed with a prefix.', 'If *first*, only the first tick is displayed with a prefix.', @@ -388,6 +426,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'ticks', description: 'Sets a tick label suffix.' }, showticksuffix: { @@ -395,6 +434,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'ticks', description: 'Same as `showtickprefix` but for tick suffixes.' }, showexponent: { @@ -402,6 +442,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'ticks', description: [ 'If *all*, all exponents are shown besides their significands.', 'If *first*, only the exponent of the first tick is shown.', @@ -414,6 +455,7 @@ module.exports = { values: ['none', 'e', 'E', 'power', 'SI', 'B'], dflt: 'B', role: 'style', + editType: 'ticks', description: [ 'Determines a formatting rule for the tick exponents.', 'For example, consider the number 1,000,000,000.', @@ -429,6 +471,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'style', + editType: 'ticks', description: [ 'If "true", even 4-digit integers are separated' ].join(' ') @@ -437,6 +480,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'ticks', description: [ 'Sets the tick label formatting rule using d3 formatting mini-languages', 'which are very similar to those in Python. For numbers, see:', @@ -452,6 +496,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'none', description: [ 'Sets the hover text formatting rule using d3 formatting mini-languages', 'which are very similar to those in Python. For numbers, see:', @@ -468,6 +513,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'style', + editType: 'layoutstyle', description: [ 'Determines whether or not a line bounding this axis is drawn.' ].join(' ') @@ -476,6 +522,7 @@ module.exports = { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'layoutstyle', description: 'Sets the axis line color.' }, linewidth: { @@ -483,11 +530,13 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'ticks+layoutstyle', description: 'Sets the width (in px) of the axis line.' }, showgrid: { valType: 'boolean', role: 'style', + editType: 'ticks', description: [ 'Determines whether or not grid lines are drawn.', 'If *true*, the grid lines are drawn at every tick mark.' @@ -497,6 +546,7 @@ module.exports = { valType: 'color', dflt: colorAttrs.lightLine, role: 'style', + editType: 'ticks', description: 'Sets the color of the grid lines.' }, gridwidth: { @@ -504,11 +554,13 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'ticks', description: 'Sets the width (in px) of the grid lines.' }, zeroline: { valType: 'boolean', role: 'style', + editType: 'ticks', description: [ 'Determines whether or not a line is drawn at along the 0 value', 'of this axis.', @@ -519,12 +571,14 @@ module.exports = { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'ticks', description: 'Sets the line color of the zero line.' }, zerolinewidth: { valType: 'number', dflt: 1, role: 'style', + editType: 'ticks', description: 'Sets the width (in px) of the zero line.' }, // positioning attributes @@ -538,6 +592,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', + editType: 'plot', description: [ 'If set to an opposite-letter axis id (e.g. `x2`, `y`), this axis is bound to', 'the corresponding opposite-letter axis.', @@ -550,6 +605,7 @@ module.exports = { valType: 'enumerated', values: ['top', 'bottom', 'left', 'right'], role: 'info', + editType: 'plot', description: [ 'Determines whether a x (y) axis is positioned', 'at the *bottom* (*left*) or *top* (*right*)', @@ -567,6 +623,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', + editType: 'calc', description: [ 'If set a same-letter axis id, this axis is overlaid on top of', 'the corresponding same-letter axis.', @@ -578,6 +635,7 @@ module.exports = { values: ['above traces', 'below traces'], dflt: 'above traces', role: 'info', + editType: 'plot', description: [ 'Sets the layer on which this axis is displayed.', 'If *above traces*, this axis is displayed above all the subplot\'s traces', @@ -591,10 +649,11 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'calc'}, + {valType: 'number', min: 0, max: 1, editType: 'calc'} ], dflt: [0, 1], + editType: 'calc', description: [ 'Sets the domain of this axis (in plot fraction).' ].join(' ') @@ -605,6 +664,7 @@ module.exports = { max: 1, dflt: 0, role: 'style', + editType: 'plot', description: [ 'Sets the position of this axis in the plotting space', '(in normalized coordinates).', @@ -619,6 +679,7 @@ module.exports = { ], dflt: 'trace', role: 'info', + editType: 'calc', 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.', @@ -634,17 +695,20 @@ module.exports = { categoryarray: { valType: 'data_array', role: 'info', + editType: 'calc', 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(' ') }, + editType: 'calc', _deprecated: { autotick: { valType: 'boolean', role: 'info', + editType: 'ticks', description: [ 'Obsolete.', 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', diff --git a/src/plots/cartesian/transition_axes.js b/src/plots/cartesian/transition_axes.js index 42591e49217..181ce35cb19 100644 --- a/src/plots/cartesian/transition_axes.js +++ b/src/plots/cartesian/transition_axes.js @@ -15,7 +15,7 @@ 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$/; +var axisRegex = require('./constants').attrRegex; module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCompleteCallback) { var fullLayout = gd._fullLayout; @@ -29,8 +29,8 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo attrList = ai.split('.'); match = attrList[0].match(axisRegex); if(match) { - var axisLetter = match[1]; - var axisName = axisLetter + 'axis'; + var axisLetter = ai.charAt(0); + var axisName = attrList[0]; axis = fullLayout[axisName]; update = {}; diff --git a/src/plots/font_attributes.js b/src/plots/font_attributes.js index daf2490c563..65cefe60aba 100644 --- a/src/plots/font_attributes.js +++ b/src/plots/font_attributes.js @@ -8,33 +8,67 @@ 'use strict'; +/* + * make a font attribute group + * + * @param {object} opts + * @param {string} + * opts.description: where & how this font is used + * @param {optional bool} arrayOk: + * should each part (family, size, color) be arrayOk? default false. + * @param {string} editType: + * the editType for all pieces of this font + * @param {optional string} colorEditType: + * a separate editType just for color + * + * @return {object} attributes object containing {family, size, color} as specified + */ +module.exports = function(opts) { + var editType = opts.editType; + var colorEditType = opts.colorEditType; + if(colorEditType === undefined) colorEditType = editType; + var attrs = { + family: { + valType: 'string', + role: 'style', + noBlank: true, + strict: true, + editType: editType, + 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, + editType: editType + }, + color: { + valType: 'color', + role: 'style', + editType: colorEditType + }, + editType: editType, + // blank strings so compress_attributes can remove + // TODO - that's uber hacky... better solution? + description: '' + (opts.description || '') + '' + }; -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' + if(opts.arrayOk) { + attrs.family.arrayOk = true; + attrs.size.arrayOk = true; + attrs.color.arrayOk = true; } + + return attrs; }; diff --git a/src/plots/geo/index.js b/src/plots/geo/index.js index baac5e1cf42..c56651f8a6e 100644 --- a/src/plots/geo/index.js +++ b/src/plots/geo/index.js @@ -12,17 +12,18 @@ var Geo = require('./geo'); var Plots = require('../../plots/plots'); +var counterRegex = require('../../lib').counterRegex; +var GEO = 'geo'; -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 = exports.attrRegex = counterRegex(GEO); exports.attributes = require('./layout/attributes'); @@ -33,7 +34,7 @@ exports.supplyLayoutDefaults = require('./layout/defaults'); exports.plot = function plotGeo(gd) { var fullLayout = gd._fullLayout, calcData = gd.calcdata, - geoIds = Plots.getSubplotIds(fullLayout, 'geo'); + geoIds = Plots.getSubplotIds(fullLayout, GEO); /** * If 'plotly-geo-assets.js' is not included, @@ -45,7 +46,7 @@ exports.plot = function plotGeo(gd) { for(var i = 0; i < geoIds.length; i++) { var geoId = geoIds[i], - geoCalcData = Plots.getSubplotCalcData(calcData, 'geo', geoId), + geoCalcData = Plots.getSubplotCalcData(calcData, GEO, geoId), geo = fullLayout[geoId]._subplot; if(!geo) { @@ -64,7 +65,7 @@ exports.plot = function plotGeo(gd) { }; exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldGeoKeys = Plots.getSubplotIds(oldFullLayout, 'geo'); + var oldGeoKeys = Plots.getSubplotIds(oldFullLayout, GEO); for(var i = 0; i < oldGeoKeys.length; i++) { var oldGeoKey = oldGeoKeys[i]; diff --git a/src/plots/geo/layout/attributes.js b/src/plots/geo/layout/attributes.js index e721fb6b0c5..02fa320ed3b 100644 --- a/src/plots/geo/layout/attributes.js +++ b/src/plots/geo/layout/attributes.js @@ -14,6 +14,7 @@ module.exports = { valType: 'subplotid', role: 'info', dflt: 'geo', + editType: 'calc', description: [ 'Sets a reference between this trace\'s geospatial coordinates and', 'a geographic map.', diff --git a/src/plots/geo/layout/layout_attributes.js b/src/plots/geo/layout/layout_attributes.js index 0a6b9e26140..98a5043dcfa 100644 --- a/src/plots/geo/layout/layout_attributes.js +++ b/src/plots/geo/layout/layout_attributes.js @@ -11,9 +11,10 @@ var colorAttrs = require('../../../components/color/attributes'); var constants = require('../constants'); var geoAxesAttrs = require('./axis_attributes'); +var overrideAll = require('../../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ domain: { x: { valType: 'info_array', @@ -254,4 +255,4 @@ module.exports = { }, lonaxis: geoAxesAttrs, lataxis: geoAxesAttrs -}; +}, 'plot', 'from-root'); diff --git a/src/plots/gl2d/index.js b/src/plots/gl2d/index.js index 482d55fa4b1..3618c491212 100644 --- a/src/plots/gl2d/index.js +++ b/src/plots/gl2d/index.js @@ -9,11 +9,14 @@ 'use strict'; +var overrideAll = require('../../plot_api/edit_types').overrideAll; + var Scene2D = require('./scene2d'); var Plots = require('../plots'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); var constants = require('../cartesian/constants'); var Cartesian = require('../cartesian'); +var fxAttrs = require('../../components/fx/layout_attributes'); exports.name = 'gl2d'; @@ -27,6 +30,21 @@ exports.attrRegex = constants.attrRegex; exports.attributes = require('../cartesian/attributes'); +// gl2d uses svg axis attributes verbatim, but overrides editType +// this could potentially be just `layoutAttributes` but it would +// still need special handling somewhere to give it precedence over +// the svg version when both are in use on one plot +exports.layoutAttrOverrides = overrideAll(Cartesian.layoutAttributes, 'plot', 'from-root'); + +// similar overrides for base plot attributes (and those added by components) +exports.baseLayoutAttrOverrides = overrideAll({ + plot_bgcolor: Plots.layoutAttributes.plot_bgcolor, + hoverlabel: fxAttrs.hoverlabel + // dragmode needs calc but only when transitioning TO lasso or select + // so for now it's left inside _relayout + // dragmode: fxAttrs.dragmode +}, 'plot', 'nested'); + exports.plot = function plotGl2d(gd) { var fullLayout = gd._fullLayout, fullData = gd._fullData, diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index c30a7228dee..3c003165a8a 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -9,35 +9,44 @@ 'use strict'; +var overrideAll = require('../../plot_api/edit_types').overrideAll; +var fxAttrs = require('../../components/fx/layout_attributes'); + var Scene = require('./scene'); var Plots = require('../plots'); var Lib = require('../../lib'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); -exports.name = 'gl3d'; +var GL3D = 'gl3d'; +var SCENE = 'scene'; + -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 = exports.attrRegex = Lib.counterRegex('scene'); exports.attributes = require('./layout/attributes'); exports.layoutAttributes = require('./layout/layout_attributes'); +exports.baseLayoutAttrOverrides = overrideAll({ + hoverlabel: fxAttrs.hoverlabel +}, 'plot', 'nested'); + exports.supplyLayoutDefaults = require('./layout/defaults'); exports.plot = function plotGl3d(gd) { var fullLayout = gd._fullLayout, fullData = gd._fullData, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'); + sceneIds = Plots.getSubplotIds(fullLayout, GL3D); for(var i = 0; i < sceneIds.length; i++) { var sceneId = sceneIds[i], - fullSceneData = Plots.getSubplotData(fullData, 'gl3d', sceneId), + fullSceneData = Plots.getSubplotData(fullData, GL3D, sceneId), sceneLayout = fullLayout[sceneId], scene = sceneLayout._scene; @@ -66,7 +75,7 @@ exports.plot = function plotGl3d(gd) { }; exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl3d'); + var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, GL3D); for(var i = 0; i < oldSceneKeys.length; i++) { var oldSceneKey = oldSceneKeys[i]; @@ -85,7 +94,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) exports.toSVG = function(gd) { var fullLayout = gd._fullLayout, - sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'), + sceneIds = Plots.getSubplotIds(fullLayout, GL3D), size = fullLayout._size; for(var i = 0; i < sceneIds.length; i++) { @@ -117,5 +126,5 @@ exports.cleanId = function cleanId(id) { var sceneNum = id.substr(5); if(sceneNum === '1') sceneNum = ''; - return 'scene' + sceneNum; + return SCENE + sceneNum; }; diff --git a/src/plots/gl3d/layout/attributes.js b/src/plots/gl3d/layout/attributes.js index 4c8c714766a..790239e8e90 100644 --- a/src/plots/gl3d/layout/attributes.js +++ b/src/plots/gl3d/layout/attributes.js @@ -14,6 +14,7 @@ module.exports = { valType: 'subplotid', role: 'info', dflt: 'scene', + editType: 'calc+clearAxisTypes', description: [ 'Sets a reference between this trace\'s 3D coordinate system and', 'a 3D scene.', diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index 16f901d56d0..f6e91b3daa3 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -11,9 +11,10 @@ var Color = require('../../../components/color'); var axesAttrs = require('../../cartesian/layout_attributes'); var extendFlat = require('../../../lib/extend').extendFlat; +var overrideAll = require('../../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ visible: axesAttrs.visible, showspikes: { valType: 'boolean', @@ -112,4 +113,4 @@ module.exports = { zeroline: axesAttrs.zeroline, zerolinecolor: axesAttrs.zerolinecolor, zerolinewidth: axesAttrs.zerolinewidth -}; +}, 'plot', 'from-root'); diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index e0fdb42397e..04d4f841a8c 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -59,7 +59,7 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { var bgcolor = coerce('bgcolor'), bgColorCombined = Color.combine(bgcolor, opts.paper_bgcolor); - var cameraKeys = Object.keys(layoutAttributes.camera); + var cameraKeys = ['up', 'center', 'eye']; for(var j = 0; j < cameraKeys.length; j++) { coerce('camera.' + cameraKeys[j] + '.x'); @@ -89,6 +89,12 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { sceneLayoutIn.aspectratio = sceneLayoutOut.aspectratio = {x: 1, y: 1, z: 1}; if(aspectMode === 'manual') sceneLayoutOut.aspectmode = 'auto'; + + /* + * kind of like autorange - we need the calculated aspectmode back in + * the input layout or relayout can cause problems later + */ + sceneLayoutIn.aspectmode = sceneLayoutOut.aspectmode; } supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { diff --git a/src/plots/gl3d/layout/layout_attributes.js b/src/plots/gl3d/layout/layout_attributes.js index 92e9d8c1ab0..33ac9b61f00 100644 --- a/src/plots/gl3d/layout/layout_attributes.js +++ b/src/plots/gl3d/layout/layout_attributes.js @@ -11,37 +11,44 @@ var gl3dAxisAttrs = require('./axis_attributes'); var extendFlat = require('../../../lib/extend').extendFlat; +var counterRegex = require('../../../lib').counterRegex; -function makeVector(x, y, z) { + +function makeCameraVector(x, y, z) { return { x: { valType: 'number', role: 'info', - dflt: x + dflt: x, + editType: 'camera' }, y: { valType: 'number', role: 'info', - dflt: y + dflt: y, + editType: 'camera' }, z: { valType: 'number', role: 'info', - dflt: z - } + dflt: z, + editType: 'camera' + }, + editType: 'camera' }; } module.exports = { - _arrayAttrRegexps: [/^scene([2-9]|[1-9][0-9]+)?\.annotations/], + _arrayAttrRegexps: [counterRegex('scene', '.annotations', true)], bgcolor: { valType: 'color', role: 'style', - dflt: 'rgba(0,0,0,0)' + dflt: 'rgba(0,0,0,0)', + editType: 'plot' }, camera: { - up: extendFlat(makeVector(0, 0, 1), { + up: extendFlat(makeCameraVector(0, 0, 1), { description: [ 'Sets the (x,y,z) components of the \'up\' camera vector.', 'This vector determines the up direction of this scene', @@ -50,7 +57,7 @@ module.exports = { 'the z axis points up.' ].join(' ') }), - center: extendFlat(makeVector(0, 0, 0), { + center: extendFlat(makeCameraVector(0, 0, 0), { description: [ 'Sets the (x,y,z) components of the \'center\' camera vector', 'This vector determines the translation (x,y,z) space', @@ -58,23 +65,25 @@ module.exports = { 'By default, there is no such translation.' ].join(' ') }), - eye: extendFlat(makeVector(1.25, 1.25, 1.25), { + eye: extendFlat(makeCameraVector(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(' ') - }) + }), + editType: 'camera' }, domain: { x: { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'plot'}, + {valType: 'number', min: 0, max: 1, editType: 'plot'} ], dflt: [0, 1], + editType: 'plot', description: [ 'Sets the horizontal domain of this scene', '(in plot fraction).' @@ -84,21 +93,29 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'plot'}, + {valType: 'number', min: 0, max: 1, editType: 'plot'} ], dflt: [0, 1], + editType: 'plot', description: [ 'Sets the vertical domain of this scene', '(in plot fraction).' ].join(' ') - } + }, + editType: 'plot' }, aspectmode: { valType: 'enumerated', role: 'info', values: ['auto', 'cube', 'data', 'manual'], dflt: 'auto', + editType: 'plot', + impliedEdits: { + 'aspectratio.x': undefined, + 'aspectratio.y': undefined, + 'aspectratio.z': undefined + }, description: [ 'If *cube*, this scene\'s axes are drawn as a cube,', 'regardless of the axes\' ranges.', @@ -120,18 +137,26 @@ module.exports = { x: { valType: 'number', role: 'info', - min: 0 + min: 0, + editType: 'plot', + impliedEdits: {'^aspectmode': 'manual'} }, y: { valType: 'number', role: 'info', - min: 0 + min: 0, + editType: 'plot', + impliedEdits: {'^aspectmode': 'manual'} }, z: { valType: 'number', role: 'info', - min: 0 + min: 0, + editType: 'plot', + impliedEdits: {'^aspectmode': 'manual'} }, + editType: 'plot', + impliedEdits: {aspectmode: 'manual'}, description: [ 'Sets this scene\'s axis aspectratio.' ].join(' ') @@ -146,6 +171,7 @@ module.exports = { role: 'info', values: ['orbit', 'turntable', 'zoom', 'pan', false], dflt: 'turntable', + editType: 'plot', description: [ 'Determines the mode of drag interactions for this scene.' ].join(' ') @@ -155,15 +181,18 @@ module.exports = { role: 'info', values: ['closest', false], dflt: 'closest', + editType: 'modebar', description: [ 'Determines the mode of hover interactions for this scene.' ].join(' ') }, + editType: 'plot', _deprecated: { cameraposition: { valType: 'info_array', role: 'info', + editType: 'camera', description: 'Obsolete. Use `camera` instead.' } } diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 4043ac267f9..aebb30c6a47 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -8,44 +8,43 @@ 'use strict'; -var Lib = require('../lib'); -var extendFlat = Lib.extendFlat; - var fontAttrs = require('./font_attributes'); var colorAttrs = require('../components/color/attributes'); +var globalFont = fontAttrs({ + editType: 'calc', + description: [ + 'Sets the global font.', + 'Note that fonts used in traces and other', + 'layout components inherit from the global font.' + ].join(' ') +}); +globalFont.family.dflt = '"Open Sans", verdana, arial, sans-serif'; +globalFont.size.dflt = 12; +globalFont.color.dflt = colorAttrs.defaultLine; + 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(' ') - }, + font: globalFont, title: { valType: 'string', role: 'info', dflt: 'Click to enter Plot title', + editType: 'layoutstyle', description: [ 'Sets the plot\'s title.' ].join(' ') }, - titlefont: extendFlat({}, fontAttrs, { + titlefont: fontAttrs({ + editType: 'layoutstyle', description: 'Sets the title font.' }), autosize: { valType: 'boolean', role: 'info', dflt: false, + // autosize, width, and height get special editType treatment in _relayout + // so we can handle noop resizes more efficiently + editType: 'none', description: [ 'Determines whether or not a layout width or height', 'that has been left undefined by the user', @@ -61,6 +60,7 @@ module.exports = { role: 'info', min: 10, dflt: 700, + editType: 'none', description: [ 'Sets the plot\'s width (in px).' ].join(' ') @@ -70,6 +70,7 @@ module.exports = { role: 'info', min: 10, dflt: 450, + editType: 'none', description: [ 'Sets the plot\'s height (in px).' ].join(' ') @@ -80,6 +81,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, + editType: 'calc', description: 'Sets the left margin (in px).' }, r: { @@ -87,6 +89,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, + editType: 'calc', description: 'Sets the right margin (in px).' }, t: { @@ -94,6 +97,7 @@ module.exports = { role: 'info', min: 0, dflt: 100, + editType: 'calc', description: 'Sets the top margin (in px).' }, b: { @@ -101,6 +105,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, + editType: 'calc', description: 'Sets the bottom margin (in px).' }, pad: { @@ -108,6 +113,7 @@ module.exports = { role: 'info', min: 0, dflt: 0, + editType: 'calc', description: [ 'Sets the amount of padding (in px)', 'between the plotting area and the axis lines' @@ -116,13 +122,16 @@ module.exports = { autoexpand: { valType: 'boolean', role: 'info', - dflt: true - } + dflt: true, + editType: 'calc' + }, + editType: 'calc' }, paper_bgcolor: { valType: 'color', role: 'style', dflt: colorAttrs.background, + editType: 'plot', description: 'Sets the color of paper where the graph is drawn.' }, plot_bgcolor: { @@ -131,6 +140,7 @@ module.exports = { valType: 'color', role: 'style', dflt: colorAttrs.background, + editType: 'layoutstyle', description: [ 'Sets the color of plotting area in-between x and y axes.' ].join(' ') @@ -139,6 +149,7 @@ module.exports = { valType: 'string', role: 'style', dflt: '.,', + editType: 'plot', description: [ 'Sets the decimal and thousand separators.', 'For example, *. * puts a \'.\' before decimals and', @@ -149,6 +160,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: false, + editType: 'plot', description: [ 'Determines whether or not a text link citing the data source is', 'placed at the bottom-right cored of the figure.', @@ -161,13 +173,15 @@ module.exports = { valType: 'enumerated', role: 'info', values: [false], - dflt: false + dflt: false, + editType: 'none' }, showlegend: { // handled in legend.supplyLayoutDefaults // but included here because it's not in the legend object valType: 'boolean', role: 'info', + editType: 'legend', description: 'Determines whether or not a legend is drawn.' } }; diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index 8eb77e1f520..a63393fe6dd 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -18,22 +18,23 @@ var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); var createMapbox = require('./mapbox'); var constants = require('./constants'); +var MAPBOX = 'mapbox'; -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.attrRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/; +exports.idRegex = exports.attrRegex = Lib.counterRegex(MAPBOX); exports.attributes = { subplot: { valType: 'subplotid', role: 'info', dflt: 'mapbox', + editType: 'calc', description: [ 'Sets a reference between this trace\'s data coordinates and', 'a mapbox subplot.', @@ -50,14 +51,14 @@ exports.supplyLayoutDefaults = require('./layout_defaults'); exports.plot = function plotMapbox(gd) { var fullLayout = gd._fullLayout, calcData = gd.calcdata, - mapboxIds = Plots.getSubplotIds(fullLayout, 'mapbox'); + mapboxIds = Plots.getSubplotIds(fullLayout, MAPBOX); 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), + subplotCalcData = Plots.getSubplotCalcData(calcData, MAPBOX, id), opts = fullLayout[id], mapbox = opts._subplot; @@ -90,7 +91,7 @@ exports.plot = function plotMapbox(gd) { }; exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldMapboxKeys = Plots.getSubplotIds(oldFullLayout, 'mapbox'); + var oldMapboxKeys = Plots.getSubplotIds(oldFullLayout, MAPBOX); for(var i = 0; i < oldMapboxKeys.length; i++) { var oldMapboxKey = oldMapboxKeys[i]; @@ -103,7 +104,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) exports.toSVG = function(gd) { var fullLayout = gd._fullLayout, - subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'), + subplotIds = Plots.getSubplotIds(fullLayout, MAPBOX), size = fullLayout._size; for(var i = 0; i < subplotIds.length; i++) { diff --git a/src/plots/mapbox/layout_attributes.js b/src/plots/mapbox/layout_attributes.js index 0578eaa0029..6d43c8c40be 100644 --- a/src/plots/mapbox/layout_attributes.js +++ b/src/plots/mapbox/layout_attributes.js @@ -13,10 +13,19 @@ var Lib = require('../../lib'); var defaultLine = require('../../components/color').defaultLine; var fontAttrs = require('../font_attributes'); var textposition = require('../../traces/scatter/attributes').textposition; +var overrideAll = require('../../plot_api/edit_types').overrideAll; -module.exports = { - _arrayAttrRegexps: [/^mapbox([2-9]|[1-9][0-9]+)?\.layers/], +var fontAttr = fontAttrs({ + description: [ + 'Sets the icon text font.', + 'Has an effect only when `type` is set to *symbol*.' + ].join(' ') +}); +fontAttr.family.dflt = 'Open Sans Regular, Arial Unicode MS Regular'; + +module.exports = overrideAll({ + _arrayAttrRegexps: [Lib.counterRegex('mapbox', '.layers', true)], domain: { x: { valType: 'info_array', @@ -250,17 +259,8 @@ module.exports = { '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' - } - }), + textfont: fontAttr, textposition: Lib.extendFlat({}, textposition, { arrayOk: false }) } } - -}; +}, 'plot', 'from-root'); diff --git a/src/plots/pad_attributes.js b/src/plots/pad_attributes.js index f5c92700b5d..4ef41cdf790 100644 --- a/src/plots/pad_attributes.js +++ b/src/plots/pad_attributes.js @@ -8,29 +8,37 @@ 'use strict'; +// This is used exclusively by components inside component arrays, +// hence the 'arraydraw' editType. If this ever gets used elsewhere +// we could generalize it as a function ala font_attributes module.exports = { t: { valType: 'number', dflt: 0, role: 'style', + editType: 'arraydraw', description: 'The amount of padding (in px) along the top of the component.' }, r: { valType: 'number', dflt: 0, role: 'style', + editType: 'arraydraw', description: 'The amount of padding (in px) on the right side of the component.' }, b: { valType: 'number', dflt: 0, role: 'style', + editType: 'arraydraw', description: 'The amount of padding (in px) along the bottom of the component.' }, l: { valType: 'number', dflt: 0, role: 'style', + editType: 'arraydraw', description: 'The amount of padding (in px) on the left side of the component.' - } + }, + editType: 'arraydraw' }; diff --git a/src/plots/plots.js b/src/plots/plots.js index e8ff7e50bc8..64bc8952617 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -103,14 +103,14 @@ plots.getSubplotIds = function getSubplotIds(layout, type) { return Object.keys(layout._plots || {}); } - var idRegex = _module.idRegex, + var attrRegex = _module.attrRegex, layoutKeys = Object.keys(layout), subplotIds = []; for(var i = 0; i < layoutKeys.length; i++) { var layoutKey = layoutKeys[i]; - if(idRegex.test(layoutKey)) subplotIds.push(layoutKey); + if(attrRegex.test(layoutKey)) subplotIds.push(layoutKey); } // order the ids diff --git a/src/plots/polar/area_attributes.js b/src/plots/polar/area_attributes.js index 4ae8b7edae9..1ce99e2979a 100644 --- a/src/plots/polar/area_attributes.js +++ b/src/plots/polar/area_attributes.js @@ -18,6 +18,7 @@ module.exports = { color: scatterMarkerAttrs.color, size: scatterMarkerAttrs.size, symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity + opacity: scatterMarkerAttrs.opacity, + editType: 'calc' } }; diff --git a/src/plots/polar/axis_attributes.js b/src/plots/polar/axis_attributes.js index 681763d90b6..56ecd483f5d 100644 --- a/src/plots/polar/axis_attributes.js +++ b/src/plots/polar/axis_attributes.js @@ -11,6 +11,7 @@ var axesAttrs = require('../cartesian/layout_attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var domainAttr = extendFlat({}, axesAttrs.domain, { description: [ @@ -86,7 +87,7 @@ function mergeAttrs(axisName, nonCommonAttrs) { return extendFlat({}, nonCommonAttrs, commonAttrs); } -module.exports = { +module.exports = overrideAll({ radialaxis: mergeAttrs('radial', { range: { valType: 'info_array', @@ -145,4 +146,4 @@ module.exports = { ].join(' ') } } -}; +}, 'plot', 'nested'); diff --git a/src/plots/ternary/index.js b/src/plots/ternary/index.js index e59f2f6e1a4..a4227ecbc01 100644 --- a/src/plots/ternary/index.js +++ b/src/plots/ternary/index.js @@ -12,17 +12,16 @@ var Ternary = require('./ternary'); var Plots = require('../../plots/plots'); +var counterRegex = require('../../lib').counterRegex; +var TERNARY = 'ternary'; - -exports.name = 'ternary'; +exports.name = TERNARY; exports.attr = 'subplot'; -exports.idRoot = 'ternary'; - -exports.idRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; +exports.idRoot = TERNARY; -exports.attrRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; +exports.idRegex = exports.attrRegex = counterRegex(TERNARY); exports.attributes = require('./layout/attributes'); @@ -33,11 +32,11 @@ exports.supplyLayoutDefaults = require('./layout/defaults'); exports.plot = function plotTernary(gd) { var fullLayout = gd._fullLayout, calcData = gd.calcdata, - ternaryIds = Plots.getSubplotIds(fullLayout, 'ternary'); + ternaryIds = Plots.getSubplotIds(fullLayout, TERNARY); for(var i = 0; i < ternaryIds.length; i++) { var ternaryId = ternaryIds[i], - ternaryCalcData = Plots.getSubplotCalcData(calcData, 'ternary', ternaryId), + ternaryCalcData = Plots.getSubplotCalcData(calcData, TERNARY, ternaryId), ternary = fullLayout[ternaryId]._subplot; // If ternary is not instantiated, create one! @@ -58,7 +57,7 @@ exports.plot = function plotTernary(gd) { }; exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { - var oldTernaryKeys = Plots.getSubplotIds(oldFullLayout, 'ternary'); + var oldTernaryKeys = Plots.getSubplotIds(oldFullLayout, TERNARY); for(var i = 0; i < oldTernaryKeys.length; i++) { var oldTernaryKey = oldTernaryKeys[i]; diff --git a/src/plots/ternary/layout/attributes.js b/src/plots/ternary/layout/attributes.js index 0a95e1deb33..6615f6d4f29 100644 --- a/src/plots/ternary/layout/attributes.js +++ b/src/plots/ternary/layout/attributes.js @@ -14,6 +14,7 @@ module.exports = { valType: 'subplotid', role: 'info', dflt: 'ternary', + editType: 'calc', description: [ 'Sets a reference between this trace\'s data coordinates and', 'a ternary subplot.', diff --git a/src/plots/ternary/layout/layout_attributes.js b/src/plots/ternary/layout/layout_attributes.js index 4ac0400e472..6c2e3149738 100644 --- a/src/plots/ternary/layout/layout_attributes.js +++ b/src/plots/ternary/layout/layout_attributes.js @@ -10,9 +10,10 @@ var colorAttrs = require('../../../components/color/attributes'); var ternaryAxesAttrs = require('./axis_attributes'); +var overrideAll = require('../../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ domain: { x: { valType: 'info_array', @@ -60,4 +61,4 @@ module.exports = { aaxis: ternaryAxesAttrs, baxis: ternaryAxesAttrs, caxis: ternaryAxesAttrs -}; +}, 'plot', 'from-root'); diff --git a/src/registry.js b/src/registry.js index 163bdda74b8..70b6a2015c6 100644 --- a/src/registry.js +++ b/src/registry.js @@ -12,7 +12,12 @@ var Loggers = require('./lib/loggers'); var noop = require('./lib/noop'); var pushUnique = require('./lib/push_unique'); +var ExtendModule = require('./lib/extend'); +var extendFlat = ExtendModule.extendFlat; +var extendDeepAll = ExtendModule.extendDeepAll; + var basePlotAttributes = require('./plots/attributes'); +var baseLayoutAttributes = require('./plots/layout_attributes'); exports.modules = {}; exports.allCategories = {}; @@ -22,6 +27,7 @@ exports.transformsRegistry = {}; exports.componentsRegistry = {}; exports.layoutArrayContainers = []; exports.layoutArrayRegexes = []; +exports.traceLayoutAttributes = {}; /** * register a module as the handler for a trace type @@ -54,6 +60,19 @@ exports.register = function(_module, thisType, categoriesIn, meta) { } exports.allTypes.push(thisType); + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToTrace(componentName, thisType); + } + + /* + * Collect all trace layout attributes in one place for easier lookup later + * but don't merge them into the base schema as it would confuse the docs + * (at least after https://github.com/plotly/documentation/issues/202 gets done!) + */ + if(_module.layoutAttributes) { + extendFlat(exports.traceLayoutAttributes, _module.layoutAttributes); + } }; /** @@ -90,6 +109,10 @@ exports.registerSubplot = function(_module) { // not sure what's best for the 'cartesian' type at this point exports.subplotsRegistry[plotType] = _module; + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToSubplot(componentName, _module.name); + } }; exports.registerComponent = function(_module) { @@ -103,6 +126,30 @@ exports.registerComponent = function(_module) { } findArrayRegexps(_module); } + + for(var traceType in exports.modules) { + mergeComponentAttrsToTrace(name, traceType); + } + + for(var subplotName in exports.subplotsRegistry) { + mergeComponentAttrsToSubplot(name, subplotName); + } + + for(var transformType in exports.transformsRegistry) { + mergeComponentAttrsToTransform(name, transformType); + } + + if(_module.schema && _module.schema.layout) { + extendDeepAll(baseLayoutAttributes, _module.schema.layout); + } +}; + +exports.registerTransform = function(_module) { + exports.transformsRegistry[_module.name] = _module; + + for(var componentName in exports.componentsRegistry) { + mergeComponentAttrsToTransform(componentName, _module.name); + } }; function findArrayRegexps(_module) { @@ -116,6 +163,41 @@ function findArrayRegexps(_module) { } } +function mergeComponentAttrsToTrace(componentName, traceType) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.traces) return; + + var traceAttrs = componentSchema.traces[traceType]; + if(traceAttrs) { + extendDeepAll(exports.modules[traceType]._module.attributes, traceAttrs); + } +} + +function mergeComponentAttrsToTransform(componentName, transformType) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.transforms) return; + + var transformAttrs = componentSchema.transforms[transformType]; + if(transformAttrs) { + extendDeepAll(exports.transformsRegistry[transformType].attributes, transformAttrs); + } +} + +function mergeComponentAttrsToSubplot(componentName, subplotName) { + var componentSchema = exports.componentsRegistry[componentName].schema; + if(!componentSchema || !componentSchema.subplots) return; + + var subplotModule = exports.subplotsRegistry[subplotName]; + var subplotAttrs = subplotModule.layoutAttributes; + var subplotAttr = subplotModule.attr === 'subplot' ? subplotModule.name : subplotModule.attr; + if(Array.isArray(subplotAttr)) subplotAttr = subplotAttr[0]; + + var componentLayoutAttrs = componentSchema.subplots[subplotAttr]; + if(subplotAttrs && componentLayoutAttrs) { + extendDeepAll(subplotAttrs, componentLayoutAttrs); + } +} + /** * Get registered module using trace object or trace type * diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 3a56661ec07..fe07126e117 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -15,12 +15,12 @@ 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 textFontAttrs = extendDeep({}, fontAttrs); -textFontAttrs.family.arrayOk = true; -textFontAttrs.size.arrayOk = true; -textFontAttrs.color.arrayOk = true; +var textFontAttrs = fontAttrs({ + editType: 'calc', + arrayOk: true, + description: '' +}); var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; @@ -28,12 +28,14 @@ var scatterMarkerLineAttrs = scatterMarkerAttrs.line; var markerLineWidth = extendFlat({}, scatterMarkerLineAttrs.width, { dflt: 0 }); -var markerLine = extendFlat({}, { - width: markerLineWidth +var markerLine = extendFlat({ + width: markerLineWidth, + editType: 'calc' }, colorAttributes('marker.line')); -var marker = extendFlat({}, { - line: markerLine +var marker = extendFlat({ + line: markerLine, + editType: 'calc' }, colorAttributes('marker'), { showscale: scatterMarkerAttrs.showscale, colorbar: colorbarAttrs @@ -57,6 +59,7 @@ module.exports = { values: ['inside', 'outside', 'auto', 'none'], dflt: 'none', arrayOk: true, + editType: 'calc', description: [ 'Specifies the location of the `text`.', '*inside* positions `text` inside, next to the bar end', @@ -85,6 +88,7 @@ module.exports = { values: ['inside', 'outside', 'both', 'none'], role: 'info', dflt: 'both', + editType: 'calc', description: [ 'Constrain the size of text inside or outside a bar to be no', 'larger than the bar itself.' @@ -95,6 +99,7 @@ module.exports = { valType: 'enumerated', role: 'info', values: ['v', 'h'], + editType: 'calc+clearAxisTypes', description: [ 'Sets the orientation of the bars.', 'With *v* (*h*), the value of the each bar spans', @@ -107,6 +112,7 @@ module.exports = { dflt: null, arrayOk: true, role: 'info', + editType: 'calc', description: [ 'Sets where the bar base is drawn (in position axis units).', 'In *stack* or *relative* barmode,', @@ -120,6 +126,7 @@ module.exports = { dflt: null, arrayOk: true, role: 'info', + editType: 'calc', description: [ 'Shifts the position where the bar is drawn', '(in position axis units).', @@ -135,6 +142,7 @@ module.exports = { min: 0, arrayOk: true, role: 'info', + editType: 'calc', description: [ 'Sets the bar width (in position axis units).' ].join(' ') @@ -152,6 +160,7 @@ module.exports = { bardir: { valType: 'enumerated', role: 'info', + editType: 'calc', values: ['v', 'h'], description: 'Renamed to `orientation`.' } diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index 5dfb7c78191..205dc9dab5b 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -15,6 +15,7 @@ module.exports = { values: ['stack', 'group', 'overlay', 'relative'], dflt: 'group', role: 'info', + editType: 'calc', description: [ 'Determines how bars at the same location coordinate', 'are displayed on the graph.', @@ -32,6 +33,7 @@ module.exports = { values: ['', 'fraction', 'percent'], dflt: '', role: 'info', + editType: 'calc', description: [ 'Sets the normalization for bar traces on the graph.', 'With *fraction*, the value of each bar is divide by the sum of the', @@ -44,6 +46,7 @@ module.exports = { min: 0, max: 1, role: 'style', + editType: 'calc', description: [ 'Sets the gap (in plot fraction) between bars of', 'adjacent location coordinates.' @@ -55,6 +58,7 @@ module.exports = { max: 1, dflt: 0, role: 'style', + editType: 'calc', description: [ 'Sets the gap (in plot fraction) between bars of', 'the same location coordinate.' diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js index f1308538480..d029069cb2a 100644 --- a/src/traces/box/attributes.js +++ b/src/traces/box/attributes.js @@ -19,6 +19,7 @@ var scatterMarkerAttrs = scatterAttrs.marker, module.exports = { y: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'Sets the y sample data or coordinates.', 'See overview for more info.' @@ -26,6 +27,7 @@ module.exports = { }, x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'Sets the x sample data or coordinates.', 'See overview for more info.' @@ -34,6 +36,7 @@ module.exports = { x0: { valType: 'any', role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'Sets the x coordinate of the box.', 'See overview for more info.' @@ -42,19 +45,31 @@ module.exports = { y0: { valType: 'any', role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'Sets the y coordinate of the box.', 'See overview for more info.' ].join(' ') }, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, + name: { + valType: 'string', + role: 'info', + editType: 'calc+clearAxisTypes', + description: [ + 'Sets the trace name.', + 'The trace name appear as the legend item and on hover.', + 'For box traces, the name will also be used for the position', + 'coordinate, if `x` and `x0` (`y` and `y0` if horizontal) are', + 'missing and the position axis is categorical' + ].join(' ') + }, whiskerwidth: { valType: 'number', min: 0, max: 1, dflt: 0.5, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the width of the whiskers relative to', 'the box\' width.', @@ -66,6 +81,7 @@ module.exports = { values: ['all', 'outliers', 'suspectedoutliers', false], dflt: 'outliers', role: 'style', + editType: 'calcIfAutorange', description: [ 'If *outliers*, only the sample points lying outside the whiskers', 'are shown', @@ -81,6 +97,7 @@ module.exports = { values: [true, 'sd', false], dflt: false, role: 'style', + editType: 'calcIfAutorange', description: [ 'If *true*, the mean of the box(es)\' underlying distribution is', 'drawn as a dashed line inside the box(es).', @@ -92,6 +109,7 @@ module.exports = { min: 0, max: 1, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the amount of jitter in the sample points drawn.', 'If *0*, the sample points align along the distribution axis.', @@ -104,6 +122,7 @@ module.exports = { min: -2, max: 2, role: 'style', + editType: 'calcIfAutorange', 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).', @@ -115,6 +134,7 @@ module.exports = { valType: 'enumerated', values: ['v', 'h'], role: 'style', + editType: 'calc+clearAxisTypes', description: [ 'Sets the orientation of the box(es).', 'If *v* (*h*), the distribution is visualized along', @@ -126,24 +146,26 @@ module.exports = { valType: 'color', dflt: 'rgba(0, 0, 0, 0)', role: 'style', + editType: 'style', description: 'Sets the color of the outlier sample points.' }, symbol: extendFlat({}, scatterMarkerAttrs.symbol, - {arrayOk: false}), + {arrayOk: false, editType: 'plot'}), opacity: extendFlat({}, scatterMarkerAttrs.opacity, - {arrayOk: false, dflt: 1}), + {arrayOk: false, dflt: 1, editType: 'style'}), size: extendFlat({}, scatterMarkerAttrs.size, - {arrayOk: false}), + {arrayOk: false, editType: 'calcIfAutorange'}), color: extendFlat({}, scatterMarkerAttrs.color, - {arrayOk: false}), + {arrayOk: false, editType: 'style'}), line: { color: extendFlat({}, scatterMarkerLineAttrs.color, - {arrayOk: false, dflt: colorAttrs.defaultLine}), + {arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'}), width: extendFlat({}, scatterMarkerLineAttrs.width, - {arrayOk: false, dflt: 0}), + {arrayOk: false, dflt: 0, editType: 'style'}), outliercolor: { valType: 'color', role: 'style', + editType: 'style', description: [ 'Sets the border line color of the outlier sample points.', 'Defaults to marker.color' @@ -154,16 +176,20 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'style', description: [ 'Sets the border line width (in px) of the outlier sample points.' ].join(' ') - } - } + }, + editType: 'style' + }, + editType: 'plot' }, line: { color: { valType: 'color', role: 'style', + editType: 'style', description: 'Sets the color of line bounding the box(es).' }, width: { @@ -171,8 +197,10 @@ module.exports = { role: 'style', min: 0, dflt: 2, + editType: 'style', description: 'Sets the width (in px) of line bounding the box(es).' - } + }, + editType: 'plot' }, fillcolor: scatterAttrs.fillcolor }; diff --git a/src/traces/box/layout_attributes.js b/src/traces/box/layout_attributes.js index 7e2d9f0fc75..40a2a565e89 100644 --- a/src/traces/box/layout_attributes.js +++ b/src/traces/box/layout_attributes.js @@ -15,6 +15,7 @@ module.exports = { values: ['group', 'overlay'], dflt: 'overlay', role: 'info', + editType: 'calc', description: [ 'Determines how boxes at the same location coordinate', 'are displayed on the graph.', @@ -30,6 +31,7 @@ module.exports = { max: 1, dflt: 0.3, role: 'style', + editType: 'calc', description: [ 'Sets the gap (in plot fraction) between boxes of', 'adjacent location coordinates.' @@ -41,6 +43,7 @@ module.exports = { max: 1, dflt: 0.3, role: 'style', + editType: 'calc', description: [ 'Sets the gap (in plot fraction) between boxes of', 'the same location coordinate.' diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index c6c4e18ac3e..27b4aa76885 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -9,21 +9,25 @@ 'use strict'; -var Lib = require('../../lib'); +var extendFlat = require('../../lib').extendFlat; var OHLCattrs = require('../ohlc/attributes'); var boxAttrs = require('../box/attributes'); -var directionAttrs = { - name: OHLCattrs.increasing.name, - showlegend: OHLCattrs.increasing.showlegend, +function directionAttrs(lineColorDefault) { + return { + name: OHLCattrs.increasing.name, + showlegend: OHLCattrs.increasing.showlegend, - line: { - color: Lib.extendFlat({}, boxAttrs.line.color), - width: Lib.extendFlat({}, boxAttrs.line.width) - }, + line: { + color: extendFlat({}, boxAttrs.line.color, {dflt: lineColorDefault}), + width: boxAttrs.line.width, + editType: 'style' + }, - fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), -}; + fillcolor: boxAttrs.fillcolor, + editType: 'style' + }; +} module.exports = { x: OHLCattrs.x, @@ -33,24 +37,21 @@ module.exports = { close: OHLCattrs.close, line: { - width: Lib.extendFlat({}, boxAttrs.line.width, { + width: 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(' ') - }) + }), + editType: 'style' }, - increasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: OHLCattrs.increasing.line.color.dflt } } - }), + increasing: directionAttrs(OHLCattrs.increasing.line.color.dflt), - decreasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: OHLCattrs.decreasing.line.color.dflt } } - }), + decreasing: directionAttrs(OHLCattrs.decreasing.line.color.dflt), text: OHLCattrs.text, - whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }) + whiskerwidth: extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }) }; diff --git a/src/traces/carpet/attributes.js b/src/traces/carpet/attributes.js index 8774f610ae5..095591938f4 100644 --- a/src/traces/carpet/attributes.js +++ b/src/traces/carpet/attributes.js @@ -8,15 +8,24 @@ 'use strict'; -var extendFlat = require('../../lib/extend').extendFlat; var fontAttrs = require('../../plots/font_attributes'); var axisAttrs = require('./axis_attributes'); var colorAttrs = require('../../components/color/attributes'); +var carpetFont = fontAttrs({ + editType: 'calc', + description: 'The default font used for axis & tick labels on this carpet' +}); +// TODO: inherit from global font +carpetFont.family.dflt = '"Open Sans", verdana, arial, sans-serif'; +carpetFont.size.dflt = 12; +carpetFont.color.dflt = colorAttrs.defaultLine; + module.exports = { carpet: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'An identifier for this carpet, so that `scattercarpet` and', '`scattercontour` traces can specify a carpet plot on which', @@ -25,6 +34,7 @@ module.exports = { }, x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'A two dimensional array of x coordinates at each carpet point.', 'If ommitted, the plot is a cheater plot and the xaxis is hidden', @@ -33,10 +43,12 @@ module.exports = { }, y: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: 'A two dimensional array of y coordinates at each carpet point.' }, a: { valType: 'data_array', + editType: 'calc', description: [ 'An array containing values of the first parameter value' ].join(' ') @@ -45,6 +57,7 @@ module.exports = { valType: 'number', dflt: 0, role: 'info', + editType: 'calc', description: [ 'Alternate to `a`.', 'Builds a linear space of a coordinates.', @@ -56,6 +69,7 @@ module.exports = { valType: 'number', dflt: 1, role: 'info', + editType: 'calc', description: [ 'Sets the a coordinate step.', 'See `a0` for more info.' @@ -63,12 +77,14 @@ module.exports = { }, b: { valType: 'data_array', + editType: 'calc', description: 'A two dimensional array of y coordinates at each carpet point.' }, b0: { valType: 'number', dflt: 0, role: 'info', + editType: 'calc', description: [ 'Alternate to `b`.', 'Builds a linear space of a coordinates.', @@ -80,6 +96,7 @@ module.exports = { valType: 'number', dflt: 1, role: 'info', + editType: 'calc', description: [ 'Sets the b coordinate step.', 'See `b0` for more info.' @@ -89,28 +106,20 @@ module.exports = { valType: 'number', role: 'info', dflt: 1, + editType: 'calc', description: [ 'The shift applied to each successive row of data in creating a cheater plot.', 'Only used if `x` is been ommitted.' ].join(' ') }, - aaxis: extendFlat({}, axisAttrs), - baxis: extendFlat({}, axisAttrs), - 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 - }), - }, + aaxis: axisAttrs, + baxis: axisAttrs, + font: carpetFont, color: { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'plot', description: [ 'Sets default for all colors associated with this axis', 'all at once: line, font, tick, and grid colors.', diff --git a/src/traces/carpet/axis_attributes.js b/src/traces/carpet/axis_attributes.js index cd689e2772f..75f4f37132b 100644 --- a/src/traces/carpet/axis_attributes.js +++ b/src/traces/carpet/axis_attributes.js @@ -8,7 +8,6 @@ 'use strict'; -var extendFlat = require('../../lib/extend').extendFlat; var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../../components/color/attributes'); @@ -16,6 +15,7 @@ module.exports = { color: { valType: 'color', role: 'style', + editType: 'calc', description: [ 'Sets default for all colors associated with this axis', 'all at once: line, font, tick, and grid colors.', @@ -28,14 +28,17 @@ module.exports = { dflt: 1, min: 0, max: 1.3, - role: 'info' + role: 'info', + editType: 'calc' }, title: { valType: 'string', role: 'info', + editType: 'calc', description: 'Sets the title of this axis.' }, - titlefont: extendFlat({}, fontAttrs, { + titlefont: fontAttrs({ + editType: 'calc', description: [ 'Sets this axis\' title font.' ].join(' ') @@ -44,6 +47,7 @@ module.exports = { valType: 'number', role: 'info', dflt: 10, + editType: 'calc', description: [ 'An additional amount by which to offset the title from the tick', 'labels, given in pixels' @@ -57,6 +61,7 @@ module.exports = { values: ['-', 'linear', 'date', 'category'], dflt: '-', role: 'info', + editType: 'calc', description: [ 'Sets the axis type.', 'By default, plotly attempts to determined the axis type', @@ -69,6 +74,7 @@ module.exports = { values: [true, false, 'reversed'], dflt: true, role: 'style', + editType: 'calc', description: [ 'Determines whether or not the range of this axis is', 'computed in relation to the input data.', @@ -81,6 +87,7 @@ module.exports = { values: ['normal', 'tozero', 'nonnegative'], dflt: 'normal', role: 'style', + editType: 'calc', description: [ 'If *normal*, the range is computed in relation to the extrema', 'of the input data.', @@ -93,9 +100,10 @@ module.exports = { range: { valType: 'info_array', role: 'info', + editType: 'calc', items: [ - {valType: 'any'}, - {valType: 'any'} + {valType: 'any', editType: 'calc'}, + {valType: 'any', editType: 'calc'} ], description: [ 'Sets the range of this axis.', @@ -115,6 +123,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: [ 'Determines whether or not this axis is zoom-able.', 'If true, then zoom is disabled.' @@ -124,19 +133,22 @@ module.exports = { valType: 'enumerated', values: ['index', 'value'], dflt: 'value', - role: 'info' + role: 'info', + editType: 'calc' }, tickmode: { valType: 'enumerated', values: ['linear', 'array'], dflt: 'array', role: 'info', + editType: 'calc' }, nticks: { valType: 'integer', min: 0, dflt: 0, role: 'style', + editType: 'calc', description: [ 'Specifies the maximum number of ticks for the particular axis.', 'The actual number of ticks will be chosen automatically to be', @@ -146,6 +158,7 @@ module.exports = { }, tickvals: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the values at which ticks on this axis appear.', 'Only has an effect if `tickmode` is set to *array*.', @@ -154,6 +167,7 @@ module.exports = { }, ticktext: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the text displayed at the ticks position via `tickvals`.', 'Only has an effect if `tickmode` is set to *array*.', @@ -165,18 +179,21 @@ module.exports = { values: ['start', 'end', 'both', 'none'], dflt: 'start', role: 'style', + editType: 'calc', description: [ 'Determines whether axis labels are drawn on the low side,', 'the high side, both, or neither side of the axis.' ].join(' ') }, - tickfont: extendFlat({}, fontAttrs, { + tickfont: fontAttrs({ + editType: 'calc', description: 'Sets the tick font.' }), tickangle: { valType: 'angle', dflt: 'auto', role: 'style', + editType: 'calc', description: [ 'Sets the angle of the tick labels with respect to the horizontal.', 'For example, a `tickangle` of -90 draws the tick labels', @@ -187,6 +204,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'calc', description: 'Sets a tick label prefix.' }, showtickprefix: { @@ -194,6 +212,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'calc', description: [ 'If *all*, all tick labels are displayed with a prefix.', 'If *first*, only the first tick is displayed with a prefix.', @@ -205,6 +224,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'calc', description: 'Sets a tick label suffix.' }, showticksuffix: { @@ -212,6 +232,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'calc', description: 'Same as `showtickprefix` but for tick suffixes.' }, showexponent: { @@ -219,6 +240,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', + editType: 'calc', description: [ 'If *all*, all exponents are shown besides their significands.', 'If *first*, only the exponent of the first tick is shown.', @@ -231,6 +253,7 @@ module.exports = { values: ['none', 'e', 'E', 'power', 'SI', 'B'], dflt: 'B', role: 'style', + editType: 'calc', description: [ 'Determines a formatting rule for the tick exponents.', 'For example, consider the number 1,000,000,000.', @@ -246,6 +269,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'style', + editType: 'calc', description: [ 'If "true", even 4-digit integers are separated' ].join(' ') @@ -254,6 +278,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', + editType: 'calc', description: [ 'Sets the tick label formatting rule using d3 formatting mini-languages', 'which are very similar to those in Python. For numbers, see:', @@ -273,6 +298,7 @@ module.exports = { ], dflt: 'trace', role: 'info', + editType: 'calc', 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.', @@ -288,6 +314,7 @@ module.exports = { categoryarray: { valType: 'data_array', role: 'info', + editType: 'calc', description: [ 'Sets the order in which categories on this axis appear.', 'Only has an effect if `categoryorder` is set to *array*.', @@ -298,17 +325,20 @@ module.exports = { valType: 'integer', role: 'style', dflt: 10, + editType: 'calc', description: 'Extra padding between label and the axis' }, labelprefix: { valType: 'string', role: 'style', + editType: 'calc', description: 'Sets a axis label prefix.' }, labelsuffix: { valType: 'string', dflt: '', role: 'style', + editType: 'calc', description: 'Sets a axis label suffix.' }, // lines and grids @@ -316,6 +346,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'style', + editType: 'calc', description: [ 'Determines whether or not a line bounding this axis is drawn.' ].join(' ') @@ -324,6 +355,7 @@ module.exports = { valType: 'color', dflt: colorAttrs.defaultLine, role: 'style', + editType: 'calc', description: 'Sets the axis line color.' }, linewidth: { @@ -331,11 +363,13 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'calc', description: 'Sets the width (in px) of the axis line.' }, gridcolor: { valType: 'color', role: 'style', + editType: 'calc', description: 'Sets the axis line color.' }, gridwidth: { @@ -343,12 +377,14 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'calc', description: 'Sets the width (in px) of the axis line.' }, showgrid: { valType: 'boolean', role: 'style', dflt: true, + editType: 'calc', description: [ 'Determines whether or not grid lines are drawn.', 'If *true*, the grid lines are drawn at every tick mark.' @@ -359,6 +395,7 @@ module.exports = { min: 0, dflt: 0, role: 'info', + editType: 'calc', description: 'Sets the number of minor grid ticks per major grid tick' }, minorgridwidth: { @@ -366,17 +403,20 @@ module.exports = { min: 0, dflt: 1, role: 'style', + editType: 'calc', description: 'Sets the width (in px) of the grid lines.' }, minorgridcolor: { valType: 'color', dflt: colorAttrs.lightLine, role: 'style', + editType: 'calc', description: 'Sets the color of the grid lines.' }, startline: { valType: 'boolean', role: 'style', + editType: 'calc', description: [ 'Determines whether or not a line is drawn at along the starting value', 'of this axis.', @@ -386,17 +426,20 @@ module.exports = { startlinecolor: { valType: 'color', role: 'style', + editType: 'calc', description: 'Sets the line color of the start line.' }, startlinewidth: { valType: 'number', dflt: 1, role: 'style', + editType: 'calc', description: 'Sets the width (in px) of the start line.' }, endline: { valType: 'boolean', role: 'style', + editType: 'calc', description: [ 'Determines whether or not a line is drawn at along the final value', 'of this axis.', @@ -407,11 +450,13 @@ module.exports = { valType: 'number', dflt: 1, role: 'style', + editType: 'calc', description: 'Sets the width (in px) of the end line.' }, endlinecolor: { valType: 'color', role: 'style', + editType: 'calc', description: 'Sets the line color of the end line.' }, tick0: { @@ -419,6 +464,7 @@ module.exports = { min: 0, dflt: 0, role: 'info', + editType: 'calc', description: 'The starting index of grid lines along the axis' }, dtick: { @@ -426,6 +472,7 @@ module.exports = { min: 0, dflt: 1, role: 'info', + editType: 'calc', description: 'The stride between grid lines along the axis' }, arraytick0: { @@ -433,6 +480,7 @@ module.exports = { min: 0, dflt: 0, role: 'info', + editType: 'calc', description: 'The starting index of grid lines along the axis' }, arraydtick: { @@ -440,6 +488,8 @@ module.exports = { min: 1, dflt: 1, role: 'info', + editType: 'calc', description: 'The stride between grid lines along the axis' }, + editType: 'calc' }; diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 85523db3ed5..8c29ff7c384 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -13,13 +13,16 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var plotAttrs = require('../../plots/attributes'); -var extendFlat = require('../../lib/extend').extendFlat; +var extend = require('../../lib/extend'); +var extendFlat = extend.extendFlat; +var extendDeepAll = extend.extendDeepAll; var ScatterGeoMarkerLineAttrs = ScatterGeoAttrs.marker.line; -module.exports = extendFlat({}, { +module.exports = extendFlat({ locations: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the coordinates via location IDs or names.', 'See `locationmode` for more info.' @@ -28,22 +31,30 @@ module.exports = extendFlat({}, { locationmode: ScatterGeoAttrs.locationmode, z: { valType: 'data_array', + editType: 'calc', description: 'Sets the color values.' }, text: { valType: 'data_array', + editType: 'calc', description: 'Sets the text elements associated with each location.' }, marker: { line: { color: ScatterGeoMarkerLineAttrs.color, - width: extendFlat({}, ScatterGeoMarkerLineAttrs.width, {dflt: 1}) - } + width: extendFlat({}, ScatterGeoMarkerLineAttrs.width, {dflt: 1}), + editType: 'calc' + }, + editType: 'calc' }, hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + editType: 'calc', flags: ['location', 'z', 'text', 'name'] }), }, - colorscaleAttrs, + extendDeepAll({}, colorscaleAttrs, { + zmax: {editType: 'calc'}, + zmin: {editType: 'calc'} + }), { colorbar: colorbarAttrs } ); diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 92e6d2d82e6..5414afe9f48 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -18,7 +18,7 @@ var extendFlat = require('../../lib/extend').extendFlat; var scatterLineAttrs = scatterAttrs.line; -module.exports = extendFlat({}, { +module.exports = extendFlat({ z: heatmapAttrs.z, x: heatmapAttrs.x, x0: heatmapAttrs.x0, @@ -37,7 +37,12 @@ module.exports = extendFlat({}, { valType: 'boolean', dflt: true, role: 'style', - editType: 'docalc', + editType: 'calc', + impliedEdits: { + 'contours.start': undefined, + 'contours.end': undefined, + 'contours.size': undefined + }, description: [ 'Determines whether or not the contour level attributes are', 'picked by an algorithm.', @@ -50,7 +55,7 @@ module.exports = extendFlat({}, { dflt: 15, min: 1, role: 'style', - editType: 'docalc', + editType: 'calc', description: [ 'Sets the maximum number of contour levels. The actual number', 'of contours will be chosen automatically to be less than or', @@ -65,7 +70,8 @@ module.exports = extendFlat({}, { valType: 'number', dflt: null, role: 'style', - editType: 'doplot', + editType: 'plot', + impliedEdits: {'^autocontour': false}, description: [ 'Sets the starting contour level value.', 'Must be less than `contours.end`' @@ -75,7 +81,8 @@ module.exports = extendFlat({}, { valType: 'number', dflt: null, role: 'style', - editType: 'doplot', + editType: 'plot', + impliedEdits: {'^autocontour': false}, description: [ 'Sets the end contour level value.', 'Must be more than `contours.start`' @@ -86,7 +93,8 @@ module.exports = extendFlat({}, { dflt: null, min: 0, role: 'style', - editType: 'doplot', + editType: 'plot', + impliedEdits: {'^autocontour': false}, description: [ 'Sets the step between each contour level.', 'Must be positive.' @@ -97,7 +105,7 @@ module.exports = extendFlat({}, { values: ['fill', 'heatmap', 'lines', 'none'], dflt: 'fill', role: 'style', - editType: 'docalc', + editType: 'calc', description: [ 'Determines the coloring method showing the contour values.', 'If *fill*, coloring is done evenly between each contour level', @@ -111,7 +119,7 @@ module.exports = extendFlat({}, { valType: 'boolean', dflt: true, role: 'style', - editType: 'doplot', + editType: 'plot', description: [ 'Determines whether or not the contour lines are drawn.', 'Has an effect only if `contours.coloring` is set to *fill*.' @@ -121,55 +129,60 @@ module.exports = extendFlat({}, { valType: 'boolean', dflt: false, role: 'style', - editType: 'doplot', + editType: 'plot', description: [ 'Determines whether to label the contour lines with their values.' ].join(' ') }, - labelfont: extendFlat({}, fontAttrs, { + labelfont: fontAttrs({ + editType: 'plot', + colorEditType: 'style', description: [ 'Sets the font used for labeling the contour levels.', 'The default color comes from the lines, if shown.', 'The default family and size come from `layout.font`.' ].join(' '), - family: extendFlat({}, fontAttrs.family, {editType: 'doplot'}), - size: extendFlat({}, fontAttrs.size, {editType: 'doplot'}), - color: extendFlat({}, fontAttrs.color, {editType: 'dostyle'}) }), labelformat: { valType: 'string', dflt: '', role: 'style', - editType: 'doplot', + editType: 'plot', description: [ 'Sets the contour label formatting rule using d3 formatting', 'mini-language which is very similar to Python, see:', 'https://github.com/d3/d3-format/blob/master/README.md#locale_format.' ].join(' ') - } + }, + editType: 'calc', + impliedEdits: {'autocontour': false} }, line: { color: extendFlat({}, scatterLineAttrs.color, { + editType: 'style+colorbars', description: [ 'Sets the color of the contour level.', 'Has no effect if `contours.coloring` is set to *lines*.' ].join(' ') }), - width: scatterLineAttrs.width, + width: extendFlat({}, scatterLineAttrs.width, { + editType: 'style+colorbars' + }), dash: dash, smoothing: extendFlat({}, scatterLineAttrs.smoothing, { description: [ 'Sets the amount of smoothing for the contour lines,', 'where *0* corresponds to no smoothing.' ].join(' ') - }) + }), + editType: 'plot' } }, colorscaleAttrs, { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), - zmin: extendFlat({}, colorscaleAttrs.zmin, {editType: 'docalc'}), - zmax: extendFlat({}, colorscaleAttrs.zmax, {editType: 'docalc'}) + zmin: extendFlat({}, colorscaleAttrs.zmin, {editType: 'calc'}), + zmax: extendFlat({}, colorscaleAttrs.zmax, {editType: 'calc'}) }, { colorbar: colorbarAttrs } ); diff --git a/src/traces/contourcarpet/attributes.js b/src/traces/contourcarpet/attributes.js index 78fd52e099b..939fff55bcc 100644 --- a/src/traces/contourcarpet/attributes.js +++ b/src/traces/contourcarpet/attributes.js @@ -24,6 +24,7 @@ module.exports = extendFlat({}, { carpet: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'The `carpet` of the carpet axes on which this contour trace lies' ].join(' ') @@ -45,6 +46,7 @@ module.exports = extendFlat({}, { flags: ['lines', 'fill'], extras: ['none'], role: 'info', + editType: 'calc', description: ['The mode.'].join(' ') }, @@ -53,6 +55,7 @@ module.exports = extendFlat({}, { fillcolor: { valType: 'color', role: 'style', + editType: 'calc', description: [ 'Sets the fill color.', 'Defaults to a half-transparent variant of the line color,', @@ -69,7 +72,7 @@ module.exports = extendFlat({}, { values: ['levels', 'constraint'], dflt: 'levels', role: 'info', - editType: 'docalc', + editType: 'calc', description: [ 'If `levels`, the data is represented as a contour plot with multiple', 'levels displayed. If `constraint`, the data is represented as constraints', @@ -86,7 +89,7 @@ module.exports = extendFlat({}, { values: ['fill', 'lines', 'none'], dflt: 'fill', role: 'style', - editType: 'docalc', + editType: 'calc', description: [ 'Determines the coloring method showing the contour values.', 'If *fill*, coloring is done evenly between each contour level', @@ -103,7 +106,7 @@ module.exports = extendFlat({}, { values: [].concat(constants.INEQUALITY_OPS).concat(constants.INTERVAL_OPS).concat(constants.SET_OPS), role: 'info', dflt: '=', - editType: 'docalc', + editType: 'calc', description: [ 'Sets the filter operation.', @@ -130,7 +133,7 @@ module.exports = extendFlat({}, { valType: 'any', dflt: 0, role: 'info', - editType: 'docalc', + editType: 'calc', description: [ 'Sets the value or values by which to filter by.', @@ -151,7 +154,8 @@ module.exports = extendFlat({}, { '*value* is expected to be an array with as many items as', 'the desired set elements.' ].join(' ') - } + }, + editType: 'calc' }, line: { @@ -168,7 +172,8 @@ module.exports = extendFlat({}, { 'Sets the amount of smoothing for the contour lines,', 'where *0* corresponds to no smoothing.' ].join(' ') - }) + }), + editType: 'plot' } }, colorscaleAttrs, diff --git a/src/traces/contourgl/index.js b/src/traces/contourgl/index.js index ac4fca3b72d..9dfb61d0de5 100644 --- a/src/traces/contourgl/index.js +++ b/src/traces/contourgl/index.js @@ -9,9 +9,11 @@ 'use strict'; +var overrideAll = require('../../plot_api/edit_types').overrideAll; + var ContourGl = {}; -ContourGl.attributes = require('../contour/attributes'); +ContourGl.attributes = overrideAll(require('../contour/attributes'), 'calc', 'nested'); ContourGl.supplyDefaults = require('../contour/defaults'); ContourGl.colorbar = require('../contour/colorbar'); diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 2b4d98ce245..372197a5f7b 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -17,29 +17,33 @@ var extendFlat = require('../../lib/extend').extendFlat; module.exports = extendFlat({}, { z: { valType: 'data_array', + editType: 'calc', description: 'Sets the z data.' }, - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, + x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}), + x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}), + dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}), + y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}), + y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}), + dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}), text: { valType: 'data_array', + editType: 'calc', description: 'Sets the text elements associated with each z value.' }, transpose: { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: 'Transposes the z data.' }, xtype: { valType: 'enumerated', values: ['array', 'scaled'], role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'If *array*, the heatmap\'s x coordinates are given by *x*', '(the default behavior when `x` is provided).', @@ -51,6 +55,7 @@ module.exports = extendFlat({}, { valType: 'enumerated', values: ['array', 'scaled'], role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'If *array*, the heatmap\'s y coordinates are given by *y*', '(the default behavior when `y` is provided)', @@ -63,6 +68,7 @@ module.exports = extendFlat({}, { values: ['fast', 'best', false], dflt: false, role: 'style', + editType: 'calc', description: [ 'Picks a smoothing algorithm use to smooth `z` data.' ].join(' ') @@ -71,6 +77,7 @@ module.exports = extendFlat({}, { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: [ 'Determines whether or not gaps', '(i.e. {nan} or missing values)', @@ -82,6 +89,7 @@ module.exports = extendFlat({}, { dflt: 0, min: 0, role: 'style', + editType: 'plot', description: 'Sets the horizontal gap (in pixels) between bricks.' }, ygap: { @@ -89,6 +97,7 @@ module.exports = extendFlat({}, { dflt: 0, min: 0, role: 'style', + editType: 'plot', description: 'Sets the vertical gap (in pixels) between bricks.' }, }, diff --git a/src/traces/heatmapgl/attributes.js b/src/traces/heatmapgl/attributes.js index d3dae984714..c5b1d7241d5 100644 --- a/src/traces/heatmapgl/attributes.js +++ b/src/traces/heatmapgl/attributes.js @@ -14,6 +14,8 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; + var commonList = [ 'z', @@ -37,4 +39,4 @@ extendFlat( { colorbar: colorbarAttrs } ); -module.exports = attrs; +module.exports = overrideAll(attrs, 'calc', 'nested'); diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 889dda231fc..2abb11323e7 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -14,12 +14,14 @@ var barAttrs = require('../bar/attributes'); module.exports = { x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'Sets the sample data to be binned on the x axis.' ].join(' ') }, y: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'Sets the sample data to be binned on the y axis.' ].join(' ') @@ -33,6 +35,7 @@ module.exports = { values: ['count', 'sum', 'avg', 'min', 'max'], role: 'style', dflt: 'count', + editType: 'calc', description: [ 'Specifies the binning function used for this histogram trace.', @@ -50,6 +53,7 @@ module.exports = { values: ['', 'percent', 'probability', 'density', 'probability density'], dflt: '', role: 'style', + editType: 'calc', description: [ 'Specifies the type of normalization used for this histogram trace.', @@ -77,6 +81,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: [ 'If true, display the cumulative distribution by summing the', 'binned values. Use the `direction` and `centralbin` attributes', @@ -94,6 +99,7 @@ module.exports = { values: ['increasing', 'decreasing'], dflt: 'increasing', role: 'info', + editType: 'calc', description: [ 'Only applies if cumulative is enabled.', 'If *increasing* (default) we sum all prior bins, so the result', @@ -107,6 +113,7 @@ module.exports = { values: ['include', 'exclude', 'half'], dflt: 'include', role: 'info', + editType: 'calc', description: [ 'Only applies if cumulative is enabled.', 'Sets whether the current bin is included, excluded, or has half', @@ -116,13 +123,20 @@ module.exports = { '*exclude* makes the opposite half-bin bias, and *half* removes', 'it.' ].join(' ') - } + }, + editType: 'calc' }, autobinx: { valType: 'boolean', dflt: null, role: 'style', + editType: 'calc', + impliedEdits: { + 'xbins.start': undefined, + 'xbins.end': undefined, + 'xbins.size': undefined + }, 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', @@ -135,6 +149,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'calc', 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', @@ -147,6 +162,12 @@ module.exports = { valType: 'boolean', dflt: null, role: 'style', + editType: 'calc', + impliedEdits: { + 'ybins.start': undefined, + 'ybins.end': undefined, + 'ybins.size': undefined + }, 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', @@ -159,6 +180,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'calc', 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', @@ -178,11 +200,18 @@ module.exports = { }; function makeBinsAttr(axLetter) { + var impliedEdits = {}; + impliedEdits['autobin' + axLetter] = false; + var impliedEditsInner = {}; + impliedEditsInner['^autobin' + axLetter] = false; + return { start: { valType: 'any', // for date axes dflt: null, role: 'style', + editType: 'calc', + impliedEdits: impliedEditsInner, description: [ 'Sets the starting value for the', axLetter, 'axis bins.' @@ -192,6 +221,8 @@ function makeBinsAttr(axLetter) { valType: 'any', // for date axes dflt: null, role: 'style', + editType: 'calc', + impliedEdits: impliedEditsInner, description: [ 'Sets the end value for the', axLetter, 'axis bins.' @@ -201,10 +232,14 @@ function makeBinsAttr(axLetter) { valType: 'any', // for date axes dflt: null, role: 'style', + editType: 'calc', + impliedEdits: impliedEditsInner, description: [ 'Sets the step in-between value each', axLetter, 'axis bin.' ].join(' ') - } + }, + editType: 'calc', + impliedEdits: impliedEdits }; } diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index fee1099eb4c..c293544f06a 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -232,7 +232,7 @@ function calcAllAutoBins(gd, trace, pa, mainData) { maxEnd = Math.max(maxEnd, pa.r2c(binSpec.end, 0, calendar)); // add the flag that lets us abort autobin on later traces - if(i) trace._autoBinFinished = 1; + if(i) tracei._autoBinFinished = 1; } // do what we can to match the auto bins to the first manual bins diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 06d6e8eacaf..4884f2dd2bd 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -22,13 +22,16 @@ module.exports = extendFlat({}, z: { valType: 'data_array', + editType: 'calc', description: 'Sets the aggregation data.' }, marker: { color: { valType: 'data_array', + editType: 'calc', description: 'Sets the aggregation data.' - } + }, + editType: 'calc' }, histnorm: histogramAttrs.histnorm, diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index b8164531ee0..028a9abeba1 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -15,7 +15,7 @@ var colorbarAttrs = require('../../components/colorbar/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({}, { +module.exports = extendFlat({ x: histogram2dAttrs.x, y: histogram2dAttrs.y, z: histogram2dAttrs.z, @@ -36,8 +36,8 @@ module.exports = extendFlat({}, { line: contourAttrs.line }, colorscaleAttrs, { - zmin: extendFlat({}, colorscaleAttrs.zmin, {editType: 'docalc'}), - zmax: extendFlat({}, colorscaleAttrs.zmax, {editType: 'docalc'}) + zmin: extendFlat({}, colorscaleAttrs.zmin, {editType: 'calc'}), + zmax: extendFlat({}, colorscaleAttrs.zmax, {editType: 'calc'}) }, { colorbar: colorbarAttrs } ); diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index a21d81956d5..1ee8be0b3b4 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -8,6 +8,7 @@ 'use strict'; +var colorAttrs = require('../../components/colorscale/color_attributes'); var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var surfaceAtts = require('../surface/attributes'); @@ -15,9 +16,10 @@ var surfaceAtts = require('../surface/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = { +module.exports = extendFlat(colorAttrs('', 'calc', false), { x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', 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.' @@ -25,6 +27,7 @@ module.exports = { }, y: { valType: 'data_array', + editType: 'calc+clearAxisTypes', 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.' @@ -32,6 +35,7 @@ module.exports = { }, z: { valType: 'data_array', + editType: 'calc+clearAxisTypes', 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.' @@ -40,6 +44,7 @@ module.exports = { i: { valType: 'data_array', + editType: 'calc', 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]}`', @@ -50,6 +55,7 @@ module.exports = { }, j: { valType: 'data_array', + editType: 'calc', 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]}` ', @@ -61,6 +67,7 @@ module.exports = { }, k: { valType: 'data_array', + editType: 'calc', 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]}`', @@ -76,6 +83,7 @@ module.exports = { role: 'info', values: [ 'x', 'y', 'z' ], dflt: 'z', + editType: 'calc', description: [ 'Sets the Delaunay axis, which is the axis that is perpendicular to the surface of the', 'Delaunay triangulation.', @@ -88,6 +96,7 @@ module.exports = { valType: 'number', role: 'style', dflt: -1, + editType: 'calc', description: [ 'Determines how the mesh surface triangles are derived from the set of', 'vertices (points) represented by the `x`, `y` and `z` arrays, if', @@ -112,6 +121,7 @@ module.exports = { intensity: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the vertex intensity values,', 'used for plotting fields on meshes' @@ -122,11 +132,13 @@ module.exports = { color: { valType: 'color', role: 'style', + editType: 'calc', description: 'Sets the color of the whole mesh' }, vertexcolor: { valType: 'data_array', role: 'style', + editType: 'calc', description: [ 'Sets the color of each vertex', 'Overrides *color*.' @@ -135,6 +147,7 @@ module.exports = { facecolor: { valType: 'data_array', role: 'style', + editType: 'calc', description: [ 'Sets the color of each face', 'Overrides *color* and *vertexcolor*.' @@ -142,13 +155,14 @@ module.exports = { }, // Opacity - opacity: extendFlat({}, surfaceAtts.opacity), + opacity: surfaceAtts.opacity, // Flat shaded mode flatshading: { valType: 'boolean', role: 'style', dflt: false, + editType: 'calc', description: [ 'Determines whether or not normal smoothing is applied to the meshes,', 'creating meshes with an angular, low-poly look via flat reflections.' @@ -161,31 +175,28 @@ module.exports = { 'Sets whether or not dynamic contours are shown on hover' ].join(' ') }), - color: extendFlat({}, surfaceAtts.contours.x.color), - width: extendFlat({}, surfaceAtts.contours.x.width) + color: surfaceAtts.contours.x.color, + width: surfaceAtts.contours.x.width, + editType: 'calc' }, - cauto: colorscaleAttrs.zauto, - cmin: colorscaleAttrs.zmin, - cmax: colorscaleAttrs.zmax, - colorscale: colorscaleAttrs.colorscale, - reversescale: colorscaleAttrs.reversescale, - autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}), 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}) + x: extendFlat({}, surfaceAtts.lightposition.x, {dflt: 1e5}), + y: extendFlat({}, surfaceAtts.lightposition.y, {dflt: 1e5}), + z: extendFlat({}, surfaceAtts.lightposition.z, {dflt: 0}), + editType: 'calc' }, - lighting: extendFlat({}, { + 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 + editType: 'calc', description: 'Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.' }, facenormalsepsilon: { @@ -194,7 +205,9 @@ module.exports = { min: 0.00, max: 1, dflt: 1e-6, // even the brain model doesn't appear to need finer than this + editType: 'calc', description: 'Epsilon for face normals calculation avoids math issues arising from degenerate geometry.' - } + }, + editType: 'calc' }, surfaceAtts.lighting) -}; +}); diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 938a1d1c81a..8a888ebb9c2 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -9,7 +9,7 @@ 'use strict'; -var Lib = require('../../lib'); +var extendFlat = require('../../lib').extendFlat; var scatterAttrs = require('../scatter/attributes'); var dash = require('../../components/drawing/attributes').dash; @@ -18,37 +18,44 @@ var DECREASING_COLOR = '#FF4136'; 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(' ') - }, +function directionAttrs(lineColorDefault) { + return { + name: { + valType: 'string', + role: 'info', + editType: 'style', + description: [ + 'Sets the segment name.', + 'The segment name appear as the legend item and on hover.' + ].join(' ') + }, - line: { - color: lineAttrs.color, - width: lineAttrs.width, - dash: dash, - } -}; + showlegend: { + valType: 'boolean', + role: 'info', + dflt: true, + editType: 'style', + description: [ + 'Determines whether or not an item corresponding to this', + 'segment is shown in the legend.' + ].join(' ') + }, + + line: { + color: extendFlat({}, lineAttrs.color, {dflt: lineColorDefault}), + width: lineAttrs.width, + dash: dash, + editType: 'style' + }, + editType: 'style' + }; +} module.exports = { x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: [ 'Sets the x coordinates.', 'If absent, linear coordinate will be generated.' @@ -58,29 +65,33 @@ module.exports = { open: { valType: 'data_array', dflt: [], + editType: 'calc', description: 'Sets the open values.' }, high: { valType: 'data_array', dflt: [], + editType: 'calc', description: 'Sets the high values.' }, low: { valType: 'data_array', dflt: [], + editType: 'calc', description: 'Sets the low values.' }, close: { valType: 'data_array', dflt: [], + editType: 'calc', description: 'Sets the close values.' }, line: { - width: Lib.extendFlat({}, lineAttrs.width, { + width: extendFlat({}, lineAttrs.width, { description: [ lineAttrs.width, 'Note that this style setting can also be set per', @@ -88,7 +99,7 @@ module.exports = { '`decreasing.line.width`.' ].join(' ') }), - dash: Lib.extendFlat({}, dash, { + dash: extendFlat({}, dash, { description: [ dash.description, 'Note that this style setting can also be set per', @@ -96,21 +107,19 @@ module.exports = { '`decreasing.line.dash`.' ].join(' ') }), + editType: 'style' }, - increasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: INCREASING_COLOR } } - }), + increasing: directionAttrs(INCREASING_COLOR), - decreasing: Lib.extendDeep({}, directionAttrs, { - line: { color: { dflt: DECREASING_COLOR } } - }), + decreasing: directionAttrs(DECREASING_COLOR), text: { valType: 'string', role: 'info', dflt: '', arrayOk: true, + editType: 'calc', description: [ 'Sets hover text elements associated with each sample point.', 'If a single string, the same string appears over', @@ -126,6 +135,7 @@ module.exports = { max: 0.5, dflt: 0.3, role: 'style', + editType: 'calcIfAutorange', description: [ 'Sets the width of the open/close tick marks', 'relative to the *x* minimal interval.' diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js index 83855a2da16..e994a5d2c9a 100644 --- a/src/traces/parcoords/attributes.js +++ b/src/traces/parcoords/attributes.js @@ -14,8 +14,9 @@ var colorscales = require('../../components/colorscale/scales'); var axesAttrs = require('../../plots/cartesian/layout_attributes'); var fontAttrs = require('../../plots/font_attributes'); -var extendDeep = require('../../lib/extend').extendDeep; -var extendFlat = require('../../lib/extend').extendFlat; +var extend = require('../../lib/extend'); +var extendDeepAll = extend.extendDeepAll; +var extendFlat = extend.extendFlat; module.exports = { @@ -24,10 +25,11 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'calc'}, + {valType: 'number', min: 0, max: 1, editType: 'calc'} ], dflt: [0, 1], + editType: 'calc', description: [ 'Sets the horizontal domain of this `parcoords` trace', '(in plot fraction).' @@ -37,24 +39,29 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'calc'}, + {valType: 'number', min: 0, max: 1, editType: 'calc'} ], dflt: [0, 1], + editType: 'calc', description: [ 'Sets the vertical domain of this `parcoords` trace', '(in plot fraction).' ].join(' ') - } + }, + editType: 'calc' }, - labelfont: extendFlat({}, fontAttrs, { + labelfont: fontAttrs({ + editType: 'calc', description: 'Sets the font for the `dimension` labels.' }), - tickfont: extendFlat({}, fontAttrs, { + tickfont: fontAttrs({ + editType: 'calc', description: 'Sets the font for the `dimension` tick values.' }), - rangefont: extendFlat({}, fontAttrs, { + rangefont: fontAttrs({ + editType: 'calc', description: 'Sets the font for the `dimension` range values.' }), @@ -63,14 +70,16 @@ module.exports = { label: { valType: 'string', role: 'info', + editType: 'calc', description: 'The shown name of the dimension.' }, - tickvals: axesAttrs.tickvals, - ticktext: axesAttrs.ticktext, + tickvals: extendFlat({}, axesAttrs.tickvals, {editType: 'calc'}), + ticktext: extendFlat({}, axesAttrs.ticktext, {editType: 'calc'}), tickformat: { valType: 'string', dflt: '3s', role: 'style', + editType: 'calc', description: [ 'Sets the tick label formatting rule using d3 formatting mini-language', 'which is similar to those of Python. See', @@ -81,15 +90,17 @@ module.exports = { valType: 'boolean', dflt: true, role: 'info', + editType: 'calc', description: 'Shows the dimension when set to `true` (the default). Hides the dimension for `false`.' }, range: { valType: 'info_array', role: 'info', items: [ - {valType: 'number'}, - {valType: 'number'} + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} ], + editType: 'calc', description: [ 'The domain range that represents the full, shown axis extent. Defaults to the `values` extent.', 'Must be an array of `[fromValue, toValue]` with finite numbers as elements.' @@ -99,9 +110,10 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number'}, - {valType: 'number'} + {valType: 'number', editType: 'calc'}, + {valType: 'number', editType: 'calc'} ], + editType: 'calc', description: [ 'The domain range to which the filter on the dimension is constrained. Must be an array', 'of `[fromValue, toValue]` with finite numbers as elements.' @@ -111,44 +123,36 @@ module.exports = { valType: 'data_array', role: 'info', dflt: [], + editType: 'calc', description: [ 'Dimension values. `values[n]` represents the value of the `n`th point in the dataset,', 'therefore the `values` vector for all dimensions must be the same (longer vectors', 'will be truncated). Each value must be a finite number.' ].join(' ') }, + editType: 'calc', description: 'The dimensions (variables) of the parallel coordinates chart. 2..60 dimensions are supported.' }, - line: extendFlat({}, - + line: extendFlat( // the default autocolorscale isn't quite usable for parcoords due to context ambiguity around 0 (grey, off-white) // autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette - extendDeep( - {}, - colorAttributes('line'), + extendDeepAll( + colorAttributes('line', 'calc'), { - colorscale: extendDeep( - {}, - colorAttributes('line').colorscale, - {dflt: colorscales.Viridis} - ), - autocolorscale: extendDeep( - {}, - colorAttributes('line').autocolorscale, - { - dflt: false, - description: [ - 'Has an effect only if line.color` is set to a numerical array.', - 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', - 'or the palette determined by `line.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.', - 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' - ].join(' ') - } - ) + colorscale: {dflt: colorscales.Viridis}, + autocolorscale: { + dflt: false, + description: [ + 'Has an effect only if line.color` is set to a numerical array.', + 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', + 'or the palette determined by `line.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.', + 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' + ].join(' ') + } } ), @@ -158,12 +162,14 @@ module.exports = { valType: 'boolean', role: 'info', dflt: false, + editType: 'calc', description: [ 'Has an effect only if `line.color` is set to a numerical array.', 'Determines whether or not a colorbar is displayed.' ].join(' ') }, - colorbar: colorbarAttrs + colorbar: colorbarAttrs, + editType: 'calc' } ) }; diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index f1ca7b6426c..5adbd886ca4 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -14,10 +14,16 @@ var plotAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var textFontAttrs = fontAttrs({ + editType: 'calc', + colorEditType: 'style', + description: 'Sets the font used for `textinfo`.' +}); module.exports = { labels: { valType: 'data_array', + editType: 'calc', description: 'Sets the sector labels.' }, // equivalent of x0 and dx, if label is missing @@ -25,6 +31,7 @@ module.exports = { valType: 'number', role: 'info', dflt: 0, + editType: 'calc', description: [ 'Alternate to `labels`.', 'Builds a numeric set of labels.', @@ -36,17 +43,20 @@ module.exports = { valType: 'number', role: 'info', dflt: 1, + editType: 'calc', description: 'Sets the label step. See `label0` for more info.' }, values: { valType: 'data_array', + editType: 'calc', description: 'Sets the values of the sectors of this pie chart.' }, marker: { colors: { valType: 'data_array', // TODO 'color_array' ? + editType: 'calc', description: [ 'Sets the color of each sector of this pie chart.', 'If not specified, the default trace color set is used', @@ -60,6 +70,7 @@ module.exports = { role: 'style', dflt: colorAttrs.defaultLine, arrayOk: true, + editType: 'style', description: [ 'Sets the color of the line enclosing each sector.' ].join(' ') @@ -70,15 +81,19 @@ module.exports = { min: 0, dflt: 0, arrayOk: true, + editType: 'style', description: [ 'Sets the width (in px) of the line enclosing each sector.' ].join(' ') - } - } + }, + editType: 'calc' + }, + editType: 'calc' }, text: { valType: 'data_array', + editType: 'calc', description: [ 'Sets text elements associated with each sector.', 'If trace `textinfo` contains a *text* flag, these elements will seen', @@ -92,6 +107,7 @@ module.exports = { role: 'info', dflt: '', arrayOk: true, + editType: 'style', description: [ 'Sets hover text elements associated with each sector.', 'If a single string, the same string appears for', @@ -110,6 +126,7 @@ module.exports = { valType: 'string', role: 'info', dflt: '', + editType: 'calc', description: [ 'If there are multiple pies that should be sized according to', 'their totals, link them by providing a non-empty group id here', @@ -123,6 +140,7 @@ module.exports = { role: 'info', flags: ['label', 'text', 'value', 'percent'], extras: ['none'], + editType: 'calc', description: [ 'Determines which trace information appear on the graph.' ].join(' ') @@ -136,18 +154,19 @@ module.exports = { values: ['inside', 'outside', 'auto', 'none'], dflt: 'auto', arrayOk: true, + editType: 'calc', description: [ 'Specifies the location of the `textinfo`.' ].join(' ') }, // TODO make those arrayOk? - textfont: extendFlat({}, fontAttrs, { + textfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo`.' }), - insidetextfont: extendFlat({}, fontAttrs, { + insidetextfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo` lying inside the pie.' }), - outsidetextfont: extendFlat({}, fontAttrs, { + outsidetextfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo` lying outside the pie.' }), @@ -157,10 +176,11 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'calc'}, + {valType: 'number', min: 0, max: 1, editType: 'calc'} ], dflt: [0, 1], + editType: 'calc', description: [ 'Sets the horizontal domain of this pie trace', '(in plot fraction).' @@ -170,15 +190,17 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: 'calc'}, + {valType: 'number', min: 0, max: 1, editType: 'calc'} ], dflt: [0, 1], + editType: 'calc', description: [ 'Sets the vertical domain of this pie trace', '(in plot fraction).' ].join(' ') - } + }, + editType: 'calc' }, hole: { valType: 'number', @@ -186,6 +208,7 @@ module.exports = { min: 0, max: 1, dflt: 0, + editType: 'calc', description: [ 'Sets the fraction of the radius to cut out of the pie.', 'Use this to make a donut chart.' @@ -197,6 +220,7 @@ module.exports = { valType: 'boolean', role: 'style', dflt: true, + editType: 'calc', description: [ 'Determines whether or not the sectors are reordered', 'from largest to smallest.' @@ -214,6 +238,7 @@ module.exports = { values: ['clockwise', 'counterclockwise'], role: 'style', dflt: 'counterclockwise', + editType: 'calc', description: [ 'Specifies the direction at which succeeding sectors follow', 'one another.' @@ -225,6 +250,7 @@ module.exports = { min: -360, max: 360, dflt: 0, + editType: 'calc', description: [ 'Instead of the first slice starting at 12 o\'clock,', 'rotate to some other angle.' @@ -238,6 +264,7 @@ module.exports = { max: 1, dflt: 0, arrayOk: true, + editType: 'calc', description: [ 'Sets the fraction of larger radius to pull the sectors', 'out from the center. This can be a constant', diff --git a/src/traces/pie/layout_attributes.js b/src/traces/pie/layout_attributes.js index 29167d778c2..6e795693ad8 100644 --- a/src/traces/pie/layout_attributes.js +++ b/src/traces/pie/layout_attributes.js @@ -14,5 +14,8 @@ module.exports = { * but it can contain many labels, and can hide slices * from several pies simultaneously */ - hiddenlabels: {valType: 'data_array'} + hiddenlabels: { + valType: 'data_array', + editType: 'calc' + } }; diff --git a/src/traces/pointcloud/attributes.js b/src/traces/pointcloud/attributes.js index 3c3d76277c4..17f4af0418a 100644 --- a/src/traces/pointcloud/attributes.js +++ b/src/traces/pointcloud/attributes.js @@ -15,6 +15,7 @@ module.exports = { y: scatterglAttrs.y, xy: { valType: 'data_array', + editType: 'calc', description: [ 'Faster alternative to specifying `x` and `y` separately.', 'If supplied, it must be a typed `Float32Array` array that', @@ -23,6 +24,7 @@ module.exports = { }, indices: { valType: 'data_array', + editType: 'calc', description: [ 'A sequential value, 0..n, supply it to avoid creating this array inside plotting.', 'If specified, it must be a typed `Int32Array` array.', @@ -34,6 +36,7 @@ module.exports = { }, xbounds: { valType: 'data_array', + editType: 'calc', 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.' @@ -41,6 +44,7 @@ module.exports = { }, ybounds: { valType: 'data_array', + editType: 'calc', 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.' @@ -52,6 +56,7 @@ module.exports = { valType: 'color', arrayOk: false, role: 'style', + editType: 'calc', description: [ 'Sets the marker fill color. It accepts a specific color.', 'If the color is not fully opaque and there are hundreds of thousands', @@ -65,6 +70,7 @@ module.exports = { dflt: 1, arrayOk: false, role: 'style', + editType: 'calc', 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', @@ -77,6 +83,7 @@ module.exports = { valType: 'boolean', dflt: null, role: 'style', + editType: 'calc', description: [ 'Determines if colors are blended together for a translucency effect', 'in case `opacity` is specified as a value less then `1`.', @@ -90,6 +97,7 @@ module.exports = { max: 2, dflt: 0.5, role: 'style', + editType: 'calc', description: [ 'Sets the minimum size (in px) of the rendered marker points, effective when', 'the `pointcloud` shows a million or more points.' @@ -100,6 +108,7 @@ module.exports = { min: 0.1, dflt: 20, role: 'style', + editType: 'calc', description: [ 'Sets the maximum size (in px) of the rendered marker points.', 'Effective when the `pointcloud` shows only few points.' @@ -110,6 +119,7 @@ module.exports = { valType: 'color', arrayOk: false, role: 'style', + editType: 'calc', description: [ 'Sets the stroke color. It accepts a specific color.', 'If the color is not fully opaque and there are hundreds of thousands', @@ -122,11 +132,14 @@ module.exports = { max: 1, dflt: 0, role: 'style', + editType: 'calc', description: [ 'Specifies what fraction of the marker area is covered with the', 'border.' ].join(' ') - } - } + }, + editType: 'calc' + }, + editType: 'calc' } }; diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index ccd49ccba70..f102d82f188 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -8,17 +8,19 @@ 'use strict'; -var shapeAttrs = require('../../components/shapes/attributes'); var fontAttrs = require('../../plots/font_attributes'); var plotAttrs = require('../../plots/attributes'); var colorAttrs = require('../../components/color/attributes'); +var fxAttrs = require('../../components/fx/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; -module.exports = { +module.exports = overrideAll({ hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['label', 'text', 'value', 'percent', 'name'] + flags: ['label', 'text', 'value', 'percent', 'name'], }), + hoverlabel: fxAttrs.hoverlabel, // needs editType override domain: { x: { valType: 'info_array', @@ -91,7 +93,9 @@ module.exports = { ].join(' ') }, - textfont: fontAttrs, + textfont: fontAttrs({ + description: 'Sets the font for node labels' + }), node: { label: { @@ -100,7 +104,9 @@ module.exports = { role: 'info', description: 'The shown name of the node.' }, - color: extendFlat({}, shapeAttrs.fillcolor, { + color: { + valType: 'color', + role: 'style', arrayOk: true, description: [ 'Sets the `node` color. It can be a single value, or an array for specifying color for each `node`.', @@ -108,7 +114,7 @@ module.exports = { 'to have a variety of colors. These defaults are not fully opaque, to allow some visibility of', 'what is beneath the node.' ].join(' ') - }), + }, line: { color: { valType: 'color', @@ -156,13 +162,15 @@ module.exports = { role: 'info', description: 'The shown name of the link.' }, - color: extendFlat({}, shapeAttrs.fillcolor, { + color: { + valType: 'color', + role: 'style', arrayOk: true, description: [ 'Sets the `link` color. It can be a single value, or an array for specifying color for each `link`.', 'If `link.color` is omitted, then by default, a translucent grey link will be used.' ].join(' ') - }), + }, line: { color: { valType: 'color', @@ -204,4 +212,4 @@ module.exports = { }, description: 'The links of the Sankey plot.' } -}; +}, 'calc', 'nested'); diff --git a/src/traces/sankey/base_plot.js b/src/traces/sankey/base_plot.js index 479ead95f0d..942dfd58b6e 100644 --- a/src/traces/sankey/base_plot.js +++ b/src/traces/sankey/base_plot.js @@ -8,13 +8,19 @@ 'use strict'; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var Plots = require('../../plots/plots'); var plot = require('./plot'); +var fxAttrs = require('../../components/fx/layout_attributes'); exports.name = 'sankey'; exports.attr = 'type'; +exports.baseLayoutAttrOverrides = overrideAll({ + hoverlabel: fxAttrs.hoverlabel +}, 'plot', 'nested'); + exports.plot = function(gd) { var calcData = Plots.getSubplotCalcData(gd.calcdata, 'sankey', 'sankey'); if(calcData.length) plot(gd, calcData); diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index f48092247b3..4b719db4a9a 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -11,6 +11,7 @@ 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 dash = require('../../components/drawing/attributes').dash; var Drawing = require('../../components/drawing'); @@ -20,12 +21,14 @@ var extendFlat = require('../../lib/extend').extendFlat; module.exports = { x: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: 'Sets the x coordinates.' }, x0: { valType: 'any', dflt: 0, role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'Alternate to `x`.', 'Builds a linear space of x coordinates.', @@ -37,6 +40,7 @@ module.exports = { valType: 'number', dflt: 1, role: 'info', + editType: 'calc', description: [ 'Sets the x coordinate step.', 'See `x0` for more info.' @@ -44,12 +48,14 @@ module.exports = { }, y: { valType: 'data_array', + editType: 'calc+clearAxisTypes', description: 'Sets the y coordinates.' }, y0: { valType: 'any', dflt: 0, role: 'info', + editType: 'calc+clearAxisTypes', description: [ 'Alternate to `y`.', 'Builds a linear space of y coordinates.', @@ -61,6 +67,7 @@ module.exports = { valType: 'number', dflt: 1, role: 'info', + editType: 'calc', description: [ 'Sets the y coordinate step.', 'See `y0` for more info.' @@ -71,6 +78,7 @@ module.exports = { role: 'info', dflt: '', arrayOk: true, + editType: 'calc', description: [ 'Sets text elements associated with each (x,y) pair.', 'If a single string, the same string appears over', @@ -86,6 +94,7 @@ module.exports = { role: 'info', dflt: '', arrayOk: true, + editType: 'style', description: [ 'Sets hover text elements associated with each (x,y) pair.', 'If a single string, the same string appears over', @@ -100,6 +109,7 @@ module.exports = { flags: ['lines', 'markers', 'text'], extras: ['none'], role: 'info', + editType: 'calc', description: [ 'Determines the drawing mode for this scatter trace.', 'If the provided `mode` includes *text* then the `text` elements', @@ -113,6 +123,7 @@ module.exports = { valType: 'flaglist', flags: ['points', 'fills'], role: 'info', + editType: 'style', description: [ 'Do the hover effects highlight individual points (markers or', 'line points) or do they highlight filled regions?', @@ -124,6 +135,7 @@ module.exports = { color: { valType: 'color', role: 'style', + editType: 'style', description: 'Sets the line color.' }, width: { @@ -131,6 +143,7 @@ module.exports = { min: 0, dflt: 2, role: 'style', + editType: 'style', description: 'Sets the line width (in px).' }, shape: { @@ -138,6 +151,7 @@ module.exports = { values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'], dflt: 'linear', role: 'style', + editType: 'plot', description: [ 'Determines the line shape.', 'With *spline* the lines are drawn using spline interpolation.', @@ -150,29 +164,33 @@ module.exports = { max: 1.3, dflt: 1, role: 'style', + editType: 'plot', 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: dash, + dash: extendFlat({}, dash, {editType: 'style'}), simplify: { valType: 'boolean', dflt: true, role: 'info', + editType: 'plot', 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(' ') - } + }, + editType: 'plot' }, connectgaps: { valType: 'boolean', dflt: false, role: 'info', + editType: 'calc', description: [ 'Determines whether or not gaps', '(i.e. {nan} or missing values)', @@ -183,7 +201,7 @@ module.exports = { valType: 'boolean', dflt: true, role: 'info', - editType: 'doplot', + editType: 'plot', description: [ 'Determines whether or not markers and text nodes', 'are clipped about the subplot axes.', @@ -197,6 +215,7 @@ module.exports = { values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'], dflt: 'none', role: 'style', + editType: 'calc', description: [ 'Sets the area to fill with a solid color.', 'Use with `fillcolor` if not *none*.', @@ -217,19 +236,21 @@ module.exports = { fillcolor: { valType: 'color', role: 'style', + editType: '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({}, { + marker: extendFlat({ symbol: { valType: 'enumerated', values: Drawing.symbolList, dflt: 'circle', arrayOk: true, role: 'style', + editType: 'style', description: [ 'Sets the marker symbol type.', 'Adding 100 is equivalent to appending *-open* to a symbol name.', @@ -244,6 +265,7 @@ module.exports = { max: 1, arrayOk: true, role: 'style', + editType: 'style', description: 'Sets the marker opacity.' }, size: { @@ -252,6 +274,7 @@ module.exports = { dflt: 6, arrayOk: true, role: 'style', + editType: 'calcIfAutorange', description: 'Sets the marker size (in px).' }, maxdisplayed: { @@ -259,6 +282,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'plot', description: [ 'Sets a maximum number of points to be drawn on the graph.', '*0* corresponds to no limit.' @@ -268,6 +292,7 @@ module.exports = { valType: 'number', dflt: 1, role: 'style', + editType: 'calc', 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', @@ -279,6 +304,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', + editType: 'calc', 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.' @@ -289,6 +315,7 @@ module.exports = { values: ['diameter', 'area'], dflt: 'diameter', role: 'info', + editType: 'calc', 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', @@ -300,6 +327,7 @@ module.exports = { valType: 'boolean', role: 'info', dflt: false, + editType: 'calc', description: [ 'Has an effect only if `marker.color` is set to a numerical array.', 'Determines whether or not a colorbar is displayed.' @@ -307,14 +335,16 @@ module.exports = { }, colorbar: colorbarAttrs, - line: extendFlat({}, { + line: extendFlat({ width: { valType: 'number', min: 0, arrayOk: true, role: 'style', + editType: 'style', description: 'Sets the width (in px) of the lines bounding the marker points.' - } + }, + editType: 'calc' }, colorAttributes('marker.line') ), @@ -325,6 +355,7 @@ module.exports = { arrayOk: true, dflt: 'none', role: 'style', + editType: 'calc', description: [ 'Sets the type of gradient used to fill the markers' ].join(' ') @@ -333,13 +364,16 @@ module.exports = { valType: 'color', arrayOk: true, role: 'style', + editType: 'calc', description: [ 'Sets the final color of the gradient fill:', 'the center color for radial, the right for horizontal,', 'or the bottom for vertical.', ].join(' ') - } - } + }, + editType: 'calc' + }, + editType: 'calc' }, colorAttributes('marker') ), @@ -353,35 +387,22 @@ module.exports = { dflt: 'middle center', arrayOk: true, role: 'style', + editType: 'calc', description: [ 'Sets the positions of the `text` elements', 'with respects to the (x,y) coordinates.' ].join(' ') }, - textfont: { - family: { - valType: 'string', - role: 'style', - noBlank: true, - strict: true, - arrayOk: true - }, - size: { - valType: 'number', - role: 'style', - min: 1, - arrayOk: true - }, - color: { - valType: 'color', - role: 'style', - arrayOk: true - }, + textfont: fontAttrs({ + editType: 'calc', + colorEditType: 'style', + arrayOk: true, description: 'Sets the text font.' - }, + }), r: { valType: 'data_array', + editType: 'calc', description: [ 'For polar chart only.', 'Sets the radial coordinates.' @@ -389,6 +410,7 @@ module.exports = { }, t: { valType: 'data_array', + editType: 'calc', description: [ 'For polar chart only.', 'Sets the angular coordinates.' diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index fe082ee519f..91cf3d71e3b 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -15,10 +15,11 @@ var DASHES = require('../../constants/gl3d_dashes'); var MARKER_SYMBOLS = require('../../constants/gl3d_markers'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; -var scatterLineAttrs = scatterAttrs.line, - scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; +var scatterLineAttrs = scatterAttrs.line; +var scatterMarkerAttrs = scatterAttrs.marker; +var scatterMarkerLineAttrs = scatterMarkerAttrs.line; function makeProjectionAttr(axLetter) { return { @@ -53,15 +54,9 @@ function makeProjectionAttr(axLetter) { }; } -module.exports = { - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' - }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' - }, +var attrs = module.exports = overrideAll({ + x: scatterAttrs.x, + y: scatterAttrs.y, z: { valType: 'data_array', description: 'Sets the z coordinates.' @@ -113,7 +108,7 @@ module.exports = { z: makeProjectionAttr('z') }, connectgaps: scatterAttrs.connectgaps, - line: extendFlat({}, { + line: extendFlat({ width: scatterLineAttrs.width, dash: { valType: 'enumerated', @@ -134,7 +129,7 @@ module.exports = { }, colorAttributes('line') ), - marker: extendFlat({}, { // Parity with scatter.js? + marker: extendFlat({ // Parity with scatter.js? symbol: { valType: 'enumerated', values: Object.keys(MARKER_SYMBOLS), @@ -161,8 +156,9 @@ module.exports = { showscale: scatterMarkerAttrs.showscale, colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({}, - {width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false})}, + line: extendFlat({ + width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false}) + }, colorAttributes('marker.line') ) }, @@ -175,4 +171,6 @@ module.exports = { error_x: errorBarAttrs, error_y: errorBarAttrs, error_z: errorBarAttrs, -}; +}, 'calc', 'nested'); + +attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index 06f73f20fe2..391a5b9a727 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -23,6 +23,7 @@ module.exports = { carpet: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'An identifier for this carpet, so that `scattercarpet` and', '`scattercontour` traces can specify a carpet plot on which', @@ -31,6 +32,7 @@ module.exports = { }, a: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the quantity of component `a` in each data point.', 'If `a`, `b`, and `c` are all provided, they need not be', @@ -41,6 +43,7 @@ module.exports = { }, b: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the quantity of component `a` in each data point.', 'If `a`, `b`, and `c` are all provided, they need not be', @@ -49,19 +52,6 @@ module.exports = { '`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: [ @@ -78,7 +68,8 @@ module.exports = { dash: scatterLineAttrs.dash, shape: extendFlat({}, scatterLineAttrs.shape, {values: ['linear', 'spline']}), - smoothing: scatterLineAttrs.smoothing + smoothing: scatterLineAttrs.smoothing, + editType: 'calc' }, connectgaps: scatterAttrs.connectgaps, fill: extendFlat({}, scatterAttrs.fill, { @@ -96,7 +87,7 @@ module.exports = { ].join(' ') }), fillcolor: scatterAttrs.fillcolor, - marker: extendFlat({}, { + marker: extendFlat({ symbol: scatterMarkerAttrs.symbol, opacity: scatterMarkerAttrs.opacity, maxdisplayed: scatterMarkerAttrs.maxdisplayed, @@ -104,11 +95,14 @@ module.exports = { sizeref: scatterMarkerAttrs.sizeref, sizemin: scatterMarkerAttrs.sizemin, sizemode: scatterMarkerAttrs.sizemode, - line: extendFlat({}, - {width: scatterMarkerLineAttrs.width}, + line: extendFlat({ + width: scatterMarkerLineAttrs.width, + editType: 'calc' + }, colorAttributes('marker'.line) ), - gradient: scatterMarkerAttrs.gradient + gradient: scatterMarkerAttrs.gradient, + editType: 'calc' }, colorAttributes('marker'), { showscale: scatterMarkerAttrs.showscale, colorbar: colorbarAttrs diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index 40415bef2c3..5bf92d1a02f 100644 --- a/src/traces/scattercarpet/defaults.js +++ b/src/traces/scattercarpet/defaults.js @@ -48,8 +48,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(a && len < a.length) traceOut.a = a.slice(0, len); if(b && len < b.length) traceOut.b = b.slice(0, len); - coerce('sum'); - coerce('text'); var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index c8e65869fe3..a6075bdd81f 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -14,12 +14,13 @@ var colorAttributes = require('../../components/colorscale/color_attributes'); var dash = require('../../components/drawing/attributes').dash; var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var scatterMarkerAttrs = scatterAttrs.marker, scatterLineAttrs = scatterAttrs.line, scatterMarkerLineAttrs = scatterMarkerAttrs.line; -module.exports = { +module.exports = overrideAll({ lon: { valType: 'data_array', description: 'Sets the longitude coordinates (in degrees East).' @@ -84,7 +85,7 @@ module.exports = { }, connectgaps: scatterAttrs.connectgaps, - marker: extendFlat({}, { + marker: extendFlat({ symbol: scatterMarkerAttrs.symbol, opacity: scatterMarkerAttrs.opacity, size: scatterMarkerAttrs.size, @@ -93,8 +94,9 @@ module.exports = { sizemode: scatterMarkerAttrs.sizemode, showscale: scatterMarkerAttrs.showscale, colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({}, - {width: scatterMarkerLineAttrs.width}, + line: extendFlat({ + width: scatterMarkerLineAttrs.width + }, colorAttributes('marker.line') ), gradient: scatterMarkerAttrs.gradient @@ -119,4 +121,4 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['lon', 'lat', 'location', 'text', 'name'] }) -}; +}, 'calc', 'nested'); diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 637b6812338..cf225193709 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -14,13 +14,13 @@ var colorAttributes = require('../../components/colorscale/color_attributes'); var DASHES = require('../../constants/gl2d_dashes'); var MARKERS = require('../../constants/gl2d_markers'); var extendFlat = require('../../lib/extend').extendFlat; -var extendDeep = require('../../lib/extend').extendDeep; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var scatterLineAttrs = scatterAttrs.line, scatterMarkerAttrs = scatterAttrs.marker, scatterMarkerLineAttrs = scatterMarkerAttrs.line; -module.exports = { +var attrs = module.exports = overrideAll({ x: scatterAttrs.x, x0: scatterAttrs.x0, dx: scatterAttrs.dx, @@ -57,7 +57,7 @@ module.exports = { description: 'Sets the style of the lines.' } }, - marker: extendDeep({}, colorAttributes('marker'), { + marker: extendFlat({}, colorAttributes('marker'), { symbol: { valType: 'enumerated', values: Object.keys(MARKERS), @@ -73,7 +73,7 @@ module.exports = { opacity: scatterMarkerAttrs.opacity, showscale: scatterMarkerAttrs.showscale, colorbar: scatterMarkerAttrs.colorbar, - line: extendDeep({}, colorAttributes('marker.line'), { + line: extendFlat({}, colorAttributes('marker.line'), { width: scatterMarkerLineAttrs.width }) }), @@ -85,4 +85,6 @@ module.exports = { error_y: scatterAttrs.error_y, error_x: scatterAttrs.error_x -}; +}, 'calc', 'nested'); + +attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 5085eeb48fe..a907e34905d 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -15,12 +15,13 @@ var plotAttrs = require('../../plots/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; var lineAttrs = scatterGeoAttrs.line; var markerAttrs = scatterGeoAttrs.marker; -module.exports = { +module.exports = overrideAll({ lon: scatterGeoAttrs.lon, lat: scatterGeoAttrs.lat, @@ -95,7 +96,7 @@ module.exports = { autocolorscale: markerAttrs.autocolorscale, reversescale: markerAttrs.reversescale, showscale: markerAttrs.showscale, - colorbar: colorbarAttrs + colorbar: colorbarAttrs, // line }, @@ -108,5 +109,5 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['lon', 'lat', 'text', 'name'] - }), -}; + }) +}, 'calc', 'nested'); diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 4b5b73bca6f..5af29cb9f89 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -23,6 +23,7 @@ var scatterMarkerAttrs = scatterAttrs.marker, module.exports = { a: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the quantity of component `a` in each data point.', 'If `a`, `b`, and `c` are all provided, they need not be', @@ -33,6 +34,7 @@ module.exports = { }, b: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the quantity of component `a` in each data point.', 'If `a`, `b`, and `c` are all provided, they need not be', @@ -43,6 +45,7 @@ module.exports = { }, c: { valType: 'data_array', + editType: 'calc', description: [ 'Sets the quantity of component `a` in each data point.', 'If `a`, `b`, and `c` are all provided, they need not be', @@ -56,6 +59,7 @@ module.exports = { role: 'info', dflt: 0, min: 0, + editType: 'calc', description: [ 'The number each triplet should sum to,', 'if only two of `a`, `b`, and `c` are provided.', @@ -92,7 +96,8 @@ module.exports = { dash: dash, shape: extendFlat({}, scatterLineAttrs.shape, {values: ['linear', 'spline']}), - smoothing: scatterLineAttrs.smoothing + smoothing: scatterLineAttrs.smoothing, + editType: 'calc' }, connectgaps: scatterAttrs.connectgaps, cliponaxis: scatterAttrs.cliponaxis, @@ -111,7 +116,7 @@ module.exports = { ].join(' ') }), fillcolor: scatterAttrs.fillcolor, - marker: extendFlat({}, { + marker: extendFlat({ symbol: scatterMarkerAttrs.symbol, opacity: scatterMarkerAttrs.opacity, maxdisplayed: scatterMarkerAttrs.maxdisplayed, @@ -119,11 +124,14 @@ module.exports = { sizeref: scatterMarkerAttrs.sizeref, sizemin: scatterMarkerAttrs.sizemin, sizemode: scatterMarkerAttrs.sizemode, - line: extendFlat({}, - {width: scatterMarkerLineAttrs.width}, - colorAttributes('marker'.line) + line: extendFlat({ + width: scatterMarkerLineAttrs.width, + editType: 'calc' + }, + colorAttributes('marker.line') ), - gradient: scatterMarkerAttrs.gradient + gradient: scatterMarkerAttrs.gradient, + editType: 'calc' }, colorAttributes('marker'), { showscale: scatterMarkerAttrs.showscale, colorbar: colorbarAttrs diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index 35761fd4496..122ae5e4778 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -13,6 +13,7 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; function makeContourProjAttr(axLetter) { return { @@ -96,7 +97,7 @@ function makeContourAttr(axLetter) { }; } -module.exports = { +var attrs = module.exports = overrideAll({ z: { valType: 'data_array', description: 'Sets the z coordinates.' @@ -242,4 +243,6 @@ module.exports = { description: 'Obsolete. Use `cmax` instead.' }) } -}; +}, 'calc', 'nested'); + +attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; diff --git a/src/transforms/aggregate.js b/src/transforms/aggregate.js index 932e54f4ccc..90b00a073bc 100644 --- a/src/transforms/aggregate.js +++ b/src/transforms/aggregate.js @@ -21,6 +21,8 @@ var attrs = exports.attributes = { enabled: { valType: 'boolean', dflt: true, + role: 'info', + editType: 'calc', description: [ 'Determines whether this aggregate transform is enabled or disabled.' ].join(' ') @@ -33,6 +35,8 @@ var attrs = exports.attributes = { noBlank: true, arrayOk: true, dflt: 'x', + role: 'info', + editType: 'calc', description: [ 'Sets the grouping target to which the aggregation is applied.', 'Data points with matching group values will be coalesced into', @@ -51,6 +55,7 @@ var attrs = exports.attributes = { target: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'A reference to the data array in the parent trace to aggregate.', 'To aggregate by nested variables, use *.* to access them.', @@ -65,6 +70,7 @@ var attrs = exports.attributes = { values: ['count', 'sum', 'avg', 'median', 'mode', 'rms', 'stddev', 'min', 'max', 'first', 'last'], dflt: 'first', role: 'info', + editType: 'calc', description: [ 'Sets the aggregation function.', 'All values from the linked `target`, corresponding to the same value', @@ -87,6 +93,7 @@ var attrs = exports.attributes = { values: ['sample', 'population'], dflt: 'sample', role: 'info', + editType: 'calc', description: [ '*stddev* supports two formula variants: *sample* (normalize by N-1)', 'and *population* (normalize by N).' @@ -95,11 +102,15 @@ var attrs = exports.attributes = { enabled: { valType: 'boolean', dflt: true, + role: 'info', + editType: 'calc', description: [ 'Determines whether this aggregation function is enabled or disabled.' ].join(' ') - } - } + }, + editType: 'calc' + }, + editType: 'calc' }; var aggAttrs = attrs.aggregations; diff --git a/src/transforms/filter.js b/src/transforms/filter.js index e895fa89b91..6e948651558 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -24,6 +24,8 @@ exports.attributes = { enabled: { valType: 'boolean', dflt: true, + role: 'info', + editType: 'calc', description: [ 'Determines whether this filter transform is enabled or disabled.' ].join(' ') @@ -34,6 +36,8 @@ exports.attributes = { noBlank: true, arrayOk: true, dflt: 'x', + role: 'info', + editType: 'calc', description: [ 'Sets the filter target by which the filter is applied.', @@ -53,6 +57,8 @@ exports.attributes = { .concat(INTERVAL_OPS) .concat(SET_OPS), dflt: '=', + role: 'info', + editType: 'calc', description: [ 'Sets the filter operation.', @@ -82,6 +88,8 @@ exports.attributes = { value: { valType: 'any', dflt: 0, + role: 'info', + editType: 'calc', description: [ 'Sets the value or values by which to filter.', @@ -106,6 +114,8 @@ exports.attributes = { preservegaps: { valType: 'boolean', dflt: false, + role: 'info', + editType: 'calc', description: [ 'Determines whether or not gaps in data arrays produced by the filter operation', 'are preserved.', @@ -113,6 +123,7 @@ exports.attributes = { 'with `connectgaps` set to *false*.' ].join(' ') }, + editType: 'calc' }; exports.supplyDefaults = function(transformIn) { diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 632acab80e2..3b90ea562ae 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -20,6 +20,8 @@ exports.attributes = { enabled: { valType: 'boolean', dflt: true, + role: 'info', + editType: 'calc', description: [ 'Determines whether this group-by transform is enabled or disabled.' ].join(' ') @@ -27,6 +29,8 @@ exports.attributes = { groups: { valType: 'data_array', dflt: [], + role: 'info', + editType: 'calc', description: [ 'Sets the groups in which the trace data will be split.', 'For example, with `x` set to *[1, 2, 3, 4]* and', @@ -37,6 +41,8 @@ exports.attributes = { }, nameformat: { valType: 'string', + role: 'info', + editType: 'calc', description: [ 'Pattern by which grouped traces are named. If only one trace is present,', 'defaults to the group name (`"%{group}"`), otherwise defaults to the group name', @@ -51,6 +57,7 @@ exports.attributes = { target: { valType: 'string', role: 'info', + editType: 'calc', description: [ 'The group value which receives these styles.' ].join(' ') @@ -59,6 +66,7 @@ exports.attributes = { valType: 'any', role: 'info', dflt: {}, + editType: 'calc', description: [ 'Sets each group styles.', 'For example, with `groups` set to *[\'a\', \'b\', \'a\', \'b\']*', @@ -66,7 +74,9 @@ exports.attributes = { 'marker points in group *\'a\'* will be drawn in red.' ].join(' ') }, - } + editType: 'calc' + }, + editType: 'calc' }; /** diff --git a/src/transforms/sort.js b/src/transforms/sort.js index 41e34f1e8e9..9dc082b492c 100644 --- a/src/transforms/sort.js +++ b/src/transforms/sort.js @@ -19,6 +19,8 @@ exports.attributes = { enabled: { valType: 'boolean', dflt: true, + role: 'info', + editType: 'calc', description: [ 'Determines whether this sort transform is enabled or disabled.' ].join(' ') @@ -29,6 +31,8 @@ exports.attributes = { noBlank: true, arrayOk: true, dflt: 'x', + role: 'info', + editType: 'calc', description: [ 'Sets the target by which the sort transform is applied.', @@ -46,10 +50,13 @@ exports.attributes = { valType: 'enumerated', values: ['ascending', 'descending'], dflt: 'ascending', + role: 'info', + editType: 'calc', description: [ 'Sets the sort transform order.' ].join(' ') - } + }, + editType: 'calc' }; exports.supplyDefaults = function(transformIn) { diff --git a/tasks/bundle.js b/tasks/bundle.js index 0fc7c7cd4d8..b1a175d4654 100644 --- a/tasks/bundle.js +++ b/tasks/bundle.js @@ -1,7 +1,7 @@ var constants = require('./util/constants'); var common = require('./util/common'); var _bundle = require('./util/browserify_wrapper'); - +var makeSchema = require('./util/make_schema'); /* * This script takes one argument * @@ -41,7 +41,8 @@ _bundle(constants.pathToPlotlyGeoAssetsSrc, constants.pathToPlotlyGeoAssetsDist, // Browserify the plotly.js with meta _bundle(constants.pathToPlotlyIndex, constants.pathToPlotlyDistWithMeta, { standalone: 'Plotly', - debug: DEV + debug: DEV, + then: makeSchema(constants.pathToPlotlyDistWithMeta, constants.pathToSchema) }); // Browserify the plotly.js partial bundles diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index 7050706eb93..fa57092764e 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -50,6 +50,9 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) { bundleWriteStream.on('finish', function() { logger(pathToBundle); + if(opts.then) { + opts.then(); + } }); b.bundle(function(err, buf) { diff --git a/tasks/util/constants.js b/tasks/util/constants.js index 7cffc50cfe0..1815c740616 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -41,6 +41,8 @@ module.exports = { pathToPlotlyDistMin: path.join(pathToDist, 'plotly.min.js'), pathToPlotlyDistWithMeta: path.join(pathToDist, 'plotly-with-meta.js'), + pathToSchema: path.join(pathToDist, 'plot-schema.json'), + partialBundleNames: partialBundleNames, partialBundlePaths: partialBundlePaths, diff --git a/tasks/util/make_schema.js b/tasks/util/make_schema.js new file mode 100644 index 00000000000..85c5299e7c6 --- /dev/null +++ b/tasks/util/make_schema.js @@ -0,0 +1,24 @@ +var fs = require('fs'); +var path = require('path'); +var JSDOM = require('jsdom').JSDOM; + +module.exports = function makeSchema(plotlyPath, schemaPath) { + return function() { + var plotlyjsCode = fs.readFileSync(plotlyPath, 'utf-8'); + + var w = new JSDOM('', {runScripts: 'dangerously'}).window; + + // jsdom by itself doesn't support getContext, and adding the npm canvas + // package is annoying and platform-dependent. + // see https://github.com/tmpvar/jsdom/issues/1782 + w.HTMLCanvasElement.prototype.getContext = function() { return null; }; + + w.eval(plotlyjsCode); + + var plotSchema = w.Plotly.PlotSchema.get(); + var plotSchemaStr = JSON.stringify(plotSchema, null, 4); + fs.writeFileSync(schemaPath, plotSchemaStr); + + console.log('ok ' + path.basename(schemaPath)); + }; +}; diff --git a/test/jasmine/assets/check_component.js b/test/jasmine/assets/check_component.js new file mode 100644 index 00000000000..dfc2decc2a8 --- /dev/null +++ b/test/jasmine/assets/check_component.js @@ -0,0 +1,90 @@ +var d3 = require('d3'); + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); + +/** + * common test that components work as registered + * expects bar, scatter3d, filter, and calendars to be registered + * but the test is that they may have been registered in any order + */ +module.exports = function checkComponent(Plotly) { + describe('core (svg 2d, scatter) and registered (bar) traces and transforms', function() { + var gd; + + var mock = { + data: [ + { + // x data is date so we coerce a calendar + x: ['2001-01-01', '2002-01-01', '2003-01-01'], + y: [1, 3, 5] + }, + { + x: ['2001-01-01', '2002-01-01', '2003-01-01'], + y: [1, 3, 5], + type: 'bar', + transforms: [{ + type: 'filter', + operation: '<', + target: 'y', + value: '4', + // need an explicit calendar, as filter uses a default of null + // the rest of them get the default calendar filled in + valuecalendar: 'nepali' + }] + } + ] + }; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.plot(gd, mock.data, mock.layout).then(done); + }); + + afterEach(destroyGraphDiv); + + it('should graph scatter traces with calendar attributes', function() { + var nodes = d3.selectAll('g.trace.scatter'); + + expect(nodes.size()).toEqual(1); + + // compare to core_test + expect(gd._fullLayout.calendar).toBe('gregorian'); + expect(gd._fullLayout.xaxis.calendar).toBe('gregorian'); + expect(gd._fullData[0].xcalendar).toBe('gregorian'); + }); + + it('should graph bar traces with calendar attributes', function() { + var nodes = d3.selectAll('g.trace.bars'); + + expect(nodes.size()).toEqual(1); + expect(gd._fullData[1].xcalendar).toBe('gregorian'); + expect(gd._fullData[1].transforms[0].valuecalendar).toBe('nepali'); + }); + }); + + describe('registered subplot (gl3d)', function() { + var gd; + + var mock = require('@mocks/gl3d_world-cals'); + // just pick out the scatter3d trace + mock.data = [mock.data[1]]; + var xaxisCalendar = mock.layout.scene.xaxis.calendar; + var zDataCalendar = mock.data[0].zcalendar; + + beforeEach(function(done) { + gd = createGraphDiv(); + Plotly.plot(gd, mock.data, mock.layout).then(done); + }); + + afterEach(destroyGraphDiv); + + it('should graph gl3d axes and 3d plot types with calendars', function() { + expect(xaxisCalendar).toBeDefined(); + expect(zDataCalendar).toBeDefined(); + + expect(gd._fullLayout.scene.xaxis.calendar).toBe(xaxisCalendar); + expect(gd._fullData[0].zcalendar).toBe(zDataCalendar); + }); + }); +}; diff --git a/test/jasmine/assets/custom_assertions.js b/test/jasmine/assets/custom_assertions.js index 756c0b8f290..cd6832fe03f 100644 --- a/test/jasmine/assets/custom_assertions.js +++ b/test/jasmine/assets/custom_assertions.js @@ -73,3 +73,11 @@ exports.assertNodeDisplay = function(sel, expectation, msg) { .toBe(expectation[i], msg + ' display ' + '(item ' + i + ')'); }); }; + +exports.checkTicks = function(axLetter, vals, msg) { + var selection = d3.selectAll('.' + axLetter + 'tick text'); + expect(selection.size()).toBe(vals.length); + selection.each(function(d, i) { + expect(d3.select(this).text()).toBe(vals[i], msg + ': ' + i); + }); +}; diff --git a/test/jasmine/assets/custom_matchers.js b/test/jasmine/assets/custom_matchers.js index f56255d82a2..c8aaf5c6602 100644 --- a/test/jasmine/assets/custom_matchers.js +++ b/test/jasmine/assets/custom_matchers.js @@ -1,10 +1,25 @@ +/* + * custom_matchers - to be included in karma.conf.js, so it can + * add these matchers to jasmine globally and all suites have access. + * + * Also adds `.negateIf` which is not a matcher but a conditional `.not`: + * + * expect(x).negateIf(condition).toBe(0); + * + * is equivalent to: + * + * if(condition) expect(x).toBe(0); + * else expect(x).not.toBe(0); + */ + 'use strict'; var isNumeric = require('fast-isnumeric'); -var Lib = require('../../../src/lib'); +var isPlainObject = require('../../../src/lib/is_plain_object'); +var extendDeep = require('../../../src/lib/extend').extendDeep; var deepEqual = require('deep-equal'); -module.exports = { +var matchers = { // toEqual except with sparse arrays populated. This arises because: // // var x = new Array(2) @@ -21,7 +36,7 @@ module.exports = { for(i = 0; i < x.length; i++) { x[i] = x[i]; } - } else if(Lib.isPlainObject(x)) { + } else if(isPlainObject(x)) { var keys = Object.keys(x); for(i = 0; i < keys.length; i++) { populateUndefinedArrayEls(x[keys[i]]); @@ -32,8 +47,8 @@ module.exports = { return { compare: function(actual, expected, msgExtra) { - var actualExpanded = populateUndefinedArrayEls(Lib.extendDeep({}, actual)); - var expectedExpanded = populateUndefinedArrayEls(Lib.extendDeep({}, expected)); + var actualExpanded = populateUndefinedArrayEls(extendDeep({}, actual)); + var expectedExpanded = populateUndefinedArrayEls(extendDeep({}, expected)); var passed = deepEqual(actualExpanded, expectedExpanded); @@ -165,3 +180,12 @@ function coercePosition(precision) { function arrayToStr(array) { return '[ ' + array.join(', ') + ' ]'; } + +beforeAll(function() { + jasmine.addMatchers(matchers); + + jasmine.Expectation.prototype.negateIf = function(negate) { + if(negate) return this.not; + return this; + }; +}); diff --git a/test/jasmine/bundle_tests/component_first_test.js b/test/jasmine/bundle_tests/component_first_test.js new file mode 100644 index 00000000000..0b6bd24e9da --- /dev/null +++ b/test/jasmine/bundle_tests/component_first_test.js @@ -0,0 +1,15 @@ +var Plotly = require('@lib/core'); +var Bar = require('@lib/bar'); +var Scatter3d = require('@lib/scatter3d'); +var Filter = require('@lib/filter'); +var Calendars = require('@lib/calendars'); + +var checkComponent = require('../assets/check_component'); + +describe('Bundle with a component loaded before traces and transforms', function() { + 'use strict'; + + Plotly.register([Calendars, Filter, Scatter3d, Bar]); + + checkComponent(Plotly); +}); diff --git a/test/jasmine/bundle_tests/component_last_test.js b/test/jasmine/bundle_tests/component_last_test.js new file mode 100644 index 00000000000..f0227ca102e --- /dev/null +++ b/test/jasmine/bundle_tests/component_last_test.js @@ -0,0 +1,15 @@ +var Plotly = require('@lib/core'); +var Bar = require('@lib/bar'); +var Scatter3d = require('@lib/scatter3d'); +var Filter = require('@lib/filter'); +var Calendars = require('@lib/calendars'); + +var checkComponent = require('../assets/check_component'); + +describe('Bundle with a component loaded after traces and transforms', function() { + 'use strict'; + + Plotly.register([Bar, Scatter3d, Filter, Calendars]); + + checkComponent(Plotly); +}); diff --git a/test/jasmine/bundle_tests/core_test.js b/test/jasmine/bundle_tests/core_test.js index 0079548ba1c..fbbbe6199b9 100644 --- a/test/jasmine/bundle_tests/core_test.js +++ b/test/jasmine/bundle_tests/core_test.js @@ -8,11 +8,13 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); describe('Bundle with core only', function() { 'use strict'; + var gd; var mock = require('@mocks/bar_line.json'); beforeEach(function(done) { - Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(done); + gd = createGraphDiv(); + Plotly.plot(gd, mock.data, mock.layout).then(done); }); afterEach(destroyGraphDiv); @@ -28,4 +30,11 @@ describe('Bundle with core only', function() { expect(nodes.size()).toEqual(0); }); + + it('should not have calendar attributes', function() { + // calendars is a register-able component that we have not registered + expect(gd._fullLayout.calendar).toBeUndefined(); + expect(gd._fullLayout.xaxis.calendar).toBeUndefined(); + expect(gd._fullData[0].xcalendar).toBeUndefined(); + }); }); diff --git a/test/jasmine/bundle_tests/requirejs_test.js b/test/jasmine/bundle_tests/requirejs_test.js index ab6228e0e83..7d300a69298 100644 --- a/test/jasmine/bundle_tests/requirejs_test.js +++ b/test/jasmine/bundle_tests/requirejs_test.js @@ -1,6 +1,9 @@ describe('plotly.js + require.js', function() { 'use strict'; + // Note: this test doesn't have access to custom_matchers.js + // so you can only use standard jasmine matchers here. + it('should preserve require.js globals', function() { expect(window.requirejs).toBeDefined(); expect(window.define).toBeDefined(); diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index 8145b5dfb89..ab9d38a3a09 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -99,6 +99,7 @@ var pathToStrictD3 = path.join(__dirname, '..', '..', 'tasks', 'util', 'strict_d var pathToMain = path.join(__dirname, '..', '..', 'lib', 'index.js'); var pathToJQuery = path.join(__dirname, 'assets', 'jquery-1.8.3.min.js'); var pathToIE9mock = path.join(__dirname, 'assets', 'ie9_mock.js'); +var pathToCustomMatchers = path.join(__dirname, 'assets', 'custom_matchers.js'); function func(config) { @@ -132,8 +133,8 @@ func.defaultConfig = { // list of files / patterns to load in the browser // - // N.B. this field is filled below - files: [], + // N.B. the rest of this field is filled below + files: [pathToCustomMatchers], // list of files / pattern to exclude exclude: [], @@ -220,16 +221,21 @@ func.defaultConfig = { } }; +func.defaultConfig.preprocessors[pathToCustomMatchers] = ['browserify']; + if(isFullSuite) { func.defaultConfig.files.push(pathToJQuery); func.defaultConfig.preprocessors[testFileGlob] = ['browserify']; } else if(isBundleTest) { switch(basename(testFileGlob)) { case 'requirejs': + // browserified custom_matchers doesn't work with this route + // so clear them out of the files and preprocessors func.defaultConfig.files = [ constants.pathToRequireJS, constants.pathToRequireJSFixture ]; + delete func.defaultConfig.preprocessors[pathToCustomMatchers]; break; case 'ie9': // load ie9_mock.js before plotly.js+test bundle diff --git a/test/jasmine/tests/animate_test.js b/test/jasmine/tests/animate_test.js index bcb93fe4ca9..e7edbef2760 100644 --- a/test/jasmine/tests/animate_test.js +++ b/test/jasmine/tests/animate_test.js @@ -707,6 +707,51 @@ describe('Animate API details', function() { }); }); +describe('Animating multiple axes', function() { + 'use strict'; + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('updates ranges of secondary axes', function(done) { + Plotly.plot(gd, [ + {y: [1, 2, 3]}, + {y: [1, 2, 3], yaxis: 'y2'} + ], { + yaxis: {range: [0, 5]}, + yaxis2: {range: [-1, 4]} + }) + .then(function() { + expect(gd._fullLayout.yaxis.range).toEqual([0, 5]); + expect(gd._fullLayout.yaxis2.range).toEqual([-1, 4]); + + return Plotly.animate(gd, [ + {layout: {'yaxis.range': [2, 3]}}, + {layout: {'yaxis2.range': [1, 2]}} + ], { + // TODO: if the durations are the same, yaxis.range gets some + // random endpoint, often close to what it's supposed to be but + // sometimes very far away. + frame: {redraw: false, duration: 60}, + transition: {duration: 30} + }); + }) + .then(function() { + expect(gd._fullLayout.yaxis.range).toEqual([2, 3]); + expect(gd._fullLayout.yaxis2.range).toEqual([1, 2]); + }) + .catch(fail) + .then(done); + }); +}); + describe('non-animatable fallback', function() { 'use strict'; var gd; diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js index cad65de3123..e3f5fc44e1d 100644 --- a/test/jasmine/tests/annotations_test.js +++ b/test/jasmine/tests/annotations_test.js @@ -9,7 +9,6 @@ var HOVERMINTIME = require('@src/components/fx').constants.HOVERMINTIME; var DBLCLICKDELAY = require('@src/constants/interactions').DBLCLICKDELAY; var d3 = require('d3'); -var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); @@ -561,10 +560,6 @@ describe('annotations autorange', function() { var mock; var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); mock = Lib.extendDeep({}, require('@mocks/annotations-autorange.json')); @@ -908,10 +903,6 @@ describe('annotation effects', function() { function arrowDrag() { return gd.querySelector('.annotation-arrow-g>.anndrag'); } function textBox() { return gd.querySelector('.annotation-text-g'); } - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function makePlot(annotations, config) { gd = createGraphDiv(); diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index d5594d4c5d6..f2ebb0f4741 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -10,7 +10,6 @@ var Axes = require('@src/plots/cartesian/axes'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var failTest = require('../assets/fail_test'); @@ -577,7 +576,6 @@ describe('Test axes', function() { beforeEach(function() { gd = createGraphDiv(); - jasmine.addMatchers(customMatchers); }); afterEach(destroyGraphDiv); diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 6de79cdf40a..b9257e4e21c 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -10,7 +10,9 @@ var Axes = require('@src/plots/cartesian/axes'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); -var customMatchers = require('../assets/custom_matchers'); +var checkTicks = require('../assets/custom_assertions').checkTicks; + +var d3 = require('d3'); describe('Bar.supplyDefaults', function() { 'use strict'; @@ -150,10 +152,6 @@ describe('Bar.supplyDefaults', function() { describe('bar calc / setPositions', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - it('should fill in calc pt fields (stack case)', function() { var gd = mockBarPlot([{ y: [2, 1, 2] @@ -265,10 +263,6 @@ describe('bar calc / setPositions', function() { describe('Bar.calc', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - it('should guard against invalid base items', function() { var gd = mockBarPlot([{ base: [null, 1, 2], @@ -324,10 +318,6 @@ describe('Bar.calc', function() { describe('Bar.setPositions', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - it('should guard against invalid offset items', function() { var gd = mockBarPlot([{ offset: [null, 0, 1], @@ -762,8 +752,10 @@ describe('Bar.setPositions', function() { describe('A bar plot', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); + var gd; + + beforeEach(function() { + gd = createGraphDiv(); }); afterEach(destroyGraphDiv); @@ -830,15 +822,13 @@ describe('A bar plot', function() { } it('should show bar texts (inside case)', function(done) { - var gd = createGraphDiv(), - data = [{ - y: [10, 20, 30], - type: 'bar', - text: ['1', 'Very very very very very long bar text'], - textposition: 'inside', - }], - layout = { - }; + var data = [{ + y: [10, 20, 30], + type: 'bar', + text: ['1', 'Very very very very very long bar text'], + textposition: 'inside', + }]; + var layout = {}; Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), @@ -862,16 +852,15 @@ describe('A bar plot', function() { }); it('should show bar texts (outside case)', function(done) { - var gd = createGraphDiv(), - data = [{ - y: [10, -20, 30], - type: 'bar', - text: ['1', 'Very very very very very long bar text'], - textposition: 'outside', - }], - layout = { - barmode: 'relative' - }; + var data = [{ + y: [10, -20, 30], + type: 'bar', + text: ['1', 'Very very very very very long bar text'], + textposition: 'outside', + }]; + var layout = { + barmode: 'relative' + }; Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), @@ -896,15 +885,13 @@ describe('A bar plot', function() { }); it('should show bar texts (horizontal case)', function(done) { - var gd = createGraphDiv(), - data = [{ - x: [10, -20, 30], - type: 'bar', - text: ['Very very very very very long bar text', -20], - textposition: 'outside', - }], - layout = { - }; + var data = [{ + x: [10, -20, 30], + type: 'bar', + text: ['Very very very very very long bar text', -20], + textposition: 'outside', + }]; + var layout = {}; Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), @@ -929,17 +916,16 @@ describe('A bar plot', function() { }); it('should show bar texts (barnorm case)', function(done) { - var gd = createGraphDiv(), - data = [{ - x: [100, -100, 100], - type: 'bar', - text: [100, -100, 100], - textposition: 'outside', - }], - layout = { - barmode: 'relative', - barnorm: 'percent' - }; + var data = [{ + x: [100, -100, 100], + type: 'bar', + text: [100, -100, 100], + textposition: 'outside', + }]; + var layout = { + barmode: 'relative', + barnorm: 'percent' + }; Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), @@ -964,8 +950,7 @@ describe('A bar plot', function() { }); it('should be able to restyle', function(done) { - var gd = createGraphDiv(), - mock = Lib.extendDeep({}, require('@mocks/bar_attrs_relative')); + var mock = Lib.extendDeep({}, require('@mocks/bar_attrs_relative')); Plotly.plot(gd, mock.data, mock.layout).then(function() { var cd = gd.calcdata; @@ -1114,27 +1099,26 @@ describe('A bar plot', function() { }); it('should coerce text-related attributes', function(done) { - var gd = createGraphDiv(), - data = [{ - y: [10, 20, 30, 40], - type: 'bar', - text: ['T1P1', 'T1P2', 13, 14], - textposition: ['inside', 'outside', 'auto', 'BADVALUE'], - textfont: { - family: ['"comic sans"'], - color: ['red', 'green'], - }, - insidetextfont: { - size: [8, 12, 16], - color: ['black'], - }, - outsidetextfont: { - size: [null, 24, 32] - } - }], - layout = { - font: {family: 'arial', color: 'blue', size: 13} - }; + var data = [{ + y: [10, 20, 30, 40], + type: 'bar', + text: ['T1P1', 'T1P2', 13, 14], + textposition: ['inside', 'outside', 'auto', 'BADVALUE'], + textfont: { + family: ['"comic sans"'], + color: ['red', 'green'], + }, + insidetextfont: { + size: [8, 12, 16], + color: ['black'], + }, + outsidetextfont: { + size: [null, 24, 32] + } + }]; + var layout = { + font: {family: 'arial', color: 'blue', size: 13} + }; var expected = { y: [10, 20, 30, 40], @@ -1194,6 +1178,72 @@ describe('A bar plot', function() { .catch(fail) .then(done); }); + + it('can change orientation and correctly sets axis types', function(done) { + function checkBarsMatch(dims, msg) { + var bars = d3.selectAll('.bars .point'); + var bbox1 = bars.node().getBoundingClientRect(); + bars.each(function(d, i) { + if(!i) return; + var bbox = this.getBoundingClientRect(); + ['left', 'right', 'top', 'bottom', 'width', 'height'].forEach(function(dim) { + expect(bbox[dim]).negateIf(dims.indexOf(dim) === -1) + .toBeWithin(bbox1[dim], 0.1, msg + ' (' + i + '): ' + dim); + }); + }); + } + + Plotly.newPlot(gd, [{ + x: ['a', 'b', 'c'], + y: [1, 2, 3], + type: 'bar' + }], { + width: 400, height: 400 + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'initial x'); + checkTicks('y', ['0', '0.5', '1', '1.5', '2', '2.5', '3'], 'initial y'); + + checkBarsMatch(['bottom', 'width'], 'initial'); + + // turn implicit "v" into explicit "v" - a noop but specifically + // for orientation this was broken at one point... + return Plotly.restyle(gd, {orientation: 'v'}); + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'explicit v x'); + checkTicks('y', ['0', '0.5', '1', '1.5', '2', '2.5', '3'], 'explicit v y'); + + checkBarsMatch(['bottom', 'width'], 'explicit v'); + + // back to implicit v + return Plotly.restyle(gd, {orientation: null}); + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'implicit v x'); + checkTicks('y', ['0', '0.5', '1', '1.5', '2', '2.5', '3'], 'implicit v y'); + + checkBarsMatch(['bottom', 'width'], 'implicit v'); + + return Plotly.restyle(gd, {orientation: 'h'}); + }) + .then(function() { + checkTicks('x', ['0', '1', '2', '3'], 'h x'); + checkTicks('y', ['a', 'b', 'c'], 'h y'); + + checkBarsMatch(['left', 'height'], 'initial'); + + return Plotly.restyle(gd, {orientation: 'v'}); + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'final x'); + checkTicks('y', ['0', '0.5', '1', '1.5', '2', '2.5', '3'], 'final y'); + + checkBarsMatch(['bottom', 'width'], 'final'); + }) + .catch(fail) + .then(done); + }); }); describe('bar hover', function() { @@ -1201,10 +1251,6 @@ describe('bar hover', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); function getPointData(gd) { @@ -1464,8 +1510,6 @@ function mockBarPlot(dataWithoutTraceType, layout) { } function assertArrayField(calcData, prop, expectation) { - // Note that this functions requires to add `customMatchers` to jasmine - // matchers; i.e: `jasmine.addMatchers(customMatchers);`. var values = Lib.nestedProperty(calcData, prop).get(); if(!Array.isArray(values)) values = [values]; @@ -1473,8 +1517,6 @@ function assertArrayField(calcData, prop, expectation) { } function assertPointField(calcData, prop, expectation) { - // Note that this functions requires to add `customMatchers` to jasmine - // matchers; i.e: `jasmine.addMatchers(customMatchers);`. var values = []; calcData.forEach(function(calcTrace) { @@ -1489,8 +1531,6 @@ function assertPointField(calcData, prop, expectation) { } function assertTraceField(calcData, prop, expectation) { - // Note that this functions requires to add `customMatchers` to jasmine - // matchers; i.e: `jasmine.addMatchers(customMatchers);`. var values = calcData.map(function(calcTrace) { return Lib.nestedProperty(calcTrace[0], prop).get(); }); diff --git a/test/jasmine/tests/carpet_test.js b/test/jasmine/tests/carpet_test.js index 59a9de96018..a302de085e5 100644 --- a/test/jasmine/tests/carpet_test.js +++ b/test/jasmine/tests/carpet_test.js @@ -7,7 +7,6 @@ var smoothFill2D = require('@src/traces/carpet/smooth_fill_2d_array'); var smoothFill = require('@src/traces/carpet/smooth_fill_array'); var d3 = require('d3'); -var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); @@ -215,8 +214,6 @@ describe('supplyDefaults visibility check', function() { describe('carpet smooth_fill_2d_array', function() { var _; - beforeAll(function() { jasmine.addMatchers(customMatchers); }); - it('fills in all points trivially', function() { // Given only corners, should just propagate the constant throughout: expect(smoothFill2D( @@ -381,8 +378,6 @@ describe('carpet smooth_fill_2d_array', function() { describe('smooth_fill_array', function() { var _; - beforeAll(function() { jasmine.addMatchers(customMatchers); }); - it('fills in via linear interplation', function() { expect(smoothFill([_, _, 2, 3, _, _, 6, 7, _, _, 10, 11, _])) .toBeCloseToArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index e59b79188dd..019cbeb519b 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -8,7 +8,6 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); var failTest = require('../assets/fail_test'); -var customMatchers = require('../assets/custom_matchers'); var selectButton = require('../assets/modebar_button'); var drag = require('../assets/drag'); var doubleClick = require('../assets/double_click'); @@ -85,8 +84,6 @@ describe('main plot pan', function() { it('should respond to pan interactions', function(done) { - jasmine.addMatchers(customMatchers); - var precision = 5; var buttonPan = selectButton(modeBar, 'pan2d'); @@ -182,10 +179,6 @@ describe('main plot pan', function() { describe('axis zoom/pan and main plot zoom', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index d6d192efafc..77125d01bc9 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -8,7 +8,6 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); var getRectCenter = require('../assets/get_rect_center'); -var customMatchers = require('../assets/custom_matchers'); // cartesian click events events use the hover data // from the mousemove events and then simulate @@ -52,10 +51,6 @@ describe('Test click interactions:', function() { var autoRangeX = [-3.011967491973726, 2.1561305597186564], autoRangeY = [-0.9910086301469277, 1.389382716298284]; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); mockCopy = Lib.extendDeep({}, mock); diff --git a/test/jasmine/tests/contour_test.js b/test/jasmine/tests/contour_test.js index 764a5d94218..989a164eeb4 100644 --- a/test/jasmine/tests/contour_test.js +++ b/test/jasmine/tests/contour_test.js @@ -1,3 +1,4 @@ +var Plotly = require('@lib/index'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); @@ -5,7 +6,10 @@ var Contour = require('@src/traces/contour'); var makeColorMap = require('@src/traces/contour/make_color_map'); var colorScales = require('@src/components/colorscale/scales'); -var customMatchers = require('../assets/custom_matchers'); +var fail = require('../assets/fail_test'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var checkTicks = require('../assets/custom_assertions').checkTicks; describe('contour defaults', function() { @@ -173,10 +177,6 @@ describe('contour makeColorMap', function() { describe('contour calc', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _calc(opts) { var base = { type: 'contour' }, trace = Lib.extendFlat({}, base, opts), @@ -344,3 +344,46 @@ describe('contour calc', function() { }); }); }); + +describe('contour edits', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + afterEach(destroyGraphDiv); + + it('can restyle x/y to different types', function(done) { + Plotly.newPlot(gd, [{ + type: 'contour', + x: [1, 2, 3], + y: [3, 4, 5], + z: [[10, 11, 12], [13, 14, 15], [17, 18, 19]] + }], {width: 400, height: 400}) + .then(function() { + checkTicks('x', ['1', '1.5', '2', '2.5', '3'], 'linear x'); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + checkTicks('y', ['3', '3.5', '4', '4.5', '5'], 'linear y'); + expect(gd._fullLayout.yaxis.type).toBe('linear'); + + return Plotly.restyle(gd, {x: [['a', 'b', 'c']], y: [['2016-01', '2016-02', '2016-03']]}); + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'category x'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + checkTicks('y', ['Jan 102016', 'Jan 24', 'Feb 7', 'Feb 21'], 'date y'); + expect(gd._fullLayout.yaxis.type).toBe('date'); + + // should be a noop, but one that raises no errors! + return Plotly.relayout(gd, {'xaxis.type': '-', 'yaxis.type': '-'}); + }) + .then(function() { + checkTicks('x', ['a', 'b', 'c'], 'category x #2'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + checkTicks('y', ['Jan 102016', 'Jan 24', 'Feb 7', 'Feb 21'], 'date y #2'); + expect(gd._fullLayout.yaxis.type).toBe('date'); + }) + .catch(fail) + .then(done); + }); +}); diff --git a/test/jasmine/tests/drawing_test.js b/test/jasmine/tests/drawing_test.js index fe6821f6246..899ecef6527 100644 --- a/test/jasmine/tests/drawing_test.js +++ b/test/jasmine/tests/drawing_test.js @@ -5,15 +5,10 @@ var svgTextUtils = require('@src/lib/svg_text_utils'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); -var customMatchers = require('../assets/custom_matchers'); describe('Drawing', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - describe('setClipUrl', function() { beforeEach(function() { diff --git a/test/jasmine/tests/errorbars_test.js b/test/jasmine/tests/errorbars_test.js new file mode 100644 index 00000000000..3f6f9bd49bf --- /dev/null +++ b/test/jasmine/tests/errorbars_test.js @@ -0,0 +1,53 @@ +var Plotly = require('@lib/index'); + +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var fail = require('../assets/fail_test'); + + +describe('errorbar plotting', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should autorange to the visible bars and remove invisible bars', function(done) { + function check(xrange, yrange, xcount, ycount) { + var xa = gd._fullLayout.xaxis; + var ya = gd._fullLayout.yaxis; + expect(xa.range).toBeCloseToArray(xrange, 3); + expect(ya.range).toBeCloseToArray(yrange, 3); + + expect(d3.selectAll('.xerror').size()).toBe(xcount); + expect(d3.selectAll('.yerror').size()).toBe(ycount); + } + Plotly.newPlot(gd, [{ + y: [1, 2, 3], + error_x: {type: 'constant', value: 0.5}, + error_y: {type: 'sqrt'} + }], { + width: 400, height: 400 + }) + .then(function() { + check([-0.6667, 2.6667], [-0.2629, 4.9949], 3, 3); + return Plotly.restyle(gd, {'error_x.visible': false}); + }) + .then(function() { + check([-0.1511, 2.1511], [-0.2629, 4.9949], 0, 3); + return Plotly.restyle(gd, {'error_y.visible': false}); + }) + .then(function() { + check([-0.1511, 2.1511], [0.8451, 3.1549], 0, 0); + return Plotly.restyle(gd, {'error_x.visible': true, 'error_y.visible': true}); + }) + .then(function() { + check([-0.6667, 2.6667], [-0.2629, 4.9949], 3, 3); + }) + .catch(fail) + .then(done); + }); +}); diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index 0641f603309..effb8ebc64e 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -11,7 +11,6 @@ var topojsonUtils = require('@src/lib/topojson_utils'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var getClientPosition = require('../assets/get_client_position'); var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); @@ -1060,8 +1059,6 @@ describe('Test event property of interactions on a geo plot:', function() { nearPos; beforeAll(function(done) { - jasmine.addMatchers(customMatchers); - gd = createGraphDiv(); mockCopy = Lib.extendDeep({}, mock); Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index c3c8035b184..7ab364f11aa 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -4,7 +4,6 @@ var Lib = require('@src/lib'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var fail = require('../assets/fail_test.js'); // cartesian click events events use the hover data @@ -196,10 +195,6 @@ describe('Test hover and click interactions', function() { }; } - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/gl3dlayout_test.js b/test/jasmine/tests/gl3dlayout_test.js index 31099fd7a46..4ae24334afd 100644 --- a/test/jasmine/tests/gl3dlayout_test.js +++ b/test/jasmine/tests/gl3dlayout_test.js @@ -1,8 +1,13 @@ +var Plotly = require('@lib/index'); var Gl3d = require('@src/plots/gl3d'); var tinycolor = require('tinycolor2'); var Color = require('@src/components/color'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var fail = require('../assets/fail_test'); + describe('Test Gl3d layout defaults', function() { 'use strict'; @@ -227,7 +232,8 @@ describe('Test Gl3d layout defaults', function() { supplyLayoutDefaults(layoutIn, layoutOut, fullData); expect(layoutIn.scene).toEqual({ - aspectratio: { x: 1, y: 1, z: 1 } + aspectratio: { x: 1, y: 1, z: 1 }, + aspectmode: 'auto' }); }); @@ -262,3 +268,35 @@ describe('Test Gl3d layout defaults', function() { }); }); }); + +describe('Gl3d layout edge cases', function() { + var gd; + + beforeEach(function() {gd = createGraphDiv(); }); + afterEach(destroyGraphDiv); + + it('should handle auto aspect ratio correctly on data changes', function(done) { + Plotly.plot(gd, [{x: [1, 2], y: [1, 3], z: [1, 4], type: 'scatter3d'}]) + .then(function() { + var aspect = gd.layout.scene.aspectratio; + expect(aspect.x).toBeCloseTo(0.5503); + expect(aspect.y).toBeCloseTo(1.1006); + expect(aspect.z).toBeCloseTo(1.6510); + + return Plotly.restyle(gd, 'x', [[1, 6]]); + }) + .then(function() { + /* + * Check that now it's the same as if you had just made the plot with + * x: [1, 6] in the first place + */ + var aspect = gd.layout.scene.aspectratio; + expect(aspect.x).toBeCloseTo(1.6091); + expect(aspect.y).toBeCloseTo(0.6437); + expect(aspect.z).toBeCloseTo(0.9655); + }) + .catch(fail) + .then(done); + }); + +}); diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 7e06a5de92f..1384eac3e5a 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -10,7 +10,6 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); var selectButton = require('../assets/modebar_button'); -var customMatchers = require('../assets/custom_matchers'); var delay = require('../assets/delay'); function countCanvases() { @@ -431,10 +430,6 @@ describe('Test gl3d modebar handlers', function() { expect(camera.eye.z).toBeCloseTo(eyeZ); } - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function(done) { gd = createGraphDiv(); @@ -829,10 +824,6 @@ describe('Test gl2d plots', function() { var mock = require('@mocks/gl2d_10.json'); - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); @@ -1308,10 +1299,6 @@ describe('Test gl plot side effects', function() { describe('Test gl2d interactions', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); @@ -1370,10 +1357,6 @@ describe('Test gl2d interactions', function() { describe('Test gl3d annotations', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index a1b2c89cad7..81e7737c736 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -9,7 +9,6 @@ var Heatmap = require('@src/traces/heatmap'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); describe('heatmap supplyDefaults', function() { @@ -294,10 +293,6 @@ describe('heatmap convertColumnXYZ', function() { describe('heatmap calc', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _calc(opts) { var base = { type: 'heatmap' }, trace = Lib.extendFlat({}, base, opts), @@ -599,10 +594,6 @@ describe('heatmap hover', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _hover(gd, xval, yval) { var fullLayout = gd._fullLayout, calcData = gd.calcdata, diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index efd03c6da4b..8ee79f9fbcb 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -7,7 +7,6 @@ var calc = require('@src/traces/histogram/calc'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); describe('Test histogram', function() { @@ -163,10 +162,6 @@ describe('Test histogram', function() { describe('calc', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _calc(opts, extraTraces) { var base = { type: 'histogram' }; var trace = Lib.extendFlat({}, base, opts); @@ -513,5 +508,51 @@ describe('Test histogram', function() { expect(gd._fullData[0].xbins).toEqual({start: 1, end: 6, size: 1}); expect(gd._fullData[0].autobinx).toBe(false); }); + + it('allows changing axis type with new x data', function() { + var x1 = [1, 1, 1, 1, 2, 2, 2, 3, 3, 4]; + var x2 = ['2017-01-01', '2017-01-01', '2017-01-01', '2017-01-02', '2017-01-02', '2017-01-03']; + + Plotly.newPlot(gd, [{x: x1, type: 'histogram'}]); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([0.5, 4.5], 3); + + Plotly.restyle(gd, {x: [x2]}); + expect(gd._fullLayout.xaxis.type).toBe('date'); + expect(gd._fullLayout.xaxis.range).toEqual(['2016-12-31 12:00', '2017-01-03 12:00']); + }); + + it('can resize a plot with several histograms', function(done) { + Plotly.newPlot(gd, [{ + type: 'histogram', + x: [1, 1, 1, 1, 2, 2, 2, 3, 3, 4] + }, { + type: 'histogram', + x: [1, 1, 1, 1, 2, 2, 2, 3, 3, 4] + }], { + width: 400, + height: 400 + }) + .then(function() { + expect(gd._fullLayout.width).toBe(400); + expect(gd._fullLayout.height).toBe(400); + + gd._fullData.forEach(function(trace, i) { + expect(trace._autoBinFinished).toBeUndefined(i); + }); + + return Plotly.relayout(gd, {width: 500, height: 500}); + }) + .then(function() { + expect(gd._fullLayout.width).toBe(500); + expect(gd._fullLayout.height).toBe(500); + + gd._fullData.forEach(function(trace, i) { + expect(trace._autoBinFinished).toBeUndefined(i); + }); + }) + .catch(fail) + .then(done); + }); }); }); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index b28f044a0ed..2acd558e0f4 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1077,13 +1077,13 @@ describe('Test hover label custom styling:', function() { expect(g.size()).toBe(0); } else { var path = g.select('path'); - expect(path.style('fill')).toEqual(expectation.path[0], 'bgcolor'); - expect(path.style('stroke')).toEqual(expectation.path[1], 'bordercolor'); + expect(path.style('fill')).toBe(expectation.path[0], 'bgcolor'); + expect(path.style('stroke')).toBe(expectation.path[1], 'bordercolor'); var text = g.select({hovertext: 'text.nums', axistext: 'text'}[className]); - expect(parseInt(text.style('font-size'))).toEqual(expectation.text[0], 'font.size'); - expect(text.style('font-family').split(',')[0]).toEqual(expectation.text[1], 'font.family'); - expect(text.style('fill')).toEqual(expectation.text[2], 'font.color'); + expect(parseInt(text.style('font-size'))).toBe(expectation.text[0], 'font.size'); + expect(text.style('font-family').split(',')[0]).toBe(expectation.text[1], 'font.family'); + expect(text.style('fill')).toBe(expectation.text[2], 'font.color'); } } diff --git a/test/jasmine/tests/hover_spikeline_test.js b/test/jasmine/tests/hover_spikeline_test.js index dad000be93f..e983609b6ae 100644 --- a/test/jasmine/tests/hover_spikeline_test.js +++ b/test/jasmine/tests/hover_spikeline_test.js @@ -7,15 +7,10 @@ var Lib = require('@src/lib'); var fail = require('../assets/fail_test'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); describe('spikeline', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); describe('hover', function() { diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index e994811d6ae..0d8ade22f7a 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -13,7 +13,6 @@ var fail = require('../assets/fail_test'); var delay = require('../assets/delay'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); describe('legend defaults', function() { @@ -510,13 +509,34 @@ describe('legend anchor utils:', function() { describe('legend relayout update', function() { 'use strict'; + var gd; + var mock = require('@mocks/0.json'); + beforeEach(function() { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); + it('should hide and show the legend', function(done) { + var mockCopy = Lib.extendDeep({}, mock); + Plotly.newPlot(gd, mockCopy.data, mockCopy.layout) + .then(function() { + expect(d3.selectAll('g.legend').size()).toBe(1); + return Plotly.relayout(gd, {showlegend: false}); + }) + .then(function() { + expect(d3.selectAll('g.legend').size()).toBe(0); + return Plotly.relayout(gd, {showlegend: true}); + }) + .then(function() { + expect(d3.selectAll('g.legend').size()).toBe(1); + }) + .catch(fail) + .then(done); + }); + it('should update border styling', function(done) { - var mock = require('@mocks/0.json'), - mockCopy = Lib.extendDeep({}, mock), - gd = createGraphDiv(); + var mockCopy = Lib.extendDeep({}, mock); function assertLegendStyle(bgColor, borderColor, borderWidth) { var node = d3.select('g.legend').select('rect'); @@ -547,9 +567,9 @@ describe('legend relayout update', function() { return Plotly.relayout(gd, 'paper_bgcolor', 'blue'); }).then(function() { assertLegendStyle('rgb(0, 0, 255)', 'rgb(255, 0, 0)', 10); - - done(); - }); + }) + .catch(fail) + .then(done); }); }); @@ -579,10 +599,6 @@ describe('legend orientation change:', function() { describe('legend restyle update', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); it('should update trace toggle background rectangle', function(done) { diff --git a/test/jasmine/tests/lib_geometry2d_test.js b/test/jasmine/tests/lib_geometry2d_test.js index 475bdad75b3..6bc8a81f09f 100644 --- a/test/jasmine/tests/lib_geometry2d_test.js +++ b/test/jasmine/tests/lib_geometry2d_test.js @@ -1,5 +1,4 @@ var geom2d = require('@src/lib/geometry2d'); -var customMatchers = require('../assets/custom_matchers'); var Drawing = require('@src/components/drawing'); // various reversals of segments and endpoints that should all give identical results @@ -15,10 +14,6 @@ function permute(_inner, x1, y1, x2, y2, x3, y3, x4, y4, expected) { } describe('segmentsIntersect', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function check(x1, y1, x2, y2, x3, y3, x4, y4, expected) { // test swapping x/y var result1 = geom2d.segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4); @@ -58,10 +53,6 @@ describe('segmentsIntersect', function() { }); describe('segmentDistance', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function check(x1, y1, x2, y2, x3, y3, x4, y4, expected) { var result1 = geom2d.segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4); var result2 = geom2d.segmentDistance(y1, x1, y2, x2, y3, x3, y4, x4); @@ -97,7 +88,6 @@ describe('segmentDistance', function() { describe('getVisibleSegment', function() { beforeAll(function() { Drawing.makeTester(); - jasmine.addMatchers(customMatchers); }); var path; diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index e18d0ee6a28..4e97759e6b8 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -9,7 +9,6 @@ var PlotlyInternal = require('@src/plotly'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var Plots = PlotlyInternal.Plots; -var customMatchers = require('../assets/custom_matchers'); var failTest = require('../assets/fail_test'); describe('Test lib.js:', function() { @@ -471,10 +470,6 @@ describe('Test lib.js:', function() { }); describe('expandObjectPaths', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - it('returns the original object', function() { var x = {}; expect(Lib.expandObjectPaths(x)).toBe(x); @@ -846,7 +841,7 @@ describe('Test lib.js:', function() { }); describe('coerceFont', function() { - var fontAttrs = Plots.fontAttrs, + var fontAttrs = Plots.fontAttrs({}), extendFlat = Lib.extendFlat, coerceFont = Lib.coerceFont; @@ -1936,6 +1931,34 @@ describe('Test lib.js:', function() { expect(Lib.templateString('foo %{} %{}', {})).toEqual('foo '); }); }); + + describe('relativeAttr()', function() { + it('replaces the last part always', function() { + expect(Lib.relativeAttr('annotations[3].x', 'y')).toBe('annotations[3].y'); + expect(Lib.relativeAttr('x', 'z')).toBe('z'); + expect(Lib.relativeAttr('marker.line.width', 'colorbar.x')).toBe('marker.line.colorbar.x'); + }); + + it('ascends with ^', function() { + expect(Lib.relativeAttr('annotations[3].x', '^[2].z')).toBe('annotations[2].z'); + expect(Lib.relativeAttr('annotations[3].x', '^^margin')).toBe('margin'); + expect(Lib.relativeAttr('annotations[3].x', '^^margin.r')).toBe('margin.r'); + expect(Lib.relativeAttr('marker.line.width', '^colorbar.x')).toBe('marker.colorbar.x'); + }); + + it('fails on ascending too far', function() { + expect(function() { return Lib.relativeAttr('x', '^y'); }).toThrow(); + expect(function() { return Lib.relativeAttr('marker.line.width', '^^^colorbar.x'); }).toThrow(); + }); + + it('fails with malformed baseAttr', function() { + expect(function() { return Lib.relativeAttr('x[]', 'z'); }).toThrow(); + expect(function() { return Lib.relativeAttr('x.a]', 'z'); }).toThrow(); + expect(function() { return Lib.relativeAttr('x[a]', 'z'); }).toThrow(); + expect(function() { return Lib.relativeAttr('x[3].', 'z'); }).toThrow(); + expect(function() { return Lib.relativeAttr('x.y.', 'z'); }).toThrow(); + }); + }); }); describe('Queue', function() { @@ -1985,14 +2008,14 @@ describe('Queue', function() { }) .then(function() { expect(gd.undoQueue.index).toEqual(1); - expect(gd.undoQueue.queue[0].undo.args[0][1]['marker.color']).toEqual([undefined]); + expect(gd.undoQueue.queue[0].undo.args[0][1]['marker.color']).toEqual([null]); expect(gd.undoQueue.queue[0].redo.args[0][1]['marker.color']).toEqual('red'); return Plotly.relayout(gd, 'title', 'A title'); }) .then(function() { expect(gd.undoQueue.index).toEqual(2); - expect(gd.undoQueue.queue[1].undo.args[0][1].title).toEqual(undefined); + expect(gd.undoQueue.queue[1].undo.args[0][1].title).toEqual(null); expect(gd.undoQueue.queue[1].redo.args[0][1].title).toEqual('A title'); return Plotly.restyle(gd, 'mode', 'markers'); @@ -2001,10 +2024,10 @@ describe('Queue', function() { expect(gd.undoQueue.index).toEqual(2); expect(gd.undoQueue.queue[2]).toBeUndefined(); - expect(gd.undoQueue.queue[1].undo.args[0][1].mode).toEqual([undefined]); + expect(gd.undoQueue.queue[1].undo.args[0][1].mode).toEqual([null]); expect(gd.undoQueue.queue[1].redo.args[0][1].mode).toEqual('markers'); - expect(gd.undoQueue.queue[0].undo.args[0][1].title).toEqual(undefined); + expect(gd.undoQueue.queue[0].undo.args[0][1].title).toEqual(null); expect(gd.undoQueue.queue[0].redo.args[0][1].title).toEqual('A title'); return Plotly.restyle(gd, 'transforms[0]', { type: 'filter' }); diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js index 3f469d56c95..1cf2c05fa6f 100644 --- a/test/jasmine/tests/mapbox_test.js +++ b/test/jasmine/tests/mapbox_test.js @@ -9,7 +9,6 @@ var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); -var customMatchers = require('../assets/custom_matchers'); var failTest = require('../assets/fail_test'); var MAPBOX_ACCESS_TOKEN = require('@build/credentials.json').MAPBOX_ACCESS_TOKEN; @@ -295,10 +294,6 @@ describe('@noCI, mapbox plots', function() { var pointPos = [579, 276], blankPos = [650, 120]; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function(done) { gd = createGraphDiv(); diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 888b0b9e075..c6c08ec98d9 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -2,7 +2,6 @@ var d3 = require('d3'); var createModeBar = require('@src/components/modebar/modebar'); var manageModeBar = require('@src/components/modebar/manage'); -var customMatchers = require('../assets/custom_matchers'); var Plotly = require('@lib/index'); var Plots = require('@src/plots/plots'); @@ -652,10 +651,6 @@ describe('ModeBar', function() { describe('modebar on clicks', function() { var gd, modeBar, buttonClosest, buttonCompare, buttonToggle, hovermodeButtons; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); function assertRange(axName, expected) { diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index 5af83988b33..957d1388e6c 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -2,7 +2,6 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); var d3 = require('d3'); -var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); @@ -352,8 +351,6 @@ describe('Test event property of interactions on a pie plot:', function() { pointPos; beforeAll(function(done) { - jasmine.addMatchers(customMatchers); - gd = createGraphDiv(); mockCopy = Lib.extendDeep({}, mock); Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index c9035febbbc..600321d0064 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -2,6 +2,7 @@ var Plotly = require('@lib/index'); var PlotlyInternal = require('@src/plotly'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); +var Queue = require('@src/lib/queue'); var Scatter = require('@src/traces/scatter'); var Bar = require('@src/traces/bar'); var Legend = require('@src/components/legend'); @@ -11,10 +12,10 @@ var helpers = require('@src/plot_api/helpers'); var editTypes = require('@src/plot_api/edit_types'); var d3 = require('d3'); -var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); +var checkTicks = require('../assets/custom_assertions').checkTicks; describe('Test plot api', function() { @@ -107,15 +108,20 @@ describe('Test plot api', function() { describe('Plotly.relayout', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); + + // some of these tests use the undo/redo queue + // OK, this is weird... the undo/redo queue length is a global config only. + // It's ignored on the plot, even though the queue itself is per-plot. + // We may ditch this later, but probably not until v2 + Plotly.setPlotConfig({queueLength: 3}); }); - afterEach(destroyGraphDiv); + afterEach(function() { + destroyGraphDiv(); + Plotly.setPlotConfig({queueLength: 0}); + }); it('should update the plot clipPath if the plot is resized', function(done) { @@ -331,6 +337,162 @@ describe('Test plot api', function() { .catch(fail) .then(done); }); + + it('clears autorange when you modify a range or part of a range', function(done) { + var initialXRange; + var initialYRange; + + Plotly.plot(gd, [{x: [1, 2], y: [1, 2]}]) + .then(function() { + expect(gd.layout.xaxis.autorange).toBe(true); + expect(gd.layout.yaxis.autorange).toBe(true); + + initialXRange = gd.layout.xaxis.range.slice(); + initialYRange = gd.layout.yaxis.range.slice(); + + return Plotly.relayout(gd, {'xaxis.range': [0, 1], 'yaxis.range[1]': 3}); + }) + .then(function() { + expect(gd.layout.xaxis.autorange).toBe(false); + expect(gd.layout.xaxis.range).toEqual([0, 1]); + expect(gd.layout.yaxis.autorange).toBe(false); + expect(gd.layout.yaxis.range[1]).toBe(3); + + return Plotly.relayout(gd, {'xaxis.autorange': true, 'yaxis.autorange': true}); + }) + .then(function() { + expect(gd.layout.xaxis.range).toEqual(initialXRange); + expect(gd.layout.yaxis.range).toEqual(initialYRange); + + // finally, test that undoing autorange puts back the previous explicit range + return Queue.undo(gd); + }) + .then(function() { + expect(gd.layout.xaxis.autorange).toBe(false); + expect(gd.layout.xaxis.range).toEqual([0, 1]); + expect(gd.layout.yaxis.autorange).toBe(false); + expect(gd.layout.yaxis.range[1]).toBe(3); + }) + .catch(fail) + .then(done); + }); + + it('sets aspectmode to manual when you provide any aspectratio', function(done) { + Plotly.plot(gd, [{x: [1, 2], y: [1, 2], z: [1, 2], type: 'scatter3d'}]) + .then(function() { + expect(gd.layout.scene.aspectmode).toBe('auto'); + + return Plotly.relayout(gd, {'scene.aspectratio.x': 2}); + }) + .then(function() { + expect(gd.layout.scene.aspectmode).toBe('manual'); + + return Queue.undo(gd); + }) + .then(function() { + expect(gd.layout.scene.aspectmode).toBe('auto'); + }) + .catch(fail) + .then(done); + }); + + it('sets tickmode to linear when you edit tick0 or dtick', function(done) { + Plotly.plot(gd, [{x: [1, 2], y: [1, 2]}]) + .then(function() { + expect(gd.layout.xaxis.tickmode).toBeUndefined(); + expect(gd.layout.yaxis.tickmode).toBeUndefined(); + + return Plotly.relayout(gd, {'xaxis.tick0': 0.23, 'yaxis.dtick': 0.34}); + }) + .then(function() { + expect(gd.layout.xaxis.tickmode).toBe('linear'); + expect(gd.layout.yaxis.tickmode).toBe('linear'); + + return Queue.undo(gd); + }) + .then(function() { + expect(gd.layout.xaxis.tickmode).toBeUndefined(); + expect(gd.layout.yaxis.tickmode).toBeUndefined(); + + expect(gd.layout.xaxis.tick0).toBeUndefined(); + expect(gd.layout.yaxis.dtick).toBeUndefined(); + }) + .catch(fail) + .then(done); + }); + + it('updates non-auto ranges for linear/log changes', function(done) { + Plotly.plot(gd, [{x: [3, 5], y: [3, 5]}], { + xaxis: {range: [1, 10]}, + yaxis: {type: 'log', range: [0, 1]} + }) + .then(function() { + return Plotly.relayout(gd, {'xaxis.type': 'log', 'yaxis.type': 'linear'}); + }) + .then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([0, 1], 5); + expect(gd.layout.yaxis.range).toBeCloseToArray([1, 10], 5); + + return Queue.undo(gd); + }) + .then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([1, 10], 5); + expect(gd.layout.yaxis.range).toBeCloseToArray([0, 1], 5); + }) + .catch(fail) + .then(done); + }); + + it('respects reversed autorange when switching linear to log', function(done) { + Plotly.plot(gd, [{x: [1, 2], y: [1, 2]}]) + .then(function() { + // Ideally we should change this to xaxis.autorange: 'reversed' + // but that's a weird disappearing setting used just to force + // an initial reversed autorange. Proposed v2 change at: + // https://github.com/plotly/plotly.js/issues/420#issuecomment-323435082 + return Plotly.relayout(gd, 'xaxis.reverse', true); + }) + .then(function() { + var xRange = gd.layout.xaxis.range; + expect(xRange[1]).toBeLessThan(xRange[0]); + expect(xRange[0]).toBeGreaterThan(1); + + return Plotly.relayout(gd, 'xaxis.type', 'log'); + }) + .then(function() { + var xRange = gd.layout.xaxis.range; + expect(xRange[1]).toBeLessThan(xRange[0]); + // make sure it's a real loggy range + expect(xRange[0]).toBeLessThan(1); + }) + .catch(fail) + .then(done); + }); + + it('autoranges automatically when switching to/from any other axis type than linear <-> log', function(done) { + Plotly.plot(gd, [{x: ['1.5', '0.8'], y: [1, 2]}], {xaxis: {range: [0.6, 1.7]}}) + .then(function() { + expect(gd.layout.xaxis.autorange).toBeUndefined(); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + expect(gd.layout.xaxis.range).toEqual([0.6, 1.7]); + + return Plotly.relayout(gd, 'xaxis.type', 'category'); + }) + .then(function() { + expect(gd.layout.xaxis.autorange).toBe(true); + expect(gd._fullLayout.xaxis.type).toBe('category'); + expect(gd.layout.xaxis.range[0]).toBeLessThan(0); + + return Queue.undo(gd); + }) + .then(function() { + expect(gd.layout.xaxis.autorange).toBeUndefined(); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + expect(gd.layout.xaxis.range).toEqual([0.6, 1.7]); + }) + .catch(fail) + .then(done); + }); }); describe('Plotly.relayout subroutines switchboard', function() { @@ -672,10 +834,17 @@ describe('Test plot api', function() { beforeEach(function() { gd = createGraphDiv(); + + // some of these tests use the undo/redo queue + // OK, this is weird... the undo/redo queue length is a global config only. + // It's ignored on the plot, even though the queue itself is per-plot. + // We may ditch this later, but probably not until v2 + Plotly.setPlotConfig({queueLength: 3}); }); afterEach(function() { destroyGraphDiv(); + Plotly.setPlotConfig({queueLength: 0}); }); it('should redo auto z/contour when editing z array', function(done) { @@ -733,6 +902,315 @@ describe('Test plot api', function() { .catch(fail) .then(done); }); + + it('turns off zauto when you edit zmin or zmax', function(done) { + var zmin0 = 2; + var zmax1 = 10; + + function check(auto, msg) { + expect(gd.data[0].zmin).negateIf(auto).toBe(zmin0, msg); + expect(gd.data[0].zauto).toBe(auto, msg); + expect(gd.data[1].zmax).negateIf(auto).toBe(zmax1, msg); + expect(gd.data[1].zauto).toBe(auto, msg); + } + + Plotly.plot(gd, [ + {z: [[1, 2], [3, 4]], type: 'heatmap'}, + {x: [2, 3], z: [[5, 6], [7, 8]], type: 'contour'} + ]) + .then(function() { + check(true, 'initial'); + return Plotly.restyle(gd, 'zmin', zmin0, [0]); + }) + .then(function() { + return Plotly.restyle(gd, 'zmax', zmax1, [1]); + }) + .then(function() { + check(false, 'set min/max'); + return Plotly.restyle(gd, 'zauto', true); + }) + .then(function() { + check(true, 'reset'); + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('turns off cauto (autocolorscale) when you edit cmin or cmax (colorscale)', function(done) { + var autocscale = require('@src/components/colorscale/scales').Reds; + + var mcmin0 = 3; + var mcscl0 = 'rainbow'; + var mlcmax1 = 6; + var mlcscl1 = 'greens'; + + function check(auto, msg) { + expect(gd.data[0].marker.cauto).toBe(auto, msg); + expect(gd.data[0].marker.cmin).negateIf(auto).toBe(mcmin0); + expect(gd._fullData[0].marker.autocolorscale).toBe(auto, msg); + expect(gd.data[0].marker.colorscale).toEqual(auto ? autocscale : mcscl0); + expect(gd.data[1].marker.line.cauto).toBe(auto, msg); + expect(gd.data[1].marker.line.cmax).negateIf(auto).toBe(mlcmax1); + expect(gd._fullData[1].marker.line.autocolorscale).toBe(auto, msg); + expect(gd.data[1].marker.line.colorscale).toEqual(auto ? autocscale : mlcscl1); + } + + Plotly.plot(gd, [ + {y: [1, 2], mode: 'markers', marker: {color: [1, 10]}}, + {y: [2, 1], mode: 'markers', marker: {line: {width: 2, color: [3, 4]}}} + ]) + .then(function() { + check(true, 'initial'); + return Plotly.restyle(gd, {'marker.cmin': mcmin0, 'marker.colorscale': mcscl0}, null, [0]); + }) + .then(function() { + return Plotly.restyle(gd, {'marker.line.cmax': mlcmax1, 'marker.line.colorscale': mlcscl1}, null, [1]); + }) + .then(function() { + check(false, 'set min/max/scale'); + return Plotly.restyle(gd, {'marker.cauto': true, 'marker.autocolorscale': true}, null, [0]); + }) + .then(function() { + return Plotly.restyle(gd, {'marker.line.cauto': true, 'marker.line.autocolorscale': true}, null, [1]); + }) + .then(function() { + check(true, 'reset'); + return Queue.undo(gd); + }) + .then(function() { + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('turns off autobin when you edit bin specs', function(done) { + var start0 = 0.2; + var end1 = 6; + var size1 = 0.5; + + function check(auto, msg) { + expect(gd.data[0].autobinx).toBe(auto, msg); + expect(gd.data[0].xbins.start).negateIf(auto).toBe(start0, msg); + expect(gd.data[1].autobinx).toBe(auto, msg); + expect(gd.data[1].autobiny).toBe(auto, msg); + expect(gd.data[1].xbins.end).negateIf(auto).toBe(end1, msg); + expect(gd.data[1].ybins.size).negateIf(auto).toBe(size1, msg); + } + + Plotly.plot(gd, [ + {x: [1, 1, 1, 1, 2, 2, 2, 3, 3, 4], type: 'histogram'}, + {x: [1, 1, 2, 2, 3, 3, 4, 4], y: [1, 1, 2, 2, 3, 3, 4, 4], type: 'histogram2d'} + ]) + .then(function() { + check(true, 'initial'); + return Plotly.restyle(gd, 'xbins.start', start0, [0]); + }) + .then(function() { + return Plotly.restyle(gd, {'xbins.end': end1, 'ybins.size': size1}, null, [1]); + }) + .then(function() { + check(false, 'set start/end/size'); + return Plotly.restyle(gd, {autobinx: true, autobiny: true}); + }) + .then(function() { + check(true, 'reset'); + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('turns off autocontour when you edit contour specs', function(done) { + var start0 = 1.7; + var size1 = 0.6; + + function check(auto, msg) { + expect(gd.data[0].autocontour).toBe(auto, msg); + expect(gd.data[1].autocontour).toBe(auto, msg); + expect(gd.data[0].contours.start).negateIf(auto).toBe(start0, msg); + expect(gd.data[1].contours.size).negateIf(auto).toBe(size1, msg); + } + + Plotly.plot(gd, [ + {z: [[1, 2], [3, 4]], type: 'contour'}, + {x: [1, 2, 3, 4], y: [3, 4, 5, 6], type: 'histogram2dcontour'} + ]) + .then(function() { + check(true, 'initial'); + return Plotly.restyle(gd, 'contours.start', start0, [0]); + }) + .then(function() { + return Plotly.restyle(gd, 'contours.size', size1, [1]); + }) + .then(function() { + check(false, 'set start/size'); + return Plotly.restyle(gd, 'autocontour', true); + }) + .then(function() { + check(true, 'reset'); + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('sets x/ytype scaled when editing heatmap x0/dx/y0/dy', function(done) { + var x0 = 3; + var dy = 5; + + function check(scaled, msg) { + expect(gd.data[0].x0).negateIf(!scaled).toBe(x0, msg); + expect(gd.data[0].xtype).toBe(scaled ? 'scaled' : undefined, msg); + expect(gd.data[0].dy).negateIf(!scaled).toBe(dy, msg); + expect(gd.data[0].ytype).toBe(scaled ? 'scaled' : undefined, msg); + } + + Plotly.plot(gd, [{x: [1, 2, 4], y: [2, 3, 5], z: [[1, 2], [3, 4]], type: 'heatmap'}]) + .then(function() { + check(false, 'initial'); + return Plotly.restyle(gd, {x0: x0, dy: dy}); + }) + .then(function() { + check(true, 'set x0 & dy'); + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('sets colorbar.tickmode to linear when editing colorbar.tick0/dtick', function(done) { + // note: this *should* apply to marker.colorbar etc too but currently that's not implemented + // once we get this all in the schema it will work though. + var tick00 = 0.33; + var dtick1 = 0.8; + + function check(auto, msg) { + expect(gd._fullData[0].colorbar.tick0).negateIf(auto).toBe(tick00, msg); + expect(gd._fullData[0].colorbar.tickmode).toBe(auto ? 'auto' : 'linear', msg); + expect(gd._fullData[1].colorbar.dtick).negateIf(auto).toBe(dtick1, msg); + expect(gd._fullData[1].colorbar.tickmode).toBe(auto ? 'auto' : 'linear', msg); + } + + Plotly.plot(gd, [ + {z: [[1, 2], [3, 4]], type: 'heatmap'}, + {x: [2, 3], z: [[1, 2], [3, 4]], type: 'heatmap'} + ]) + .then(function() { + check(true, 'initial'); + return Plotly.restyle(gd, 'colorbar.tick0', tick00, [0]); + }) + .then(function() { + return Plotly.restyle(gd, 'colorbar.dtick', dtick1, [1]); + }) + .then(function() { + check(false, 'change tick0, dtick'); + return Plotly.restyle(gd, 'colorbar.tickmode', 'auto'); + }) + .then(function() { + check(true, 'reset'); + return Queue.undo(gd); + }) + .then(function() { + check(false, 'undo'); + }) + .catch(fail) + .then(done); + }); + + it('updates colorbars when editing bar charts', function(done) { + var mock = require('@mocks/bar-colorscale-colorbar.json'); + + Plotly.newPlot(gd, mock.data, mock.layout) + .then(function() { + expect(d3.select('.cbaxis text').style('fill')).not.toBe('rgb(255, 0, 0)'); + + return Plotly.restyle(gd, {'marker.colorbar.tickfont.color': 'rgb(255, 0, 0)'}); + }) + .then(function() { + expect(d3.select('.cbaxis text').style('fill')).toBe('rgb(255, 0, 0)'); + + return Plotly.restyle(gd, {'marker.showscale': false}); + }) + .then(function() { + expect(d3.select('.cbaxis').size()).toBe(0); + }) + .catch(fail) + .then(done); + }); + + it('updates colorbars when editing gl3d plots', function(done) { + Plotly.newPlot(gd, [{z: [[1, 2], [3, 6]], type: 'surface'}]) + .then(function() { + expect(d3.select('.cbaxis text').style('fill')).not.toBe('rgb(255, 0, 0)'); + + return Plotly.restyle(gd, {'colorbar.tickfont.color': 'rgb(255, 0, 0)'}); + }) + .then(function() { + expect(d3.select('.cbaxis text').style('fill')).toBe('rgb(255, 0, 0)'); + + return Plotly.restyle(gd, {'showscale': false}); + }) + .then(function() { + expect(d3.select('.cbaxis').size()).toBe(0); + }) + .catch(fail) + .then(done); + }); + + it('updates box position and axis type when it falls back to name', function(done) { + Plotly.newPlot(gd, [{name: 'A', y: [1, 2, 3, 4, 5], type: 'box'}], + {width: 400, height: 400, xaxis: {nticks: 3}} + ) + .then(function() { + checkTicks('x', ['A'], 'initial'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + + return Plotly.restyle(gd, {name: 'B'}); + }) + .then(function() { + checkTicks('x', ['B'], 'changed category'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + + return Plotly.restyle(gd, {x0: 12.3}); + }) + .then(function() { + checkTicks('x', ['12', '12.5'], 'switched to numeric'); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + }) + .catch(fail) + .then(done); + }); + + it('updates scene axis types automatically', function(done) { + Plotly.newPlot(gd, [{x: [1, 2], y: [1, 2], z: [1, 2], type: 'scatter3d'}]) + .then(function() { + expect(gd._fullLayout.scene.xaxis.type).toBe('linear'); + + return Plotly.restyle(gd, {z: [['a', 'b']]}); + }) + .then(function() { + expect(gd._fullLayout.scene.zaxis.type).toBe('category'); + }) + .catch(fail) + .then(done); + }); }); describe('Plotly.deleteTraces', function() { @@ -1745,7 +2223,7 @@ describe('plot_api helpers', function() { describe('plot_api edit_types', function() { it('initializes flags with all false', function() { - ['traces', 'layout'].forEach(function(container) { + ['traceFlags', 'layoutFlags'].forEach(function(container) { var initFlags = editTypes[container](); Object.keys(initFlags).forEach(function(key) { expect(initFlags[key]).toBe(false, container + '.' + key); @@ -1754,7 +2232,7 @@ describe('plot_api edit_types', function() { }); it('makes no changes if editType is not included', function() { - var flags = {docalc: false, dostyle: true}; + var flags = {calc: false, style: true}; editTypes.update(flags, { valType: 'boolean', @@ -1762,7 +2240,7 @@ describe('plot_api edit_types', function() { role: 'style' }); - expect(flags).toEqual({docalc: false, dostyle: true}); + expect(flags).toEqual({calc: false, style: true}); editTypes.update(flags, { family: {valType: 'string', dflt: 'Comic sans'}, @@ -1770,19 +2248,19 @@ describe('plot_api edit_types', function() { color: {valType: 'color', dflt: 'red'} }); - expect(flags).toEqual({docalc: false, dostyle: true}); + expect(flags).toEqual({calc: false, style: true}); }); it('gets updates from the outer object and ignores nested items', function() { - var flags = {docalc: false, dolegend: true}; + var flags = {calc: false, legend: true}; editTypes.update(flags, { - editType: 'docalc+dostyle', + editType: 'calc+style', valType: 'number', dflt: 1, role: 'style' }); - expect(flags).toEqual({docalc: true, dolegend: true, dostyle: true}); + expect(flags).toEqual({calc: true, legend: true, style: true}); }); }); diff --git a/test/jasmine/tests/plot_interact_test.js b/test/jasmine/tests/plot_interact_test.js index f8fba331d24..bcddf542b33 100644 --- a/test/jasmine/tests/plot_interact_test.js +++ b/test/jasmine/tests/plot_interact_test.js @@ -5,7 +5,6 @@ var Lib = require('@src/lib'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); // This suite is more of a test of the structure of interaction elements on // various plot types. Tests of actual mouse interactions on cartesian plots @@ -121,8 +120,6 @@ describe('Test plot structure', function() { }); it('should restore layout axes when they get deleted', function(done) { - jasmine.addMatchers(customMatchers); - expect(countScatterTraces()).toEqual(mock.data.length); expect(countSubplots()).toEqual(1); diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index e13210bc8fc..1d79a6163e5 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -1,6 +1,19 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var Registry = require('@src/registry'); + +var baseAttrs = require('@src/plots/attributes'); +var scatter = require('@src/traces/scatter'); +var parcoords = require('@src/traces/parcoords'); +var surface = require('@src/traces/surface'); + +var baseLayoutAttrs = require('@src/plots/layout_attributes'); +var cartesianAttrs = require('@src/plots/cartesian').layoutAttributes; +var gl3dAttrs = require('@src/plots/gl3d').layoutAttributes; +var polarLayoutAttrs = require('@src/plots/polar/axis_attributes'); +var annotationAttrs = require('@src/components/annotations').layoutAttributes; +var updatemenuAttrs = require('@src/components/updatemenus').layoutAttributes; describe('plot schema', function() { 'use strict'; @@ -13,23 +26,48 @@ describe('plot schema', function() { var VALTYPES = Object.keys(valObjects); var ROLES = ['info', 'style', 'data']; - var editTypes = plotSchema.defs.editTypes; + var editType = plotSchema.defs.editType; + + function assertTraceSchema(callback) { + var traces = plotSchema.traces; + + Object.keys(traces).forEach(function(traceName) { + Plotly.PlotSchema.crawl(traces[traceName].attributes, callback, 0, traceName); + }); + } + + function assertTransformSchema(callback) { + var transforms = plotSchema.transforms; + + Object.keys(transforms).forEach(function(transformName) { + Plotly.PlotSchema.crawl(transforms[transformName].attributes, callback, 0, transformName); + }); + } + + function assertLayoutSchema(callback) { + Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback, 0, 'layout'); - function assertPlotSchema(callback) { var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { - Plotly.PlotSchema.crawl(traces[traceName].attributes, callback); + var layoutAttrs = traces[traceName].layoutAttributes; + if(layoutAttrs) { + Plotly.PlotSchema.crawl(layoutAttrs, callback, 0, traceName + ': layout'); + } }); + } - Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback); + function assertPlotSchema(callback) { + assertTraceSchema(callback); + assertLayoutSchema(callback); + assertTransformSchema(callback); } it('all attributes should have a valid `valType`', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { - expect(VALTYPES.indexOf(attr.valType) !== -1).toBe(true); + expect(VALTYPES.indexOf(attr.valType) !== -1).toBe(true, attr); } } ); @@ -77,7 +115,7 @@ describe('plot schema', function() { var valObject = valObjects[attr.valType], opts = valObject.requiredOpts .concat(valObject.otherOpts) - .concat(['valType', 'description', 'role', 'editType']); + .concat(['valType', 'description', 'role', 'editType', 'impliedEdits']); Object.keys(attr).forEach(function(key) { expect(opts.indexOf(key) !== -1).toBe(true, key, attr); @@ -147,6 +185,18 @@ describe('plot schema', function() { }); }); + it('includes xaxis-only items on only the x axis, not y or bare', function() { + var items = ['rangeselector', 'rangeslider']; + + var layoutAttrs = plotSchema.layout.layoutAttributes; + + items.forEach(function(item) { + expect(layoutAttrs.xaxis[item]).toBeDefined(item); + expect(layoutAttrs.yaxis[item]).toBeUndefined(item); + expect(layoutAttrs[item]).toBeUndefined(item); + }); + }); + it('valObjects descriptions should be strings', function() { assertPlotSchema( function(attr) { @@ -167,38 +217,60 @@ describe('plot schema', function() { var DEPRECATED = '_deprecated'; assertPlotSchema( - function(attr) { + function(attr, attrName, attrs, level, attrString) { if(isPlainObject(attr[DEPRECATED])) { Object.keys(attr[DEPRECATED]).forEach(function(dAttrName) { var dAttr = attr[DEPRECATED][dAttrName]; - expect(VALTYPES.indexOf(dAttr.valType) !== -1).toBe(true); - expect(ROLES.indexOf(dAttr.role) !== -1).toBe(true); + expect(VALTYPES.indexOf(dAttr.valType) !== -1) + .toBe(true, attrString + ': ' + dAttrName); + expect(ROLES.indexOf(dAttr.role) !== -1) + .toBe(true, attrString + ': ' + dAttrName); }); } } ); }); - it('has valid or no `editType` in every attribute', function() { - var validEditTypes = editTypes.traces; - assertPlotSchema( - function(attr, attrName, attrs) { - if(attrs === plotSchema.layout.layoutAttributes) { - // detect when we switch from trace attributes to layout - // attributes - depends on doing all the trace attributes - // first, then switching to layout attributes - validEditTypes = editTypes.layout; - } - if(attr.editType !== undefined) { - var editTypeParts = attr.editType.split('+'); - editTypeParts.forEach(function(editTypePart) { - expect(validEditTypes[editTypePart]) - .toBe(false, editTypePart); - }); - } + it('has valid or no `impliedEdits` in every attribute', function() { + assertPlotSchema(function(attr, attrName, attrs, level, attrString) { + if(attr.impliedEdits !== undefined) { + expect(isPlainObject(attr.impliedEdits)) + .toBe(true, attrString + ': ' + JSON.stringify(attr.impliedEdits)); + // make sure it wasn't emptied out + expect(Object.keys(attr.impliedEdits).length).not.toBe(0, attrString); } - ); + }); + }); + + it('has valid `editType` in all attributes and containers', function() { + function shouldHaveEditType(attr, attrName) { + // ensure any object (container or regular val object) has editType + // array containers have extra nesting where editType would be redundant + return Lib.isPlainObject(attr) && attrName !== 'impliedEdits' && + attrName !== 'items' && !Lib.isPlainObject(attr.items); + } + + assertTraceSchema(function(attr, attrName, attrs, level, attrString) { + if(shouldHaveEditType(attr, attrName)) { + expect(Lib.validate(attr.editType, editType.traces)) + .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); + } + }); + + assertTransformSchema(function(attr, attrName, attrs, level, attrString) { + if(shouldHaveEditType(attr, attrName)) { + expect(Lib.validate(attr.editType, editType.traces)) + .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); + } + }); + + assertLayoutSchema(function(attr, attrName, attrs, level, attrString) { + if(shouldHaveEditType(attr, attrName)) { + expect(Lib.validate(attr.editType, editType.layout)) + .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); + } + }); }); it('should work with registered transforms', function() { @@ -235,7 +307,7 @@ describe('plot schema', function() { expect(plotSchema.defs.metaKeys) .toEqual([ '_isSubplotObj', '_isLinkedToArray', '_arrayAttrRegexps', - '_deprecated', 'description', 'role' + '_deprecated', 'description', 'role', 'editType', 'impliedEdits' ]); }); @@ -246,3 +318,253 @@ describe('plot schema', function() { expect(plotSchema.frames.items.frames_entry.role).toEqual('object'); }); }); + +describe('getTraceValObject', function() { + var getTraceValObject = Plotly.PlotSchema.getTraceValObject; + + it('finds base attributes', function() { + expect(getTraceValObject({}, ['type'])) + .toBe(baseAttrs.type); + expect(getTraceValObject({_module: parcoords}, ['customdata', 0, 'charm'])) + .toBe(baseAttrs.customdata); + }); + + it('looks first for trace._module, then for trace.type, then dflt', function() { + expect(getTraceValObject({_module: parcoords}, ['domain', 'x', 0])) + .toBe(parcoords.attributes.domain.x.items[0]); + expect(getTraceValObject({_module: parcoords}, ['fugacity'])).toBe(false); + + expect(getTraceValObject({type: 'parcoords'}, ['dimensions', 5, 'range', 1])) + .toBe(parcoords.attributes.dimensions.range.items[1]); + expect(getTraceValObject({type: 'parcoords'}, ['llamas'])).toBe(false); + + expect(getTraceValObject({}, ['marker', 'opacity'])) + .toBe(scatter.attributes.marker.opacity); + expect(getTraceValObject({}, ['dimensions', 5, 'range', 1])).toBe(false); + }); + + it('finds subplot attributes', function() { + expect(getTraceValObject({}, ['xaxis'])) + .toBe(require('@src/plots/cartesian').attributes.xaxis); + + expect(getTraceValObject({type: 'surface'}, ['scene'])) + .toBe(require('@src/plots/gl3d').attributes.scene); + expect(getTraceValObject({type: 'surface'}, ['xaxis'])).toBe(false); + }); + + it('finds pre-merged component attributes', function() { + expect(getTraceValObject({}, ['xcalendar'])) + .toBe(scatter.attributes.xcalendar); + expect(getTraceValObject({_module: surface}, ['xcalendar'])) + .toBe(surface.attributes.xcalendar); + expect(getTraceValObject({_module: surface}, ['zcalendar'])) + .toBe(surface.attributes.zcalendar); + }); + + it('supports transform attributes', function() { + var mockTrace = {transforms: [ + {type: 'filter'}, + {type: 'groupby'} + ]}; + + var filterAttrs = require('@src/transforms/filter').attributes; + expect(getTraceValObject(mockTrace, ['transforms', 0, 'operation'])) + .toBe(filterAttrs.operation); + // check a component-provided attr + expect(getTraceValObject(mockTrace, ['transforms', 0, 'valuecalendar'])) + .toBe(filterAttrs.valuecalendar); + + expect(getTraceValObject(mockTrace, ['transforms', 1, 'styles', 13, 'value', 'line', 'color'])) + .toBe(require('@src/transforms/groupby').attributes.styles.value); + + [ + ['transforms', 0], + ['transforms', 0, 'nameformat'], + ['transforms', 2, 'enabled'], + ['transforms', '0', 'operation'] + ].forEach(function(attrArray) { + expect(getTraceValObject(mockTrace, attrArray)).toBe(false, attrArray); + }); + + expect(getTraceValObject({}, ['transforms', 0, 'operation'])).toBe(false); + }); + + it('supports polar area attributes', function() { + var areaAttrs = require('@src/plots/polar/area_attributes'); + expect(getTraceValObject({type: 'area'}, ['r'])).toBe(areaAttrs.r); + expect(getTraceValObject({type: 'area'}, ['t', 23])).toBe(areaAttrs.t); + expect(getTraceValObject({type: 'area'}, ['q'])).toBe(false); + }); + + it('does not return attribute properties', function() { + // it still returns the attribute itself - but maybe we should only do this + // for valType: any? (or data_array/arrayOk with just an index) + [ + 'valType', 'dflt', 'role', 'description', 'arrayOk', + 'editType', 'min', 'max', 'values' + ].forEach(function(prop) { + expect(getTraceValObject({}, ['x', prop])) + .toBe(scatter.attributes.x, prop); + + expect(getTraceValObject({}, ['xcalendar', prop])) + .toBe(scatter.attributes.xcalendar, prop); + + expect(getTraceValObject({}, ['line', 'smoothing', prop])) + .toBe(scatter.attributes.line.smoothing, prop); + }); + }); +}); + +describe('getLayoutValObject', function() { + var getLayoutValObject = Plotly.PlotSchema.getLayoutValObject; + var blankLayout = {}; + + it('finds base attributes', function() { + expect(getLayoutValObject(blankLayout, ['font', 'family'])).toBe(baseLayoutAttrs.font.family); + expect(getLayoutValObject(blankLayout, ['margin'])).toBe(baseLayoutAttrs.margin); + expect(getLayoutValObject(blankLayout, ['margarine'])).toBe(false); + }); + + it('finds trace layout attributes', function() { + var layoutBar = {_modules: [Registry.modules.bar._module]}; + expect(getLayoutValObject(layoutBar, ['barmode'])) + .toBe(require('@src/traces/bar').layoutAttributes.barmode); + var layoutBox = {_modules: [Registry.modules.box._module]}; + expect(getLayoutValObject(layoutBox, ['boxgap'])) + .toBe(require('@src/traces/box').layoutAttributes.boxgap); + var layoutPie = {_modules: [Registry.modules.pie._module]}; + expect(getLayoutValObject(layoutPie, ['hiddenlabels'])) + .toBe(require('@src/traces/pie').layoutAttributes.hiddenlabels); + + // not found when these traces are unused on the plot + expect(getLayoutValObject(blankLayout, ['barmode'])).toBe(false); + expect(getLayoutValObject(blankLayout, ['boxgap'])).toBe(false); + expect(getLayoutValObject(blankLayout, ['hiddenlabels'])).toBe(false); + }); + + it('finds component attributes', function() { + var layout3D = {_basePlotModules: [Registry.subplotsRegistry.gl3d]}; + // the ones with schema are already merged into other places + expect(getLayoutValObject(blankLayout, ['calendar'])) + .toBe(baseLayoutAttrs.calendar); + expect(getLayoutValObject(layout3D, ['scene4', 'annotations', 44, 'z'])) + .toBe(gl3dAttrs.annotations.z); + + // still need to have the subplot in the plot to find these + expect(getLayoutValObject(blankLayout, ['scene4', 'annotations', 44, 'z'])).toBe(false); + + // ones with only layoutAttributes we need to look in the component + expect(getLayoutValObject(blankLayout, ['annotations'])) + .toBe(annotationAttrs); + expect(getLayoutValObject(blankLayout, ['annotations', 123])) + .toBe(annotationAttrs); + expect(getLayoutValObject(blankLayout, ['annotations', 123, 'textangle'])) + .toBe(annotationAttrs.textangle); + + expect(getLayoutValObject(blankLayout, ['updatemenus', 3, 'buttons', 4, 'args', 2])) + .toBe(updatemenuAttrs.buttons.args.items[2]); + }); + + it('finds cartesian subplot attributes', function() { + var layoutCartesian = {_basePlotModules: [Registry.subplotsRegistry.cartesian]}; + expect(getLayoutValObject(layoutCartesian, ['xaxis', 'title'])) + .toBe(cartesianAttrs.title); + expect(getLayoutValObject(layoutCartesian, ['yaxis', 'tickfont', 'family'])) + .toBe(cartesianAttrs.tickfont.family); + expect(getLayoutValObject(layoutCartesian, ['xaxis3', 'range', 1])) + .toBe(cartesianAttrs.range.items[1]); + expect(getLayoutValObject(layoutCartesian, ['yaxis12', 'dtick'])) + .toBe(cartesianAttrs.dtick); + + // not found when cartesian is unused + expect(getLayoutValObject(blankLayout, ['xaxis', 'title'])).toBe(false); + expect(getLayoutValObject(blankLayout, ['yaxis', 'tickfont', 'family'])).toBe(false); + expect(getLayoutValObject(blankLayout, ['xaxis3', 'range', 1])).toBe(false); + expect(getLayoutValObject(blankLayout, ['yaxis12', 'dtick'])).toBe(false); + + // improper axis names + [ + 'xaxis0', 'yaxis1', 'xaxis2a', 'yaxis3x3', 'zaxis', 'aaxis' + ].forEach(function(name) { + expect(getLayoutValObject(layoutCartesian, [name, 'dtick'])).toBe(false, name); + }); + }); + + it('finds 3d subplot attributes if 3d is present', function() { + var layout3D = {_basePlotModules: [Registry.subplotsRegistry.gl3d]}; + expect(getLayoutValObject(layout3D, ['scene', 'zaxis', 'spikesides'])) + .toBe(gl3dAttrs.zaxis.spikesides); + expect(getLayoutValObject(layout3D, ['scene45', 'bgcolor'])) + .toBe(gl3dAttrs.bgcolor); + + // not found when the gl3d is unused + expect(getLayoutValObject(blankLayout, ['scene', 'zaxis', 'spikesides'])).toBe(false); + expect(getLayoutValObject(blankLayout, ['scene45', 'bgcolor'])).toBe(false); + + // improper scene names + expect(getLayoutValObject(layout3D, ['scene0', 'bgcolor'])).toBe(false); + expect(getLayoutValObject(layout3D, ['scene1', 'bgcolor'])).toBe(false); + expect(getLayoutValObject(layout3D, ['scene2k', 'bgcolor'])).toBe(false); + }); + + it('finds polar attributes', function() { + expect(getLayoutValObject(blankLayout, ['direction'])) + .toBe(polarLayoutAttrs.layout.direction); + + expect(getLayoutValObject(blankLayout, ['radialaxis', 'range', 0])) + .toBe(polarLayoutAttrs.radialaxis.range.items[0]); + + expect(getLayoutValObject(blankLayout, ['angularaxis', 'domain'])) + .toBe(polarLayoutAttrs.angularaxis.domain); + }); + + it('lets gl2d override cartesian & global attrs', function() { + var svgModule = Registry.subplotsRegistry.cartesian; + var gl2dModule = Registry.subplotsRegistry.gl2d; + var layoutSVG = {_basePlotModules: [svgModule]}; + var layoutGL2D = {_basePlotModules: [gl2dModule]}; + var combinedLayout1 = {_basePlotModules: [svgModule, gl2dModule]}; + var combinedLayout2 = {_basePlotModules: [gl2dModule, svgModule]}; + + var bgParts = ['plot_bgcolor']; + var baseBG = baseLayoutAttrs.plot_bgcolor; + var gl2dBG = gl2dModule.baseLayoutAttrOverrides.plot_bgcolor; + expect(getLayoutValObject(blankLayout, bgParts)).toBe(baseBG); + expect(getLayoutValObject(layoutSVG, bgParts)).toBe(baseBG); + expect(getLayoutValObject(layoutGL2D, bgParts)).toBe(gl2dBG); + expect(getLayoutValObject(combinedLayout1, bgParts)).toBe(gl2dBG); + expect(getLayoutValObject(combinedLayout2, bgParts)).toBe(gl2dBG); + + var ticklenParts = ['xaxis4', 'ticklen']; + var svgTicklen = svgModule.layoutAttributes.ticklen; + var gl2dTicklen = gl2dModule.layoutAttrOverrides.ticklen; + expect(getLayoutValObject(blankLayout, ticklenParts)).toBe(false); + expect(getLayoutValObject(layoutSVG, ticklenParts)).toBe(svgTicklen); + expect(getLayoutValObject(layoutGL2D, ticklenParts)).toBe(gl2dTicklen); + expect(getLayoutValObject(combinedLayout1, ticklenParts)).toBe(gl2dTicklen); + expect(getLayoutValObject(combinedLayout2, ticklenParts)).toBe(gl2dTicklen); + }); +}); + +describe('component schemas', function() { + it('does not have yaxis-only attributes or mismatched x/yaxis attributes', function() { + // in principle either of these should be allowable, but we don't currently + // support them so lets simply test that we haven't added them accidentally! + + function delDescription(attr) { delete attr.description; } + + for(var key in Registry.componentsRegistry) { + var _module = Registry.componentsRegistry[key]; + var schema = _module.schema; + if(schema && schema.subplots && schema.subplots.yaxis) { + expect(schema.subplots.xaxis).toBeDefined(_module.name); + + var xa = Lib.extendDeep({}, schema.subplots.xaxis); + var ya = Lib.extendDeep({}, schema.subplots.yaxis); + Plotly.PlotSchema.crawl(xa, delDescription); + Plotly.PlotSchema.crawl(ya, delDescription); + expect(JSON.stringify(xa)).toBe(JSON.stringify(ya), _module.name); + } + } + }); +}); diff --git a/test/jasmine/tests/range_slider_test.js b/test/jasmine/tests/range_slider_test.js index f023429df6b..1cd215ab62c 100644 --- a/test/jasmine/tests/range_slider_test.js +++ b/test/jasmine/tests/range_slider_test.js @@ -11,7 +11,6 @@ var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); -var customMatchers = require('../assets/custom_matchers'); var TOL = 6; @@ -44,10 +43,6 @@ describe('the range slider', function() { describe('when specified as visible', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function(done) { gd = createGraphDiv(); @@ -620,10 +615,6 @@ describe('the range slider', function() { describe('in general', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index ec42e45a2e4..c372189e464 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -7,7 +7,6 @@ var Lib = require('@src/lib'); var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var customAssertions = require('../assets/custom_assertions'); var fail = require('../assets/fail_test'); @@ -604,10 +603,6 @@ describe('end-to-end scatter tests', function() { describe('scatter hoverPoints', function() { - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); function _hover(gd, xval, yval, hovermode) { diff --git a/test/jasmine/tests/scattergeo_test.js b/test/jasmine/tests/scattergeo_test.js index 2104e23b4f0..95b62af55d5 100644 --- a/test/jasmine/tests/scattergeo_test.js +++ b/test/jasmine/tests/scattergeo_test.js @@ -8,7 +8,6 @@ var ScatterGeo = require('@src/traces/scattergeo'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var mouseEvent = require('../assets/mouse_event'); describe('Test scattergeo defaults', function() { @@ -238,10 +237,6 @@ describe('Test scattergeo hover', function() { // because geo hover relies on mouse event // to set the c2p conversion functions - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function(done) { gd = createGraphDiv(); diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 938f96cd445..931f946326c 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -7,7 +7,6 @@ var convert = require('@src/traces/scattermapbox/convert'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); @@ -110,10 +109,6 @@ describe('scattermapbox defaults', function() { describe('scattermapbox convert', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _convert(trace, selected) { var gd = { data: [trace] }; Plots.supplyDefaults(gd); @@ -437,8 +432,6 @@ describe('@noCI scattermapbox hover', function() { var gd; beforeAll(function(done) { - jasmine.addMatchers(customMatchers); - Plotly.setPlotConfig({ mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }); @@ -637,8 +630,6 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { } beforeAll(function(done) { - jasmine.addMatchers(customMatchers); - Plotly.setPlotConfig({ mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }); diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index da49b10a206..ad46a26303f 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -7,7 +7,6 @@ var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); -var customMatchers = require('../assets/custom_matchers'); var customAssertions = require('../assets/custom_assertions'); var assertClip = customAssertions.assertClip; @@ -208,10 +207,6 @@ describe('scatterternary calc', function() { var calc = ScatterTernary.calc; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - var gd, trace, cd; beforeEach(function() { diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index eea9bc2db0f..b2be82a162c 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -9,7 +9,6 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); var touchEvent = require('../assets/touch_event'); -var customMatchers = require('../assets/custom_matchers'); var LONG_TIMEOUT_INTERVAL = 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL; @@ -20,10 +19,6 @@ describe('select box and lasso', function() { var selectPath = [[93, 193], [143, 193]]; var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]]; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); function drag(path, options) { diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index 63d0f461b70..10675a5b6ff 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -10,7 +10,6 @@ var Plots = PlotlyInternal.Plots; var Axes = PlotlyInternal.Axes; var d3 = require('d3'); -var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); @@ -20,10 +19,6 @@ var drag = require('../assets/drag'); describe('Test shapes defaults:', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - function _supply(layoutIn, layoutOut) { layoutOut = layoutOut || {}; layoutOut._has = Plots._hasPlotType.bind(layoutOut); @@ -469,10 +464,6 @@ describe('shapes edge cases', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); @@ -512,10 +503,6 @@ describe('shapes autosize', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); it('should adapt to relayout calls', function(done) { diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index 5140fdf40ab..5ba69ec71d1 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -10,16 +10,11 @@ var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); var doubleClick = require('../assets/double_click'); -var customMatchers = require('../assets/custom_matchers'); var getClientPosition = require('../assets/get_client_position'); describe('ternary plots', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - afterEach(destroyGraphDiv); describe('with scatterternary trace(s)', function() { @@ -434,8 +429,6 @@ describe('Test event property of interactions on a ternary plot:', function() { pointPos; beforeAll(function(done) { - jasmine.addMatchers(customMatchers); - gd = createGraphDiv(); mockCopy = Lib.extendDeep({}, mock); Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index c63e9565de4..ad9b3c5e159 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -4,7 +4,6 @@ var Lib = require('@src/lib'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); -var customMatchers = require('../assets/custom_matchers'); var subplotMock = require('@mocks/multiple_subplots.json'); var FORMATS = ['png', 'jpeg', 'webp', 'svg']; @@ -14,10 +13,6 @@ describe('Plotly.toImage', function() { var gd; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - beforeEach(function() { gd = createGraphDiv(); }); diff --git a/test/jasmine/tests/transform_aggregate_test.js b/test/jasmine/tests/transform_aggregate_test.js index 0377963138b..dbb8bd7e015 100644 --- a/test/jasmine/tests/transform_aggregate_test.js +++ b/test/jasmine/tests/transform_aggregate_test.js @@ -2,13 +2,10 @@ var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); describe('aggregate', function() { var gd; - beforeAll(function() { jasmine.addMatchers(customMatchers);}); - beforeEach(function() { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js index 716c30bf6ca..d338251a546 100644 --- a/test/jasmine/tests/transform_filter_test.js +++ b/test/jasmine/tests/transform_filter_test.js @@ -6,7 +6,6 @@ var Lib = require('@src/lib'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var customMatchers = require('../assets/custom_matchers'); var customAssertions = require('../assets/custom_assertions'); var assertDims = customAssertions.assertDims; @@ -998,10 +997,6 @@ describe('filter transforms calc:', function() { describe('filter transforms interactions', function() { 'use strict'; - beforeAll(function() { - jasmine.addMatchers(customMatchers); - }); - var mockData0 = [{ x: [-2, -1, -2, 0, 1, 2, 3], y: [1, 2, 3, 1, 2, 3, 1],