@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.
@@ -1,126 +1,9 @@
1
- import { T as rowRuleStyles, _ as tableColumns, f as isBuiltinChart, h as percentColumn, o as chartCardStyle, u as customChartProps } from "../layout-nViz3MdU.js";
2
- import { a as formatCurrency, c as measureFormat, d as useDashboard, i as fieldLabel, l as measureFormatter, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as DashboardProvider } from "../ModelDictionary-Dib4Vk3i.js";
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: clickToFilter ? series ? [dimension, series] : [dimension] : void 0,
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 (hideEmpty) data = data.filter((point) => bars.some((bar) => Number(point[bar.dataKey])));
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: showLabels ? 96 : 16,
353
+ right: labelsOutside ? 96 : 16,
405
354
  left: 8,
406
355
  bottom: 4
407
356
  } : {
408
- top: showLabels ? 24 : 8,
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: [clickToFilter && data.map((point) => /* @__PURE__ */ jsx(Cell, { fillOpacity: isDimmed(point, bar) ? .35 : 1 }, String(point.value))), showLabels && !stacked && /* @__PURE__ */ jsx(LabelList, {
471
- dataKey: bar.dataKey,
472
- position: horizontal ? "right" : "top",
473
- formatter: (value) => bar.format(value),
474
- style: {
475
- fontSize: 12,
476
- fill: "#333",
477
- fontWeight: 600
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
- alignItems: "center",
1140
- gap: 2,
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
- height: "100%",
1148
- flex: "0 0 50%",
1149
- minWidth: 140
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: card.shadow ?? 0,
1406
+ elevation: 0,
1345
1407
  sx: {
1346
- p: px(card.padding) ?? 1.5,
1408
+ p: 1.5,
1347
1409
  minWidth: 0,
1348
1410
  height: "100%",
1349
- bgcolor: card.background,
1350
- border: `${card.borderWidth ?? 1}px solid`,
1351
- borderColor: card.borderColor ?? "divider",
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
- color: card.titleColor,
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
- gap: current.cards?.gap === void 0 ? gap : `${current.cards.gap}px`,
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