@internetstiftelsen/charts 0.3.1 → 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 CHANGED
@@ -21,6 +21,7 @@ export declare abstract class BaseChart {
21
21
  protected readonly theme: ChartTheme;
22
22
  protected readonly scaleConfig: AxisScaleConfig;
23
23
  protected width: number;
24
+ protected height: number;
24
25
  protected xAxis: XAxis | null;
25
26
  protected yAxis: YAxis | null;
26
27
  protected grid: Grid | null;
@@ -53,6 +54,7 @@ export declare abstract class BaseChart {
53
54
  * Override in subclasses to provide chart-specific components
54
55
  */
55
56
  protected getLayoutComponents(): LayoutAwareComponent[];
57
+ protected prepareLayout(): void;
56
58
  /**
57
59
  * Setup ResizeObserver for automatic resize handling
58
60
  */
package/base-chart.js CHANGED
@@ -31,6 +31,12 @@ export class BaseChart {
31
31
  writable: true,
32
32
  value: void 0
33
33
  }); // Current rendering width
34
+ Object.defineProperty(this, "height", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ }); // Current rendering height
34
40
  Object.defineProperty(this, "xAxis", {
35
41
  enumerable: true,
36
42
  configurable: true,
@@ -120,6 +126,7 @@ export class BaseChart {
120
126
  this.data = config.data;
121
127
  this.theme = { ...defaultTheme, ...config.theme };
122
128
  this.width = this.theme.width;
129
+ this.height = this.theme.height;
123
130
  this.scaleConfig = config.scales || {};
124
131
  this.layoutManager = new LayoutManager(this.theme);
125
132
  }
@@ -148,15 +155,22 @@ export class BaseChart {
148
155
  // Calculate current width
149
156
  this.width =
150
157
  this.container.getBoundingClientRect().width || this.theme.width;
158
+ this.height =
159
+ this.container.getBoundingClientRect().height || this.theme.height;
151
160
  // Clear and setup SVG
152
161
  this.container.innerHTML = '';
153
162
  this.svg = create('svg')
154
163
  .attr('width', '100%')
155
- .attr('height', this.theme.height)
164
+ .attr('height', this.height)
156
165
  .style('display', 'block');
157
166
  this.container.appendChild(this.svg.node());
167
+ this.prepareLayout();
158
168
  // Calculate layout
159
- const layoutTheme = { ...this.theme, width: this.width };
169
+ const layoutTheme = {
170
+ ...this.theme,
171
+ width: this.width,
172
+ height: this.height,
173
+ };
160
174
  this.layoutManager = new LayoutManager(layoutTheme);
161
175
  const components = this.getLayoutComponents();
162
176
  this.plotArea = this.layoutManager.calculateLayout(components);
@@ -181,6 +195,9 @@ export class BaseChart {
181
195
  components.push(this.legend);
182
196
  return components;
183
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() { }
184
201
  /**
185
202
  * Setup ResizeObserver for automatic resize handling
186
203
  */
@@ -267,7 +284,7 @@ export class BaseChart {
267
284
  const clone = this.svg.node().cloneNode(true);
268
285
  clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
269
286
  clone.setAttribute('width', String(this.width));
270
- clone.setAttribute('height', String(this.theme.height));
287
+ clone.setAttribute('height', String(this.height));
271
288
  return clone.outerHTML;
272
289
  }
273
290
  exportJSON() {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.1",
2
+ "version": "0.3.3",
3
3
  "name": "@internetstiftelsen/charts",
4
4
  "type": "module",
5
5
  "sideEffects": false,
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 readonly fontSize;
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');