@quarri/claude-data-tools 1.2.6 → 1.3.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/package.json +1 -1
- package/skills/quarri-chart/SKILL.md +456 -207
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Generate interactive Plotly charts
|
|
2
|
+
description: Generate interactive Plotly charts as HTML files
|
|
3
3
|
globs:
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /quarri-chart - Interactive Chart Generation
|
|
8
8
|
|
|
9
|
-
Generate data visualizations as interactive Plotly
|
|
9
|
+
Generate data visualizations as interactive **Plotly.js** HTML files that open in the browser.
|
|
10
|
+
|
|
11
|
+
**IMPORTANT**: Always generate HTML files with Plotly.js from CDN. Do NOT generate React components, inline JSON, or any other format.
|
|
10
12
|
|
|
11
13
|
## When to Use
|
|
12
14
|
|
|
@@ -16,41 +18,445 @@ Use `/quarri-chart` when users want visualizations:
|
|
|
16
18
|
- "Show me a graph of this data"
|
|
17
19
|
- "Chart sales by category"
|
|
18
20
|
|
|
21
|
+
## Data Size Limits
|
|
22
|
+
|
|
23
|
+
**CRITICAL**: Charts should contain at most **500 data points**. Large datasets must be handled appropriately:
|
|
24
|
+
|
|
25
|
+
| Scenario | Solution |
|
|
26
|
+
|----------|----------|
|
|
27
|
+
| Time series with many dates | Aggregate to appropriate granularity (day→week→month→quarter) |
|
|
28
|
+
| Too many categories | Show Top N (10-20) + "Other" bucket |
|
|
29
|
+
| Scatter plot with many points | Sample data or use density/heatmap |
|
|
30
|
+
| Raw transactional data | Always GROUP BY before charting - never chart individual transactions |
|
|
31
|
+
|
|
32
|
+
### Example: Handling Large Categorical Data
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
-- BAD: Returns potentially thousands of products
|
|
36
|
+
SELECT product_name, SUM(sales) FROM quarri.schema GROUP BY product_name
|
|
37
|
+
|
|
38
|
+
-- GOOD: Top 10 + Other
|
|
39
|
+
WITH ranked AS (
|
|
40
|
+
SELECT product_name, SUM(sales) as total_sales,
|
|
41
|
+
ROW_NUMBER() OVER (ORDER BY SUM(sales) DESC) as rn
|
|
42
|
+
FROM quarri.schema
|
|
43
|
+
GROUP BY product_name
|
|
44
|
+
)
|
|
45
|
+
SELECT
|
|
46
|
+
CASE WHEN rn <= 10 THEN product_name ELSE 'Other' END as product_name,
|
|
47
|
+
SUM(total_sales) as total_sales
|
|
48
|
+
FROM ranked
|
|
49
|
+
GROUP BY CASE WHEN rn <= 10 THEN product_name ELSE 'Other' END
|
|
50
|
+
ORDER BY total_sales DESC
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Example: Handling Long Time Series
|
|
54
|
+
|
|
55
|
+
```sql
|
|
56
|
+
-- BAD: Daily data for 5 years = 1800+ points
|
|
57
|
+
SELECT order_date, SUM(sales) FROM quarri.schema GROUP BY order_date
|
|
58
|
+
|
|
59
|
+
-- GOOD: Aggregate to months
|
|
60
|
+
SELECT DATE_TRUNC('month', order_date) as month, SUM(sales) as total_sales
|
|
61
|
+
FROM quarri.schema
|
|
62
|
+
GROUP BY DATE_TRUNC('month', order_date)
|
|
63
|
+
ORDER BY month
|
|
64
|
+
```
|
|
65
|
+
|
|
19
66
|
## Primary Workflow
|
|
20
67
|
|
|
21
|
-
### Step 1: Query the data
|
|
68
|
+
### Step 1: Query the data (with appropriate aggregation)
|
|
69
|
+
|
|
70
|
+
Use `quarri_execute_sql` to get the data:
|
|
71
|
+
|
|
22
72
|
```sql
|
|
23
73
|
SELECT category, SUM(sales) as total_sales
|
|
24
|
-
FROM
|
|
74
|
+
FROM quarri.schema
|
|
25
75
|
GROUP BY category
|
|
26
76
|
ORDER BY total_sales DESC
|
|
77
|
+
LIMIT 20
|
|
27
78
|
```
|
|
28
79
|
|
|
29
|
-
### Step 2:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
### Step 2: Generate HTML file with Plotly
|
|
81
|
+
|
|
82
|
+
Write an HTML file that:
|
|
83
|
+
1. Loads Plotly.js from CDN
|
|
84
|
+
2. Embeds the query results as a JavaScript data array
|
|
85
|
+
3. Builds Plotly traces from the data
|
|
86
|
+
4. Renders the chart
|
|
87
|
+
|
|
88
|
+
### Step 3: Open in browser
|
|
89
|
+
|
|
90
|
+
Use `open <filepath>` (macOS) or `xdg-open <filepath>` (Linux) to display the chart.
|
|
91
|
+
|
|
92
|
+
## HTML Template
|
|
93
|
+
|
|
94
|
+
Use this template structure for all charts:
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<!DOCTYPE html>
|
|
98
|
+
<html lang="en">
|
|
99
|
+
<head>
|
|
100
|
+
<meta charset="UTF-8">
|
|
101
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
102
|
+
<title>CHART_TITLE</title>
|
|
103
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
104
|
+
<style>
|
|
105
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
106
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
107
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<h1>CHART_TITLE</h1>
|
|
112
|
+
<div id="chart"></div>
|
|
113
|
+
<script>
|
|
114
|
+
// Data from query results
|
|
115
|
+
const data = DATA_ARRAY;
|
|
116
|
+
|
|
117
|
+
// Build Plotly traces
|
|
118
|
+
const traces = [TRACE_CONFIG];
|
|
119
|
+
|
|
120
|
+
// Layout configuration
|
|
121
|
+
const layout = LAYOUT_CONFIG;
|
|
122
|
+
|
|
123
|
+
// Render chart
|
|
124
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
125
|
+
</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
48
128
|
```
|
|
49
129
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
130
|
+
## Complete Examples
|
|
131
|
+
|
|
132
|
+
### Bar Chart Example
|
|
133
|
+
|
|
134
|
+
**Query:**
|
|
135
|
+
```sql
|
|
136
|
+
SELECT category, SUM(sales) as total_sales
|
|
137
|
+
FROM quarri.schema
|
|
138
|
+
GROUP BY category
|
|
139
|
+
ORDER BY total_sales DESC
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**HTML file to write:**
|
|
143
|
+
```html
|
|
144
|
+
<!DOCTYPE html>
|
|
145
|
+
<html lang="en">
|
|
146
|
+
<head>
|
|
147
|
+
<meta charset="UTF-8">
|
|
148
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
149
|
+
<title>Sales by Category</title>
|
|
150
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
151
|
+
<style>
|
|
152
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
153
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
154
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
155
|
+
</style>
|
|
156
|
+
</head>
|
|
157
|
+
<body>
|
|
158
|
+
<h1>Sales by Category</h1>
|
|
159
|
+
<div id="chart"></div>
|
|
160
|
+
<script>
|
|
161
|
+
const data = [
|
|
162
|
+
{ category: "Technology", total_sales: 836154.03 },
|
|
163
|
+
{ category: "Furniture", total_sales: 741999.80 },
|
|
164
|
+
{ category: "Office Supplies", total_sales: 719047.03 }
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const traces = [{
|
|
168
|
+
x: data.map(d => d.category),
|
|
169
|
+
y: data.map(d => d.total_sales),
|
|
170
|
+
type: 'bar',
|
|
171
|
+
marker: { color: '#4F46E5' },
|
|
172
|
+
text: data.map(d => '$' + (d.total_sales / 1000).toFixed(0) + 'K'),
|
|
173
|
+
textposition: 'auto'
|
|
174
|
+
}];
|
|
175
|
+
|
|
176
|
+
const layout = {
|
|
177
|
+
xaxis: { title: 'Category' },
|
|
178
|
+
yaxis: { title: 'Sales ($)', tickformat: '$,.0f' },
|
|
179
|
+
margin: { t: 20, r: 20, b: 60, l: 80 }
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
183
|
+
</script>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Line Chart Example (Time Series)
|
|
189
|
+
|
|
190
|
+
**Query:**
|
|
191
|
+
```sql
|
|
192
|
+
SELECT DATE_TRUNC('month', order_date) as month, SUM(sales) as total_sales
|
|
193
|
+
FROM quarri.schema
|
|
194
|
+
GROUP BY DATE_TRUNC('month', order_date)
|
|
195
|
+
ORDER BY month
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**HTML file to write:**
|
|
199
|
+
```html
|
|
200
|
+
<!DOCTYPE html>
|
|
201
|
+
<html lang="en">
|
|
202
|
+
<head>
|
|
203
|
+
<meta charset="UTF-8">
|
|
204
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
205
|
+
<title>Monthly Sales Trend</title>
|
|
206
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
207
|
+
<style>
|
|
208
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
209
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
210
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
211
|
+
</style>
|
|
212
|
+
</head>
|
|
213
|
+
<body>
|
|
214
|
+
<h1>Monthly Sales Trend</h1>
|
|
215
|
+
<div id="chart"></div>
|
|
216
|
+
<script>
|
|
217
|
+
const data = [
|
|
218
|
+
{ month: "2021-01-01", total_sales: 94925.57 },
|
|
219
|
+
{ month: "2021-02-01", total_sales: 59751.25 },
|
|
220
|
+
{ month: "2021-03-01", total_sales: 115973.79 }
|
|
221
|
+
// ... more data points
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const traces = [{
|
|
225
|
+
x: data.map(d => d.month),
|
|
226
|
+
y: data.map(d => d.total_sales),
|
|
227
|
+
type: 'scatter',
|
|
228
|
+
mode: 'lines+markers',
|
|
229
|
+
line: { color: '#4F46E5', width: 3 },
|
|
230
|
+
marker: { size: 8 }
|
|
231
|
+
}];
|
|
232
|
+
|
|
233
|
+
const layout = {
|
|
234
|
+
xaxis: { title: 'Month' },
|
|
235
|
+
yaxis: { title: 'Sales ($)', tickformat: '$,.0f' },
|
|
236
|
+
margin: { t: 20, r: 20, b: 60, l: 80 }
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
240
|
+
</script>
|
|
241
|
+
</body>
|
|
242
|
+
</html>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Multi-Series Line Chart
|
|
246
|
+
|
|
247
|
+
**Query:**
|
|
248
|
+
```sql
|
|
249
|
+
SELECT DATE_TRUNC('month', order_date) as month, category, SUM(sales) as total_sales
|
|
250
|
+
FROM quarri.schema
|
|
251
|
+
GROUP BY DATE_TRUNC('month', order_date), category
|
|
252
|
+
ORDER BY month, category
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**HTML file to write:**
|
|
256
|
+
```html
|
|
257
|
+
<!DOCTYPE html>
|
|
258
|
+
<html lang="en">
|
|
259
|
+
<head>
|
|
260
|
+
<meta charset="UTF-8">
|
|
261
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
262
|
+
<title>Sales Trend by Category</title>
|
|
263
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
264
|
+
<style>
|
|
265
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
266
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
267
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
268
|
+
</style>
|
|
269
|
+
</head>
|
|
270
|
+
<body>
|
|
271
|
+
<h1>Sales Trend by Category</h1>
|
|
272
|
+
<div id="chart"></div>
|
|
273
|
+
<script>
|
|
274
|
+
const data = [
|
|
275
|
+
{ month: "2021-01-01", category: "Furniture", total_sales: 28262.03 },
|
|
276
|
+
{ month: "2021-01-01", category: "Office Supplies", total_sales: 21879.28 },
|
|
277
|
+
{ month: "2021-01-01", category: "Technology", total_sales: 44784.26 }
|
|
278
|
+
// ... more data points
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
// Group by category
|
|
282
|
+
const categories = [...new Set(data.map(d => d.category))];
|
|
283
|
+
const colors = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'];
|
|
284
|
+
|
|
285
|
+
const traces = categories.map((cat, i) => {
|
|
286
|
+
const catData = data.filter(d => d.category === cat);
|
|
287
|
+
return {
|
|
288
|
+
x: catData.map(d => d.month),
|
|
289
|
+
y: catData.map(d => d.total_sales),
|
|
290
|
+
name: cat,
|
|
291
|
+
type: 'scatter',
|
|
292
|
+
mode: 'lines+markers',
|
|
293
|
+
line: { color: colors[i % colors.length], width: 2 }
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const layout = {
|
|
298
|
+
xaxis: { title: 'Month' },
|
|
299
|
+
yaxis: { title: 'Sales ($)', tickformat: '$,.0f' },
|
|
300
|
+
margin: { t: 20, r: 20, b: 60, l: 80 },
|
|
301
|
+
legend: { orientation: 'h', y: -0.15 }
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
305
|
+
</script>
|
|
306
|
+
</body>
|
|
307
|
+
</html>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Horizontal Bar Chart (Many Categories)
|
|
311
|
+
|
|
312
|
+
**HTML file to write:**
|
|
313
|
+
```html
|
|
314
|
+
<!DOCTYPE html>
|
|
315
|
+
<html lang="en">
|
|
316
|
+
<head>
|
|
317
|
+
<meta charset="UTF-8">
|
|
318
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
319
|
+
<title>Top 10 Products by Sales</title>
|
|
320
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
321
|
+
<style>
|
|
322
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
323
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
324
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
325
|
+
</style>
|
|
326
|
+
</head>
|
|
327
|
+
<body>
|
|
328
|
+
<h1>Top 10 Products by Sales</h1>
|
|
329
|
+
<div id="chart"></div>
|
|
330
|
+
<script>
|
|
331
|
+
const data = [
|
|
332
|
+
{ product_name: "Canon imageCLASS 2200", total_sales: 61599.82 },
|
|
333
|
+
{ product_name: "Fellowes PB500", total_sales: 27453.38 }
|
|
334
|
+
// ... more products
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
// Reverse for horizontal bar (top item at top)
|
|
338
|
+
const sorted = [...data].reverse();
|
|
339
|
+
|
|
340
|
+
const traces = [{
|
|
341
|
+
y: sorted.map(d => d.product_name),
|
|
342
|
+
x: sorted.map(d => d.total_sales),
|
|
343
|
+
type: 'bar',
|
|
344
|
+
orientation: 'h',
|
|
345
|
+
marker: { color: '#4F46E5' }
|
|
346
|
+
}];
|
|
347
|
+
|
|
348
|
+
const layout = {
|
|
349
|
+
xaxis: { title: 'Sales ($)', tickformat: '$,.0f' },
|
|
350
|
+
margin: { t: 20, r: 20, b: 60, l: 200 }
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
354
|
+
</script>
|
|
355
|
+
</body>
|
|
356
|
+
</html>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Pie/Donut Chart
|
|
360
|
+
|
|
361
|
+
**HTML file to write:**
|
|
362
|
+
```html
|
|
363
|
+
<!DOCTYPE html>
|
|
364
|
+
<html lang="en">
|
|
365
|
+
<head>
|
|
366
|
+
<meta charset="UTF-8">
|
|
367
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
368
|
+
<title>Sales Distribution</title>
|
|
369
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
370
|
+
<style>
|
|
371
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
372
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
373
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
374
|
+
</style>
|
|
375
|
+
</head>
|
|
376
|
+
<body>
|
|
377
|
+
<h1>Sales Distribution by Category</h1>
|
|
378
|
+
<div id="chart"></div>
|
|
379
|
+
<script>
|
|
380
|
+
const data = [
|
|
381
|
+
{ category: "Technology", total_sales: 836154.03 },
|
|
382
|
+
{ category: "Furniture", total_sales: 741999.80 },
|
|
383
|
+
{ category: "Office Supplies", total_sales: 719047.03 }
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
const traces = [{
|
|
387
|
+
labels: data.map(d => d.category),
|
|
388
|
+
values: data.map(d => d.total_sales),
|
|
389
|
+
type: 'pie',
|
|
390
|
+
hole: 0.4,
|
|
391
|
+
marker: { colors: ['#4F46E5', '#10B981', '#F59E0B'] },
|
|
392
|
+
textinfo: 'label+percent'
|
|
393
|
+
}];
|
|
394
|
+
|
|
395
|
+
const layout = {
|
|
396
|
+
margin: { t: 20, r: 20, b: 20, l: 20 },
|
|
397
|
+
showlegend: true
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
401
|
+
</script>
|
|
402
|
+
</body>
|
|
403
|
+
</html>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Grouped Bar Chart
|
|
407
|
+
|
|
408
|
+
**HTML file to write:**
|
|
409
|
+
```html
|
|
410
|
+
<!DOCTYPE html>
|
|
411
|
+
<html lang="en">
|
|
412
|
+
<head>
|
|
413
|
+
<meta charset="UTF-8">
|
|
414
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
415
|
+
<title>Year over Year Comparison</title>
|
|
416
|
+
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
|
417
|
+
<style>
|
|
418
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f8fafc; }
|
|
419
|
+
#chart { width: 100%; height: 600px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
420
|
+
h1 { color: #1e293b; font-size: 1.5rem; margin-bottom: 16px; }
|
|
421
|
+
</style>
|
|
422
|
+
</head>
|
|
423
|
+
<body>
|
|
424
|
+
<h1>Year over Year Comparison</h1>
|
|
425
|
+
<div id="chart"></div>
|
|
426
|
+
<script>
|
|
427
|
+
const data = [
|
|
428
|
+
{ year: 2023, quarter: "Q1", sales: 120000 },
|
|
429
|
+
{ year: 2023, quarter: "Q2", sales: 150000 },
|
|
430
|
+
{ year: 2024, quarter: "Q1", sales: 140000 },
|
|
431
|
+
{ year: 2024, quarter: "Q2", sales: 165000 }
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
const years = [...new Set(data.map(d => d.year))];
|
|
435
|
+
const colors = ['#4F46E5', '#10B981'];
|
|
436
|
+
|
|
437
|
+
const traces = years.map((year, i) => {
|
|
438
|
+
const yearData = data.filter(d => d.year === year);
|
|
439
|
+
return {
|
|
440
|
+
x: yearData.map(d => d.quarter),
|
|
441
|
+
y: yearData.map(d => d.sales),
|
|
442
|
+
name: String(year),
|
|
443
|
+
type: 'bar',
|
|
444
|
+
marker: { color: colors[i] }
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const layout = {
|
|
449
|
+
barmode: 'group',
|
|
450
|
+
xaxis: { title: 'Quarter' },
|
|
451
|
+
yaxis: { title: 'Sales ($)', tickformat: '$,.0f' },
|
|
452
|
+
margin: { t: 20, r: 20, b: 60, l: 80 }
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
Plotly.newPlot('chart', traces, layout, { responsive: true });
|
|
456
|
+
</script>
|
|
457
|
+
</body>
|
|
458
|
+
</html>
|
|
459
|
+
```
|
|
54
460
|
|
|
55
461
|
## Chart Type Selection
|
|
56
462
|
|
|
@@ -81,158 +487,6 @@ START: What is the primary analysis goal?
|
|
|
81
487
|
└─→ Two numeric → SCATTER PLOT
|
|
82
488
|
```
|
|
83
489
|
|
|
84
|
-
## Plotly Templates
|
|
85
|
-
|
|
86
|
-
### Bar Chart
|
|
87
|
-
```javascript
|
|
88
|
-
{
|
|
89
|
-
"data": [{
|
|
90
|
-
"x": ["Category A", "Category B", "Category C"],
|
|
91
|
-
"y": [450000, 380000, 290000],
|
|
92
|
-
"type": "bar",
|
|
93
|
-
"marker": {
|
|
94
|
-
"color": ["#4F46E5", "#7C3AED", "#A78BFA"]
|
|
95
|
-
},
|
|
96
|
-
"text": ["$450K", "$380K", "$290K"],
|
|
97
|
-
"textposition": "auto"
|
|
98
|
-
}],
|
|
99
|
-
"layout": {
|
|
100
|
-
"title": "Sales by Category",
|
|
101
|
-
"xaxis": { "title": "Category" },
|
|
102
|
-
"yaxis": { "title": "Sales ($)", "tickformat": "$,.0f" }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Horizontal Bar (many categories)
|
|
108
|
-
```javascript
|
|
109
|
-
{
|
|
110
|
-
"data": [{
|
|
111
|
-
"y": ["Product A", "Product B", "Product C", "Product D", "Product E"],
|
|
112
|
-
"x": [85000, 72000, 65000, 58000, 45000],
|
|
113
|
-
"type": "bar",
|
|
114
|
-
"orientation": "h",
|
|
115
|
-
"marker": { "color": "#4F46E5" }
|
|
116
|
-
}],
|
|
117
|
-
"layout": {
|
|
118
|
-
"title": "Top Products by Revenue",
|
|
119
|
-
"xaxis": { "title": "Revenue ($)", "tickformat": "$,.0f" },
|
|
120
|
-
"margin": { "l": 120 }
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Line Chart (time series)
|
|
126
|
-
```javascript
|
|
127
|
-
{
|
|
128
|
-
"data": [{
|
|
129
|
-
"x": ["2024-01", "2024-02", "2024-03", "2024-04", "2024-05", "2024-06"],
|
|
130
|
-
"y": [120000, 135000, 128000, 145000, 160000, 175000],
|
|
131
|
-
"type": "scatter",
|
|
132
|
-
"mode": "lines+markers",
|
|
133
|
-
"line": { "color": "#4F46E5", "width": 3 },
|
|
134
|
-
"marker": { "size": 8 }
|
|
135
|
-
}],
|
|
136
|
-
"layout": {
|
|
137
|
-
"title": "Monthly Revenue Trend",
|
|
138
|
-
"xaxis": { "title": "Month" },
|
|
139
|
-
"yaxis": { "title": "Revenue ($)", "tickformat": "$,.0f" }
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Multi-Line Chart
|
|
145
|
-
```javascript
|
|
146
|
-
{
|
|
147
|
-
"data": [
|
|
148
|
-
{
|
|
149
|
-
"x": ["Jan", "Feb", "Mar", "Apr"],
|
|
150
|
-
"y": [100, 120, 115, 140],
|
|
151
|
-
"name": "Product A",
|
|
152
|
-
"type": "scatter",
|
|
153
|
-
"mode": "lines+markers"
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"x": ["Jan", "Feb", "Mar", "Apr"],
|
|
157
|
-
"y": [80, 95, 110, 120],
|
|
158
|
-
"name": "Product B",
|
|
159
|
-
"type": "scatter",
|
|
160
|
-
"mode": "lines+markers"
|
|
161
|
-
}
|
|
162
|
-
],
|
|
163
|
-
"layout": {
|
|
164
|
-
"title": "Product Comparison",
|
|
165
|
-
"xaxis": { "title": "Month" },
|
|
166
|
-
"yaxis": { "title": "Sales" }
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Pie/Donut Chart
|
|
172
|
-
```javascript
|
|
173
|
-
{
|
|
174
|
-
"data": [{
|
|
175
|
-
"labels": ["Technology", "Furniture", "Office Supplies"],
|
|
176
|
-
"values": [5471124, 4730801, 4144724],
|
|
177
|
-
"type": "pie",
|
|
178
|
-
"hole": 0.4,
|
|
179
|
-
"marker": {
|
|
180
|
-
"colors": ["#4F46E5", "#10B981", "#F59E0B"]
|
|
181
|
-
},
|
|
182
|
-
"textinfo": "label+percent"
|
|
183
|
-
}],
|
|
184
|
-
"layout": {
|
|
185
|
-
"title": "Sales Distribution by Category"
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Scatter Plot
|
|
191
|
-
```javascript
|
|
192
|
-
{
|
|
193
|
-
"data": [{
|
|
194
|
-
"x": [/* x values */],
|
|
195
|
-
"y": [/* y values */],
|
|
196
|
-
"mode": "markers",
|
|
197
|
-
"type": "scatter",
|
|
198
|
-
"marker": {
|
|
199
|
-
"size": 10,
|
|
200
|
-
"color": "#4F46E5",
|
|
201
|
-
"opacity": 0.7
|
|
202
|
-
}
|
|
203
|
-
}],
|
|
204
|
-
"layout": {
|
|
205
|
-
"title": "Correlation Analysis",
|
|
206
|
-
"xaxis": { "title": "Variable X" },
|
|
207
|
-
"yaxis": { "title": "Variable Y" }
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Grouped Bar Chart
|
|
213
|
-
```javascript
|
|
214
|
-
{
|
|
215
|
-
"data": [
|
|
216
|
-
{
|
|
217
|
-
"x": ["Q1", "Q2", "Q3", "Q4"],
|
|
218
|
-
"y": [120, 150, 180, 200],
|
|
219
|
-
"name": "2023",
|
|
220
|
-
"type": "bar"
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
"x": ["Q1", "Q2", "Q3", "Q4"],
|
|
224
|
-
"y": [140, 165, 195, 220],
|
|
225
|
-
"name": "2024",
|
|
226
|
-
"type": "bar"
|
|
227
|
-
}
|
|
228
|
-
],
|
|
229
|
-
"layout": {
|
|
230
|
-
"title": "Year over Year Comparison",
|
|
231
|
-
"barmode": "group"
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
490
|
## Color Palettes
|
|
237
491
|
|
|
238
492
|
```javascript
|
|
@@ -265,41 +519,36 @@ tickformat: '.1%' // 45.2%
|
|
|
265
519
|
tickformat: '~s' // 1.2M, 3.4K
|
|
266
520
|
```
|
|
267
521
|
|
|
268
|
-
##
|
|
269
|
-
|
|
270
|
-
The chart should be returned as an MCP UI resource with the following structure:
|
|
522
|
+
## File Output
|
|
271
523
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
"data": [/* Plotly data traces */],
|
|
278
|
-
"layout": {/* Plotly layout config */}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
524
|
+
**Always write to a descriptive filename:**
|
|
525
|
+
```
|
|
526
|
+
/tmp/quarri-chart-sales-by-category.html
|
|
527
|
+
/tmp/quarri-chart-monthly-trend.html
|
|
528
|
+
/tmp/quarri-chart-top-products.html
|
|
281
529
|
```
|
|
282
530
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
531
|
+
**Then open in browser:**
|
|
532
|
+
```bash
|
|
533
|
+
# macOS
|
|
534
|
+
open /tmp/quarri-chart-sales-by-category.html
|
|
286
535
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
```
|
|
290
|
-
https://quickchart.io/chart?c={type:'bar',data:{labels:['A','B','C'],datasets:[{data:[10,20,15]}]}}
|
|
536
|
+
# Linux
|
|
537
|
+
xdg-open /tmp/quarri-chart-sales-by-category.html
|
|
291
538
|
```
|
|
292
539
|
|
|
293
|
-
|
|
294
|
-
When user is in a terminal-only environment:
|
|
295
|
-
```
|
|
296
|
-
Sales by Category
|
|
540
|
+
## Validation Checklist
|
|
297
541
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
542
|
+
Before generating a chart, verify:
|
|
543
|
+
|
|
544
|
+
1. **Format**: Generating an HTML file with Plotly.js from CDN
|
|
545
|
+
2. **Data embedded**: Query results are in a `const data = [...]` JavaScript array
|
|
546
|
+
3. **Data points**: ≤ 500 points total (aggregate or sample if more)
|
|
547
|
+
4. **Categories**: ≤ 20 categories (use Top N + Other if more)
|
|
548
|
+
5. **Time granularity**: Appropriate for date range (don't show daily for multi-year)
|
|
549
|
+
6. **Aggregation**: Never chart raw transactions - always GROUP BY
|
|
550
|
+
7. **File written**: HTML file saved to /tmp/quarri-chart-*.html
|
|
551
|
+
8. **Browser opened**: File opened with `open` or `xdg-open`
|
|
303
552
|
|
|
304
553
|
## Integration
|
|
305
554
|
|