@trebco/treb 28.7.0 → 28.10.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/dist/treb-spreadsheet-light.mjs +14 -14
- package/dist/treb-spreadsheet.mjs +12 -12
- package/dist/treb.d.ts +37 -6
- package/package.json +1 -1
- package/treb-base-types/src/gradient.ts +1 -1
- package/treb-base-types/src/localization.ts +6 -0
- package/treb-calculator/src/calculator.ts +23 -30
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +7 -0
- package/treb-calculator/src/dag/graph.ts +7 -0
- package/treb-calculator/src/functions/base-functions.ts +30 -1
- package/treb-charts/src/chart-functions.ts +11 -0
- package/treb-charts/src/chart-types.ts +18 -0
- package/treb-charts/src/chart-utils.ts +87 -0
- package/treb-charts/src/chart.ts +4 -0
- package/treb-charts/src/default-chart-renderer.ts +26 -1
- package/treb-charts/src/renderer.ts +81 -0
- package/treb-charts/style/charts.scss +8 -0
- package/treb-embed/markup/toolbar.html +35 -34
- package/treb-embed/src/custom-element/treb-global.ts +10 -2
- package/treb-embed/src/embedded-spreadsheet.ts +57 -101
- package/treb-embed/src/options.ts +7 -0
- package/treb-embed/style/layout.scss +4 -0
- package/treb-embed/style/toolbar.scss +37 -0
- package/treb-grid/src/types/conditional_format.ts +1 -1
- package/treb-grid/src/types/grid.ts +11 -1
- package/treb-grid/src/types/grid_base.ts +127 -5
- package/treb-grid/src/types/grid_command.ts +32 -0
- package/treb-grid/src/types/grid_events.ts +7 -0
- package/treb-grid/src/types/grid_options.ts +8 -0
- package/treb-grid/src/types/sheet.ts +0 -56
- package/treb-grid/src/types/update_flags.ts +1 -0
package/dist/treb.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! API v28.
|
|
1
|
+
/*! API v28.10. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* add our tag to the map
|
|
@@ -19,12 +19,20 @@ declare global {
|
|
|
19
19
|
export declare class TREBGlobal {
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Package version
|
|
23
23
|
*/
|
|
24
24
|
version: string;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Create a spreadsheet. The `USER_DATA_TYPE` template parameter is the type
|
|
28
|
+
* assigned to the `user_data` field of the spreadsheet instance -- it can
|
|
29
|
+
* help simplify typing if you are storing extra data in spreadsheet
|
|
30
|
+
* files.
|
|
31
|
+
*
|
|
32
|
+
* Just ignore this parameter if you don't need it.
|
|
33
|
+
*
|
|
34
|
+
* @typeParam USER_DATA_TYPE - type for the `user_data` field in the
|
|
35
|
+
* spreadsheet instance
|
|
28
36
|
*/
|
|
29
37
|
CreateSpreadsheet<USER_DATA_TYPE = unknown>(options: EmbeddedSpreadsheetOptions): EmbeddedSpreadsheet<USER_DATA_TYPE>;
|
|
30
38
|
}
|
|
@@ -256,6 +264,13 @@ export interface EmbeddedSpreadsheetOptions {
|
|
|
256
264
|
* top-left of the spreadsheet when a network document has local changes.
|
|
257
265
|
*/
|
|
258
266
|
revert_indicator?: boolean;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* handle the F9 key and recalculate the spreadsheet. for compatibility.
|
|
270
|
+
* we're leaving this option to default `false` for now, but that may
|
|
271
|
+
* change in the future. key modifiers have no effect.
|
|
272
|
+
*/
|
|
273
|
+
recalculate_on_f9?: boolean;
|
|
259
274
|
}
|
|
260
275
|
|
|
261
276
|
/**
|
|
@@ -291,11 +306,17 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
291
306
|
/** document name (metadata) */
|
|
292
307
|
set document_name(name: string | undefined);
|
|
293
308
|
|
|
294
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* opaque user data (metadata). `USER_DATA_TYPE` is a template
|
|
311
|
+
* parameter you can set when creating the spreadsheet.
|
|
312
|
+
*/
|
|
295
313
|
get user_data(): USER_DATA_TYPE | undefined;
|
|
296
314
|
|
|
297
|
-
/**
|
|
298
|
-
|
|
315
|
+
/**
|
|
316
|
+
* opaque user data (metadata). `USER_DATA_TYPE` is a template
|
|
317
|
+
* parameter you can set when creating the spreadsheet.
|
|
318
|
+
*/
|
|
319
|
+
set user_data(data: USER_DATA_TYPE | undefined);
|
|
299
320
|
|
|
300
321
|
/** current grid scale */
|
|
301
322
|
get scale(): number;
|
|
@@ -849,6 +870,16 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
849
870
|
*/
|
|
850
871
|
FormatNumber(value: number, format?: string): string;
|
|
851
872
|
|
|
873
|
+
/**
|
|
874
|
+
* convert a javascript date (or timestamp) to a spreadsheet date
|
|
875
|
+
*/
|
|
876
|
+
SpreadsheetDate(javascript_date: number | Date): number;
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* convert a spreadsheet date to a javascript date
|
|
880
|
+
*/
|
|
881
|
+
JavascriptDate(spreadsheet_date: number): number;
|
|
882
|
+
|
|
852
883
|
/**
|
|
853
884
|
* Apply borders to range.
|
|
854
885
|
*
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ export class Gradient {
|
|
|
15
15
|
|
|
16
16
|
public mapped: Array<GradientStop & { resolved: number[] }>;
|
|
17
17
|
|
|
18
|
-
constructor(stops: GradientStop[], theme: Theme, public color_space: ColorSpace = '
|
|
18
|
+
constructor(stops: GradientStop[], theme: Theme, public color_space: ColorSpace = 'RGB') {
|
|
19
19
|
|
|
20
20
|
this.mapped = stops.map(stop => {
|
|
21
21
|
|
|
@@ -98,6 +98,12 @@ export class Localization {
|
|
|
98
98
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// don't allow the "C" locale
|
|
102
|
+
if (this.locale === 'C') {
|
|
103
|
+
console.warn('Locale not set, defaulting to en-us');
|
|
104
|
+
this.locale = 'en-us';
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
const decimal_separator = new Intl.NumberFormat(this.locale,
|
|
102
108
|
{minimumFractionDigits: 1}).format(3.3).replace(/\d/g, '');
|
|
103
109
|
|
|
@@ -1769,7 +1769,25 @@ export class Calculator extends Graph {
|
|
|
1769
1769
|
|
|
1770
1770
|
public UpdateConditionals(list?: ConditionalFormat|ConditionalFormat[], context?: Sheet): void {
|
|
1771
1771
|
|
|
1772
|
+
// this method is (1) relying on the leaf vertex Set to avoid duplication,
|
|
1773
|
+
// and (2) leaving orphansed conditionals in place. we should look to
|
|
1774
|
+
// cleaning things up.
|
|
1775
|
+
|
|
1776
|
+
// is it also (3) adding unecessary calculations (building the expression,
|
|
1777
|
+
// below)?
|
|
1778
|
+
|
|
1772
1779
|
if (!list) {
|
|
1780
|
+
|
|
1781
|
+
// we could in theory remove all of the leaves (the ones we know to
|
|
1782
|
+
// be used for conditionals), because they will be added back below.
|
|
1783
|
+
// how wasteful is that?
|
|
1784
|
+
|
|
1785
|
+
// or maybe we could change the mark, and then use invalid marks
|
|
1786
|
+
// to check?
|
|
1787
|
+
|
|
1788
|
+
// the alternative is just to leave them as orphans until the graph
|
|
1789
|
+
// is rebuilt. which is lazy, but probably not that bad...
|
|
1790
|
+
|
|
1773
1791
|
for (const sheet of this.model.sheets.list) {
|
|
1774
1792
|
if (sheet.conditional_formats?.length) {
|
|
1775
1793
|
this.UpdateConditionals(sheet.conditional_formats, sheet);
|
|
@@ -1833,7 +1851,11 @@ export class Calculator extends Graph {
|
|
|
1833
1851
|
entry.internal = {};
|
|
1834
1852
|
}
|
|
1835
1853
|
if (!entry.internal.vertex) {
|
|
1836
|
-
|
|
1854
|
+
|
|
1855
|
+
const vertex = new CalculationLeafVertex();
|
|
1856
|
+
vertex.use = 'conditional';
|
|
1857
|
+
|
|
1858
|
+
entry.internal.vertex = vertex;
|
|
1837
1859
|
|
|
1838
1860
|
let options: EvaluateOptions|undefined;
|
|
1839
1861
|
if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
|
|
@@ -1851,35 +1873,6 @@ export class Calculator extends Graph {
|
|
|
1851
1873
|
this.AddLeafVertex(vertex);
|
|
1852
1874
|
this.UpdateLeafVertex(vertex, expression, context);
|
|
1853
1875
|
|
|
1854
|
-
/*
|
|
1855
|
-
if (entry.type === 'cell-match') {
|
|
1856
|
-
if (!entry.internal) {
|
|
1857
|
-
entry.internal = {};
|
|
1858
|
-
}
|
|
1859
|
-
if (!entry.internal.vertex) {
|
|
1860
|
-
entry.internal.vertex = new CalculationLeafVertex();
|
|
1861
|
-
}
|
|
1862
|
-
const vertex = entry.internal.vertex as LeafVertex;
|
|
1863
|
-
this.AddLeafVertex(vertex);
|
|
1864
|
-
this.UpdateLeafVertex(vertex, entry.expression, context);
|
|
1865
|
-
}
|
|
1866
|
-
else if (entry.type === 'expression') {
|
|
1867
|
-
if (!entry.internal) {
|
|
1868
|
-
entry.internal = {};
|
|
1869
|
-
}
|
|
1870
|
-
if (!entry.internal.vertex) {
|
|
1871
|
-
entry.internal.vertex = new CalculationLeafVertex();
|
|
1872
|
-
|
|
1873
|
-
// set initial state based on current state
|
|
1874
|
-
entry.internal.vertex.result = { type: ValueType.boolean, value: !!entry.applied };
|
|
1875
|
-
|
|
1876
|
-
}
|
|
1877
|
-
const vertex = entry.internal.vertex as LeafVertex;
|
|
1878
|
-
this.AddLeafVertex(vertex);
|
|
1879
|
-
this.UpdateLeafVertex(vertex, entry.expression, context);
|
|
1880
|
-
}
|
|
1881
|
-
*/
|
|
1882
|
-
|
|
1883
1876
|
}
|
|
1884
1877
|
|
|
1885
1878
|
}
|
|
@@ -42,6 +42,13 @@ export class CalculationLeafVertex extends SpreadsheetVertex {
|
|
|
42
42
|
|
|
43
43
|
public address = { row: -1, column: -1 }; // fake address
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* this type is currently only used for conditional formatting.
|
|
47
|
+
* but that might change in the future. I want to identify what
|
|
48
|
+
* it's used for so we can selectively prune them when necessary.
|
|
49
|
+
*/
|
|
50
|
+
public use?: string;
|
|
51
|
+
|
|
45
52
|
/**
|
|
46
53
|
* flag, to reduce unecessary application. work in progress. this
|
|
47
54
|
* indicates that we reached the calculation step. that means either
|
|
@@ -816,6 +816,13 @@ export abstract class Graph implements GraphCallbacks {
|
|
|
816
816
|
* managing and maintaining these vertices: we only need references.
|
|
817
817
|
*/
|
|
818
818
|
public AddLeafVertex(vertex: LeafVertex): void {
|
|
819
|
+
|
|
820
|
+
/*
|
|
821
|
+
if (this.leaf_vertices.has(vertex)) {
|
|
822
|
+
console.info("TLV already has", vertex);
|
|
823
|
+
}
|
|
824
|
+
*/
|
|
825
|
+
|
|
819
826
|
this.leaf_vertices.add(vertex);
|
|
820
827
|
}
|
|
821
828
|
|
|
@@ -848,6 +848,17 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
848
848
|
}),
|
|
849
849
|
},
|
|
850
850
|
|
|
851
|
+
RoundUp: {
|
|
852
|
+
fn: Utils.ApplyAsArray2((a, digits = 0) => {
|
|
853
|
+
const m = Math.pow(10, digits);
|
|
854
|
+
const positive = a >= 0;
|
|
855
|
+
return {
|
|
856
|
+
type: ValueType.number,
|
|
857
|
+
value: positive ? Math.ceil(m * a) / m : Math.floor(m * a) / m,
|
|
858
|
+
};
|
|
859
|
+
}),
|
|
860
|
+
},
|
|
861
|
+
|
|
851
862
|
/*
|
|
852
863
|
|
|
853
864
|
Round: {
|
|
@@ -1259,13 +1270,31 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1259
1270
|
columns = area.value[0]?.length || 0;
|
|
1260
1271
|
|
|
1261
1272
|
const result: UnionValue[][] = [];
|
|
1273
|
+
|
|
1262
1274
|
for (let r = 0; r < rows; r++) {
|
|
1263
1275
|
const row: UnionValue[] = [];
|
|
1264
1276
|
for (let c = 0; c < columns; c++) {
|
|
1265
1277
|
const src = area.value[r][c];
|
|
1266
1278
|
if (src.type === ValueType.number) {
|
|
1267
1279
|
let calc = 0;
|
|
1268
|
-
|
|
1280
|
+
|
|
1281
|
+
// special case: max === min. this can be used to do binary
|
|
1282
|
+
// coloring over a set of data (ignoring the pivot).
|
|
1283
|
+
|
|
1284
|
+
// FIXME: use a separate loop?
|
|
1285
|
+
|
|
1286
|
+
if (max === min) {
|
|
1287
|
+
if (src.value > max) {
|
|
1288
|
+
calc = 1;
|
|
1289
|
+
}
|
|
1290
|
+
else if (src.value < max) {
|
|
1291
|
+
calc = 0;
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
calc = 0.5
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
else if (range > 0) {
|
|
1269
1298
|
calc = (src.value - min) / range;
|
|
1270
1299
|
}
|
|
1271
1300
|
row.push({ type: ValueType.number, value: calc });
|
|
@@ -153,4 +153,15 @@ export const ChartFunctions: FunctionMap = {
|
|
|
153
153
|
fn: Identity,
|
|
154
154
|
},
|
|
155
155
|
|
|
156
|
+
'Bubble.Chart': {
|
|
157
|
+
arguments: [
|
|
158
|
+
{ name: 'X', metadata: true, },
|
|
159
|
+
{ name: 'Y', metadata: true, },
|
|
160
|
+
{ name: 'Z', metadata: true, },
|
|
161
|
+
{ name: 'Categories' },
|
|
162
|
+
{ name: 'Chart Title' },
|
|
163
|
+
],
|
|
164
|
+
fn: Identity,
|
|
165
|
+
},
|
|
166
|
+
|
|
156
167
|
};
|
|
@@ -107,6 +107,23 @@ export interface ScatterData2 extends ChartDataBaseType {
|
|
|
107
107
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
export interface BubbleChartData extends ChartDataBaseType {
|
|
111
|
+
|
|
112
|
+
type: 'bubble';
|
|
113
|
+
|
|
114
|
+
x?: SubSeries;
|
|
115
|
+
y?: SubSeries;
|
|
116
|
+
z?: SubSeries;
|
|
117
|
+
c?: any[];
|
|
118
|
+
|
|
119
|
+
x_scale: RangeScale;
|
|
120
|
+
y_scale: RangeScale;
|
|
121
|
+
|
|
122
|
+
x_labels?: string[];
|
|
123
|
+
y_labels?: string[];
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
110
127
|
/** base for column types (FIXME: probably common to scatter/line/area also) */
|
|
111
128
|
export interface ColumnDataBaseType extends ChartDataBaseType {
|
|
112
129
|
column_width: number;
|
|
@@ -190,6 +207,7 @@ export type ChartData
|
|
|
190
207
|
| AreaData
|
|
191
208
|
| ColumnData
|
|
192
209
|
| BarData
|
|
210
|
+
| BubbleChartData
|
|
193
211
|
;
|
|
194
212
|
|
|
195
213
|
export enum LegendLayout {
|
|
@@ -364,6 +364,93 @@ const ApplyLabels = (series_list: SeriesType[], pattern: string, category_labels
|
|
|
364
364
|
|
|
365
365
|
//------------------------------------------------------------------------------
|
|
366
366
|
|
|
367
|
+
export const CreateBubbleChart = (args: UnionValue[]): ChartData => {
|
|
368
|
+
|
|
369
|
+
const [x, y, z] = [0,1,2].map(index => {
|
|
370
|
+
const arg = args[index];
|
|
371
|
+
if (arg.type === ValueType.array) {
|
|
372
|
+
return ArrayToSeries(arg).y;
|
|
373
|
+
}
|
|
374
|
+
return undefined;
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
let c: string[]|undefined = undefined;
|
|
378
|
+
if (Array.isArray(args[3])) {
|
|
379
|
+
c = Util.Flatten(args[3]).map(value => (value||'').toString());
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const title = args[4]?.toString() || undefined;
|
|
383
|
+
|
|
384
|
+
// FIXME: need to pad out the axes by the values at the edges,
|
|
385
|
+
// so the whole circle is included in the chart area.
|
|
386
|
+
|
|
387
|
+
const [x_scale, y_scale] = [x, y].map(subseries => {
|
|
388
|
+
|
|
389
|
+
let series_min = 0;
|
|
390
|
+
let series_max = 1;
|
|
391
|
+
let first = false;
|
|
392
|
+
|
|
393
|
+
if (subseries?.data) {
|
|
394
|
+
|
|
395
|
+
for (const [index, value] of subseries.data.entries()) {
|
|
396
|
+
if (typeof value === 'number') {
|
|
397
|
+
|
|
398
|
+
if (!first) {
|
|
399
|
+
first = true;
|
|
400
|
+
series_min = value;
|
|
401
|
+
series_max = value;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const size = (z?.data?.[index]) || 0;
|
|
405
|
+
series_min = Math.min(series_min, value - size / 2);
|
|
406
|
+
series_max = Math.max(series_max, value + size / 2);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return Util.Scale(series_min, series_max, 7);
|
|
412
|
+
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
let x_labels: string[] | undefined;
|
|
416
|
+
let y_labels: string[] | undefined;
|
|
417
|
+
|
|
418
|
+
if (x?.format) {
|
|
419
|
+
x_labels = [];
|
|
420
|
+
const format = NumberFormatCache.Get(x.format);
|
|
421
|
+
for (let i = 0; i <= x_scale.count; i++) {
|
|
422
|
+
x_labels.push(format.Format(x_scale.min + i * x_scale.step));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (y?.format) {
|
|
427
|
+
y_labels = [];
|
|
428
|
+
const format = NumberFormatCache.Get(y.format);
|
|
429
|
+
for (let i = 0; i <= y_scale.count; i++) {
|
|
430
|
+
y_labels.push(format.Format(y_scale.min + i * y_scale.step));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
|
|
436
|
+
type: 'bubble',
|
|
437
|
+
|
|
438
|
+
title,
|
|
439
|
+
|
|
440
|
+
x,
|
|
441
|
+
y,
|
|
442
|
+
z,
|
|
443
|
+
c,
|
|
444
|
+
|
|
445
|
+
x_scale,
|
|
446
|
+
y_scale,
|
|
447
|
+
|
|
448
|
+
x_labels,
|
|
449
|
+
y_labels,
|
|
450
|
+
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
};
|
|
367
454
|
|
|
368
455
|
/**
|
|
369
456
|
* args is [data, title, options]
|
package/treb-charts/src/chart.ts
CHANGED
|
@@ -95,6 +95,7 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
95
95
|
|| chart_data.type === 'histogram2'
|
|
96
96
|
|| chart_data.type === 'bar'
|
|
97
97
|
|| chart_data.type === 'scatter2'
|
|
98
|
+
|| chart_data.type === 'bubble'
|
|
98
99
|
) {
|
|
99
100
|
|
|
100
101
|
// we need to measure first, then lay out the other axis, then we
|
|
@@ -122,7 +123,7 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
122
123
|
let max_width = 0;
|
|
123
124
|
let max_height = 0;
|
|
124
125
|
|
|
125
|
-
const scale = (chart_data.type === 'scatter2') ? chart_data.y_scale : chart_data.scale;
|
|
126
|
+
const scale = (chart_data.type === 'scatter2' || chart_data.type === 'bubble') ? chart_data.y_scale : chart_data.scale;
|
|
126
127
|
|
|
127
128
|
const count = (chart_data.type === 'bar') ?
|
|
128
129
|
chart_data.y_labels.length :
|
|
@@ -163,6 +164,7 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
163
164
|
chart_data.type !== 'area' &&
|
|
164
165
|
chart_data.type !== 'bar' &&
|
|
165
166
|
chart_data.type !== 'scatter2' &&
|
|
167
|
+
chart_data.type !== 'bubble' &&
|
|
166
168
|
chart_data.type !== 'histogram2'
|
|
167
169
|
);
|
|
168
170
|
|
|
@@ -199,6 +201,29 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
199
201
|
this.renderer.RenderPoints(area, chart_data.x, chart_data.y, 'mc mc-correlation series-1');
|
|
200
202
|
break;
|
|
201
203
|
|
|
204
|
+
case 'bubble':
|
|
205
|
+
|
|
206
|
+
this.renderer.RenderGrid(area,
|
|
207
|
+
chart_data.y_scale.count,
|
|
208
|
+
chart_data.x_scale.count + 1, // (sigh)
|
|
209
|
+
'chart-grid');
|
|
210
|
+
|
|
211
|
+
if (chart_data.x && chart_data.y && chart_data.z) {
|
|
212
|
+
this.renderer.RenderBubbleSeries(area,
|
|
213
|
+
chart_data.x.data,
|
|
214
|
+
chart_data.y.data,
|
|
215
|
+
chart_data.z.data,
|
|
216
|
+
chart_data.c || [],
|
|
217
|
+
chart_data.x_scale,
|
|
218
|
+
chart_data.y_scale,
|
|
219
|
+
undefined,
|
|
220
|
+
undefined,
|
|
221
|
+
'bubble-chart',
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
break;
|
|
226
|
+
|
|
202
227
|
case 'scatter2':
|
|
203
228
|
|
|
204
229
|
this.renderer.RenderGrid(area,
|
|
@@ -1193,6 +1193,87 @@ export class ChartRenderer {
|
|
|
1193
1193
|
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
public RenderBubbleSeries(area: Area,
|
|
1197
|
+
x: Array<number | undefined>,
|
|
1198
|
+
y: Array<number | undefined>,
|
|
1199
|
+
z: Array<number | undefined>,
|
|
1200
|
+
c: any[] = [],
|
|
1201
|
+
x_scale: RangeScale,
|
|
1202
|
+
y_scale: RangeScale,
|
|
1203
|
+
min = 10,
|
|
1204
|
+
max = 30,
|
|
1205
|
+
classes?: string | string[]): void {
|
|
1206
|
+
|
|
1207
|
+
const count = Math.max(x.length, y.length, z.length);
|
|
1208
|
+
const xrange = (x_scale.max - x_scale.min) || 1;
|
|
1209
|
+
const yrange = (y_scale.max - y_scale.min) || 1;
|
|
1210
|
+
|
|
1211
|
+
// const marker_elements: string[] = [];
|
|
1212
|
+
const points: Array<{x: number, y: number, z: number, series: number} | undefined> = [];
|
|
1213
|
+
|
|
1214
|
+
const d: string[] = [];
|
|
1215
|
+
const areas: string[] = [];
|
|
1216
|
+
|
|
1217
|
+
const group = SVGNode('g', {class: classes});
|
|
1218
|
+
|
|
1219
|
+
// if (title) node.setAttribute('title', title);
|
|
1220
|
+
this.group.appendChild(group);
|
|
1221
|
+
|
|
1222
|
+
let z_min = z[0] || 0;
|
|
1223
|
+
let z_max = z[0] || 0;
|
|
1224
|
+
|
|
1225
|
+
const map: Map<string, number> = new Map();
|
|
1226
|
+
|
|
1227
|
+
for (let i = 0; i < count; i++) {
|
|
1228
|
+
|
|
1229
|
+
const a = x[i];
|
|
1230
|
+
const b = y[i];
|
|
1231
|
+
|
|
1232
|
+
if (typeof a === 'undefined' || typeof b === 'undefined') {
|
|
1233
|
+
points.push(undefined);
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
|
|
1237
|
+
const series_key = c[i] || '';
|
|
1238
|
+
let series = map.get(series_key);
|
|
1239
|
+
|
|
1240
|
+
if (typeof series === 'undefined') {
|
|
1241
|
+
|
|
1242
|
+
series = map.size + 1;
|
|
1243
|
+
|
|
1244
|
+
map.set(series_key, series);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
let size = z[i] || 0;
|
|
1248
|
+
if (size) {
|
|
1249
|
+
const size_x = size / xrange * area.width;
|
|
1250
|
+
const size_y = size / yrange * area.height;
|
|
1251
|
+
size = Math.min(size_x, size_y);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
points.push({
|
|
1255
|
+
x: area.left + ((a - x_scale.min) / xrange) * area.width,
|
|
1256
|
+
y: area.bottom - ((b - y_scale.min) / yrange) * area.height,
|
|
1257
|
+
z: size,
|
|
1258
|
+
series,
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
{
|
|
1266
|
+
for (const point of points) {
|
|
1267
|
+
if (point) {
|
|
1268
|
+
group.appendChild(SVGNode('circle', {cx: point.x, cy: point.y, r: point.z / 2, class: `point series-${point.series}`}));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1196
1277
|
public RenderScatterSeries(area: Area,
|
|
1197
1278
|
x: Array<number | undefined>,
|
|
1198
1279
|
y: Array<number | undefined>,
|
|
@@ -161,6 +161,14 @@
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
.bubble-chart {
|
|
165
|
+
|
|
166
|
+
stroke-width: 3;
|
|
167
|
+
fill: color-mix(in srgb, currentColor 75%, transparent);
|
|
168
|
+
stroke: currentColor;
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
/* scatter plot line (and marker -- change that class name) */
|
|
165
173
|
.scatter-plot {
|
|
166
174
|
|
|
@@ -65,6 +65,27 @@
|
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
|
+
<div composite>
|
|
69
|
+
<button data-command="border-bottom" data-target="border" title="Bottom border"></button>
|
|
70
|
+
<div class="treb-menu">
|
|
71
|
+
<button dropdown title="Border options"></button>
|
|
72
|
+
<div class="treb-icon-buttons" data-replace="border">
|
|
73
|
+
<button data-command="border-top" title="Top border"></button>
|
|
74
|
+
<button data-command="border-left" title="Left border"></button>
|
|
75
|
+
<button data-command="border-right" title="Right border"></button>
|
|
76
|
+
<button data-command="border-bottom" title="Bottom border"></button>
|
|
77
|
+
<button data-command="border-double-bottom" title="Double bottom border"></button>
|
|
78
|
+
<button data-command="border-outside" title="Outside borders"></button>
|
|
79
|
+
<button data-command="border-all" title="All borders"></button>
|
|
80
|
+
<button data-command="border-none" title="Clear borders"></button>
|
|
81
|
+
<div separator></div>
|
|
82
|
+
<div class="treb-menu treb-color-menu treb-submenu" data-color-command="border-color" data-replace-color="border" title="Border color" data-default-color-text="Default border color">
|
|
83
|
+
<button data-icon="palette" data-color-bar="border" data-color="{}"></button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
68
89
|
<div composite>
|
|
69
90
|
<button data-command="fill-color" data-color-bar="fill" data-color="{}" title="Fill color"></button>
|
|
70
91
|
<div class="treb-menu treb-color-menu" data-color-command="fill-color" data-replace-color="fill" data-default-color-text="No fill">
|
|
@@ -94,28 +115,8 @@
|
|
|
94
115
|
</div>
|
|
95
116
|
</div>
|
|
96
117
|
|
|
97
|
-
<div composite>
|
|
98
|
-
<button data-command="border-bottom" data-target="border" title="Bottom border"></button>
|
|
99
|
-
<div class="treb-menu">
|
|
100
|
-
<button dropdown title="Border options"></button>
|
|
101
|
-
<div class="treb-icon-buttons" data-replace="border">
|
|
102
|
-
<button data-command="border-top" title="Top border"></button>
|
|
103
|
-
<button data-command="border-left" title="Left border"></button>
|
|
104
|
-
<button data-command="border-right" title="Right border"></button>
|
|
105
|
-
<button data-command="border-bottom" title="Bottom border"></button>
|
|
106
|
-
<button data-command="border-double-bottom" title="Double bottom border"></button>
|
|
107
|
-
<button data-command="border-outside" title="Outside borders"></button>
|
|
108
|
-
<button data-command="border-all" title="All borders"></button>
|
|
109
|
-
<button data-command="border-none" title="Clear borders"></button>
|
|
110
|
-
<div separator></div>
|
|
111
|
-
<div class="treb-menu treb-color-menu treb-submenu" data-color-command="border-color" data-replace-color="border" title="Border color" data-default-color-text="Default border color">
|
|
112
|
-
<button data-icon="palette" data-color-bar="border" data-color="{}"></button>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
118
|
<div composite font-scale>
|
|
119
|
+
<div class="treb-font-scale-icon"></div>
|
|
119
120
|
<input class="treb-font-scale" title="Font scale">
|
|
120
121
|
<div class="treb-menu">
|
|
121
122
|
<button dropdown title="Font scale options"></button>
|
|
@@ -131,19 +132,6 @@
|
|
|
131
132
|
</div>
|
|
132
133
|
</div>
|
|
133
134
|
|
|
134
|
-
<div class="treb-menu">
|
|
135
|
-
<button data-icon="layout" title="Rows & columns"></button>
|
|
136
|
-
<div>
|
|
137
|
-
<button data-command="insert-row">Insert row</button>
|
|
138
|
-
<button data-command="insert-column">Insert column</button>
|
|
139
|
-
<button data-command="delete-row">Delete row</button>
|
|
140
|
-
<button data-command="delete-column">Delete column</button>
|
|
141
|
-
<div separator add-remove-sheet></div>
|
|
142
|
-
<button data-command="insert-sheet" add-remove-sheet>Add sheet</button>
|
|
143
|
-
<button data-command="delete-sheet" add-remove-sheet>Delete sheet</button>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
135
|
<div composite>
|
|
148
136
|
<input class="treb-number-format" title="Number format">
|
|
149
137
|
<div class="treb-menu">
|
|
@@ -157,6 +145,19 @@
|
|
|
157
145
|
<button data-command="increase-precision" title="Increase precision"></button>
|
|
158
146
|
</div>
|
|
159
147
|
|
|
148
|
+
<div class="treb-menu">
|
|
149
|
+
<button data-icon="layout" title="Rows & columns"></button>
|
|
150
|
+
<div>
|
|
151
|
+
<button data-command="insert-row">Insert row</button>
|
|
152
|
+
<button data-command="insert-column">Insert column</button>
|
|
153
|
+
<button data-command="delete-row">Delete row</button>
|
|
154
|
+
<button data-command="delete-column">Delete column</button>
|
|
155
|
+
<div separator add-remove-sheet></div>
|
|
156
|
+
<button data-command="insert-sheet" add-remove-sheet>Add sheet</button>
|
|
157
|
+
<button data-command="delete-sheet" add-remove-sheet>Delete sheet</button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
160
161
|
<div composite chart-menu>
|
|
161
162
|
<button data-command="insert-column-chart" data-target="annotation" title="Insert column chart"></button>
|
|
162
163
|
<div class="treb-menu">
|