StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
StackGenVis/frontend/node_modules/vega-view-transforms/build/vega-view-transforms.js

1388 lines
40 KiB

4 years ago
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-dataflow'), require('vega-scenegraph'), require('vega-util')) :
typeof define === 'function' && define.amd ? define(['exports', 'vega-dataflow', 'vega-scenegraph', 'vega-util'], factory) :
(global = global || self, factory((global.vega = global.vega || {}, global.vega.transforms = {}), global.vega, global.vega, global.vega));
}(this, (function (exports, vegaDataflow, vegaScenegraph, vegaUtil) { 'use strict';
const Top = 'top';
const Left = 'left';
const Right = 'right';
const Bottom = 'bottom';
const TopLeft = 'top-left';
const TopRight = 'top-right';
const BottomLeft = 'bottom-left';
const BottomRight = 'bottom-right';
const Start = 'start';
const Middle = 'middle';
const End = 'end';
const X = 'x';
const Y = 'y';
const Group = 'group';
const AxisRole = 'axis';
const TitleRole = 'title';
const FrameRole = 'frame';
const ScopeRole = 'scope';
const LegendRole = 'legend';
const RowHeader = 'row-header';
const RowFooter = 'row-footer';
const RowTitle = 'row-title';
const ColHeader = 'column-header';
const ColFooter = 'column-footer';
const ColTitle = 'column-title';
const Padding = 'padding';
const Symbols = 'symbol';
const Fit = 'fit';
const FitX = 'fit-x';
const FitY = 'fit-y';
const Pad = 'pad';
const None = 'none';
const All = 'all';
const Each = 'each';
const Flush = 'flush';
const Column = 'column';
const Row = 'row';
/**
* Calculate bounding boxes for scenegraph items.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {object} params.mark - The scenegraph mark instance to bound.
*/
function Bound(params) {
vegaDataflow.Transform.call(this, null, params);
}
var prototype = vegaUtil.inherits(Bound, vegaDataflow.Transform);
prototype.transform = function(_, pulse) {
var view = pulse.dataflow,
mark = _.mark,
type = mark.marktype,
entry = vegaScenegraph.Marks[type],
bound = entry.bound,
markBounds = mark.bounds, rebound;
if (entry.nested) {
// multi-item marks have a single bounds instance
if (mark.items.length) view.dirty(mark.items[0]);
markBounds = boundItem(mark, bound);
mark.items.forEach(function(item) {
item.bounds.clear().union(markBounds);
});
}
else if (type === Group || _.modified()) {
// operator parameters modified -> re-bound all items
// updates group bounds in response to modified group content
pulse.visit(pulse.MOD, function(item) { view.dirty(item); });
markBounds.clear();
mark.items.forEach(function(item) {
markBounds.union(boundItem(item, bound));
});
// force reflow for axes/legends/titles to propagate any layout changes
switch (mark.role) {
case AxisRole:
case LegendRole:
case TitleRole:
pulse.reflow();
}
}
else {
// incrementally update bounds, re-bound mark as needed
rebound = pulse.changed(pulse.REM);
pulse.visit(pulse.ADD, function(item) {
markBounds.union(boundItem(item, bound));
});
pulse.visit(pulse.MOD, function(item) {
rebound = rebound || markBounds.alignsWith(item.bounds);
view.dirty(item);
markBounds.union(boundItem(item, bound));
});
if (rebound) {
markBounds.clear();
mark.items.forEach(function(item) { markBounds.union(item.bounds); });
}
}
// ensure mark bounds do not exceed any clipping region
vegaScenegraph.boundClip(mark);
return pulse.modifies('bounds');
};
function boundItem(item, bound, opt) {
return bound(item.bounds.clear(), item, opt);
}
var COUNTER_NAME = ':vega_identifier:';
/**
* Adds a unique identifier to all added tuples.
* This transform creates a new signal that serves as an id counter.
* As a result, the id counter is shared across all instances of this
* transform, generating unique ids across multiple data streams. In
* addition, this signal value can be included in a snapshot of the
* dataflow state, enabling correct resumption of id allocation.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {string} params.as - The field name for the generated identifier.
*/
function Identifier(params) {
vegaDataflow.Transform.call(this, 0, params);
}
Identifier.Definition = {
"type": "Identifier",
"metadata": {"modifies": true},
"params": [
{ "name": "as", "type": "string", "required": true }
]
};
var prototype$1 = vegaUtil.inherits(Identifier, vegaDataflow.Transform);
prototype$1.transform = function(_, pulse) {
var counter = getCounter(pulse.dataflow),
id = counter.value,
as = _.as;
pulse.visit(pulse.ADD, function(t) {
if (!t[as]) t[as] = ++id;
});
counter.set(this.value = id);
return pulse;
};
function getCounter(view) {
var counter = view._signals[COUNTER_NAME];
if (!counter) {
view._signals[COUNTER_NAME] = (counter = view.add(0));
}
return counter;
}
/**
* Bind scenegraph items to a scenegraph mark instance.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {object} params.markdef - The mark definition for creating the mark.
* This is an object of legal scenegraph mark properties which *must* include
* the 'marktype' property.
*/
function Mark(params) {
vegaDataflow.Transform.call(this, null, params);
}
var prototype$2 = vegaUtil.inherits(Mark, vegaDataflow.Transform);
prototype$2.transform = function(_, pulse) {
var mark = this.value;
// acquire mark on first invocation, bind context and group
if (!mark) {
mark = pulse.dataflow.scenegraph().mark(_.markdef, lookup(_), _.index);
mark.group.context = _.context;
if (!_.context.group) _.context.group = mark.group;
mark.source = this.source; // point to upstream collector
mark.clip = _.clip;
mark.interactive = _.interactive;
this.value = mark;
}
// initialize entering items
var Init = mark.marktype === Group ? vegaScenegraph.GroupItem : vegaScenegraph.Item;
pulse.visit(pulse.ADD, function(item) { Init.call(item, mark); });
// update clipping and/or interactive status
if (_.modified('clip') || _.modified('interactive')) {
mark.clip = _.clip;
mark.interactive = !!_.interactive;
mark.zdirty = true; // force scenegraph re-eval
pulse.reflow();
}
// bind items array to scenegraph mark
mark.items = pulse.source;
return pulse;
};
function lookup(_) {
var g = _.groups, p = _.parent;
return g && g.size === 1 ? g.get(Object.keys(g.object)[0])
: g && p ? g.lookup(p)
: null;
}
/**
* Analyze items for overlap, changing opacity to hide items with
* overlapping bounding boxes. This transform will preserve at least
* two items (e.g., first and last) even if overlap persists.
* @param {object} params - The parameters for this operator.
* @param {function(*,*): number} [params.sort] - A comparator
* function for sorting items.
* @param {object} [params.method] - The overlap removal method to apply.
* One of 'parity' (default, hide every other item until there is no
* more overlap) or 'greedy' (sequentially scan and hide and items that
* overlap with the last visible item).
* @param {object} [params.boundScale] - A scale whose range should be used
* to bound the items. Items exceeding the bounds of the scale range
* will be treated as overlapping. If null or undefined, no bounds check
* will be applied.
* @param {object} [params.boundOrient] - The orientation of the scale
* (top, bottom, left, or right) used to bound items. This parameter is
* ignored if boundScale is null or undefined.
* @param {object} [params.boundTolerance] - The tolerance in pixels for
* bound inclusion testing (default 1). This specifies by how many pixels
* an item's bounds may exceed the scale range bounds and not be culled.
* @constructor
*/
function Overlap(params) {
vegaDataflow.Transform.call(this, null, params);
}
var prototype$3 = vegaUtil.inherits(Overlap, vegaDataflow.Transform);
var methods = {
parity: function(items) {
return items.filter((item, i) => i % 2 ? (item.opacity = 0) : 1);
},
greedy: function(items, sep) {
var a;
return items.filter((b, i) => {
if (!i || !intersect(a.bounds, b.bounds, sep)) {
a = b;
return 1;
} else {
return b.opacity = 0;
}
});
}
};
// compute bounding box intersection
// including padding pixels of separation
function intersect(a, b, sep) {
return sep > Math.max(
b.x1 - a.x2,
a.x1 - b.x2,
b.y1 - a.y2,
a.y1 - b.y2
);
}
function hasOverlap(items, pad) {
for (var i=1, n=items.length, a=items[0].bounds, b; i<n; a=b, ++i) {
if (intersect(a, b = items[i].bounds, pad)) return true;
}
}
function hasBounds(item) {
var b = item.bounds;
return b.width() > 1 && b.height() > 1;
}
function boundTest(scale, orient, tolerance) {
var range = scale.range(),
b = new vegaScenegraph.Bounds();
if (orient === Top || orient === Bottom) {
b.set(range[0], -Infinity, range[1], +Infinity);
} else {
b.set(-Infinity, range[0], +Infinity, range[1]);
}
b.expand(tolerance || 1);
return item => b.encloses(item.bounds);
}
// reset all items to be fully opaque
function reset(source) {
source.forEach(item => item.opacity = 1);
return source;
}
// add all tuples to mod, fork pulse if parameters were modified
// fork prevents cross-stream tuple pollution (e.g., pulse from scale)
function reflow(pulse, _) {
return pulse.reflow(_.modified()).modifies('opacity');
}
prototype$3.transform = function(_, pulse) {
var reduce = methods[_.method] || methods.parity,
source = pulse.materialize(pulse.SOURCE).source,
sep = _.separation || 0,
items, test, bounds;
if (!source || !source.length) return;
if (!_.method) {
// early exit if method is falsy
if (_.modified('method')) {
reset(source);
pulse = reflow(pulse, _);
}
return pulse;
}
if (_.sort) {
source = source.slice().sort(_.sort);
}
// skip labels with no content
source = source.filter(hasBounds);
items = reset(source);
pulse = reflow(pulse, _);
if (items.length >= 3 && hasOverlap(items, sep)) {
do {
items = reduce(items, sep);
} while (items.length >= 3 && hasOverlap(items, sep));
if (items.length < 3 && !vegaUtil.peek(source).opacity) {
if (items.length > 1) vegaUtil.peek(items).opacity = 0;
vegaUtil.peek(source).opacity = 1;
}
}
if (_.boundScale && _.boundTolerance >= 0) {
test = boundTest(_.boundScale, _.boundOrient, +_.boundTolerance);
source.forEach(item => {
if (!test(item)) item.opacity = 0;
});
}
// re-calculate mark bounds
bounds = items[0].mark.bounds.clear();
source.forEach(item => {
if (item.opacity) bounds.union(item.bounds);
});
return pulse;
};
/**
* Queue modified scenegraph items for rendering.
* @constructor
*/
function Render(params) {
vegaDataflow.Transform.call(this, null, params);
}
var prototype$4 = vegaUtil.inherits(Render, vegaDataflow.Transform);
prototype$4.transform = function(_, pulse) {
var view = pulse.dataflow;
pulse.visit(pulse.ALL, function(item) { view.dirty(item); });
// set z-index dirty flag as needed
if (pulse.fields && pulse.fields['zindex']) {
var item = pulse.source && pulse.source[0];
if (item) item.mark.zdirty = true;
}
};
const tempBounds = new vegaScenegraph.Bounds();
function set(item, property, value) {
return item[property] === value ? 0
: (item[property] = value, 1);
}
function isYAxis(mark) {
var orient = mark.items[0].datum.orient;
return orient === Left || orient === Right;
}
function axisIndices(datum) {
var index = +datum.grid;
return [
datum.ticks ? index++ : -1, // ticks index
datum.labels ? index++ : -1, // labels index
index + (+datum.domain) // title index
];
}
function axisLayout(view, axis, width, height) {
var item = axis.items[0],
datum = item.datum,
orient = datum.orient,
delta = datum.translate != null ? datum.translate : 0.5,
indices = axisIndices(datum),
range = item.range,
offset = item.offset,
position = item.position,
minExtent = item.minExtent,
maxExtent = item.maxExtent,
title = datum.title && item.items[indices[2]].items[0],
titlePadding = item.titlePadding,
bounds = item.bounds,
dl = title && vegaScenegraph.multiLineOffset(title),
x = 0, y = 0, i, s;
tempBounds.clear().union(bounds);
bounds.clear();
if ((i=indices[0]) > -1) bounds.union(item.items[i].bounds);
if ((i=indices[1]) > -1) bounds.union(item.items[i].bounds);
// position axis group and title
switch (orient) {
case Top:
x = position || 0;
y = -offset;
s = Math.max(minExtent, Math.min(maxExtent, -bounds.y1));
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 0, -1, bounds);
bounds.add(0, -s).add(range, 0);
break;
case Left:
x = -offset;
y = position || 0;
s = Math.max(minExtent, Math.min(maxExtent, -bounds.x1));
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 1, -1, bounds);
bounds.add(-s, 0).add(0, range);
break;
case Right:
x = width + offset;
y = position || 0;
s = Math.max(minExtent, Math.min(maxExtent, bounds.x2));
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 1, 1, bounds);
bounds.add(0, 0).add(s, range);
break;
case Bottom:
x = position || 0;
y = height + offset;
s = Math.max(minExtent, Math.min(maxExtent, bounds.y2));
if (title) s = axisTitleLayout(view, title, s, titlePadding, 0, 0, 1, bounds);
bounds.add(0, 0).add(range, s);
break;
default:
x = item.x;
y = item.y;
}
// update bounds
vegaScenegraph.boundStroke(bounds.translate(x, y), item);
if (set(item, 'x', x + delta) | set(item, 'y', y + delta)) {
item.bounds = tempBounds;
view.dirty(item);
item.bounds = bounds;
view.dirty(item);
}
return item.mark.bounds.clear().union(bounds);
}
function axisTitleLayout(view, title, offset, pad, dl, isYAxis, sign, bounds) {
var b = title.bounds, dx = 0, dy = 0;
if (title.auto) {
view.dirty(title);
offset += pad;
isYAxis
? dx = (title.x || 0) - (title.x = sign * (offset + dl))
: dy = (title.y || 0) - (title.y = sign * (offset + dl));
title.mark.bounds.clear().union(b.translate(-dx, -dy));
view.dirty(title);
if (isYAxis) {
bounds.add(0, b.y1).add(0, b.y2);
offset += b.width();
} else {
bounds.add(b.x1, 0).add(b.x2, 0);
offset += b.height();
}
} else {
bounds.union(b);
}
return offset;
}
function gridLayoutGroups(group) {
var groups = group.items,
n = groups.length,
i = 0, mark, items;
var views = {
marks: [],
rowheaders: [],
rowfooters: [],
colheaders: [],
colfooters: [],
rowtitle: null,
coltitle: null
};
// layout axes, gather legends, collect bounds
for (; i<n; ++i) {
mark = groups[i];
items = mark.items;
if (mark.marktype === Group) {
switch (mark.role) {
case AxisRole:
case LegendRole:
case TitleRole:
break;
case RowHeader: views.rowheaders.push(...items); break;
case RowFooter: views.rowfooters.push(...items); break;
case ColHeader: views.colheaders.push(...items); break;
case ColFooter: views.colfooters.push(...items); break;
case RowTitle: views.rowtitle = items[0]; break;
case ColTitle: views.coltitle = items[0]; break;
default: views.marks.push(...items);
}
}
}
return views;
}
function bboxFlush(item) {
return new vegaScenegraph.Bounds().set(0, 0, item.width || 0, item.height || 0);
}
function bboxFull(item) {
var b = item.bounds.clone();
return b.empty()
? b.set(0, 0, 0, 0)
: b.translate(-(item.x || 0), -(item.y || 0));
}
function get(opt, key, d) {
var v = vegaUtil.isObject(opt) ? opt[key] : opt;
return v != null ? v : (d !== undefined ? d : 0);
}
function offsetValue(v) {
return v < 0 ? Math.ceil(-v) : 0;
}
function gridLayout(view, groups, opt) {
var dirty = !opt.nodirty,
bbox = opt.bounds === Flush ? bboxFlush : bboxFull,
bounds = tempBounds.set(0, 0, 0, 0),
alignCol = get(opt.align, Column),
alignRow = get(opt.align, Row),
padCol = get(opt.padding, Column),
padRow = get(opt.padding, Row),
ncols = opt.columns || groups.length,
nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
n = groups.length,
xOffset = Array(n), xExtent = Array(ncols), xMax = 0,
yOffset = Array(n), yExtent = Array(nrows), yMax = 0,
dx = Array(n), dy = Array(n), boxes = Array(n),
m, i, c, r, b, g, px, py, x, y, offset;
for (i=0; i<ncols; ++i) xExtent[i] = 0;
for (i=0; i<nrows; ++i) yExtent[i] = 0;
// determine offsets for each group
for (i=0; i<n; ++i) {
g = groups[i];
b = boxes[i] = bbox(g);
g.x = g.x || 0; dx[i] = 0;
g.y = g.y || 0; dy[i] = 0;
c = i % ncols;
r = ~~(i / ncols);
xMax = Math.max(xMax, px = Math.ceil(b.x2));
yMax = Math.max(yMax, py = Math.ceil(b.y2));
xExtent[c] = Math.max(xExtent[c], px);
yExtent[r] = Math.max(yExtent[r], py);
xOffset[i] = padCol + offsetValue(b.x1);
yOffset[i] = padRow + offsetValue(b.y1);
if (dirty) view.dirty(groups[i]);
}
// set initial alignment offsets
for (i=0; i<n; ++i) {
if (i % ncols === 0) xOffset[i] = 0;
if (i < ncols) yOffset[i] = 0;
}
// enforce column alignment constraints
if (alignCol === Each) {
for (c=1; c<ncols; ++c) {
for (offset=0, i=c; i<n; i += ncols) {
if (offset < xOffset[i]) offset = xOffset[i];
}
for (i=c; i<n; i += ncols) {
xOffset[i] = offset + xExtent[c-1];
}
}
} else if (alignCol === All) {
for (offset=0, i=0; i<n; ++i) {
if (i % ncols && offset < xOffset[i]) offset = xOffset[i];
}
for (i=0; i<n; ++i) {
if (i % ncols) xOffset[i] = offset + xMax;
}
} else {
for (alignCol=false, c=1; c<ncols; ++c) {
for (i=c; i<n; i += ncols) {
xOffset[i] += xExtent[c-1];
}
}
}
// enforce row alignment constraints
if (alignRow === Each) {
for (r=1; r<nrows; ++r) {
for (offset=0, i=r*ncols, m=i+ncols; i<m; ++i) {
if (offset < yOffset[i]) offset = yOffset[i];
}
for (i=r*ncols; i<m; ++i) {
yOffset[i] = offset + yExtent[r-1];
}
}
} else if (alignRow === All) {
for (offset=0, i=ncols; i<n; ++i) {
if (offset < yOffset[i]) offset = yOffset[i];
}
for (i=ncols; i<n; ++i) {
yOffset[i] = offset + yMax;
}
} else {
for (alignRow=false, r=1; r<nrows; ++r) {
for (i=r*ncols, m=i+ncols; i<m; ++i) {
yOffset[i] += yExtent[r-1];
}
}
}
// perform horizontal grid layout
for (x=0, i=0; i<n; ++i) {
x = xOffset[i] + (i % ncols ? x : 0);
dx[i] += x - groups[i].x;
}
// perform vertical grid layout
for (c=0; c<ncols; ++c) {
for (y=0, i=c; i<n; i += ncols) {
y += yOffset[i];
dy[i] += y - groups[i].y;
}
}
// perform horizontal centering
if (alignCol && get(opt.center, Column) && nrows > 1) {
for (i=0; i<n; ++i) {
b = alignCol === All ? xMax : xExtent[i % ncols];
x = b - boxes[i].x2 - groups[i].x - dx[i];
if (x > 0) dx[i] += x / 2;
}
}
// perform vertical centering
if (alignRow && get(opt.center, Row) && ncols !== 1) {
for (i=0; i<n; ++i) {
b = alignRow === All ? yMax : yExtent[~~(i / ncols)];
y = b - boxes[i].y2 - groups[i].y - dy[i];
if (y > 0) dy[i] += y / 2;
}
}
// position grid relative to anchor
for (i=0; i<n; ++i) {
bounds.union(boxes[i].translate(dx[i], dy[i]));
}
x = get(opt.anchor, X);
y = get(opt.anchor, Y);
switch (get(opt.anchor, Column)) {
case End: x -= bounds.width(); break;
case Middle: x -= bounds.width() / 2;
}
switch (get(opt.anchor, Row)) {
case End: y -= bounds.height(); break;
case Middle: y -= bounds.height() / 2;
}
x = Math.round(x);
y = Math.round(y);
// update mark positions, bounds, dirty
bounds.clear();
for (i=0; i<n; ++i) {
groups[i].mark.bounds.clear();
}
for (i=0; i<n; ++i) {
g = groups[i];
g.x += (dx[i] += x);
g.y += (dy[i] += y);
bounds.union(g.mark.bounds.union(g.bounds.translate(dx[i], dy[i])));
if (dirty) view.dirty(g);
}
return bounds;
}
function trellisLayout(view, group, opt) {
var views = gridLayoutGroups(group),
groups = views.marks,
bbox = opt.bounds === Flush ? boundFlush : boundFull,
off = opt.offset,
ncols = opt.columns || groups.length,
nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols),
cells = nrows * ncols,
x, y, x2, y2, anchor, band, offset;
// -- initial grid layout
const bounds = gridLayout(view, groups, opt);
// -- layout grid headers and footers --
// perform row header layout
if (views.rowheaders) {
band = get(opt.headerBand, Row, null);
x = layoutHeaders(view, views.rowheaders, groups, ncols, nrows, -get(off, 'rowHeader'), min, 0, bbox, 'x1', 0, ncols, 1, band);
}
// perform column header layout
if (views.colheaders) {
band = get(opt.headerBand, Column, null);
y = layoutHeaders(view, views.colheaders, groups, ncols, ncols, -get(off, 'columnHeader'), min, 1, bbox, 'y1', 0, 1, ncols, band);
}
// perform row footer layout
if (views.rowfooters) {
band = get(opt.footerBand, Row, null);
x2 = layoutHeaders(view, views.rowfooters, groups, ncols, nrows, get(off, 'rowFooter'), max, 0, bbox, 'x2', ncols-1, ncols, 1, band);
}
// perform column footer layout
if (views.colfooters) {
band = get(opt.footerBand, Column, null);
y2 = layoutHeaders(view, views.colfooters, groups, ncols, ncols, get(off, 'columnFooter'), max, 1, bbox, 'y2', cells-ncols, 1, ncols, band);
}
// perform row title layout
if (views.rowtitle) {
anchor = get(opt.titleAnchor, Row);
offset = get(off, 'rowTitle');
offset = anchor === End ? x2 + offset : x - offset;
band = get(opt.titleBand, Row, 0.5);
layoutTitle(view, views.rowtitle, offset, 0, bounds, band);
}
// perform column title layout
if (views.coltitle) {
anchor = get(opt.titleAnchor, Column);
offset = get(off, 'columnTitle');
offset = anchor === End ? y2 + offset : y - offset;
band = get(opt.titleBand, Column, 0.5);
layoutTitle(view, views.coltitle, offset, 1, bounds, band);
}
}
function boundFlush(item, field) {
return field === 'x1' ? (item.x || 0)
: field === 'y1' ? (item.y || 0)
: field === 'x2' ? (item.x || 0) + (item.width || 0)
: field === 'y2' ? (item.y || 0) + (item.height || 0)
: undefined;
}
function boundFull(item, field) {
return item.bounds[field];
}
// aggregation functions for grid margin determination
function min(a, b) { return Math.floor(Math.min(a, b)); }
function max(a, b) { return Math.ceil(Math.max(a, b)); }
function layoutHeaders(view, headers, groups, ncols, limit, offset, agg, isX, bound, bf, start, stride, back, band) {
var n = groups.length,
init = 0,
edge = 0,
i, j, k, m, b, h, g, x, y;
// if no groups, early exit and return 0
if (!n) return init;
// compute margin
for (i=start; i<n; i+=stride) {
if (groups[i]) init = agg(init, bound(groups[i], bf));
}
// if no headers, return margin calculation
if (!headers.length) return init;
// check if number of headers exceeds number of rows or columns
if (headers.length > limit) {
view.warn('Grid headers exceed limit: ' + limit);
headers = headers.slice(0, limit);
}
// apply offset
init += offset;
// clear mark bounds for all headers
for (j=0, m=headers.length; j<m; ++j) {
view.dirty(headers[j]);
headers[j].mark.bounds.clear();
}
// layout each header
for (i=start, j=0, m=headers.length; j<m; ++j, i+=stride) {
h = headers[j];
b = h.mark.bounds;
// search for nearest group to align to
// necessary if table has empty cells
for (k=i; k >= 0 && (g = groups[k]) == null; k-=back);
// assign coordinates and update bounds
if (isX) {
x = band == null ? g.x : Math.round(g.bounds.x1 + band * g.bounds.width());
y = init;
} else {
x = init;
y = band == null ? g.y : Math.round(g.bounds.y1 + band * g.bounds.height());
}
b.union(h.bounds.translate(x - (h.x || 0), y - (h.y || 0)));
h.x = x;
h.y = y;
view.dirty(h);
// update current edge of layout bounds
edge = agg(edge, b[bf]);
}
return edge;
}
function layoutTitle(view, g, offset, isX, bounds, band) {
if (!g) return;
view.dirty(g);
// compute title coordinates
var x = offset, y = offset;
isX
? (x = Math.round(bounds.x1 + band * bounds.width()))
: (y = Math.round(bounds.y1 + band * bounds.height()));
// assign coordinates and update bounds
g.bounds.translate(x - (g.x || 0), y - (g.y || 0));
g.mark.bounds.clear().union(g.bounds);
g.x = x;
g.y = y;
// queue title for redraw
view.dirty(g);
}
// utility for looking up legend layout configuration
function lookup$1(config, orient) {
const opt = config[orient] || {};
return (key, d) => opt[key] != null ? opt[key]
: config[key] != null ? config[key]
: d;
}
// if legends specify offset directly, use the maximum specified value
function offsets(legends, value) {
var max = -Infinity;
legends.forEach(item => {
if (item.offset != null) max = Math.max(max, item.offset);
});
return max > -Infinity ? max : value;
}
function legendParams(g, orient, config, xb, yb, w, h) {
const _ = lookup$1(config, orient),
offset = offsets(g, _('offset', 0)),
anchor = _('anchor', Start),
mult = anchor === End ? 1 : anchor === Middle ? 0.5 : 0;
const p = {
align: Each,
bounds: _('bounds', Flush),
columns: _('direction') === 'vertical' ? 1 : g.length,
padding: _('margin', 8),
center: _('center'),
nodirty: true
};
switch (orient) {
case Left:
p.anchor = {
x: Math.floor(xb.x1) - offset, column: End,
y: mult * (h || xb.height() + 2 * xb.y1), row: anchor
};
break;
case Right:
p.anchor = {
x: Math.ceil(xb.x2) + offset,
y: mult * (h || xb.height() + 2 * xb.y1), row: anchor
};
break;
case Top:
p.anchor = {
y: Math.floor(yb.y1) - offset, row: End,
x: mult * (w || yb.width() + 2 * yb.x1), column: anchor
};
break;
case Bottom:
p.anchor = {
y: Math.ceil(yb.y2) + offset,
x: mult * (w || yb.width() + 2 * yb.x1), column: anchor
};
break;
case TopLeft:
p.anchor = {x: offset, y: offset};
break;
case TopRight:
p.anchor = {x: w - offset, y: offset, column: End};
break;
case BottomLeft:
p.anchor = {x: offset, y: h - offset, row: End};
break;
case BottomRight:
p.anchor = {x: w - offset, y: h - offset, column: End, row: End};
break;
}
return p;
}
function legendLayout(view, legend) {
var item = legend.items[0],
datum = item.datum,
orient = item.orient,
bounds = item.bounds,
x = item.x, y = item.y, w, h;
// cache current bounds for later comparison
item._bounds
? item._bounds.clear().union(bounds)
: item._bounds = bounds.clone();
bounds.clear();
// adjust legend to accommodate padding and title
legendGroupLayout(view, item, item.items[0].items[0]);
// aggregate bounds to determine size, and include origin
bounds = legendBounds(item, bounds);
w = 2 * item.padding;
h = 2 * item.padding;
if (!bounds.empty()) {
w = Math.ceil(bounds.width() + w);
h = Math.ceil(bounds.height() + h);
}
if (datum.type === Symbols) {
legendEntryLayout(item.items[0].items[0].items[0].items);
}
if (orient !== None) {
item.x = x = 0;
item.y = y = 0;
}
item.width = w;
item.height = h;
vegaScenegraph.boundStroke(bounds.set(x, y, x + w, y + h), item);
item.mark.bounds.clear().union(bounds);
return item;
}
function legendBounds(item, b) {
// aggregate item bounds
item.items.forEach(_ => b.union(_.bounds));
// anchor to legend origin
b.x1 = item.padding;
b.y1 = item.padding;
return b;
}
function legendGroupLayout(view, item, entry) {
var pad = item.padding,
ex = pad - entry.x,
ey = pad - entry.y;
if (!item.datum.title) {
if (ex || ey) translate(view, entry, ex, ey);
} else {
var title = item.items[1].items[0],
anchor = title.anchor,
tpad = item.titlePadding || 0,
tx = pad - title.x,
ty = pad - title.y;
switch (title.orient) {
case Left:
ex += Math.ceil(title.bounds.width()) + tpad;
break;
case Right:
case Bottom:
break;
default:
ey += title.bounds.height() + tpad;
}
if (ex || ey) translate(view, entry, ex, ey);
switch (title.orient) {
case Left:
ty += legendTitleOffset(item, entry, title, anchor, 1, 1);
break;
case Right:
tx += legendTitleOffset(item, entry, title, End, 0, 0) + tpad;
ty += legendTitleOffset(item, entry, title, anchor, 1, 1);
break;
case Bottom:
tx += legendTitleOffset(item, entry, title, anchor, 0, 0);
ty += legendTitleOffset(item, entry, title, End, -1, 0, 1) + tpad;
break;
default:
tx += legendTitleOffset(item, entry, title, anchor, 0, 0);
}
if (tx || ty) translate(view, title, tx, ty);
// translate legend if title pushes into negative coordinates
if ((tx = Math.round(title.bounds.x1 - pad)) < 0) {
translate(view, entry, -tx, 0);
translate(view, title, -tx, 0);
}
}
}
function legendTitleOffset(item, entry, title, anchor, y, lr, noBar) {
const grad = item.datum.type !== 'symbol',
vgrad = title.datum.vgrad,
e = grad && (lr || !vgrad) && !noBar ? entry.items[0] : entry,
s = e.bounds[y ? 'y2' : 'x2'] - item.padding,
u = vgrad && lr ? s : 0,
v = vgrad && lr ? 0 : s,
o = y <= 0 ? 0 : vegaScenegraph.multiLineOffset(title);
return Math.round(anchor === Start ? u
: anchor === End ? (v - o)
: 0.5 * (s - o));
}
function translate(view, item, dx, dy) {
item.x += dx;
item.y += dy;
item.bounds.translate(dx, dy);
item.mark.bounds.translate(dx, dy);
view.dirty(item);
}
function legendEntryLayout(entries) {
// get max widths for each column
var widths = entries.reduce(function(w, g) {
w[g.column] = Math.max(g.bounds.x2 - g.x, w[g.column] || 0);
return w;
}, {});
// set dimensions of legend entry groups
entries.forEach(function(g) {
g.width = widths[g.column];
g.height = g.bounds.y2 - g.y;
});
}
function titleLayout(view, mark, width, height, viewBounds) {
var group = mark.items[0],
frame = group.frame,
orient = group.orient,
anchor = group.anchor,
offset = group.offset,
padding = group.padding,
title = group.items[0].items[0],
subtitle = group.items[1] && group.items[1].items[0],
end = (orient === Left || orient === Right) ? height : width,
start = 0, x = 0, y = 0, sx = 0, sy = 0, pos;
if (frame !== Group) {
orient === Left ? (start = viewBounds.y2, end = viewBounds.y1)
: orient === Right ? (start = viewBounds.y1, end = viewBounds.y2)
: (start = viewBounds.x1, end = viewBounds.x2);
} else if (orient === Left) {
start = height, end = 0;
}
pos = (anchor === Start) ? start
: (anchor === End) ? end
: (start + end) / 2;
if (subtitle && subtitle.text) {
// position subtitle
switch (orient) {
case Top:
case Bottom:
sy = title.bounds.height() + padding;
break;
case Left:
sx = title.bounds.width() + padding;
break;
case Right:
sx = -title.bounds.width() - padding;
break;
}
tempBounds.clear().union(subtitle.bounds);
tempBounds.translate(sx - (subtitle.x || 0), sy - (subtitle.y || 0));
if (set(subtitle, 'x', sx) | set(subtitle, 'y', sy)) {
view.dirty(subtitle);
subtitle.bounds.clear().union(tempBounds);
subtitle.mark.bounds.clear().union(tempBounds);
view.dirty(subtitle);
}
tempBounds.clear().union(subtitle.bounds);
} else {
tempBounds.clear();
}
tempBounds.union(title.bounds);
// position title group
switch (orient) {
case Top:
x = pos;
y = viewBounds.y1 - tempBounds.height() - offset;
break;
case Left:
x = viewBounds.x1 - tempBounds.width() - offset;
y = pos;
break;
case Right:
x = viewBounds.x2 + tempBounds.width() + offset;
y = pos;
break;
case Bottom:
x = pos;
y = viewBounds.y2 + offset;
break;
default:
x = group.x;
y = group.y;
}
if (set(group, 'x', x) | set(group, 'y', y)) {
tempBounds.translate(x, y);
view.dirty(group);
group.bounds.clear().union(tempBounds);
mark.bounds.clear().union(tempBounds);
view.dirty(group);
}
return group.bounds;
}
/**
* Layout view elements such as axes and legends.
* Also performs size adjustments.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {object} params.mark - Scenegraph mark of groups to layout.
*/
function ViewLayout(params) {
vegaDataflow.Transform.call(this, null, params);
}
var prototype$5 = vegaUtil.inherits(ViewLayout, vegaDataflow.Transform);
prototype$5.transform = function(_, pulse) {
// TODO incremental update, output?
var view = pulse.dataflow;
_.mark.items.forEach(function(group) {
if (_.layout) trellisLayout(view, group, _.layout);
layoutGroup(view, group, _);
});
if (_.modified()) pulse.reflow();
return pulse;
};
function layoutGroup(view, group, _) {
var items = group.items,
width = Math.max(0, group.width || 0),
height = Math.max(0, group.height || 0),
viewBounds = new vegaScenegraph.Bounds().set(0, 0, width, height),
xBounds = viewBounds.clone(),
yBounds = viewBounds.clone(),
legends = [], title,
mark, orient, b, i, n;
// layout axes, gather legends, collect bounds
for (i=0, n=items.length; i<n; ++i) {
mark = items[i];
switch (mark.role) {
case AxisRole:
b = isYAxis(mark) ? xBounds : yBounds;
b.union(axisLayout(view, mark, width, height));
break;
case TitleRole:
title = mark;
break;
case LegendRole:
legends.push(legendLayout(view, mark));
break;
case FrameRole:
case ScopeRole:
case RowHeader:
case RowFooter:
case RowTitle:
case ColHeader:
case ColFooter:
case ColTitle:
xBounds.union(mark.bounds);
yBounds.union(mark.bounds);
break;
default:
viewBounds.union(mark.bounds);
}
}
// layout legends, adjust viewBounds
if (legends.length) {
// group legends by orient
const l = {};
legends.forEach(item => {
orient = item.orient || Right;
if (orient !== None) (l[orient] || (l[orient] = [])).push(item);
});
// perform grid layout for each orient group
for (let orient in l) {
const g = l[orient];
gridLayout(view, g, legendParams(
g, orient, _.legends, xBounds, yBounds, width, height
));
}
// update view bounds
legends.forEach(item => {
const b = item.bounds;
if (!b.equals(item._bounds)) {
item.bounds = item._bounds;
view.dirty(item); // dirty previous location
item.bounds = b;
view.dirty(item);
}
if (_.autosize && _.autosize.type === Fit) {
// For autosize fit, incorporate the orthogonal dimension only.
// Legends that overrun the chart area will then be clipped;
// otherwise the chart area gets reduced to nothing!
switch(item.orient) {
case Left:
case Right:
viewBounds.add(b.x1, 0).add(b.x2, 0);
break;
case Top:
case Bottom:
viewBounds.add(0, b.y1).add(0, b.y2);
}
} else {
viewBounds.union(b);
}
});
}
// combine bounding boxes
viewBounds.union(xBounds).union(yBounds);
// layout title, adjust bounds
if (title) {
viewBounds.union(titleLayout(view, title, width, height, viewBounds));
}
// override aggregated view bounds if content is clipped
if (group.clip) {
viewBounds.set(0, 0, group.width || 0, group.height || 0);
}
// perform size adjustment
viewSizeLayout(view, group, viewBounds, _);
}
function viewSizeLayout(view, group, viewBounds, _) {
var auto = _.autosize || {},
type = auto.type,
viewWidth = view._width,
viewHeight = view._height,
padding = view.padding();
if (view._autosize < 1 || !type) return;
var width = Math.max(0, group.width || 0),
left = Math.max(0, Math.ceil(-viewBounds.x1)),
right = Math.max(0, Math.ceil(viewBounds.x2 - width)),
height = Math.max(0, group.height || 0),
top = Math.max(0, Math.ceil(-viewBounds.y1)),
bottom = Math.max(0, Math.ceil(viewBounds.y2 - height));
if (auto.contains === Padding) {
viewWidth -= padding.left + padding.right;
viewHeight -= padding.top + padding.bottom;
}
if (type === None) {
left = 0;
top = 0;
width = viewWidth;
height = viewHeight;
}
else if (type === Fit) {
width = Math.max(0, viewWidth - left - right);
height = Math.max(0, viewHeight - top - bottom);
}
else if (type === FitX) {
width = Math.max(0, viewWidth - left - right);
viewHeight = height + top + bottom;
}
else if (type === FitY) {
viewWidth = width + left + right;
height = Math.max(0, viewHeight - top - bottom);
}
else if (type === Pad) {
viewWidth = width + left + right;
viewHeight = height + top + bottom;
}
view._resizeView(
viewWidth, viewHeight,
width, height,
[left, top],
auto.resize
);
}
exports.bound = Bound;
exports.identifier = Identifier;
exports.mark = Mark;
exports.overlap = Overlap;
exports.render = Render;
exports.viewlayout = ViewLayout;
Object.defineProperty(exports, '__esModule', { value: true });
})));