@graphit/cli 0.1.1 → 0.1.3
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/.claude-plugin/marketplace.json +25 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.codex-plugin/plugin.json +22 -0
- package/package.json +4 -2
- package/{skill → skills/graphit}/SKILL.md +78 -23
- package/skills/graphit/cursor/graphit-chart-patterns.mdc +86 -0
- package/skills/graphit/graphit.mdc +117 -0
- package/skills/graphit/references/chart-patterns.md +80 -0
- package/dist/api/client.d.ts +0 -16
- package/dist/api/client.js +0 -67
- package/dist/api/client.js.map +0 -1
- package/dist/auth/credentials.d.ts +0 -15
- package/dist/auth/credentials.js +0 -34
- package/dist/auth/credentials.js.map +0 -1
- package/dist/auth/login.d.ts +0 -2
- package/dist/auth/login.js +0 -230
- package/dist/auth/login.js.map +0 -1
- package/dist/auth/token.d.ts +0 -5
- package/dist/auth/token.js +0 -42
- package/dist/auth/token.js.map +0 -1
- package/dist/commands/auth.d.ts +0 -2
- package/dist/commands/auth.js +0 -68
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/connector.d.ts +0 -2
- package/dist/commands/connector.js +0 -97
- package/dist/commands/connector.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -2
- package/dist/commands/dashboard.js +0 -124
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/ds.d.ts +0 -2
- package/dist/commands/ds.js +0 -53
- package/dist/commands/ds.js.map +0 -1
- package/dist/commands/kb.d.ts +0 -2
- package/dist/commands/kb.js +0 -259
- package/dist/commands/kb.js.map +0 -1
- package/dist/commands/query.d.ts +0 -2
- package/dist/commands/query.js +0 -61
- package/dist/commands/query.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -2
- package/dist/commands/setup.js +0 -173
- package/dist/commands/setup.js.map +0 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.js +0 -9
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -24
- package/dist/index.js.map +0 -1
- package/dist/output/format.d.ts +0 -8
- package/dist/output/format.js +0 -30
- package/dist/output/format.js.map +0 -1
- package/dist/output/json.d.ts +0 -1
- package/dist/output/json.js +0 -4
- package/dist/output/json.js.map +0 -1
- package/dist/output/table.d.ts +0 -2
- package/dist/output/table.js +0 -17
- package/dist/output/table.js.map +0 -1
- package/skill/cursor/graphit-chart-patterns.mdc +0 -148
- package/skill/graphit.mdc +0 -135
- package/skill/references/chart-patterns.md +0 -257
- /package/{skill → skills/graphit}/cursor/graphit-chart-selection.mdc +0 -0
- /package/{skill → skills/graphit}/cursor/graphit-dashboard-planning.mdc +0 -0
- /package/{skill → skills/graphit}/cursor/graphit-domain-lenses.mdc +0 -0
- /package/{skill → skills/graphit}/cursor/graphit-kb-exploration.mdc +0 -0
- /package/{skill → skills/graphit}/cursor/graphit-sql-reference.mdc +0 -0
- /package/{skill → skills/graphit}/cursor/graphit-style.mdc +0 -0
- /package/{skill → skills/graphit}/references/chart-selection.md +0 -0
- /package/{skill → skills/graphit}/references/dashboard-planning.md +0 -0
- /package/{skill → skills/graphit}/references/domain-lenses.md +0 -0
- /package/{skill → skills/graphit}/references/graphit-style.md +0 -0
- /package/{skill → skills/graphit}/references/kb-exploration.md +0 -0
- /package/{skill → skills/graphit}/references/sql-reference.md +0 -0
package/dist/index.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { registerAuthCommands } from "./commands/auth.js";
|
|
4
|
-
import { registerKBCommands } from "./commands/kb.js";
|
|
5
|
-
import { registerQueryCommands } from "./commands/query.js";
|
|
6
|
-
import { registerDSCommands } from "./commands/ds.js";
|
|
7
|
-
import { registerDashboardCommands } from "./commands/dashboard.js";
|
|
8
|
-
import { registerConnectorCommands } from "./commands/connector.js";
|
|
9
|
-
import { registerSetupCommand } from "./commands/setup.js";
|
|
10
|
-
const program = new Command();
|
|
11
|
-
program
|
|
12
|
-
.name("graphit")
|
|
13
|
-
.description("Graphit CLI - Build custom dashboards from any AI coding assistant")
|
|
14
|
-
.version("0.1.1")
|
|
15
|
-
.option("--output <format>", "Output format: json (default) or table", "json");
|
|
16
|
-
registerAuthCommands(program);
|
|
17
|
-
registerKBCommands(program);
|
|
18
|
-
registerQueryCommands(program);
|
|
19
|
-
registerDSCommands(program);
|
|
20
|
-
registerDashboardCommands(program);
|
|
21
|
-
registerConnectorCommands(program);
|
|
22
|
-
registerSetupCommand(program);
|
|
23
|
-
program.parse();
|
|
24
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,oEAAoE,CAAC;KACjF,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,mBAAmB,EAAE,wCAAwC,EAAE,MAAM,CAAC,CAAC;AAEjF,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,yBAAyB,CAAC,OAAO,CAAC,CAAC;AACnC,yBAAyB,CAAC,OAAO,CAAC,CAAC;AACnC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAE9B,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/output/format.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
export type OutputFormat = "json" | "table";
|
|
3
|
-
export declare function getOutputFormat(cmd: Command): OutputFormat;
|
|
4
|
-
export declare function output(cmd: Command, data: unknown, opts?: {
|
|
5
|
-
columns?: string[];
|
|
6
|
-
keyValue?: boolean;
|
|
7
|
-
}): void;
|
|
8
|
-
export declare function errorOutput(err: unknown): void;
|
package/dist/output/format.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { formatJson } from "./json.js";
|
|
2
|
-
import { formatTable, formatKeyValue } from "./table.js";
|
|
3
|
-
export function getOutputFormat(cmd) {
|
|
4
|
-
const root = cmd.parent ?? cmd;
|
|
5
|
-
const format = root.opts().output;
|
|
6
|
-
return format === "table" ? "table" : "json";
|
|
7
|
-
}
|
|
8
|
-
export function output(cmd, data, opts) {
|
|
9
|
-
const format = getOutputFormat(cmd);
|
|
10
|
-
if (format === "json") {
|
|
11
|
-
console.log(formatJson(data));
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (opts?.keyValue && !Array.isArray(data)) {
|
|
15
|
-
console.log(formatKeyValue(data));
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const rows = Array.isArray(data) ? data : [data];
|
|
19
|
-
console.log(formatTable(rows, opts?.columns));
|
|
20
|
-
}
|
|
21
|
-
export function errorOutput(err) {
|
|
22
|
-
const message = err instanceof Error
|
|
23
|
-
? err.message
|
|
24
|
-
: typeof err === "object" && err !== null && "detail" in err
|
|
25
|
-
? err.detail
|
|
26
|
-
: String(err);
|
|
27
|
-
console.error(JSON.stringify({ error: message }));
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=format.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,MAAgB,CAAC;IAC5C,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,GAAY,EACZ,IAAa,EACb,IAAiD;IAEjD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,IAAI,IAAI,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAA+B,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAiC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,OAAO,GACX,GAAG,YAAY,KAAK;QAClB,CAAC,CAAC,GAAG,CAAC,OAAO;QACb,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,IAAI,GAAG;YAC1D,CAAC,CAAE,GAA0B,CAAC,MAAM;YACpC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEpB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/output/json.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function formatJson(data: unknown): string;
|
package/dist/output/json.js
DELETED
package/dist/output/json.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/output/json.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC"}
|
package/dist/output/table.d.ts
DELETED
package/dist/output/table.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export function formatTable(rows, columns) {
|
|
2
|
-
if (rows.length === 0)
|
|
3
|
-
return "(no results)";
|
|
4
|
-
const cols = columns || Object.keys(rows[0]);
|
|
5
|
-
const widths = cols.map((col) => Math.max(col.length, ...rows.map((r) => String(r[col] ?? "").length)));
|
|
6
|
-
const header = cols.map((col, i) => col.padEnd(widths[i])).join(" ");
|
|
7
|
-
const separator = widths.map((w) => "-".repeat(w)).join(" ");
|
|
8
|
-
const body = rows.map((row) => cols.map((col, i) => String(row[col] ?? "").padEnd(widths[i])).join(" "));
|
|
9
|
-
return [header, separator, ...body].join("\n");
|
|
10
|
-
}
|
|
11
|
-
export function formatKeyValue(data) {
|
|
12
|
-
const maxKeyLen = Math.max(...Object.keys(data).map((k) => k.length));
|
|
13
|
-
return Object.entries(data)
|
|
14
|
-
.map(([k, v]) => `${k.padEnd(maxKeyLen)} ${v}`)
|
|
15
|
-
.join("\n");
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=table.js.map
|
package/dist/output/table.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/output/table.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CACzB,IAA+B,EAC/B,OAAkB;IAElB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,cAAc,CAAC;IAE7C,MAAM,IAAI,GAAG,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC9B,IAAI,CAAC,GAAG,CACN,GAAG,CAAC,MAAM,EACV,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAChD,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC5B,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC1E,CAAC;IAEF,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAA6B;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;SACxB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;SAC/C,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
alwaysApply: false
|
|
3
|
-
description: "Graphit: Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources."
|
|
4
|
-
globs: []
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Chart Patterns
|
|
8
|
-
|
|
9
|
-
Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources.
|
|
10
|
-
|
|
11
|
-
## CSS Horizontal Bar
|
|
12
|
-
```js
|
|
13
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
14
|
-
data.forEach(function(d) {
|
|
15
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
16
|
-
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
17
|
-
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + d.name + '</span>'
|
|
18
|
-
+ '<div style="flex:1;height:28px;background:var(--graphit-surface-sunken);border-radius:6px;overflow:hidden">'
|
|
19
|
-
+ '<div style="width:' + pct + '%;height:100%;background:var(--graphit-accent);border-radius:6px;min-width:2px"></div></div>'
|
|
20
|
-
+ '<span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + fmt(d.value) + '</span></div>';
|
|
21
|
-
});
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## CSS Vertical Bar (Column)
|
|
25
|
-
```js
|
|
26
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
27
|
-
container.style.cssText = 'display:flex;align-items:flex-end;gap:8px;height:200px;padding-top:20px';
|
|
28
|
-
data.forEach(function(d) {
|
|
29
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
30
|
-
container.innerHTML += '<div style="flex:1;height:' + pct + '%;background:var(--graphit-accent);border-radius:4px 4px 0 0;'
|
|
31
|
-
+ 'position:relative;min-width:30px">'
|
|
32
|
-
+ '<span style="position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-size:12px;font-weight:600">'
|
|
33
|
-
+ d.value + '</span>'
|
|
34
|
-
+ '<span style="position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--graphit-fg-subtle);white-space:nowrap">'
|
|
35
|
-
+ d.name + '</span></div>';
|
|
36
|
-
});
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## CSS Stacked Bar
|
|
40
|
-
```js
|
|
41
|
-
var colors = ['var(--graphit-accent)','#80CBC4','#26A69A','#5FA39B'];
|
|
42
|
-
data.forEach(function(row) {
|
|
43
|
-
var total = row.segments.reduce(function(a,s){return a+s.value},0);
|
|
44
|
-
var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
45
|
-
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + row.name + '</span>'
|
|
46
|
-
+ '<div style="flex:1;height:28px;display:flex;border-radius:6px;overflow:hidden">';
|
|
47
|
-
row.segments.forEach(function(s, i) {
|
|
48
|
-
html += '<div style="width:' + (s.value/total*100).toFixed(1) + '%;height:100%;background:' + colors[i%colors.length] + '"></div>';
|
|
49
|
-
});
|
|
50
|
-
html += '</div><span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + total + '</span></div>';
|
|
51
|
-
container.innerHTML += html;
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## SVG Line/Area Chart
|
|
56
|
-
```js
|
|
57
|
-
var w = 600, h = 200, pad = 20;
|
|
58
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
59
|
-
var points = data.map(function(d, i) {
|
|
60
|
-
var x = pad + (i / (data.length - 1)) * (w - 2*pad);
|
|
61
|
-
var y = h - pad - ((d.value / maxVal) * (h - 2*pad));
|
|
62
|
-
return x + ',' + y;
|
|
63
|
-
});
|
|
64
|
-
svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
|
65
|
-
svg.innerHTML =
|
|
66
|
-
'<path d="M' + points.join(' L') + ' L' + (w-pad) + ',' + (h-pad) + ' L' + pad + ',' + (h-pad) + ' Z" fill="rgba(77,182,172,0.15)"/>'
|
|
67
|
-
+ '<path d="M' + points.join(' L') + '" fill="none" stroke="var(--graphit-accent)" stroke-width="2"/>';
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## SVG Multi-Series Line
|
|
71
|
-
```js
|
|
72
|
-
var colors = ['var(--graphit-accent)','#FF7043','#7E57C2','#42A5F5'];
|
|
73
|
-
series.forEach(function(s, si) {
|
|
74
|
-
var pts = s.values.map(function(v, i) {
|
|
75
|
-
var x = pad + (i / (s.values.length - 1)) * (w - 2*pad);
|
|
76
|
-
var y = h - pad - ((v / maxAll) * (h - 2*pad));
|
|
77
|
-
return x + ',' + y;
|
|
78
|
-
});
|
|
79
|
-
svg.innerHTML += '<path d="M' + pts.join(' L') + '" fill="none" stroke="' + colors[si] + '" stroke-width="2"/>';
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## SVG Sparkline (for KPI cards)
|
|
84
|
-
```js
|
|
85
|
-
var vals = [10,15,12,18,22,19,25];
|
|
86
|
-
var max = Math.max.apply(null,vals), min = Math.min.apply(null,vals);
|
|
87
|
-
var pts = vals.map(function(v,i){
|
|
88
|
-
return (i/(vals.length-1))*120 + ',' + (30 - ((v-min)/(max-min||1))*26 - 2);
|
|
89
|
-
}).join(' L');
|
|
90
|
-
sparkSvg.innerHTML = '<path d="M' + pts + '" fill="none" stroke="var(--graphit-accent)" stroke-width="1.5"/>';
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Canvas Donut Chart
|
|
94
|
-
```js
|
|
95
|
-
var ctx = canvas.getContext('2d');
|
|
96
|
-
var slices = [{v:40,c:'var(--graphit-accent)'},{v:25,c:'#80CBC4'},{v:20,c:'#26A69A'},{v:15,c:'#009688'}];
|
|
97
|
-
var total = slices.reduce(function(a,s){return a+s.v},0);
|
|
98
|
-
var cx=150, cy=150, r=120, hole=70, angle = -Math.PI/2;
|
|
99
|
-
slices.forEach(function(s) {
|
|
100
|
-
var arc = (s.v/total)*Math.PI*2;
|
|
101
|
-
ctx.beginPath(); ctx.moveTo(cx,cy);
|
|
102
|
-
ctx.arc(cx,cy,r,angle,angle+arc);
|
|
103
|
-
ctx.fillStyle=s.c; ctx.fill(); angle+=arc;
|
|
104
|
-
});
|
|
105
|
-
ctx.beginPath(); ctx.arc(cx,cy,hole,0,Math.PI*2);
|
|
106
|
-
ctx.fillStyle='white'; ctx.fill();
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## CSS Heatmap Grid
|
|
110
|
-
```js
|
|
111
|
-
var max = Math.max.apply(null, cells.map(function(c){return c.value}));
|
|
112
|
-
container.style.cssText = 'display:grid;grid-template-columns:repeat(' + cols + ',1fr);gap:2px';
|
|
113
|
-
cells.forEach(function(c) {
|
|
114
|
-
var intensity = c.value / max;
|
|
115
|
-
var bg = 'rgba(77,182,172,' + (0.1 + intensity*0.9).toFixed(2) + ')';
|
|
116
|
-
var fg = intensity > 0.6 ? 'white' : 'var(--graphit-fg)';
|
|
117
|
-
container.innerHTML += '<div style="aspect-ratio:1;border-radius:3px;display:flex;align-items:center;'
|
|
118
|
-
+ 'justify-content:center;font-size:11px;font-weight:600;background:' + bg + ';color:' + fg + '">'
|
|
119
|
-
+ c.value + '</div>';
|
|
120
|
-
});
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## CSS Funnel
|
|
124
|
-
```js
|
|
125
|
-
var maxVal = data[0].value;
|
|
126
|
-
data.forEach(function(d, i) {
|
|
127
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
128
|
-
var rate = i > 0 ? (d.value / data[i-1].value * 100).toFixed(1) + '%' : '';
|
|
129
|
-
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">'
|
|
130
|
-
+ '<span style="width:120px;font-size:13px;text-align:right">' + d.name + '</span>'
|
|
131
|
-
+ '<div style="width:' + pct + '%;height:36px;background:var(--graphit-accent);border-radius:4px;'
|
|
132
|
-
+ 'display:flex;align-items:center;padding:0 12px;color:white;font-weight:600;font-size:13px">'
|
|
133
|
-
+ d.value.toLocaleString() + '</div>'
|
|
134
|
-
+ '<span style="font-size:12px;color:var(--graphit-fg-subtle)">' + rate + '</span></div>';
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## CSS Gauge / Progress
|
|
139
|
-
```html
|
|
140
|
-
<div style="position:relative;width:200px;height:100px;overflow:hidden">
|
|
141
|
-
<div id="gauge-arc" style="width:200px;height:200px;border-radius:50%;
|
|
142
|
-
background:conic-gradient(var(--graphit-accent) 0% 72%, var(--graphit-surface-sunken) 72% 100%);
|
|
143
|
-
clip-path:inset(0 0 50% 0)"></div>
|
|
144
|
-
<div style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);
|
|
145
|
-
font-size:28px;font-weight:700">72%</div>
|
|
146
|
-
</div>
|
|
147
|
-
```
|
|
148
|
-
Dynamic: set `background: conic-gradient(var(--graphit-accent) 0% ${pct}%, var(--graphit-surface-sunken) ${pct}% 100%)`.
|
package/skill/graphit.mdc
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
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).
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
# Chart Patterns
|
|
2
|
-
|
|
3
|
-
Inline CSS/SVG implementations for dashboards. No external libraries - the iframe CSP blocks all external resources.
|
|
4
|
-
|
|
5
|
-
**NEVER use `<canvas>`.** Canvas produces blurry charts inside the sandboxed iframe due to DPI scaling issues. Always use SVG with `viewBox` (resolution-independent) or CSS-based techniques.
|
|
6
|
-
|
|
7
|
-
**Tooltips:** Every chart with data points should include hover tooltips. Use a shared tooltip div and attach DOM events on SVG elements or HTML rows. See the Tooltip Pattern section at the bottom.
|
|
8
|
-
|
|
9
|
-
## CSS Horizontal Bar
|
|
10
|
-
```js
|
|
11
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
12
|
-
data.forEach(function(d) {
|
|
13
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
14
|
-
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
15
|
-
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + d.name + '</span>'
|
|
16
|
-
+ '<div style="flex:1;height:28px;background:var(--graphit-surface-sunken);border-radius:6px;overflow:hidden">'
|
|
17
|
-
+ '<div style="width:' + pct + '%;height:100%;background:var(--graphit-accent);border-radius:6px;min-width:2px"></div></div>'
|
|
18
|
-
+ '<span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + fmt(d.value) + '</span></div>';
|
|
19
|
-
});
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## CSS Vertical Bar (Column)
|
|
23
|
-
```js
|
|
24
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
25
|
-
container.style.cssText = 'display:flex;align-items:flex-end;gap:8px;height:200px;padding-top:20px';
|
|
26
|
-
data.forEach(function(d) {
|
|
27
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
28
|
-
container.innerHTML += '<div style="flex:1;height:' + pct + '%;background:var(--graphit-accent);border-radius:4px 4px 0 0;'
|
|
29
|
-
+ 'position:relative;min-width:30px">'
|
|
30
|
-
+ '<span style="position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-size:12px;font-weight:600">'
|
|
31
|
-
+ d.value + '</span>'
|
|
32
|
-
+ '<span style="position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--graphit-fg-subtle);white-space:nowrap">'
|
|
33
|
-
+ d.name + '</span></div>';
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## CSS Stacked Bar
|
|
38
|
-
```js
|
|
39
|
-
var colors = ['var(--graphit-accent)','#80CBC4','#26A69A','#5FA39B'];
|
|
40
|
-
data.forEach(function(row) {
|
|
41
|
-
var total = row.segments.reduce(function(a,s){return a+s.value},0);
|
|
42
|
-
var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
43
|
-
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + row.name + '</span>'
|
|
44
|
-
+ '<div style="flex:1;height:28px;display:flex;border-radius:6px;overflow:hidden">';
|
|
45
|
-
row.segments.forEach(function(s, i) {
|
|
46
|
-
html += '<div style="width:' + (s.value/total*100).toFixed(1) + '%;height:100%;background:' + colors[i%colors.length] + '"></div>';
|
|
47
|
-
});
|
|
48
|
-
html += '</div><span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + total + '</span></div>';
|
|
49
|
-
container.innerHTML += html;
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## SVG Line/Area Chart
|
|
54
|
-
```js
|
|
55
|
-
var w = 600, h = 200, pad = 20;
|
|
56
|
-
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
57
|
-
var points = data.map(function(d, i) {
|
|
58
|
-
var x = pad + (i / (data.length - 1)) * (w - 2*pad);
|
|
59
|
-
var y = h - pad - ((d.value / maxVal) * (h - 2*pad));
|
|
60
|
-
return x + ',' + y;
|
|
61
|
-
});
|
|
62
|
-
svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
|
63
|
-
svg.innerHTML =
|
|
64
|
-
'<path d="M' + points.join(' L') + ' L' + (w-pad) + ',' + (h-pad) + ' L' + pad + ',' + (h-pad) + ' Z" fill="rgba(77,182,172,0.15)"/>'
|
|
65
|
-
+ '<path d="M' + points.join(' L') + '" fill="none" stroke="var(--graphit-accent)" stroke-width="2"/>';
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## SVG Multi-Series Line
|
|
69
|
-
```js
|
|
70
|
-
var colors = ['var(--graphit-accent)','#FF7043','#7E57C2','#42A5F5'];
|
|
71
|
-
series.forEach(function(s, si) {
|
|
72
|
-
var pts = s.values.map(function(v, i) {
|
|
73
|
-
var x = pad + (i / (s.values.length - 1)) * (w - 2*pad);
|
|
74
|
-
var y = h - pad - ((v / maxAll) * (h - 2*pad));
|
|
75
|
-
return x + ',' + y;
|
|
76
|
-
});
|
|
77
|
-
svg.innerHTML += '<path d="M' + pts.join(' L') + '" fill="none" stroke="' + colors[si] + '" stroke-width="2"/>';
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## SVG Sparkline (for KPI cards)
|
|
82
|
-
```js
|
|
83
|
-
var vals = [10,15,12,18,22,19,25];
|
|
84
|
-
var max = Math.max.apply(null,vals), min = Math.min.apply(null,vals);
|
|
85
|
-
var pts = vals.map(function(v,i){
|
|
86
|
-
return (i/(vals.length-1))*120 + ',' + (30 - ((v-min)/(max-min||1))*26 - 2);
|
|
87
|
-
}).join(' L');
|
|
88
|
-
sparkSvg.innerHTML = '<path d="M' + pts + '" fill="none" stroke="var(--graphit-accent)" stroke-width="1.5"/>';
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## SVG Donut Chart
|
|
92
|
-
```js
|
|
93
|
-
var slices = [{v:40,c:'var(--graphit-accent)',label:'Category A'},{v:25,c:'#80CBC4',label:'Category B'},{v:20,c:'#26A69A',label:'Category C'},{v:15,c:'#009688',label:'Category D'}];
|
|
94
|
-
var total = slices.reduce(function(a,s){return a+s.v},0);
|
|
95
|
-
var cx=100, cy=100, r=80, hole=50, angle=-Math.PI/2;
|
|
96
|
-
svg.setAttribute('viewBox','0 0 200 200');
|
|
97
|
-
slices.forEach(function(s) {
|
|
98
|
-
var arc = (s.v/total)*Math.PI*2;
|
|
99
|
-
var x1 = cx + r*Math.cos(angle), y1 = cy + r*Math.sin(angle);
|
|
100
|
-
var x2 = cx + r*Math.cos(angle+arc), y2 = cy + r*Math.sin(angle+arc);
|
|
101
|
-
var large = arc > Math.PI ? 1 : 0;
|
|
102
|
-
var path = 'M'+x1+','+y1+' A'+r+','+r+' 0 '+large+' 1 '+x2+','+y2+' L'+cx+','+cy+' Z';
|
|
103
|
-
var el = document.createElementNS('http://www.w3.org/2000/svg','path');
|
|
104
|
-
el.setAttribute('d', path); el.setAttribute('fill', s.c);
|
|
105
|
-
el.style.cursor = 'pointer';
|
|
106
|
-
el.addEventListener('mouseenter', function(e) { showTooltip(e, s.label+': '+s.v+' ('+(s.v/total*100).toFixed(1)+'%)'); });
|
|
107
|
-
el.addEventListener('mouseleave', hideTooltip);
|
|
108
|
-
svg.appendChild(el);
|
|
109
|
-
angle += arc;
|
|
110
|
-
});
|
|
111
|
-
// Center hole
|
|
112
|
-
var circle = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
|
113
|
-
circle.setAttribute('cx',cx); circle.setAttribute('cy',cy); circle.setAttribute('r',hole);
|
|
114
|
-
circle.setAttribute('fill','var(--graphit-surface-raised)');
|
|
115
|
-
svg.appendChild(circle);
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## SVG Scatter / Bubble Chart
|
|
119
|
-
```js
|
|
120
|
-
// data: [{label, x, y, size}] x/y are raw values, size is relative (0-1)
|
|
121
|
-
var svgW=500, svgH=300, pad={top:16,right:16,bottom:36,left:50};
|
|
122
|
-
var pW=svgW-pad.left-pad.right, pH=svgH-pad.top-pad.bottom;
|
|
123
|
-
svg.setAttribute('viewBox','0 0 '+svgW+' '+svgH);
|
|
124
|
-
var maxX=Math.max.apply(null,data.map(function(d){return d.x}))*1.1;
|
|
125
|
-
var maxY=Math.max.apply(null,data.map(function(d){return d.y}))*1.15;
|
|
126
|
-
// Sort large-to-small so small bubbles render on top
|
|
127
|
-
data.sort(function(a,b){return b.size-a.size});
|
|
128
|
-
data.forEach(function(d) {
|
|
129
|
-
var cx = pad.left + (d.x/maxX)*pW;
|
|
130
|
-
var cy = svgH - pad.bottom - (d.y/maxY)*pH;
|
|
131
|
-
var r = Math.max(5, Math.sqrt(d.size)*28);
|
|
132
|
-
var g = document.createElementNS('http://www.w3.org/2000/svg','g');
|
|
133
|
-
g.style.cursor = 'pointer';
|
|
134
|
-
var c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
|
135
|
-
c.setAttribute('cx',cx); c.setAttribute('cy',cy); c.setAttribute('r',r);
|
|
136
|
-
c.setAttribute('fill','var(--graphit-accent)'); c.setAttribute('fill-opacity','0.55');
|
|
137
|
-
c.setAttribute('stroke','var(--graphit-accent)'); c.setAttribute('stroke-width','1.5');
|
|
138
|
-
g.appendChild(c);
|
|
139
|
-
var t = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
140
|
-
t.setAttribute('x',cx); t.setAttribute('y',cy-r-4);
|
|
141
|
-
t.setAttribute('text-anchor','middle'); t.setAttribute('font-size','10');
|
|
142
|
-
t.setAttribute('fill','var(--graphit-fg-subtle)');
|
|
143
|
-
t.textContent = d.label; g.appendChild(t);
|
|
144
|
-
g.addEventListener('mouseenter', function(e){ showTooltip(e, d.tooltipHtml); });
|
|
145
|
-
g.addEventListener('mousemove', function(e){ showTooltip(e, tooltip.innerHTML); });
|
|
146
|
-
g.addEventListener('mouseleave', hideTooltip);
|
|
147
|
-
svg.appendChild(g);
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## CSS Heatmap Grid
|
|
152
|
-
```js
|
|
153
|
-
var max = Math.max.apply(null, cells.map(function(c){return c.value}));
|
|
154
|
-
container.style.cssText = 'display:grid;grid-template-columns:repeat(' + cols + ',1fr);gap:2px';
|
|
155
|
-
cells.forEach(function(c) {
|
|
156
|
-
var intensity = c.value / max;
|
|
157
|
-
var bg = 'rgba(77,182,172,' + (0.1 + intensity*0.9).toFixed(2) + ')';
|
|
158
|
-
var fg = intensity > 0.6 ? 'white' : 'var(--graphit-fg)';
|
|
159
|
-
container.innerHTML += '<div style="aspect-ratio:1;border-radius:3px;display:flex;align-items:center;'
|
|
160
|
-
+ 'justify-content:center;font-size:11px;font-weight:600;background:' + bg + ';color:' + fg + '">'
|
|
161
|
-
+ c.value + '</div>';
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## CSS Funnel
|
|
166
|
-
```js
|
|
167
|
-
var maxVal = data[0].value;
|
|
168
|
-
data.forEach(function(d, i) {
|
|
169
|
-
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
170
|
-
var rate = i > 0 ? (d.value / data[i-1].value * 100).toFixed(1) + '%' : '';
|
|
171
|
-
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">'
|
|
172
|
-
+ '<span style="width:120px;font-size:13px;text-align:right">' + d.name + '</span>'
|
|
173
|
-
+ '<div style="width:' + pct + '%;height:36px;background:var(--graphit-accent);border-radius:4px;'
|
|
174
|
-
+ 'display:flex;align-items:center;padding:0 12px;color:white;font-weight:600;font-size:13px">'
|
|
175
|
-
+ d.value.toLocaleString() + '</div>'
|
|
176
|
-
+ '<span style="font-size:12px;color:var(--graphit-fg-subtle)">' + rate + '</span></div>';
|
|
177
|
-
});
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## CSS Gauge / Progress
|
|
181
|
-
```html
|
|
182
|
-
<div style="position:relative;width:200px;height:100px;overflow:hidden">
|
|
183
|
-
<div id="gauge-arc" style="width:200px;height:200px;border-radius:50%;
|
|
184
|
-
background:conic-gradient(var(--graphit-accent) 0% 72%, var(--graphit-surface-sunken) 72% 100%);
|
|
185
|
-
clip-path:inset(0 0 50% 0)"></div>
|
|
186
|
-
<div style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);
|
|
187
|
-
font-size:28px;font-weight:700">72%</div>
|
|
188
|
-
</div>
|
|
189
|
-
```
|
|
190
|
-
Dynamic: set `background: conic-gradient(var(--graphit-accent) 0% ${pct}%, var(--graphit-surface-sunken) ${pct}% 100%)`.
|
|
191
|
-
|
|
192
|
-
## Tooltip Pattern
|
|
193
|
-
|
|
194
|
-
Every chart with data points should have hover tooltips. Add ONE shared tooltip div to the page and reuse it from all charts.
|
|
195
|
-
|
|
196
|
-
### HTML (add once, before `<script>`)
|
|
197
|
-
```html
|
|
198
|
-
<div id="tooltip" style="position:fixed;pointer-events:none;z-index:100;
|
|
199
|
-
background:var(--graphit-surface-raised);color:var(--graphit-fg);
|
|
200
|
-
border:1px solid var(--graphit-border);border-radius:8px;
|
|
201
|
-
padding:10px 14px;font-size:13px;line-height:1.5;
|
|
202
|
-
box-shadow:0 4px 12px rgba(0,0,0,0.15);opacity:0;transition:opacity 0.15s;
|
|
203
|
-
max-width:240px"></div>
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### JS helpers (add once)
|
|
207
|
-
```js
|
|
208
|
-
var tooltip = document.getElementById('tooltip');
|
|
209
|
-
function showTooltip(e, html) {
|
|
210
|
-
tooltip.innerHTML = html;
|
|
211
|
-
tooltip.style.opacity = '1';
|
|
212
|
-
var tx = Math.min(e.clientX + 12, window.innerWidth - 260);
|
|
213
|
-
var ty = e.clientY - tooltip.offsetHeight - 8;
|
|
214
|
-
tooltip.style.left = tx + 'px';
|
|
215
|
-
tooltip.style.top = (ty < 4 ? e.clientY + 16 : ty) + 'px';
|
|
216
|
-
}
|
|
217
|
-
function hideTooltip() { tooltip.style.opacity = '0'; }
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Tooltip row layout
|
|
221
|
-
```html
|
|
222
|
-
<!-- Inside tooltip content -->
|
|
223
|
-
<div style="font-weight:700;margin-bottom:4px">Title</div>
|
|
224
|
-
<div style="display:flex;justify-content:space-between;gap:16px">
|
|
225
|
-
<span style="color:var(--graphit-fg-muted)">Label</span>
|
|
226
|
-
<span style="font-weight:600">Value</span>
|
|
227
|
-
</div>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### SVG event attachment patterns
|
|
231
|
-
```js
|
|
232
|
-
// Bubble/scatter/donut: attach on each SVG <g> or <path>
|
|
233
|
-
element.addEventListener('mouseenter', function(e) { showTooltip(e, htmlContent); });
|
|
234
|
-
element.addEventListener('mousemove', function(e) { showTooltip(e, tooltip.innerHTML); });
|
|
235
|
-
element.addEventListener('mouseleave', hideTooltip);
|
|
236
|
-
|
|
237
|
-
// Line chart: invisible rect columns per x-tick + vertical guide line
|
|
238
|
-
weekData.forEach(function(w, i) {
|
|
239
|
-
var rect = document.createElementNS(NS, 'rect');
|
|
240
|
-
// x, y, width=colWidth, height=plotHeight, fill='transparent', cursor='pointer'
|
|
241
|
-
var vLine = document.createElementNS(NS, 'line');
|
|
242
|
-
// vertical dashed line at xPos(i), opacity=0
|
|
243
|
-
rect.addEventListener('mouseenter', function(e) {
|
|
244
|
-
vLine.setAttribute('opacity', '1');
|
|
245
|
-
showTooltip(e, buildWeekTooltip(w));
|
|
246
|
-
});
|
|
247
|
-
rect.addEventListener('mouseleave', function() {
|
|
248
|
-
vLine.setAttribute('opacity', '0');
|
|
249
|
-
hideTooltip();
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// CSS bar rows: attach on each bar <div> row
|
|
254
|
-
row.addEventListener('mouseenter', function(e) { showTooltip(e, htmlContent); });
|
|
255
|
-
row.addEventListener('mousemove', function(e) { showTooltip(e, tooltip.innerHTML); });
|
|
256
|
-
row.addEventListener('mouseleave', hideTooltip);
|
|
257
|
-
```
|
|
File without changes
|