vega-label
Advanced tools
Comparing version 0.0.5 to 1.0.0
@@ -7,16 +7,12 @@ (function (global, factory) { | ||
// bit mask for getting first 2 bytes of alpha value | ||
const ALPHA_MASK = 0xff000000; | ||
const ALPHA_MASK = 0xff000000; // alpha value equivalent to opacity 0.0625 | ||
// alpha value equivalent to opacity 0.0625 | ||
const INSIDE_OPACITY_IN_ALPHA = 0x10000000; | ||
const INSIDE_OPACITY = 0.0625; | ||
function baseBitmaps($, data) { | ||
const bitmap = $.bitmap(); // when there is no base mark but data points are to be avoided | ||
function baseBitmaps($, data) { | ||
const bitmap = $.bitmap(); | ||
// when there is no base mark but data points are to be avoided | ||
(data || []).forEach(d => bitmap.set($(d.boundary[0]), $(d.boundary[3]))); | ||
return [bitmap, undefined]; | ||
} | ||
function markBitmaps($, avoidMarks, labelInside, isGroupArea) { | ||
@@ -27,17 +23,16 @@ // create canvas | ||
border = labelInside || isGroupArea, | ||
context = vegaCanvas.canvas(width, height).getContext('2d'); | ||
context = vegaCanvas.canvas(width, height).getContext('2d'); // render all marks to be avoided into canvas | ||
// render all marks to be avoided into canvas | ||
avoidMarks.forEach(items => draw(context, items, border)); | ||
avoidMarks.forEach(items => draw(context, items, border)); // get canvas buffer, create bitmaps | ||
// get canvas buffer, create bitmaps | ||
const buffer = new Uint32Array(context.getImageData(0, 0, width, height).data.buffer), | ||
layer1 = $.bitmap(), | ||
layer2 = border && $.bitmap(); | ||
layer2 = border && $.bitmap(); // populate bitmap layers | ||
// populate bitmap layers | ||
let x, y, u, v, alpha; | ||
for (y=0; y < height; ++y) { | ||
for (x=0; x < width; ++x) { | ||
for (y = 0; y < height; ++y) { | ||
for (x = 0; x < width; ++x) { | ||
alpha = buffer[y * width + x] & ALPHA_MASK; | ||
if (alpha) { | ||
@@ -47,2 +42,3 @@ u = $(x); | ||
if (!isGroupArea) layer1.set(u, v); // update interior bitmap | ||
if (border && alpha ^ INSIDE_OPACITY_IN_ALPHA) layer2.set(u, v); // update border bitmap | ||
@@ -65,6 +61,7 @@ } | ||
} else { | ||
vegaScenegraph.Marks[type].draw(context, {items: interior ? items.map(prepare) : items}); | ||
vegaScenegraph.Marks[type].draw(context, { | ||
items: interior ? items.map(prepare) : items | ||
}); | ||
} | ||
} | ||
/** | ||
@@ -75,2 +72,4 @@ * Prepare item before drawing into canvas (setting stroke and opacity) | ||
*/ | ||
function prepare(source) { | ||
@@ -93,16 +92,21 @@ const item = vegaDataflow.rederive(source, {}); | ||
const DIV = 5, // bit shift from x, y index to bit vector array index | ||
MOD = 31, // bit mask for index lookup within a bit vector | ||
SIZE = 32, // individual bit vector size | ||
RIGHT0 = new Uint32Array(SIZE + 1), // left-anchored bit vectors, full -> 0 | ||
RIGHT1 = new Uint32Array(SIZE + 1); // right-anchored bit vectors, 0 -> full | ||
const DIV = 5, | ||
// bit shift from x, y index to bit vector array index | ||
MOD = 31, | ||
// bit mask for index lookup within a bit vector | ||
SIZE = 32, | ||
// individual bit vector size | ||
RIGHT0 = new Uint32Array(SIZE + 1), | ||
// left-anchored bit vectors, full -> 0 | ||
RIGHT1 = new Uint32Array(SIZE + 1); // right-anchored bit vectors, 0 -> full | ||
RIGHT1[0] = 0; | ||
RIGHT0[0] = ~RIGHT1[0]; | ||
for (let i=1; i <= SIZE; ++i) { | ||
RIGHT1[i] = (RIGHT1[i - 1] << 1) | 1; | ||
for (let i = 1; i <= SIZE; ++i) { | ||
RIGHT1[i] = RIGHT1[i - 1] << 1 | 1; | ||
RIGHT0[i] = ~RIGHT1[i]; | ||
} | ||
function Bitmap(w, h) { | ||
function Bitmap (w, h) { | ||
const array = new Uint32Array(~~((w * h + SIZE) / SIZE)); | ||
@@ -120,20 +124,23 @@ | ||
array: array, | ||
get: (x, y) => { | ||
const index = y * w + x; | ||
return array[index >>> DIV] & (1 << (index & MOD)); | ||
return array[index >>> DIV] & 1 << (index & MOD); | ||
}, | ||
set: (x, y) => { | ||
const index = y * w + x; | ||
_set(index >>> DIV, 1 << (index & MOD)); | ||
}, | ||
clear: (x, y) => { | ||
const index = y * w + x; | ||
_clear(index >>> DIV, ~(1 << (index & MOD))); | ||
}, | ||
getRange: (x, y, x2, y2) => { | ||
let r = y2, | ||
start, | ||
end, | ||
indexStart, | ||
indexEnd; | ||
getRange: (x, y, x2, y2) => { | ||
let r = y2, start, end, indexStart, indexEnd; | ||
for (; r >= y; --r) { | ||
@@ -144,2 +151,3 @@ start = r * w + x; | ||
indexEnd = end >>> DIV; | ||
if (indexStart === indexEnd) { | ||
@@ -152,2 +160,3 @@ if (array[indexStart] & RIGHT0[start & MOD] & RIGHT1[(end & MOD) + 1]) { | ||
if (array[indexEnd] & RIGHT1[(end & MOD) + 1]) return true; | ||
for (let i = indexStart + 1; i < indexEnd; ++i) { | ||
@@ -158,7 +167,8 @@ if (array[i]) return true; | ||
} | ||
return false; | ||
}, | ||
setRange: (x, y, x2, y2) => { | ||
let start, end, indexStart, indexEnd, i; | ||
for (; y <= y2; ++y) { | ||
@@ -169,2 +179,3 @@ start = y * w + x; | ||
indexEnd = end >>> DIV; | ||
if (indexStart === indexEnd) { | ||
@@ -174,3 +185,5 @@ _set(indexStart, RIGHT0[start & MOD] & RIGHT1[(end & MOD) + 1]); | ||
_set(indexStart, RIGHT0[start & MOD]); | ||
_set(indexEnd, RIGHT1[(end & MOD) + 1]); | ||
for (i = indexStart + 1; i < indexEnd; ++i) _set(i, 0xffffffff); | ||
@@ -180,5 +193,5 @@ } | ||
}, | ||
clearRange: (x, y, x2, y2) => { | ||
let start, end, indexStart, indexEnd, i; | ||
for (; y <= y2; ++y) { | ||
@@ -189,2 +202,3 @@ start = y * w + x; | ||
indexEnd = end >>> DIV; | ||
if (indexStart === indexEnd) { | ||
@@ -194,3 +208,5 @@ _clear(indexStart, RIGHT1[start & MOD] | RIGHT0[(end & MOD) + 1]); | ||
_clear(indexStart, RIGHT1[start & MOD]); | ||
_clear(indexEnd, RIGHT0[(end & MOD) + 1]); | ||
for (i = indexStart + 1; i < indexEnd; ++i) _clear(i, 0); | ||
@@ -200,3 +216,2 @@ } | ||
}, | ||
outOfBounds: (x, y, x2, y2) => x < 0 || y < 0 || y2 >= h || x2 >= w | ||
@@ -206,4 +221,4 @@ }; | ||
function scaler(width, height, padding) { | ||
const ratio = Math.max(1, Math.sqrt((width * height) / 1e6)), | ||
function scaler (width, height, padding) { | ||
const ratio = Math.max(1, Math.sqrt(width * height / 1e6)), | ||
w = ~~((width + 2 * padding + ratio) / ratio), | ||
@@ -213,4 +228,6 @@ h = ~~((height + 2 * padding + ratio) / ratio), | ||
scale.invert = _ => (_ * ratio) - padding; | ||
scale.invert = _ => _ * ratio - padding; | ||
scale.bitmap = () => Bitmap(w, h); | ||
scale.ratio = ratio; | ||
@@ -220,22 +237,28 @@ scale.padding = padding; | ||
scale.height = height; | ||
return scale; | ||
} | ||
function placeAreaLabelNaive($, bitmaps, avoidBaseMark, markIndex) { | ||
function placeAreaLabelNaive ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height; | ||
// try to place a label within an input area mark | ||
return function(d) { | ||
const items = d.datum.datum.items[markIndex].items, // area points | ||
n = items.length, // number of points | ||
textHeight = d.datum.fontSize, // label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text); // label height | ||
height = $.height; // try to place a label within an input area mark | ||
return function (d) { | ||
const items = d.datum.datum.items[markIndex].items, | ||
// area points | ||
n = items.length, | ||
// number of points | ||
textHeight = d.datum.fontSize, | ||
// label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text); // label height | ||
let maxAreaWidth = 0, | ||
x1, x2, y1, y2, x, y, areaWidth; | ||
x1, | ||
x2, | ||
y1, | ||
y2, | ||
x, | ||
y, | ||
areaWidth; // for each area sample point | ||
// for each area sample point | ||
for (let i=0; i<n; ++i) { | ||
for (let i = 0; i < n; ++i) { | ||
x1 = items[i].x; | ||
@@ -247,4 +270,4 @@ y1 = items[i].y; | ||
y = (y1 + y2) / 2; | ||
areaWidth = Math.abs(x2 - x1 + y2 - y1); | ||
areaWidth = Math.abs(x2 - x1 + y2 - y1); | ||
if (areaWidth >= maxAreaWidth) { | ||
@@ -263,4 +286,4 @@ maxAreaWidth = areaWidth; | ||
y2 = d.y + y; | ||
d.align = 'center'; | ||
d.align = 'center'; | ||
if (x1 < 0 && x2 <= width) { | ||
@@ -273,2 +296,3 @@ d.align = 'left'; | ||
d.baseline = 'middle'; | ||
if (y1 < 0 && y2 <= height) { | ||
@@ -286,26 +310,20 @@ d.baseline = 'top'; | ||
let r = textWidth / 2; | ||
return x - r < 0 | ||
|| x + r > width | ||
|| y - (r = textHeight / 2) < 0 | ||
|| y + r > height; | ||
return x - r < 0 || x + r > width || y - (r = textHeight / 2) < 0 || y + r > height; | ||
} | ||
function collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
const w = (textWidth * h) / (textHeight * 2), | ||
const w = textWidth * h / (textHeight * 2), | ||
x1 = $(x - w), | ||
x2 = $(x + w), | ||
y1 = $(y - (h = h/2)), | ||
y1 = $(y - (h = h / 2)), | ||
y2 = $(y + h); | ||
return bm0.outOfBounds(x1, y1, x2, y2) | ||
|| bm0.getRange(x1, y1, x2, y2) | ||
|| (bm1 && bm1.getRange(x1, y1, x2, y2)); | ||
return bm0.outOfBounds(x1, y1, x2, y2) || bm0.getRange(x1, y1, x2, y2) || bm1 && bm1.getRange(x1, y1, x2, y2); | ||
} | ||
function placeAreaLabelReducedSearch($, bitmaps, avoidBaseMark, markIndex) { | ||
function placeAreaLabelReducedSearch ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
bm0 = bitmaps[0], // where labels have been placed | ||
bm1 = bitmaps[1]; // area outlines | ||
height = $.height, | ||
bm0 = bitmaps[0], | ||
// where labels have been placed | ||
bm1 = bitmaps[1]; // area outlines | ||
function tryLabel(_x, _y, maxSize, textWidth, textHeight) { | ||
@@ -317,7 +335,4 @@ const x = $.invert(_x), | ||
mid; | ||
if ( | ||
!outOfBounds(x, y, textWidth, textHeight, width, height) && | ||
!collision($, x, y, textHeight, textWidth, lo, bm0, bm1) && | ||
!collision($, x, y, textHeight, textWidth, textHeight, bm0, null) | ||
) { | ||
if (!outOfBounds(x, y, textWidth, textHeight, width, height) && !collision($, x, y, textHeight, textWidth, lo, bm0, bm1) && !collision($, x, y, textHeight, textWidth, textHeight, bm0, null)) { | ||
// if the label fits at the current sample point, | ||
@@ -327,2 +342,3 @@ // perform binary search to find the largest font size that fits | ||
mid = (lo + hi) / 2; | ||
if (collision($, x, y, textHeight, textWidth, mid, bm0, bm1)) { | ||
@@ -333,4 +349,5 @@ hi = mid; | ||
} | ||
} | ||
// place label if current lower bound exceeds prior max font size | ||
} // place label if current lower bound exceeds prior max font size | ||
if (lo > maxSize) { | ||
@@ -340,11 +357,14 @@ return [x, y, lo, true]; | ||
} | ||
} | ||
} // try to place a label within an input area mark | ||
// try to place a label within an input area mark | ||
return function(d) { | ||
const items = d.datum.datum.items[markIndex].items, // area points | ||
n = items.length, // number of points | ||
textHeight = d.datum.fontSize, // label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text); // label height | ||
return function (d) { | ||
const items = d.datum.datum.items[markIndex].items, | ||
// area points | ||
n = items.length, | ||
// number of points | ||
textHeight = d.datum.fontSize, | ||
// label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text); // label height | ||
let maxSize = avoidBaseMark ? textHeight : 0, | ||
@@ -354,6 +374,22 @@ labelPlaced = false, | ||
maxAreaWidth = 0, | ||
x1, x2, y1, y2, x, y, _x, _y, _x1, _xMid, _x2, _y1, _yMid, _y2, areaWidth, result, swapTmp; | ||
x1, | ||
x2, | ||
y1, | ||
y2, | ||
x, | ||
y, | ||
_x, | ||
_y, | ||
_x1, | ||
_xMid, | ||
_x2, | ||
_y1, | ||
_yMid, | ||
_y2, | ||
areaWidth, | ||
result, | ||
swapTmp; // for each area sample point | ||
// for each area sample point | ||
for (let i=0; i<n; ++i) { | ||
for (let i = 0; i < n; ++i) { | ||
x1 = items[i].x; | ||
@@ -381,8 +417,8 @@ y1 = items[i].y; | ||
_y2 = $(y2); | ||
_yMid = ~~((_y1 + _y2) / 2); | ||
_yMid = ~~((_y1 + _y2) / 2); // search along the line from mid point between the 2 border to lower border | ||
// search along the line from mid point between the 2 border to lower border | ||
for (_x = _xMid; _x >= _x1; --_x) { | ||
for (_y = _yMid; _y >= _y1; --_y) { | ||
result = tryLabel(_x, _y, maxSize, textWidth, textHeight); | ||
if (result) { | ||
@@ -392,8 +428,9 @@ [d.x, d.y, maxSize, labelPlaced] = result; | ||
} | ||
} | ||
} // search along the line from mid point between the 2 border to upper border | ||
// search along the line from mid point between the 2 border to upper border | ||
for (_x = _xMid; _x <= _x2; ++_x) { | ||
for (_y = _yMid; _y <= _y2; ++_y) { | ||
result = tryLabel(_x, _y, maxSize, textWidth, textHeight); | ||
if (result) { | ||
@@ -403,6 +440,6 @@ [d.x, d.y, maxSize, labelPlaced] = result; | ||
} | ||
} | ||
} // place label at slice center if not placed through other means | ||
// and if we're not avoiding overlap with other areas | ||
// place label at slice center if not placed through other means | ||
// and if we're not avoiding overlap with other areas | ||
if (!labelPlaced && !avoidBaseMark) { | ||
@@ -412,10 +449,5 @@ // one span is zero, hence we can add | ||
x = (x1 + x2) / 2; | ||
y = (y1 + y2) / 2; | ||
y = (y1 + y2) / 2; // place label if it fits and improves the max area width | ||
// place label if it fits and improves the max area width | ||
if ( | ||
areaWidth >= maxAreaWidth && | ||
!outOfBounds(x, y, textWidth, textHeight, width, height) && | ||
!collision($, x, y, textHeight, textWidth, textHeight, bm0, null) | ||
) { | ||
if (areaWidth >= maxAreaWidth && !outOfBounds(x, y, textWidth, textHeight, width, height) && !collision($, x, y, textHeight, textWidth, textHeight, bm0, null)) { | ||
maxAreaWidth = areaWidth; | ||
@@ -427,5 +459,5 @@ d.x = x; | ||
} | ||
} | ||
} // record current label placement information, update label bitmap | ||
// record current label placement information, update label bitmap | ||
if (labelPlaced || labelPlaced2) { | ||
@@ -444,21 +476,25 @@ x = textWidth / 2; | ||
// pixel direction offsets for flood fill search | ||
const X_DIR = [-1, -1, 1, 1]; | ||
const Y_DIR = [-1, 1, -1, 1]; | ||
function placeAreaLabelFloodFill($, bitmaps, avoidBaseMark, markIndex) { | ||
function placeAreaLabelFloodFill ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
bm0 = bitmaps[0], // where labels have been placed | ||
bm1 = bitmaps[1], // area outlines | ||
bm2 = $.bitmap(); // flood-fill visitations | ||
height = $.height, | ||
bm0 = bitmaps[0], | ||
// where labels have been placed | ||
bm1 = bitmaps[1], | ||
// area outlines | ||
bm2 = $.bitmap(); // flood-fill visitations | ||
// try to place a label within an input area mark | ||
return function(d) { | ||
const items = d.datum.datum.items[markIndex].items, // area points | ||
n = items.length, // number of points | ||
textHeight = d.datum.fontSize, // label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text), // label height | ||
stack = []; // flood fill stack | ||
return function (d) { | ||
const items = d.datum.datum.items[markIndex].items, | ||
// area points | ||
n = items.length, | ||
// number of points | ||
textHeight = d.datum.fontSize, | ||
// label width | ||
textWidth = vegaScenegraph.textMetrics.width(d.datum, d.datum.text), | ||
// label height | ||
stack = []; // flood fill stack | ||
let maxSize = avoidBaseMark ? textHeight : 0, | ||
@@ -468,31 +504,39 @@ labelPlaced = false, | ||
maxAreaWidth = 0, | ||
x1, x2, y1, y2, x, y, _x, _y, lo, hi, mid, areaWidth; | ||
x1, | ||
x2, | ||
y1, | ||
y2, | ||
x, | ||
y, | ||
_x, | ||
_y, | ||
lo, | ||
hi, | ||
mid, | ||
areaWidth; // for each area sample point | ||
// for each area sample point | ||
for (let i=0; i<n; ++i) { | ||
for (let i = 0; i < n; ++i) { | ||
x1 = items[i].x; | ||
y1 = items[i].y; | ||
x2 = items[i].x2 === undefined ? x1 : items[i].x2; | ||
y2 = items[i].y2 === undefined ? y1 : items[i].y2; | ||
y2 = items[i].y2 === undefined ? y1 : items[i].y2; // add scaled center point to stack | ||
// add scaled center point to stack | ||
stack.push([$((x1 + x2) / 2), $((y1 + y2) / 2)]); | ||
stack.push([$((x1 + x2) / 2), $((y1 + y2) / 2)]); // perform flood fill, visit points | ||
// perform flood fill, visit points | ||
while (stack.length) { | ||
[_x, _y] = stack.pop(); | ||
[_x, _y] = stack.pop(); // exit if point already marked | ||
// exit if point already marked | ||
if (bm0.get(_x, _y) || bm1.get(_x, _y) || bm2.get(_x, _y)) continue; | ||
if (bm0.get(_x, _y) || bm1.get(_x, _y) || bm2.get(_x, _y)) continue; // mark point in flood fill bitmap | ||
// add search points for all (in bound) directions | ||
// mark point in flood fill bitmap | ||
// add search points for all (in bound) directions | ||
bm2.set(_x, _y); | ||
for (let j=0; j<4; ++j) { | ||
for (let j = 0; j < 4; ++j) { | ||
x = _x + X_DIR[j]; | ||
y = _y + Y_DIR[j]; | ||
if (!bm2.outOfBounds(x, y, x, y)) stack.push([x, y]); | ||
} | ||
} // unscale point back to x, y space | ||
// unscale point back to x, y space | ||
x = $.invert(_x); | ||
@@ -503,7 +547,3 @@ y = $.invert(_y); | ||
if ( | ||
!outOfBounds(x, y, textWidth, textHeight, width, height) && | ||
!collision($, x, y, textHeight, textWidth, lo, bm0, bm1) && | ||
!collision($, x, y, textHeight, textWidth, textHeight, bm0, null) | ||
) { | ||
if (!outOfBounds(x, y, textWidth, textHeight, width, height) && !collision($, x, y, textHeight, textWidth, lo, bm0, bm1) && !collision($, x, y, textHeight, textWidth, textHeight, bm0, null)) { | ||
// if the label fits at the current sample point, | ||
@@ -513,2 +553,3 @@ // perform binary search to find the largest font size that fits | ||
mid = (lo + hi) / 2; | ||
if (collision($, x, y, textHeight, textWidth, mid, bm0, bm1)) { | ||
@@ -519,4 +560,5 @@ hi = mid; | ||
} | ||
} | ||
// place label if current lower bound exceeds prior max font size | ||
} // place label if current lower bound exceeds prior max font size | ||
if (lo > maxSize) { | ||
@@ -529,6 +571,6 @@ d.x = x; | ||
} | ||
} | ||
} // place label at slice center if not placed through other means | ||
// and if we're not avoiding overlap with other areas | ||
// place label at slice center if not placed through other means | ||
// and if we're not avoiding overlap with other areas | ||
if (!labelPlaced && !avoidBaseMark) { | ||
@@ -538,10 +580,5 @@ // one span is zero, hence we can add | ||
x = (x1 + x2) / 2; | ||
y = (y1 + y2) / 2; | ||
y = (y1 + y2) / 2; // place label if it fits and improves the max area width | ||
// place label if it fits and improves the max area width | ||
if ( | ||
areaWidth >= maxAreaWidth && | ||
!outOfBounds(x, y, textWidth, textHeight, width, height) && | ||
!collision($, x, y, textHeight, textWidth, textHeight, bm0, null) | ||
) { | ||
if (areaWidth >= maxAreaWidth && !outOfBounds(x, y, textWidth, textHeight, width, height) && !collision($, x, y, textHeight, textWidth, textHeight, bm0, null)) { | ||
maxAreaWidth = areaWidth; | ||
@@ -553,5 +590,5 @@ d.x = x; | ||
} | ||
} | ||
} // record current label placement information, update label bitmap | ||
// record current label placement information, update label bitmap | ||
if (labelPlaced || labelPlaced2) { | ||
@@ -572,4 +609,3 @@ x = textWidth / 2; | ||
Baselines = ['bottom', 'middle', 'top']; | ||
function placeMarkLabel($, bitmaps, anchors, offsets) { | ||
function placeMarkLabel ($, bitmaps, anchors, offsets) { | ||
const width = $.width, | ||
@@ -580,8 +616,6 @@ height = $.height, | ||
n = offsets.length; | ||
return function(d) { | ||
return function (d) { | ||
const boundary = d.boundary, | ||
textHeight = d.datum.fontSize; | ||
textHeight = d.datum.fontSize; // can not be placed if the mark is not visible in the graph bound | ||
// can not be placed if the mark is not visible in the graph bound | ||
if (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height) { | ||
@@ -592,20 +626,29 @@ return false; | ||
let textWidth = 0, | ||
dx, dy, isInside, sizeFactor, insideFactor, | ||
x1, x2, y1, y2, xc, yc, | ||
_x1, _x2, _y1, _y2; | ||
dx, | ||
dy, | ||
isInside, | ||
sizeFactor, | ||
insideFactor, | ||
x1, | ||
x2, | ||
y1, | ||
y2, | ||
xc, | ||
yc, | ||
_x1, | ||
_x2, | ||
_y1, | ||
_y2; // for each anchor and offset | ||
// for each anchor and offset | ||
for (let i=0; i<n; ++i) { | ||
for (let i = 0; i < n; ++i) { | ||
dx = (anchors[i] & 0x3) - 1; | ||
dy = ((anchors[i] >>> 0x2) & 0x3) - 1; | ||
isInside = (dx === 0 && dy === 0) || offsets[i] < 0; | ||
dy = (anchors[i] >>> 0x2 & 0x3) - 1; | ||
isInside = dx === 0 && dy === 0 || offsets[i] < 0; | ||
sizeFactor = dx && dy ? Math.SQRT1_2 : 1; | ||
insideFactor = offsets[i] < 0 ? -1 : 1; | ||
x1 = boundary[1 + dx] + offsets[i] * dx * sizeFactor; | ||
yc = boundary[4 + dy] + (insideFactor * textHeight * dy) / 2 + offsets[i] * dy * sizeFactor; | ||
yc = boundary[4 + dy] + insideFactor * textHeight * dy / 2 + offsets[i] * dy * sizeFactor; | ||
y1 = yc - textHeight / 2; | ||
y2 = yc + textHeight / 2; | ||
_x1 = $(x1); | ||
@@ -626,6 +669,5 @@ _y1 = $(y1); | ||
xc = x1 + (insideFactor * textWidth * dx) / 2; | ||
xc = x1 + insideFactor * textWidth * dx / 2; | ||
x1 = xc - textWidth / 2; | ||
x2 = xc + textWidth / 2; | ||
_x1 = $(x1); | ||
@@ -638,6 +680,4 @@ _x2 = $(x2); | ||
d.y = !dy ? yc : dy * insideFactor < 0 ? y2 : y1; | ||
d.align = Aligns[dx * insideFactor + 1]; | ||
d.baseline = Baselines[dy * insideFactor + 1]; | ||
bm0.setRange(_x1, _y1, _x2, _y2); | ||
@@ -650,40 +690,30 @@ return true; | ||
}; | ||
} | ||
} // Test if a label with the given dimensions can be added without overlap | ||
// Test if a label with the given dimensions can be added without overlap | ||
function test(_x1, _x2, _y1, _y2, bm0, bm1, x1, x2, y1, y2, boundary, isInside) { | ||
return !( | ||
bm0.outOfBounds(_x1, _y1, _x2, _y2) || | ||
(isInside && bm1 | ||
? bm1.getRange(_x1, _y1, _x2, _y2) || !isInMarkBound(x1, y1, x2, y2, boundary) | ||
: bm0.getRange(_x1, _y1, _x2, _y2)) | ||
); | ||
return !(bm0.outOfBounds(_x1, _y1, _x2, _y2) || (isInside && bm1 ? bm1.getRange(_x1, _y1, _x2, _y2) || !isInMarkBound(x1, y1, x2, y2, boundary) : bm0.getRange(_x1, _y1, _x2, _y2))); | ||
} | ||
function isInMarkBound(x1, y1, x2, y2, boundary) { | ||
return boundary[0] <= x1 && x2 <= boundary[2] | ||
&& boundary[3] <= y1 && y2 <= boundary[5]; | ||
return boundary[0] <= x1 && x2 <= boundary[2] && boundary[3] <= y1 && y2 <= boundary[5]; | ||
} | ||
// 8-bit representation of anchors | ||
const TOP = 0x0, | ||
const TOP = 0x0, | ||
MIDDLE = 0x4, | ||
BOTTOM = 0x8, | ||
LEFT = 0x0, | ||
LEFT = 0x0, | ||
CENTER = 0x1, | ||
RIGHT = 0x2; | ||
RIGHT = 0x2; // Mapping from text anchor to number representation | ||
// Mapping from text anchor to number representation | ||
const anchorCode = { | ||
'top-left': TOP + LEFT, | ||
'top': TOP + CENTER, | ||
'top-right': TOP + RIGHT, | ||
'left': MIDDLE + LEFT, | ||
'middle': MIDDLE + CENTER, | ||
'right': MIDDLE + RIGHT, | ||
'bottom-left': BOTTOM + LEFT, | ||
'bottom': BOTTOM + CENTER, | ||
'top-left': TOP + LEFT, | ||
'top': TOP + CENTER, | ||
'top-right': TOP + RIGHT, | ||
'left': MIDDLE + LEFT, | ||
'middle': MIDDLE + CENTER, | ||
'right': MIDDLE + RIGHT, | ||
'bottom-left': BOTTOM + LEFT, | ||
'bottom': BOTTOM + CENTER, | ||
'bottom-right': BOTTOM + RIGHT | ||
}; | ||
const placeAreaLabel = { | ||
@@ -694,9 +724,5 @@ 'naive': placeAreaLabelNaive, | ||
}; | ||
function labelLayout(texts, size, compare, offset, anchor, | ||
avoidMarks, avoidBaseMark, lineAnchor, markIndex, padding, method) | ||
{ | ||
function labelLayout (texts, size, compare, offset, anchor, avoidMarks, avoidBaseMark, lineAnchor, markIndex, padding, method) { | ||
// early exit for empty data | ||
if (!texts.length) return texts; | ||
const positions = Math.max(offset.length, anchor.length), | ||
@@ -710,5 +736,4 @@ offsets = getOffsets(offset, positions), | ||
$ = scaler(size[0], size[1], padding), | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; // prepare text mark data for placing | ||
// prepare text mark data for placing | ||
const data = texts.map(d => ({ | ||
@@ -723,4 +748,4 @@ datum: d, | ||
})); | ||
let bitmaps; | ||
let bitmaps; | ||
if (!isNaiveGroupArea) { | ||
@@ -730,32 +755,27 @@ // sort labels in priority order, if comparator is provided | ||
data.sort((a, b) => compare(a.datum, b.datum)); | ||
} | ||
} // flag indicating if label can be placed inside its base mark | ||
// flag indicating if label can be placed inside its base mark | ||
let labelInside = false; | ||
for (let i=0; i < anchors.length && !labelInside; ++i) { | ||
for (let i = 0; i < anchors.length && !labelInside; ++i) { | ||
// label inside if anchor is at center | ||
// label inside if offset to be inside the mark bound | ||
labelInside = anchors[i] === 0x5 || offsets[i] < 0; | ||
} | ||
} // extract data information from base mark when base mark is to be avoided | ||
// base mark is implicitly avoided if it is a group area | ||
// extract data information from base mark when base mark is to be avoided | ||
// base mark is implicitly avoided if it is a group area | ||
if (marktype && (avoidBaseMark || isGroupArea)) { | ||
avoidMarks = [texts.map(d => d.datum)].concat(avoidMarks); | ||
} | ||
} // generate bitmaps for layout calculation | ||
// generate bitmaps for layout calculation | ||
bitmaps = avoidMarks.length | ||
? markBitmaps($, avoidMarks, labelInside, isGroupArea) | ||
: baseBitmaps($, avoidBaseMark && data); | ||
} | ||
// generate label placement function | ||
const place = isGroupArea | ||
? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex) | ||
: placeMarkLabel($, bitmaps, anchors, offsets); | ||
bitmaps = avoidMarks.length ? markBitmaps($, avoidMarks, labelInside, isGroupArea) : baseBitmaps($, avoidBaseMark && data); | ||
} // generate label placement function | ||
// place all labels | ||
const place = isGroupArea ? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex) : placeMarkLabel($, bitmaps, anchors, offsets); // place all labels | ||
data.forEach(d => d.opacity = +place(d)); | ||
return data; | ||
@@ -767,4 +787,7 @@ } | ||
n = _.length; | ||
for (let i=0; i<n; ++i) offsets[i] = _[i] || 0; | ||
for (let i=n; i<count; ++i) offsets[i] = offsets[n - 1]; | ||
for (let i = 0; i < n; ++i) offsets[i] = _[i] || 0; | ||
for (let i = n; i < count; ++i) offsets[i] = offsets[n - 1]; | ||
return offsets; | ||
@@ -776,4 +799,7 @@ } | ||
n = _.length; | ||
for (let i=0; i<n; ++i) anchors[i] |= anchorCode[_[i]]; | ||
for (let i=n; i<count; ++i) anchors[i] = anchors[n - 1]; | ||
for (let i = 0; i < n; ++i) anchors[i] |= anchorCode[_[i]]; | ||
for (let i = n; i < count; ++i) anchors[i] = anchors[n - 1]; | ||
return anchors; | ||
@@ -785,3 +811,2 @@ } | ||
} | ||
/** | ||
@@ -794,2 +819,4 @@ * Factory function for function for getting base mark boundary, depending | ||
*/ | ||
function markBoundary(marktype, grouptype, lineAnchor, markIndex) { | ||
@@ -800,18 +827,13 @@ const xy = d => [d.x, d.x, d.x, d.y, d.y, d.y]; | ||
return xy; // no reactive geometry | ||
} | ||
else if (marktype === 'line' || marktype === 'area') { | ||
} else if (marktype === 'line' || marktype === 'area') { | ||
return d => xy(d.datum); | ||
} | ||
else if (grouptype === 'line') { | ||
} else if (grouptype === 'line') { | ||
return d => { | ||
const items = d.datum.items[markIndex].items; | ||
return xy(items.length | ||
? items[lineAnchor === 'start' ? 0 : items.length - 1] | ||
: {x: NaN, y: NaN}); | ||
return xy(items.length ? items[lineAnchor === 'start' ? 0 : items.length - 1] : { | ||
x: NaN, | ||
y: NaN | ||
}); | ||
}; | ||
} | ||
else { | ||
} else { | ||
return d => { | ||
@@ -824,21 +846,4 @@ const b = d.datum.bounds; | ||
const Output = [ | ||
'x', | ||
'y', | ||
'opacity', | ||
'align', | ||
'baseline' | ||
]; | ||
const Anchors = [ | ||
'top-left', | ||
'left', | ||
'bottom-left', | ||
'top', | ||
'bottom', | ||
'top-right', | ||
'right', | ||
'bottom-right' | ||
]; | ||
const Output = ['x', 'y', 'opacity', 'align', 'baseline']; | ||
const Anchors = ['top-left', 'left', 'bottom-left', 'top', 'bottom', 'top-right', 'right', 'bottom-right']; | ||
/** | ||
@@ -870,24 +875,63 @@ * Compute text label layout to annotate marks. | ||
*/ | ||
function Label(params) { | ||
vegaDataflow.Transform.call(this, null, params); | ||
} | ||
Label.Definition = { | ||
type: 'Label', | ||
metadata: { modifies: true }, | ||
params: [ | ||
{ name: 'size', type: 'number', array: true, length: 2, required: true }, | ||
{ name: 'sort', type: 'compare' }, | ||
{ name: 'anchor', type: 'string', array: true, default: Anchors }, | ||
{ name: 'offset', type: 'number', array: true, default: [1] }, | ||
{ name: 'padding', type: 'number', default: 0 }, | ||
{ name: 'lineAnchor', type: 'string', values: ['start', 'end'], default: 'end' }, | ||
{ name: 'markIndex', type: 'number', default: 0 }, | ||
{ name: 'avoidBaseMark', type: 'boolean', default: true }, | ||
{ name: 'avoidMarks', type: 'data', array: true }, | ||
{ name: 'method', type: 'string', default: 'naive'}, | ||
{ name: 'as', type: 'string', array: true, length: Output.length, default: Output } | ||
] | ||
metadata: { | ||
modifies: true | ||
}, | ||
params: [{ | ||
name: 'size', | ||
type: 'number', | ||
array: true, | ||
length: 2, | ||
required: true | ||
}, { | ||
name: 'sort', | ||
type: 'compare' | ||
}, { | ||
name: 'anchor', | ||
type: 'string', | ||
array: true, | ||
default: Anchors | ||
}, { | ||
name: 'offset', | ||
type: 'number', | ||
array: true, | ||
default: [1] | ||
}, { | ||
name: 'padding', | ||
type: 'number', | ||
default: 0 | ||
}, { | ||
name: 'lineAnchor', | ||
type: 'string', | ||
values: ['start', 'end'], | ||
default: 'end' | ||
}, { | ||
name: 'markIndex', | ||
type: 'number', | ||
default: 0 | ||
}, { | ||
name: 'avoidBaseMark', | ||
type: 'boolean', | ||
default: true | ||
}, { | ||
name: 'avoidMarks', | ||
type: 'data', | ||
array: true | ||
}, { | ||
name: 'method', | ||
type: 'string', | ||
default: 'naive' | ||
}, { | ||
name: 'as', | ||
type: 'string', | ||
array: true, | ||
length: Output.length, | ||
default: Output | ||
}] | ||
}; | ||
vegaUtil.inherits(Label, vegaDataflow.Transform, { | ||
@@ -901,3 +945,5 @@ transform(_, pulse) { | ||
const mod = _.modified(); | ||
if (!(mod || pulse.changed(pulse.ADD_REM) || modp('sort'))) return; | ||
if (!_.size || _.size.length !== 2) { | ||
@@ -907,18 +953,5 @@ vegaUtil.error('Size parameter should be specified as a [width, height] array.'); | ||
const as = _.as || Output; | ||
const as = _.as || Output; // run label layout | ||
// run label layout | ||
labelLayout( | ||
pulse.materialize(pulse.SOURCE).source, | ||
_.size, | ||
_.sort, | ||
vegaUtil.array(_.offset || 1), | ||
vegaUtil.array(_.anchor || Anchors), | ||
_.avoidMarks || [], | ||
_.avoidBaseMark === false ? false : true, | ||
_.lineAnchor || 'end', | ||
_.markIndex || 0, | ||
_.padding || 0, | ||
_.method || 'naive' | ||
).forEach(l => { | ||
labelLayout(pulse.materialize(pulse.SOURCE).source, _.size, _.sort, vegaUtil.array(_.offset || 1), vegaUtil.array(_.anchor || Anchors), _.avoidMarks || [], _.avoidBaseMark === false ? false : true, _.lineAnchor || 'end', _.markIndex || 0, _.padding || 0, _.method || 'naive').forEach(l => { | ||
// write layout results to data stream | ||
@@ -932,5 +965,5 @@ const t = l.datum; | ||
}); | ||
return pulse.reflow(mod).modifies(as); | ||
} | ||
}); | ||
@@ -937,0 +970,0 @@ |
@@ -1,1 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("vega-canvas"),require("vega-dataflow"),require("vega-scenegraph"),require("vega-util")):"function"==typeof define&&define.amd?define(["exports","vega-canvas","vega-dataflow","vega-scenegraph","vega-util"],e):e(((t="undefined"!=typeof globalThis?globalThis:t||self).vega=t.vega||{},t.vega.transforms={}),t.vega,t.vega,t.vega,t.vega)}(this,(function(t,e,n,r,a){"use strict";function i(t,n,a,i){const u=t.width,s=t.height,f=a||i,l=e.canvas(u,s).getContext("2d");n.forEach(t=>function t(e,n,a){if(!n.length)return;const i=n[0].mark.marktype;"group"===i?n.forEach(n=>{n.items.forEach(n=>t(e,n.items,a))}):r.Marks[i].draw(e,{items:a?n.map(o):n})}(l,t,f));const d=new Uint32Array(l.getImageData(0,0,u,s).data.buffer),c=t.bitmap(),m=f&&t.bitmap();let g,h,y,p,x;for(h=0;h<s;++h)for(g=0;g<u;++g)x=4278190080&d[h*u+g],x&&(y=t(g),p=t(h),i||c.set(y,p),f&&268435456^x&&m.set(y,p));return[c,m]}function o(t){const e=n.rederive(t,{});return e.stroke&&(e.strokeOpacity=1),e.fill&&(e.fillOpacity=.0625,e.stroke="#000",e.strokeOpacity=1,e.strokeWidth=2),e}const u=31,s=new Uint32Array(33),f=new Uint32Array(33);f[0]=0,s[0]=~f[0];for(let t=1;t<=32;++t)f[t]=f[t-1]<<1|1,s[t]=~f[t];function l(t,e,n){const r=Math.max(1,Math.sqrt(t*e/1e6)),a=~~((t+2*n+r)/r),i=~~((e+2*n+r)/r),o=t=>~~((t+n)/r);return o.invert=t=>t*r-n,o.bitmap=()=>function(t,e){const n=new Uint32Array(~~((t*e+32)/32));function r(t,e){n[t]|=e}function a(t,e){n[t]&=e}return{array:n,get:(e,r)=>{const a=r*t+e;return n[a>>>5]&1<<(a&u)},set:(e,n)=>{const a=n*t+e;r(a>>>5,1<<(a&u))},clear:(e,n)=>{const r=n*t+e;a(r>>>5,~(1<<(r&u)))},getRange:(e,r,a,i)=>{let o,l,d,c,m=i;for(;m>=r;--m)if(o=m*t+e,l=m*t+a,d=o>>>5,c=l>>>5,d===c){if(n[d]&s[o&u]&f[1+(l&u)])return!0}else{if(n[d]&s[o&u])return!0;if(n[c]&f[1+(l&u)])return!0;for(let t=d+1;t<c;++t)if(n[t])return!0}return!1},setRange:(e,n,a,i)=>{let o,l,d,c,m;for(;n<=i;++n)if(o=n*t+e,l=n*t+a,d=o>>>5,c=l>>>5,d===c)r(d,s[o&u]&f[1+(l&u)]);else for(r(d,s[o&u]),r(c,f[1+(l&u)]),m=d+1;m<c;++m)r(m,4294967295)},clearRange:(e,n,r,i)=>{let o,l,d,c,m;for(;n<=i;++n)if(o=n*t+e,l=n*t+r,d=o>>>5,c=l>>>5,d===c)a(d,f[o&u]|s[1+(l&u)]);else for(a(d,f[o&u]),a(c,s[1+(l&u)]),m=d+1;m<c;++m)a(m,0)},outOfBounds:(n,r,a,i)=>n<0||r<0||i>=e||a>=t}}(a,i),o.ratio=r,o.padding=n,o.width=t,o.height=e,o}function d(t,e,n,r,a,i){let o=n/2;return t-o<0||t+o>a||e-(o=r/2)<0||e+o>i}function c(t,e,n,r,a,i,o,u){const s=a*i/(2*r),f=t(e-s),l=t(e+s),d=t(n-(i/=2)),c=t(n+i);return o.outOfBounds(f,d,l,c)||o.getRange(f,d,l,c)||u&&u.getRange(f,d,l,c)}const m=[-1,-1,1,1],g=[-1,1,-1,1];const h=["right","center","left"],y=["bottom","middle","top"];function p(t,e,n,r,a,i,o,u,s,f,l,d){return!(a.outOfBounds(t,n,e,r)||(d&&i?i.getRange(t,n,e,r)||!function(t,e,n,r,a){return a[0]<=t&&n<=a[2]&&a[3]<=e&&r<=a[5]}(o,s,u,f,l):a.getRange(t,n,e,r)))}const x={"top-left":0,top:1,"top-right":2,left:4,middle:5,right:6,"bottom-left":8,bottom:9,"bottom-right":10},v={naive:function(t,e,n,a){const i=t.width,o=t.height;return function(t){const e=t.datum.datum.items[a].items,n=e.length,u=t.datum.fontSize,s=r.textMetrics.width(t.datum,t.datum.text);let f,l,d,c,m,g,h,y=0;for(let r=0;r<n;++r)f=e[r].x,d=e[r].y,l=void 0===e[r].x2?f:e[r].x2,c=void 0===e[r].y2?d:e[r].y2,m=(f+l)/2,g=(d+c)/2,h=Math.abs(l-f+c-d),h>=y&&(y=h,t.x=m,t.y=g);return m=s/2,g=u/2,f=t.x-m,l=t.x+m,d=t.y-g,c=t.y+g,t.align="center",f<0&&l<=i?t.align="left":0<=f&&i<l&&(t.align="right"),t.baseline="middle",d<0&&c<=o?t.baseline="top":0<=d&&o<c&&(t.baseline="bottom"),!0}},"reduced-search":function(t,e,n,a){const i=t.width,o=t.height,u=e[0],s=e[1];function f(e,n,r,a,f){const l=t.invert(e),m=t.invert(n);let g,h=r,y=o;if(!d(l,m,a,f,i,o)&&!c(t,l,m,f,a,h,u,s)&&!c(t,l,m,f,a,f,u,null)){for(;y-h>=1;)g=(h+y)/2,c(t,l,m,f,a,g,u,s)?y=g:h=g;if(h>r)return[l,m,h,!0]}}return function(e){const s=e.datum.datum.items[a].items,l=s.length,m=e.datum.fontSize,g=r.textMetrics.width(e.datum,e.datum.text);let h,y,p,x,v,b,w,k,M,R,z,A,O,E,S,q,B,T=n?m:0,U=!1,D=!1,I=0;for(let r=0;r<l;++r){for(h=s[r].x,p=s[r].y,y=void 0===s[r].x2?h:s[r].x2,x=void 0===s[r].y2?p:s[r].y2,h>y&&(B=h,h=y,y=B),p>x&&(B=p,p=x,x=B),M=t(h),z=t(y),R=~~((M+z)/2),A=t(p),E=t(x),O=~~((A+E)/2),w=R;w>=M;--w)for(k=O;k>=A;--k)q=f(w,k,T,g,m),q&&([e.x,e.y,T,U]=q);for(w=R;w<=z;++w)for(k=O;k<=E;++k)q=f(w,k,T,g,m),q&&([e.x,e.y,T,U]=q);U||n||(S=Math.abs(y-h+x-p),v=(h+y)/2,b=(p+x)/2,S>=I&&!d(v,b,g,m,i,o)&&!c(t,v,b,m,g,m,u,null)&&(I=S,e.x=v,e.y=b,D=!0))}return!(!U&&!D)&&(v=g/2,b=m/2,u.setRange(t(e.x-v),t(e.y-b),t(e.x+v),t(e.y+b)),e.align="center",e.baseline="middle",!0)}},floodfill:function(t,e,n,a){const i=t.width,o=t.height,u=e[0],s=e[1],f=t.bitmap();return function(e){const l=e.datum.datum.items[a].items,h=l.length,y=e.datum.fontSize,p=r.textMetrics.width(e.datum,e.datum.text),x=[];let v,b,w,k,M,R,z,A,O,E,S,q,B=n?y:0,T=!1,U=!1,D=0;for(let r=0;r<h;++r){for(v=l[r].x,w=l[r].y,b=void 0===l[r].x2?v:l[r].x2,k=void 0===l[r].y2?w:l[r].y2,x.push([t((v+b)/2),t((w+k)/2)]);x.length;)if([z,A]=x.pop(),!(u.get(z,A)||s.get(z,A)||f.get(z,A))){f.set(z,A);for(let t=0;t<4;++t)M=z+m[t],R=A+g[t],f.outOfBounds(M,R,M,R)||x.push([M,R]);if(M=t.invert(z),R=t.invert(A),O=B,E=o,!d(M,R,p,y,i,o)&&!c(t,M,R,y,p,O,u,s)&&!c(t,M,R,y,p,y,u,null)){for(;E-O>=1;)S=(O+E)/2,c(t,M,R,y,p,S,u,s)?E=S:O=S;O>B&&(e.x=M,e.y=R,B=O,T=!0)}}T||n||(q=Math.abs(b-v+k-w),M=(v+b)/2,R=(w+k)/2,q>=D&&!d(M,R,p,y,i,o)&&!c(t,M,R,y,p,y,u,null)&&(D=q,e.x=M,e.y=R,U=!0))}return!(!T&&!U)&&(M=p/2,R=y/2,u.setRange(t(e.x-M),t(e.y-R),t(e.x+M),t(e.y+R)),e.align="center",e.baseline="middle",!0)}}};function b(t,e,n,a,o,u,s,f,d,c,m){if(!t.length)return t;const g=Math.max(a.length,o.length),b=function(t,e){const n=new Float64Array(e),r=t.length;for(let e=0;e<r;++e)n[e]=t[e]||0;for(let t=r;t<e;++t)n[t]=n[r-1];return n}(a,g),w=function(t,e){const n=new Int8Array(e),r=t.length;for(let e=0;e<r;++e)n[e]|=x[t[e]];for(let t=r;t<e;++t)n[t]=n[r-1];return n}(o,g),k=(E=t[0].datum)&&E.mark&&E.mark.marktype,M="group"===k&&t[0].datum.items[d].marktype,R="area"===M,z=function(t,e,n,r){const a=t=>[t.x,t.x,t.x,t.y,t.y,t.y];return t?"line"===t||"area"===t?t=>a(t.datum):"line"===e?t=>{const e=t.datum.items[r].items;return a(e.length?e["start"===n?0:e.length-1]:{x:NaN,y:NaN})}:t=>{const e=t.datum.bounds;return[e.x1,(e.x1+e.x2)/2,e.x2,e.y1,(e.y1+e.y2)/2,e.y2]}:a}(k,M,f,d),A=l(e[0],e[1],c),O=R&&"naive"===m;var E;const S=t.map(t=>({datum:t,opacity:0,x:void 0,y:void 0,align:void 0,baseline:void 0,boundary:z(t)}));let q;if(!O){n&&S.sort((t,e)=>n(t.datum,e.datum));let e=!1;for(let t=0;t<w.length&&!e;++t)e=5===w[t]||b[t]<0;k&&(s||R)&&(u=[t.map(t=>t.datum)].concat(u)),q=u.length?i(A,u,e,R):function(t,e){const n=t.bitmap();return(e||[]).forEach(e=>n.set(t(e.boundary[0]),t(e.boundary[3]))),[n,void 0]}(A,s&&S)}const B=R?v[m](A,q,s,d):function(t,e,n,a){const i=t.width,o=t.height,u=e[0],s=e[1],f=a.length;return function(e){const l=e.boundary,d=e.datum.fontSize;if(l[2]<0||l[5]<0||l[0]>i||l[3]>o)return!1;let c,m,g,x,v,b,w,k,M,R,z,A,O,E,S,q=0;for(let i=0;i<f;++i){if(c=(3&n[i])-1,m=(n[i]>>>2&3)-1,g=0===c&&0===m||a[i]<0,x=c&&m?Math.SQRT1_2:1,v=a[i]<0?-1:1,b=l[1+c]+a[i]*c*x,z=l[4+m]+v*d*m/2+a[i]*m*x,k=z-d/2,M=z+d/2,A=t(b),E=t(k),S=t(M),!q){if(!p(A,A,E,S,u,s,b,b,k,M,l,g))continue;q=r.textMetrics.width(e.datum,e.datum.text)}if(R=b+v*q*c/2,b=R-q/2,w=R+q/2,A=t(b),O=t(w),p(A,O,E,S,u,s,b,w,k,M,l,g))return e.x=c?c*v<0?w:b:R,e.y=m?m*v<0?M:k:z,e.align=h[c*v+1],e.baseline=y[m*v+1],u.setRange(A,E,O,S),!0}return!1}}(A,q,w,b);return S.forEach(t=>t.opacity=+B(t)),S}const w=["x","y","opacity","align","baseline"],k=["top-left","left","bottom-left","top","bottom","top-right","right","bottom-right"];function M(t){n.Transform.call(this,null,t)}M.Definition={type:"Label",metadata:{modifies:!0},params:[{name:"size",type:"number",array:!0,length:2,required:!0},{name:"sort",type:"compare"},{name:"anchor",type:"string",array:!0,default:k},{name:"offset",type:"number",array:!0,default:[1]},{name:"padding",type:"number",default:0},{name:"lineAnchor",type:"string",values:["start","end"],default:"end"},{name:"markIndex",type:"number",default:0},{name:"avoidBaseMark",type:"boolean",default:!0},{name:"avoidMarks",type:"data",array:!0},{name:"method",type:"string",default:"naive"},{name:"as",type:"string",array:!0,length:w.length,default:w}]},a.inherits(M,n.Transform,{transform(t,e){const n=t.modified();if(!(n||e.changed(e.ADD_REM)||function(n){const r=t[n];return a.isFunction(r)&&e.modified(r.fields)}("sort")))return;t.size&&2===t.size.length||a.error("Size parameter should be specified as a [width, height] array.");const r=t.as||w;return b(e.materialize(e.SOURCE).source,t.size,t.sort,a.array(t.offset||1),a.array(t.anchor||k),t.avoidMarks||[],!1!==t.avoidBaseMark,t.lineAnchor||"end",t.markIndex||0,t.padding||0,t.method||"naive").forEach(t=>{const e=t.datum;e[r[0]]=t.x,e[r[1]]=t.y,e[r[2]]=t.opacity,e[r[3]]=t.align,e[r[4]]=t.baseline}),e.reflow(n).modifies(r)}}),t.label=M,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
this.vega=this.vega||{},this.vega.transforms=function(t,e,n,r,a){"use strict";function i(t,n,r,a){const i=t.width,s=t.height,u=r||a,f=e.canvas(i,s).getContext("2d");n.forEach(t=>o(f,t,u));const l=new Uint32Array(f.getImageData(0,0,i,s).data.buffer),d=t.bitmap(),c=u&&t.bitmap();let m,g,h,y,p;for(g=0;g<s;++g)for(m=0;m<i;++m)p=4278190080&l[g*i+m],p&&(h=t(m),y=t(g),a||d.set(h,y),u&&268435456^p&&c.set(h,y));return[d,c]}function o(t,e,n){if(!e.length)return;const a=e[0].mark.marktype;"group"===a?e.forEach(e=>{e.items.forEach(e=>o(t,e.items,n))}):r.Marks[a].draw(t,{items:n?e.map(s):e})}function s(t){const e=n.rederive(t,{});return e.stroke&&(e.strokeOpacity=1),e.fill&&(e.fillOpacity=.0625,e.stroke="#000",e.strokeOpacity=1,e.strokeWidth=2),e}const u=31,f=new Uint32Array(33),l=new Uint32Array(33);l[0]=0,f[0]=~l[0];for(let t=1;t<=32;++t)l[t]=l[t-1]<<1|1,f[t]=~l[t];function d(t,e,n){const r=Math.max(1,Math.sqrt(t*e/1e6)),a=~~((t+2*n+r)/r),i=~~((e+2*n+r)/r),o=t=>~~((t+n)/r);return o.invert=t=>t*r-n,o.bitmap=()=>function(t,e){const n=new Uint32Array(~~((t*e+32)/32));function r(t,e){n[t]|=e}function a(t,e){n[t]&=e}return{array:n,get:(e,r)=>{const a=r*t+e;return n[a>>>5]&1<<(a&u)},set:(e,n)=>{const a=n*t+e;r(a>>>5,1<<(a&u))},clear:(e,n)=>{const r=n*t+e;a(r>>>5,~(1<<(r&u)))},getRange:(e,r,a,i)=>{let o,s,d,c,m=i;for(;m>=r;--m)if(o=m*t+e,s=m*t+a,d=o>>>5,c=s>>>5,d===c){if(n[d]&f[o&u]&l[1+(s&u)])return!0}else{if(n[d]&f[o&u])return!0;if(n[c]&l[1+(s&u)])return!0;for(let t=d+1;t<c;++t)if(n[t])return!0}return!1},setRange:(e,n,a,i)=>{let o,s,d,c,m;for(;n<=i;++n)if(o=n*t+e,s=n*t+a,d=o>>>5,c=s>>>5,d===c)r(d,f[o&u]&l[1+(s&u)]);else for(r(d,f[o&u]),r(c,l[1+(s&u)]),m=d+1;m<c;++m)r(m,4294967295)},clearRange:(e,n,r,i)=>{let o,s,d,c,m;for(;n<=i;++n)if(o=n*t+e,s=n*t+r,d=o>>>5,c=s>>>5,d===c)a(d,l[o&u]|f[1+(s&u)]);else for(a(d,l[o&u]),a(c,f[1+(s&u)]),m=d+1;m<c;++m)a(m,0)},outOfBounds:(n,r,a,i)=>n<0||r<0||i>=e||a>=t}}(a,i),o.ratio=r,o.padding=n,o.width=t,o.height=e,o}function c(t,e,n,r,a,i){let o=n/2;return t-o<0||t+o>a||e-(o=r/2)<0||e+o>i}function m(t,e,n,r,a,i,o,s){const u=a*i/(2*r),f=t(e-u),l=t(e+u),d=t(n-(i/=2)),c=t(n+i);return o.outOfBounds(f,d,l,c)||o.getRange(f,d,l,c)||s&&s.getRange(f,d,l,c)}const g=[-1,-1,1,1],h=[-1,1,-1,1];const y=["right","center","left"],p=["bottom","middle","top"];function x(t,e,n,r,a,i,o,s,u,f,l,d){return!(a.outOfBounds(t,n,e,r)||(d&&i?i.getRange(t,n,e,r)||!function(t,e,n,r,a){return a[0]<=t&&n<=a[2]&&a[3]<=e&&r<=a[5]}(o,u,s,f,l):a.getRange(t,n,e,r)))}const b={"top-left":0,top:1,"top-right":2,left:4,middle:5,right:6,"bottom-left":8,bottom:9,"bottom-right":10},v={naive:function(t,e,n,a){const i=t.width,o=t.height;return function(t){const e=t.datum.datum.items[a].items,n=e.length,s=t.datum.fontSize,u=r.textMetrics.width(t.datum,t.datum.text);let f,l,d,c,m,g,h,y=0;for(let r=0;r<n;++r)f=e[r].x,d=e[r].y,l=void 0===e[r].x2?f:e[r].x2,c=void 0===e[r].y2?d:e[r].y2,m=(f+l)/2,g=(d+c)/2,h=Math.abs(l-f+c-d),h>=y&&(y=h,t.x=m,t.y=g);return m=u/2,g=s/2,f=t.x-m,l=t.x+m,d=t.y-g,c=t.y+g,t.align="center",f<0&&l<=i?t.align="left":0<=f&&i<l&&(t.align="right"),t.baseline="middle",d<0&&c<=o?t.baseline="top":0<=d&&o<c&&(t.baseline="bottom"),!0}},"reduced-search":function(t,e,n,a){const i=t.width,o=t.height,s=e[0],u=e[1];function f(e,n,r,a,f){const l=t.invert(e),d=t.invert(n);let g,h=r,y=o;if(!c(l,d,a,f,i,o)&&!m(t,l,d,f,a,h,s,u)&&!m(t,l,d,f,a,f,s,null)){for(;y-h>=1;)g=(h+y)/2,m(t,l,d,f,a,g,s,u)?y=g:h=g;if(h>r)return[l,d,h,!0]}}return function(e){const u=e.datum.datum.items[a].items,l=u.length,d=e.datum.fontSize,g=r.textMetrics.width(e.datum,e.datum.text);let h,y,p,x,b,v,w,k,M,R,z,A,E,O,S,B,U,D=n?d:0,I=!1,N=!1,T=0;for(let r=0;r<l;++r){for(h=u[r].x,p=u[r].y,y=void 0===u[r].x2?h:u[r].x2,x=void 0===u[r].y2?p:u[r].y2,h>y&&(U=h,h=y,y=U),p>x&&(U=p,p=x,x=U),M=t(h),z=t(y),R=~~((M+z)/2),A=t(p),O=t(x),E=~~((A+O)/2),w=R;w>=M;--w)for(k=E;k>=A;--k)B=f(w,k,D,g,d),B&&([e.x,e.y,D,I]=B);for(w=R;w<=z;++w)for(k=E;k<=O;++k)B=f(w,k,D,g,d),B&&([e.x,e.y,D,I]=B);I||n||(S=Math.abs(y-h+x-p),b=(h+y)/2,v=(p+x)/2,S>=T&&!c(b,v,g,d,i,o)&&!m(t,b,v,d,g,d,s,null)&&(T=S,e.x=b,e.y=v,N=!0))}return!(!I&&!N)&&(b=g/2,v=d/2,s.setRange(t(e.x-b),t(e.y-v),t(e.x+b),t(e.y+v)),e.align="center",e.baseline="middle",!0)}},floodfill:function(t,e,n,a){const i=t.width,o=t.height,s=e[0],u=e[1],f=t.bitmap();return function(e){const l=e.datum.datum.items[a].items,d=l.length,y=e.datum.fontSize,p=r.textMetrics.width(e.datum,e.datum.text),x=[];let b,v,w,k,M,R,z,A,E,O,S,B,U=n?y:0,D=!1,I=!1,N=0;for(let r=0;r<d;++r){for(b=l[r].x,w=l[r].y,v=void 0===l[r].x2?b:l[r].x2,k=void 0===l[r].y2?w:l[r].y2,x.push([t((b+v)/2),t((w+k)/2)]);x.length;)if([z,A]=x.pop(),!(s.get(z,A)||u.get(z,A)||f.get(z,A))){f.set(z,A);for(let t=0;t<4;++t)M=z+g[t],R=A+h[t],f.outOfBounds(M,R,M,R)||x.push([M,R]);if(M=t.invert(z),R=t.invert(A),E=U,O=o,!c(M,R,p,y,i,o)&&!m(t,M,R,y,p,E,s,u)&&!m(t,M,R,y,p,y,s,null)){for(;O-E>=1;)S=(E+O)/2,m(t,M,R,y,p,S,s,u)?O=S:E=S;E>U&&(e.x=M,e.y=R,U=E,D=!0)}}D||n||(B=Math.abs(v-b+k-w),M=(b+v)/2,R=(w+k)/2,B>=N&&!c(M,R,p,y,i,o)&&!m(t,M,R,y,p,y,s,null)&&(N=B,e.x=M,e.y=R,I=!0))}return!(!D&&!I)&&(M=p/2,R=y/2,s.setRange(t(e.x-M),t(e.y-R),t(e.x+M),t(e.y+R)),e.align="center",e.baseline="middle",!0)}}};function w(t,e,n,a,o,s,u,f,l,c,m){if(!t.length)return t;const g=Math.max(a.length,o.length),h=function(t,e){const n=new Float64Array(e),r=t.length;for(let e=0;e<r;++e)n[e]=t[e]||0;for(let t=r;t<e;++t)n[t]=n[r-1];return n}(a,g),w=function(t,e){const n=new Int8Array(e),r=t.length;for(let e=0;e<r;++e)n[e]|=b[t[e]];for(let t=r;t<e;++t)n[t]=n[r-1];return n}(o,g),k=(O=t[0].datum)&&O.mark&&O.mark.marktype,M="group"===k&&t[0].datum.items[l].marktype,R="area"===M,z=function(t,e,n,r){const a=t=>[t.x,t.x,t.x,t.y,t.y,t.y];return t?"line"===t||"area"===t?t=>a(t.datum):"line"===e?t=>{const e=t.datum.items[r].items;return a(e.length?e["start"===n?0:e.length-1]:{x:NaN,y:NaN})}:t=>{const e=t.datum.bounds;return[e.x1,(e.x1+e.x2)/2,e.x2,e.y1,(e.y1+e.y2)/2,e.y2]}:a}(k,M,f,l),A=d(e[0],e[1],c),E=R&&"naive"===m;var O;const S=t.map(t=>({datum:t,opacity:0,x:void 0,y:void 0,align:void 0,baseline:void 0,boundary:z(t)}));let B;if(!E){n&&S.sort((t,e)=>n(t.datum,e.datum));let e=!1;for(let t=0;t<w.length&&!e;++t)e=5===w[t]||h[t]<0;k&&(u||R)&&(s=[t.map(t=>t.datum)].concat(s)),B=s.length?i(A,s,e,R):function(t,e){const n=t.bitmap();return(e||[]).forEach(e=>n.set(t(e.boundary[0]),t(e.boundary[3]))),[n,void 0]}(A,u&&S)}const U=R?v[m](A,B,u,l):function(t,e,n,a){const i=t.width,o=t.height,s=e[0],u=e[1],f=a.length;return function(e){const l=e.boundary,d=e.datum.fontSize;if(l[2]<0||l[5]<0||l[0]>i||l[3]>o)return!1;let c,m,g,h,b,v,w,k,M,R,z,A,E,O,S,B=0;for(let i=0;i<f;++i){if(c=(3&n[i])-1,m=(n[i]>>>2&3)-1,g=0===c&&0===m||a[i]<0,h=c&&m?Math.SQRT1_2:1,b=a[i]<0?-1:1,v=l[1+c]+a[i]*c*h,z=l[4+m]+b*d*m/2+a[i]*m*h,k=z-d/2,M=z+d/2,A=t(v),O=t(k),S=t(M),!B){if(!x(A,A,O,S,s,u,v,v,k,M,l,g))continue;B=r.textMetrics.width(e.datum,e.datum.text)}if(R=v+b*B*c/2,v=R-B/2,w=R+B/2,A=t(v),E=t(w),x(A,E,O,S,s,u,v,w,k,M,l,g))return e.x=c?c*b<0?w:v:R,e.y=m?m*b<0?M:k:z,e.align=y[c*b+1],e.baseline=p[m*b+1],s.setRange(A,O,E,S),!0}return!1}}(A,B,w,h);return S.forEach(t=>t.opacity=+U(t)),S}const k=["x","y","opacity","align","baseline"],M=["top-left","left","bottom-left","top","bottom","top-right","right","bottom-right"];function R(t){n.Transform.call(this,null,t)}return R.Definition={type:"Label",metadata:{modifies:!0},params:[{name:"size",type:"number",array:!0,length:2,required:!0},{name:"sort",type:"compare"},{name:"anchor",type:"string",array:!0,default:M},{name:"offset",type:"number",array:!0,default:[1]},{name:"padding",type:"number",default:0},{name:"lineAnchor",type:"string",values:["start","end"],default:"end"},{name:"markIndex",type:"number",default:0},{name:"avoidBaseMark",type:"boolean",default:!0},{name:"avoidMarks",type:"data",array:!0},{name:"method",type:"string",default:"naive"},{name:"as",type:"string",array:!0,length:k.length,default:k}]},a.inherits(R,n.Transform,{transform(t,e){const n=t.modified();if(!(n||e.changed(e.ADD_REM)||function(n){const r=t[n];return a.isFunction(r)&&e.modified(r.fields)}("sort")))return;t.size&&2===t.size.length||a.error("Size parameter should be specified as a [width, height] array.");const r=t.as||k;return w(e.materialize(e.SOURCE).source,t.size,t.sort,a.array(t.offset||1),a.array(t.anchor||M),t.avoidMarks||[],!1!==t.avoidBaseMark,t.lineAnchor||"end",t.markIndex||0,t.padding||0,t.method||"naive").forEach(t=>{const e=t.datum;e[r[0]]=t.x,e[r[1]]=t.y,e[r[2]]=t.opacity,e[r[3]]=t.align,e[r[4]]=t.baseline}),e.reflow(n).modifies(r)}}),t.label=R,t}({},vega,vega,vega,vega); | ||
//# sourceMappingURL=vega-label.min.js.map |
{ | ||
"name": "vega-label", | ||
"version": "0.0.5", | ||
"version": "1.0.0", | ||
"description": "Label layout transform for Vega dataflows.", | ||
@@ -16,19 +16,17 @@ "keywords": [ | ||
"main": "build/vega-label.js", | ||
"module": "index", | ||
"module": "build/vega-label.module.js", | ||
"unpkg": "build/vega-label.min.js", | ||
"repository": "vega/vega", | ||
"scripts": { | ||
"rollup": "rollup -g vega-canvas:vega,vega-dataflow:vega,vega-scenegraph:vega,vega-util:vega -f umd -n vega.transforms -o build/vega-label.js -- index.js", | ||
"prebuild": "rimraf build && mkdir build", | ||
"build": "yarn rollup", | ||
"postbuild": "terser build/vega-label.js -c -m -o build/vega-label.min.js", | ||
"pretest": "yarn prebuild && yarn rollup", | ||
"prebuild": "rimraf build", | ||
"build": "rollup -c --config-transform", | ||
"pretest": "yarn build --config-test", | ||
"test": "tape 'test/**/*-test.js'", | ||
"prepublishOnly": "yarn test && yarn build", | ||
"postpublish": "git push && git push --tags" | ||
"prepublishOnly": "yarn test && yarn build" | ||
}, | ||
"dependencies": { | ||
"vega-canvas": "^1.2.4", | ||
"vega-dataflow": "^5.7.2", | ||
"vega-scenegraph": "^4.9.1", | ||
"vega-util": "^1.15.1" | ||
"vega-canvas": "^1.2.5", | ||
"vega-dataflow": "^5.7.3", | ||
"vega-scenegraph": "^4.9.2", | ||
"vega-util": "^1.15.2" | ||
}, | ||
@@ -38,3 +36,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "8d6793f4ca7eaaf2d22186764e9ce2dae687cf52" | ||
"gitHead": "4affcbedb9d14815dbb6d3b250ed231b54fc95c0" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
140815
19
2339
1
Updatedvega-canvas@^1.2.5
Updatedvega-dataflow@^5.7.3
Updatedvega-scenegraph@^4.9.2
Updatedvega-util@^1.15.2