Skip to content

Templates #2761

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 24 commits into from
Jul 3, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b8950c5
fix devtools timeit sorting
alexcjohnson Jun 19, 2018
355fb09
stop pushing tickmode back to axis in for a particular edge case
alexcjohnson Jun 18, 2018
8ddcfd6
minor simplification in pie defaults
alexcjohnson Jun 21, 2018
b5ccfbe
combine annotation defaults files & shape defaults files
alexcjohnson Jun 21, 2018
86e09bb
add visible attribute to lots of container array items:
alexcjohnson Jun 20, 2018
3dee25c
use handleArrayContainerDefaults for various array containers:
alexcjohnson Jun 21, 2018
5cca8d3
templates - big commit implementing most of it, coerce-level integration
bpostlethwaite Apr 12, 2018
b3da4e7
:hocho: itemIsNotPlainObject from handleArrayContainerDefaults
alexcjohnson Jun 26, 2018
8525953
add template attributes to array containers
alexcjohnson Jun 26, 2018
e306d1c
template-safe axis default color inheritance logic
alexcjohnson Jun 27, 2018
8e2a321
template-safe GUI editing of array objects
alexcjohnson Jun 27, 2018
4346611
template mock
alexcjohnson Jun 27, 2018
fb489aa
tickformatstops.visible -> enabled
alexcjohnson Jun 28, 2018
bc21cc8
:hocho: done TODO
alexcjohnson Jun 28, 2018
8490804
fix test failures - and simplify handleArrayContainerDefaults even mo…
alexcjohnson Jun 28, 2018
c2bcfe3
fix makeTemplate and test it & template interactions
alexcjohnson Jul 1, 2018
890a324
fix Plotly.validate with attributes that end in numbers
alexcjohnson Jul 2, 2018
aed44dc
Plotly.validateTemplate
alexcjohnson Jul 3, 2018
6df61e0
loosen template default item interaction tests to pass on CI
alexcjohnson Jul 3, 2018
ef4c3cc
TODO -> note re: axis.type _noTemplate
alexcjohnson Jul 3, 2018
b1c6f0a
_noTemplating for angularaxis.type
alexcjohnson Jul 3, 2018
818cac9
:cow2: test name typo
alexcjohnson Jul 3, 2018
15931cf
recurse into (template)layout looking for unused containers
alexcjohnson Jul 3, 2018
8598bc9
:hocho: obsolete code
alexcjohnson Jul 3, 2018
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
116 changes: 88 additions & 28 deletions src/plot_api/make_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
var Lib = require('../lib');
var isPlainObject = Lib.isPlainObject;
var PlotSchema = require('./plot_schema');
var Plots = require('../plots/plots');
var plotAttributes = require('../plots/attributes');
var Template = require('./plot_template');
var dfltConfig = require('./plot_config');

/**
* Plotly.makeTemplate: create a template off an existing figure to reuse
Expand All @@ -29,8 +31,13 @@ var Template = require('./plot_template');
* `layout.template` in another figure.
*/
module.exports = function makeTemplate(figure) {
figure = Lib.extendDeep({_context: dfltConfig}, figure);
Plots.supplyDefaults(figure);
var data = figure.data || [];
var layout = figure.layout || {};
// copy over a few items to help follow the schema
layout._basePlotModules = figure._fullLayout._basePlotModules;
layout._modules = figure._fullLayout._modules;

var template = {
data: {},
Expand Down Expand Up @@ -75,6 +82,7 @@ module.exports = function makeTemplate(figure) {
* since valid options can be context-dependent. It could be solved with
* a *list* of values, but that would be huge complexity for little gain.
*/
delete template.layout.template;
var oldTemplate = layout.template;
if(isPlainObject(oldTemplate)) {
var oldLayoutTemplate = oldTemplate.layout;
Expand All @@ -93,9 +101,9 @@ module.exports = function makeTemplate(figure) {
typeLen = typeTemplates.length;
oldTypeLen = oldTypeTemplates.length;
for(i = 0; i < typeLen; i++) {
mergeTemplates(oldTypeTemplates[i % typeLen], typeTemplates[i]);
mergeTemplates(oldTypeTemplates[i % oldTypeLen], typeTemplates[i]);
}
for(; i < oldTypeLen; i++) {
for(i = typeLen; i < oldTypeLen; i++) {
typeTemplates.push(Lib.extendDeep({}, oldTypeTemplates[i]));
}
}
Expand All @@ -121,68 +129,120 @@ function mergeTemplates(oldTemplate, newTemplate) {
var oldKeys = Object.keys(oldTemplate).sort();
var i, j;

function mergeOne(oldVal, newVal, key) {
if(isPlainObject(newVal) && isPlainObject(oldVal)) {
mergeTemplates(oldVal, newVal);
}
else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
// Note: omitted `inclusionAttr` from arrayTemplater here,
// it's irrelevant as we only want the resulting `_template`.
var templater = Template.arrayTemplater({_template: oldTemplate}, key);
for(j = 0; j < newVal.length; j++) {
var item = newVal[j];
var oldItem = templater.newItem(item)._template;
if(oldItem) mergeTemplates(oldItem, item);
}
var defaultItems = templater.defaultItems();
for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]._template);

// templateitemname only applies to receiving plots
for(j = 0; j < newVal.length; j++) delete newVal[j].templateitemname;
}
}

for(i = 0; i < oldKeys.length; i++) {
var key = oldKeys[i];
var oldVal = oldTemplate[key];
if(key in newTemplate) {
var newVal = newTemplate[key];
if(isPlainObject(newVal) && isPlainObject(oldVal)) {
mergeTemplates(oldVal, newVal);
}
else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
// Note: omitted `inclusionAttr` from arrayTemplater here,
// it's irrelevant as we only want the resulting `_template`.
var templater = Template.arrayTemplater({_template: oldTemplate}, key);
for(j = 0; j < newVal.length; j++) {
var item = newVal[j];
var oldItem = templater.newItem(item)._template;
if(oldItem) mergeTemplates(oldItem, item);
mergeOne(oldVal, newTemplate[key], key);
}
else newTemplate[key] = oldVal;

// if this is a base key from the old template (eg xaxis), look for
// extended keys (eg xaxis2) in the new template to merge into
if(getBaseKey(key) === key) {
for(var key2 in newTemplate) {
var baseKey2 = getBaseKey(key2);
if(key2 !== baseKey2 && baseKey2 === key && !(key2 in oldTemplate)) {
mergeOne(oldVal, newTemplate[key2], key);
}
var defaultItems = templater.defaultItems();
for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]);
}
}
else newTemplate[key] = oldVal;
}
}

function walkStyleKeys(parent, templateOut, getAttributeInfo, path) {
function getBaseKey(key) {
return key.replace(/[0-9]+$/, '');
}

function walkStyleKeys(parent, templateOut, getAttributeInfo, path, basePath) {
var pathAttr = basePath && getAttributeInfo(basePath);
for(var key in parent) {
var child = parent[key];
var nextPath = getNextPath(parent, key, path);
var attr = getAttributeInfo(nextPath);
var nextBasePath = getNextPath(parent, key, basePath);
var attr = getAttributeInfo(nextBasePath);
if(!attr) {
var baseKey = getBaseKey(key);
if(baseKey !== key) {
nextBasePath = getNextPath(parent, baseKey, basePath);
attr = getAttributeInfo(nextBasePath);
}
}

// we'll get an attr if path starts with a valid part, then has an
// invalid ending. Make sure we got all the way to the end.
if(pathAttr && (pathAttr === attr)) continue;

if(!attr ||
if(!attr || attr._noTemplating ||
attr.valType === 'data_array' ||
(attr.arrayOk && Array.isArray(child))
) {
continue;
}

if(!attr.valType && isPlainObject(child)) {
walkStyleKeys(child, templateOut, getAttributeInfo, nextPath);
walkStyleKeys(child, templateOut, getAttributeInfo, nextPath, nextBasePath);
}
else if(attr._isLinkedToArray && Array.isArray(child)) {
var dfltDone = false;
var namedIndex = 0;
var usedNames = {};
for(var i = 0; i < child.length; i++) {
var item = child[i];
if(isPlainObject(item)) {
if(item.name) {
walkStyleKeys(item, templateOut, getAttributeInfo,
getNextPath(child, namedIndex, nextPath));
namedIndex++;
var name = item.name;
if(name) {
if(!usedNames[name]) {
// named array items: allow all attributes except data arrays
walkStyleKeys(item, templateOut, getAttributeInfo,
getNextPath(child, namedIndex, nextPath),
getNextPath(child, namedIndex, nextBasePath));
namedIndex++;
usedNames[name] = 1;
}
}
else if(!dfltDone) {
var dfltKey = Template.arrayDefaultKey(key);
walkStyleKeys(item, templateOut, getAttributeInfo,
getNextPath(parent, dfltKey, path));
var dfltPath = getNextPath(parent, dfltKey, path);

// getAttributeInfo will fail if we try to use dfltKey directly.
// Instead put this item into the next array element, then
// pull it out and move it to dfltKey.
var pathInArray = getNextPath(child, namedIndex, nextPath);
walkStyleKeys(item, templateOut, getAttributeInfo, pathInArray,
getNextPath(child, namedIndex, nextBasePath));
var itemPropInArray = Lib.nestedProperty(templateOut, pathInArray);
var dfltProp = Lib.nestedProperty(templateOut, dfltPath);
dfltProp.set(itemPropInArray.get());
itemPropInArray.set(null);

dfltDone = true;
}
}
}
}
else if(attr.role === 'style') {
else {
var templateProp = Lib.nestedProperty(templateOut, nextPath);
templateProp.set(child);
}
Expand Down
3 changes: 2 additions & 1 deletion src/plots/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module.exports = {
role: 'info',
values: [], // listed dynamically
dflt: 'scatter',
editType: 'calc+clearAxisTypes'
editType: 'calc+clearAxisTypes',
_noTemplating: true // we handle this at a higher level
},
visible: {
valType: 'enumerated',
Expand Down
5 changes: 5 additions & 0 deletions src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ module.exports = {
dflt: '-',
role: 'info',
editType: 'calc',
// we forget when an axis has been autotyped, just writing the auto
// value back to the input - so it doesn't make sense to template this.
// TODO: should we prohibit this in `coerce` as well, or honor it if
// someone enters it explicitly?
_noTemplating: true,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I prevented makeTemplate from pulling out axis.type with this flag, as it seems more likely to cause problems than fix them, but I did NOT prevent coerce from using it if someone makes a template of manually and includes it (to say "always make this axis categorical" for example). Seem reasonable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seem reasonable?

Very reasonable 👌

Copy link
Contributor

Choose a reason for hiding this comment

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

polar.angularaxis.type does not inherit from type in cartesian/layout_attributes.js. Should it also have _noTemplating: true?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good call - angularaxis.type gets _noTemplating in b1c6f0a

description: [
'Sets the axis type.',
'By default, plotly attempts to determined the axis type',
Expand Down
2 changes: 1 addition & 1 deletion test/image/mocks/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"yaxis2": {"anchor": "x2", "title": "y2"},
"annotations": [
{"text": "Hi!", "x": 0, "y": 3.5},
{"templateitemname": "watermark", "font": {"size": 120}},
{"templateitemname": "watermark", "font": {"size": 120}, "name": "new watermark"},
{"templateitemname": "watermark", "font": {"size": 110}},
{"templateitemname": "watermark", "font": {"size": 100}},
{"templateitemname": "nope", "text": "Buh-bye"}
Expand Down
6 changes: 5 additions & 1 deletion test/jasmine/tests/plotschema_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ describe('plot schema', function() {
var valObject = valObjects[attr.valType],
opts = valObject.requiredOpts
.concat(valObject.otherOpts)
.concat(['valType', 'description', 'role', 'editType', 'impliedEdits', '_compareAsJSON']);
.concat([
'valType', 'description', 'role',
'editType', 'impliedEdits',
'_compareAsJSON', '_noTemplating'
]);

Object.keys(attr).forEach(function(key) {
expect(opts.indexOf(key) !== -1).toBe(true, key, attr);
Expand Down
Loading