8000 restyle/relayout refactor by alexcjohnson · Pull Request #1999 · plotly/plotly.js · GitHub
[go: up one dir, main page]

Skip to content

restyle/relayout refactor #1999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Sep 20, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4d3e2ec
test - and fix - most of the relayout doextras
alexcjohnson Aug 18, 2017
95d7d71
test - and fix - most of the doextra calls in restyle
alexcjohnson Aug 29, 2017
3326ecc
test that xaxis-only items are only in the xaxis in the schema
alexcjohnson Aug 30, 2017
a948cce
merge component attribute schemas into core at register time
alexcjohnson Aug 30, 2017
dd52922
fix lib test for undefined -> null in undoqueue
alexcjohnson Sep 1, 2017
f90f079
abstract id/name counter regex and standardize cartesian attrRegex
alexcjohnson Sep 1, 2017
e036fea
fix #1325 - animating multiple axes
alexcjohnson Sep 1, 2017
69e0188
Plotschema getTraceValObject and getLayoutValObject methods
alexcjohnson Sep 1, 2017
bbfe399
relativeAttr
alexcjohnson Sep 12, 2017
29931ec
fix annotation comments/descriptions
alexcjohnson Sep 12, 2017
fad72a2
make common hover label pick up changes quicker
alexcjohnson Sep 13, 2017
e895b32
edit_types.overrideAll
alexcjohnson Sep 13, 2017
7a7dc6d
let PlotSchema.crawl report the complete attribute string
alexcjohnson Sep 13, 2017
c87b01a
better reporting from hover label test
alexcjohnson Sep 13, 2017
658e5cb
fix registry for new circular dep
alexcjohnson Sep 13, 2017
f49ae5e
massive commit to lock in editType and impliedEdits and clean up rest…
alexcjohnson Sep 13, 2017
7ea0d25
lint
alexcjohnson Sep 13, 2017
284c87f
remove obsolete comment in gl3d
alexcjohnson Sep 14, 2017
b9826c8
change overrideAll API to nested/from-root only
alexcjohnson Sep 14, 2017
7c38a4a
clean up restyle/relayout flag names
alexcjohnson Sep 14, 2017
96cc57f
clean up editTypes/impliedEdits and formalize & document their schema…
alexcjohnson Sep 14, 2017
cb94e95
test restrictions on component xaxis/yaxis schemas
alexcjohnson Sep 15, 2017
238e248
preserve impliedEdits: {key: undefined} by extendDeepAll
alexcjohnson Sep 15, 2017
42662ba
comments on relative_attr regexps
alexcjohnson Sep 15, 2017
50aa1ca
include schema in dist
alexcjohnson Sep 15, 2017
62a1392
fix plotschema test for metaKeys
alexcjohnson Sep 15, 2017
87b26d5
test order-independence of trace/transform/component registration
alexcjohnson Sep 18, 2017
040ed1b
test colorbar editing
alexcjohnson Sep 19, 2017
388a7fe
abstract - and fix - automatic axis type clearing
alexcjohnson Sep 19, 2017
6e8a68c
coerceTraceIndices earlier so clearAxisTypes can use it
alexcjohnson Sep 19, 2017
4f8fc66
move clearAxisTypes into helpers
alexcjohnson Sep 19, 2017
97ddf48
update jsdom to v11.2 with new API
alexcjohnson Sep 19, 2017
fe7db79
oops didn't mean to commit that commented out...
alexcjohnson Sep 19, 2017
68f5dbc
fix and test errorbar visibility toggling
alexcjohnson Sep 19, 2017
d15e541
layout.showlegend test
alexcjohnson Sep 19, 2017
7ec1634
closes #615 - something else in this PR fixed it, just nailing a test
alexcjohnson Sep 19, 2017
a107466
fix #358 - restyling orientation
alexcjohnson Sep 20, 2017
42805c2
test histogram changing data type
alexcjohnson Sep 20, 2017
8d9feaf
fix #2020 - editing plots with multiple histograms
alexcjohnson Sep 20, 2017
0a98a6d
lint
alexcjohnson Sep 20, 2017
e4227aa
move checkTicks into custom_assertions
alexcjohnson Sep 20, 2017
0729921
load custom_matchers globally, and refactor negateIf as a method
alexcjohnson Sep 20, 2017
407ae5a
pull custom_matchers out of requirejs bundle test
alexcjohnson Sep 20, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Plotschema getTraceValObject and getLayoutValObject methods
  • Loading branch information
alexcjohnson committed Sep 13, 2017
commit 69e0188dc24c0a711e2582dfbd7bc3771707f9a8
136 changes: 136 additions & 0 deletions src/plot_api/plot_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,142 @@ exports.findArrayAttributes = function(trace) {
return arrayAttributes;
};

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆘 semi-useless lint comment alert 🆘

jsDOC is enabled on comments starting with /** (see http://usejsdoc.org/about-getting-started.html). As we don't extract those jsDOC comment (yet), adding this extra * would just make that comment look prettier in my editor. Non-blocking.

* Find the valObject for one attribute in an existing trace
Copy link
Contributor
@rreusser rreusser Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a valObject just a value? Or is it a nested property object? Or the attribute definition?
Or something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less vaguely, valObjects are defined here, but I'm trying to process how the return value relates to those definitions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah yeah, coerce.valObjects describes valObjects but it isn't itself at set of valObjects. What we mean by it in this context is a schema entry describing one attribute. I'll see what I can do in terms of documenting this better...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal. Just another part of the code I haven't gone too deep in. To clear up a common misconception though, naming in CS is a very easy problem (randomly generated strings will do). Naming for maximum interpretability is the hard part. 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like coerce.valObjects should be named coerce.valObjectMeta instead. To me and to PlotSchema.isValObject a val object is any object with a valType key - which is essentially what getTraceValObject and getLayoutValObject return.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valObjectMeta - and descriptions for editType and impliedEdits that make their way into the schema - in 96cc57f

*
* @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 global attributes
if(!valObject) valObject = baseAttributes[head];

// finally look in the subplot attributes
if(!valObject) {
var subplotModule = _module.basePlotModule;
if(subplotModule && subplotModule.attributes) {
valObject = subplotModule.attributes[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(parts) {
var valObject = layoutHeadAttr(parts[0]);

return recurseIntoValObject(valObject, parts, 1);
};

function layoutHeadAttr(head) {
if(head in baseLayoutAttributes) return baseLayoutAttributes[head];
if(head in Registry.traceLayoutAttributes) return Registry.traceLayoutAttributes[head];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely remember reading that k in obj is much slower than obj[k] !== undefined, but maybe newer browsers have optimized this. k in obj is safer though as some of our objects have keys set to undefined.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is baseLayoutAttributes.hasOwnProperty('head') also (effectively) equivalent?


var key, _module;

for(key in Registry.componentsRegistry) {
_module = Registry.componentsRegistry[key];
if(head === _module.name) return _module.layoutAttributes;
}

for(key in Registry.subplotsRegistry) {
_module = Registry.subplotsRegistry[key];
if(_module.attrRegex && _module.attrRegex.test(head)) return _module.layoutAttributes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful.

}

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number.isInteger is a thing, but IE doesn't support it, so 👌

Copy link
Contributor
@rreusser rreusser Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, javascript.

> Number.isInteger(new Number(5))
< false
> Number.isInteger(Number(5))
< true

(AFAIK, there is never a reason to use that, but it still seems surprising every time.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plotly.js would be very unhappy if you use new Number in your plot.

Copy link
Contributor
@etpinard etpinard Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔈 plotly.js is 100% broken with new Number() numbers 🔈

}

function getTraceAttributes(type) {
var _module, basePlotModule;

Expand Down
10 changes: 10 additions & 0 deletions src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ exports.transformsRegistry = {};
exports.componentsRegistry = {};
exports.layoutArrayContainers = [];
exports.layoutArrayRegexes = [];
exports.traceLayoutAttributes = {};

/**
* register a module as the handler for a trace type
Expand Down Expand Up @@ -60,6 +61,15 @@ exports.register = function(_module, thisType, categoriesIn, meta) {
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) {
Lib.extendFlat(exports.traceLayoutAttributes, _module.layoutAttributes);
}
};

/**
Expand Down
187 changes: 187 additions & 0 deletions test/jasmine/tests/plotschema_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ var Plotly = require('@lib/index');

var Lib = require('@src/lib');

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';

Expand Down Expand Up @@ -258,3 +270,178 @@ 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',
'editTypes', '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;

it('finds base attributes', function() {
expect(getLayoutValObject(['font', 'family'])).toBe(baseLayoutAttrs.font.family);
expect(getLayoutValObject(['margin'])).toBe(baseLayoutAttrs.margin);
expect(getLayoutValObject(['margarine'])).toBe(false);
});

it('finds trace layout attributes', function() {
expect(getLayoutValObject(['barmode']))
.toBe(require('@src/traces/bar').layoutAttributes.barmode);
expect(getLayoutValObject(['boxgap']))
.toBe(require('@src/traces/box').layoutAttributes.boxgap);
expect(getLayoutValObject(['hiddenlabels']))
.toBe(require('@src/traces/pie').layoutAttributes.hiddenlabels);
});

it('finds component attributes', function() {
// the ones with schema are already merged into other places
expect(getLayoutValObject(['calendar']))
.toBe(baseLayoutAttrs.calendar);
expect(getLayoutValObject(['scene4', 'annotations', 44, 'z']))
.toBe(gl3dAttrs.annotations.z);

// ones with only layoutAttributes we need to look in the component
expect(getLayoutValObject(['annotations']))
.toBe(annotationAttrs);
expect(getLayoutValObject(['annotations', 123]))
.toBe(annotationAttrs);
expect(getLayoutValObject(['annotations', 123, 'textangle']))
.toBe(annotationAttrs.textangle);

expect(getLayoutValObject(['updatemenus', 3, 'buttons', 4, 'args', 2]))
.toBe(updatemenuAttrs.buttons.args.items[2]);
});

it('finds cartesian subplot attributes', function() {
expect(getLayoutValObject(['xaxis', 'title']))
.toBe(cartesianAttrs.title);
expect(getLayoutValObject(['yaxis', 'tickfont', 'family']))
.toBe(cartesianAttrs.tickfont.family);
expect(getLayoutValObject(['xaxis3', 'range', 1]))
.toBe(cartesianAttrs.range.items[1]);
expect(getLayoutValObject(['yaxis12', 'dtick']))
.toBe(cartesianAttrs.dtick);

// improper axis names
[
'xaxis0', 'yaxis1', 'xaxis2a', 'yaxis3x3', 'zaxis', 'aaxis'
].forEach(function(name) {
expect(getLayoutValObject([name, 'dtick'])).toBe(false, name);
});
});

it('finds 3d subplot attributes', function() {
expect(getLayoutValObject(['scene', 'zaxis', 'spikesides']))
.toBe(gl3dAttrs.zaxis.spikesides);
expect(getLayoutValObject(['scene45', 'bgcolor']))
.toBe(gl3dAttrs.bgcolor);

// improper scene names
expect(getLayoutValObject(['scene0', 'bgcolor'])).toBe(false);
expect(getLayoutValObject(['scene1', 'bgcolor'])).toBe(false);
expect(getLayoutValObject(['scene2k', 'bgcolor'])).toBe(false);
});

it('finds polar attributes', function() {
expect(getLayoutValObject(['direction']))
.toBe(polarLayoutAttrs.layout.direction);

expect(getLayoutValObject(['radialaxis', 'range', 0]))
.toBe(polarLayoutAttrs.radialaxis.range.items[0]);

expect(getLayoutValObject(['angularaxis', 'domain']))
.toBe(polarLayoutAttrs.angularaxis.domain);
});
});
0