pmx-canvas 0.1.9 → 0.1.11
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/CHANGELOG.md +154 -0
- package/dist/canvas/index.js +44 -44
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +115 -115
- package/dist/types/client/canvas/auto-fit.d.ts +1 -1
- package/dist/types/json-render/catalog.d.ts +326 -310
- package/dist/types/json-render/charts/components.d.ts +18 -0
- package/dist/types/json-render/charts/definitions.d.ts +4 -0
- package/dist/types/json-render/charts/extra-components.d.ts +3 -0
- package/dist/types/json-render/charts/extra-definitions.d.ts +6 -0
- package/dist/types/json-render/server.d.ts +4 -0
- package/dist/types/server/canvas-operations.d.ts +2 -0
- package/dist/types/server/index.d.ts +2 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +9 -0
- package/src/cli/agent.ts +103 -5
- package/src/cli/index.ts +6 -3
- package/src/client/canvas/CanvasNode.tsx +3 -1
- package/src/client/canvas/auto-fit.ts +3 -3
- package/src/json-render/catalog.ts +9 -0
- package/src/json-render/charts/components.tsx +18 -10
- package/src/json-render/charts/definitions.ts +4 -0
- package/src/json-render/charts/extra-components.tsx +23 -16
- package/src/json-render/charts/extra-definitions.ts +6 -0
- package/src/json-render/renderer/index.css +61 -0
- package/src/json-render/renderer/index.tsx +22 -0
- package/src/json-render/server.ts +11 -11
- package/src/mcp/server.ts +10 -0
- package/src/server/canvas-operations.ts +21 -1
- package/src/server/canvas-schema.ts +5 -0
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/diagram-presets.ts +82 -4
- package/src/server/index.ts +7 -1
- package/src/server/server.ts +33 -2
|
@@ -34,7 +34,11 @@ import {
|
|
|
34
34
|
import {
|
|
35
35
|
CHART_COLORS,
|
|
36
36
|
CartesianChart,
|
|
37
|
+
axisTickMargin,
|
|
37
38
|
axisStyle,
|
|
39
|
+
chartMargin,
|
|
40
|
+
legendMargin,
|
|
41
|
+
polarChartMargin,
|
|
38
42
|
tooltipStyle,
|
|
39
43
|
type CartesianChartProps,
|
|
40
44
|
} from './components';
|
|
@@ -47,7 +51,7 @@ function ChartAreaChart({ props }: BaseComponentProps<AreaChartProps>) {
|
|
|
47
51
|
return (
|
|
48
52
|
<CartesianChart props={props} className="pmx-chart--area">
|
|
49
53
|
{(data) => (
|
|
50
|
-
<RechartsAreaChart data={data}>
|
|
54
|
+
<RechartsAreaChart data={data} margin={chartMargin}>
|
|
51
55
|
<defs>
|
|
52
56
|
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
|
53
57
|
<stop offset="0%" stopColor={stroke} stopOpacity={0.45} />
|
|
@@ -55,8 +59,8 @@ function ChartAreaChart({ props }: BaseComponentProps<AreaChartProps>) {
|
|
|
55
59
|
</linearGradient>
|
|
56
60
|
</defs>
|
|
57
61
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
58
|
-
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
59
|
-
<YAxis tick={axisStyle} />
|
|
62
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} />
|
|
63
|
+
<YAxis tick={axisStyle} tickMargin={axisTickMargin} />
|
|
60
64
|
<Tooltip contentStyle={tooltipStyle} />
|
|
61
65
|
<Area
|
|
62
66
|
type="monotone"
|
|
@@ -91,10 +95,10 @@ function ChartScatterChart({ props }: BaseComponentProps<ScatterChartProps>) {
|
|
|
91
95
|
<div className="pmx-chart pmx-chart--scatter">
|
|
92
96
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
93
97
|
<ResponsiveContainer width="100%" height={h}>
|
|
94
|
-
<RechartsScatterChart>
|
|
98
|
+
<RechartsScatterChart margin={chartMargin}>
|
|
95
99
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
96
|
-
<XAxis type="number" dataKey={props.xKey} tick={axisStyle} name={props.xKey} />
|
|
97
|
-
<YAxis type="number" dataKey={props.yKey} tick={axisStyle} name={props.yKey} />
|
|
100
|
+
<XAxis type="number" dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} name={props.xKey} />
|
|
101
|
+
<YAxis type="number" dataKey={props.yKey} tick={axisStyle} tickMargin={axisTickMargin} name={props.yKey} />
|
|
98
102
|
{props.zKey && <ZAxis type="number" dataKey={props.zKey} range={[40, 400]} name={props.zKey} />}
|
|
99
103
|
<Tooltip contentStyle={tooltipStyle} cursor={{ strokeDasharray: '3 3' }} />
|
|
100
104
|
<Scatter data={data} fill={fill} />
|
|
@@ -110,6 +114,7 @@ interface RadarChartProps {
|
|
|
110
114
|
axisKey: string;
|
|
111
115
|
metrics: string[];
|
|
112
116
|
height?: number | null;
|
|
117
|
+
showLegend?: boolean | null;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
function ChartRadarChart({ props }: BaseComponentProps<RadarChartProps>) {
|
|
@@ -121,12 +126,12 @@ function ChartRadarChart({ props }: BaseComponentProps<RadarChartProps>) {
|
|
|
121
126
|
<div className="pmx-chart pmx-chart--radar">
|
|
122
127
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
123
128
|
<ResponsiveContainer width="100%" height={h}>
|
|
124
|
-
<RechartsRadarChart data={data} outerRadius="
|
|
129
|
+
<RechartsRadarChart data={data} outerRadius="66%" margin={polarChartMargin}>
|
|
125
130
|
<PolarGrid stroke="var(--border, #e5e5e5)" />
|
|
126
131
|
<PolarAngleAxis dataKey={props.axisKey} tick={axisStyle} />
|
|
127
132
|
<PolarRadiusAxis tick={axisStyle} />
|
|
128
133
|
<Tooltip contentStyle={tooltipStyle} />
|
|
129
|
-
<Legend />
|
|
134
|
+
{props.showLegend !== false && <Legend wrapperStyle={legendMargin} />}
|
|
130
135
|
{metrics.map((metric, i) => {
|
|
131
136
|
const color = CHART_COLORS[i % CHART_COLORS.length];
|
|
132
137
|
return (
|
|
@@ -153,6 +158,7 @@ interface StackedBarChartProps {
|
|
|
153
158
|
series: string[];
|
|
154
159
|
aggregate?: 'sum' | 'count' | 'avg' | null;
|
|
155
160
|
height?: number | null;
|
|
161
|
+
showLegend?: boolean | null;
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
function ChartStackedBarChart({ props }: BaseComponentProps<StackedBarChartProps>) {
|
|
@@ -166,12 +172,12 @@ function ChartStackedBarChart({ props }: BaseComponentProps<StackedBarChartProps
|
|
|
166
172
|
<div className="pmx-chart pmx-chart--stacked-bar">
|
|
167
173
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
168
174
|
<ResponsiveContainer width="100%" height={h}>
|
|
169
|
-
<RechartsBarChart data={chartData}>
|
|
175
|
+
<RechartsBarChart data={chartData} margin={chartMargin}>
|
|
170
176
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
171
|
-
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
172
|
-
<YAxis tick={axisStyle} />
|
|
177
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} />
|
|
178
|
+
<YAxis tick={axisStyle} tickMargin={axisTickMargin} />
|
|
173
179
|
<Tooltip contentStyle={tooltipStyle} cursor={false} />
|
|
174
|
-
<Legend />
|
|
180
|
+
{props.showLegend !== false && <Legend wrapperStyle={legendMargin} />}
|
|
175
181
|
{series.map((key, i) => (
|
|
176
182
|
<Bar
|
|
177
183
|
key={key}
|
|
@@ -225,6 +231,7 @@ interface ComposedChartProps {
|
|
|
225
231
|
barColor?: string | null;
|
|
226
232
|
lineColor?: string | null;
|
|
227
233
|
height?: number | null;
|
|
234
|
+
showLegend?: boolean | null;
|
|
228
235
|
}
|
|
229
236
|
|
|
230
237
|
function ChartComposedChart({ props }: BaseComponentProps<ComposedChartProps>) {
|
|
@@ -237,12 +244,12 @@ function ChartComposedChart({ props }: BaseComponentProps<ComposedChartProps>) {
|
|
|
237
244
|
<div className="pmx-chart pmx-chart--composed">
|
|
238
245
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
239
246
|
<ResponsiveContainer width="100%" height={h}>
|
|
240
|
-
<RechartsComposedChart data={data}>
|
|
247
|
+
<RechartsComposedChart data={data} margin={chartMargin}>
|
|
241
248
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
242
|
-
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
243
|
-
<YAxis tick={axisStyle} />
|
|
249
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} />
|
|
250
|
+
<YAxis tick={axisStyle} tickMargin={axisTickMargin} />
|
|
244
251
|
<Tooltip contentStyle={tooltipStyle} cursor={false} />
|
|
245
|
-
<Legend />
|
|
252
|
+
{props.showLegend !== false && <Legend wrapperStyle={legendMargin} />}
|
|
246
253
|
<Bar dataKey={props.barKey} fill={barFill} radius={[4, 4, 0, 0]} />
|
|
247
254
|
<Line
|
|
248
255
|
type="monotone"
|
|
@@ -73,6 +73,7 @@ export const extraChartComponentDefinitions = {
|
|
|
73
73
|
axisKey: z.string(),
|
|
74
74
|
metrics: z.array(z.string()),
|
|
75
75
|
height: z.number().nullable(),
|
|
76
|
+
showLegend: z.boolean().optional(),
|
|
76
77
|
}),
|
|
77
78
|
description:
|
|
78
79
|
'Radar chart for comparing multiple metrics across categories. Each metric in `metrics` is plotted as its own polygon.',
|
|
@@ -86,6 +87,7 @@ export const extraChartComponentDefinitions = {
|
|
|
86
87
|
axisKey: 'skill',
|
|
87
88
|
metrics: ['alice', 'bob'],
|
|
88
89
|
height: null,
|
|
90
|
+
showLegend: true,
|
|
89
91
|
},
|
|
90
92
|
},
|
|
91
93
|
|
|
@@ -97,6 +99,7 @@ export const extraChartComponentDefinitions = {
|
|
|
97
99
|
series: z.array(z.string()),
|
|
98
100
|
aggregate: z.enum(['sum', 'count', 'avg']).nullable(),
|
|
99
101
|
height: z.number().nullable(),
|
|
102
|
+
showLegend: z.boolean().optional(),
|
|
100
103
|
}),
|
|
101
104
|
description:
|
|
102
105
|
'Stacked bar chart for compositional data. Each entry in `series` is plotted as its own bar segment per x value.',
|
|
@@ -111,6 +114,7 @@ export const extraChartComponentDefinitions = {
|
|
|
111
114
|
series: ['north', 'south', 'east'],
|
|
112
115
|
aggregate: null,
|
|
113
116
|
height: null,
|
|
117
|
+
showLegend: true,
|
|
114
118
|
},
|
|
115
119
|
},
|
|
116
120
|
|
|
@@ -124,6 +128,7 @@ export const extraChartComponentDefinitions = {
|
|
|
124
128
|
barColor: z.string().nullable(),
|
|
125
129
|
lineColor: z.string().nullable(),
|
|
126
130
|
height: z.number().nullable(),
|
|
131
|
+
showLegend: z.boolean().optional(),
|
|
127
132
|
}),
|
|
128
133
|
description:
|
|
129
134
|
'Combined bar + line chart for paired metrics (e.g. counts + a derived rate) on the same axis.',
|
|
@@ -140,6 +145,7 @@ export const extraChartComponentDefinitions = {
|
|
|
140
145
|
barColor: null,
|
|
141
146
|
lineColor: null,
|
|
142
147
|
height: null,
|
|
148
|
+
showLegend: true,
|
|
143
149
|
},
|
|
144
150
|
},
|
|
145
151
|
} as const;
|
|
@@ -143,6 +143,67 @@ button {
|
|
|
143
143
|
cursor: pointer;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
.pmx-badge {
|
|
147
|
+
display: inline-flex;
|
|
148
|
+
width: fit-content;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
gap: 0.25rem;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
border: 1px solid transparent;
|
|
155
|
+
border-radius: 9999px;
|
|
156
|
+
padding: 0.125rem 0.5rem;
|
|
157
|
+
font-size: 0.75rem;
|
|
158
|
+
font-weight: 500;
|
|
159
|
+
line-height: 1.25rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.pmx-badge--default {
|
|
163
|
+
background: var(--primary);
|
|
164
|
+
color: var(--primary-foreground);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.pmx-badge--secondary {
|
|
168
|
+
background: var(--secondary);
|
|
169
|
+
color: var(--secondary-foreground);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.pmx-badge--destructive {
|
|
173
|
+
background: var(--destructive);
|
|
174
|
+
color: var(--destructive-foreground);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.pmx-badge--outline {
|
|
178
|
+
border-color: var(--border);
|
|
179
|
+
color: var(--foreground);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.pmx-badge--success {
|
|
183
|
+
border-color: color-mix(in oklch, var(--chart-2) 45%, transparent);
|
|
184
|
+
background: color-mix(in oklch, var(--chart-2) 16%, transparent);
|
|
185
|
+
color: var(--chart-2);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.pmx-badge--info {
|
|
189
|
+
border-color: color-mix(in oklch, var(--chart-1) 45%, transparent);
|
|
190
|
+
background: color-mix(in oklch, var(--chart-1) 14%, transparent);
|
|
191
|
+
color: var(--chart-1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.pmx-badge--warning {
|
|
195
|
+
border-color: color-mix(in oklch, var(--chart-3) 50%, transparent);
|
|
196
|
+
background: color-mix(in oklch, var(--chart-3) 18%, transparent);
|
|
197
|
+
color: var(--chart-3);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.pmx-badge--error,
|
|
201
|
+
.pmx-badge--danger {
|
|
202
|
+
border-color: color-mix(in oklch, var(--destructive) 55%, transparent);
|
|
203
|
+
background: color-mix(in oklch, var(--destructive) 18%, transparent);
|
|
204
|
+
color: var(--destructive);
|
|
205
|
+
}
|
|
206
|
+
|
|
146
207
|
/* -- Chart components -- */
|
|
147
208
|
|
|
148
209
|
.pmx-chart {
|
|
@@ -15,9 +15,31 @@ import { catalog } from '../catalog';
|
|
|
15
15
|
import { chartComponents } from '../charts/components';
|
|
16
16
|
import { extraChartComponents } from '../charts/extra-components';
|
|
17
17
|
|
|
18
|
+
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'info' | 'warning' | 'error' | 'danger';
|
|
19
|
+
type BadgeProps = {
|
|
20
|
+
text: string;
|
|
21
|
+
variant?: BadgeVariant | null;
|
|
22
|
+
className?: string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function Badge({ props }: { props: BadgeProps }) {
|
|
26
|
+
const variant = props.variant;
|
|
27
|
+
const resolvedVariant = variant ?? 'default';
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
data-slot="badge"
|
|
31
|
+
data-variant={resolvedVariant}
|
|
32
|
+
className={`pmx-badge pmx-badge--${resolvedVariant}`}
|
|
33
|
+
>
|
|
34
|
+
{props.text}
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
const { registry } = defineRegistry(catalog as never, {
|
|
19
40
|
components: {
|
|
20
41
|
...shadcnComponents,
|
|
42
|
+
Badge,
|
|
21
43
|
...chartComponents,
|
|
22
44
|
...extraChartComponents,
|
|
23
45
|
} as never,
|
|
@@ -17,6 +17,7 @@ export interface JsonRenderNodeInput {
|
|
|
17
17
|
y?: number;
|
|
18
18
|
width?: number;
|
|
19
19
|
height?: number;
|
|
20
|
+
strictSize?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface GraphNodeInput {
|
|
@@ -38,10 +39,13 @@ export interface GraphNodeInput {
|
|
|
38
39
|
barColor?: string;
|
|
39
40
|
lineColor?: string;
|
|
40
41
|
height?: number;
|
|
42
|
+
showLegend?: boolean;
|
|
43
|
+
showLabels?: boolean;
|
|
41
44
|
x?: number;
|
|
42
45
|
y?: number;
|
|
43
46
|
width?: number;
|
|
44
47
|
heightPx?: number;
|
|
48
|
+
strictSize?: boolean;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export const JSON_RENDER_NODE_SIZE = { width: 840, height: 620 };
|
|
@@ -262,14 +266,6 @@ function normalizeButtonVariant(value: unknown): unknown {
|
|
|
262
266
|
return value;
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
function normalizeBadgeVariant(value: unknown): unknown {
|
|
266
|
-
if (value === 'success') return 'default';
|
|
267
|
-
if (value === 'info') return 'secondary';
|
|
268
|
-
if (value === 'warning') return 'outline';
|
|
269
|
-
if (value === 'error' || value === 'danger') return 'destructive';
|
|
270
|
-
return value;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
269
|
function deriveElementName(elementKey: string): string {
|
|
274
270
|
const normalized = elementKey.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
275
271
|
return normalized || 'field';
|
|
@@ -344,9 +340,6 @@ function normalizeElementProps(
|
|
|
344
340
|
if ('label' in props) {
|
|
345
341
|
delete props.label;
|
|
346
342
|
}
|
|
347
|
-
if ('variant' in props) {
|
|
348
|
-
props.variant = normalizeBadgeVariant(props.variant);
|
|
349
|
-
}
|
|
350
343
|
}
|
|
351
344
|
|
|
352
345
|
if (type === 'Select' || type === 'Radio') {
|
|
@@ -525,6 +518,8 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
525
518
|
case 'PieChart': {
|
|
526
519
|
chartProps.nameKey = input.nameKey ?? 'name';
|
|
527
520
|
chartProps.valueKey = input.valueKey ?? 'value';
|
|
521
|
+
chartProps.showLegend = input.showLegend !== false;
|
|
522
|
+
chartProps.showLabels = input.showLabels !== false;
|
|
528
523
|
break;
|
|
529
524
|
}
|
|
530
525
|
case 'ScatterChart': {
|
|
@@ -544,6 +539,7 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
544
539
|
}
|
|
545
540
|
chartProps.axisKey = axisKey;
|
|
546
541
|
chartProps.metrics = metrics;
|
|
542
|
+
chartProps.showLegend = input.showLegend !== false;
|
|
547
543
|
break;
|
|
548
544
|
}
|
|
549
545
|
case 'StackedBarChart': {
|
|
@@ -557,6 +553,7 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
557
553
|
chartProps.xKey = xKey;
|
|
558
554
|
chartProps.series = series;
|
|
559
555
|
chartProps.aggregate = input.aggregate ?? null;
|
|
556
|
+
chartProps.showLegend = input.showLegend !== false;
|
|
560
557
|
break;
|
|
561
558
|
}
|
|
562
559
|
case 'ComposedChart': {
|
|
@@ -567,6 +564,7 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
567
564
|
?? 'rate';
|
|
568
565
|
chartProps.barColor = input.barColor ?? null;
|
|
569
566
|
chartProps.lineColor = input.lineColor ?? null;
|
|
567
|
+
chartProps.showLegend = input.showLegend !== false;
|
|
570
568
|
break;
|
|
571
569
|
}
|
|
572
570
|
case 'AreaChart':
|
|
@@ -624,6 +622,8 @@ export function buildGraphConfig(input: GraphNodeInput): Record<string, unknown>
|
|
|
624
622
|
...(input.barColor ? { barColor: input.barColor } : {}),
|
|
625
623
|
...(input.lineColor ? { lineColor: input.lineColor } : {}),
|
|
626
624
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
625
|
+
...(typeof input.showLegend === 'boolean' ? { showLegend: input.showLegend } : {}),
|
|
626
|
+
...(typeof input.showLabels === 'boolean' ? { showLabels: input.showLabels } : {}),
|
|
627
627
|
};
|
|
628
628
|
}
|
|
629
629
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -155,6 +155,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
155
155
|
y: z.number().optional().describe('Y position (auto-placed if omitted)'),
|
|
156
156
|
width: z.number().optional().describe('Width in pixels (default: 720)'),
|
|
157
157
|
height: z.number().optional().describe('Height in pixels (default: 600)'),
|
|
158
|
+
strictSize: z.boolean().optional().describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
|
|
158
159
|
},
|
|
159
160
|
async (input) => {
|
|
160
161
|
const c = await ensureCanvas();
|
|
@@ -173,6 +174,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
173
174
|
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
174
175
|
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
175
176
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
177
|
+
...(input.strictSize === true ? { strictSize: true } : {}),
|
|
176
178
|
});
|
|
177
179
|
return {
|
|
178
180
|
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
@@ -461,6 +463,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
461
463
|
y: z.number().optional().describe('Optional Y position'),
|
|
462
464
|
width: z.number().optional().describe('Optional node width'),
|
|
463
465
|
height: z.number().optional().describe('Optional node height'),
|
|
466
|
+
strictSize: z.boolean().optional().describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
|
|
464
467
|
},
|
|
465
468
|
async (input) => {
|
|
466
469
|
const c = await ensureCanvas();
|
|
@@ -472,6 +475,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
472
475
|
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
473
476
|
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
474
477
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
478
|
+
...(input.strictSize === true ? { strictSize: true } : {}),
|
|
475
479
|
});
|
|
476
480
|
return {
|
|
477
481
|
content: [{
|
|
@@ -515,10 +519,13 @@ export async function startMcpServer(): Promise<void> {
|
|
|
515
519
|
barColor: z.string().optional().describe('Optional bar color for composed charts'),
|
|
516
520
|
lineColor: z.string().optional().describe('Optional line color for composed charts'),
|
|
517
521
|
height: z.number().optional().describe('Optional chart content height'),
|
|
522
|
+
showLegend: z.boolean().optional().describe('Show chart legend when supported; pass false for compact node layouts'),
|
|
523
|
+
showLabels: z.boolean().optional().describe('Show direct labels when supported, such as pie slice labels (defaults to true)'),
|
|
518
524
|
x: z.number().optional().describe('Optional X position'),
|
|
519
525
|
y: z.number().optional().describe('Optional Y position'),
|
|
520
526
|
width: z.number().optional().describe('Optional node width'),
|
|
521
527
|
nodeHeight: z.number().optional().describe('Optional node height'),
|
|
528
|
+
strictSize: z.boolean().optional().describe('Keep explicit node size fixed and scroll overflowing content instead of browser auto-fitting'),
|
|
522
529
|
},
|
|
523
530
|
async (input) => {
|
|
524
531
|
const c = await ensureCanvas();
|
|
@@ -542,10 +549,13 @@ export async function startMcpServer(): Promise<void> {
|
|
|
542
549
|
...(typeof input.barColor === 'string' ? { barColor: input.barColor } : {}),
|
|
543
550
|
...(typeof input.lineColor === 'string' ? { lineColor: input.lineColor } : {}),
|
|
544
551
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
552
|
+
...(typeof input.showLegend === 'boolean' ? { showLegend: input.showLegend } : {}),
|
|
553
|
+
...(typeof input.showLabels === 'boolean' ? { showLabels: input.showLabels } : {}),
|
|
545
554
|
...(typeof input.x === 'number' ? { x: input.x } : {}),
|
|
546
555
|
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
547
556
|
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
548
557
|
...(typeof input.nodeHeight === 'number' ? { heightPx: input.nodeHeight } : {}),
|
|
558
|
+
...(input.strictSize === true ? { strictSize: true } : {}),
|
|
549
559
|
});
|
|
550
560
|
return {
|
|
551
561
|
content: [{
|
|
@@ -67,6 +67,7 @@ export interface CanvasStructuredNodeUpdateInput extends Omit<CanvasGraphNodeUpd
|
|
|
67
67
|
content?: unknown;
|
|
68
68
|
data?: unknown;
|
|
69
69
|
arrangeLocked?: unknown;
|
|
70
|
+
strictSize?: boolean;
|
|
70
71
|
chartHeight?: unknown;
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -82,6 +83,7 @@ interface CanvasAddNodeInput {
|
|
|
82
83
|
defaultWidth?: number;
|
|
83
84
|
defaultHeight?: number;
|
|
84
85
|
fileMode?: 'path' | 'inline' | 'auto';
|
|
86
|
+
strictSize?: boolean;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
interface CanvasCreateGroupInput {
|
|
@@ -164,6 +166,8 @@ function hasGraphUpdateFields(input: Record<string, unknown>): boolean {
|
|
|
164
166
|
input.zKey !== undefined ||
|
|
165
167
|
input.nameKey !== undefined ||
|
|
166
168
|
input.valueKey !== undefined ||
|
|
169
|
+
input.showLegend !== undefined ||
|
|
170
|
+
input.showLabels !== undefined ||
|
|
167
171
|
input.axisKey !== undefined ||
|
|
168
172
|
input.metrics !== undefined ||
|
|
169
173
|
input.series !== undefined ||
|
|
@@ -200,6 +204,7 @@ function mergeNodeDataFields(
|
|
|
200
204
|
...base,
|
|
201
205
|
...(isRecord(input.data) ? input.data : {}),
|
|
202
206
|
...(typeof input.arrangeLocked === 'boolean' ? { arrangeLocked: input.arrangeLocked } : {}),
|
|
207
|
+
...(typeof input.strictSize === 'boolean' ? { strictSize: input.strictSize } : {}),
|
|
203
208
|
};
|
|
204
209
|
}
|
|
205
210
|
|
|
@@ -251,6 +256,8 @@ function graphConfigToInput(config: Record<string, unknown>, fallbackTitle: stri
|
|
|
251
256
|
...(pickString(config, 'zKey') ? { zKey: pickString(config, 'zKey') } : {}),
|
|
252
257
|
...(pickString(config, 'nameKey') ? { nameKey: pickString(config, 'nameKey') } : {}),
|
|
253
258
|
...(pickString(config, 'valueKey') ? { valueKey: pickString(config, 'valueKey') } : {}),
|
|
259
|
+
...(typeof config.showLegend === 'boolean' ? { showLegend: config.showLegend } : {}),
|
|
260
|
+
...(typeof config.showLabels === 'boolean' ? { showLabels: config.showLabels } : {}),
|
|
254
261
|
...(pickString(config, 'axisKey') ? { axisKey: pickString(config, 'axisKey') } : {}),
|
|
255
262
|
...(pickStringArray(config, 'metrics') ? { metrics: pickStringArray(config, 'metrics') } : {}),
|
|
256
263
|
...(pickStringArray(config, 'series') ? { series: pickStringArray(config, 'series') } : {}),
|
|
@@ -276,6 +283,12 @@ function mergeGraphInput(source: Record<string, unknown>, fallback: GraphNodeInp
|
|
|
276
283
|
...((pickString(source, 'zKey') ?? fallback?.zKey) ? { zKey: pickString(source, 'zKey') ?? fallback?.zKey } : {}),
|
|
277
284
|
...((pickString(source, 'nameKey') ?? fallback?.nameKey) ? { nameKey: pickString(source, 'nameKey') ?? fallback?.nameKey } : {}),
|
|
278
285
|
...((pickString(source, 'valueKey') ?? fallback?.valueKey) ? { valueKey: pickString(source, 'valueKey') ?? fallback?.valueKey } : {}),
|
|
286
|
+
...(typeof source.showLegend === 'boolean' || typeof fallback?.showLegend === 'boolean'
|
|
287
|
+
? { showLegend: typeof source.showLegend === 'boolean' ? source.showLegend : fallback?.showLegend }
|
|
288
|
+
: {}),
|
|
289
|
+
...(typeof source.showLabels === 'boolean' || typeof fallback?.showLabels === 'boolean'
|
|
290
|
+
? { showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : fallback?.showLabels }
|
|
291
|
+
: {}),
|
|
279
292
|
...((pickString(source, 'axisKey') ?? fallback?.axisKey) ? { axisKey: pickString(source, 'axisKey') ?? fallback?.axisKey } : {}),
|
|
280
293
|
...((pickStringArray(source, 'metrics') ?? fallback?.metrics) ? { metrics: pickStringArray(source, 'metrics') ?? fallback?.metrics } : {}),
|
|
281
294
|
...((pickStringArray(source, 'series') ?? fallback?.series) ? { series: pickStringArray(source, 'series') ?? fallback?.series } : {}),
|
|
@@ -752,6 +765,7 @@ function buildNodeData(input: CanvasAddNodeInput): Record<string, unknown> {
|
|
|
752
765
|
...(input.data ?? {}),
|
|
753
766
|
...(input.title ? { title: input.title } : {}),
|
|
754
767
|
...(input.content ? { content: input.content } : {}),
|
|
768
|
+
...(input.strictSize ? { strictSize: true } : {}),
|
|
755
769
|
};
|
|
756
770
|
}
|
|
757
771
|
|
|
@@ -1271,6 +1285,7 @@ export function createCanvasJsonRenderNode(
|
|
|
1271
1285
|
dockPosition: null,
|
|
1272
1286
|
data: createJsonRenderNodeData(id, input.title?.trim() || inferJsonRenderNodeTitle(spec), spec, {
|
|
1273
1287
|
viewerType: 'json-render',
|
|
1288
|
+
...(input.strictSize ? { strictSize: true } : {}),
|
|
1274
1289
|
}),
|
|
1275
1290
|
};
|
|
1276
1291
|
|
|
@@ -1302,6 +1317,7 @@ export function createCanvasGraphNode(
|
|
|
1302
1317
|
data: createJsonRenderNodeData(id, title, spec, {
|
|
1303
1318
|
viewerType: 'graph',
|
|
1304
1319
|
graphConfig: buildGraphConfig(input),
|
|
1320
|
+
...(input.strictSize ? { strictSize: true } : {}),
|
|
1305
1321
|
}),
|
|
1306
1322
|
};
|
|
1307
1323
|
|
|
@@ -1418,6 +1434,7 @@ export async function executeCanvasBatch(
|
|
|
1418
1434
|
...(typeof args.y === 'number' ? { y: args.y } : {}),
|
|
1419
1435
|
...(typeof args.width === 'number' ? { width: args.width } : {}),
|
|
1420
1436
|
...(typeof args.height === 'number' ? { height: args.height } : {}),
|
|
1437
|
+
...(args.strictSize === true ? { strictSize: true } : {}),
|
|
1421
1438
|
defaultWidth: 520,
|
|
1422
1439
|
defaultHeight: 420,
|
|
1423
1440
|
});
|
|
@@ -1441,6 +1458,7 @@ export async function executeCanvasBatch(
|
|
|
1441
1458
|
...(typeof args.y === 'number' ? { y: args.y } : {}),
|
|
1442
1459
|
...(typeof args.width === 'number' ? { width: args.width } : {}),
|
|
1443
1460
|
...(typeof args.height === 'number' ? { height: args.height } : {}),
|
|
1461
|
+
...(args.strictSize === true ? { strictSize: true } : {}),
|
|
1444
1462
|
defaultWidth: 360,
|
|
1445
1463
|
defaultHeight: 200,
|
|
1446
1464
|
fileMode: 'auto',
|
|
@@ -1471,12 +1489,13 @@ export async function executeCanvasBatch(
|
|
|
1471
1489
|
if (args.dockPosition === null || args.dockPosition === 'left' || args.dockPosition === 'right') {
|
|
1472
1490
|
patch.dockPosition = args.dockPosition;
|
|
1473
1491
|
}
|
|
1474
|
-
if (typeof args.title === 'string' || typeof args.content === 'string' || typeof args.arrangeLocked === 'boolean' || isPlainRecord(args.data)) {
|
|
1492
|
+
if (typeof args.title === 'string' || typeof args.content === 'string' || typeof args.arrangeLocked === 'boolean' || typeof args.strictSize === 'boolean' || isPlainRecord(args.data)) {
|
|
1475
1493
|
patch.data = {
|
|
1476
1494
|
...node.data,
|
|
1477
1495
|
...(typeof args.title === 'string' ? { title: args.title } : {}),
|
|
1478
1496
|
...(typeof args.content === 'string' ? { content: args.content } : {}),
|
|
1479
1497
|
...(typeof args.arrangeLocked === 'boolean' ? { arrangeLocked: args.arrangeLocked } : {}),
|
|
1498
|
+
...(typeof args.strictSize === 'boolean' ? { strictSize: args.strictSize } : {}),
|
|
1480
1499
|
...(isPlainRecord(args.data) ? args.data : {}),
|
|
1481
1500
|
};
|
|
1482
1501
|
}
|
|
@@ -1511,6 +1530,7 @@ export async function executeCanvasBatch(
|
|
|
1511
1530
|
...(typeof args.y === 'number' ? { y: args.y } : {}),
|
|
1512
1531
|
...(typeof args.width === 'number' ? { width: args.width } : {}),
|
|
1513
1532
|
...(typeof args.nodeHeight === 'number' ? { heightPx: args.nodeHeight } : {}),
|
|
1533
|
+
...(args.strictSize === true ? { strictSize: true } : {}),
|
|
1514
1534
|
});
|
|
1515
1535
|
result = {
|
|
1516
1536
|
ok: true,
|
|
@@ -72,6 +72,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
72
72
|
{ name: 'y', type: 'number', required: false, description: 'Optional Y position.' },
|
|
73
73
|
{ name: 'width', type: 'number', required: false, description: 'Optional node width.' },
|
|
74
74
|
{ name: 'height', type: 'number', required: false, description: 'Optional node height.' },
|
|
75
|
+
{ name: 'strictSize', type: 'boolean', required: false, description: 'Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting.', aliases: ['strict-size', 'scroll-overflow'] },
|
|
75
76
|
],
|
|
76
77
|
example: {
|
|
77
78
|
type: 'markdown',
|
|
@@ -203,6 +204,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
203
204
|
{ name: 'y', type: 'number', required: false, description: 'Optional Y position.' },
|
|
204
205
|
{ name: 'width', type: 'number', required: false, description: 'Optional node width.' },
|
|
205
206
|
{ name: 'height', type: 'number', required: false, description: 'Optional node height.' },
|
|
207
|
+
{ name: 'strictSize', type: 'boolean', required: false, description: 'Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting.', aliases: ['strict-size', 'scroll-overflow'] },
|
|
206
208
|
],
|
|
207
209
|
example: {
|
|
208
210
|
type: 'webpage',
|
|
@@ -337,8 +339,11 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
337
339
|
{ name: 'barColor', type: 'string', required: false, description: 'Optional bar color for composed charts.', aliases: ['bar-color'] },
|
|
338
340
|
{ name: 'lineColor', type: 'string', required: false, description: 'Optional line color for composed charts.', aliases: ['line-color'] },
|
|
339
341
|
{ name: 'height', type: 'number', required: false, description: 'Optional chart content height.', aliases: ['chart-height'] },
|
|
342
|
+
{ name: 'showLegend', type: 'boolean', required: false, description: 'Show chart legend when supported; pass false for compact node layouts.', aliases: ['show-legend'] },
|
|
343
|
+
{ name: 'showLabels', type: 'boolean', required: false, description: 'Show direct labels when supported, such as pie slice labels; defaults to true.', aliases: ['show-labels'] },
|
|
340
344
|
{ name: 'width', type: 'number', required: false, description: 'Optional node width.' },
|
|
341
345
|
{ name: 'nodeHeight', type: 'number', required: false, description: 'Optional node height (canvas frame). Distinct from `height`, which sets only the chart content height inside the node.', aliases: ['node-height'] },
|
|
346
|
+
{ name: 'strictSize', type: 'boolean', required: false, description: 'Keep explicit node size fixed and scroll overflowing content instead of browser auto-fitting.', aliases: ['strict-size', 'scroll-overflow'] },
|
|
342
347
|
],
|
|
343
348
|
example: {
|
|
344
349
|
title: 'Deploy Trend',
|
|
@@ -49,6 +49,13 @@ function fullyContains(group: CanvasNodeState, child: CanvasNodeState): boolean
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function isGroupChildPair(group: CanvasNodeState, child: CanvasNodeState): boolean {
|
|
53
|
+
if (group.type !== 'group') return false;
|
|
54
|
+
if (child.data.parentGroup === group.id) return true;
|
|
55
|
+
const children = group.data.children;
|
|
56
|
+
return Array.isArray(children) && children.includes(child.id);
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
function pair(a: CanvasNodeState, b: CanvasNodeState): CanvasValidationPair {
|
|
53
60
|
return {
|
|
54
61
|
aId: a.id,
|
|
@@ -78,11 +85,11 @@ export function validateCanvasLayout(layout: CanvasLayout): CanvasValidationResu
|
|
|
78
85
|
const b = layout.nodes[j]!;
|
|
79
86
|
if (!overlaps(a, b)) continue;
|
|
80
87
|
|
|
81
|
-
if (a
|
|
88
|
+
if (isGroupChildPair(a, b)) {
|
|
82
89
|
(fullyContains(a, b) ? containments : containmentViolations).push(containment(a, b));
|
|
83
90
|
continue;
|
|
84
91
|
}
|
|
85
|
-
if (b
|
|
92
|
+
if (isGroupChildPair(b, a)) {
|
|
86
93
|
(fullyContains(b, a) ? containments : containmentViolations).push(containment(b, a));
|
|
87
94
|
continue;
|
|
88
95
|
}
|