UNPKG

@fusedashlabs/ui9000-workspace

Version:

UI9000 workspace MCP. One tool, show_workspace, draws a chart from your table. Set TYPESAFE_API_KEY to let Jev choose the chart.

430 lines (429 loc) • 18.5 kB
/** * Visual families for the widgets charts. * * `show_workspace` asks Jev for a class, then a drawing inside that class. * A named chart in that class is drawn. A named chart in another class waits * for the user. When Jev does not answer, `decide()` still picks the chart. * Many widgets draw the same columns with a different mark. This file records * that: bar is the comparison main, and the same category + metric can also * be a lollipop, a line, or an area when the `when` holds. * * It does not add charts to the frozen 20-id engine catalog. */ export const CHART_ROLES = { 'bar-chart': { id: 'bar-chart', chartTypeKeys: [ 'barChart', 'barGrouped', 'barStacked', 'cumulativeBar', 'barHorizontal', 'barHorizontalGrouped', 'barHorizontalStacked', ], role: 'Compare discrete groups on one metric.', data: 'Category, one number, optional second category as a series.', visual: 'Bar length. Vertical or horizontal; plain, grouped, stacked, or with a cumulative line.', }, lollipop: { id: 'lollipop', chartTypeKeys: ['lollipopChart', 'lollipopGroupedChart', 'lollipopStackedChart'], role: 'Same comparison as a bar, drawn as a stem and a dot.', data: 'Category, one number, optional series.', visual: 'Stem length plus a head. Vertical or horizontal; plain, grouped, or stacked.', }, 'line-chart': { id: 'line-chart', chartTypeKeys: ['lineChart', 'lineGroupedChart'], role: 'Show how a metric moves along an ordered axis.', data: 'Ordered x (time or sequence) and a number, optional series.', visual: 'Connected points. One line, or one line per series.', }, 'area-chart': { id: 'area-chart', chartTypeKeys: ['areaChart', 'areaStackedChart'], role: 'Same ordered series as a line, with the region under the line filled.', data: 'Ordered x and a number, optional series.', visual: 'Filled area. Grouped or stacked.', }, 'area-grouped-bar-chart': { id: 'area-grouped-bar-chart', chartTypeKeys: ['areaGroupedBarChart'], role: 'Compare groups with bars and overlay an area or line on the same categories.', data: 'Category, a bar metric, and a second series for the overlay.', visual: 'Grouped bars plus an area or line. Vertical only.', }, 'radial-bar-chart': { id: 'radial-bar-chart', chartTypeKeys: ['radialBarChart'], role: 'Compare a few categories by ring length instead of a bar.', data: 'Category and one number. Few categories.', visual: 'One ring per category over a 270° sweep.', }, 'polar-area-chart': { id: 'polar-area-chart', chartTypeKeys: ['polarAreaChart'], role: 'Compare a few categories by wedge radius.', data: 'Category and one number, optional group.', visual: 'Coxcomb slices. Radius is the metric, not the share of a whole.', }, 'pie-chart': { id: 'pie-chart', chartTypeKeys: ['pieChart'], role: 'Show each category as a share of a total.', data: 'Categories whose metric sums to a whole. Low cardinality.', visual: 'Full disc. Angle is the share.', }, 'donut-chart': { id: 'donut-chart', chartTypeKeys: ['donutChart'], role: 'Same shares as a pie, with a hole in the middle.', data: 'Same as pie: categories that sum to a whole.', visual: 'Ring. Angle is the share.', }, 'treemap-chart': { id: 'treemap-chart', chartTypeKeys: ['treemapChart'], role: 'Show magnitude, or a share of a total, as tile area. A second category nests tiles.', data: 'Category and a number. Optional subgroup for one card per group.', visual: 'Area-proportional tiles. Color follows magnitude.', }, 'histogram-chart': { id: 'histogram-chart', chartTypeKeys: ['histogramChart'], role: 'Show the shape of one numeric variable.', data: 'Numeric samples, binned. Optional series stacks the bins.', visual: 'Adjacent bars along a linear x-axis.', }, 'box-plot-chart': { id: 'box-plot-chart', chartTypeKeys: ['boxplotChart'], role: 'Summarize a numeric sample per category: median, quartiles, outliers.', data: 'Numeric samples, split by a category.', visual: 'Box, whiskers, and outlier points. Vertical or horizontal.', }, 'violin-chart': { id: 'violin-chart', chartTypeKeys: ['violinChart'], role: 'Show the density of a numeric sample per category, with a box inside.', data: 'Numeric samples, split by a category.', visual: 'Kernel density plus a box overlay.', }, 'scatter-plot-chart': { id: 'scatter-plot-chart', chartTypeKeys: ['scatterplotChart', 'qqPlot'], role: 'Show the relationship between two numbers. Also a QQ plot of quantiles.', data: 'Numeric x, numeric y, optional group.', visual: 'One point per row. Color marks the group.', }, 'bubble-chart': { id: 'bubble-chart', chartTypeKeys: ['bubbleChart'], role: 'Same two-number relationship as a scatter, with a third number as size.', data: 'Numeric x, numeric y, a size number, optional group.', visual: 'Point radius from the absolute size. Color marks the group.', }, 'matrix-chart': { id: 'matrix-chart', chartTypeKeys: ['matrixChart'], role: 'Show the metric at each pair of categories.', data: 'Row category, column category, one number.', visual: 'Cell color on a sequential palette.', }, 'punchcard-chart': { id: 'punchcard-chart', chartTypeKeys: ['punchcardChart'], role: 'Same two-category grid as a matrix, drawn as dots.', data: 'Row category, column category, one number.', visual: 'Dot size (and position) encodes the metric.', }, 'radar-chart': { id: 'radar-chart', chartTypeKeys: ['radarChart', 'radarGroupedChart'], role: 'Compare one or a few entities across several metrics on a shared scale.', data: 'Few entities, several numeric axes.', visual: 'Polygon on a polar grid. One shape, or one per series.', }, 'parallel-coordinates-chart': { id: 'parallel-coordinates-chart', chartTypeKeys: ['parallelCoordinatesChart'], role: 'Compare many records across several numeric dimensions.', data: 'Many rows. Every numeric column is an axis.', visual: 'One polyline per record. Color follows the active axis.', }, 'sankey-chart': { id: 'sankey-chart', chartTypeKeys: ['sankeyChart'], role: 'Show how much moves from a source category to a target category.', data: 'Source, target, and a value.', visual: 'Nodes and value-coloured ribbons.', }, 'waterfall-chart': { id: 'waterfall-chart', chartTypeKeys: ['waterfallChart'], role: 'Show how signed steps add up to a total.', data: 'Ordered categories and a signed delta, or a running level.', visual: 'Floating bars. Vertical or horizontal.', }, 'map-chart': { id: 'map-chart', chartTypeKeys: ['mapChart', 'UniversalMap'], role: 'Place a metric on geography.', data: 'Region id or lat/lng, optional magnitude.', visual: 'Choropleth fill, bubbles, or spikes on a map.', }, 'network-graph': { id: 'network-graph', chartTypeKeys: ['networkGraphChart'], role: 'Show what is connected to what.', data: 'Nodes and links.', visual: 'Force layout. Node size from a value, color from a category.', }, 'kpi-widget': { id: 'kpi-widget', chartTypeKeys: ['KPI', 'KPIs'], role: 'State a headline card: one number, its change, current versus previous, or the high and low of a category.', data: 'One metric. A change, a previous period, or a category for high and low, when that reading was asked.', visual: 'Cards, not a plot.', }, 'status-gauge-widget': { id: 'status-gauge-widget', chartTypeKeys: ['statusGaugeWidget'], role: 'State one health score as a dial, plus the measures behind it.', data: 'A health or condition score that already exists, plus at least one other measure. Optional unit, status, min, and max.', visual: 'Semicircular health gauge and KPI cards with ticked range bars.', }, 'step-line-chart': { id: 'step-line-chart', chartTypeKeys: ['stepLineChart', 'ksPlotChart', 'rocCurveChart'], role: 'Show a metric that holds until the next x. Also KS and ROC reference overlays.', data: 'Ordered x and a number, optional series. KS/ROC add a reference curve.', visual: 'Step line. `ksPlotChart` draws the ideal curve; `rocCurveChart` draws the diagonal.', }, 'spark-line-chart': { id: 'spark-line-chart', chartTypeKeys: ['sparkLineChart'], role: 'Compact reading of a datetime series.', data: 'Datetime x and a number, optional series.', visual: 'Line with axes and a crosshair.', }, 'spark-area-chart': { id: 'spark-area-chart', chartTypeKeys: ['sparkAreaChart'], role: 'Compact datetime series with the region under the line filled.', data: 'Datetime x and a number, optional series.', visual: 'Line filled to y = 0.', }, 'scatter-sparkline-chart': { id: 'scatter-sparkline-chart', chartTypeKeys: ['scatterSparklineChart'], role: 'Datetime series as a line, with the underlying points still visible.', data: 'Datetime x, a number, and the raw points.', visual: 'Aggregated line with gradient fill plus jittered markers.', }, 'partial-dependence-chart': { id: 'partial-dependence-chart', chartTypeKeys: ['partialDependenceChart'], role: 'Show how a model prediction moves as one feature changes.', data: 'Feature values, ICE curves, and their average.', visual: 'Faint ICE lines under a dashed average.', }, 'bias-variance-tradeoff-chart': { id: 'bias-variance-tradeoff-chart', chartTypeKeys: ['biasVarianceTradeoffChart'], role: 'Show bias, variance, and total error against model complexity.', data: 'Complexity on x and the three error curves.', visual: 'Fixed diagnostic lines, not a general series.', }, 'gini-impurity-entropy-chart': { id: 'gini-impurity-entropy-chart', chartTypeKeys: ['giniImpurityEntropyChart'], role: 'Show impurity of a binary split across the class probability.', data: 'Probability p, and the entropy / Gini / misclassification curves.', visual: 'Fixed diagnostic curves, optional split annotations.', }, }; export const CHART_FAMILIES = [ { id: 'category-magnitude', question: 'How do discrete groups compare on one metric?', data: 'One category, one number, optional second category as a series.', main: 'bar-chart', alternatives: [ { id: 'lollipop', when: 'Same axes. Prefer it when a stem and a dot stay readable.' }, { id: 'line-chart', when: 'The category has an order (time or sequence), so the slope means something.' }, { id: 'area-chart', when: 'The category is ordered and the fill should show volume, not only the slope.' }, { id: 'area-grouped-bar-chart', when: 'The same categories need bars and an area or line overlay together.', }, { id: 'radial-bar-chart', when: 'Few categories, and ring length is the comparison.' }, { id: 'polar-area-chart', when: 'Few categories, and wedge radius is the comparison. Not a share of a whole.' }, ], }, { id: 'part-to-whole', question: 'What share of a total does each category take?', data: 'Categories whose metric sums to a whole. Low cardinality.', main: 'pie-chart', alternatives: [ { id: 'donut-chart', when: 'Same slices. The hole is only a drawing change.' }, { id: 'treemap-chart', when: 'Area should carry the share, or a second category nests tiles inside each group.', }, ], }, { id: 'ordered-series', question: 'How does a metric move along an ordered axis?', data: 'Ordered x (time or sequence) and a number, optional series.', main: 'line-chart', alternatives: [ { id: 'area-chart', when: 'Same line. The fill shows volume under it.' }, { id: 'step-line-chart', when: 'The value holds until the next x, or the chart is a KS / ROC overlay.' }, { id: 'spark-line-chart', when: 'The x is datetime and the reading should stay compact.' }, { id: 'spark-area-chart', when: 'Compact datetime series with a fill to zero.' }, { id: 'scatter-sparkline-chart', when: 'The datetime line should keep the raw points visible.' }, { id: 'bar-chart', when: 'The ordered x is a short list of categories and the reading is a comparison, not a slope.', }, ], }, { id: 'distribution', question: 'What shape does a numeric variable take?', data: 'Numeric samples. Optional category that splits the sample.', main: 'histogram-chart', alternatives: [ { id: 'box-plot-chart', when: 'The sample is split by category and the reading is quartiles and outliers.' }, { id: 'violin-chart', when: 'The sample is split by category and the reading is the density, not only the bins.' }, ], }, { id: 'two-measures', question: 'How do two numeric measures relate?', data: 'Numeric x, numeric y, optional group.', main: 'scatter-plot-chart', alternatives: [ { id: 'bubble-chart', when: 'A third number should set the point size.' }, ], }, { id: 'two-way-magnitude', question: 'What is the metric at each pair of categories?', data: 'Row category, column category, one number.', main: 'matrix-chart', alternatives: [ { id: 'punchcard-chart', when: 'The same grid should be dots sized by the metric, not colored cells.' }, ], }, { id: 'many-metrics', question: 'How does an entity compare across several metrics?', data: 'Several numeric measures. One profile, or many records.', main: 'radar-chart', alternatives: [ { id: 'parallel-coordinates-chart', when: 'There are many records, not one or a few profiles.', }, ], }, { id: 'flow', question: 'How much moves from a source category to a target category?', data: 'Source, target, and a value.', main: 'sankey-chart', alternatives: [], }, { id: 'contribution', question: 'How do signed steps add up to a total?', data: 'Ordered categories and a signed delta, or a running level.', main: 'waterfall-chart', alternatives: [], }, { id: 'spatial', question: 'Where is the metric?', data: 'Region id or lat/lng, optional magnitude.', main: 'map-chart', alternatives: [], }, { id: 'graph', question: 'What is connected to what?', data: 'Nodes and links.', main: 'network-graph', alternatives: [], }, { id: 'headline', question: 'What headline card fits: one number, its change, current versus previous, or the high and low category?', data: 'One metric. Optional change, previous period, or a category that splits high and low.', main: 'kpi-widget', alternatives: [ { id: 'status-gauge-widget', when: 'At least two measures, and one of them is already a health or condition score. The dial is that score. Do not invent it.' }, ], }, { id: 'model-dependence', question: 'How does a model prediction move as one feature changes?', data: 'Feature values and ICE curves.', main: 'partial-dependence-chart', alternatives: [], }, { id: 'model-error', question: 'How do bias and variance trade off against complexity?', data: 'Complexity and the bias, variance, and total-error curves.', main: 'bias-variance-tradeoff-chart', alternatives: [], }, { id: 'model-impurity', question: 'How impure is a binary split across the class probability?', data: 'Class probability and the impurity curves.', main: 'gini-impurity-entropy-chart', alternatives: [], }, ]; export function chartRole(id) { return CHART_ROLES[id]; } /** Words for a chart. The component id stays on the spec. */ export function chartName(id) { return id.replace(/-/g, ' '); } /** Name plus what the drawing shows, so a reply never says `bar-chart`. */ export function chartLabel(id) { const role = chartRole(id); if (!role) return 'this chart'; const described = role.role.trim().replace(/[.\s]+$/, ''); const gloss = described.charAt(0).toLowerCase() + described.slice(1); return `${chartName(id)} (${gloss})`; } /** Families where this chart is the main drawing or an alternative. */ export function familiesForChart(id) { return CHART_FAMILIES.filter((family) => family.main === id || family.alternatives.some((alt) => alt.id === id)); } /** * The main chart and the other drawings of the same data. * Looks up the family whose `main` is this id. */ export function drawingsFor(mainId) { const family = CHART_FAMILIES.find((item) => item.main === mainId); if (!family) return undefined; const main = chartRole(family.main); if (!main) return undefined; const alternatives = family.alternatives .map((alt) => chartRole(alt.id)) .filter((role) => role != null); return { family, main, alternatives }; }