@internetstiftelsen/charts 0.4.0 → 0.4.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/bar.js +19 -7
- package/legend.d.ts +0 -1
- package/legend.js +2 -31
- package/package.json +1 -1
- package/utils.d.ts +1 -0
- package/utils.js +38 -0
package/bar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeForCSS, mergeDeep } from './utils.js';
|
|
1
|
+
import { getContrastTextColor, sanitizeForCSS, mergeDeep } from './utils.js';
|
|
2
2
|
export class Bar {
|
|
3
3
|
constructor(config) {
|
|
4
4
|
Object.defineProperty(this, "type", {
|
|
@@ -362,7 +362,7 @@ export class Bar {
|
|
|
362
362
|
const fontSize = config.fontSize ?? theme.valueLabel.fontSize;
|
|
363
363
|
const fontFamily = config.fontFamily ?? theme.valueLabel.fontFamily;
|
|
364
364
|
const fontWeight = config.fontWeight ?? theme.valueLabel.fontWeight;
|
|
365
|
-
const
|
|
365
|
+
const defaultLabelColor = config.color ?? theme.valueLabel.color;
|
|
366
366
|
const background = config.background ?? theme.valueLabel.background;
|
|
367
367
|
const border = config.border ?? theme.valueLabel.border;
|
|
368
368
|
const borderRadius = config.borderRadius ?? theme.valueLabel.borderRadius;
|
|
@@ -370,11 +370,14 @@ export class Bar {
|
|
|
370
370
|
const labelGroup = plotGroup
|
|
371
371
|
.append('g')
|
|
372
372
|
.attr('class', `bar-value-labels-${sanitizeForCSS(this.dataKey)}`);
|
|
373
|
-
data.forEach((d) => {
|
|
373
|
+
data.forEach((d, i) => {
|
|
374
374
|
const categoryKey = String(d[xKey]);
|
|
375
375
|
const value = parseValue(d[this.dataKey]);
|
|
376
376
|
const valueText = String(value);
|
|
377
377
|
const xPos = this.getScaledPosition(d, xKey, x, xScaleType);
|
|
378
|
+
const barColor = this.colorAdapter
|
|
379
|
+
? this.colorAdapter(d, i)
|
|
380
|
+
: this.fill;
|
|
378
381
|
// Calculate bar position based on stacking mode
|
|
379
382
|
let barTop;
|
|
380
383
|
let barBottom;
|
|
@@ -497,6 +500,9 @@ export class Bar {
|
|
|
497
500
|
}
|
|
498
501
|
tempText.remove();
|
|
499
502
|
if (shouldRender) {
|
|
503
|
+
const labelColor = position === 'inside' && config.color === undefined
|
|
504
|
+
? getContrastTextColor(barColor)
|
|
505
|
+
: defaultLabelColor;
|
|
500
506
|
const group = labelGroup.append('g');
|
|
501
507
|
if (position === 'outside') {
|
|
502
508
|
// Draw rounded rectangle background
|
|
@@ -522,7 +528,7 @@ export class Bar {
|
|
|
522
528
|
.style('font-size', `${fontSize}px`)
|
|
523
529
|
.style('font-family', fontFamily)
|
|
524
530
|
.style('font-weight', fontWeight)
|
|
525
|
-
.style('fill',
|
|
531
|
+
.style('fill', labelColor)
|
|
526
532
|
.style('pointer-events', 'none')
|
|
527
533
|
.text(valueText);
|
|
528
534
|
}
|
|
@@ -574,7 +580,7 @@ export class Bar {
|
|
|
574
580
|
const fontSize = config.fontSize ?? theme.valueLabel.fontSize;
|
|
575
581
|
const fontFamily = config.fontFamily ?? theme.valueLabel.fontFamily;
|
|
576
582
|
const fontWeight = config.fontWeight ?? theme.valueLabel.fontWeight;
|
|
577
|
-
const
|
|
583
|
+
const defaultLabelColor = config.color ?? theme.valueLabel.color;
|
|
578
584
|
const background = config.background ?? theme.valueLabel.background;
|
|
579
585
|
const border = config.border ?? theme.valueLabel.border;
|
|
580
586
|
const borderRadius = config.borderRadius ?? theme.valueLabel.borderRadius;
|
|
@@ -582,11 +588,14 @@ export class Bar {
|
|
|
582
588
|
const labelGroup = plotGroup
|
|
583
589
|
.append('g')
|
|
584
590
|
.attr('class', `bar-value-labels-${sanitizeForCSS(this.dataKey)}`);
|
|
585
|
-
data.forEach((d) => {
|
|
591
|
+
data.forEach((d, i) => {
|
|
586
592
|
const categoryKey = String(d[xKey]);
|
|
587
593
|
const value = parseValue(d[this.dataKey]);
|
|
588
594
|
const valueText = String(value);
|
|
589
595
|
const yPos = this.getScaledPosition(d, xKey, y, yScaleType);
|
|
596
|
+
const barColor = this.colorAdapter
|
|
597
|
+
? this.colorAdapter(d, i)
|
|
598
|
+
: this.fill;
|
|
590
599
|
// Calculate bar position based on stacking mode
|
|
591
600
|
let barLeft;
|
|
592
601
|
let barRight;
|
|
@@ -711,6 +720,9 @@ export class Bar {
|
|
|
711
720
|
}
|
|
712
721
|
tempText.remove();
|
|
713
722
|
if (shouldRender) {
|
|
723
|
+
const labelColor = position === 'inside' && config.color === undefined
|
|
724
|
+
? getContrastTextColor(barColor)
|
|
725
|
+
: defaultLabelColor;
|
|
714
726
|
const group = labelGroup.append('g');
|
|
715
727
|
if (position === 'outside') {
|
|
716
728
|
// Draw rounded rectangle background
|
|
@@ -736,7 +748,7 @@ export class Bar {
|
|
|
736
748
|
.style('font-size', `${fontSize}px`)
|
|
737
749
|
.style('font-family', fontFamily)
|
|
738
750
|
.style('font-weight', fontWeight)
|
|
739
|
-
.style('fill',
|
|
751
|
+
.style('fill', labelColor)
|
|
740
752
|
.style('pointer-events', 'none')
|
|
741
753
|
.text(valueText);
|
|
742
754
|
}
|
package/legend.d.ts
CHANGED
|
@@ -16,7 +16,6 @@ export declare class Legend implements LayoutAwareComponent<LegendConfigBase> {
|
|
|
16
16
|
setToggleCallback(callback: () => void): void;
|
|
17
17
|
isSeriesVisible(dataKey: string): boolean;
|
|
18
18
|
private getCheckmarkPath;
|
|
19
|
-
private parseColor;
|
|
20
19
|
/**
|
|
21
20
|
* Returns the space required by the legend
|
|
22
21
|
*/
|
package/legend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSeriesColor } from './types.js';
|
|
2
|
-
import { mergeDeep } from './utils.js';
|
|
2
|
+
import { getContrastTextColor, mergeDeep } from './utils.js';
|
|
3
3
|
export class Legend {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
Object.defineProperty(this, "type", {
|
|
@@ -83,29 +83,6 @@ export class Legend {
|
|
|
83
83
|
const offsetY = size * 0.15;
|
|
84
84
|
return `M ${4 * scale + offsetX} ${12 * scale + offsetY} L ${9 * scale + offsetX} ${17 * scale + offsetY} L ${20 * scale + offsetX} ${6 * scale + offsetY}`;
|
|
85
85
|
}
|
|
86
|
-
parseColor(color) {
|
|
87
|
-
// Handle hex colors
|
|
88
|
-
if (color.startsWith('#')) {
|
|
89
|
-
const hex = color.slice(1);
|
|
90
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
91
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
92
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
93
|
-
return { r, g, b };
|
|
94
|
-
}
|
|
95
|
-
// Handle rgb/rgba colors
|
|
96
|
-
if (color.startsWith('rgb')) {
|
|
97
|
-
const match = color.match(/\d+/g);
|
|
98
|
-
if (match) {
|
|
99
|
-
return {
|
|
100
|
-
r: parseInt(match[0]),
|
|
101
|
-
g: parseInt(match[1]),
|
|
102
|
-
b: parseInt(match[2]),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Default to black if we can't parse
|
|
107
|
-
return { r: 0, g: 0, b: 0 };
|
|
108
|
-
}
|
|
109
86
|
/**
|
|
110
87
|
* Returns the space required by the legend
|
|
111
88
|
*/
|
|
@@ -185,13 +162,7 @@ export class Legend {
|
|
|
185
162
|
.append('path')
|
|
186
163
|
.attr('d', this.getCheckmarkPath(boxSize))
|
|
187
164
|
.attr('fill', 'none')
|
|
188
|
-
.attr('stroke', (d) =>
|
|
189
|
-
// Calculate luminance to determine if we need black or white checkmark
|
|
190
|
-
const color = d.color;
|
|
191
|
-
const rgb = this.parseColor(color);
|
|
192
|
-
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
193
|
-
return luminance > 0.5 ? '#000' : '#fff';
|
|
194
|
-
})
|
|
165
|
+
.attr('stroke', (d) => getContrastTextColor(d.color))
|
|
195
166
|
.attr('stroke-width', 2)
|
|
196
167
|
.attr('stroke-linecap', 'round')
|
|
197
168
|
.attr('stroke-linejoin', 'round')
|
package/package.json
CHANGED
package/utils.d.ts
CHANGED
|
@@ -60,4 +60,5 @@ export declare function breakWord(word: string, maxWidth: number, fontSize: stri
|
|
|
60
60
|
* @returns Array of lines
|
|
61
61
|
*/
|
|
62
62
|
export declare function wrapText(text: string, maxWidth: number, fontSize: string | number, fontFamily: string, fontWeight: string, svg: SVGSVGElement): string[];
|
|
63
|
+
export declare function getContrastTextColor(backgroundColor: string, lightTextColor?: string, darkTextColor?: string): string;
|
|
63
64
|
export declare function mergeDeep<T extends Record<string, unknown>>(base: T, override?: Partial<T>): T;
|
package/utils.js
CHANGED
|
@@ -168,6 +168,44 @@ export function wrapText(text, maxWidth, fontSize, fontFamily, fontWeight, svg)
|
|
|
168
168
|
}
|
|
169
169
|
return lines.length > 0 ? lines : [text];
|
|
170
170
|
}
|
|
171
|
+
function parseColorToRGB(color) {
|
|
172
|
+
const normalizedColor = color.trim();
|
|
173
|
+
if (normalizedColor.startsWith('#')) {
|
|
174
|
+
const hex = normalizedColor.slice(1);
|
|
175
|
+
if (hex.length === 3) {
|
|
176
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
177
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
178
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
179
|
+
return { r, g, b };
|
|
180
|
+
}
|
|
181
|
+
if (hex.length === 6) {
|
|
182
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
183
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
184
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
185
|
+
return { r, g, b };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (normalizedColor.startsWith('rgb')) {
|
|
189
|
+
const matches = normalizedColor.match(/\d+/g);
|
|
190
|
+
if (matches && matches.length >= 3) {
|
|
191
|
+
return {
|
|
192
|
+
r: parseInt(matches[0], 10),
|
|
193
|
+
g: parseInt(matches[1], 10),
|
|
194
|
+
b: parseInt(matches[2], 10),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Fallback matches legend behavior: unknown colors are treated as dark.
|
|
199
|
+
return { r: 0, g: 0, b: 0 };
|
|
200
|
+
}
|
|
201
|
+
export function getContrastTextColor(backgroundColor, lightTextColor = '#fff', darkTextColor = '#000') {
|
|
202
|
+
const { r, g, b } = parseColorToRGB(backgroundColor);
|
|
203
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
204
|
+
if (luminance > 0.5) {
|
|
205
|
+
return darkTextColor;
|
|
206
|
+
}
|
|
207
|
+
return lightTextColor;
|
|
208
|
+
}
|
|
171
209
|
export function mergeDeep(base, override) {
|
|
172
210
|
if (!override) {
|
|
173
211
|
return { ...base };
|