From a490514079e58f1e2c25901ebaca6c21c0961b25 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Tue, 15 Mar 2022 17:10:00 -0700 Subject: [PATCH] Add a graph visualization page based on the analyze script --- analyze.rb | 8 +- content/blog/_index.md | 5 + content/graph.html | 22 + static/graph/graph.gen.js | 826 ++++++++++++++++++++++++++++++++++++++ static/graph/graph.js | 53 +++ 5 files changed, 913 insertions(+), 1 deletion(-) create mode 100644 content/graph.html create mode 100644 static/graph/graph.gen.js create mode 100644 static/graph/graph.js diff --git a/analyze.rb b/analyze.rb index 9c36ac5..87fbc0e 100644 --- a/analyze.rb +++ b/analyze.rb @@ -40,11 +40,14 @@ files.each do |file| name = file tags = [] group = 1 + draft = false value = File.size(file) url = file.gsub(/^content/, "https://danilafe.com").delete_suffix("/index.md").delete_suffix(".md") File.readlines(file).each do |l| if l =~ /^title: (.+)$/ name = $~[1].delete_prefix('"').delete_suffix('"') + elsif l =~ /^draft: true$/ + draft = true elsif l =~ /^tags: (.+)$/ tags = $~[1].delete_prefix("[").delete_suffix("]").split(/,\s?/).map { |it| it.gsub('"', '') } if tags.include? "Compilers" @@ -60,7 +63,8 @@ files.each do |file| 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 edges = [] @@ -72,6 +76,8 @@ files.each do |file1| # end next unless frefs = refs[file1] frefs.each do |ref| + next unless data[file1] + next unless data[ref] edges << { :from => data[file1][:id], :to => data[ref][:id] } end end diff --git a/content/blog/_index.md b/content/blog/_index.md index 6c432cb..18d45bf 100644 --- a/content/blog/_index.md +++ b/content/blog/_index.md @@ -1,3 +1,8 @@ --- 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. diff --git a/content/graph.html b/content/graph.html new file mode 100644 index 0000000..00cc6f1 --- /dev/null +++ b/content/graph.html @@ -0,0 +1,22 @@ +--- +title: "Content Graph" +--- + + + +

+Here you can see a visualization of the posts on this article using Vis.js. +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. +

+
+
diff --git a/static/graph/graph.gen.js b/static/graph/graph.gen.js new file mode 100644 index 0000000..bd3f5ab --- /dev/null +++ b/static/graph/graph.gen.js @@ -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 + } +]; diff --git a/static/graph/graph.js b/static/graph/graph.js new file mode 100644 index 0000000..3bc3a1b --- /dev/null +++ b/static/graph/graph.js @@ -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 }); +// } +// });