abstract-chart 7.4.1 → 8.0.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/CHANGELOG.md +41 -0
- package/README.md +15 -18
- package/lib/chart.d.ts +19 -10
- package/lib/chart.d.ts.map +1 -1
- package/lib/chart.js +289 -140
- package/lib/chart.js.map +1 -1
- package/package.json +3 -3
- package/src/chart.ts +453 -173
package/src/chart.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import * as AI from "abstract-image";
|
|
2
3
|
import * as Axis from "./axis";
|
|
3
4
|
import { exhaustiveCheck } from "ts-exhaustive-check";
|
|
@@ -8,16 +9,22 @@ export type Partial<T> = { [P in keyof T]?: T[P] };
|
|
|
8
9
|
|
|
9
10
|
export type LabelLayout = "original" | "end" | "center";
|
|
10
11
|
|
|
12
|
+
const axisLabelPosFactor = 0.65;
|
|
13
|
+
|
|
11
14
|
export interface Chart {
|
|
12
15
|
readonly width: number;
|
|
13
16
|
readonly height: number;
|
|
14
17
|
readonly chartPoints: Array<ChartPoint>;
|
|
15
18
|
readonly chartLines: Array<ChartLine>;
|
|
16
19
|
readonly chartStack: ChartStack;
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
19
|
-
readonly
|
|
20
|
-
readonly
|
|
20
|
+
readonly chartDataAxisesBottom: Array<ChartDataAxis>;
|
|
21
|
+
readonly chartDataAxisesTop: Array<ChartDataAxis>;
|
|
22
|
+
readonly chartDataAxisesLeft: Array<ChartDataAxis>;
|
|
23
|
+
readonly chartDataAxisesRight: Array<ChartDataAxis>;
|
|
24
|
+
readonly xAxisesBottom: ReadonlyArray<Axis.Axis>;
|
|
25
|
+
readonly xAxisesTop: ReadonlyArray<Axis.Axis>;
|
|
26
|
+
readonly yAxisesLeft: ReadonlyArray<Axis.Axis>;
|
|
27
|
+
readonly yAxisesRight: ReadonlyArray<Axis.Axis>;
|
|
21
28
|
readonly backgroundColor: AI.Color;
|
|
22
29
|
readonly xGrid: ChartGrid;
|
|
23
30
|
readonly yGrid: ChartGrid;
|
|
@@ -27,6 +34,7 @@ export interface Chart {
|
|
|
27
34
|
readonly textOutlineColor: AI.Color;
|
|
28
35
|
readonly labelLayout: LabelLayout;
|
|
29
36
|
readonly padding: Padding;
|
|
37
|
+
readonly axisWidth: number;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
export type ChartGrid = { readonly color: AI.Color; readonly thickness: number };
|
|
@@ -40,10 +48,14 @@ export function createChart(props: ChartProps): Chart {
|
|
|
40
48
|
chartPoints: props.chartPoints ?? [],
|
|
41
49
|
chartLines: props.chartLines ?? [],
|
|
42
50
|
chartStack: props.chartStack ?? createChartStack({}),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
chartDataAxisesBottom: props.chartDataAxisesBottom ?? [],
|
|
52
|
+
chartDataAxisesTop: props.chartDataAxisesTop ?? [],
|
|
53
|
+
chartDataAxisesLeft: props.chartDataAxisesLeft ?? [],
|
|
54
|
+
chartDataAxisesRight: props.chartDataAxisesRight ?? [],
|
|
55
|
+
xAxisesBottom: props.xAxisesBottom ?? [],
|
|
56
|
+
xAxisesTop: props.xAxisesTop ?? [],
|
|
57
|
+
yAxisesLeft: props.yAxisesLeft ?? [],
|
|
58
|
+
yAxisesRight: props.yAxisesRight ?? [],
|
|
47
59
|
backgroundColor: props.backgroundColor ?? AI.white,
|
|
48
60
|
font: props.font ?? "Arial",
|
|
49
61
|
fontSize: props.fontSize ?? 12,
|
|
@@ -51,11 +63,12 @@ export function createChart(props: ChartProps): Chart {
|
|
|
51
63
|
textOutlineColor: props.textOutlineColor ?? AI.transparent,
|
|
52
64
|
labelLayout: props.labelLayout ?? "original",
|
|
53
65
|
padding: {
|
|
54
|
-
top: props.padding?.top ??
|
|
55
|
-
right: props.padding?.right ??
|
|
56
|
-
bottom: props.padding?.bottom ??
|
|
57
|
-
left: props.padding?.left ??
|
|
66
|
+
top: props.padding?.top ?? 10,
|
|
67
|
+
right: props.padding?.right ?? 10,
|
|
68
|
+
bottom: props.padding?.bottom ?? 10,
|
|
69
|
+
left: props.padding?.left ?? 10,
|
|
58
70
|
},
|
|
71
|
+
axisWidth: props.axisWidth ?? 80,
|
|
59
72
|
xGrid: { color: props.xGrid?.color ?? AI.gray, thickness: props.xGrid?.thickness ?? 1 },
|
|
60
73
|
yGrid: { color: props.yGrid?.color ?? AI.gray, thickness: props.yGrid?.thickness ?? 1 },
|
|
61
74
|
};
|
|
@@ -182,49 +195,140 @@ export function createChartStack(props: ChartStackProps): ChartStack {
|
|
|
182
195
|
return { points, xAxis, yAxis, config };
|
|
183
196
|
}
|
|
184
197
|
|
|
185
|
-
export
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
export type ChartDataAxis = Axis.AxisBase & {
|
|
199
|
+
readonly points: Array<AI.Point>;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export function createChartDataAxis(
|
|
203
|
+
points: Array<AI.Point>,
|
|
204
|
+
label: string,
|
|
205
|
+
labelRotation?: number,
|
|
206
|
+
tickLabelDisp?: number,
|
|
207
|
+
labelColor?: AI.Color,
|
|
208
|
+
tickLabelColor?: AI.Color,
|
|
209
|
+
thickness?: number,
|
|
210
|
+
axisColor?: AI.Color,
|
|
211
|
+
tickFontSize?: number,
|
|
212
|
+
axisFontSize?: number,
|
|
213
|
+
id?: string
|
|
214
|
+
): ChartDataAxis {
|
|
215
|
+
return {
|
|
216
|
+
points,
|
|
217
|
+
label,
|
|
218
|
+
labelRotation,
|
|
219
|
+
tickLabelDisp,
|
|
220
|
+
labelColor,
|
|
221
|
+
tickLabelColor,
|
|
222
|
+
thickness,
|
|
223
|
+
axisColor,
|
|
224
|
+
tickFontSize,
|
|
225
|
+
axisFontSize,
|
|
226
|
+
id,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function inverseTransformPoint(point: AI.Point, chart: Chart, xAxis: XAxis, yAxis: YAxis): AI.Point | undefined {
|
|
231
|
+
const padding = finalPadding(chart);
|
|
192
232
|
const xMin = padding.left;
|
|
193
233
|
const xMax = chart.width - padding.right;
|
|
194
234
|
const yMin = chart.height - padding.bottom;
|
|
195
235
|
const yMax = padding.top;
|
|
196
|
-
const x = Axis.inverseTransformValue(
|
|
197
|
-
|
|
236
|
+
const x = Axis.inverseTransformValue(
|
|
237
|
+
point.x,
|
|
238
|
+
xMin,
|
|
239
|
+
xMax,
|
|
240
|
+
xAxis === "top" ? chart.xAxisesTop[0] : chart.xAxisesBottom[0]
|
|
241
|
+
);
|
|
242
|
+
const y = Axis.inverseTransformValue(
|
|
243
|
+
point.y,
|
|
244
|
+
yMin,
|
|
245
|
+
yMax,
|
|
246
|
+
yAxis === "right" ? chart.yAxisesRight[0] : chart.yAxisesLeft[0]
|
|
247
|
+
);
|
|
198
248
|
if (x === undefined || y === undefined) {
|
|
199
249
|
return undefined;
|
|
200
250
|
}
|
|
201
251
|
return AI.createPoint(x, y);
|
|
202
252
|
}
|
|
203
253
|
|
|
254
|
+
function finalPadding(chart: Chart): Padding {
|
|
255
|
+
return {
|
|
256
|
+
bottom: chart.padding.bottom + (chart.xAxisesBottom.length + chart.chartDataAxisesBottom.length) * chart.axisWidth,
|
|
257
|
+
top: chart.padding.top + (chart.xAxisesTop.length + chart.chartDataAxisesTop.length) * chart.axisWidth,
|
|
258
|
+
left: chart.padding.left + (chart.yAxisesLeft.length + chart.chartDataAxisesLeft.length) * chart.axisWidth,
|
|
259
|
+
right: chart.padding.right + (chart.yAxisesRight.length + chart.chartDataAxisesRight.length) * chart.axisWidth,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
204
263
|
export function renderChart(chart: Chart): AI.AbstractImage {
|
|
205
|
-
const { width, height,
|
|
264
|
+
const { width, height, xAxisesBottom, xAxisesTop, yAxisesLeft, yAxisesRight } = chart;
|
|
265
|
+
|
|
266
|
+
const padding = finalPadding(chart);
|
|
206
267
|
|
|
207
268
|
const gridWidth = width - padding.left - padding.right;
|
|
208
269
|
const gridHeight = height - padding.bottom - padding.top;
|
|
209
270
|
|
|
210
271
|
const xMin = padding.left;
|
|
211
|
-
const xMax = width - padding.right;
|
|
212
|
-
const yMin = height - padding.bottom;
|
|
272
|
+
const xMax = chart.width - padding.right;
|
|
273
|
+
const yMin = chart.height - padding.bottom;
|
|
213
274
|
const yMax = padding.top;
|
|
214
275
|
|
|
215
276
|
const renderedBackground = generateBackground(xMin, xMax, yMin, yMax, chart);
|
|
216
277
|
|
|
217
278
|
const xNumTicks = gridWidth / 40;
|
|
218
|
-
const renderedXAxisBottom =
|
|
219
|
-
const renderedXAxisTop =
|
|
279
|
+
const renderedXAxisBottom = generateXAxises("bottom", xNumTicks, xAxisesBottom, xMin, xMax, yMin, yMax, chart);
|
|
280
|
+
const renderedXAxisTop = generateXAxises("top", xNumTicks, xAxisesTop, xMin, xMax, yMin, yMax, chart);
|
|
220
281
|
|
|
221
282
|
const yNumTicks = gridHeight / 40;
|
|
222
|
-
const renderedYAxisLeft =
|
|
223
|
-
const renderedYAxisRight =
|
|
283
|
+
const renderedYAxisLeft = generateYAxises("left", yNumTicks, yAxisesLeft, xMin, xMax, yMin, yMax, chart);
|
|
284
|
+
const renderedYAxisRight = generateYAxises("right", yNumTicks, yAxisesRight, xMin, xMax, yMin, yMax, chart);
|
|
224
285
|
|
|
225
286
|
const renderedPoints = generatePoints(xMin, xMax, yMin, yMax, chart);
|
|
226
287
|
const renderedLines = generateLines(xMin, xMax, yMin, yMax, chart);
|
|
227
288
|
const renderedStack = generateStack(xMin, xMax, yMin, yMax, chart);
|
|
289
|
+
const dataNumTicksX = gridWidth / 70;
|
|
290
|
+
const renderedDataAxisesBottom = generateDataAxisesX(
|
|
291
|
+
"bottom",
|
|
292
|
+
chart.chartDataAxisesBottom,
|
|
293
|
+
dataNumTicksX,
|
|
294
|
+
xMin,
|
|
295
|
+
xMax,
|
|
296
|
+
yMin,
|
|
297
|
+
yMax,
|
|
298
|
+
chart
|
|
299
|
+
);
|
|
300
|
+
const renderedDataAxisesTop = generateDataAxisesX(
|
|
301
|
+
"top",
|
|
302
|
+
chart.chartDataAxisesTop,
|
|
303
|
+
dataNumTicksX,
|
|
304
|
+
xMin,
|
|
305
|
+
xMax,
|
|
306
|
+
yMin,
|
|
307
|
+
yMax,
|
|
308
|
+
chart
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const dataNumTicksY = gridHeight / 70;
|
|
312
|
+
const renderedDataAxisesLeft = generateDataAxisesY(
|
|
313
|
+
"left",
|
|
314
|
+
chart.chartDataAxisesLeft,
|
|
315
|
+
dataNumTicksY,
|
|
316
|
+
xMin,
|
|
317
|
+
xMax,
|
|
318
|
+
yMin,
|
|
319
|
+
yMax,
|
|
320
|
+
chart
|
|
321
|
+
);
|
|
322
|
+
const renderedDataAxisesRight = generateDataAxisesY(
|
|
323
|
+
"right",
|
|
324
|
+
chart.chartDataAxisesRight,
|
|
325
|
+
dataNumTicksY,
|
|
326
|
+
xMin,
|
|
327
|
+
xMax,
|
|
328
|
+
yMin,
|
|
329
|
+
yMax,
|
|
330
|
+
chart
|
|
331
|
+
);
|
|
228
332
|
|
|
229
333
|
const components = [
|
|
230
334
|
renderedBackground,
|
|
@@ -235,6 +339,10 @@ export function renderChart(chart: Chart): AI.AbstractImage {
|
|
|
235
339
|
renderedStack,
|
|
236
340
|
renderedLines,
|
|
237
341
|
renderedPoints,
|
|
342
|
+
renderedDataAxisesBottom,
|
|
343
|
+
renderedDataAxisesTop,
|
|
344
|
+
renderedDataAxisesLeft,
|
|
345
|
+
renderedDataAxisesRight,
|
|
238
346
|
];
|
|
239
347
|
const topLeft = AI.createPoint(0, 0);
|
|
240
348
|
const size = AI.createSize(width, height);
|
|
@@ -251,9 +359,10 @@ export function generateBackground(xMin: number, xMax: number, yMin: number, yMa
|
|
|
251
359
|
);
|
|
252
360
|
}
|
|
253
361
|
|
|
254
|
-
export function
|
|
362
|
+
export function generateXAxises(
|
|
363
|
+
xAxis: XAxis,
|
|
255
364
|
xNumTicks: number,
|
|
256
|
-
|
|
365
|
+
axises: ReadonlyArray<Axis.Axis>,
|
|
257
366
|
xMin: number,
|
|
258
367
|
xMax: number,
|
|
259
368
|
yMin: number,
|
|
@@ -261,96 +370,105 @@ export function generateXAxisBottom(
|
|
|
261
370
|
chart: Chart
|
|
262
371
|
): AI.Component {
|
|
263
372
|
const components = Array<AI.Component>();
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
components.push(generateXAxisGridLines(xMin, xMax, yMin + 10, yMax, xTicks, axis, chart.xGrid));
|
|
271
|
-
}
|
|
272
|
-
components.push(
|
|
273
|
-
AI.createLine({ x: xMin, y: yMin }, { x: xMax, y: yMin }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
|
|
274
|
-
generateXAxisLabels(xMin, xMax, yMin + (axis.tickLabelDisp ?? 10), "down", xTicks, axis, chart)
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
switch (chart.labelLayout) {
|
|
278
|
-
case "original":
|
|
373
|
+
let lineY = xAxis === "bottom" ? yMin : yMax;
|
|
374
|
+
const dirFactor = xAxis == "bottom" ? 1 : -1;
|
|
375
|
+
for (const [ix, axis] of axises.entries()) {
|
|
376
|
+
const fullGrid = ix === 0 && xAxis === "bottom";
|
|
377
|
+
const xTicks = Axis.getTicks(xNumTicks, axis);
|
|
378
|
+
if (chart.xGrid) {
|
|
279
379
|
components.push(
|
|
280
|
-
|
|
281
|
-
xMax + chart.padding.right,
|
|
282
|
-
yMin + (axis.tickLabelDisp ?? 10),
|
|
283
|
-
"uniform",
|
|
284
|
-
"down",
|
|
285
|
-
axis,
|
|
286
|
-
chart
|
|
287
|
-
)
|
|
380
|
+
generateXAxisGridLines(xMin, xMax, lineY + dirFactor * 10, fullGrid ? yMax : lineY, xTicks, axis, chart.xGrid)
|
|
288
381
|
);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
382
|
+
}
|
|
383
|
+
components.push(
|
|
384
|
+
AI.createLine({ x: xMin, y: lineY }, { x: xMax, y: lineY }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
|
|
385
|
+
generateXAxisLabels(xMin, xMax, lineY + dirFactor * 12, xAxis === "bottom" ? "down" : "up", xTicks, axis, chart)
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const axisLabelPosY = lineY + dirFactor * chart.axisWidth * axisLabelPosFactor;
|
|
389
|
+
switch (chart.labelLayout) {
|
|
390
|
+
case "original":
|
|
391
|
+
components.push(
|
|
392
|
+
generateXAxisLabel(
|
|
393
|
+
xMax + chart.padding.right,
|
|
394
|
+
lineY + (axis.tickLabelDisp ?? 10),
|
|
395
|
+
"uniform",
|
|
396
|
+
"down",
|
|
397
|
+
axis,
|
|
398
|
+
chart
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
break;
|
|
402
|
+
case "end":
|
|
403
|
+
components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "uniform", axis, chart));
|
|
404
|
+
break;
|
|
405
|
+
case "center":
|
|
406
|
+
components.push(generateXAxisLabel((xMin + xMax) / 2, axisLabelPosY, "uniform", "uniform", axis, chart));
|
|
407
|
+
break;
|
|
408
|
+
default:
|
|
409
|
+
return exhaustiveCheck(chart.labelLayout);
|
|
410
|
+
}
|
|
411
|
+
lineY += dirFactor * chart.axisWidth;
|
|
298
412
|
}
|
|
299
413
|
|
|
300
|
-
return AI.createGroup("
|
|
414
|
+
return AI.createGroup(xAxis + "XAxis", components);
|
|
301
415
|
}
|
|
302
416
|
|
|
303
|
-
export function
|
|
304
|
-
|
|
305
|
-
|
|
417
|
+
export function generateYAxises(
|
|
418
|
+
yAxis: YAxis,
|
|
419
|
+
yNumTicks: number,
|
|
420
|
+
axises: ReadonlyArray<Axis.Axis>,
|
|
306
421
|
xMin: number,
|
|
307
422
|
xMax: number,
|
|
423
|
+
yMin: number,
|
|
308
424
|
yMax: number,
|
|
309
425
|
chart: Chart
|
|
310
426
|
): AI.Component {
|
|
311
427
|
const components = Array<AI.Component>();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
components.push(generateXAxisGridLines(xMin, xMax, yMax - 10, yMax, xTicks, axis, chart.xGrid));
|
|
319
|
-
}
|
|
320
|
-
components.push(
|
|
321
|
-
AI.createLine({ x: xMin, y: yMax }, { x: xMax, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
|
|
322
|
-
generateXAxisLabels(xMin, xMax, yMax - (axis.tickLabelDisp ?? 13), "up", xTicks, axis, chart)
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
switch (chart.labelLayout) {
|
|
326
|
-
case "original":
|
|
428
|
+
let lineX = yAxis === "left" ? xMin : xMax;
|
|
429
|
+
const dirFactor = yAxis == "left" ? -1 : 1;
|
|
430
|
+
for (const [ix, axis] of axises.entries()) {
|
|
431
|
+
const fullGrid = ix === 0 && yAxis === "left";
|
|
432
|
+
const yTicks = Axis.getTicks(yNumTicks, axis);
|
|
433
|
+
if (chart.yGrid) {
|
|
327
434
|
components.push(
|
|
328
|
-
|
|
329
|
-
xMax + 0.5 * chart.padding.right,
|
|
330
|
-
yMax - (axis.tickLabelDisp ?? 13),
|
|
331
|
-
"uniform",
|
|
332
|
-
"up",
|
|
333
|
-
axis,
|
|
334
|
-
chart
|
|
335
|
-
)
|
|
435
|
+
generateYAxisLines(lineX + dirFactor * 10, fullGrid ? xMax : lineX, yMin, yMax, yTicks, axis, chart.yGrid)
|
|
336
436
|
);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
437
|
+
}
|
|
438
|
+
components.push(
|
|
439
|
+
AI.createLine({ x: lineX, y: yMin }, { x: lineX, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
|
|
440
|
+
generateYAxisLabels(lineX + dirFactor * 12, yMin, yMax, yAxis, yTicks, axis, chart)
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const axisLabelPosX = lineX + dirFactor * chart.axisWidth * axisLabelPosFactor;
|
|
444
|
+
const rotation = yAxis === "left" ? -90 : 90;
|
|
445
|
+
switch (chart.labelLayout) {
|
|
446
|
+
case "original":
|
|
447
|
+
components.push(
|
|
448
|
+
generateYAxisLabel(axisLabelPosX, yMax + 0.5 * chart.padding.bottom, rotation, "uniform", "up", axis, chart)
|
|
449
|
+
);
|
|
450
|
+
break;
|
|
451
|
+
case "end":
|
|
452
|
+
components.push(generateYAxisLabel(axisLabelPosX, yMax, rotation, yAxis, "uniform", axis, chart));
|
|
453
|
+
break;
|
|
454
|
+
case "center":
|
|
455
|
+
components.push(
|
|
456
|
+
generateYAxisLabel(axisLabelPosX, (yMin + yMax) / 2, rotation, "uniform", "uniform", axis, chart)
|
|
457
|
+
);
|
|
458
|
+
break;
|
|
459
|
+
default:
|
|
460
|
+
return exhaustiveCheck(chart.labelLayout);
|
|
461
|
+
}
|
|
462
|
+
lineX += dirFactor * chart.axisWidth;
|
|
346
463
|
}
|
|
347
464
|
|
|
348
|
-
return AI.createGroup("
|
|
465
|
+
return AI.createGroup("YAxisLeft", components);
|
|
349
466
|
}
|
|
350
467
|
|
|
351
|
-
export function
|
|
352
|
-
|
|
353
|
-
|
|
468
|
+
export function generateDataAxisesX(
|
|
469
|
+
xAxis: XAxis,
|
|
470
|
+
axises: Array<ChartDataAxis>,
|
|
471
|
+
numTicks: number,
|
|
354
472
|
xMin: number,
|
|
355
473
|
xMax: number,
|
|
356
474
|
yMin: number,
|
|
@@ -358,79 +476,216 @@ export function generateYAxisLeft(
|
|
|
358
476
|
chart: Chart
|
|
359
477
|
): AI.Component {
|
|
360
478
|
const components = Array<AI.Component>();
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
479
|
+
let lineY =
|
|
480
|
+
xAxis === "bottom"
|
|
481
|
+
? yMin + chart.xAxisesBottom.length * chart.axisWidth
|
|
482
|
+
: yMax - chart.xAxisesTop.length * chart.axisWidth;
|
|
483
|
+
const dirFactor = xAxis === "bottom" ? 1 : -1;
|
|
484
|
+
for (const axis of axises) {
|
|
485
|
+
const min = Math.min(...axis.points.map((p) => p.y));
|
|
486
|
+
const max = Math.max(...axis.points.map((p) => p.y));
|
|
487
|
+
const linear = Axis.createLinearAxis(
|
|
488
|
+
min,
|
|
489
|
+
max,
|
|
490
|
+
axis.label,
|
|
491
|
+
axis.labelColor,
|
|
492
|
+
axis.labelRotation,
|
|
493
|
+
axis.tickLabelDisp,
|
|
494
|
+
axis.thickness,
|
|
495
|
+
axis.axisColor,
|
|
496
|
+
axis.id
|
|
497
|
+
);
|
|
498
|
+
const findX = (y: number): number => {
|
|
499
|
+
for (let i = 0; i < axis.points.length; ++i) {
|
|
500
|
+
const p0 = i > 0 ? axis.points[i - 1] : undefined;
|
|
501
|
+
const p1 = axis.points[i];
|
|
502
|
+
if (!p1) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (p0 && p0.y <= y && p1.y >= y) {
|
|
506
|
+
const k = (p1.x - p0.x) / (p1.y - p0.y);
|
|
507
|
+
const x = p0.x + k * (y - p0.y);
|
|
508
|
+
return x;
|
|
509
|
+
}
|
|
510
|
+
if (!p0 && p1.y >= y) {
|
|
511
|
+
return p1.x;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return axis.points[axis.points.length - 1]?.x ?? 0;
|
|
515
|
+
};
|
|
516
|
+
const yValues = Axis.getTicks(numTicks, linear).map((t) => t.value);
|
|
517
|
+
const lineY2 = lineY;
|
|
518
|
+
components.push(
|
|
519
|
+
...yValues.flatMap((y) => {
|
|
520
|
+
const tickX = findX(y);
|
|
521
|
+
const x = Axis.transformValue(tickX, xMin, xMax, chart.xAxisesBottom[0]);
|
|
522
|
+
const start = AI.createPoint(x, lineY2);
|
|
523
|
+
const end = AI.createPoint(x, lineY2 + dirFactor * 10);
|
|
524
|
+
const textPos = AI.createPoint(x, lineY2 + dirFactor * 12);
|
|
525
|
+
return [
|
|
526
|
+
AI.createLine(start, end, chart.xGrid.color, chart.xGrid.thickness),
|
|
527
|
+
AI.createText(
|
|
528
|
+
textPos,
|
|
529
|
+
formatNumber(y),
|
|
530
|
+
chart.font,
|
|
531
|
+
axis.tickFontSize ?? chart.fontSize,
|
|
532
|
+
axis.labelColor ?? AI.black,
|
|
533
|
+
"normal",
|
|
534
|
+
axis.labelRotation ?? 0,
|
|
535
|
+
"center",
|
|
536
|
+
"uniform",
|
|
537
|
+
xAxis === "bottom" ? "down" : "up",
|
|
538
|
+
0,
|
|
539
|
+
axis.labelColor ?? AI.black,
|
|
540
|
+
false
|
|
541
|
+
),
|
|
542
|
+
];
|
|
543
|
+
})
|
|
544
|
+
);
|
|
365
545
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
components.push(
|
|
371
|
-
AI.createLine({ x: xMin, y: yMin }, { x: xMin, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1),
|
|
372
|
-
generateYAxisLabels(xMin - (axis.tickLabelDisp ?? 7), yMin, yMax, "left", yTicks, axis, chart)
|
|
373
|
-
);
|
|
546
|
+
components.push(
|
|
547
|
+
AI.createLine({ x: xMin, y: lineY }, { x: xMax, y: lineY }, axis.axisColor ?? AI.gray, axis.thickness ?? 1)
|
|
548
|
+
);
|
|
374
549
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
550
|
+
const axisLabelPosY = lineY + dirFactor * chart.axisWidth * axisLabelPosFactor;
|
|
551
|
+
switch (chart.labelLayout) {
|
|
552
|
+
case "original":
|
|
553
|
+
components.push(
|
|
554
|
+
generateXAxisLabel(
|
|
555
|
+
xMax + chart.padding.right,
|
|
556
|
+
yMin + (axis.tickLabelDisp ?? 10),
|
|
557
|
+
"uniform",
|
|
558
|
+
"up",
|
|
559
|
+
linear,
|
|
560
|
+
chart
|
|
561
|
+
)
|
|
562
|
+
);
|
|
563
|
+
break;
|
|
564
|
+
case "end":
|
|
565
|
+
components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "uniform", linear, chart));
|
|
566
|
+
break;
|
|
567
|
+
case "center":
|
|
568
|
+
components.push(generateXAxisLabel((xMin + xMax) / 2, axisLabelPosY, "uniform", "uniform", linear, chart));
|
|
569
|
+
break;
|
|
570
|
+
default:
|
|
571
|
+
return exhaustiveCheck(chart.labelLayout);
|
|
572
|
+
}
|
|
573
|
+
lineY += dirFactor * chart.axisWidth;
|
|
389
574
|
}
|
|
390
|
-
|
|
391
|
-
return AI.createGroup("YAxisLeft", components);
|
|
575
|
+
return AI.createGroup(xAxis + "XDataAxis", components);
|
|
392
576
|
}
|
|
393
577
|
|
|
394
|
-
export function
|
|
395
|
-
|
|
396
|
-
|
|
578
|
+
export function generateDataAxisesY(
|
|
579
|
+
yAxis: YAxis,
|
|
580
|
+
axises: Array<ChartDataAxis>,
|
|
581
|
+
numTicks: number,
|
|
582
|
+
xMin: number,
|
|
397
583
|
xMax: number,
|
|
398
584
|
yMin: number,
|
|
399
585
|
yMax: number,
|
|
400
586
|
chart: Chart
|
|
401
587
|
): AI.Component {
|
|
402
588
|
const components = Array<AI.Component>();
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
589
|
+
let lineX =
|
|
590
|
+
yAxis === "left"
|
|
591
|
+
? xMin - chart.yAxisesLeft.length * chart.axisWidth
|
|
592
|
+
: xMax + chart.yAxisesRight.length * chart.axisWidth;
|
|
593
|
+
const dirFactor = yAxis === "left" ? -1 : 1;
|
|
594
|
+
for (const axis of axises) {
|
|
595
|
+
const min = Math.min(...axis.points.map((p) => p.y));
|
|
596
|
+
const max = Math.max(...axis.points.map((p) => p.y));
|
|
597
|
+
const linear = Axis.createLinearAxis(
|
|
598
|
+
min,
|
|
599
|
+
max,
|
|
600
|
+
axis.label,
|
|
601
|
+
axis.labelColor,
|
|
602
|
+
axis.labelRotation,
|
|
603
|
+
axis.tickLabelDisp,
|
|
604
|
+
axis.thickness,
|
|
605
|
+
axis.axisColor,
|
|
606
|
+
axis.id
|
|
607
|
+
);
|
|
608
|
+
const findX = (y: number): number => {
|
|
609
|
+
for (let i = 0; i < axis.points.length; ++i) {
|
|
610
|
+
const p0 = i > 0 ? axis.points[i - 1] : undefined;
|
|
611
|
+
const p1 = axis.points[i];
|
|
612
|
+
if (!p1) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (p0 && p0.y <= y && p1.y >= y) {
|
|
616
|
+
const k = (p1.x - p0.x) / (p1.y - p0.y);
|
|
617
|
+
const x = p0.x + k * (y - p0.y);
|
|
618
|
+
return x;
|
|
619
|
+
}
|
|
620
|
+
if (!p0 && p1.y >= y) {
|
|
621
|
+
return p1.x;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return axis.points[axis.points.length - 1]?.x ?? 0;
|
|
625
|
+
};
|
|
626
|
+
const yValues = Axis.getTicks(numTicks, linear).map((t) => t.value);
|
|
627
|
+
const lineX2 = lineX;
|
|
628
|
+
components.push(
|
|
629
|
+
...yValues.flatMap((y) => {
|
|
630
|
+
const tickY = findX(y);
|
|
631
|
+
const yPx = Axis.transformValue(tickY, yMin, yMax, chart.yAxisesLeft[0]);
|
|
632
|
+
const start = AI.createPoint(lineX2, yPx);
|
|
633
|
+
const end = AI.createPoint(lineX2 + dirFactor * 10, yPx);
|
|
634
|
+
const textPos = AI.createPoint(lineX2 + dirFactor * 12, yPx);
|
|
635
|
+
return [
|
|
636
|
+
AI.createLine(start, end, chart.xGrid.color, chart.xGrid.thickness),
|
|
637
|
+
AI.createText(
|
|
638
|
+
textPos,
|
|
639
|
+
formatNumber(y),
|
|
640
|
+
chart.font,
|
|
641
|
+
axis.tickFontSize ?? chart.fontSize,
|
|
642
|
+
axis.labelColor ?? AI.black,
|
|
643
|
+
"normal",
|
|
644
|
+
0,
|
|
645
|
+
"center",
|
|
646
|
+
yAxis,
|
|
647
|
+
"uniform",
|
|
648
|
+
0,
|
|
649
|
+
axis.labelColor ?? AI.black,
|
|
650
|
+
false
|
|
651
|
+
),
|
|
652
|
+
];
|
|
653
|
+
})
|
|
654
|
+
);
|
|
416
655
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
656
|
+
components.push(
|
|
657
|
+
AI.createLine({ x: lineX, y: yMin }, { x: lineX, y: yMax }, axis.axisColor ?? AI.gray, axis.thickness ?? 1)
|
|
658
|
+
);
|
|
659
|
+
const rotation = yAxis === "left" ? -90 : 90;
|
|
660
|
+
const axisLabelPosX = lineX + dirFactor * chart.axisWidth * axisLabelPosFactor;
|
|
661
|
+
switch (chart.labelLayout) {
|
|
662
|
+
case "original":
|
|
663
|
+
components.push(
|
|
664
|
+
generateYAxisLabel(
|
|
665
|
+
xMax + chart.padding.right,
|
|
666
|
+
yMin + (axis.tickLabelDisp ?? 10),
|
|
667
|
+
rotation,
|
|
668
|
+
"uniform",
|
|
669
|
+
"up",
|
|
670
|
+
linear,
|
|
671
|
+
chart
|
|
672
|
+
)
|
|
673
|
+
);
|
|
674
|
+
break;
|
|
675
|
+
case "end":
|
|
676
|
+
components.push(generateYAxisLabel(axisLabelPosX, yMax, rotation, "left", "uniform", linear, chart));
|
|
677
|
+
break;
|
|
678
|
+
case "center":
|
|
679
|
+
components.push(
|
|
680
|
+
generateYAxisLabel(axisLabelPosX, (yMin + yMax) / 2, rotation, "uniform", "uniform", linear, chart)
|
|
681
|
+
);
|
|
682
|
+
break;
|
|
683
|
+
default:
|
|
684
|
+
return exhaustiveCheck(chart.labelLayout);
|
|
685
|
+
}
|
|
686
|
+
lineX += dirFactor * chart.axisWidth;
|
|
431
687
|
}
|
|
432
|
-
|
|
433
|
-
return AI.createGroup("YAxisRight", components);
|
|
688
|
+
return AI.createGroup(yAxis + "YDataAxis", components);
|
|
434
689
|
}
|
|
435
690
|
|
|
436
691
|
export function generateStack(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
|
|
@@ -462,8 +717,8 @@ function generateUnsignedStack(xMin: number, xMax: number, yMin: number, yMax: n
|
|
|
462
717
|
return AI.createGroup("stack", []);
|
|
463
718
|
}
|
|
464
719
|
|
|
465
|
-
const xAxis = chart.chartStack.xAxis === "top" ? chart.
|
|
466
|
-
const yAxis = chart.chartStack.yAxis === "right" ? chart.
|
|
720
|
+
const xAxis = chart.chartStack.xAxis === "top" ? chart.xAxisesTop[0] : chart.xAxisesBottom[0];
|
|
721
|
+
const yAxis = chart.chartStack.yAxis === "right" ? chart.yAxisesRight[0] : chart.yAxisesLeft[0];
|
|
467
722
|
|
|
468
723
|
const xPoints = chart.chartStack.points.map((stackPoints) => {
|
|
469
724
|
let sumY = 0;
|
|
@@ -506,8 +761,8 @@ export function generateLines(xMin: number, xMax: number, yMin: number, yMax: nu
|
|
|
506
761
|
if (l.points.length < 2) {
|
|
507
762
|
return AI.createGroup(l.label, []);
|
|
508
763
|
}
|
|
509
|
-
const xAxis = l.xAxis === "top" ? chart.
|
|
510
|
-
const yAxis = l.yAxis === "right" ? chart.
|
|
764
|
+
const xAxis = l.xAxis === "top" ? chart.xAxisesTop[0] : chart.xAxisesBottom[0];
|
|
765
|
+
const yAxis = l.yAxis === "right" ? chart.yAxisesRight[0] : chart.yAxisesLeft[0];
|
|
511
766
|
const points = l.points.map((p) => Axis.transformPoint(p, xMin, xMax, yMin, yMax, xAxis, yAxis));
|
|
512
767
|
const segments = getLineSegmentsInsideChart(xMin, xMax, yMin, yMax, points);
|
|
513
768
|
const components = [];
|
|
@@ -623,8 +878,8 @@ function lineLine(a0: AI.Point, a1: AI.Point, b0: AI.Point, b1: AI.Point): AI.Po
|
|
|
623
878
|
|
|
624
879
|
export function generatePoints(xMin: number, xMax: number, yMin: number, yMax: number, chart: Chart): AI.Component {
|
|
625
880
|
const points = chart.chartPoints.map((p) => {
|
|
626
|
-
const xAxis = p.xAxis === "top" ? chart.
|
|
627
|
-
const yAxis = p.yAxis === "right" ? chart.
|
|
881
|
+
const xAxis = p.xAxis === "top" ? chart.xAxisesTop[0] : chart.xAxisesBottom[0];
|
|
882
|
+
const yAxis = p.yAxis === "right" ? chart.yAxisesRight[0] : chart.yAxisesLeft[0];
|
|
628
883
|
const position = Axis.transformPoint(p.position, xMin, xMax, yMin, yMax, xAxis, yAxis);
|
|
629
884
|
const outlineColor = p.textOutlineColor ?? chart.textOutlineColor;
|
|
630
885
|
const components = [
|
|
@@ -722,6 +977,18 @@ export function generateXAxisLabels(
|
|
|
722
977
|
axis: Axis.Axis,
|
|
723
978
|
chart: Chart
|
|
724
979
|
): AI.Component {
|
|
980
|
+
const rotation = axis.labelRotation ?? 0;
|
|
981
|
+
|
|
982
|
+
const horizontalGrowth: AI.GrowthDirection = (() => {
|
|
983
|
+
if (rotation === 0) {
|
|
984
|
+
return "uniform";
|
|
985
|
+
}
|
|
986
|
+
if (growVertical === "down") {
|
|
987
|
+
return rotation < 0 ? "left" : "right";
|
|
988
|
+
} else {
|
|
989
|
+
return rotation < 0 ? "right" : "left";
|
|
990
|
+
}
|
|
991
|
+
})();
|
|
725
992
|
const xLabels = ticks.map((l) => {
|
|
726
993
|
const position = AI.createPoint(Axis.transformValue(l.value, xMin, xMax, axis), y);
|
|
727
994
|
return AI.createText(
|
|
@@ -731,9 +998,9 @@ export function generateXAxisLabels(
|
|
|
731
998
|
axis.tickFontSize ?? chart.fontSize,
|
|
732
999
|
axis.labelColor ?? AI.black,
|
|
733
1000
|
"normal",
|
|
734
|
-
|
|
1001
|
+
rotation,
|
|
735
1002
|
"center",
|
|
736
|
-
|
|
1003
|
+
horizontalGrowth,
|
|
737
1004
|
growVertical,
|
|
738
1005
|
0,
|
|
739
1006
|
axis.labelColor ?? AI.black,
|
|
@@ -797,6 +1064,18 @@ export function generateYAxisLabels(
|
|
|
797
1064
|
yAxis: Axis.Axis,
|
|
798
1065
|
chart: Chart
|
|
799
1066
|
): AI.Component {
|
|
1067
|
+
const rotation = yAxis.labelRotation ?? 0;
|
|
1068
|
+
const growVertical: AI.GrowthDirection = (() => {
|
|
1069
|
+
if (rotation === 0) {
|
|
1070
|
+
return "uniform";
|
|
1071
|
+
}
|
|
1072
|
+
if (growHorizontal === "left") {
|
|
1073
|
+
return rotation < 0 ? "up" : "down";
|
|
1074
|
+
} else {
|
|
1075
|
+
return rotation < 0 ? "down" : "up";
|
|
1076
|
+
}
|
|
1077
|
+
})();
|
|
1078
|
+
|
|
800
1079
|
const yLabels = yTicks.map((l) => {
|
|
801
1080
|
const position = AI.createPoint(x, Axis.transformValue(l.value, yMin, yMax, yAxis));
|
|
802
1081
|
return AI.createText(
|
|
@@ -806,10 +1085,10 @@ export function generateYAxisLabels(
|
|
|
806
1085
|
yAxis.tickFontSize ?? chart.fontSize,
|
|
807
1086
|
yAxis.labelColor ?? AI.black,
|
|
808
1087
|
"normal",
|
|
809
|
-
|
|
1088
|
+
rotation,
|
|
810
1089
|
"center",
|
|
811
1090
|
growHorizontal,
|
|
812
|
-
|
|
1091
|
+
growVertical,
|
|
813
1092
|
0,
|
|
814
1093
|
yAxis.labelColor ?? AI.black,
|
|
815
1094
|
false
|
|
@@ -821,6 +1100,7 @@ export function generateYAxisLabels(
|
|
|
821
1100
|
export function generateYAxisLabel(
|
|
822
1101
|
x: number,
|
|
823
1102
|
y: number,
|
|
1103
|
+
rotation: number,
|
|
824
1104
|
horizontalGrowthDirection: AI.GrowthDirection,
|
|
825
1105
|
verticalGrowthDirection: AI.GrowthDirection,
|
|
826
1106
|
axis: Axis.Axis,
|
|
@@ -834,7 +1114,7 @@ export function generateYAxisLabel(
|
|
|
834
1114
|
axis.axisFontSize ?? chart.fontSize,
|
|
835
1115
|
axis.labelColor ?? AI.black,
|
|
836
1116
|
"normal",
|
|
837
|
-
|
|
1117
|
+
rotation,
|
|
838
1118
|
"center",
|
|
839
1119
|
horizontalGrowthDirection,
|
|
840
1120
|
verticalGrowthDirection,
|