@internetstiftelsen/charts 0.1.0 → 0.1.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/line.js CHANGED
@@ -63,7 +63,13 @@ export class Line {
63
63
  // Handle band scales with bandwidth
64
64
  return (scaled || 0) + (x.bandwidth ? x.bandwidth() / 2 : 0);
65
65
  };
66
+ // Helper to check if a data point has a valid (non-null) value
67
+ const hasValidValue = (d) => {
68
+ const value = d[this.dataKey];
69
+ return value !== null && value !== undefined;
70
+ };
66
71
  const lineGenerator = line()
72
+ .defined(hasValidValue)
67
73
  .x(getXPosition)
68
74
  .y((d) => y(parseValue(d[this.dataKey])) || 0);
69
75
  const lineStrokeWidth = this.strokeWidth ?? theme.line.strokeWidth;
@@ -79,11 +85,12 @@ export class Line {
79
85
  .attr('stroke', this.stroke)
80
86
  .attr('stroke-width', lineStrokeWidth)
81
87
  .attr('d', lineGenerator);
82
- // Add data point circles
88
+ // Add data point circles (only for valid values)
89
+ const validData = data.filter(hasValidValue);
83
90
  const sanitizedKey = sanitizeForCSS(this.dataKey);
84
91
  plotGroup
85
92
  .selectAll(`.circle-${sanitizedKey}`)
86
- .data(data)
93
+ .data(validData)
87
94
  .join('circle')
88
95
  .attr('class', `circle-${sanitizedKey}`)
89
96
  .attr('cx', getXPosition)
@@ -92,9 +99,9 @@ export class Line {
92
99
  .attr('fill', pointColor)
93
100
  .attr('stroke', pointStrokeColor)
94
101
  .attr('stroke-width', pointStrokeWidth);
95
- // Render value labels if enabled
102
+ // Render value labels if enabled (only for valid values)
96
103
  if (this.valueLabel?.show) {
97
- this.renderValueLabels(plotGroup, data, y, parseValue, theme, getXPosition);
104
+ this.renderValueLabels(plotGroup, validData, y, parseValue, theme, getXPosition);
98
105
  }
99
106
  }
100
107
  renderValueLabels(plotGroup, data, y, parseValue, theme, getXPosition) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0",
2
+ "version": "0.1.1",
3
3
  "name": "@internetstiftelsen/charts",
4
4
  "type": "module",
5
5
  "sideEffects": false,
package/types.d.ts CHANGED
@@ -97,6 +97,7 @@ export type XAxisConfig = {
97
97
  rotatedLabels?: boolean;
98
98
  maxLabelWidth?: number;
99
99
  oversizedBehavior?: LabelOversizedBehavior;
100
+ tickFormat?: string | ((value: number) => string) | null;
100
101
  };
101
102
  export type YAxisConfig = {
102
103
  tickFormat?: string | ((value: number) => string) | null;
package/x-axis.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare class XAxis implements LayoutAwareComponent {
9
9
  private readonly fontSize;
10
10
  private readonly maxLabelWidth?;
11
11
  private readonly oversizedBehavior;
12
+ private readonly tickFormat;
12
13
  private wrapLineCount;
13
14
  constructor(config?: XAxisConfig);
14
15
  /**
package/x-axis.js CHANGED
@@ -44,6 +44,13 @@ export class XAxis {
44
44
  writable: true,
45
45
  value: void 0
46
46
  });
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ Object.defineProperty(this, "tickFormat", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: void 0
53
+ });
47
54
  Object.defineProperty(this, "wrapLineCount", {
48
55
  enumerable: true,
49
56
  configurable: true,
@@ -54,6 +61,7 @@ export class XAxis {
54
61
  this.rotatedLabels = config?.rotatedLabels ?? false;
55
62
  this.maxLabelWidth = config?.maxLabelWidth;
56
63
  this.oversizedBehavior = config?.oversizedBehavior ?? 'truncate';
64
+ this.tickFormat = config?.tickFormat ?? null;
57
65
  }
58
66
  /**
59
67
  * Returns the space required by the x-axis
@@ -74,14 +82,24 @@ export class XAxis {
74
82
  };
75
83
  }
76
84
  render(svg, x, theme, yPosition) {
85
+ const axisGenerator = axisBottom(x)
86
+ .tickSizeOuter(0)
87
+ .tickSize(0)
88
+ .tickPadding(this.tickPadding);
89
+ // Apply tick formatting if specified
90
+ if (this.tickFormat) {
91
+ if (typeof this.tickFormat === 'function') {
92
+ axisGenerator.tickFormat(this.tickFormat);
93
+ }
94
+ else {
95
+ axisGenerator.ticks(5, this.tickFormat);
96
+ }
97
+ }
77
98
  const axis = svg
78
99
  .append('g')
79
100
  .attr('class', 'x-axis')
80
101
  .attr('transform', `translate(0,${yPosition})`)
81
- .call(axisBottom(x)
82
- .tickSizeOuter(0)
83
- .tickSize(0)
84
- .tickPadding(this.tickPadding))
102
+ .call(axisGenerator)
85
103
  .attr('font-size', theme.axis.fontSize)
86
104
  .attr('font-family', theme.axis.fontFamily)
87
105
  .attr('font-weight', theme.axis.fontWeight || 'normal')
package/xy-chart.d.ts CHANGED
@@ -6,8 +6,6 @@ export type XYChartConfig = BaseChartConfig & {
6
6
  };
7
7
  export declare class XYChart extends BaseChart {
8
8
  private readonly series;
9
- private sortedDataCache;
10
- private xKeyCache;
11
9
  private barStackMode;
12
10
  private barStackGap;
13
11
  constructor(config: XYChartConfig);
@@ -15,7 +13,6 @@ export declare class XYChart extends BaseChart {
15
13
  private rerender;
16
14
  protected renderChart(): void;
17
15
  private getXKey;
18
- private getSortedData;
19
16
  private setupScales;
20
17
  private isHorizontalOrientation;
21
18
  private createScale;
package/xy-chart.js CHANGED
@@ -11,18 +11,6 @@ export class XYChart extends BaseChart {
11
11
  writable: true,
12
12
  value: []
13
13
  });
14
- Object.defineProperty(this, "sortedDataCache", {
15
- enumerable: true,
16
- configurable: true,
17
- writable: true,
18
- value: null
19
- });
20
- Object.defineProperty(this, "xKeyCache", {
21
- enumerable: true,
22
- configurable: true,
23
- writable: true,
24
- value: null
25
- });
26
14
  Object.defineProperty(this, "barStackMode", {
27
15
  enumerable: true,
28
16
  configurable: true,
@@ -97,9 +85,7 @@ export class XYChart extends BaseChart {
97
85
  if (this.xAxis?.dataKey) {
98
86
  ChartValidator.validateDataKey(this.data, this.xAxis.dataKey, 'XAxis');
99
87
  }
100
- // Cache sorted data
101
88
  const xKey = this.getXKey();
102
- const sortedData = this.getSortedData(xKey);
103
89
  this.setupScales();
104
90
  // Render title if present
105
91
  if (this.title) {
@@ -124,7 +110,7 @@ export class XYChart extends BaseChart {
124
110
  // Render tooltip
125
111
  if (this.tooltip && this.x && this.y) {
126
112
  this.tooltip.initialize(this.theme);
127
- this.tooltip.attachToArea(this.svg, sortedData, this.series, xKey, this.x, this.y, this.theme, this.plotArea, this.parseValue.bind(this), this.isHorizontalOrientation());
113
+ this.tooltip.attachToArea(this.svg, this.data, this.series, xKey, this.x, this.y, this.theme, this.plotArea, this.parseValue.bind(this), this.isHorizontalOrientation());
128
114
  }
129
115
  // Render legend if present
130
116
  if (this.legend) {
@@ -138,16 +124,6 @@ export class XYChart extends BaseChart {
138
124
  }
139
125
  return (Object.keys(this.data[0]).find((key) => !this.series.some((s) => s.dataKey === key)) || 'column');
140
126
  }
141
- getSortedData(xKey) {
142
- // Return cached data if xKey matches
143
- if (this.sortedDataCache && this.xKeyCache === xKey) {
144
- return this.sortedDataCache;
145
- }
146
- // Sort and cache
147
- this.xKeyCache = xKey;
148
- this.sortedDataCache = [...this.data].sort((a, b) => String(a[xKey]).localeCompare(String(b[xKey])));
149
- return this.sortedDataCache;
150
- }
151
127
  setupScales() {
152
128
  const xKey = this.getXKey();
153
129
  const isHorizontal = this.isHorizontalOrientation();
@@ -285,7 +261,6 @@ export class XYChart extends BaseChart {
285
261
  if (!this.plotGroup || !this.x || !this.y)
286
262
  return;
287
263
  const xKey = this.getXKey();
288
- const sortedData = this.getSortedData(xKey);
289
264
  const isHorizontal = this.isHorizontalOrientation();
290
265
  // For horizontal bars, the category scale is on Y (user's X config becomes Y)
291
266
  // For vertical bars, the category scale is on X (user's X config stays X)
@@ -299,7 +274,7 @@ export class XYChart extends BaseChart {
299
274
  // Get only bar series for stacking calculations
300
275
  const barSeries = visibleSeries.filter((s) => s.type === 'bar');
301
276
  // Compute stacking data for bar charts
302
- const { cumulativeDataBySeriesIndex, totalData } = this.computeStackingData(sortedData, xKey, barSeries);
277
+ const { cumulativeDataBySeriesIndex, totalData } = this.computeStackingData(this.data, xKey, barSeries);
303
278
  visibleSeries.forEach((series) => {
304
279
  if (series.type === 'bar') {
305
280
  const barIndex = barSeries.indexOf(series);
@@ -311,10 +286,10 @@ export class XYChart extends BaseChart {
311
286
  totalData,
312
287
  gap: this.barStackGap,
313
288
  };
314
- series.render(this.plotGroup, sortedData, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme, stackingContext);
289
+ series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme, stackingContext);
315
290
  }
316
291
  else {
317
- series.render(this.plotGroup, sortedData, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme);
292
+ series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme);
318
293
  }
319
294
  });
320
295
  }