@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 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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.2",
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');