package.src.plot_api.manage_arrays.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plotly.js Show documentation
Show all versions of plotly.js Show documentation
The open source javascript graphing library that powers plotly
The newest version!
'use strict';
var isPlainObject = require('../lib/is_plain_object');
var noop = require('../lib/noop');
var Loggers = require('../lib/loggers');
var sorterAsc = require('../lib/search').sorterAsc;
var Registry = require('../registry');
exports.containerArrayMatch = require('./container_array_match');
var isAddVal = exports.isAddVal = function isAddVal(val) {
return val === 'add' || isPlainObject(val);
};
var isRemoveVal = exports.isRemoveVal = function isRemoveVal(val) {
return val === null || val === 'remove';
};
/*
* applyContainerArrayChanges: for managing arrays of layout components in relayout
* handles them all with a consistent interface.
*
* Here are the supported actions -> relayout calls -> edits we get here
* (as prepared in _relayout):
*
* add an empty obj -> {'annotations[2]': 'add'} -> {2: {'': 'add'}}
* add a specific obj -> {'annotations[2]': {attrs}} -> {2: {'': {attrs}}}
* delete an obj -> {'annotations[2]': 'remove'} -> {2: {'': 'remove'}}
* -> {'annotations[2]': null} -> {2: {'': null}}
* delete the whole array -> {'annotations': 'remove'} -> {'': {'': 'remove'}}
* -> {'annotations': null} -> {'': {'': null}}
* edit an object -> {'annotations[2].text': 'boo'} -> {2: {'text': 'boo'}}
*
* You can combine many edits to different objects. Objects are added and edited
* in ascending order, then removed in descending order.
* For example, starting with [a, b, c], if you want to:
* - replace b with d:
* {'annotations[1]': d, 'annotations[2]': null} (b is item 2 after adding d)
* - add a new item d between a and b, and edit b:
* {'annotations[1]': d, 'annotations[2].x': newX} (b is item 2 after adding d)
* - delete b and edit c:
* {'annotations[1]': null, 'annotations[2].x': newX} (c is edited before b is removed)
*
* You CANNOT combine adding/deleting an item at index `i` with edits to the same index `i`
* You CANNOT combine replacing/deleting the whole array with anything else (for the same array).
*
* @param {HTMLDivElement} gd
* the DOM element of the graph container div
* @param {Lib.nestedProperty} componentType: the array we are editing
* @param {Object} edits
* the changes to make; keys are indices to edit, values are themselves objects:
* {attr: newValue} of changes to make to that index (with add/remove behavior
* in special values of the empty attr)
* @param {Object} flags
* the flags for which actions we're going to perform to display these (and
* any other) changes. If we're already `recalc`ing, we don't need to redraw
* individual items
* @param {function} _nestedProperty
* a (possibly modified for gui edits) nestedProperty constructor
* The modified version takes a 3rd argument, for a prefix to the attribute
* string necessary for storing GUI edits
*
* @returns {bool} `true` if it managed to complete drawing of the changes
* `false` would mean the parent should replot.
*/
exports.applyContainerArrayChanges = function applyContainerArrayChanges(gd, np, edits, flags, _nestedProperty) {
var componentType = np.astr;
var supplyComponentDefaults = Registry.getComponentMethod(componentType, 'supplyLayoutDefaults');
var draw = Registry.getComponentMethod(componentType, 'draw');
var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
var replotLater = flags.replot || flags.recalc || (supplyComponentDefaults === noop) || (draw === noop);
var layout = gd.layout;
var fullLayout = gd._fullLayout;
if(edits['']) {
if(Object.keys(edits).length > 1) {
Loggers.warn('Full array edits are incompatible with other edits',
componentType);
}
var fullVal = edits[''][''];
if(isRemoveVal(fullVal)) np.set(null);
else if(Array.isArray(fullVal)) np.set(fullVal);
else {
Loggers.warn('Unrecognized full array edit value', componentType, fullVal);
return true;
}
if(replotLater) return false;
supplyComponentDefaults(layout, fullLayout);
draw(gd);
return true;
}
var componentNums = Object.keys(edits).map(Number).sort(sorterAsc);
var componentArrayIn = np.get();
var componentArray = componentArrayIn || [];
// componentArrayFull is used just to keep splices in line between
// full and input arrays, so private keys can be copied over after
// redoing supplyDefaults
// TODO: this assumes componentArray is in gd.layout - which will not be
// true after we extend this to restyle
var componentArrayFull = _nestedProperty(fullLayout, componentType).get();
var deletes = [];
var firstIndexChange = -1;
var maxIndex = componentArray.length;
var i;
var j;
var componentNum;
var objEdits;
var objKeys;
var objVal;
var adding, prefix;
// first make the add and edit changes
for(i = 0; i < componentNums.length; i++) {
componentNum = componentNums[i];
objEdits = edits[componentNum];
objKeys = Object.keys(objEdits);
objVal = objEdits[''],
adding = isAddVal(objVal);
if(componentNum < 0 || componentNum > componentArray.length - (adding ? 0 : 1)) {
Loggers.warn('index out of range', componentType, componentNum);
continue;
}
if(objVal !== undefined) {
if(objKeys.length > 1) {
Loggers.warn(
'Insertion & removal are incompatible with edits to the same index.',
componentType, componentNum);
}
if(isRemoveVal(objVal)) {
deletes.push(componentNum);
} else if(adding) {
if(objVal === 'add') objVal = {};
componentArray.splice(componentNum, 0, objVal);
if(componentArrayFull) componentArrayFull.splice(componentNum, 0, {});
} else {
Loggers.warn('Unrecognized full object edit value',
componentType, componentNum, objVal);
}
if(firstIndexChange === -1) firstIndexChange = componentNum;
} else {
for(j = 0; j < objKeys.length; j++) {
prefix = componentType + '[' + componentNum + '].';
_nestedProperty(componentArray[componentNum], objKeys[j], prefix)
.set(objEdits[objKeys[j]]);
}
}
}
// now do deletes
for(i = deletes.length - 1; i >= 0; i--) {
componentArray.splice(deletes[i], 1);
// TODO: this drops private keys that had been stored in componentArrayFull
// does this have any ill effects?
if(componentArrayFull) componentArrayFull.splice(deletes[i], 1);
}
if(!componentArray.length) np.set(null);
else if(!componentArrayIn) np.set(componentArray);
if(replotLater) return false;
supplyComponentDefaults(layout, fullLayout);
// finally draw all the components we need to
// if we added or removed any, redraw all after it
if(drawOne !== noop) {
var indicesToDraw;
if(firstIndexChange === -1) {
// there's no re-indexing to do, so only redraw components that changed
indicesToDraw = componentNums;
} else {
// in case the component array was shortened, we still need do call
// drawOne on the latter items so they get properly removed
maxIndex = Math.max(componentArray.length, maxIndex);
indicesToDraw = [];
for(i = 0; i < componentNums.length; i++) {
componentNum = componentNums[i];
if(componentNum >= firstIndexChange) break;
indicesToDraw.push(componentNum);
}
for(i = firstIndexChange; i < maxIndex; i++) {
indicesToDraw.push(i);
}
}
for(i = 0; i < indicesToDraw.length; i++) {
drawOne(gd, indicesToDraw[i]);
}
} else draw(gd);
return true;
};