mviz 1.6.3 → 1.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/README.md +8 -8
- package/dist/charts/area.js +3 -2
- package/dist/charts/bar.js +4 -3
- package/dist/charts/bubble.js +9 -7
- package/dist/charts/combo.js +5 -3
- package/dist/charts/dumbbell.js +15 -14
- package/dist/charts/funnel.js +12 -7
- package/dist/charts/heatmap.js +8 -6
- package/dist/charts/line.js +3 -2
- package/dist/charts/scatter.js +7 -5
- package/dist/cli.js +4 -3
- package/dist/components/table-interactivity.d.ts +53 -0
- package/dist/components/table-interactivity.js +183 -0
- package/dist/components/table.d.ts +6 -1
- package/dist/components/table.js +53 -12
- package/dist/core/export.js +7 -2
- package/dist/core/formatting.d.ts +28 -4
- package/dist/core/formatting.js +81 -17
- package/dist/core/lint-rules/registry.d.ts +4 -2
- package/dist/core/lint-rules/registry.js +6 -1
- package/dist/core/lint-rules/rules/index.d.ts +1 -0
- package/dist/core/lint-rules/rules/index.js +1 -0
- package/dist/core/lint-rules/rules/pct-scalar-gt-one.d.ts +13 -0
- package/dist/core/lint-rules/rules/pct-scalar-gt-one.js +46 -0
- package/dist/core/lint-rules/types.d.ts +12 -0
- package/dist/core/linter.d.ts +7 -2
- package/dist/core/linter.js +8 -4
- package/dist/core/themes.js +10 -5
- package/dist/layout/parser.d.ts +2 -2
- package/dist/layout/parser.js +94 -22
- package/dist/layout/templates.js +23 -19
- package/package.json +7 -7
- package/schema/mviz.v1.schema.json +44 -1
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ title: My Report
|
|
|
156
156
|
## Section Name
|
|
157
157
|
|
|
158
158
|
```big_value size=[4,2]
|
|
159
|
-
{"value": 125000, "label": "Revenue", "format": "
|
|
159
|
+
{"value": 125000, "label": "Revenue", "format": "currency0m"}
|
|
160
160
|
```
|
|
161
161
|
```delta size=[4,2]
|
|
162
162
|
{"value": 0.15, "label": "vs Last Month", "format": "pct0"}
|
|
@@ -200,7 +200,7 @@ Tables support inline sparkline columns for trend visualization:
|
|
|
200
200
|
"type": "table",
|
|
201
201
|
"columns": [
|
|
202
202
|
{"id": "product", "title": "Product"},
|
|
203
|
-
{"id": "sales", "title": "Sales", "fmt": "
|
|
203
|
+
{"id": "sales", "title": "Sales", "fmt": "currency"},
|
|
204
204
|
{"id": "trend", "title": "Trend", "type": "sparkline", "sparkType": "line"},
|
|
205
205
|
{"id": "progress", "title": "Goal", "type": "sparkline", "sparkType": "pct_bar", "width": 100}
|
|
206
206
|
],
|
|
@@ -217,10 +217,10 @@ Sparkline types: `line`, `bar`, `area`, `pct_bar` (progress bar), `dumbbell` (be
|
|
|
217
217
|
| Format | Output | Description |
|
|
218
218
|
|--------|--------|-------------|
|
|
219
219
|
| `auto` | 1.000m, 10.00k | **Smart auto-format (default)** |
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
220
|
+
| `currency_auto` | $1.000m, $10.00k | Smart auto-format with currency symbol |
|
|
221
|
+
| `currency0m` | $1.2m | Millions |
|
|
222
|
+
| `currency0k` | $125k | Compact thousands |
|
|
223
|
+
| `currency` | $1,250,000 | Full currency |
|
|
224
224
|
| `pct0` | 15% | Percentage integer |
|
|
225
225
|
| `pct` | 15.0% | Percentage with decimal |
|
|
226
226
|
| `pct1` | 15.0% | Percentage with 1 decimal |
|
|
@@ -234,7 +234,7 @@ Chart axes automatically detect the appropriate format based on field names:
|
|
|
234
234
|
|
|
235
235
|
| Field Pattern | Auto Format | Example |
|
|
236
236
|
|---------------|-------------|---------|
|
|
237
|
-
| revenue, sales, price, cost, profit | `
|
|
237
|
+
| revenue, sales, price, cost, profit | `currency_auto` | $1.250m |
|
|
238
238
|
| pct, percent, rate, ratio | `pct` or `pct0` | 15.0% |
|
|
239
239
|
| All other fields | `auto` | 1.250m |
|
|
240
240
|
|
|
@@ -375,7 +375,7 @@ The skill is automatically available when working in this project directory.
|
|
|
375
375
|
|
|
376
376
|
## Dependencies
|
|
377
377
|
|
|
378
|
-
- Node.js
|
|
378
|
+
- Node.js 24.12+
|
|
379
379
|
|
|
380
380
|
## Design Philosophy
|
|
381
381
|
|
package/dist/charts/area.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { FONT_SIZE_XXS, LEGEND_ITEM_WIDTH, LEGEND_ITEM_HEIGHT, LEGEND_ITEM_GAP, DEFAULT_CHART_HEIGHT, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, getAxisFormatterJs, inferAxisType, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, getAxisFormatterJs, inferAxisType, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
8
8
|
/**
|
|
9
9
|
* Build ECharts options for an area chart
|
|
10
10
|
*/
|
|
@@ -24,7 +24,8 @@ export function buildAreaOptions(spec) {
|
|
|
24
24
|
const sampleValue = data.length > 0 && data[0] ? data[0][firstYKey] ?? 0 : 0;
|
|
25
25
|
const valueFormat = spec.format ?? inferFormat(firstYKey, sampleValue);
|
|
26
26
|
const currency = resolveCurrency(spec.currency);
|
|
27
|
-
const
|
|
27
|
+
const pctMultiply = shouldPctMultiply(valueFormat, collectNumericFieldValues(data, yKeys));
|
|
28
|
+
const axisFormatter = getAxisFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
28
29
|
// Detect x-axis type: time, value, or category
|
|
29
30
|
const xAxisType = spec.xAxisType ?? inferAxisType(categories);
|
|
30
31
|
// Get axis label config based on axis type
|
package/dist/charts/bar.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { BAR_MAX_WIDTH, LEGEND_ITEM_WIDTH, LEGEND_ITEM_HEIGHT, LEGEND_ITEM_GAP, FONT_SIZE_TINY, FONT_SIZE_XXS, DEFAULT_CHART_HEIGHT, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, getLabelFormatterJs, getAxisFormatterJs, inferAxisType, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, getLabelFormatterJs, getAxisFormatterJs, inferAxisType, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
8
8
|
/**
|
|
9
9
|
* Build ECharts options for a bar chart
|
|
10
10
|
*/
|
|
@@ -24,8 +24,9 @@ export function buildBarOptions(spec) {
|
|
|
24
24
|
const sampleValue = data.length > 0 && data[0] ? data[0][firstYKey] ?? 0 : 0;
|
|
25
25
|
const valueFormat = spec.format ?? inferFormat(firstYKey, sampleValue);
|
|
26
26
|
const currency = resolveCurrency(spec.currency);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const pctMultiply = shouldPctMultiply(valueFormat, collectNumericFieldValues(data, yKeys));
|
|
28
|
+
const labelFormatter = getLabelFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
29
|
+
const axisFormatter = getAxisFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
29
30
|
// Detect category axis type (this is x-axis for vertical, y-axis for horizontal)
|
|
30
31
|
const categoryAxisType = spec.xAxisType ?? inferAxisType(categories);
|
|
31
32
|
// For horizontal bars, x-axis is always 'value'; for vertical it's the category axis type
|
package/dist/charts/bubble.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { FONT_SIZE_TINY, DEFAULT_CHART_HEIGHT, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, getAxisFormatterJs, formatValue, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, getAxisFormatterJs, formatValue, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
8
8
|
import { buildPointLabelConfig, groupDataBySeries, buildSeriesLegend } from '../core/chart-helpers.js';
|
|
9
9
|
/** Min/max bubble pixel sizes */
|
|
10
10
|
const BUBBLE_SIZE_MIN = 10;
|
|
@@ -91,10 +91,10 @@ function buildBubbleSeries(data, ctx, seriesField, showLabels, palette, colors)
|
|
|
91
91
|
/**
|
|
92
92
|
* Build tooltip config for bubble chart.
|
|
93
93
|
*/
|
|
94
|
-
function buildBubbleTooltip(ctx, seriesField, xFormat, yFormat, currency, colors) {
|
|
94
|
+
function buildBubbleTooltip(ctx, seriesField, xFormat, yFormat, currency, colors, xPctMultiply = true, yPctMultiply = true) {
|
|
95
95
|
const labelIdx = ctx.labelField ? BUBBLE_LABEL_INDEX : -1;
|
|
96
|
-
const xFormatExpr = formatValue(xFormat, currency.symbol, currency.locale);
|
|
97
|
-
const yFormatExpr = formatValue(yFormat, currency.symbol, currency.locale);
|
|
96
|
+
const xFormatExpr = formatValue(xFormat, currency.symbol, currency.locale, xPctMultiply);
|
|
97
|
+
const yFormatExpr = formatValue(yFormat, currency.symbol, currency.locale, yPctMultiply);
|
|
98
98
|
const seriesNameLine = seriesField
|
|
99
99
|
? "if (params.seriesName) result += '<strong>' + params.seriesName + '</strong><br/>';"
|
|
100
100
|
: '';
|
|
@@ -157,6 +157,8 @@ export function buildBubbleOptions(spec) {
|
|
|
157
157
|
const xFormat = spec.xFormat ?? inferFormat(xField, xSample);
|
|
158
158
|
const yFormat = spec.yFormat ?? inferFormat(yField, ySample);
|
|
159
159
|
const currency = resolveCurrency(spec.currency);
|
|
160
|
+
const xPctMultiply = shouldPctMultiply(xFormat, collectNumericFieldValues(data, [xField]));
|
|
161
|
+
const yPctMultiply = shouldPctMultiply(yFormat, collectNumericFieldValues(data, [yField]));
|
|
160
162
|
// Build context for point construction
|
|
161
163
|
const xAxis = detectAxisCategories(data, xField);
|
|
162
164
|
const yAxis = detectAxisCategories(data, yField);
|
|
@@ -174,14 +176,14 @@ export function buildBubbleOptions(spec) {
|
|
|
174
176
|
backgroundColor: 'transparent',
|
|
175
177
|
animation: false,
|
|
176
178
|
color: palette,
|
|
177
|
-
tooltip: buildBubbleTooltip(ctx, seriesField, xFormat, yFormat, currency, colors),
|
|
179
|
+
tooltip: buildBubbleTooltip(ctx, seriesField, xFormat, yFormat, currency, colors, xPctMultiply, yPctMultiply),
|
|
178
180
|
grid: {
|
|
179
181
|
left: '6%', right: '8%',
|
|
180
182
|
top: hasLegend ? '14%' : '12%',
|
|
181
183
|
bottom: '14%', containLabel: true,
|
|
182
184
|
},
|
|
183
|
-
xAxis: buildBubbleAxis(xField, xAxis.isCategory, xAxis.categories, getAxisFormatterJs(xFormat, currency.symbol, currency.locale), 24, colors),
|
|
184
|
-
yAxis: buildBubbleAxis(yField, yAxis.isCategory, yAxis.categories, getAxisFormatterJs(yFormat, currency.symbol, currency.locale), 32, colors),
|
|
185
|
+
xAxis: buildBubbleAxis(xField, xAxis.isCategory, xAxis.categories, getAxisFormatterJs(xFormat, currency.symbol, currency.locale, xPctMultiply), 24, colors),
|
|
186
|
+
yAxis: buildBubbleAxis(yField, yAxis.isCategory, yAxis.categories, getAxisFormatterJs(yFormat, currency.symbol, currency.locale, yPctMultiply), 32, colors),
|
|
185
187
|
series: seriesList,
|
|
186
188
|
};
|
|
187
189
|
if (hasLegend) {
|
package/dist/charts/combo.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { FONT_SIZE_XXS, BAR_MAX_WIDTH, LEGEND_ITEM_WIDTH, LEGEND_ITEM_HEIGHT, LEGEND_ITEM_GAP, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
|
-
import { inferAxisType, inferFormat, getAxisFormatterJs, resolveCurrency } from '../core/formatting.js';
|
|
6
|
+
import { inferAxisType, inferFormat, getAxisFormatterJs, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
7
7
|
import { registerChart, registerOptions } from './registry.js';
|
|
8
8
|
/**
|
|
9
9
|
* Build ECharts options for a combo chart (bar + line with optional dual axis)
|
|
@@ -24,11 +24,13 @@ export function buildComboOptions(spec) {
|
|
|
24
24
|
const barSample = data.length > 0 && data[0] && barKeys[0] ? data[0][barKeys[0]] ?? 0 : 0;
|
|
25
25
|
const primaryFormat = spec.format ?? inferFormat(barKeys[0] ?? 'value', barSample);
|
|
26
26
|
const currency = resolveCurrency(spec.currency);
|
|
27
|
-
const
|
|
27
|
+
const primaryPctMultiply = shouldPctMultiply(primaryFormat, collectNumericFieldValues(data, barKeys));
|
|
28
|
+
const primaryAxisFormatter = getAxisFormatterJs(primaryFormat, currency.symbol, currency.locale, primaryPctMultiply);
|
|
28
29
|
// Infer format for secondary y-axis (line if dual axis)
|
|
29
30
|
const lineSample = data.length > 0 && data[0] && lineKeys[0] ? data[0][lineKeys[0]] ?? 0 : 0;
|
|
30
31
|
const secondaryFormat = spec.secondaryFormat ?? inferFormat(lineKeys[0] ?? 'value', lineSample);
|
|
31
|
-
const
|
|
32
|
+
const secondaryPctMultiply = shouldPctMultiply(secondaryFormat, collectNumericFieldValues(data, lineKeys));
|
|
33
|
+
const secondaryAxisFormatter = getAxisFormatterJs(secondaryFormat, currency.symbol, currency.locale, secondaryPctMultiply);
|
|
32
34
|
// Detect x-axis type
|
|
33
35
|
const xAxisType = inferAxisType(categories);
|
|
34
36
|
// Get axis label config based on axis type
|
package/dist/charts/dumbbell.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { COLORS, FONT_SIZE_TINY, DUMBBELL_SYMBOL_SIZE, DEFAULT_CHART_HEIGHT, getThemeColors, } from '../core/themes.js';
|
|
8
8
|
import { wrapHtml } from '../core/serializer.js';
|
|
9
|
-
import { inferFormat, getAxisFormatterJs, formatValue, formatNumber, resolveCurrency, localeFixed } from '../core/formatting.js';
|
|
9
|
+
import { inferFormat, getAxisFormatterJs, formatValue, formatNumber, resolveCurrency, localeFixed, shouldPctMultiply, } from '../core/formatting.js';
|
|
10
10
|
import { registerChart, registerOptions } from './registry.js';
|
|
11
11
|
// Directional colors
|
|
12
12
|
const POSITIVE_COLOR = COLORS.POSITIVE_GREEN;
|
|
@@ -50,7 +50,7 @@ const COMPACT_LABEL_CHAR_THRESHOLD = 5;
|
|
|
50
50
|
* Compute the compact dumbbell label string for a given value.
|
|
51
51
|
* Mirrors the JS formatter logic so we can measure character count at build time.
|
|
52
52
|
*/
|
|
53
|
-
function compactFormatLabel(value, fmt, currencySymbol = '$', locale = 'en-US') {
|
|
53
|
+
function compactFormatLabel(value, fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
54
54
|
if (fmt === 'auto' || fmt === 'currency_auto') {
|
|
55
55
|
const abs = Math.abs(value);
|
|
56
56
|
const p = fmt === 'currency_auto' ? currencySymbol : '';
|
|
@@ -76,13 +76,13 @@ function compactFormatLabel(value, fmt, currencySymbol = '$', locale = 'en-US')
|
|
|
76
76
|
}
|
|
77
77
|
return value < 0 ? '(' + r + ')' : r;
|
|
78
78
|
}
|
|
79
|
-
return formatNumber(value, fmt);
|
|
79
|
+
return formatNumber(value, fmt, false, undefined, pctMultiply);
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* Get the appropriate font size for a dumbbell dot label.
|
|
83
83
|
*/
|
|
84
|
-
function dumbbellLabelFontSize(value, fmt, currencySymbol = '$', locale = 'en-US') {
|
|
85
|
-
const label = compactFormatLabel(value, fmt, currencySymbol, locale);
|
|
84
|
+
function dumbbellLabelFontSize(value, fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
85
|
+
const label = compactFormatLabel(value, fmt, currencySymbol, locale, pctMultiply);
|
|
86
86
|
return label.length >= COMPACT_LABEL_CHAR_THRESHOLD
|
|
87
87
|
? DUMBBELL_LABEL_FONT_SMALL
|
|
88
88
|
: DUMBBELL_LABEL_FONT;
|
|
@@ -92,7 +92,7 @@ function dumbbellLabelFontSize(value, fmt, currencySymbol = '$', locale = 'en-US
|
|
|
92
92
|
* Dumbbell stores values as [value, categoryIndex], so we extract value from params.value[0].
|
|
93
93
|
* Uses compact formatting (~4-5 chars max) to fit inside small dot symbols.
|
|
94
94
|
*/
|
|
95
|
-
function buildDumbbellLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
95
|
+
function buildDumbbellLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
96
96
|
if (fmt === 'auto' || fmt === 'currency_auto') {
|
|
97
97
|
const isCurrency = fmt === 'currency_auto' ? 'true' : 'false';
|
|
98
98
|
const symbolStr = JSON.stringify(currencySymbol);
|
|
@@ -124,14 +124,14 @@ function buildDumbbellLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US'
|
|
|
124
124
|
return neg ? '(' + r + ')' : r;
|
|
125
125
|
}`;
|
|
126
126
|
}
|
|
127
|
-
const expr = formatValue(fmt, currencySymbol, locale);
|
|
127
|
+
const expr = formatValue(fmt, currencySymbol, locale, pctMultiply);
|
|
128
128
|
return `function(params) { var value = params.value[0]; if (value == null) return ''; return ${expr}; }`;
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* Build a tooltip value formatting expression for dumbbell charts.
|
|
132
132
|
* Tooltip can be more verbose than dot labels, so uses full smart formatting.
|
|
133
133
|
*/
|
|
134
|
-
function buildDumbbellTooltipExpr(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
134
|
+
function buildDumbbellTooltipExpr(fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
135
135
|
if (fmt === 'auto' || fmt === 'currency_auto') {
|
|
136
136
|
const isCurrency = fmt === 'currency_auto' ? 'true' : 'false';
|
|
137
137
|
const symbolStr = JSON.stringify(currencySymbol);
|
|
@@ -153,7 +153,7 @@ function buildDumbbellTooltipExpr(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
|
153
153
|
return neg ? '(' + r + ')' : r;
|
|
154
154
|
})(val)`;
|
|
155
155
|
}
|
|
156
|
-
const expr = formatValue(fmt, currencySymbol, locale);
|
|
156
|
+
const expr = formatValue(fmt, currencySymbol, locale, pctMultiply);
|
|
157
157
|
// formatValue uses 'value' as the variable name, replace with 'val' for tooltip context
|
|
158
158
|
return expr.replace(/\bvalue\b/g, 'val');
|
|
159
159
|
}
|
|
@@ -194,7 +194,8 @@ export function buildDumbbellOptions(spec) {
|
|
|
194
194
|
const sampleValue = startValues[0] ?? 0;
|
|
195
195
|
const valueFormat = spec.format ?? inferFormat(startField, sampleValue);
|
|
196
196
|
const currency = resolveCurrency(spec.currency);
|
|
197
|
-
const
|
|
197
|
+
const pctMultiply = shouldPctMultiply(valueFormat, [...startValues, ...endValues]);
|
|
198
|
+
const axisFormatter = getAxisFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
198
199
|
// Calculate axis limits
|
|
199
200
|
const allValues = [...startValues, ...endValues];
|
|
200
201
|
let xMin = spec.xMin;
|
|
@@ -215,7 +216,7 @@ export function buildDumbbellOptions(spec) {
|
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
// Value formatter for labels inside dots (uses formatValue for all format types)
|
|
218
|
-
const valueFormatterJs = buildDumbbellLabelFormatter(valueFormat, currency.symbol, currency.locale);
|
|
219
|
+
const valueFormatterJs = buildDumbbellLabelFormatter(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
219
220
|
// Build line data for custom series
|
|
220
221
|
const lineData = startValues.map((s, i) => {
|
|
221
222
|
const e = endValues[i];
|
|
@@ -326,7 +327,7 @@ export function buildDumbbellOptions(spec) {
|
|
|
326
327
|
label: {
|
|
327
328
|
show: showValues,
|
|
328
329
|
position: 'inside',
|
|
329
|
-
fontSize: dumbbellLabelFontSize(v, valueFormat, currency.symbol, currency.locale),
|
|
330
|
+
fontSize: dumbbellLabelFontSize(v, valueFormat, currency.symbol, currency.locale, pctMultiply),
|
|
330
331
|
fontWeight: 'bold',
|
|
331
332
|
color: rowColors[i],
|
|
332
333
|
formatter: { _js_: valueFormatterJs },
|
|
@@ -349,7 +350,7 @@ export function buildDumbbellOptions(spec) {
|
|
|
349
350
|
label: {
|
|
350
351
|
show: showValues,
|
|
351
352
|
position: 'inside',
|
|
352
|
-
fontSize: dumbbellLabelFontSize(v, valueFormat, currency.symbol, currency.locale),
|
|
353
|
+
fontSize: dumbbellLabelFontSize(v, valueFormat, currency.symbol, currency.locale, pctMultiply),
|
|
353
354
|
fontWeight: 'bold',
|
|
354
355
|
color: '#ffffff',
|
|
355
356
|
formatter: { _js_: valueFormatterJs },
|
|
@@ -363,7 +364,7 @@ export function buildDumbbellOptions(spec) {
|
|
|
363
364
|
z: 10,
|
|
364
365
|
});
|
|
365
366
|
// Tooltip formatter with value formatting
|
|
366
|
-
const tooltipValueExpr = buildDumbbellTooltipExpr(valueFormat, currency.symbol, currency.locale);
|
|
367
|
+
const tooltipValueExpr = buildDumbbellTooltipExpr(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
367
368
|
const tooltipFormatterJs = `function(params) {
|
|
368
369
|
if (params.seriesName === 'range') return '';
|
|
369
370
|
var cat = ${JSON.stringify(categories)}[params.data.value ? params.data.value[1] : params.data[1]];
|
package/dist/charts/funnel.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { FONT_SIZE_XXS, getThemeColors, getPaletteWithCustom } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
|
-
import { inferFormat, resolveCurrency } from '../core/formatting.js';
|
|
6
|
+
import { inferFormat, resolveCurrency, shouldPctMultiply } from '../core/formatting.js';
|
|
7
7
|
import { registerChart, registerOptions } from './registry.js';
|
|
8
8
|
/**
|
|
9
9
|
* Get JavaScript formatter for funnel chart labels/tooltips
|
|
10
10
|
* @param currencySymbol - Symbol to use for currency formats
|
|
11
11
|
* @param locale - Locale for number formatting (defaults to 'en-US')
|
|
12
12
|
*/
|
|
13
|
-
function getFunnelFormatterJs(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
13
|
+
function getFunnelFormatterJs(fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
14
14
|
// Smart auto-formatting based on magnitude
|
|
15
15
|
if (fmt === 'auto' || fmt === 'currency_auto' || fmt === null || fmt === undefined) {
|
|
16
16
|
const isCurrency = fmt === 'currency_auto' ? 'true' : 'false';
|
|
@@ -66,14 +66,18 @@ function getFunnelFormatterJs(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
|
66
66
|
_js_: `function(params) { return params.name + ': ' + ${sym} + (params.value/1000000000).toLocaleString(${locStr}, {minimumFractionDigits:1, maximumFractionDigits:1}) + 'b'; }`,
|
|
67
67
|
};
|
|
68
68
|
case 'pct':
|
|
69
|
-
case 'pct1':
|
|
69
|
+
case 'pct1': {
|
|
70
|
+
const scaleExpr = pctMultiply ? '(params.value * 100)' : 'params.value';
|
|
70
71
|
return {
|
|
71
|
-
_js_:
|
|
72
|
+
_js_: `function(params) { return params.name + ': ' + ${scaleExpr}.toFixed(1) + '%'; }`,
|
|
72
73
|
};
|
|
73
|
-
|
|
74
|
+
}
|
|
75
|
+
case 'pct0': {
|
|
76
|
+
const scaleExpr = pctMultiply ? '(params.value * 100)' : 'params.value';
|
|
74
77
|
return {
|
|
75
|
-
_js_:
|
|
78
|
+
_js_: `function(params) { return params.name + ': ' + ${scaleExpr}.toFixed(0) + '%'; }`,
|
|
76
79
|
};
|
|
80
|
+
}
|
|
77
81
|
case 'num0':
|
|
78
82
|
return { _js_: "function(params) { return params.name + ': ' + params.value.toLocaleString(); }" };
|
|
79
83
|
case 'num1':
|
|
@@ -107,8 +111,9 @@ export function buildFunnelOptions(spec) {
|
|
|
107
111
|
const sampleValue = funnelData[0]?.value ?? 0;
|
|
108
112
|
const valueFormat = spec.format ?? inferFormat(valueField, sampleValue);
|
|
109
113
|
const currency = resolveCurrency(spec.currency);
|
|
114
|
+
const pctMultiply = shouldPctMultiply(valueFormat, funnelData.map((d) => d.value));
|
|
110
115
|
// Get JavaScript formatter
|
|
111
|
-
const formatter = getFunnelFormatterJs(valueFormat, currency.symbol, currency.locale);
|
|
116
|
+
const formatter = getFunnelFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
112
117
|
return {
|
|
113
118
|
backgroundColor: 'transparent',
|
|
114
119
|
animation: false,
|
package/dist/charts/heatmap.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { FONT_SIZE_XXS, getThemeColors, getPaletteWithCustom } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, resolveCurrency, shouldPctMultiply } from '../core/formatting.js';
|
|
8
8
|
/**
|
|
9
9
|
* Parse a formatted string value like "1.9k" or "$2.5m" to a number.
|
|
10
10
|
* Returns the original value if parsing fails.
|
|
@@ -38,7 +38,7 @@ function parseFormattedValue(value) {
|
|
|
38
38
|
* @param currencySymbol - Symbol to use for currency formats
|
|
39
39
|
* @param locale - Locale for number formatting (defaults to 'en-US')
|
|
40
40
|
*/
|
|
41
|
-
function getHeatmapLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
41
|
+
function getHeatmapLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US', pctMultiply = true) {
|
|
42
42
|
if (fmt === null || fmt === undefined) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
@@ -80,13 +80,14 @@ function getHeatmapLabelFormatter(fmt, currencySymbol = '$', locale = 'en-US') {
|
|
|
80
80
|
// Format-specific formatters (with null checks)
|
|
81
81
|
const sym = JSON.stringify(currencySymbol);
|
|
82
82
|
const locStr = JSON.stringify(locale);
|
|
83
|
+
const pctExpr = pctMultiply ? '(v * 100)' : 'v';
|
|
83
84
|
const formatExprs = {
|
|
84
85
|
currency: `${sym} + v.toLocaleString(${locStr})`,
|
|
85
86
|
currency0k: `${sym} + (v/1000).toFixed(0) + 'k'`,
|
|
86
87
|
currency0m: `${sym} + (v/1000000).toLocaleString(${locStr}, {minimumFractionDigits:1, maximumFractionDigits:1}) + 'm'`,
|
|
87
|
-
pct:
|
|
88
|
-
pct0:
|
|
89
|
-
pct1:
|
|
88
|
+
pct: `${pctExpr}.toFixed(1) + '%'`,
|
|
89
|
+
pct0: `${pctExpr}.toFixed(0) + '%'`,
|
|
90
|
+
pct1: `${pctExpr}.toFixed(1) + '%'`,
|
|
90
91
|
num0: "v.toLocaleString()",
|
|
91
92
|
num1: "v.toFixed(1)",
|
|
92
93
|
num0k: "(v/1000).toFixed(0) + 'k'",
|
|
@@ -160,7 +161,8 @@ export function buildHeatmapOptions(spec) {
|
|
|
160
161
|
const sampleValue = values[0] ?? 0;
|
|
161
162
|
const valueFormat = spec.format ?? inferFormat('value', sampleValue);
|
|
162
163
|
const currency = resolveCurrency(spec.currency);
|
|
163
|
-
const
|
|
164
|
+
const pctMultiply = shouldPctMultiply(valueFormat, values);
|
|
165
|
+
const labelFormatter = getHeatmapLabelFormatter(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
164
166
|
// Build label config
|
|
165
167
|
const labelConfig = { show: true, color: colors.text };
|
|
166
168
|
if (labelFormatter) {
|
package/dist/charts/line.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { FONT_SIZE_TINY, DEFAULT_CHART_HEIGHT, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, getAxisFormatterJs, inferAxisType, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, getAxisFormatterJs, inferAxisType, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
8
8
|
/**
|
|
9
9
|
* Build ECharts options for a line chart
|
|
10
10
|
*/
|
|
@@ -23,7 +23,8 @@ export function buildLineOptions(spec) {
|
|
|
23
23
|
const sampleValue = data.length > 0 && data[0] ? data[0][firstYKey] ?? 0 : 0;
|
|
24
24
|
const valueFormat = spec.format ?? inferFormat(firstYKey, sampleValue);
|
|
25
25
|
const currency = resolveCurrency(spec.currency);
|
|
26
|
-
const
|
|
26
|
+
const pctMultiply = shouldPctMultiply(valueFormat, collectNumericFieldValues(data, yKeys));
|
|
27
|
+
const axisFormatter = getAxisFormatterJs(valueFormat, currency.symbol, currency.locale, pctMultiply);
|
|
27
28
|
// Detect x-axis type: time, value, or category
|
|
28
29
|
// User can override with xAxisType in spec
|
|
29
30
|
const xAxisType = spec.xAxisType ?? inferAxisType(categories);
|
package/dist/charts/scatter.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { SCATTER_SYMBOL_SIZE, FONT_SIZE_TINY, DEFAULT_CHART_HEIGHT, getThemeColors, getPaletteWithCustom, } from '../core/themes.js';
|
|
5
5
|
import { wrapHtml } from '../core/serializer.js';
|
|
6
6
|
import { registerChart, registerOptions } from './registry.js';
|
|
7
|
-
import { inferFormat, getAxisFormatterJs, formatValue, resolveCurrency } from '../core/formatting.js';
|
|
7
|
+
import { inferFormat, getAxisFormatterJs, formatValue, resolveCurrency, shouldPctMultiply, collectNumericFieldValues, } from '../core/formatting.js';
|
|
8
8
|
import { buildPointLabelConfig, groupDataBySeries, buildSeriesLegend } from '../core/chart-helpers.js';
|
|
9
9
|
/** Scatter point opacity */
|
|
10
10
|
const SCATTER_OPACITY = 0.75;
|
|
@@ -38,8 +38,10 @@ export function buildScatterOptions(spec) {
|
|
|
38
38
|
const xFormat = spec.xFormat ?? inferFormat(xField, xSample);
|
|
39
39
|
const yFormat = spec.yFormat ?? inferFormat(yField, ySample);
|
|
40
40
|
const currency = resolveCurrency(spec.currency);
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const xPctMultiply = shouldPctMultiply(xFormat, collectNumericFieldValues(data, [xField]));
|
|
42
|
+
const yPctMultiply = shouldPctMultiply(yFormat, collectNumericFieldValues(data, [yField]));
|
|
43
|
+
const xAxisFormatter = getAxisFormatterJs(xFormat, currency.symbol, currency.locale, xPctMultiply);
|
|
44
|
+
const yAxisFormatter = getAxisFormatterJs(yFormat, currency.symbol, currency.locale, yPctMultiply);
|
|
43
45
|
// Label config for persistent point labels
|
|
44
46
|
const labelConfig = showLabels && labelField
|
|
45
47
|
? buildPointLabelConfig(SCATTER_LABEL_INDEX, colors)
|
|
@@ -77,8 +79,8 @@ export function buildScatterOptions(spec) {
|
|
|
77
79
|
seriesList = [seriesObj];
|
|
78
80
|
}
|
|
79
81
|
// Build tooltip formatter with label support and value formatting
|
|
80
|
-
const xFormatExpr = formatValue(xFormat, currency.symbol, currency.locale);
|
|
81
|
-
const yFormatExpr = formatValue(yFormat, currency.symbol, currency.locale);
|
|
82
|
+
const xFormatExpr = formatValue(xFormat, currency.symbol, currency.locale, xPctMultiply);
|
|
83
|
+
const yFormatExpr = formatValue(yFormat, currency.symbol, currency.locale, yPctMultiply);
|
|
82
84
|
const tooltipFormatterJs = `function(params) {
|
|
83
85
|
var d = params.value || params.data;
|
|
84
86
|
var result = '';
|
package/dist/cli.js
CHANGED
|
@@ -177,22 +177,23 @@ async function main() {
|
|
|
177
177
|
let errors = [];
|
|
178
178
|
// Detect input type
|
|
179
179
|
const trimmed = input.trim();
|
|
180
|
+
const lintMode = lintOnly ? 'lint' : 'generate';
|
|
180
181
|
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
181
182
|
// JSON input - lint then generate
|
|
182
183
|
const spec = JSON.parse(trimmed);
|
|
183
|
-
lintSpec(spec);
|
|
184
|
+
lintSpec(spec, lintMode);
|
|
184
185
|
html = await generateChartAsync(spec);
|
|
185
186
|
}
|
|
186
187
|
else if (trimmed.startsWith('---') || trimmed.includes('```')) {
|
|
187
188
|
// Markdown input - parser handles linting internally (async for mermaid)
|
|
188
|
-
const result = await parseMarkdownToDashboardAsync(input, 'light', baseDir, false, false, customTheme);
|
|
189
|
+
const result = await parseMarkdownToDashboardAsync(input, 'light', baseDir, false, false, customTheme, lintMode);
|
|
189
190
|
html = result.html;
|
|
190
191
|
errors = result.errors;
|
|
191
192
|
}
|
|
192
193
|
else {
|
|
193
194
|
// Try JSON anyway - lint then generate
|
|
194
195
|
const spec = JSON.parse(trimmed);
|
|
195
|
-
lintSpec(spec);
|
|
196
|
+
lintSpec(spec, lintMode);
|
|
196
197
|
html = await generateChartAsync(spec);
|
|
197
198
|
}
|
|
198
199
|
// Check for errors (unless --allow-errors is set)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table interactivity (sorting and filtering).
|
|
3
|
+
*
|
|
4
|
+
* Used by both standalone tables (`renderStandaloneTable`) and dashboard-embedded
|
|
5
|
+
* tables (`renderTable` in the layout parser). Produces the chrome (global filter
|
|
6
|
+
* input), the per-cell `data-sort` attribute for numeric sorting, and one
|
|
7
|
+
* vanilla-JS init function that wires up click/input handlers.
|
|
8
|
+
*/
|
|
9
|
+
export interface TableInteractivity {
|
|
10
|
+
sortable: boolean;
|
|
11
|
+
filter: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read sortable/filter options from a spec. Sort defaults ON, filter defaults OFF.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseTableInteractivity(spec: Record<string, unknown>): TableInteractivity;
|
|
17
|
+
/**
|
|
18
|
+
* Generate an HTML attribute string for a sortable `<th>`.
|
|
19
|
+
* `colIdx` maps to the visual column index (including the row-number column if present).
|
|
20
|
+
*/
|
|
21
|
+
export declare function sortableHeaderAttrs(colIdx: number, sortable: boolean): string;
|
|
22
|
+
/**
|
|
23
|
+
* Generate `data-sort="..."` for a cell so numeric sort doesn't depend on the
|
|
24
|
+
* rendered text (which may be formatted, contain SVG sparklines, etc.).
|
|
25
|
+
*/
|
|
26
|
+
export declare function cellSortAttr(value: unknown): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build the filter toolbar (global search input when `filter: true`).
|
|
29
|
+
*/
|
|
30
|
+
export interface TableChrome {
|
|
31
|
+
toolbarHtml: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildTableChrome(tableId: string, interactivity: TableInteractivity, colors: {
|
|
34
|
+
text: string;
|
|
35
|
+
textSecondary: string;
|
|
36
|
+
border: string;
|
|
37
|
+
background: string;
|
|
38
|
+
}): TableChrome;
|
|
39
|
+
/**
|
|
40
|
+
* Per-table init call. Invoked after the HTML is in the DOM.
|
|
41
|
+
*/
|
|
42
|
+
export declare function tableInitCall(tableId: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Shared JS that defines `window.mvizTableInit`. Include once per output document.
|
|
45
|
+
* Responsible for sort clicks and the global filter input.
|
|
46
|
+
*/
|
|
47
|
+
export declare function sharedTableScript(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Shared CSS for sort indicators. Appended once per standalone document or
|
|
50
|
+
* injected once into dashboard mode alongside the shared script.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sharedTableCss(): string;
|
|
53
|
+
//# sourceMappingURL=table-interactivity.d.ts.map
|