@vuu-ui/vuu-chart 4.0.0-alpha.21 → 4.0.0-alpha.22
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/README.md +324 -0
- package/package.json +12 -15
- package/src/Chart.css.js +22 -0
- package/src/Chart.js +4 -4
- package/src/ChartSeries.js +20 -3
- package/src/chart-data/ChartDataStore.js +131 -0
- package/src/chart-data/projection-utils.js +119 -0
- package/src/charts/CandlestickChart.js +31 -0
- package/src/charts/CartesianChart.js +28 -0
- package/src/charts/DepthChart.js +28 -0
- package/src/charts/HeatmapChart.js +31 -0
- package/src/charts/HierarchyChart.js +41 -0
- package/src/charts/candlestick-model.js +373 -0
- package/src/charts/cartesian-model.js +234 -0
- package/src/charts/depth-model.js +159 -0
- package/src/charts/heatmap-model.js +243 -0
- package/src/charts/hierarchy-model.js +224 -0
- package/src/echarts.js +26 -0
- package/src/engine/ChartModel.js +2 -0
- package/src/engine/VuuChartContainer.js +196 -0
- package/src/engine/useStructuralMemo.js +18 -0
- package/src/index.js +8 -0
- package/src/options/common.js +181 -0
- package/src/options/mergeOptions.js +37 -0
- package/src/theme/chartTheme.js +74 -0
- package/src/useChartOptions.js +25 -2
- package/src/vuu-chart-types.js +0 -0
- package/types/Chart.d.ts +0 -28
- package/types/ChartSeries.d.ts +0 -32
- package/types/DataRowMovingWindow.d.ts +0 -23
- package/types/chart-types.d.ts +0 -42
- package/types/chartTooltip.d.ts +0 -12
- package/types/index.d.ts +0 -5
- package/types/src/Chart.d.ts +0 -28
- package/types/src/ChartSeries.d.ts +0 -32
- package/types/src/DataRowMovingWindow.d.ts +0 -23
- package/types/src/chart-types.d.ts +0 -42
- package/types/src/chartTooltip.d.ts +0 -12
- package/types/src/index.d.ts +0 -5
- package/types/src/useChartContextMenu.d.ts +0 -16
- package/types/src/useChartOptions.d.ts +0 -28
- package/types/src/useChartSelection.d.ts +0 -14
- package/types/useChartContextMenu.d.ts +0 -16
- package/types/useChartOptions.d.ts +0 -28
- package/types/useChartSelection.d.ts +0 -14
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { Aggregator, colorForRow, filterRows, getColumnValue, getNumber, sortRowsByNumber, toNumber } from "../chart-data/projection-utils.js";
|
|
2
|
+
import { GRID_OUTER_BOUNDS, axisStyle, dataZoomOption, hasSlider, legendOption, markLineOption, textStyle, tooltipOption } from "../options/common.js";
|
|
3
|
+
import { resolveColor } from "../theme/chartTheme.js";
|
|
4
|
+
const seriesIds = (series)=>{
|
|
5
|
+
const ids = new Set();
|
|
6
|
+
return series.map(({ columnName, id = columnName }, i)=>{
|
|
7
|
+
const uniqueId = ids.has(id) ? `${id}-${i}` : id;
|
|
8
|
+
ids.add(uniqueId);
|
|
9
|
+
return uniqueId;
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
const createCartesianModel = ({ aggregation, crosshair = true, dataSource, dataZoom, legend, orientation = "vertical", series, showTooltip = true, xAxis, yAxes = [
|
|
13
|
+
{}
|
|
14
|
+
] })=>{
|
|
15
|
+
const xType = xAxis.type ?? "category";
|
|
16
|
+
const isCategory = "category" === xType;
|
|
17
|
+
const horizontal = "horizontal" === orientation;
|
|
18
|
+
const hasBars = series.some((s)=>"bar" === s.type);
|
|
19
|
+
const ids = seriesIds(series);
|
|
20
|
+
const legendPosition = legend ?? (series.length > 1 ? "top" : "none");
|
|
21
|
+
const dataSources = [
|
|
22
|
+
dataSource
|
|
23
|
+
].concat(series.flatMap((s)=>s.dataSource ? [
|
|
24
|
+
s.dataSource
|
|
25
|
+
] : []));
|
|
26
|
+
const buildBaseOption = (theme)=>{
|
|
27
|
+
const { palette } = theme;
|
|
28
|
+
const categoryAxis = {
|
|
29
|
+
id: "x",
|
|
30
|
+
type: xType,
|
|
31
|
+
name: xAxis.name,
|
|
32
|
+
boundaryGap: isCategory ? hasBars : void 0,
|
|
33
|
+
scale: "value" === xType ? true : void 0,
|
|
34
|
+
...axisStyle(theme, false),
|
|
35
|
+
...isCategory ? {
|
|
36
|
+
data: []
|
|
37
|
+
} : void 0
|
|
38
|
+
};
|
|
39
|
+
const valueAxes = yAxes.map((y, i)=>({
|
|
40
|
+
id: `y${i}`,
|
|
41
|
+
type: y.type ?? "value",
|
|
42
|
+
name: y.name,
|
|
43
|
+
position: y.position ?? (horizontal ? void 0 : 0 === i ? "left" : "right"),
|
|
44
|
+
scale: y.scale ?? !hasBars,
|
|
45
|
+
min: y.min,
|
|
46
|
+
max: y.max,
|
|
47
|
+
...axisStyle(theme, 0 === i)
|
|
48
|
+
}));
|
|
49
|
+
const slider = hasSlider(dataZoom);
|
|
50
|
+
return {
|
|
51
|
+
animation: false,
|
|
52
|
+
backgroundColor: theme.backgroundColor,
|
|
53
|
+
color: palette,
|
|
54
|
+
textStyle: textStyle(theme),
|
|
55
|
+
grid: {
|
|
56
|
+
id: "main",
|
|
57
|
+
left: 8,
|
|
58
|
+
right: 8,
|
|
59
|
+
outerBounds: GRID_OUTER_BOUNDS,
|
|
60
|
+
top: "top" === legendPosition ? 32 : 16,
|
|
61
|
+
bottom: (slider ? 32 : 8) + ("bottom" === legendPosition ? 24 : 0)
|
|
62
|
+
},
|
|
63
|
+
legend: legendOption(theme, legendPosition),
|
|
64
|
+
tooltip: tooltipOption(theme, {
|
|
65
|
+
crosshair,
|
|
66
|
+
show: showTooltip
|
|
67
|
+
}),
|
|
68
|
+
xAxis: horizontal ? valueAxes : [
|
|
69
|
+
categoryAxis
|
|
70
|
+
],
|
|
71
|
+
yAxis: horizontal ? [
|
|
72
|
+
categoryAxis
|
|
73
|
+
] : valueAxes,
|
|
74
|
+
dataZoom: dataZoomOption(dataZoom, theme, [
|
|
75
|
+
0
|
|
76
|
+
], horizontal ? "y" : "x"),
|
|
77
|
+
series: series.map((s, i)=>{
|
|
78
|
+
const color = resolveColor(s.color, theme) ?? palette[i % palette.length];
|
|
79
|
+
const isLine = "line" === s.type || "area" === s.type;
|
|
80
|
+
const axisIndex = s.yAxisIndex ?? 0;
|
|
81
|
+
return {
|
|
82
|
+
id: ids[i],
|
|
83
|
+
name: s.name ?? s.columnName,
|
|
84
|
+
type: isLine ? "line" : s.type,
|
|
85
|
+
color,
|
|
86
|
+
...horizontal ? {
|
|
87
|
+
xAxisIndex: axisIndex
|
|
88
|
+
} : {
|
|
89
|
+
yAxisIndex: axisIndex
|
|
90
|
+
},
|
|
91
|
+
encode: horizontal ? {
|
|
92
|
+
x: 1,
|
|
93
|
+
y: 0
|
|
94
|
+
} : {
|
|
95
|
+
x: 0,
|
|
96
|
+
y: 1
|
|
97
|
+
},
|
|
98
|
+
stack: s.stack,
|
|
99
|
+
...isLine ? {
|
|
100
|
+
step: s.step ?? false,
|
|
101
|
+
smooth: s.smooth ?? false,
|
|
102
|
+
showSymbol: s.showSymbol ?? false,
|
|
103
|
+
symbolSize: s.symbolSize ?? 4,
|
|
104
|
+
connectNulls: true,
|
|
105
|
+
lineStyle: {
|
|
106
|
+
width: 1.5
|
|
107
|
+
},
|
|
108
|
+
areaStyle: "area" === s.type ? {
|
|
109
|
+
opacity: s.areaOpacity ?? 0.25
|
|
110
|
+
} : void 0
|
|
111
|
+
} : void 0,
|
|
112
|
+
..."scatter" === s.type ? {
|
|
113
|
+
symbolSize: s.symbolSize ?? 8
|
|
114
|
+
} : void 0,
|
|
115
|
+
..."bar" === s.type ? {
|
|
116
|
+
barMaxWidth: 40
|
|
117
|
+
} : void 0,
|
|
118
|
+
data: []
|
|
119
|
+
};
|
|
120
|
+
})
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const buildDataOption = (data, theme)=>{
|
|
124
|
+
const { columnMap: mainColumnMap, rows: mainRows } = data.get(dataSource);
|
|
125
|
+
const categories = [];
|
|
126
|
+
const categoryIndex = new Map();
|
|
127
|
+
const addCategory = (value)=>{
|
|
128
|
+
const key = String(value);
|
|
129
|
+
let index = categoryIndex.get(key);
|
|
130
|
+
if (void 0 === index) {
|
|
131
|
+
index = categories.length;
|
|
132
|
+
categories.push(key);
|
|
133
|
+
categoryIndex.set(key, index);
|
|
134
|
+
}
|
|
135
|
+
return index;
|
|
136
|
+
};
|
|
137
|
+
if (isCategory) for (const row of mainRows)addCategory(getColumnValue(row, mainColumnMap, xAxis.columnName));
|
|
138
|
+
const rowLookup = {};
|
|
139
|
+
const seriesData = series.map((s, i)=>{
|
|
140
|
+
const ds = s.dataSource ?? dataSource;
|
|
141
|
+
const { columnMap, rows: allRows } = data.get(ds);
|
|
142
|
+
const xColumn = s.xColumnName ?? xAxis.columnName;
|
|
143
|
+
let rows = filterRows(allRows, columnMap, s.where);
|
|
144
|
+
if (!isCategory) rows = sortRowsByNumber(rows, columnMap, xColumn);
|
|
145
|
+
const items = [];
|
|
146
|
+
const values = [];
|
|
147
|
+
const itemRows = [];
|
|
148
|
+
const pushItem = (x, y, row)=>{
|
|
149
|
+
const color = s.colorBy ? colorForRow(row, columnMap, s.colorBy, theme) : void 0;
|
|
150
|
+
items.push(color ? {
|
|
151
|
+
value: [
|
|
152
|
+
x,
|
|
153
|
+
y
|
|
154
|
+
],
|
|
155
|
+
itemStyle: {
|
|
156
|
+
color
|
|
157
|
+
}
|
|
158
|
+
} : [
|
|
159
|
+
x,
|
|
160
|
+
y
|
|
161
|
+
]);
|
|
162
|
+
values.push(y);
|
|
163
|
+
itemRows.push(row);
|
|
164
|
+
};
|
|
165
|
+
if (isCategory && aggregation) {
|
|
166
|
+
const aggregators = new Map();
|
|
167
|
+
for (const row of rows){
|
|
168
|
+
const x = addCategory(getColumnValue(row, columnMap, xColumn));
|
|
169
|
+
let entry = aggregators.get(x);
|
|
170
|
+
if (void 0 === entry) {
|
|
171
|
+
entry = [
|
|
172
|
+
new Aggregator(aggregation),
|
|
173
|
+
row
|
|
174
|
+
];
|
|
175
|
+
aggregators.set(x, entry);
|
|
176
|
+
}
|
|
177
|
+
entry[0].add(getNumber(row, columnMap, s.columnName));
|
|
178
|
+
}
|
|
179
|
+
const entries = Array.from(aggregators.entries()).sort(([x1], [x2])=>x1 - x2);
|
|
180
|
+
for (const [x, [aggregator, row]] of entries)pushItem(x, aggregator.value, row);
|
|
181
|
+
} else for (const row of rows){
|
|
182
|
+
const xValue = getColumnValue(row, columnMap, xColumn);
|
|
183
|
+
const x = isCategory ? addCategory(xValue) : toNumber(xValue);
|
|
184
|
+
if (null !== x) pushItem(x, getNumber(row, columnMap, s.columnName), row);
|
|
185
|
+
}
|
|
186
|
+
rowLookup[ids[i]] = {
|
|
187
|
+
dataSource: ds,
|
|
188
|
+
rows: itemRows
|
|
189
|
+
};
|
|
190
|
+
const markLine = markLineOption(s.markLines, theme, values, {
|
|
191
|
+
axis: horizontal ? "xAxis" : "yAxis",
|
|
192
|
+
defaultColor: resolveColor(s.color, theme) ?? theme.palette[i % theme.palette.length],
|
|
193
|
+
weights: s.markLines?.reduce((w, def)=>{
|
|
194
|
+
if (void 0 === w && "object" == typeof def.value) {
|
|
195
|
+
const weightColumn = def.value.weightedAverage;
|
|
196
|
+
return itemRows.map((row)=>getNumber(row, columnMap, weightColumn));
|
|
197
|
+
}
|
|
198
|
+
return w;
|
|
199
|
+
}, void 0)
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
id: ids[i],
|
|
203
|
+
data: items,
|
|
204
|
+
...markLine ? {
|
|
205
|
+
markLine
|
|
206
|
+
} : void 0
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
const categoryAxisData = isCategory ? [
|
|
210
|
+
{
|
|
211
|
+
id: "x",
|
|
212
|
+
data: categories
|
|
213
|
+
}
|
|
214
|
+
] : void 0;
|
|
215
|
+
const option = {
|
|
216
|
+
series: seriesData,
|
|
217
|
+
...categoryAxisData ? horizontal ? {
|
|
218
|
+
yAxis: categoryAxisData
|
|
219
|
+
} : {
|
|
220
|
+
xAxis: categoryAxisData
|
|
221
|
+
} : void 0
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
option,
|
|
225
|
+
rowLookup
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
buildBaseOption,
|
|
230
|
+
buildDataOption,
|
|
231
|
+
dataSources
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
export { createCartesianModel, seriesIds };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getColumnValue, getNumber } from "../chart-data/projection-utils.js";
|
|
2
|
+
import { GRID_OUTER_BOUNDS, axisStyle, legendOption, textStyle, tooltipOption } from "../options/common.js";
|
|
3
|
+
const cumulativeDepth = (levels, side)=>{
|
|
4
|
+
const sorted = levels.slice().sort((l1, l2)=>"bid" === side ? l2.price - l1.price : l1.price - l2.price);
|
|
5
|
+
let total = 0;
|
|
6
|
+
return sorted.map((level)=>{
|
|
7
|
+
total = level.cum ?? total + level.size;
|
|
8
|
+
return {
|
|
9
|
+
price: level.price,
|
|
10
|
+
cumSize: total,
|
|
11
|
+
row: level.row
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
const createDepthModel = ({ askValue = "SELL", bidValue = "BUY", cumulativeSizeColumnName, dataSource, legend = "top", priceColumnName = "price", showMidPrice = true, showTooltip = true, sideColumnName = "side", sizeColumnName = "size" })=>{
|
|
16
|
+
const buildBaseOption = (theme)=>{
|
|
17
|
+
const sideSeries = (id, color)=>({
|
|
18
|
+
id,
|
|
19
|
+
name: "bids" === id ? "Bids" : "Asks",
|
|
20
|
+
type: "line",
|
|
21
|
+
step: "bids" === id ? "start" : "end",
|
|
22
|
+
color,
|
|
23
|
+
showSymbol: false,
|
|
24
|
+
lineStyle: {
|
|
25
|
+
width: 1.5
|
|
26
|
+
},
|
|
27
|
+
areaStyle: {
|
|
28
|
+
opacity: 0.25
|
|
29
|
+
},
|
|
30
|
+
encode: {
|
|
31
|
+
x: 0,
|
|
32
|
+
y: 1
|
|
33
|
+
},
|
|
34
|
+
data: []
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
animation: false,
|
|
38
|
+
backgroundColor: theme.backgroundColor,
|
|
39
|
+
textStyle: textStyle(theme),
|
|
40
|
+
grid: {
|
|
41
|
+
id: "main",
|
|
42
|
+
left: 8,
|
|
43
|
+
right: 8,
|
|
44
|
+
outerBounds: GRID_OUTER_BOUNDS,
|
|
45
|
+
top: "top" === legend ? 32 : 16,
|
|
46
|
+
bottom: "bottom" === legend ? 32 : 8
|
|
47
|
+
},
|
|
48
|
+
legend: legendOption(theme, legend),
|
|
49
|
+
tooltip: tooltipOption(theme, {
|
|
50
|
+
show: showTooltip
|
|
51
|
+
}),
|
|
52
|
+
xAxis: [
|
|
53
|
+
{
|
|
54
|
+
id: "x",
|
|
55
|
+
type: "value",
|
|
56
|
+
scale: true,
|
|
57
|
+
...axisStyle(theme, false)
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
yAxis: [
|
|
61
|
+
{
|
|
62
|
+
id: "y0",
|
|
63
|
+
type: "value",
|
|
64
|
+
position: "right",
|
|
65
|
+
...axisStyle(theme)
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
series: [
|
|
69
|
+
sideSeries("bids", theme.upColor),
|
|
70
|
+
sideSeries("asks", theme.downColor)
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const buildDataOption = (data, theme)=>{
|
|
75
|
+
const { columnMap, rows } = data.get(dataSource);
|
|
76
|
+
const bids = [];
|
|
77
|
+
const asks = [];
|
|
78
|
+
for (const row of rows){
|
|
79
|
+
const price = getNumber(row, columnMap, priceColumnName);
|
|
80
|
+
if (null === price) continue;
|
|
81
|
+
const side = String(getColumnValue(row, columnMap, sideColumnName));
|
|
82
|
+
const level = {
|
|
83
|
+
price,
|
|
84
|
+
size: getNumber(row, columnMap, sizeColumnName) ?? 0,
|
|
85
|
+
cum: cumulativeSizeColumnName ? getNumber(row, columnMap, cumulativeSizeColumnName) ?? void 0 : void 0,
|
|
86
|
+
row
|
|
87
|
+
};
|
|
88
|
+
if (side === bidValue) bids.push(level);
|
|
89
|
+
else if (side === askValue) asks.push(level);
|
|
90
|
+
}
|
|
91
|
+
const bidPoints = cumulativeDepth(bids, "bid").reverse();
|
|
92
|
+
const askPoints = cumulativeDepth(asks, "ask");
|
|
93
|
+
const bestBid = bidPoints.at(-1)?.price;
|
|
94
|
+
const bestAsk = askPoints[0]?.price;
|
|
95
|
+
const midPrice = void 0 !== bestBid && void 0 !== bestAsk ? (bestBid + bestAsk) / 2 : void 0;
|
|
96
|
+
const rowLookup = {
|
|
97
|
+
bids: {
|
|
98
|
+
dataSource,
|
|
99
|
+
rows: bidPoints.map((p)=>p.row)
|
|
100
|
+
},
|
|
101
|
+
asks: {
|
|
102
|
+
dataSource,
|
|
103
|
+
rows: askPoints.map((p)=>p.row)
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const option = {
|
|
107
|
+
series: [
|
|
108
|
+
{
|
|
109
|
+
id: "bids",
|
|
110
|
+
data: bidPoints.map((p)=>[
|
|
111
|
+
p.price,
|
|
112
|
+
p.cumSize
|
|
113
|
+
]),
|
|
114
|
+
markLine: {
|
|
115
|
+
silent: true,
|
|
116
|
+
symbol: [
|
|
117
|
+
"none",
|
|
118
|
+
"none"
|
|
119
|
+
],
|
|
120
|
+
animation: false,
|
|
121
|
+
data: showMidPrice && void 0 !== midPrice ? [
|
|
122
|
+
{
|
|
123
|
+
xAxis: midPrice,
|
|
124
|
+
lineStyle: {
|
|
125
|
+
color: theme.crosshairColor,
|
|
126
|
+
type: "dashed",
|
|
127
|
+
width: 1
|
|
128
|
+
},
|
|
129
|
+
label: {
|
|
130
|
+
color: theme.secondaryForegroundColor,
|
|
131
|
+
formatter: `Mid ${+midPrice.toFixed(6)}`
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
] : []
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "asks",
|
|
139
|
+
data: askPoints.map((p)=>[
|
|
140
|
+
p.price,
|
|
141
|
+
p.cumSize
|
|
142
|
+
])
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
option,
|
|
148
|
+
rowLookup
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
buildBaseOption,
|
|
153
|
+
buildDataOption,
|
|
154
|
+
dataSources: [
|
|
155
|
+
dataSource
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
export { createDepthModel, cumulativeDepth };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Aggregator, filterRows, getColumnValue, getNumber, toNumber } from "../chart-data/projection-utils.js";
|
|
2
|
+
import { axisStyle, dataZoomOption, formatTimeLabel, hasSlider, textStyle, tooltipOption } from "../options/common.js";
|
|
3
|
+
const decimalPlaces = (n)=>{
|
|
4
|
+
const [, fraction = ""] = String(n).split(".");
|
|
5
|
+
return fraction.length;
|
|
6
|
+
};
|
|
7
|
+
const bucketFloor = (value, bucketSize)=>{
|
|
8
|
+
const bucket = Math.floor(value / bucketSize + 1e-9) * bucketSize;
|
|
9
|
+
return Number(bucket.toFixed(decimalPlaces(bucketSize)));
|
|
10
|
+
};
|
|
11
|
+
const bucketValue = (value, axis)=>{
|
|
12
|
+
const isNumeric = "time" === axis.type || "value" === axis.type;
|
|
13
|
+
if (isNumeric || void 0 !== axis.bucketSize) {
|
|
14
|
+
const num = toNumber(value);
|
|
15
|
+
if (null === num) return null;
|
|
16
|
+
return axis.bucketSize ? bucketFloor(num, axis.bucketSize) : num;
|
|
17
|
+
}
|
|
18
|
+
return null == value ? null : String(value);
|
|
19
|
+
};
|
|
20
|
+
const sortKeys = (keys, axis)=>"time" === axis.type || "value" === axis.type || void 0 !== axis.bucketSize ? keys.slice().sort((k1, k2)=>Number(k1) - Number(k2)) : keys;
|
|
21
|
+
const createHeatmapModel = ({ aggregation = "sum", colorScale = "sequential", dataSource, dataZoom, formatTime = formatTimeLabel, max, min, showTooltip = true, showVisualMap = true, valueColumnName, where, xAxis, yAxis })=>{
|
|
22
|
+
const labelFormatter = (axis)=>"time" === axis.type ? (value)=>formatTime(Number(value)) : void 0;
|
|
23
|
+
const diverging = "diverging" === colorScale;
|
|
24
|
+
const visualMapRange = (hasValues, minValue, maxValue)=>{
|
|
25
|
+
if (diverging) {
|
|
26
|
+
const extent = hasValues ? Math.max(Math.abs(minValue), Math.abs(maxValue), 1) : 1;
|
|
27
|
+
return {
|
|
28
|
+
min: min ?? -extent,
|
|
29
|
+
max: max ?? extent
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
min: min ?? (hasValues ? Math.min(0, minValue) : 0),
|
|
34
|
+
max: max ?? (hasValues ? Math.max(maxValue, minValue + 1) : 1)
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
let xLabels = [];
|
|
38
|
+
let yLabels = [];
|
|
39
|
+
const formatKey = (key, axis)=>void 0 !== key && "time" === axis.type ? formatTime(Number(key)) : key ?? "";
|
|
40
|
+
const buildBaseOption = (theme)=>{
|
|
41
|
+
const slider = hasSlider(dataZoom);
|
|
42
|
+
return {
|
|
43
|
+
animation: false,
|
|
44
|
+
backgroundColor: theme.backgroundColor,
|
|
45
|
+
textStyle: textStyle(theme),
|
|
46
|
+
grid: {
|
|
47
|
+
id: "main",
|
|
48
|
+
left: 8,
|
|
49
|
+
right: showVisualMap ? 56 : 8,
|
|
50
|
+
top: 16,
|
|
51
|
+
bottom: slider ? 36 : 8
|
|
52
|
+
},
|
|
53
|
+
tooltip: {
|
|
54
|
+
...tooltipOption(theme, {
|
|
55
|
+
show: showTooltip,
|
|
56
|
+
trigger: "item"
|
|
57
|
+
}),
|
|
58
|
+
formatter: ({ value })=>{
|
|
59
|
+
const [x, y, v] = value;
|
|
60
|
+
return `${xAxis.name ?? xAxis.columnName}: ${formatKey(xLabels[x], xAxis)}<br/>${yAxis.name ?? yAxis.columnName}: ${formatKey(yLabels[y], yAxis)}<br/>${valueColumnName}: <b>${v ?? "-"}</b>`;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
xAxis: [
|
|
64
|
+
{
|
|
65
|
+
id: "x",
|
|
66
|
+
type: "category",
|
|
67
|
+
name: xAxis.name,
|
|
68
|
+
data: [],
|
|
69
|
+
splitArea: {
|
|
70
|
+
show: false
|
|
71
|
+
},
|
|
72
|
+
...axisStyle(theme, false),
|
|
73
|
+
axisLabel: {
|
|
74
|
+
...axisStyle(theme).axisLabel,
|
|
75
|
+
formatter: labelFormatter(xAxis)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
yAxis: [
|
|
80
|
+
{
|
|
81
|
+
id: "y0",
|
|
82
|
+
type: "category",
|
|
83
|
+
name: yAxis.name,
|
|
84
|
+
data: [],
|
|
85
|
+
...axisStyle(theme, false),
|
|
86
|
+
axisLabel: {
|
|
87
|
+
...axisStyle(theme).axisLabel,
|
|
88
|
+
formatter: labelFormatter(yAxis)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
visualMap: [
|
|
93
|
+
{
|
|
94
|
+
id: "vm",
|
|
95
|
+
type: "continuous",
|
|
96
|
+
show: showVisualMap,
|
|
97
|
+
calculable: true,
|
|
98
|
+
orient: "vertical",
|
|
99
|
+
right: 0,
|
|
100
|
+
top: "center",
|
|
101
|
+
itemHeight: 120,
|
|
102
|
+
itemWidth: 12,
|
|
103
|
+
min: min ?? 0,
|
|
104
|
+
max: max ?? 1,
|
|
105
|
+
inRange: {
|
|
106
|
+
color: diverging ? [
|
|
107
|
+
theme.downColor,
|
|
108
|
+
theme.splitLineColor,
|
|
109
|
+
theme.upColor
|
|
110
|
+
] : theme.heatmapColors
|
|
111
|
+
},
|
|
112
|
+
textStyle: {
|
|
113
|
+
color: theme.secondaryForegroundColor
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
dataZoom: dataZoomOption(dataZoom, theme, [
|
|
118
|
+
0
|
|
119
|
+
]),
|
|
120
|
+
series: [
|
|
121
|
+
{
|
|
122
|
+
id: "heatmap",
|
|
123
|
+
name: valueColumnName,
|
|
124
|
+
type: "heatmap",
|
|
125
|
+
encode: {
|
|
126
|
+
x: 0,
|
|
127
|
+
y: 1,
|
|
128
|
+
value: 2
|
|
129
|
+
},
|
|
130
|
+
progressive: 0,
|
|
131
|
+
emphasis: {
|
|
132
|
+
itemStyle: {
|
|
133
|
+
borderColor: theme.foregroundColor,
|
|
134
|
+
borderWidth: 1
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
data: []
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
const buildDataOption = (data)=>{
|
|
143
|
+
const { columnMap, rows: allRows } = data.get(dataSource);
|
|
144
|
+
const rows = filterRows(allRows, columnMap, where);
|
|
145
|
+
const cells = new Map();
|
|
146
|
+
const xKeys = new Set();
|
|
147
|
+
const yKeys = new Set();
|
|
148
|
+
for (const row of rows){
|
|
149
|
+
const x = bucketValue(getColumnValue(row, columnMap, xAxis.columnName), xAxis);
|
|
150
|
+
const y = bucketValue(getColumnValue(row, columnMap, yAxis.columnName), yAxis);
|
|
151
|
+
if (null === x || null === y) continue;
|
|
152
|
+
xKeys.add(x);
|
|
153
|
+
yKeys.add(y);
|
|
154
|
+
const key = `${x}|${y}`;
|
|
155
|
+
let cell = cells.get(key);
|
|
156
|
+
if (void 0 === cell) {
|
|
157
|
+
cell = {
|
|
158
|
+
x,
|
|
159
|
+
y,
|
|
160
|
+
agg: new Aggregator(aggregation),
|
|
161
|
+
rows: []
|
|
162
|
+
};
|
|
163
|
+
cells.set(key, cell);
|
|
164
|
+
}
|
|
165
|
+
cell.agg.add(getNumber(row, columnMap, valueColumnName));
|
|
166
|
+
cell.rows.push(row);
|
|
167
|
+
}
|
|
168
|
+
const xCategories = sortKeys(Array.from(xKeys), xAxis);
|
|
169
|
+
const yCategories = sortKeys(Array.from(yKeys), yAxis);
|
|
170
|
+
const xIndex = new Map(xCategories.map((k, i)=>[
|
|
171
|
+
k,
|
|
172
|
+
i
|
|
173
|
+
]));
|
|
174
|
+
const yIndex = new Map(yCategories.map((k, i)=>[
|
|
175
|
+
k,
|
|
176
|
+
i
|
|
177
|
+
]));
|
|
178
|
+
let minValue = 1 / 0;
|
|
179
|
+
let maxValue = -1 / 0;
|
|
180
|
+
const items = [];
|
|
181
|
+
const itemRows = [];
|
|
182
|
+
for (const cell of cells.values()){
|
|
183
|
+
const value = cell.agg.value;
|
|
184
|
+
if (null !== value) {
|
|
185
|
+
minValue = Math.min(minValue, value);
|
|
186
|
+
maxValue = Math.max(maxValue, value);
|
|
187
|
+
}
|
|
188
|
+
items.push([
|
|
189
|
+
xIndex.get(cell.x) ?? 0,
|
|
190
|
+
yIndex.get(cell.y) ?? 0,
|
|
191
|
+
value
|
|
192
|
+
]);
|
|
193
|
+
itemRows.push(cell.rows[0]);
|
|
194
|
+
}
|
|
195
|
+
const hasValues = Number.isFinite(minValue);
|
|
196
|
+
xLabels = xCategories.map(String);
|
|
197
|
+
yLabels = yCategories.map(String);
|
|
198
|
+
const rowLookup = {
|
|
199
|
+
heatmap: {
|
|
200
|
+
dataSource,
|
|
201
|
+
rows: itemRows
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const option = {
|
|
205
|
+
xAxis: [
|
|
206
|
+
{
|
|
207
|
+
id: "x",
|
|
208
|
+
data: xLabels
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
yAxis: [
|
|
212
|
+
{
|
|
213
|
+
id: "y0",
|
|
214
|
+
data: yLabels
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
visualMap: [
|
|
218
|
+
{
|
|
219
|
+
id: "vm",
|
|
220
|
+
...visualMapRange(hasValues, minValue, maxValue)
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
series: [
|
|
224
|
+
{
|
|
225
|
+
id: "heatmap",
|
|
226
|
+
data: items
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
option,
|
|
232
|
+
rowLookup
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
buildBaseOption,
|
|
237
|
+
buildDataOption,
|
|
238
|
+
dataSources: [
|
|
239
|
+
dataSource
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
export { createHeatmapModel };
|