@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.
- package/dist/api/client.d.ts +16 -0
- package/dist/api/client.js +67 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/credentials.d.ts +15 -0
- package/dist/auth/credentials.js +34 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/login.d.ts +2 -0
- package/dist/auth/login.js +229 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/token.d.ts +5 -0
- package/dist/auth/token.js +42 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +68 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/connector.d.ts +2 -0
- package/dist/commands/connector.js +97 -0
- package/dist/commands/connector.js.map +1 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +124 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/ds.d.ts +2 -0
- package/dist/commands/ds.js +53 -0
- package/dist/commands/ds.js.map +1 -0
- package/dist/commands/kb.d.ts +2 -0
- package/dist/commands/kb.js +259 -0
- package/dist/commands/kb.js.map +1 -0
- package/dist/commands/query.d.ts +2 -0
- package/dist/commands/query.js +61 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +173 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/output/format.d.ts +8 -0
- package/dist/output/format.js +30 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/json.d.ts +1 -0
- package/dist/output/json.js +4 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +17 -0
- package/dist/output/table.js.map +1 -0
- package/package.json +38 -0
- package/skill/SKILL.md +163 -0
- package/skill/cursor/graphit-chart-patterns.mdc +148 -0
- package/skill/cursor/graphit-chart-selection.mdc +89 -0
- package/skill/cursor/graphit-dashboard-planning.mdc +100 -0
- package/skill/cursor/graphit-domain-lenses.mdc +120 -0
- package/skill/cursor/graphit-kb-exploration.mdc +75 -0
- package/skill/cursor/graphit-sql-reference.mdc +127 -0
- package/skill/cursor/graphit-style.mdc +123 -0
- package/skill/graphit.mdc +135 -0
- package/skill/references/chart-patterns.md +142 -0
- package/skill/references/chart-selection.md +83 -0
- package/skill/references/dashboard-planning.md +94 -0
- package/skill/references/domain-lenses.md +114 -0
- package/skill/references/graphit-style.md +117 -0
- package/skill/references/kb-exploration.md +69 -0
- 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
|