Skip to content

Commit c2bcfe3

Browse files
committed
fix makeTemplate and test it & template interactions
1 parent 8490804 commit c2bcfe3

File tree

6 files changed

+316
-50
lines changed

6 files changed

+316
-50
lines changed

src/plot_api/make_template.js

+88-28
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
var Lib = require('../lib');
1313
var isPlainObject = Lib.isPlainObject;
1414
var PlotSchema = require('./plot_schema');
15+
var Plots = require('../plots/plots');
1516
var plotAttributes = require('../plots/attributes');
1617
var Template = require('./plot_template');
18+
var dfltConfig = require('./plot_config');
1719

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

3542
var template = {
3643
data: {},
@@ -75,6 +82,7 @@ module.exports = function makeTemplate(figure) {
7582
* since valid options can be context-dependent. It could be solved with
7683
* a *list* of values, but that would be huge complexity for little gain.
7784
*/
85+
delete template.layout.template;
7886
var oldTemplate = layout.template;
7987
if(isPlainObject(oldTemplate)) {
8088
var oldLayoutTemplate = oldTemplate.layout;
@@ -93,9 +101,9 @@ module.exports = function makeTemplate(figure) {
93101
typeLen = typeTemplates.length;
94102
oldTypeLen = oldTypeTemplates.length;
95103
for(i = 0; i < typeLen; i++) {
96-
mergeTemplates(oldTypeTemplates[i % typeLen], typeTemplates[i]);
104+
mergeTemplates(oldTypeTemplates[i % oldTypeLen], typeTemplates[i]);
97105
}
98-
for(; i < oldTypeLen; i++) {
106+
for(i = typeLen; i < oldTypeLen; i++) {
99107
typeTemplates.push(Lib.extendDeep({}, oldTypeTemplates[i]));
100108
}
101109
}
@@ -121,68 +129,120 @@ function mergeTemplates(oldTemplate, newTemplate) {
121129
var oldKeys = Object.keys(oldTemplate).sort();
122130
var i, j;
123131

132+
function mergeOne(oldVal, newVal, key) {
133+
if(isPlainObject(newVal) && isPlainObject(oldVal)) {
134+
mergeTemplates(oldVal, newVal);
135+
}
136+
else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
137+
// Note: omitted `inclusionAttr` from arrayTemplater here,
138+
// it's irrelevant as we only want the resulting `_template`.
139+
var templater = Template.arrayTemplater({_template: oldTemplate}, key);
140+
for(j = 0; j < newVal.length; j++) {
141+
var item = newVal[j];
142+
var oldItem = templater.newItem(item)._template;
143+
if(oldItem) mergeTemplates(oldItem, item);
144+
}
145+
var defaultItems = templater.defaultItems();
146+
for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]._template);
147+
148+
// templateitemname only applies to receiving plots
149+
for(j = 0; j < newVal.length; j++) delete newVal[j].templateitemname;
150+
}
151+
}
152+
124153
for(i = 0; i < oldKeys.length; i++) {
125154
var key = oldKeys[i];
126155
var oldVal = oldTemplate[key];
127156
if(key in newTemplate) {
128-
var newVal = newTemplate[key];
129-
if(isPlainObject(newVal) && isPlainObject(oldVal)) {
130-
mergeTemplates(oldVal, newVal);
131-
}
132-
else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
133-
// Note: omitted `inclusionAttr` from arrayTemplater here,
134-
// it's irrelevant as we only want the resulting `_template`.
135-
var templater = Template.arrayTemplater({_template: oldTemplate}, key);
136-
for(j = 0; j < newVal.length; j++) {
137-
var item = newVal[j];
138-
var oldItem = templater.newItem(item)._template;
139-
if(oldItem) mergeTemplates(oldItem, item);
157+
mergeOne(oldVal, newTemplate[key], key);
158+
}
159+
else newTemplate[key] = oldVal;
160+
161+
// if this is a base key from the old template (eg xaxis), look for
162+
// extended keys (eg xaxis2) in the new template to merge into
163+
if(getBaseKey(key) === key) {
164+
for(var key2 in newTemplate) {
165+
var baseKey2 = getBaseKey(key2);
166+
if(key2 !== baseKey2 && baseKey2 === key && !(key2 in oldTemplate)) {
167+
mergeOne(oldVal, newTemplate[key2], key);
140168
}
141-
var defaultItems = templater.defaultItems();
142-
for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]);
143169
}
144170
}
145-
else newTemplate[key] = oldVal;
146171
}
147172
}
148173

149-
function walkStyleKeys(parent, templateOut, getAttributeInfo, path) {
174+
function getBaseKey(key) {
175+
return key.replace(/[0-9]+$/, '');
176+
}
177+
178+
function walkStyleKeys(parent, templateOut, getAttributeInfo, path, basePath) {
179+
var pathAttr = basePath && getAttributeInfo(basePath);
150180
for(var key in parent) {
151181
var child = parent[key];
152182
var nextPath = getNextPath(parent, key, path);
153-
var attr = getAttributeInfo(nextPath);
183+
var nextBasePath = getNextPath(parent, key, basePath);
184+
var attr = getAttributeInfo(nextBasePath);
185+
if(!attr) {
186+
var baseKey = getBaseKey(key);
187+
if(baseKey !== key) {
188+
nextBasePath = getNextPath(parent, baseKey, basePath);
189+
attr = getAttributeInfo(nextBasePath);
190+
}
191+
}
192+
193+
// we'll get an attr if path starts with a valid part, then has an
194+
// invalid ending. Make sure we got all the way to the end.
195+
if(pathAttr && (pathAttr === attr)) continue;
154196

155-
if(!attr ||
197+
if(!attr || attr._noTemplating ||
156198
attr.valType === 'data_array' ||
157199
(attr.arrayOk && Array.isArray(child))
158200
) {
159201
continue;
160202
}
161203

162204
if(!attr.valType && isPlainObject(child)) {
163-
walkStyleKeys(child, templateOut, getAttributeInfo, nextPath);
205+
walkStyleKeys(child, templateOut, getAttributeInfo, nextPath, nextBasePath);
164206
}
165207
else if(attr._isLinkedToArray && Array.isArray(child)) {
166208
var dfltDone = false;
167209
var namedIndex = 0;
210+
var usedNames = {};
168211
for(var i = 0; i < child.length; i++) {
169212
var item = child[i];
170213
if(isPlainObject(item)) {
171-
if(item.name) {
172-
walkStyleKeys(item, templateOut, getAttributeInfo,
173-
getNextPath(child, namedIndex, nextPath));
174-
namedIndex++;
214+
var name = item.name;
215+
if(name) {
216+
if(!usedNames[name]) {
217+
// named array items: allow all attributes except data arrays
218+
walkStyleKeys(item, templateOut, getAttributeInfo,
219+
getNextPath(child, namedIndex, nextPath),
220+
getNextPath(child, namedIndex, nextBasePath));
221+
namedIndex++;
222+
usedNames[name] = 1;
223+
}
175224
}
176225
else if(!dfltDone) {
177226
var dfltKey = Template.arrayDefaultKey(key);
178-
walkStyleKeys(item, templateOut, getAttributeInfo,
179-
getNextPath(parent, dfltKey, path));
227+
var dfltPath = getNextPath(parent, dfltKey, path);
228+
229+
// getAttributeInfo will fail if we try to use dfltKey directly.
230+
// Instead put this item into the next array element, then
231+
// pull it out and move it to dfltKey.
232+
var pathInArray = getNextPath(child, namedIndex, nextPath);
233+
walkStyleKeys(item, templateOut, getAttributeInfo, pathInArray,
234+
getNextPath(child, namedIndex, nextBasePath));
235+
var itemPropInArray = Lib.nestedProperty(templateOut, pathInArray);
236+
var dfltProp = Lib.nestedProperty(templateOut, dfltPath);
237+
dfltProp.set(itemPropInArray.get());
238+
itemPropInArray.set(null);
239+
180240
dfltDone = true;
181241
}
182242
}
183243
}
184244
}
185-
else if(attr.role === 'style') {
245+
else {
186246
var templateProp = Lib.nestedProperty(templateOut, nextPath);
187247
templateProp.set(child);
188248
}

src/plots/attributes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ module.exports = {
1616
role: 'info',
1717
values: [], // listed dynamically
1818
dflt: 'scatter',
19-
editType: 'calc+clearAxisTypes'
19+
editType: 'calc+clearAxisTypes',
20+
_noTemplating: true // we handle this at a higher level
2021
},
2122
visible: {
2223
valType: 'enumerated',

src/plots/cartesian/layout_attributes.js

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ module.exports = {
6161
dflt: '-',
6262
role: 'info',
6363
editType: 'calc',
64+
// we forget when an axis has been autotyped, just writing the auto
65+
// value back to the input - so it doesn't make sense to template this.
66+
// TODO: should we prohibit this in `coerce` as well, or honor it if
67+
// someone enters it explicitly?
68+
_noTemplating: true,
6469
description: [
6570
'Sets the axis type.',
6671
'By default, plotly attempts to determined the axis type',

test/image/mocks/template.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"yaxis2": {"anchor": "x2", "title": "y2"},
1414
"annotations": [
1515
{"text": "Hi!", "x": 0, "y": 3.5},
16-
{"templateitemname": "watermark", "font": {"size": 120}},
16+
{"templateitemname": "watermark", "font": {"size": 120}, "name": "new watermark"},
1717
{"templateitemname": "watermark", "font": {"size": 110}},
1818
{"templateitemname": "watermark", "font": {"size": 100}},
1919
{"templateitemname": "nope", "text": "Buh-bye"}

test/jasmine/tests/plotschema_test.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ describe('plot schema', function() {
115115
var valObject = valObjects[attr.valType],
116116
opts = valObject.requiredOpts
117117
.concat(valObject.otherOpts)
118-
.concat(['valType', 'description', 'role', 'editType', 'impliedEdits', '_compareAsJSON']);
118+
.concat([
119+
'valType', 'description', 'role',
120+
'editType', 'impliedEdits',
121+
'_compareAsJSON', '_noTemplating'
122+
]);
119123

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

0 commit comments

Comments
 (0)