@bluemarble/bi 0.6.4 → 0.6.6
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/{DashboardDevtools-CSRt9lib.js → DashboardDevtools-I00Iz0Z8.js} +424 -240
- package/dist/DashboardDevtools-I00Iz0Z8.js.map +1 -0
- package/dist/{ModelDictionary-Dib4Vk3i.js → ModelDictionary-DUGFHBo-.js} +123 -6
- package/dist/ModelDictionary-DUGFHBo-.js.map +1 -0
- package/dist/cli.js +167 -11
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +65 -7
- package/dist/client/index.js +222 -161
- package/dist/client/index.js.map +1 -1
- package/dist/{codegen-DkfI6UJr.js → codegen-LoQzFAle.js} +3 -1
- package/dist/codegen-LoQzFAle.js.map +1 -0
- package/dist/{layout-CoGyGWYM.d.ts → formatRules-DTKbov47.d.ts} +272 -207
- package/dist/{layout-nViz3MdU.js → layout-CrZ00tB0.js} +187 -15
- package/dist/layout-CrZ00tB0.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/DashboardDevtools-CSRt9lib.js.map +0 -1
- package/dist/ModelDictionary-Dib4Vk3i.js.map +0 -1
- package/dist/codegen-DkfI6UJr.js.map +0 -1
- package/dist/layout-nViz3MdU.js.map +0 -1
package/dist/client/index.js
CHANGED
|
@@ -1,126 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as formatCurrency, c as measureFormat, d as
|
|
1
|
+
import { D as matchesValueRule, O as rowRuleStyles, d as customChartProps, g as percentColumn, o as cardsGap, p as isBuiltinChart, s as chartCardStyle, v as tableColumns } from "../layout-CrZ00tB0.js";
|
|
2
|
+
import { S as layoutItemKey, _ as useClickFilter, a as formatCurrency, c as measureFormat, d as OTHER_COLOR, f as SERIES_COLORS, g as seriesColor, h as numberOf, i as fieldLabel, l as measureFormatter, m as chartPlaceholder, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as TooltipBox, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as Legend, v as DashboardProvider, x as LAYOUT_ITEM_ATTRIBUTE, y as useDashboard } from "../ModelDictionary-DUGFHBo-.js";
|
|
3
3
|
import { Box, Checkbox, CircularProgress, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Paper, Select, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, Typography } from "@mui/material";
|
|
4
4
|
import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, ComposedChart, LabelList, Line, Pie, PieChart, ResponsiveContainer, Tooltip as Tooltip$1, XAxis, YAxis } from "recharts";
|
|
5
5
|
import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
//#region src/client/chartKit.tsx
|
|
8
|
-
/**
|
|
9
|
-
* Peças comuns dos gráficos ligados ao modelo: paleta, filtro por clique, legenda e os estados de
|
|
10
|
-
* carregando/erro/vazio.
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* Paleta categórica, sempre nesta ordem (a série N tem sempre a mesma cor). Laranja Rocha primeiro;
|
|
14
|
-
* a ordem passa no validador de daltonismo (CVD ΔE ≥ 9 entre vizinhas) e de visão normal (≥ 19).
|
|
15
|
-
*/
|
|
16
|
-
const SERIES_COLORS = [
|
|
17
|
-
"#F58220",
|
|
18
|
-
"#2a78d6",
|
|
19
|
-
"#1baf7a",
|
|
20
|
-
"#eda100",
|
|
21
|
-
"#e87ba4",
|
|
22
|
-
"#008300",
|
|
23
|
-
"#6250d6",
|
|
24
|
-
"#e34948"
|
|
25
|
-
];
|
|
26
|
-
/** "Outros" (fatias agrupadas) fica neutro, fora da paleta. */
|
|
27
|
-
const OTHER_COLOR = "#9e9e9e";
|
|
28
|
-
const seriesColor = (index) => SERIES_COLORS[index % SERIES_COLORS.length];
|
|
29
|
-
/** Valor numérico de uma coluna da linha (`null` quando vazio). */
|
|
30
|
-
const numberOf = (row, key) => key && row[key] !== null && row[key] !== void 0 ? Number(row[key]) : null;
|
|
31
|
-
/**
|
|
32
|
-
* Filtro cruzado a partir do gráfico: clicar num valor filtra o dashboard por ele (Ctrl/Cmd+clique
|
|
33
|
-
* acumula; clicar de novo no único selecionado limpa). O gráfico deve ignorar o próprio filtro na
|
|
34
|
-
* consulta (`ignoreFilters`) e só destacar a seleção.
|
|
35
|
-
*/
|
|
36
|
-
function useClickFilter(dimension, enabled) {
|
|
37
|
-
const { filters, setFilter } = useDashboard();
|
|
38
|
-
const selected = enabled && dimension ? filters[dimension] ?? [] : [];
|
|
39
|
-
const toggle = (value, event) => {
|
|
40
|
-
if (!enabled || !dimension) return;
|
|
41
|
-
if (event?.ctrlKey || event?.metaKey) setFilter(dimension, selected.includes(value) ? selected.filter((item) => item !== value) : [...selected, value]);
|
|
42
|
-
else setFilter(dimension, selected.length === 1 && selected[0] === value ? [] : [value]);
|
|
43
|
-
};
|
|
44
|
-
return {
|
|
45
|
-
selected,
|
|
46
|
-
/** Há seleção e este valor não está nela. */
|
|
47
|
-
isDimmed: (value) => selected.length > 0 && !selected.includes(value),
|
|
48
|
-
toggle,
|
|
49
|
-
clear: () => {
|
|
50
|
-
if (enabled && dimension && selected.length) setFilter(dimension, []);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function Legend({ items, loading }) {
|
|
55
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
56
|
-
sx: {
|
|
57
|
-
display: "flex",
|
|
58
|
-
flexWrap: "wrap",
|
|
59
|
-
columnGap: 1.5,
|
|
60
|
-
rowGap: .5,
|
|
61
|
-
mb: 1,
|
|
62
|
-
fontSize: 14,
|
|
63
|
-
color: "#555",
|
|
64
|
-
minHeight: 21
|
|
65
|
-
},
|
|
66
|
-
children: [items.map((item) => /* @__PURE__ */ jsxs(Box, {
|
|
67
|
-
sx: {
|
|
68
|
-
display: "flex",
|
|
69
|
-
alignItems: "center",
|
|
70
|
-
gap: .5
|
|
71
|
-
},
|
|
72
|
-
children: [/* @__PURE__ */ jsx(Box, { sx: {
|
|
73
|
-
width: 9,
|
|
74
|
-
height: 9,
|
|
75
|
-
borderRadius: "50%",
|
|
76
|
-
bgcolor: item.color
|
|
77
|
-
} }), item.label]
|
|
78
|
-
}, item.label)), loading && /* @__PURE__ */ jsx(CircularProgress, {
|
|
79
|
-
size: 14,
|
|
80
|
-
sx: { ml: "auto" }
|
|
81
|
-
})]
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
/** Erro / carregando / sem dados no lugar do gráfico; `null` quando há o que desenhar (função, não componente). */
|
|
85
|
-
function chartPlaceholder({ height, error, isLoading, empty }) {
|
|
86
|
-
if (!error && !empty) return null;
|
|
87
|
-
return /* @__PURE__ */ jsx(Box, {
|
|
88
|
-
sx: {
|
|
89
|
-
height,
|
|
90
|
-
display: "flex",
|
|
91
|
-
alignItems: "center",
|
|
92
|
-
justifyContent: "center",
|
|
93
|
-
color: error ? "error.main" : "#999"
|
|
94
|
-
},
|
|
95
|
-
children: error ? "Erro ao carregar o gráfico" : isLoading ? "Carregando…" : "Sem dados para os filtros selecionados"
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/** `caption`: o que é o título (ex.: o rótulo da dimensão), em cinza acima dele. */
|
|
99
|
-
function TooltipBox({ title, caption, children }) {
|
|
100
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
101
|
-
sx: {
|
|
102
|
-
bgcolor: "#fff",
|
|
103
|
-
border: "1px solid #ccc",
|
|
104
|
-
p: 1,
|
|
105
|
-
fontSize: 13
|
|
106
|
-
},
|
|
107
|
-
children: [
|
|
108
|
-
caption && /* @__PURE__ */ jsx(Box, {
|
|
109
|
-
sx: {
|
|
110
|
-
fontSize: 11,
|
|
111
|
-
color: "#777"
|
|
112
|
-
},
|
|
113
|
-
children: caption
|
|
114
|
-
}),
|
|
115
|
-
/* @__PURE__ */ jsx(Box, {
|
|
116
|
-
sx: { fontWeight: 700 },
|
|
117
|
-
children: title
|
|
118
|
-
}),
|
|
119
|
-
children
|
|
120
|
-
]
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
//#endregion
|
|
124
7
|
//#region src/client/seriesPivot.ts
|
|
125
8
|
const EMPTY_SERIES_LABEL = "(em branco)";
|
|
126
9
|
const OTHERS$1 = "Outros";
|
|
@@ -197,9 +80,52 @@ function pivotSeries(rows, dimension, series, measure, items, format = String) {
|
|
|
197
80
|
}
|
|
198
81
|
/** Soma das séries de um ponto (a altura da barra empilhada). */
|
|
199
82
|
const pointTotal = (point, items) => items.reduce((total, item) => total + Number(point[item.dataKey] ?? 0), 0);
|
|
83
|
+
/**
|
|
84
|
+
* Todas as categorias da dimensão (consulta só da dimensão, sem medidas), na ordem dela, com os
|
|
85
|
+
* valores dos pontos que vieram da consulta e zero nas que não têm dado (o "Mostrar itens sem
|
|
86
|
+
* dados" do Power BI). Ponto fora da lista (não deveria haver) vai no fim.
|
|
87
|
+
*/
|
|
88
|
+
function fillCategories(points, categories, dimension, dataKeys, format = String) {
|
|
89
|
+
const byValue = new Map(points.map((point) => [String(point.value), point]));
|
|
90
|
+
const seen = /* @__PURE__ */ new Set();
|
|
91
|
+
const filled = [];
|
|
92
|
+
for (const row of categories) {
|
|
93
|
+
const value = row[dimension] ?? "";
|
|
94
|
+
const key = String(value);
|
|
95
|
+
if (seen.has(key)) continue;
|
|
96
|
+
seen.add(key);
|
|
97
|
+
const point = byValue.get(key);
|
|
98
|
+
if (point) filled.push(point);
|
|
99
|
+
else filled.push({
|
|
100
|
+
value,
|
|
101
|
+
label: format(value),
|
|
102
|
+
...Object.fromEntries(dataKeys.map((dataKey) => [dataKey, 0]))
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return [...filled, ...points.filter((point) => !seen.has(String(point.value)))];
|
|
106
|
+
}
|
|
200
107
|
//#endregion
|
|
201
108
|
//#region src/client/BarChart.tsx
|
|
202
109
|
const MAX_VALUE_LABELS = 30;
|
|
110
|
+
/** Coluna do total da pilha no ponto (para o rótulo do `showTotal`). */
|
|
111
|
+
const TOTAL_KEY = "\0total";
|
|
112
|
+
/** Posição do valor no Recharts, conforme a orientação. */
|
|
113
|
+
const LABEL_POSITION = {
|
|
114
|
+
vertical: {
|
|
115
|
+
outside: "top",
|
|
116
|
+
insideEnd: "insideTop",
|
|
117
|
+
center: "center",
|
|
118
|
+
insideBase: "insideBottom"
|
|
119
|
+
},
|
|
120
|
+
horizontal: {
|
|
121
|
+
outside: "right",
|
|
122
|
+
insideEnd: "insideRight",
|
|
123
|
+
center: "center",
|
|
124
|
+
insideBase: "insideLeft"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/** Chave de uma série em `colors`: o valor, "(em branco)" ou "Outros". */
|
|
128
|
+
const seriesColorKey = (item) => item.others ? "Outros" : item.value === null ? EMPTY_SERIES_LABEL : String(item.value);
|
|
203
129
|
/** Altura de cada categoria no modo horizontal (barras finas, com respiro). */
|
|
204
130
|
const ROW_HEIGHT = 28;
|
|
205
131
|
/**
|
|
@@ -208,13 +134,14 @@ const ROW_HEIGHT = 28;
|
|
|
208
134
|
* Com `series`, uma medida quebrada pelos valores de outra dimensão:
|
|
209
135
|
* `<BarChart dimension="mes" series="tipo_descarga" measures={['volume']} stacked />`.
|
|
210
136
|
*/
|
|
211
|
-
function BarChart({ dimension, measures: measureList, series, formatSeries = String, maxSeries = 6, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showValues = true, clickToFilter = false, height = 360, labels, formats: formatsByMeasure, queryLabel }) {
|
|
137
|
+
function BarChart({ dimension, measures: measureList, series, formatSeries = String, maxSeries = 6, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showEmpty = false, showValues = true, valuePosition = "outside", valueColor, valueSize = 12, showTotal = false, valueRule, hideUnselected = false, colors, clickToFilter = false, height = 360, labels, formats: formatsByMeasure, queryLabel }) {
|
|
212
138
|
const { meta, filters, setFilter } = useDashboard();
|
|
139
|
+
const dimming = clickToFilter && !hideUnselected;
|
|
213
140
|
const measures = series ? measureList.slice(0, 1) : measureList;
|
|
214
141
|
const { rows, isLoading, error, inspectProps } = useModelQuery({
|
|
215
142
|
dimensions: series ? [dimension, series] : [dimension],
|
|
216
143
|
measures,
|
|
217
|
-
ignoreFilters:
|
|
144
|
+
ignoreFilters: dimming ? series ? [dimension, series] : [dimension] : void 0,
|
|
218
145
|
label: queryLabel ?? `BarChart · ${dimension}`
|
|
219
146
|
});
|
|
220
147
|
const domain = useModelQuery({
|
|
@@ -224,6 +151,13 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
224
151
|
label: `${queryLabel ?? `BarChart · ${dimension}`} · séries`,
|
|
225
152
|
skip: !series
|
|
226
153
|
});
|
|
154
|
+
const table = meta?.dimensions[dimension]?.table;
|
|
155
|
+
const categories = useModelQuery({
|
|
156
|
+
dimensions: [dimension],
|
|
157
|
+
ignoreFilters: Object.keys(filters).filter((name) => dimming && name === dimension || !table || meta?.dimensions[name]?.table !== table),
|
|
158
|
+
label: `${queryLabel ?? `BarChart · ${dimension}`} · categorias`,
|
|
159
|
+
skip: !showEmpty || !meta
|
|
160
|
+
});
|
|
227
161
|
const filter = useClickFilter(dimension, clickToFilter);
|
|
228
162
|
const seriesFilter = useClickFilter(series, clickToFilter);
|
|
229
163
|
const formats = measures.map((measure) => measureFormatter(meta, measure, 0, formatsByMeasure));
|
|
@@ -241,6 +175,7 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
241
175
|
const pivot = pivotSeries(rows, dimension, series, first, seriesItems(domain.rows, series, first, maxSeries, formatSeries), formatDimension);
|
|
242
176
|
bars = pivot.items.map((item) => ({
|
|
243
177
|
...item,
|
|
178
|
+
color: colors?.[seriesColorKey(item)] ?? item.color,
|
|
244
179
|
item,
|
|
245
180
|
format
|
|
246
181
|
}));
|
|
@@ -249,7 +184,7 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
249
184
|
bars = measures.map((measure, index) => ({
|
|
250
185
|
dataKey: measure,
|
|
251
186
|
label: labelOf(measure),
|
|
252
|
-
color: seriesColor(index),
|
|
187
|
+
color: colors?.[measure] ?? seriesColor(index),
|
|
253
188
|
format: formats[index] ?? String
|
|
254
189
|
}));
|
|
255
190
|
data = rows.map((row) => {
|
|
@@ -262,18 +197,32 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
262
197
|
return point;
|
|
263
198
|
});
|
|
264
199
|
}
|
|
265
|
-
if (
|
|
200
|
+
if (showEmpty) data = fillCategories(data, categories.rows, dimension, bars.map((bar) => bar.dataKey), formatDimension);
|
|
201
|
+
else if (hideEmpty) data = data.filter((point) => bars.some((bar) => Number(point[bar.dataKey])));
|
|
266
202
|
const sortValue = (point) => series ? pointTotal(point, bars.map((bar) => bar.item).filter((item) => !!item)) : Number(point[first ?? ""]);
|
|
267
203
|
if (sort !== "dimension" && first) data = [...data].sort((a, b) => sort === "desc" ? sortValue(b) - sortValue(a) : sortValue(a) - sortValue(b));
|
|
268
204
|
if (limit) data = data.slice(0, limit);
|
|
269
205
|
const horizontal = orientation === "horizontal";
|
|
270
206
|
const showLabels = showValues && data.length <= MAX_VALUE_LABELS;
|
|
207
|
+
const segmentLabels = showLabels && (!stacked || valuePosition !== "outside");
|
|
208
|
+
const totalLabel = showLabels && stacked && showTotal && bars.length > 0;
|
|
209
|
+
const labelsOutside = segmentLabels && valuePosition === "outside" || totalLabel;
|
|
210
|
+
if (totalLabel) data = data.map((point) => ({
|
|
211
|
+
...point,
|
|
212
|
+
[TOTAL_KEY]: bars.reduce((total, bar) => total + Number(point[bar.dataKey] ?? 0), 0)
|
|
213
|
+
}));
|
|
214
|
+
const labelStyle = (outside) => ({
|
|
215
|
+
fontSize: valueSize,
|
|
216
|
+
fill: valueColor ?? (outside ? "#333" : "#fff"),
|
|
217
|
+
fontWeight: 600
|
|
218
|
+
});
|
|
219
|
+
const position = LABEL_POSITION[orientation === "horizontal" ? "horizontal" : "vertical"];
|
|
271
220
|
const chartHeight = horizontal ? Math.max(height, data.length * ROW_HEIGHT + 24) : height;
|
|
272
221
|
const longest = Math.max(0, ...data.map((point) => point.label.length));
|
|
273
|
-
const loading = isLoading || domain.isLoading;
|
|
222
|
+
const loading = isLoading || domain.isLoading || categories.isLoading || showEmpty && !meta;
|
|
274
223
|
const state = chartPlaceholder({
|
|
275
224
|
height,
|
|
276
|
-
error: error ?? domain.error,
|
|
225
|
+
error: error ?? domain.error ?? categories.error,
|
|
277
226
|
isLoading: loading,
|
|
278
227
|
empty: !data.length
|
|
279
228
|
});
|
|
@@ -401,11 +350,11 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
401
350
|
barCategoryGap: "20%",
|
|
402
351
|
margin: horizontal ? {
|
|
403
352
|
top: 4,
|
|
404
|
-
right:
|
|
353
|
+
right: labelsOutside ? 96 : 16,
|
|
405
354
|
left: 8,
|
|
406
355
|
bottom: 4
|
|
407
356
|
} : {
|
|
408
|
-
top:
|
|
357
|
+
top: labelsOutside ? valueSize + 12 : 8,
|
|
409
358
|
right: 8,
|
|
410
359
|
left: 8,
|
|
411
360
|
bottom: 0
|
|
@@ -467,16 +416,25 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
|
|
|
467
416
|
isAnimationActive: false,
|
|
468
417
|
onClick: clickToFilter ? (_bar, barIndex, event) => handleClick(barIndex, bar, event) : void 0,
|
|
469
418
|
cursor: clickToFilter ? "pointer" : void 0,
|
|
470
|
-
children: [
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
419
|
+
children: [
|
|
420
|
+
clickToFilter && data.map((point) => /* @__PURE__ */ jsx(Cell, { fillOpacity: isDimmed(point, bar) ? .35 : 1 }, String(point.value))),
|
|
421
|
+
segmentLabels && /* @__PURE__ */ jsx(LabelList, {
|
|
422
|
+
dataKey: bar.dataKey,
|
|
423
|
+
position: position[valuePosition],
|
|
424
|
+
formatter: (value) => stacked && !Number(value) || !matchesValueRule(valueRule, Number(value)) ? "" : bar.format(value),
|
|
425
|
+
style: labelStyle(valuePosition === "outside")
|
|
426
|
+
}),
|
|
427
|
+
totalLabel && index === bars.length - 1 && /* @__PURE__ */ jsx(LabelList, {
|
|
428
|
+
dataKey: TOTAL_KEY,
|
|
429
|
+
position: position.outside,
|
|
430
|
+
formatter: (value) => matchesValueRule(valueRule, Number(value)) ? (bars[0]?.format ?? String)(value) : "",
|
|
431
|
+
style: {
|
|
432
|
+
...labelStyle(true),
|
|
433
|
+
...valuePosition !== "outside" && { fill: "#333" },
|
|
434
|
+
fontWeight: 700
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
]
|
|
480
438
|
}, bar.dataKey))
|
|
481
439
|
]
|
|
482
440
|
})
|
|
@@ -1064,11 +1022,22 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
|
|
|
1064
1022
|
//#endregion
|
|
1065
1023
|
//#region src/client/DonutChart.tsx
|
|
1066
1024
|
const OTHERS = "Outros";
|
|
1025
|
+
/** Fatia abaixo dessa fração fica sem valor de fora (os rótulos se atropelariam); tem o tooltip. */
|
|
1026
|
+
const MIN_VALUE_SHARE = .02;
|
|
1027
|
+
const RADIAN = Math.PI / 180;
|
|
1028
|
+
/** Direção da caixa (rosca + legenda) conforme o lado da legenda. */
|
|
1029
|
+
const LEGEND_DIRECTION = {
|
|
1030
|
+
right: "row",
|
|
1031
|
+
left: "row-reverse",
|
|
1032
|
+
top: "column-reverse",
|
|
1033
|
+
bottom: "column"
|
|
1034
|
+
};
|
|
1067
1035
|
/**
|
|
1068
|
-
* Rosca de uma medida por uma dimensão, com o total no centro e a legenda ao lado (nome, % e valor)
|
|
1036
|
+
* Rosca de uma medida por uma dimensão, com o total no centro e a legenda ao lado (nome, % e valor)
|
|
1037
|
+
* ou em cima/embaixo; `showValues` põe o valor de cada fatia do lado de fora.
|
|
1069
1038
|
* Ex.: `<DonutChart dimension="operacao_status" measure="custoTotal" />`.
|
|
1070
1039
|
*/
|
|
1071
|
-
function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, labels, formats, percentDecimals = 1, queryLabel }) {
|
|
1040
|
+
function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, labels, formats, percentDecimals = 1, legendPosition = "right", showValues = false, valueContent = "both", showTotal = true, queryLabel }) {
|
|
1072
1041
|
const { meta } = useDashboard();
|
|
1073
1042
|
const { rows, isLoading, error, inspectProps } = useModelQuery({
|
|
1074
1043
|
dimensions: [dimension],
|
|
@@ -1111,6 +1080,26 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1111
1080
|
if (slice?.value !== null && slice?.value !== void 0) filter.toggle(slice.value, event);
|
|
1112
1081
|
};
|
|
1113
1082
|
const dimmed = (slice) => filter.selected.length > 0 && (slice.value === null || filter.isDimmed(slice.value));
|
|
1083
|
+
const valueText = (amount) => valueContent === "value" ? format(amount) : valueContent === "percent" ? percent(amount) : `${format(amount)} (${percent(amount)})`;
|
|
1084
|
+
const SliceValue = ({ cx, cy, midAngle, outerRadius, payload }) => {
|
|
1085
|
+
const slice = payload;
|
|
1086
|
+
if (slice.amount / total < MIN_VALUE_SHARE) return null;
|
|
1087
|
+
const radius = outerRadius + 10;
|
|
1088
|
+
const cos = Math.cos(-midAngle * RADIAN);
|
|
1089
|
+
const x = cx + radius * cos;
|
|
1090
|
+
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
|
1091
|
+
return /* @__PURE__ */ jsx("text", {
|
|
1092
|
+
x,
|
|
1093
|
+
y,
|
|
1094
|
+
textAnchor: Math.abs(cos) < .15 ? "middle" : cos > 0 ? "start" : "end",
|
|
1095
|
+
dominantBaseline: "central",
|
|
1096
|
+
fontSize: 12,
|
|
1097
|
+
fill: "#333",
|
|
1098
|
+
opacity: dimmed(slice) ? .45 : 1,
|
|
1099
|
+
children: valueText(slice.amount)
|
|
1100
|
+
});
|
|
1101
|
+
};
|
|
1102
|
+
const side = legendPosition === "left" || legendPosition === "right";
|
|
1114
1103
|
const SliceTooltip = ({ active, payload }) => {
|
|
1115
1104
|
if (!active || !payload?.length) return null;
|
|
1116
1105
|
const slice = payload[0].payload;
|
|
@@ -1136,17 +1125,21 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1136
1125
|
sx: {
|
|
1137
1126
|
height,
|
|
1138
1127
|
display: "flex",
|
|
1139
|
-
|
|
1140
|
-
|
|
1128
|
+
flexDirection: LEGEND_DIRECTION[legendPosition],
|
|
1129
|
+
alignItems: side ? "center" : "stretch",
|
|
1130
|
+
gap: side ? 2 : 1,
|
|
1141
1131
|
opacity: isLoading ? .5 : 1,
|
|
1142
1132
|
transition: "opacity .2s"
|
|
1143
1133
|
},
|
|
1144
1134
|
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1145
1135
|
sx: {
|
|
1146
1136
|
position: "relative",
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1137
|
+
minWidth: 140,
|
|
1138
|
+
minHeight: 0,
|
|
1139
|
+
...side ? {
|
|
1140
|
+
height: "100%",
|
|
1141
|
+
flex: showValues ? "1 1 0" : "0 0 50%"
|
|
1142
|
+
} : { flex: "1 1 0" }
|
|
1150
1143
|
},
|
|
1151
1144
|
children: [/* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
1152
1145
|
width: "100%",
|
|
@@ -1155,13 +1148,15 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1155
1148
|
data: slices,
|
|
1156
1149
|
dataKey: "amount",
|
|
1157
1150
|
nameKey: "label",
|
|
1158
|
-
innerRadius: "62%",
|
|
1159
|
-
outerRadius: "92%",
|
|
1151
|
+
innerRadius: showValues ? "46%" : "62%",
|
|
1152
|
+
outerRadius: showValues ? "68%" : "92%",
|
|
1160
1153
|
startAngle: 90,
|
|
1161
1154
|
endAngle: -270,
|
|
1162
1155
|
stroke: "#fff",
|
|
1163
1156
|
strokeWidth: 2,
|
|
1164
1157
|
isAnimationActive: false,
|
|
1158
|
+
label: showValues ? SliceValue : false,
|
|
1159
|
+
labelLine: false,
|
|
1165
1160
|
onClick: (_, index, event) => select(slices[index], event),
|
|
1166
1161
|
cursor: clickToFilter ? "pointer" : void 0,
|
|
1167
1162
|
children: slices.map((slice) => /* @__PURE__ */ jsx(Cell, {
|
|
@@ -1169,7 +1164,7 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1169
1164
|
fillOpacity: dimmed(slice) ? .35 : 1
|
|
1170
1165
|
}, slice.key))
|
|
1171
1166
|
})] })
|
|
1172
|
-
}), /* @__PURE__ */ jsxs(Box, {
|
|
1167
|
+
}), showTotal && /* @__PURE__ */ jsxs(Box, {
|
|
1173
1168
|
sx: {
|
|
1174
1169
|
position: "absolute",
|
|
1175
1170
|
inset: 0,
|
|
@@ -1195,14 +1190,15 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1195
1190
|
children: format(total)
|
|
1196
1191
|
})]
|
|
1197
1192
|
})]
|
|
1198
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
1193
|
+
}), side ? /* @__PURE__ */ jsx(Box, {
|
|
1199
1194
|
component: "ul",
|
|
1200
1195
|
sx: {
|
|
1201
1196
|
listStyle: "none",
|
|
1202
1197
|
m: 0,
|
|
1203
1198
|
p: 0,
|
|
1204
1199
|
minWidth: 0,
|
|
1205
|
-
flex: 1,
|
|
1200
|
+
flex: showValues ? "0 1 auto" : 1,
|
|
1201
|
+
maxWidth: showValues ? "45%" : void 0,
|
|
1206
1202
|
overflow: "auto",
|
|
1207
1203
|
maxHeight: "100%"
|
|
1208
1204
|
},
|
|
@@ -1253,6 +1249,43 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
|
|
|
1253
1249
|
})
|
|
1254
1250
|
]
|
|
1255
1251
|
}, slice.key))
|
|
1252
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
1253
|
+
component: "ul",
|
|
1254
|
+
sx: {
|
|
1255
|
+
listStyle: "none",
|
|
1256
|
+
m: 0,
|
|
1257
|
+
p: 0,
|
|
1258
|
+
display: "flex",
|
|
1259
|
+
flexWrap: "wrap",
|
|
1260
|
+
justifyContent: "center",
|
|
1261
|
+
columnGap: 1.5,
|
|
1262
|
+
rowGap: .25,
|
|
1263
|
+
flex: "0 0 auto",
|
|
1264
|
+
maxHeight: "40%",
|
|
1265
|
+
overflow: "auto"
|
|
1266
|
+
},
|
|
1267
|
+
children: slices.map((slice) => /* @__PURE__ */ jsxs(Box, {
|
|
1268
|
+
component: "li",
|
|
1269
|
+
title: `${format(slice.amount)} · ${percent(slice.amount)}`,
|
|
1270
|
+
onClick: (event) => select(slice, event),
|
|
1271
|
+
sx: {
|
|
1272
|
+
display: "flex",
|
|
1273
|
+
alignItems: "center",
|
|
1274
|
+
gap: .5,
|
|
1275
|
+
fontSize: 13,
|
|
1276
|
+
color: "#333",
|
|
1277
|
+
whiteSpace: "nowrap",
|
|
1278
|
+
opacity: dimmed(slice) ? .45 : 1,
|
|
1279
|
+
cursor: clickToFilter && slice.value !== null ? "pointer" : void 0
|
|
1280
|
+
},
|
|
1281
|
+
children: [/* @__PURE__ */ jsx(Box, { sx: {
|
|
1282
|
+
width: 10,
|
|
1283
|
+
height: 10,
|
|
1284
|
+
borderRadius: "50%",
|
|
1285
|
+
bgcolor: slice.color,
|
|
1286
|
+
flex: "0 0 auto"
|
|
1287
|
+
} }), slice.label]
|
|
1288
|
+
}, slice.key))
|
|
1256
1289
|
})]
|
|
1257
1290
|
})
|
|
1258
1291
|
});
|
|
@@ -1338,26 +1371,52 @@ function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, q
|
|
|
1338
1371
|
}
|
|
1339
1372
|
//#endregion
|
|
1340
1373
|
//#region src/client/DashboardCharts.tsx
|
|
1374
|
+
const px = (value) => value === void 0 ? void 0 : `${value}px`;
|
|
1375
|
+
/** Tira as chaves `undefined`, para o estilo do layout só sobrepor o que foi definido. */
|
|
1376
|
+
const defined = (object) => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
|
|
1377
|
+
/**
|
|
1378
|
+
* `sx` da moldura de um card com o estilo do layout (`card` do `panel`). Só traz o que o layout
|
|
1379
|
+
* definiu, para um `panel` do app espalhar por cima do visual dele:
|
|
1380
|
+
* `<Paper sx={{ p: 2, borderRadius: 0, ...chartCardSx(card) }}>`.
|
|
1381
|
+
*/
|
|
1382
|
+
function chartCardSx(card) {
|
|
1383
|
+
return {
|
|
1384
|
+
boxSizing: "border-box",
|
|
1385
|
+
...defined({
|
|
1386
|
+
p: px(card.padding),
|
|
1387
|
+
bgcolor: card.background,
|
|
1388
|
+
borderWidth: px(card.borderWidth),
|
|
1389
|
+
borderStyle: card.borderWidth === void 0 ? void 0 : "solid",
|
|
1390
|
+
borderColor: card.borderColor,
|
|
1391
|
+
borderRadius: px(card.borderRadius),
|
|
1392
|
+
boxShadow: card.shadow
|
|
1393
|
+
})
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
/** `sx` do título de um card com o estilo do layout (idem `chartCardSx`). */
|
|
1397
|
+
function chartTitleSx(card) {
|
|
1398
|
+
return defined({
|
|
1399
|
+
color: card.titleColor,
|
|
1400
|
+
fontSize: px(card.titleSize),
|
|
1401
|
+
textAlign: card.titleAlign
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1341
1404
|
function DefaultPanel({ title, card, children }) {
|
|
1342
|
-
const px = (value) => value === void 0 ? void 0 : `${value}px`;
|
|
1343
1405
|
return /* @__PURE__ */ jsxs(Paper, {
|
|
1344
|
-
elevation:
|
|
1406
|
+
elevation: 0,
|
|
1345
1407
|
sx: {
|
|
1346
|
-
p:
|
|
1408
|
+
p: 1.5,
|
|
1347
1409
|
minWidth: 0,
|
|
1348
1410
|
height: "100%",
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
borderRadius: px(card.borderRadius)
|
|
1411
|
+
border: "1px solid",
|
|
1412
|
+
borderColor: "divider",
|
|
1413
|
+
...chartCardSx(card)
|
|
1353
1414
|
},
|
|
1354
1415
|
children: [title && /* @__PURE__ */ jsx(Typography, {
|
|
1355
1416
|
sx: {
|
|
1356
1417
|
fontWeight: 700,
|
|
1357
1418
|
mb: 1,
|
|
1358
|
-
|
|
1359
|
-
fontSize: px(card.titleSize),
|
|
1360
|
-
textAlign: card.titleAlign
|
|
1419
|
+
...chartTitleSx(card)
|
|
1361
1420
|
},
|
|
1362
1421
|
children: title
|
|
1363
1422
|
}), children]
|
|
@@ -1378,10 +1437,12 @@ function DashboardCharts({ layout, formatters = {}, panel: Panel = DefaultPanel,
|
|
|
1378
1437
|
const { charts } = current;
|
|
1379
1438
|
const fixed = useFixedCanvas();
|
|
1380
1439
|
const columns = `repeat(12, minmax(0, 1fr))`;
|
|
1440
|
+
const space = cardsGap(current.cards);
|
|
1381
1441
|
return /* @__PURE__ */ jsx(Box, {
|
|
1382
1442
|
sx: {
|
|
1383
1443
|
display: "grid",
|
|
1384
|
-
|
|
1444
|
+
rowGap: px(space.row) ?? gap,
|
|
1445
|
+
columnGap: px(space.column) ?? gap,
|
|
1385
1446
|
gridTemplateColumns: fixed ? columns : {
|
|
1386
1447
|
xs: "1fr",
|
|
1387
1448
|
lg: columns
|
|
@@ -1563,6 +1624,6 @@ function DashboardFilters({ layout, formatters = {}, gap = 2 }) {
|
|
|
1563
1624
|
});
|
|
1564
1625
|
}
|
|
1565
1626
|
//#endregion
|
|
1566
|
-
export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartPlaceholder, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
1627
|
+
export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartCardSx, chartPlaceholder, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
1567
1628
|
|
|
1568
1629
|
//# sourceMappingURL=index.js.map
|