semiotic 3.0.1 → 3.1.1
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/CLAUDE.md +227 -27
- package/README.md +147 -11
- package/ai/dist/componentRegistry.js +5 -0
- package/ai/dist/mcp-server.js +305 -30
- package/ai/examples.md +358 -18
- package/ai/schema.json +64 -2
- package/ai/system-prompt.md +50 -12
- package/dist/components/Legend.d.ts +7 -1
- package/dist/components/charts/geo/ChoroplethMap.d.ts +53 -0
- package/dist/components/charts/geo/DistanceCartogram.d.ts +90 -0
- package/dist/components/charts/geo/FlowMap.d.ts +83 -0
- package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +67 -0
- package/dist/components/charts/geo/index.d.ts +8 -0
- package/dist/components/charts/index.d.ts +2 -0
- package/dist/components/charts/network/ChordDiagram.d.ts +6 -5
- package/dist/components/charts/network/CirclePack.d.ts +2 -2
- package/dist/components/charts/network/ForceDirectedGraph.d.ts +9 -7
- package/dist/components/charts/network/OrbitDiagram.d.ts +21 -20
- package/dist/components/charts/network/SankeyDiagram.d.ts +6 -5
- package/dist/components/charts/network/TreeDiagram.d.ts +2 -2
- package/dist/components/charts/network/Treemap.d.ts +2 -2
- package/dist/components/charts/ordinal/BarChart.d.ts +7 -5
- package/dist/components/charts/ordinal/BoxPlot.d.ts +8 -6
- package/dist/components/charts/ordinal/DonutChart.d.ts +8 -6
- package/dist/components/charts/ordinal/DotPlot.d.ts +8 -6
- package/dist/components/charts/ordinal/GroupedBarChart.d.ts +7 -5
- package/dist/components/charts/ordinal/Histogram.d.ts +8 -5
- package/dist/components/charts/ordinal/PieChart.d.ts +8 -6
- package/dist/components/charts/ordinal/RidgelinePlot.d.ts +2 -0
- package/dist/components/charts/ordinal/StackedBarChart.d.ts +7 -5
- package/dist/components/charts/ordinal/SwarmPlot.d.ts +8 -6
- package/dist/components/charts/ordinal/ViolinPlot.d.ts +8 -5
- package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +24 -6
- package/dist/components/charts/realtime/RealtimeHistogram.d.ts +28 -7
- package/dist/components/charts/realtime/RealtimeLineChart.d.ts +23 -5
- package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +24 -6
- package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +23 -5
- package/dist/components/charts/shared/ChartError.d.ts +3 -1
- package/dist/components/charts/shared/colorUtils.d.ts +5 -0
- package/dist/components/charts/shared/hooks.d.ts +13 -1
- package/dist/components/charts/shared/legendUtils.d.ts +2 -3
- package/dist/components/charts/shared/statisticalOverlays.d.ts +1 -2
- package/dist/components/charts/shared/statisticalOverlaysLazy.d.ts +10 -0
- package/dist/components/charts/shared/tooltipUtils.d.ts +1 -1
- package/dist/components/charts/shared/types.d.ts +10 -4
- package/dist/components/charts/shared/useChartSetup.d.ts +112 -0
- package/dist/components/charts/shared/useStreamingLegend.d.ts +65 -0
- package/dist/components/charts/shared/withChartWrapper.d.ts +4 -3
- package/dist/components/charts/xy/AreaChart.d.ts +11 -6
- package/dist/components/charts/xy/BubbleChart.d.ts +11 -6
- package/dist/components/charts/xy/ConnectedScatterplot.d.ts +7 -6
- package/dist/components/charts/xy/Heatmap.d.ts +16 -5
- package/dist/components/charts/xy/LineChart.d.ts +21 -5
- package/dist/components/charts/xy/MinimapChart.d.ts +3 -0
- package/dist/components/charts/xy/QuadrantChart.d.ts +120 -0
- package/dist/components/charts/xy/Scatterplot.d.ts +9 -6
- package/dist/components/charts/xy/StackedAreaChart.d.ts +11 -6
- package/dist/components/geo/mergeData.d.ts +18 -0
- package/dist/components/geo/referenceGeography.d.ts +10 -0
- package/dist/components/geo/useReferenceAreas.d.ts +13 -0
- package/dist/components/realtime/RingBuffer.d.ts +1 -0
- package/dist/components/realtime/types.d.ts +17 -0
- package/dist/components/semiotic-data.d.ts +1 -0
- package/dist/components/semiotic-geo.d.ts +16 -0
- package/dist/components/semiotic-server.d.ts +1 -1
- package/dist/components/semiotic-xy.d.ts +1 -0
- package/dist/components/semiotic.d.ts +4 -4
- package/dist/components/server/renderToStaticSVG.d.ts +4 -2
- package/dist/components/stream/AccessibleDataTable.d.ts +50 -0
- package/dist/components/stream/CanvasHitTester.d.ts +8 -2
- package/dist/components/stream/DataSourceAdapter.d.ts +33 -4
- package/dist/components/stream/GeoCanvasHitTester.d.ts +19 -0
- package/dist/components/stream/GeoParticlePool.d.ts +46 -0
- package/dist/components/stream/GeoPipelineStore.d.ts +81 -0
- package/dist/components/stream/GeoTileRenderer.d.ts +31 -0
- package/dist/components/stream/NetworkPipelineStore.d.ts +16 -4
- package/dist/components/stream/NetworkSVGOverlay.d.ts +4 -1
- package/dist/components/stream/OrdinalPipelineStore.d.ts +8 -4
- package/dist/components/stream/OrdinalSVGOverlay.d.ts +23 -1
- package/dist/components/stream/PipelineStore.d.ts +57 -5
- package/dist/components/stream/SVGOverlay.d.ts +28 -1
- package/dist/components/stream/SceneGraph.d.ts +7 -3
- package/dist/components/stream/SceneToSVG.d.ts +2 -0
- package/dist/components/stream/StreamGeoFrame.d.ts +4 -0
- package/dist/components/stream/accessorUtils.d.ts +1 -0
- package/dist/components/stream/canvasSetup.d.ts +26 -0
- package/dist/components/stream/geoTypes.d.ts +186 -0
- package/dist/components/stream/layouts/forceLayoutPlugin.d.ts +0 -7
- package/dist/components/stream/layouts/index.d.ts +2 -1
- package/dist/components/stream/layouts/orbitLayoutPlugin.d.ts +2 -0
- package/dist/components/stream/legendRenderer.d.ts +33 -0
- package/dist/components/stream/networkTypes.d.ts +49 -1
- package/dist/components/stream/ordinalTypes.d.ts +10 -0
- package/dist/components/stream/pipelineTransitionUtils.d.ts +42 -0
- package/dist/components/stream/renderers/geoCanvasRenderer.d.ts +9 -0
- package/dist/components/stream/renderers/heatmapCanvasRenderer.d.ts +2 -1
- package/dist/components/stream/renderers/lineCanvasRenderer.d.ts +1 -0
- package/dist/components/stream/renderers/renderPulse.d.ts +50 -0
- package/dist/components/stream/types.d.ts +77 -3
- package/dist/components/types/legendTypes.d.ts +27 -3
- package/dist/geo.min.js +1 -0
- package/dist/geo.module.min.js +1 -0
- package/dist/network.min.js +1 -1
- package/dist/network.module.min.js +1 -1
- package/dist/ordinal.min.js +1 -1
- package/dist/ordinal.module.min.js +1 -1
- package/dist/realtime.min.js +1 -1
- package/dist/realtime.module.min.js +1 -1
- package/dist/semiotic-ai-statisticalOverlays-C1f7TYyD.js +1 -0
- package/dist/semiotic-ai.min.js +1 -1
- package/dist/semiotic-ai.module.min.js +1 -1
- package/dist/semiotic-data.d.ts +1 -0
- package/dist/semiotic-data.min.js +1 -1
- package/dist/semiotic-data.module.min.js +1 -1
- package/dist/semiotic-geo.d.ts +16 -0
- package/dist/semiotic-server.d.ts +1 -1
- package/dist/semiotic-statisticalOverlays-C1f7TYyD.js +1 -0
- package/dist/semiotic-xy.d.ts +1 -0
- package/dist/semiotic.d.ts +4 -4
- package/dist/semiotic.min.js +1 -1
- package/dist/semiotic.module.min.js +1 -1
- package/dist/server.min.js +1 -1
- package/dist/server.module.min.js +1 -1
- package/dist/test-utils/canvasMock.d.ts +3 -0
- package/dist/xy-statisticalOverlays-C1f7TYyD.js +1 -0
- package/dist/xy.min.js +1 -1
- package/dist/xy.module.min.js +1 -1
- package/package.json +76 -8
- package/dist/test/canvasMock.d.ts +0 -2
package/ai/dist/mcp-server.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Semiotic MCP Server
|
|
5
5
|
*
|
|
6
|
-
* Exposes
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Exposes five tools:
|
|
7
|
+
* 1. renderChart — renders any HOC chart to static SVG
|
|
8
|
+
* 2. diagnoseConfig — anti-pattern detector for chart configurations
|
|
9
|
+
* 3. reportIssue — generates a pre-filled GitHub issue URL for bugs/features
|
|
10
|
+
* 4. getSchema — returns the prop schema for a specific component
|
|
11
|
+
* 5. suggestChart — recommends chart types for a given data shape
|
|
9
12
|
*
|
|
10
13
|
* Usage (Claude Desktop / claude_desktop_config.json):
|
|
11
14
|
* {
|
|
@@ -58,7 +61,7 @@ const path = __importStar(require("path"));
|
|
|
58
61
|
const renderHOCToSVG_1 = require("./renderHOCToSVG");
|
|
59
62
|
const componentRegistry_1 = require("./componentRegistry");
|
|
60
63
|
const ai_1 = require("semiotic/ai");
|
|
61
|
-
// Load schema.json for
|
|
64
|
+
// Load schema.json for version info
|
|
62
65
|
const schemaPath = path.resolve(__dirname, "../schema.json");
|
|
63
66
|
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
|
|
64
67
|
// Build MCP server
|
|
@@ -66,38 +69,276 @@ const server = new mcp_js_1.McpServer({
|
|
|
66
69
|
name: "semiotic",
|
|
67
70
|
version: schema.version || "3.0.0",
|
|
68
71
|
});
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
// Build component name → schema lookup from schema.json
|
|
73
|
+
const schemaByComponent = {};
|
|
74
|
+
for (const tool of schema.tools) {
|
|
75
|
+
schemaByComponent[tool.function.name] = tool.function;
|
|
76
|
+
}
|
|
77
|
+
// ── getSchema tool ──────────────────────────────────────────────────────
|
|
78
|
+
// Returns the prop schema for a specific component, or lists all components.
|
|
79
|
+
server.tool("getSchema", `Return the prop schema for a Semiotic chart component. Pass { component: '<name>' } to get its props, or omit component to list all available components. Components marked [renderable] can be passed to renderChart for static SVG output.`, {}, async (args) => {
|
|
80
|
+
const component = args.component;
|
|
81
|
+
if (!component) {
|
|
82
|
+
const all = Object.keys(schemaByComponent).sort();
|
|
83
|
+
const renderable = new Set(Object.keys(componentRegistry_1.COMPONENT_REGISTRY));
|
|
84
|
+
const list = all.map(name => renderable.has(name) ? `${name} [renderable]` : name);
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: `Available components (${all.length}):\n${list.join(", ")}\n\nComponents marked [renderable] can be rendered to SVG via renderChart. Others (Realtime*) require a browser environment.\n\nPass { component: '<name>' } to get the prop schema for a specific component.` }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const entry = schemaByComponent[component];
|
|
90
|
+
if (!entry) {
|
|
91
|
+
const available = Object.keys(schemaByComponent).sort();
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: `Unknown component "${component}". Available: ${available.join(", ")}` }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const renderable = componentRegistry_1.COMPONENT_REGISTRY[component] ? "This component can be rendered to SVG via renderChart." : "This component requires a browser environment and cannot be rendered via renderChart.";
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: `${renderable}\n\n${JSON.stringify(entry, null, 2)}` }],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
// ── suggestChart tool ───────────────────────────────────────────────────
|
|
103
|
+
// Analyzes a data sample and recommends appropriate chart types.
|
|
104
|
+
server.tool("suggestChart", "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass { intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy' } to narrow suggestions. Returns ranked recommendations with example props.", {}, async (args) => {
|
|
105
|
+
const data = args.data;
|
|
106
|
+
const intent = args.intent;
|
|
107
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'." }],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const sample = data[0];
|
|
114
|
+
if (!sample || typeof sample !== "object") {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: "Data items must be objects with key-value pairs." }],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const keys = Object.keys(sample);
|
|
121
|
+
const suggestions = [];
|
|
122
|
+
// Classify fields
|
|
123
|
+
const numericFields = [];
|
|
124
|
+
const stringFields = [];
|
|
125
|
+
const dateFields = [];
|
|
126
|
+
const geoFields = {};
|
|
127
|
+
const networkFields = {};
|
|
128
|
+
const hierarchyFields = {};
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
const values = data.map(d => d[key]).filter(v => v != null);
|
|
131
|
+
if (values.length === 0)
|
|
132
|
+
continue;
|
|
133
|
+
const first = values[0];
|
|
134
|
+
if (typeof first === "number") {
|
|
135
|
+
numericFields.push(key);
|
|
136
|
+
}
|
|
137
|
+
else if (typeof first === "string") {
|
|
138
|
+
// Check for dates — require ISO-like pattern (YYYY-MM or YYYY/MM or YYYY-MM-DD, etc.)
|
|
139
|
+
// to avoid false positives on 4-digit IDs like "1234"
|
|
140
|
+
if (/^\d{4}[-/]\d{2}/.test(first) && !isNaN(Date.parse(first))) {
|
|
141
|
+
dateFields.push(key);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
stringFields.push(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Detect geo fields
|
|
148
|
+
const kl = key.toLowerCase();
|
|
149
|
+
if (kl === "lat" || kl === "latitude")
|
|
150
|
+
geoFields.lat = key;
|
|
151
|
+
if (kl === "lon" || kl === "lng" || kl === "longitude")
|
|
152
|
+
geoFields.lon = key;
|
|
153
|
+
// Detect network fields
|
|
154
|
+
if (kl === "source" || kl === "from")
|
|
155
|
+
networkFields.source = key;
|
|
156
|
+
if (kl === "target" || kl === "to")
|
|
157
|
+
networkFields.target = key;
|
|
158
|
+
if (kl === "value" || kl === "weight" || kl === "amount")
|
|
159
|
+
networkFields.value = key;
|
|
160
|
+
// Detect hierarchy fields
|
|
161
|
+
if (kl === "children" || kl === "values")
|
|
162
|
+
hierarchyFields.children = key;
|
|
163
|
+
if (kl === "parent")
|
|
164
|
+
hierarchyFields.parent = key;
|
|
165
|
+
}
|
|
166
|
+
const hasTime = dateFields.length > 0;
|
|
167
|
+
const hasCat = stringFields.length > 0;
|
|
168
|
+
const hasNum = numericFields.length > 0;
|
|
169
|
+
const hasGeo = geoFields.lat && geoFields.lon;
|
|
170
|
+
const hasNetwork = networkFields.source && networkFields.target;
|
|
171
|
+
const hasHierarchy = hierarchyFields.children || hierarchyFields.parent;
|
|
172
|
+
// Network data
|
|
173
|
+
if (hasNetwork && (!intent || intent === "network")) {
|
|
174
|
+
const src = networkFields.source;
|
|
175
|
+
const tgt = networkFields.target;
|
|
176
|
+
if (networkFields.value) {
|
|
177
|
+
suggestions.push({
|
|
178
|
+
component: "SankeyDiagram",
|
|
179
|
+
confidence: "high",
|
|
180
|
+
reason: `Data has ${src}→${tgt} with ${networkFields.value} — ideal for flow visualization`,
|
|
181
|
+
props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"`, valueAccessor: `"${networkFields.value}"` },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
suggestions.push({
|
|
185
|
+
component: "ForceDirectedGraph",
|
|
186
|
+
confidence: networkFields.value ? "medium" : "high",
|
|
187
|
+
reason: `Data has ${src}→${tgt} edges — force layout shows network structure. Nodes are auto-inferred from edges when not provided.`,
|
|
188
|
+
props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"` },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// Hierarchy data
|
|
192
|
+
if (hasHierarchy && (!intent || intent === "hierarchy")) {
|
|
193
|
+
suggestions.push({
|
|
194
|
+
component: "Treemap",
|
|
195
|
+
confidence: "high",
|
|
196
|
+
reason: `Data has nested ${hierarchyFields.children || "parent"} structure — treemap shows hierarchical proportions`,
|
|
197
|
+
props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"`, ...(numericFields[0] ? { valueAccessor: `"${numericFields[0]}"` } : {}) },
|
|
198
|
+
});
|
|
199
|
+
suggestions.push({
|
|
200
|
+
component: "TreeDiagram",
|
|
201
|
+
confidence: "medium",
|
|
202
|
+
reason: "Tree layout shows hierarchical relationships",
|
|
203
|
+
props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"` },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Geographic data
|
|
207
|
+
if (hasGeo && (!intent || intent === "geographic")) {
|
|
208
|
+
const sizeField = numericFields.find(f => f !== geoFields.lat && f !== geoFields.lon);
|
|
209
|
+
suggestions.push({
|
|
210
|
+
component: "ProportionalSymbolMap",
|
|
211
|
+
confidence: "high",
|
|
212
|
+
reason: `Data has ${geoFields.lat}/${geoFields.lon} coordinates — map shows spatial distribution`,
|
|
213
|
+
props: { points: "data", xAccessor: `"${geoFields.lon}"`, yAccessor: `"${geoFields.lat}"`, ...(sizeField ? { sizeBy: `"${sizeField}"` } : {}) },
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
// Time series
|
|
217
|
+
if (hasTime && hasNum && (!intent || intent === "trend")) {
|
|
218
|
+
const timeField = dateFields[0];
|
|
219
|
+
const valueField = numericFields[0];
|
|
220
|
+
suggestions.push({
|
|
221
|
+
component: "LineChart",
|
|
222
|
+
confidence: "high",
|
|
223
|
+
reason: `Data has dates (${timeField}) and numeric values (${valueField}) — line chart shows trends over time`,
|
|
224
|
+
props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, ...(hasCat ? { lineBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` } : {}) },
|
|
225
|
+
});
|
|
226
|
+
if (hasCat) {
|
|
227
|
+
suggestions.push({
|
|
228
|
+
component: "StackedAreaChart",
|
|
229
|
+
confidence: "medium",
|
|
230
|
+
reason: `Multiple categories (${stringFields[0]}) over time — stacked area shows composition trends`,
|
|
231
|
+
props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, areaBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Categorical + numeric
|
|
236
|
+
if (hasCat && hasNum && (!intent || intent === "comparison" || intent === "composition" || intent === "distribution")) {
|
|
237
|
+
const catField = stringFields[0];
|
|
238
|
+
const valField = numericFields[0];
|
|
239
|
+
if (!intent || intent === "comparison") {
|
|
240
|
+
suggestions.push({
|
|
241
|
+
component: "BarChart",
|
|
242
|
+
confidence: hasTime ? "medium" : "high",
|
|
243
|
+
reason: `Categorical field (${catField}) with values (${valField}) — bar chart for comparison`,
|
|
244
|
+
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` },
|
|
245
|
+
});
|
|
86
246
|
}
|
|
247
|
+
if (stringFields.length >= 2 && (!intent || intent === "composition")) {
|
|
248
|
+
suggestions.push({
|
|
249
|
+
component: "StackedBarChart",
|
|
250
|
+
confidence: "medium",
|
|
251
|
+
reason: `Two categorical fields (${stringFields.join(", ")}) — stacked bar shows composition within categories`,
|
|
252
|
+
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"`, stackBy: `"${stringFields[1]}"` },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (data.length >= 10 && (!intent || intent === "distribution")) {
|
|
256
|
+
suggestions.push({
|
|
257
|
+
component: "Histogram",
|
|
258
|
+
confidence: "medium",
|
|
259
|
+
reason: `${data.length}+ data points — histogram shows value distribution`,
|
|
260
|
+
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (!intent || intent === "composition") {
|
|
264
|
+
const uniqueCats = new Set(data.map(d => d[catField])).size;
|
|
265
|
+
if (uniqueCats <= 8) {
|
|
266
|
+
suggestions.push({
|
|
267
|
+
component: "DonutChart",
|
|
268
|
+
confidence: "medium",
|
|
269
|
+
reason: `${uniqueCats} categories — donut chart shows proportional composition`,
|
|
270
|
+
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` },
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Two numeric fields → scatterplot
|
|
276
|
+
if (numericFields.length >= 2 && (!intent || intent === "relationship")) {
|
|
277
|
+
const xField = numericFields[0];
|
|
278
|
+
const yField = numericFields[1];
|
|
279
|
+
suggestions.push({
|
|
280
|
+
component: "Scatterplot",
|
|
281
|
+
confidence: "high",
|
|
282
|
+
reason: `Two numeric fields (${xField}, ${yField}) — scatterplot shows relationships`,
|
|
283
|
+
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${yField}"`, ...(hasCat ? { colorBy: `"${stringFields[0]}"` } : {}), ...(numericFields[2] ? { sizeBy: `"${numericFields[2]}"` } : {}) },
|
|
284
|
+
});
|
|
285
|
+
if (numericFields.length >= 3) {
|
|
286
|
+
suggestions.push({
|
|
287
|
+
component: "BubbleChart",
|
|
288
|
+
confidence: "medium",
|
|
289
|
+
reason: `Three numeric fields — bubble chart adds size dimension to scatter`,
|
|
290
|
+
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${yField}"`, sizeBy: `"${numericFields[2]}"` },
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (numericFields.length >= 2 && hasCat) {
|
|
294
|
+
suggestions.push({
|
|
295
|
+
component: "Heatmap",
|
|
296
|
+
confidence: "medium",
|
|
297
|
+
reason: `Numeric values across dimensions — heatmap shows density/intensity`,
|
|
298
|
+
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${hasCat ? stringFields[0] : yField}"`, valueAccessor: `"${hasCat ? numericFields[0] : numericFields[2] || yField}"` },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Fallback
|
|
303
|
+
if (suggestions.length === 0) {
|
|
304
|
+
const fieldSummary = `Fields: ${keys.join(", ")} (${numericFields.length} numeric, ${stringFields.length} categorical, ${dateFields.length} date)`;
|
|
87
305
|
return {
|
|
88
|
-
content: [{ type: "text", text:
|
|
306
|
+
content: [{ type: "text", text: `Could not confidently recommend a chart type.\n\n${fieldSummary}\n\nTry providing intent ('comparison', 'trend', 'distribution', 'relationship', 'composition', 'geographic', 'network', 'hierarchy') to narrow recommendations, or use getSchema to browse available components.` }],
|
|
89
307
|
};
|
|
308
|
+
}
|
|
309
|
+
// Format output
|
|
310
|
+
const lines = suggestions.map((s, i) => {
|
|
311
|
+
const propsStr = Object.entries(s.props).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
312
|
+
return `${i + 1}. **${s.component}** (${s.confidence} confidence)\n ${s.reason}\n \`<${s.component} ${propsStr} />\``;
|
|
90
313
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
314
|
+
return {
|
|
315
|
+
content: [{ type: "text", text: lines.join("\n\n") }],
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
// ── renderChart tool ─────────────────────────────────────────────────────
|
|
319
|
+
// Generic tool that renders any Semiotic HOC chart to static SVG.
|
|
320
|
+
// Accepts { component, props } — the single entry point for all chart rendering.
|
|
321
|
+
const componentNames = Object.keys(componentRegistry_1.COMPONENT_REGISTRY).sort();
|
|
322
|
+
server.tool("renderChart", `Render any Semiotic chart to static SVG. Pass { component: '<name>', props: { ... } }. Returns SVG string or validation errors. Available components: ${componentNames.join(", ")}.`, {}, async (args) => {
|
|
96
323
|
const component = args.component;
|
|
97
|
-
|
|
324
|
+
let props;
|
|
325
|
+
if (args.props) {
|
|
326
|
+
props = args.props;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Flatten shape: { component, data, ... } — strip component before forwarding
|
|
330
|
+
const { component: _, ...rest } = args;
|
|
331
|
+
props = rest;
|
|
332
|
+
}
|
|
98
333
|
if (!component) {
|
|
99
334
|
return {
|
|
100
|
-
content: [{ type: "text", text:
|
|
335
|
+
content: [{ type: "text", text: `Missing 'component' field. Provide { component: '<name>', props: { ... } }. Available: ${componentNames.join(", ")}` }],
|
|
336
|
+
isError: true,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (!componentRegistry_1.COMPONENT_REGISTRY[component]) {
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: `Unknown component "${component}". Available: ${componentNames.join(", ")}` }],
|
|
101
342
|
isError: true,
|
|
102
343
|
};
|
|
103
344
|
}
|
|
@@ -117,7 +358,14 @@ server.tool("renderChart", "Render any Semiotic chart to static SVG. Pass { comp
|
|
|
117
358
|
// actionable fix instructions.
|
|
118
359
|
server.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, etc). Pass { component: 'LineChart', props: { ... } }. Returns structured diagnoses with fix instructions.", {}, async (args) => {
|
|
119
360
|
const component = args.component;
|
|
120
|
-
|
|
361
|
+
let props;
|
|
362
|
+
if (args.props) {
|
|
363
|
+
props = args.props;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const { component: _, ...rest } = args;
|
|
367
|
+
props = rest;
|
|
368
|
+
}
|
|
121
369
|
if (!component) {
|
|
122
370
|
return {
|
|
123
371
|
content: [{ type: "text", text: "Missing 'component' field. Provide { component: 'LineChart', props: { ... } }." }],
|
|
@@ -142,6 +390,33 @@ server.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for commo
|
|
|
142
390
|
isError: true,
|
|
143
391
|
};
|
|
144
392
|
});
|
|
393
|
+
// ── reportIssue tool ─────────────────────────────────────────────────────
|
|
394
|
+
// Generates a pre-filled GitHub issue URL for bug reports or feature requests.
|
|
395
|
+
// The user (or AI agent) can open the URL to submit — no auth needed.
|
|
396
|
+
const REPO = "nteract/semiotic";
|
|
397
|
+
server.tool("reportIssue", "Generate a GitHub issue URL for Semiotic bug reports or feature requests. Pass { title, body, labels? }. Returns a URL the user can open to submit. For rendering bugs, include the component name, props summary, and any diagnoseConfig output in the body.", {}, async (args) => {
|
|
398
|
+
const title = args.title;
|
|
399
|
+
const body = args.body;
|
|
400
|
+
const labels = args.labels;
|
|
401
|
+
if (!title) {
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: "text", text: "Missing 'title' field. Provide { title: 'Bug: ...', body: '...', labels?: ['bug'] }." }],
|
|
404
|
+
isError: true,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const params = new URLSearchParams();
|
|
408
|
+
params.set("title", title);
|
|
409
|
+
if (body)
|
|
410
|
+
params.set("body", body);
|
|
411
|
+
if (labels) {
|
|
412
|
+
const labelList = Array.isArray(labels) ? labels.join(",") : labels;
|
|
413
|
+
params.set("labels", labelList);
|
|
414
|
+
}
|
|
415
|
+
const url = `https://github.com/${REPO}/issues/new?${params.toString()}`;
|
|
416
|
+
return {
|
|
417
|
+
content: [{ type: "text", text: `Open this URL to submit the issue:\n\n${url}` }],
|
|
418
|
+
};
|
|
419
|
+
});
|
|
145
420
|
// Start the server
|
|
146
421
|
async function main() {
|
|
147
422
|
const transport = new stdio_js_1.StdioServerTransport();
|