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/CHANGELOG.md +34 -2
- package/lib/axis.d.ts +33 -13
- package/lib/axis.d.ts.map +1 -1
- package/lib/axis.js +71 -38
- package/lib/axis.js.map +1 -1
- package/lib/chart.d.ts +56 -36
- package/lib/chart.d.ts.map +1 -1
- package/lib/chart.js +170 -166
- package/lib/chart.js.map +1 -1
- package/lib/index.js +5 -1
- package/lib/index.js.map +1 -1
- package/package.json +3 -7
- package/src/axis.ts +114 -53
- package/src/chart.ts +265 -322
package/src/chart.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
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:
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
76
|
-
readonly color:
|
|
77
|
-
readonly 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 =
|
|
89
|
-
color =
|
|
90
|
-
size =
|
|
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<
|
|
108
|
-
readonly 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
|
-
|
|
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:
|
|
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 =
|
|
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:
|
|
142
|
+
point: AI.Point,
|
|
179
143
|
chart: Chart,
|
|
180
144
|
xAxis: XAxis,
|
|
181
|
-
yAxis: YAxis
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const
|
|
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
|
|
157
|
+
return AI.createPoint(x, y);
|
|
193
158
|
}
|
|
194
159
|
|
|
195
|
-
export function renderChart(chart: Chart):
|
|
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 -
|
|
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 -
|
|
204
|
-
const yMax =
|
|
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 =
|
|
231
|
-
const size =
|
|
232
|
-
return
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
):
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
+
components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "down", axis, chart));
|
|
277
248
|
break;
|
|
278
|
-
|
|
279
249
|
case "center":
|
|
280
|
-
|
|
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
|
|
256
|
+
return AI.createGroup("XAxisBottom", components);
|
|
288
257
|
}
|
|
289
258
|
|
|
290
259
|
export function generateXAxisTop(
|
|
291
260
|
xNumTicks: number,
|
|
292
|
-
|
|
261
|
+
axis: Axis.Axis | undefined,
|
|
293
262
|
xMin: number,
|
|
294
263
|
xMax: number,
|
|
295
264
|
yMax: number,
|
|
296
265
|
chart: Chart
|
|
297
|
-
):
|
|
298
|
-
|
|
299
|
-
|
|
266
|
+
): AI.Component {
|
|
267
|
+
const components = Array<AI.Component>();
|
|
268
|
+
if (!axis) {
|
|
269
|
+
return AI.createGroup("XAxisTop", components);
|
|
300
270
|
}
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
+
components.push(generateXAxisLabel(xMax, axisLabelPosY, "left", "up", axis, chart));
|
|
313
296
|
break;
|
|
314
|
-
|
|
315
297
|
case "center":
|
|
316
|
-
|
|
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
|
|
304
|
+
return AI.createGroup("XAxisTop", components);
|
|
324
305
|
}
|
|
325
306
|
|
|
326
307
|
export function generateYAxisLeft(
|
|
327
308
|
yNumTicks: number,
|
|
328
|
-
|
|
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
|
-
):
|
|
335
|
-
|
|
336
|
-
|
|
315
|
+
): AI.Component {
|
|
316
|
+
const components = Array<AI.Component>();
|
|
317
|
+
if (!axis) {
|
|
318
|
+
return AI.createGroup("YAxisLeft", components);
|
|
337
319
|
}
|
|
338
|
-
const
|
|
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
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
338
|
+
components.push(generateYAxisLabel(axisLabelPosX, yMax, "left", "up", axis, chart));
|
|
359
339
|
break;
|
|
360
|
-
|
|
361
340
|
case "center":
|
|
362
|
-
|
|
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
|
|
347
|
+
return AI.createGroup("YAxisLeft", components);
|
|
370
348
|
}
|
|
371
349
|
|
|
372
350
|
export function generateYAxisRight(
|
|
373
351
|
yNumTicks: number,
|
|
374
|
-
|
|
352
|
+
axis: Axis.Axis | undefined,
|
|
375
353
|
xMax: number,
|
|
376
354
|
yMin: number,
|
|
377
355
|
yMax: number,
|
|
378
356
|
chart: Chart
|
|
379
|
-
):
|
|
380
|
-
|
|
381
|
-
|
|
357
|
+
): AI.Component {
|
|
358
|
+
const components = Array<AI.Component>();
|
|
359
|
+
if (!axis) {
|
|
360
|
+
return AI.createGroup("YAxisRight", components);
|
|
382
361
|
}
|
|
383
|
-
const
|
|
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
|
|
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
|
-
|
|
393
|
-
|
|
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
|
-
|
|
380
|
+
components.push(generateYAxisLabel(axisLabelPosX, yMax, "left", "up", axis, chart));
|
|
404
381
|
break;
|
|
405
|
-
|
|
406
382
|
case "center":
|
|
407
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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<
|
|
479
|
-
for (let i = 0; i < xPoints[0]
|
|
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]
|
|
438
|
+
lines[i]!.push(points[i]!);
|
|
483
439
|
}
|
|
484
440
|
}
|
|
485
441
|
|
|
486
|
-
const polygons: Array<
|
|
442
|
+
const polygons: Array<AI.Polygon> = [];
|
|
487
443
|
let lastLine = chart.chartStack.points.map((stackPoint) =>
|
|
488
|
-
Axis.transformPoint(
|
|
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(
|
|
454
|
+
polygons.push(AI.createPolygon(points, color, 0, color));
|
|
499
455
|
});
|
|
500
456
|
|
|
501
|
-
return
|
|
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
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
482
|
+
AI.black,
|
|
534
483
|
false
|
|
535
484
|
),
|
|
536
485
|
]);
|
|
537
486
|
});
|
|
538
|
-
return
|
|
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
|
|
496
|
+
return AI.createGroup(p.label, [
|
|
554
497
|
shape,
|
|
555
|
-
|
|
498
|
+
AI.createText(
|
|
556
499
|
position,
|
|
557
500
|
p.label,
|
|
558
501
|
chart.font,
|
|
559
|
-
chart.fontSize,
|
|
560
|
-
|
|
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
|
-
|
|
510
|
+
AI.black,
|
|
568
511
|
false
|
|
569
512
|
),
|
|
570
513
|
]);
|
|
571
514
|
});
|
|
572
|
-
return
|
|
515
|
+
return AI.createGroup("Points", points);
|
|
573
516
|
}
|
|
574
517
|
|
|
575
|
-
function generatePointShape(p: ChartPoint, position:
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
527
|
+
return AI.createPolygon(trianglePoints, AI.black, 1, p.color);
|
|
585
528
|
} else if (p.shape === "square") {
|
|
586
|
-
const topLeft =
|
|
587
|
-
const bottomRight =
|
|
588
|
-
return
|
|
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 =
|
|
591
|
-
const bottomRight =
|
|
592
|
-
return
|
|
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:
|
|
544
|
+
xTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
|
|
602
545
|
xAxis: Axis.Axis,
|
|
603
|
-
|
|
604
|
-
):
|
|
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 =
|
|
608
|
-
const end =
|
|
609
|
-
return
|
|
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
|
|
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:
|
|
620
|
-
|
|
621
|
-
|
|
562
|
+
growVertical: AI.GrowthDirection,
|
|
563
|
+
ticks: ReadonlyArray<Axis.DiscreteAxisPoint>,
|
|
564
|
+
axis: Axis.Axis,
|
|
622
565
|
chart: Chart
|
|
623
|
-
):
|
|
624
|
-
const xLabels =
|
|
625
|
-
const position =
|
|
626
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
581
|
+
axis.labelColor ?? AI.black,
|
|
639
582
|
false
|
|
640
583
|
);
|
|
641
584
|
});
|
|
642
|
-
return
|
|
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:
|
|
649
|
-
verticalGrowthDirection:
|
|
650
|
-
|
|
591
|
+
horizontalGrowthDirection: AI.GrowthDirection,
|
|
592
|
+
verticalGrowthDirection: AI.GrowthDirection,
|
|
593
|
+
axis: Axis.Axis,
|
|
651
594
|
chart: Chart
|
|
652
|
-
):
|
|
653
|
-
const position =
|
|
654
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
619
|
+
yTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
|
|
677
620
|
yAxis: Axis.Axis,
|
|
678
|
-
|
|
679
|
-
):
|
|
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 =
|
|
683
|
-
const end =
|
|
684
|
-
return
|
|
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
|
|
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:
|
|
694
|
-
yTicks:
|
|
636
|
+
growHorizontal: AI.GrowthDirection,
|
|
637
|
+
yTicks: ReadonlyArray<Axis.DiscreteAxisPoint>,
|
|
695
638
|
yAxis: Axis.Axis,
|
|
696
639
|
chart: Chart
|
|
697
|
-
):
|
|
640
|
+
): AI.Component {
|
|
698
641
|
const yLabels = yTicks.map((l) => {
|
|
699
|
-
const position =
|
|
700
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
655
|
+
yAxis.labelColor ?? AI.black,
|
|
713
656
|
false
|
|
714
657
|
);
|
|
715
658
|
});
|
|
716
|
-
return
|
|
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:
|
|
723
|
-
verticalGrowthDirection:
|
|
724
|
-
|
|
665
|
+
horizontalGrowthDirection: AI.GrowthDirection,
|
|
666
|
+
verticalGrowthDirection: AI.GrowthDirection,
|
|
667
|
+
axis: Axis.Axis,
|
|
725
668
|
chart: Chart
|
|
726
|
-
):
|
|
727
|
-
const position =
|
|
728
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
683
|
+
axis.labelColor ?? AI.black,
|
|
741
684
|
false
|
|
742
685
|
);
|
|
743
686
|
}
|