Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tech tree expanded #331

Merged
merged 3 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
448 changes: 448 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
"@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.3.3",
"@codemirror/view": "^6.24.0",
"@observablehq/plot": "^0.6.13",
"@tailwindcss/typography": "^0.5.10",
"@types/codemirror": "^5.60.15",
"@types/html-escaper": "^3.0.2",
"astro": "^3.6.4",
"astro-auto-import": "^0.4.2",
"codemirror": "^6.0.1",
"d3": "^7.8.5",
"hast-util-to-string": "^3.0.0",
"hastscript": "^9.0.0",
"html-escaper": "^3.0.3",
Expand Down
14 changes: 14 additions & 0 deletions src/components/data/flattenTree.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function flattenTree(node, path = '') {
const fullPath = path ? `${path}/${node.name}` : node.name;
let array = [{ name: fullPath }];

if (node.children) {
node.children.forEach(child => {
array = array.concat(flattenTree(child, fullPath));
});
}

return array;
}

export default flattenTree;
26 changes: 26 additions & 0 deletions src/components/data/tree.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{ "name": "Communications" },
{ "name": "Communications/Written Language" },
{ "name": "Communications/Written Language/Printing Press" },
{ "name": "Communications/Written Language/Printing Press/Newspapers" },
{ "name": "Communications/Written Language/Printing Press/Books" },
{ "name": "Communications/Electronic" },
{ "name": "Communications/Electronic/Telegraph" },
{ "name": "Communications/Electronic/Telegraph/Radio" },
{ "name": "Communications/Electronic/Telegraph/Radio/Television" },
{
"name": "Communications/Electronic/Telegraph/Radio/Television/Satellite TV"
},
{ "name": "Communications/Electronic/Telegraph/Radio/Internet" },
{
"name": "Communications/Electronic/Telegraph/Radio/Internet/Social Media"
},
{
"name": "Communications/Electronic/Telegraph/Radio/Internet/Streaming Services"
},
{ "name": "Communications/Electronic/Telephony" },
{ "name": "Communications/Electronic/Telephony/Mobile Phones" },
{
"name": "Communications/Electronic/Telephony/Mobile Phones/Smartphones"
}
]
52 changes: 52 additions & 0 deletions src/components/data/treePlot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "Communications",
"children": [
{
"name": "Written Language",
"children": [
{
"name": "Printing Press",
"children": [
{ "name": "Newspapers", "children": [] },
{ "name": "Books", "children": [] }
]
}
]
},
{
"name": "Electronic",
"children": [
{
"name": "Telegraph",
"children": [
{
"name": "Radio",
"children": [
{
"name": "Television",
"children": [{ "name": "Satellite TV", "children": [] }]
},
{
"name": "Internet",
"children": [
{ "name": "Social Media", "children": [] },
{ "name": "Streaming Services", "children": [] }
]
}
]
}
]
},
{
"name": "Telephony",
"children": [
{
"name": "Mobile Phones",
"children": [{ "name": "Smartphones", "children": [] }]
}
]
}
]
}
]
}
67 changes: 67 additions & 0 deletions src/components/lib/IndentedTree.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script>
import * as Plot from "@observablehq/plot";
import flare from "../data/tree.json";

let div;
// let data = d3.ticks(-2, 2, 200).map(Math.sin);

// function onMousemove(event) {
// const [x, y] = d3.pointer(event);
// data = data.slice(-200).concat(Math.atan2(x, y));
// }

function indent() {
return (root) => {
root.eachBefore((node, i) => {
node.y = node.depth;
node.x = i;
});
};
}

$: {
// div?.firstChild?.remove(); // remove old chart, if any
// div?.append(Plot.lineY(data).plot({ grid: true })); // add the new chart
// div?.append(
// Plot.plot({
// axis: null,
// margin: 10,
// marginLeft: 100,
// marginRight: 160,
// width: 800,
// height: 300,
// marks: [
// Plot.tree(d3.hierarchy(flare).leaves(), {
// path: (node) =>
// node
// .ancestors()
// .reverse()
// .map(({ data: { name } }) => name)
// .join("|"),
// delimiter: "|",
// }),
// ],
// })
// );
div?.append(
Plot.plot({
axis: null,
inset: 10,
insetRight: 120,
round: true,
width: 200,
height: 600,
marks: Plot.tree(flare, {
path: "name",
delimiter: "/",
treeLayout: indent,
strokeWidth: 1,
curve: "step-before",
textStroke: "none",
}),
})
);
}
</script>

<div bind:this={div} role="img"></div>
16 changes: 16 additions & 0 deletions src/components/lib/TechNode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
type TechNode = {
name: string;
};
export let tech: TechNode;
</script>

<div
on:click={() => alert(`You clicked on ${tech.name}`)}
on:keydown={() => alert(`You clicked on ${tech.name}`)}
class="bg-blue-200 p-3 m-2 cursor-pointer"
role="button"
tabindex="0"
>
{tech.name}
</div>
45 changes: 45 additions & 0 deletions src/components/lib/TechnologyTree.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script>
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import * as d3 from 'd3';
export let technologies;

const svgNodes = writable([]);
const svgLinks = writable([]);
const xOffset = 300; // Adjust for vertical layout
const yOffset = 20; // Increase y-offset for visibility
const nodeDistance = 100; // Adjust vertical distance between nodes

function updateDisplayNodes() {
const hierarchyData = d3.hierarchy(technologies);
const treeLayout = d3.tree().size([600, 400]).nodeSize([nodeDistance, 40]); // Adjust for vertical layout
treeLayout(hierarchyData);

const nodes = hierarchyData.descendants().map(node => ({
...node, x: node.x + xOffset, y: node.y + yOffset
}));

const links = hierarchyData.links().map(link => ({
source: { ...link.source, x: link.source.x + xOffset, y: link.source.y + yOffset },
target: { ...link.target, x: link.target.x + xOffset, y: link.target.y + yOffset }
}));

svgNodes.set(nodes);
svgLinks.set(links);
}

onMount(() => {
updateDisplayNodes();
});
</script>

<svg width="800" height="300" style="border: 1px solid black;">
{#each $svgLinks as link}
<line x1={link.source.x} y1={link.source.y} x2={link.target.x} y2={link.target.y} stroke="black" stroke-linejoin="round" />
{/each}

{#each $svgNodes as node}
<circle cx={node.x} cy={node.y} r="5" fill="blue" />
<text x={node.x + 8} y={node.y + 3} dy=".35em" class="font-thin text-sm">{node.data.name}</text>
{/each}
</svg>
34 changes: 34 additions & 0 deletions src/components/lib/TidyTree.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script>
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import flare from "../data/treePlot.json";

let div;

$: {
div?.append(
Plot.plot({
axis: null,
margin: 10,
marginLeft: 100,
marginRight: 160,
width: 800,
height: 300,
marks: [
Plot.tree(d3.hierarchy(flare).leaves(), {
path: (node) =>
node
.ancestors()
.reverse()
.map(({ data: { name } }) => name)
.join("|"),
delimiter: "|",
}),
],
})
);

}
</script>

<div bind:this={div} role="img"></div>
52 changes: 52 additions & 0 deletions src/content/project/2024-02-13-technology-tree.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Technology Tree
description: Visualized Technology Tree, templated for easy customization, with the help of ChatGPT.
createdDate: "2024-02-13"
coverImage: "../../images/tech-tree.png"
imageAlt: "Screenshot of tech tree"
---

import TechnologyTree from "../../components/lib/TechnologyTree.svelte";
import technologies from "../../components/data/treePlot.json";

The goal of this project was to explore video game technology trees. I wanted an easy way to
generate a visualization without too much hassle.

My end goal is to have it clickable so that as I progress through my game, I can have a duplicate
tree on this side that I can play with and to see what I need to do to unlock the next leaf,
as well as review its benefits.

<div class="not-prose">
<TechnologyTree client:load technologies={technologies} />
</div>

In my first attempt building a technology tree, I used the base example of communications technologies.
This was generated from ChatGPT where I prompted it to create one using some technologies I'm familiar with.
Ultimately, I need to have it in Svelte, and if I could utilize d3.js, I would. It falls short ultimately because
it feels like the customizations not where I want it to be, and the updates I want to make appear challenging.

import TidyTree from "../../components/lib/TidyTree.svelte";

<TidyTree client:load />

I took a step back and reviewed the code ChatGPT came up with. While it was spot on what it was supposed to build,
the way it went about it appeared wrong. If you've explored observable's collection of [d3.hierarchy examples](https://observablehq.com/collection/@d3/d3-hierarchy),
you'll find a gallery of examples that are similar to where I want to build. So I stepped back and instead of going
full d3, I used their Plot library. This is their tidy tree example, after massaging the data to fit its form.


import IndentedTree from "../../components/lib/IndentedTree.svelte";

<IndentedTree client:load />

In Plot's [tree mark docs](https://observablehq.com/plot/marks/tree), there was an indented tree that I really liked.
I also learned how to deliminate the data to fit the form better, and make it a lot easier to generate.
I've been thinking quite a lot about graphs and trees because I think it's yet another way to create an exploratory learning environment
where others can click through the parts that most interest them. And the path to get there is of least resistance.
It's like rock climbers finding their path up to the top, and other climbers often traverse in similar or different ways.

### Future Endeavors

This project isn't complete. I want to make it clickable, and be able to generate a lot of data for someone to explore
the tree slowly, and while you've highlighted that item, it will appear to be new content the viewer can review.
I'm not sure how the content works as I update the visualization, but I'll figure it out.
Binary file added src/images/tech-tree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.