@opendata-ai/openchart-vanilla 2.2.1 → 2.3.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-vanilla",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -46,8 +46,8 @@
46
46
  "typecheck": "tsc --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@opendata-ai/openchart-core": "2.2.1",
50
- "@opendata-ai/openchart-engine": "2.2.1",
49
+ "@opendata-ai/openchart-core": "2.3.1",
50
+ "@opendata-ai/openchart-engine": "2.3.1",
51
51
  "d3-force": "^3.0.0",
52
52
  "d3-quadtree": "^3.0.1"
53
53
  },
@@ -247,7 +247,7 @@ export class GraphCanvasRenderer {
247
247
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
248
248
  const padding = theme.spacing.padding;
249
249
  const x = w - padding;
250
- const y = h - 4;
250
+ const y = h - padding;
251
251
  ctx.font = `600 20px ${theme.fonts.family}`;
252
252
  ctx.fillStyle = theme.colors.axis;
253
253
  ctx.globalAlpha = 0.5;
@@ -662,14 +662,22 @@ export class GraphCanvasRenderer {
662
662
 
663
663
  const labelY = node.y + node.radius + 3;
664
664
 
665
- // Dark halo for readability (fall back to semi-transparent black when bg is transparent)
666
- ctx.strokeStyle =
667
- theme.colors.background === 'transparent' ? 'rgba(0, 0, 0, 0.7)' : theme.colors.background;
665
+ // Halo for readability: stroke behind text in the background color
666
+ // so labels stay legible over edges and other nodes.
667
+ if (theme.colors.background !== 'transparent') {
668
+ ctx.strokeStyle = theme.colors.background;
669
+ } else {
670
+ // Transparent bg: infer page background from text luminance.
671
+ // Light text = dark page, dark text = light page.
672
+ ctx.strokeStyle = isLightColor(theme.colors.text)
673
+ ? 'rgba(0, 0, 0, 0.7)'
674
+ : 'rgba(255, 255, 255, 0.85)';
675
+ }
668
676
  ctx.lineWidth = 3;
669
677
  ctx.lineJoin = 'round';
678
+ ctx.miterLimit = 2;
670
679
  ctx.strokeText(node.label, node.x, labelY);
671
680
 
672
- // White/light text
673
681
  ctx.fillStyle = theme.colors.text;
674
682
  ctx.fillText(node.label, node.x, labelY);
675
683
  }
@@ -715,3 +723,24 @@ function brighten(color: string): string {
715
723
 
716
724
  return color;
717
725
  }
726
+
727
+ /**
728
+ * Returns true if a color is perceptually light (luminance > 0.5).
729
+ * Used to pick a contrasting halo color for labels on transparent backgrounds.
730
+ */
731
+ function isLightColor(color: string): boolean {
732
+ const hex = color.replace('#', '');
733
+ const full =
734
+ hex.length === 3
735
+ ? hex
736
+ .split('')
737
+ .map((c) => c + c)
738
+ .join('')
739
+ : hex;
740
+ if (full.length !== 6) return false;
741
+ const r = parseInt(full.slice(0, 2), 16) / 255;
742
+ const g = parseInt(full.slice(2, 4), 16) / 255;
743
+ const b = parseInt(full.slice(4, 6), 16) / 255;
744
+ const toLinear = (c: number) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
745
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b) > 0.5;
746
+ }