File: //opt/textanalyse/web_console/node_modules/vega-view-transforms/src/layout/legend.js
import {
Bottom, BottomLeft, BottomRight, Each, End, Flush, Left, Middle,
None, Right, Start, Symbols, Top,
TopLeft, TopRight
} from '../constants';
import {boundStroke, multiLineOffset} from 'vega-scenegraph';
// utility for looking up legend layout configuration
function lookup(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) {
let max = -Infinity;
legends.forEach(item => {
if (item.offset != null) max = Math.max(max, item.offset);
});
return max > -Infinity ? max : value;
}
export function legendParams(g, orient, config, xb, yb, w, h) {
const _ = lookup(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;
}
export 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;
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 : 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
const widths = entries.reduce((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(g => {
g.width = widths[g.column];
g.height = g.bounds.y2 - g.y;
});
}