function Geo(options) { this.id = options.id; this.graphDiv = options.graphDiv; this.container = options.container; this.topojsonURL = options.topojsonURL; this.topojsonName = null; this.topojson = null; this.projectionType = null; this.projection = null; this.clipAngle = null; this.setScale = null; this.path = null; this.zoom = null; this.zoomReset = null; this.makeFramework(); this.traceHash = {}; }
n/a
function ScrollBox(gd, container, id) { this.gd = gd; this.container = container; this.id = id; // See ScrollBox.prototype.enable for further definition this.position = null; // scrollbox position this.translateX = null; // scrollbox horizontal translation this.translateY = null; // scrollbox vertical translation this.hbar = null; // horizontal scrollbar D3 selection this.vbar = null; // vertical scrollbar D3 selection // <rect> element to capture pointer events this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]); this.bg.exit() .on('.drag', null) .on('wheel', null) .remove(); this.bg.enter().append('rect') .classed('scrollbox-bg', true) .style('pointer-events', 'all') .attr({ opacity: 0, x: 0, y: 0, width: 0, height: 0 }); }
n/a
function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) { this.traces = traces; this.separateNegativeValues = separateNegativeValues; this.dontMergeOverlappingData = dontMergeOverlappingData; var positions = []; for(var i = 0; i < traces.length; i++) { var trace = traces[i]; for(var j = 0; j < trace.length; j++) { var bar = trace[j]; if(bar.p !== BADNUM) positions.push(bar.p); } } this.positions = positions; var dv = Lib.distinctVals(this.positions); this.distinctPositions = dv.vals; this.minDiff = dv.minDiff; this.binWidth = this.minDiff; this.bins = {}; }
n/a
function Ternary(options, fullLayout) { this.id = options.id; this.graphDiv = options.graphDiv; this.init(fullLayout); this.makeFramework(); }
n/a
function isBottomAnchor(opts) { return ( opts.yanchor === 'bottom' || (opts.yanchor === 'auto' && opts.y <= 1 / 3) ); }
n/a
function isCenterAnchor(opts) { return ( opts.xanchor === 'center' || (opts.xanchor === 'auto' && opts.x > 1 / 3 && opts.x < 2 / 3) ); }
n/a
function isMiddleAnchor(opts) { return ( opts.yanchor === 'middle' || (opts.yanchor === 'auto' && opts.y > 1 / 3 && opts.y < 2 / 3) ); }
n/a
function isRightAnchor(opts) { return ( opts.xanchor === 'right' || (opts.xanchor === 'auto' && opts.x >= 2 / 3) ); }
n/a
autoBin = function (data, ax, nbins, is2d, calendar) { var dataMin = Lib.aggNums(Math.min, null, data), dataMax = Lib.aggNums(Math.max, null, data); if(!calendar) calendar = ax.calendar; if(ax.type === 'category') { return { start: dataMin - 0.5, end: dataMax + 0.5, size: 1 }; } var size0; if(nbins) size0 = ((dataMax - dataMin) / nbins); else { // totally auto: scale off std deviation so the highest bin is // somewhat taller than the total number of bins, but don't let // the size get smaller than the 'nice' rounded down minimum // difference between values var distinctData = Lib.distinctVals(data), msexp = Math.pow(10, Math.floor( Math.log(distinctData.minDiff) / Math.LN10)), minSize = msexp * Lib.roundUp( distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true); size0 = Math.max(minSize, 2 * Lib.stdev(data) / Math.pow(data.length, is2d ? 0.25 : 0.4)); // fallback if ax.d2c output BADNUMs // e.g. when user try to plot categorical bins // on a layout.xaxis.type: 'linear' if(!isNumeric(size0)) size0 = 1; } // piggyback off autotick code to make "nice" bin sizes var dummyAx; if(ax.type === 'log') { dummyAx = { type: 'linear', range: [dataMin, dataMax] }; } else { dummyAx = { type: ax.type, range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar), calendar: calendar }; } axes.setConvert(dummyAx); axes.autoTicks(dummyAx, size0); var binStart = axes.tickIncrement( axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar), binEnd; // check for too many data points right at the edges of bins // (>50% within 1% of bin edges) or all data points integral // and offset the bins accordingly if(typeof dummyAx.dtick === 'number') { binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax); var bincount = 1 + Math.floor((dataMax - binStart) / dummyAx.dtick); binEnd = binStart + bincount * dummyAx.dtick; } else { // month ticks - should be the only nonlinear kind we have at this point. // dtick (as supplied by axes.autoTick) only has nonlinear values on // date and log axes, but even if you display a histogram on a log axis // we bin it on a linear axis (which one could argue against, but that's // a separate issue) if(dummyAx.dtick.charAt(0) === 'M') { binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin, calendar); } // calculate the endpoint for nonlinear ticks - you have to // just increment until you're done binEnd = binStart; while(binEnd <= dataMax) { binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar); } } return { start: ax.c2r(binStart, 0, calendar), end: ax.c2r(binEnd, 0, calendar), size: dummyAx.dtick }; }
n/a
autoTicks = function (ax, roughDTick) { var base; if(ax.type === 'date') { ax.tick0 = Lib.dateTick0(ax.calendar); // the criteria below are all based on the rough spacing we calculate // being > half of the final unit - so precalculate twice the rough val var roughX2 = 2 * roughDTick; if(roughX2 > ONEAVGYEAR) { roughDTick /= ONEAVGYEAR; base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10)); } else if(roughX2 > ONEAVGMONTH) { roughDTick /= ONEAVGMONTH; ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24); } else if(roughX2 > ONEDAY) { ax.dtick = roundDTick(roughDTick, ONEDAY, roundDays); // get week ticks on sunday // this will also move the base tick off 2000-01-01 if dtick is // 2 or 3 days... but that's a weird enough case that we'll ignore it. ax.tick0 = Lib.dateTick0(ax.calendar, true); } else if(roughX2 > ONEHOUR) { ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24); } else if(roughX2 > ONEMIN) { ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60); } else if(roughX2 > ONESEC) { ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60); } else { // milliseconds base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); ax.dtick = roundDTick(roughDTick, base, roundBase10); } } else if(ax.type === 'log') { ax.tick0 = 0; var rng = Lib.simpleMap(ax.range, ax.r2l); if(roughDTick > 0.7) { // only show powers of 10 ax.dtick = Math.ceil(roughDTick); } else if(Math.abs(rng[1] - rng[0]) < 1) { // span is less than one power of 10 var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick); // ticks on a linear scale, labeled fully roughDTick = Math.abs(Math.pow(10, rng[1]) - Math.pow(10, rng[0])) / nt; base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10); } else { // include intermediates between powers of 10, // labeled with small digits // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits) ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1'; } } else if(ax.type === 'category') { ax.tick0 = 0; ax.dtick = Math.ceil(Math.max(roughDTick, 1)); } else { // auto ticks always start at 0 ax.tick0 = 0; base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10)); ax.dtick = roundDTick(roughDTick, base, roundBase10); } // prevent infinite loops if(ax.dtick === 0) ax.dtick = 1; // TODO: this is from log axis histograms with autorange off if(!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') { var olddtick = ax.dtick; ax.dtick = 1; throw 'ax.dtick error: ' + String(olddtick); } }
...
type: ax.type,
range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
calendar: calendar
};
}
axes.setConvert(dummyAx);
axes.autoTicks(dummyAx, size0);
var binStart = axes.tickIncrement(
axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
binEnd;
// check for too many data points right at the edges of bins
// (>50% within 1% of bin edges) or all data points integral
// and offset the bins accordingly
...
function calcTicks(ax) { var rng = Lib.simpleMap(ax.range, ax.r2l); // calculate max number of (auto) ticks to display based on plot size if(ax.tickmode === 'auto' || !ax.dtick) { var nt = ax.nticks, minPx; if(!nt) { if(ax.type === 'category') { minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15; nt = ax._length / minPx; } else { minPx = ax._id.charAt(0) === 'y' ? 40 : 80; nt = Lib.constrain(ax._length / minPx, 4, 9) + 1; } } // add a couple of extra digits for filling in ticks when we // have explicit tickvals without tick text if(ax.tickmode === 'array') nt *= 100; axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt); // check for a forced minimum dtick if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) { ax.dtick = ax._minDtick; ax.tick0 = ax.l2r(ax._forceTick0); } } // check for missing tick0 if(!ax.tick0) { ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0; } // now figure out rounding of tick values autoTickRound(ax); // now that we've figured out the auto values for formatting // in case we're missing some ticktext, we can break out for array ticks if(ax.tickmode === 'array') return arrayTicks(ax); // find the first tick ax._tmin = axes.tickFirst(ax); // check for reversed axis var axrev = (rng[1] < rng[0]); // return the full set of tick vals var vals = [], // add a tiny bit so we get ticks which may have rounded out endtick = rng[1] * 1.0001 - rng[0] * 0.0001; if(ax.type === 'category') { endtick = (axrev) ? Math.max(-0.5, endtick) : Math.min(ax._categories.length - 0.5, endtick); } for(var x = ax._tmin; (axrev) ? (x >= endtick) : (x <= endtick); x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) { vals.push(x); // prevent infinite loops if(vals.length > 1000) break; } // save the last tick as well as first, so we can // show the exponent only on the last one ax._tmax = vals[vals.length - 1]; // for showing the rest of a date when the main tick label is only the // latter part: ax._prevDateHead holds what we showed most recently. // Start with it cleared and mark that we're in calcTicks (ie calculating a // whole string of these so we should care what the previous date head was!) ax._prevDateHead = ''; ax._inCalcTicks = true; var ticksOut = new Array(vals.length); for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]); ax._inCalcTicks = false; return ticksOut; }
...
}
// set scaling to pixels
ax.setScale();
var axLetter = axid.charAt(0),
counterLetter = axes.counterLetter(axid),
vals = axes.calcTicks(ax),
datafn = function(d) { return [d.text, d.x, ax.mirror].join('_'); },
tcls = axid + 'tick',
gcls = axid + 'grid',
zcls = axid + 'zl',
pad = (ax.linewidth || 1) / 2,
labelStandoff =
(ax.ticks === 'outside' ? ax.ticklen : 1) + (ax.linewidth || 0),
...
function cleanId(id, axLetter) { if(!id.match(constants.AX_ID_PATTERN)) return; if(axLetter && id.charAt(0) !== axLetter) return; var axNum = id.substr(1).replace(/^0+/, ''); if(axNum === '1') axNum = ''; return id.charAt(0) + axNum; }
...
delete layout.yaxis1;
}
var axList = Axes.list({_fullLayout: layout});
for(i = 0; i < axList.length; i++) {
var ax = axList[i];
if(ax.anchor && ax.anchor !== 'free') {
ax.anchor = Axes.cleanId(ax.anchor);
}
if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
// old method of axis type - isdate and islog (before category existed)
if(!ax.type) {
if(ax.isdate) ax.type = 'date';
else if(ax.islog) ax.type = 'log';
...
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; }); }
...
} else if(hovermode.get() === 'y') {
hovermode.set('x');
}
}
// 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) ||
...
coercePosition = function (containerOut, gd, coerce, axRef, attr, dflt) { var pos, newPos; if(axRef === 'paper' || axRef === 'pixel') { pos = coerce(attr, dflt); } else { var ax = axes.getFromId(gd, axRef); dflt = ax.fraction2r(dflt); pos = coerce(attr, dflt); if(ax.type === 'category') { // if position is given as a category name, convert it to a number if(typeof pos === 'string' && (ax._categories || []).length) { newPos = ax._categories.indexOf(pos); containerOut[attr] = (newPos === -1) ? dflt : newPos; return; } } else if(ax.type === 'date') { containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar); return; } } // finally make sure we have a number (unless date type already returned a string) containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt; }
...
for(var i = 0; i < 2; i++) {
var axLetter = axLetters[i];
// xref, yref
var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper');
// x, y
Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);
if(showArrow) {
var arrowPosAttr = 'a' + axLetter,
// axref, ayref
aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel');
// for now the arrow can only be on the same axis or specified as pixels
...
coerceRef = function (containerIn, containerOut, gd, attr, dflt, extraOption) { var axLetter = attr.charAt(attr.length - 1), axlist = axes.listIds(gd, axLetter), refAttr = attr + 'ref', attrDef = {}; if(!dflt) dflt = axlist[0] || extraOption; if(!extraOption) extraOption = dflt; // data-ref annotations are not supported in gl2d yet attrDef[refAttr] = { valType: 'enumerated', values: axlist.concat(extraOption ? [extraOption] : []), dflt: dflt }; // xref, yref return Lib.coerce(containerIn, containerOut, attrDef, refAttr); }
...
var axLetters = ['x', 'y'],
arrowPosDflt = [-10, -30],
gdMock = {_fullLayout: fullLayout};
for(var i = 0; i < 2; i++) {
var axLetter = axLetters[i];
// xref, yref
var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper
');
// x, y
Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);
if(showArrow) {
var arrowPosAttr = 'a' + axLetter,
// axref, ayref
...
counterLetter = function (id) { var axLetter = id.charAt(0); if(axLetter === 'x') return 'y'; if(axLetter === 'y') return 'x'; }
...
}
}
// set scaling to pixels
ax.setScale();
var axLetter = axid.charAt(0),
counterLetter = axes.counterLetter(axid),
vals = axes.calcTicks(ax),
datafn = function(d) { return [d.text, d.x, ax.mirror].join('_'); },
tcls = axid + 'tick',
gcls = axid + 'grid',
zcls = axid + 'zl',
pad = (ax.linewidth || 1) / 2,
labelStandoff =
...
doAutoRange = function (ax) { if(!ax._length) ax.setScale(); // TODO do we really need this? var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length); if(ax.autorange && hasDeps) { ax.range = axes.getAutoRange(ax); // doAutoRange will get called on fullLayout, // but we want to report its results back to layout var axIn = ax._input; axIn.range = ax.range.slice(); axIn.autorange = ax.autorange; } }
...
}
function doAutoRangeAndConstraints() {
if(gd._transitioning) return;
var axList = Plotly.Axes.list(gd, '', true);
for(var i = 0; i < axList.length; i++) {
Plotly.Axes.doAutoRange(axList[i]);
}
enforceAxisConstraints(gd);
// store initial ranges *after* enforcing constraints, otherwise
// we will never look like we're at the initial ranges
if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
...
doTicks = function (gd, axid, skipTitle) { var fullLayout = gd._fullLayout, ax, independent = false; // allow passing an independent axis object instead of id if(typeof axid === 'object') { ax = axid; axid = ax._id; independent = true; } else { ax = axes.getFromId(gd, axid); if(axid === 'redraw') { fullLayout._paper.selectAll('g.subplot').each(function(subplot) { var plotinfo = fullLayout._plots[subplot], xa = plotinfo.xaxis, ya = plotinfo.yaxis; plotinfo.xaxislayer .selectAll('.' + xa._id + 'tick').remove(); plotinfo.yaxislayer .selectAll('.' + ya._id + 'tick').remove(); plotinfo.gridlayer .selectAll('path').remove(); plotinfo.zerolinelayer .selectAll('path').remove(); }); } if(!axid || axid === 'redraw') { return Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) { return function() { if(!ax._id) return; var axDone = axes.doTicks(gd, ax._id); if(axid === 'redraw') { ax._r = ax.range.slice(); ax._rl = Lib.simpleMap(ax._r, ax.r2l); } return axDone; }; })); } } // make sure we only have allowed options for exponents // (others can make confusing errors) if(!ax.tickformat) { if(['none', 'e', 'E', 'power', 'SI', 'B'].indexOf(ax.exponentformat) === -1) { ax.exponentformat = 'e'; } if(['all', 'first', 'last', 'none'].indexOf(ax.showexponent) === -1) { ax.showexponent = 'all'; } } // set scaling to pixels ax.setScale(); var axLetter = axid.charAt(0), counterLetter = axes.counterLetter(axid), vals = axes.calcTicks(ax), datafn = function(d) { return [d.text, d.x, ax.mirror].join('_'); }, tcls = axid + 'tick', gcls = axid + 'grid', zcls = axid + 'zl', pad = (ax.linewidth || 1) / 2, labelStandoff = (ax.ticks === 'outside' ? ax.ticklen : 1) + (ax.linewidth || 0), labelShift = 0, gridWidth = Drawing.crispRound(gd, ax.gridwidth, 1), zeroLineWidth = Drawing.crispRound(gd, ax.zerolinewidth, gridWidth), tickWidth = Drawing.crispRound(gd, ax.tickwidth, 1), sides, transfn, tickpathfn, subplots, i; if(ax._counterangle && ax.ticks === 'outside') { var caRad = ax._counterangle * Math.PI / 180; labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0); labelShift = ax.ticklen * Math.sin(caRad); } // positioning arguments for x vs y axes if(axLetter === 'x') { sides = ['bottom', 'top']; transfn = function(d) { return 'translate(' + ax.l2p(d.x) + ',0)'; }; tickpathfn = function(shift, len) { if(ax._counterangle) { var caRad = ax._counterangle * Math.PI / 180; return 'M0,' + shift + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len); } else return 'M0,' + shift + 'v' + len; }; } else if(axLetter === 'y') { sides = ['left', 'right']; transfn = function(d) { return 'translate(0,' + ax.l2p(d.x) + ')'; }; tickpathfn = function(shift, len) { if(ax._counterangle) { var caRad = ax._counterangle * Math.PI / 180; return 'M' + shift + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len); } else return 'M' + shift + ',0h' + len; }; } else { Lib.warn('Unrecognized doTicks axis:', axid); return; } var axside = ax.side || sides[0], // which direction do the side[0], side[1], and ...
...
// store initial ranges *after* enforcing constraints, otherwise
// we will never look like we're at the initial ranges
if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
}
// draw ticks, titles, and calculate axis scaling (._b, ._m)
function drawAxes() {
return Plotly.Axes.doTicks(gd, 'redraw');
}
// Now plot the data
function drawData() {
var calcdata = gd.calcdata,
i;
...
expand = function (ax, data, options) { var needsAutorange = ( ax.autorange || !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() ); if(!needsAutorange || !data) return; if(!ax._min) ax._min = []; if(!ax._max) ax._max = []; if(!options) options = {}; if(!ax._m) ax.setScale(); var len = data.length, extrappad = options.padded ? ax._length * 0.05 : 0, tozero = options.tozero && (ax.type === 'linear' || ax.type === '-'), i, j, v, di, dmin, dmax, ppadiplus, ppadiminus, includeThis, vmin, vmax; function getPad(item) { if(Array.isArray(item)) { return function(i) { return Math.max(Number(item[i]||0), 0); }; } else { var v = Math.max(Number(item||0), 0); return function() { return v; }; } } var ppadplus = getPad((ax._m > 0 ? options.ppadplus : options.ppadminus) || options.ppad || 0), ppadminus = getPad((ax._m > 0 ? options.ppadminus : options.ppadplus) || options.ppad || 0), vpadplus = getPad(options.vpadplus || options.vpad), vpadminus = getPad(options.vpadminus || options.vpad); function addItem(i) { di = data[i]; if(!isNumeric(di)) return; ppadiplus = ppadplus(i) + extrappad; ppadiminus = ppadminus(i) + extrappad; vmin = di - vpadminus(i); vmax = di + vpadplus(i); // special case for log axes: if vpad makes this object span // more than an order of mag, clip it to one order. This is so // we don't have non-positive errors or absurdly large lower // range due to rounding errors if(ax.type === 'log' && vmin < vmax / 10) { vmin = vmax / 10; } dmin = ax.c2l(vmin); dmax = ax.c2l(vmax); if(tozero) { dmin = Math.min(0, dmin); dmax = Math.max(0, dmax); } // In order to stop overflow errors, don't consider points // too close to the limits of js floating point function goodNumber(v) { return isNumeric(v) && Math.abs(v) < FP_SAFE; } if(goodNumber(dmin)) { includeThis = true; // take items v from ax._min and compare them to the // presently active point: // - if the item supercedes the new point, set includethis false // - if the new pt supercedes the item, delete it from ax._min for(j = 0; j < ax._min.length && includeThis; j++) { v = ax._min[j]; if(v.val <= dmin && v.pad >= ppadiminus) { includeThis = false; } else if(v.val >= dmin && v.pad <= ppadiminus) { ax._min.splice(j, 1); j--; } } if(includeThis) { ax._min.push({ val: dmin, pad: (tozero && dmin === 0) ? 0 : ppadiminus }); } } if(goodNumber(dmax)) { includeThis = true; for(j = 0; j < ax._max.length && includeThis; j++) { v = ax._max[j]; if(v.val >= dmax && v.pad >= ppadiplus) { includeThis = false; } else if(v.val <= dmax && v.pad <= ppadiplus) { ax._max.splice(j, 1); j--; } } if(includeThis) { ax._max.push({ val: dmax, pad: (tozero && dmax === 0) ? 0 : ppadiplus }); } } } // For efficiency covering monotonic or near-monotonic data, // check a few points at both ends first and then sweep // through the middle for(i = 0; i < 6; i++) addItem(i); for(i = len - 1; i > 5; i--) addItem(i); }
...
if(xa && xa.autorange) {
headPlus = headSize + ann.xshift;
headMinus = headSize - ann.xshift;
if(ann.axref === ann.xref) {
// expand for the arrowhead (padded by arrowhead)
Axes.expand(xa, [xa.r2c(ann.x)], {
ppadplus: headPlus,
ppadminus: headMinus
});
// again for the textbox (padded by textbox)
Axes.expand(xa, [xa.r2c(ann.ax)], {
ppadplus: ann._xpadplus,
ppadminus: ann._xpadminus
...
findSubplotsWithAxis = function (subplots, ax) { var axMatch = new RegExp( (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$') ); var subplotsWithAxis = []; for(var i = 0; i < subplots.length; i++) { var sp = subplots[i]; if(axMatch.test(sp)) subplotsWithAxis.push(sp); } return subplotsWithAxis; }
...
if(aMatch[1] === bMatch[1]) {
return +(aMatch[2] || 1) - (bMatch[2] || 1);
}
return +(aMatch[1]||0) - (bMatch[1]||0);
});
if(ax) return axes.findSubplotsWithAxis(allSubplots, ax);
return allSubplots;
};
// find all subplots with axis 'ax'
axes.findSubplotsWithAxis = function(subplots, ax) {
var axMatch = new RegExp(
(ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$')
...
getAutoRange = function (ax) { var newRange = []; var minmin = ax._min[0].val, maxmax = ax._max[0].val, i; for(i = 1; i < ax._min.length; i++) { if(minmin !== maxmax) break; minmin = Math.min(minmin, ax._min[i].val); } for(i = 1; i < ax._max.length; i++) { if(minmin !== maxmax) break; maxmax = Math.max(maxmax, ax._max[i].val); } var j, minpt, maxpt, minbest, maxbest, dp, dv, mbest = 0, axReverse = false; if(ax.range) { var rng = Lib.simpleMap(ax.range, ax.r2l); axReverse = rng[1] < rng[0]; } // one-time setting to easily reverse the axis // when plotting from code if(ax.autorange === 'reversed') { axReverse = true; ax.autorange = true; } for(i = 0; i < ax._min.length; i++) { minpt = ax._min[i]; for(j = 0; j < ax._max.length; j++) { maxpt = ax._max[j]; dv = maxpt.val - minpt.val; dp = ax._length - minpt.pad - maxpt.pad; if(dv > 0 && dp > 0 && dv / dp > mbest) { minbest = minpt; maxbest = maxpt; mbest = dv / dp; } } } if(minmin === maxmax) { var lower = minmin - 1; var upper = minmin + 1; if(ax.rangemode === 'tozero') { newRange = minmin < 0 ? [lower, 0] : [0, upper]; } else if(ax.rangemode === 'nonnegative') { newRange = [Math.max(0, lower), Math.max(0, upper)]; } else { newRange = [lower, upper]; } } else if(mbest) { if(ax.type === 'linear' || ax.type === '-') { if(ax.rangemode === 'tozero') { if(minbest.val >= 0) { minbest = {val: 0, pad: 0}; } if(maxbest.val <= 0) { maxbest = {val: 0, pad: 0}; } } else if(ax.rangemode === 'nonnegative') { if(minbest.val - mbest * minbest.pad < 0) { minbest = {val: 0, pad: 0}; } if(maxbest.val < 0) { maxbest = {val: 1, pad: 0}; } } // in case it changed again... mbest = (maxbest.val - minbest.val) / (ax._length - minbest.pad - maxbest.pad); } newRange = [ minbest.val - mbest * minbest.pad, maxbest.val + mbest * maxbest.pad ]; } // don't let axis have zero size, while still respecting tozero and nonnegative if(newRange[0] === newRange[1]) { if(ax.rangemode === 'tozero') { if(newRange[0] < 0) { newRange = [newRange[0], 0]; } else if(newRange[0] > 0) { newRange = [0, newRange[0]]; } else { newRange = [0, 1]; } } else { newRange = [newRange[0] - 1, newRange[0] + 1]; if(ax.rangemode === 'nonnegative') { newRange[0] = Math.max(0, newRange[0]); } } } // maintain reversal if(axReverse) newRange.reverse(); return Lib.simpleMap(newRange, ax.l2r || Number); }
...
axes.doAutoRange = function(ax) {
if(!ax._length) ax.setScale();
// TODO do we really need this?
var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length);
if(ax.autorange && hasDeps) {
ax.range = axes.getAutoRange(ax);
// doAutoRange will get called on fullLayout,
// but we want to report its results back to layout
var axIn = ax._input;
axIn.range = ax.range.slice();
axIn.autorange = ax.autorange;
...
getFromId = function (gd, id, type) { var fullLayout = gd._fullLayout; if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); return fullLayout[exports.id2name(id)]; }
...
if(val !== undefined) p.set(val);
}
// for editing annotations or shapes - is it on autoscaled axes?
function refAutorange(obj, axLetter) {
if(!Lib.isPlainObject(obj)) return false;
var axRef = obj[axLetter + 'ref'] || axLetter,
ax = Plotly.Axes.getFromId(gd, axRef);
if(!ax && axRef.charAt(0) === axLetter) {
// fall back on the primary axis in case we've referenced a
// nonexistent axis (as we do above if axRef is missing).
// This assumes the object defaults to data referenced, which
// is the case for shapes and annotations but not for images.
// The only thing this is used for is to determine whether to
...
getFromTrace = function (gd, fullTrace, type) { var fullLayout = gd._fullLayout; var ax = null; if(Registry.traceIs(fullTrace, 'gl3d')) { var scene = fullTrace.scene; if(scene.substr(0, 5) === 'scene') { ax = fullLayout[scene][type + 'axis']; } } else { ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type); } return ax; }
n/a
getSubplots = function (gd, ax) { var subplots = []; var i, j, sp; // look for subplots in the data var data = gd._fullData || gd.data || []; for(i = 0; i < data.length; i++) { var trace = data[i]; if(trace.visible === false || trace.visible === 'legendonly' || !(Registry.traceIs(trace, 'cartesian') || Registry.traceIs(trace, 'gl2d')) ) continue; var xId = trace.xaxis || 'x', yId = trace.yaxis || 'y'; sp = xId + yId; if(subplots.indexOf(sp) === -1) subplots.push(sp); } // look for subplots in the axes/anchors, so that we at least draw all axes var axesList = axes.list(gd, '', true); function hasAx2(sp, ax2) { return sp.indexOf(ax2._id) !== -1; } for(i = 0; i < axesList.length; i++) { var ax2 = axesList[i], ax2Letter = ax2._id.charAt(0), ax3Id = (ax2.anchor === 'free') ? ((ax2Letter === 'x') ? 'y' : 'x') : ax2.anchor, ax3 = axes.getFromId(gd, ax3Id); // look if ax2 is already represented in the data var foundAx2 = false; for(j = 0; j < subplots.length; j++) { if(hasAx2(subplots[j], ax2)) { foundAx2 = true; break; } } // ignore free axes that already represented in the data if(ax2.anchor === 'free' && foundAx2) continue; // ignore anchor-less axes if(!ax3) continue; sp = (ax2Letter === 'x') ? ax2._id + ax3._id : ax3._id + ax2._id; if(subplots.indexOf(sp) === -1) subplots.push(sp); } // filter invalid subplots var spMatch = axes.subplotMatch, allSubplots = []; for(i = 0; i < subplots.length; i++) { sp = subplots[i]; if(spMatch.test(sp)) allSubplots.push(sp); } // sort the subplot ids allSubplots.sort(function(a, b) { var aMatch = a.match(spMatch), bMatch = b.match(spMatch); if(aMatch[1] === bMatch[1]) { return +(aMatch[2] || 1) - (bMatch[2] || 1); } return +(aMatch[1]||0) - (bMatch[1]||0); }); if(ax) return axes.findSubplotsWithAxis(allSubplots, ax); return allSubplots; }
...
newSubplots = newFullLayout._plots = {};
var mockGd = {
_fullData: newFullData,
_fullLayout: newFullLayout
};
var ids = Plotly.Axes.getSubplots(mockGd);
for(var i = 0; i < ids.length; i++) {
var id = ids[i],
oldSubplot = oldSubplots[id],
plotinfo;
if(oldSubplot) {
...
function id2name(id) { if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; var axNum = id.substr(1); if(axNum === '1') axNum = ''; return id.charAt(0) + 'axis' + axNum; }
...
});
// make a new empty vals array for undoit
function a0() { return traces.map(function() { return undefined; }); }
// for autoranging multiple axes
function addToAxlist(axid) {
var axName = Plotly.Axes.id2name(axid);
if(axlist.indexOf(axName) === -1) axlist.push(axName);
}
function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
...
list = function (gd, axletter, only2d) { return listNames(gd, axletter, only2d) .map(function(axName) { return Lib.nestedProperty(gd._fullLayout, axName).get(); }); }
...
delete layout.xaxis1;
}
if(layout.yaxis1) {
if(!layout.yaxis) layout.yaxis = layout.yaxis1;
delete layout.yaxis1;
}
var axList = Axes.list({_fullLayout: layout});
for(i = 0; i < axList.length; i++) {
var ax = axList[i];
if(ax.anchor && ax.anchor !== 'free') {
ax.anchor = Axes.cleanId(ax.anchor);
}
if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
...
listIds = function (gd, axletter) { return listNames(gd, axletter, true).map(exports.name2id); }
...
* dflt: the default to coerce to, or blank to use the first axis (falling back on
* extraOption if there is no axis)
* extraOption: aside from existing axes with this letter, what non-axis value is allowed?
* Only required if it's different from `dflt`
*/
axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
var axLetter = attr.charAt(attr.length - 1),
axlist = axes.listIds(gd, axLetter),
refAttr = attr + 'ref',
attrDef = {};
if(!dflt) dflt = axlist[0] || extraOption;
if(!extraOption) extraOption = dflt;
// data-ref annotations are not supported in gl2d yet
...
makeClipPaths = function (gd) { var fullLayout = gd._fullLayout, defs = fullLayout._defs, fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}, fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}, xaList = axes.list(gd, 'x', true), yaList = axes.list(gd, 'y', true), clipList = [], i, j; for(i = 0; i < xaList.length; i++) { clipList.push({x: xaList[i], y: fullHeight}); for(j = 0; j < yaList.length; j++) { if(i === 0) clipList.push({x: fullWidth, y: yaList[j]}); clipList.push({x: xaList[i], y: yaList[j]}); } } var defGroup = defs.selectAll('g.clips') .data([0]); defGroup.enter().append('g') .classed('clips', true); // selectors don't work right with camelCase tags, // have to use class instead // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I var axClips = defGroup.selectAll('.axesclip') .data(clipList, function(d) { return d.x._id + d.y._id; }); axClips.enter().append('clipPath') .classed('axesclip', true) .attr('id', function(d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id; }) .append('rect'); axClips.exit().remove(); axClips.each(function(d) { d3.select(this).select('rect').attr({ x: d.x._offset || 0, y: d.y._offset || 0, width: d.x._length || 1, height: d.y._length || 1 }); }); }
...
plotinfo.draglayer.attr('transform', origin);
// mark free axes as displayed, so we don't draw them again
if(showfreex) { freefinished.push(xa._id); }
if(showfreey) { freefinished.push(ya._id); }
});
Plotly.Axes.makeClipPaths(gd);
exports.drawMainTitle(gd);
ModeBar.manage(gd);
return gd._promises.length && Promise.all(gd._promises);
};
exports.drawMainTitle = function(gd) {
...
minDtick = function (ax, newDiff, newFirst, allow) { // doesn't make sense to do forced min dTick on log or category axes, // and the plot itself may decide to cancel (ie non-grouped bars) if(['log', 'category'].indexOf(ax.type) !== -1 || !allow) { ax._minDtick = 0; } // undefined means there's nothing there yet else if(ax._minDtick === undefined) { ax._minDtick = newDiff; ax._forceTick0 = newFirst; } else if(ax._minDtick) { // existing minDtick is an integer multiple of newDiff // (within rounding err) // and forceTick0 can be shifted to newFirst if((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 && (((newFirst - ax._forceTick0) / newDiff % 1) + 1.000001) % 1 < 2e-6) { ax._minDtick = newDiff; ax._forceTick0 = newFirst; } // if the converse is true (newDiff is a multiple of minDtick and // newFirst can be shifted to forceTick0) then do nothing - same // forcing stands. Otherwise, cancel forced minimum else if((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 || (((newFirst - ax._forceTick0) / ax._minDtick % 1) + 1.000001) % 1 > 2e-6) { ax._minDtick = 0; } } }
...
function updatePositionAxis(gd, pa, sieve, allowMinDtick) {
var calcTraces = sieve.traces,
distinctPositions = sieve.distinctPositions,
distinctPositions0 = distinctPositions[0],
minDiff = sieve.minDiff,
vpad = minDiff / 2;
Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick);
// If the user set the bar width or the offset,
// then bars can be shifted away from their positions
// and widths can be larger than minDiff.
//
// Here, we compute pMin and pMax to expand the position axis,
// so that all bars are fully within the axis range.
...
saveRangeInitial = function (gd, overwrite) { var axList = axes.list(gd, '', true), hasOneAxisChanged = false; for(var i = 0; i < axList.length; i++) { var ax = axList[i]; var isNew = (ax._rangeInitial === undefined); var hasChanged = ( isNew || !( ax.range[0] === ax._rangeInitial[0] && ax.range[1] === ax._rangeInitial[1] ) ); if((isNew && ax.autorange === false) || (overwrite && hasChanged)) { ax._rangeInitial = ax.range.slice(); hasOneAxisChanged = true; } } return hasOneAxisChanged; }
...
Plotly.Axes.doAutoRange(axList[i]);
}
enforceAxisConstraints(gd);
// store initial ranges *after* enforcing constraints, otherwise
// we will never look like we're at the initial ranges
if(graphWasEmpty) Plotly.Axes.saveRangeInitial(gd);
}
// draw ticks, titles, and calculate axis scaling (._b, ._m)
function drawAxes() {
return Plotly.Axes.doTicks(gd, 'redraw');
}
...
saveShowSpikeInitial = function (gd, overwrite) { var axList = axes.list(gd, '', true), hasOneAxisChanged = false, allEnabled = 'on'; for(var i = 0; i < axList.length; i++) { var ax = axList[i]; var isNew = (ax._showSpikeInitial === undefined); var hasChanged = ( isNew || !( ax.showspikes === ax._showspikes ) ); if((isNew) || (overwrite && hasChanged)) { ax._showSpikeInitial = ax.showspikes; hasOneAxisChanged = true; } if(allEnabled === 'on' && !ax.showspikes) { allEnabled = 'off'; } } gd._fullLayout._cartesianSpikesEnabled = allEnabled; return hasOneAxisChanged; }
...
// polar need a different framework
if(gd.framework !== makePlotFramework) {
gd.framework = makePlotFramework;
makePlotFramework(gd);
}
// save initial show spikes once per graph
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);
// prepare the data and find the autorange
// generate calcdata, if we need to
// to force redoing calcdata, just delete it before calling Plotly.plot
var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
if(recalc) Plots.doCalcdata(gd);
...
function setConvert(ax, fullLayout) {
fullLayout = fullLayout || {};
// clipMult: how many axis lengths past the edge do we render?
// for panning, 1-2 would suffice, but for zooming more is nice.
// also, clipping can affect the direction of lines off the edge...
var clipMult = 10;
function toLog(v, clip) {
if(v > 0) return Math.log(v) / Math.LN10;
else if(v <= 0 && clip && ax.range && ax.range.length === 2) {
// clip NaN (ie past negative infinity) to clipMult axis
// length past the negative edge
var r0 = ax.range[0],
r1 = ax.range[1];
return 0.5 * (r0 + r1 - 3 * clipMult * Math.abs(r0 - r1));
}
else return BADNUM;
}
/*
* wrapped dateTime2ms that:
* - accepts ms numbers for backward compatibility
* - inserts a dummy arg so calendar is the 3rd arg (see notes below).
* - defaults to ax.calendar
*/
function dt2ms(v, _, calendar) {
// NOTE: Changed this behavior: previously we took any numeric value
// to be a ms, even if it was a string that could be a bare year.
// Now we convert it as a date if at all possible, and only try
// as (local) ms if that fails.
var ms = dateTime2ms(v, calendar || ax.calendar);
if(ms === BADNUM) {
if(isNumeric(v)) ms = dateTime2ms(new Date(+v));
else return BADNUM;
}
return ms;
}
// wrapped ms2DateTime to insert default ax.calendar
function ms2dt(v, r, calendar) {
return ms2DateTime(v, r, calendar || ax.calendar);
}
function getCategoryName(v) {
return ax._categories[Math.round(v)];
}
/*
* setCategoryIndex: return the index of category v,
* inserting it in the list if it's not already there
*
* this will enter the categories in the order it
* encounters them, ie all the categories from the
* first data set, then all the ones from the second
* that aren't in the first etc.
*
* it is assumed that this function is being invoked in the
* already sorted category order; otherwise there would be
* a disconnect between the array and the index returned
*/
function setCategoryIndex(v) {
if(v !== null && v !== undefined) {
if(ax._categoriesMap === undefined) {
ax._categoriesMap = {};
}
if(ax._categoriesMap[v] !== undefined) {
return ax._categoriesMap[v];
} else {
ax._categories.push(v);
var curLength = ax._categories.length - 1;
ax._categoriesMap[v] = curLength;
return curLength;
}
}
return BADNUM;
}
function getCategoryIndex(v) {
// d2l/d2c variant that that won't add categories but will also
// allow numbers to be mapped to the linearized axis positions
if(ax._categoriesMap) {
var index = ax._categoriesMap[v];
if(index !== undefined) return index;
}
if(typeof v === 'number') { return v; }
}
function l2p(v) {
if(!isNumeric(v)) return BADNUM;
// include 2 fractional digits on pixel, for PDF zooming etc
return d3.round(ax._b + ax._m * v, 2);
}
function p2l(px) { return (px - ax._b) / ax._m; }
// conversions among c/l/p are fairly simple - do them together for all axis types
ax.c2l = (ax.type === 'log') ? toLog : num;
ax.l2c = (ax.type === 'log') ? fromLog : num;
ax.l2p = l2p;
ax.p2l = p2l;
ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p;
ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l;
/*
* now type-specific conversions for **ALL** other combinations
* they're all written out, instead of being combinations of each other, for
* both clarity and speed.
*/
if(['linear', '-'].indexOf(ax.type) !== -1) {
// all are data vals, ...
...
else {
dummyAx = {
type: ax.type,
range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
calendar: calendar
};
}
axes.setConvert(dummyAx);
axes.autoTicks(dummyAx, size0);
var binStart = axes.tickIncrement(
axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
binEnd;
// check for too many data points right at the edges of bins
...
function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var layoutKeys = Object.keys(layoutIn), xaListCartesian = [], yaListCartesian = [], xaListGl2d = [], yaListGl2d = [], xaListCheater = [], xaListNonCheater = [], outerTicks = {}, noGrids = {}, i; // look for axes in the data for(i = 0; i < fullData.length; i++) { var trace = fullData[i]; var listX, listY; if(Registry.traceIs(trace, 'cartesian')) { listX = xaListCartesian; listY = yaListCartesian; } else if(Registry.traceIs(trace, 'gl2d')) { listX = xaListGl2d; listY = yaListGl2d; } else continue; var xaName = axisIds.id2name(trace.xaxis), yaName = axisIds.id2name(trace.yaxis); // Two things trigger axis visibility: // 1. is not carpet // 2. carpet that's not cheater if(!Registry.traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) { if(xaName) Lib.pushUnique(xaListNonCheater, xaName); } // The above check for definitely-not-cheater is not adequate. This // second list tracks which axes *could* be a cheater so that the // full condition triggering hiding is: // *could* be a cheater and *is not definitely visible* if(trace.type === 'carpet' && trace._cheater) { if(xaName) Lib.pushUnique(xaListCheater, xaName); } // add axes implied by traces if(xaName && listX.indexOf(xaName) === -1) listX.push(xaName); if(yaName && listY.indexOf(yaName) === -1) listY.push(yaName); // check for default formatting tweaks if(Registry.traceIs(trace, '2dMap')) { outerTicks[xaName] = true; outerTicks[yaName] = true; } if(Registry.traceIs(trace, 'oriented')) { var positionAxis = trace.orientation === 'h' ? yaName : xaName; noGrids[positionAxis] = true; } } // N.B. Ignore orphan axes (i.e. axes that have no data attached to them) // if gl3d or geo is present on graph. This is retain backward compatible. // // TODO drop this in version 2.0 var ignoreOrphan = (layoutOut._has('gl3d') || layoutOut._has('geo')); if(!ignoreOrphan) { for(i = 0; i < layoutKeys.length; i++) { var key = layoutKeys[i]; // orphan layout axes are considered cartesian subplots if(xaListGl2d.indexOf(key) === -1 && xaListCartesian.indexOf(key) === -1 && constants.xAxisMatch.test(key)) { xaListCartesian.push(key); } else if(yaListGl2d.indexOf(key) === -1 && yaListCartesian.indexOf(key) === -1 && constants.yAxisMatch.test(key)) { yaListCartesian.push(key); } } } // make sure that plots with orphan cartesian axes // are considered 'cartesian' if(xaListCartesian.length && yaListCartesian.length) { Lib.pushUnique(layoutOut._basePlotModules, Registry.subplotsRegistry.cartesian); } function axSort(a, b) { var aNum = Number(a.substr(5) || 1), bNum = Number(b.substr(5) || 1); return aNum - bNum; } var xaList = xaListCartesian.concat(xaListGl2d).sort(axSort), yaList = yaListCartesian.concat(yaListGl2d).sort(axSort), axesList = xaList.concat(yaList); // plot_bgcolor only makes sense if there's a (2D) plot! // TODO: bgcolor for each subplot, to inherit from the main one var plot_bgcolor = Color.background; if(xaList.length && yaList.length) { plot_bgcolor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor'); } var bgColor = Color.combine(plot_bgcolor, layoutOut.paper_bgcolor); var axName, axLetter, axLayoutIn, axLayoutOut; function coerce(attr, dflt) { return Lib.coerce(axLayoutIn, axLayoutOut, la ...
...
}
plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
var i, _module;
// can't be be part of basePlotModules loop
// in order to handle the orphan axes case
Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
// base plot module layout defaults
var basePlotModules = layoutOut._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
_module = basePlotModules[i];
// done above already
...
swap = function (gd, traces) { var axGroups = makeAxisGroups(gd, traces); for(var i = 0; i < axGroups.length; i++) { swapAxisGroup(gd, axGroups[i].x, axGroups[i].y); } }
...
// all the other ones, just modify that one attribute
param.set(newVal);
}
}
// swap the data attributes of the relevant x and y axes?
if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) {
Plotly.Axes.swap(gd, traces);
}
// swap hovermode if set to "compare x/y data"
if(ai === 'orientationaxes') {
var hovermode = Lib.nestedProperty(gd.layout, 'hovermode');
if(hovermode.get() === 'x') {
hovermode.set('y');
...
tickFirst = function (ax) { var r2l = ax.r2l || Number, rng = Lib.simpleMap(ax.range, r2l), axrev = rng[1] < rng[0], sRound = axrev ? Math.floor : Math.ceil, // add a tiny extra bit to make sure we get ticks // that may have been rounded out r0 = rng[0] * 1.0001 - rng[1] * 0.0001, dtick = ax.dtick, tick0 = r2l(ax.tick0); if(isNumeric(dtick)) { var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0; // make sure no ticks outside the category list if(ax.type === 'category') { tmin = Lib.constrain(tmin, 0, ax._categories.length - 1); } return tmin; } var tType = dtick.charAt(0), dtNum = Number(dtick.substr(1)); // Dates: months (or years) if(tType === 'M') { var cnt = 0, t0 = tick0, t1, mult, newDTick; // This algorithm should work for *any* nonlinear (but close to linear!) // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3. while(cnt < 10) { t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar); if((t1 - r0) * (t0 - r0) <= 0) { // t1 and t0 are on opposite sides of r0! we've succeeded! if(axrev) return Math.min(t0, t1); return Math.max(t0, t1); } mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0); newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum); t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar); cnt++; } Lib.error('tickFirst did not converge', ax); return t0; } // Log scales: Linear, Digits else if(tType === 'L') { return Math.log(sRound( (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10; } else if(tType === 'D') { var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev); return Math.floor(r0) + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; } else throw 'unrecognized dtick ' + String(dtick); }
...
calendar: calendar
};
}
axes.setConvert(dummyAx);
axes.autoTicks(dummyAx, size0);
var binStart = axes.tickIncrement(
axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
binEnd;
// check for too many data points right at the edges of bins
// (>50% within 1% of bin edges) or all data points integral
// and offset the bins accordingly
if(typeof dummyAx.dtick === 'number') {
binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax);
...
tickIncrement = function (x, dtick, axrev, calendar) { var axSign = axrev ? -1 : 1; // includes linear, all dates smaller than month, and pure 10^n in log if(isNumeric(dtick)) return x + axSign * dtick; // everything else is a string, one character plus a number var tType = dtick.charAt(0), dtSigned = axSign * Number(dtick.substr(1)); // Dates: months (or years - see Lib.incrementMonth) if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar); // Log scales: Linear, Digits else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10; // log10 of 2,5,10, or all digits (logs just have to be // close enough to round) else if(tType === 'D') { var tickset = (dtick === 'D2') ? roundLog2 : roundLog1, x2 = x + axSign * 0.01, frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev); return Math.floor(x2) + Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10; } else throw 'unrecognized dtick ' + String(dtick); }
...
range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
calendar: calendar
};
}
axes.setConvert(dummyAx);
axes.autoTicks(dummyAx, size0);
var binStart = axes.tickIncrement(
axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
binEnd;
// check for too many data points right at the edges of bins
// (>50% within 1% of bin edges) or all data points integral
// and offset the bins accordingly
if(typeof dummyAx.dtick === 'number') {
...
tickText = function (ax, x, hover) { var out = tickTextObj(ax, x), hideexp, arrayMode = ax.tickmode === 'array', extraPrecision = hover || arrayMode, i, tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; if(arrayMode && Array.isArray(ax.ticktext)) { var rng = Lib.simpleMap(ax.range, ax.r2l), minDiff = Math.abs(rng[1] - rng[0]) / 10000; for(i = 0; i < ax.ticktext.length; i++) { if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break; } if(i < ax.ticktext.length) { out.text = String(ax.ticktext[i]); return out; } } function isHidden(showAttr) { var first_or_last; if(showAttr === undefined) return true; if(hover) return showAttr === 'none'; first_or_last = { first: ax._tmin, last: ax._tmax }[showAttr]; return showAttr !== 'all' && x !== first_or_last; } hideexp = ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : ''; if(ax.type === 'date') formatDate(ax, out, hover, extraPrecision); else if(ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp); else if(ax.type === 'category') formatCategory(ax, out); else formatLinear(ax, out, hover, extraPrecision, hideexp); // add prefix and suffix if(ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text; if(ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix; return out; }
...
// latter part: ax._prevDateHead holds what we showed most recently.
// Start with it cleared and mark that we're in calcTicks (ie calculating a
// whole string of these so we should care what the previous date head was!)
ax._prevDateHead = '';
ax._inCalcTicks = true;
var ticksOut = new Array(vals.length);
for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]);
ax._inCalcTicks = false;
return ticksOut;
};
function arrayTicks(ax) {
...
function cleanId(id, axLetter) { if(!id.match(constants.AX_ID_PATTERN)) return; if(axLetter && id.charAt(0) !== axLetter) return; var axNum = id.substr(1).replace(/^0+/, ''); if(axNum === '1') axNum = ''; return id.charAt(0) + axNum; }
...
delete layout.yaxis1;
}
var axList = Axes.list({_fullLayout: layout});
for(i = 0; i < axList.length; i++) {
var ax = axList[i];
if(ax.anchor && ax.anchor !== 'free') {
ax.anchor = Axes.cleanId(ax.anchor);
}
if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
// old method of axis type - isdate and islog (before category existed)
if(!ax.type) {
if(ax.isdate) ax.type = 'date';
else if(ax.islog) ax.type = 'log';
...
getFromId = function (gd, id, type) { var fullLayout = gd._fullLayout; if(type === 'x') id = id.replace(/y[0-9]*/, ''); else if(type === 'y') id = id.replace(/x[0-9]*/, ''); return fullLayout[exports.id2name(id)]; }
...
if(val !== undefined) p.set(val);
}
// for editing annotations or shapes - is it on autoscaled axes?
function refAutorange(obj, axLetter) {
if(!Lib.isPlainObject(obj)) return false;
var axRef = obj[axLetter + 'ref'] || axLetter,
ax = Plotly.Axes.getFromId(gd, axRef);
if(!ax && axRef.charAt(0) === axLetter) {
// fall back on the primary axis in case we've referenced a
// nonexistent axis (as we do above if axRef is missing).
// This assumes the object defaults to data referenced, which
// is the case for shapes and annotations but not for images.
// The only thing this is used for is to determine whether to
...
getFromTrace = function (gd, fullTrace, type) { var fullLayout = gd._fullLayout; var ax = null; if(Registry.traceIs(fullTrace, 'gl3d')) { var scene = fullTrace.scene; if(scene.substr(0, 5) === 'scene') { ax = fullLayout[scene][type + 'axis']; } } else { ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type); } return ax; }
n/a
function id2name(id) { if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return; var axNum = id.substr(1); if(axNum === '1') axNum = ''; return id.charAt(0) + 'axis' + axNum; }
...
});
// make a new empty vals array for undoit
function a0() { return traces.map(function() { return undefined; }); }
// for autoranging multiple axes
function addToAxlist(axid) {
var axName = Plotly.Axes.id2name(axid);
if(axlist.indexOf(axName) === -1) axlist.push(axName);
}
function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
...
list = function (gd, axletter, only2d) { return listNames(gd, axletter, only2d) .map(function(axName) { return Lib.nestedProperty(gd._fullLayout, axName).get(); }); }
...
delete layout.xaxis1;
}
if(layout.yaxis1) {
if(!layout.yaxis) layout.yaxis = layout.yaxis1;
delete layout.yaxis1;
}
var axList = Axes.list({_fullLayout: layout});
for(i = 0; i < axList.length; i++) {
var ax = axList[i];
if(ax.anchor && ax.anchor !== 'free') {
ax.anchor = Axes.cleanId(ax.anchor);
}
if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
...
listIds = function (gd, axletter) { return listNames(gd, axletter, true).map(exports.name2id); }
...
* dflt: the default to coerce to, or blank to use the first axis (falling back on
* extraOption if there is no axis)
* extraOption: aside from existing axes with this letter, what non-axis value is allowed?
* Only required if it's different from `dflt`
*/
axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
var axLetter = attr.charAt(attr.length - 1),
axlist = axes.listIds(gd, axLetter),
refAttr = attr + 'ref',
attrDef = {};
if(!dflt) dflt = axlist[0] || extraOption;
if(!extraOption) extraOption = dflt;
// data-ref annotations are not supported in gl2d yet
...
function name2id(name) { if(!name.match(constants.AX_NAME_PATTERN)) return; var axNum = name.substr(5); if(axNum === '1') axNum = ''; return name.charAt(0) + axNum; }
...
// for constraint enforcement: keep track of all axes (as {id: name})
// we're editing the (auto)range of, so we can tell the others constrained
// to scale with them that it's OK for them to shrink
var rangesAltered = {};
function recordAlteredAxis(pleafPlus) {
var axId = axisIds.name2id(pleafPlus.split('.')[0]);
rangesAltered[axId] = 1;
}
// alter gd.layout
for(var ai in aobj) {
if(helpers.hasParent(aobj, ai)) {
throw new Error('cannot set ' + ai + 'and a parent attribute simultaneously');
...
clean = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { var hadParcoords = (oldFullLayout._has && oldFullLayout._has('parcoords')); var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords')); if(hadParcoords && !hasParcoords) { oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._glimages.selectAll('*').remove(); } }
...
delete scene.cameraposition;
}
}
// sanitize rgb(fractions) and rgba(fractions) that old tinycolor
// supported, but new tinycolor does not because they're not valid css
Color.clean(layout);
return layout;
};
function cleanAxRef(container, attr) {
var valIn = container[attr],
axLetter = attr.charAt(0);
...
plot = function (gd) { var calcData = Plots.getSubplotCalcData(gd.calcdata, 'parcoords', 'parcoords'); if(calcData.length) parcoordsPlot(gd, calcData); }
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
toSVG = function (gd) { var imageRoot = gd._fullLayout._glimages; var root = d3.selectAll('.svg-container'); var canvases = root.filter(function(d, i) {return i === root.size() - 1;}) .selectAll('.parcoords-lines.context, .parcoords-lines.focus'); function canvasToImage(d) { var canvas = this; var imageData = canvas.toDataURL('image/png'); var image = imageRoot.append('svg:image'); var size = gd._fullLayout._size; var domain = gd._fullData[d.model.key].domain; image.attr({ xmlns: xmlnsNamespaces.svg, 'xlink:href': imageData, x: size.l + size.w * domain.x[0] - c.overdrag, y: size.t + size.h * (1 - domain.y[1]), width: (domain.x[1] - domain.x[0]) * size.w + 2 * c.overdrag, height: (domain.y[1] - domain.y[0]) * size.h, preserveAspectRatio: 'none' }); } canvases.each(canvasToImage); // Chrome / Safari bug workaround - browser apparently loses connection to the defined pattern // Without the workaround, these browsers 'lose' the filter brush styling (color etc.) after a snapshot // on a subsequent interaction. // Firefox works fine without this workaround window.setTimeout(function() { d3.selectAll('#filterBarPattern') .attr('id', 'filterBarPattern'); }, 60); }
...
// subplot-specific to-SVG methods
// which notably add the contents of the gl-container
// into the main svg node
var basePlotModules = fullLayout._basePlotModules || [];
for(i = 0; i < basePlotModules.length; i++) {
var _module = basePlotModules[i];
if(_module.toSVG) _module.toSVG(gd);
}
// add top items above them assumes everything in toppaper is either
// a group or a defs, and if it's empty (like hoverlayer) we can ignore it.
if(toppaper) {
var nodes = toppaper.node().childNodes;
...
avg = function (n, i, size, counterData, counts) { var v = counterData[i]; if(isNumeric(v)) { v = Number(v); size[n] += v; counts[n]++; } return 0; }
n/a
count = function (n, i, size) { size[n]++; return 1; }
n/a
max = function (n, i, size, counterData) { var v = counterData[i]; if(isNumeric(v)) { v = Number(v); if(!isNumeric(size[n])) { size[n] = v; return v; } else if(size[n] < v) { var delta = v - size[n]; size[n] = v; return delta; } } return 0; }
...
if(e.preventDefault) e.preventDefault();
e.cancelBubble = true;
return false;
};
// constrain - restrict a number v to be between v0 and v1
lib.constrain = function(v, v0, v1) {
if(v0 > v1) return Math.max(v1, Math.min(v0, v));
return Math.max(v0, Math.min(v1, v));
};
/**
* do two bounding boxes from getBoundingClientRect,
* ie {left,right,top,bottom,width,height}, overlap?
* takes optional padding pixels
...
min = function (n, i, size, counterData) { var v = counterData[i]; if(isNumeric(v)) { v = Number(v); if(!isNumeric(size[n])) { size[n] = v; return v; } else if(size[n] > v) { var delta = v - size[n]; size[n] = v; return delta; } } return 0; }
...
* d3's vocabulary:
* %{n}f where n is the max number of digits of fractional seconds
*/
var fracMatch = /%\d?f/g;
function modDateFormat(fmt, x, calendar) {
fmt = fmt.replace(fracMatch, function(match) {
var digits = Math.min(+(match.charAt(1)) || 6, 6),
fracSecs = ((x / 1000 % 1) + 2)
.toFixed(digits)
.substr(2).replace(/0+$/, '') || '0';
return fracSecs;
});
var d = new Date(Math.floor(x + 0.05));
...
sum = function (n, i, size, counterData) { var v = counterData[i]; if(isNumeric(v)) { v = Number(v); size[n] += v; return v; } return 0; }
...
if (typeof keys === 'string') keys = keys.split('.');
var next = keys.shift();
return obj[next] && (!keys.length || objHasKeys(obj[next], keys));
};
µ.util.sumArrays = function(a, b) {
return d3.zip(a, b).map(function(d, i) {
return d3.sum(d);
});
};
µ.util.arrayLast = function(a) {
return a[a.length - 1];
};
...
function getCal(calendar) { var calendarObj = allCals[calendar]; if(calendarObj) return calendarObj; calendarObj = allCals[calendar] = calendars.instance(calendar); return calendarObj; }
n/a
handleDefaults = function (contIn, contOut, attr, dflt) { var attrs = {}; attrs[attr] = attributes; return Lib.coerce(contIn, contOut, attrs, attr, dflt); }
n/a
handleTraceDefaults = function (traceIn, traceOut, coords, layout) { for(var i = 0; i < coords.length; i++) { handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar); } }
n/a
function worldCalFmt(fmt, x, calendar) { var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, cDate = getCal(calendar).fromJD(dateJD), i = 0, modifier, directive, directiveLen, directiveObj, replacementPart; while((i = fmt.indexOf('%', i)) !== -1) { modifier = fmt.charAt(i + 1); if(modifier === '0' || modifier === '-' || modifier === '_') { directiveLen = 3; directive = fmt.charAt(i + 2); if(modifier === '_') modifier = '-'; } else { directive = modifier; modifier = '0'; directiveLen = 2; } directiveObj = d3ToWorldCalendars[directive]; if(!directiveObj) { i += directiveLen; } else { // code is recognized as a date part but world-calendars doesn't support it if(directiveObj === UNKNOWN) replacementPart = UNKNOWN; // format the cDate according to the translated directive else replacementPart = cDate.formatDate(directiveObj[modifier]); fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen); i += replacementPart.length; } } return fmt; }
n/a
function calc(gd, trace) { var xa = Axes.getFromId(gd, trace.xaxis || 'x'); var ya = Axes.getFromId(gd, trace.yaxis || 'y'); var aax = trace.aaxis; var bax = trace.baxis; var a = trace._a = trace.a; var b = trace._b = trace.b; var t = {}; var x; var y = trace.y; if(trace._cheater) { var avals = aax.cheatertype === 'index' ? a.length : a; var bvals = bax.cheatertype === 'index' ? b.length : b; trace.x = x = cheaterBasis(avals, bvals, trace.cheaterslope); } else { x = trace.x; } trace._x = trace.x = x = clean2dArray(x); trace._y = trace.y = y = clean2dArray(y); // Fill in any undefined values with elliptic smoothing. This doesn't take // into account the spacing of the values. That is, the derivatives should // be modified to use a and b values. It's not that hard, but this is already // moderate overkill for just filling in missing values. smoothFill2dArray(x, a, b); smoothFill2dArray(y, a, b); // create conversion functions that depend on the data trace.setScale(); // Convert cartesian-space x/y coordinates to screen space pixel coordinates: t.xp = trace.xp = map2dArray(trace.xp, x, xa.c2p); t.yp = trace.yp = map2dArray(trace.yp, y, ya.c2p); // This is a rather expensive scan. Nothing guarantees monotonicity, // so we need to scan through all data to get proper ranges: var xrange = arrayMinmax(x); var yrange = arrayMinmax(y); var dx = 0.5 * (xrange[1] - xrange[0]); var xc = 0.5 * (xrange[1] + xrange[0]); var dy = 0.5 * (yrange[1] - yrange[0]); var yc = 0.5 * (yrange[1] + yrange[0]); // Expand the axes to fit the plot, except just grow it by a factor of 1.3 // because the labels should be taken into account except that's difficult // hence 1.3. var grow = 1.3; xrange = [xc - dx * grow, xc + dx * grow]; yrange = [yc - dy * grow, yc + dy * grow]; Axes.expand(xa, xrange, {padded: true}); Axes.expand(ya, yrange, {padded: true}); // Enumerate the gridlines, both major and minor, and store them on the trace // object: calcGridlines(trace, t, 'a', 'b'); calcGridlines(trace, t, 'b', 'a'); // Calculate the text labels for each major gridline and store them on the // trace object: calcLabels(trace, aax); calcLabels(trace, bax); // Tabulate points for the four segments that bound the axes so that we can // map to pixel coordinates in the plot function and create a clip rect: t.clipsegments = calcClipPath(trace.xctrl, trace.yctrl, aax, bax); t.x = x; t.y = y; t.a = a; t.b = b; return [t]; }
...
for(var j = 0; j < modules.length; j++) {
_module = modules[j];
if(_module.setPositions) _module.setPositions(gd, subplotInfo);
}
}
// calc and autorange for errorbars
ErrorBars.calc(gd);
// TODO: autosize extra for text markers and images
// see https://github.com/plotly/plotly.js/issues/1111
return Lib.syncOrAsync([
Registry.getComponentMethod('shapes', 'calcAutorange'),
Registry.getComponentMethod('annotations', 'calcAutorange'),
doAutoRangeAndConstraints,
...
function plot(gd, plotinfo, cdcarpet) { for(var i = 0; i < cdcarpet.length; i++) { plotOne(gd, plotinfo, cdcarpet[i]); } }
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
function supplyDefaults(traceIn, traceOut, dfltColor, fullLayout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } var defaultColor = coerce('color', colorAttrs.defaultLine); Lib.coerceFont(coerce, 'font'); coerce('carpet'); handleABDefaults(traceIn, traceOut, fullLayout, coerce, defaultColor); if(!traceOut.a || !traceOut.b) { traceOut.visible = false; return; } if(traceOut.a.length < 3) { traceOut.aaxis.smoothing = 0; } if(traceOut.b.length < 3) { traceOut.baxis.smoothing = 0; } // NB: the input is x/y arrays. You should know that the *first* dimension of x and y // corresponds to b and the second to a. This sounds backwards but ends up making sense // the important part to know is that when you write y[j][i], j goes from 0 to b.length - 1 // and i goes from 0 to a.length - 1. var len = handleXYDefaults(traceIn, traceOut, coerce); setConvert(traceOut); if(traceOut._cheater) { coerce('cheaterslope'); } if(!len) { traceOut.visible = false; return; } }
...
gd._replotPending = true;
return Promise.reject();
} else {
// we're going ahead with a replot now
gd._replotPending = false;
}
Plots.supplyDefaults(gd);
var fullLayout = gd._fullLayout;
// Polar plots
if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
// so we don't try to re-call Plotly.plot from inside
...
function hasClickToShow(gd, hoverData) { var sets = getToggleSets(gd, hoverData); return sets.on.length > 0 || sets.explicitOff.length > 0; }
n/a
function onClick(gd, hoverData) { var toggleSets = getToggleSets(gd, hoverData), onSet = toggleSets.on, offSet = toggleSets.off.concat(toggleSets.explicitOff), update = {}, i; if(!(onSet.length || offSet.length)) return; for(i = 0; i < onSet.length; i++) { update['annotations[' + onSet[i] + '].visible'] = true; } for(i = 0; i < offSet.length; i++) { update['annotations[' + offSet[i] + '].visible'] = false; } return Plotly.update(gd, {}, update); }
n/a
coerce = function (containerIn, containerOut, attributes, attribute, dflt) {
var opts = nestedProperty(attributes, attribute).get(),
propIn = nestedProperty(containerIn, attribute),
propOut = nestedProperty(containerOut, attribute),
v = propIn.get();
if(dflt === undefined) dflt = opts.dflt;
/**
* arrayOk: value MAY be an array, then we do no value checking
* at this point, because it can be more complicated than the
* individual form (eg. some array vals can be numbers, even if the
* single values must be color strings)
*/
if(opts.arrayOk && Array.isArray(v)) {
propOut.set(v);
return v;
}
exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts);
return propOut.get();
}
...
}
var items = opts.items,
vOut = [];
dflt = Array.isArray(dflt) ? dflt : [];
for(var i = 0; i < items.length; i++) {
exports.coerce(v, vOut, items, '[' + i + ']', dflt[i]);
}
propOut.set(vOut);
},
validateFunction: function(v, opts) {
if(!Array.isArray(v)) return false;
...
coerce2 = function (containerIn, containerOut, attributes, attribute, dflt) { var propIn = nestedProperty(containerIn, attribute), propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt), valIn = propIn.get(); return (valIn !== undefined && valIn !== null) ? propOut : false; }
...
var letter = options.letter,
font = options.font || {},
defaultTitle = 'Click to enter ' +
(options.title || (letter.toUpperCase() + ' axis')) +
' title';
function coerce2(attr, dflt) {
return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt);
}
var visible = coerce('visible', !options.cheateronly);
var axType = containerOut.type;
if(axType === 'date') {
...
coerceFont = function (coerce, attr, dfltObj) { var out = {}; dfltObj = dfltObj || {}; out.family = coerce(attr + '.family', dfltObj.family); out.size = coerce(attr + '.size', dfltObj.size); out.color = coerce(attr + '.color', dfltObj.color); return out; }
...
}
plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt);
}
var globalFont = Lib.coerceFont(coerce, 'font');
coerce('title');
Lib.coerceFont(coerce, 'titlefont', {
family: globalFont.family,
size: Math.round(globalFont.size * 1.4),
color: globalFont.color
...
validate = function (value, opts) { var valObject = exports.valObjects[opts.valType]; if(opts.arrayOk && Array.isArray(value)) return true; if(valObject.validateFunction) { return valObject.validateFunction(value, opts); } var failed = {}, out = failed, propMock = { set: function(v) { out = v; } }; // 'failed' just something mutable that won't be === anything else valObject.coerceFunction(value, propMock, failed, opts); return out !== failed; }
...
var items = opts.items;
// when free length is off, input and declared lengths must match
if(!opts.freeLength && v.length !== items.length) return false;
// valid when all input items are valid
for(var i = 0; i < v.length; i++) {
var isItemValid = exports.validate(v[i], opts.items[i]);
if(!isItemValid) return false;
}
return true;
}
}
...
computeAPICommandBindings = function (gd, method, args) { var bindings; if(!Array.isArray(args)) args = []; switch(method) { case 'restyle': bindings = computeDataBindings(gd, args); break; case 'relayout': bindings = computeLayoutBindings(gd, args); break; case 'update': bindings = computeDataBindings(gd, [args[0], args[2]]) .concat(computeLayoutBindings(gd, [args[1]])); break; case 'animate': bindings = computeAnimateBindings(gd, args); break; default: // This is the case where intelligent logic about what affects // this command is not implemented. It causes no ill effects. // For example, addFrames simply won't bind to a control component. bindings = []; } return bindings; }
...
if(!Array.isArray(args)) args = [];
// If any command has no method, refuse to bind:
if(!method) {
return false;
}
var bindings = exports.computeAPICommandBindings(gd, method, args);
// Right now, handle one and *only* one property being set:
if(bindings.length !== 1) {
return false;
}
if(!refBinding) {
...
executeAPICommand = function (gd, method, args) { var apiMethod = Plotly[method]; var allArgs = [gd]; if(!Array.isArray(args)) args = []; for(var i = 0; i < args.length; i++) { allArgs.push(args[i]); } return apiMethod.apply(null, allArgs).catch(function(err) { Lib.warn('API call to Plotly.' + method + ' rejected.', err); return Promise.reject(err); }); }
n/a
hasSimpleAPICommandBindings = function (gd, commandList, bindingsByValue) { var i; var n = commandList.length; var refBinding; for(i = 0; i < n; i++) { var binding; var command = commandList[i]; var method = command.method; var args = command.args; if(!Array.isArray(args)) args = []; // If any command has no method, refuse to bind: if(!method) { return false; } var bindings = exports.computeAPICommandBindings(gd, method, args); // Right now, handle one and *only* one property being set: if(bindings.length !== 1) { return false; } if(!refBinding) { refBinding = bindings[0]; if(Array.isArray(refBinding.traces)) { refBinding.traces.sort(); } } else { binding = bindings[0]; if(binding.type !== refBinding.type) { return false; } if(binding.prop !== refBinding.prop) { return false; } if(Array.isArray(refBinding.traces)) { if(Array.isArray(binding.traces)) { binding.traces.sort(); for(var j = 0; j < refBinding.traces.length; j++) { if(refBinding.traces[j] !== binding.traces[j]) { return false; } } } else { return false; } } else { if(binding.prop !== refBinding.prop) { return false; } } } binding = bindings[0]; var value = binding.value; if(Array.isArray(value)) { if(value.length === 1) { value = value[0]; } else { return false; } } if(bindingsByValue) { bindingsByValue[value] = i; } } return refBinding; }
...
if(!ret.cache) {
ret.cache = {};
}
// Either create or just recompute this:
ret.lookupTable = {};
var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable);
if(container && container._commandObserver) {
if(!binding) {
// If container exists and there are no longer any bindings,
// remove existing:
if(container._commandObserver.remove) {
container._commandObserver.remove();
...
manageCommandObserver = function (gd, container, commandList, onchange) { var ret = {}; var enabled = true; if(container && container._commandObserver) { ret = container._commandObserver; } if(!ret.cache) { ret.cache = {}; } // Either create or just recompute this: ret.lookupTable = {}; var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable); if(container && container._commandObserver) { if(!binding) { // If container exists and there are no longer any bindings, // remove existing: if(container._commandObserver.remove) { container._commandObserver.remove(); container._commandObserver = null; return ret; } } else { // If container exists and there *are* bindings, then the lookup // table should have been updated and check is already attached, // so there's nothing to be done: return ret; } } // Determine whether there's anything to do for this binding: if(binding) { // Build the cache: bindingValueHasChanged(gd, binding, ret.cache); ret.check = function check() { if(!enabled) return; var update = bindingValueHasChanged(gd, binding, ret.cache); if(update.changed && onchange) { // Disable checks for the duration of this command in order to avoid // infinite loops: if(ret.lookupTable[update.value] !== undefined) { ret.disable(); Promise.resolve(onchange({ value: update.value, type: binding.type, prop: binding.prop, traces: binding.traces, index: ret.lookupTable[update.value] })).then(ret.enable, ret.enable); } } return update.changed; }; var checkEvents = [ 'plotly_relayout', 'plotly_redraw', 'plotly_restyle', 'plotly_update', 'plotly_animatingframe', 'plotly_afterplot' ]; for(var i = 0; i < checkEvents.length; i++) { gd._internalOn(checkEvents[i], ret.check); } ret.remove = function() { for(var i = 0; i < checkEvents.length; i++) { gd._removeInternalListener(checkEvents[i], ret.check); } }; } else { // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning // is a start Lib.warn('Unable to automatically bind plot updates to API command'); ret.lookupTable = {}; ret.remove = function() {}; } ret.disable = function disable() { enabled = false; }; ret.enable = function enable() { enabled = true; }; if(container) { container._commandObserver = ret; } return ret; }
n/a
doesDirExist = function (dirPath) { try { if(fs.statSync(dirPath).isDirectory()) return true; } catch(e) { return false; } return false; }
...
logger('make build/credentials.json');
}
// Make artifact folders for image tests
function makeTestImageFolders() {
function makeOne(folderPath, info) {
if(!common.doesDirExist(folderPath)) {
fs.mkdirSync(folderPath);
logger('initialize ' + info);
}
else logger(info + ' is present');
}
makeOne(constants.pathToTestImages, 'test image folder');
...
doesFileExist = function (filePath) { try { if(fs.statSync(filePath).isFile()) return true; } catch(e) { return false; } return false; }
...
date.toLocaleDateString(),
date.toLocaleTimeString(),
date.toString().match(/\(([A-Za-z\s].*)\)/)[1]
].join(' ');
};
exports.getTimeLastModified = function(filePath) {
if(!exports.doesFileExist(filePath)) {
throw new Error(filePath + ' does not exist');
}
var stats = fs.statSync(filePath),
formattedTime = exports.formatTime(stats.mtime);
return formattedTime;
...
execCmd = function (cmd, cb, errorCb) { cb = cb ? cb : function() {}; errorCb = errorCb ? errorCb : function(err) { if(err) throw err; }; exec(cmd, function(err) { errorCb(err); cb(); }) .stdout.pipe(process.stdout); }
...
case 'run':
msg = 'Booting up ' + constants.testContainerName + ' docker container';
cmd = containerCommands.dockerRun;
// if docker-run fails, try docker-start.
errorCb = function(err) {
if(err) common.execCmd('docker start ' + constants.testContainerName
);
};
break;
case 'setup':
msg = 'Setting up ' + constants.testContainerName + ' docker container for testing';
cmd = containerCommands.getRunCmd(isCI, containerCommands.setup);
...
formatTime = function (date) { return [ date.toLocaleDateString(), date.toLocaleTimeString(), date.toString().match(/\(([A-Za-z\s].*)\)/)[1] ].join(' '); }
...
exports.getTimeLastModified = function(filePath) {
if(!exports.doesFileExist(filePath)) {
throw new Error(filePath + ' does not exist');
}
var stats = fs.statSync(filePath),
formattedTime = exports.formatTime(stats.mtime);
return formattedTime;
};
exports.touch = function(filePath) {
fs.closeSync(fs.openSync(filePath, 'w'));
};
...
getTimeLastModified = function (filePath) { if(!exports.doesFileExist(filePath)) { throw new Error(filePath + ' does not exist'); } var stats = fs.statSync(filePath), formattedTime = exports.formatTime(stats.mtime); return formattedTime; }
n/a
throwOnError = function (err) { if(err) throw err; }
n/a
touch = function (filePath) { fs.closeSync(fs.openSync(filePath, 'w')); }
n/a
writeFile = function (filePath, content, cb) { fs.writeFile(filePath, content, function(err) { if(err) throw err; if(cb) cb(); }); }
...
// Create a credentials json file,
// to be required in jasmine test suites and test dashboard
function makeCredentialsFile() {
var credentials = JSON.stringify({
MAPBOX_ACCESS_TOKEN: constants.mapboxAccessToken
}, null, 2);
common.writeFile(constants.pathToCredentials, credentials);
logger('make build/credentials.json');
}
// Make artifact folders for image tests
function makeTestImageFolders() {
function makeOne(folderPath, info) {
...
getRunCmd = function (isCI, commands) { var _commands = Array.isArray(commands) ? commands.slice() : [commands]; if(isCI) return getRunCI(_commands); // add setup commands locally _commands = [containerCommands.setup].concat(_commands); return getRunLocal(_commands); }
...
if(err) common.execCmd('docker start ' + constants.testContainerName);
};
break;
case 'setup':
msg = 'Setting up ' + constants.testContainerName + ' docker container for testing';
cmd = containerCommands.getRunCmd(isCI, containerCommands.setup);
break;
case 'stop':
msg = 'Stopping ' + constants.testContainerName + ' docker container';
cmd = 'docker stop ' + constants.testContainerName;
break;
...
cleanDate = function (v, dflt, calendar) { if(exports.isJSDate(v) || typeof v === 'number') { // do not allow milliseconds (old) or jsdate objects (inherently // described as gregorian dates) with world calendars if(isWorldCalendar(calendar)) { logError('JS Dates and milliseconds are incompatible with world calendars', v); return dflt; } // NOTE: if someone puts in a year as a number rather than a string, // this will mistakenly convert it thinking it's milliseconds from 1970 // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds v = exports.ms2DateTimeLocal(+v); if(!v && dflt !== undefined) return dflt; } else if(!exports.isDateTime(v, calendar)) { logError('unrecognized date', v); return dflt; } return v; }
...
if(typeof pos === 'string' && (ax._categories || []).length) {
newPos = ax._categories.indexOf(pos);
containerOut[attr] = (newPos === -1) ? dflt : newPos;
return;
}
}
else if(ax.type === 'date') {
containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar);
return;
}
}
// finally make sure we have a number (unless date type already returned a string)
containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt;
};
...
dateTick0 = function (calendar, sunday) { if(isWorldCalendar(calendar)) { return sunday ? Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar]; } else { return sunday ? '2000-01-02' : '2000-01-01'; } }
...
// log with linear ticks: L# where # is the linear tick spacing
// log showing powers plus some intermediates:
// D1 shows all digits, D2 shows 2 and 5
axes.autoTicks = function(ax, roughDTick) {
var base;
if(ax.type === 'date') {
ax.tick0 = Lib.dateTick0(ax.calendar);
// the criteria below are all based on the rough spacing we calculate
// being > half of the final unit - so precalculate twice the rough val
var roughX2 = 2 * roughDTick;
if(roughX2 > ONEAVGYEAR) {
roughDTick /= ONEAVGYEAR;
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
...
dateTime2ms = function (s, calendar) { // first check if s is a date object if(exports.isJSDate(s)) { // Convert to the UTC milliseconds that give the same // hours as this date has in the local timezone s = Number(s) - s.getTimezoneOffset() * ONEMIN; if(s >= MIN_MS && s <= MAX_MS) return s; return BADNUM; } // otherwise only accept strings and numbers if(typeof s !== 'string' && typeof s !== 'number') return BADNUM; s = String(s); var isWorld = isWorldCalendar(calendar); // to handle out-of-range dates in international calendars, accept // 'G' as a prefix to force the built-in gregorian calendar. var s0 = s.charAt(0); if(isWorld && (s0 === 'G' || s0 === 'g')) { s = s.substr(1); calendar = ''; } var isChinese = isWorld && calendar.substr(0, 7) === 'chinese'; var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP); if(!match) return BADNUM; var y = match[1], m = match[3] || '1', d = Number(match[5] || 1), H = Number(match[7] || 0), M = Number(match[9] || 0), S = Number(match[11] || 0); if(isWorld) { // disallow 2-digit years for world calendars if(y.length === 2) return BADNUM; y = Number(y); var cDate; try { var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar); if(isChinese) { var isIntercalary = m.charAt(m.length - 1) === 'i'; m = parseInt(m, 10); cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d); } else { cDate = calInstance.newDate(y, Number(m), d); } } catch(e) { return BADNUM; } // Invalid ... date if(!cDate) return BADNUM; return ((cDate.toJD() - EPOCHJD) * ONEDAY) + (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC); } if(y.length === 2) { y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST; } else y = Number(y); // new Date uses months from 0; subtract 1 here just so we // don't have to do it again during the validity test below m -= 1; // javascript takes new Date(0..99,m,d) to mean 1900-1999, so // to support years 0-99 we need to use setFullYear explicitly // Note that 2000 is a leap year. var date = new Date(Date.UTC(2000, m, d, H, M)); date.setUTCFullYear(y); if(date.getUTCMonth() !== m) return BADNUM; if(date.getUTCDate() !== d) return BADNUM; return date.getTime() + S * ONESEC; }
...
if(date.getUTCMonth() !== m) return BADNUM;
if(date.getUTCDate() !== d) return BADNUM;
return date.getTime() + S * ONESEC;
};
MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
// is string s a date? (see above)
exports.isDateTime = function(s, calendar) {
return (exports.dateTime2ms(s, calendar) !== BADNUM);
};
...
dfltRange = function (calendar) { if(isWorldCalendar(calendar)) { return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar]; } else { return ['2000-01-01', '2001-01-01']; } }
...
*/
ax.cleanRange = function(rangeAttr) {
if(!rangeAttr) rangeAttr = 'range';
var range = Lib.nestedProperty(ax, rangeAttr).get(),
axLetter = (ax._id || 'x').charAt(0),
i, dflt;
if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
else if(axLetter === 'y') dflt = constants.DFLTRANGEY;
else dflt = constants.DFLTRANGEX;
// make sure we don't later mutate the defaults
dflt = dflt.slice();
if(!range || range.length !== 2) {
...
findExactDates = function (data, calendar) { var exactYears = 0, exactMonths = 0, exactDays = 0, blankCount = 0, d, di; var calInstance = ( isWorldCalendar(calendar) && Registry.getComponentMethod('calendars', 'getCal')(calendar) ); for(var i = 0; i < data.length; i++) { di = data[i]; // not date data at all if(!isNumeric(di)) { blankCount ++; continue; } // not an exact date if(di % ONEDAY) continue; if(calInstance) { try { d = calInstance.fromJD(di / ONEDAY + EPOCHJD); if(d.day() === 1) { if(d.month() === 1) exactYears++; else exactMonths++; } else exactDays++; } catch(e) { // invalid date in this calendar - ignore it here. } } else { d = new Date(di); if(d.getUTCDate() === 1) { if(d.getUTCMonth() === 0) exactYears++; else exactMonths++; } else exactDays++; } } exactMonths += exactYears; exactDays += exactMonths; var dataCount = data.length - blankCount; return { exactYears: exactYears / dataCount, exactMonths: exactMonths / dataCount, exactDays: exactDays / dataCount }; }
...
}
}
return binStart;
}
function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
var stats = Lib.findExactDates(data, calendar);
// number of data points that needs to be an exact value
// to shift that increment to (near) the bin center
var threshold = 0.8;
if(stats.exactDays > threshold) {
var numMonths = Number(dtick.substr(1));
...
formatDate = function (x, fmt, tr, calendar) { var headStr, dateStr; calendar = isWorldCalendar(calendar) && calendar; if(fmt) return modDateFormat(fmt, x, calendar); if(calendar) { try { var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) .fromJD(dateJD); if(tr === 'y') dateStr = yearFormatWorld(cDate); else if(tr === 'm') dateStr = monthFormatWorld(cDate); else if(tr === 'd') { headStr = yearFormatWorld(cDate); dateStr = dayFormatWorld(cDate); } else { headStr = yearMonthDayFormatWorld(cDate); dateStr = formatTime(x, tr); } } catch(e) { return 'Invalid'; } } else { var d = new Date(Math.floor(x + 0.05)); if(tr === 'y') dateStr = yearFormat(d); else if(tr === 'm') dateStr = monthFormat(d); else if(tr === 'd') { headStr = yearFormat(d); dateStr = dayFormat(d); } else { headStr = yearMonthDayFormat(d); dateStr = formatTime(x, tr); } } return dateStr + (headStr ? '\n' + headStr : ''); }
...
dateStr, h, m, s, msec10, d;
if(isWorldCalendar(calendar)) {
var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD,
timeMs = Math.floor(mod(ms, ONEDAY));
try {
dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar)
.fromJD(dateJD).formatDate('yyyy-mm-dd');
}
catch(e) {
// invalid date in this calendar - fall back to Gyyyy-mm-dd
dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
}
// yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
...
incrementMonth = function (ms, dMonth, calendar) { calendar = isWorldCalendar(calendar) && calendar; // pull time out and operate on pure dates, then add time back at the end // this gives maximum precision - not that we *normally* care if we're // incrementing by month, but better to be safe! var timeMs = mod(ms, ONEDAY); ms = Math.round(ms - timeMs); if(calendar) { try { var dateJD = Math.round(ms / ONEDAY) + EPOCHJD, calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar), cDate = calInstance.fromJD(dateJD); if(dMonth % 12) calInstance.add(cDate, dMonth, 'm'); else calInstance.add(cDate, dMonth / 12, 'y'); return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs; } catch(e) { logError('invalid ms ' + ms + ' in calendar ' + calendar); // then keep going in gregorian even though the result will be 'Invalid' } } var y = new Date(ms + THREEDAYS); return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS; }
...
if(isNumeric(dtick)) return x + axSign * dtick;
// everything else is a string, one character plus a number
var tType = dtick.charAt(0),
dtSigned = axSign * Number(dtick.substr(1));
// Dates: months (or years - see Lib.incrementMonth)
if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
// Log scales: Linear, Digits
else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
// log10 of 2,5,10, or all digits (logs just have to be
// close enough to round)
else if(tType === 'D') {
...
isDateTime = function (s, calendar) { return (exports.dateTime2ms(s, calendar) !== BADNUM); }
...
// NOTE: if someone puts in a year as a number rather than a string,
// this will mistakenly convert it thinking it's milliseconds from 1970
// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
v = exports.ms2DateTimeLocal(+v);
if(!v && dflt !== undefined) return dflt;
}
else if(!exports.isDateTime(v, calendar)) {
logError('unrecognized date', v);
return dflt;
}
return v;
};
/*
...
isJSDate = function (v) { return typeof v === 'object' && v !== null && typeof v.getTime === 'function'; }
...
* make now will cover all possibilities. mostly this will all be taken
* care of in initial parsing, should only be an issue for hand-entered data
* currently (2016) this range is:
* 1946-2045
*/
exports.dateTime2ms = function(s, calendar) {
// first check if s is a date object
if(exports.isJSDate(s)) {
// Convert to the UTC milliseconds that give the same
// hours as this date has in the local timezone
s = Number(s) - s.getTimezoneOffset() * ONEMIN;
if(s >= MIN_MS && s <= MAX_MS) return s;
return BADNUM;
}
// otherwise only accept strings and numbers
...
ms2DateTime = function (ms, r, calendar) { if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM; if(!r) r = 0; var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), msRounded = Math.round(ms - msecTenths / 10), dateStr, h, m, s, msec10, d; if(isWorldCalendar(calendar)) { var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD, timeMs = Math.floor(mod(ms, ONEDAY)); try { dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) .fromJD(dateJD).formatDate('yyyy-mm-dd'); } catch(e) { // invalid date in this calendar - fall back to Gyyyy-mm-dd dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded)); } // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does // other things for a few calendars, so we can't trust it. Just pad // it manually (after the '-' if there is one) if(dateStr.charAt(0) === '-') { while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1); } else { while(dateStr.length < 10) dateStr = '0' + dateStr; } // TODO: if this is faster, we could use this block for extracting // the time components of regular gregorian too h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0; m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0; s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0; msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0; } else { d = new Date(msRounded); dateStr = utcFormat('%Y-%m-%d')(d); // <90 days: add hours and minutes - never *only* add hours h = (r < NINETYDAYS) ? d.getUTCHours() : 0; m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0; // <3 hours: add seconds s = (r < THREEHOURS) ? d.getUTCSeconds() : 0; // <5 minutes: add ms (plus one extra digit, this is msec*10) msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0; } return includeTime(dateStr, h, m, s, msec10); }
n/a
ms2DateTimeLocal = function (ms) { if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM; var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), d = new Date(Math.round(ms - msecTenths / 10)), dateStr = d3.time.format('%Y-%m-%d')(d), h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(), msec10 = d.getUTCMilliseconds() * 10 + msecTenths; return includeTime(dateStr, h, m, s, msec10); }
...
logError('JS Dates and milliseconds are incompatible with world calendars', v);
return dflt;
}
// NOTE: if someone puts in a year as a number rather than a string,
// this will mistakenly convert it thinking it's milliseconds from 1970
// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
v = exports.ms2DateTimeLocal(+v);
if(!v && dflt !== undefined) return dflt;
}
else if(!exports.isDateTime(v, calendar)) {
logError('unrecognized date', v);
return dflt;
}
return v;
...
function draw(gd) { var fullLayout = gd._fullLayout; fullLayout._infolayer.selectAll('.annotation').remove(); for(var i = 0; i < fullLayout.annotations.length; i++) { if(fullLayout.annotations[i].visible) { drawOne(gd, i); } } return Plots.previousPromises(gd); }
...
return gd._promises.length && Promise.all(gd._promises);
};
exports.drawMainTitle = function(gd) {
var fullLayout = gd._fullLayout;
Titles.draw(gd, 'gtitle', {
propContainer: fullLayout,
propName: 'title',
dfltName: 'Plot',
attributes: {
x: fullLayout.width / 2,
y: fullLayout._size.t / 2,
'text-anchor': 'middle'
...
function drawOne(gd, index) { var layout = gd.layout, fullLayout = gd._fullLayout, gs = gd._fullLayout._size; // remove the existing annotation if there is one fullLayout._infolayer.selectAll('.annotation[data-index="' + index + '"]').remove(); // remember a few things about what was already there, var optionsIn = (layout.annotations || [])[index], options = fullLayout.annotations[index]; var annClipID = 'clip' + fullLayout._uid + '_ann' + index; // this annotation is gone - quit now after deleting it // TODO: use d3 idioms instead of deleting and redrawing every time if(!optionsIn || options.visible === false) { d3.selectAll('#' + annClipID).remove(); return; } var xa = Axes.getFromId(gd, options.xref), ya = Axes.getFromId(gd, options.yref), // calculated pixel positions // x & y each will get text, head, and tail as appropriate annPosPx = {x: {}, y: {}}, textangle = +options.textangle || 0; // create the components // made a single group to contain all, so opacity can work right // with border/arrow together this could handle a whole bunch of // cleanup at this point, but works for now var annGroup = fullLayout._infolayer.append('g') .classed('annotation', true) .attr('data-index', String(index)) .style('opacity', options.opacity); // another group for text+background so that they can rotate together var annTextGroup = annGroup.append('g') .classed('annotation-text-g', true) .attr('data-index', String(index)); var annTextGroupInner = annTextGroup.append('g') .style('pointer-events', options.captureevents ? 'all' : null) .call(setCursor, 'default') .on('click', function() { gd._dragging = false; gd.emit('plotly_clickannotation', { index: index, annotation: optionsIn, fullAnnotation: options }); }); if(options.hovertext) { annTextGroupInner .on('mouseover', function() { var hoverOptions = options.hoverlabel; var hoverFont = hoverOptions.font; var bBox = this.getBoundingClientRect(); var bBoxRef = gd.getBoundingClientRect(); Fx.loneHover({ x0: bBox.left - bBoxRef.left, x1: bBox.right - bBoxRef.left, y: (bBox.top + bBox.bottom) / 2 - bBoxRef.top, text: options.hovertext, color: hoverOptions.bgcolor, borderColor: hoverOptions.bordercolor, fontFamily: hoverFont.family, fontSize: hoverFont.size, fontColor: hoverFont.color }, { container: fullLayout._hoverlayer.node(), outerContainer: fullLayout._paper.node() }); }) .on('mouseout', function() { Fx.loneUnhover(fullLayout._hoverlayer.node()); }); } var borderwidth = options.borderwidth, borderpad = options.borderpad, borderfull = borderwidth + borderpad; var annTextBG = annTextGroupInner.append('rect') .attr('class', 'bg') .style('stroke-width', borderwidth + 'px') .call(Color.stroke, options.bordercolor) .call(Color.fill, options.bgcolor); var isSizeConstrained = options.width || options.height; var annTextClip = fullLayout._defs.select('.clips') .selectAll('#' + annClipID) .data(isSizeConstrained ? [0] : []); annTextClip.enter().append('clipPath') .classed('annclip', true) .attr('id', annClipID) .append('rect'); annTextClip.exit().remove(); var font = options.font; var annText = annTextGroupInner.append('text') .classed('annotation', true) .attr('data-unformatted', options.text) .text(options.text); function textLayout(s) { s.call(Drawing.font, font) .attr({ 'text-anc ...
n/a
init = function (plotObj) {
/*
* If we have already instantiated an emitter for this plot
* return early.
*/
if(plotObj._ev instanceof EventEmitter) return plotObj;
var ev = new EventEmitter();
var internalEv = new EventEmitter();
/*
* Assign to plot._ev while we still live in a land
* where plot is a DOM element with stuff attached to it.
* In the future we can make plot the event emitter itself.
*/
plotObj._ev = ev;
/*
* Create a second event handler that will manage events *internally*.
* This allows parts of plotly to respond to thing like relayout without
* having to use the user-facing event handler. They cannot peacefully
* coexist on the same handler because a user invoking
* plotObj.removeAllListeners() would detach internal events, breaking
* plotly.
*/
plotObj._internalEv = internalEv;
/*
* Assign bound methods from the ev to the plot object. These methods
* will reference the 'this' of plot._ev even though they are methods
* of plot. This will keep the event machinery away from the plot object
* which currently is often a DOM element but presents an API that will
* continue to function when plot becomes an emitter. Not all EventEmitter
* methods have been bound to `plot` as some do not currently add value to
* the Plotly event API.
*/
plotObj.on = ev.on.bind(ev);
plotObj.once = ev.once.bind(ev);
plotObj.removeListener = ev.removeListener.bind(ev);
plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
/*
* Create funtions for managing internal events. These are *only* triggered
* by the mirroring of external events via the emit function.
*/
plotObj._internalOn = internalEv.on.bind(internalEv);
plotObj._internalOnce = internalEv.once.bind(internalEv);
plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv);
plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv);
/*
* We must wrap emit to continue to support JQuery events. The idea
* is to check to see if the user is using JQuery events, if they are
* we emit JQuery events to trigger user handlers as well as the EventEmitter
* events.
*/
plotObj.emit = function(event, data) {
if(typeof jQuery !== 'undefined') {
jQuery(plotObj).trigger(event, data);
}
ev.emit(event, data);
internalEv.emit(event, data);
};
return plotObj;
}
...
*/
Plotly.plot = function(gd, data, layout, config) {
var frames;
gd = helpers.getGraphDiv(gd);
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
if(Lib.isPlainObject(data)) {
var obj = data;
data = obj.data;
layout = obj.layout;
config = obj.config;
frames = obj.frames;
...
purge = function (plotObj) { delete plotObj._ev; delete plotObj.on; delete plotObj.once; delete plotObj.removeListener; delete plotObj.removeAllListeners; delete plotObj.emit; delete plotObj._ev; delete plotObj._internalEv; delete plotObj._internalOn; delete plotObj._internalOnce; delete plotObj._removeInternalListener; delete plotObj._removeAllInternalListeners; return plotObj; }
...
*/
Plotly.newPlot = function(gd, data, layout, config) {
gd = helpers.getGraphDiv(gd);
// remove gl contexts
Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
Plots.purge(gd);
return Plotly.plot(gd, data, layout, config);
};
/**
* Wrap negative indicies to their positive counterparts.
*
* @param {Number[]} indices An array of indices
...
triggerHandler = function (plotObj, event, data) {
var jQueryHandlerValue;
var nodeEventHandlerValue;
/*
* If Jquery exists run all its handlers for this event and
* collect the return value of the LAST handler function
*/
if(typeof jQuery !== 'undefined') {
jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
}
/*
* Now run all the node style event handlers
*/
var ev = plotObj._ev;
if(!ev) return jQueryHandlerValue;
var handlers = ev._events[event];
if(!handlers) return jQueryHandlerValue;
/*
* handlers can be function or an array of functions
*/
if(typeof handlers === 'function') handlers = [handlers];
var lastHandler = handlers.pop();
/*
* Call all the handlers except the last one.
*/
for(var i = 0; i < handlers.length; i++) {
handlers[i](data);
}
/*
* Now call the final handler and collect its value
*/
nodeEventHandlerValue = lastHandler(data);
/*
* Return either the jquery handler value if it exists or the
* nodeEventHandler value. Jquery event value superceeds nodejs
* events for backwards compatability reasons.
*/
return jQueryHandlerValue !== undefined ? jQueryHandlerValue :
nodeEventHandlerValue;
}
...
var jQueryHandlerValue;
var nodeEventHandlerValue;
/*
* If Jquery exists run all its handlers for this event and
* collect the return value of the LAST handler function
*/
if(typeof jQuery !== 'undefined') {
jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
}
/*
* Now run all the node style event handlers
*/
var ev = plotObj._ev;
if(!ev) return jQueryHandlerValue;
...
extendDeep = function () { return _extend(arguments, true, false, false); }
...
for(var i = 0; i < args.length; i++) {
arg = args[i];
if(arg === gd) copy[i] = arg;
else if(typeof arg === 'object') {
copy[i] = Array.isArray(arg) ?
Lib.extendDeep([], arg) :
Lib.extendDeepAll({}, arg);
}
else copy[i] = arg;
}
return copy;
}
...
extendDeepAll = function () { return _extend(arguments, true, true, false); }
...
for(var i = 0; i < args.length; i++) {
arg = args[i];
if(arg === gd) copy[i] = arg;
else if(typeof arg === 'object') {
copy[i] = Array.isArray(arg) ?
Lib.extendDeep([], arg) :
Lib.extendDeepAll({}, arg);
}
else copy[i] = arg;
}
return copy;
}
...
extendDeepNoArrays = function () { return _extend(arguments, true, false, true); }
...
if(data.hasOwnProperty(key)) {
if((match = key.match(dottedPropertyRegex))) {
datum = data[key];
prop = match[1];
delete data[key];
data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths
(datum))[prop]);
} else if((match = key.match(indexedPropertyRegex))) {
datum = data[key];
prop = match[1];
idx = parseInt(match[2]);
delete data[key];
...
extendFlat = function () { return _extend(arguments, false, false, false); }
...
function opaqueSetBackground(gd, bgColor) {
gd._fullLayout._paperdiv.style('background', 'white');
Plotly.defaultConfig.setBackground(gd, bgColor);
}
function setPlotContext(gd, config) {
if(!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig);
var context = gd._context;
if(config) {
Object.keys(config).forEach(function(key) {
if(key in context) {
if(key === 'setBackground' && config[key] === 'opaque') {
context[key] = opaqueSetBackground;
...
function Geo(options) { this.id = options.id; this.graphDiv = options.graphDiv; this.container = options.container; this.topojsonURL = options.topojsonURL; this.topojsonName = null; this.topojson = null; this.projectionType = null; this.projection = null; this.clipAngle = null; this.setScale = null; this.path = null; this.zoom = null; this.zoomReset = null; this.makeFramework(); this.traceHash = {}; }
n/a
adjustLayout = function (geoLayout, graphSize) { var domain = geoLayout.domain; var left = graphSize.l + graphSize.w * domain.x[0] + geoLayout._marginX, top = graphSize.t + graphSize.h * (1 - domain.y[1]) + geoLayout._marginY; Drawing.setTranslate(this.framework, left, top); var dimsAttrs = { x: 0, y: 0, width: geoLayout._width, height: geoLayout._height }; this.clipDef.select('rect') .attr(dimsAttrs); this.framework.select('.bglayer').select('rect') .attr(dimsAttrs) .call(Color.fill, geoLayout.bgcolor); this.xaxis._offset = left; this.xaxis._length = geoLayout._width; this.yaxis._offset = top; this.yaxis._length = geoLayout._height; }
...
// TODO don't reset projection on all graph edits
_this.projection = null;
_this.setScale = createGeoScale(geoLayout, graphSize);
_this.makeProjection(geoLayout);
_this.makePath();
_this.adjustLayout(geoLayout, graphSize);
_this.zoom = createGeoZoom(_this, geoLayout);
_this.zoomReset = createGeoZoomReset(_this, geoLayout);
_this.mockAxis = createMockAxis(fullLayout);
_this.framework
.call(_this.zoom)
...
drawGraticule = function (selection, axisName, geoLayout) { var axisLayout = geoLayout[axisName]; if(axisLayout.showgrid !== true) return; var scopeDefaults = constants.scopeDefaults[geoLayout.scope], lonaxisRange = scopeDefaults.lonaxisRange, lataxisRange = scopeDefaults.lataxisRange, step = axisName === 'lonaxis' ? [axisLayout.dtick] : [0, axisLayout.dtick], graticule = makeGraticule(lonaxisRange, lataxisRange, step); selection.append('g') .datum(graticule) .attr('class', axisName + 'graticule') .append('path') .attr('class', 'graticulepath'); }
...
// N.B. html('') does not work in IE11
gBaseLayer.selectAll('*').remove();
for(var i = 0; i < baseLayers.length; i++) {
layerName = baseLayers[i];
if(axesNames.indexOf(layerName) !== -1) {
this.drawGraticule(gBaseLayer, layerName, geoLayout);
}
else this.drawTopo(gBaseLayer, layerName, geoLayout);
}
this.styleLayout(geoLayout);
};
...
drawLayout = function (geoLayout) { var gBaseLayer = this.framework.select('g.baselayer'), baseLayers = constants.baseLayers, axesNames = constants.axesNames, layerName; // TODO move to more d3-idiomatic pattern (that's work on replot) // N.B. html('') does not work in IE11 gBaseLayer.selectAll('*').remove(); for(var i = 0; i < baseLayers.length; i++) { layerName = baseLayers[i]; if(axesNames.indexOf(layerName) !== -1) { this.drawGraticule(gBaseLayer, layerName, geoLayout); } else this.drawTopo(gBaseLayer, layerName, geoLayout); } this.styleLayout(geoLayout); }
...
else _this.onceTopojsonIsLoaded(geoCalcData, geoLayout);
// TODO handle topojson-is-loading case
// to avoid making multiple request while streaming
};
proto.onceTopojsonIsLoaded = function(geoCalcData, geoLayout) {
this.drawLayout(geoLayout);
Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout);
this.render();
};
proto.makeProjection = function(geoLayout) {
...
drawTopo = function (selection, layerName, geoLayout) { if(geoLayout['show' + layerName] !== true) return; var topojson = this.topojson, datum = layerName === 'frame' ? constants.sphereSVG : topojsonFeature(topojson, topojson.objects[layerName]); selection.append('g') .datum(datum) .attr('class', layerName) .append('path') .attr('class', 'basepath'); }
...
for(var i = 0; i < baseLayers.length; i++) {
layerName = baseLayers[i];
if(axesNames.indexOf(layerName) !== -1) {
this.drawGraticule(gBaseLayer, layerName, geoLayout);
}
else this.drawTopo(gBaseLayer, layerName, geoLayout);
}
this.styleLayout(geoLayout);
};
function styleFillLayer(selection, layerName, geoLayout) {
var layerAdj = constants.layerNameToAdjective[layerName];
...
isLonLatOverEdges = function (lonlat) { var clipAngle = this.clipAngle; if(clipAngle === null) return false; var p = this.projection.rotate(), angle = d3.geo.distance(lonlat, [-p[0], -p[1]]), maxAngle = clipAngle * Math.PI / 180; return angle > maxAngle; }
...
if(!lonlatPx) return null;
return 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')';
}
// hide paths over edges of clipped projections
function hideShowPoints(d) {
return _this.isLonLatOverEdges(d.lonlat) ? '0' : '1.0';
}
framework.selectAll('path.basepath').attr('d', path);
framework.selectAll('path.graticulepath').attr('d', path);
gChoropleth.selectAll('path.choroplethlocation').attr('d', path);
gChoropleth.selectAll('path.basepath').attr('d', path);
...
makeFramework = function () { var fullLayout = this.graphDiv._fullLayout; var clipId = 'clip' + fullLayout._uid + this.id; var defGroup = fullLayout._defs.selectAll('g.clips') .data([0]); defGroup.enter().append('g') .classed('clips', true); var clipDef = this.clipDef = defGroup.selectAll('#' + clipId) .data([0]); clipDef.enter().append('clipPath').attr('id', clipId) .append('rect'); var framework = this.framework = d3.select(this.container).append('g'); framework .attr('class', 'geo ' + this.id) .style('pointer-events', 'all') .call(Drawing.setClipUrl, clipId); framework.append('g') .attr('class', 'bglayer') .append('rect'); framework.append('g').attr('class', 'baselayer'); framework.append('g').attr('class', 'choroplethlayer'); framework.append('g').attr('class', 'baselayeroverchoropleth'); framework.append('g').attr('class', 'scattergeolayer'); // N.B. disable dblclick zoom default framework.on('dblclick.zoom', null); this.xaxis = { _id: 'x' }; this.yaxis = { _id: 'y' }; }
...
this.clipAngle = null;
this.setScale = null;
this.path = null;
this.zoom = null;
this.zoomReset = null;
this.makeFramework();
this.traceHash = {};
}
module.exports = Geo;
var proto = Geo.prototype;
...
makePath = function () { this.path = d3.geo.path().projection(this.projection); }
...
// N.B. 'geoLayout' is unambiguous, no need for 'user' geo layout here
// TODO don't reset projection on all graph edits
_this.projection = null;
_this.setScale = createGeoScale(geoLayout, graphSize);
_this.makeProjection(geoLayout);
_this.makePath();
_this.adjustLayout(geoLayout, graphSize);
_this.zoom = createGeoZoom(_this, geoLayout);
_this.zoomReset = createGeoZoomReset(_this, geoLayout);
_this.mockAxis = createMockAxis(fullLayout);
_this.framework
...
makeProjection = function (geoLayout) { var projLayout = geoLayout.projection, projType = projLayout.type, isNew = this.projection === null || projType !== this.projectionType, projection; if(isNew) { this.projectionType = projType; projection = this.projection = d3.geo[constants.projNames[projType]](); } else projection = this.projection; projection .translate(projLayout._translate0) .precision(constants.precision); if(!geoLayout._isAlbersUsa) { projection .rotate(projLayout._rotate) .center(projLayout._center); } if(geoLayout._clipAngle) { this.clipAngle = geoLayout._clipAngle; // needed in proto.render projection .clipAngle(geoLayout._clipAngle - constants.clipPad); } else this.clipAngle = null; // for graph edits if(projLayout.parallels) { projection .parallels(projLayout.parallels); } if(isNew) this.setScale(projection); projection .translate(projLayout._translate) .scale(projLayout._scale); }
...
// N.B. 'geoLayout' is unambiguous, no need for 'user' geo layout here
// TODO don't reset projection on all graph edits
_this.projection = null;
_this.setScale = createGeoScale(geoLayout, graphSize);
_this.makeProjection(geoLayout);
_this.makePath();
_this.adjustLayout(geoLayout, graphSize);
_this.zoom = createGeoZoom(_this, geoLayout);
_this.zoomReset = createGeoZoomReset(_this, geoLayout);
_this.mockAxis = createMockAxis(fullLayout);
...
onceTopojsonIsLoaded = function (geoCalcData, geoLayout) { this.drawLayout(geoLayout); Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout); this.render(); }
...
topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout);
if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) {
_this.topojsonName = topojsonNameNew;
if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) {
_this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName];
_this.onceTopojsonIsLoaded(geoCalcData, geoLayout);
}
else {
topojsonPath = topojsonUtils.getTopojsonPath(
_this.topojsonURL,
_this.topojsonName
);
...
plot = function (geoCalcData, fullLayout, promises) { var _this = this, geoLayout = fullLayout[_this.id], graphSize = fullLayout._size; var topojsonNameNew, topojsonPath; // N.B. 'geoLayout' is unambiguous, no need for 'user' geo layout here // TODO don't reset projection on all graph edits _this.projection = null; _this.setScale = createGeoScale(geoLayout, graphSize); _this.makeProjection(geoLayout); _this.makePath(); _this.adjustLayout(geoLayout, graphSize); _this.zoom = createGeoZoom(_this, geoLayout); _this.zoomReset = createGeoZoomReset(_this, geoLayout); _this.mockAxis = createMockAxis(fullLayout); _this.framework .call(_this.zoom) .on('dblclick.zoom', _this.zoomReset); _this.framework.on('mousemove', function() { var mouse = d3.mouse(this), lonlat = _this.projection.invert(mouse); if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) return; var evt = d3.event; evt.xpx = mouse[0]; evt.ypx = mouse[1]; _this.xaxis.c2p = function() { return mouse[0]; }; _this.xaxis.p2c = function() { return lonlat[0]; }; _this.yaxis.c2p = function() { return mouse[1]; }; _this.yaxis.p2c = function() { return lonlat[1]; }; Fx.hover(_this.graphDiv, evt, _this.id); }); _this.framework.on('mouseout', function() { Fx.loneUnhover(fullLayout._toppaper); }); _this.framework.on('click', function() { Fx.click(_this.graphDiv, d3.event); }); topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout); if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) { _this.topojsonName = topojsonNameNew; if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) { _this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName]; _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); } else { topojsonPath = topojsonUtils.getTopojsonPath( _this.topojsonURL, _this.topojsonName ); promises.push(new Promise(function(resolve, reject) { d3.json(topojsonPath, function(error, topojson) { if(error) { if(error.status === 404) { reject(new Error([ 'plotly.js could not find topojson file at', topojsonPath, '.', 'Make sure the *topojsonURL* plot config option', 'is set properly.' ].join(' '))); } else { reject(new Error([ 'unexpected error while fetching topojson file at', topojsonPath ].join(' '))); } return; } _this.topojson = topojson; PlotlyGeoAssets.topojson[_this.topojsonName] = topojson; _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); resolve(); }); })); } } else _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); // TODO handle topojson-is-loading case // to avoid making multiple request while streaming }
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
render = function () { var _this = this, framework = _this.framework, gChoropleth = framework.select('g.choroplethlayer'), gScatterGeo = framework.select('g.scattergeolayer'), path = _this.path; function translatePoints(d) { var lonlatPx = _this.projection(d.lonlat); if(!lonlatPx) return null; return 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')'; } // hide paths over edges of clipped projections function hideShowPoints(d) { return _this.isLonLatOverEdges(d.lonlat) ? '0' : '1.0'; } framework.selectAll('path.basepath').attr('d', path); framework.selectAll('path.graticulepath').attr('d', path); gChoropleth.selectAll('path.choroplethlocation').attr('d', path); gChoropleth.selectAll('path.basepath').attr('d', path); gScatterGeo.selectAll('path.js-line').attr('d', path); if(_this.clipAngle !== null) { gScatterGeo.selectAll('path.point') .style('opacity', hideShowPoints) .attr('transform', translatePoints); gScatterGeo.selectAll('text') .style('opacity', hideShowPoints) .attr('transform', translatePoints); } else { gScatterGeo.selectAll('path.point') .attr('transform', translatePoints); gScatterGeo.selectAll('text') .attr('transform', translatePoints); } }
...
var geo = fullLayout[geoIds[i]]._subplot;
if(attr === 'zoom') {
var scale = geo.projection.scale();
var newScale = (val === 'in') ? 2 * scale : 0.5 * scale;
geo.projection.scale(newScale);
geo.zoom.scale(newScale);
geo.render();
}
else if(attr === 'reset') geo.zoomReset();
}
}
modeBarButtons.hoverClosestGl2d = {
name: 'hoverClosestGl2d',
...
styleLayer = function (selection, layerName, geoLayout) { var fillLayers = constants.fillLayers, lineLayers = constants.lineLayers; if(fillLayers.indexOf(layerName) !== -1) { styleFillLayer(selection, layerName, geoLayout); } else if(lineLayers.indexOf(layerName) !== -1) { styleLineLayer(selection, layerName, geoLayout); } }
...
for(var i = 0; i < baseLayers.length; i++) {
layerName = baseLayers[i];
if(axesNames.indexOf(layerName) !== -1) {
styleGraticule(gBaseLayer, layerName, geoLayout);
}
else this.styleLayer(gBaseLayer, layerName, geoLayout);
}
};
proto.isLonLatOverEdges = function(lonlat) {
var clipAngle = this.clipAngle;
if(clipAngle === null) return false;
...
styleLayout = function (geoLayout) { var gBaseLayer = this.framework.select('g.baselayer'), baseLayers = constants.baseLayers, axesNames = constants.axesNames, layerName; for(var i = 0; i < baseLayers.length; i++) { layerName = baseLayers[i]; if(axesNames.indexOf(layerName) !== -1) { styleGraticule(gBaseLayer, layerName, geoLayout); } else this.styleLayer(gBaseLayer, layerName, geoLayout); } }
...
if(axesNames.indexOf(layerName) !== -1) {
this.drawGraticule(gBaseLayer, layerName, geoLayout);
}
else this.drawTopo(gBaseLayer, layerName, geoLayout);
}
this.styleLayout(geoLayout);
};
function styleFillLayer(selection, layerName, geoLayout) {
var layerAdj = constants.layerNameToAdjective[layerName];
selection.select('.' + layerName)
.selectAll('path')
...
locationToFeature = function (locationmode, location, features) { var locationId = getLocationId(locationmode, location); if(locationId) { for(var i = 0; i < features.length; i++) { var feature = features[i]; if(feature.id === locationId) return feature; } Lib.warn([ 'Location with id', locationId, 'does not have a matching topojson feature at this resolution.' ].join(' ')); } return false; }
n/a
calcTraceToLineCoords = function (calcTrace) { var trace = calcTrace[0].trace; var connectgaps = trace.connectgaps; var coords = []; var lineString = []; for(var i = 0; i < calcTrace.length; i++) { var calcPt = calcTrace[i]; var lonlat = calcPt.lonlat; if(lonlat[0] !== BADNUM) { lineString.push(lonlat); } else if(!connectgaps && lineString.length > 0) { coords.push(lineString); lineString = []; } } if(lineString.length > 0) { coords.push(lineString); } return coords; }
n/a
makeBlank = function () { return { type: 'Point', coordinates: [] }; }
n/a
makeLine = function (coords, trace) { var out = {}; if(coords.length === 1) { out = { type: 'LineString', coordinates: coords[0] }; } else { out = { type: 'MultiLineString', coordinates: coords }; } if(trace) out.trace = trace; return out; }
n/a
makePolygon = function (coords, trace) { var out = {}; if(coords.length === 1) { out = { type: 'Polygon', coordinates: coords }; } else { var _coords = new Array(coords.length); for(var i = 0; i < coords.length; i++) { _coords[i] = [coords[i]]; } out = { type: 'MultiPolygon', coordinates: _coords }; } if(trace) out.trace = trace; return out; }
n/a
click = function (gd, evt) { var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata); function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); } if(gd._hoverdata && evt && evt.target) { if(annotationsDone && annotationsDone.then) { annotationsDone.then(emitClick); } else emitClick(); // why do we get a double event without this??? if(evt.stopImmediatePropagation) evt.stopImmediatePropagation(); } }
...
name = 'download';
}
if(canUseSaveLink) {
saveLink.href = url;
saveLink.download = name;
document.body.appendChild(saveLink);
saveLink.click();
document.body.removeChild(saveLink);
resolve(name);
}
// IE 10+ (native saveAs)
if(typeof navigator !== 'undefined' && navigator.msSaveBlob) {
navigator.msSaveBlob(new Blob([url]), name);
...
getClosest = function (cd, distfn, pointData) { // do we already have a point number? (array mode only) if(pointData.index !== false) { if(pointData.index >= 0 && pointData.index < cd.length) { pointData.distance = 0; } else pointData.index = false; } else { // apply the distance function to each data point // this is the longest loop... if this bogs down, we may need // to create pre-sorted data (by x or y), not sure how to // do this for 'closest' for(var i = 0; i < cd.length; i++) { var newDistance = distfn(cd[i]); if(newDistance <= pointData.distance) { pointData.index = i; pointData.distance = newDistance; } } } return pointData; }
...
maxPos = (hovermode === 'closest') ?
thisBarMaxPos :
function(di) {
return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2);
};
var distfn = Fx.getDistanceFunction(hovermode, dx, dy);
Fx.getClosest(cd, distfn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index === false) return;
// the closest data point
var index = pointData.index,
di = cd[index],
...
getDistanceFunction = function (mode, dx, dy, dxy) { if(mode === 'closest') return dxy || quadrature(dx, dy); return mode === 'x' ? dx : dy; }
...
maxPos = (hovermode === 'closest') ?
thisBarMaxPos :
function(di) {
return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2);
};
var distfn = Fx.getDistanceFunction(hovermode, dx, dy);
Fx.getClosest(cd, distfn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index === false) return;
// the closest data point
var index = pointData.index,
...
hover = function (gd, evt, subplot) { if(typeof gd === 'string') gd = document.getElementById(gd); if(gd._lastHoverTime === undefined) gd._lastHoverTime = 0; // If we have an update queued, discard it now if(gd._hoverTimer !== undefined) { clearTimeout(gd._hoverTimer); gd._hoverTimer = undefined; } // Is it more than 100ms since the last update? If so, force // an update now (synchronously) and exit if(Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) { hover(gd, evt, subplot); gd._lastHoverTime = Date.now(); return; } // Queue up the next hover for 100ms from now (if no further events) gd._hoverTimer = setTimeout(function() { hover(gd, evt, subplot); gd._lastHoverTime = Date.now(); gd._hoverTimer = undefined; }, constants.HOVERMINTIME); }
...
xa._length, ya._length, 'ns', 'ew');
maindrag.onmousemove = function(evt) {
// This is on `gd._fullLayout`, *not* fullLayout because the reference
// changes by the time this is called again.
gd._fullLayout._rehover = function() {
if(gd._fullLayout._hoversubplot === subplot) {
fx.hover(gd, evt, subplot);
}
};
fx.hover(gd, evt, subplot);
// Note that we have *not* used the cached fullLayout variable here
// since that may be outdated when this is called as a callback later on
...
inbox = function (v0, v1) { if(v0 * v1 < 0 || v0 === 0) { return constants.MAXDIST * (0.6 - 0.3 / Math.max(3, Math.abs(v0 - v1))); } return Infinity; }
...
var t = cd[0].t;
var xa = pointData.xa;
var ya = pointData.ya;
var posVal, thisBarMinPos, thisBarMaxPos, minPos, maxPos, dx, dy;
var positionFn = function(di) {
return Fx.inbox(minPos(di) - posVal, maxPos(di) - posVal);
};
if(trace.orientation === 'h') {
posVal = yval;
thisBarMinPos = function(di) { return di.y - di.w / 2; };
thisBarMaxPos = function(di) { return di.y + di.w / 2; };
dx = function(di) {
...
init = function (gd) {
var fullLayout = gd._fullLayout;
if(!fullLayout._has('cartesian') || gd._context.staticPlot) return;
var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
// sort overlays last, then by x axis number, then y axis number
if((fullLayout._plots[a].mainplot && true) ===
(fullLayout._plots[b].mainplot && true)) {
var aParts = a.split('y'),
bParts = b.split('y');
return (aParts[0] === bParts[0]) ?
(Number(aParts[1] || 1) - Number(bParts[1] || 1)) :
(Number(aParts[0] || 1) - Number(bParts[0] || 1));
}
return fullLayout._plots[a].mainplot ? 1 : -1;
});
subplots.forEach(function(subplot) {
var plotinfo = fullLayout._plots[subplot];
if(!fullLayout._has('cartesian')) return;
var xa = plotinfo.xaxis,
ya = plotinfo.yaxis,
// the y position of the main x axis line
y0 = (xa._linepositions[subplot] || [])[3],
// the x position of the main y axis line
x0 = (ya._linepositions[subplot] || [])[3];
var DRAGGERSIZE = constants.DRAGGERSIZE;
if(isNumeric(y0) && xa.side === 'top') y0 -= DRAGGERSIZE;
if(isNumeric(x0) && ya.side !== 'right') x0 -= DRAGGERSIZE;
// main and corner draggers need not be repeated for
// overlaid subplots - these draggers drag them all
if(!plotinfo.mainplot) {
// main dragger goes over the grids and data, so we use its
// mousemove events for all data hover effects
var maindrag = dragBox(gd, plotinfo, 0, 0,
xa._length, ya._length, 'ns', 'ew');
maindrag.onmousemove = function(evt) {
// This is on `gd._fullLayout`, *not* fullLayout because the reference
// changes by the time this is called again.
gd._fullLayout._rehover = function() {
if(gd._fullLayout._hoversubplot === subplot) {
fx.hover(gd, evt, subplot);
}
};
fx.hover(gd, evt, subplot);
// Note that we have *not* used the cached fullLayout variable here
// since that may be outdated when this is called as a callback later on
gd._fullLayout._lasthover = maindrag;
gd._fullLayout._hoversubplot = subplot;
};
/*
* IMPORTANT:
* We must check for the presence of the drag cover here.
* If we don't, a 'mouseout' event is triggered on the
* maindrag before each 'click' event, which has the effect
* of clearing the hoverdata; thus, cancelling the click event.
*/
maindrag.onmouseout = function(evt) {
if(gd._dragging) return;
// When the mouse leaves this maindrag, unset the hovered subplot.
// This may cause problems if it leaves the subplot directly *onto*
// another subplot, but that's a tiny corner case at the moment.
gd._fullLayout._hoversubplot = null;
dragElement.unhover(gd, evt);
};
maindrag.onclick = function(evt) {
fx.click(gd, evt);
};
// corner draggers
if(gd._context.showAxisDragHandles) {
dragBox(gd, plotinfo, -DRAGGERSIZE, -DRAGGERSIZE,
DRAGGERSIZE, DRAGGERSIZE, 'n', 'w');
dragBox(gd, plotinfo, xa._length, -DRAGGERSIZE,
DRAGGERSIZE, DRAGGERSIZE, 'n', 'e');
dragBox(gd, plotinfo, -DRAGGERSIZE, ya._length,
DRAGGERSIZE, DRAGGERSIZE, 's', 'w');
dragBox(gd, plotinfo, xa._length, ya._length,
DRAGGERSIZE, DRAGGERSIZE, 's', 'e');
}
}
if(gd._context.showAxisDragHandles) {
// x axis draggers - if you have overlaid plots, ...
...
*/
Plotly.plot = function(gd, data, layout, config) {
var frames;
gd = helpers.getGraphDiv(gd);
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
if(Lib.isPlainObject(data)) {
var obj = data;
data = obj.data;
layout = obj.layout;
config = obj.config;
frames = obj.frames;
...
isHoriz = function (fullData) { var isHoriz = true; for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; if(trace.orientation !== 'h') { isHoriz = false; break; } } return isHoriz; }
...
coerce('dragmode');
var hovermodeDflt;
if(layoutOut._has('cartesian')) {
// flag for 'horizontal' plots:
// determines the state of the mode bar 'compare' hovermode button
var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData);
hovermodeDflt = isHoriz ? 'y' : 'x';
}
else hovermodeDflt = 'closest';
coerce('hovermode', hovermodeDflt);
};
...
loneHover = function (hoverItem, opts) { var pointData = { color: hoverItem.color || Color.defaultLine, x0: hoverItem.x0 || hoverItem.x || 0, x1: hoverItem.x1 || hoverItem.x || 0, y0: hoverItem.y0 || hoverItem.y || 0, y1: hoverItem.y1 || hoverItem.y || 0, xLabel: hoverItem.xLabel, yLabel: hoverItem.yLabel, zLabel: hoverItem.zLabel, text: hoverItem.text, name: hoverItem.name, idealAlign: hoverItem.idealAlign, // optional extra bits of styling borderColor: hoverItem.borderColor, fontFamily: hoverItem.fontFamily, fontSize: hoverItem.fontSize, fontColor: hoverItem.fontColor, // filler to make createHoverText happy trace: { index: 0, hoverinfo: '' }, xa: {_offset: 0}, ya: {_offset: 0}, index: 0 }; var container3 = d3.select(opts.container), outerContainer3 = opts.outerContainer ? d3.select(opts.outerContainer) : container3; var fullOpts = { hovermode: 'closest', rotateLabels: false, bgColor: opts.bgColor || Color.background, container: container3, outerContainer: outerContainer3 }; var hoverLabel = createHoverText([pointData], fullOpts); alignHoverText(hoverLabel, fullOpts.rotateLabels); return hoverLabel.node(); }
...
annTextGroupInner
.on('mouseover', function() {
var hoverOptions = options.hoverlabel;
var hoverFont = hoverOptions.font;
var bBox = this.getBoundingClientRect();
var bBoxRef = gd.getBoundingClientRect();
Fx.loneHover({
x0: bBox.left - bBoxRef.left,
x1: bBox.right - bBoxRef.left,
y: (bBox.top + bBox.bottom) / 2 - bBoxRef.top,
text: options.hovertext,
color: hoverOptions.bgcolor,
borderColor: hoverOptions.bordercolor,
fontFamily: hoverFont.family,
...
loneUnhover = function (containerOrSelection) { // duck type whether the arg is a d3 selection because ie9 doesn't // handle instanceof like modern browsers do. var selection = Lib.isD3Selection(containerOrSelection) ? containerOrSelection : d3.select(containerOrSelection); selection.selectAll('g.hovertext').remove(); selection.selectAll('.spikeline').remove(); }
...
fontColor: hoverFont.color
}, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node()
});
})
.on('mouseout', function() {
Fx.loneUnhover(fullLayout._hoverlayer.node());
});
}
var borderwidth = options.borderwidth,
borderpad = options.borderpad,
borderfull = borderwidth + borderpad;
...
supplyLayoutDefaults = function (layoutIn, layoutOut, fullData) { function coerce(attr, dflt) { return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); } coerce('dragmode'); var hovermodeDflt; if(layoutOut._has('cartesian')) { // flag for 'horizontal' plots: // determines the state of the mode bar 'compare' hovermode button var isHoriz = layoutOut._isHoriz = fx.isHoriz(fullData); hovermodeDflt = isHoriz ? 'y' : 'x'; } else hovermodeDflt = 'closest'; coerce('hovermode', hovermodeDflt); }
...
}
plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
var i, _module;
// can't be be part of basePlotModules loop
// in order to handle the orphan axes case
Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
// base plot module layout defaults
var basePlotModules = layoutOut._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
_module = basePlotModules[i];
// done above already
...
unhover = function (gd, evt, subplot) { if(typeof gd === 'string') gd = document.getElementById(gd); // Important, clear any queued hovers if(gd._hoverTimer) { clearTimeout(gd._hoverTimer); gd._hoverTimer = undefined; } unhover.raw(gd, evt, subplot); }
...
if(gd._dragging) return;
// When the mouse leaves this maindrag, unset the hovered subplot.
// This may cause problems if it leaves the subplot directly *onto*
// another subplot, but that's a tiny corner case at the moment.
gd._fullLayout._hoversubplot = null;
dragElement.unhover(gd, evt);
};
maindrag.onclick = function(evt) {
fx.click(gd, evt);
};
// corner draggers
...
cleanData = function (data, existingData) { // Enforce unique IDs var suids = [], // seen uids --- so we can weed out incoming repeats uids = data.concat(Array.isArray(existingData) ? existingData : []) .filter(function(trace) { return 'uid' in trace; }) .map(function(trace) { return trace.uid; }); for(var tracei = 0; tracei < data.length; tracei++) { var trace = data[tracei]; var i; // assign uids to each trace and detect collisions. if(!('uid' in trace) || suids.indexOf(trace.uid) !== -1) { var newUid; for(i = 0; i < 100; i++) { newUid = Lib.randstr(uids); if(suids.indexOf(newUid) === -1) break; } trace.uid = Lib.randstr(uids); uids.push(trace.uid); } // keep track of already seen uids, so that if there are // doubles we force the trace with a repeat uid to // acquire a new one suids.push(trace.uid); // BACKWARD COMPATIBILITY FIXES // use xbins to bin data in x, and ybins to bin data in y if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) { trace.ybins = trace.xbins; delete trace.xbins; } // error_y.opacity is obsolete - merge into color if(trace.error_y && 'opacity' in trace.error_y) { var dc = Color.defaults, yeColor = trace.error_y.color || (Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc.length]); trace.error_y.color = Color.addOpacity( Color.rgb(yeColor), Color.opacity(yeColor) * trace.error_y.opacity); delete trace.error_y.opacity; } // convert bardir to orientation, and put the data into // the axes it's eventually going to be used with if('bardir' in trace) { if(trace.bardir === 'h' && (Registry.traceIs(trace, 'bar') || trace.type.substr(0, 9) === 'histogram')) { trace.orientation = 'h'; exports.swapXYData(trace); } delete trace.bardir; } // now we have only one 1D histogram type, and whether // it uses x or y data depends on trace.orientation if(trace.type === 'histogramy') exports.swapXYData(trace); if(trace.type === 'histogramx' || trace.type === 'histogramy') { trace.type = 'histogram'; } // scl->scale, reversescl->reversescale if('scl' in trace) { trace.colorscale = trace.scl; delete trace.scl; } if('reversescl' in trace) { trace.reversescale = trace.reversescl; delete trace.reversescl; } // axis ids x1 -> x, y1-> y if(trace.xaxis) trace.xaxis = Axes.cleanId(trace.xaxis, 'x'); if(trace.yaxis) trace.yaxis = Axes.cleanId(trace.yaxis, 'y'); // scene ids scene1 -> scene if(Registry.traceIs(trace, 'gl3d') && trace.scene) { trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene); } if(!Registry.traceIs(trace, 'pie') && !Registry.traceIs(trace, 'bar')) { if(Array.isArray(trace.textposition)) { trace.textposition = trace.textposition.map(cleanTextPosition); } else if(trace.textposition) { trace.textposition = cleanTextPosition(trace.textposition); } } // fix typo in colorscale definition if(Registry.traceIs(trace, '2dMap')) { if(trace.colorscale === 'YIGnBu') trace.colorscale = 'YlGnBu'; if(trace.colorscale === 'YIOrRd') trace.colorscale = 'YlOrRd'; } if(Registry.traceIs(trace, 'markerColorscale') && trace.marker) { var cont = trace.marker; if(cont.colorscale === 'YIGnBu') cont.colorscale = 'YlGnBu'; if(cont.colorscale === 'YIOrRd') cont.colorscale = 'YlOrRd'; } ...
...
gd._promises = [];
var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data));
// if there is already data on the graph, append the new data
// if you only want to redraw, pass a non-array for data
if(Array.isArray(data)) {
helpers.cleanData(data, gd.data);
if(graphWasEmpty) gd.data = data;
else gd.data.push.apply(gd.data, data);
// for routines outside graph_obj that want a clean tab
// (rather than appending to an existing one) gd.empty
// is used to determine whether to make a new tab
...
cleanLayout = function (layout) {
var i, j;
if(!layout) layout = {};
// cannot have (x|y)axis1, numbering goes axis, axis2, axis3...
if(layout.xaxis1) {
if(!layout.xaxis) layout.xaxis = layout.xaxis1;
delete layout.xaxis1;
}
if(layout.yaxis1) {
if(!layout.yaxis) layout.yaxis = layout.yaxis1;
delete layout.yaxis1;
}
var axList = Axes.list({_fullLayout: layout});
for(i = 0; i < axList.length; i++) {
var ax = axList[i];
if(ax.anchor && ax.anchor !== 'free') {
ax.anchor = Axes.cleanId(ax.anchor);
}
if(ax.overlaying) ax.overlaying = Axes.cleanId(ax.overlaying);
// old method of axis type - isdate and islog (before category existed)
if(!ax.type) {
if(ax.isdate) ax.type = 'date';
else if(ax.islog) ax.type = 'log';
else if(ax.isdate === false && ax.islog === false) ax.type = 'linear';
}
if(ax.autorange === 'withzero' || ax.autorange === 'tozero') {
ax.autorange = true;
ax.rangemode = 'tozero';
}
delete ax.islog;
delete ax.isdate;
delete ax.categories; // replaced by _categories
// prune empty domain arrays made before the new nestedProperty
if(emptyContainer(ax, 'domain')) delete ax.domain;
// autotick -> tickmode
if(ax.autotick !== undefined) {
if(ax.tickmode === undefined) {
ax.tickmode = ax.autotick ? 'auto' : 'linear';
}
delete ax.autotick;
}
}
var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0;
for(i = 0; i < annotationsLen; i++) {
var ann = layout.annotations[i];
if(!Lib.isPlainObject(ann)) continue;
if(ann.ref) {
if(ann.ref === 'paper') {
ann.xref = 'paper';
ann.yref = 'paper';
}
else if(ann.ref === 'data') {
ann.xref = 'x';
ann.yref = 'y';
}
delete ann.ref;
}
cleanAxRef(ann, 'xref');
cleanAxRef(ann, 'yref');
}
var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0;
for(i = 0; i < shapesLen; i++) {
var shape = layout.shapes[i];
if(!Lib.isPlainObject(shape)) continue;
cleanAxRef(shape, 'xref');
cleanAxRef(shape, 'yref');
}
var legend = layout.legend;
if(legend) {
// check for old-style legend positioning (x or y is +/- 100)
if(legend.x > 3) {
legend.x = 1.02;
legend.xanchor = 'left';
}
else if(legend.x < -2) {
legend.x = -0.02;
legend.xanchor = 'right';
}
if(legend.y > 3) {
legend.y = 1.02;
legend.yanchor = 'bottom';
}
else if(legend.y < -2) {
legend.y = -0.02;
legend.yanchor = 'top';
}
}
/*
* Moved from rotate -> orbit for dragmode
*/
if(layout.dragmode === 'rotate') layout.dragmode = 'orbit';
// cannot have scene1, numbering goes scene, scene2, scene3...
if(layout.scene1) {
if(!layout.scene) layout.scene = layout.scene1;
delete layout.scene1;
}
/*
* Clean up Scene layouts
*/
var sceneIds = Plots.getSubplotIds(layout, 'gl3d');
for(i = 0; i < sceneIds.length; i++) {
var scene = layout[sceneIds[i]];
// clean old Camera coords
var cameraposition = scene.cameraposition;
if(Array.isArray(cameraposition) && cameraposition[0].length === 4) {
var rotation = cameraposition[0],
center = cameraposition[1],
radius = cameraposition[2],
mat = m4FromQuat([], rotation),
eye = [];
for(j = 0; j < 3; ++j) {
eye[j] = center[i] + radius * mat[2 + 4 * j];
}
scene.camera = {
eye: {x: eye[0], y: eye[1], z: eye[2]}, ...
...
// for routines outside graph_obj that want a clean tab
// (rather than appending to an existing one) gd.empty
// is used to determine whether to make a new tab
gd.empty = false;
}
if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout);
// if the user is trying to drag the axes, allow new data and layout
// to come in but don't allow a replot.
if(gd._dragging && !gd._transitioning) {
// signal to drag handler that after everything else is done
// we need to replot, because something has changed
gd._replotPending = true;
...
clearPromiseQueue = function (gd) { if(Array.isArray(gd._promises) && gd._promises.length > 0) { Lib.log('Clearing previous rejected promises from queue.'); } gd._promises = []; }
...
* to apply different values to each trace.
*
* 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) {
gd = helpers.getGraphDiv(gd);
helpers.clearPromiseQueue(gd);
var aobj = {};
if(typeof astr === 'string') aobj[astr] = val;
else if(Lib.isPlainObject(astr)) {
// the 3-arg form
aobj = Lib.extendFlat({}, astr);
if(traces === undefined) traces = val;
...
coerceTraceIndices = function (gd, traceIndices) { if(isNumeric(traceIndices)) { return [traceIndices]; } else if(!Array.isArray(traceIndices) || !traceIndices.length) { return gd.data.map(function(_, i) { return i; }); } return traceIndices; }
...
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 = {
docalc: false,
docalcAutorange: false,
doplot: false,
dostyle: false,
...
getGraphDiv = function (gd) { var gdElement; if(typeof gd === 'string') { gdElement = document.getElementById(gd); if(gdElement === null) { throw new Error('No DOM element with id \'' + gd + '\' exists on the page.'); } return gdElement; } else if(gd === null || gd === undefined) { throw new Error('DOM element provided is null or undefined'); } return gd; // otherwise assume that gd is a DOM element }
...
* @param {object} config
* configuration options (see ./plot_config.js for more info)
*
*/
Plotly.plot = function(gd, data, layout, config) {
var frames;
gd = helpers.getGraphDiv(gd);
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
if(Lib.isPlainObject(data)) {
var obj = data;
data = obj.data;
...
hasParent = function (aobj, attr) { var attrParent = getParent(attr); while(attrParent) { if(attrParent in aobj) return true; attrParent = getParent(attrParent); } return false; }
...
// attr can be an array to set several at once (all to the same val)
function doextra(attr, val, i) {
if(Array.isArray(attr)) {
attr.forEach(function(a) { doextra(a, val, i); });
return;
}
// quit if explicitly setting this elsewhere
if(attr in aobj || helpers.hasParent(aobj, attr)) return;
var extraparam;
if(attr.substr(0, 6) === 'LAYOUT') {
extraparam = Lib.nestedProperty(gd.layout, attr.replace('LAYOUT', ''));
} else {
extraparam = Lib.nestedProperty(data[traces[i]], attr);
}
...
manageArrayContainers = function (np, newVal, undoit) { var obj = np.obj, parts = np.parts, pLength = parts.length, pLast = parts[pLength - 1]; var pLastIsNumber = isNumeric(pLast); // delete item if(pLastIsNumber && newVal === null) { // Clear item in array container when new value is null var contPath = parts.slice(0, pLength - 1).join('.'), cont = Lib.nestedProperty(obj, contPath).get(); cont.splice(pLast, 1); // Note that nested property clears null / undefined at end of // array container, but not within them. } // create item else if(pLastIsNumber && np.get() === undefined) { // When adding a new item, make sure undo command will remove it if(np.get() === undefined) undoit[np.astr] = null; np.set(newVal); } // update item else { // If the last part of attribute string isn't a number, // np.set is all we need. np.set(newVal); } }
...
cont.orientation =
{v: 'h', h: 'v'}[contFull.orientation];
}
helpers.swapXYData(cont);
}
else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) {
// TODO: use manageArrays.applyContainerArrayChanges here too
helpers.manageArrayContainers(param, newVal, undoit);
flags.docalc = true;
}
else {
var moduleAttrs = (contFull._module || {}).attributes || {};
var valObject = Lib.nestedProperty(moduleAttrs, ai).get() || {};
// if restyling entire attribute container, assume worse case
...
swapXYData = function (trace) { var i; Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']); if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) { if(trace.transpose) delete trace.transpose; else trace.transpose = true; } if(trace.error_x && trace.error_y) { var errorY = trace.error_y, copyYstyle = ('copy_ystyle' in errorY) ? errorY.copy_ystyle : !(errorY.color || errorY.thickness || errorY.width); Lib.swapAttrs(trace, ['error_?.copy_ystyle']); if(copyYstyle) { Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']); } } if(trace.hoverinfo) { var hoverInfoParts = trace.hoverinfo.split('+'); for(i = 0; i < hoverInfoParts.length; i++) { if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y'; else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x'; } trace.hoverinfo = hoverInfoParts.join('+'); } }
...
// convert bardir to orientation, and put the data into
// the axes it's eventually going to be used with
if('bardir' in trace) {
if(trace.bardir === 'h' && (Registry.traceIs(trace, 'bar') ||
trace.type.substr(0, 9) === 'histogram')) {
trace.orientation = 'h';
exports.swapXYData(trace);
}
delete trace.bardir;
}
// now we have only one 1D histogram type, and whether
// it uses x or y data depends on trace.orientation
if(trace.type === 'histogramy') exports.swapXYData(trace);
...
OptionControl = function (opt, optname) {
/*
* An environment to contain all option setters and
* getters that collectively modify opts.
*
* You can call up opts from any function in new object
* as this.optname || this.opt
*
* See FitOpts for example of usage
*/
if(!opt) opt = {};
if(!optname) optname = 'opt';
var self = {};
self.optionList = [];
self._newoption = function(optObj) {
optObj[optname] = opt;
self[optObj.name] = optObj;
self.optionList.push(optObj);
};
self['_' + optname] = opt;
return self;
}
n/a
addStyleRule = function (selector, styleString) { if(!lib.styleSheet) { var style = document.createElement('style'); // WebKit hack :( style.appendChild(document.createTextNode('')); document.head.appendChild(style); lib.styleSheet = style.sheet; } var styleSheet = lib.styleSheet; if(styleSheet.insertRule) { styleSheet.insertRule(selector + '{' + styleString + '}', 0); } else if(styleSheet.addRule) { styleSheet.addRule(selector, styleString, 0); } else lib.warn('addStyleRule failed'); }
...
'var Lib = require(\'../src/lib\');',
'var rules = ' + rulesStr + ';',
'',
'for(var selector in rules) {',
' var fullSelector = selector.replace(/^,/,\' ,\')',
' .replace(/X/g, \'.js-plotly-plot .plotly\')',
' .replace(/Y/g, \'.plotly-notifier\');',
' Lib.addStyleRule(fullSelector, rules[selector]);',
'}',
''
].join('\n');
fs.writeFile(pathOut, outStr, function(err) {
if(err) throw err;
});
...
aggNums = function (f, v, a, len) { var i, b; if(!len) len = a.length; if(!isNumeric(v)) v = false; if(Array.isArray(a[0])) { b = new Array(len); for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]); a = b; } for(i = 0; i < len; i++) { if(!isNumeric(v)) v = a[i]; else if(isNumeric(a[i])) v = f(+v, +a[i]); } return v; }
...
exports.aggNums = function(f, v, a, len) {
var i,
b;
if(!len) len = a.length;
if(!isNumeric(v)) v = false;
if(Array.isArray(a[0])) {
b = new Array(len);
for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
a = b;
}
for(i = 0; i < len; i++) {
if(!isNumeric(v)) v = a[i];
else if(isNumeric(a[i])) v = f(+v, +a[i]);
}
...
apply2DTransform = function (transform) { return function() { var args = arguments; if(args.length === 3) { args = args[0]; }// from map var xy = arguments.length === 1 ? args[0] : [args[0], args[1]]; return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2); }; }
...
var xy = arguments.length === 1 ? args[0] : [args[0], args[1]];
return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
};
};
// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment)
exports.apply2DTransform2 = function(transform) {
var at = exports.apply2DTransform(transform);
return function(xys) {
return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
};
};
...
apply2DTransform2 = function (transform) { var at = exports.apply2DTransform(transform); return function(xys) { return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); }; }
...
textX = annPosPx.x.text + dx,
textY = annPosPx.y.text + dy,
// find the edge of the text box, where we'll start the arrow:
// create transform matrix to rotate the text box corners
transform = Lib.rotationXYMatrix(textangle, textX, textY),
applyTransform = Lib.apply2DTransform(transform),
applyTransform2 = Lib.apply2DTransform2(transform),
// calculate and transform bounding box
width = +annTextBG.attr('width'),
height = +annTextBG.attr('height'),
xLeft = textX - 0.5 * width,
xRight = xLeft + width,
yTop = textY - 0.5 * height,
...
bBoxIntersect = function (a, b, pad) { pad = pad || 0; return (a.left <= b.right + pad && b.left <= a.right + pad && a.top <= b.bottom + pad && b.top <= a.bottom + pad); }
...
left: x - bb.width / 2,
// impose a 2px gap
right: x + bb.width / 2 + 2,
width: bb.width + 2
});
});
for(i = 0; i < lbbArray.length - 1; i++) {
if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) {
// any overlap at all - set 30 degrees
autoangle = 30;
break;
}
}
if(autoangle) {
var tickspacing = Math.abs(
...
cleanDate = function (v, dflt, calendar) { if(exports.isJSDate(v) || typeof v === 'number') { // do not allow milliseconds (old) or jsdate objects (inherently // described as gregorian dates) with world calendars if(isWorldCalendar(calendar)) { logError('JS Dates and milliseconds are incompatible with world calendars', v); return dflt; } // NOTE: if someone puts in a year as a number rather than a string, // this will mistakenly convert it thinking it's milliseconds from 1970 // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds v = exports.ms2DateTimeLocal(+v); if(!v && dflt !== undefined) return dflt; } else if(!exports.isDateTime(v, calendar)) { logError('unrecognized date', v); return dflt; } return v; }
...
if(typeof pos === 'string' && (ax._categories || []).length) {
newPos = ax._categories.indexOf(pos);
containerOut[attr] = (newPos === -1) ? dflt : newPos;
return;
}
}
else if(ax.type === 'date') {
containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar);
return;
}
}
// finally make sure we have a number (unless date type already returned a string)
containerOut[attr] = isNumeric(pos) ? Number(pos) : dflt;
};
...
function cleanNumber(v) { if(typeof v === 'string') { v = v.replace(JUNK, ''); } if(isNumeric(v)) return Number(v); return BADNUM; }
...
var inc = Math.max(1, (a.length - 1) / 1000),
curvenums = 0,
curvecats = 0,
ai;
for(var i = 0; i < a.length; i += inc) {
ai = a[Math.round(i)];
if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats
++;
}
return curvecats > curvenums * 2;
}
...
coerce = function (containerIn, containerOut, attributes, attribute, dflt) {
var opts = nestedProperty(attributes, attribute).get(),
propIn = nestedProperty(containerIn, attribute),
propOut = nestedProperty(containerOut, attribute),
v = propIn.get();
if(dflt === undefined) dflt = opts.dflt;
/**
* arrayOk: value MAY be an array, then we do no value checking
* at this point, because it can be more complicated than the
* individual form (eg. some array vals can be numbers, even if the
* single values must be color strings)
*/
if(opts.arrayOk && Array.isArray(v)) {
propOut.set(v);
return v;
}
exports.valObjects[opts.valType].coerceFunction(v, propOut, dflt, opts);
return propOut.get();
}
...
}
var items = opts.items,
vOut = [];
dflt = Array.isArray(dflt) ? dflt : [];
for(var i = 0; i < items.length; i++) {
exports.coerce(v, vOut, items, '[' + i + ']', dflt[i]);
}
propOut.set(vOut);
},
validateFunction: function(v, opts) {
if(!Array.isArray(v)) return false;
...
coerce2 = function (containerIn, containerOut, attributes, attribute, dflt) { var propIn = nestedProperty(containerIn, attribute), propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt), valIn = propIn.get(); return (valIn !== undefined && valIn !== null) ? propOut : false; }
...
var letter = options.letter,
font = options.font || {},
defaultTitle = 'Click to enter ' +
(options.title || (letter.toUpperCase() + ' axis')) +
' title';
function coerce2(attr, dflt) {
return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt);
}
var visible = coerce('visible', !options.cheateronly);
var axType = containerOut.type;
if(axType === 'date') {
...
coerceFont = function (coerce, attr, dfltObj) { var out = {}; dfltObj = dfltObj || {}; out.family = coerce(attr + '.family', dfltObj.family); out.size = coerce(attr + '.size', dfltObj.size); out.color = coerce(attr + '.color', dfltObj.color); return out; }
...
}
plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt);
}
var globalFont = Lib.coerceFont(coerce, 'font');
coerce('title');
Lib.coerceFont(coerce, 'titlefont', {
family: globalFont.family,
size: Math.round(globalFont.size * 1.4),
color: globalFont.color
...
constrain = function (v, v0, v1) { if(v0 > v1) return Math.max(v1, Math.min(v0, v)); return Math.max(v0, Math.min(v1, v)); }
...
posPx.text = posPx.tail + textShift;
// constrain pixel/paper referenced so the draggers are at least
// partially visible
var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height'];
if(axRef === 'paper') {
posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1);
}
if(tailRef === 'pixel') {
var shiftPlus = -Math.max(posPx.tail - 3, posPx.text),
shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx;
if(shiftPlus > 0) {
posPx.tail += shiftPlus;
posPx.text += shiftPlus;
...
containsAny = function (s, fragments) { for(var i = 0; i < fragments.length; i++) { if(s.indexOf(fragments[i]) !== -1) return true; } return false; }
...
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;
}
// prepare the edits object we'll send to applyContainerArrayChanges
if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {};
var objEdits = arrayEdits[arrayStr][i];
if(!objEdits) objEdits = arrayEdits[arrayStr][i] = {};
...
dateTick0 = function (calendar, sunday) { if(isWorldCalendar(calendar)) { return sunday ? Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar]; } else { return sunday ? '2000-01-02' : '2000-01-01'; } }
...
// log with linear ticks: L# where # is the linear tick spacing
// log showing powers plus some intermediates:
// D1 shows all digits, D2 shows 2 and 5
axes.autoTicks = function(ax, roughDTick) {
var base;
if(ax.type === 'date') {
ax.tick0 = Lib.dateTick0(ax.calendar);
// the criteria below are all based on the rough spacing we calculate
// being > half of the final unit - so precalculate twice the rough val
var roughX2 = 2 * roughDTick;
if(roughX2 > ONEAVGYEAR) {
roughDTick /= ONEAVGYEAR;
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
...
dateTime2ms = function (s, calendar) { // first check if s is a date object if(exports.isJSDate(s)) { // Convert to the UTC milliseconds that give the same // hours as this date has in the local timezone s = Number(s) - s.getTimezoneOffset() * ONEMIN; if(s >= MIN_MS && s <= MAX_MS) return s; return BADNUM; } // otherwise only accept strings and numbers if(typeof s !== 'string' && typeof s !== 'number') return BADNUM; s = String(s); var isWorld = isWorldCalendar(calendar); // to handle out-of-range dates in international calendars, accept // 'G' as a prefix to force the built-in gregorian calendar. var s0 = s.charAt(0); if(isWorld && (s0 === 'G' || s0 === 'g')) { s = s.substr(1); calendar = ''; } var isChinese = isWorld && calendar.substr(0, 7) === 'chinese'; var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP); if(!match) return BADNUM; var y = match[1], m = match[3] || '1', d = Number(match[5] || 1), H = Number(match[7] || 0), M = Number(match[9] || 0), S = Number(match[11] || 0); if(isWorld) { // disallow 2-digit years for world calendars if(y.length === 2) return BADNUM; y = Number(y); var cDate; try { var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar); if(isChinese) { var isIntercalary = m.charAt(m.length - 1) === 'i'; m = parseInt(m, 10); cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d); } else { cDate = calInstance.newDate(y, Number(m), d); } } catch(e) { return BADNUM; } // Invalid ... date if(!cDate) return BADNUM; return ((cDate.toJD() - EPOCHJD) * ONEDAY) + (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC); } if(y.length === 2) { y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST; } else y = Number(y); // new Date uses months from 0; subtract 1 here just so we // don't have to do it again during the validity test below m -= 1; // javascript takes new Date(0..99,m,d) to mean 1900-1999, so // to support years 0-99 we need to use setFullYear explicitly // Note that 2000 is a leap year. var date = new Date(Date.UTC(2000, m, d, H, M)); date.setUTCFullYear(y); if(date.getUTCMonth() !== m) return BADNUM; if(date.getUTCDate() !== d) return BADNUM; return date.getTime() + S * ONESEC; }
...
if(date.getUTCMonth() !== m) return BADNUM;
if(date.getUTCDate() !== d) return BADNUM;
return date.getTime() + S * ONESEC;
};
MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
// is string s a date? (see above)
exports.isDateTime = function(s, calendar) {
return (exports.dateTime2ms(s, calendar) !== BADNUM);
};
...
dfltRange = function (calendar) { if(isWorldCalendar(calendar)) { return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar]; } else { return ['2000-01-01', '2001-01-01']; } }
...
*/
ax.cleanRange = function(rangeAttr) {
if(!rangeAttr) rangeAttr = 'range';
var range = Lib.nestedProperty(ax, rangeAttr).get(),
axLetter = (ax._id || 'x').charAt(0),
i, dflt;
if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
else if(axLetter === 'y') dflt = constants.DFLTRANGEY;
else dflt = constants.DFLTRANGEX;
// make sure we don't later mutate the defaults
dflt = dflt.slice();
if(!range || range.length !== 2) {
...
distinctVals = function (valsIn) { var vals = valsIn.slice(); // otherwise we sort the original array... vals.sort(exports.sorterAsc); var l = vals.length - 1, minDiff = (vals[l] - vals[0]) || 1, errDiff = minDiff / (l || 1) / 10000, v2 = [vals[0]]; for(var i = 0; i < l; i++) { // make sure values aren't just off by a rounding error if(vals[i + 1] > vals[i] + errDiff) { minDiff = Math.min(minDiff, vals[i + 1] - vals[i]); v2.push(vals[i + 1]); } } return {vals: v2, minDiff: minDiff}; }
...
var size0;
if(nbins) size0 = ((dataMax - dataMin) / nbins);
else {
// totally auto: scale off std deviation so the highest bin is
// somewhat taller than the total number of bins, but don't let
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
...
dot = function (x, y) { if(!(x.length && y.length) || x.length !== y.length) return null; var len = x.length, out, i; if(x[0].length) { // mat-vec or mat-mat out = new Array(len); for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y); } else if(y[0].length) { // vec-mat var yTranspose = exports.transposeRagged(y); out = new Array(yTranspose.length); for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]); } else { // vec-vec out = 0; for(i = 0; i < len; i++) out += x[i] * y[i]; } return out; }
...
var len = x.length,
out,
i;
if(x[0].length) {
// mat-vec or mat-mat
out = new Array(len);
for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
}
else if(y[0].length) {
// vec-mat
var yTranspose = exports.transposeRagged(y);
out = new Array(yTranspose.length);
for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
}
...
function ensureArray(out, n) { if(!Array.isArray(out)) out = []; // If too long, truncate. (If too short, it will grow // automatically so we don't care about that case) out.length = n; return out; }
...
return d3.range(_count).map(function(d, i) {
return arr[i] || arr[0];
});
};
µ.util.fillArrays = function(_obj, _valueNames, _count) {
_valueNames.forEach(function(d, i) {
_obj[d] = µ.util.ensureArray(_obj[d], _count);
});
return _obj;
};
µ.util.cloneJson = function(json) {
return JSON.parse(JSON.stringify(json));
};
...
error = function () { if(config.logging > 0) { var messages = ['ERROR:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.error, messages); } }
...
// so we define the default (plotly.js) behavior here
function defaultSetBackground(gd, bgColor) {
try {
gd._fullLayout._paper.style('background', bgColor);
}
catch(e) {
if(module.exports.logging > 0) {
console.error(e);
}
}
}
...
expandObjectPaths = function (data) { var match, key, prop, datum, idx, dest, trailingPath; if(typeof data === 'object' && !Array.isArray(data)) { for(key in data) { if(data.hasOwnProperty(key)) { if((match = key.match(dottedPropertyRegex))) { datum = data[key]; prop = match[1]; delete data[key]; data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[ prop]); } else if((match = key.match(indexedPropertyRegex))) { datum = data[key]; prop = match[1]; idx = parseInt(match[2]); delete data[key]; data[prop] = data[prop] || []; if(match[3] === '.') { // This is the case where theere are subsequent properties into which // we must recurse, e.g. transforms[0].value trailingPath = match[4]; dest = data[prop][idx] = data[prop][idx] || {}; // NB: Extend deep no arrays prevents this from working on multiple // nested properties in the same object, e.g. // // { // foo[0].bar[1].range // foo[0].bar[0].range // } // // In this case, the extendDeepNoArrays will overwrite one array with // the other, so that both properties *will not* be present in the // result. Fixing this would require a more intelligent tracking // of changes and merging than extendDeepNoArrays currently accomplishes. lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum))); } else { // This is the case where this property is the end of the line, // e.g. xaxis.range[0] data[prop][idx] = lib.expandObjectPaths(datum); } } else { data[key] = lib.expandObjectPaths(data[key]); } } } } return data; }
...
};
/**
* Iterate through an object in-place, converting dotted properties to objects.
*
* Examples:
*
* lib.expandObjectPaths({'nested.test.path': 'value'});
* => { nested: { test: {path: 'value'}}}
*
* It also handles array notation, e.g.:
*
* lib.expandObjectPaths({'foo[1].bar': 'value'});
* => { foo: [null, {bar: value}] }
*
...
extendDeep = function () { return _extend(arguments, true, false, false); }
...
for(var i = 0; i < args.length; i++) {
arg = args[i];
if(arg === gd) copy[i] = arg;
else if(typeof arg === 'object') {
copy[i] = Array.isArray(arg) ?
Lib.extendDeep([], arg) :
Lib.extendDeepAll({}, arg);
}
else copy[i] = arg;
}
return copy;
}
...
extendDeepAll = function () { return _extend(arguments, true, true, false); }
...
for(var i = 0; i < args.length; i++) {
arg = args[i];
if(arg === gd) copy[i] = arg;
else if(typeof arg === 'object') {
copy[i] = Array.isArray(arg) ?
Lib.extendDeep([], arg) :
Lib.extendDeepAll({}, arg);
}
else copy[i] = arg;
}
return copy;
}
...
extendDeepNoArrays = function () { return _extend(arguments, true, false, true); }
...
if(data.hasOwnProperty(key)) {
if((match = key.match(dottedPropertyRegex))) {
datum = data[key];
prop = match[1];
delete data[key];
data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths
(datum))[prop]);
} else if((match = key.match(indexedPropertyRegex))) {
datum = data[key];
prop = match[1];
idx = parseInt(match[2]);
delete data[key];
...
extendFlat = function () { return _extend(arguments, false, false, false); }
...
function opaqueSetBackground(gd, bgColor) {
gd._fullLayout._paperdiv.style('background', 'white');
Plotly.defaultConfig.setBackground(gd, bgColor);
}
function setPlotContext(gd, config) {
if(!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig);
var context = gd._context;
if(config) {
Object.keys(config).forEach(function(key) {
if(key in context) {
if(key === 'setBackground' && config[key] === 'opaque') {
context[key] = opaqueSetBackground;
...
function filterUnique(array) { var seen = {}, out = [], j = 0; for(var i = 0; i < array.length; i++) { var item = array[i]; if(seen[item] !== 1) { seen[item] = 1; out[j++] = item; } } return out; }
...
/**
* Return news array containing only the unique items
* found in input array.
*
* IMPORTANT: Note that items are considered unique
* if `String({})` is unique. For example;
*
* Lib.filterUnique([ { a: 1 }, { b: 2 } ])
*
* returns [{ a: 1 }]
*
* and
*
* Lib.filterUnique([ '1', 1 ])
*
...
function filterVisible(container) { var out = []; for(var i = 0; i < container.length; i++) { var item = container[i]; if(item.visible === true) out.push(item); } return out; }
...
var Axes = require('../../plots/cartesian/axes');
var draw = require('./draw').draw;
module.exports = function calcAutorange(gd) {
var fullLayout = gd._fullLayout,
annotationList = Lib.filterVisible(fullLayout.annotations);
if(!annotationList.length || !gd._fullData.length) return;
var annotationAxes = {};
annotationList.forEach(function(ann) {
annotationAxes[ann.xref] = true;
annotationAxes[ann.yref] = true;
...
findBin = function (val, bins, linelow) { if(isNumeric(bins.start)) { return linelow ? Math.ceil((val - bins.start) / bins.size) - 1 : Math.floor((val - bins.start) / bins.size); } else { var n1 = 0, n2 = bins.length, c = 0, n, test; if(bins[bins.length - 1] >= bins[0]) { test = linelow ? lessThan : lessOrEqual; } else { test = linelow ? greaterOrEqual : greaterThan; } // c is just to avoid infinite loops if there's an error while(n1 < n2 && c++ < 100) { n = Math.floor((n1 + n2) / 2); if(test(bins[n], val)) n1 = n + 1; else n2 = n; } if(c > 90) loggers.log('Long binary search...'); return n1 - 1; } }
...
var i1, i2, text;
if(hasColumnText) text = Lib.init2dArray(col2vals.length, col1vals.length);
for(i = 0; i < colLen; i++) {
if(col1[i] !== BADNUM && col2[i] !== BADNUM) {
i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals);
i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals);
for(j = 0; j < arrayVarNames.length; j++) {
arrayVarName = arrayVarNames[j];
arrayVar = trace[arrayVarName];
newArray = newArrays[j];
newArray[i2][i1] = arrayVar[i];
...
findExactDates = function (data, calendar) { var exactYears = 0, exactMonths = 0, exactDays = 0, blankCount = 0, d, di; var calInstance = ( isWorldCalendar(calendar) && Registry.getComponentMethod('calendars', 'getCal')(calendar) ); for(var i = 0; i < data.length; i++) { di = data[i]; // not date data at all if(!isNumeric(di)) { blankCount ++; continue; } // not an exact date if(di % ONEDAY) continue; if(calInstance) { try { d = calInstance.fromJD(di / ONEDAY + EPOCHJD); if(d.day() === 1) { if(d.month() === 1) exactYears++; else exactMonths++; } else exactDays++; } catch(e) { // invalid date in this calendar - ignore it here. } } else { d = new Date(di); if(d.getUTCDate() === 1) { if(d.getUTCMonth() === 0) exactYears++; else exactMonths++; } else exactDays++; } } exactMonths += exactYears; exactDays += exactMonths; var dataCount = data.length - blankCount; return { exactYears: exactYears / dataCount, exactMonths: exactMonths / dataCount, exactDays: exactDays / dataCount }; }
...
}
}
return binStart;
}
function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
var stats = Lib.findExactDates(data, calendar);
// number of data points that needs to be an exact value
// to shift that increment to (near) the bin center
var threshold = 0.8;
if(stats.exactDays > threshold) {
var numMonths = Number(dtick.substr(1));
...
formatDate = function (x, fmt, tr, calendar) { var headStr, dateStr; calendar = isWorldCalendar(calendar) && calendar; if(fmt) return modDateFormat(fmt, x, calendar); if(calendar) { try { var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) .fromJD(dateJD); if(tr === 'y') dateStr = yearFormatWorld(cDate); else if(tr === 'm') dateStr = monthFormatWorld(cDate); else if(tr === 'd') { headStr = yearFormatWorld(cDate); dateStr = dayFormatWorld(cDate); } else { headStr = yearMonthDayFormatWorld(cDate); dateStr = formatTime(x, tr); } } catch(e) { return 'Invalid'; } } else { var d = new Date(Math.floor(x + 0.05)); if(tr === 'y') dateStr = yearFormat(d); else if(tr === 'm') dateStr = monthFormat(d); else if(tr === 'd') { headStr = yearFormat(d); dateStr = dayFormat(d); } else { headStr = yearMonthDayFormat(d); dateStr = formatTime(x, tr); } } return dateStr + (headStr ? '\n' + headStr : ''); }
...
dateStr, h, m, s, msec10, d;
if(isWorldCalendar(calendar)) {
var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD,
timeMs = Math.floor(mod(ms, ONEDAY));
try {
dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar)
.fromJD(dateJD).formatDate('yyyy-mm-dd');
}
catch(e) {
// invalid date in this calendar - fall back to Gyyyy-mm-dd
dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
}
// yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
...
getPlotDiv = function (el) { for(; el && el.removeAttribute; el = el.parentNode) { if(lib.isPlotDiv(el)) return el; } }
...
_context.style('pointer-events', 'all');
}
if(_callback) _callback.call(that);
}
if(tex) {
var gd = Lib.getPlotDiv(that.node());
((gd && gd._promises) || []).push(new Promise(function(resolve) {
that.style({visibility: 'hidden'});
var config = {fontSize: parseInt(that.style('font-size'), 10)};
texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
parent.selectAll('svg.' + svgClass).remove();
parent.selectAll('g.' + svgClass + '-group').remove();
...
function identity(d) { return d; }
n/a
incrementMonth = function (ms, dMonth, calendar) { calendar = isWorldCalendar(calendar) && calendar; // pull time out and operate on pure dates, then add time back at the end // this gives maximum precision - not that we *normally* care if we're // incrementing by month, but better to be safe! var timeMs = mod(ms, ONEDAY); ms = Math.round(ms - timeMs); if(calendar) { try { var dateJD = Math.round(ms / ONEDAY) + EPOCHJD, calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar), cDate = calInstance.fromJD(dateJD); if(dMonth % 12) calInstance.add(cDate, dMonth, 'm'); else calInstance.add(cDate, dMonth / 12, 'y'); return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs; } catch(e) { logError('invalid ms ' + ms + ' in calendar ' + calendar); // then keep going in gregorian even though the result will be 'Invalid' } } var y = new Date(ms + THREEDAYS); return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS; }
...
if(isNumeric(dtick)) return x + axSign * dtick;
// everything else is a string, one character plus a number
var tType = dtick.charAt(0),
dtSigned = axSign * Number(dtick.substr(1));
// Dates: months (or years - see Lib.incrementMonth)
if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
// Log scales: Linear, Digits
else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
// log10 of 2,5,10, or all digits (logs just have to be
// close enough to round)
else if(tType === 'D') {
...
init2dArray = function (rowLength, colLength) { var array = new Array(rowLength); for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength); return array; }
...
var col1dv = Lib.distinctVals(col1),
col1vals = col1dv.vals,
col2dv = Lib.distinctVals(col2),
col2vals = col2dv.vals,
newArrays = [];
for(i = 0; i < arrayVarNames.length; i++) {
newArrays[i] = Lib.init2dArray(col2vals.length, col1vals.length);
}
var i1, i2, text;
if(hasColumnText) text = Lib.init2dArray(col2vals.length, col1vals.length);
for(i = 0; i < colLen; i++) {
...
interp = function (arr, n) { if(!isNumeric(n)) throw 'n should be a finite number'; n = n * arr.length - 0.5; if(n < 0) return arr[0]; if(n > arr.length - 1) return arr[arr.length - 1]; var frac = n % 1; return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)]; }
n/a
function isArray(a) { return Array.isArray(a) || ab.isView(a); }
...
*/
'use strict';
// similar to Lib.mergeArray, but using inside a loop
module.exports = function arrayToCalcItem(traceAttr, calcItem, calcAttr, i) {
if(Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i];
};
...
isD3Selection = function (obj) { return obj && (typeof obj.classed === 'function'); }
...
return hoverLabel.node();
};
fx.loneUnhover = function(containerOrSelection) {
// duck type whether the arg is a d3 selection because ie9 doesn't
// handle instanceof like modern browsers do.
var selection = Lib.isD3Selection(containerOrSelection) ?
containerOrSelection :
d3.select(containerOrSelection);
selection.selectAll('g.hovertext').remove();
selection.selectAll('.spikeline').remove();
};
...
isDateTime = function (s, calendar) { return (exports.dateTime2ms(s, calendar) !== BADNUM); }
...
// NOTE: if someone puts in a year as a number rather than a string,
// this will mistakenly convert it thinking it's milliseconds from 1970
// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
v = exports.ms2DateTimeLocal(+v);
if(!v && dflt !== undefined) return dflt;
}
else if(!exports.isDateTime(v, calendar)) {
logError('unrecognized date', v);
return dflt;
}
return v;
};
/*
...
isIE = function () { return typeof window.navigator.msSaveBlob !== 'undefined'; }
...
}
// see comments within svgtoimg for additional
// discussion of problems with IE
// can now draw to canvas, but CORS tainted canvas
// does not allow toDataURL
// svg format will work though
if(Lib.isIE() && opts.format !== 'svg') {
reject(new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.'));
}
gd._snapshotInProgress = true;
var promise = toImage(gd, opts);
var filename = opts.filename || gd.fn || 'newplot';
...
isJSDate = function (v) { return typeof v === 'object' && v !== null && typeof v.getTime === 'function'; }
...
* make now will cover all possibilities. mostly this will all be taken
* care of in initial parsing, should only be an issue for hand-entered data
* currently (2016) this range is:
* 1946-2045
*/
exports.dateTime2ms = function(s, calendar) {
// first check if s is a date object
if(exports.isJSDate(s)) {
// Convert to the UTC milliseconds that give the same
// hours as this date has in the local timezone
s = Number(s) - s.getTimezoneOffset() * ONEMIN;
if(s >= MIN_MS && s <= MAX_MS) return s;
return BADNUM;
}
// otherwise only accept strings and numbers
...
function isPlainObject(obj) { // We need to be a little less strict in the `imagetest` container because // of how async image requests are handled. // // N.B. isPlainObject(new Constructor()) will return true in `imagetest` if(window && window.process && window.process.versions) { return Object.prototype.toString.call(obj) === '[object Object]'; } return ( Object.prototype.toString.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === Object.prototype ); }
...
}
}
var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0;
for(i = 0; i < annotationsLen; i++) {
var ann = layout.annotations[i];
if(!Lib.isPlainObject(ann)) continue;
if(ann.ref) {
if(ann.ref === 'paper') {
ann.xref = 'paper';
ann.yref = 'paper';
}
else if(ann.ref === 'data') {
...
isPlotDiv = function (el) { var el3 = d3.select(el); return el3.node() instanceof HTMLElement && el3.size() && el3.classed('js-plotly-plot'); }
...
}
return false;
};
// get the parent Plotly plot of any element. Whoo jquery-free tree climbing!
lib.getPlotDiv = function(el) {
for(; el && el.removeAttribute; el = el.parentNode) {
if(lib.isPlotDiv(el)) return el;
}
};
lib.isPlotDiv = function(el) {
var el3 = d3.select(el);
return el3.node() instanceof HTMLElement &&
el3.size() &&
...
len = function (data) { return exports.aggNums(function(a) { return a + 1; }, 0, data); }
...
* even need to use aggNums instead of .length, to toss out non-numerics
*/
exports.len = function(data) {
return exports.aggNums(function(a) { return a + 1; }, 0, data);
};
exports.mean = function(data, len) {
if(!len) len = exports.len(data);
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};
exports.variance = function(data, len, mean) {
if(!len) len = exports.len(data);
if(!isNumeric(mean)) mean = exports.mean(data, len);
...
log = function () { if(config.logging > 1) { var messages = ['LOG:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.trace || console.log, messages); } }
...
* @param {string} thisType
* @param {array of strings} categoriesIn all the categories this type is in,
* tested by calls: traceIs(trace, oneCategory)
* @param {object} meta meta information about the trace type
*/
exports.register = function(_module, thisType, categoriesIn, meta) {
if(exports.modules[thisType]) {
Loggers.log('Type ' + thisType + ' already registered');
return;
}
var categoryObj = {};
for(var i = 0; i < categoriesIn.length; i++) {
categoryObj[categoriesIn[i]] = true;
exports.allCategories[categoriesIn[i]] = true;
...
mean = function (data, len) { if(!len) len = exports.len(data); return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len; }
...
exports.mean = function(data, len) {
if(!len) len = exports.len(data);
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};
exports.variance = function(data, len, mean) {
if(!len) len = exports.len(data);
if(!isNumeric(mean)) mean = exports.mean(data, len);
return exports.aggNums(function(a, b) {
return a + Math.pow(b - mean, 2);
}, 0, data) / len;
};
exports.stdev = function(data, len, mean) {
...
mergeArray = function (traceAttr, cd, cdAttr) { if(Array.isArray(traceAttr)) { var imax = Math.min(traceAttr.length, cd.length); for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i]; } }
n/a
minExtend = function (obj1, obj2) { var objOut = {}; if(typeof obj2 !== 'object') obj2 = {}; var arrayLen = 3, keys = Object.keys(obj1), i, k, v; for(i = 0; i < keys.length; i++) { k = keys[i]; v = obj1[k]; if(k.charAt(0) === '_' || typeof v === 'function') continue; else if(k === 'module') objOut[k] = v; else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen); else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]); else objOut[k] = v; } keys = Object.keys(obj2); for(i = 0; i < keys.length; i++) { k = keys[i]; v = obj2[k]; if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') { objOut[k] = v; } } return objOut; }
...
v;
for(i = 0; i < keys.length; i++) {
k = keys[i];
v = obj1[k];
if(k.charAt(0) === '_' || typeof v === 'function') continue;
else if(k === 'module') objOut[k] = v;
else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen);
else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);
else objOut[k] = v;
}
keys = Object.keys(obj2);
for(i = 0; i < keys.length; i++) {
k = keys[i];
v = obj2[k];
...
function mod(v, d) { var out = v % d; return out < 0 ? out + d : out; }
...
else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
// log10 of 2,5,10, or all digits (logs just have to be
// close enough to round)
else if(tType === 'D') {
var tickset = (dtick === 'D2') ? roundLog2 : roundLog1,
x2 = x + axSign * 0.01,
frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev);
return Math.floor(x2) +
Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
}
else throw 'unrecognized dtick ' + String(dtick);
};
...
ms2DateTime = function (ms, r, calendar) { if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM; if(!r) r = 0; var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), msRounded = Math.round(ms - msecTenths / 10), dateStr, h, m, s, msec10, d; if(isWorldCalendar(calendar)) { var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD, timeMs = Math.floor(mod(ms, ONEDAY)); try { dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) .fromJD(dateJD).formatDate('yyyy-mm-dd'); } catch(e) { // invalid date in this calendar - fall back to Gyyyy-mm-dd dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded)); } // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does // other things for a few calendars, so we can't trust it. Just pad // it manually (after the '-' if there is one) if(dateStr.charAt(0) === '-') { while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1); } else { while(dateStr.length < 10) dateStr = '0' + dateStr; } // TODO: if this is faster, we could use this block for extracting // the time components of regular gregorian too h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0; m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0; s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0; msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0; } else { d = new Date(msRounded); dateStr = utcFormat('%Y-%m-%d')(d); // <90 days: add hours and minutes - never *only* add hours h = (r < NINETYDAYS) ? d.getUTCHours() : 0; m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0; // <3 hours: add seconds s = (r < THREEHOURS) ? d.getUTCSeconds() : 0; // <5 minutes: add ms (plus one extra digit, this is msec*10) msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0; } return includeTime(dateStr, h, m, s, msec10); }
n/a
ms2DateTimeLocal = function (ms) { if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM; var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10), d = new Date(Math.round(ms - msecTenths / 10)), dateStr = d3.time.format('%Y-%m-%d')(d), h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(), msec10 = d.getUTCMilliseconds() * 10 + msecTenths; return includeTime(dateStr, h, m, s, msec10); }
...
logError('JS Dates and milliseconds are incompatible with world calendars', v);
return dflt;
}
// NOTE: if someone puts in a year as a number rather than a string,
// this will mistakenly convert it thinking it's milliseconds from 1970
// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
v = exports.ms2DateTimeLocal(+v);
if(!v && dflt !== undefined) return dflt;
}
else if(!exports.isDateTime(v, calendar)) {
logError('unrecognized date', v);
return dflt;
}
return v;
...
function nestedProperty(container, propStr) { if(isNumeric(propStr)) propStr = String(propStr); else if(typeof propStr !== 'string' || propStr.substr(propStr.length - 4) === '[-1]') { throw 'bad property string'; } var j = 0, propParts = propStr.split('.'), indexed, indices, i; // check for parts of the nesting hierarchy that are numbers (ie array elements) while(j < propParts.length) { // look for non-bracket chars, then any number of [##] blocks indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/); if(indexed) { if(indexed[1]) propParts[j] = indexed[1]; // allow propStr to start with bracketed array indices else if(j === 0) propParts.splice(0, 1); else throw 'bad property string'; indices = indexed[2] .substr(1, indexed[2].length - 2) .split(']['); for(i = 0; i < indices.length; i++) { j++; propParts.splice(j, 0, Number(indices[i])); } } j++; } if(typeof container !== 'object') { return badContainer(container, propStr, propParts); } return { set: npSet(container, propParts, propStr), get: npGet(container, propParts), astr: propStr, parts: propParts, obj: container }; }
...
* you can also swap other things than x/y by providing part1 and part2
*/
lib.swapAttrs = function(cont, attrList, part1, part2) {
if(!part1) part1 = 'x';
if(!part2) part2 = 'y';
for(var i = 0; i < attrList.length; i++) {
var attr = attrList[i],
xp = lib.nestedProperty(cont, attr.replace('?', part1)),
yp = lib.nestedProperty(cont, attr.replace('?', part2)),
temp = xp.get();
xp.set(yp.get());
yp.set(temp);
}
};
...
noneOrAll = function (containerIn, containerOut, attrList) {
/**
* some attributes come together, so if you have one of them
* in the input, you should copy the default values of the others
* to the input as well.
*/
if(!containerIn) return;
var hasAny = false,
hasAll = true,
i,
val;
for(i = 0; i < attrList.length; i++) {
val = containerIn[attrList[i]];
if(val !== undefined && val !== null) hasAny = true;
else hasAll = false;
}
if(hasAny && !hasAll) {
for(i = 0; i < attrList.length; i++) {
containerIn[attrList[i]] = containerOut[attrList[i]];
}
}
}
...
coerce(axLetter + 'anchor');
// xshift, yshift
coerce(axLetter + 'shift');
}
// if you have one coordinate you should have both
Lib.noneOrAll(annIn, annOut, ['x', 'y']);
if(showArrow) {
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
coerce('arrowhead');
coerce('arrowsize');
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
coerce('standoff');
...
function noop() {}
n/a
notifier = function (text, displayLength) { if(NOTEDATA.indexOf(text) !== -1) return; NOTEDATA.push(text); var ts = 1000; if(isNumeric(displayLength)) ts = displayLength; else if(displayLength === 'long') ts = 3000; var notifierContainer = d3.select('body') .selectAll('.plotly-notifier') .data([0]); notifierContainer.enter() .append('div') .classed('plotly-notifier', true); var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA); function killNote(transition) { transition .duration(700) .style('opacity', 0) .each('end', function(thisText) { var thisIndex = NOTEDATA.indexOf(thisText); if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1); d3.select(this).remove(); }); } notes.enter().append('div') .classed('notifier-note', true) .style('opacity', 0) .each(function(thisText) { var note = d3.select(this); note.append('button') .classed('notifier-close', true) .html('×') .on('click', function() { note.transition().call(killNote); }); var p = note.append('p'); var lines = thisText.split(/<br\s*\/?>/g); for(var i = 0; i < lines.length; i++) { if(i) p.append('br'); p.append('span').text(lines[i]); } note.transition() .duration(700) .style('opacity', 1) .transition() .delay(ts) .call(killNote); }); }
...
modeBarButtons.toImage = {
name: 'toImage',
title: 'Download plot as a png',
icon: Icons.camera,
click: function(gd) {
var format = 'png';
Lib.notifier('Taking snapshot - this may take a few seconds', 'long'
;);
if(Lib.isIE()) {
Lib.notifier('IE only supports svg. Changing format to svg.', 'long');
format = 'svg';
}
downloadImage(gd, {'format': format})
...
numSeparate = function (value, separators, separatethousands) { if(!separatethousands) separatethousands = false; if(typeof separators !== 'string' || separators.length === 0) { throw new Error('Separator string required for formatting!'); } if(typeof value === 'number') { value = String(value); } var thousandsRe = /(\d+)(\d{3})/, decimalSep = separators.charAt(0), thouSep = separators.charAt(1); var x = value.split('.'), x1 = x[0], x2 = x.length > 1 ? decimalSep + x[1] : ''; // Years are ignored for thousands separators if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { while(thousandsRe.test(x1)) { x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2'); } } return x1 + x2; }
...
return data;
};
/**
* Converts value to string separated by the provided separators.
*
* @example
* lib.numSeparate(2016, '.,');
* // returns '2016'
*
* @example
* lib.numSeparate(3000, '.,', true);
* // returns '3,000'
*
* @example
...
objectFromPath = function (path, value) { var keys = path.split('.'), tmpObj, obj = tmpObj = {}; for(var i = 0; i < keys.length; i++) { var key = keys[i]; var el = null; var parts = keys[i].match(/(.*)\[([0-9]+)\]/); if(parts) { key = parts[1]; el = parts[2]; tmpObj = tmpObj[key] = []; if(i === keys.length - 1) { tmpObj[el] = value; } else { tmpObj[el] = {}; } tmpObj = tmpObj[el]; } else { if(i === keys.length - 1) { tmpObj[key] = value; } else { tmpObj[key] = {}; } tmpObj = tmpObj[key]; } } return obj; }
...
/**
* Converts a string path to an object.
*
* When given a string containing an array element, it will create a `null`
* filled array of the given size.
*
* @example
* lib.objectFromPath('nested.test[2].path', 'value');
* // returns { nested: { test: [null, null, { path: 'value' }]}
*
* @param {string} path to nested value
* @param {*} any value to be set
*
* @return {Object} the constructed object with a full nested path
*/
...
pauseEvent = function (e) { if(e.stopPropagation) e.stopPropagation(); if(e.preventDefault) e.preventDefault(); e.cancelBubble = true; return false; }
...
// but _ ensures this setting won't leave their page
if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) {
return;
}
// If a transition is in progress, then disable any behavior:
if(gd._transitioningWithDuration) {
return Lib.pauseEvent(e);
}
var pc = gd.querySelector('.plotly');
recomputeAxisLists();
// if the plot has scrollbars (more than a tiny excess)
...
function pushUnique(array, item) { if(item instanceof RegExp) { var itemStr = item.toString(), i; for(i = 0; i < array.length; i++) { if(array[i] instanceof RegExp && array[i].toString() === itemStr) { return array; } } array.push(item); } else if(item && array.indexOf(item) === -1) array.push(item); return array; }
...
function pushModule(fullTrace) {
dataOut.push(fullTrace);
var _module = fullTrace._module;
if(!_module) return;
Lib.pushUnique(modules, _module);
Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule);
cnt++;
}
var carpetIndex = {};
var carpetDependents = [];
...
function randstr(existing, bits, base) {
/*
* Include number of bits, the base of the string you want
* and an optional array of existing strings to avoid.
*/
if(!base) base = 16;
if(bits === undefined) bits = 24;
if(bits <= 0) return '0';
var digits = Math.log(Math.pow(2, bits)) / Math.log(base),
res = '',
i,
b,
x;
for(i = 2; digits === Infinity; i *= 2) {
digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
}
var rem = digits - Math.floor(digits);
for(i = 0; i < Math.floor(digits); i++) {
x = Math.floor(Math.random() * base).toString(base);
res = x + res;
}
if(rem) {
b = Math.pow(base, rem);
x = Math.floor(Math.random() * b).toString(base);
res = x + res;
}
var parsed = parseInt(res, base);
if((existing && (existing.indexOf(res) > -1)) ||
(parsed !== Infinity && parsed >= Math.pow(2, bits))) {
return randstr(existing, bits, base);
}
else return res;
}
...
function cleanEscapesForTex(s) {
return s.replace(/(<|<|<)/g, '\\lt ')
.replace(/(>|>|>)/g, '\\gt ');
}
function texToSVG(_texString, _config, _callback) {
var randomID = 'math-output-' + Lib.randstr([], 64);
var tmpDiv = d3.select('body').append('div')
.attr({id: randomID})
.style({visibility: 'hidden', position: 'absolute'})
.style({'font-size': _config.fontSize + 'px'})
.text(cleanEscapesForTex(_texString));
MathJax.Hub.Queue(['Typeset', MathJax.Hub, tmpDiv.node()], function() {
...
function relinkPrivateKeys(toContainer, fromContainer) { var keys = Object.keys(fromContainer || {}); for(var i = 0; i < keys.length; i++) { var k = keys[i], fromVal = fromContainer[k], toVal = toContainer[k]; if(k.charAt(0) === '_' || typeof fromVal === 'function') { // if it already exists at this point, it's something // that we recreate each time around, so ignore it if(k in toContainer) continue; toContainer[k] = fromVal; } else if(isArray(fromVal) && isArray(toVal) && isPlainObject(fromVal[0])) { // recurse into arrays containers for(var j = 0; j < fromVal.length; j++) { if(isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) { relinkPrivateKeys(toVal[j], fromVal[j]); } } } else if(isPlainObject(fromVal) && isPlainObject(toVal)) { // recurse into objects, but only if they still exist relinkPrivateKeys(toVal, fromVal); if(!Object.keys(toVal).length) delete toContainer[k]; } } }
...
}
// in case this array gets its defaults rebuilt independent of the whole layout,
// relink the private keys just for this array.
if(Lib.isArray(previousContOut)) {
var len = Math.min(previousContOut.length, contOut.length);
for(i = 0; i < len; i++) {
Lib.relinkPrivateKeys(contOut[i], previousContOut[i]);
}
}
};
...
removeElement = function (el) { var elParent = el && el.parentNode; if(elParent) elParent.removeChild(el); }
...
this.element.removeChild(this.element.firstChild);
}
this.hasLogo = false;
};
proto.destroy = function() {
Lib.removeElement(this.container.querySelector('.modebar'));
};
function createModeBar(gd, buttons) {
var fullLayout = gd._fullLayout;
var modeBar = new ModeBar({
graphInfo: gd,
...
rotationMatrix = function (alpha) { var a = alpha * Math.PI / 180; return [[Math.cos(a), -Math.sin(a), 0], [Math.sin(a), Math.cos(a), 0], [0, 0, 1]]; }
...
[0, 0, 1]];
};
// rotate by alpha around (x,y)
exports.rotationXYMatrix = function(a, x, y) {
return exports.dot(
exports.dot(exports.translationMatrix(x, y),
exports.rotationMatrix(a)),
exports.translationMatrix(-x, -y));
};
// applies a 2D transformation matrix to either x and y params or an [x,y] array
exports.apply2DTransform = function(transform) {
return function() {
var args = arguments;
...
rotationXYMatrix = function (a, x, y) { return exports.dot( exports.dot(exports.translationMatrix(x, y), exports.rotationMatrix(a)), exports.translationMatrix(-x, -y)); }
...
tailX = annPosPx.x.tail + dx,
tailY = annPosPx.y.tail + dy,
textX = annPosPx.x.text + dx,
textY = annPosPx.y.text + dy,
// find the edge of the text box, where we'll start the arrow:
// create transform matrix to rotate the text box corners
transform = Lib.rotationXYMatrix(textangle, textX, textY),
applyTransform = Lib.apply2DTransform(transform),
applyTransform2 = Lib.apply2DTransform2(transform),
// calculate and transform bounding box
width = +annTextBG.attr('width'),
height = +annTextBG.attr('height'),
xLeft = textX - 0.5 * width,
...
roundUp = function (val, arrayIn, reverse) { var low = 0, high = arrayIn.length - 1, mid, c = 0, dlow = reverse ? 0 : 1, dhigh = reverse ? 1 : 0, rounded = reverse ? Math.ceil : Math.floor; // c is just to avoid infinite loops if there's an error while(low < high && c++ < 100) { mid = rounded((low + high) / 2); if(arrayIn[mid] <= val) low = mid + dlow; else high = mid - dhigh; } return arrayIn[low]; }
...
// totally auto: scale off std deviation so the highest bin is
// somewhat taller than the total number of bins, but don't let
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
// fallback if ax.d2c output BADNUMs
// e.g. when user try to plot categorical bins
// on a layout.xaxis.type: 'linear'
...
simpleMap = function (array, func, x1, x2) { var len = array.length, out = new Array(len); for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2); return out; }
...
}
var j, minpt, maxpt, minbest, maxbest, dp, dv,
mbest = 0,
axReverse = false;
if(ax.range) {
var rng = Lib.simpleMap(ax.range, ax.r2l);
axReverse = rng[1] < rng[0];
}
// one-time setting to easily reverse the axis
// when plotting from code
if(ax.autorange === 'reversed') {
axReverse = true;
...
smooth = function (arrayIn, FWHM) { FWHM = Math.round(FWHM) || 0; // only makes sense for integers if(FWHM < 2) return arrayIn; var alen = arrayIn.length, alen2 = 2 * alen, wlen = 2 * FWHM - 1, w = new Array(wlen), arrayOut = new Array(alen), i, j, k, v; // first make the window array for(i = 0; i < wlen; i++) { w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM); } // now do the convolution for(i = 0; i < alen; i++) { v = 0; for(j = 0; j < wlen; j++) { k = i + j + 1 - FWHM; // multibounce if(k < -alen) k -= alen2 * Math.round(k / alen2); else if(k >= alen2) k -= alen2 * Math.floor(k / alen2); // single bounce if(k < 0) k = - 1 - k; else if(k >= alen) k = alen2 - 1 - k; v += arrayIn[k] * w[j]; } arrayOut[i] = v; } return arrayOut; }
n/a
sorterAsc = function (a, b) { return a - b; }
n/a
sorterDes = function (a, b) { return b - a; }
n/a
stdev = function (data, len, mean) { return Math.sqrt(exports.variance(data, len, mean)); }
...
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
// fallback if ax.d2c output BADNUMs
// e.g. when user try to plot categorical bins
// on a layout.xaxis.type: 'linear'
if(!isNumeric(size0)) size0 = 1;
}
...
stripTrailingSlash = function (str) { if(str.substr(-1) === '/') return str.substr(0, str.length - 1); return str; }
n/a
swapAttrs = function (cont, attrList, part1, part2) { if(!part1) part1 = 'x'; if(!part2) part2 = 'y'; for(var i = 0; i < attrList.length; i++) { var attr = attrList[i], xp = lib.nestedProperty(cont, attr.replace('?', part1)), yp = lib.nestedProperty(cont, attr.replace('?', part2)), temp = xp.get(); xp.set(yp.get()); yp.set(temp); } }
...
(Object.keys(outer[innerStr]).length === 0);
}
// swap all the data and data attributes associated with x and y
exports.swapXYData = function(trace) {
var i;
Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins'
;, 'nbins?', 'autobin?', '?src', 'error_?']);
if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) {
if(trace.transpose) delete trace.transpose;
else trace.transpose = true;
}
if(trace.error_x && trace.error_y) {
var errorY = trace.error_y,
copyYstyle = ('copy_ystyle' in errorY) ? errorY.copy_ystyle :
...
syncOrAsync = function (sequence, arg, finalStep) { var ret, fni; function continueAsync() { return lib.syncOrAsync(sequence, arg, finalStep); } while(sequence.length) { fni = sequence.splice(0, 1)[0]; ret = fni(arg); if(ret && ret.then) { return ret.then(continueAsync) .then(undefined, lib.promiseError); } } return finalStep && finalStep(arg); }
...
* this doesn't happen yet because we want to make sure
* that it gets reported
*/
lib.syncOrAsync = function(sequence, arg, finalStep) {
var ret, fni;
function continueAsync() {
return lib.syncOrAsync(sequence, arg, finalStep);
}
while(sequence.length) {
fni = sequence.splice(0, 1)[0];
ret = fni(arg);
if(ret && ret.then) {
...
titleCase = function (s) { return s.charAt(0).toUpperCase() + s.substr(1); }
n/a
function toLogRange(val, range) { if(val > 0) return Math.log(val) / Math.LN10; // move a negative value reference to a log axis - just put the // result at the lowest range value on the plot (or if the range also went negative, // one millionth of the top of the range) var newVal = Math.log(Math.min(range[0], range[1])) / Math.LN10; if(!isNumeric(newVal)) newVal = Math.log(Math.max(range[0], range[1])) / Math.LN10 - 6; return newVal; }
n/a
translationMatrix = function (x, y) { return [[1, 0, x], [0, 1, y], [0, 0, 1]]; }
...
[Math.sin(a), Math.cos(a), 0],
[0, 0, 1]];
};
// rotate by alpha around (x,y)
exports.rotationXYMatrix = function(a, x, y) {
return exports.dot(
exports.dot(exports.translationMatrix(x, y),
exports.rotationMatrix(a)),
exports.translationMatrix(-x, -y));
};
// applies a 2D transformation matrix to either x and y params or an [x,y] array
exports.apply2DTransform = function(transform) {
return function() {
...
transposeRagged = function (z) { var maxlen = 0, zlen = z.length, i, j; // Maximum row length: for(i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length); var t = new Array(maxlen); for(i = 0; i < maxlen; i++) { t[i] = new Array(zlen); for(j = 0; j < zlen; j++) t[i][j] = z[j][i]; } return t; }
...
if(x[0].length) {
// mat-vec or mat-mat
out = new Array(len);
for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
}
else if(y[0].length) {
// vec-mat
var yTranspose = exports.transposeRagged(y);
out = new Array(yTranspose.length);
for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
}
else {
// vec-vec
out = 0;
for(i = 0; i < len; i++) out += x[i] * y[i];
...
validate = function (value, opts) { var valObject = exports.valObjects[opts.valType]; if(opts.arrayOk && Array.isArray(value)) return true; if(valObject.validateFunction) { return valObject.validateFunction(value, opts); } var failed = {}, out = failed, propMock = { set: function(v) { out = v; } }; // 'failed' just something mutable that won't be === anything else valObject.coerceFunction(value, propMock, failed, opts); return out !== failed; }
...
var items = opts.items;
// when free length is off, input and declared lengths must match
if(!opts.freeLength && v.length !== items.length) return false;
// valid when all input items are valid
for(var i = 0; i < v.length; i++) {
var isItemValid = exports.validate(v[i], opts.items[i]);
if(!isItemValid) return false;
}
return true;
}
}
...
variance = function (data, len, mean) { if(!len) len = exports.len(data); if(!isNumeric(mean)) mean = exports.mean(data, len); return exports.aggNums(function(a, b) { return a + Math.pow(b - mean, 2); }, 0, data) / len; }
...
return exports.aggNums(function(a, b) {
return a + Math.pow(b - mean, 2);
}, 0, data) / len;
};
exports.stdev = function(data, len, mean) {
return Math.sqrt(exports.variance(data, len, mean));
};
/**
* interp() computes a percentile (quantile) for a given distribution.
* We interpolate the distribution (to compute quantiles, we follow method #10 here:
* http://www.amstat.org/publications/jse/v14n3/langford.html).
* Typically the index or rank (n * arr.length) may be non-integer.
...
warn = function () { if(config.logging > 0) { var messages = ['WARN:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.trace || console.log, messages); } }
...
* @param {object||string} trace
* trace object with prop 'type' or trace type as a string
* @return {object}
* module object corresponding to trace type
*/
exports.getModule = function(trace) {
if(trace.r !== undefined) {
Loggers.warn('Tried to put a polar trace ' +
'on an incompatible graph of cartesian ' +
'data. Ignoring this dataset.', trace
);
return false;
}
var _module = exports.modules[getTraceType(trace)];
...
error = function () { if(config.logging > 0) { var messages = ['ERROR:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.error, messages); } }
...
// so we define the default (plotly.js) behavior here
function defaultSetBackground(gd, bgColor) {
try {
gd._fullLayout._paper.style('background', bgColor);
}
catch(e) {
if(module.exports.logging > 0) {
console.error(e);
}
}
}
...
log = function () { if(config.logging > 1) { var messages = ['LOG:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.trace || console.log, messages); } }
...
* @param {string} thisType
* @param {array of strings} categoriesIn all the categories this type is in,
* tested by calls: traceIs(trace, oneCategory)
* @param {object} meta meta information about the trace type
*/
exports.register = function(_module, thisType, categoriesIn, meta) {
if(exports.modules[thisType]) {
Loggers.log('Type ' + thisType + ' already registered');
return;
}
var categoryObj = {};
for(var i = 0; i < categoriesIn.length; i++) {
categoryObj[categoriesIn[i]] = true;
exports.allCategories[categoriesIn[i]] = true;
...
warn = function () { if(config.logging > 0) { var messages = ['WARN:']; for(var i = 0; i < arguments.length; i++) { messages.push(arguments[i]); } apply(console.trace || console.log, messages); } }
...
* @param {object||string} trace
* trace object with prop 'type' or trace type as a string
* @return {object}
* module object corresponding to trace type
*/
exports.getModule = function(trace) {
if(trace.r !== undefined) {
Loggers.warn('Tried to put a polar trace ' +
'on an incompatible graph of cartesian ' +
'data. Ignoring this dataset.', trace
);
return false;
}
var _module = exports.modules[getTraceType(trace)];
...
function applyContainerArrayChanges(gd, np, edits, flags) { var componentType = np.astr, supplyComponentDefaults = Registry.getComponentMethod(componentType, 'supplyLayoutDefaults'), draw = Registry.getComponentMethod(componentType, 'draw'), drawOne = Registry.getComponentMethod(componentType, 'drawOne'), replotLater = flags.replot || flags.recalc || (supplyComponentDefaults === noop) || (draw === noop), layout = gd.layout, 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(), componentArrayIn = np.get(), 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 componentArrayFull = nestedProperty(fullLayout, componentType).get(); var deletes = [], firstIndexChange = -1, maxIndex = componentArray.length, i, j, componentNum, objEdits, objKeys, objVal, adding; // 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++) { nestedProperty(componentArray[componentNum], objKeys[j]).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) { ...
...
p.set(vi);
}
}
// now we've collected component edits - execute them all together
for(arrayStr in arrayEdits) {
var finished = manageArrays.applyContainerArrayChanges(gd,
Lib.nestedProperty(layout, arrayStr), arrayEdits[arrayStr], flags);
if(!finished) flags.doplot = true;
}
// figure out if we need to recalculate axis constraints
var constraints = fullLayout._axisConstraintGroups;
for(var axId in rangesAltered) {
...
function containerArrayMatch(astr) { var rootContainers = Registry.layoutArrayContainers, regexpContainers = Registry.layoutArrayRegexes, rootPart = astr.split('[')[0], arrayStr, match; // look for regexp matches first, because they may be nested inside root matches // eg updatemenus[i].buttons is nested inside updatemenus for(var i = 0; i < regexpContainers.length; i++) { match = astr.match(regexpContainers[i]); if(match && match.index === 0) { arrayStr = match[0]; break; } } // now look for root matches if(!arrayStr) arrayStr = rootContainers[rootContainers.indexOf(rootPart)]; if(!arrayStr) return false; var tail = astr.substr(arrayStr.length); if(!tail) return {array: arrayStr, index: '', property: ''}; match = tail.match(/^\[(0|[1-9][0-9]*)\](\.(.+))?$/); if(!match) return false; return {array: arrayStr, index: Number(match[1]), property: match[3] || ''}; }
...
// alter gd.layout
// collect array component edits for execution all together
// so we can ensure consistent behavior adding/removing items
// and order-independence for add/remove/edit all together in
// one relayout call
var containerArrayMatch = manageArrays.containerArrayMatch(ai);
if(containerArrayMatch) {
arrayStr = containerArrayMatch.array;
i = containerArrayMatch.index;
var propStr = containerArrayMatch.property,
componentArray = Lib.nestedProperty(layout, arrayStr),
obji = (componentArray || [])[i] || {};
...
function isAddVal(val) { return val === 'add' || isPlainObject(val); }
...
// replacing the entire array: too much going on, force recalc
if(ai.indexOf('updatemenus') === -1) flags.docalc = true;
}
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;
if(manageArrays.isAddVal(vi)) {
undoit[ai] = null;
}
else if(manageArrays.isRemoveVal(vi)) {
undoit[ai] = obji;
toggledObj = obji;
}
else Lib.warn('unrecognized full object value', aobj);
...
function isRemoveVal(val) { return val === null || val === 'remove'; }
...
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;
if(manageArrays.isAddVal(vi)) {
undoit[ai] = null;
}
else if(manageArrays.isRemoveVal(vi)) {
undoit[ai] = obji;
toggledObj = obji;
}
else Lib.warn('unrecognized full object value', aobj);
if(refAutorange(toggledObj, 'x') || refAutorange(toggledObj, 'y') &&
ai.indexOf('updatemenus') === -1) {
...
apply2DTransform = function (transform) { return function() { var args = arguments; if(args.length === 3) { args = args[0]; }// from map var xy = arguments.length === 1 ? args[0] : [args[0], args[1]]; return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2); }; }
...
var xy = arguments.length === 1 ? args[0] : [args[0], args[1]];
return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
};
};
// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment)
exports.apply2DTransform2 = function(transform) {
var at = exports.apply2DTransform(transform);
return function(xys) {
return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
};
};
...
apply2DTransform2 = function (transform) { var at = exports.apply2DTransform(transform); return function(xys) { return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4))); }; }
...
textX = annPosPx.x.text + dx,
textY = annPosPx.y.text + dy,
// find the edge of the text box, where we'll start the arrow:
// create transform matrix to rotate the text box corners
transform = Lib.rotationXYMatrix(textangle, textX, textY),
applyTransform = Lib.apply2DTransform(transform),
applyTransform2 = Lib.apply2DTransform2(transform),
// calculate and transform bounding box
width = +annTextBG.attr('width'),
height = +annTextBG.attr('height'),
xLeft = textX - 0.5 * width,
xRight = xLeft + width,
yTop = textY - 0.5 * height,
...
dot = function (x, y) { if(!(x.length && y.length) || x.length !== y.length) return null; var len = x.length, out, i; if(x[0].length) { // mat-vec or mat-mat out = new Array(len); for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y); } else if(y[0].length) { // vec-mat var yTranspose = exports.transposeRagged(y); out = new Array(yTranspose.length); for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]); } else { // vec-vec out = 0; for(i = 0; i < len; i++) out += x[i] * y[i]; } return out; }
...
var len = x.length,
out,
i;
if(x[0].length) {
// mat-vec or mat-mat
out = new Array(len);
for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
}
else if(y[0].length) {
// vec-mat
var yTranspose = exports.transposeRagged(y);
out = new Array(yTranspose.length);
for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
}
...
init2dArray = function (rowLength, colLength) { var array = new Array(rowLength); for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength); return array; }
...
var col1dv = Lib.distinctVals(col1),
col1vals = col1dv.vals,
col2dv = Lib.distinctVals(col2),
col2vals = col2dv.vals,
newArrays = [];
for(i = 0; i < arrayVarNames.length; i++) {
newArrays[i] = Lib.init2dArray(col2vals.length, col1vals.length);
}
var i1, i2, text;
if(hasColumnText) text = Lib.init2dArray(col2vals.length, col1vals.length);
for(i = 0; i < colLen; i++) {
...
rotationMatrix = function (alpha) { var a = alpha * Math.PI / 180; return [[Math.cos(a), -Math.sin(a), 0], [Math.sin(a), Math.cos(a), 0], [0, 0, 1]]; }
...
[0, 0, 1]];
};
// rotate by alpha around (x,y)
exports.rotationXYMatrix = function(a, x, y) {
return exports.dot(
exports.dot(exports.translationMatrix(x, y),
exports.rotationMatrix(a)),
exports.translationMatrix(-x, -y));
};
// applies a 2D transformation matrix to either x and y params or an [x,y] array
exports.apply2DTransform = function(transform) {
return function() {
var args = arguments;
...
rotationXYMatrix = function (a, x, y) { return exports.dot( exports.dot(exports.translationMatrix(x, y), exports.rotationMatrix(a)), exports.translationMatrix(-x, -y)); }
...
tailX = annPosPx.x.tail + dx,
tailY = annPosPx.y.tail + dy,
textX = annPosPx.x.text + dx,
textY = annPosPx.y.text + dy,
// find the edge of the text box, where we'll start the arrow:
// create transform matrix to rotate the text box corners
transform = Lib.rotationXYMatrix(textangle, textX, textY),
applyTransform = Lib.apply2DTransform(transform),
applyTransform2 = Lib.apply2DTransform2(transform),
// calculate and transform bounding box
width = +annTextBG.attr('width'),
height = +annTextBG.attr('height'),
xLeft = textX - 0.5 * width,
...
translationMatrix = function (x, y) { return [[1, 0, x], [0, 1, y], [0, 0, 1]]; }
...
[Math.sin(a), Math.cos(a), 0],
[0, 0, 1]];
};
// rotate by alpha around (x,y)
exports.rotationXYMatrix = function(a, x, y) {
return exports.dot(
exports.dot(exports.translationMatrix(x, y),
exports.rotationMatrix(a)),
exports.translationMatrix(-x, -y));
};
// applies a 2D transformation matrix to either x and y params or an [x,y] array
exports.apply2DTransform = function(transform) {
return function() {
...
transposeRagged = function (z) { var maxlen = 0, zlen = z.length, i, j; // Maximum row length: for(i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length); var t = new Array(maxlen); for(i = 0; i < maxlen; i++) { t[i] = new Array(zlen); for(j = 0; j < zlen; j++) t[i][j] = z[j][i]; } return t; }
...
if(x[0].length) {
// mat-vec or mat-mat
out = new Array(len);
for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
}
else if(y[0].length) {
// vec-mat
var yTranspose = exports.transposeRagged(y);
out = new Array(yTranspose.length);
for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
}
else {
// vec-vec
out = 0;
for(i = 0; i < len; i++) out += x[i] * y[i];
...
function module() { return µ.PolyChart(); }
n/a
function module() { var config = { data: [], layout: {} }, inputConfig = {}, liveConfig = {}; var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale; var exports = {}; function render(_container) { container = _container || container; var data = config.data; var axisConfig = config.layout; if (typeof container == 'string' || container.nodeName) container = d3.select(container); container.datum(data).each(function(_data, _index) { var dataOriginal = _data.slice(); liveConfig = { data: µ.util.cloneJson(dataOriginal), layout: µ.util.cloneJson(axisConfig) }; var colorIndex = 0; dataOriginal.forEach(function(d, i) { if (!d.color) { d.color = axisConfig.defaultColorRange[colorIndex]; colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length; } if (!d.strokeColor) { d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString(); } liveConfig.data[i].color = d.color; liveConfig.data[i].strokeColor = d.strokeColor; liveConfig.data[i].strokeDash = d.strokeDash; liveConfig.data[i].strokeSize = d.strokeSize; }); var data = dataOriginal.filter(function(d, i) { var visible = d.visible; return typeof visible === 'undefined' || visible === true; }); var isStacked = false; var dataWithGroupId = data.map(function(d, i) { isStacked = isStacked || typeof d.groupId !== 'undefined'; return d; }); if (isStacked) { var grouped = d3.nest().key(function(d, i) { return typeof d.groupId != 'undefined' ? d.groupId : 'unstacked'; }).entries(dataWithGroupId); var dataYStack = []; var stacked = grouped.map(function(d, i) { if (d.key === 'unstacked') return d.values; else { var prevArray = d.values[0].r.map(function(d, i) { return 0; }); d.values.forEach(function(d, i, a) { d.yStack = [ prevArray ]; dataYStack.push(prevArray); prevArray = µ.util.sumArrays(d.r, prevArray); }); return d.values; } }); data = d3.merge(stacked); } data.forEach(function(d, i) { d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ]; d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ]; }); var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig .margin.top - axisConfig.margin.bottom) / 2; radius = Math.max(10, radius); var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ]; var extent; if (isStacked) { var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack))); extent = [ 0, highestStackedValue ]; } else extent = d3.extent(µ.util.flattenArray(data.map(function(d, i) { return d.r; }))); if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0; radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]); liveConfig.layout.radialAxis.domain = radialScale.domain(); var angularDataMerged = µ.util.flattenArray(data.map(function(d, i) { ret ...
...
if(_container) container = _container;
d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove();
config = (!config) ?
_inputConfig :
extendDeepAll(config, _inputConfig);
if(!plot) plot = micropolar.Axis();
convertedInput = micropolar.adapter.plotly().convert(config);
plot.config(convertedInput).render(container);
_gd.data = config.data;
_gd.layout = config.layout;
manager.fillLayout(_gd);
return config;
}
...
function module() { return µ.PolyChart(); }
n/a
function module() { return µ.PolyChart(); }
n/a
function module() { var config = µ.Legend.defaultConfig(); var dispatch = d3.dispatch('hover'); function exports() { var legendConfig = config.legendConfig; var flattenData = config.data.map(function(d, i) { return [].concat(d).map(function(dB, iB) { var element = extendDeepAll({}, legendConfig.elements[i]); element.name = dB; element.color = [].concat(legendConfig.elements[i].color)[iB]; return element; }); }); var data = d3.merge(flattenData); data = data.filter(function(d, i) { return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined'); }); if (legendConfig.reverseOrder) data = data.reverse(); var container = legendConfig.container; if (typeof container == 'string' || container.nodeName) container = d3.select(container); var colors = data.map(function(d, i) { return d.color; }); var lineHeight = legendConfig.fontSize; var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous; var height = isContinuous ? legendConfig.height : lineHeight * data.length; var legendContainerGroup = container.classed('legend-group', true); var svg = legendContainerGroup.selectAll('svg').data([ 0 ]); var svgEnter = svg.enter().append('svg').attr({ width: 300, height: height + lineHeight, xmlns: 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', version: '1.1' }); svgEnter.append('g').classed('legend-axis', true); svgEnter.append('g').classed('legend-marks', true); var dataNumbered = d3.range(data.length); var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors); var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints ']([ 0, height ]); var shapeGenerator = function(_type, _size) { var squareSize = _size * 3; if (_type === 'line') { return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z'; } else if (d3.svg.symbolTypes.indexOf(_type) != -1) return d3.svg.symbol().type(_type).size(squareSize)(); else return d3.svg.symbol().type('square').size(squareSize)(); }; if (isContinuous) { var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({ id: 'grad1', x1: '0%', y1: '0%', x2: '0%', y2: '100%' }).selectAll('stop').data(colors); gradient.enter().append('stop'); gradient.attr({ offset: function(d, i) { return i / (colors.length - 1) * 100 + '%'; } }).style({ 'stop-color': function(d, i) { return d; } }); svg.append('rect').classed('legend-mark', true).attr({ height: legendConfig.height, width: legendConfig.colorBandWidth, fill: 'url(#grad1)' }); } else { var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data); legendElement.enter().append('path').classed('legend-mark', true); legendElement.attr({ transform: function(d, i) { return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')'; }, d: function(d, i) { var symbolType = d.symbol; return shapeGenerator(symbolType, lineHeight); }, ...
...
var datumClone = µ.util.cloneJson(d);
datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot'
; ? 'square' : 'line';
datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend;
datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color;
return datumClone;
});
µ.Legend().config({
data: data.map(function(d, i) {
return d.name || 'Element' + i;
}),
legendConfig: extendDeepAll({},
µ.Legend.defaultConfig().legendConfig,
{
container: legendContainer,
...
function module() { return µ.PolyChart(); }
n/a
function module() { var config = [ µ.PolyChart.defaultConfig() ]; var dispatch = d3.dispatch('hover'); var dashArray = { solid: 'none', dash: [ 5, 2 ], dot: [ 2, 5 ] }; var colorScale; function exports() { var geometryConfig = config[0].geometryConfig; var container = geometryConfig.container; if (typeof container == 'string') container = d3.select(container); container.datum(config).each(function(_config, _index) { var isStack = !!_config[0].data.yStack; var data = _config.map(function(d, i) { if (isStack) return d3.zip(d.data.t[0], d.data.r[0], d.data.yStack[0]); else return d3.zip(d.data.t[0], d.data.r [0]); }); var angularScale = geometryConfig.angularScale; var domainMin = geometryConfig.radialScale.domain()[0]; var generator = {}; generator.bar = function(d, i, pI) { var dataConfig = _config[pI].data; var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0); var stackTop = geometryConfig.radialScale(d[2] || 0); var w = dataConfig.barWidth; d3.select(this).attr({ 'class': 'mark bar', d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join ('L') + 'Z', transform: function(d, i) { return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')'; } }); }; generator.dot = function(d, i, pI) { var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d; var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i); d3.select(this).attr({ 'class': 'mark dot', d: symbol, transform: function(d, i) { var coord = convertToCartesian(getPolarCoordinates(stackedData)); return 'translate(' + [ coord.x, coord.y ] + ')'; } }); }; var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function(d) { return geometryConfig.radialScale(d[1]); }).angle(function(d) { return geometryConfig.angularScale(d[0]) * Math.PI / 180; }); generator.line = function(d, i, pI) { var lineData = d[2] ? data[pI].map(function(d, i) { return [ d[0], d[1] + d[2] ]; }) : data[pI]; d3.select(this).each(generator['dot']).style({ opacity: function(dB, iB) { return +_config[pI].data.dotVisible; }, fill: markStyle.stroke(d, i, pI) }).attr({ 'class': 'mark dot' }); if (i > 0) return; var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]); lineSelection.enter().insert('path'); lineSelection.attr({ 'class': 'line', d: line(lineData), transform: function(dB, iB) { return 'rotate(' + (geometryConfig.orientation + 90) + ')'; }, 'pointer-events': 'none' }).style({ fill: function(dB, iB) { return markStyle.fill(d, i, pI); }, 'fill-opacity': 0, stroke: function(dB, iB) { return markStyle.stroke(d, i, pI); }, 'stroke-width': function(dB, iB) { return markStyle['stroke-width'](d, i, pI); }, 'stroke-dasharray': ...
...
colorScale: d3.scale.category20()
}
};
return config;
};
µ.BarChart = function module() {
return µ.PolyChart();
};
µ.BarChart.defaultConfig = function() {
var config = {
geometryConfig: {
geometryType: 'bar'
}
...
tooltipPanel = function () { var tooltipEl, tooltipTextEl, backgroundEl; var config = { container: null, hasTick: false, fontSize: 12, color: 'white', padding: 5 }; var id = 'tooltip-' + µ.tooltipPanel.uid++; var tickSize = 10; var exports = function() { tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]); var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({ 'pointer-events': 'none', display: 'none' }); backgroundEl = tooltipEnter.append('path').style({ fill: 'white', 'fill-opacity': .9 }).attr({ d: 'M0 0' }); tooltipTextEl = tooltipEnter.append('text').attr({ dx: config.padding + tickSize, dy: +config.fontSize * .3 }); return exports; }; exports.text = function(_text) { var l = d3.hsl(config.color).l; var strokeColor = l >= .5 ? '#aaa' : 'white'; var fillColor = l >= .5 ? 'black' : 'white'; var text = _text || ''; tooltipTextEl.style({ fill: fillColor, 'font-size': config.fontSize + 'px' }).text(text); var padding = config.padding; var bbox = tooltipTextEl.node().getBBox(); var boxStyle = { fill: config.color, stroke: strokeColor, 'stroke-width': '2px' }; var backGroundW = bbox.width + padding * 2 + tickSize; var backGroundH = bbox.height + padding * 2; backgroundEl.attr({ d: 'M' + [ [ tickSize, -backGroundH / 2 ], [ tickSize, -backGroundH / 4 ], [ config.hasTick ? 0 : tickSize, 0 ], [ tickSize , backGroundH / 4 ], [ tickSize, backGroundH / 2 ], [ backGroundW, backGroundH / 2 ], [ backGroundW, -backGroundH / 2 ] ].join(' L') + 'Z' }).style(boxStyle); tooltipEl.attr({ transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')' }); tooltipEl.style({ display: 'block' }); return exports; }; exports.move = function(_pos) { if (!tooltipEl) return; tooltipEl.attr({ transform: 'translate(' + [ _pos[0], _pos[1] ] + ')' }).style({ display: 'block' }); return exports; }; exports.hide = function() { if (!tooltipEl) return; tooltipEl.style({ display: 'none' }); return exports; }; exports.show = function() { if (!tooltipEl) return; tooltipEl.style({ display: 'block' }); return exports; }; exports.config = function(_x) { extendDeepAll(config, _x); return exports; }; return exports; }
...
return extendDeepAll(µ[geometry].defaultConfig(), dB);
});
µ[geometry]().config(finalGeometryConfig)();
});
}
var guides = svg.select('.guides-group');
var tooltipContainer = svg.select('.tooltips-group');
var angularTooltip = µ.tooltipPanel().config({
container: tooltipContainer,
fontSize: 8
})();
var radialTooltip = µ.tooltipPanel().config({
container: tooltipContainer,
fontSize: 8
})();
...
fillLayout = function (_gd) { var container = d3.select(_gd).selectAll('.plot-container'), paperDiv = container.selectAll('.svg-container'), paper = _gd.framework && _gd.framework.svg && _gd.framework.svg(), dflts = { width: 800, height: 600, paper_bgcolor: Color.background, _container: container, _paperdiv: paperDiv, _paper: paper }; _gd._fullLayout = extendDeepAll(dflts, _gd.layout); }
...
// empty it everytime for now
paperDiv.html('');
// fulfill gd requirements
if(data) gd.data = data;
if(layout) gd.layout = layout;
Polar.manager.fillLayout(gd);
// resize canvas
paperDiv.style({
width: gd._fullLayout.width + 'px',
height: gd._fullLayout.height + 'px'
});
...
framework = function (_gd) { var config, previousConfigClone, plot, convertedInput, container; var undoManager = new UndoManager(); function exports(_inputConfig, _container) { if(_container) container = _container; d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove(); config = (!config) ? _inputConfig : extendDeepAll(config, _inputConfig); if(!plot) plot = micropolar.Axis(); convertedInput = micropolar.adapter.plotly().convert(config); plot.config(convertedInput).render(container); _gd.data = config.data; _gd.layout = config.layout; manager.fillLayout(_gd); return config; } exports.isPolar = true; exports.svg = function() { return plot.svg(); }; exports.getConfig = function() { return config; }; exports.getLiveConfig = function() { return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true); }; exports.getLiveScales = function() { return {t: plot.angularScale(), r: plot.radialScale()}; }; exports.setUndoPoint = function() { var that = this; var configClone = micropolar.util.cloneJson(config); (function(_configClone, _previousConfigClone) { undoManager.add({ undo: function() { if(_previousConfigClone) that(_previousConfigClone); }, redo: function() { that(_configClone); } }); })(configClone, previousConfigClone); previousConfigClone = micropolar.util.cloneJson(configClone); }; exports.undo = function() { undoManager.undo(); }; exports.redo = function() { undoManager.redo(); }; return exports; }
...
// resize canvas
paperDiv.style({
width: gd._fullLayout.width + 'px',
height: gd._fullLayout.height + 'px'
});
// instantiate framework
gd.framework = Polar.manager.framework(gd);
// plot
gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node());
// set undo point
gd.framework.setUndoPoint();
...
density = function (size, total, inc, yinc) { var nMax = size.length; yinc = yinc || 1; for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc; }
n/a
percent = function (size, total) { var nMax = size.length, norm = 100 / total; for(var n = 0; n < nMax; n++) size[n] *= norm; }
n/a
probability = function (size, total) { var nMax = size.length; for(var n = 0; n < nMax; n++) size[n] /= total; }
n/a
function calc(gd, trace) {
var vals = trace.values,
labels = trace.labels,
cd = [],
fullLayout = gd._fullLayout,
colorMap = fullLayout._piecolormap,
allThisTraceLabels = {},
needDefaults = false,
vTotal = 0,
hiddenLabels = fullLayout.hiddenlabels || [],
i,
v,
label,
color,
hidden,
pt;
if(trace.dlabel) {
labels = new Array(vals.length);
for(i = 0; i < vals.length; i++) {
labels[i] = String(trace.label0 + i * trace.dlabel);
}
}
for(i = 0; i < vals.length; i++) {
v = vals[i];
if(!isNumeric(v)) continue;
v = +v;
if(v < 0) continue;
label = labels[i];
if(label === undefined || label === '') label = i;
label = String(label);
// only take the first occurrence of any given label.
// TODO: perhaps (optionally?) sum values for a repeated label?
if(allThisTraceLabels[label] === undefined) allThisTraceLabels[label] = true;
else continue;
color = tinycolor(trace.marker.colors[i]);
if(color.isValid()) {
color = Color.addOpacity(color, color.getAlpha());
if(!colorMap[label]) {
colorMap[label] = color;
}
}
// have we seen this label and assigned a color to it in a previous trace?
else if(colorMap[label]) color = colorMap[label];
// color needs a default - mark it false, come back after sorting
else {
color = false;
needDefaults = true;
}
hidden = hiddenLabels.indexOf(label) !== -1;
if(!hidden) vTotal += v;
cd.push({
v: v,
label: label,
color: color,
i: i,
hidden: hidden
});
}
if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; });
/**
* now go back and fill in colors we're still missing
* this is done after sorting, so we pick defaults
* in the order slices will be displayed
*/
if(needDefaults) {
for(i = 0; i < cd.length; i++) {
pt = cd[i];
if(pt.color === false) {
colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount);
fullLayout._piedefaultcolorcount++;
}
}
}
// include the sum of all values in the first point
if(cd[0]) cd[0].vTotal = vTotal;
// now insert text
if(trace.textinfo && trace.textinfo !== 'none') {
var hasLabel = trace.textinfo.indexOf('label') !== -1,
hasText = trace.textinfo.indexOf('text') !== -1,
hasValue = trace.textinfo.indexOf('value') !== -1,
hasPercent = trace.textinfo.indexOf('percent') !== -1,
separators = fullLayout.separators,
thisText;
for(i = 0; i < cd.length; i++) {
pt = cd[i];
thisText = hasLabel ? [pt.label] : [];
if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
pt.text = thisText.join('<br>');
}
}
return cd;
}
...
for(var j = 0; j < modules.length; j++) {
_module = modules[j];
if(_module.setPositions) _module.setPositions(gd, subplotInfo);
}
}
// calc and autorange for errorbars
ErrorBars.calc(gd);
// TODO: autosize extra for text markers and images
// see https://github.com/plotly/plotly.js/issues/1111
return Lib.syncOrAsync([
Registry.getComponentMethod('shapes', 'calcAutorange'),
Registry.getComponentMethod('annotations', 'calcAutorange'),
doAutoRangeAndConstraints,
...
function plot(gd, cdpie) { var fullLayout = gd._fullLayout; scalePies(cdpie, fullLayout._size); var pieGroups = fullLayout._pielayer.selectAll('g.trace').data(cdpie); pieGroups.enter().append('g') .attr({ 'stroke-linejoin': 'round', // TODO: miter might look better but can sometimes cause problems // maybe miter with a small-ish stroke-miterlimit? 'class': 'trace' }); pieGroups.exit().remove(); pieGroups.order(); pieGroups.each(function(cd) { var pieGroup = d3.select(this), cd0 = cd[0], trace = cd0.trace, tiltRads = 0, // trace.tilt * Math.PI / 180, depthLength = (trace.depth||0) * cd0.r * Math.sin(tiltRads) / 2, tiltAxis = trace.tiltaxis || 0, tiltAxisRads = tiltAxis * Math.PI / 180, depthVector = [ depthLength * Math.sin(tiltAxisRads), depthLength * Math.cos(tiltAxisRads) ], rSmall = cd0.r * Math.cos(tiltRads); var pieParts = pieGroup.selectAll('g.part') .data(trace.tilt ? ['top', 'sides'] : ['top']); pieParts.enter().append('g').attr('class', function(d) { return d + ' part'; }); pieParts.exit().remove(); pieParts.order(); setCoords(cd); pieGroup.selectAll('.top').each(function() { var slices = d3.select(this).selectAll('g.slice').data(cd); slices.enter().append('g') .classed('slice', true); slices.exit().remove(); var quadrants = [ [[], []], // y<0: x<0, x>=0 [[], []] // y>=0: x<0, x>=0 ], hasOutsideText = false; slices.each(function(pt) { if(pt.hidden) { d3.select(this).selectAll('path,g').remove(); return; } quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt); var cx = cd0.cx + depthVector[0], cy = cd0.cy + depthVector[1], sliceTop = d3.select(this), slicePath = sliceTop.selectAll('path.surface').data([pt]), hasHoverData = false; function handleMouseOver(evt) { evt.originalEvent = d3.event; // in case fullLayout or fullData has changed without a replot var fullLayout2 = gd._fullLayout, trace2 = gd._fullData[trace.index], hoverinfo = trace2.hoverinfo; if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; // in case we dragged over the pie from another subplot, // or if hover is turned off if(gd._dragging || fullLayout2.hovermode === false || hoverinfo === 'none' || hoverinfo === 'skip' || !hoverinfo) { Fx.hover(gd, evt, 'pie'); return; } var rInscribed = getInscribedRadiusFraction(pt, cd0), hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed), hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed), separators = fullLayout.separators, thisText = []; if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); if(hoverinfo.indexOf('text') !== -1) { if(trace2.hovertext) { thisText.push( Array.isArray(trace2.hovertext) ? trace2.hovertext[pt.i] : trace2.hovertext ); } else if(trace2.text && trace2.text[pt.i]) { thisText.push(trace2.text[pt.i ...
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
function style(gd) { gd._fullLayout._pielayer.selectAll('.trace').each(function(cd) { var cd0 = cd[0], trace = cd0.trace, traceSelection = d3.select(this); traceSelection.style({opacity: trace.opacity}); traceSelection.selectAll('.top path.surface').each(function(pt) { d3.select(this).call(styleOne, pt, trace); }); }); }
...
.classed('plotly-notifier', true);
var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
function killNote(transition) {
transition
.duration(700)
.style('opacity', 0)
.each('end', function(thisText) {
var thisIndex = NOTEDATA.indexOf(thisText);
if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
d3.select(this).remove();
});
}
...
function styleOne(s, pt, trace) { var lineColor = trace.marker.line.color; if(Array.isArray(lineColor)) lineColor = lineColor[pt.i] || Color.defaultLine; var lineWidth = trace.marker.line.width || 0; if(Array.isArray(lineWidth)) lineWidth = lineWidth[pt.i] || 0; s.style({'stroke-width': lineWidth}) .call(Color.fill, pt.color) .call(Color.stroke, lineColor); }
n/a
function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } var coerceFont = Lib.coerceFont; var vals = coerce('values'); if(!Array.isArray(vals) || !vals.length) { traceOut.visible = false; return; } var labels = coerce('labels'); if(!Array.isArray(labels)) { coerce('label0'); coerce('dlabel'); } var lineWidth = coerce('marker.line.width'); if(lineWidth) coerce('marker.line.color'); var colors = coerce('marker.colors'); if(!Array.isArray(colors)) traceOut.marker.colors = []; // later this will get padded with default colors coerce('scalegroup'); // TODO: tilt, depth, and hole all need to be coerced to the same values within a scaleegroup // (ideally actually, depth would get set the same *after* scaling, ie the same absolute depth) // and if colors aren't specified we should match these up - potentially even if separate pies // are NOT in the same sharegroup var textData = coerce('text'); var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hoverinfo', (layout._dataLength === 1) ? 'label+text+value+percent' : undefined); if(textInfo && textInfo !== 'none') { var textPosition = coerce('textposition'), hasBoth = Array.isArray(textPosition) || textPosition === 'auto', hasInside = hasBoth || textPosition === 'inside', hasOutside = hasBoth || textPosition === 'outside'; if(hasInside || hasOutside) { var dfltFont = coerceFont(coerce, 'textfont', layout.font); if(hasInside) coerceFont(coerce, 'insidetextfont', dfltFont); if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont); } } coerce('domain.x'); coerce('domain.y'); // 3D attributes commented out until I finish them in a later PR // var tilt = coerce('tilt'); // if(tilt) { // coerce('tiltaxis'); // coerce('depth'); // coerce('shading'); // } coerce('hole'); coerce('sort'); coerce('direction'); coerce('rotation'); coerce('pull'); }
...
gd._replotPending = true;
return Promise.reject();
} else {
// we're going ahead with a replot now
gd._replotPending = false;
}
Plots.supplyDefaults(gd);
var fullLayout = gd._fullLayout;
// Polar plots
if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
// so we don't try to re-call Plotly.plot from inside
...
function supplyLayoutDefaults(layoutIn, layoutOut) { function coerce(attr, dflt) { return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); } coerce('hiddenlabels'); }
...
}
plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
var i, _module;
// can't be be part of basePlotModules loop
// in order to handle the orphan axes case
Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
// base plot module layout defaults
var basePlotModules = layoutOut._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
_module = basePlotModules[i];
// done above already
...
function defaultSetBackground(gd, bgColor) { try { gd._fullLayout._paper.style('background', bgColor); } catch(e) { if(module.exports.logging > 0) { console.error(e); } } }
...
return gd;
});
};
function opaqueSetBackground(gd, bgColor) {
gd._fullLayout._paperdiv.style('background', 'white');
Plotly.defaultConfig.setBackground(gd, bgColor);
}
function setPlotContext(gd, config) {
if(!gd._context) gd._context = Lib.extendFlat({}, Plotly.defaultConfig);
var context = gd._context;
if(config) {
...
addFrames = function (gd, frameList, indices) { gd = helpers.getGraphDiv(gd); var numericNameWarningCount = 0; if(frameList === null || frameList === undefined) { return Promise.resolve(); } if(!Lib.isPlotDiv(gd)) { throw new Error( 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + 'to create a plot before adding frames. For more details, see ' + 'https://plot.ly/javascript/animations/' ); } var i, frame, j, idx; var _frames = gd._transitionData._frames; var _hash = gd._transitionData._frameHash; if(!Array.isArray(frameList)) { throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList); } // Create a sorted list of insertions since we run into lots of problems if these // aren't in ascending order of index: // // Strictly for sorting. Make sure this is guaranteed to never collide with any // already-exisisting indices: var bigIndex = _frames.length + frameList.length * 2; var insertions = []; for(i = frameList.length - 1; i >= 0; i--) { if(!Lib.isPlainObject(frameList[i])) continue; var name = (_hash[frameList[i].name] || {}).name; var newName = frameList[i].name; if(name && newName && typeof newName === 'number' && _hash[name]) { numericNameWarningCount++; Lib.warn('addFrames: overwriting frame "' + _hash[name].name + '" with a frame whose name of type "number" also equates to "' + name + '". This is valid but may potentially lead to unexpected ' + 'behavior since all plotly.js frame names are stored internally ' + 'as strings.'); if(numericNameWarningCount > 5) { Lib.warn('addFrames: This API call has yielded too many warnings. ' + 'For the rest of this call, further warnings about numeric frame ' + 'names will be suppressed.'); } } insertions.push({ frame: Plots.supplyFrameDefaults(frameList[i]), index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i }); } // Sort this, taking note that undefined insertions end up at the end: insertions.sort(function(a, b) { if(a.index > b.index) return -1; if(a.index < b.index) return 1; return 0; }); var ops = []; var revops = []; var frameCount = _frames.length; for(i = insertions.length - 1; i >= 0; i--) { frame = insertions[i].frame; if(typeof frame.name === 'number') { Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' + 'implicitly cast to strings'); } if(!frame.name) { // Repeatedly assign a default name, incrementing the counter each time until // we get a name that's not in the hashed lookup table: while(_hash[(frame.name = 'frame ' + gd._transitionData._counter++)]); } if(_hash[frame.name]) { // If frame is present, overwrite its definition: for(j = 0; j < _frames.length; j++) { if((_frames[j] || {}).name === frame.name) break; } ops.push({type: 'replace', index: j, value: frame}); revops.unshift({type: 'replace', index: j, value: _frames[j]}); } else { // Otherwise insert it at the end of the list: idx = Math.max(0, Math.min(insertions[i].index, frameCount)); ops.push({type: 'insert', index: idx, value: frame}); revops.unshift({type: 'delete', index: idx}); frameCount++; } } var undoFunc = Plots.modifyFrames, redoFunc = Plots.modifyFrames, undoArgs = [gd, revops], redoArgs = [gd, ops]; if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return Plots.modifyFrames(gd ...
...
if(!data && !layout && !Lib.isPlotDiv(gd)) {
Lib.warn('Calling Plotly.plot as if redrawing ' +
'but this container doesn\'t yet have a plot.', gd);
}
function addFrames() {
if(frames) {
return Plotly.addFrames(gd, frames);
}
}
// transfer configuration options to gd until we move over to
// a more OO like model
setPlotContext(gd, config);
...
function addTraces(gd, traces, newIndices) { gd = helpers.getGraphDiv(gd); var currentIndices = [], undoFunc = Plotly.deleteTraces, redoFunc = addTraces, undoArgs = [gd, currentIndices], redoArgs = [gd, traces], // no newIndices here i, promise; // all validation is done elsewhere to remove clutter here checkAddTracesArgs(gd, traces, newIndices); // make sure traces is an array if(!Array.isArray(traces)) { traces = [traces]; } // make sure traces do not repeat existing ones traces = traces.map(function(trace) { return Lib.extendFlat({}, trace); }); helpers.cleanData(traces, gd.data); // add the traces to gd.data (no redrawing yet!) for(i = 0; i < traces.length; i++) { gd.data.push(traces[i]); } // to continue, we need to call moveTraces which requires currentIndices for(i = 0; i < traces.length; i++) { currentIndices.push(-traces.length + i); } // if the user didn't define newIndices, they just want the traces appended // i.e., we can simply redraw and be done if(typeof newIndices === 'undefined') { promise = Plotly.redraw(gd); Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; } // make sure indices is property defined if(!Array.isArray(newIndices)) { newIndices = [newIndices]; } try { // this is redundant, but necessary to not catch later possible errors! checkMoveTracesArgs(gd, currentIndices, newIndices); } catch(error) { // something went wrong, reset gd to be safe and rethrow error gd.data.splice(gd.data.length - traces.length, traces.length); throw error; } // if we're here, the user has defined specific places to place the new traces // this requires some extra work that moveTraces will do Queue.startSequence(gd); Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); promise = Plotly.moveTraces(gd, currentIndices, newIndices); Queue.stopSequence(gd); return promise; }
n/a
animate = function (gd, frameOrGroupNameOrFrameList, animationOpts) { gd = helpers.getGraphDiv(gd); if(!Lib.isPlotDiv(gd)) { throw new Error( 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' + 'to create a plot before animating it. For more details, see ' + 'https://plot.ly/javascript/animations/' ); } var trans = gd._transitionData; // This is the queue of frames that will be animated as soon as possible. They // are popped immediately upon the *start* of a transition: if(!trans._frameQueue) { trans._frameQueue = []; } animationOpts = Plots.supplyAnimationDefaults(animationOpts); var transitionOpts = animationOpts.transition; var frameOpts = animationOpts.frame; // Since frames are popped immediately, an empty queue only means all frames have // *started* to transition, not that the animation is complete. To solve that, // track a separate counter that increments at the same time as frames are added // to the queue, but decrements only when the transition is complete. if(trans._frameWaitingCnt === undefined) { trans._frameWaitingCnt = 0; } function getTransitionOpts(i) { if(Array.isArray(transitionOpts)) { if(i >= transitionOpts.length) { return transitionOpts[0]; } else { return transitionOpts[i]; } } else { return transitionOpts; } } function getFrameOpts(i) { if(Array.isArray(frameOpts)) { if(i >= frameOpts.length) { return frameOpts[0]; } else { return frameOpts[i]; } } else { return frameOpts; } } // Execute a callback after the wrapper function has been called n times. // This is used to defer the resolution until a transition has resovled *and* // the frame has completed. If it's not done this way, then we get a race // condition in which the animation might resolve before a transition is complete // or vice versa. function callbackOnNthTime(cb, n) { var cnt = 0; return function() { if(cb && ++cnt === n) { return cb(); } }; } return new Promise(function(resolve, reject) { function discardExistingFrames() { if(trans._frameQueue.length === 0) { return; } while(trans._frameQueue.length) { var next = trans._frameQueue.pop(); if(next.onInterrupt) { next.onInterrupt(); } } gd.emit('plotly_animationinterrupted', []); } function queueFrames(frameList) { if(frameList.length === 0) return; for(var i = 0; i < frameList.length; i++) { var computedFrame; if(frameList[i].type === 'byname') { // If it's a named frame, compute it: computedFrame = Plots.computeFrame(gd, frameList[i].name); } else { // Otherwise we must have been given a simple object, so treat // the input itself as the computed frame. computedFrame = frameList[i].data; } var frameOpts = getFrameOpts(i); var transitionOpts = getTransitionOpts(i); // It doesn't make much sense for the transition duration to be greater than // the frame duration, so limit it: transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration); var nextFrame = { frame: computedFrame, name: frameList[i].name, frameOpts: frameOpts, transitionOpts: transitionOpts, }; if(i === frameList.length - 1) { // The last fr ...
n/a
deleteFrames = function (gd, frameList) { gd = helpers.getGraphDiv(gd); if(!Lib.isPlotDiv(gd)) { throw new Error('This element is not a Plotly plot: ' + gd); } var i, idx; var _frames = gd._transitionData._frames; var ops = []; var revops = []; if(!frameList) { frameList = []; for(i = 0; i < _frames.length; i++) { frameList.push(i); } } frameList = frameList.slice(0); frameList.sort(); for(i = frameList.length - 1; i >= 0; i--) { idx = frameList[i]; ops.push({type: 'delete', index: idx}); revops.unshift({type: 'insert', index: idx, value: _frames[idx]}); } var undoFunc = Plots.modifyFrames, redoFunc = Plots.modifyFrames, undoArgs = [gd, revops], redoArgs = [gd, ops]; if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return Plots.modifyFrames(gd, ops); }
n/a
function deleteTraces(gd, indices) { gd = helpers.getGraphDiv(gd); var traces = [], undoFunc = Plotly.addTraces, redoFunc = deleteTraces, undoArgs = [gd, traces, indices], redoArgs = [gd, indices], i, deletedTrace; // make sure indices are defined if(typeof indices === 'undefined') { throw new Error('indices must be an integer or array of integers.'); } else if(!Array.isArray(indices)) { indices = [indices]; } assertIndexArray(gd, indices, 'indices'); // convert negative indices to positive indices indices = positivifyIndices(indices, gd.data.length - 1); // we want descending here so that splicing later doesn't affect indexing indices.sort(Lib.sorterDes); for(i = 0; i < indices.length; i += 1) { deletedTrace = gd.data.splice(indices[i], 1)[0]; traces.push(deletedTrace); } var promise = Plotly.redraw(gd); Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; }
n/a
function extendTraces(gd, update, indices, maxPoints) {
gd = helpers.getGraphDiv(gd);
var undo = spliceTraces(gd, update, indices, maxPoints,
/*
* The Lengthen operation extends trace from end with insert
*/
function(target, insert) {
return target.concat(insert);
},
/*
* Window the trace keeping maxPoints, counting back from the end
*/
function(target, maxPoints) {
return target.splice(0, target.length - maxPoints);
});
var promise = Plotly.redraw(gd);
var undoArgs = [gd, undo.update, indices, undo.maxPoints];
Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments);
return promise;
}
n/a
function moveTraces(gd, currentIndices, newIndices) { gd = helpers.getGraphDiv(gd); var newData = [], movingTraceMap = [], undoFunc = moveTraces, redoFunc = moveTraces, undoArgs = [gd, newIndices, currentIndices], redoArgs = [gd, currentIndices, newIndices], i; // to reduce complexity here, check args elsewhere // this throws errors where appropriate checkMoveTracesArgs(gd, currentIndices, newIndices); // make sure currentIndices is an array currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices]; // if undefined, define newIndices to point to the end of gd.data array if(typeof newIndices === 'undefined') { newIndices = []; for(i = 0; i < currentIndices.length; i++) { newIndices.push(-currentIndices.length + i); } } // make sure newIndices is an array if it's user-defined newIndices = Array.isArray(newIndices) ? newIndices : [newIndices]; // convert negative indices to positive indices (they're the same length) currentIndices = positivifyIndices(currentIndices, gd.data.length - 1); newIndices = positivifyIndices(newIndices, gd.data.length - 1); // at this point, we've coerced the index arrays into predictable forms // get the traces that aren't being moved around for(i = 0; i < gd.data.length; i++) { // if index isn't in currentIndices, include it in ignored! if(currentIndices.indexOf(i) === -1) { newData.push(gd.data[i]); } } // get a mapping of indices to moving traces for(i = 0; i < currentIndices.length; i++) { movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]}); } // reorder this mapping by newIndex, ascending movingTraceMap.sort(function(a, b) { return a.newIndex - b.newIndex; }); // now, add the moving traces back in, in order! for(i = 0; i < movingTraceMap.length; i += 1) { newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace); } gd.data = newData; var promise = Plotly.redraw(gd); Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; }
...
throw error;
}
// if we're here, the user has defined specific places to place the new traces
// this requires some extra work that moveTraces will do
Queue.startSequence(gd);
Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
promise = Plotly.moveTraces(gd, currentIndices, newIndices);
Queue.stopSequence(gd);
return promise;
};
/**
* Delete traces at `indices` from gd.data array.
*
...
newPlot = function (gd, data, layout, config) { gd = helpers.getGraphDiv(gd); // remove gl contexts Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {}); Plots.purge(gd); return Plotly.plot(gd, data, layout, config); }
n/a
plot = function (gd, data, layout, config) {
var frames;
gd = helpers.getGraphDiv(gd);
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
if(Lib.isPlainObject(data)) {
var obj = data;
data = obj.data;
layout = obj.layout;
config = obj.config;
frames = obj.frames;
}
var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]);
if(okToPlot === false) return Promise.reject();
// if there's no data or layout, and this isn't yet a plotly plot
// container, log a warning to help plotly.js users debug
if(!data && !layout && !Lib.isPlotDiv(gd)) {
Lib.warn('Calling Plotly.plot as if redrawing ' +
'but this container doesn\'t yet have a plot.', gd);
}
function addFrames() {
if(frames) {
return Plotly.addFrames(gd, frames);
}
}
// transfer configuration options to gd until we move over to
// a more OO like model
setPlotContext(gd, config);
if(!layout) layout = {};
// hook class for plots main container (in case of plotly.js
// this won't be #embedded-graph or .js-tab-contents)
d3.select(gd).classed('js-plotly-plot', true);
// off-screen getBoundingClientRect testing space,
// in #js-plotly-tester (and stored as gd._tester)
// so we can share cached text across tabs
Drawing.makeTester(gd);
// collect promises for any async actions during plotting
// any part of the plotting code can push to gd._promises, then
// before we move to the next step, we check that they're all
// complete, and empty out the promise list again.
gd._promises = [];
var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data));
// if there is already data on the graph, append the new data
// if you only want to redraw, pass a non-array for data
if(Array.isArray(data)) {
helpers.cleanData(data, gd.data);
if(graphWasEmpty) gd.data = data;
else gd.data.push.apply(gd.data, data);
// for routines outside graph_obj that want a clean tab
// (rather than appending to an existing one) gd.empty
// is used to determine whether to make a new tab
gd.empty = false;
}
if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout);
// if the user is trying to drag the axes, allow new data and layout
// to come in but don't allow a replot.
if(gd._dragging && !gd._transitioning) {
// signal to drag handler that after everything else is done
// we need to replot, because something has changed
gd._replotPending = true;
return Promise.reject();
} else {
// we're going ahead with a replot now
gd._replotPending = false;
}
Plots.supplyDefaults(gd);
var fullLayout = gd._fullLayout;
// Polar plots
if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
// so we don't try to re-call Plotly.plot from inside
// legend and colorbar, if margins changed
fullLayout._replotting = true;
// make or remake the framework if we need to
if(graphWasEmpty) makePlotFramework(gd);
// polar need a different framework
if(gd.framework !== makePlotFramework) {
gd.framework = makePlotFramework;
makePlotFramework(gd);
}
// save initial show spikes once per graph
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);
// prepare the data and find the autorange
// generate calcdata, if we need to
// to force redoing calcdata, just delete it before calling Plotly.plot
var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
if(recalc) Plots.doCalcdata(gd);
// in case it has changed, attach fullData traces to calcdata
for(var i = 0; i < gd.calcdata.length; i++) {
gd.calcdata[i][0].trace = gd._fullData[i];
}
/*
* start async-friendly code - now we're actually drawing things
*/
var oldmargins = ...
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
function prependTraces(gd, update, indices, maxPoints) {
gd = helpers.getGraphDiv(gd);
var undo = spliceTraces(gd, update, indices, maxPoints,
/*
* The Lengthen operation extends trace by appending insert to start
*/
function(target, insert) {
return insert.concat(target);
},
/*
* Window the trace keeping maxPoints, counting forward from the start
*/
function(target, maxPoints) {
return target.splice(maxPoints, target.length);
});
var promise = Plotly.redraw(gd);
var undoArgs = [gd, undo.update, indices, undo.maxPoints];
Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments);
return promise;
}
n/a
function purge(gd) { gd = helpers.getGraphDiv(gd); var fullLayout = gd._fullLayout || {}, fullData = gd._fullData || []; // remove gl contexts Plots.cleanPlot([], {}, fullData, fullLayout); // purge properties Plots.purge(gd); // purge event emitter methods Events.purge(gd); // remove plot container if(fullLayout._container) fullLayout._container.remove(); delete gd._context; delete gd._replotPending; delete gd._mouseDownTime; delete gd._legendMouseDownTime; delete gd._hmpixcount; delete gd._hmlumcount; return gd; }
...
*/
Plotly.newPlot = function(gd, data, layout, config) {
gd = helpers.getGraphDiv(gd);
// remove gl contexts
Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
Plots.purge(gd);
return Plotly.plot(gd, data, layout, config);
};
/**
* Wrap negative indicies to their positive counterparts.
*
* @param {Number[]} indices An array of indices
...
redraw = function (gd) { gd = helpers.getGraphDiv(gd); if(!Lib.isPlotDiv(gd)) { throw new Error('This element is not a Plotly plot: ' + gd); } helpers.cleanData(gd.data, gd.data); helpers.cleanLayout(gd.layout); gd.calcdata = undefined; return Plotly.plot(gd).then(function() { gd.emit('plotly_redraw'); return gd; }); }
...
/*
* Window the trace keeping maxPoints, counting back from the end
*/
function(target, maxPoints) {
return target.splice(0, target.length - maxPoints);
});
var promise = Plotly.redraw(gd);
var undoArgs = [gd, undo.update, indices, undo.maxPoints];
Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments);
return promise;
};
...
function relayout(gd, astr, val) { gd = helpers.getGraphDiv(gd); helpers.clearPromiseQueue(gd); if(gd.framework && gd.framework.isPolar) { return Promise.resolve(gd); } var aobj = {}; if(typeof astr === 'string') { aobj[astr] = val; } else if(Lib.isPlainObject(astr)) { aobj = Lib.extendFlat({}, astr); } else { Lib.warn('Relayout fail.', astr, val); return Promise.reject(); } if(Object.keys(aobj).length) gd.changed = true; var specs = _relayout(gd, aobj), flags = specs.flags; // clear calcdata if required if(flags.docalc) gd.calcdata = undefined; // fill in redraw sequence // even if we don't have anything left in aobj, // something may have happened within relayout that we // need to wait for var seq = [Plots.previousPromises]; if(flags.layoutReplot) { seq.push(subroutines.layoutReplot); } 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); } seq.push(Plots.rehover); Queue.add(gd, relayout, [gd, specs.undoit], relayout, [gd, specs.redoit] ); var plotDone = Lib.syncOrAsync(seq, gd); if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); return plotDone.then(function() { gd.emit('plotly_relayout', specs.eventData); return gd; }); }
...
// autosizing doesn't count as a change that needs saving
var oldchanged = gd.changed;
// nor should it be included in the undo queue
gd.autoplay = true;
Plotly.relayout(gd, { autosize: true }).then(function() {
gd.changed = oldchanged;
resolve(gd);
});
}, 100);
});
};
...
function restyle(gd, astr, val, traces) { gd = helpers.getGraphDiv(gd); helpers.clearPromiseQueue(gd); var aobj = {}; if(typeof astr === 'string') aobj[astr] = val; else if(Lib.isPlainObject(astr)) { // the 3-arg form aobj = Lib.extendFlat({}, astr); if(traces === undefined) traces = val; } else { Lib.warn('Restyle fail.', astr, val, traces); return Promise.reject(); } if(Object.keys(aobj).length) gd.changed = true; var specs = _restyle(gd, aobj, traces), flags = specs.flags; // clear calcdata if required if(flags.clearCalc) gd.calcdata = undefined; // fill in redraw sequence var seq = []; if(flags.fullReplot) { seq.push(Plotly.plot); } else { seq.push(Plots.previousPromises); Plots.supplyDefaults(gd); if(flags.dostyle) seq.push(subroutines.doTraceStyle); if(flags.docolorbars) seq.push(subroutines.doColorBars); } seq.push(Plots.rehover); Queue.add(gd, restyle, [gd, specs.undoit, specs.traces], restyle, [gd, specs.redoit, specs.traces] ); var plotDone = Lib.syncOrAsync(seq, gd); if(!plotDone || !plotDone.then) plotDone = Promise.resolve(); return plotDone.then(function() { gd.emit('plotly_restyle', specs.eventData); return gd; }); }
n/a
function update(gd, traceUpdate, layoutUpdate, traces) { gd = helpers.getGraphDiv(gd); helpers.clearPromiseQueue(gd); if(gd.framework && gd.framework.isPolar) { return Promise.resolve(gd); } if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {}; if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {}; if(Object.keys(traceUpdate).length) gd.changed = true; if(Object.keys(layoutUpdate).length) gd.changed = true; 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; // fill in redraw sequence var seq = []; if(restyleFlags.fullReplot && relayoutFlags.layoutReplot) { var data = gd.data, layout = gd.layout; // clear existing data/layout on gd // so that Plotly.plot doesn't try to extend them gd.data = undefined; gd.layout = undefined; seq.push(function() { return Plotly.plot(gd, data, layout); }); } else if(restyleFlags.fullReplot) { seq.push(Plotly.plot); } else if(relayoutFlags.layoutReplot) { seq.push(subroutines.layoutReplot); } else { 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); } seq.push(Plots.rehover); Queue.add(gd, update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces], update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces] ); var plotDone = Lib.syncOrAsync(seq, gd); if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); return plotDone.then(function() { gd.emit('plotly_update', { data: restyleSpecs.eventData, layout: relayoutSpecs.eventData }); return gd; }); }
...
var pkg = require('../../package.json');
module.exports = function updateVersion(pathToFile) {
fs.readFile(pathToFile, 'utf-8', function(err, code) {
var out = falafel(code, function(node) {
if(isVersionNode(node)) node.update('\'' + pkg.version +
x27;\'');
});
fs.writeFile(pathToFile, out, function(err) {
if(err) throw err;
});
});
};
...
_hasPlotType = function (category) { var basePlotModules = this._basePlotModules || []; for(var i = 0; i < basePlotModules.length; i++) { var _module = basePlotModules[i]; if(_module.name === category) return true; } return false; }
n/a
addLinks = function (gd) { // Do not do anything if showLink and showSources are not set to true in config if(!gd._context.showLink && !gd._context.showSources) return; var fullLayout = gd._fullLayout; var linkContainer = fullLayout._paper .selectAll('text.js-plot-link-container').data([0]); linkContainer.enter().append('text') .classed('js-plot-link-container', true) .style({ 'font-family': '"Open Sans", Arial, sans-serif', 'font-size': '12px', 'fill': Color.defaultLine, 'pointer-events': 'all' }) .each(function() { var links = d3.select(this); links.append('tspan').classed('js-link-to-tool', true); links.append('tspan').classed('js-link-spacer', true); links.append('tspan').classed('js-sourcelinks', true); }); // The text node inside svg var text = linkContainer.node(), attrs = { y: fullLayout._paper.attr('height') - 9 }; // If text's width is bigger than the layout // Check that text is a child node or document.body // because otherwise IE/Edge might throw an exception // when calling getComputedTextLength(). // Apparently offsetParent is null for invisibles. if(document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) { // Align the text at the left attrs['text-anchor'] = 'start'; attrs.x = 5; } else { // Align the text at the right attrs['text-anchor'] = 'end'; attrs.x = fullLayout._paper.attr('width') - 7; } linkContainer.attr(attrs); var toolspan = linkContainer.select('.js-link-to-tool'), spacespan = linkContainer.select('.js-link-spacer'), sourcespan = linkContainer.select('.js-sourcelinks'); if(gd._context.showSources) gd._context.showSources(gd); // 'view in plotly' link for embedded plots if(gd._context.showLink) positionPlayWithData(gd, toolspan); // separator if we have both sources and tool link spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : ''); }
...
Plots.style(gd);
// show annotations and shapes
Registry.getComponentMethod('shapes', 'draw')(gd);
Registry.getComponentMethod('annotations', 'draw')(gd);
// source links
Plots.addLinks(gd);
// Mark the first render as complete
fullLayout._replotting = false;
return Plots.previousPromises(gd);
}
...
autoMargin = function (gd, id, o) { var fullLayout = gd._fullLayout; if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; if(fullLayout.margin.autoexpand !== false) { if(!o) delete fullLayout._pushmargin[id]; else { var pad = o.pad === undefined ? 12 : o.pad; // if the item is too big, just give it enough automargin to // make sure you can still grab it and bring it back if(o.l + o.r > fullLayout.width * 0.5) o.l = o.r = 0; if(o.b + o.t > fullLayout.height * 0.5) o.b = o.t = 0; fullLayout._pushmargin[id] = { l: {val: o.x, size: o.l + pad}, r: {val: o.x, size: o.r + pad}, b: {val: o.y, size: o.b + pad}, t: {val: o.y, size: o.t + pad} }; } if(!fullLayout._replotting) plots.doAutoMargin(gd); } }
...
Registry.getComponentMethod('sliders', 'draw')(gd);
Registry.getComponentMethod('updatemenus', 'draw')(gd);
for(i = 0; i < calcdata.length; i++) {
cd = calcdata[i];
trace = cd[0].trace;
if(trace.visible !== true || !trace._module.colorbar) {
Plots.autoMargin(gd, 'cb' + trace.uid);
}
else trace._module.colorbar(gd, cd);
}
Plots.doAutoMargin(gd);
return Plots.previousPromises(gd);
}
...
cleanPlot = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { var i, j; var basePlotModules = oldFullLayout._basePlotModules || []; for(i = 0; i < basePlotModules.length; i++) { var _module = basePlotModules[i]; if(_module.clean) { _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout); } } var hasPaper = !!oldFullLayout._paper; var hasInfoLayer = !!oldFullLayout._infolayer; oldLoop: for(i = 0; i < oldFullData.length; i++) { var oldTrace = oldFullData[i], oldUid = oldTrace.uid; for(j = 0; j < newFullData.length; j++) { var newTrace = newFullData[j]; if(oldUid === newTrace.uid) continue oldLoop; } var query = ( '.hm' + oldUid + ',.contour' + oldUid + ',.carpet' + oldUid + ',#clip' + oldUid + ',.trace' + oldUid ); // clean old heatmap, contour traces and clip paths // that rely on uid identifiers if(hasPaper) { oldFullLayout._paper.selectAll(query).remove(); } // clean old colorbars and range slider plot if(hasInfoLayer) { oldFullLayout._infolayer.selectAll('.cb' + oldUid).remove(); oldFullLayout._infolayer.selectAll('g.rangeslider-container') .selectAll(query).remove(); } } }
...
* @param {Object} layout
* @param {Object} config
*/
Plotly.newPlot = function(gd, data, layout, config) {
gd = helpers.getGraphDiv(gd);
// remove gl contexts
Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
Plots.purge(gd);
return Plotly.plot(gd, data, layout, config);
};
/**
* Wrap negative indicies to their positive counterparts.
...
computeAPICommandBindings = function (gd, method, args) { var bindings; if(!Array.isArray(args)) args = []; switch(method) { case 'restyle': bindings = computeDataBindings(gd, args); break; case 'relayout': bindings = computeLayoutBindings(gd, args); break; case 'update': bindings = computeDataBindings(gd, [args[0], args[2]]) .concat(computeLayoutBindings(gd, [args[1]])); break; case 'animate': bindings = computeAnimateBindings(gd, args); break; default: // This is the case where intelligent logic about what affects // this command is not implemented. It causes no ill effects. // For example, addFrames simply won't bind to a control component. bindings = []; } return bindings; }
...
if(!Array.isArray(args)) args = [];
// If any command has no method, refuse to bind:
if(!method) {
return false;
}
var bindings = exports.computeAPICommandBindings(gd, method, args);
// Right now, handle one and *only* one property being set:
if(bindings.length !== 1) {
return false;
}
if(!refBinding) {
...
computeFrame = function (gd, frameName) { var frameLookup = gd._transitionData._frameHash; var i, traceIndices, traceIndex, destIndex; // Null or undefined will fail on .toString(). We'll allow numbers since we // make it clear frames must be given string names, but we'll allow numbers // here since they're otherwise fine for looking up frames as long as they're // properly cast to strings. We really just want to ensure here that this // 1) doesn't fail, and // 2) doens't give an incorrect answer (which String(frameName) would) if(!frameName) { throw new Error('computeFrame must be given a string frame name'); } var framePtr = frameLookup[frameName.toString()]; // Return false if the name is invalid: if(!framePtr) { return false; } var frameStack = [framePtr]; var frameNameStack = [framePtr.name]; // Follow frame pointers: while(framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) { // Avoid infinite loops: if(frameNameStack.indexOf(framePtr.name) !== -1) break; frameStack.push(framePtr); frameNameStack.push(framePtr.name); } // A new object for the merged result: var result = {}; // Merge, starting with the last and ending with the desired frame: while((framePtr = frameStack.pop())) { if(framePtr.layout) { result.layout = plots.extendLayout(result.layout, framePtr.layout); } if(framePtr.data) { if(!result.data) { result.data = []; } traceIndices = framePtr.traces; if(!traceIndices) { // If not defined, assume serial order starting at zero traceIndices = []; for(i = 0; i < framePtr.data.length; i++) { traceIndices[i] = i; } } if(!result.traces) { result.traces = []; } for(i = 0; i < framePtr.data.length; i++) { // Loop through this frames data, find out where it should go, // and merge it! traceIndex = traceIndices[i]; if(traceIndex === undefined || traceIndex === null) { continue; } destIndex = result.traces.indexOf(traceIndex); if(destIndex === -1) { destIndex = result.data.length; result.traces[destIndex] = traceIndex; } result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]); } } } return result; }
...
if(frameList.length === 0) return;
for(var i = 0; i < frameList.length; i++) {
var computedFrame;
if(frameList[i].type === 'byname') {
// If it's a named frame, compute it:
computedFrame = Plots.computeFrame(gd, frameList[i].name);
} else {
// Otherwise we must have been given a simple object, so treat
// the input itself as the computed frame.
computedFrame = frameList[i].data;
}
var frameOpts = getFrameOpts(i);
...
createTransitionData = function (gd) { // Set up the default keyframe if it doesn't exist: if(!gd._transitionData) { gd._transitionData = {}; } if(!gd._transitionData._frames) { gd._transitionData._frames = []; } if(!gd._transitionData._frameHash) { gd._transitionData._frameHash = {}; } if(!gd._transitionData._counter) { gd._transitionData._counter = 0; } if(!gd._transitionData._interruptCallbacks) { gd._transitionData._interruptCallbacks = []; } }
...
var oldFullData = gd._fullData || [],
newFullData = gd._fullData = [],
newData = gd.data || [];
var i;
// Create all the storage space for frames, but only if doesn't already exist
if(!gd._transitionData) plots.createTransitionData(gd);
// first fill in what we can of layout without looking at data
// because fullData needs a few things from layout
if(oldFullLayout._initialAutoSizeIsDone) {
// coerce the updated layout while preserving width and height
...
doAutoMargin = function (gd) { var fullLayout = gd._fullLayout; if(!fullLayout._size) fullLayout._size = {}; if(!fullLayout._pushmargin) fullLayout._pushmargin = {}; var gs = fullLayout._size, oldmargins = JSON.stringify(gs); // adjust margins for outside components // fullLayout.margin is the requested margin, // fullLayout._size has margins and plotsize after adjustment var ml = Math.max(fullLayout.margin.l || 0, 0), mr = Math.max(fullLayout.margin.r || 0, 0), mt = Math.max(fullLayout.margin.t || 0, 0), mb = Math.max(fullLayout.margin.b || 0, 0), pm = fullLayout._pushmargin; if(fullLayout.margin.autoexpand !== false) { // fill in the requested margins pm.base = { l: {val: 0, size: ml}, r: {val: 1, size: mr}, t: {val: 1, size: mt}, b: {val: 0, size: mb} }; // now cycle through all the combinations of l and r // (and t and b) to find the required margins var pmKeys = Object.keys(pm); for(var i = 0; i < pmKeys.length; i++) { var k1 = pmKeys[i]; var pushleft = pm[k1].l || {}, pushbottom = pm[k1].b || {}, fl = pushleft.val, pl = pushleft.size, fb = pushbottom.val, pb = pushbottom.size; for(var j = 0; j < pmKeys.length; j++) { var k2 = pmKeys[j]; if(isNumeric(pl) && pm[k2].r) { var fr = pm[k2].r.val, pr = pm[k2].r.size; if(fr > fl) { var newl = (pl * fr + (pr - fullLayout.width) * fl) / (fr - fl), newr = (pr * (1 - fl) + (pl - fullLayout.width) * (1 - fr)) / (fr - fl); if(newl >= 0 && newr >= 0 && newl + newr > ml + mr) { ml = newl; mr = newr; } } } if(isNumeric(pb) && pm[k2].t) { var ft = pm[k2].t.val, pt = pm[k2].t.size; if(ft > fb) { var newb = (pb * ft + (pt - fullLayout.height) * fb) / (ft - fb), newt = (pt * (1 - fb) + (pb - fullLayout.height) * (1 - ft)) / (ft - fb); if(newb >= 0 && newt >= 0 && newb + newt > mb + mt) { mb = newb; mt = newt; } } } } } } gs.l = Math.round(ml); gs.r = Math.round(mr); gs.t = Math.round(mt); gs.b = Math.round(mb); gs.p = Math.round(fullLayout.margin.pad); gs.w = Math.round(fullLayout.width) - gs.l - gs.r; gs.h = Math.round(fullLayout.height) - gs.t - gs.b; // if things changed and we're not already redrawing, trigger a redraw if(!fullLayout._replotting && oldmargins !== '{}' && oldmargins !== JSON.stringify(fullLayout._size)) { return Plotly.plot(gd); } }
...
trace = cd[0].trace;
if(trace.visible !== true || !trace._module.colorbar) {
Plots.autoMargin(gd, 'cb' + trace.uid);
}
else trace._module.colorbar(gd, cd);
}
Plots.doAutoMargin(gd);
return Plots.previousPromises(gd);
}
// in case the margins changed, draw margin pushers again
function marginPushersAgain() {
var seq = JSON.stringify(fullLayout._size) === oldmargins ?
[] :
...
doCalcdata = function (gd, traces) { var axList = Plotly.Axes.list(gd), fullData = gd._fullData, fullLayout = gd._fullLayout; var trace, _module, i, j; var hasCategoryAxis = false; // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without // *all* needing doCalcdata: var calcdata = new Array(fullData.length); var oldCalcdata = (gd.calcdata || []).slice(0); gd.calcdata = calcdata; // extra helper variables // firstscatter: fill-to-next on the first trace goes to zero gd.firstscatter = true; // how many box plots do we have (in case they're grouped) gd.numboxes = 0; // for calculating avg luminosity of heatmaps gd._hmpixcount = 0; gd._hmlumcount = 0; // for sharing colors across pies (and for legend) fullLayout._piecolormap = {}; fullLayout._piedefaultcolorcount = 0; // initialize the category list, if there is one, so we start over // to be filled in later by ax.d2c for(i = 0; i < axList.length; i++) { axList[i]._categories = axList[i]._initialCategories.slice(); // Build the lookup map for initialized categories axList[i]._categoriesMap = {}; for(j = 0; j < axList[i]._categories.length; j++) { axList[i]._categoriesMap[axList[i]._categories[j]] = j; } if(axList[i].type === 'category') hasCategoryAxis = true; } // If traces were specified and this trace was not included, // then transfer it over from the old calcdata: for(i = 0; i < fullData.length; i++) { if(Array.isArray(traces) && traces.indexOf(i) === -1) { calcdata[i] = oldCalcdata[i]; continue; } } var hasCalcTransform = false; // transform loop for(i = 0; i < fullData.length; i++) { trace = fullData[i]; if(trace.visible === true && trace.transforms) { _module = trace._module; // we need one round of trace module calc before // the calc transform to 'fill in' the categories list // used for example in the data-to-coordinate method if(_module && _module.calc) _module.calc(gd, trace); for(j = 0; j < trace.transforms.length; j++) { var transform = trace.transforms[j]; _module = transformsRegistry[transform.type]; if(_module && _module.calcTransform) { hasCalcTransform = true; _module.calcTransform(gd, trace, transform); } } } } // clear stuff that should recomputed in 'regular' loop if(hasCalcTransform) { for(i = 0; i < axList.length; i++) { axList[i]._min = []; axList[i]._max = []; axList[i]._categories = []; // Reset the look up map axList[i]._categoriesMap = {}; } } // 'regular' loop for(i = 0; i < fullData.length; i++) { var cd = []; trace = fullData[i]; if(trace.visible === true) { _module = trace._module; if(_module && _module.calc) cd = _module.calc(gd, trace); } // Make sure there is a first point. // // This ensures there is a calcdata item for every trace, // even if cartesian logic doesn't handle it (for things like legends). if(!Array.isArray(cd) || !cd[0]) { cd = [{x: BADNUM, y: BADNUM}]; } // add the trace-wide properties to the first point, // per point properties to every point // t is the holder for trace-wide properties if(!cd[0].t) cd[0].t = {}; cd[0].trace = trace; calcdata[i] = cd; } // To handle the case of components using category names as coordinates, we // need to re-supply defaults for these objects now, after calc has // finished populating the category mappings // Any component that uses `Axes.coercePosition` falls into this category if(hasCategoryAxis) { var dataRe ...
...
if(graphWasEmpty) Plotly.Axes.saveShowSpikeInitial(gd);
// prepare the data and find the autorange
// generate calcdata, if we need to
// to force redoing calcdata, just delete it before calling Plotly.plot
var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
if(recalc) Plots.doCalcdata(gd);
// in case it has changed, attach fullData traces to calcdata
for(var i = 0; i < gd.calcdata.length; i++) {
gd.calcdata[i][0].trace = gd._fullData[i];
}
/*
...
executeAPICommand = function (gd, method, args) { var apiMethod = Plotly[method]; var allArgs = [gd]; if(!Array.isArray(args)) args = []; for(var i = 0; i < args.length; i++) { allArgs.push(args[i]); } return apiMethod.apply(null, allArgs).catch(function(err) { Lib.warn('API call to Plotly.' + method + ' rejected.', err); return Promise.reject(err); }); }
n/a
extendLayout = function (destLayout, srcLayout) { return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers); }
...
// A new object for the merged result:
var result = {};
// Merge, starting with the last and ending with the desired frame:
while((framePtr = frameStack.pop())) {
if(framePtr.layout) {
result.layout = plots.extendLayout(result.layout, framePtr.layout);
}
if(framePtr.data) {
if(!result.data) {
result.data = [];
}
traceIndices = framePtr.traces;
...
extendObjectWithContainers = function (dest, src, containerPaths) { var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer; var copy = Lib.extendDeepNoArrays({}, src || {}); var expandedObj = Lib.expandObjectPaths(copy); var containerObj = {}; // Step through and extract any container properties. Otherwise extendDeepNoArrays // will clobber any existing properties with an empty array and then supplyDefaults // will reset everything to defaults. if(containerPaths && containerPaths.length) { for(i = 0; i < containerPaths.length; i++) { containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]); containerVal = containerProp.get(); if(containerVal === undefined) { Lib.nestedProperty(containerObj, containerPaths[i]).set(null); } else { containerProp.set(null); Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); } } } dest = Lib.extendDeepNoArrays(dest || {}, expandedObj); if(containerPaths && containerPaths.length) { for(i = 0; i < containerPaths.length; i++) { srcProp = Lib.nestedProperty(containerObj, containerPaths[i]); srcContainer = srcProp.get(); if(!srcContainer) continue; destProp = Lib.nestedProperty(dest, containerPaths[i]); destContainer = destProp.get(); if(!Array.isArray(destContainer)) { destContainer = []; destProp.set(destContainer); } for(j = 0; j < srcContainer.length; j++) { var srcObj = srcContainer[j]; if(srcObj === null) destContainer[j] = null; else { destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj); } } destProp.set(destContainer); } } return dest; }
...
}
for(j = 0; j < srcContainer.length; j++) {
var srcObj = srcContainer[j];
if(srcObj === null) destContainer[j] = null;
else {
destContainer[j] = plots.extendObjectWithContainers(destContainer[j],
srcObj);
}
}
destProp.set(destContainer);
}
}
...
extendTrace = function (destTrace, srcTrace) { return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers); }
...
destIndex = result.traces.indexOf(traceIndex);
if(destIndex === -1) {
destIndex = result.data.length;
result.traces[destIndex] = traceIndex;
}
result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr
.data[i]);
}
}
}
return result;
};
...
function findSubplotIds(data, type) { var subplotIds = []; if(!plots.subplotsRegistry[type]) return subplotIds; var attr = plots.subplotsRegistry[type].attr; for(var i = 0; i < data.length; i++) { var trace = data[i]; if(plots.traceIs(trace, type) && subplotIds.indexOf(trace[attr]) === -1) { subplotIds.push(trace[attr]); } } return subplotIds; }
...
*/
module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) {
var subplotType = opts.type,
subplotAttributes = opts.attributes,
handleDefaults = opts.handleDefaults,
partition = opts.partition || 'x';
var ids = Plots.findSubplotIds(fullData, subplotType),
idsLength = ids.length;
var subplotLayoutIn, subplotLayoutOut;
function coerce(attr, dflt) {
return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt);
}
...
generalUpdatePerTraceModule = function (subplot, subplotCalcData, subplotLayout) { var traceHashOld = subplot.traceHash, traceHash = {}, i; function filterVisible(calcDataIn) { var calcDataOut = []; for(var i = 0; i < calcDataIn.length; i++) { var calcTrace = calcDataIn[i], trace = calcTrace[0].trace; if(trace.visible === true) calcDataOut.push(calcTrace); } return calcDataOut; } // build up moduleName -> calcData hash for(i = 0; i < subplotCalcData.length; i++) { var calcTraces = subplotCalcData[i], trace = calcTraces[0].trace; // skip over visible === false traces // as they don't have `_module` ref if(trace.visible) { traceHash[trace.type] = traceHash[trace.type] || []; traceHash[trace.type].push(calcTraces); } } var moduleNamesOld = Object.keys(traceHashOld); var moduleNames = Object.keys(traceHash); // when a trace gets deleted, make sure that its module's // plot method is called so that it is properly // removed from the DOM. for(i = 0; i < moduleNamesOld.length; i++) { var moduleName = moduleNamesOld[i]; if(moduleNames.indexOf(moduleName) === -1) { var fakeCalcTrace = traceHashOld[moduleName][0], fakeTrace = fakeCalcTrace[0].trace; fakeTrace.visible = false; traceHash[moduleName] = [fakeCalcTrace]; } } // update list of module names to include 'fake' traces added above moduleNames = Object.keys(traceHash); // call module plot method for(i = 0; i < moduleNames.length; i++) { var moduleCalcData = traceHash[moduleNames[i]], _module = moduleCalcData[0][0].trace._module; _module.plot(subplot, filterVisible(moduleCalcData), subplotLayout); } // update moduleName -> calcData hash subplot.traceHash = traceHash; }
...
// TODO handle topojson-is-loading case
// to avoid making multiple request while streaming
};
proto.onceTopojsonIsLoaded = function(geoCalcData, geoLayout) {
this.drawLayout(geoLayout);
Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout);
this.render();
};
proto.makeProjection = function(geoLayout) {
var projLayout = geoLayout.projection,
projType = projLayout.type,
...
getComponentMethod = function (name, method) { var _module = exports.componentsRegistry[name]; if(!_module) return noop; return _module[method] || noop; }
...
* dateTick0: get the canonical tick for this calendar
*
* bool sunday is for week ticks, shift it to a Sunday.
*/
exports.dateTick0 = function(calendar, sunday) {
if(isWorldCalendar(calendar)) {
return sunday ?
Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY
')[calendar] :
Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar];
}
else {
return sunday ? '2000-01-02' : '2000-01-01';
}
};
...
getModule = function (trace) { if(trace.r !== undefined) { Loggers.warn('Tried to put a polar trace ' + 'on an incompatible graph of cartesian ' + 'data. Ignoring this dataset.', trace ); return false; } var _module = exports.modules[getTraceType(trace)]; if(!_module) return false; return _module._module; }
...
var attr = subplotsRegistry[subplotType].attr;
if(attr) coerceSubplotAttr(subplotType, attr);
}
if(visible) {
var _module = plots.getModule(traceOut);
traceOut._module = _module;
// gets overwritten in pie, geo and ternary modules
coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined);
if(plots.traceIs(traceOut, 'showLegend')) {
coerce('showlegend');
...
getSubplotCalcData = function (calcData, type, subplotId) { if(!plots.subplotsRegistry[type]) return []; var attr = plots.subplotsRegistry[type].attr; var subplotCalcData = []; for(var i = 0; i < calcData.length; i++) { var calcTrace = calcData[i], trace = calcTrace[0].trace; if(trace[attr] === subplotId) subplotCalcData.push(calcTrace); } return subplotCalcData; }
...
var c = require('./constants');
exports.name = 'parcoords';
exports.attr = 'type';
exports.plot = function(gd) {
var calcData = Plots.getSubplotCalcData(gd.calcdata, 'parcoords', 'parcoords
');
if(calcData.length) parcoordsPlot(gd, calcData);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadParcoords = (oldFullLayout._has && oldFullLayout._has('parcoords'));
var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords'));
...
function getSubplotData(data, type, subplotId) { if(!plots.subplotsRegistry[type]) return []; var attr = plots.subplotsRegistry[type].attr, subplotData = [], trace; for(var i = 0; i < data.length; i++) { trace = data[i]; if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { var spmatch = Plotly.Axes.subplotMatch, subplotX = 'x' + subplotId.match(spmatch)[1], subplotY = 'y' + subplotId.match(spmatch)[2]; if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { subplotData.push(trace); } } else { if(trace[attr] === subplotId) subplotData.push(trace); } } return subplotData; }
n/a
function getSubplotIds(layout, type) { var _module = plots.subplotsRegistry[type]; if(!_module) return []; // layout must be 'fullLayout' here if(type === 'cartesian' && (!layout._has || !layout._has('cartesian'))) return []; if(type === 'gl2d' && (!layout._has || !layout._has('gl2d'))) return []; if(type === 'cartesian' || type === 'gl2d') { return Object.keys(layout._plots || {}); } var idRegex = _module.idRegex, layoutKeys = Object.keys(layout), subplotIds = []; for(var i = 0; i < layoutKeys.length; i++) { var layoutKey = layoutKeys[i]; if(idRegex.test(layoutKey)) subplotIds.push(layoutKey); } // order the ids var idLen = _module.idRoot.length; subplotIds.sort(function(a, b) { var aNum = +(a.substr(idLen) || 1), bNum = +(b.substr(idLen) || 1); return aNum - bNum; }); return subplotIds; }
...
if(!layout.scene) layout.scene = layout.scene1;
delete layout.scene1;
}
/*
* Clean up Scene layouts
*/
var sceneIds = Plots.getSubplotIds(layout, 'gl3d');
for(i = 0; i < sceneIds.length; i++) {
var scene = layout[sceneIds[i]];
// clean old Camera coords
var cameraposition = scene.cameraposition;
if(Array.isArray(cameraposition) && cameraposition[0].length === 4) {
var rotation = cameraposition[0],
...
graphJson = function (gd, dataonly, mode, output, useDefaults) { // if the defaults aren't supplied yet, we need to do that... if((useDefaults && dataonly && !gd._fullData) || (useDefaults && !dataonly && !gd._fullLayout)) { plots.supplyDefaults(gd); } var data = (useDefaults) ? gd._fullData : gd.data, layout = (useDefaults) ? gd._fullLayout : gd.layout, frames = (gd._transitionData || {})._frames; function stripObj(d) { if(typeof d === 'function') { return null; } if(Lib.isPlainObject(d)) { var o = {}, v, src; for(v in d) { // remove private elements and functions // _ is for private, [ is a mistake ie [object Object] if(typeof d[v] === 'function' || ['_', '['].indexOf(v.charAt(0)) !== -1) { continue; } // look for src/data matches and remove the appropriate one if(mode === 'keepdata') { // keepdata: remove all ...src tags if(v.substr(v.length - 3) === 'src') { continue; } } else if(mode === 'keepstream') { // keep sourced data if it's being streamed. // similar to keepref, but if the 'stream' object exists // in a trace, we will keep the data array. src = d[v + 'src']; if(typeof src === 'string' && src.indexOf(':') > 0) { if(!Lib.isPlainObject(d.stream)) { continue; } } } else if(mode !== 'keepall') { // keepref: remove sourced data but only // if the source tag is well-formed src = d[v + 'src']; if(typeof src === 'string' && src.indexOf(':') > 0) { continue; } } // OK, we're including this... recurse into it o[v] = stripObj(d[v]); } return o; } if(Array.isArray(d)) { return d.map(stripObj); } // convert native dates to date strings... // mostly for external users exporting to plotly if(Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d); return d; } var obj = { data: (data || []).map(function(v) { var d = stripObj(v); // fit has some little arrays in it that don't contain data, // just fit params and meta if(dataonly) { delete d.fit; } return d; }) }; if(!dataonly) { obj.layout = stripObj(layout); } if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig(); if(frames) obj.frames = stripObj(frames); return (output === 'object') ? obj : JSON.stringify(obj); }
...
var hiddenformInput = hiddenform
.append('input')
.attr({
type: 'text',
name: 'data'
});
hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata');
hiddenform.node().submit();
hiddenformDiv.remove();
gd.emit('plotly_afterexport');
return false;
};
...
hasSimpleAPICommandBindings = function (gd, commandList, bindingsByValue) { var i; var n = commandList.length; var refBinding; for(i = 0; i < n; i++) { var binding; var command = commandList[i]; var method = command.method; var args = command.args; if(!Array.isArray(args)) args = []; // If any command has no method, refuse to bind: if(!method) { return false; } var bindings = exports.computeAPICommandBindings(gd, method, args); // Right now, handle one and *only* one property being set: if(bindings.length !== 1) { return false; } if(!refBinding) { refBinding = bindings[0]; if(Array.isArray(refBinding.traces)) { refBinding.traces.sort(); } } else { binding = bindings[0]; if(binding.type !== refBinding.type) { return false; } if(binding.prop !== refBinding.prop) { return false; } if(Array.isArray(refBinding.traces)) { if(Array.isArray(binding.traces)) { binding.traces.sort(); for(var j = 0; j < refBinding.traces.length; j++) { if(refBinding.traces[j] !== binding.traces[j]) { return false; } } } else { return false; } } else { if(binding.prop !== refBinding.prop) { return false; } } } binding = bindings[0]; var value = binding.value; if(Array.isArray(value)) { if(value.length === 1) { value = value[0]; } else { return false; } } if(bindingsByValue) { bindingsByValue[value] = i; } } return refBinding; }
...
if(!ret.cache) {
ret.cache = {};
}
// Either create or just recompute this:
ret.lookupTable = {};
var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable);
if(container && container._commandObserver) {
if(!binding) {
// If container exists and there are no longer any bindings,
// remove existing:
if(container._commandObserver.remove) {
container._commandObserver.remove();
...
linkSubplots = function (newFullData, newFullLayout, oldFullData, oldFullLayout) { var oldSubplots = oldFullLayout._plots || {}, newSubplots = newFullLayout._plots = {}; var mockGd = { _fullData: newFullData, _fullLayout: newFullLayout }; var ids = Plotly.Axes.getSubplots(mockGd); for(var i = 0; i < ids.length; i++) { var id = ids[i], oldSubplot = oldSubplots[id], plotinfo; if(oldSubplot) { plotinfo = newSubplots[id] = oldSubplot; if(plotinfo._scene2d) { plotinfo._scene2d.updateRefs(newFullLayout); } } else { plotinfo = newSubplots[id] = {}; plotinfo.id = id; } plotinfo.xaxis = Plotly.Axes.getFromId(mockGd, id, 'x'); plotinfo.yaxis = Plotly.Axes.getFromId(mockGd, id, 'y'); } }
...
newFullLayout._hasTernary = newFullLayout._has('ternary');
newFullLayout._hasPie = newFullLayout._has('pie');
// clean subplots and other artifacts from previous plot calls
plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout);
// relink / initialize subplot axis objects
plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout);
// relink functions and _ attributes to promote consistency between plots
relinkPrivateKeys(newFullLayout, oldFullLayout);
// TODO may return a promise
plots.doAutoMargin(gd);
...
manageCommandObserver = function (gd, container, commandList, onchange) { var ret = {}; var enabled = true; if(container && container._commandObserver) { ret = container._commandObserver; } if(!ret.cache) { ret.cache = {}; } // Either create or just recompute this: ret.lookupTable = {}; var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable); if(container && container._commandObserver) { if(!binding) { // If container exists and there are no longer any bindings, // remove existing: if(container._commandObserver.remove) { container._commandObserver.remove(); container._commandObserver = null; return ret; } } else { // If container exists and there *are* bindings, then the lookup // table should have been updated and check is already attached, // so there's nothing to be done: return ret; } } // Determine whether there's anything to do for this binding: if(binding) { // Build the cache: bindingValueHasChanged(gd, binding, ret.cache); ret.check = function check() { if(!enabled) return; var update = bindingValueHasChanged(gd, binding, ret.cache); if(update.changed && onchange) { // Disable checks for the duration of this command in order to avoid // infinite loops: if(ret.lookupTable[update.value] !== undefined) { ret.disable(); Promise.resolve(onchange({ value: update.value, type: binding.type, prop: binding.prop, traces: binding.traces, index: ret.lookupTable[update.value] })).then(ret.enable, ret.enable); } } return update.changed; }; var checkEvents = [ 'plotly_relayout', 'plotly_redraw', 'plotly_restyle', 'plotly_update', 'plotly_animatingframe', 'plotly_afterplot' ]; for(var i = 0; i < checkEvents.length; i++) { gd._internalOn(checkEvents[i], ret.check); } ret.remove = function() { for(var i = 0; i < checkEvents.length; i++) { gd._removeInternalListener(checkEvents[i], ret.check); } }; } else { // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning // is a start Lib.warn('Unable to automatically bind plot updates to API command'); ret.lookupTable = {}; ret.remove = function() {}; } ret.disable = function disable() { enabled = false; }; ret.enable = function enable() { enabled = true; }; if(container) { container._commandObserver = ret; } return ret; }
n/a
modifyFrames = function (gd, operations) {
var i, op, frame;
var _frames = gd._transitionData._frames;
var _hash = gd._transitionData._frameHash;
for(i = 0; i < operations.length; i++) {
op = operations[i];
switch(op.type) {
// No reason this couldn't exist, but is currently unused/untested:
/* case 'rename':
frame = _frames[op.index];
delete _hash[frame.name];
_hash[op.name] = frame;
frame.name = op.name;
break;*/
case 'replace':
frame = op.value;
var oldName = (_frames[op.index] || {}).name;
var newName = frame.name;
_frames[op.index] = _hash[newName] = frame;
if(newName !== oldName) {
// If name has changed in addition to replacement, then update
// the lookup table:
delete _hash[oldName];
_hash[newName] = frame;
}
break;
case 'insert':
frame = op.value;
_hash[frame.name] = frame;
_frames.splice(op.index, 0, frame);
break;
case 'delete':
frame = _frames[op.index];
delete _hash[frame.name];
_frames.splice(op.index, 1);
break;
}
}
return Promise.resolve();
}
...
var undoFunc = Plots.modifyFrames,
redoFunc = Plots.modifyFrames,
undoArgs = [gd, revops],
redoArgs = [gd, ops];
if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
return Plots.modifyFrames(gd, ops);
};
/**
* Delete frame
*
* @param {string id or DOM element} gd
* the id or DOM element of the graph container div
...
function plotAutoSize(gd, layout, fullLayout) { var context = gd._context || {}, frameMargins = context.frameMargins, newWidth, newHeight; var isPlotDiv = Lib.isPlotDiv(gd); if(isPlotDiv) gd.emit('plotly_autosize'); // embedded in an iframe - just take the full iframe size // if we get to this point, with no aspect ratio restrictions if(context.fillFrame) { newWidth = window.innerWidth; newHeight = window.innerHeight; // somehow we get a few extra px height sometimes... // just hide it document.body.style.overflow = 'hidden'; } else if(isNumeric(frameMargins) && frameMargins > 0) { var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins), reservedWidth = reservedMargins.left + reservedMargins.right, reservedHeight = reservedMargins.bottom + reservedMargins.top, factor = 1 - 2 * frameMargins; var gdBB = fullLayout._container && fullLayout._container.node ? fullLayout._container.node().getBoundingClientRect() : { width: fullLayout.width, height: fullLayout.height }; newWidth = Math.round(factor * (gdBB.width - reservedWidth)); newHeight = Math.round(factor * (gdBB.height - reservedHeight)); } else { // plotly.js - let the developers do what they want, either // provide height and width for the container div, // specify size in layout, or take the defaults, // but don't enforce any ratio restrictions var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {}; newWidth = parseFloat(computedStyle.width) || fullLayout.width; newHeight = parseFloat(computedStyle.height) || fullLayout.height; } var minWidth = plots.layoutAttributes.width.min, minHeight = plots.layoutAttributes.height.min; if(newWidth < minWidth) newWidth = minWidth; if(newHeight < minHeight) newHeight = minHeight; var widthHasChanged = !layout.width && (Math.abs(fullLayout.width - newWidth) > 1), heightHasChanged = !layout.height && (Math.abs(fullLayout.height - newHeight) > 1); if(heightHasChanged || widthHasChanged) { if(widthHasChanged) fullLayout.width = newWidth; if(heightHasChanged) fullLayout.height = newHeight; } // cache initial autosize value, used in relayout when // width or height values are set to null if(!gd._initialAutoSize) { gd._initialAutoSize = { width: newWidth, height: newHeight }; } plots.sanitizeMargins(fullLayout); }
...
}
}
var oldWidth = fullLayout.width,
oldHeight = fullLayout.height;
// calculate autosizing
if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout);
// avoid unnecessary redraws
var hasSizechanged = aobj.height || aobj.width ||
(fullLayout.width !== oldWidth) ||
(fullLayout.height !== oldHeight);
if(hasSizechanged) flags.docalc = true;
...
previousPromises = function (gd) { if((gd._promises || []).length) { return Promise.all(gd._promises) .then(function() { gd._promises = []; }); } }
...
if(trace.visible !== true || !trace._module.colorbar) {
Plots.autoMargin(gd, 'cb' + trace.uid);
}
else trace._module.colorbar(gd, cd);
}
Plots.doAutoMargin(gd);
return Plots.previousPromises(gd);
}
// in case the margins changed, draw margin pushers again
function marginPushersAgain() {
var seq = JSON.stringify(fullLayout._size) === oldmargins ?
[] :
[marginPushers, subroutines.layoutStyles];
...
purge = function (gd) { // note: we DO NOT remove _context because it doesn't change when we insert // a new plot, and may have been set outside of our scope. var fullLayout = gd._fullLayout || {}; if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove(); if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove(); // remove modebar if(fullLayout._modeBar) fullLayout._modeBar.destroy(); if(gd._transitionData) { // Ensure any dangling callbacks are simply dropped if the plot is purged. // This is more or less only actually important for testing. if(gd._transitionData._interruptCallbacks) { gd._transitionData._interruptCallbacks.length = 0; } if(gd._transitionData._animationRaf) { window.cancelAnimationFrame(gd._transitionData._animationRaf); } } // data and layout delete gd.data; delete gd.layout; delete gd._fullData; delete gd._fullLayout; delete gd.calcdata; delete gd.framework; delete gd.empty; delete gd.fid; delete gd.undoqueue; // action queue delete gd.undonum; delete gd.autoplay; // are we doing an action that doesn't go in undo queue? delete gd.changed; // these get recreated on Plotly.plot anyway, but just to be safe // (and to have a record of them...) delete gd._tester; delete gd._testref; delete gd._promises; delete gd._redrawTimer; delete gd.firstscatter; delete gd.hmlumcount; delete gd.hmpixcount; delete gd.numboxes; delete gd._hoverTimer; delete gd._lastHoverTime; delete gd._transitionData; delete gd._transitioning; delete gd._initialAutoSize; // remove all event listeners if(gd.removeAllListeners) gd.removeAllListeners(); }
...
*/
Plotly.newPlot = function(gd, data, layout, config) {
gd = helpers.getGraphDiv(gd);
// remove gl contexts
Plots.cleanPlot([], {}, gd._fullData || {}, gd._fullLayout || {});
Plots.purge(gd);
return Plotly.plot(gd, data, layout, config);
};
/**
* Wrap negative indicies to their positive counterparts.
*
* @param {Number[]} indices An array of indices
...
recomputeFrameHash = function (gd) { var hash = gd._transitionData._frameHash = {}; var frames = gd._transitionData._frames; for(var i = 0; i < frames.length; i++) { var frame = frames[i]; if(frame && frame.name) { hash[frame.name] = frame; } } }
n/a
redrawText = function (gd) { // do not work if polar is present if((gd.data && gd.data[0] && gd.data[0].r)) return; return new Promise(function(resolve) { setTimeout(function() { Registry.getComponentMethod('annotations', 'draw')(gd); Registry.getComponentMethod('legend', 'draw')(gd); (gd.calcdata || []).forEach(function(d) { if(d[0] && d[0].t && d[0].t.cb) d[0].t.cb(); }); resolve(plots.previousPromises(gd)); }, 300); }); }
n/a
register = function (_module, thisType, categoriesIn, meta) { if(exports.modules[thisType]) { Loggers.log('Type ' + thisType + ' already registered'); return; } var categoryObj = {}; for(var i = 0; i < categoriesIn.length; i++) { categoryObj[categoriesIn[i]] = true; exports.allCategories[categoriesIn[i]] = true; } exports.modules[thisType] = { _module: _module, categories: categoryObj }; if(meta && Object.keys(meta).length) { exports.modules[thisType].meta = meta; } exports.allTypes.push(thisType); }
...
If you would like to manually pick which plotly.js modules to include, you can create a *custom* bundle by using `plotly.js/lib/
core`, and loading only the trace types that you need (e.g. `pie` or `choropleth`). The recommended way to do this is by creating
a *bundling file*:
```javascript
// in custom-plotly.js
var Plotly = require('plotly.js/lib/core');
// Load in the trace types for pie, and choropleth
Plotly.register([
require('plotly.js/lib/pie'),
require('plotly.js/lib/choropleth')
]);
module.exports = Plotly;
```
...
registerComponent = function (_module) { var name = _module.name; exports.componentsRegistry[name] = _module; if(_module.layoutAttributes) { if(_module.layoutAttributes._isLinkedToArray) { pushUnique(exports.layoutArrayContainers, name); } findArrayRegexps(_module); } }
...
}
function registerComponentModule(newModule) {
if(typeof newModule.name !== 'string') {
throw new Error('Component module *name* must be a string.');
}
Registry.registerComponent(newModule);
}
...
registerSubplot = function (_module) { var plotType = _module.name; if(exports.subplotsRegistry[plotType]) { Loggers.log('Plot type ' + plotType + ' already registered.'); return; } // relayout array handling will look for component module methods with this // name and won't find them because this is a subplot module... but that // should be fine, it will just fall back on redrawing the plot. findArrayRegexps(_module); // not sure what's best for the 'cartesian' type at this point exports.subplotsRegistry[plotType] = _module; }
...
}
};
function registerTraceModule(newModule) {
Registry.register(newModule, newModule.name, newModule.categories, newModule.meta);
if(!Registry.subplotsRegistry[newModule.basePlotModule.name]) {
Registry.registerSubplot(newModule.basePlotModule);
}
}
function registerTransformModule(newModule) {
if(typeof newModule.name !== 'string') {
throw new Error('Transform module *name* must be a string.');
}
...
rehover = function (gd) { if(gd._fullLayout._rehover) { gd._fullLayout._rehover(); } }
n/a
resize = function (gd) { return new Promise(function(resolve, reject) { if(!gd || d3.select(gd).style('display') === 'none') { reject(new Error('Resize must be passed a plot div element.')); } if(gd._redrawTimer) clearTimeout(gd._redrawTimer); gd._redrawTimer = setTimeout(function() { // return if there is nothing to resize if(gd.layout.width && gd.layout.height) { resolve(gd); return; } delete gd.layout.width; delete gd.layout.height; // autosizing doesn't count as a change that needs saving var oldchanged = gd.changed; // nor should it be included in the undo queue gd.autoplay = true; Plotly.relayout(gd, { autosize: true }).then(function() { gd.changed = oldchanged; resolve(gd); }); }, 100); }); }
n/a
sanitizeMargins = function (fullLayout) { // polar doesn't do margins... if(!fullLayout || !fullLayout.margin) return; var width = fullLayout.width, height = fullLayout.height, margin = fullLayout.margin, plotWidth = width - (margin.l + margin.r), plotHeight = height - (margin.t + margin.b), correction; // if margin.l + margin.r = 0 then plotWidth > 0 // as width >= 10 by supplyDefaults // similarly for margin.t + margin.b if(plotWidth < 0) { correction = (width - 1) / (margin.l + margin.r); margin.l = Math.floor(correction * margin.l); margin.r = Math.floor(correction * margin.r); } if(plotHeight < 0) { correction = (height - 1) / (margin.t + margin.b); margin.t = Math.floor(correction * margin.t); margin.b = Math.floor(correction * margin.b); } }
...
var missingWidthOrHeight = (!newLayout.width || !newLayout.height),
autosize = newFullLayout.autosize,
autosizable = gd._context && gd._context.autosizable,
initialAutoSize = missingWidthOrHeight && (autosize || autosizable);
if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout);
else if(missingWidthOrHeight) plots.sanitizeMargins(gd);
// for backwards-compatibility with Plotly v1.x.x
if(!autosize && missingWidthOrHeight) {
newLayout.width = newFullLayout.width;
newLayout.height = newFullLayout.height;
}
}
...
sendDataToCloud = function (gd) { gd.emit('plotly_beforeexport'); var baseUrl = (window.PLOTLYENV && window.PLOTLYENV.BASE_URL) || 'https://plot.ly'; var hiddenformDiv = d3.select(gd) .append('div') .attr('id', 'hiddenform') .style('display', 'none'); var hiddenform = hiddenformDiv .append('form') .attr({ action: baseUrl + '/external', method: 'post', target: '_blank' }); var hiddenformInput = hiddenform .append('input') .attr({ type: 'text', name: 'data' }); hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata'); hiddenform.node().submit(); hiddenformDiv.remove(); gd.emit('plotly_afterexport'); return false; }
...
'class': 'link--impt link--embedview',
'font-weight': 'bold'
})
.text(gd._context.linkText + ' ' + String.fromCharCode(187));
if(gd._context.sendData) {
link.on('click', function() {
plots.sendDataToCloud(gd);
});
}
else {
var path = window.location.pathname.split('/');
var query = window.location.search;
link.attr({
'xlink:xlink:show': 'new',
...
style = function (gd) { var _modules = gd._fullLayout._modules; for(var i = 0; i < _modules.length; i++) { var _module = _modules[i]; if(_module.style) _module.style(gd); } }
...
.classed('plotly-notifier', true);
var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
function killNote(transition) {
transition
.duration(700)
.style('opacity', 0)
.each('end', function(thisText) {
var thisIndex = NOTEDATA.indexOf(thisText);
if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
d3.select(this).remove();
});
}
...
supplyAnimationDefaults = function (opts) { opts = opts || {}; var i; var optsOut = {}; function coerce(attr, dflt) { return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt); } coerce('mode'); coerce('direction'); coerce('fromcurrent'); if(Array.isArray(opts.frame)) { optsOut.frame = []; for(i = 0; i < opts.frame.length; i++) { optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {}); } } else { optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {}); } if(Array.isArray(opts.transition)) { optsOut.transition = []; for(i = 0; i < opts.transition.length; i++) { optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {}); } } else { optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {}); } return optsOut; }
...
// This is the queue of frames that will be animated as soon as possible. They
// are popped immediately upon the *start* of a transition:
if(!trans._frameQueue) {
trans._frameQueue = [];
}
animationOpts = Plots.supplyAnimationDefaults(animationOpts);
var transitionOpts = animationOpts.transition;
var frameOpts = animationOpts.frame;
// Since frames are popped immediately, an empty queue only means all frames have
// *started* to transition, not that the animation is complete. To solve that,
// track a separate counter that increments at the same time as frames are added
// to the queue, but decrements only when the transition is complete.
...
supplyAnimationFrameDefaults = function (opts) { var optsOut = {}; function coerce(attr, dflt) { return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt); } coerce('duration'); coerce('redraw'); return optsOut; }
...
coerce('mode');
coerce('direction');
coerce('fromcurrent');
if(Array.isArray(opts.frame)) {
optsOut.frame = [];
for(i = 0; i < opts.frame.length; i++) {
optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {});
}
} else {
optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {});
}
if(Array.isArray(opts.transition)) {
optsOut.transition = [];
...
supplyAnimationTransitionDefaults = function (opts) { var optsOut = {}; function coerce(attr, dflt) { return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt); } coerce('duration'); coerce('easing'); return optsOut; }
...
} else {
optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {});
}
if(Array.isArray(opts.transition)) {
optsOut.transition = [];
for(i = 0; i < opts.transition.length; i++) {
optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition
[i] || {});
}
} else {
optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {});
}
return optsOut;
};
...
supplyDataDefaults = function (dataIn, dataOut, layout, fullLayout) { var i, fullTrace, trace; var modules = fullLayout._modules = [], basePlotModules = fullLayout._basePlotModules = [], cnt = 0; fullLayout._transformModules = []; function pushModule(fullTrace) { dataOut.push(fullTrace); var _module = fullTrace._module; if(!_module) return; Lib.pushUnique(modules, _module); Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule); cnt++; } var carpetIndex = {}; var carpetDependents = []; for(i = 0; i < dataIn.length; i++) { trace = dataIn[i]; fullTrace = plots.supplyTraceDefaults(trace, cnt, fullLayout, i); fullTrace.index = i; fullTrace._input = trace; fullTrace._expandedIndex = cnt; if(fullTrace.transforms && fullTrace.transforms.length) { var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout); for(var j = 0; j < expandedTraces.length; j++) { var expandedTrace = expandedTraces[j], fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout, i); // mutate uid here using parent uid and expanded index // to promote consistency between update calls expandedTrace.uid = fullExpandedTrace.uid = fullTrace.uid + j; // add info about parent data trace fullExpandedTrace.index = i; fullExpandedTrace._input = trace; fullExpandedTrace._fullInput = fullTrace; // add info about the expanded data fullExpandedTrace._expandedIndex = cnt; fullExpandedTrace._expandedInput = expandedTrace; pushModule(fullExpandedTrace); } } else { // add identify refs for consistency with transformed traces fullTrace._fullInput = fullTrace; fullTrace._expandedInput = fullTrace; pushModule(fullTrace); } if(Registry.traceIs(fullTrace, 'carpetAxis')) { carpetIndex[fullTrace.carpet] = fullTrace; } if(Registry.traceIs(fullTrace, 'carpetDependent')) { carpetDependents.push(i); } } for(i = 0; i < carpetDependents.length; i++) { fullTrace = dataOut[carpetDependents[i]]; if(!fullTrace.visible) continue; var carpetAxis = carpetIndex[fullTrace.carpet]; fullTrace._carpet = carpetAxis; if(!carpetAxis || !carpetAxis.visible) { fullTrace.visible = false; continue; } fullTrace.xaxis = carpetAxis.xaxis; fullTrace.yaxis = carpetAxis.yaxis; } }
...
newFullLayout._initialAutoSizeIsDone = true;
// keep track of how many traces are inputted
newFullLayout._dataLength = newData.length;
// then do the data
newFullLayout._globalTransforms = (gd._context || {}).globalTransforms;
plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout);
// attach helper method to check whether a plot type is present on graph
newFullLayout._has = plots._hasPlotType.bind(newFullLayout);
// special cases that introduce interactions between traces
var _modules = newFullLayout._modules;
for(i = 0; i < _modules.length; i++) {
...
supplyDefaults = function (gd) { var oldFullLayout = gd._fullLayout || {}, newFullLayout = gd._fullLayout = {}, newLayout = gd.layout || {}; var oldFullData = gd._fullData || [], newFullData = gd._fullData = [], newData = gd.data || []; var i; // Create all the storage space for frames, but only if doesn't already exist if(!gd._transitionData) plots.createTransitionData(gd); // first fill in what we can of layout without looking at data // because fullData needs a few things from layout if(oldFullLayout._initialAutoSizeIsDone) { // coerce the updated layout while preserving width and height var oldWidth = oldFullLayout.width, oldHeight = oldFullLayout.height; plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); if(!newLayout.width) newFullLayout.width = oldWidth; if(!newLayout.height) newFullLayout.height = oldHeight; } else { // coerce the updated layout and autosize if needed plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout); var missingWidthOrHeight = (!newLayout.width || !newLayout.height), autosize = newFullLayout.autosize, autosizable = gd._context && gd._context.autosizable, initialAutoSize = missingWidthOrHeight && (autosize || autosizable); if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout); else if(missingWidthOrHeight) plots.sanitizeMargins(gd); // for backwards-compatibility with Plotly v1.x.x if(!autosize && missingWidthOrHeight) { newLayout.width = newFullLayout.width; newLayout.height = newFullLayout.height; } } newFullLayout._initialAutoSizeIsDone = true; // keep track of how many traces are inputted newFullLayout._dataLength = newData.length; // then do the data newFullLayout._globalTransforms = (gd._context || {}).globalTransforms; plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout); // attach helper method to check whether a plot type is present on graph newFullLayout._has = plots._hasPlotType.bind(newFullLayout); // special cases that introduce interactions between traces var _modules = newFullLayout._modules; for(i = 0; i < _modules.length; i++) { var _module = _modules[i]; if(_module.cleanData) _module.cleanData(newFullData); } if(oldFullData.length === newData.length) { for(i = 0; i < newFullData.length; i++) { relinkPrivateKeys(newFullData[i], oldFullData[i]); } } // finally, fill in the pieces of layout that may need to look at data plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData); // TODO remove in v2.0.0 // add has-plot-type refs to fullLayout for backward compatibility newFullLayout._hasCartesian = newFullLayout._has('cartesian'); newFullLayout._hasGeo = newFullLayout._has('geo'); newFullLayout._hasGL3D = newFullLayout._has('gl3d'); newFullLayout._hasGL2D = newFullLayout._has('gl2d'); newFullLayout._hasTernary = newFullLayout._has('ternary'); newFullLayout._hasPie = newFullLayout._has('pie'); // clean subplots and other artifacts from previous plot calls plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); // relink / initialize subplot axis objects plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout); // relink functions and _ attributes to promote consistency between plots relinkPrivateKeys(newFullLayout, oldFullLayout); // TODO may return a promise plots.doAutoMargin(gd); // set scale after auto margin routine var axList = Plotly.Axes.list(gd); for(i = 0; i < axList.length; i++) { var ax = axList[i]; ax.setScale(); } // update object references in calcdata if((gd.calcdata || []).length === newFullData.length) { for(i = 0; i < newFullData.length; i++) { var trace = newFu ...
...
gd._replotPending = true;
return Promise.reject();
} else {
// we're going ahead with a replot now
gd._replotPending = false;
}
Plots.supplyDefaults(gd);
var fullLayout = gd._fullLayout;
// Polar plots
if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
// so we don't try to re-call Plotly.plot from inside
...
supplyFrameDefaults = function (frameIn) { var frameOut = {}; function coerce(attr, dflt) { return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt); } coerce('group'); coerce('name'); coerce('traces'); coerce('baseframe'); coerce('data'); coerce('layout'); return frameOut; }
...
Lib.warn('addFrames: This API call has yielded too many warnings. ' +
'For the rest of this call, further warnings about numeric frame ' +
'names will be suppressed.');
}
}
insertions.push({
frame: Plots.supplyFrameDefaults(frameList[i]),
index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i
});
}
// Sort this, taking note that undefined insertions end up at the end:
insertions.sort(function(a, b) {
if(a.index > b.index) return -1;
...
supplyLayoutGlobalDefaults = function (layoutIn, layoutOut) { function coerce(attr, dflt) { return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt); } var globalFont = Lib.coerceFont(coerce, 'font'); coerce('title'); Lib.coerceFont(coerce, 'titlefont', { family: globalFont.family, size: Math.round(globalFont.size * 1.4), color: globalFont.color }); // Make sure that autosize is defaulted to *true* // on layouts with no set width and height for backward compatibly, // in particular https://plot.ly/javascript/responsive-fluid-layout/ // // Before https://github.com/plotly/plotly.js/pull/635 , // layouts with no set width and height were set temporary set to 'initial' // to pass through the autosize routine // // This behavior is subject to change in v2. coerce('autosize', !(layoutIn.width && layoutIn.height)); coerce('width'); coerce('height'); coerce('margin.l'); coerce('margin.r'); coerce('margin.t'); coerce('margin.b'); coerce('margin.pad'); coerce('margin.autoexpand'); if(layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut); coerce('paper_bgcolor'); coerce('separators'); coerce('hidesources'); coerce('smith'); var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); handleCalendarDefaults(layoutIn, layoutOut, 'calendar'); }
...
if(oldFullLayout._initialAutoSizeIsDone) {
// coerce the updated layout while preserving width and height
var oldWidth = oldFullLayout.width,
oldHeight = oldFullLayout.height;
plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout);
if(!newLayout.width) newFullLayout.width = oldWidth;
if(!newLayout.height) newFullLayout.height = oldHeight;
}
else {
// coerce the updated layout and autosize if needed
...
supplyLayoutModuleDefaults = function (layoutIn, layoutOut, fullData, transitionData) { var i, _module; // can't be be part of basePlotModules loop // in order to handle the orphan axes case Plotly.Axes.supplyLayoutDefaults(layoutIn, layoutOut, fullData); // base plot module layout defaults var basePlotModules = layoutOut._basePlotModules; for(i = 0; i < basePlotModules.length; i++) { _module = basePlotModules[i]; // done above already if(_module.name === 'cartesian') continue; // e.g. gl2d does not have a layout-defaults step if(_module.supplyLayoutDefaults) { _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); } } // trace module layout defaults var modules = layoutOut._modules; for(i = 0; i < modules.length; i++) { _module = modules[i]; if(_module.supplyLayoutDefaults) { _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); } } // transform module layout defaults var transformModules = layoutOut._transformModules; for(i = 0; i < transformModules.length; i++) { _module = transformModules[i]; if(_module.supplyLayoutDefaults) { _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData); } } // should FX be a component? Plotly.Fx.supplyLayoutDefaults(layoutIn, layoutOut, fullData); var components = Object.keys(Registry.componentsRegistry); for(i = 0; i < components.length; i++) { _module = Registry.componentsRegistry[components[i]]; if(_module.supplyLayoutDefaults) { _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData); } } }
...
if(oldFullData.length === newData.length) {
for(i = 0; i < newFullData.length; i++) {
relinkPrivateKeys(newFullData[i], oldFullData[i]);
}
}
// finally, fill in the pieces of layout that may need to look at data
plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData
);
// TODO remove in v2.0.0
// add has-plot-type refs to fullLayout for backward compatibility
newFullLayout._hasCartesian = newFullLayout._has('cartesian');
newFullLayout._hasGeo = newFullLayout._has('geo');
newFullLayout._hasGL3D = newFullLayout._has('gl3d');
newFullLayout._hasGL2D = newFullLayout._has('gl2d');
...
supplyTraceDefaults = function (traceIn, traceOutIndex, layout, traceInIndex) { var traceOut = {}, defaultColor = Color.defaults[traceOutIndex % Color.defaults.length]; function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt); } function coerceSubplotAttr(subplotType, subplotAttr) { if(!plots.traceIs(traceOut, subplotType)) return; return Lib.coerce(traceIn, traceOut, plots.subplotsRegistry[subplotType].attributes, subplotAttr); } var visible = coerce('visible'); coerce('type'); coerce('uid'); coerce('name', 'trace ' + traceInIndex); // coerce subplot attributes of all registered subplot types var subplotTypes = Object.keys(subplotsRegistry); for(var i = 0; i < subplotTypes.length; i++) { var subplotType = subplotTypes[i]; // done below (only when visible is true) // TODO unified this pattern if(['cartesian', 'gl2d'].indexOf(subplotType) !== -1) continue; var attr = subplotsRegistry[subplotType].attr; if(attr) coerceSubplotAttr(subplotType, attr); } if(visible) { var _module = plots.getModule(traceOut); traceOut._module = _module; // gets overwritten in pie, geo and ternary modules coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); if(plots.traceIs(traceOut, 'showLegend')) { coerce('showlegend'); coerce('legendgroup'); } // TODO add per-base-plot-module trace defaults step if(_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity'); coerceSubplotAttr('cartesian', 'xaxis'); coerceSubplotAttr('cartesian', 'yaxis'); coerceSubplotAttr('gl2d', 'xaxis'); coerceSubplotAttr('gl2d', 'yaxis'); if(plots.traceIs(traceOut, 'notLegendIsolatable')) { // This clears out the legendonly state for traces like carpet that // cannot be isolated in the legend traceOut.visible = !!traceOut.visible; } plots.supplyTransformDefaults(traceIn, traceOut, layout); } return traceOut; }
...
}
var carpetIndex = {};
var carpetDependents = [];
for(i = 0; i < dataIn.length; i++) {
trace = dataIn[i];
fullTrace = plots.supplyTraceDefaults(trace, cnt, fullLayout, i);
fullTrace.index = i;
fullTrace._input = trace;
fullTrace._expandedIndex = cnt;
if(fullTrace.transforms && fullTrace.transforms.length) {
var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout);
...
supplyTransformDefaults = function (traceIn, traceOut, layout) { var globalTransforms = layout._globalTransforms || []; var transformModules = layout._transformModules || []; if(!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return; var containerIn = traceIn.transforms || [], transformList = globalTransforms.concat(containerIn), containerOut = traceOut.transforms = []; for(var i = 0; i < transformList.length; i++) { var transformIn = transformList[i], type = transformIn.type, _module = transformsRegistry[type], transformOut; if(!_module) Lib.warn('Unrecognized transform type ' + type + '.'); if(_module && _module.supplyDefaults) { transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn); transformOut.type = type; transformOut._module = _module; Lib.pushUnique(transformModules, _module); } else { transformOut = Lib.extendFlat({}, transformIn); } containerOut.push(transformOut); } }
...
if(plots.traceIs(traceOut, 'notLegendIsolatable')) {
// This clears out the legendonly state for traces like carpet that
// cannot be isolated in the legend
traceOut.visible = !!traceOut.visible;
}
plots.supplyTransformDefaults(traceIn, traceOut, layout);
}
return traceOut;
};
plots.supplyTransformDefaults = function(traceIn, traceOut, layout) {
var globalTransforms = layout._globalTransforms || [];
...
traceIs = function (traceType, category) { traceType = getTraceType(traceType); // old plot.ly workspace hack, nothing to see here if(traceType === 'various') return false; var _module = exports.modules[traceType]; if(!_module) { if(traceType && traceType !== 'area') { Loggers.log('Unrecognized trace type ' + traceType + '.'); } _module = exports.modules[basePlotAttributes.type.dflt]; } return !!_module.categories[category]; }
...
delete trace.xbins;
}
// error_y.opacity is obsolete - merge into color
if(trace.error_y && 'opacity' in trace.error_y) {
var dc = Color.defaults,
yeColor = trace.error_y.color ||
(Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc
.length]);
trace.error_y.color = Color.addOpacity(
Color.rgb(yeColor),
Color.opacity(yeColor) * trace.error_y.opacity);
delete trace.error_y.opacity;
}
// convert bardir to orientation, and put the data into
...
transition = function (gd, data, layout, traces, frameOpts, transitionOpts) { var i, traceIdx; var dataLength = Array.isArray(data) ? data.length : 0; var traceIndices = traces.slice(0, dataLength); var transitionedTraces = []; function prepareTransitions() { var i; for(i = 0; i < traceIndices.length; i++) { var traceIdx = traceIndices[i]; var trace = gd._fullData[traceIdx]; var module = trace._module; // There's nothing to do if this module is not defined: if(!module) continue; // Don't register the trace as transitioned if it doens't know what to do. // If it *is* registered, it will receive a callback that it's responsible // for calling in order to register the transition as having completed. if(module.animatable) { transitionedTraces.push(traceIdx); } gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]); } // Follow the same procedure. Clone it so we don't mangle the input, then // expand any object paths so we can merge deep into gd.layout: var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout)); // Before merging though, we need to modify the incoming layout. We only // know how to *transition* layout ranges, so it's imperative that a new // range not be sent to the layout before the transition has started. So // we must remove the things we can transition: var axisAttrRe = /^[xy]axis[0-9]*$/; for(var attr in layoutUpdate) { if(!axisAttrRe.test(attr)) continue; delete layoutUpdate[attr].range; } plots.extendLayout(gd.layout, layoutUpdate); // Supply defaults after applying the incoming properties. Note that any attempt // to simplify this step and reduce the amount of work resulted in the reconstruction // of essentially the whole supplyDefaults step, so that it seems sensible to just use // supplyDefaults even though it's heavier than would otherwise be desired for // transitions: plots.supplyDefaults(gd); plots.doCalcdata(gd); ErrorBars.calc(gd); return Promise.resolve(); } function executeCallbacks(list) { var p = Promise.resolve(); if(!list) return p; while(list.length) { p = p.then((list.shift())); } return p; } function flushCallbacks(list) { if(!list) return; while(list.length) { list.shift(); } } var aborted = false; function executeTransitions() { gd.emit('plotly_transitioning', []); return new Promise(function(resolve) { // This flag is used to disabled things like autorange: gd._transitioning = true; // When instantaneous updates are coming through quickly, it's too much to simply disable // all interaction, so store this flag so we can disambiguate whether mouse interactions // should be fully disabled or not: if(transitionOpts.duration > 0) { gd._transitioningWithDuration = true; } // If another transition is triggered, this callback will be executed simply because it's // in the interruptCallbacks queue. If this transition completes, it will instead flush // that queue and forget about this callback. gd._transitionData._interruptCallbacks.push(function() { aborted = true; }); if(frameOpts.redraw) { gd._transitionData._interruptCallbacks.push(function() { return Plotly.redraw(gd); }); } // Emit this and make sure it happens last: gd._transitionData._interruptCallbacks.push(function() { gd.emit('plotly_transitioninterrupted', []); }); ...
...
.each(function(thisText) {
var note = d3.select(this);
note.append('button')
.classed('notifier-close', true)
.html('×')
.on('click', function() {
note.transition().call(killNote);
});
var p = note.append('p');
var lines = thisText.split(/<br\s*\/?>/g);
for(var i = 0; i < lines.length; i++) {
if(i) p.append('br');
p.append('span').text(lines[i]);
...
function filter(pts, tolerance) { var ptsFiltered = [pts[0]], doneRawIndex = 0, doneFilteredIndex = 0; function addPt(pt) { pts.push(pt); var prevFilterLen = ptsFiltered.length, iLast = doneRawIndex; ptsFiltered.splice(doneFilteredIndex + 1); for(var i = iLast + 1; i < pts.length; i++) { if(i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) { ptsFiltered.push(pts[i]); if(ptsFiltered.length < prevFilterLen - 2) { doneRawIndex = i; doneFilteredIndex = ptsFiltered.length - 1; } iLast = i; } } } if(pts.length > 1) { var lastPt = pts.pop(); addPt(lastPt); } return { addPt: addPt, raw: pts, filtered: ptsFiltered }; }
...
// Make a few changes to the data right away
// before it gets used for anything
exports.cleanData = function(data, existingData) {
// Enforce unique IDs
var suids = [], // seen uids --- so we can weed out incoming repeats
uids = data.concat(Array.isArray(existingData) ? existingData : [])
.filter(function(trace) { return 'uid' in trace; })
.map(function(trace) { return trace.uid; });
for(var tracei = 0; tracei < data.length; tracei++) {
var trace = data[tracei];
var i;
// assign uids to each trace and detect collisions.
...
function isBent(pts, start, end, tolerance) { var startPt = pts[start], segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]], segmentSquared = dot(segment, segment), segmentLen = Math.sqrt(segmentSquared), unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen], i, part, partParallel; for(i = start + 1; i < end; i++) { part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]]; partParallel = dot(part, segment); if(partParallel < 0 || partParallel > segmentSquared || Math.abs(dot(part, unitPerp)) > tolerance) return true; } return false; }
n/a
function tester(ptsIn) { var pts = ptsIn.slice(), xmin = pts[0][0], xmax = xmin, ymin = pts[0][1], ymax = ymin; pts.push(pts[0]); for(var i = 1; i < pts.length; i++) { xmin = Math.min(xmin, pts[i][0]); xmax = Math.max(xmax, pts[i][0]); ymin = Math.min(ymin, pts[i][1]); ymax = Math.max(ymax, pts[i][1]); } // do we have a rectangle? Handle this here, so we can use the same // tester for the rectangular case without sacrificing speed var isRect = false, rectFirstEdgeTest; if(pts.length === 5) { if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz if(pts[2][0] === pts[3][0] && pts[0][1] === pts[3][1] && pts[1][1] === pts[2][1]) { isRect = true; rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; }; } } else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert if(pts[2][1] === pts[3][1] && pts[0][0] === pts[3][0] && pts[1][0] === pts[2][0]) { isRect = true; rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; }; } } } function rectContains(pt, omitFirstEdge) { var x = pt[0], y = pt[1]; if(x < xmin || x > xmax || y < ymin || y > ymax) { // pt is outside the bounding box of polygon return false; } if(omitFirstEdge && rectFirstEdgeTest(pt)) return false; return true; } function contains(pt, omitFirstEdge) { var x = pt[0], y = pt[1]; if(x < xmin || x > xmax || y < ymin || y > ymax) { // pt is outside the bounding box of polygon return false; } var imax = pts.length, x1 = pts[0][0], y1 = pts[0][1], crossings = 0, i, x0, y0, xmini, ycross; for(i = 1; i < imax; i++) { // find all crossings of a vertical line upward from pt with // polygon segments // crossings exactly at xmax don't count, unless the point is // exactly on the segment, then it counts as inside. x0 = x1; y0 = y1; x1 = pts[i][0]; y1 = pts[i][1]; xmini = Math.min(x0, x1); // outside the bounding box of this segment, it's only a crossing // if it's below the box. if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) { continue; } else if(y < Math.min(y0, y1)) { // don't count the left-most point of the segment as a crossing // because we don't want to double-count adjacent crossings // UNLESS the polygon turns past vertical at exactly this x // Note that this is repeated below, but we can't factor it out // because if(x !== xmini) crossings++; } // inside the bounding box, check the actual line intercept else { // vertical segment - we know already that the point is exactly // on the segment, so mark the crossing as exactly at the point. if(x1 === x0) ycross = y; // any other angle else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0); // exactly on the edge: counts as inside the polygon, unless it's the // first edge and we're omitting it. if(y === ycross) { if(i === 1 && omitFirstEdge) return false; return true; } if(y <= ycross && x !== xmini) crossings++; } } // if we've gotten this far, odd crossings means inside, even is outside return crossings % 2 === 1; } return { xmin: xmin, ...
n/a
add = function (gd, undoFunc, undoArgs, redoFunc, redoArgs) { var queueObj, queueIndex; // make sure we have the queue and our position in it gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; queueIndex = gd.undoQueue.index; // if we're already playing an undo or redo, or if this is an auto operation // (like pane resize... any others?) then we don't save this to the undo queue if(gd.autoplay) { if(!gd.undoQueue.inSequence) gd.autoplay = false; return; } // if we're not in a sequence or are just starting, we need a new queue item if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) { queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}}; gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj); gd.undoQueue.index += 1; } else { queueObj = gd.undoQueue.queue[queueIndex - 1]; } gd.undoQueue.beginSequence = false; // we unshift to handle calls for undo in a forward for loop later if(queueObj) { queueObj.undo.calls.unshift(undoFunc); queueObj.undo.args.unshift(undoArgs); queueObj.redo.calls.push(redoFunc); queueObj.redo.args.push(redoArgs); } if(gd.undoQueue.queue.length > config.queueLength) { gd.undoQueue.queue.shift(); gd.undoQueue.index--; } }
...
if(calendar) {
try {
var dateJD = Math.round(ms / ONEDAY) + EPOCHJD,
calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar),
cDate = calInstance.fromJD(dateJD);
if(dMonth % 12) calInstance.add(cDate, dMonth, 'm');
else calInstance.add(cDate, dMonth / 12, 'y');
return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
}
catch(e) {
logError('invalid ms ' + ms + ' in calendar ' + calendar);
// then keep going in gregorian even though the result will be 'Invalid'
...
plotDo = function (gd, func, args) { gd.autoplay = true; // this *won't* copy gd and it preserves `undefined` properties! args = copyArgArray(gd, args); // call the supplied function func.apply(null, args); }
...
// get the queueObj for instructions on how to undo
queueObj = gd.undoQueue.queue[gd.undoQueue.index];
// this sequence keeps things from adding to the queue during undo/redo
gd.undoQueue.inSequence = true;
for(i = 0; i < queueObj.undo.calls.length; i++) {
queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]);
}
gd.undoQueue.inSequence = false;
gd.autoplay = false;
};
/**
* Redo the current object in the undo, then move forward in the queue.
...
function redo(gd) { var queueObj, i; if(gd.framework && gd.framework.isPolar) { gd.framework.redo(); return; } if(gd.undoQueue === undefined || isNaN(gd.undoQueue.index) || gd.undoQueue.index >= gd.undoQueue.queue.length) { return; } // get the queueObj for instructions on how to undo queueObj = gd.undoQueue.queue[gd.undoQueue.index]; // this sequence keeps things from adding to the queue during undo/redo gd.undoQueue.inSequence = true; for(i = 0; i < queueObj.redo.calls.length; i++) { queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]); } gd.undoQueue.inSequence = false; gd.autoplay = false; // index is pointing to the thing we just redid, move it gd.undoQueue.index++; }
...
*
* @param gd
*/
queue.redo = function redo(gd) {
var queueObj, i;
if(gd.framework && gd.framework.isPolar) {
gd.framework.redo();
return;
}
if(gd.undoQueue === undefined ||
isNaN(gd.undoQueue.index) ||
gd.undoQueue.index >= gd.undoQueue.queue.length) {
return;
}
...
startSequence = function (gd) { gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; gd.undoQueue.sequence = true; gd.undoQueue.beginSequence = true; }
...
// something went wrong, reset gd to be safe and rethrow error
gd.data.splice(gd.data.length - traces.length, traces.length);
throw error;
}
// if we're here, the user has defined specific places to place the new traces
// this requires some extra work that moveTraces will do
Queue.startSequence(gd);
Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
promise = Plotly.moveTraces(gd, currentIndices, newIndices);
Queue.stopSequence(gd);
return promise;
};
/**
...
stopSequence = function (gd) { gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false}; gd.undoQueue.sequence = false; gd.undoQueue.beginSequence = false; }
...
}
// if we're here, the user has defined specific places to place the new traces
// this requires some extra work that moveTraces will do
Queue.startSequence(gd);
Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
promise = Plotly.moveTraces(gd, currentIndices, newIndices);
Queue.stopSequence(gd);
return promise;
};
/**
* Delete traces at `indices` from gd.data array.
*
* @param {Object|HTMLDivElement} gd The graph div
...
function undo(gd) { var queueObj, i; if(gd.framework && gd.framework.isPolar) { gd.framework.undo(); return; } if(gd.undoQueue === undefined || isNaN(gd.undoQueue.index) || gd.undoQueue.index <= 0) { return; } // index is pointing to next *forward* queueObj, point to the one we're undoing gd.undoQueue.index--; // get the queueObj for instructions on how to undo queueObj = gd.undoQueue.queue[gd.undoQueue.index]; // this sequence keeps things from adding to the queue during undo/redo gd.undoQueue.inSequence = true; for(i = 0; i < queueObj.undo.calls.length; i++) { queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]); } gd.undoQueue.inSequence = false; gd.autoplay = false; }
...
*
* @param gd
*/
queue.undo = function undo(gd) {
var queueObj, i;
if(gd.framework && gd.framework.isPolar) {
gd.framework.undo();
return;
}
if(gd.undoQueue === undefined ||
isNaN(gd.undoQueue.index) ||
gd.undoQueue.index <= 0) {
return;
}
...
getComponentMethod = function (name, method) { var _module = exports.componentsRegistry[name]; if(!_module) return noop; return _module[method] || noop; }
...
* dateTick0: get the canonical tick for this calendar
*
* bool sunday is for week ticks, shift it to a Sunday.
*/
exports.dateTick0 = function(calendar, sunday) {
if(isWorldCalendar(calendar)) {
return sunday ?
Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY
')[calendar] :
Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar];
}
else {
return sunday ? '2000-01-02' : '2000-01-01';
}
};
...
getModule = function (trace) { if(trace.r !== undefined) { Loggers.warn('Tried to put a polar trace ' + 'on an incompatible graph of cartesian ' + 'data. Ignoring this dataset.', trace ); return false; } var _module = exports.modules[getTraceType(trace)]; if(!_module) return false; return _module._module; }
...
var attr = subplotsRegistry[subplotType].attr;
if(attr) coerceSubplotAttr(subplotType, attr);
}
if(visible) {
var _module = plots.getModule(traceOut);
traceOut._module = _module;
// gets overwritten in pie, geo and ternary modules
coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined);
if(plots.traceIs(traceOut, 'showLegend')) {
coerce('showlegend');
...
register = function (_module, thisType, categoriesIn, meta) { if(exports.modules[thisType]) { Loggers.log('Type ' + thisType + ' already registered'); return; } var categoryObj = {}; for(var i = 0; i < categoriesIn.length; i++) { categoryObj[categoriesIn[i]] = true; exports.allCategories[categoriesIn[i]] = true; } exports.modules[thisType] = { _module: _module, categories: categoryObj }; if(meta && Object.keys(meta).length) { exports.modules[thisType].meta = meta; } exports.allTypes.push(thisType); }
...
If you would like to manually pick which plotly.js modules to include, you can create a *custom* bundle by using `plotly.js/lib/
core`, and loading only the trace types that you need (e.g. `pie` or `choropleth`). The recommended way to do this is by creating
a *bundling file*:
```javascript
// in custom-plotly.js
var Plotly = require('plotly.js/lib/core');
// Load in the trace types for pie, and choropleth
Plotly.register([
require('plotly.js/lib/pie'),
require('plotly.js/lib/choropleth')
]);
module.exports = Plotly;
```
...
registerComponent = function (_module) { var name = _module.name; exports.componentsRegistry[name] = _module; if(_module.layoutAttributes) { if(_module.layoutAttributes._isLinkedToArray) { pushUnique(exports.layoutArrayContainers, name); } findArrayRegexps(_module); } }
...
}
function registerComponentModule(newModule) {
if(typeof newModule.name !== 'string') {
throw new Error('Component module *name* must be a string.');
}
Registry.registerComponent(newModule);
}
...
registerSubplot = function (_module) { var plotType = _module.name; if(exports.subplotsRegistry[plotType]) { Loggers.log('Plot type ' + plotType + ' already registered.'); return; } // relayout array handling will look for component module methods with this // name and won't find them because this is a subplot module... but that // should be fine, it will just fall back on redrawing the plot. findArrayRegexps(_module); // not sure what's best for the 'cartesian' type at this point exports.subplotsRegistry[plotType] = _module; }
...
}
};
function registerTraceModule(newModule) {
Registry.register(newModule, newModule.name, newModule.categories, newModule.meta);
if(!Registry.subplotsRegistry[newModule.basePlotModule.name]) {
Registry.registerSubplot(newModule.basePlotModule);
}
}
function registerTransformModule(newModule) {
if(typeof newModule.name !== 'string') {
throw new Error('Transform module *name* must be a string.');
}
...
traceIs = function (traceType, category) { traceType = getTraceType(traceType); // old plot.ly workspace hack, nothing to see here if(traceType === 'various') return false; var _module = exports.modules[traceType]; if(!_module) { if(traceType && traceType !== 'area') { Loggers.log('Unrecognized trace type ' + traceType + '.'); } _module = exports.modules[basePlotAttributes.type.dflt]; } return !!_module.categories[category]; }
...
delete trace.xbins;
}
// error_y.opacity is obsolete - merge into color
if(trace.error_y && 'opacity' in trace.error_y) {
var dc = Color.defaults,
yeColor = trace.error_y.color ||
(Registry.traceIs(trace, 'bar') ? Color.defaultLine : dc[tracei % dc
.length]);
trace.error_y.color = Color.addOpacity(
Color.rgb(yeColor),
Color.opacity(yeColor) * trace.error_y.opacity);
delete trace.error_y.opacity;
}
// convert bardir to orientation, and put the data into
...
function ScrollBox(gd, container, id) { this.gd = gd; this.container = container; this.id = id; // See ScrollBox.prototype.enable for further definition this.position = null; // scrollbox position this.translateX = null; // scrollbox horizontal translation this.translateY = null; // scrollbox vertical translation this.hbar = null; // horizontal scrollbar D3 selection this.vbar = null; // vertical scrollbar D3 selection // <rect> element to capture pointer events this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]); this.bg.exit() .on('.drag', null) .on('wheel', null) .remove(); this.bg.enter().append('rect') .classed('scrollbox-bg', true) .style('pointer-events', 'all') .attr({ opacity: 0, x: 0, y: 0, width: 0, height: 0 }); }
n/a
function onBarDrag() { var translateX = this.translateX, translateY = this.translateY; if(this.hbar) { var xMin = translateX + this._hbarXMin, xMax = xMin + this._hbarTranslateMax, x = Lib.constrain(d3.event.x, xMin, xMax), xf = (x - xMin) / (xMax - xMin); var translateXMax = this.position.w - this._box.w; translateX = xf * translateXMax; } if(this.vbar) { var yMin = translateY + this._vbarYMin, yMax = yMin + this._vbarTranslateMax, y = Lib.constrain(d3.event.y, yMin, yMax), yf = (y - yMin) / (yMax - yMin); var translateYMax = this.position.h - this._box.h; translateY = yf * translateYMax; } this.setTranslate(translateX, translateY); }
n/a
function onBarDrag() { var translateX = this.translateX, translateY = this.translateY; if(this.hbar) { translateX -= d3.event.dx; } if(this.vbar) { translateY -= d3.event.dy; } this.setTranslate(translateX, translateY); }
n/a
function onBarWheel() { var translateX = this.translateX, translateY = this.translateY; if(this.hbar) { translateX += d3.event.deltaY; } if(this.vbar) { translateY += d3.event.deltaY; } this.setTranslate(translateX, translateY); }
n/a
function disable() { if(this.hbar || this.vbar) { this.bg.attr({ width: 0, height: 0 }); this.container .on('wheel', null) .on('.drag', null) .call(Drawing.setClipUrl, null); delete this._clipRect; } if(this.hbar) { this.hbar.on('.drag', null); this.hbar.remove(); delete this.hbar; delete this._hbarXMin; delete this._hbarTranslateMax; } if(this.vbar) { this.vbar.on('.drag', null); this.vbar.remove(); delete this.vbar; delete this._vbarYMin; delete this._vbarTranslateMax; } }
...
var update = bindingValueHasChanged(gd, binding, ret.cache);
if(update.changed && onchange) {
// Disable checks for the duration of this command in order to avoid
// infinite loops:
if(ret.lookupTable[update.value] !== undefined) {
ret.disable();
Promise.resolve(onchange({
value: update.value,
type: binding.type,
prop: binding.prop,
traces: binding.traces,
index: ret.lookupTable[update.value]
})).then(ret.enable, ret.enable);
...
function enable(position, translateX, translateY) { var fullLayout = this.gd._fullLayout, fullWidth = fullLayout.width, fullHeight = fullLayout.height; // compute position of scrollbox this.position = position; var l = this.position.l, w = this.position.w, t = this.position.t, h = this.position.h, direction = this.position.direction, isDown = (direction === 'down'), isLeft = (direction === 'left'), isRight = (direction === 'right'), isUp = (direction === 'up'), boxW = w, boxH = h, boxL, boxR, boxT, boxB; if(!isDown && !isLeft && !isRight && !isUp) { this.position.direction = 'down'; isDown = true; } var isVertical = isDown || isUp; if(isVertical) { boxL = l; boxR = boxL + boxW; if(isDown) { // anchor to top side boxT = t; boxB = Math.min(boxT + boxH, fullHeight); boxH = boxB - boxT; } else { // anchor to bottom side boxB = t + boxH; boxT = Math.max(boxB - boxH, 0); boxH = boxB - boxT; } } else { boxT = t; boxB = boxT + boxH; if(isLeft) { // anchor to right side boxR = l + boxW; boxL = Math.max(boxR - boxW, 0); boxW = boxR - boxL; } else { // anchor to left side boxL = l; boxR = Math.min(boxL + boxW, fullWidth); boxW = boxR - boxL; } } this._box = { l: boxL, t: boxT, w: boxW, h: boxH }; // compute position of horizontal scroll bar var needsHorizontalScrollBar = (w > boxW), hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad, hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad, // draw horizontal scrollbar on the bottom side hbarL = l, hbarT = t + h; if(hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH; var hbar = this.container.selectAll('rect.scrollbar-horizontal').data( (needsHorizontalScrollBar) ? [0] : []); hbar.exit() .on('.drag', null) .remove(); hbar.enter().append('rect') .classed('scrollbar-horizontal', true) .call(Color.fill, ScrollBox.barColor); if(needsHorizontalScrollBar) { this.hbar = hbar.attr({ 'rx': ScrollBox.barRadius, 'ry': ScrollBox.barRadius, 'x': hbarL, 'y': hbarT, 'width': hbarW, 'height': hbarH }); // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax this._hbarXMin = hbarL + hbarW / 2; this._hbarTranslateMax = boxW - hbarW; } else { delete this.hbar; delete this._hbarXMin; delete this._hbarTranslateMax; } // compute position of vertical scroll bar var needsVerticalScrollBar = (h > boxH), vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad, vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad, // draw vertical scrollbar on the right side vbarL = l + w, vbarT = t; if(vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW; var vbar = this.container.selectAll('rect.scrollbar-vertical').data( (needsVerticalScrollBar) ? [0] : []); vbar.exit() .on('.drag', null) .remove(); vbar.enter().append('rect') .classed('scrollbar-vertical', true) .call(Color.fill, ScrollBox.barColor); if(needsVerticalScrollBar) { this.vbar = vbar.attr({ 'rx': ScrollBox.barRadius, 'ry': ScrollBox.barRadius, 'x': vbarL, 'y': vbarT, 'width': vbarW, 'height': vbarH }); // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax this._vbarYMin = vbarT + vbarH / 2; this._vbarTranslateMax = boxH - vbarH; } else { d ...
...
height: 1,
data: dummyPixel
});
}
function clear(regl, x, y, width, height) {
var gl = regl._gl;
gl.enable(gl.SCISSOR_TEST);
gl.scissor(x, y, width, height);
regl.clear({color: [0, 0, 0, 0], depth: 1}); // clearing is done in scissored panel only
}
function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item) {
var rafKey = item.key;
...
function setTranslate(translateX, translateY) { // store translateX and translateY (needed by mouse event handlers) var translateXMax = this.position.w - this._box.w, translateYMax = this.position.h - this._box.h; translateX = Lib.constrain(translateX || 0, 0, translateXMax); translateY = Lib.constrain(translateY || 0, 0, translateYMax); this.translateX = translateX; this.translateY = translateY; this.container.call(Drawing.setTranslate, this._box.l - this.position.l - translateX, this._box.t - this.position.t - translateY); if(this._clipRect) { this._clipRect.attr({ x: Math.floor(this.position.l + translateX - 0.5), y: Math.floor(this.position.t + translateY - 0.5) }); } if(this.hbar) { var xf = translateX / translateXMax; this.hbar.call(Drawing.setTranslate, translateX + xf * this._hbarTranslateMax, translateY); } if(this.vbar) { var yf = translateY / translateYMax; this.vbar.call(Drawing.setTranslate, translateX, translateY + yf * this._vbarTranslateMax); } }
...
this.vbar
.on('.drag', null)
.call(onBarDrag);
}
}
// set scrollbox translation
this.setTranslate(translateX, translateY);
};
/**
* If present, remove clip-path and scrollbars
*
* @method
*/
...
distinctVals = function (valsIn) { var vals = valsIn.slice(); // otherwise we sort the original array... vals.sort(exports.sorterAsc); var l = vals.length - 1, minDiff = (vals[l] - vals[0]) || 1, errDiff = minDiff / (l || 1) / 10000, v2 = [vals[0]]; for(var i = 0; i < l; i++) { // make sure values aren't just off by a rounding error if(vals[i + 1] > vals[i] + errDiff) { minDiff = Math.min(minDiff, vals[i + 1] - vals[i]); v2.push(vals[i + 1]); } } return {vals: v2, minDiff: minDiff}; }
...
var size0;
if(nbins) size0 = ((dataMax - dataMin) / nbins);
else {
// totally auto: scale off std deviation so the highest bin is
// somewhat taller than the total number of bins, but don't let
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
...
findBin = function (val, bins, linelow) { if(isNumeric(bins.start)) { return linelow ? Math.ceil((val - bins.start) / bins.size) - 1 : Math.floor((val - bins.start) / bins.size); } else { var n1 = 0, n2 = bins.length, c = 0, n, test; if(bins[bins.length - 1] >= bins[0]) { test = linelow ? lessThan : lessOrEqual; } else { test = linelow ? greaterOrEqual : greaterThan; } // c is just to avoid infinite loops if there's an error while(n1 < n2 && c++ < 100) { n = Math.floor((n1 + n2) / 2); if(test(bins[n], val)) n1 = n + 1; else n2 = n; } if(c > 90) loggers.log('Long binary search...'); return n1 - 1; } }
...
var i1, i2, text;
if(hasColumnText) text = Lib.init2dArray(col2vals.length, col1vals.length);
for(i = 0; i < colLen; i++) {
if(col1[i] !== BADNUM && col2[i] !== BADNUM) {
i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals);
i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals);
for(j = 0; j < arrayVarNames.length; j++) {
arrayVarName = arrayVarNames[j];
arrayVar = trace[arrayVarName];
newArray = newArrays[j];
newArray[i2][i1] = arrayVar[i];
...
roundUp = function (val, arrayIn, reverse) { var low = 0, high = arrayIn.length - 1, mid, c = 0, dlow = reverse ? 0 : 1, dhigh = reverse ? 1 : 0, rounded = reverse ? Math.ceil : Math.floor; // c is just to avoid infinite loops if there's an error while(low < high && c++ < 100) { mid = rounded((low + high) / 2); if(arrayIn[mid] <= val) low = mid + dlow; else high = mid - dhigh; } return arrayIn[low]; }
...
// totally auto: scale off std deviation so the highest bin is
// somewhat taller than the total number of bins, but don't let
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
// fallback if ax.d2c output BADNUMs
// e.g. when user try to plot categorical bins
// on a layout.xaxis.type: 'linear'
...
sorterAsc = function (a, b) { return a - b; }
n/a
sorterDes = function (a, b) { return b - a; }
n/a
function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) { this.traces = traces; this.separateNegativeValues = separateNegativeValues; this.dontMergeOverlappingData = dontMergeOverlappingData; var positions = []; for(var i = 0; i < traces.length; i++) { var trace = traces[i]; for(var j = 0; j < trace.length; j++) { var bar = trace[j]; if(bar.p !== BADNUM) positions.push(bar.p); } } this.positions = positions; var dv = Lib.distinctVals(this.positions); this.distinctPositions = dv.vals; this.minDiff = dv.minDiff; this.binWidth = this.minDiff; this.bins = {}; }
n/a
function put(position, value) { var label = this.getLabel(position, value); return this.bins[label] || 0; }
...
* - min, max: (number, integer only) inclusive bounds on allowed vals
* either or both may be omitted
* - dflt: if attribute is invalid or missing, use this default
* if dflt is provided as an argument to lib.coerce it takes precedence
* as a convenience, returns the value it finally set
*/
exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt) {
var opts = nestedProperty(attributes, attribute).get(),
propIn = nestedProperty(containerIn, attribute),
propOut = nestedProperty(containerOut, attribute),
v = propIn.get();
if(dflt === undefined) dflt = opts.dflt;
/**
...
function getLabel(position, value) { var prefix = (value < 0 && this.separateNegativeValues) ? 'v' : '^', label = (this.dontMergeOverlappingData) ? position : Math.round(position / this.binWidth); return prefix + label; }
...
*
* @method
* @param {number} position
* @param {number} value
* @returns {number} Previous bin value
*/
Sieve.prototype.put = function put(position, value) {
var label = this.getLabel(position, value),
oldValue = this.bins[label] || 0;
this.bins[label] = oldValue + value;
return oldValue;
};
...
function put(position, value) { var label = this.getLabel(position, value), oldValue = this.bins[label] || 0; this.bins[label] = oldValue + value; return oldValue; }
...
for(j = 0; j < trace.length; j++) {
bar = trace[j];
if(bar.s === BADNUM) continue;
// stack current bar and get previous sum
var barBase = sieve.put(bar.p, bar.b + bar.s),
barTop = barBase + bar.b + bar.s;
// store the bar base and top in each calcdata item
bar.b = barBase;
bar[sLetter] = barTop;
if(!barnorm) {
...
aggNums = function (f, v, a, len) { var i, b; if(!len) len = a.length; if(!isNumeric(v)) v = false; if(Array.isArray(a[0])) { b = new Array(len); for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]); a = b; } for(i = 0; i < len; i++) { if(!isNumeric(v)) v = a[i]; else if(isNumeric(a[i])) v = f(+v, +a[i]); } return v; }
...
exports.aggNums = function(f, v, a, len) {
var i,
b;
if(!len) len = a.length;
if(!isNumeric(v)) v = false;
if(Array.isArray(a[0])) {
b = new Array(len);
for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
a = b;
}
for(i = 0; i < len; i++) {
if(!isNumeric(v)) v = a[i];
else if(isNumeric(a[i])) v = f(+v, +a[i]);
}
...
interp = function (arr, n) { if(!isNumeric(n)) throw 'n should be a finite number'; n = n * arr.length - 0.5; if(n < 0) return arr[0]; if(n > arr.length - 1) return arr[arr.length - 1]; var frac = n % 1; return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)]; }
n/a
len = function (data) { return exports.aggNums(function(a) { return a + 1; }, 0, data); }
...
* even need to use aggNums instead of .length, to toss out non-numerics
*/
exports.len = function(data) {
return exports.aggNums(function(a) { return a + 1; }, 0, data);
};
exports.mean = function(data, len) {
if(!len) len = exports.len(data);
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};
exports.variance = function(data, len, mean) {
if(!len) len = exports.len(data);
if(!isNumeric(mean)) mean = exports.mean(data, len);
...
mean = function (data, len) { if(!len) len = exports.len(data); return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len; }
...
exports.mean = function(data, len) {
if(!len) len = exports.len(data);
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};
exports.variance = function(data, len, mean) {
if(!len) len = exports.len(data);
if(!isNumeric(mean)) mean = exports.mean(data, len);
return exports.aggNums(function(a, b) {
return a + Math.pow(b - mean, 2);
}, 0, data) / len;
};
exports.stdev = function(data, len, mean) {
...
stdev = function (data, len, mean) { return Math.sqrt(exports.variance(data, len, mean)); }
...
// the size get smaller than the 'nice' rounded down minimum
// difference between values
var distinctData = Lib.distinctVals(data),
msexp = Math.pow(10, Math.floor(
Math.log(distinctData.minDiff) / Math.LN10)),
minSize = msexp * Lib.roundUp(
distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
size0 = Math.max(minSize, 2 * Lib.stdev(data) /
Math.pow(data.length, is2d ? 0.25 : 0.4));
// fallback if ax.d2c output BADNUMs
// e.g. when user try to plot categorical bins
// on a layout.xaxis.type: 'linear'
if(!isNumeric(size0)) size0 = 1;
}
...
variance = function (data, len, mean) { if(!len) len = exports.len(data); if(!isNumeric(mean)) mean = exports.mean(data, len); return exports.aggNums(function(a, b) { return a + Math.pow(b - mean, 2); }, 0, data) / len; }
...
return exports.aggNums(function(a, b) {
return a + Math.pow(b - mean, 2);
}, 0, data) / len;
};
exports.stdev = function(data, len, mean) {
return Math.sqrt(exports.variance(data, len, mean));
};
/**
* interp() computes a percentile (quantile) for a given distribution.
* We interpolate the distribution (to compute quantiles, we follow method #10 here:
* http://www.amstat.org/publications/jse/v14n3/langford.html).
* Typically the index or rank (n * arr.length) may be non-integer.
...
doCamera = function (gd) { var fullLayout = gd._fullLayout, sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d'); for(var i = 0; i < sceneIds.length; i++) { var sceneLayout = fullLayout[sceneIds[i]], scene = sceneLayout._scene; scene.setCamera(sceneLayout.camera); } }
n/a
doColorBars = function (gd) { for(var i = 0; i < gd.calcdata.length; i++) { var cdi0 = gd.calcdata[i][0]; if((cdi0.t || {}).cb) { var trace = cdi0.trace, cb = cdi0.t.cb; if(Registry.traceIs(trace, 'contour')) { cb.line({ width: trace.contours.showlines !== false ? trace.line.width : 0, dash: trace.line.dash, color: trace.contours.coloring === 'line' ? cb._opts.line.color : trace.line.color }); } if(Registry.traceIs(trace, 'markerColorscale')) { cb.options(trace.marker.colorbar)(); } else cb.options(trace.colorbar)(); } } return Plots.previousPromises(gd); }
n/a
doLegend = function (gd) { Registry.getComponentMethod('legend', 'draw')(gd); return Plots.previousPromises(gd); }
n/a
doModeBar = function (gd) { var fullLayout = gd._fullLayout; var subplotIds, i; ModeBar.manage(gd); Plotly.Fx.init(gd); subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d'); for(i = 0; i < subplotIds.length; i++) { var scene = fullLayout[subplotIds[i]]._scene; scene.updateFx(fullLayout.dragmode, fullLayout.hovermode); } // no need to do this for gl2d subplots, // Plots.linkSubplots takes care of it all. return Plots.previousPromises(gd); }
n/a
doTicksRelayout = function (gd) { Plotly.Axes.doTicks(gd, 'redraw'); exports.drawMainTitle(gd); return Plots.previousPromises(gd); }
n/a
doTraceStyle = function (gd) { for(var i = 0; i < gd.calcdata.length; i++) { var cdi = gd.calcdata[i], _module = ((cdi[0] || {}).trace || {})._module || {}, arraysToCalcdata = _module.arraysToCalcdata; if(arraysToCalcdata) arraysToCalcdata(cdi, cdi[0].trace); } Plots.style(gd); Registry.getComponentMethod('legend', 'draw')(gd); return Plots.previousPromises(gd); }
n/a
drawMainTitle = function (gd) { var fullLayout = gd._fullLayout; Titles.draw(gd, 'gtitle', { propContainer: fullLayout, propName: 'title', dfltName: 'Plot', attributes: { x: fullLayout.width / 2, y: fullLayout._size.t / 2, 'text-anchor': 'middle' } }); }
...
// mark free axes as displayed, so we don't draw them again
if(showfreex) { freefinished.push(xa._id); }
if(showfreey) { freefinished.push(ya._id); }
});
Plotly.Axes.makeClipPaths(gd);
exports.drawMainTitle(gd);
ModeBar.manage(gd);
return gd._promises.length && Promise.all(gd._promises);
};
exports.drawMainTitle = function(gd) {
var fullLayout = gd._fullLayout;
...
layoutReplot = function (gd) { var layout = gd.layout; gd.layout = undefined; return Plotly.plot(gd, '', layout); }
n/a
layoutStyles = function (gd) { return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd); }
n/a
lsInner = function (gd) { var fullLayout = gd._fullLayout, gs = fullLayout._size, axList = Plotly.Axes.list(gd), i; // clear axis line positions, to be set in the subplot loop below for(i = 0; i < axList.length; i++) axList[i]._linepositions = {}; fullLayout._paperdiv .style({ width: fullLayout.width + 'px', height: fullLayout.height + 'px' }) .selectAll('.main-svg') .call(Drawing.setSize, fullLayout.width, fullLayout.height); gd._context.setBackground(gd, fullLayout.paper_bgcolor); var subplotSelection = fullLayout._paper.selectAll('g.subplot'); // figure out which backgrounds we need to draw, and in which layers // to put them var lowerBackgroundIDs = []; var lowerDomains = []; subplotSelection.each(function(subplot) { var plotinfo = fullLayout._plots[subplot]; if(plotinfo.mainplot) { // mainplot is a reference to the main plot this one is overlaid on // so if it exists, this is an overlaid plot and we don't need to // give it its own background if(plotinfo.bg) { plotinfo.bg.remove(); } plotinfo.bg = undefined; return; } var xa = Plotly.Axes.getFromId(gd, subplot, 'x'), ya = Plotly.Axes.getFromId(gd, subplot, 'y'), xDomain = xa.domain, yDomain = ya.domain, plotgroupBgData = []; if(overlappingDomain(xDomain, yDomain, lowerDomains)) { plotgroupBgData = [0]; } else { lowerBackgroundIDs.push(subplot); lowerDomains.push([xDomain, yDomain]); } // create the plot group backgrounds now, since // they're all independent selections var plotgroupBg = plotinfo.plotgroup.selectAll('.bg') .data(plotgroupBgData); plotgroupBg.enter().append('rect') .classed('bg', true); plotgroupBg.exit().remove(); plotgroupBg.each(function() { plotinfo.bg = plotgroupBg; var pgNode = plotinfo.plotgroup.node(); pgNode.insertBefore(this, pgNode.childNodes[0]); }); }); // now create all the lower-layer backgrounds at once now that // we have the list of subplots that need them var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg') .data(lowerBackgroundIDs); lowerBackgrounds.enter().append('rect') .classed('bg', true); lowerBackgrounds.exit().remove(); lowerBackgrounds.each(function(subplot) { fullLayout._plots[subplot].bg = d3.select(this); }); var freefinished = []; subplotSelection.each(function(subplot) { var plotinfo = fullLayout._plots[subplot], xa = Plotly.Axes.getFromId(gd, subplot, 'x'), ya = Plotly.Axes.getFromId(gd, subplot, 'y'); // reset scale in case the margins have changed xa.setScale(); ya.setScale(); if(plotinfo.bg) { plotinfo.bg .call(Drawing.setRect, xa._offset - gs.p, ya._offset - gs.p, xa._length + 2 * gs.p, ya._length + 2 * gs.p) .call(Color.fill, fullLayout.plot_bgcolor) .style('stroke-width', 0); } // Clip so that data only shows up on the plot area. plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; var plotClip = fullLayout._defs.selectAll('g.clips') .selectAll('#' + plotinfo.clipId) .data([0]); plotClip.enter().append('clipPath') .attr({ 'class': 'plotclip', 'id': plotinfo.clipId }) .append('rect'); plotClip.selectAll('rect') .attr({ 'width': xa._length, 'height': ya._length }); plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset); plotinfo.plot.call(Drawing.setClipUrl, ...
n/a
hasLines = function (trace) { return trace.visible && trace.mode && trace.mode.indexOf('lines') !== -1; }
...
var hasColorscale = require('../../components/colorscale/has_colorscale');
var calcColorscale = require('../../components/colorscale/calc');
var subTypes = require('./subtypes');
module.exports = function calcMarkerColorscale(trace) {
if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) {
calcColorscale(trace, trace.line.color, 'line', 'c');
}
if(subTypes.hasMarkers(trace)) {
if(hasColorscale(trace, 'marker')) {
calcColorscale(trace, trace.marker.color, 'marker', 'c');
}
...
hasMarkers = function (trace) { return trace.visible && trace.mode && trace.mode.indexOf('markers') !== -1; }
...
var keyFunc;
if(trace.ids) {
keyFunc = function(d) {return d.id;};
}
var sparse = (
subTypes.hasMarkers(trace) &&
trace.marker.maxdisplayed > 0
);
if(!yObj.visible && !xObj.visible) return;
var errorbars = d3.select(this).selectAll('g.errorbar')
.data(d, keyFunc);
...
hasText = function (trace) { return trace.visible && trace.mode && trace.mode.indexOf('text') !== -1; }
...
if(selectable) break;
var trace = fullData[i];
if(!trace._module || !trace._module.selectPoints) continue;
if(trace.type === 'scatter' || trace.type === 'scatterternary') {
if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
selectable = true;
}
}
// assume that in general if the trace module has selectPoints,
// then it's selectable. Scatter is an exception to this because it must
// have markers or text, not just be a scatter type.
else selectable = true;
...
isBubble = function (trace) { return Lib.isPlainObject(trace.marker) && Array.isArray(trace.marker.size); }
...
var hasColorscale = require('../../components/colorscale/has_colorscale');
var colorscaleDefaults = require('../../components/colorscale/defaults');
var subTypes = require('./subtypes');
module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
var isBubble = subTypes.isBubble(traceIn),
lineColor = (traceIn.line || {}).color,
defaultMLC;
// marker.color inherit from line.color (even if line.color is an array)
if(lineColor) defaultColor = lineColor;
coerce('marker.symbol');
...
convertToTspans = function (_context, _callback) { var str = _context.text(); var converted = convertToSVG(str); var that = _context; // Until we get tex integrated more fully (so it can be used along with non-tex) // allow some elements to prohibit it by attaching 'data-notex' to the original var tex = (!that.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/); var result = str; var parent = d3.select(that.node().parentNode); if(parent.empty()) return; var svgClass = (that.attr('class')) ? that.attr('class').split(' ')[0] : 'text'; svgClass += '-math'; parent.selectAll('svg.' + svgClass).remove(); parent.selectAll('g.' + svgClass + '-group').remove(); _context.style({visibility: null}); for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) { up.removeAttribute('data-bb'); } function showText() { if(!parent.empty()) { svgClass = that.attr('class') + '-math'; parent.select('svg.' + svgClass).remove(); } _context.text('') .style({ visibility: 'inherit', 'white-space': 'pre' }); result = _context.appendSVG(converted); if(!result) _context.text(str); if(_context.select('a').size()) { // at least in Chrome, pointer-events does not seem // to be honored in children of <text> elements // so if we have an anchor, we have to make the // whole element respond _context.style('pointer-events', 'all'); } if(_callback) _callback.call(that); } if(tex) { var gd = Lib.getPlotDiv(that.node()); ((gd && gd._promises) || []).push(new Promise(function(resolve) { that.style({visibility: 'hidden'}); var config = {fontSize: parseInt(that.style('font-size'), 10)}; texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) { parent.selectAll('svg.' + svgClass).remove(); parent.selectAll('g.' + svgClass + '-group').remove(); var newSvg = _svgEl && _svgEl.select('svg'); if(!newSvg || !newSvg.node()) { showText(); resolve(); return; } var mathjaxGroup = parent.append('g') .classed(svgClass + '-group', true) .attr({'pointer-events': 'none'}); mathjaxGroup.node().appendChild(newSvg.node()); // stitch the glyph defs if(_glyphDefs && _glyphDefs.node()) { newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true), newSvg.node().firstChild); } newSvg.attr({ 'class': svgClass, height: _svgBBox.height, preserveAspectRatio: 'xMinYMin meet' }) .style({overflow: 'visible', 'pointer-events': 'none'}); var fill = that.style('fill') || 'black'; newSvg.select('g').attr({fill: fill, stroke: fill}); var newSvgW = getSize(newSvg, 'width'), newSvgH = getSize(newSvg, 'height'), newX = +that.attr('x') - newSvgW * {start: 0, middle: 0.5, end: 1}[that.attr('text-anchor') || 'start'], // font baseline is about 1/4 fontSize below centerline textHeight = parseInt(that.style('font-size'), 10) || getSize(that, 'height'), dy = -textHeight / 4; if(svgClass[0] === 'y') { mathjaxGroup.attr({ transform: 'rotate(' + [-90, +that.attr('x'), +that.attr('y')] + ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')' }); newSvg.attr({x: +that.attr('x'), y: +that.attr(' ...
...
.attr({
'text-anchor': {
left: 'start',
right: 'end'
}[options.align] || 'middle'
});
svgTextUtils.convertToTspans(s, drawGraphicalElements);
return s;
}
function drawGraphicalElements() {
// make sure lines are aligned the way they will be
// at the end, even if their position changes
...
html_entity_decode = function (s) { var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html(''); var replaced = s.replace(/(&[^;]*;)/gi, function(d) { if(d === '<') { return '<'; } // special handling for brackets if(d === '&rt;') { return '>'; } if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; } return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode }); hiddenDiv.remove(); return replaced; }
...
// fix for IE namespacing quirk?
// http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with
-ie
svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg);
svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink);
var s = new window.XMLSerializer().serializeToString(svg.node());
s = svgTextUtils.html_entity_decode(s);
s = svgTextUtils.xml_entity_encode(s);
// Fix quotations around font strings
s = s.replace(/("TOBESTRIPPED)|(TOBESTRIPPED")/g, '\'');
return s;
};
...
makeEditable = function (context, _delegate, options) { if(!options) options = {}; var that = this; var dispatch = d3.dispatch('edit', 'input', 'cancel'); var textSelection = d3.select(this.node()) .style({'pointer-events': 'all'}); var handlerElement = _delegate || textSelection; if(_delegate) textSelection.style({'pointer-events': 'none'}); function handleClick() { appendEditable(); that.style({opacity: 0}); // also hide any mathjax svg var svgClass = handlerElement.attr('class'), mathjaxClass; if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; else mathjaxClass = '[class*=-math-group]'; if(mathjaxClass) { d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); } } function selectElementContents(_el) { var el = _el.node(); var range = document.createRange(); range.selectNodeContents(el); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); el.focus(); } function appendEditable() { var gd = Lib.getPlotDiv(that.node()), plotDiv = d3.select(gd), container = plotDiv.select('.svg-container'), div = container.append('div'); div.classed('plugin-editable editable', true) .style({ position: 'absolute', 'font-family': that.style('font-family') || 'Arial', 'font-size': that.style('font-size') || 12, color: options.fill || that.style('fill') || 'black', opacity: 1, 'background-color': options.background || 'transparent', outline: '#ffffff33 1px solid', margin: [-parseFloat(that.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px', padding: '0', 'box-sizing': 'border-box' }) .attr({contenteditable: true}) .text(options.text || that.attr('data-unformatted')) .call(alignHTMLWith(that, container, options)) .on('blur', function() { gd._editing = false; that.text(this.textContent) .style({opacity: 1}); var svgClass = d3.select(this).attr('class'), mathjaxClass; if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; else mathjaxClass = '[class*=-math-group]'; if(mathjaxClass) { d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); } var text = this.textContent; d3.select(this).transition().duration(0).remove(); d3.select(document).on('mouseup', null); dispatch.edit.call(that, text); }) .on('focus', function() { var context = this; gd._editing = true; d3.select(document).on('mouseup', function() { if(d3.event.target === context) return false; if(document.activeElement === div.node()) div.node().blur(); }); }) .on('keyup', function() { if(d3.event.which === 27) { gd._editing = false; that.style({opacity: 1}); d3.select(this) .style({opacity: 0}) .on('blur', function() { return false; }) .transition().remove(); dispatch.cancel.call(that, this.textContent); } else { dispatch.input.call(that, this.textContent); d3.select(this).call(alignHTMLWith(that, container, options)); } }) .on('keydown', function() { if(d3.event.which === 13) this.blur(); }) .call(selectElementContents); ...
n/a
plainText = function (_str) { // strip out our pseudo-html so we have a readable // version to put into text fields return (_str || '').replace(STRIP_TAGS, ' '); }
...
contrastColor = d.borderColor || Color.contrast(traceColor);
// to get custom 'name' labels pass cleanPoint
if(d.nameOverride !== undefined) d.name = d.nameOverride;
if(d.name && d.zLabelVal === undefined) {
// strip out our pseudo-html elements from d.name (if it exists at all)
name = svgTextUtils.plainText(d.name || '');
if(name.length > 15) name = name.substr(0, 12) + '...';
}
// used by other modules (initially just ternary) that
// manage their own hoverinfo independent of cleanPoint
// the rest of this will still apply, so such modules
...
xml_entity_encode = function (str) { return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&'); }
...
style = encodeForHTML(extraStyle) + (style ? ';' + style : '');
}
return tspanStart + (style ? ' style="' + style + '"' : '') + '>
;';
}
}
else {
return exports.xml_entity_encode(d).replace(/</g, '<'
;);
}
});
var indices = [];
for(var index = result.indexOf('<br>'); index > 0; index = result.indexOf('<br>', index
+ 1)) {
indices.push(index);
}
...
function Ternary(options, fullLayout) { this.id = options.id; this.graphDiv = options.graphDiv; this.init(fullLayout); this.makeFramework(); }
n/a
adjustLayout = function (ternaryLayout, graphSize) { var _this = this, domain = ternaryLayout.domain, xDomainCenter = (domain.x[0] + domain.x[1]) / 2, yDomainCenter = (domain.y[0] + domain.y[1]) / 2, xDomain = domain.x[1] - domain.x[0], yDomain = domain.y[1] - domain.y[0], wmax = xDomain * graphSize.w, hmax = yDomain * graphSize.h, sum = ternaryLayout.sum, amin = ternaryLayout.aaxis.min, bmin = ternaryLayout.baxis.min, cmin = ternaryLayout.caxis.min; var x0, y0, w, h, xDomainFinal, yDomainFinal; if(wmax > w_over_h * hmax) { h = hmax; w = h * w_over_h; } else { w = wmax; h = w / w_over_h; } xDomainFinal = xDomain * w / wmax; yDomainFinal = yDomain * h / hmax; x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2; y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2; _this.x0 = x0; _this.y0 = y0; _this.w = w; _this.h = h; _this.sum = sum; // set up the x and y axis objects we'll use to lay out the points _this.xaxis = { type: 'linear', range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin], domain: [ xDomainCenter - xDomainFinal / 2, xDomainCenter + xDomainFinal / 2 ], _id: 'x' }; setConvert(_this.xaxis, _this.graphDiv._fullLayout); _this.xaxis.setScale(); _this.yaxis = { type: 'linear', range: [amin, sum - bmin - cmin], domain: [ yDomainCenter - yDomainFinal / 2, yDomainCenter + yDomainFinal / 2 ], _id: 'y' }; setConvert(_this.yaxis, _this.graphDiv._fullLayout); _this.yaxis.setScale(); // set up the modified axes for tick drawing var yDomain0 = _this.yaxis.domain[0]; // aaxis goes up the left side. Set it up as a y axis, but with // fictitious angles and domain, but then rotate and translate // it into place at the end var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, { visible: true, range: [amin, sum - bmin - cmin], side: 'left', _counterangle: 30, // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here // so we can shift by 30. tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30, domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], _axislayer: _this.layers.aaxis, _gridlayer: _this.layers.agrid, _pos: 0, // _this.xaxis.domain[0] * graphSize.w, _id: 'y', _length: w, _gridpath: 'M0,0l' + h + ',-' + (w / 2) }); setConvert(aaxis, _this.graphDiv._fullLayout); aaxis.setScale(); // baxis goes across the bottom (backward). We can set it up as an x axis // without any enclosing transformation. var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, { visible: true, range: [sum - amin - cmin, bmin], side: 'bottom', _counterangle: 30, domain: _this.xaxis.domain, _axislayer: _this.layers.baxis, _gridlayer: _this.layers.bgrid, _counteraxis: _this.aaxis, _pos: 0, // (1 - yDomain0) * graphSize.h, _id: 'x', _length: w, _gridpath: 'M0,0l-' + (w / 2) + ',-' + h }); setConvert(baxis, _this.graphDiv._fullLayout); baxis.setScale(); aaxis._counteraxis = baxis; // caxis goes down the right side. Set it up as a y axis, with // post-transformation similar to aaxis var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, { visible: true, range: [sum - amin - bmin, cmin], side: 'right', _counterangle: 30, tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30, domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], _axislayer: _this.layers.caxis, _gridlayer: _this.layers.cgrid, _counteraxis: _this.baxis, _pos: 0, // _this.xaxis.domain[1] * graphSize.w, _id: 'y', _length: w, _gri ...
...
// TODO don't reset projection on all graph edits
_this.projection = null;
_this.setScale = createGeoScale(geoLayout, graphSize);
_this.makeProjection(geoLayout);
_this.makePath();
_this.adjustLayout(geoLayout, graphSize);
_this.zoom = createGeoZoom(_this, geoLayout);
_this.zoomReset = createGeoZoomReset(_this, geoLayout);
_this.mockAxis = createMockAxis(fullLayout);
_this.framework
.call(_this.zoom)
...
drawAxes = function (doTitles) { var _this = this, gd = _this.graphDiv, titlesuffix = _this.id.substr(7) + 'title', aaxis = _this.aaxis, baxis = _this.baxis, caxis = _this.caxis; // 3rd arg true below skips titles, so we can configure them // correctly later on. Axes.doTicks(gd, aaxis, true); Axes.doTicks(gd, baxis, true); Axes.doTicks(gd, caxis, true); if(doTitles) { var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) + (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0)); Titles.draw(gd, 'a' + titlesuffix, { propContainer: aaxis, propName: _this.id + '.aaxis.title', dfltName: 'Component A', attributes: { x: _this.x0 + _this.w / 2, y: _this.y0 - aaxis.titlefont.size / 3 - apad, 'text-anchor': 'middle' } }); var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; Titles.draw(gd, 'b' + titlesuffix, { propContainer: baxis, propName: _this.id + '.baxis.title', dfltName: 'Component B', attributes: { x: _this.x0 - bpad, y: _this.y0 + _this.h + baxis.titlefont.size * 0.83 + bpad, 'text-anchor': 'middle' } }); Titles.draw(gd, 'c' + titlesuffix, { propContainer: caxis, propName: _this.id + '.caxis.title', dfltName: 'Component C', attributes: { x: _this.x0 + _this.w + bpad, y: _this.y0 + _this.h + caxis.titlefont.size * 0.83 + bpad, 'text-anchor': 'middle' } }); } }
...
_this.layers.aaxis.attr('transform', aTransform);
_this.layers.agrid.attr('transform', aTransform);
var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(-30)';
_this.layers.caxis.attr('transform', cTransform);
_this.layers.cgrid.attr('transform', cTransform);
_this.drawAxes(true);
// remove crispEdges - all the off-square angles in ternary plots
// make these counterproductive.
_this.plotContainer.selectAll('.crisp').classed('crisp', false);
var axlines = _this.layers.axlines;
axlines.select('.aline')
...
init = function (fullLayout) { this.container = fullLayout._ternarylayer; this.defs = fullLayout._defs; this.layoutId = fullLayout._uid; this.traceHash = {}; }
...
*/
Plotly.plot = function(gd, data, layout, config) {
var frames;
gd = helpers.getGraphDiv(gd);
// Events.init is idempotent and bails early if gd has already been init'd
Events.init(gd);
if(Lib.isPlainObject(data)) {
var obj = data;
data = obj.data;
layout = obj.layout;
config = obj.config;
frames = obj.frames;
...
initInteractions = function () { var _this = this, dragger = _this.layers.plotbg.select('path').node(), gd = _this.graphDiv, zoomContainer = _this.layers.zoom; // use plotbg for the main interactions var dragOptions = { element: dragger, gd: gd, plotinfo: {plot: zoomContainer}, doubleclick: doubleClick, subplot: _this.id, prepFn: function(e, startX, startY) { // these aren't available yet when initInteractions // is called dragOptions.xaxes = [_this.xaxis]; dragOptions.yaxes = [_this.yaxis]; var dragModeNow = gd._fullLayout.dragmode; if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; else dragModeNow = 'pan'; } if(dragModeNow === 'lasso') dragOptions.minDrag = 1; else dragOptions.minDrag = undefined; if(dragModeNow === 'zoom') { dragOptions.moveFn = zoomMove; dragOptions.doneFn = zoomDone; zoomPrep(e, startX, startY); } else if(dragModeNow === 'pan') { dragOptions.moveFn = plotDrag; dragOptions.doneFn = dragDone; panPrep(); clearSelect(); } else if(dragModeNow === 'select' || dragModeNow === 'lasso') { prepSelect(e, startX, startY, dragOptions, dragModeNow); } } }; var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners; function zoomPrep(e, startX, startY) { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; mins0 = { a: _this.aaxis.range[0], b: _this.baxis.range[1], c: _this.caxis.range[1] }; mins = mins0; span0 = _this.aaxis.range[1] - mins0.a; lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance(); path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z'; dimmed = false; zb = zoomContainer.append('path') .attr('class', 'zoombox') .style({ 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', 'stroke-width': 0 }) .attr('d', path0); corners = zoomContainer.append('path') .attr('class', 'zoombox-corners') .style({ fill: Color.background, stroke: Color.defaultLine, 'stroke-width': 1, opacity: 0 }) .attr('d', 'M0,0Z'); clearSelect(); } function getAFrac(x, y) { return 1 - (y / _this.h); } function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); } function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); } function zoomMove(dx0, dy0) { var x1 = x0 + dx0, y1 = y0 + dy0, afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))), bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))), cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))), xLeft = ((afrac / 2) + cfrac) * _this.w, xRight = (1 - (afrac / 2) - bfrac) * _this.w, xCenter = (xLeft + xRight) / 2, xSpan = xRight - xLeft, yBottom = (1 - afrac) * _this.h, yTop = yBottom - xSpan / w_over_h; if(xSpan < constants.MINZOOM) { mins = mins0; zb.attr('d', path0); corners.attr('d', 'M0,0Z'); } else { mins = { a: mins0.a + afrac * span0, b: mins0.b + bfrac * span0, c: mins0.c + cfrac * span0 }; zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + 'H' + xRight + 'L' + xCenter + ',' + yTop + ...
...
.attr('class', function(d) { return 'grid ' + d; })
.each(function(d) { _this.layers[d] = d3.select(this); });
_this.plotContainer.selectAll('.backplot,.frontplot,.grids')
.call(Drawing.setClipUrl, clipId);
if(!_this.graphDiv._context.staticPlot) {
_this.initInteractions();
}
};
var w_over_h = Math.sqrt(4 / 3);
proto.adjustLayout = function(ternaryLayout, graphSize) {
var _this = this,
...
makeFramework = function () { var _this = this; var defGroup = _this.defs.selectAll('g.clips') .data([0]); defGroup.enter().append('g') .classed('clips', true); // clippath for this ternary subplot var clipId = 'clip' + _this.layoutId + _this.id; _this.clipDef = defGroup.selectAll('#' + clipId) .data([0]); _this.clipDef.enter().append('clipPath').attr('id', clipId) .append('path').attr('d', 'M0,0Z'); // container for everything in this ternary subplot _this.plotContainer = _this.container.selectAll('g.' + _this.id) .data([0]); _this.plotContainer.enter().append('g') .classed(_this.id, true); _this.layers = {}; // inside that container, we have one container for the data, and // one each for the three axes around it. var plotLayers = [ 'draglayer', 'plotbg', 'backplot', 'grids', 'frontplot', 'zoom', 'aaxis', 'baxis', 'caxis', 'axlines' ]; var toplevel = _this.plotContainer.selectAll('g.toplevel') .data(plotLayers); toplevel.enter().append('g') .attr('class', function(d) { return 'toplevel ' + d; }) .each(function(d) { var s = d3.select(this); _this.layers[d] = s; // containers for different trace types. // NOTE - this is different from cartesian, where all traces // are in front of grids. Here I'm putting maps behind the grids // so the grids will always be visible if they're requested. // Perhaps we want that for cartesian too? if(d === 'frontplot') s.append('g').classed('scatterlayer', true); else if(d === 'backplot') s.append('g').classed('maplayer', true); else if(d === 'plotbg') s.append('path').attr('d', 'M0,0Z'); else if(d === 'axlines') { s.selectAll('path').data(['aline', 'bline', 'cline']) .enter().append('path').each(function(d) { d3.select(this).classed(d, true); }); } }); var grids = _this.plotContainer.select('.grids').selectAll('g.grid') .data(['agrid', 'bgrid', 'cgrid']); grids.enter().append('g') .attr('class', function(d) { return 'grid ' + d; }) .each(function(d) { _this.layers[d] = d3.select(this); }); _this.plotContainer.selectAll('.backplot,.frontplot,.grids') .call(Drawing.setClipUrl, clipId); if(!_this.graphDiv._context.staticPlot) { _this.initInteractions(); } }
...
this.clipAngle = null;
this.setScale = null;
this.path = null;
this.zoom = null;
this.zoomReset = null;
this.makeFramework();
this.traceHash = {};
}
module.exports = Geo;
var proto = Geo.prototype;
...
plot = function (ternaryCalcData, fullLayout) { var _this = this, ternaryLayout = fullLayout[_this.id], graphSize = fullLayout._size; _this.adjustLayout(ternaryLayout, graphSize); Plots.generalUpdatePerTraceModule(_this, ternaryCalcData, ternaryLayout); _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor); }
...
fullLayout._infolayer.selectAll('.cb' + uid).remove();
}
}
// loop over the base plot modules present on graph
var basePlotModules = fullLayout._basePlotModules;
for(i = 0; i < basePlotModules.length; i++) {
basePlotModules[i].plot(gd);
}
// keep reference to shape layers in subplots
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
// styling separate from drawing
...
getTopojsonFeatures = function (trace, topojson) { var layer = locationmodeToLayer[trace.locationmode], obj = topojson.objects[layer]; return topojsonFeature(topojson, obj).features; }
n/a
getTopojsonName = function (geoLayout) { return [ geoLayout.scope.replace(/ /g, '-'), '_', geoLayout.resolution.toString(), 'm' ].join(''); }
...
Fx.loneUnhover(fullLayout._toppaper);
});
_this.framework.on('click', function() {
Fx.click(_this.graphDiv, d3.event);
});
topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout);
if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) {
_this.topojsonName = topojsonNameNew;
if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) {
_this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName];
_this.onceTopojsonIsLoaded(geoCalcData, geoLayout);
...
getTopojsonPath = function (topojsonURL, topojsonName) { return topojsonURL + topojsonName + '.json'; }
...
_this.topojsonName = topojsonNameNew;
if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) {
_this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName];
_this.onceTopojsonIsLoaded(geoCalcData, geoLayout);
}
else {
topojsonPath = topojsonUtils.getTopojsonPath(
_this.topojsonURL,
_this.topojsonName
);
promises.push(new Promise(function(resolve, reject) {
d3.json(topojsonPath, function(error, topojson) {
if(error) {
...
function transform(dataIn, state) { var dataOut = []; for(var i = 0; i < dataIn.length; i++) { var traceIn = dataIn[i]; if(traceIn.type !== 'candlestick') { dataOut.push(traceIn); continue; } dataOut.push( makeTrace(traceIn, state, 'increasing'), makeTrace(traceIn, state, 'decreasing') ); } helpers.addRangeSlider(dataOut, state.layout); return dataOut; }
...
dataOut = [fullTrace];
for(var i = 0; i < container.length; i++) {
var transform = container[i],
_module = transformsRegistry[transform.type];
if(_module && _module.transform) {
dataOut = _module.transform(dataOut, {
transform: transform,
fullTrace: fullTrace,
fullData: fullData,
layout: layout,
fullLayout: fullLayout,
transformIndex: i
});
...
function calcTransform(gd, trace, opts) { var direction = opts.direction, filterFn = helpers.getFilterFn(direction); var open = trace.open, high = trace.high, low = trace.low, close = trace.close; var len = open.length, x = [], y = []; var appendX = trace._fullInput.x ? function(i) { var v = trace.x[i]; x.push(v, v, v, v, v, v); } : function(i) { x.push(i, i, i, i, i, i); }; var appendY = function(o, h, l, c) { y.push(l, o, c, c, c, h); }; for(var i = 0; i < len; i++) { if(filterFn(open[i], close[i])) { appendX(i); appendY(open[i], high[i], low[i], close[i]); } } trace.x = x; trace.y = y; }
...
for(j = 0; j < trace.transforms.length; j++) {
var transform = trace.transforms[j];
_module = transformsRegistry[transform.type];
if(_module && _module.calcTransform) {
hasCalcTransform = true;
_module.calcTransform(gd, trace, transform);
}
}
}
}
// clear stuff that should recomputed in 'regular' loop
if(hasCalcTransform) {
...
supplyDefaults = function (transformIn, traceOut, layout, traceIn) { helpers.clearEphemeralTransformOpts(traceIn); helpers.copyOHLC(transformIn, traceOut); return transformIn; }
...
gd._replotPending = true;
return Promise.reject();
} else {
// we're going ahead with a replot now
gd._replotPending = false;
}
Plots.supplyDefaults(gd);
var fullLayout = gd._fullLayout;
// Polar plots
if(data && data[0] && data[0].r) return plotPolar(gd, data, layout);
// so we don't try to re-call Plotly.plot from inside
...
function unhoverRaw(gd, evt) { var fullLayout = gd._fullLayout; var oldhoverdata = gd._hoverdata; if(!evt) evt = {}; if(evt.target && Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { return; } fullLayout._hoverlayer.selectAll('g').remove(); fullLayout._hoverlayer.selectAll('line').remove(); fullLayout._hoverlayer.selectAll('circle').remove(); gd._hoverdata = undefined; if(evt.target && oldhoverdata) { gd.emit('plotly_unhover', { event: evt, points: oldhoverdata }); } }
...
// Important, clear any queued hovers
if(gd._hoverTimer) {
clearTimeout(gd._hoverTimer);
gd._hoverTimer = undefined;
}
unhover.raw(gd, evt, subplot);
};
// remove hover effects on mouse out, and emit unhover event
unhover.raw = function unhoverRaw(gd, evt) {
var fullLayout = gd._fullLayout;
var oldhoverdata = gd._hoverdata;
...
wrapped = function (gd, evt, subplot) { if(typeof gd === 'string') gd = document.getElementById(gd); // Important, clear any queued hovers if(gd._hoverTimer) { clearTimeout(gd._hoverTimer); gd._hoverTimer = undefined; } unhover.raw(gd, evt, subplot); }
n/a