vega-label
Advanced tools
Comparing version 1.1.0 to 1.2.0
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-canvas'), require('vega-dataflow'), require('vega-scenegraph'), require('vega-util')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'vega-canvas', 'vega-dataflow', 'vega-scenegraph', 'vega-util'], factory) : | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-scenegraph'), require('vega-canvas'), require('vega-dataflow'), require('vega-util')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'vega-scenegraph', 'vega-canvas', 'vega-dataflow', 'vega-util'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.vega = global.vega || {}, global.vega.transforms = {}), global.vega, global.vega, global.vega, global.vega)); | ||
}(this, (function (exports, vegaCanvas, vegaDataflow, vegaScenegraph, vegaUtil) { 'use strict'; | ||
})(this, (function (exports, vegaScenegraph, vegaCanvas, vegaDataflow, vegaUtil) { 'use strict'; | ||
const ALPHA_MASK = 0xff000000; // alpha value equivalent to opacity 0.0625 | ||
const INSIDE_OPACITY_IN_ALPHA = 0x10000000; | ||
const INSIDE_OPACITY = 0.0625; | ||
const ALPHA_MASK = 0xff000000; | ||
function baseBitmaps($, data) { | ||
@@ -17,3 +14,3 @@ const bitmap = $.bitmap(); // when there is no base mark but data points are to be avoided | ||
} | ||
function markBitmaps($, avoidMarks, labelInside, isGroupArea) { | ||
function markBitmaps($, baseMark, avoidMarks, labelInside, isGroupArea) { | ||
// create canvas | ||
@@ -23,22 +20,35 @@ const width = $.width, | ||
border = labelInside || isGroupArea, | ||
context = vegaCanvas.canvas(width, height).getContext('2d'); // render all marks to be avoided into canvas | ||
context = vegaCanvas.canvas(width, height).getContext('2d'), | ||
baseMarkContext = vegaCanvas.canvas(width, height).getContext('2d'), | ||
strokeContext = border && vegaCanvas.canvas(width, height).getContext('2d'); // render all marks to be avoided into canvas | ||
avoidMarks.forEach(items => draw(context, items, border)); // get canvas buffer, create bitmaps | ||
avoidMarks.forEach(items => draw(context, items, false)); | ||
draw(baseMarkContext, baseMark, false); | ||
const buffer = new Uint32Array(context.getImageData(0, 0, width, height).data.buffer), | ||
if (border) { | ||
draw(strokeContext, baseMark, true); | ||
} // get canvas buffer, create bitmaps | ||
const buffer = getBuffer(context, width, height), | ||
baseMarkBuffer = getBuffer(baseMarkContext, width, height), | ||
strokeBuffer = border && getBuffer(strokeContext, width, height), | ||
layer1 = $.bitmap(), | ||
layer2 = border && $.bitmap(); // populate bitmap layers | ||
let x, y, u, v, alpha; | ||
let x, y, u, v, index, alpha, strokeAlpha, baseMarkAlpha; | ||
for (y = 0; y < height; ++y) { | ||
for (x = 0; x < width; ++x) { | ||
alpha = buffer[y * width + x] & ALPHA_MASK; | ||
index = y * width + x; | ||
alpha = buffer[index] & ALPHA_MASK; | ||
baseMarkAlpha = baseMarkBuffer[index] & ALPHA_MASK; | ||
strokeAlpha = border && strokeBuffer[index] & ALPHA_MASK; | ||
if (alpha) { | ||
if (alpha || strokeAlpha || baseMarkAlpha) { | ||
u = $(x); | ||
v = $(y); | ||
if (!isGroupArea) layer1.set(u, v); // update interior bitmap | ||
if (!isGroupArea && (alpha || baseMarkAlpha)) layer1.set(u, v); // update interior bitmap | ||
if (border && alpha ^ INSIDE_OPACITY_IN_ALPHA) layer2.set(u, v); // update border bitmap | ||
if (border && (alpha || strokeAlpha)) layer2.set(u, v); // update border bitmap | ||
} | ||
@@ -51,2 +61,6 @@ } | ||
function getBuffer(context, width, height) { | ||
return new Uint32Array(context.getImageData(0, 0, width, height).data.buffer); | ||
} | ||
function draw(context, items, interior) { | ||
@@ -76,13 +90,10 @@ if (!items.length) return; | ||
if (item.stroke) { | ||
item.strokeOpacity = 1; | ||
if (item.stroke && item.strokeOpacity !== 0 || item.fill && item.fillOpacity !== 0) { | ||
return { ...item, | ||
strokeOpacity: 1, | ||
stroke: '#000', | ||
fillOpacity: 0 | ||
}; | ||
} | ||
if (item.fill) { | ||
item.fillOpacity = INSIDE_OPACITY; | ||
item.stroke = '#000'; | ||
item.strokeOpacity = 1; | ||
item.strokeWidth = 2; | ||
} | ||
return item; | ||
@@ -294,7 +305,2 @@ } | ||
} | ||
function _outOfBounds() { | ||
return false; | ||
} | ||
function collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
@@ -309,23 +315,5 @@ const w = textWidth * h / (textHeight * 2), | ||
function _collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
const w = textWidth * h / (textHeight * 2); | ||
let x1 = $(x - w), | ||
x2 = $(x + w), | ||
y1 = $(y - (h = h / 2)), | ||
y2 = $(y + h); | ||
x1 = x1 > 0 ? x1 : 0; | ||
y1 = y1 > 0 ? y1 : 0; | ||
x2 = x2 < $.width ? x2 : $.width - 1; | ||
y2 = y2 < $.height ? y2 : $.height - 1; | ||
return bm0.getRange(x1, y1, x2, y2) || bm1 && bm1.getRange(x1, y1, x2, y2); | ||
} | ||
function getTests(infPadding) { | ||
return infPadding ? [_collision, _outOfBounds] : [collision, outOfBounds]; | ||
} | ||
function placeAreaLabelReducedSearch ($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
function placeAreaLabelReducedSearch ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], | ||
@@ -474,6 +462,5 @@ // where labels have been placed | ||
const Y_DIR = [-1, 1, -1, 1]; | ||
function placeAreaLabelFloodFill ($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
function placeAreaLabelFloodFill ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], | ||
@@ -599,3 +586,3 @@ // where labels have been placed | ||
Baselines = ['bottom', 'middle', 'top']; | ||
function placeMarkLabel ($, bitmaps, anchors, offsets, infPadding) { | ||
function placeMarkLabel ($, bitmaps, anchors, offsets) { | ||
const width = $.width, | ||
@@ -610,7 +597,7 @@ height = $.height, | ||
if (!infPadding && (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height)) { | ||
if (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height) { | ||
return false; | ||
} | ||
let textWidth = 0, | ||
let textWidth = d.textWidth ?? 0, | ||
dx, | ||
@@ -647,8 +634,2 @@ dy, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_y1 = _y1 < 0 ? 0 : _y1; | ||
_y2 = _y2 >= $.height ? $.height - 1 : _y2; | ||
} | ||
if (!textWidth) { | ||
@@ -671,7 +652,2 @@ // to avoid finding width of text label, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_x2 = _x2 >= $.width ? $.width - 1 : _x2; | ||
} | ||
if (test(_x1, _x2, _y1, _y2, bm0, bm1, x1, x2, y1, y2, boundary, isInside)) { | ||
@@ -693,9 +669,5 @@ // place label if the position is placeable | ||
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 || 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]; | ||
} | ||
const TOP = 0x0, | ||
@@ -735,14 +707,23 @@ MIDDLE = 0x4, | ||
infPadding = padding === null || padding === Infinity, | ||
$ = scaler(size[0], size[1], infPadding ? 0 : padding), | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; // prepare text mark data for placing | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; | ||
let maxTextWidth = -1, | ||
maxTextHeight = -1; // prepare text mark data for placing | ||
const data = texts.map(d => ({ | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d) | ||
})); | ||
const data = texts.map(d => { | ||
const textWidth = infPadding ? vegaScenegraph.textMetrics.width(d, d.text) : undefined; | ||
maxTextWidth = Math.max(maxTextWidth, textWidth); | ||
maxTextHeight = Math.max(maxTextHeight, d.fontSize); | ||
return { | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d), | ||
textWidth | ||
}; | ||
}); | ||
padding = padding === null || padding === Infinity ? Math.max(maxTextWidth, maxTextHeight) + Math.max(...offset) : padding; | ||
const $ = scaler(size[0], size[1], padding); | ||
let bitmaps; | ||
@@ -767,12 +748,9 @@ | ||
if (marktype && (avoidBaseMark || isGroupArea)) { | ||
avoidMarks = [texts.map(d => d.datum)].concat(avoidMarks); | ||
} // generate bitmaps for layout calculation | ||
const baseMark = (marktype && avoidBaseMark || isGroupArea) && texts.map(d => d.datum); // generate bitmaps for layout calculation | ||
bitmaps = avoidMarks.length ? markBitmaps($, avoidMarks, labelInside, isGroupArea) : baseBitmaps($, avoidBaseMark && data); | ||
bitmaps = avoidMarks.length || baseMark ? markBitmaps($, baseMark || [], avoidMarks, labelInside, isGroupArea) : baseBitmaps($, avoidBaseMark && data); | ||
} // generate label placement function | ||
const place = isGroupArea ? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex, infPadding) : placeMarkLabel($, bitmaps, anchors, offsets, infPadding); // place all labels | ||
const place = isGroupArea ? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex) : placeMarkLabel($, bitmaps, anchors, offsets); // place all labels | ||
@@ -812,3 +790,3 @@ data.forEach(d => d.opacity = +place(d)); | ||
* is the coordinate of each data point. When base mark is grouped line, | ||
* boundary is either at the beginning or end of the line depending on the | ||
* boundary is either at the start or end of the line depending on the | ||
* value of lineAnchor. Otherwise, use bounds of base mark. | ||
@@ -967,2 +945,2 @@ */ | ||
}))); | ||
})); |
@@ -1,2 +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,r,a){const i=t.width,u=t.height,s=r||a,f=e.canvas(i,u).getContext("2d");n.forEach((t=>o(f,t,s)));const l=new Uint32Array(f.getImageData(0,0,i,u).data.buffer),d=t.bitmap(),c=s&&t.bitmap();let m,g,h,y,p;for(g=0;g<u;++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),s&&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(u):e})}function u(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 s=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&s)},set:(e,n)=>{const a=n*t+e;r(a>>>5,1<<(a&s))},clear:(e,n)=>{const r=n*t+e;a(r>>>5,~(1<<(r&s)))},getRange:(e,r,a,i)=>{let o,u,d,c,m=i;for(;m>=r;--m)if(o=m*t+e,u=m*t+a,d=o>>>5,c=u>>>5,d===c){if(n[d]&f[o&s]&l[1+(u&s)])return!0}else{if(n[d]&f[o&s])return!0;if(n[c]&l[1+(u&s)])return!0;for(let t=d+1;t<c;++t)if(n[t])return!0}return!1},setRange:(e,n,a,i)=>{let o,u,d,c,m;for(;n<=i;++n)if(o=n*t+e,u=n*t+a,d=o>>>5,c=u>>>5,d===c)r(d,f[o&s]&l[1+(u&s)]);else for(r(d,f[o&s]),r(c,l[1+(u&s)]),m=d+1;m<c;++m)r(m,4294967295)},clearRange:(e,n,r,i)=>{let o,u,d,c,m;for(;n<=i;++n)if(o=n*t+e,u=n*t+r,d=o>>>5,c=u>>>5,d===c)a(d,l[o&s]|f[1+(u&s)]);else for(a(d,l[o&s]),a(c,f[1+(u&s)]),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(){return!1}function g(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)}function h(t,e,n,r,a,i,o,u){const s=a*i/(2*r);let f=t(e-s),l=t(e+s),d=t(n-(i/=2)),c=t(n+i);return f=f>0?f:0,d=d>0?d:0,l=l<t.width?l:t.width-1,c=c<t.height?c:t.height-1,o.getRange(f,d,l,c)||u&&u.getRange(f,d,l,c)}function y(t){return t?[h,m]:[g,c]}const p=[-1,-1,1,1],x=[-1,1,-1,1];const v=["right","center","left"],b=["bottom","middle","top"];function w(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 k={"top-left":0,top:1,"top-right":2,left:4,middle:5,right:6,"bottom-left":8,bottom:9,"bottom-right":10},M={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,i){const o=t.width,u=t.height,[s,f]=y(i),l=e[0],d=e[1];function c(e,n,r,a,i){const c=t.invert(e),m=t.invert(n);let g,h=r,y=u;if(!f(c,m,a,i,o,u)&&!s(t,c,m,i,a,h,l,d)&&!s(t,c,m,i,a,i,l,null)){for(;y-h>=1;)g=(h+y)/2,s(t,c,m,i,a,g,l,d)?y=g:h=g;if(h>r)return[c,m,h,!0]}}return function(e){const i=e.datum.datum.items[a].items,d=i.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<d;++r){for(h=i[r].x,p=i[r].y,y=void 0===i[r].x2?h:i[r].x2,x=void 0===i[r].y2?p:i[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=c(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=c(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&&!f(v,b,g,m,o,u)&&!s(t,v,b,m,g,m,l,null)&&(I=S,e.x=v,e.y=b,D=!0))}return!(!U&&!D)&&(v=g/2,b=m/2,l.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,i){const o=t.width,u=t.height,[s,f]=y(i),l=e[0],d=e[1],c=t.bitmap();return function(e){const i=e.datum.datum.items[a].items,m=i.length,g=e.datum.fontSize,h=r.textMetrics.width(e.datum,e.datum.text),y=[];let v,b,w,k,M,R,z,A,O,E,S,q,B=n?g:0,T=!1,U=!1,D=0;for(let r=0;r<m;++r){for(v=i[r].x,w=i[r].y,b=void 0===i[r].x2?v:i[r].x2,k=void 0===i[r].y2?w:i[r].y2,y.push([t((v+b)/2),t((w+k)/2)]);y.length;)if([z,A]=y.pop(),!(l.get(z,A)||d.get(z,A)||c.get(z,A))){c.set(z,A);for(let t=0;t<4;++t)M=z+p[t],R=A+x[t],c.outOfBounds(M,R,M,R)||y.push([M,R]);if(M=t.invert(z),R=t.invert(A),O=B,E=u,!f(M,R,h,g,o,u)&&!s(t,M,R,g,h,O,l,d)&&!s(t,M,R,g,h,g,l,null)){for(;E-O>=1;)S=(O+E)/2,s(t,M,R,g,h,S,l,d)?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&&!f(M,R,h,g,o,u)&&!s(t,M,R,g,h,g,l,null)&&(D=q,e.x=M,e.y=R,U=!0))}return!(!T&&!U)&&(M=h/2,R=g/2,l.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 R(t,e,n,a,o,u,s,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),y=function(t,e){const n=new Int8Array(e),r=t.length;for(let e=0;e<r;++e)n[e]|=k[t[e]];for(let t=r;t<e;++t)n[t]=n[r-1];return n}(o,g),p=(S=t[0].datum)&&S.mark&&S.mark.marktype,x="group"===p&&t[0].datum.items[l].marktype,R="area"===x,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}(p,x,f,l),A=null===c||c===1/0,O=d(e[0],e[1],A?0:c),E=R&&"naive"===m;var S;const q=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&&q.sort(((t,e)=>n(t.datum,e.datum)));let e=!1;for(let t=0;t<y.length&&!e;++t)e=5===y[t]||h[t]<0;p&&(s||R)&&(u=[t.map((t=>t.datum))].concat(u)),B=u.length?i(O,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]}(O,s&&q)}const T=R?M[m](O,B,s,l,A):function(t,e,n,a,i){const o=t.width,u=t.height,s=e[0],f=e[1],l=a.length;return function(e){const d=e.boundary,c=e.datum.fontSize;if(!i&&(d[2]<0||d[5]<0||d[0]>o||d[3]>u))return!1;let m,g,h,y,p,x,k,M,R,z,A,O,E,S,q,B=0;for(let o=0;o<l;++o){if(m=(3&n[o])-1,g=(n[o]>>>2&3)-1,h=0===m&&0===g||a[o]<0,y=m&&g?Math.SQRT1_2:1,p=a[o]<0?-1:1,x=d[1+m]+a[o]*m*y,A=d[4+g]+p*c*g/2+a[o]*g*y,M=A-c/2,R=A+c/2,O=t(x),S=t(M),q=t(R),i&&(O=O<0?0:O,S=S<0?0:S,q=q>=t.height?t.height-1:q),!B){if(!w(O,O,S,q,s,f,x,x,M,R,d,h))continue;B=r.textMetrics.width(e.datum,e.datum.text)}if(z=x+p*B*m/2,x=z-B/2,k=z+B/2,O=t(x),E=t(k),i&&(O=O<0?0:O,E=E>=t.width?t.width-1:E),w(O,E,S,q,s,f,x,k,M,R,d,h))return e.x=m?m*p<0?k:x:z,e.y=g?g*p<0?R:M:A,e.align=v[m*p+1],e.baseline=b[g*p+1],s.setRange(O,S,E,q),!0}return!1}}(O,B,y,h,A);return q.forEach((t=>t.opacity=+T(t))),q}const z=["x","y","opacity","align","baseline"],A=["top-left","left","bottom-left","top","bottom","top-right","right","bottom-right"];function O(t){n.Transform.call(this,null,t)}O.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:A},{name:"offset",type:"number",array:!0,default:[1]},{name:"padding",type:"number",default:0,null:!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:z.length,default:z}]},a.inherits(O,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||z;return R(e.materialize(e.SOURCE).source||[],t.size,t.sort,a.array(null==t.offset?1:t.offset),a.array(t.anchor||A),t.avoidMarks||[],!1!==t.avoidBaseMark,t.lineAnchor||"end",t.markIndex||0,void 0===t.padding?0:t.padding,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=O,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("vega-scenegraph"),require("vega-canvas"),require("vega-dataflow"),require("vega-util")):"function"==typeof define&&define.amd?define(["exports","vega-scenegraph","vega-canvas","vega-dataflow","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,a,r){"use strict";const i=4278190080;function o(t,e,n){return new Uint32Array(t.getImageData(0,0,e,n).data.buffer)}function u(t,n,a){if(!n.length)return;const r=n[0].mark.marktype;"group"===r?n.forEach((e=>{e.items.forEach((e=>u(t,e.items,a)))})):e.Marks[r].draw(t,{items:a?n.map(s):n})}function s(t){const e=a.rederive(t,{});return e.stroke&&0!==e.strokeOpacity||e.fill&&0!==e.fillOpacity?{...e,strokeOpacity:1,stroke:"#000",fillOpacity:0}:e}const l=31,f=new Uint32Array(33),d=new Uint32Array(33);d[0]=0,f[0]=~d[0];for(let t=1;t<=32;++t)d[t]=d[t-1]<<1|1,f[t]=~d[t];function c(t,e,n){const a=Math.max(1,Math.sqrt(t*e/1e6)),r=~~((t+2*n+a)/a),i=~~((e+2*n+a)/a),o=t=>~~((t+n)/a);return o.invert=t=>t*a-n,o.bitmap=()=>function(t,e){const n=new Uint32Array(~~((t*e+32)/32));function a(t,e){n[t]|=e}function r(t,e){n[t]&=e}return{array:n,get:(e,a)=>{const r=a*t+e;return n[r>>>5]&1<<(r&l)},set:(e,n)=>{const r=n*t+e;a(r>>>5,1<<(r&l))},clear:(e,n)=>{const a=n*t+e;r(a>>>5,~(1<<(a&l)))},getRange:(e,a,r,i)=>{let o,u,s,c,m=i;for(;m>=a;--m)if(o=m*t+e,u=m*t+r,s=o>>>5,c=u>>>5,s===c){if(n[s]&f[o&l]&d[1+(u&l)])return!0}else{if(n[s]&f[o&l])return!0;if(n[c]&d[1+(u&l)])return!0;for(let t=s+1;t<c;++t)if(n[t])return!0}return!1},setRange:(e,n,r,i)=>{let o,u,s,c,m;for(;n<=i;++n)if(o=n*t+e,u=n*t+r,s=o>>>5,c=u>>>5,s===c)a(s,f[o&l]&d[1+(u&l)]);else for(a(s,f[o&l]),a(c,d[1+(u&l)]),m=s+1;m<c;++m)a(m,4294967295)},clearRange:(e,n,a,i)=>{let o,u,s,c,m;for(;n<=i;++n)if(o=n*t+e,u=n*t+a,s=o>>>5,c=u>>>5,s===c)r(s,d[o&l]|f[1+(u&l)]);else for(r(s,d[o&l]),r(c,f[1+(u&l)]),m=s+1;m<c;++m)r(m,0)},outOfBounds:(n,a,r,i)=>n<0||a<0||i>=e||r>=t}}(r,i),o.ratio=a,o.padding=n,o.width=t,o.height=e,o}function m(t,e,n,a,r,i){let o=n/2;return t-o<0||t+o>r||e-(o=a/2)<0||e+o>i}function g(t,e,n,a,r,i,o,u){const s=r*i/(2*a),l=t(e-s),f=t(e+s),d=t(n-(i/=2)),c=t(n+i);return o.outOfBounds(l,d,f,c)||o.getRange(l,d,f,c)||u&&u.getRange(l,d,f,c)}const h=[-1,-1,1,1],y=[-1,1,-1,1];const p=["right","center","left"],x=["bottom","middle","top"];function v(t,e,n,a,r,i,o,u,s,l,f,d){return!(r.outOfBounds(t,n,e,a)||(d&&i||r).getRange(t,n,e,a))}const b={"top-left":0,top:1,"top-right":2,left:4,middle:5,right:6,"bottom-left":8,bottom:9,"bottom-right":10},M={naive:function(t,n,a,r){const i=t.width,o=t.height;return function(t){const n=t.datum.datum.items[r].items,a=n.length,u=t.datum.fontSize,s=e.textMetrics.width(t.datum,t.datum.text);let l,f,d,c,m,g,h,y=0;for(let e=0;e<a;++e)l=n[e].x,d=n[e].y,f=void 0===n[e].x2?l:n[e].x2,c=void 0===n[e].y2?d:n[e].y2,m=(l+f)/2,g=(d+c)/2,h=Math.abs(f-l+c-d),h>=y&&(y=h,t.x=m,t.y=g);return m=s/2,g=u/2,l=t.x-m,f=t.x+m,d=t.y-g,c=t.y+g,t.align="center",l<0&&f<=i?t.align="left":0<=l&&i<f&&(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,n,a,r){const i=t.width,o=t.height,u=n[0],s=n[1];function l(e,n,a,r,l){const f=t.invert(e),d=t.invert(n);let c,h=a,y=o;if(!m(f,d,r,l,i,o)&&!g(t,f,d,l,r,h,u,s)&&!g(t,f,d,l,r,l,u,null)){for(;y-h>=1;)c=(h+y)/2,g(t,f,d,l,r,c,u,s)?y=c:h=c;if(h>a)return[f,d,h,!0]}}return function(n){const s=n.datum.datum.items[r].items,f=s.length,d=n.datum.fontSize,c=e.textMetrics.width(n.datum,n.datum.text);let h,y,p,x,v,b,M,w,k,R,z,O,A,E,S,q,B,T=a?d:0,U=!1,C=!1,D=0;for(let e=0;e<f;++e){for(h=s[e].x,p=s[e].y,y=void 0===s[e].x2?h:s[e].x2,x=void 0===s[e].y2?p:s[e].y2,h>y&&(B=h,h=y,y=B),p>x&&(B=p,p=x,x=B),k=t(h),z=t(y),R=~~((k+z)/2),O=t(p),E=t(x),A=~~((O+E)/2),M=R;M>=k;--M)for(w=A;w>=O;--w)q=l(M,w,T,c,d),q&&([n.x,n.y,T,U]=q);for(M=R;M<=z;++M)for(w=A;w<=E;++w)q=l(M,w,T,c,d),q&&([n.x,n.y,T,U]=q);U||a||(S=Math.abs(y-h+x-p),v=(h+y)/2,b=(p+x)/2,S>=D&&!m(v,b,c,d,i,o)&&!g(t,v,b,d,c,d,u,null)&&(D=S,n.x=v,n.y=b,C=!0))}return!(!U&&!C)&&(v=c/2,b=d/2,u.setRange(t(n.x-v),t(n.y-b),t(n.x+v),t(n.y+b)),n.align="center",n.baseline="middle",!0)}},floodfill:function(t,n,a,r){const i=t.width,o=t.height,u=n[0],s=n[1],l=t.bitmap();return function(n){const f=n.datum.datum.items[r].items,d=f.length,c=n.datum.fontSize,p=e.textMetrics.width(n.datum,n.datum.text),x=[];let v,b,M,w,k,R,z,O,A,E,S,q,B=a?c:0,T=!1,U=!1,C=0;for(let e=0;e<d;++e){for(v=f[e].x,M=f[e].y,b=void 0===f[e].x2?v:f[e].x2,w=void 0===f[e].y2?M:f[e].y2,x.push([t((v+b)/2),t((M+w)/2)]);x.length;)if([z,O]=x.pop(),!(u.get(z,O)||s.get(z,O)||l.get(z,O))){l.set(z,O);for(let t=0;t<4;++t)k=z+h[t],R=O+y[t],l.outOfBounds(k,R,k,R)||x.push([k,R]);if(k=t.invert(z),R=t.invert(O),A=B,E=o,!m(k,R,p,c,i,o)&&!g(t,k,R,c,p,A,u,s)&&!g(t,k,R,c,p,c,u,null)){for(;E-A>=1;)S=(A+E)/2,g(t,k,R,c,p,S,u,s)?E=S:A=S;A>B&&(n.x=k,n.y=R,B=A,T=!0)}}T||a||(q=Math.abs(b-v+w-M),k=(v+b)/2,R=(M+w)/2,q>=C&&!m(k,R,p,c,i,o)&&!g(t,k,R,c,p,c,u,null)&&(C=q,n.x=k,n.y=R,U=!0))}return!(!T&&!U)&&(k=p/2,R=c/2,u.setRange(t(n.x-k),t(n.y-R),t(n.x+k),t(n.y+R)),n.align="center",n.baseline="middle",!0)}}};function w(t,a,r,s,l,f,d,m,g,h,y){if(!t.length)return t;const w=Math.max(s.length,l.length),k=function(t,e){const n=new Float64Array(e),a=t.length;for(let e=0;e<a;++e)n[e]=t[e]||0;for(let t=a;t<e;++t)n[t]=n[a-1];return n}(s,w),R=function(t,e){const n=new Int8Array(e),a=t.length;for(let e=0;e<a;++e)n[e]|=b[t[e]];for(let t=a;t<e;++t)n[t]=n[a-1];return n}(l,w),z=(B=t[0].datum)&&B.mark&&B.mark.marktype,O="group"===z&&t[0].datum.items[g].marktype,A="area"===O,E=function(t,e,n,a){const r=t=>[t.x,t.x,t.x,t.y,t.y,t.y];return t?"line"===t||"area"===t?t=>r(t.datum):"line"===e?t=>{const e=t.datum.items[a].items;return r(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]}:r}(z,O,m,g),S=null===h||h===1/0,q=A&&"naive"===y;var B;let T=-1,U=-1;const C=t.map((t=>{const n=S?e.textMetrics.width(t,t.text):void 0;return T=Math.max(T,n),U=Math.max(U,t.fontSize),{datum:t,opacity:0,x:void 0,y:void 0,align:void 0,baseline:void 0,boundary:E(t),textWidth:n}}));h=null===h||h===1/0?Math.max(T,U)+Math.max(...s):h;const D=c(a[0],a[1],h);let I;if(!q){r&&C.sort(((t,e)=>r(t.datum,e.datum)));let e=!1;for(let t=0;t<R.length&&!e;++t)e=5===R[t]||k[t]<0;const a=(z&&d||A)&&t.map((t=>t.datum));I=f.length||a?function(t,e,a,r,s){const l=t.width,f=t.height,d=r||s,c=n.canvas(l,f).getContext("2d"),m=n.canvas(l,f).getContext("2d"),g=d&&n.canvas(l,f).getContext("2d");a.forEach((t=>u(c,t,!1))),u(m,e,!1),d&&u(g,e,!0);const h=o(c,l,f),y=o(m,l,f),p=d&&o(g,l,f),x=t.bitmap(),v=d&&t.bitmap();let b,M,w,k,R,z,O,A;for(M=0;M<f;++M)for(b=0;b<l;++b)R=M*l+b,z=h[R]&i,A=y[R]&i,O=d&&p[R]&i,(z||O||A)&&(w=t(b),k=t(M),s||!z&&!A||x.set(w,k),d&&(z||O)&&v.set(w,k));return[x,v]}(D,a||[],f,e,A):function(t,e){const n=t.bitmap();return(e||[]).forEach((e=>n.set(t(e.boundary[0]),t(e.boundary[3])))),[n,void 0]}(D,d&&C)}const N=A?M[y](D,I,d,g):function(t,n,a,r){const i=t.width,o=t.height,u=n[0],s=n[1],l=r.length;return function(n){var f;const d=n.boundary,c=n.datum.fontSize;if(d[2]<0||d[5]<0||d[0]>i||d[3]>o)return!1;let m,g,h,y,b,M,w,k,R,z,O,A,E,S,q,B=null!==(f=n.textWidth)&&void 0!==f?f:0;for(let i=0;i<l;++i){if(m=(3&a[i])-1,g=(a[i]>>>2&3)-1,h=0===m&&0===g||r[i]<0,y=m&&g?Math.SQRT1_2:1,b=r[i]<0?-1:1,M=d[1+m]+r[i]*m*y,O=d[4+g]+b*c*g/2+r[i]*g*y,k=O-c/2,R=O+c/2,A=t(M),S=t(k),q=t(R),!B){if(!v(A,A,S,q,u,s,0,0,0,0,0,h))continue;B=e.textMetrics.width(n.datum,n.datum.text)}if(z=M+b*B*m/2,M=z-B/2,w=z+B/2,A=t(M),E=t(w),v(A,E,S,q,u,s,0,0,0,0,0,h))return n.x=m?m*b<0?w:M:z,n.y=g?g*b<0?R:k:O,n.align=p[m*b+1],n.baseline=x[g*b+1],u.setRange(A,S,E,q),!0}return!1}}(D,I,R,k);return C.forEach((t=>t.opacity=+N(t))),C}const k=["x","y","opacity","align","baseline"],R=["top-left","left","bottom-left","top","bottom","top-right","right","bottom-right"];function z(t){a.Transform.call(this,null,t)}z.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:R},{name:"offset",type:"number",array:!0,default:[1]},{name:"padding",type:"number",default:0,null:!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}]},r.inherits(z,a.Transform,{transform(t,e){const n=t.modified();if(!(n||e.changed(e.ADD_REM)||function(n){const a=t[n];return r.isFunction(a)&&e.modified(a.fields)}("sort")))return;t.size&&2===t.size.length||r.error("Size parameter should be specified as a [width, height] array.");const a=t.as||k;return w(e.materialize(e.SOURCE).source||[],t.size,t.sort,r.array(null==t.offset?1:t.offset),r.array(t.anchor||R),t.avoidMarks||[],!1!==t.avoidBaseMark,t.lineAnchor||"end",t.markIndex||0,void 0===t.padding?0:t.padding,t.method||"naive").forEach((t=>{const e=t.datum;e[a[0]]=t.x,e[a[1]]=t.y,e[a[2]]=t.opacity,e[a[3]]=t.align,e[a[4]]=t.baseline})),e.reflow(n).modifies(a)}}),t.label=z,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=vega-label.min.js.map |
@@ -0,10 +1,7 @@ | ||
import { Marks, textMetrics } from 'vega-scenegraph'; | ||
import { canvas } from 'vega-canvas'; | ||
import { rederive, Transform } from 'vega-dataflow'; | ||
import { Marks, textMetrics } from 'vega-scenegraph'; | ||
import { inherits, isFunction, error, array } from 'vega-util'; | ||
const ALPHA_MASK = 0xff000000; // alpha value equivalent to opacity 0.0625 | ||
const INSIDE_OPACITY_IN_ALPHA = 0x10000000; | ||
const INSIDE_OPACITY = 0.0625; | ||
const ALPHA_MASK = 0xff000000; | ||
function baseBitmaps($, data) { | ||
@@ -16,3 +13,3 @@ const bitmap = $.bitmap(); // when there is no base mark but data points are to be avoided | ||
} | ||
function markBitmaps($, avoidMarks, labelInside, isGroupArea) { | ||
function markBitmaps($, baseMark, avoidMarks, labelInside, isGroupArea) { | ||
// create canvas | ||
@@ -22,22 +19,35 @@ const width = $.width, | ||
border = labelInside || isGroupArea, | ||
context = canvas(width, height).getContext('2d'); // render all marks to be avoided into canvas | ||
context = canvas(width, height).getContext('2d'), | ||
baseMarkContext = canvas(width, height).getContext('2d'), | ||
strokeContext = border && canvas(width, height).getContext('2d'); // render all marks to be avoided into canvas | ||
avoidMarks.forEach(items => draw(context, items, border)); // get canvas buffer, create bitmaps | ||
avoidMarks.forEach(items => draw(context, items, false)); | ||
draw(baseMarkContext, baseMark, false); | ||
const buffer = new Uint32Array(context.getImageData(0, 0, width, height).data.buffer), | ||
if (border) { | ||
draw(strokeContext, baseMark, true); | ||
} // get canvas buffer, create bitmaps | ||
const buffer = getBuffer(context, width, height), | ||
baseMarkBuffer = getBuffer(baseMarkContext, width, height), | ||
strokeBuffer = border && getBuffer(strokeContext, width, height), | ||
layer1 = $.bitmap(), | ||
layer2 = border && $.bitmap(); // populate bitmap layers | ||
let x, y, u, v, alpha; | ||
let x, y, u, v, index, alpha, strokeAlpha, baseMarkAlpha; | ||
for (y = 0; y < height; ++y) { | ||
for (x = 0; x < width; ++x) { | ||
alpha = buffer[y * width + x] & ALPHA_MASK; | ||
index = y * width + x; | ||
alpha = buffer[index] & ALPHA_MASK; | ||
baseMarkAlpha = baseMarkBuffer[index] & ALPHA_MASK; | ||
strokeAlpha = border && strokeBuffer[index] & ALPHA_MASK; | ||
if (alpha) { | ||
if (alpha || strokeAlpha || baseMarkAlpha) { | ||
u = $(x); | ||
v = $(y); | ||
if (!isGroupArea) layer1.set(u, v); // update interior bitmap | ||
if (!isGroupArea && (alpha || baseMarkAlpha)) layer1.set(u, v); // update interior bitmap | ||
if (border && alpha ^ INSIDE_OPACITY_IN_ALPHA) layer2.set(u, v); // update border bitmap | ||
if (border && (alpha || strokeAlpha)) layer2.set(u, v); // update border bitmap | ||
} | ||
@@ -50,2 +60,6 @@ } | ||
function getBuffer(context, width, height) { | ||
return new Uint32Array(context.getImageData(0, 0, width, height).data.buffer); | ||
} | ||
function draw(context, items, interior) { | ||
@@ -75,13 +89,10 @@ if (!items.length) return; | ||
if (item.stroke) { | ||
item.strokeOpacity = 1; | ||
if (item.stroke && item.strokeOpacity !== 0 || item.fill && item.fillOpacity !== 0) { | ||
return { ...item, | ||
strokeOpacity: 1, | ||
stroke: '#000', | ||
fillOpacity: 0 | ||
}; | ||
} | ||
if (item.fill) { | ||
item.fillOpacity = INSIDE_OPACITY; | ||
item.stroke = '#000'; | ||
item.strokeOpacity = 1; | ||
item.strokeWidth = 2; | ||
} | ||
return item; | ||
@@ -293,7 +304,2 @@ } | ||
} | ||
function _outOfBounds() { | ||
return false; | ||
} | ||
function collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
@@ -308,23 +314,5 @@ const w = textWidth * h / (textHeight * 2), | ||
function _collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
const w = textWidth * h / (textHeight * 2); | ||
let x1 = $(x - w), | ||
x2 = $(x + w), | ||
y1 = $(y - (h = h / 2)), | ||
y2 = $(y + h); | ||
x1 = x1 > 0 ? x1 : 0; | ||
y1 = y1 > 0 ? y1 : 0; | ||
x2 = x2 < $.width ? x2 : $.width - 1; | ||
y2 = y2 < $.height ? y2 : $.height - 1; | ||
return bm0.getRange(x1, y1, x2, y2) || bm1 && bm1.getRange(x1, y1, x2, y2); | ||
} | ||
function getTests(infPadding) { | ||
return infPadding ? [_collision, _outOfBounds] : [collision, outOfBounds]; | ||
} | ||
function placeAreaLabelReducedSearch ($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
function placeAreaLabelReducedSearch ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], | ||
@@ -473,6 +461,5 @@ // where labels have been placed | ||
const Y_DIR = [-1, 1, -1, 1]; | ||
function placeAreaLabelFloodFill ($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
function placeAreaLabelFloodFill ($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], | ||
@@ -598,3 +585,3 @@ // where labels have been placed | ||
Baselines = ['bottom', 'middle', 'top']; | ||
function placeMarkLabel ($, bitmaps, anchors, offsets, infPadding) { | ||
function placeMarkLabel ($, bitmaps, anchors, offsets) { | ||
const width = $.width, | ||
@@ -606,10 +593,12 @@ height = $.height, | ||
return function (d) { | ||
var _d$textWidth; | ||
const boundary = d.boundary, | ||
textHeight = d.datum.fontSize; // can not be placed if the mark is not visible in the graph bound | ||
if (!infPadding && (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height)) { | ||
if (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height) { | ||
return false; | ||
} | ||
let textWidth = 0, | ||
let textWidth = (_d$textWidth = d.textWidth) !== null && _d$textWidth !== void 0 ? _d$textWidth : 0, | ||
dx, | ||
@@ -646,8 +635,2 @@ dy, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_y1 = _y1 < 0 ? 0 : _y1; | ||
_y2 = _y2 >= $.height ? $.height - 1 : _y2; | ||
} | ||
if (!textWidth) { | ||
@@ -670,7 +653,2 @@ // to avoid finding width of text label, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_x2 = _x2 >= $.width ? $.width - 1 : _x2; | ||
} | ||
if (test(_x1, _x2, _y1, _y2, bm0, bm1, x1, x2, y1, y2, boundary, isInside)) { | ||
@@ -692,9 +670,5 @@ // place label if the position is placeable | ||
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 || 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]; | ||
} | ||
const TOP = 0x0, | ||
@@ -734,14 +708,23 @@ MIDDLE = 0x4, | ||
infPadding = padding === null || padding === Infinity, | ||
$ = scaler(size[0], size[1], infPadding ? 0 : padding), | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; // prepare text mark data for placing | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; | ||
let maxTextWidth = -1, | ||
maxTextHeight = -1; // prepare text mark data for placing | ||
const data = texts.map(d => ({ | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d) | ||
})); | ||
const data = texts.map(d => { | ||
const textWidth = infPadding ? textMetrics.width(d, d.text) : undefined; | ||
maxTextWidth = Math.max(maxTextWidth, textWidth); | ||
maxTextHeight = Math.max(maxTextHeight, d.fontSize); | ||
return { | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d), | ||
textWidth | ||
}; | ||
}); | ||
padding = padding === null || padding === Infinity ? Math.max(maxTextWidth, maxTextHeight) + Math.max(...offset) : padding; | ||
const $ = scaler(size[0], size[1], padding); | ||
let bitmaps; | ||
@@ -766,12 +749,9 @@ | ||
if (marktype && (avoidBaseMark || isGroupArea)) { | ||
avoidMarks = [texts.map(d => d.datum)].concat(avoidMarks); | ||
} // generate bitmaps for layout calculation | ||
const baseMark = (marktype && avoidBaseMark || isGroupArea) && texts.map(d => d.datum); // generate bitmaps for layout calculation | ||
bitmaps = avoidMarks.length ? markBitmaps($, avoidMarks, labelInside, isGroupArea) : baseBitmaps($, avoidBaseMark && data); | ||
bitmaps = avoidMarks.length || baseMark ? markBitmaps($, baseMark || [], avoidMarks, labelInside, isGroupArea) : baseBitmaps($, avoidBaseMark && data); | ||
} // generate label placement function | ||
const place = isGroupArea ? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex, infPadding) : placeMarkLabel($, bitmaps, anchors, offsets, infPadding); // place all labels | ||
const place = isGroupArea ? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex) : placeMarkLabel($, bitmaps, anchors, offsets); // place all labels | ||
@@ -811,3 +791,3 @@ data.forEach(d => d.opacity = +place(d)); | ||
* is the coordinate of each data point. When base mark is grouped line, | ||
* boundary is either at the beginning or end of the line depending on the | ||
* boundary is either at the start or end of the line depending on the | ||
* value of lineAnchor. Otherwise, use bounds of base mark. | ||
@@ -814,0 +794,0 @@ */ |
{ | ||
"name": "vega-label", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Label layout transform for Vega dataflows.", | ||
@@ -27,3 +27,3 @@ "keywords": [ | ||
"dependencies": { | ||
"vega-canvas": "^1.2.5", | ||
"vega-canvas": "^1.2.6", | ||
"vega-dataflow": "^5.7.3", | ||
@@ -36,3 +36,3 @@ "vega-scenegraph": "^4.9.2", | ||
}, | ||
"gitHead": "774165e29850b66ec8b79ba52a7955f1ab936ea6" | ||
"gitHead": "9a3faca4395cade9ecdfde90af98f1c53e9916b2" | ||
} |
@@ -0,1 +1,2 @@ | ||
import {textMetrics} from 'vega-scenegraph'; | ||
import {baseBitmaps, markBitmaps} from './util/markBitmaps'; | ||
@@ -49,16 +50,30 @@ import scaler from './util/scaler'; | ||
infPadding = padding === null || padding === Infinity, | ||
$ = scaler(size[0], size[1], infPadding ? 0 : padding), | ||
isNaiveGroupArea = isGroupArea && method === 'naive'; | ||
let maxTextWidth = -1, | ||
maxTextHeight = -1; | ||
// prepare text mark data for placing | ||
const data = texts.map(d => ({ | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d) | ||
})); | ||
const data = texts.map(d => { | ||
const textWidth = infPadding ? textMetrics.width(d, d.text) : undefined; | ||
maxTextWidth = Math.max(maxTextWidth, textWidth); | ||
maxTextHeight = Math.max(maxTextHeight, d.fontSize); | ||
return { | ||
datum: d, | ||
opacity: 0, | ||
x: undefined, | ||
y: undefined, | ||
align: undefined, | ||
baseline: undefined, | ||
boundary: boundary(d), | ||
textWidth | ||
}; | ||
}); | ||
padding = (padding === null || padding === Infinity) | ||
? Math.max(maxTextWidth, maxTextHeight) + Math.max(...offset) | ||
: padding; | ||
const $ = scaler(size[0], size[1], padding); | ||
let bitmaps; | ||
@@ -81,9 +96,7 @@ if (!isNaiveGroupArea) { | ||
// base mark is implicitly avoided if it is a group area | ||
if (marktype && (avoidBaseMark || isGroupArea)) { | ||
avoidMarks = [texts.map(d => d.datum)].concat(avoidMarks); | ||
} | ||
const baseMark = ((marktype && avoidBaseMark) || isGroupArea) && texts.map(d => d.datum); | ||
// generate bitmaps for layout calculation | ||
bitmaps = avoidMarks.length | ||
? markBitmaps($, avoidMarks, labelInside, isGroupArea) | ||
bitmaps = avoidMarks.length || baseMark | ||
? markBitmaps($, baseMark || [], avoidMarks, labelInside, isGroupArea) | ||
: baseBitmaps($, avoidBaseMark && data); | ||
@@ -94,4 +107,4 @@ } | ||
const place = isGroupArea | ||
? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex, infPadding) | ||
: placeMarkLabel($, bitmaps, anchors, offsets, infPadding); | ||
? placeAreaLabel[method]($, bitmaps, avoidBaseMark, markIndex) | ||
: placeMarkLabel($, bitmaps, anchors, offsets); | ||
@@ -128,3 +141,3 @@ // place all labels | ||
* is the coordinate of each data point. When base mark is grouped line, | ||
* boundary is either at the beginning or end of the line depending on the | ||
* boundary is either at the start or end of the line depending on the | ||
* value of lineAnchor. Otherwise, use bounds of base mark. | ||
@@ -131,0 +144,0 @@ */ |
@@ -8,6 +8,2 @@ import {canvas} from 'vega-canvas'; | ||
// alpha value equivalent to opacity 0.0625 | ||
const INSIDE_OPACITY_IN_ALPHA = 0x10000000; | ||
const INSIDE_OPACITY = 0.0625; | ||
export function baseBitmaps($, data) { | ||
@@ -20,3 +16,3 @@ const bitmap = $.bitmap(); | ||
export function markBitmaps($, avoidMarks, labelInside, isGroupArea) { | ||
export function markBitmaps($, baseMark, avoidMarks, labelInside, isGroupArea) { | ||
// create canvas | ||
@@ -26,9 +22,17 @@ const width = $.width, | ||
border = labelInside || isGroupArea, | ||
context = canvas(width, height).getContext('2d'); | ||
context = canvas(width, height).getContext('2d'), | ||
baseMarkContext = canvas(width, height).getContext('2d'), | ||
strokeContext = border && canvas(width, height).getContext('2d'); | ||
// render all marks to be avoided into canvas | ||
avoidMarks.forEach(items => draw(context, items, border)); | ||
avoidMarks.forEach(items => draw(context, items, false)); | ||
draw(baseMarkContext, baseMark, false); | ||
if (border) { | ||
draw(strokeContext, baseMark, true); | ||
} | ||
// get canvas buffer, create bitmaps | ||
const buffer = new Uint32Array(context.getImageData(0, 0, width, height).data.buffer), | ||
const buffer = getBuffer(context, width, height), | ||
baseMarkBuffer = getBuffer(baseMarkContext, width, height), | ||
strokeBuffer = border && getBuffer(strokeContext, width, height), | ||
layer1 = $.bitmap(), | ||
@@ -38,11 +42,16 @@ layer2 = border && $.bitmap(); | ||
// populate bitmap layers | ||
let x, y, u, v, alpha; | ||
let x, y, u, v, index, alpha, strokeAlpha, baseMarkAlpha; | ||
for (y=0; y < height; ++y) { | ||
for (x=0; x < width; ++x) { | ||
alpha = buffer[y * width + x] & ALPHA_MASK; | ||
if (alpha) { | ||
index = y * width + x; | ||
alpha = buffer[index] & ALPHA_MASK; | ||
baseMarkAlpha = baseMarkBuffer[index] & ALPHA_MASK; | ||
strokeAlpha = border && (strokeBuffer[index] & ALPHA_MASK); | ||
if (alpha || strokeAlpha || baseMarkAlpha) { | ||
u = $(x); | ||
v = $(y); | ||
if (!isGroupArea) layer1.set(u, v); // update interior bitmap | ||
if (border && alpha ^ INSIDE_OPACITY_IN_ALPHA) layer2.set(u, v); // update border bitmap | ||
if (!isGroupArea && (alpha || baseMarkAlpha)) layer1.set(u, v); // update interior bitmap | ||
if (border && (alpha || strokeAlpha)) layer2.set(u, v); // update border bitmap | ||
} | ||
@@ -55,2 +64,6 @@ } | ||
function getBuffer(context, width, height) { | ||
return new Uint32Array(context.getImageData(0, 0, width, height).data.buffer); | ||
} | ||
function draw(context, items, interior) { | ||
@@ -77,14 +90,15 @@ if (!items.length) return; | ||
if (item.stroke) { | ||
item.strokeOpacity = 1; | ||
if ( | ||
(item.stroke && item.strokeOpacity !== 0) || | ||
(item.fill && item.fillOpacity !== 0) | ||
) { | ||
return { | ||
...item, | ||
strokeOpacity: 1, | ||
stroke: '#000', | ||
fillOpacity: 0 | ||
}; | ||
} | ||
if (item.fill) { | ||
item.fillOpacity = INSIDE_OPACITY; | ||
item.stroke = '#000'; | ||
item.strokeOpacity = 1; | ||
item.strokeWidth = 2; | ||
} | ||
return item; | ||
} |
@@ -1,2 +0,2 @@ | ||
function outOfBounds(x, y, textWidth, textHeight, width, height) { | ||
export function outOfBounds(x, y, textWidth, textHeight, width, height) { | ||
let r = textWidth / 2; | ||
@@ -9,7 +9,3 @@ return x - r < 0 | ||
function _outOfBounds() { | ||
return false; | ||
} | ||
function collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
export function collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
const w = (textWidth * h) / (textHeight * 2), | ||
@@ -24,23 +20,2 @@ x1 = $(x - w), | ||
|| (bm1 && bm1.getRange(x1, y1, x2, y2)); | ||
} | ||
function _collision($, x, y, textHeight, textWidth, h, bm0, bm1) { | ||
const w = (textWidth * h) / (textHeight * 2); | ||
let x1 = $(x - w), | ||
x2 = $(x + w), | ||
y1 = $(y - (h = h/2)), | ||
y2 = $(y + h); | ||
x1 = x1 > 0 ? x1 : 0; | ||
y1 = y1 > 0 ? y1 : 0; | ||
x2 = x2 < $.width ? x2 : $.width - 1; | ||
y2 = y2 < $.height ? y2 : $.height - 1; | ||
return bm0.getRange(x1, y1, x2, y2) || (bm1 && bm1.getRange(x1, y1, x2, y2)); | ||
} | ||
export function getTests(infPadding) { | ||
return infPadding | ||
? [_collision, _outOfBounds] | ||
: [collision, outOfBounds]; | ||
} |
import {textMetrics} from 'vega-scenegraph'; | ||
import {getTests} from './common'; | ||
import {collision, outOfBounds} from './common'; | ||
@@ -8,6 +8,5 @@ // pixel direction offsets for flood fill search | ||
export default function($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
export default function($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], // where labels have been placed | ||
@@ -14,0 +13,0 @@ bm1 = bitmaps[1], // area outlines |
import {textMetrics} from 'vega-scenegraph'; | ||
import {getTests} from './common'; | ||
import {collision, outOfBounds} from './common'; | ||
export default function($, bitmaps, avoidBaseMark, markIndex, infPadding) { | ||
export default function($, bitmaps, avoidBaseMark, markIndex) { | ||
const width = $.width, | ||
height = $.height, | ||
[collision, outOfBounds] = getTests(infPadding), | ||
bm0 = bitmaps[0], // where labels have been placed | ||
@@ -9,0 +8,0 @@ bm1 = bitmaps[1]; // area outlines |
@@ -6,3 +6,3 @@ import {textMetrics} from 'vega-scenegraph'; | ||
export default function($, bitmaps, anchors, offsets, infPadding) { | ||
export default function($, bitmaps, anchors, offsets) { | ||
const width = $.width, | ||
@@ -19,7 +19,7 @@ height = $.height, | ||
// can not be placed if the mark is not visible in the graph bound | ||
if (!infPadding && (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height)) { | ||
if (boundary[2] < 0 || boundary[5] < 0 || boundary[0] > width || boundary[3] > height) { | ||
return false; | ||
} | ||
let textWidth = 0, | ||
let textWidth = d.textWidth ?? 0, | ||
dx, dy, isInside, sizeFactor, insideFactor, | ||
@@ -47,8 +47,2 @@ x1, x2, y1, y2, xc, yc, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_y1 = _y1 < 0 ? 0 : _y1; | ||
_y2 = _y2 >= $.height ? ($.height - 1) : _y2; | ||
} | ||
if (!textWidth) { | ||
@@ -72,7 +66,2 @@ // to avoid finding width of text label, | ||
if (infPadding) { | ||
_x1 = _x1 < 0 ? 0 : _x1; | ||
_x2 = _x2 >= $.width ? ($.width - 1) : _x2; | ||
} | ||
if (test(_x1, _x2, _y1, _y2, bm0, bm1, x1, x2, y1, y2, boundary, isInside)) { | ||
@@ -99,11 +88,4 @@ // place label if the position is placeable | ||
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)) | ||
((isInside && bm1) || 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]; | ||
} |
Sorry, the diff of this file is not supported yet
147557
2397
Updatedvega-canvas@^1.2.6