Add a graph visualization page based on the analyze script

This commit is contained in:
Danila Fedorin 2022-03-15 17:10:00 -07:00
parent a658286776
commit a490514079
5 changed files with 913 additions and 1 deletions

View File

@ -40,11 +40,14 @@ files.each do |file|
name = file name = file
tags = [] tags = []
group = 1 group = 1
draft = false
value = File.size(file) value = File.size(file)
url = file.gsub(/^content/, "https://danilafe.com").delete_suffix("/index.md").delete_suffix(".md") url = file.gsub(/^content/, "https://danilafe.com").delete_suffix("/index.md").delete_suffix(".md")
File.readlines(file).each do |l| File.readlines(file).each do |l|
if l =~ /^title: (.+)$/ if l =~ /^title: (.+)$/
name = $~[1].delete_prefix('"').delete_suffix('"') name = $~[1].delete_prefix('"').delete_suffix('"')
elsif l =~ /^draft: true$/
draft = true
elsif l =~ /^tags: (.+)$/ elsif l =~ /^tags: (.+)$/
tags = $~[1].delete_prefix("[").delete_suffix("]").split(/,\s?/).map { |it| it.gsub('"', '') } tags = $~[1].delete_prefix("[").delete_suffix("]").split(/,\s?/).map { |it| it.gsub('"', '') }
if tags.include? "Compilers" if tags.include? "Compilers"
@ -60,7 +63,8 @@ files.each do |file|
end end
end end
end end
data[file] = { :id => id, :label => name, :group => group, :tags => tags, :url => url, :value => value } next if draft
data[file] = { :id => id, :name => name, :group => group, :tags => tags, :url => url, :value => value }
end end
edges = [] edges = []
@ -72,6 +76,8 @@ files.each do |file1|
# end # end
next unless frefs = refs[file1] next unless frefs = refs[file1]
frefs.each do |ref| frefs.each do |ref|
next unless data[file1]
next unless data[ref]
edges << { :from => data[file1][:id], :to => data[ref][:id] } edges << { :from => data[file1][:id], :to => data[ref][:id] }
end end
end end

View File

@ -1,3 +1,8 @@
--- ---
title: "Posts" title: "Posts"
--- ---
On this page you can see every article on this blog in reverse
chronological order. If you like pretty colors and patterns,
you can also see a visualization of all the articles and links between them
on the [content graph]({{< relref "graph" >}}) page. If you have
something specific in mind, you can also try using the [search]({{< relref "search" >}}) page.

22
content/graph.html Normal file
View File

@ -0,0 +1,22 @@
---
title: "Content Graph"
---
<script src="//unpkg.com/vis-network@9.1.0/dist/vis-network.min.js"></script>
<script type="module" src="/graph/graph.js"></script>
<style>
#graph-container {
width: 100%;
height: 50vh;
border-radius: 0.2rem;
border: .075rem solid #bfbfbf
}
</style>
<p>
Here you can see a visualization of the posts on this article using <a href="https://visjs.org/">Vis.js</a>.
In the below graph, each node is an article, and each edge between nodes is a reference from
one article to another. Each article is sized to be roughly proportional to its word count (file size
is used as a quick metric for this purpose). You can hover over a node to see the title of the article
it represents, and double-click to go to that article.
</p>
<div id="graph-container">
</div>

826
static/graph/graph.gen.js Normal file
View File

@ -0,0 +1,826 @@
export const nodes = [
{
"id": 1,
"name": "Advent of Code in Coq - Day 1",
"group": 3,
"tags": [
"Advent of Code",
"Coq"
],
"url": "https://danilafe.com/blog/00_aoc_coq",
"value": 16279
},
{
"id": 2,
"name": "Compiling a Functional Language Using C++, Part 0 - Intro",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/00_compiler_intro",
"value": 7087
},
{
"id": 3,
"name": "Compiling a Functional Language Using C++, Part 1 - Tokenizing",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/01_compiler_tokenizing",
"value": 10885
},
{
"id": 4,
"name": "Compiling a Functional Language Using C++, Part 2 - Parsing",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/02_compiler_parsing",
"value": 16450
},
{
"id": 5,
"name": "Compiling a Functional Language Using C++, Part 3 - Type Checking",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/03_compiler_typechecking",
"value": 27055
},
{
"id": 6,
"name": "Compiling a Functional Language Using C++, Part 4 - Small Improvements",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/04_compiler_improvements",
"value": 10003
},
{
"id": 7,
"name": "Compiling a Functional Language Using C++, Part 5 - Execution",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/05_compiler_execution",
"value": 28832
},
{
"id": 8,
"name": "Compiling a Functional Language Using C++, Part 6 - Compilation",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/06_compiler_compilation",
"value": 22584
},
{
"id": 9,
"name": "Compiling a Functional Language Using C++, Part 7 - Runtime",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/07_compiler_runtime",
"value": 7410
},
{
"id": 10,
"name": "Compiling a Functional Language Using C++, Part 8 - LLVM",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/08_compiler_llvm",
"value": 23607
},
{
"id": 11,
"name": "Compiling a Functional Language Using C++, Part 9 - Garbage Collection",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/09_compiler_garbage_collection",
"value": 25989
},
{
"id": 12,
"name": "Compiling a Functional Language Using C++, Part 10 - Polymorphism",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/10_compiler_polymorphism",
"value": 42606
},
{
"id": 13,
"name": "Compiling a Functional Language Using C++, Part 11 - Polymorphic Data Types",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/11_compiler_polymorphic_data_types",
"value": 19586
},
{
"id": 14,
"name": "Compiling a Functional Language Using C++, Part 12 - Let/In and Lambdas",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/12_compiler_let_in_lambda",
"value": 49622
},
{
"id": 15,
"name": "Compiling a Functional Language Using C++, Part 13 - Cleanup",
"group": 2,
"tags": [
"C and C++",
"Functional Languages",
"Compilers"
],
"url": "https://danilafe.com/blog/13_compiler_cleanup",
"value": 44195
},
{
"id": 16,
"name": "A Language for an Assignment - Homework 1",
"group": 4,
"tags": [
"Haskell",
"Python",
"Algorithms",
"Programming Languages"
],
"url": "https://danilafe.com/blog/00_cs325_languages_hw1",
"value": 21326
},
{
"id": 17,
"name": "Advent of Code in Coq - Day 8",
"group": 3,
"tags": [
"Advent of Code",
"Coq"
],
"url": "https://danilafe.com/blog/01_aoc_coq",
"value": 46511
},
{
"id": 18,
"name": "A Language for an Assignment - Homework 2",
"group": 4,
"tags": [
"Haskell",
"Python",
"Algorithms",
"Programming Languages"
],
"url": "https://danilafe.com/blog/01_cs325_languages_hw2",
"value": 8949
},
{
"id": 19,
"name": "Learning Emulation, Part 1",
"group": 1,
"tags": [
"C and C++",
"Emulation"
],
"url": "https://danilafe.com/blog/01_learning_emulation",
"value": 3387
},
{
"id": 20,
"name": "A Language for an Assignment - Homework 3",
"group": 4,
"tags": [
"Haskell",
"Python",
"Algorithms",
"Programming Languages"
],
"url": "https://danilafe.com/blog/02_cs325_languages_hw3",
"value": 19899
},
{
"id": 21,
"name": "Learning Emulation, Part 2",
"group": 1,
"tags": [
"C and C++",
"Emulation"
],
"url": "https://danilafe.com/blog/02_learning_emulation",
"value": 3965
},
{
"id": 22,
"name": "Learning Emulation, Part 2.5 - Implementation",
"group": 1,
"tags": [
"C and C++",
"Emulation"
],
"url": "https://danilafe.com/blog/03_learning_emulation",
"value": 4881
},
{
"id": 23,
"name": "Posts",
"group": 1,
"tags": [
],
"url": "https://danilafe.com/blog/_index",
"value": 23
},
{
"id": 24,
"name": "Rendering Mathematics On The Back End",
"group": 1,
"tags": [
"Website",
"Nix",
"Ruby",
"KaTeX"
],
"url": "https://danilafe.com/blog/backend_math_rendering",
"value": 13994
},
{
"id": 25,
"name": "Math Rendering is Wrong",
"group": 1,
"tags": [
"Website"
],
"url": "https://danilafe.com/blog/math_rendering_is_wrong",
"value": 10313
},
{
"id": 26,
"name": "Thoughts on Better Explanations",
"group": 1,
"tags": [
"Language Server Protocol"
],
"url": "https://danilafe.com/blog/better_explanations",
"value": 5815
},
{
"id": 28,
"name": "Rendering Mathematics On The Back End",
"group": 1,
"tags": [
"Website",
"Nix",
"Ruby",
"KaTeX"
],
"url": "https://danilafe.com/blog/./backend_math_rendering",
"value": 13994
},
{
"id": 29,
"name": "How Many Values Does a Boolean Have?",
"group": 5,
"tags": [
"Java",
"Haskell",
"C and C++"
],
"url": "https://danilafe.com/blog/boolean_values",
"value": 12955
},
{
"id": 30,
"name": "Pleasant Code Includes with Hugo",
"group": 1,
"tags": [
"Hugo"
],
"url": "https://danilafe.com/blog/codelines",
"value": 11846
},
{
"id": 31,
"name": "Formalizing Dawn in Coq",
"group": 3,
"tags": [
"Coq",
"Dawn",
"Programming Languages"
],
"url": "https://danilafe.com/blog/coq_dawn",
"value": 20615
},
{
"id": 32,
"name": "A Verified Evaluator for the Untyped Concatenative Calculus",
"group": 3,
"tags": [
"Dawn",
"Coq",
"Programming Languages"
],
"url": "https://danilafe.com/blog/coq_dawn_eval",
"value": 31852
},
{
"id": 33,
"name": "Formalizing Dawn in Coq",
"group": 3,
"tags": [
"Coq",
"Dawn",
"Programming Languages"
],
"url": "https://danilafe.com/blog/./coq_dawn",
"value": 20615
},
{
"id": 35,
"name": "Proof of Inductive Palindrome Definition in Coq",
"group": 3,
"tags": [
"Coq"
],
"url": "https://danilafe.com/blog/coq_palindrome",
"value": 8177
},
{
"id": 36,
"name": "Building a Basic Crystal Project with Nix",
"group": 6,
"tags": [
"Crystal",
"Nix"
],
"url": "https://danilafe.com/blog/crystal_nix",
"value": 5981
},
{
"id": 37,
"name": "Building a Crystal Project with Nix, Revisited",
"group": 6,
"tags": [
"Crystal",
"Nix"
],
"url": "https://danilafe.com/blog/crystal_nix_revisited",
"value": 9141
},
{
"id": 38,
"name": "Setting Up Crystal on ARM",
"group": 6,
"tags": [
"Crystal",
"ARM"
],
"url": "https://danilafe.com/blog/crystal_on_arm",
"value": 5414
},
{
"id": 39,
"name": "DELL Is A Horrible Company And You Should Avoid Them At All Costs",
"group": 1,
"tags": [
"Electronics"
],
"url": "https://danilafe.com/blog/dell_is_horrible",
"value": 21686
},
{
"id": 40,
"name": "Haskell Error Checking and Autocompletion With LSP",
"group": 5,
"tags": [
"Haskell",
"Language Server Protocol"
],
"url": "https://danilafe.com/blog/haskell_language_server",
"value": 10468
},
{
"id": 41,
"name": "Using GHC IDE for Haskell Error Checking and Autocompletion",
"group": 5,
"tags": [
"Haskell",
"Language Server Protocol"
],
"url": "https://danilafe.com/blog/haskell_language_server_again",
"value": 4758
},
{
"id": 42,
"name": "Time Traveling In Haskell: How It Works And How To Use It",
"group": 5,
"tags": [
"Haskell"
],
"url": "https://danilafe.com/blog/haskell_lazy_evaluation",
"value": 25280
},
{
"id": 43,
"name": "Approximating Custom Functions in Hugo",
"group": 1,
"tags": [
"Hugo"
],
"url": "https://danilafe.com/blog/hugo_functions",
"value": 4034
},
{
"id": 44,
"name": "Introducing Matrix Highlight",
"group": 1,
"tags": [
"Matrix",
"Project",
"Matrix Highlight"
],
"url": "https://danilafe.com/blog/introducing_highlight",
"value": 7481
},
{
"id": 45,
"name": "Local Development Environment for JOS and CS 444",
"group": 1,
"tags": [
"C and C++",
"OS Dev"
],
"url": "https://danilafe.com/blog/jos_local",
"value": 4839
},
{
"id": 46,
"name": "Lambda Calculus and Church Encoded Integers",
"group": 1,
"tags": [
"Lambda Calculus"
],
"url": "https://danilafe.com/blog/lambda_calculus_integers",
"value": 10351
},
{
"id": 47,
"name": "Digit Sum Patterns and Modular Arithmetic",
"group": 1,
"tags": [
"Ruby",
"Mathematics"
],
"url": "https://danilafe.com/blog/modulo_patterns",
"value": 39663
},
{
"id": 48,
"name": "New Look, New Features!",
"group": 1,
"tags": [
"Website"
],
"url": "https://danilafe.com/blog/new_look",
"value": 727
},
{
"id": 50,
"name": "JavaScript-Free Sidenotes in Hugo",
"group": 1,
"tags": [
"Website",
"Hugo",
"CSS"
],
"url": "https://danilafe.com/blog/sidenotes",
"value": 7762
},
{
"id": 51,
"name": "Creating Recursive Functions in a Stack Based Language",
"group": 4,
"tags": [
"Programming Languages"
],
"url": "https://danilafe.com/blog/stack_recursion",
"value": 15694
},
{
"id": 52,
"name": "A Look Into Starbound's File Formats",
"group": 1,
"tags": [
"Starbound"
],
"url": "https://danilafe.com/blog/starbound",
"value": 11817
},
{
"id": 53,
"name": "Switching to a Static Site Generator",
"group": 1,
"tags": [
"Website"
],
"url": "https://danilafe.com/blog/static_site",
"value": 3402
},
{
"id": 55,
"name": "A Typesafe Representation of an Imperative Language",
"group": 4,
"tags": [
"Idris",
"Programming Languages"
],
"url": "https://danilafe.com/blog/typesafe_imperative_lang",
"value": 20619
},
{
"id": 56,
"name": "Meaningfully Typechecking a Language in Idris",
"group": 4,
"tags": [
"Haskell",
"Idris",
"Programming Languages"
],
"url": "https://danilafe.com/blog/typesafe_interpreter",
"value": 13655
},
{
"id": 57,
"name": "Meaningfully Typechecking a Language in Idris, Revisited",
"group": 4,
"tags": [
"Idris",
"Programming Languages"
],
"url": "https://danilafe.com/blog/typesafe_interpreter_revisited",
"value": 16100
},
{
"id": 58,
"name": "Meaningfully Typechecking a Language in Idris, With Tuples",
"group": 4,
"tags": [
"Idris",
"Programming Languages"
],
"url": "https://danilafe.com/blog/typesafe_interpreter_tuples",
"value": 10235
},
{
"id": 59,
"name": "Type-Safe Event Emitter in TypeScript",
"group": 1,
"tags": [
"TypeScript"
],
"url": "https://danilafe.com/blog/typescript_typesafe_events",
"value": 5589
}
];
export const edges = [
{
"from": 2,
"to": 3
},
{
"from": 2,
"to": 4
},
{
"from": 2,
"to": 5
},
{
"from": 2,
"to": 6
},
{
"from": 2,
"to": 7
},
{
"from": 2,
"to": 8
},
{
"from": 2,
"to": 9
},
{
"from": 2,
"to": 10
},
{
"from": 2,
"to": 11
},
{
"from": 2,
"to": 12
},
{
"from": 2,
"to": 13
},
{
"from": 2,
"to": 14
},
{
"from": 2,
"to": 15
},
{
"from": 3,
"to": 2
},
{
"from": 3,
"to": 4
},
{
"from": 4,
"to": 5
},
{
"from": 5,
"to": 6
},
{
"from": 6,
"to": 7
},
{
"from": 7,
"to": 8
},
{
"from": 8,
"to": 9
},
{
"from": 9,
"to": 10
},
{
"from": 10,
"to": 11
},
{
"from": 11,
"to": 12
},
{
"from": 12,
"to": 10
},
{
"from": 12,
"to": 5
},
{
"from": 12,
"to": 8
},
{
"from": 12,
"to": 13
},
{
"from": 13,
"to": 12
},
{
"from": 13,
"to": 4
},
{
"from": 13,
"to": 14
},
{
"from": 14,
"to": 12
},
{
"from": 14,
"to": 7
},
{
"from": 14,
"to": 8
},
{
"from": 14,
"to": 6
},
{
"from": 14,
"to": 15
},
{
"from": 15,
"to": 14
},
{
"from": 18,
"to": 16
},
{
"from": 21,
"to": 19
},
{
"from": 22,
"to": 19
},
{
"from": 24,
"to": 2
},
{
"from": 24,
"to": 25
},
{
"from": 30,
"to": 2
},
{
"from": 32,
"to": 33
},
{
"from": 37,
"to": 36
},
{
"from": 41,
"to": 40
},
{
"from": 43,
"to": 30
},
{
"from": 50,
"to": 2
},
{
"from": 57,
"to": 56
},
{
"from": 58,
"to": 56
},
{
"from": 58,
"to": 57
}
];

53
static/graph/graph.js Normal file
View File

@ -0,0 +1,53 @@
import { nodes, edges } from "./graph.gen.js";
var container = document.getElementById("graph-container");
var options = {
interaction: {
hover: true
},
nodes: {
shape: "dot",
size: 16,
},
physics: {
forceAtlas2Based: {
gravitationalConstant: -10,
centralGravity: 0.005,
springLength: 230,
springConstant: 0.18,
},
maxVelocity: 146,
solver: "forceAtlas2Based",
timestep: 0.35,
stabilization: { iterations: 150 },
},
};
var nodesDs = new vis.DataSet();
nodesDs.add(nodes);
var edgesDs = new vis.DataSet();
edgesDs.add(edges);
var network = new vis.Network(container, { nodes: nodesDs, edges: edgesDs }, options);
network.on("doubleClick", function (params) {
params.event = "[original event]";
if (params.nodes.length !== 1) return;
window.open(nodesDs.get(params.nodes[0]).url, "_blank")
});
network.on("hoverNode", function (params) {
nodesDs.update({ id: params.node, label: nodesDs.get(params.node).name });
});
network.on("blurNode", function (params) {
nodesDs.update({ id: params.node, label: undefined });
});
// network.on("selectNode", function (params) {
// for (const node of params.nodes) {
// nodesDs.update({ id: node, label: nodesDs.get(node).name });
// }
// });
// network.on("deselectNode", function (params) {
// for (const node of params.previousSelection.nodes) {
// if (params.nodes.some(n => n === node)) continue;
// nodesDs.update({ id: node, label: undefined });
// }
// });