cx 26.7.6 → 26.9.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/build/charts/Legend.d.ts +8 -1
- package/build/charts/Legend.d.ts.map +1 -1
- package/build/charts/Legend.js +8 -4
- package/build/charts/LineGraph.d.ts +8 -16
- package/build/charts/LineGraph.d.ts.map +1 -1
- package/build/charts/LineGraph.js +76 -69
- package/build/charts/axis/NumericAxis.d.ts.map +1 -1
- package/build/charts/axis/NumericAxis.js +1 -254
- package/build/charts/axis/NumericScale.d.ts +59 -0
- package/build/charts/axis/NumericScale.d.ts.map +1 -0
- package/build/charts/axis/NumericScale.js +254 -0
- package/build/jsx-dev-runtime.d.ts +2 -1
- package/build/jsx-dev-runtime.d.ts.map +1 -1
- package/build/svg/Svg.d.ts +9 -0
- package/build/svg/Svg.d.ts.map +1 -1
- package/build/svg/Svg.js +9 -1
- package/build/ui/index.d.ts +3 -2
- package/build/ui/index.d.ts.map +1 -1
- package/build/ui/index.js +1 -1
- package/build/util/DOM.d.ts.map +1 -1
- package/build/util/DOM.js +16 -4
- package/build/util/getActiveElement.d.ts +1 -1
- package/build/util/getActiveElement.d.ts.map +1 -1
- package/build/util/getActiveElement.js +14 -2
- package/build/widgets/form/TextArea.d.ts +4 -0
- package/build/widgets/form/TextArea.d.ts.map +1 -1
- package/build/widgets/form/TextArea.js +7 -1
- package/build/widgets/grid/Grid.d.ts +12 -2
- package/build/widgets/grid/Grid.d.ts.map +1 -1
- package/build/widgets/grid/Grid.js +17 -4
- package/build/widgets/nav/MenuItem.js +2 -2
- package/build/widgets/overlay/Dropdown.d.ts +4 -4
- package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
- package/build/widgets/overlay/Dropdown.js +82 -38
- package/build/widgets/overlay/Overlay.d.ts.map +1 -1
- package/build/widgets/overlay/Overlay.js +7 -3
- package/build/widgets/overlay/Window.d.ts.map +1 -1
- package/build/widgets/overlay/Window.js +2 -1
- package/dist/charts.js +166 -168
- package/dist/manifest.js +805 -802
- package/dist/svg.js +13 -0
- package/dist/util.js +31 -9
- package/dist/widgets.js +153 -50
- package/package.json +1 -1
- package/src/charts/Legend.tsx +22 -2
- package/src/charts/LineGraph.tsx +80 -98
- package/src/charts/axis/NumericAxis.spec.ts +40 -0
- package/src/charts/axis/NumericAxis.tsx +1 -290
- package/src/charts/axis/NumericScale.ts +290 -0
- package/src/jsx-dev-runtime.ts +2 -1
- package/src/svg/Svg.tsx +24 -1
- package/src/ui/Cx.tsx +505 -505
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/Rescope.ts +50 -50
- package/src/ui/exprHelpers.ts +96 -96
- package/src/ui/index.ts +3 -6
- package/src/util/DOM.ts +15 -4
- package/src/util/getActiveElement.ts +16 -2
- package/src/widgets/Sandbox.ts +104 -104
- package/src/widgets/form/ColorField.scss +112 -112
- package/src/widgets/form/DateTimeField.scss +111 -111
- package/src/widgets/form/LookupField.maps.scss +26 -26
- package/src/widgets/form/MonthField.scss +113 -113
- package/src/widgets/form/Select.scss +104 -104
- package/src/widgets/form/TextArea.tsx +10 -1
- package/src/widgets/form/TextField.scss +66 -66
- package/src/widgets/grid/Grid.sorting.spec.tsx +188 -0
- package/src/widgets/grid/Grid.tsx +31 -7
- package/src/widgets/index.ts +41 -41
- package/src/widgets/nav/MenuItem.tsx +2 -2
- package/src/widgets/overlay/Dropdown.tsx +87 -27
- package/src/widgets/overlay/Overlay.tsx +5 -3
- package/src/widgets/overlay/Window.tsx +2 -1
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Stack } from "./Stack";
|
|
2
|
+
import { isNumber } from "../../util/isNumber";
|
|
3
|
+
import { Console } from "../../util/Console";
|
|
4
|
+
|
|
5
|
+
export class NumericScale {
|
|
6
|
+
min: number;
|
|
7
|
+
max: number;
|
|
8
|
+
snapToTicks: number;
|
|
9
|
+
tickDivisions: number[][];
|
|
10
|
+
minLabelDistance: number;
|
|
11
|
+
minLabelTickSize: number;
|
|
12
|
+
minTickDistance: number;
|
|
13
|
+
minTickStep: number;
|
|
14
|
+
tickSizes: number[];
|
|
15
|
+
normalized: boolean;
|
|
16
|
+
inverted: boolean;
|
|
17
|
+
minValue?: number;
|
|
18
|
+
maxValue?: number;
|
|
19
|
+
minValuePadded: number;
|
|
20
|
+
maxValuePadded: number;
|
|
21
|
+
stacks: Record<string, Stack>;
|
|
22
|
+
lowerDeadZone: number;
|
|
23
|
+
upperDeadZone: number;
|
|
24
|
+
origin: number;
|
|
25
|
+
scale: { factor: number; min: number; max: number; minPadding: number; maxPadding: number };
|
|
26
|
+
a: number;
|
|
27
|
+
b: number;
|
|
28
|
+
shouldUpdate: boolean;
|
|
29
|
+
|
|
30
|
+
reset(
|
|
31
|
+
min: number,
|
|
32
|
+
max: number,
|
|
33
|
+
snapToTicks: number,
|
|
34
|
+
tickDivisions: number[][],
|
|
35
|
+
minTickDistance: number,
|
|
36
|
+
minTickStep: number,
|
|
37
|
+
minLabelDistance: number,
|
|
38
|
+
minLabelTickSize: number,
|
|
39
|
+
normalized: boolean,
|
|
40
|
+
inverted: boolean,
|
|
41
|
+
lowerDeadZone: number,
|
|
42
|
+
upperDeadZone: number,
|
|
43
|
+
): void {
|
|
44
|
+
this.min = min;
|
|
45
|
+
this.max = max;
|
|
46
|
+
this.snapToTicks = snapToTicks;
|
|
47
|
+
this.tickDivisions = tickDivisions;
|
|
48
|
+
this.minLabelDistance = minLabelDistance;
|
|
49
|
+
this.minLabelTickSize = minLabelTickSize;
|
|
50
|
+
this.minTickDistance = minTickDistance;
|
|
51
|
+
this.minTickStep = minTickStep;
|
|
52
|
+
this.tickSizes = [];
|
|
53
|
+
this.normalized = normalized;
|
|
54
|
+
this.inverted = inverted;
|
|
55
|
+
delete this.minValue;
|
|
56
|
+
delete this.maxValue;
|
|
57
|
+
this.stacks = {};
|
|
58
|
+
this.lowerDeadZone = lowerDeadZone || 0;
|
|
59
|
+
this.upperDeadZone = upperDeadZone || 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
map(v: number, offset: number = 0): number {
|
|
63
|
+
return this.origin + (v + offset - this.scale.min + this.scale.minPadding) * this.scale.factor;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
decodeValue(n: number): number {
|
|
67
|
+
return n;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
encodeValue(v: number): number {
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
constrainValue(v: number): number {
|
|
75
|
+
return Math.max(this.scale.min, Math.min(this.scale.max, v));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
trackValue(v: number, offset: number = 0, constrain: boolean = false): number {
|
|
79
|
+
let value = (v - this.origin) / this.scale.factor - offset + this.scale.min - this.scale.minPadding;
|
|
80
|
+
if (constrain) value = this.constrainValue(value);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
hash(): any {
|
|
85
|
+
let r: any = {
|
|
86
|
+
origin: this.origin,
|
|
87
|
+
factor: this.scale.factor,
|
|
88
|
+
min: this.scale.min,
|
|
89
|
+
max: this.scale.max,
|
|
90
|
+
minPadding: this.scale.minPadding,
|
|
91
|
+
maxPadding: this.scale.maxPadding,
|
|
92
|
+
};
|
|
93
|
+
r.stacks = Object.keys(this.stacks)
|
|
94
|
+
.map((s) => this.stacks[s].info?.join(","))
|
|
95
|
+
.join(":");
|
|
96
|
+
return r;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
isSame(x: any): boolean {
|
|
100
|
+
let hash = this.hash();
|
|
101
|
+
let same = x && !Object.keys(hash).some((k) => x[k] !== hash[k]);
|
|
102
|
+
this.shouldUpdate = !same;
|
|
103
|
+
return same;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
measure(a: number, b: number): void {
|
|
107
|
+
this.a = a;
|
|
108
|
+
this.b = b;
|
|
109
|
+
|
|
110
|
+
if (this.minValue != null && this.min == null) this.min = this.minValue;
|
|
111
|
+
if (this.maxValue != null && this.max == null) this.max = this.maxValue;
|
|
112
|
+
|
|
113
|
+
for (let s in this.stacks) {
|
|
114
|
+
let info = this.stacks[s].measure(this.normalized);
|
|
115
|
+
let [min, max] = info;
|
|
116
|
+
if (this.min == null || min < this.min) this.min = min;
|
|
117
|
+
if (this.max == null || max > this.max) this.max = max;
|
|
118
|
+
this.stacks[s].info = info;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.min == null) this.min = 0;
|
|
122
|
+
if (this.max == null) this.max = this.normalized ? 1 : 100;
|
|
123
|
+
|
|
124
|
+
if (this.min == this.max) {
|
|
125
|
+
if (this.min == 0) {
|
|
126
|
+
this.min = -1;
|
|
127
|
+
this.max = 1;
|
|
128
|
+
} else {
|
|
129
|
+
let delta = Math.abs(this.min) * 0.1;
|
|
130
|
+
this.min -= delta;
|
|
131
|
+
this.max += delta;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.origin = this.inverted ? this.b : this.a;
|
|
136
|
+
|
|
137
|
+
this.scale = this.getScale();
|
|
138
|
+
|
|
139
|
+
this.calculateTicks();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getScale(tickSizes?: number[]): { factor: number; min: number; max: number; minPadding: number; maxPadding: number } {
|
|
143
|
+
let { min, max } = this;
|
|
144
|
+
let smin = min;
|
|
145
|
+
let smax = max;
|
|
146
|
+
|
|
147
|
+
let tickSize;
|
|
148
|
+
if (tickSizes && isNumber(this.snapToTicks) && tickSizes.length > 0) {
|
|
149
|
+
tickSize = tickSizes[Math.min(tickSizes.length - 1, this.snapToTicks)];
|
|
150
|
+
smin = Math.floor(smin / tickSize) * tickSize;
|
|
151
|
+
smax = Math.ceil(smax / tickSize) * tickSize;
|
|
152
|
+
} else {
|
|
153
|
+
if (this.minValue === min) smin = this.minValuePadded;
|
|
154
|
+
if (this.maxValue === max) smax = this.maxValuePadded;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
|
|
158
|
+
let maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
|
|
159
|
+
|
|
160
|
+
let sign = this.b > this.a ? 1 : -1;
|
|
161
|
+
|
|
162
|
+
let factor =
|
|
163
|
+
smin < smax
|
|
164
|
+
? (Math.abs(this.b - this.a) - this.lowerDeadZone - this.upperDeadZone) /
|
|
165
|
+
(smax - smin + minPadding + maxPadding)
|
|
166
|
+
: 0;
|
|
167
|
+
|
|
168
|
+
if (factor < 0) factor = 0;
|
|
169
|
+
|
|
170
|
+
if (factor > 0 && (this.lowerDeadZone > 0 || this.upperDeadZone > 0)) {
|
|
171
|
+
while (factor * (min - smin) < this.lowerDeadZone) smin -= this.lowerDeadZone / factor;
|
|
172
|
+
|
|
173
|
+
while (factor * (smax - max) < this.upperDeadZone) smax += this.upperDeadZone / factor;
|
|
174
|
+
|
|
175
|
+
if (tickSize! > 0 && isNumber(this.snapToTicks)) {
|
|
176
|
+
smin = Math.floor(smin / tickSize!) * tickSize!;
|
|
177
|
+
smax = Math.ceil(smax / tickSize!) * tickSize!;
|
|
178
|
+
minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
|
|
179
|
+
maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
factor: sign * (this.inverted ? -factor : factor),
|
|
187
|
+
min: smin,
|
|
188
|
+
max: smax,
|
|
189
|
+
minPadding,
|
|
190
|
+
maxPadding,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
acknowledge(value: number, width: number = 0, offset: number = 0): void {
|
|
195
|
+
if (value == null) return;
|
|
196
|
+
|
|
197
|
+
if (this.minValue == null || value < this.minValue) {
|
|
198
|
+
this.minValue = value;
|
|
199
|
+
this.minValuePadded = value + offset - width / 2;
|
|
200
|
+
}
|
|
201
|
+
if (this.maxValue == null || value > this.maxValue) {
|
|
202
|
+
this.maxValue = value;
|
|
203
|
+
this.maxValuePadded = value + offset + width / 2;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getStack(name: string): Stack {
|
|
208
|
+
let s = this.stacks[name];
|
|
209
|
+
if (!s) s = this.stacks[name] = new Stack();
|
|
210
|
+
return s;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
stacknowledge(name: string, ordinal: any, value: any): any {
|
|
214
|
+
return this.getStack(name).acknowledge(ordinal, value);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
stack(name: string, ordinal: any, value: any): number | null {
|
|
218
|
+
let v = this.getStack(name).stack(ordinal, value);
|
|
219
|
+
return v != null ? this.map(v) : null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
findTickSize(minPxDist: number): number | undefined {
|
|
223
|
+
return this.tickSizes.find((a) => a >= this.minLabelTickSize && a * Math.abs(this.scale.factor) >= minPxDist);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
getTickSizes(): number[] {
|
|
227
|
+
return this.tickSizes;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
calculateTicks(): void {
|
|
231
|
+
let dist = this.minLabelDistance / Math.abs(this.scale.factor);
|
|
232
|
+
let unit = Math.pow(10, Math.floor(Math.log10(dist)));
|
|
233
|
+
|
|
234
|
+
let bestLabelDistance = Infinity;
|
|
235
|
+
let bestTicks: number[] = [];
|
|
236
|
+
let bestScale = this.scale;
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < this.tickDivisions.length; i++) {
|
|
239
|
+
let divs = this.tickDivisions[i];
|
|
240
|
+
let tickSizes = divs.filter((ts) => ts >= this.minTickStep).map((ts) => ts * unit);
|
|
241
|
+
let scale = this.getScale(tickSizes);
|
|
242
|
+
tickSizes.forEach((size, level) => {
|
|
243
|
+
let labelDistance = size * Math.abs(scale.factor);
|
|
244
|
+
if (labelDistance >= this.minLabelDistance && labelDistance < bestLabelDistance) {
|
|
245
|
+
bestScale = scale;
|
|
246
|
+
bestTicks = tickSizes;
|
|
247
|
+
bestLabelDistance = labelDistance;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
this.scale = bestScale;
|
|
252
|
+
this.tickSizes = bestTicks.filter(
|
|
253
|
+
(ts) => ts >= this.minTickStep && ts * Math.abs(bestScale.factor) >= this.minTickDistance,
|
|
254
|
+
);
|
|
255
|
+
if (this.tickSizes.length > 0) {
|
|
256
|
+
let max = this.tickSizes[this.tickSizes.length - 1];
|
|
257
|
+
this.tickSizes.push(2 * max);
|
|
258
|
+
this.tickSizes.push(5 * max);
|
|
259
|
+
this.tickSizes.push(10 * max);
|
|
260
|
+
let min = this.tickSizes[0];
|
|
261
|
+
let minDist = min * Math.abs(bestScale.factor);
|
|
262
|
+
if (min / 10 >= this.minTickStep && minDist / 10 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 10);
|
|
263
|
+
else if (min / 5 >= this.minTickStep && minDist / 5 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 5);
|
|
264
|
+
else if (min / 2 >= this.minTickStep && minDist / 2 >= this.minTickDistance) this.tickSizes.splice(0, 0, min / 2);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getTicks(tickSizes: number[]): number[][] {
|
|
269
|
+
return tickSizes.map((size) => {
|
|
270
|
+
let start = Math.ceil((this.scale.min - this.scale.minPadding) / size);
|
|
271
|
+
let end = Math.floor((this.scale.max + this.scale.maxPadding) / size);
|
|
272
|
+
let result: number[] = [];
|
|
273
|
+
for (let i = start; i <= end; i++) result.push(i * size + 0);
|
|
274
|
+
return result;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
mapGridlines(): number[] {
|
|
279
|
+
let size = this.tickSizes[0];
|
|
280
|
+
let start = Math.ceil((this.scale.min - this.scale.minPadding) / size);
|
|
281
|
+
let end = Math.floor((this.scale.max + this.scale.maxPadding) / size);
|
|
282
|
+
let result: number[] = [];
|
|
283
|
+
for (let i = start; i <= end; i++) result.push(this.map(i * size));
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
book(): void {
|
|
288
|
+
Console.warn("NumericAxis does not support the autoSize flag for column and bar graphs.");
|
|
289
|
+
}
|
|
290
|
+
}
|
package/src/jsx-dev-runtime.ts
CHANGED
package/src/svg/Svg.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
+
import { StringProp } from "../ui/Prop";
|
|
2
3
|
import { RenderingContext } from "../ui/RenderingContext";
|
|
3
4
|
import { ResizeManager } from "../ui/ResizeManager";
|
|
4
5
|
import { VDOM, Widget } from "../ui/Widget";
|
|
@@ -18,6 +19,15 @@ interface SvgInstance extends BoundedObjectInstance {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface SvgConfig extends BoundedObjectConfig {
|
|
22
|
+
/** ARIA role for the `svg` element. Defaults to `img` when `ariaLabel` is set. */
|
|
23
|
+
role?: StringProp;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Accessible name for the `svg` element. Setting it announces the drawing as a single graphic
|
|
27
|
+
* instead of as its individual shapes, which is also how a tagged PDF export tags it.
|
|
28
|
+
*/
|
|
29
|
+
ariaLabel?: StringProp;
|
|
30
|
+
|
|
21
31
|
/** Set to `true` to automatically calculate width based on the measured height and `aspectRatio`. */
|
|
22
32
|
autoWidth?: boolean;
|
|
23
33
|
|
|
@@ -53,6 +63,16 @@ export class Svg extends BoundedObject<SvgConfig, SvgInstance> {
|
|
|
53
63
|
super(config);
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
declareData(...args: any[]) {
|
|
67
|
+
return super.declareData(
|
|
68
|
+
{
|
|
69
|
+
role: undefined,
|
|
70
|
+
ariaLabel: undefined,
|
|
71
|
+
},
|
|
72
|
+
...args,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
initState(context: RenderingContext, instance: SvgInstance) {
|
|
57
77
|
const size = {
|
|
58
78
|
width: 0,
|
|
@@ -147,6 +167,9 @@ class SvgComponent extends VDOM.Component<SvgComponentProps> {
|
|
|
147
167
|
const { instance, data, size, children, eventHandlers } = this.props;
|
|
148
168
|
const { widget } = instance;
|
|
149
169
|
|
|
170
|
+
// An unnamed drawing is left alone: `role="img"` would hide its text and put nothing in its place.
|
|
171
|
+
const role = data.role ?? (data.ariaLabel ? "img" : undefined);
|
|
172
|
+
|
|
150
173
|
const defs: any[] = [];
|
|
151
174
|
for (const k in (instance as any).clipRects) {
|
|
152
175
|
let cr = (instance as any).clipRects[k];
|
|
@@ -181,7 +204,7 @@ class SvgComponent extends VDOM.Component<SvgComponentProps> {
|
|
|
181
204
|
{...eventHandlers}
|
|
182
205
|
>
|
|
183
206
|
{size.width > 0 && size.height > 0 && (
|
|
184
|
-
<svg>
|
|
207
|
+
<svg role={role} aria-label={data.ariaLabel}>
|
|
185
208
|
<defs>{defs}</defs>
|
|
186
209
|
{children}
|
|
187
210
|
</svg>
|