Skip to content

Commit 840dafa

Browse files
committed
new graph example
1 parent 2a7b20b commit 840dafa

File tree

4 files changed

+306
-0
lines changed

4 files changed

+306
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ tmp/
1313
examples/scratch.ts
1414
**/moa.html
1515
**/descript.html
16+
**/knowledge-graph.html

examples/knowledge-graph/generate.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env -S npx ts-node --transpileOnly
2+
3+
import { Substrate, ComputeJSON, ComputeText, sb } from "substrate";
4+
import fs from "fs";
5+
import { currentDir, jsonSchema } from "./util";
6+
const sampleBook = "War and Peace by Tolstoy";
7+
const book = process.argv[2] || sampleBook;
8+
const substrate = new Substrate({ apiKey: process.env["SUBSTRATE_API_KEY"] });
9+
10+
async function main() {
11+
const initialList = new ComputeText({
12+
prompt: `List all the main characters in "${book}", then for all the meaningful relationships between them, provide a short label for the relationship`,
13+
model: "Llama3Instruct405B",
14+
});
15+
const graph = new ComputeJSON({
16+
prompt: sb.interpolate`Make a JSON graph composed of the characters in ${book} that illustrates the relationships between them.
17+
Use this context:
18+
${initialList.future.text}`,
19+
json_schema: jsonSchema,
20+
temperature: 0.2,
21+
model: "Llama3Instruct8B",
22+
});
23+
const res = await substrate.run(graph);
24+
const jsonOut = res.get(graph).json_object;
25+
const htmlTemplate = fs.readFileSync(`${currentDir}/index.html`, "utf8");
26+
const html = htmlTemplate.replace(
27+
'"{{ graphData }}"',
28+
JSON.stringify(jsonOut, null, 2),
29+
);
30+
fs.writeFileSync("knowledge-graph.html", html);
31+
}
32+
main();

examples/knowledge-graph/index.html

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Character Relationship Graph</title>
7+
<script src="https://d3js.org/d3.v7.min.js"></script>
8+
<style>
9+
body {
10+
font-family: "Arial", sans-serif;
11+
background-color: #f0f4f8;
12+
display: flex;
13+
justify-content: center;
14+
align-items: center;
15+
height: 100vh;
16+
margin: 0;
17+
}
18+
#graph-container {
19+
background-color: white;
20+
border-radius: 12px;
21+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
22+
padding: 20px;
23+
width: 90vw;
24+
height: 80vh;
25+
}
26+
.node {
27+
stroke: #fff;
28+
stroke-width: 2px;
29+
}
30+
.link {
31+
fill: none;
32+
stroke-opacity: 0.6;
33+
}
34+
.node-label {
35+
font-size: 12px;
36+
font-weight: bold;
37+
pointer-events: none;
38+
}
39+
.link-label {
40+
font-size: 10px;
41+
fill: #555;
42+
pointer-events: none;
43+
}
44+
</style>
45+
</head>
46+
<body>
47+
<div id="graph-container"></div>
48+
49+
<script>
50+
const graphData = "{{ graphData }}";
51+
52+
const colorPalette = {
53+
"Soft Sage": "#CCD5AE",
54+
"Dusty Blue": "#A2B5BB",
55+
"Pale Peach": "#E9DAC1",
56+
"Muted Lavender": "#9F86C0",
57+
"Light Teal": "#A4C3B2",
58+
"Warm Sand": "#EEE0C9",
59+
"Faded Denim": "#6E85B7",
60+
"Dusty Rose": "#D7B8AC",
61+
"Soft Mint": "#B5D5C5",
62+
"Muted Coral": "#E6BEAE",
63+
};
64+
65+
const edgeColorPalette = {
66+
"Charcoal Gray": "#36454F",
67+
"Deep Olive": "#3C341F",
68+
"Navy Blue": "#000080",
69+
Burgundy: "#800020",
70+
"Forest Green": "#228B22",
71+
"Dark Taupe": "#483C32",
72+
"Slate Blue": "#6A5ACD",
73+
"Deep Plum": "#4B0082",
74+
"Moss Green": "#8A9A5B",
75+
"Dark Sienna": "#3C1414",
76+
};
77+
const container = document.getElementById("graph-container");
78+
const width = container.clientWidth - 40;
79+
const height = container.clientHeight - 40;
80+
81+
const svg = d3
82+
.select("#graph-container")
83+
.append("svg")
84+
.attr("width", "100%")
85+
.attr("height", "100%");
86+
87+
const g = svg.append("g").attr("transform", "translate(20,20)");
88+
89+
const zoom = d3
90+
.zoom()
91+
.scaleExtent([0.1, 4])
92+
.on("zoom", (event) => {
93+
g.attr("transform", event.transform);
94+
});
95+
96+
svg.call(zoom);
97+
98+
const simulation = d3
99+
.forceSimulation(graphData.nodes)
100+
.force(
101+
"link",
102+
d3
103+
.forceLink(graphData.edges)
104+
.id((d) => d.id)
105+
.distance(200),
106+
)
107+
.force("charge", d3.forceManyBody().strength(-1000))
108+
.force("center", d3.forceCenter(width / 2, height / 2))
109+
.force("collision", d3.forceCollide().radius(60));
110+
111+
const link = g
112+
.append("g")
113+
.selectAll("path")
114+
.data(graphData.edges)
115+
.join("path")
116+
.attr("class", "link")
117+
.attr("stroke", (d) => edgeColorPalette[d.color]);
118+
119+
const node = g
120+
.append("g")
121+
.selectAll("circle")
122+
.data(graphData.nodes)
123+
.join("circle")
124+
.attr("class", "node")
125+
.attr("r", 20)
126+
.attr("fill", (d) => colorPalette[d.color])
127+
.call(
128+
d3
129+
.drag()
130+
.on("start", dragstarted)
131+
.on("drag", dragged)
132+
.on("end", dragended),
133+
);
134+
135+
const nodeLabel = g
136+
.append("g")
137+
.selectAll("text")
138+
.data(graphData.nodes)
139+
.join("text")
140+
.attr("class", "node-label")
141+
.text((d) => d.label)
142+
.attr("text-anchor", "middle")
143+
.attr("dy", 30);
144+
145+
const linkLabel = g
146+
.append("g")
147+
.selectAll("text")
148+
.data(graphData.edges)
149+
.join("text")
150+
.attr("class", "link-label")
151+
.text((d) => d.label)
152+
.attr("text-anchor", "middle");
153+
154+
simulation.on("tick", () => {
155+
link.attr("d", linkArc);
156+
157+
node
158+
.attr("cx", (d) => (d.x = Math.max(0, Math.min(width, d.x))))
159+
.attr("cy", (d) => (d.y = Math.max(0, Math.min(height, d.y))));
160+
161+
nodeLabel.attr("x", (d) => d.x).attr("y", (d) => d.y);
162+
163+
linkLabel
164+
.attr("x", (d) => (d.source.x + d.target.x) / 2)
165+
.attr("y", (d) => (d.source.y + d.target.y) / 2);
166+
});
167+
168+
function linkArc(d) {
169+
const dx = d.target.x - d.source.x,
170+
dy = d.target.y - d.source.y,
171+
dr = Math.sqrt(dx * dx + dy * dy);
172+
return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;
173+
}
174+
175+
function dragstarted(event, d) {
176+
if (!event.active) simulation.alphaTarget(0.3).restart();
177+
d.fx = d.x;
178+
d.fy = d.y;
179+
}
180+
181+
function dragged(event, d) {
182+
d.fx = event.x;
183+
d.fy = event.y;
184+
}
185+
186+
function dragended(event, d) {
187+
if (!event.active) simulation.alphaTarget(0);
188+
d.fx = null;
189+
d.fy = null;
190+
}
191+
192+
function zoomToFit() {
193+
const bounds = g.node().getBBox();
194+
const scale =
195+
0.8 / Math.max(bounds.width / width, bounds.height / height);
196+
const translate = [
197+
(width - scale * (bounds.x * 2 + bounds.width)) / 2,
198+
(height - scale * (bounds.y * 2 + bounds.height)) / 2,
199+
];
200+
201+
svg
202+
.transition()
203+
.duration(750)
204+
.call(
205+
zoom.transform,
206+
d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale),
207+
);
208+
}
209+
210+
simulation.on("end", zoomToFit);
211+
</script>
212+
</body>
213+
</html>

examples/knowledge-graph/util.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { fileURLToPath } from "url";
2+
import { dirname } from "path";
3+
4+
export const edgeColors = [
5+
"Charcoal Gray",
6+
"Deep Olive",
7+
"Navy Blue",
8+
"Burgundy",
9+
"Forest Green",
10+
"Dark Taupe",
11+
"Slate Blue",
12+
"Deep Plum",
13+
"Moss Green",
14+
"Dark Sienna",
15+
];
16+
export const colors = [
17+
"Soft Sage",
18+
"Dusty Blue",
19+
"Pale Peach",
20+
"Muted Lavender",
21+
"Light Teal",
22+
"Warm Sand",
23+
"Faded Denim",
24+
"Dusty Rose",
25+
"Soft Mint",
26+
"Muted Coral",
27+
];
28+
export const jsonSchema = {
29+
type: "object",
30+
properties: {
31+
nodes: {
32+
type: "array",
33+
items: {
34+
type: "object",
35+
properties: {
36+
id: { type: "integer" },
37+
label: { type: "string" },
38+
color: { type: "string", enum: colors },
39+
},
40+
},
41+
},
42+
edges: {
43+
type: "array",
44+
items: {
45+
type: "object",
46+
properties: {
47+
source: { type: "integer" },
48+
target: { type: "integer" },
49+
label: { type: "string" },
50+
color: { type: "string", enum: edgeColors },
51+
},
52+
},
53+
},
54+
},
55+
required: ["nodes", "edges"],
56+
description: "A knowledge graph with nodes and edges.",
57+
};
58+
59+
// @ts-ignore
60+
export const currentDir = dirname(fileURLToPath(import.meta.url));

0 commit comments

Comments
 (0)