mviz 1.6.3 → 1.6.4

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.
@@ -37,7 +37,7 @@ export function generateExportCss() {
37
37
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
38
38
  padding: 4px 0;
39
39
  min-width: 200px;
40
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
40
+ font-family: var(--font-family, 'Helvetica Neue', Helvetica, Arial, sans-serif);
41
41
  font-size: 12px;
42
42
  color: ${COLORS.TEXT_DARK};
43
43
  }
@@ -136,9 +136,14 @@ export function generateExportJs() {
136
136
  return { div: tmpDiv, chart: tmpChart };
137
137
  }
138
138
 
139
+ function getThemeFont() {
140
+ var fontFamily = getComputedStyle(document.body).fontFamily;
141
+ return fontFamily || '"Helvetica Neue", Helvetica, Arial, sans-serif';
142
+ }
143
+
139
144
  function drawTitle(ctx, title, pixelRatio) {
140
145
  ctx.fillStyle = isDarkTheme() ? CFG.TEXT_DARK : CFG.TEXT_LIGHT;
141
- ctx.font = 'bold ' + Math.round(CFG.TITLE_FONT_SIZE * pixelRatio) + 'px "Helvetica Neue", Helvetica, Arial, sans-serif';
146
+ ctx.font = 'bold ' + Math.round(CFG.TITLE_FONT_SIZE * pixelRatio) + 'px ' + getThemeFont();
142
147
  ctx.textAlign = 'left';
143
148
  ctx.textBaseline = 'top';
144
149
  ctx.fillText(
@@ -193,11 +193,15 @@ export function getPalette(theme = 'light') {
193
193
  * Custom theme can specify which base theme to extend.
194
194
  */
195
195
  export function getThemeColorsWithCustom(theme = 'light', custom) {
196
- const baseTheme = custom?.extends ?? theme;
197
- const baseColors = getThemeColors(baseTheme);
198
- if (!custom?.colors) {
199
- return baseColors;
196
+ // Only apply custom color overrides when the requested theme matches
197
+ // the theme the custom colors were designed for (custom.extends).
198
+ // For the opposite theme, use standard defaults so dark mode isn't
199
+ // broken by light-mode custom colors (and vice versa).
200
+ const customExtends = custom?.extends ?? 'light';
201
+ if (!custom?.colors || theme !== customExtends) {
202
+ return getThemeColors(theme);
200
203
  }
204
+ const baseColors = getThemeColors(customExtends);
201
205
  return { ...baseColors, ...custom.colors };
202
206
  }
203
207
  /**
@@ -249,10 +253,11 @@ export function getTooltipConfig(theme = 'light') {
249
253
  export function getBaseOption(spec) {
250
254
  const theme = spec.theme ?? 'light';
251
255
  const colors = getThemeColors(theme);
256
+ const fonts = getFontsWithCustom(spec.customTheme);
252
257
  return {
253
258
  backgroundColor: 'transparent',
254
259
  textStyle: {
255
- fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
260
+ fontFamily: fonts.family,
256
261
  color: colors.text,
257
262
  },
258
263
  title: spec.title
@@ -12,7 +12,7 @@ import { getOptionsBuilder } from '../charts/index.js';
12
12
  import { serializeOption } from '../core/serializer.js';
13
13
  import { formatNumber, inferFormat, resolveCurrency } from '../core/formatting.js';
14
14
  import { lintSpec } from '../core/linter.js';
15
- import { COLORS, GRID_TOTAL_COLUMNS, DEFAULT_SIZES, autoSizeChart, getHeatmapColors, getThemeColors, getThemeColorsWithCustom, ALERT_ICONS, } from '../core/themes.js';
15
+ import { COLORS, GRID_TOTAL_COLUMNS, DEFAULT_SIZES, autoSizeChart, getHeatmapColors, getThemeColors, getThemeColorsWithCustom, getFontsWithCustom, ALERT_ICONS, } from '../core/themes.js';
16
16
  import { renderMermaid, renderMermaidAscii } from 'beautiful-mermaid';
17
17
  import { formatCell, computeHeatmapRanges, computeDumbbellRanges, } from '../components/table.js';
18
18
  // Height per row unit (compact for print) - must match Python's ROW_HEIGHT_PX
@@ -139,16 +139,25 @@ function renderEchartsComponent(compType, spec, chartId, colSpan, itemHeight, an
139
139
  }
140
140
  const chartHeight = itemHeight - (title ? 24 : 0);
141
141
  // Note: Time series validation is handled by the linter when specs are parsed
142
+ // Resolve custom font for ECharts textStyle injection
143
+ const fontFamily = getFontsWithCustom(customTheme).family;
144
+ function applyFont(option) {
145
+ option.textStyle = { ...(option.textStyle ?? {}), fontFamily };
146
+ }
142
147
  // Generate light theme options
143
148
  const specLight = { ...spec, theme: 'light', height: chartHeight, customTheme };
144
149
  const optionLight = builder(specLight);
145
150
  if (!optionLight) {
146
151
  return { html: null, script: null };
147
152
  }
153
+ applyFont(optionLight);
148
154
  const optionJsonLight = serializeOption(optionLight);
149
155
  // Generate dark theme options
150
156
  const specDark = { ...spec, theme: 'dark', height: chartHeight, customTheme };
151
157
  const optionDark = builder(specDark);
158
+ if (optionDark) {
159
+ applyFont(optionDark);
160
+ }
152
161
  const optionJsonDark = optionDark ? serializeOption(optionDark) : null;
153
162
  const anchorAttr = anchorId ? ` id="${anchorId}"` : '';
154
163
  const html = `
@@ -103,7 +103,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
103
103
  margin-bottom: 8px;
104
104
  }
105
105
  .page-title {
106
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
106
+ font-family: var(--font-family);
107
107
  font-size: 18px;
108
108
  font-weight: 900;
109
109
  margin-bottom: 12px;
@@ -119,7 +119,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
119
119
  border-top: 1px solid var(--grid);
120
120
  }
121
121
  .section-title {
122
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
122
+ font-family: var(--font-family);
123
123
  font-size: 14px;
124
124
  font-weight: 900;
125
125
  letter-spacing: 0.05em;
@@ -128,7 +128,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
128
128
  margin-bottom: 16px;
129
129
  }
130
130
  .chart-title {
131
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
131
+ font-family: var(--font-family); font-weight: 700;
132
132
  font-size: 10px;
133
133
  font-weight: bold;
134
134
  color: var(--text);
@@ -157,6 +157,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
157
157
  .grid-item { break-inside: avoid; page-break-inside: avoid; }
158
158
  .dashboard-section { break-inside: avoid; page-break-inside: avoid; }
159
159
  .dashboard-section.page-break { page-break-before: always; }
160
+ .section-title { break-after: avoid; page-break-after: avoid; }
160
161
  .data-table { break-inside: avoid; page-break-inside: avoid; }
161
162
  .theme-toggle { display: none; }
162
163
  .title-row { break-after: avoid; page-break-after: avoid; }
@@ -172,7 +173,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
172
173
  transition: background 0.3s;
173
174
  }
174
175
  .big-value {
175
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
176
+ font-family: var(--font-family); font-weight: 700;
176
177
  font-size: 28px;
177
178
  font-weight: bold;
178
179
  color: var(--text);
@@ -187,7 +188,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
187
188
  font-size: 16px;
188
189
  }
189
190
  .delta .value {
190
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
191
+ font-family: var(--font-family);
191
192
  font-size: 16px;
192
193
  font-weight: bold;
193
194
  }
@@ -244,7 +245,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
244
245
  width: 100%; border-collapse: collapse; font-size: 11px;
245
246
  }
246
247
  .data-table th {
247
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9px; font-weight: bold;
248
+ font-family: var(--font-family); font-size: 9px; font-weight: bold;
248
249
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em;
249
250
  padding: 6px 8px; border-bottom: 1px solid var(--text); text-align: left;
250
251
  }
@@ -290,7 +291,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
290
291
  margin-bottom: 4px;
291
292
  }
292
293
  .inline-header-text {
293
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
294
+ font-family: var(--font-family);
294
295
  font-size: 11px;
295
296
  font-weight: 700;
296
297
  color: var(--text);
@@ -319,7 +320,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
319
320
  .pct-bar-value {
320
321
  font-size: 11px;
321
322
  color: var(--text);
322
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
323
+ font-family: var(--font-family);
323
324
  font-weight: 700;
324
325
  min-width: 32px;
325
326
  flex-shrink: 0;
@@ -340,7 +341,7 @@ export function generateDashboardCss(customTheme, orientation = 'portrait') {
340
341
  padding: 4px 8px;
341
342
  border-radius: 4px;
342
343
  font-size: 11px;
343
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
344
+ font-family: var(--font-family);
344
345
  white-space: nowrap;
345
346
  opacity: 0;
346
347
  visibility: hidden;
@@ -447,6 +448,8 @@ export function generateTestHarnessCss() {
447
448
  --grid: ${colorsLight.border};
448
449
  --red: ${colorsLight.accent};
449
450
  --link: ${linkColor};
451
+ --font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
452
+ --font-mono: 'Consolas', 'Monaco', monospace;
450
453
  }
451
454
  body.theme-dark {
452
455
  --bg: ${colorsDark.background};
@@ -459,7 +462,7 @@ export function generateTestHarnessCss() {
459
462
  }
460
463
 
461
464
  body {
462
- font-family: 'Inter', sans-serif;
465
+ font-family: var(--font-family);
463
466
  background: var(--bg);
464
467
  color: var(--text);
465
468
  transition: background-color 0.3s, color 0.3s;
@@ -604,7 +607,7 @@ export function generateTestHarnessCss() {
604
607
  margin-bottom: 40px;
605
608
  }
606
609
  .section-title {
607
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
610
+ font-family: var(--font-family);
608
611
  font-size: 14px;
609
612
  font-weight: 900;
610
613
  letter-spacing: 0.05em;
@@ -634,7 +637,7 @@ export function generateTestHarnessCss() {
634
637
  margin-bottom: 8px;
635
638
  }
636
639
  .page-title {
637
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
640
+ font-family: var(--font-family);
638
641
  font-size: 18px;
639
642
  font-weight: 900;
640
643
  margin-bottom: 12px;
@@ -650,7 +653,7 @@ export function generateTestHarnessCss() {
650
653
  border-top: 1px solid var(--grid);
651
654
  }
652
655
  .chart-title {
653
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
656
+ font-family: var(--font-family); font-weight: 700;
654
657
  font-size: 10px;
655
658
  font-weight: bold;
656
659
  color: var(--text);
@@ -688,7 +691,7 @@ export function generateTestHarnessCss() {
688
691
 
689
692
  /* Component styles */
690
693
  .big-value {
691
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
694
+ font-family: var(--font-family); font-weight: 700;
692
695
  font-size: 28px;
693
696
  font-weight: bold;
694
697
  color: var(--text);
@@ -703,7 +706,7 @@ export function generateTestHarnessCss() {
703
706
  font-size: 16px;
704
707
  }
705
708
  .delta .value {
706
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
709
+ font-family: var(--font-family);
707
710
  font-size: 16px;
708
711
  font-weight: bold;
709
712
  }
@@ -757,7 +760,7 @@ export function generateTestHarnessCss() {
757
760
  width: 100%; border-collapse: collapse; font-size: 11px;
758
761
  }
759
762
  .data-table th {
760
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9px; font-weight: bold;
763
+ font-family: var(--font-family); font-size: 9px; font-weight: bold;
761
764
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em;
762
765
  padding: 6px 8px; border-bottom: 1px solid var(--text); text-align: left;
763
766
  }
@@ -784,7 +787,7 @@ export function generateTestHarnessCss() {
784
787
  margin-bottom: 4px;
785
788
  }
786
789
  .inline-header-text {
787
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
790
+ font-family: var(--font-family);
788
791
  font-size: 11px;
789
792
  font-weight: 700;
790
793
  color: var(--text);
@@ -814,7 +817,7 @@ export function generateTestHarnessCss() {
814
817
  .pct-bar-value {
815
818
  font-size: 11px;
816
819
  color: var(--text);
817
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
820
+ font-family: var(--font-family);
818
821
  font-weight: 700;
819
822
  min-width: 32px;
820
823
  flex-shrink: 0;
@@ -836,7 +839,7 @@ export function generateTestHarnessCss() {
836
839
  padding: 4px 8px;
837
840
  border-radius: 4px;
838
841
  font-size: 11px;
839
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
842
+ font-family: var(--font-family);
840
843
  white-space: nowrap;
841
844
  opacity: 0;
842
845
  visibility: hidden;
@@ -880,6 +883,7 @@ export function generateTestHarnessCss() {
880
883
  .grid-item { break-inside: avoid; page-break-inside: avoid; }
881
884
  .dashboard-section { break-inside: avoid; page-break-inside: avoid; }
882
885
  .dashboard-section.page-break { page-break-before: always; }
886
+ .section-title { break-after: avoid; page-break-after: avoid; }
883
887
  .data-table { break-inside: avoid; page-break-inside: avoid; }
884
888
  header { display: none; }
885
889
  .annotation-bar { display: none; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mviz",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "A chart & report builder designed for use by AI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -83,6 +83,6 @@
83
83
  "dependencies": {
84
84
  "ajv": "^8.17.1",
85
85
  "ajv-formats": "^3.0.1",
86
- "beautiful-mermaid": "^0.1.3"
86
+ "beautiful-mermaid": "^1.1.3"
87
87
  }
88
88
  }