abstract-chart 5.0.1 → 7.0.0

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/src/chart.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as AbstractImage from "abstract-image";
1
+ import * as AI from "abstract-image";
2
2
  import * as Axis from "./axis";
3
3
  import { exhaustiveCheck } from "ts-exhaustive-check";
4
4
 
@@ -18,53 +18,47 @@ export interface Chart {
18
18
  readonly xAxisTop: Axis.Axis | undefined;
19
19
  readonly yAxisLeft: Axis.Axis | undefined;
20
20
  readonly yAxisRight: Axis.Axis | undefined;
21
- readonly backgroundColor: AbstractImage.Color;
22
- readonly gridColor: AbstractImage.Color;
23
- readonly gridThickness: number;
21
+ readonly backgroundColor: AI.Color;
22
+ readonly xGrid: ChartGrid;
23
+ readonly yGrid: ChartGrid;
24
24
  readonly font: string;
25
25
  readonly fontSize: number;
26
26
  readonly labelLayout: LabelLayout;
27
+ readonly padding: Padding;
27
28
  }
28
29
 
30
+ export type ChartGrid = { readonly color: AI.Color; readonly thickness: number };
31
+
29
32
  export type ChartProps = Partial<Chart>;
30
33
 
31
34
  export function createChart(props: ChartProps): Chart {
32
- const {
33
- width = 600,
34
- height = 400,
35
- chartPoints = [],
36
- chartLines = [],
37
- chartStack = createChartStack({}),
38
- xAxisBottom = Axis.createLinearAxis(0, 100, ""),
39
- xAxisTop = undefined,
40
- yAxisLeft = Axis.createLinearAxis(0, 100, ""),
41
- yAxisRight = undefined,
42
- backgroundColor = AbstractImage.white,
43
- gridColor = AbstractImage.gray,
44
- gridThickness = 1,
45
- font = "Arial",
46
- fontSize = 12,
47
- labelLayout = "original",
48
- } = props || {};
49
35
  return {
50
- width,
51
- height,
52
- chartPoints,
53
- chartLines,
54
- chartStack,
55
- xAxisBottom,
56
- xAxisTop,
57
- yAxisLeft,
58
- yAxisRight,
59
- backgroundColor,
60
- gridColor,
61
- gridThickness,
62
- font,
63
- fontSize,
64
- labelLayout,
36
+ width: props.width ?? 600,
37
+ height: props.height ?? 600,
38
+ chartPoints: props.chartPoints ?? [],
39
+ chartLines: props.chartLines ?? [],
40
+ chartStack: props.chartStack ?? createChartStack({}),
41
+ xAxisBottom: props.xAxisBottom ?? Axis.createLinearAxis(0, 100, ""),
42
+ xAxisTop: props.xAxisTop ?? undefined,
43
+ yAxisLeft: props.yAxisLeft ?? Axis.createLinearAxis(0, 100, ""),
44
+ yAxisRight: props.yAxisRight ?? undefined,
45
+ backgroundColor: props.backgroundColor ?? AI.white,
46
+ font: props.font ?? "Arial",
47
+ fontSize: props.fontSize ?? 12,
48
+ labelLayout: props.labelLayout ?? "original",
49
+ padding: {
50
+ top: props.padding?.top ?? (props.xAxisTop !== undefined ? 45 : 10),
51
+ right: props.padding?.right ?? (props.yAxisRight !== undefined ? 45 : 10),
52
+ bottom: props.padding?.bottom ?? (props.xAxisBottom === undefined ? 10 : 45),
53
+ left: props.padding?.left ?? (!props.yAxisLeft === undefined ? 10 : 45),
54
+ },
55
+ xGrid: { color: props.xGrid?.color ?? AI.gray, thickness: props.xGrid?.thickness ?? 1 },
56
+ yGrid: { color: props.yGrid?.color ?? AI.gray, thickness: props.yGrid?.thickness ?? 1 },
65
57
  };
66
58
  }
67
59
 
60
+ type Padding = { readonly top: number; readonly right: number; readonly bottom: number; readonly left: number };
61
+
68
62
  export type XAxis = "bottom" | "top";
69
63
  export type YAxis = "left" | "right";
70
64
 
@@ -72,12 +66,13 @@ export type ChartPointShape = "circle" | "triangle" | "square";
72
66
 
73
67
  export interface ChartPoint {
74
68
  readonly shape: ChartPointShape;
75
- readonly position: AbstractImage.Point;
76
- readonly color: AbstractImage.Color;
77
- readonly size: AbstractImage.Size;
69
+ readonly position: AI.Point;
70
+ readonly color: AI.Color;
71
+ readonly size: AI.Size;
78
72
  readonly label: string;
79
73
  readonly xAxis: XAxis;
80
74
  readonly yAxis: YAxis;
75
+ readonly fontSize?: number;
81
76
  }
82
77
 
83
78
  export type ChartPointProps = Partial<ChartPoint>;
@@ -85,67 +80,43 @@ export type ChartPointProps = Partial<ChartPoint>;
85
80
  export function createChartPoint(props?: ChartPointProps): ChartPoint {
86
81
  const {
87
82
  shape = "circle",
88
- position = AbstractImage.createPoint(0, 0),
89
- color = AbstractImage.black,
90
- size = AbstractImage.createSize(6, 6),
83
+ position = AI.createPoint(0, 0),
84
+ color = AI.black,
85
+ size = AI.createSize(6, 6),
91
86
  label = "",
92
87
  xAxis = "bottom",
93
88
  yAxis = "left",
94
89
  } = props || {};
95
- return {
96
- shape,
97
- position,
98
- color,
99
- size,
100
- label,
101
- xAxis,
102
- yAxis,
103
- };
90
+ return { shape, position, color, size, label, xAxis, yAxis };
104
91
  }
105
92
 
106
93
  export interface ChartLine {
107
- readonly points: Array<AbstractImage.Point>;
108
- readonly color: AbstractImage.Color;
94
+ readonly points: Array<AI.Point>;
95
+ readonly color: AI.Color;
109
96
  readonly thickness: number;
110
97
  readonly label: string;
111
98
  readonly xAxis: XAxis;
112
99
  readonly yAxis: YAxis;
100
+ readonly fontSize?: number;
113
101
  }
114
102
 
115
103
  export type ChartLineProps = Partial<ChartLine>;
116
104
 
117
105
  export function createChartLine(props: ChartLineProps): ChartLine {
118
- const {
119
- points = [],
120
- color = AbstractImage.black,
121
- thickness = 1,
122
- label = "",
123
- xAxis = "bottom",
124
- yAxis = "left",
125
- } = props || {};
126
- return {
127
- points,
128
- color,
129
- thickness,
130
- label,
131
- xAxis,
132
- yAxis,
133
- };
106
+ const { points = [], color = AI.black, thickness = 1, label = "", xAxis = "bottom", yAxis = "left" } = props || {};
107
+ return { points, color, thickness, label, xAxis, yAxis };
134
108
  }
135
109
 
136
110
  export interface ChartStackConfig {
137
- readonly color: AbstractImage.Color;
111
+ readonly color: AI.Color;
138
112
  readonly label: string;
139
113
  }
140
114
 
141
115
  export type ChartStackConfigProps = Partial<ChartStackConfig>;
142
116
 
143
117
  export function createChartStackConfig(props: ChartStackConfigProps): ChartStackConfig {
144
- const { color = AbstractImage.black, label = "" } = props || {};
145
- return {
146
- color,
147
- label,
148
- };
118
+ const { color = AI.black, label = "" } = props || {};
119
+ return { color, label };
149
120
  }
150
121
 
151
122
  export interface StackPoints {
@@ -164,44 +135,38 @@ export type ChartStackProps = Partial<ChartStack>;
164
135
 
165
136
  export function createChartStack(props: ChartStackProps): ChartStack {
166
137
  const { points = [], xAxis = "bottom", yAxis = "left", config = [createChartStackConfig({})] } = props || {};
167
- return {
168
- points,
169
- xAxis,
170
- yAxis,
171
- config,
172
- };
138
+ return { points, xAxis, yAxis, config };
173
139
  }
174
140
 
175
- const padding = 80;
176
-
177
141
  export function inverseTransformPoint(
178
- point: AbstractImage.Point,
142
+ point: AI.Point,
179
143
  chart: Chart,
180
144
  xAxis: XAxis,
181
- yAxis: YAxis
182
- ): AbstractImage.Point | undefined {
183
- const xMin = padding;
184
- const xMax = chart.width - padding;
185
- const yMin = chart.height - 0.5 * padding;
186
- const yMax = 0.5 * padding;
145
+ yAxis: YAxis,
146
+ padding: Padding
147
+ ): AI.Point | undefined {
148
+ const xMin = padding.left;
149
+ const xMax = chart.width - padding.right;
150
+ const yMin = chart.height - padding.bottom;
151
+ const yMax = padding.top;
187
152
  const x = Axis.inverseTransformValue(point.x, xMin, xMax, xAxis === "top" ? chart.xAxisTop : chart.xAxisBottom);
188
153
  const y = Axis.inverseTransformValue(point.y, yMin, yMax, yAxis === "right" ? chart.yAxisRight : chart.yAxisLeft);
189
154
  if (x === undefined || y === undefined) {
190
155
  return undefined;
191
156
  }
192
- return AbstractImage.createPoint(x, y);
157
+ return AI.createPoint(x, y);
193
158
  }
194
159
 
195
- export function renderChart(chart: Chart): AbstractImage.AbstractImage {
196
- const { width, height, xAxisBottom, xAxisTop, yAxisLeft, yAxisRight } = chart;
160
+ export function renderChart(chart: Chart): AI.AbstractImage {
161
+ const { width, height, xAxisBottom, xAxisTop, yAxisLeft, yAxisRight, padding } = chart;
197
162
 
198
- const gridWidth = width - 2 * padding;
199
- const gridHeight = height - padding;
163
+ const gridWidth = width - padding.left - padding.right;
164
+ const gridHeight = height - padding.bottom - padding.top;
200
165
 
201
- const xMin = padding;
202
- const xMax = width - padding;
203
- const yMin = height - 0.5 * padding;
204
- const yMax = 0.5 * padding;
166
+ const xMin = padding.left;
167
+ const xMax = width - padding.right;
168
+ const yMin = height - padding.bottom;
169
+ const yMax = padding.top;
205
170
 
206
171
  const renderedBackground = generateBackground(xMin, xMax, yMin, yMax, chart);
207
172
 
@@ -227,207 +192,204 @@ export function renderChart(chart: Chart): AbstractImage.AbstractImage {
227
192
  renderedLines,
228
193
  renderedPoints,
229
194
  ];
230
- const topLeft = AbstractImage.createPoint(0, 0);
231
- const size = AbstractImage.createSize(width, height);
232
- return AbstractImage.createAbstractImage(topLeft, size, AbstractImage.white, components);
195
+ const topLeft = AI.createPoint(0, 0);
196
+ const size = AI.createSize(width, height);
197
+ return AI.createAbstractImage(topLeft, size, AI.white, components);
233
198
  }
234
199
 
235
- export function generateBackground(
236
- xMin: number,
237
- xMax: number,
238
- yMin: number,
239
- yMax: number,
240
- chart: Chart
241
- ): AbstractImage.Component {
242
- const topLeft = AbstractImage.createPoint(xMin, yMax);
243
- const bottomRight = AbstractImage.createPoint(xMax, yMin);
244
- return AbstractImage.createRectangle(
245
- topLeft,
246
- bottomRight,
247
- chart.gridColor,
248
- chart.gridThickness,
200
+ export function generateBackground(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
201
+ return AI.createRectangle(
202
+ AI.createPoint(xMin, yMax),
203
+ AI.createPoint(xMax, yMin),
204
+ AI.transparent,
205
+ 0,
249
206
  chart.backgroundColor
250
207
  );
251
208
  }
252
209
 
253
210
  export function generateXAxisBottom(
254
211
  xNumTicks: number,
255
- xAxisBottom: Axis.Axis | undefined,
212
+ axis: Axis.Axis | undefined,
256
213
  xMin: number,
257
214
  xMax: number,
258
215
  yMin: number,
259
216
  yMax: number,
260
217
  chart: Chart
261
- ): AbstractImage.Component {
262
- if (!xAxisBottom) {
263
- return AbstractImage.createGroup("XAxisBottom", []);
218
+ ): AI.Component {
219
+ const components = Array<AI.Component>();
220
+ if (!axis) {
221
+ return AI.createGroup("XAxisBottom", components);
222
+ }
223
+ const axisLabelPosY = yMin + chart.padding.bottom - chart.fontSize;
224
+ const xTicks = Axis.getTicks(xNumTicks, axis);
225
+ if (chart.xGrid) {
226
+ components.push(generateXAxisGridLines(xMin, xMax, yMin + 10, yMax, xTicks, axis, chart.xGrid));
264
227
  }
265
- const xTicks = Axis.getTicks(xNumTicks, xAxisBottom);
266
- const xLines = generateXAxisGridLines(xMin, xMax, yMin + 10, yMax, xTicks, xAxisBottom, chart);
267
- const xLabels = generateXAxisLabels(xMin, xMax, yMin + 10, "down", xTicks, xAxisBottom, chart);
228
+ components.push(
229
+ AI.createLine({ x: xMin, y: yMin }, { x: xMax, y: yMin }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
230
+ generateXAxisLabels(xMin, xMax, yMin + (axis.tickLabelDisp ?? 10), "down", xTicks, axis, chart)
231
+ );
268
232
 
269
- let xLabel: AbstractImage.Component;
270
233
  switch (chart.labelLayout) {
271
234
  case "original":
272
- xLabel = generateXAxisLabel(xMax + 0.5 * padding, yMin + 10, "uniform", "down", xAxisBottom.label, chart);
235
+ components.push(
236
+ generateXAxisLabel(
237
+ xMax + chart.padding.right,
238
+ yMin + (axis.tickLabelDisp ?? 10),
239
+ "uniform",
240
+ "down",
241
+ axis,
242
+ chart
243
+ )
244
+ );
273
245
  break;
274
-
275
246
  case "end":
276
- xLabel = generateXAxisLabel(xMax, yMin + 25, "left", "down", xAxisBottom.label, chart);
247
+ components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "down", axis, chart));
277
248
  break;
278
-
279
249
  case "center":
280
- xLabel = generateXAxisLabel((xMin + xMax) / 2, yMin + 25, "uniform", "down", xAxisBottom.label, chart);
250
+ components.push(generateXAxisLabel((xMin + xMax) / 2, axisLabelPosY, "uniform", "down", axis, chart));
281
251
  break;
282
-
283
252
  default:
284
253
  return exhaustiveCheck(chart.labelLayout);
285
254
  }
286
255
 
287
- return AbstractImage.createGroup("XAxisBottom", [xLines, xLabels, xLabel]);
256
+ return AI.createGroup("XAxisBottom", components);
288
257
  }
289
258
 
290
259
  export function generateXAxisTop(
291
260
  xNumTicks: number,
292
- xAxisTop: Axis.Axis | undefined,
261
+ axis: Axis.Axis | undefined,
293
262
  xMin: number,
294
263
  xMax: number,
295
264
  yMax: number,
296
265
  chart: Chart
297
- ): AbstractImage.Component {
298
- if (!xAxisTop) {
299
- return AbstractImage.createGroup("XAxisTop", []);
266
+ ): AI.Component {
267
+ const components = Array<AI.Component>();
268
+ if (!axis) {
269
+ return AI.createGroup("XAxisTop", components);
300
270
  }
301
- const xTicks2 = Axis.getTicks(xNumTicks, xAxisTop);
302
- const xLines2 = generateXAxisGridLines(xMin, xMax, yMax - 10, yMax, xTicks2, xAxisTop, chart);
303
- const xLabels2 = generateXAxisLabels(xMin, xMax, yMax - 13, "up", xTicks2, xAxisTop, chart);
271
+ const axisLabelPosY = yMax - chart.padding.top + chart.fontSize;
272
+ const xTicks = Axis.getTicks(xNumTicks, axis);
273
+ if (chart.xGrid) {
274
+ components.push(generateXAxisGridLines(xMin, xMax, yMax - 10, yMax, xTicks, axis, chart.xGrid));
275
+ }
276
+ components.push(
277
+ AI.createLine({ x: xMin, y: yMax }, { x: xMax, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
278
+ generateXAxisLabels(xMin, xMax, yMax - (axis.tickLabelDisp ?? 13), "up", xTicks, axis, chart)
279
+ );
304
280
 
305
- let xLabel2: AbstractImage.Component;
306
281
  switch (chart.labelLayout) {
307
282
  case "original":
308
- xLabel2 = generateXAxisLabel(xMax + 0.5 * padding, yMax - 13, "uniform", "up", xAxisTop.label, chart);
283
+ components.push(
284
+ generateXAxisLabel(
285
+ xMax + 0.5 * chart.padding.right,
286
+ yMax - (axis.tickLabelDisp ?? 13),
287
+ "uniform",
288
+ "up",
289
+ axis,
290
+ chart
291
+ )
292
+ );
309
293
  break;
310
-
311
294
  case "end":
312
- xLabel2 = generateXAxisLabel(xMax, yMax - 30, "left", "up", xAxisTop.label, chart);
295
+ components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "up", axis, chart));
313
296
  break;
314
-
315
297
  case "center":
316
- xLabel2 = generateXAxisLabel((xMin + xMax) / 2, yMax - 30, "uniform", "up", xAxisTop.label, chart);
298
+ components.push(generateXAxisLabel((xMin + xMax) / 2, axisLabelPosY, "uniform", "up", axis, chart));
317
299
  break;
318
-
319
300
  default:
320
301
  return exhaustiveCheck(chart.labelLayout);
321
302
  }
322
303
 
323
- return AbstractImage.createGroup("XAxisTop", [xLines2, xLabels2, xLabel2]);
304
+ return AI.createGroup("XAxisTop", components);
324
305
  }
325
306
 
326
307
  export function generateYAxisLeft(
327
308
  yNumTicks: number,
328
- yAxisLeft: Axis.Axis | undefined,
309
+ axis: Axis.Axis | undefined,
329
310
  xMin: number,
330
311
  xMax: number,
331
312
  yMin: number,
332
313
  yMax: number,
333
314
  chart: Chart
334
- ): AbstractImage.Component {
335
- if (!yAxisLeft) {
336
- return AbstractImage.createGroup("YAxisLeft", []);
315
+ ): AI.Component {
316
+ const components = Array<AI.Component>();
317
+ if (!axis) {
318
+ return AI.createGroup("YAxisLeft", components);
337
319
  }
338
- const yTicks = Axis.getTicks(yNumTicks, yAxisLeft);
339
- const yLines = generateYAxisLines(xMin - 5, xMax, yMin, yMax, yTicks, yAxisLeft, chart);
340
- const yLabels = generateYAxisLabels(xMin - 7, yMin, yMax, "left", yTicks, yAxisLeft, chart);
320
+ const axisLabelPosX = xMin - chart.padding.left + chart.fontSize;
341
321
 
342
- const labelPaddingLeft = 5 + labelPadding(formatNumber(yAxisLeft.max).length, chart.fontSize, 0.5);
322
+ const yTicks = Axis.getTicks(yNumTicks, axis);
323
+ if (chart.yGrid) {
324
+ components.push(generateYAxisLines(xMin - 5, xMax, yMin, yMax, yTicks, axis, chart.yGrid));
325
+ }
326
+ components.push(
327
+ AI.createLine({ x: xMin, y: yMin }, { x: xMin, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
328
+ generateYAxisLabels(xMin - (axis.tickLabelDisp ?? 7), yMin, yMax, "left", yTicks, axis, chart)
329
+ );
343
330
 
344
- let yLabel: AbstractImage.Component;
345
331
  switch (chart.labelLayout) {
346
332
  case "original":
347
- yLabel = generateYAxisLabel(
348
- xMin - labelPaddingLeft,
349
- yMax + 0.5 * padding,
350
- "uniform",
351
- "up",
352
- yAxisLeft.label,
353
- chart
333
+ components.push(
334
+ generateYAxisLabel(axisLabelPosX, yMax + 0.5 * chart.padding.bottom, "uniform", "up", axis, chart)
354
335
  );
355
336
  break;
356
-
357
337
  case "end":
358
- yLabel = generateYAxisLabel(xMin - labelPaddingLeft, yMax, "left", "up", yAxisLeft.label, chart);
338
+ components.push(generateYAxisLabel(axisLabelPosX, yMax, "left", "up", axis, chart));
359
339
  break;
360
-
361
340
  case "center":
362
- yLabel = generateYAxisLabel(xMin - labelPaddingLeft, (yMin + yMax) / 2, "uniform", "up", yAxisLeft.label, chart);
341
+ components.push(generateYAxisLabel(axisLabelPosX, (yMin + yMax) / 2, "uniform", "up", axis, chart));
363
342
  break;
364
-
365
343
  default:
366
344
  return exhaustiveCheck(chart.labelLayout);
367
345
  }
368
346
 
369
- return AbstractImage.createGroup("YAxisLeft", [yLines, yLabels, yLabel]);
347
+ return AI.createGroup("YAxisLeft", components);
370
348
  }
371
349
 
372
350
  export function generateYAxisRight(
373
351
  yNumTicks: number,
374
- yAxisRight: Axis.Axis | undefined,
352
+ axis: Axis.Axis | undefined,
375
353
  xMax: number,
376
354
  yMin: number,
377
355
  yMax: number,
378
356
  chart: Chart
379
- ): AbstractImage.Component {
380
- if (!yAxisRight) {
381
- return AbstractImage.createGroup("YAxisRight", []);
357
+ ): AI.Component {
358
+ const components = Array<AI.Component>();
359
+ if (!axis) {
360
+ return AI.createGroup("YAxisRight", components);
382
361
  }
383
- const yTicks2 = Axis.getTicks(yNumTicks, yAxisRight);
384
- const yLines2 = generateYAxisLines(xMax - 5, xMax + 5, yMin, yMax, yTicks2, yAxisRight, chart);
385
- const yLabels2 = generateYAxisLabels(xMax + 7, yMin, yMax, "right", yTicks2, yAxisRight, chart);
362
+ const axisLabelPosX = xMax + chart.padding.right - chart.fontSize;
386
363
 
387
- const labelPaddingRight = 7 + labelPadding(formatNumber(yAxisRight.max).length, chart.fontSize, 1.5);
364
+ const yTicks = Axis.getTicks(yNumTicks, axis);
365
+ if (chart.yGrid) {
366
+ components.push(generateYAxisLines(xMax - 5, xMax + 5, yMin, yMax, yTicks, axis, chart.yGrid));
367
+ }
368
+ components.push(
369
+ AI.createLine({ x: xMax, y: yMin }, { x: xMax, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
370
+ generateYAxisLabels(xMax + (axis.tickLabelDisp ?? 7), yMin, yMax, "right", yTicks, axis, chart)
371
+ );
388
372
 
389
- let yLabel2: AbstractImage.Component;
390
373
  switch (chart.labelLayout) {
391
374
  case "original":
392
- yLabel2 = generateYAxisLabel(
393
- xMax + labelPaddingRight,
394
- yMax + 0.5 * padding,
395
- "uniform",
396
- "up",
397
- yAxisRight.label,
398
- chart
375
+ components.push(
376
+ generateYAxisLabel(axisLabelPosX, yMax + 0.5 * chart.padding.bottom, "uniform", "up", axis, chart)
399
377
  );
400
378
  break;
401
-
402
379
  case "end":
403
- yLabel2 = generateYAxisLabel(xMax + labelPaddingRight, yMax, "left", "up", yAxisRight.label, chart);
380
+ components.push(generateYAxisLabel(axisLabelPosX, yMax, "left", "up", axis, chart));
404
381
  break;
405
-
406
382
  case "center":
407
- yLabel2 = generateYAxisLabel(
408
- xMax + labelPaddingRight,
409
- (yMin + yMax) / 2,
410
- "uniform",
411
- "up",
412
- yAxisRight.label,
413
- chart
414
- );
383
+ components.push(generateYAxisLabel(axisLabelPosX, (yMin + yMax) / 2, "uniform", "up", axis, chart));
415
384
  break;
416
-
417
385
  default:
418
386
  return exhaustiveCheck(chart.labelLayout);
419
387
  }
420
388
 
421
- return AbstractImage.createGroup("YAxisRight", [yLines2, yLabels2, yLabel2]);
389
+ return AI.createGroup("YAxisRight", components);
422
390
  }
423
391
 
424
- export function generateStack(
425
- xMin: number,
426
- xMax: number,
427
- yMin: number,
428
- yMax: number,
429
- chart: Chart
430
- ): AbstractImage.Component {
392
+ export function generateStack(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
431
393
  const pointsPos = chart.chartStack.points.map((stackPoint) => ({
432
394
  x: stackPoint.x,
433
395
  ys: [...stackPoint.ys.map((y) => Math.min(0, y))],
@@ -448,18 +410,12 @@ export function generateStack(
448
410
  chartStack: { ...chart.chartStack, points: pointsNeg },
449
411
  });
450
412
 
451
- return AbstractImage.createGroup("Stacks", [stackPos, stackNeg]);
413
+ return AI.createGroup("Stacks", [stackPos, stackNeg]);
452
414
  }
453
415
 
454
- function generateUnsignedStack(
455
- xMin: number,
456
- xMax: number,
457
- yMin: number,
458
- yMax: number,
459
- chart: Chart
460
- ): AbstractImage.Component {
416
+ function generateUnsignedStack(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
461
417
  if (chart.chartStack.points.length < 2) {
462
- return AbstractImage.createGroup("stack", []);
418
+ return AI.createGroup("stack", []);
463
419
  }
464
420
 
465
421
  const xAxis = chart.chartStack.xAxis === "top" ? chart.xAxisTop : chart.xAxisBottom;
@@ -469,23 +425,23 @@ function generateUnsignedStack(
469
425
  let sumY = 0;
470
426
  const points = stackPoints.ys.map((y) => {
471
427
  sumY += y;
472
- return Axis.transformPoint(AbstractImage.createPoint(stackPoints.x, sumY), xMin, xMax, yMin, yMax, xAxis, yAxis);
428
+ return Axis.transformPoint(AI.createPoint(stackPoints.x, sumY), xMin, xMax, yMin, yMax, xAxis, yAxis);
473
429
  });
474
430
  return points;
475
431
  });
476
432
 
477
433
  // Transpose the xPoints data to lines.
478
- const lines: Array<Array<AbstractImage.Point>> = [];
479
- for (let i = 0; i < xPoints[0].length; ++i) {
434
+ const lines: Array<Array<AI.Point>> = [];
435
+ for (let i = 0; i < (xPoints[0]?.length ?? 0); ++i) {
480
436
  lines[i] = [];
481
437
  for (const points of xPoints) {
482
- lines[i].push(points[i]);
438
+ lines[i]!.push(points[i]!);
483
439
  }
484
440
  }
485
441
 
486
- const polygons: Array<AbstractImage.Polygon> = [];
442
+ const polygons: Array<AI.Polygon> = [];
487
443
  let lastLine = chart.chartStack.points.map((stackPoint) =>
488
- Axis.transformPoint(AbstractImage.createPoint(stackPoint.x, 0), xMin, xMax, yMin, yMax, xAxis, yAxis)
444
+ Axis.transformPoint(AI.createPoint(stackPoint.x, 0), xMin, xMax, yMin, yMax, xAxis, yAxis)
489
445
  );
490
446
  lines.forEach((line, index) => {
491
447
  const config = chart.chartStack.config[index];
@@ -495,101 +451,88 @@ function generateUnsignedStack(
495
451
  const color = config.color;
496
452
  const points = [...line, ...lastLine.slice().reverse()];
497
453
  lastLine = line;
498
- polygons.push(AbstractImage.createPolygon(points, color, 0, color));
454
+ polygons.push(AI.createPolygon(points, color, 0, color));
499
455
  });
500
456
 
501
- return AbstractImage.createGroup("Stack", polygons);
457
+ return AI.createGroup("Stack", polygons);
502
458
  }
503
459
 
504
- export function generateLines(
505
- xMin: number,
506
- xMax: number,
507
- yMin: number,
508
- yMax: number,
509
- chart: Chart
510
- ): AbstractImage.Component {
460
+ export function generateLines(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
511
461
  const lines = chart.chartLines.map((l: ChartLine) => {
512
462
  if (l.points.length < 2) {
513
- return AbstractImage.createGroup(l.label, []);
463
+ return AI.createGroup(l.label, []);
514
464
  }
515
465
  const xAxis = l.xAxis === "top" ? chart.xAxisTop : chart.xAxisBottom;
516
466
  const yAxis = l.yAxis === "right" ? chart.yAxisRight : chart.yAxisLeft;
517
467
  const points = l.points.map((p) => Axis.transformPoint(p, xMin, xMax, yMin, yMax, xAxis, yAxis));
518
- const last = points[points.length - 1];
519
- return AbstractImage.createGroup(l.label, [
520
- AbstractImage.createPolyLine(points, l.color, l.thickness),
521
- AbstractImage.createText(
522
- last,
468
+ return AI.createGroup(l.label, [
469
+ AI.createPolyLine(points, l.color, l.thickness),
470
+ AI.createText(
471
+ points.at(-1)!,
523
472
  l.label,
524
473
  chart.font,
525
- chart.fontSize,
526
- AbstractImage.black,
474
+ l.fontSize ?? chart.fontSize,
475
+ AI.black,
527
476
  "normal",
528
477
  0,
529
478
  "center",
530
479
  "right",
531
480
  "down",
532
481
  0,
533
- AbstractImage.black,
482
+ AI.black,
534
483
  false
535
484
  ),
536
485
  ]);
537
486
  });
538
- return AbstractImage.createGroup("Lines", lines);
487
+ return AI.createGroup("Lines", lines);
539
488
  }
540
489
 
541
- export function generatePoints(
542
- xMin: number,
543
- xMax: number,
544
- yMin: number,
545
- yMax: number,
546
- chart: Chart
547
- ): AbstractImage.Component {
490
+ export function generatePoints(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
548
491
  const points = chart.chartPoints.map((p) => {
549
492
  const xAxis = p.xAxis === "top" ? chart.xAxisTop : chart.xAxisBottom;
550
493
  const yAxis = p.yAxis === "right" ? chart.yAxisRight : chart.yAxisLeft;
551
494
  const position = Axis.transformPoint(p.position, xMin, xMax, yMin, yMax, xAxis, yAxis);
552
495
  const shape = generatePointShape(p, position);
553
- return AbstractImage.createGroup(p.label, [
496
+ return AI.createGroup(p.label, [
554
497
  shape,
555
- AbstractImage.createText(
498
+ AI.createText(
556
499
  position,
557
500
  p.label,
558
501
  chart.font,
559
- chart.fontSize,
560
- AbstractImage.black,
502
+ p.fontSize ?? chart.fontSize,
503
+ AI.black,
561
504
  "normal",
562
505
  0,
563
506
  "center",
564
507
  "right",
565
508
  "down",
566
509
  0,
567
- AbstractImage.black,
510
+ AI.black,
568
511
  false
569
512
  ),
570
513
  ]);
571
514
  });
572
- return AbstractImage.createGroup("Points", points);
515
+ return AI.createGroup("Points", points);
573
516
  }
574
517
 
575
- function generatePointShape(p: ChartPoint, position: AbstractImage.Point): AbstractImage.Component {
518
+ function generatePointShape(p: ChartPoint, position: AI.Point): AI.Component {
576
519
  const halfWidth = p.size.width * 0.5;
577
520
  const halfHeight = p.size.height * 0.5;
578
521
  if (p.shape === "triangle") {
579
522
  const trianglePoints = [
580
- AbstractImage.createPoint(position.x, position.y + halfHeight),
581
- AbstractImage.createPoint(position.x - halfWidth, position.y - halfHeight),
582
- AbstractImage.createPoint(position.x + halfWidth, position.y - halfHeight),
523
+ AI.createPoint(position.x, position.y + halfHeight),
524
+ AI.createPoint(position.x - halfWidth, position.y - halfHeight),
525
+ AI.createPoint(position.x + halfWidth, position.y - halfHeight),
583
526
  ];
584
- return AbstractImage.createPolygon(trianglePoints, AbstractImage.black, 1, p.color);
527
+ return AI.createPolygon(trianglePoints, AI.black, 1, p.color);
585
528
  } else if (p.shape === "square") {
586
- const topLeft = AbstractImage.createPoint(position.x - halfWidth, position.y - halfHeight);
587
- const bottomRight = AbstractImage.createPoint(position.x + halfWidth, position.y + halfHeight);
588
- return AbstractImage.createRectangle(topLeft, bottomRight, AbstractImage.black, 1, p.color);
529
+ const topLeft = AI.createPoint(position.x - halfWidth, position.y - halfHeight);
530
+ const bottomRight = AI.createPoint(position.x + halfWidth, position.y + halfHeight);
531
+ return AI.createRectangle(topLeft, bottomRight, AI.black, 1, p.color);
589
532
  } else {
590
- const topLeft = AbstractImage.createPoint(position.x - halfWidth, position.y - halfHeight);
591
- const bottomRight = AbstractImage.createPoint(position.x + halfWidth, position.y + halfHeight);
592
- return AbstractImage.createEllipse(topLeft, bottomRight, AbstractImage.black, 1, p.color);
533
+ const topLeft = AI.createPoint(position.x - halfWidth, position.y - halfHeight);
534
+ const bottomRight = AI.createPoint(position.x + halfWidth, position.y + halfHeight);
535
+ return AI.createEllipse(topLeft, bottomRight, AI.black, 1, p.color);
593
536
  }
594
537
  }
595
538
 
@@ -598,72 +541,72 @@ export function generateXAxisGridLines(
598
541
  xMax: number,
599
542
  yMin: number,
600
543
  yMax: number,
601
- xTicks: Array<number>,
544
+ xTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
602
545
  xAxis: Axis.Axis,
603
- chart: Chart
604
- ): AbstractImage.Component {
546
+ grid: { readonly color: AI.Color; readonly thickness: number }
547
+ ): AI.Component {
605
548
  const xLines = xTicks.map((l) => {
606
- const x = Axis.transformValue(l, xMin, xMax, xAxis);
607
- const start = AbstractImage.createPoint(x, yMin);
608
- const end = AbstractImage.createPoint(x, yMax);
609
- return AbstractImage.createLine(start, end, chart.gridColor, chart.gridThickness);
549
+ const x = Axis.transformValue(l.value, xMin, xMax, xAxis);
550
+ const start = AI.createPoint(x, yMin);
551
+ const end = AI.createPoint(x, yMax);
552
+ return AI.createLine(start, end, grid.color, grid.thickness);
610
553
  });
611
554
 
612
- return AbstractImage.createGroup("Lines", xLines);
555
+ return AI.createGroup("Lines", xLines);
613
556
  }
614
557
 
615
558
  export function generateXAxisLabels(
616
559
  xMin: number,
617
560
  xMax: number,
618
561
  y: number,
619
- growVertical: AbstractImage.GrowthDirection,
620
- xTicks: Array<number>,
621
- xAxis: Axis.Axis,
562
+ growVertical: AI.GrowthDirection,
563
+ ticks: ReadonlyArray<Axis.DiscreteAxisPoint>,
564
+ axis: Axis.Axis,
622
565
  chart: Chart
623
- ): AbstractImage.Component {
624
- const xLabels = xTicks.map((l) => {
625
- const position = AbstractImage.createPoint(Axis.transformValue(l, xMin, xMax, xAxis), y);
626
- return AbstractImage.createText(
566
+ ): AI.Component {
567
+ const xLabels = ticks.map((l) => {
568
+ const position = AI.createPoint(Axis.transformValue(l.value, xMin, xMax, axis), y);
569
+ return AI.createText(
627
570
  position,
628
- formatNumber(l),
571
+ l.label ?? formatNumber(l.value),
629
572
  chart.font,
630
- chart.fontSize,
631
- AbstractImage.black,
573
+ axis.tickFontSize ?? chart.fontSize,
574
+ axis.labelColor ?? AI.black,
632
575
  "normal",
633
- 0,
576
+ axis.labelRotation ?? 0,
634
577
  "center",
635
578
  "uniform",
636
579
  growVertical,
637
580
  0,
638
- AbstractImage.black,
581
+ axis.labelColor ?? AI.black,
639
582
  false
640
583
  );
641
584
  });
642
- return AbstractImage.createGroup("Labels", xLabels);
585
+ return AI.createGroup("Labels", xLabels);
643
586
  }
644
587
 
645
588
  export function generateXAxisLabel(
646
589
  x: number,
647
590
  y: number,
648
- horizontalGrowthDirection: AbstractImage.GrowthDirection,
649
- verticalGrowthDirection: AbstractImage.GrowthDirection,
650
- label: string,
591
+ horizontalGrowthDirection: AI.GrowthDirection,
592
+ verticalGrowthDirection: AI.GrowthDirection,
593
+ axis: Axis.Axis,
651
594
  chart: Chart
652
- ): AbstractImage.Component {
653
- const position = AbstractImage.createPoint(x, y);
654
- return AbstractImage.createText(
595
+ ): AI.Component {
596
+ const position = AI.createPoint(x, y);
597
+ return AI.createText(
655
598
  position,
656
- label,
599
+ axis.label,
657
600
  chart.font,
658
- chart.fontSize,
659
- AbstractImage.black,
601
+ axis.axisFontSize ?? chart.fontSize,
602
+ axis.labelColor ?? AI.black,
660
603
  "normal",
661
604
  0,
662
605
  "center",
663
606
  horizontalGrowthDirection,
664
607
  verticalGrowthDirection,
665
608
  0,
666
- AbstractImage.black,
609
+ axis.labelColor ?? AI.black,
667
610
  false
668
611
  );
669
612
  }
@@ -673,71 +616,71 @@ export function generateYAxisLines(
673
616
  xMax: number,
674
617
  yMin: number,
675
618
  yMax: number,
676
- yTicks: Array<number>,
619
+ yTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
677
620
  yAxis: Axis.Axis,
678
- chart: Chart
679
- ): AbstractImage.Component {
621
+ grid: { readonly color: AI.Color; readonly thickness: number }
622
+ ): AI.Component {
680
623
  const yLines = yTicks.map((l) => {
681
- const y = Axis.transformValue(l, yMin, yMax, yAxis);
682
- const start = AbstractImage.createPoint(xMin, y);
683
- const end = AbstractImage.createPoint(xMax, y);
684
- return AbstractImage.createLine(start, end, chart.gridColor, chart.gridThickness);
624
+ const y = Axis.transformValue(l.value, yMin, yMax, yAxis);
625
+ const start = AI.createPoint(xMin, y);
626
+ const end = AI.createPoint(xMax, y);
627
+ return AI.createLine(start, end, grid.color, grid.thickness);
685
628
  });
686
- return AbstractImage.createGroup("Lines", yLines);
629
+ return AI.createGroup("Lines", yLines);
687
630
  }
688
631
 
689
632
  export function generateYAxisLabels(
690
633
  x: number,
691
634
  yMin: number,
692
635
  yMax: number,
693
- growHorizontal: AbstractImage.GrowthDirection,
694
- yTicks: Array<number>,
636
+ growHorizontal: AI.GrowthDirection,
637
+ yTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
695
638
  yAxis: Axis.Axis,
696
639
  chart: Chart
697
- ): AbstractImage.Component {
640
+ ): AI.Component {
698
641
  const yLabels = yTicks.map((l) => {
699
- const position = AbstractImage.createPoint(x, Axis.transformValue(l, yMin, yMax, yAxis));
700
- return AbstractImage.createText(
642
+ const position = AI.createPoint(x, Axis.transformValue(l.value, yMin, yMax, yAxis));
643
+ return AI.createText(
701
644
  position,
702
- formatNumber(l),
645
+ l.label ?? formatNumber(l.value),
703
646
  chart.font,
704
- chart.fontSize,
705
- AbstractImage.black,
647
+ yAxis.tickFontSize ?? chart.fontSize,
648
+ yAxis.labelColor ?? AI.black,
706
649
  "normal",
707
- 0,
650
+ yAxis.labelRotation ?? 0,
708
651
  "center",
709
652
  growHorizontal,
710
653
  "uniform",
711
654
  0,
712
- AbstractImage.black,
655
+ yAxis.labelColor ?? AI.black,
713
656
  false
714
657
  );
715
658
  });
716
- return AbstractImage.createGroup("Labels", yLabels);
659
+ return AI.createGroup("Labels", yLabels);
717
660
  }
718
661
 
719
662
  export function generateYAxisLabel(
720
663
  x: number,
721
664
  y: number,
722
- horizontalGrowthDirection: AbstractImage.GrowthDirection,
723
- verticalGrowthDirection: AbstractImage.GrowthDirection,
724
- label: string,
665
+ horizontalGrowthDirection: AI.GrowthDirection,
666
+ verticalGrowthDirection: AI.GrowthDirection,
667
+ axis: Axis.Axis,
725
668
  chart: Chart
726
- ): AbstractImage.Component {
727
- const position = AbstractImage.createPoint(x, y);
728
- return AbstractImage.createText(
669
+ ): AI.Component {
670
+ const position = AI.createPoint(x, y);
671
+ return AI.createText(
729
672
  position,
730
- label,
673
+ axis.label,
731
674
  chart.font,
732
- chart.fontSize,
733
- AbstractImage.black,
675
+ axis.axisFontSize ?? chart.fontSize,
676
+ axis.labelColor ?? AI.black,
734
677
  "normal",
735
678
  -90,
736
679
  "center",
737
680
  horizontalGrowthDirection,
738
681
  verticalGrowthDirection,
739
682
  0,
740
- AbstractImage.black,
683
+ axis.labelColor ?? AI.black,
741
684
  false
742
685
  );
743
686
  }