@quarri/claude-data-tools 1.1.0 → 1.2.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.
@@ -1,12 +1,12 @@
1
1
  ---
2
- description: Generate charts in multiple formats - QuickChart URLs, matplotlib, or ASCII
2
+ description: Generate interactive Plotly charts rendered as MCP UI resources
3
3
  globs:
4
4
  alwaysApply: false
5
5
  ---
6
6
 
7
- # /quarri-chart - Flexible Chart Generation
7
+ # /quarri-chart - Interactive Chart Generation
8
8
 
9
- Generate data visualizations in multiple formats optimized for Claude Code users.
9
+ Generate data visualizations as interactive Plotly charts using MCP UI resources.
10
10
 
11
11
  ## When to Use
12
12
 
@@ -14,73 +14,45 @@ Use `/quarri-chart` when users want visualizations:
14
14
  - "Create a chart of revenue by month"
15
15
  - "Visualize customer distribution"
16
16
  - "Show me a graph of this data"
17
- - "What's the best way to display these results?"
17
+ - "Chart sales by category"
18
18
 
19
- ## Output Formats
19
+ ## Primary Workflow
20
20
 
21
- ### 1. QuickChart URL (DEFAULT)
22
-
23
- Generate a URL that renders instantly in any browser or terminal with image support.
24
-
25
- **When to use**: Quick visualization, sharing, embedding in documents
26
-
27
- ```
28
- https://quickchart.io/chart?c={encoded_config}
29
- ```
30
-
31
- **Example**:
32
- ```
33
- https://quickchart.io/chart?c={type:'bar',data:{labels:['Q1','Q2','Q3','Q4'],datasets:[{label:'Revenue',data:[120,150,180,200]}]}}
21
+ ### Step 1: Query the data
22
+ ```sql
23
+ SELECT category, SUM(sales) as total_sales
24
+ FROM orders
25
+ GROUP BY category
26
+ ORDER BY total_sales DESC
34
27
  ```
35
28
 
36
- ### 2. Matplotlib Code
37
-
38
- Generate Python code that creates and saves chart images.
39
-
40
- **When to use**: Custom styling, publication-quality charts, local files
41
-
42
- ```python
43
- import matplotlib.pyplot as plt
44
- import pandas as pd
29
+ ### Step 2: Return chart as MCP UI resource
45
30
 
46
- # Data
47
- categories = ['Q1', 'Q2', 'Q3', 'Q4']
48
- values = [120, 150, 180, 200]
49
-
50
- # Create figure
51
- fig, ax = plt.subplots(figsize=(10, 6))
52
- ax.bar(categories, values, color='#4F46E5')
53
-
54
- # Styling
55
- ax.set_xlabel('Quarter')
56
- ax.set_ylabel('Revenue ($K)')
57
- ax.set_title('Quarterly Revenue')
58
- ax.spines['top'].set_visible(False)
59
- ax.spines['right'].set_visible(False)
60
-
61
- # Save
62
- plt.tight_layout()
63
- plt.savefig('revenue_chart.png', dpi=150)
64
- plt.show()
65
- ```
66
-
67
- ### 3. ASCII Chart
68
-
69
- Terminal-friendly text visualization for quick inspection.
70
-
71
- **When to use**: Terminal environments, quick data review, no graphics support
31
+ After getting the data, construct a Plotly configuration and include it in your response. The chart will be rendered as an interactive UI component.
72
32
 
33
+ **Example Plotly config for bar chart:**
34
+ ```json
35
+ {
36
+ "data": [{
37
+ "x": ["Category A", "Category B", "Category C"],
38
+ "y": [450000, 380000, 290000],
39
+ "type": "bar",
40
+ "marker": { "color": "#4F46E5" }
41
+ }],
42
+ "layout": {
43
+ "title": "Sales by Category",
44
+ "xaxis": { "title": "Category" },
45
+ "yaxis": { "title": "Sales ($)", "tickformat": "$,.0f" }
46
+ }
47
+ }
73
48
  ```
74
- Revenue by Quarter
75
49
 
76
- Q1 |████████████ | $120K
77
- Q2 |███████████████ | $150K
78
- Q3 |██████████████████ | $180K
79
- Q4 |████████████████████ | $200K
80
- 0 100 200
81
- ```
50
+ The response should include this as a resource block with:
51
+ - URI: `ui://quarri/chart`
52
+ - MIME type: `application/vnd.quarri.chart+json`
53
+ - Content: JSON with `type: "chart"` and `plotly: { data, layout }`
82
54
 
83
- ## Chart Selection Logic
55
+ ## Chart Type Selection
84
56
 
85
57
  ### Decision Tree
86
58
 
@@ -96,315 +68,237 @@ START: What is the primary analysis goal?
96
68
  └─→ 2-7 categories → VERTICAL BAR
97
69
  └─→ 8-15 categories → HORIZONTAL BAR
98
70
  └─→ 15+ categories → TOP N + "Other"
99
- └─→ Multiple dimensions → GROUPED BAR
100
71
 
101
72
  3. SHOWING DISTRIBUTION?
102
73
  └─→ Continuous data → HISTOGRAM
103
74
  └─→ Categorical → BAR (sorted)
104
- └─→ Compare groups → BOX PLOT
105
75
 
106
76
  4. PARTS OF A WHOLE?
107
77
  └─→ 2-6 parts → PIE/DONUT
108
- └─→ 7+ parts → STACKED BAR (100%)
78
+ └─→ 7+ parts → STACKED BAR
109
79
 
110
80
  5. RELATIONSHIP BETWEEN VARIABLES?
111
81
  └─→ Two numeric → SCATTER PLOT
112
- └─→ With grouping → SCATTER with colors
113
-
114
- DEFAULT: TABLE (when visualization unclear)
115
82
  ```
116
83
 
117
- ## QuickChart Configuration
84
+ ## Plotly Templates
118
85
 
119
86
  ### Bar Chart
120
87
  ```javascript
121
88
  {
122
- type: 'bar',
123
- data: {
124
- labels: ['A', 'B', 'C'],
125
- datasets: [{
126
- label: 'Values',
127
- data: [10, 20, 15],
128
- backgroundColor: '#4F46E5'
129
- }]
130
- },
131
- options: {
132
- title: { display: true, text: 'Chart Title' },
133
- legend: { display: false }
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" }
134
103
  }
135
104
  }
136
105
  ```
137
106
 
138
- ### Line Chart
107
+ ### Horizontal Bar (many categories)
139
108
  ```javascript
140
109
  {
141
- type: 'line',
142
- data: {
143
- labels: ['Jan', 'Feb', 'Mar', 'Apr'],
144
- datasets: [{
145
- label: 'Revenue',
146
- data: [100, 120, 115, 140],
147
- borderColor: '#4F46E5',
148
- fill: false
149
- }]
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 }
150
121
  }
151
122
  }
152
123
  ```
153
124
 
154
- ### Horizontal Bar
125
+ ### Line Chart (time series)
155
126
  ```javascript
156
127
  {
157
- type: 'horizontalBar',
158
- data: {
159
- labels: ['Product A', 'Product B', 'Product C'],
160
- datasets: [{
161
- data: [300, 250, 200],
162
- backgroundColor: ['#4F46E5', '#10B981', '#F59E0B']
163
- }]
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" }
164
140
  }
165
141
  }
166
142
  ```
167
143
 
168
- ### Pie/Donut Chart
144
+ ### Multi-Line Chart
169
145
  ```javascript
170
146
  {
171
- type: 'doughnut',
172
- data: {
173
- labels: ['Desktop', 'Mobile', 'Tablet'],
174
- datasets: [{
175
- data: [60, 30, 10],
176
- backgroundColor: ['#4F46E5', '#10B981', '#F59E0B']
177
- }]
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" }
178
167
  }
179
168
  }
180
169
  ```
181
170
 
182
- ### Scatter Plot
171
+ ### Pie/Donut Chart
183
172
  ```javascript
184
173
  {
185
- type: 'scatter',
186
- data: {
187
- datasets: [{
188
- label: 'Data Points',
189
- data: [{x: 1, y: 2}, {x: 2, y: 4}, {x: 3, y: 3}],
190
- backgroundColor: '#4F46E5'
191
- }]
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"
192
186
  }
193
187
  }
194
188
  ```
195
189
 
196
- ## Matplotlib Templates
197
-
198
- ### Bar Chart
199
- ```python
200
- import matplotlib.pyplot as plt
201
-
202
- fig, ax = plt.subplots(figsize=(10, 6))
203
- categories = ['A', 'B', 'C', 'D']
204
- values = [25, 40, 30, 35]
205
-
206
- bars = ax.bar(categories, values, color='#4F46E5', edgecolor='white')
207
- ax.bar_label(bars, fmt='%.0f')
208
-
209
- ax.set_ylabel('Value')
210
- ax.set_title('Category Comparison')
211
- ax.spines['top'].set_visible(False)
212
- ax.spines['right'].set_visible(False)
213
-
214
- plt.tight_layout()
215
- plt.savefig('bar_chart.png', dpi=150)
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
+ }
216
210
  ```
217
211
 
218
- ### Line Chart
219
- ```python
220
- import matplotlib.pyplot as plt
221
- import matplotlib.dates as mdates
222
-
223
- fig, ax = plt.subplots(figsize=(12, 6))
224
- dates = ['2024-01', '2024-02', '2024-03', '2024-04']
225
- values = [100, 120, 115, 140]
226
-
227
- ax.plot(dates, values, marker='o', color='#4F46E5', linewidth=2, markersize=8)
228
- ax.fill_between(dates, values, alpha=0.1, color='#4F46E5')
229
-
230
- ax.set_ylabel('Revenue ($K)')
231
- ax.set_title('Monthly Revenue Trend')
232
- ax.grid(True, alpha=0.3)
233
- ax.spines['top'].set_visible(False)
234
- ax.spines['right'].set_visible(False)
235
-
236
- plt.tight_layout()
237
- plt.savefig('line_chart.png', dpi=150)
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
+ }
238
234
  ```
239
235
 
240
- ### Horizontal Bar (for many categories)
241
- ```python
242
- import matplotlib.pyplot as plt
236
+ ## Color Palettes
243
237
 
244
- fig, ax = plt.subplots(figsize=(10, 8))
245
- categories = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']
246
- values = [45, 38, 32, 28, 22]
238
+ ```javascript
239
+ // Primary (single series)
240
+ const PRIMARY = '#4F46E5';
247
241
 
248
- bars = ax.barh(categories, values, color='#4F46E5')
249
- ax.bar_label(bars, fmt='%.0f', padding=5)
242
+ // Categorical (multiple series)
243
+ const CATEGORICAL = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'];
250
244
 
251
- ax.set_xlabel('Sales ($K)')
252
- ax.set_title('Top Products by Sales')
253
- ax.invert_yaxis() # Largest at top
254
- ax.spines['top'].set_visible(False)
255
- ax.spines['right'].set_visible(False)
245
+ // Sequential (gradient)
246
+ const SEQUENTIAL = ['#E0E7FF', '#A5B4FC', '#818CF8', '#6366F1', '#4F46E5', '#4338CA', '#3730A3'];
256
247
 
257
- plt.tight_layout()
258
- plt.savefig('horizontal_bar.png', dpi=150)
248
+ // Diverging (positive/negative)
249
+ const DIVERGING_NEG = '#EF4444';
250
+ const DIVERGING_POS = '#10B981';
259
251
  ```
260
252
 
261
- ## ASCII Chart Generation
262
-
263
- ### Horizontal Bar (ASCII)
264
- ```
265
- def ascii_bar_chart(data, title, max_width=40):
266
- """Generate ASCII horizontal bar chart"""
267
- print(f"\n{title}\n")
268
- max_val = max(data.values())
253
+ ## Number Formatting
269
254
 
270
- for label, value in data.items():
271
- bar_len = int((value / max_val) * max_width)
272
- bar = '' * bar_len
273
- print(f"{label:>15} |{bar} {value:,.0f}")
255
+ ```javascript
256
+ // Currency
257
+ tickformat: '$,.0f' // $1,234,567
258
+ tickformat: '$,.2f' // $1,234,567.89
259
+ tickformat: '$~s' // $1.2M
274
260
 
275
- print(f"{'':>15} {'─' * max_width}")
261
+ // Percentages
262
+ tickformat: '.1%' // 45.2%
276
263
 
277
- # Example
278
- data = {'Electronics': 45000, 'Clothing': 32000, 'Home': 28000}
279
- ascii_bar_chart(data, "Revenue by Category")
264
+ // Large numbers
265
+ tickformat: '~s' // 1.2M, 3.4K
280
266
  ```
281
267
 
282
- **Output:**
283
- ```
284
- Revenue by Category
268
+ ## MCP UI Resource Format
285
269
 
286
- Electronics |████████████████████████████████████████ 45,000
287
- Clothing |████████████████████████████ 32,000
288
- Home |████████████████████████ 28,000
289
- ────────────────────────────────────────
290
- ```
270
+ The chart should be returned as an MCP UI resource with the following structure:
291
271
 
292
- ### Vertical Bar (ASCII)
293
- ```
294
- def ascii_vertical_bar(data, title, height=10):
295
- """Generate ASCII vertical bar chart"""
296
- print(f"\n{title}\n")
297
- max_val = max(data.values())
298
-
299
- for row in range(height, 0, -1):
300
- line = ""
301
- for value in data.values():
302
- threshold = (row / height) * max_val
303
- line += " █ " if value >= threshold else " "
304
- print(f"{int(max_val * row / height):>6} |{line}")
305
-
306
- print(f"{'':>6} +{'─────' * len(data)}")
307
- labels = "".join(f"{k:^5}" for k in data.keys())
308
- print(f"{'':>8}{labels}")
309
-
310
- # Example
311
- data = {'Q1': 120, 'Q2': 150, 'Q3': 180, 'Q4': 200}
312
- ascii_vertical_bar(data, "Quarterly Revenue")
313
- ```
314
-
315
- ### Sparkline (ASCII)
316
- ```
317
- def ascii_sparkline(values, width=30):
318
- """Generate inline ASCII sparkline"""
319
- chars = '▁▂▃▄▅▆▇█'
320
- min_val, max_val = min(values), max(values)
321
- range_val = max_val - min_val or 1
322
-
323
- line = ""
324
- for v in values:
325
- idx = int((v - min_val) / range_val * (len(chars) - 1))
326
- line += chars[idx]
327
-
328
- return f"[{line}] {values[0]:,.0f} → {values[-1]:,.0f}"
329
-
330
- # Example
331
- monthly = [100, 105, 98, 112, 120, 118, 125, 130, 128, 140, 145, 155]
332
- print(f"Revenue trend: {ascii_sparkline(monthly)}")
333
- # Output: Revenue trend: [▁▂▁▃▄▄▅▆▅▇▇█] 100 → 155
272
+ ```json
273
+ {
274
+ "type": "chart",
275
+ "title": "Chart Title",
276
+ "plotly": {
277
+ "data": [/* Plotly data traces */],
278
+ "layout": {/* Plotly layout config */}
279
+ }
280
+ }
334
281
  ```
335
282
 
336
- ## Workflow
337
-
338
- 1. **Analyze data shape**:
339
- - Count rows and columns
340
- - Identify column types (numeric, categorical, date)
341
- - Check value distributions
342
-
343
- 2. **Determine visualization goal**:
344
- - Parse user question for intent
345
- - Consider data characteristics
346
-
347
- 3. **Select chart type**:
348
- - Apply decision tree
349
- - Consider data density and readability
350
-
351
- 4. **Choose output format**:
352
- - Default: QuickChart URL (instant rendering)
353
- - If user needs customization: matplotlib code
354
- - If terminal-only: ASCII chart
355
-
356
- 5. **Generate and present**:
357
- - Show chart or URL
358
- - Include rationale
359
- - Suggest alternatives
360
-
361
- ## Output Format
362
-
363
- ```markdown
364
- ## Visualization: [Data Description]
365
-
366
- ### Analysis
367
- - Data: [rows] rows, [columns] columns
368
- - Question type: [trend/comparison/distribution/etc.]
369
- - Key columns: [list]
283
+ This is automatically rendered by Claude Code when you include the resource block in your response.
370
284
 
371
- ### Recommended Chart: [Type]
372
- **Why**: [Brief rationale]
285
+ ## Alternative Outputs
373
286
 
374
- ### Chart
375
-
376
- **QuickChart URL** (click to view):
377
- [URL]
378
-
379
- **Alternative: Matplotlib Code**
380
- ```python
381
- [Code block]
287
+ ### QuickChart URL (for sharing/embedding)
288
+ When user specifically needs a URL:
382
289
  ```
383
-
384
- **Alternative: ASCII Preview**
385
- ```
386
- [ASCII chart]
290
+ https://quickchart.io/chart?c={type:'bar',data:{labels:['A','B','C'],datasets:[{data:[10,20,15]}]}}
387
291
  ```
388
292
 
389
- ### Alternatives
390
- - [Alternative chart 1]: [When it might be better]
391
- - [Alternative chart 2]: [When it might be better]
293
+ ### ASCII Chart (terminal only)
294
+ When user is in a terminal-only environment:
392
295
  ```
296
+ Sales by Category
393
297
 
394
- ## Color Palettes
395
-
396
- ```python
397
- # Primary palette
398
- PRIMARY = '#4F46E5' # Indigo
399
-
400
- # Categorical (multiple series)
401
- CATEGORICAL = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899']
402
-
403
- # Sequential (single metric, gradient)
404
- SEQUENTIAL = ['#E0E7FF', '#A5B4FC', '#818CF8', '#6366F1', '#4F46E5']
405
-
406
- # Diverging (positive/negative)
407
- DIVERGING = ['#EF4444', '#FCA5A5', '#E5E7EB', '#86EFAC', '#22C55E']
298
+ Technology |████████████████████████████████████████ $5.47M
299
+ Furniture |██████████████████████████████████ $4.73M
300
+ Office Supplies |██████████████████████████████ $4.14M
301
+ 0 $3M $6M
408
302
  ```
409
303
 
410
304
  ## Integration