@graphit/cli 0.1.0

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.
Files changed (62) hide show
  1. package/dist/api/client.d.ts +16 -0
  2. package/dist/api/client.js +67 -0
  3. package/dist/api/client.js.map +1 -0
  4. package/dist/auth/credentials.d.ts +15 -0
  5. package/dist/auth/credentials.js +34 -0
  6. package/dist/auth/credentials.js.map +1 -0
  7. package/dist/auth/login.d.ts +2 -0
  8. package/dist/auth/login.js +229 -0
  9. package/dist/auth/login.js.map +1 -0
  10. package/dist/auth/token.d.ts +5 -0
  11. package/dist/auth/token.js +42 -0
  12. package/dist/auth/token.js.map +1 -0
  13. package/dist/commands/auth.d.ts +2 -0
  14. package/dist/commands/auth.js +68 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/connector.d.ts +2 -0
  17. package/dist/commands/connector.js +97 -0
  18. package/dist/commands/connector.js.map +1 -0
  19. package/dist/commands/dashboard.d.ts +2 -0
  20. package/dist/commands/dashboard.js +124 -0
  21. package/dist/commands/dashboard.js.map +1 -0
  22. package/dist/commands/ds.d.ts +2 -0
  23. package/dist/commands/ds.js +53 -0
  24. package/dist/commands/ds.js.map +1 -0
  25. package/dist/commands/kb.d.ts +2 -0
  26. package/dist/commands/kb.js +259 -0
  27. package/dist/commands/kb.js.map +1 -0
  28. package/dist/commands/query.d.ts +2 -0
  29. package/dist/commands/query.js +61 -0
  30. package/dist/commands/query.js.map +1 -0
  31. package/dist/commands/setup.d.ts +2 -0
  32. package/dist/commands/setup.js +173 -0
  33. package/dist/commands/setup.js.map +1 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +24 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/output/format.d.ts +8 -0
  38. package/dist/output/format.js +30 -0
  39. package/dist/output/format.js.map +1 -0
  40. package/dist/output/json.d.ts +1 -0
  41. package/dist/output/json.js +4 -0
  42. package/dist/output/json.js.map +1 -0
  43. package/dist/output/table.d.ts +2 -0
  44. package/dist/output/table.js +17 -0
  45. package/dist/output/table.js.map +1 -0
  46. package/package.json +38 -0
  47. package/skill/SKILL.md +163 -0
  48. package/skill/cursor/graphit-chart-patterns.mdc +148 -0
  49. package/skill/cursor/graphit-chart-selection.mdc +89 -0
  50. package/skill/cursor/graphit-dashboard-planning.mdc +100 -0
  51. package/skill/cursor/graphit-domain-lenses.mdc +120 -0
  52. package/skill/cursor/graphit-kb-exploration.mdc +75 -0
  53. package/skill/cursor/graphit-sql-reference.mdc +127 -0
  54. package/skill/cursor/graphit-style.mdc +123 -0
  55. package/skill/graphit.mdc +135 -0
  56. package/skill/references/chart-patterns.md +142 -0
  57. package/skill/references/chart-selection.md +83 -0
  58. package/skill/references/dashboard-planning.md +94 -0
  59. package/skill/references/domain-lenses.md +114 -0
  60. package/skill/references/graphit-style.md +117 -0
  61. package/skill/references/kb-exploration.md +69 -0
  62. package/skill/references/sql-reference.md +121 -0
@@ -0,0 +1,123 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Graphit: Consult when building the HTML dashboard. Covers design principles, the Graphit aesthetic, typography, color system, layout patterns, and all inline chart implementations."
4
+ globs: []
5
+ ---
6
+
7
+ # Graphit Visual Style
8
+
9
+ Consult when building the HTML dashboard. Covers design principles, the Graphit aesthetic, typography, color system, layout patterns, and all inline chart implementations.
10
+
11
+ ## Design Principles
12
+
13
+ Graphit dashboards are clean, minimal, and data-forward:
14
+ - **Card on themed surface** - content cards on `var(--graphit-surface)` background
15
+ - **Generous whitespace** - 24px page padding, 16px card gaps, 24px card padding
16
+ - **Subtle depth** - `box-shadow: 0 1px 3px rgba(0,0,0,0.08)`, not heavy borders
17
+ - **Uppercase micro-labels** - 12px, 600 weight, `var(--graphit-fg-subtle)`, letter-spacing 0.05em for section headers
18
+ - **Data is hero** - numbers are large and bold; chrome is minimal and muted
19
+ - **Responsive** - grids collapse at 900px breakpoint
20
+
21
+ ## Color Tokens
22
+
23
+ NEVER use hardcoded hex colors. Use CSS custom properties that automatically adapt to light/dark theme:
24
+
25
+ | Token | Usage |
26
+ |---|---|
27
+ | `var(--graphit-surface)` | Page background |
28
+ | `var(--graphit-surface-raised)` | Cards, panels |
29
+ | `var(--graphit-surface-sunken)` | Inset areas, wells |
30
+ | `var(--graphit-fg)` | Primary text |
31
+ | `var(--graphit-fg-muted)` | Secondary text |
32
+ | `var(--graphit-fg-subtle)` | Tertiary text, placeholders, labels |
33
+ | `var(--graphit-border)` | Default borders |
34
+ | `var(--graphit-border-strong)` | Emphasized borders |
35
+ | `var(--graphit-accent)` | Brand teal, primary action color |
36
+ | `var(--graphit-error)` | Error/danger text |
37
+ | `var(--graphit-success)` | Success indicators |
38
+ | `var(--graphit-warning)` | Warning indicators |
39
+ | `var(--graphit-highlight)` | Highlighted/selected backgrounds |
40
+
41
+ ### Semantic Colors
42
+ - **Positive / good:** `var(--graphit-accent)` (teal) or `var(--graphit-success)`
43
+ - **Warning:** `var(--graphit-warning)`
44
+ - **Danger / bad:** `var(--graphit-error)`
45
+
46
+ ### Multi-Series
47
+ Use `var(--graphit-accent)` as the primary series. For additional series, choose muted complementary colors that work on both light and dark backgrounds.
48
+
49
+ ### Usage Rules
50
+ - Accent teal for primary bars, lines, and positive indicators
51
+ - Assign series colors consistently across charts sharing a dimension
52
+ - Financial variance: green = favorable, red = unfavorable (regardless of sign direction)
53
+
54
+ ## Typography Scale
55
+
56
+ Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif`
57
+
58
+ | Element | Size | Weight | Color | Use |
59
+ |---|---|---|---|---|
60
+ | KPI number | 32px | 700 | `var(--graphit-fg)` | Primary metric value |
61
+ | Card title / section header | 12px | 600 | `var(--graphit-fg-subtle)` | Uppercase, letter-spaced |
62
+ | Body text / table cells | 14px | 400 | `var(--graphit-fg)` | Default content |
63
+ | Labels / subtitles | 13px | 500 | `var(--graphit-fg-subtle)` | Supporting text |
64
+ | Badges / deltas | 12px | 700 | contextual | Change indicators |
65
+ | Chart axis labels | 11px | 400 | `var(--graphit-fg-subtle)` | Axis ticks |
66
+
67
+ ## Layout Patterns
68
+
69
+ ### Page Structure
70
+ ```html
71
+ <style>
72
+ * { margin:0; padding:0; box-sizing:border-box; }
73
+ body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
74
+ background:var(--graphit-surface); color:var(--graphit-fg); padding:24px; }
75
+ .kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:24px; }
76
+ .charts-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:24px; }
77
+ .full-width { grid-column:1/-1; }
78
+ .card { background:var(--graphit-surface-raised); border-radius:12px; padding:24px;
79
+ box-shadow:0 1px 3px rgba(0,0,0,0.08); }
80
+ .card h3 { font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
81
+ letter-spacing:0.05em; margin-bottom:16px; }
82
+ @media(max-width:900px) {
83
+ .kpi-grid { grid-template-columns:repeat(2,1fr); }
84
+ .charts-grid { grid-template-columns:1fr; }
85
+ }
86
+ </style>
87
+ ```
88
+
89
+ ### Dashboard Composition (top to bottom)
90
+ 1. **Title row** - dashboard name, optional subtitle with date range
91
+ 2. **KPI row** - 3-4 cards in a grid. Each: big number, label, delta badge
92
+ 3. **Primary charts** - 2-column grid of detail visualizations
93
+ 4. **Detail table** - full-width card at bottom for raw data exploration
94
+
95
+ ### KPI Card
96
+ ```html
97
+ <div class="card">
98
+ <div style="font-size:32px;font-weight:700;line-height:1.2">$144,661</div>
99
+ <div style="font-size:13px;color:var(--graphit-fg-subtle);margin-top:4px">Total Ad Spend</div>
100
+ <div style="font-size:12px;margin-top:8px;font-weight:600;color:var(--graphit-accent)">+12% vs prior</div>
101
+ </div>
102
+ ```
103
+
104
+ ### Data Table
105
+ ```html
106
+ <style>
107
+ table { width:100%; border-collapse:collapse; }
108
+ th { text-align:left; font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
109
+ letter-spacing:0.05em; padding:10px 16px; border-bottom:2px solid var(--graphit-border); }
110
+ th:not(:first-child) { text-align:right; }
111
+ td { padding:12px 16px; font-size:14px; border-bottom:1px solid var(--graphit-border); }
112
+ td:not(:first-child) { text-align:right; }
113
+ tr:hover td { background:var(--graphit-surface-sunken); }
114
+ .badge { display:inline-block; padding:2px 8px; border-radius:12px; font-size:12px; font-weight:700; }
115
+ .badge-good { background:var(--graphit-highlight); color:var(--graphit-success); }
116
+ .badge-warn { background:var(--graphit-highlight); color:var(--graphit-warning); }
117
+ .badge-bad { background:var(--graphit-highlight); color:var(--graphit-error); }
118
+ </style>
119
+ ```
120
+
121
+
122
+
123
+ For inline chart implementations (bar, line, donut, heatmap, funnel, sparkline, gauge, stacked bar, multi-series), see `chart-patterns.md`.
@@ -0,0 +1,135 @@
1
+ ---
2
+ alwaysApply: false
3
+ description: "Build Graphit HTML dashboards with KB-aware queries and entity wrapping"
4
+ globs: []
5
+ ---
6
+
7
+ # Graphit CLI
8
+
9
+ Build custom HTML dashboards from real data using the Graphit CLI.
10
+
11
+ ## HARD CONSTRAINTS (violating these produces a broken dashboard)
12
+
13
+ ### 1. ZERO external resources
14
+ The dashboard renders in a sandboxed iframe with a strict CSP. External requests are BLOCKED.
15
+ Your HTML must NEVER contain:
16
+ - `<script src="...">` - no Chart.js, D3.js, Alpine.js, ANY external JS
17
+ - `<link href="...">` - no Tailwind CDN, Google Fonts, ANY external CSS
18
+ - `<img src="https://...">` - no external images
19
+
20
+ If you use ANY `src=` or `href=` pointing to a URL, the dashboard will be blank.
21
+ Build charts with: CSS width% bars, inline SVG paths, or `<canvas>` with inline drawing code.
22
+ All CSS in `<style>`, all JS in `<script>`, all fonts from system stack.
23
+
24
+ ### 2. EVERY element must have entity wrapping
25
+ Without `data-graphit-*` attributes, elements are invisible to the platform - no click info, no mentions, no KB provenance. Every chart, KPI card, table, and text section needs ALL FOUR attributes:
26
+
27
+ ```html
28
+ <div data-graphit-id="revenue-trend"
29
+ data-graphit-label="Revenue Trend"
30
+ data-graphit-kb="metric:REVENUE,dimension:REGION,table:ORDERS"
31
+ data-graphit-sql="SELECT region, SUM(revenue) FROM orders GROUP BY region">
32
+ <!-- chart/KPI/table content here -->
33
+ </div>
34
+ ```
35
+
36
+ | Attribute | Format | Example |
37
+ |-----------|--------|---------|
38
+ | `data-graphit-id` | Unique kebab-case | `"spend-by-source"` |
39
+ | `data-graphit-label` | Human-readable name | `"Ad Spend by Source"` |
40
+ | `data-graphit-kb` | `type:NAME` comma-separated | `"metric:CPI,dimension:MEDIA_SOURCE,table:MARKETING_UA"` |
41
+ | `data-graphit-sql` | SQL query (HTML-encode `<>&"`) | `"SELECT ..."` |
42
+
43
+ KB types: `metric`, `dimension`, `table`, `rule`. Names are UPPER_SNAKE_CASE matching the KB exactly.
44
+
45
+ **Label = visible title.** The `data-graphit-label` MUST match the card's visible heading exactly. Users see labels in @ mention dropdowns - mismatched labels mean they can't find their chart.
46
+
47
+ ### 2. ALWAYS query through data sources
48
+ NEVER query the warehouse directly when a cached data source covers the table. Data sources return in ~100ms. Warehouse queries take ~10s and cost Snowflake credits.
49
+
50
+ Run `graphit ds list` FIRST. If a DS covers your table, use `graphit query "SQL" --ds <id>`. Only use `--warehouse` if NO data source exists and the user approves.
51
+
52
+ ---
53
+
54
+ ## Workflow
55
+
56
+ 1. **Ask the user** what dashboard they want. Don't start querying until you know what they need.
57
+ 2. **Explore KB** to understand available metrics, dimensions, tables, and tables.
58
+ 3. **Find a data source** (`graphit ds list`) - prefer cached data sources (~100ms) over live warehouse (~10s).
59
+ 4. **Query data** and embed results as inline JS variables.
60
+ 5. **Build HTML** - all CSS and JS must be inline. Write to a local `.html` file.
61
+ 6. **Save** with `graphit dashboard update-html <id> --file <path>`.
62
+ 7. Give the user the dashboard URL so they can open it.
63
+
64
+ ## Commands
65
+
66
+ | Command | Description |
67
+ |---------|-------------|
68
+ | `graphit kb list <type>` | List metrics, dimensions, tables, rules, domains, synonyms |
69
+ | `graphit kb get <type> <name>` | Full entity details by name |
70
+ | `graphit kb search <query>` | Search across all KB types |
71
+ | `graphit kb explore metric <name>` | Metric -> tables -> dimensions graph |
72
+ | `graphit ds list` | List cached data sources (use these for fast queries) |
73
+ | `graphit query "<sql>" --ds <id>` | Query cached data source (~100ms) |
74
+ | `graphit query "<sql>" --warehouse --connection <id>` | Query live Snowflake (~10s) |
75
+ | `graphit dashboard create --name "..."` | Create dashboard (returns ID) |
76
+ | `graphit dashboard update-html <id> --file <path>` | Upload HTML to dashboard |
77
+ | `graphit dashboard list` | List existing dashboards |
78
+ | `graphit metadata schemas --connection <id>` | List Snowflake schemas |
79
+ | `graphit connector list` | List active connections |
80
+
81
+ ## Presenting Results to the User
82
+
83
+ The user CANNOT see raw CLI output clearly. You MUST format and present every result - never silently consume tool output and move on.
84
+
85
+ **After every query**, show the data:
86
+ ```
87
+ Queried **MARKETING_UA** (ds_abc123, 6 rows):
88
+
89
+ | Channel | Spend | Installs | CPI |
90
+ |-------------|----------|----------|-------|
91
+ | Facebook | $42,100 | 12,400 | $3.40 |
92
+ | Google UAC | $38,500 | 9,800 | $3.93 |
93
+ | TikTok | $21,300 | 8,200 | $2.60 |
94
+ ```
95
+ ```
96
+
97
+ **After KB exploration**, summarize what you found:
98
+ ```
99
+ Found **12 metrics** and **8 dimensions** on table MARKETING_UA:
100
+ - **Metrics:** TOTAL_SPEND, CPI, ROAS_D7, ROAS_D30, INSTALLS, ...
101
+ - **Dimensions:** MEDIA_SOURCE, CAMPAIGN_NAME, COUNTRY, PLATFORM, ...
102
+ - **Rules:** EXCLUDE_ORGANIC (filters organic installs from paid metrics)
103
+ ```
104
+
105
+ **Formatting rules:**
106
+ - **Bold** metric names, table names, and key numbers
107
+ - Use **markdown tables** for any tabular data (query results, entity lists, comparisons)
108
+ - Format numbers: commas for thousands (`12,400`), `$` for currency, `%` for rates
109
+ - After KB or data source discovery, list what's available before asking what to build
110
+ - Show the SQL you ran (in a code block) so the user can validate the logic
111
+ - When a query returns nulls or zero rows, explain what you checked and what went wrong
112
+ - Narrate your progress between steps: "Found 3 data sources. Using **Marketing UA DS** (ds_abc123) which covers spend, installs, and ROAS columns."
113
+
114
+ **Never do these:**
115
+ - Run 3 queries silently then jump to building HTML
116
+ - Say "I found the data" without showing what the data looks like
117
+ - Present raw JSON output without formatting
118
+ - Skip showing KB exploration results before proposing a dashboard
119
+
120
+ ---
121
+
122
+ ## Chart Type Quick Reference
123
+
124
+ | Data shape | Chart | Inline technique |
125
+ |---|---|---|
126
+ | 1 temporal + 1 numeric | Line | SVG path |
127
+ | 1 categorical + 1 numeric | Bar | CSS width% divs |
128
+ | Single number | KPI card | Styled div |
129
+ | Part-whole (max 5) | Donut | Canvas arc |
130
+ | Matrix / cohort | Heatmap | CSS grid + opacity |
131
+ | Stages | Funnel | CSS width% bars, narrowing |
132
+ | Time sparkline | Sparkline | Inline SVG path |
133
+ | Detail / raw data | Table | HTML table + hover rows |
134
+
135
+ For detailed chart selection logic, dashboard planning, SQL reference, domain-specific lenses, Graphit visual style, and chart pattern implementations, see the companion rule files (graphit-planning, graphit-sql, graphit-lenses, graphit-style, graphit-kb, graphit-patterns).
@@ -0,0 +1,142 @@
1
+ # Chart Patterns
2
+
3
+ Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources.
4
+
5
+ ## CSS Horizontal Bar
6
+ ```js
7
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
8
+ data.forEach(function(d) {
9
+ var pct = (d.value / maxVal * 100).toFixed(1);
10
+ container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
11
+ + '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + d.name + '</span>'
12
+ + '<div style="flex:1;height:28px;background:var(--graphit-surface-sunken);border-radius:6px;overflow:hidden">'
13
+ + '<div style="width:' + pct + '%;height:100%;background:var(--graphit-accent);border-radius:6px;min-width:2px"></div></div>'
14
+ + '<span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + fmt(d.value) + '</span></div>';
15
+ });
16
+ ```
17
+
18
+ ## CSS Vertical Bar (Column)
19
+ ```js
20
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
21
+ container.style.cssText = 'display:flex;align-items:flex-end;gap:8px;height:200px;padding-top:20px';
22
+ data.forEach(function(d) {
23
+ var pct = (d.value / maxVal * 100).toFixed(1);
24
+ container.innerHTML += '<div style="flex:1;height:' + pct + '%;background:var(--graphit-accent);border-radius:4px 4px 0 0;'
25
+ + 'position:relative;min-width:30px">'
26
+ + '<span style="position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-size:12px;font-weight:600">'
27
+ + d.value + '</span>'
28
+ + '<span style="position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--graphit-fg-subtle);white-space:nowrap">'
29
+ + d.name + '</span></div>';
30
+ });
31
+ ```
32
+
33
+ ## CSS Stacked Bar
34
+ ```js
35
+ var colors = ['var(--graphit-accent)','#80CBC4','#26A69A','#5FA39B'];
36
+ data.forEach(function(row) {
37
+ var total = row.segments.reduce(function(a,s){return a+s.value},0);
38
+ var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
39
+ + '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + row.name + '</span>'
40
+ + '<div style="flex:1;height:28px;display:flex;border-radius:6px;overflow:hidden">';
41
+ row.segments.forEach(function(s, i) {
42
+ html += '<div style="width:' + (s.value/total*100).toFixed(1) + '%;height:100%;background:' + colors[i%colors.length] + '"></div>';
43
+ });
44
+ html += '</div><span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + total + '</span></div>';
45
+ container.innerHTML += html;
46
+ });
47
+ ```
48
+
49
+ ## SVG Line/Area Chart
50
+ ```js
51
+ var w = 600, h = 200, pad = 20;
52
+ var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
53
+ var points = data.map(function(d, i) {
54
+ var x = pad + (i / (data.length - 1)) * (w - 2*pad);
55
+ var y = h - pad - ((d.value / maxVal) * (h - 2*pad));
56
+ return x + ',' + y;
57
+ });
58
+ svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
59
+ svg.innerHTML =
60
+ '<path d="M' + points.join(' L') + ' L' + (w-pad) + ',' + (h-pad) + ' L' + pad + ',' + (h-pad) + ' Z" fill="rgba(77,182,172,0.15)"/>'
61
+ + '<path d="M' + points.join(' L') + '" fill="none" stroke="var(--graphit-accent)" stroke-width="2"/>';
62
+ ```
63
+
64
+ ## SVG Multi-Series Line
65
+ ```js
66
+ var colors = ['var(--graphit-accent)','#FF7043','#7E57C2','#42A5F5'];
67
+ series.forEach(function(s, si) {
68
+ var pts = s.values.map(function(v, i) {
69
+ var x = pad + (i / (s.values.length - 1)) * (w - 2*pad);
70
+ var y = h - pad - ((v / maxAll) * (h - 2*pad));
71
+ return x + ',' + y;
72
+ });
73
+ svg.innerHTML += '<path d="M' + pts.join(' L') + '" fill="none" stroke="' + colors[si] + '" stroke-width="2"/>';
74
+ });
75
+ ```
76
+
77
+ ## SVG Sparkline (for KPI cards)
78
+ ```js
79
+ var vals = [10,15,12,18,22,19,25];
80
+ var max = Math.max.apply(null,vals), min = Math.min.apply(null,vals);
81
+ var pts = vals.map(function(v,i){
82
+ return (i/(vals.length-1))*120 + ',' + (30 - ((v-min)/(max-min||1))*26 - 2);
83
+ }).join(' L');
84
+ sparkSvg.innerHTML = '<path d="M' + pts + '" fill="none" stroke="var(--graphit-accent)" stroke-width="1.5"/>';
85
+ ```
86
+
87
+ ## Canvas Donut Chart
88
+ ```js
89
+ var ctx = canvas.getContext('2d');
90
+ var slices = [{v:40,c:'var(--graphit-accent)'},{v:25,c:'#80CBC4'},{v:20,c:'#26A69A'},{v:15,c:'#009688'}];
91
+ var total = slices.reduce(function(a,s){return a+s.v},0);
92
+ var cx=150, cy=150, r=120, hole=70, angle = -Math.PI/2;
93
+ slices.forEach(function(s) {
94
+ var arc = (s.v/total)*Math.PI*2;
95
+ ctx.beginPath(); ctx.moveTo(cx,cy);
96
+ ctx.arc(cx,cy,r,angle,angle+arc);
97
+ ctx.fillStyle=s.c; ctx.fill(); angle+=arc;
98
+ });
99
+ ctx.beginPath(); ctx.arc(cx,cy,hole,0,Math.PI*2);
100
+ ctx.fillStyle='white'; ctx.fill();
101
+ ```
102
+
103
+ ## CSS Heatmap Grid
104
+ ```js
105
+ var max = Math.max.apply(null, cells.map(function(c){return c.value}));
106
+ container.style.cssText = 'display:grid;grid-template-columns:repeat(' + cols + ',1fr);gap:2px';
107
+ cells.forEach(function(c) {
108
+ var intensity = c.value / max;
109
+ var bg = 'rgba(77,182,172,' + (0.1 + intensity*0.9).toFixed(2) + ')';
110
+ var fg = intensity > 0.6 ? 'white' : 'var(--graphit-fg)';
111
+ container.innerHTML += '<div style="aspect-ratio:1;border-radius:3px;display:flex;align-items:center;'
112
+ + 'justify-content:center;font-size:11px;font-weight:600;background:' + bg + ';color:' + fg + '">'
113
+ + c.value + '</div>';
114
+ });
115
+ ```
116
+
117
+ ## CSS Funnel
118
+ ```js
119
+ var maxVal = data[0].value;
120
+ data.forEach(function(d, i) {
121
+ var pct = (d.value / maxVal * 100).toFixed(1);
122
+ var rate = i > 0 ? (d.value / data[i-1].value * 100).toFixed(1) + '%' : '';
123
+ container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">'
124
+ + '<span style="width:120px;font-size:13px;text-align:right">' + d.name + '</span>'
125
+ + '<div style="width:' + pct + '%;height:36px;background:var(--graphit-accent);border-radius:4px;'
126
+ + 'display:flex;align-items:center;padding:0 12px;color:white;font-weight:600;font-size:13px">'
127
+ + d.value.toLocaleString() + '</div>'
128
+ + '<span style="font-size:12px;color:var(--graphit-fg-subtle)">' + rate + '</span></div>';
129
+ });
130
+ ```
131
+
132
+ ## CSS Gauge / Progress
133
+ ```html
134
+ <div style="position:relative;width:200px;height:100px;overflow:hidden">
135
+ <div id="gauge-arc" style="width:200px;height:200px;border-radius:50%;
136
+ background:conic-gradient(var(--graphit-accent) 0% 72%, var(--graphit-surface-sunken) 72% 100%);
137
+ clip-path:inset(0 0 50% 0)"></div>
138
+ <div style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);
139
+ font-size:28px;font-weight:700">72%</div>
140
+ </div>
141
+ ```
142
+ Dynamic: set `background: conic-gradient(var(--graphit-accent) 0% ${pct}%, var(--graphit-surface-sunken) ${pct}% 100%)`.
@@ -0,0 +1,83 @@
1
+ # Chart Selection
2
+
3
+ Consult when choosing chart types for dashboard elements. Matches data shapes to the right visualization.
4
+
5
+ ## Dimension/Measure Defaults
6
+
7
+ | Dimensions | Measures | Default chart |
8
+ |---|---|---|
9
+ | 1 temporal | 1+ | line |
10
+ | 1 categorical | 1 | bar |
11
+ | 1 categorical | 2+ | bar (grouped or dual axis) |
12
+ | 2 categorical | 1 | bar with series on 2nd dim |
13
+ | 0 | 1 | KPI card |
14
+ | 1 categorical (50+ values) | 1 | table |
15
+
16
+ When ambiguous, propose 2-3 options and ask the user. Do not guess.
17
+
18
+ ## Full Chart Type Table
19
+
20
+ | Data shape | Chart type | Required columns |
21
+ |---|---|---|
22
+ | Time series | line | 1 temporal + 1 numeric |
23
+ | Categories | bar | 1 categorical + 1 numeric |
24
+ | Stacked areas | area | 1 temporal/categorical + 1 numeric |
25
+ | Part-whole (max 5 slices) | pie | 1 categorical + 1 numeric |
26
+ | Single metric | KPI card | 1 numeric |
27
+ | Stages | funnel | 1 categorical + 1 numeric |
28
+ | Target/progress | gauge | 1 numeric |
29
+ | Matrix (unpivoted) | heatmap | 2 categorical + 1 numeric |
30
+ | Hierarchy | treemap | 1 categorical + 1 numeric |
31
+ | Flows | sankey | 2 categorical + 1 numeric |
32
+ | Correlation | scatter | 2 numeric |
33
+ | 3 variables | bubble | 3 numeric |
34
+ | Countries | choropleth map | 1 categorical + 1 numeric |
35
+ | Lat/lng | scatter map | 2 numeric (lat + lng) |
36
+ | Detail/raw data | table | any columns |
37
+
38
+ ## Perception Ranking (Cleveland-McGill)
39
+
40
+ Position > length > angle > area > color.
41
+
42
+ | Goal | First choice | Never |
43
+ |---|---|---|
44
+ | Trend (8+ points) | line | bar, pie |
45
+ | Trend (2-7 points) | bar (column) | line with dots |
46
+ | Compare (12 or fewer categories) | sorted bar | pie, radar |
47
+ | Compare (13-30 categories) | horizontal bar + top-N | column |
48
+ | Part-to-whole | stacked bar, treemap | pie with 6+ |
49
+ | Distribution | histogram, box | pie, line |
50
+ | Correlation | scatter, bubble | dual-axis |
51
+ | Ranking | sorted bar + min-N | pie, treemap |
52
+ | Funnel | funnel + stage% | pie |
53
+ | KPI vs target | big-number + sparkline | gauge (unbounded) |
54
+ | 2D matrix / cohort | heatmap | bar with 100+ |
55
+
56
+ ## Cardinality Guards
57
+
58
+ | Cardinality of categorical dim | Action |
59
+ |---|---|
60
+ | 1-5 | Show data labels, no rotation |
61
+ | 6-20 | Default styling |
62
+ | 21-50 | Rotate labels 45 deg, consider Top-N + Other |
63
+ | 50+ | Use table chart or require a filter |
64
+
65
+ COALESCE categorical dimensions in SQL (`COALESCE(region, 'Other')`) to prevent blank axis labels. For 21+ categories on a bar/line, use a "Top N + Other" window-function pattern:
66
+
67
+ ```sql
68
+ WITH ranked AS (
69
+ SELECT *, ROW_NUMBER() OVER (ORDER BY metric DESC) AS rn
70
+ FROM data
71
+ )
72
+ SELECT CASE WHEN rn <= 10 THEN category ELSE 'Other' END AS category,
73
+ SUM(metric) AS metric
74
+ FROM ranked GROUP BY 1 ORDER BY 2 DESC
75
+ ```
76
+
77
+ ## Hard Caps
78
+
79
+ - Pie: max 5 slices (3 preferred)
80
+ - Line series: max 5-7 (else use small multiples)
81
+ - Stacked bar segments: max 4
82
+ - Categorical colors: max 7 distinct
83
+ - Sort by value DESC unless the axis is ordinal or temporal
@@ -0,0 +1,94 @@
1
+ # Dashboard Planning
2
+
3
+ Consult before building any multi-chart dashboard. Transforms vague requests into structured, cohesive dashboards.
4
+
5
+ ## Frame the Question
6
+
7
+ Every dashboard answers ONE primary question. Name the shape first:
8
+
9
+ | Shape | Lead chart | Example question |
10
+ |---|---|---|
11
+ | Trend | line/area | "How is revenue changing?" |
12
+ | Comparison | sorted bar | "Revenue by segment?" |
13
+ | Ranking | sorted bar + top-N | "Top 10 customers?" |
14
+ | Composition | stacked bar/treemap | "Revenue share by product?" |
15
+ | Distribution | histogram | "Spread of order values?" |
16
+ | Correlation | scatter | "Spend vs conversion?" |
17
+ | Funnel | funnel + stage% | "Signup-to-purchase conversion?" |
18
+ | Cohort | heatmap/overlay lines | "D1/D7/D30 retention?" |
19
+ | Deviation | diverging bar | "Budget vs actual variance?" |
20
+
21
+ Compound intent ("what and why") - sequence trend then root-cause as two charts.
22
+
23
+ ## Pick the Archetype
24
+
25
+ | Audience | Archetype | KPIs | Charts | Interactivity |
26
+ |---|---|---|---|---|
27
+ | Exec / "are we on track" | Strategic | 4-6 hero | 5-9 | Low |
28
+ | Analyst / "why is X" | Analytical | 3-5 | 6-12 + detail | High |
29
+ | Ops / "what's broken now" | Operational | 8-16 dense | 6-12 | Medium |
30
+
31
+ Never mix archetypes on a single page.
32
+
33
+ ## Mandatory Rules
34
+
35
+ - **Time-series**: if data has a date column, include at least 1 line/area trend. A dashboard without a trend is a snapshot - it cannot answer "is it improving?"
36
+ - **Cohort**: if data has install/signup date + retention/LTV, add cohort curves (D0/D3/D7/D30). Period-over-period is NOT a substitute.
37
+ - **Diversity**: 4+ charts must use 3+ distinct chart types. All-bar or all-line wastes the visual encoding.
38
+ - **KPI-first**: lead with 1-3 KPI cards before detail charts unless the user says otherwise.
39
+ - **Distribution over averages**: heavy-tailed metrics (spend, session length, deal size) need percentile bands or tier breakdowns. Median + p90, never bare mean.
40
+ - **Insight titles**: state the takeaway ("Revenue grew 23% Q3"), not the metric name.
41
+ - **Reference lines**: goal-oriented metrics carry target/prior-period/benchmark lines. KPI cards need delta + comparison label.
42
+
43
+ ## Metric Contract
44
+
45
+ Every KPI or measure needs all 6 properties:
46
+
47
+ 1. **Definition** - what counts, what's excluded (bots, refunds, internal)
48
+ 2. **Grain** - per-user-per-day, per-cohort, per-org-per-month
49
+ 3. **Window** - rolling 7d/28d, MTD/QTD/YTD, cohort-anchored
50
+ 4. **Population** - all users, payers, certified, segment
51
+ 5. **Baseline** - vs prior period / target / cohort avg / benchmark
52
+ 6. **Direction** - up=good or up=bad (drives color meaning)
53
+
54
+ Pair lagging + leading: revenue (lagging) needs retention (leading). Replace vanity cumulative counts ("total signups") with cohort/period rates. Never show AVG of a rate column - rates must be weighted. Show denominators on rates (or hide when N < 30).
55
+
56
+ ## Anti-Patterns to Block
57
+
58
+ - Pie with 6+ slices (max 5, prefer 3)
59
+ - 3D charts of any kind
60
+ - Dual Y-axis without clear justification (use small multiples)
61
+ - Truncated bar baselines (always start at 0)
62
+ - Stacked bar with 5+ segments
63
+ - Gauge for unbounded KPIs (use bullet chart)
64
+ - Rainbow palette on ordinal data
65
+ - Red-green without shape encoding (accessibility)
66
+ - Aggregate-only without segment drill-down (Simpson's paradox)
67
+ - Rate without denominator visible
68
+ - KPI without baseline comparison
69
+ - Vanity cumulative counts ("total signups since launch")
70
+
71
+ ## Asking Good Questions
72
+
73
+ Purpose before data. The first response should mirror the user's intent and ask ONE narrowing question - never start querying immediately.
74
+
75
+ **Batch related questions** - ask multiple things at once instead of sequential single questions. Each option should lead to a different path, not variations of the same thing.
76
+
77
+ **Use open questions for exploration** - "What business decision will this dashboard support?" beats presenting a restrictive multiple-choice.
78
+
79
+ **Clarification triggers:**
80
+ - User says "revenue" - ask: bookings, ARR, or GAAP recognized?
81
+ - User says "conversion" - ask: what's the start and end event?
82
+ - User says "active users" - ask: what defines active? (logged in? performed action? within what window?)
83
+ - User mentions MQL/SQL - ask: how does your org define the handoff? (Same column can mean 13% or 40%)
84
+
85
+ ## Pre-Build Checklist
86
+
87
+ Before generating the HTML:
88
+ - [ ] Question shape stated (trend, comparison, funnel, etc.)
89
+ - [ ] Archetype + audience identified
90
+ - [ ] Time-series present if date column exists
91
+ - [ ] Cohort view present if install/signup + retention data
92
+ - [ ] 3+ chart types for 4+ graphs
93
+ - [ ] Every KPI has definition + baseline + direction
94
+ - [ ] No anti-patterns present