<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Network Graphs in Hugo on brege.org</title>
    <link>https://brege.org/series/network-graphs-in-hugo/</link>
    <description>Recent content in Network Graphs in Hugo on brege.org</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>Copyright (c) 2016-2026 Wyatt Brege</copyright>
    <lastBuildDate>Sun, 12 Apr 2026 21:45:09 -0400</lastBuildDate>
    <atom:link href="https://brege.org/series/network-graphs-in-hugo/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Flavor Network</title>
      <link>https://brege.org/post/the-flavor-network/</link>
      <pubDate>Wed, 04 Jan 2023 04:04:49 -0500</pubDate>
      <guid>https://brege.org/post/the-flavor-network/</guid>
      <description>This tool allows you to explore the flavor network, a social graph for flavor profiles. The network is based on the &lt;a href=&#34;https://karenandandrew.com/books/the-flavor-bible/&#34;&gt;&lt;em&gt;Flavor Bible&lt;/em&gt;&lt;/a&gt; and soon the companion book &lt;a href=&#34;https://karenandandrew.com/books/what-to-drink-with-what-you-eat/&#34;&gt;&lt;em&gt;What to Drink with What You Eat&lt;/em&gt;&lt;/a&gt;.</description>
      <content:encoded><![CDATA[




<link rel="stylesheet" href="/css/network.css">

<div id="network-title">
  <div id="recipe-link"></div>
</div>
<div id="network"
  data-nodes-path=/data/flavor/nodes.json 
  data-edges-path=/data/flavor/edges.json
  data-sim-path=/data/flavor/similarity.json
></div>
<div id="network-settings">
  <div id="physics-toggle">
    <label for="physics">
      dynamics
    </label>
    <input type="checkbox" id="physics" checked>
  </div>
  <div id="scroll-toggle">
    <label for="click-to-use">
      zoom lock
    </label>
    <input type="checkbox" id="click-to-use" checked>
  </div>
  <div id="lenses-dropdown">
    <label for="lenses" title="used to see flavor combinations by 'social circle' or 'besties'">
      lens:
    </label>
    <select id="lenses">
      <option value="similarity" title="'friends list' - see ingredients that have similar overall 'friendships' as my recipe items" selected>
        similarity
      </option>
      <option value="affinity" title="'besties' - see ingredients that are 'best friends' with my recipe items">
        affinity
      </option>
      <option value="hybrid" title="uses a mix of 'friendships' and 'besties' to see flavor combinations">
        hybrid
      </option>
    </select>
  </div>
</div>

<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<script src="/js/flavor-network.js"></script>




<link rel="stylesheet" href="/css/search-bar.css">
<div id="searchbox">
  <div id="search-form" data-search-path=/data/flavor/nodes.json>
    <input id="search-input" autofocus placeholder="Search.." aria-label="search" type="search" autocomplete="off">
  </div>
  <div id="search-results-container" aria-label="search results"></div>
</div>
<script src="/js/search-plots.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.4.6/fuse.min.js" ></script>


<p>This tool allows you to explore the flavor network, a social graph for flavor profiles.
The network is based on the
<a href="https://karenandandrew.com/books/the-flavor-bible/"><em>Flavor Bible</em></a> and soon the companion book
<a href="https://karenandandrew.com/books/what-to-drink-with-what-you-eat/"><em>What to Drink with What You Eat</em></a>.</p>
<p>Search for an ingredient you like, and the graph will refine to give you a web of ingredients that share highly similar flavor profiles.
Then, click on a new ingredient in the network to add it to your recipe above the search box (or to remove it).
Clicking on a recipe item or a node has the same effect.
Search is not sorted by the flavor metric, it is instead sorted <a href="https://fusejs.io/">lexically</a>.</p>
<p>In this way, you can start building out recipes, menu items and tastings from a consensus of flavor combinations.</p>
<h2 id="overview">Overview</h2>
<p>What you are seeing:</p>
<ul>
<li>the nodes with color are your recipe ingredients</li>
<li>the suggested ingredients are determined by <a href="https://en.wikipedia.org/wiki/Jaccard_index">Jaccard <strong>similarity</strong></a> (default) or by one of the other options in the &lsquo;<strong>lens</strong>&rsquo; dropdown</li>
<li>if you choose the <strong>hybrid</strong> option, the suggested ingredients are fiducially split between:
<ul>
<li>the most similar ingredients in the flavor metric (<strong>similarity</strong>)</li>
<li>the most similar ingredients by text ranking (<strong>affinity</strong>)</li>
</ul>
</li>
<li>the edges from one ingredient to another are weighted by a consensus of chef and expert opinion <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></li>
<li>if your ingredient is missing, it was likely missing in the book (<em>quinoa</em>) or was pruned because its mentions were too sparse <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
</ul>
<p>If a node is present without an edge, it means that the ingredient has a very good similarity with your recipe, but wasn&rsquo;t mentioned (connected) in its book-entry literally.
Reconstructing &lsquo;ghost&rsquo; entries and connections by training a model with listed affinities is one of the ultimate goals of this project.</p>
<p>The amount of suggestions gradually decreases as you add more ingredients to your recipe.
This is for performance reasons, as with the physics simulation disabling itself at destabilization.
When that happens, maybe you discovered a flavor affinity.</p>
<p>I have included autogenerated links from your recipe basket to a few popular recipe resources above the network graph, including a database for cocktail mixing. <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<h2 id="why-and-how">Why and how</h2>
<p>Understanding why I chose this text for the dataset is probably already apparent to its readers, but the key thing to take away is that the authors did a fine job formatting something computer readable and human usable&ndash;a rare feat!
Most importantly, it is aggregated from <em>chefs</em>, from real humans in kitchens doing what works, what&rsquo;s delicious, and what&rsquo;s in season.
Recipe API&rsquo;s don&rsquo;t have this kind of granularity, many rely too heavily on user data to seed recommendations.
To my knowledge, this is the only dataset of this kind.</p>
<p>Technical tools only involve <a href="https://visjs.org/">vis.js</a> for visualization and <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup</a> for parsing.
The data is scraped from the <em>Flavor Bible</em>, and the similarity matrix is calculated using <a href="https://en.wikipedia.org/wiki/Jaccard_index">Jaccard similarity</a> for <a href="https://en.wikipedia.org/wiki/Pairwise_comparison">pairwise comparisons</a>.
I am working on cleaning up the initial data with some mix of modern techniques with some concoction of
<a href="https://www.nltk.org/">nltk</a>,
<a href="https://github.com/seatgeek/fuzzywuzzy">fuzzywuzzy</a>
and/or
<a href="https://huggingface.co/docs/transformers/model_doc/bert">Bert</a>.
The current form was done entirely with regexp/bs4 parsing.
The suggested nodes can be improved by using a weighted Jaccard probability distribution (<a href="https://arxiv.org/abs/1809.04052">arXiv</a>).
Source code for this part of the calculation (the text → dataset chain) is <a href="https://github.com/brege/flavor-project">available on GitHub</a>.</p>
<h2 id="inspiration">Inspiration</h2>
<p>In 2019, I was helping fellow chefs come up with new specials.
At this point in time, we were rolling about four-ten new specials as a team every week,
ranging from brunch, cocktails, lunch, football apps and our highly anticipated farm-to-fork pop-up dinners.
But sometimes you just get plain stuck.
A good trick, at least for creativity, is to set rules so you have some boundaries to push.
But if you are going to set rules, they should at least solve a few things:</p>
<ol>
<li>do something new</li>
<li>use something old</li>
<li>feature three things in season</li>
</ol>
<p>I hate having extra stuff around, but I love new stuff coming in, yet I don&rsquo;t like wasting things, but then I actually look forward to doing inventory.  Ah, Schrodinger&rsquo;s cook.</p>
<p>Specials:  we would work out new ideas together over the prep table.
Sometimes ideas required working things out on paper, usually butcher&rsquo;s,
and occoasionally crude graphs of our plate setups evolved.
These were sketches of sauce and protein layouts, heavy edges between ideas if their pairing &lsquo;sang&rsquo;, then as a guide hanging from the ticket rail on the night a feature debuted.</p>
<p>Karen and Andrew&rsquo;s book was gifted to me later that year, and it changed my game.
It finally put in words a mental ranking of flavor profiles based on ingredient query.
I had a good resource that gave answers, and especially new ideas, quickly.
And it was thorough enough to trust.</p>
<p>This method was so helpful, I started dreaming of a computer tool to help me sketch out this process. I remembered a reddit
<a href="https://www.reddit.com/r/datasets/comments/3bxlg7/i_have_every_publicly_available_reddit_comment/">post</a>
that
<a href="https://www.reddit.com/r/dataisbeautiful/comments/ae88pk/interactive_visualization_of_related_subreddits/">spurred others</a>
to lay out some of the underlying ideas here: overlapping communities :: compatible flavors.</p>
<h2 id="broader-thoughts">Broader thoughts</h2>
<p>I believe the impact of mathematical concepts to the broader culinary scope to be a major upgrade in our thoughtfulness about food.
To extend its application, in creativity and clarity, not abused in statistics to pressure a sale and disable the <em>creative mind</em>.
While I do see how a tool like this could provide immense practical application in the distribution world, my focus here is to empower chefs, bartenders, brewers, baristas, and sommeliers to create new things.</p>
<p>When it comes to tools available to chefs,
compared to musicians, writers, and artists,
chef&rsquo;s are unfortunately at a disadvantage creatively.
Yes, we have recipes, but those are instructions, and do little to help us build on <a href="https://ruhlman.com/ruhlmans-books/">ratio</a> or <a href="https://www.saltfatacidheat.com/">balance</a>.
What might be more helpful, I think, is a playground for putting new food ideas together.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>In the book, the weight of the pairing is given by the emphasis of the text:</p>
<ul>
<li>normal text means mentioned by at least one expert</li>
<li><strong>bold</strong> is recommended by many experts</li>
<li><strong>BOLD CAPS</strong> is highly recommended</li>
<li>*<strong>BOLD CAPS</strong> is the &ldquo;Holy Grail&rdquo; of pairings</li>
</ul>
<p>If the ingredient is not mentioned, it is given no weight (or edge) but it does not mean a flavor pairing doesn&rsquo;t exist.
This is part of the purpose of this tool! Lastly, there are a few dozen mentions of &ldquo;Avoid&rdquo;, and should be thought of as opposite charges.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>If you encounter a bug, please feel free to contact me by <a href="mailto:wyatt@brege.org">email</a>
or open an issue on
<a href="https://github.com/brege/flavor-project/issues">GitHub</a>!&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>When <em>What to Drink</em> has been parsed and merged with the network, the latter link in the recipe site list should become much more robust. How fun!&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Les Miserables</title>
      <link>https://brege.org/post/les-miserables-graph-search/</link>
      <pubDate>Sat, 24 Dec 2022 05:30:47 -0500</pubDate>
      <guid>https://brege.org/post/les-miserables-graph-search/</guid>
      <description>A network graph of character connections from one of my favorite books
and authors of all time, Victor Hugo&amp;rsquo;s Les Miserables.</description>
      <content:encoded><![CDATA[<p><em>Les Miserables is one of my favorite books.  I read most of the original translation on a train ride to Portland, OR from Chicago, IL back in 2008 and enjoyed the remainder on the return trip back East.  It taught me compassion: when Valjean places the coin in Cosette&rsquo;s shoe.  Father Christmas always misses her.  There was an earlier passage of a man stepping on a coin in front of her, while she swept dressed in rags.</em></p>
<p>The graph may take a moment to load.</p>
<p>



<style>
  #network { height: 60vh; }   
</style>

<div id="network" data-nodes-path=data/nodes.json data-edges-path=data/edges.json></div>

<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<script src="js/lesmis-network.js"></script>



<link rel="stylesheet" href="/css/search-bar.css">
<div id="searchbox">
  <div id="search-form" data-search-path=data/nodes.json>
    <input id="search-input" autofocus placeholder="Search.." aria-label="search" type="search" autocomplete="off">
  </div>
  <div id="search-results-container" aria-label="search results"></div>
</div>
<script src="/js/search-plots.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.4.6/fuse.min.js" ></script>

</p>
<p>The search bar is the major addition to the graphing methods.
Nodes can be clicked and added to a subgraph builder.
You can continue to search for new node members in the search bar
(which has a rudimentary autofill that&rsquo;s a straight json query)
and clicking on them will add them to the builder.
Simultaneously, the graph will reduce to a graph containing only
all nodes with edges linked to nodes in the builder.</p>
<p>Items can be removed from the builder either by clicking the little builder tabs or re-clicking the node.  Clearing the builder bar completely will redraw the whole graph.</p>
<p>Testing and development was done on the mini pesto data set I made for <a href="/post/what-is-pesto/">What is Pesto?</a>.  Recipe builder coming soon(!)</p>
<p>Please email me at <a href="mailto:wyatt@brege.org">wyatt@brege.org</a> with any questions.</p>
<p>Dataset can be found here:</p>
<ul>
<li><a href="data/nodes.json"><code>nodes.json</code></a></li>
<li><a href="data/edges.json"><code>edges.json</code></a></li>
</ul>
<blockquote>
<p>Lingering annoyances:</p>
<ul>
<li>Slow</li>
<li>Javascript needs clean up</li>
<li>I have great fear running this on my 700x3000 dataset..</li>
</ul>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Network Graphs with Images</title>
      <link>https://brege.org/post/network-graphs-with-images/</link>
      <pubDate>Wed, 21 Dec 2022 02:15:04 -0500</pubDate>
      <guid>https://brege.org/post/network-graphs-with-images/</guid>
      <description>A followup to the Network Graphs in Hugo post, this time with avatars for
the nodes.</description>
      <content:encoded><![CDATA[<p>This is a follow-up to the previous post <a href="/post/network-graphs-in-hugo/">Network Graphs in Hugo</a>.
I&rsquo;m feeling fruity.  These aren&rsquo;t <em>all</em> tree fruits, but a few clusters organized by tree grafting compatibility.</p>




<style>
  #mynetwork {
    background-color: #EFEBE9;  
    border-radius: 10px;
    border: 1px solid #cccccc;
    margin: 5px 0 40px 0;
  }
</style>

<div id="mynetwork" data-nodes-path=data/nodes.json data-edges-path=data/edges.json></div>

<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="js/fruit-network.js"></script>

<ol>
<li>
<p>Data for the network is stored in two separate JSON files in this page bundle:</p>
<ul>
<li><a href="data/nodes.json"><code>nodes.json</code></a></li>
<li><a href="data/edges.json"><code>edges.json</code></a></li>
</ul>
</li>
<li>
<p>The shortcode and post-local javascript work together:</p>
<ul>
<li><code>fruit-network.html</code></li>
<li><a href="js/fruit-network.js"><code>fruit-network.js</code></a>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{ $nodesPath := .Get &#34;nodesPath&#34; }}
</span></span><span class="line"><span class="cl">{{ $edgesPath := .Get &#34;edgesPath&#34; }}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">#</span><span class="nn">mynetwork</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="mh">#f5f5f5</span><span class="p">;</span> <span class="c">/* a medium gray color */</span>
</span></span><span class="line"><span class="cl">    <span class="k">border-radius</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#cccccc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">margin</span><span class="p">:</span> <span class="mi">5</span><span class="kt">px</span> <span class="mi">0</span> <span class="mi">40</span><span class="kt">px</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;mynetwork&#34;</span> <span class="na">data-nodes-path</span><span class="o">=</span><span class="s">{{</span> <span class="err">$</span><span class="na">nodesPath</span> <span class="err">}}</span> <span class="na">data-edges-path</span><span class="o">=</span><span class="s">{{</span> <span class="err">$</span><span class="na">edgesPath</span> <span class="err">}}</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://code.jquery.com/jquery-3.6.0.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;js/fruit-network.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div></li>
</ul>
</li>
</ol>
<p>This will provide network graph physics where the nodes are images (all sourced from <a href="https://www.wikipedia.org/">Wikipedia</a>. Hugo template for completeness:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{&lt; fruit-network nodesPath=&#34;data/nodes.json&#34; edgesPath=&#34;data/edges.json&#34; scriptPath=&#34;js/fruit-network.js&#34; &gt;}}
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Network Graphs in Hugo</title>
      <link>https://brege.org/post/network-graphs-in-hugo/</link>
      <pubDate>Fri, 09 Dec 2022 23:02:42 -0500</pubDate>
      <guid>https://brege.org/post/network-graphs-in-hugo/</guid>
      <description>First crack at making a simple toy network graph in Hugo.</description>
      <content:encoded><![CDATA[<p>This is a simple toy to see how a network graph can be added in a Hugo article.  I&rsquo;ll be testing new features on it as I learn new things.</p>




<div id="mynetwork" data-nodes-path="data/nodes.json" data-edges-path="data/edges.json">
  <script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
  <script src="js/toy-network.js"></script>
</div>

<p>Relative to the root of the Hugo website directory, here&rsquo;s some basic files to make this interactive.
Note that The JSON data and CSS is added inline here to make the scope of this tutorial focus on Hugo-specific structures.</p>
<ol>
<li>
<p>The javascript file lives in this page bundle:</p>
<ul>
<li><a href="js/toy-network.js"><code>toy-network.js</code></a></li>
</ul>
</li>
<li>
<p>This file accesses data for the nodes and edges from two JSON files in this page bundle:</p>
<ul>
<li><a href="data/nodes.json"><code>nodes.json</code></a></li>
<li><a href="data/edges.json"><code>edges.json</code></a></li>
</ul>
</li>
<li>
<p>In the shortcodes directory <code>/layouts/shortcodes/</code>:</p>
<ul>
<li><code>toy-network.html</code>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;mynetwork&#34;</span> <span class="na">data-nodes-path</span><span class="o">=</span><span class="s">&#34;data/nodes.json&#34;</span> <span class="na">data-edges-path</span><span class="o">=</span><span class="s">&#34;data/edges.json&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;js/toy-network.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div></li>
</ul>
</li>
<li>
<p>Do the normal way of making a post in Hugo, but invoke the shortcode within the body of your markdown:</p>
<ul>
<li><code>index.md</code>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{&lt; toy-network nodesPath=&#34;data/nodes.json&#34; edgesPath=&#34;data/edges.json&#34; scriptPath=&#34;js/toy-network.js&#34; &gt;}}
</span></span></code></pre></div></li>
</ul>
</li>
</ol>
<p>This will provide the simple network graph above.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
