smart-labels 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,27 @@
1
- import * as d3 from "d3";
1
+ // smart-labels v0.0.15 Copyright (c) 2024 John Alexis Guerra Gómez
2
+ 'use strict';
3
+
4
+ var d3 = require('d3');
5
+
6
+ function _interopNamespaceDefault(e) {
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
15
+ });
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+
23
+ var d3__namespace = /*#__PURE__*/_interopNamespaceDefault(d3);
24
+
2
25
  // Based on https://observablehq.com/@d3/voronoi-labels by Mike Bostock
3
26
 
4
27
  // ISC License
@@ -15,7 +38,7 @@ import * as d3 from "d3";
15
38
  // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
39
  // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
40
  // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
- export default function smartLabels(
41
+ function smartLabels(
19
42
  data,
20
43
  {
21
44
  x = (d) => d[0], // x coordinate accessor, expected to be in pixels
@@ -35,7 +58,7 @@ export default function smartLabels(
35
58
  labelsInCentroids = true,
36
59
  threshold = 2000, // Areas over this size would get labels
37
60
  alwaysShow = (d) => false, // If returns true for the node, it will always show the label
38
- showLabel = (d, cell) => alwaysShow(d) || -d3.polygonArea(cell) > threshold, // If true, show the label
61
+ showLabel = (d, cell) => alwaysShow(d) || -d3__namespace.polygonArea(cell) > threshold, // If true, show the label
39
62
 
40
63
  backgroundFill = "#fefefe01", // What to paint the bg rect of the labels. Needed for the onHover
41
64
  strokeWidth = 5,
@@ -73,11 +96,19 @@ export default function smartLabels(
73
96
  y(d, index) !== null
74
97
  );
75
98
 
76
- if (typeof font === "string") font = () => font;
77
- if (typeof hoverFont === "string") hoverFont = () => hoverFont;
99
+ // Keep the string in its own constant: `font = () => font` would return the
100
+ // arrow function itself (font is reassigned), so string fonts were ignored.
101
+ if (typeof font === "string") {
102
+ const fontString = font;
103
+ font = () => fontString;
104
+ }
105
+ if (typeof hoverFont === "string") {
106
+ const hoverFontString = hoverFont;
107
+ hoverFont = () => hoverFontString;
108
+ }
78
109
 
79
- let xExtent = d3.extent(data, x),
80
- yExtent = d3.extent(data, y);
110
+ let xExtent = d3__namespace.extent(data, x),
111
+ yExtent = d3__namespace.extent(data, y);
81
112
  width = width || xExtent[1] - xExtent[0];
82
113
  height = height || yExtent[1] - yExtent[0];
83
114
 
@@ -111,21 +142,21 @@ export default function smartLabels(
111
142
  // Try to reuse the target
112
143
  if (renderer.toLocaleLowerCase() === "canvas") {
113
144
  if (target) {
114
- target = d3.select(target.node ? target.node() : target);
145
+ target = d3__namespace.select(target.node ? target.node() : target);
115
146
  } else {
116
- target = d3.create("canvas").attr("width", width).attr("height", height);
147
+ target = d3__namespace.create("canvas").attr("width", width).attr("height", height);
117
148
  }
118
149
  useOcclusion = false;
119
150
  } else {
120
151
  if (target) {
121
- target = d3.select(target.node ? target.node() : target);
152
+ target = d3__namespace.select(target.node ? target.node() : target);
122
153
  } else {
123
- target = d3.create("svg").attr("viewBox", [0, 0, width, height]);
154
+ target = d3__namespace.create("svg").attr("viewBox", [0, 0, width, height]);
124
155
  }
125
- target = target || d3.create("svg").attr("viewBox", [0, 0, width, height]);
156
+ target = target || d3__namespace.create("svg").attr("viewBox", [0, 0, width, height]);
126
157
  }
127
158
 
128
- const delaunay = d3.Delaunay.from(data, x, y);
159
+ const delaunay = d3__namespace.Delaunay.from(data, x, y);
129
160
  const voronoi = delaunay.voronoi([
130
161
  xExtent[0] - 1,
131
162
  yExtent[0] - 1,
@@ -213,7 +244,7 @@ export default function smartLabels(
213
244
  .attr("display", ({ show }) => (show ? null : "none"))
214
245
  .attr("d", ({ d, cell }, index) =>
215
246
  cell
216
- ? `M${d3.polygonCentroid(cell)}L${x(d, index)},${y(d, index)}`
247
+ ? `M${d3__namespace.polygonCentroid(cell)}L${x(d, index)},${y(d, index)}`
217
248
  : null
218
249
  );
219
250
  }
@@ -259,7 +290,7 @@ export default function smartLabels(
259
290
  .style("font", ({d}, i) => font(d, i))
260
291
  .each(function ({ d, cell }, index) {
261
292
  if (!cell) return;
262
- const [cx, cy] = d3.polygonCentroid(cell);
293
+ const [cx, cy] = d3__namespace.polygonCentroid(cell);
263
294
  const angle =
264
295
  (Math.round(
265
296
  (Math.atan2(cy - y(d, index), cx - x(d, index)) / Math.PI) * 2
@@ -267,7 +298,7 @@ export default function smartLabels(
267
298
  4) %
268
299
  4;
269
300
 
270
- d3.select(this).call(
301
+ d3__namespace.select(this).call(
271
302
  angle === 0
272
303
  ? orient.right
273
304
  : angle === 3
@@ -279,7 +310,7 @@ export default function smartLabels(
279
310
  })
280
311
  .attr("transform", ({ d, cell }, index) => {
281
312
  if (labelsInCentroids) {
282
- const [cx, cy] = d3.polygonCentroid(cell);
313
+ const [cx, cy] = d3__namespace.polygonCentroid(cell);
283
314
 
284
315
  return `translate(${cx}, ${cy})`;
285
316
  } else {
@@ -293,7 +324,7 @@ export default function smartLabels(
293
324
  if (hover) {
294
325
  mouseRect.on("mousemove", (event) => {
295
326
  // Find the selected point
296
- const i = delaunay.find(...d3.pointer(event));
327
+ const i = delaunay.find(...d3__namespace.pointer(event));
297
328
  if (i === selected) return;
298
329
  selected = i;
299
330
 
@@ -361,7 +392,7 @@ export default function smartLabels(
361
392
  if (showAnchors || labelsInCentroids) {
362
393
  cells.forEach(({ d, cell, show }, index) => {
363
394
  if (!show) return;
364
- const [cx, cy] = d3.polygonCentroid(cell);
395
+ const [cx, cy] = d3__namespace.polygonCentroid(cell);
365
396
  context.beginPath();
366
397
  context.moveTo(cx, cy);
367
398
  context.lineTo(x(d, index), y(d, index));
@@ -398,7 +429,7 @@ export default function smartLabels(
398
429
  function renderCanvasLabels(context, selected) {
399
430
  cells.forEach(({ d, cell, show }, index) => {
400
431
  if (!show && index !== selected) return;
401
- const [cx, cy] = d3.polygonCentroid(cell);
432
+ const [cx, cy] = d3__namespace.polygonCentroid(cell);
402
433
  const angle =
403
434
  (Math.round(
404
435
  (Math.atan2(cy - y(d, index), cx - x(d, index)) / Math.PI) * 2
@@ -451,7 +482,7 @@ export default function smartLabels(
451
482
  if (debug) console.log("setting onmousemove", target);
452
483
 
453
484
  target.on("mousemove", (event) => {
454
- const [mx, my] = d3.pointer(event);
485
+ const [mx, my] = d3__namespace.pointer(event);
455
486
  const i = delaunay.find(mx, my);
456
487
  if (i === selected) return;
457
488
  selected = i;
@@ -489,7 +520,7 @@ export default function smartLabels(
489
520
  // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
490
521
  // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
491
522
  function occlusion(svg, against = "text") {
492
- const nodes = d3
523
+ const nodes = d3__namespace
493
524
  .sort(svg.selectAll(against), (node) => +node.getAttribute("data-priority"))
494
525
  .reverse()
495
526
  .map((node) => {
@@ -500,7 +531,7 @@ function occlusion(svg, against = "text") {
500
531
  const visible = [];
501
532
  for (const d of nodes) {
502
533
  const occluded = visible.some((e) => intersectRect(d, e));
503
- d3.select(d.node).classed("occluded", occluded);
534
+ d3__namespace.select(d.node).classed("occluded", occluded);
504
535
  if (!occluded) visible.push(d);
505
536
  }
506
537
  return visible;
@@ -515,3 +546,5 @@ function intersectRect(a, b) {
515
546
  b.y + b.height < a.y
516
547
  );
517
548
  }
549
+
550
+ module.exports = smartLabels;
@@ -1,4 +1,4 @@
1
- // smart-labels v0.0.13 Copyright (c) 2024 John Alexis Guerra Gómez
1
+ // smart-labels v0.0.15 Copyright (c) 2024 John Alexis Guerra Gómez
2
2
  import * as d3 from 'd3';
3
3
 
4
4
  // Based on https://observablehq.com/@d3/voronoi-labels by Mike Bostock
@@ -75,8 +75,16 @@ function smartLabels(
75
75
  y(d, index) !== null
76
76
  );
77
77
 
78
- if (typeof font === "string") font = () => font;
79
- if (typeof hoverFont === "string") hoverFont = () => hoverFont;
78
+ // Keep the string in its own constant: `font = () => font` would return the
79
+ // arrow function itself (font is reassigned), so string fonts were ignored.
80
+ if (typeof font === "string") {
81
+ const fontString = font;
82
+ font = () => fontString;
83
+ }
84
+ if (typeof hoverFont === "string") {
85
+ const hoverFontString = hoverFont;
86
+ hoverFont = () => hoverFontString;
87
+ }
80
88
 
81
89
  let xExtent = d3.extent(data, x),
82
90
  yExtent = d3.extent(data, y);
@@ -1,4 +1,4 @@
1
- // smart-labels v0.0.13 Copyright (c) 2024 John Alexis Guerra Gómez
1
+ // smart-labels v0.0.15 Copyright (c) 2024 John Alexis Guerra Gómez
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('d3')) :
4
4
  typeof define === 'function' && define.amd ? define(['d3'], factory) :
@@ -98,8 +98,16 @@ function smartLabels(
98
98
  y(d, index) !== null
99
99
  );
100
100
 
101
- if (typeof font === "string") font = () => font;
102
- if (typeof hoverFont === "string") hoverFont = () => hoverFont;
101
+ // Keep the string in its own constant: `font = () => font` would return the
102
+ // arrow function itself (font is reassigned), so string fonts were ignored.
103
+ if (typeof font === "string") {
104
+ const fontString = font;
105
+ font = () => fontString;
106
+ }
107
+ if (typeof hoverFont === "string") {
108
+ const hoverFontString = hoverFont;
109
+ hoverFont = () => hoverFontString;
110
+ }
103
111
 
104
112
  let xExtent = d3__namespace.extent(data, x),
105
113
  yExtent = d3__namespace.extent(data, y);
@@ -1,2 +1,2 @@
1
- // smart-labels v0.0.13 Copyright (c) 2024 John Alexis Guerra Gómez
2
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("d3")):"function"==typeof define&&define.amd?define(["d3"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).smartLabels=e(t.d3)}(this,function(t){"use strict";function e(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(o){if("default"!==o){var n=Object.getOwnPropertyDescriptor(t,o);Object.defineProperty(e,o,n.get?n:{enumerable:!0,get:function(){return t[o]}})}}),e.default=t,Object.freeze(e)}var o=e(t);function n(t,e="text"){const n=o.sort(t.selectAll(e),t=>+t.getAttribute("data-priority")).reverse().map(t=>{const{x:e,y:o,width:n,height:r}=t.getBoundingClientRect();return{node:t,x:e,y:o,width:n,height:r}}),r=[];for(const t of n){const e=r.some(e=>{return n=e,!((o=t).x+o.width<n.x||n.x+n.width<o.x||o.y+o.height<n.y||n.y+n.height<o.y);var o,n});o.select(t.node).classed("occluded",e),e||r.push(t)}return r}return function(t,{x:e=t=>t[0],y:r=t=>t[1],r:l=()=>3,label:a=(t,e)=>e,fill:s="#333",stroke:i="white",width:c=null,height:d=null,target:h=null,renderer:f="svg",font:u=()=>"10pt sans-serif",hover:g=!0,onHover:p=t=>t,hoverFont:y=()=>"bolder 12pt sans-serif",labelsInCentroids:b=!0,threshold:m=2e3,alwaysShow:v=t=>!1,showLabel:w=(t,e)=>v(t)||-o.polygonArea(e)>m,backgroundFill:x="#fefefe01",strokeWidth:k=5,showVoronoi:L=!1,voronoiStroke:A="#ccc",showAnchors:j=!1,anchorsStroke:S="orange",anchorsFill:C="none",useOcclusion:P=!0,occludedStyle:M="opacity: 0.2",showPoints:O=!1,pointsFill:T="#ccc",pointsSelectedFill:$="firebrick",pointsStroke:E="#ccc",debug:F=!1,selected:I=null,padding:B=3}={}){if(!t||0===t?.length)return console.log("smartLabels: No data to render"),h;t=t.filter((t,o)=>void 0!==e(t,o)&&null!==e(t,o)&&void 0!==r(t,o)&&null!==r(t,o)),"string"==typeof u&&(u=()=>u),"string"==typeof y&&(y=()=>y);let V=o.extent(t,e),D=o.extent(t,r);if(c=c||V[1]-V[0],d=d||D[1]-D[0],F&&console.log("✅ smartLabels renderer",f),h&&!function(t,e){return t&&t.node&&(t=t.node()),"canvas"===e.toLocaleLowerCase()?t instanceof HTMLCanvasElement:t instanceof SVGElement}(h,f))throw F&&console.log("❌ smartLabels Target doesn't match the renderer",h,f),new Error("Smartlabels Target doesn't match the renderer",h,f);"canvas"===f.toLocaleLowerCase()?(h=h?o.select(h.node?h.node():h):o.create("canvas").attr("width",c).attr("height",d),P=!1):h=(h=h?o.select(h.node?h.node():h):o.create("svg").attr("viewBox",[0,0,c,d]))||o.create("svg").attr("viewBox",[0,0,c,d]);const H=o.Delaunay.from(t,e,r),R=H.voronoi([V[0]-1,D[0]-1,V[1]+1,D[1]+1]);let W=t.map((t,e)=>[t,R.cellPolygon(e)]);function q(t,o,n){if(!o){const l=H.find(e(t,n),r(t,n));if(-1===l)return console.log("couldn't find cell",l,t,e(t,n),r(t,n)),null;o=W[l][1]}return o}function z(n){F&&console.log("renderCanvasAnchorsVoronoiPoints","showAnchors",j,"labelsInCentroids",b,"showVoronoi",L,"showPoints",O),(j||b)&&W.forEach(({d:t,cell:l,show:a},s)=>{if(!a)return;const[i,c]=o.polygonCentroid(l);n.beginPath(),n.moveTo(i,c),n.lineTo(e(t,s),r(t,s)),n.strokeStyle=S,n.stroke()}),L&&(n.beginPath(),R.render(n),n.strokeStyle=A,n.stroke()),O&&(n.beginPath(),n.save(),n.globalAlpha=.7,n.fillStyle="black",t.forEach((t,o)=>{n.beginPath(),n.arc(e(t,o),r(t,o),l(t,o),0,2*Math.PI)}),n.restore(),n.fillStyle=T,n.fill(),n.strokeStyle=E,n.stroke())}function G(t,n){W.forEach(({d:c,cell:d,show:h},f)=>{if(!h&&f!==n)return;const[g,p]=o.polygonCentroid(d),m=(Math.round(Math.atan2(p-r(c,f),g-e(c,f))/Math.PI*2)+4)%4;t.save();const v=l(c,f)+B,w=0===m?v:2===m?-v:v,x=1===m?v:3===m?-v:v;t.translate(b?g:e(c,f)+w,b?p:r(c,f)+x),t.textAlign=0===m?"left":1===m?"center":2===m?"right":"center",t.textBaseline=0===m?"middle":1===m?"top":2===m?"middle":"bottom",t.font=f===n?y(c,f):u(c,f),t.fillStyle=s,t.strokeStyle=i,t.lineWidth=k,t.strokeText(a(c,f),0,0),t.fillText(a(c,f),0,0),t.restore()})}return W=W.map(([t,e],o)=>[t,q(t,e,o)]).map(([t,e])=>({d:t,cell:e,show:w(t,e)})),"canvas"===f.toLocaleLowerCase()?function(){const t=h.node().getContext("2d");z(t),G(t,I),g&&(F&&console.log("setting onmousemove",h),h.on("mousemove",e=>{const[n,r]=o.pointer(e),l=H.find(n,r);l!==I&&(I=l,t.clearRect(0,0,c,d),z(t),p&&"function"==typeof p&&p(l),G(t,I))}))}():function(){let t=null;P&&h.selectAll("style.smartLabels").data([0]).join("style").attr("class","smartLabels").html(`\n svg g.labels > text.occluded:not(.selected) { ${M} }\n `);const f=h.selectAll("rect.smartLabels").data([0]).join("rect").attr("class","smartLabels").attr("width",c).attr("height",d).attr("fill",x).attr("stroke","none"),m={top:t=>t.attr("text-anchor","middle").attr("y",(t,e)=>-(l(t,e)+B)),right:t=>t.attr("text-anchor","start").attr("dy","0.35em").attr("x",(t,e)=>l(t,e)+B),bottom:t=>t.attr("text-anchor","middle").attr("dy","0.71em").attr("y",(t,e)=>l(t,e)+B),left:t=>t.attr("text-anchor","end").attr("dy","0.35em").attr("x",(t,e)=>-(l(t,e)+B))};let v;(j||b)&&(t=h.selectAll("g#anchors").data([W]).join("g").attr("pointer-events","none").attr("id","anchors").attr("stroke",S).attr("fill",C).selectAll("path.anchor").data(t=>t).join("path").attr("class","anchor").attr("display",({show:t})=>t?null:"none").attr("d",({d:t,cell:n},l)=>n?`M${o.polygonCentroid(n)}L${e(t,l)},${r(t,l)}`:null)),L&&h.selectAll("path#voronoi").data([1]).join("path").attr("id","voronoi").attr("stroke",A).attr("pointer-events","none").attr("d",R.render()),O&&(v=h.selectAll("circle#points").data([1]).join("path").attr("id","points").attr("pointer-events","none").attr("stroke",E).attr("fill",T).attr("d",H.renderPoints(null,2)));const w=h.selectAll("g.labels").data([W]).join("g").attr("class","labels").attr("fill",s).attr("stroke",i).attr("stroke-width",k).attr("paint-order","stroke").attr("pointer-events","none").selectAll("text").data(t=>t).join("text").style("font",({d:t},e)=>u(t,e)).each(function({d:t,cell:n},l){if(!n)return;const[a,s]=o.polygonCentroid(n),i=(Math.round(Math.atan2(s-r(t,l),a-e(t,l))/Math.PI*2)+4)%4;o.select(this).call(0===i?m.right:3===i?m.top:1===i?m.bottom:m.left)}).attr("transform",({d:t,cell:n},l)=>{if(b){const[t,e]=o.polygonCentroid(n);return`translate(${t}, ${e})`}return`translate(${e(t,l)}, ${r(t,l)})`}).attr("display",({show:t})=>t?null:"none").text(({d:t},e,o)=>a(t,e,o));let F=null;if(g&&f.on("mousemove",e=>{const n=H.find(...o.pointer(e));n!==F&&(F=n,w.attr("display",({show:t},e)=>e===F||t?null:"none").style("font",({d:t},e)=>e===F?y(t,e):u(t,e)).classed("selected",(t,e)=>e===F).filter((t,e)=>e===F).raise(),t&&t.attr("display",({show:t},e)=>t||e===F?null:"none"),O&&v.attr("fill",(t,e)=>$).classed("selected",(t,e)=>e===F),p&&"function"==typeof p&&p(n))}),P){const t=new MutationObserver(function(){document.contains(h.node())&&(h.call(n,"g.labels > text"),t.disconnect())});t.observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}}(),Object.assign(h.node(),{delaunay:H,voronoi:R,cells:W})}});
1
+ // smart-labels v0.0.15 Copyright (c) 2024 John Alexis Guerra Gómez
2
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("d3")):"function"==typeof define&&define.amd?define(["d3"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).smartLabels=e(t.d3)}(this,function(t){"use strict";function e(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(o){if("default"!==o){var n=Object.getOwnPropertyDescriptor(t,o);Object.defineProperty(e,o,n.get?n:{enumerable:!0,get:function(){return t[o]}})}}),e.default=t,Object.freeze(e)}var o=e(t);function n(t,e="text"){const n=o.sort(t.selectAll(e),t=>+t.getAttribute("data-priority")).reverse().map(t=>{const{x:e,y:o,width:n,height:r}=t.getBoundingClientRect();return{node:t,x:e,y:o,width:n,height:r}}),r=[];for(const t of n){const e=r.some(e=>{return n=e,!((o=t).x+o.width<n.x||n.x+n.width<o.x||o.y+o.height<n.y||n.y+n.height<o.y);var o,n});o.select(t.node).classed("occluded",e),e||r.push(t)}return r}return function(t,{x:e=t=>t[0],y:r=t=>t[1],r:l=()=>3,label:a=(t,e)=>e,fill:s="#333",stroke:i="white",width:c=null,height:d=null,target:h=null,renderer:f="svg",font:u=()=>"10pt sans-serif",hover:g=!0,onHover:p=t=>t,hoverFont:y=()=>"bolder 12pt sans-serif",labelsInCentroids:b=!0,threshold:m=2e3,alwaysShow:v=t=>!1,showLabel:w=(t,e)=>v(t)||-o.polygonArea(e)>m,backgroundFill:x="#fefefe01",strokeWidth:k=5,showVoronoi:L=!1,voronoiStroke:A="#ccc",showAnchors:j=!1,anchorsStroke:S="orange",anchorsFill:C="none",useOcclusion:P=!0,occludedStyle:M="opacity: 0.2",showPoints:O=!1,pointsFill:T="#ccc",pointsSelectedFill:$="firebrick",pointsStroke:E="#ccc",debug:F=!1,selected:I=null,padding:B=3}={}){if(!t||0===t?.length)return console.log("smartLabels: No data to render"),h;if(t=t.filter((t,o)=>void 0!==e(t,o)&&null!==e(t,o)&&void 0!==r(t,o)&&null!==r(t,o)),"string"==typeof u){const t=u;u=()=>t}if("string"==typeof y){const t=y;y=()=>t}let V=o.extent(t,e),D=o.extent(t,r);if(c=c||V[1]-V[0],d=d||D[1]-D[0],F&&console.log("✅ smartLabels renderer",f),h&&!function(t,e){return t&&t.node&&(t=t.node()),"canvas"===e.toLocaleLowerCase()?t instanceof HTMLCanvasElement:t instanceof SVGElement}(h,f))throw F&&console.log("❌ smartLabels Target doesn't match the renderer",h,f),new Error("Smartlabels Target doesn't match the renderer",h,f);"canvas"===f.toLocaleLowerCase()?(h=h?o.select(h.node?h.node():h):o.create("canvas").attr("width",c).attr("height",d),P=!1):h=(h=h?o.select(h.node?h.node():h):o.create("svg").attr("viewBox",[0,0,c,d]))||o.create("svg").attr("viewBox",[0,0,c,d]);const H=o.Delaunay.from(t,e,r),R=H.voronoi([V[0]-1,D[0]-1,V[1]+1,D[1]+1]);let W=t.map((t,e)=>[t,R.cellPolygon(e)]);function q(t,o,n){if(!o){const l=H.find(e(t,n),r(t,n));if(-1===l)return console.log("couldn't find cell",l,t,e(t,n),r(t,n)),null;o=W[l][1]}return o}function z(n){F&&console.log("renderCanvasAnchorsVoronoiPoints","showAnchors",j,"labelsInCentroids",b,"showVoronoi",L,"showPoints",O),(j||b)&&W.forEach(({d:t,cell:l,show:a},s)=>{if(!a)return;const[i,c]=o.polygonCentroid(l);n.beginPath(),n.moveTo(i,c),n.lineTo(e(t,s),r(t,s)),n.strokeStyle=S,n.stroke()}),L&&(n.beginPath(),R.render(n),n.strokeStyle=A,n.stroke()),O&&(n.beginPath(),n.save(),n.globalAlpha=.7,n.fillStyle="black",t.forEach((t,o)=>{n.beginPath(),n.arc(e(t,o),r(t,o),l(t,o),0,2*Math.PI)}),n.restore(),n.fillStyle=T,n.fill(),n.strokeStyle=E,n.stroke())}function G(t,n){W.forEach(({d:c,cell:d,show:h},f)=>{if(!h&&f!==n)return;const[g,p]=o.polygonCentroid(d),m=(Math.round(Math.atan2(p-r(c,f),g-e(c,f))/Math.PI*2)+4)%4;t.save();const v=l(c,f)+B,w=0===m?v:2===m?-v:v,x=1===m?v:3===m?-v:v;t.translate(b?g:e(c,f)+w,b?p:r(c,f)+x),t.textAlign=0===m?"left":1===m?"center":2===m?"right":"center",t.textBaseline=0===m?"middle":1===m?"top":2===m?"middle":"bottom",t.font=f===n?y(c,f):u(c,f),t.fillStyle=s,t.strokeStyle=i,t.lineWidth=k,t.strokeText(a(c,f),0,0),t.fillText(a(c,f),0,0),t.restore()})}return W=W.map(([t,e],o)=>[t,q(t,e,o)]).map(([t,e])=>({d:t,cell:e,show:w(t,e)})),"canvas"===f.toLocaleLowerCase()?function(){const t=h.node().getContext("2d");z(t),G(t,I),g&&(F&&console.log("setting onmousemove",h),h.on("mousemove",e=>{const[n,r]=o.pointer(e),l=H.find(n,r);l!==I&&(I=l,t.clearRect(0,0,c,d),z(t),p&&"function"==typeof p&&p(l),G(t,I))}))}():function(){let t=null;P&&h.selectAll("style.smartLabels").data([0]).join("style").attr("class","smartLabels").html(`\n svg g.labels > text.occluded:not(.selected) { ${M} }\n `);const f=h.selectAll("rect.smartLabels").data([0]).join("rect").attr("class","smartLabels").attr("width",c).attr("height",d).attr("fill",x).attr("stroke","none"),m={top:t=>t.attr("text-anchor","middle").attr("y",(t,e)=>-(l(t,e)+B)),right:t=>t.attr("text-anchor","start").attr("dy","0.35em").attr("x",(t,e)=>l(t,e)+B),bottom:t=>t.attr("text-anchor","middle").attr("dy","0.71em").attr("y",(t,e)=>l(t,e)+B),left:t=>t.attr("text-anchor","end").attr("dy","0.35em").attr("x",(t,e)=>-(l(t,e)+B))};let v;(j||b)&&(t=h.selectAll("g#anchors").data([W]).join("g").attr("pointer-events","none").attr("id","anchors").attr("stroke",S).attr("fill",C).selectAll("path.anchor").data(t=>t).join("path").attr("class","anchor").attr("display",({show:t})=>t?null:"none").attr("d",({d:t,cell:n},l)=>n?`M${o.polygonCentroid(n)}L${e(t,l)},${r(t,l)}`:null)),L&&h.selectAll("path#voronoi").data([1]).join("path").attr("id","voronoi").attr("stroke",A).attr("pointer-events","none").attr("d",R.render()),O&&(v=h.selectAll("circle#points").data([1]).join("path").attr("id","points").attr("pointer-events","none").attr("stroke",E).attr("fill",T).attr("d",H.renderPoints(null,2)));const w=h.selectAll("g.labels").data([W]).join("g").attr("class","labels").attr("fill",s).attr("stroke",i).attr("stroke-width",k).attr("paint-order","stroke").attr("pointer-events","none").selectAll("text").data(t=>t).join("text").style("font",({d:t},e)=>u(t,e)).each(function({d:t,cell:n},l){if(!n)return;const[a,s]=o.polygonCentroid(n),i=(Math.round(Math.atan2(s-r(t,l),a-e(t,l))/Math.PI*2)+4)%4;o.select(this).call(0===i?m.right:3===i?m.top:1===i?m.bottom:m.left)}).attr("transform",({d:t,cell:n},l)=>{if(b){const[t,e]=o.polygonCentroid(n);return`translate(${t}, ${e})`}return`translate(${e(t,l)}, ${r(t,l)})`}).attr("display",({show:t})=>t?null:"none").text(({d:t},e,o)=>a(t,e,o));let F=null;if(g&&f.on("mousemove",e=>{const n=H.find(...o.pointer(e));n!==F&&(F=n,w.attr("display",({show:t},e)=>e===F||t?null:"none").style("font",({d:t},e)=>e===F?y(t,e):u(t,e)).classed("selected",(t,e)=>e===F).filter((t,e)=>e===F).raise(),t&&t.attr("display",({show:t},e)=>t||e===F?null:"none"),O&&v.attr("fill",(t,e)=>$).classed("selected",(t,e)=>e===F),p&&"function"==typeof p&&p(n))}),P){const t=new MutationObserver(function(){document.contains(h.node())&&(h.call(n,"g.labels > text"),t.disconnect())});t.observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}}(),Object.assign(h.node(),{delaunay:H,voronoi:R,cells:W})}});
package/package.json CHANGED
@@ -1,13 +1,37 @@
1
1
  {
2
2
  "name": "smart-labels",
3
- "version": "0.0.13",
4
- "description": "A basic library for adding labels to a visualization when there is enough space",
3
+ "version": "0.0.15",
4
+ "description": "Fast automatic label placement for D3 scatterplots using Voronoi cells and occlusion detection",
5
+ "keywords": [
6
+ "d3",
7
+ "labels",
8
+ "label-placement",
9
+ "voronoi",
10
+ "delaunay",
11
+ "occlusion",
12
+ "scatterplot",
13
+ "annotation",
14
+ "visualization",
15
+ "dataviz"
16
+ ],
5
17
  "type": "module",
6
- "main": "dist/smartLabels.min.js",
18
+ "main": "dist/smartLabels.cjs",
7
19
  "module": "dist/smartLabels.es.js",
8
20
  "browser": "dist/smartLabels.min.js",
9
21
  "unpkg": "dist/smartLabels.min.js",
10
22
  "jsdelivr": "dist/smartLabels.min.js",
23
+ "exports": {
24
+ ".": {
25
+ "import": "./dist/smartLabels.es.js",
26
+ "require": "./dist/smartLabels.cjs",
27
+ "default": "./dist/smartLabels.min.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "LICENSE",
33
+ "README.md"
34
+ ],
11
35
  "scripts": {
12
36
  "build": "rollup -c",
13
37
  "dev": "rollup -c -w",
@@ -15,18 +39,16 @@
15
39
  },
16
40
  "devDependencies": {
17
41
  "@eslint/js": "^9.14.0",
18
- "@rollup/plugin-json": "^6.1.0",
19
- "@rollup/plugin-node-resolve": "^15.2.3",
20
- "@rollup/plugin-terser": "^1.0.0",
42
+ "@rollup/plugin-terser": "^1.0.2",
43
+ "d3": "^7.9.2",
21
44
  "eslint": "^9.14.0",
22
45
  "eslint-config-prettier": "^9.1.0",
23
- "eslint-plugin-html": "^8.1.2",
46
+ "eslint-plugin-html": "^8.2.1",
24
47
  "globals": "^15.12.0",
25
- "prettier": "^3.3.3",
26
- "rollup": "^4.20.0",
27
- "rollup-plugin-commonjs": "^10.1.0"
48
+ "prettier": "^3.10.1",
49
+ "rollup": "^4.68.2"
28
50
  },
29
- "dependencies": {
51
+ "peerDependencies": {
30
52
  "d3": "^7.9.0"
31
53
  },
32
54
  "author": {
@@ -34,6 +56,10 @@
34
56
  "url": "https://johnguerra.co"
35
57
  },
36
58
  "license": "MIT",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/john-guerra/smart-labels.git"
62
+ },
37
63
  "bugs": {
38
64
  "url": "https://github.com/john-guerra/smart-labels/issues"
39
65
  },
package/rollup.config.js DELETED
@@ -1,66 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import node from "@rollup/plugin-node-resolve";
3
- import commonjs from "rollup-plugin-commonjs";
4
- import terser from "@rollup/plugin-terser";
5
- // import babel from "rollup-plugin-babel";
6
- import json from "@rollup/plugin-json";
7
- import meta from "./package.json" with { type: "json" };
8
-
9
- // Extract copyrights from the LICENSE.
10
- const copyrights = readFileSync("./LICENSE", "utf-8")
11
- .split(/\n/g)
12
- .filter((line) => /^copyright\s+/i.test(line))
13
- .map((line) => line.replace(/^copyright\s+/i, ""));
14
-
15
- const filename = "smartLabels";
16
-
17
- // Observable Plot style
18
- const config = {
19
- input: "src/index.js",
20
- external: ["d3"],
21
- output: {
22
- indent: false,
23
- banner: `// ${meta.name} v${meta.version} Copyright ${copyrights.join(", ")}`,
24
- extend: true,
25
- globals: {
26
- "d3": "d3",
27
- },
28
- name: "smartLabels",
29
- },
30
- plugins: [commonjs(), json(), node()],
31
- };
32
-
33
- export default [
34
- {
35
- ...config,
36
- output: {
37
- ...config.output,
38
- format: "umd",
39
- file: `dist/${filename}.js`,
40
- },
41
- },
42
- {
43
- ...config,
44
- output: {
45
- ...config.output,
46
- format: "es",
47
- file: `dist/${filename}.es.js`,
48
- },
49
- },
50
- {
51
- ...config,
52
- output: {
53
- ...config.output,
54
- format: "umd",
55
- file: `dist/${filename}.min.js`,
56
- },
57
- plugins: [
58
- ...config.plugins,
59
- terser({
60
- output: {
61
- preamble: config.output.banner,
62
- },
63
- }),
64
- ],
65
- },
66
- ];