@internetstiftelsen/charts 0.3.2 → 0.3.3
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/base-chart.d.ts +1 -0
- package/base-chart.js +4 -0
- package/package.json +1 -1
- package/x-axis.d.ts +4 -1
- package/x-axis.js +62 -0
- package/xy-chart.d.ts +1 -0
- package/xy-chart.js +8 -0
package/base-chart.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export declare abstract class BaseChart {
|
|
|
54
54
|
* Override in subclasses to provide chart-specific components
|
|
55
55
|
*/
|
|
56
56
|
protected getLayoutComponents(): LayoutAwareComponent[];
|
|
57
|
+
protected prepareLayout(): void;
|
|
57
58
|
/**
|
|
58
59
|
* Setup ResizeObserver for automatic resize handling
|
|
59
60
|
*/
|
package/base-chart.js
CHANGED
|
@@ -164,6 +164,7 @@ export class BaseChart {
|
|
|
164
164
|
.attr('height', this.height)
|
|
165
165
|
.style('display', 'block');
|
|
166
166
|
this.container.appendChild(this.svg.node());
|
|
167
|
+
this.prepareLayout();
|
|
167
168
|
// Calculate layout
|
|
168
169
|
const layoutTheme = {
|
|
169
170
|
...this.theme,
|
|
@@ -194,6 +195,9 @@ export class BaseChart {
|
|
|
194
195
|
components.push(this.legend);
|
|
195
196
|
return components;
|
|
196
197
|
}
|
|
198
|
+
// Hook for subclasses to update component layout estimates before layout calc
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
200
|
+
prepareLayout() { }
|
|
197
201
|
/**
|
|
198
202
|
* Setup ResizeObserver for automatic resize handling
|
|
199
203
|
*/
|
package/package.json
CHANGED
package/x-axis.d.ts
CHANGED
|
@@ -6,11 +6,12 @@ export declare class XAxis implements LayoutAwareComponent {
|
|
|
6
6
|
readonly dataKey?: string;
|
|
7
7
|
private readonly rotatedLabels;
|
|
8
8
|
private readonly tickPadding;
|
|
9
|
-
private
|
|
9
|
+
private fontSize;
|
|
10
10
|
private readonly maxLabelWidth?;
|
|
11
11
|
private readonly oversizedBehavior;
|
|
12
12
|
private readonly tickFormat;
|
|
13
13
|
private wrapLineCount;
|
|
14
|
+
private estimatedHeight;
|
|
14
15
|
private readonly autoHideOverlapping;
|
|
15
16
|
private readonly minLabelGap;
|
|
16
17
|
private readonly preserveEndLabels;
|
|
@@ -19,6 +20,8 @@ export declare class XAxis implements LayoutAwareComponent {
|
|
|
19
20
|
* Returns the space required by the x-axis
|
|
20
21
|
*/
|
|
21
22
|
getRequiredSpace(): ComponentSpace;
|
|
23
|
+
estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
|
|
24
|
+
clearEstimatedSpace(): void;
|
|
22
25
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number): void;
|
|
23
26
|
private applyLabelConstraints;
|
|
24
27
|
private wrapTextElement;
|
package/x-axis.js
CHANGED
|
@@ -57,6 +57,12 @@ export class XAxis {
|
|
|
57
57
|
writable: true,
|
|
58
58
|
value: 1
|
|
59
59
|
});
|
|
60
|
+
Object.defineProperty(this, "estimatedHeight", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: null
|
|
65
|
+
});
|
|
60
66
|
Object.defineProperty(this, "autoHideOverlapping", {
|
|
61
67
|
enumerable: true,
|
|
62
68
|
configurable: true,
|
|
@@ -88,6 +94,13 @@ export class XAxis {
|
|
|
88
94
|
* Returns the space required by the x-axis
|
|
89
95
|
*/
|
|
90
96
|
getRequiredSpace() {
|
|
97
|
+
if (this.estimatedHeight !== null) {
|
|
98
|
+
return {
|
|
99
|
+
width: 0,
|
|
100
|
+
height: this.estimatedHeight,
|
|
101
|
+
position: 'bottom',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
91
104
|
// Height = tick padding + font size + some extra space for descenders
|
|
92
105
|
// Rotated labels need more vertical space (roughly 2.5x for -45deg rotation)
|
|
93
106
|
const baseHeight = this.tickPadding + this.fontSize + 5;
|
|
@@ -104,6 +117,55 @@ export class XAxis {
|
|
|
104
117
|
position: 'bottom',
|
|
105
118
|
};
|
|
106
119
|
}
|
|
120
|
+
estimateLayoutSpace(labels, theme, svg) {
|
|
121
|
+
if (!labels.length) {
|
|
122
|
+
this.estimatedHeight = null;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const parsedFontSize = typeof theme.axis.fontSize === 'string'
|
|
126
|
+
? parseFloat(theme.axis.fontSize)
|
|
127
|
+
: theme.axis.fontSize;
|
|
128
|
+
this.fontSize = Number.isFinite(parsedFontSize)
|
|
129
|
+
? parsedFontSize
|
|
130
|
+
: this.fontSize;
|
|
131
|
+
const fontSize = this.fontSize;
|
|
132
|
+
const fontFamily = theme.axis.fontFamily;
|
|
133
|
+
const fontWeight = theme.axis.fontWeight || 'normal';
|
|
134
|
+
let maxWidth = 0;
|
|
135
|
+
let maxLines = 1;
|
|
136
|
+
for (const label of labels) {
|
|
137
|
+
const text = String(label ?? '');
|
|
138
|
+
if (!text)
|
|
139
|
+
continue;
|
|
140
|
+
const textWidth = measureTextWidth(text, fontSize, fontFamily, fontWeight, svg);
|
|
141
|
+
if (this.maxLabelWidth && this.oversizedBehavior === 'wrap') {
|
|
142
|
+
const lines = wrapText(text, this.maxLabelWidth, fontSize, fontFamily, fontWeight, svg);
|
|
143
|
+
maxLines = Math.max(maxLines, lines.length || 1);
|
|
144
|
+
maxWidth = Math.max(maxWidth, this.maxLabelWidth);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const effectiveWidth = this.maxLabelWidth
|
|
148
|
+
? Math.min(textWidth, this.maxLabelWidth)
|
|
149
|
+
: textWidth;
|
|
150
|
+
maxWidth = Math.max(maxWidth, effectiveWidth);
|
|
151
|
+
}
|
|
152
|
+
const lineHeight = fontSize * 1.2;
|
|
153
|
+
const textHeight = lineHeight * maxLines;
|
|
154
|
+
if (this.rotatedLabels) {
|
|
155
|
+
const radians = Math.PI / 4;
|
|
156
|
+
const verticalFootprint = Math.sin(radians) * maxWidth +
|
|
157
|
+
Math.cos(radians) * textHeight;
|
|
158
|
+
this.estimatedHeight =
|
|
159
|
+
this.tickPadding + verticalFootprint + 5;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.estimatedHeight = this.tickPadding + textHeight + 5;
|
|
163
|
+
}
|
|
164
|
+
this.wrapLineCount = Math.max(this.wrapLineCount, maxLines);
|
|
165
|
+
}
|
|
166
|
+
clearEstimatedSpace() {
|
|
167
|
+
this.estimatedHeight = null;
|
|
168
|
+
}
|
|
107
169
|
render(svg, x, theme, yPosition) {
|
|
108
170
|
const axisGenerator = axisBottom(x)
|
|
109
171
|
.tickSizeOuter(0)
|
package/xy-chart.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class XYChart extends BaseChart {
|
|
|
11
11
|
constructor(config: XYChartConfig);
|
|
12
12
|
addChild(component: ChartComponent): this;
|
|
13
13
|
private rerender;
|
|
14
|
+
protected prepareLayout(): void;
|
|
14
15
|
protected renderChart(): void;
|
|
15
16
|
private getXKey;
|
|
16
17
|
private setupScales;
|
package/xy-chart.js
CHANGED
|
@@ -71,6 +71,14 @@ export class XYChart extends BaseChart {
|
|
|
71
71
|
rerender() {
|
|
72
72
|
this.update(this.data);
|
|
73
73
|
}
|
|
74
|
+
prepareLayout() {
|
|
75
|
+
this.xAxis?.clearEstimatedSpace?.();
|
|
76
|
+
if (!this.xAxis || !this.svg)
|
|
77
|
+
return;
|
|
78
|
+
const xKey = this.getXKey();
|
|
79
|
+
const labels = this.data.map((item) => item[xKey]);
|
|
80
|
+
this.xAxis.estimateLayoutSpace?.(labels, this.theme, this.svg.node());
|
|
81
|
+
}
|
|
74
82
|
renderChart() {
|
|
75
83
|
if (!this.plotArea) {
|
|
76
84
|
throw new Error('Plot area not calculated');
|