overlapDodgeY.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { ascending } from 'd3-array';
  2. function isSegmentIntersect([a, b], [c, d]) {
  3. return d > a && b > c;
  4. }
  5. function useMap() {
  6. const map = new Map();
  7. const get = (key) => map.get(key);
  8. const set = (key, value) => map.set(key, value);
  9. return [get, set];
  10. }
  11. function getBoundsWithoutConnector(shape) {
  12. const node = shape.cloneNode(true);
  13. const connectorShape = node.getElementById('connector');
  14. connectorShape && node.removeChild(connectorShape);
  15. const { min, max } = node.getRenderBounds();
  16. node.destroy();
  17. return { min, max };
  18. }
  19. /**
  20. * An iterative dodge method avoids label overlap. (n * log(n))
  21. */
  22. export const OverlapDodgeY = (options) => {
  23. const { maxIterations = 10, maxError = 0.1, padding = 1 } = options;
  24. return (labels) => {
  25. const n = labels.length;
  26. if (n <= 1)
  27. return labels;
  28. // Index y, x0, x, height, by label.
  29. const [y0, setY0] = useMap();
  30. const [y, setY] = useMap();
  31. const [h, setH] = useMap();
  32. const [xx, setXX] = useMap();
  33. for (const label of labels) {
  34. const { min, max } = getBoundsWithoutConnector(label);
  35. const [x0, y0] = min;
  36. const [x1, y1] = max;
  37. setY0(label, y0);
  38. setY(label, y0);
  39. setH(label, y1 - y0);
  40. setXX(label, [x0, x1]);
  41. }
  42. // Offsets position Y.
  43. for (let iter = 0; iter < maxIterations; iter++) {
  44. labels.sort((a, b) => ascending(y(a), y(b)));
  45. let error = 0;
  46. for (let i = 0; i < n - 1; i++) {
  47. const l0 = labels[i];
  48. let j = i + 1;
  49. let l1;
  50. // Find the next label overlapping with the current label in x direction.
  51. while ((l1 = labels[j]) && !isSegmentIntersect(xx(l0), xx(l1)))
  52. j += 1;
  53. if (l1) {
  54. const y0 = y(l0);
  55. const h0 = h(l0);
  56. const y1 = y(l1);
  57. const delta = y1 - (y0 + h0);
  58. if (delta < padding) {
  59. const newDelta = (padding - delta) / 2;
  60. error = Math.max(error, newDelta);
  61. setY(l0, y0 - newDelta);
  62. setY(l1, y1 + newDelta);
  63. }
  64. }
  65. }
  66. if (error < maxError)
  67. break;
  68. }
  69. for (const label of labels) {
  70. label.style.y += y(label) - y0(label);
  71. }
  72. return labels;
  73. };
  74. };
  75. //# sourceMappingURL=overlapDodgeY.js.map