<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tom Larkworthy</title>
    <description>The latest articles on DEV Community by Tom Larkworthy (@tomlarkworthy).</description>
    <link>https://dev.to/tomlarkworthy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F509616%2F4625af55-d1e9-4f8d-9c2e-b3fb39d5bf7a.jpeg</url>
      <title>DEV Community: Tom Larkworthy</title>
      <link>https://dev.to/tomlarkworthy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomlarkworthy"/>
    <language>en</language>
    <item>
      <title>A Custom Domain in 4 lines of code</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Thu, 17 Mar 2022 18:22:20 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/custom-domain-in-4loc-l6f</link>
      <guid>https://dev.to/tomlarkworthy/custom-domain-in-4loc-l6f</guid>
      <description>&lt;p&gt;When clients ask me to add a vanity domain, I usually use their Cloud provider, which is ok but tends to be a little fiddly and not particularly cheap.&lt;/p&gt;

&lt;p&gt;For a private project, I wanted a custom domain ASAP, for free. I think I found my simplest setup using Netlify rewrite rules. The beauty of Netlify is that it can sync with a git repository and so the only native tool you need is git.&lt;/p&gt;

&lt;p&gt;So all I needed to do was commit the following &lt;em&gt;netlify.toml&lt;/em&gt; to a Github and then deploy my custom domain from Github.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"*"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://webcode.run/observablehq.com/@tomlarkworthy/tarot-webserver:splat"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting up Netlify to handle a custom domain is one of the easiest processes, even if you register the domain elsewhere.&lt;/p&gt;

&lt;p&gt;This 1 hour of work the first time you do it and less than 15 minutes of work going forward. Custom domains with SSL could not be easier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover Photo by &lt;a href="https://unsplash.com/@mk__s?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;mk. s&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/golf?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>WEBCode.run Private Endpoints on Observablehq.com Released!</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Tue, 18 Jan 2022 15:27:20 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/webcoderun-private-endpoints-on-observablehqcom-released-8n</link>
      <guid>https://dev.to/tomlarkworthy/webcoderun-private-endpoints-on-observablehqcom-released-8n</guid>
      <description>&lt;h1&gt;
  
  
  &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; Private Endpoints Released
&lt;/h1&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/HqITeIFlRXI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; is growing! We have added our first paid feature which will help support it long term. WEBCode is bootstrapped so this is a big milestone!&lt;/p&gt;

&lt;p&gt;For those that do not know, &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; allows you to run serverless compute workloads from within &lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt; notebooks. Until now, notebooks containing &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; handlers had to be public so that WEBCode's compute layer could read the code. Not anymore!&lt;/p&gt;

&lt;p&gt;We have added a new feature for Observable &lt;a href="https://observablehq.com/@observablehq/getting-started-with-teams"&gt;Team&lt;/a&gt; accounts that allows public access to serverless endpoints while keeping the source code private. We feel this feature best suits commercial teams, and thus, is a perfect fit for a paid tier. Find out more on the &lt;a href="https://webcode.run"&gt;webcode.run website&lt;/a&gt; or contact &lt;a href="//mailto://sales@webcode.run"&gt;sales@webcode.run&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For those that want to use WEBCode to build transparent open source services, they can continue do so at no charge.&lt;/p&gt;

&lt;p&gt;As part of the work in providing a paid tier, we have updated the &lt;a href="https://webcode.run"&gt;website&lt;/a&gt; significantly. You can also email me at &lt;a href="//mailto://tom@webcode.run"&gt;tom@webcode.run&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming soon
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; enables running HTTP services with excellent performance and observability from within a notebook. In the coming months we will demonstrate why this is an important primitive. &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; is a connection technology for something larger, it enables completely self documenting projects to be bundled into a single encapsulated artifact. In the coming months the focus will be on delivering preconfigured artifacts you can one click fork to self-host and customize.&lt;/p&gt;

&lt;p&gt;In our vision of the future, source level customization and self-hosting at the service level is a single click away. This is only possible when the backend and frontend + all the extras (documentation, monitoring) are a single forkable artifact, &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; &lt;em&gt;with&lt;/em&gt; &lt;a href="https://observablehq.com"&gt;Observablehq.com&lt;/a&gt; are able to deliver this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Support us on Product Hunt
&lt;/h3&gt;

&lt;p&gt;We are launching the paid tier and &lt;a href="https://webcode.run"&gt;webcode.run&lt;/a&gt; on Product Hunt. Help us get to #1 with some love &lt;a href="https://www.producthunt.com/posts/webcode-run"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow the newsletter
&lt;/h3&gt;

&lt;p&gt;I am going to start posting webcode.run updates to a &lt;a href="https://webcode.substack.com/"&gt;newsletter&lt;/a&gt;, so this is another option you can stay up to date&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>100 Beautiful and Informative Notebooks of 2021</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Fri, 31 Dec 2021 10:33:08 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/100-beautiful-and-informative-notebooks-of-2021-23lg</link>
      <guid>https://dev.to/tomlarkworthy/100-beautiful-and-informative-notebooks-of-2021-23lg</guid>
      <description>&lt;p&gt;It's the end of 2021, and I would like to reminisce about the amazing work that was produced on Observable over the last year. I have taken all the notebooks that were &lt;a href="https://observablehq.com/trending" rel="noopener noreferrer"&gt;trending&lt;/a&gt; from the &lt;a href="https://twitter.com/trendingnotebo2" rel="noopener noreferrer"&gt;Top Trending Notebook Twitter Bot&lt;/a&gt; and picked 100 good ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt6fql1oaz2awrb87cnj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt6fql1oaz2awrb87cnj.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The broad categories apparent in 2021 were "dataviz, art, mathematics, maps, apps, development, design, tutorials and tools". Every notebook listed here is worth a read, but there is rather a lot so it might take you a few sessions!&lt;/p&gt;

&lt;p&gt;OK, enough chat... here are my favorite notebooks of 2021, sorted into topics...&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataviz
&lt;/h2&gt;

&lt;p&gt;Observable is a digital community for data practitioners. In 2021 an enormous quantity of informative dataviz notebooks were produced, but here is some of the very best. Special shout out to &lt;a href="https://observablehq.com/@karimdouieb" rel="noopener noreferrer"&gt;@karimdouieb&lt;/a&gt; who has been on fire using 3D graphics to visualize large datasets with great effect in works like &lt;a href="https://observablehq.com/@karimdouieb/all-the-passes" rel="noopener noreferrer"&gt;All the passes&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@fil/the-suns-analemma" rel="noopener noreferrer"&gt;The Sun’s analemma&lt;/a&gt; by &lt;a href="https://observablehq.com/@fil" rel="noopener noreferrer"&gt;@fil&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@fil/the-suns-analemma" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fa469dac699631b6c013791f375a8c133e014d567c54884a6e981c38a22fe540b.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you were to look at the sky every   of the year at the exact same hour, where would the Sun be? The diagram below shows its position for each hour of the day, limited by the horizon. Each twisted loop (called an  analemma), is in fact made of   dots, one for each   of the year. The analemma shows by how much solar time is fast or slow compared to clock time, at different periods of the year. Click on the map to set the observer’s location (defaults to Greenwich).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@kerryrodden/equal-area-radial-matrix-of-lgbt-rights" rel="noopener noreferrer"&gt;Equal-Area Radial Matrix of LGBT Rights&lt;/a&gt; by &lt;a href="https://observablehq.com/@kerryrodden" rel="noopener noreferrer"&gt;@kerryrodden&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@kerryrodden/equal-area-radial-matrix-of-lgbt-rights" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fcc9e9c032662b0e948e7fa4a5ea707e1e9f4e5dc220af2a520185da0fbfe04f0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Why use a radial data visualization?, I wrote about some of the pros and cons of choosing a radial layout for time series, categorical, or hierarchical data. I left out matrix/heatmap data, though—so for Pride month, here’s a partial re-creation of a radial matrix visualization I love, The Guardian’s Gay rights in the US, state by state (whose scope covers both sexual orientation and gender identity, despite the title).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@karimdouieb/all-the-passes" rel="noopener noreferrer"&gt;All the passes ⚽️&lt;/a&gt; by &lt;a href="https://observablehq.com/@karimdouieb" rel="noopener noreferrer"&gt;@karimdouieb&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@karimdouieb/all-the-passes" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F1f66644982c5ce9839471e840ac302cd0e5dae81e265704b320fb79923fdcce6.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A visualisation of 882,536 passes from 890 matches played in various major leagues/cups such as the Champion League 1999 FA Women’s Super League 2018 FIFA World Cup 2018, La Liga 2004 – 2020 NWSL 2018 Premier League 2003 – 2004 Women’s World Cup 2019 Data provided by StatsBomb Original inspiration: Alexander Varlamov's blog post Data Dependencies&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/state-of-dataviz-2021" rel="noopener noreferrer"&gt;State of Dataviz&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/state-of-dataviz-2021" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F9e3d8fa41c5437af5ca47367b917509881fbbde85b4fc0b3afef819fcdbe7a6e.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2021 Observations: Data through the lens of the Observable Community One of Observable’s motivating principles is to listen to and learn from its passionate and fast-growing community. Hundreds of our community members generously participated in two research surveys about data analysis and visualization over the past year. Here’s what we learned. Data is exploding, for everyone With the explosive growth in online technologies, services and information, data is overwhelming for all of us.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@ralucanicola/women-in-space" rel="noopener noreferrer"&gt;Women in space 👩🏼‍🚀&lt;/a&gt; by &lt;a href="https://observablehq.com/@ralucanicola" rel="noopener noreferrer"&gt;@ralucanicola&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@ralucanicola/women-in-space" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd9434b4c7ab2409f54356e8032c4abcd7c272dc3ca90c934f58cf0742df48a2d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data I parsed the data from the Wikipedia site List of female spacefarers. I manually enhanced the comments section with information from individual Wikipedia profiles of the astronauts and information from the NASA site. Sky image by Max McKinnon on Unsplash.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visnup/day-9-smoke-basin" rel="noopener noreferrer"&gt;Day 9: Smoke Basin&lt;/a&gt; by &lt;a href="https://observablehq.com/@visnup" rel="noopener noreferrer"&gt;@visnup&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visnup/day-9-smoke-basin" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3fbc343401f2c48ff4e7aa13b4e82da26047e3e1c3ac15b5cd368dfd74fc3e84.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These caves seem to be lava tubes. Parts are even still volcanically active; small hydrothermal vents release smoke into the caves that slowly settles like rain. – Advent of Code 2021, Day 9 Part 2 Find the three largest basins. Starting from a low point (or really, any point in a basin), breadth-first search out from it visiting all its neighbors and keep going, stopping at any 9s. Use a FIFO queue(&lt;a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)&lt;/a&gt; and when it’s exhausted we’re done.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@benjamesdavis/eu-fishing-flow" rel="noopener noreferrer"&gt;EU Fishing Flow&lt;/a&gt; by &lt;a href="https://observablehq.com/@benjamesdavis" rel="noopener noreferrer"&gt;@benjamesdavis&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@benjamesdavis/eu-fishing-flow" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd8189a875cc1c5ec9337d4adee5e346721df4ec2ea2830696a3af9e7338f1a49.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data Helper Functions Scales Dependancies&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@analyzer2004/weather-grid" rel="noopener noreferrer"&gt;What story do two decades of weather data tell you?&lt;/a&gt; by &lt;a href="https://observablehq.com/@analyzer2004" rel="noopener noreferrer"&gt;@analyzer2004&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@analyzer2004/weather-grid" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F4d11846a14d7c9db4f466fb46f22c20df16a0f6c5ff74669780bc1bb6b67beab.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What story do two decades of daily weather data tell you? You are about to find out 😊. As usual, I picked these five major cities across 1000 miles of the US West Coast. The top part of the graph contains two stacked column charts. The left one represents the temperature distribution of the current month, and the right one shows weather condition distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@dbridges/visualizing-seasonal-daylight" rel="noopener noreferrer"&gt;Visualizing Seasonal Daylight&lt;/a&gt; by &lt;a href="https://observablehq.com/@dbridges" rel="noopener noreferrer"&gt;@dbridges&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@dbridges/visualizing-seasonal-daylight" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F1423c51e87dfd3cd1afcd14a0c80830cd0fd3dc1bd1c8edffe8a605e9dc9f053.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone who lives away from the equator knows how the days lengthen and shorten throughout the year. We hear about the long dark polar winters, and the endlessly looping sun of the polar summer. Many people might be able to toss out the answer to these natural phenomena, "because the Earth is tilted," but in my experience far fewer intuitively grasp the geometries involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@fil/how-much-warmer-bbc" rel="noopener noreferrer"&gt;How much warmer? (BBC)&lt;/a&gt; by &lt;a href="https://observablehq.com/@fil" rel="noopener noreferrer"&gt;@fil&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@fil/how-much-warmer-bbc" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fccd72badb737756827b40280db3f31d98f71458579ce4a4184fe9a824d5aae5a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A recreation, for educational purposes, of this beautiful map by the BBC Visual and Data Journalism team (31 July 2019). You can learn more on the original project in this very detailed and complete write-up by the BBC team. Made with D3 (the original visualization uses THREE.js). See also Matt’s version, which uses a phyllotaxis distribution instead of the original’s rectangular grid.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@oliviafvane/votes-for-women-suffragette-collage-maker" rel="noopener noreferrer"&gt;Votes for Women: Suffragette Collage Maker&lt;/a&gt; by &lt;a href="https://observablehq.com/@oliviafvane" rel="noopener noreferrer"&gt;@oliviafvane&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@oliviafvane/votes-for-women-suffragette-collage-maker" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fa26c4a0b1f7bfd25dcb304c72cede83f96172aaf62b2c547335d45e09dc7271f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Submission for Observable's Women's History Month DataViz Contest. Thank you to Jacob Rus for his help optimising the code. Political movements generate their own “stuff”: the posters from the the 2017 Women's March (and the Pussyhat), the Brexit tea towel, the gilets of the Gilets Jaunes. The same is true when women were fighting for the vote in the 20th Century.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/2021-observable-community-recognition/2" rel="noopener noreferrer"&gt;2021 Observable Community Recognition&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/2021-observable-community-recognition/2" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe61e48479a6bff6b34178cbe5e499110de9229b71f9bfca2cb6fc5287ac9573f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observable is built as a space where those who work with data can come together to explore, analyze, discover and communicate with each other. Working with data and making sense of it is challenging, but with the contributions of you, the community, it is easier than ever to work together to make sense of the world with data. Our community inspires data collaborators around the world to collaborate, share, learn, and teach. Every day our community invents new and creative ways to explore and visualize data.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@jenniferdaniel/unicode-emoji-mirror" rel="noopener noreferrer"&gt;The Most Frequently Used Emoji of 2021&lt;/a&gt; by &lt;a href="https://observablehq.com/@jenniferdaniel" rel="noopener noreferrer"&gt;@jenniferdaniel&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@jenniferdaniel/unicode-emoji-mirror" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F14350b8fede0499d46cdaaa74ed6e1d55a16296f714a758d08350976c5b001c9.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;92% of the world’s online population use emoji — but which emoji are we using? Well, it appears that reports of Tears of Joy’s death are greatly exaggerated 😂. According to data collected by the Unicode Consortium, the not-for-profit organization responsible for digitizing the world’s languages, Tears of Joy accounts for over 5% of all emoji use (the only other character that comes close is ❤️ and there is a steeeeeep cliff after that). The top ten emoji used worldwide are 😂 ❤️ 🤣 👍 😭 🙏 😘 🥰 😍 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@analyzer2004/plot-powerbi" rel="noopener noreferrer"&gt;Hacking Power BI Charts with Plot&lt;/a&gt; by &lt;a href="https://observablehq.com/@analyzer2004" rel="noopener noreferrer"&gt;@analyzer2004&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@analyzer2004/plot-powerbi" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fede7117f6c60a10e5747f67b4538c860e981ab3c784be2078d5eae91d07ac56d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Are you a fan of both PowerBI and Observable Plot? Here are some ideas for recreating PowerBI charts with the 🙏almighty Plot🙏!! Waterfall Chart Ribbon Chart It's a variant of Bump Chart which reflects both value and the changes in rank over time. Tornado Chart LineDot Chart Waffle Chart Funnel Chart 🌐ericlo.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@theeconomist/lucy-mission-animation" rel="noopener noreferrer"&gt;Lucy mission animation&lt;/a&gt; by &lt;a href="https://observablehq.com/@theeconomist" rel="noopener noreferrer"&gt;@theeconomist&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@theeconomist/lucy-mission-animation" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F9bfb3f8fa95658c88e24555e43cb7e127efa84b5899d2b8d2ac0b42cba6cc669.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This notebook contains the code for generating the animated diagram of &lt;small&gt;NASA&lt;/small&gt;’s Lucy probe, which will visit many of the “Trojan asteroids”, a pair of clusters of asteroids at the Lagrange points of Jupiter’s orbit. You can read more about Lucy’s mission and see the complete animation in our article. The visualisation above shows one frame of the animation. Labels were added to the final video in AfterEffects, so this diagram has none.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@floatingpurr/observable-twitter-2021" rel="noopener noreferrer"&gt;The story of Observable on Twitter&lt;/a&gt; by &lt;a href="https://observablehq.com/@floatingpurr" rel="noopener noreferrer"&gt;@floatingpurr&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@floatingpurr/observable-twitter-2021" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F6f3358430fd2eb98acd45ed23b2527007bdc495d1409049cf5d95245764a7739.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;** Main info** 📝 forked integration notebook: Google Sheets Starter ℹ️ dataset: the Observable twitter activity up to (and updated to) 🗓    (source: Twitter API) 📗 Data are hosted in a Google Sheet and published as a csv file Once Upon a Time... ✨✨✨ 💫 Welcome to the data story of Observable on Twitter. Every story has a beginning... Do you remember this? Back in 2017, that was the first "official" Observable tweet, and it has been liked   times 💙 (as of &lt;code&gt;2021-07-26&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Digital and Generative Art
&lt;/h2&gt;

&lt;p&gt;The Observable platform is great for iterating on visuals quickly. In 2021 there were many digital artists producing novel and interesting works, some of my top picks are shown below. Makio135's &lt;a href="https://observablehq.com/@makio135/creative-coding" rel="noopener noreferrer"&gt;Creative Coding&lt;/a&gt; tutorial notebook on producing generative art on Observable remains a must-read for aspiring digital artists.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@esperanc/flow-fields" rel="noopener noreferrer"&gt;Flow fields&lt;/a&gt; by &lt;a href="https://observablehq.com/@esperanc" rel="noopener noreferrer"&gt;@esperanc&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@esperanc/flow-fields" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F57a869c5f7d513596f9314038af996a886fb80c35deb4dc33b000d35deb8db08.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An attempt to reproduce some of the techniques described in Tyler Hobbs' Flow Fields post. A simple field A more interesting field Curves through the field Position sampling Perlin noise fields Colored Perlin fields The idea here is to set the color of each flow curve to the color picked from a "seed" image.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@enjalot/simplex-stacks" rel="noopener noreferrer"&gt;Simplex Stacks&lt;/a&gt; by &lt;a href="https://observablehq.com/@enjalot" rel="noopener noreferrer"&gt;@enjalot&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@enjalot/simplex-stacks" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F12be8a6c4b36ea08f39be707e2e410dd15d067284382ee10ff86962dc9d790ee.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may have heard of Simplex Noise, you can find many amazing visuals using it on Observable. But have you ever seen a stacked area chart of Simplex noise? This notebook does just that, using Plot to visualize the noise in a number of familiar and unfamiliar ways. Try changing the &lt;code&gt;z\&lt;/code&gt; parameter to see the noise in action: The data samples the simplex function primarily along the x axis. Each series in the stack is sampled at a single y value.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@stringertheory/procedural-face-exploration" rel="noopener noreferrer"&gt;Procedural Face Exploration&lt;/a&gt; by &lt;a href="https://observablehq.com/@stringertheory" rel="noopener noreferrer"&gt;@stringertheory&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@stringertheory/procedural-face-exploration" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F4ae6906c45d5ed36a1a67ec0223930b10110b1c53c58dc8a8c53cc842f6d801a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a copy, with only very slight modifications, of the excellent Procedural Face Generator written by Amit Patel. I wanted to play around with some of the facial features so I ported it into Observable, but you should check out the original (especially the cool sliders that help understand how changing the parameters will change the face 😍)! It's interesting to explore how the face color, eye shape, and the addition of eyelashes change the way the face looks.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@jobleonard/pseudo-blue-noise" rel="noopener noreferrer"&gt;Pseudo-Blue Noise&lt;/a&gt; by &lt;a href="https://observablehq.com/@jobleonard" rel="noopener noreferrer"&gt;@jobleonard&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@jobleonard/pseudo-blue-noise" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F4ec29900fd05a0ebe1b59672361be6d757b18d454f7f68f64868b2b763f021df.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blue noise is a noise color[1] that is particularly well-suited for image dithering purposes (see also [2][3]). Unfortunately it's also computationally intensive to generate. For example, Bart Wronski mentions that generating a "64×64 wrapping blue noise-like sequence" took "a couple hours on an old MacBook." This was in 2016, but unless dramatic progress has been made in the algorithms to generate blue noise this will not be significantly faster today.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mootari/d3-path-force" rel="noopener noreferrer"&gt;D3 Force Along a Path&lt;/a&gt; by &lt;a href="https://observablehq.com/@mootari" rel="noopener noreferrer"&gt;@mootari&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mootari/d3-path-force" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe98143070073f2faab03bc3e68f1298b77b9e469c002c4ee23024b20c605d15a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implements a custom force for d3-force which moves nodes along an SVG path. "Track strength" vs "Max step size" At each simulation step ("tick") a node is moved towards its target position on the path. The further a node is from its target, the more distance it will cover during a tick.&lt;br&gt;&lt;br&gt;
The "Track strength" multiplier (&lt;code&gt;strength\&lt;/code&gt; option) gets applied to this distance. A value of &lt;code&gt;0.5\&lt;/code&gt; means that the node will cover half the remaining distance towards its target on each tick.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@karimdouieb/generative-painting" rel="noopener noreferrer"&gt;Generative Painting&lt;/a&gt; by &lt;a href="https://observablehq.com/@karimdouieb" rel="noopener noreferrer"&gt;@karimdouieb&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@karimdouieb/generative-painting" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F56212453698f8654771d8386fe38eaf6addd0b24133a76f1405e49acbd1c92f3.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a recreation of a generative water colour like painting technique described beautifully by Tyler Hobbs during his "How to Hack a Painting" talk in the Strange Loop Conference '17. Also checkout his essay on the same subject on his blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@bronna/parametric-seashell" rel="noopener noreferrer"&gt;Building a Parametric Seashell&lt;/a&gt; by &lt;a href="https://observablehq.com/@bronna" rel="noopener noreferrer"&gt;@bronna&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@bronna/parametric-seashell" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3d1a416ecd8eb84db078c219a320bc0577df4a7d41a06b5f74be1682febfe7f0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The clam and the conch seem to be drastically different creatures. One spends its life burrowing in the sand, staying in one spot to glean nutrients from the water. The other constantly vaults itself around the seafloor to find its next meal. One has a gently curving, sand-colored shell, whereas the other has a shell spiking and spiraling and splaying out in the colors of the sunset. These two species are opposites in many ways, yet the same equation underlies both of their shells.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@daformat/svg-flowers" rel="noopener noreferrer"&gt;SVG flowers&lt;/a&gt; by &lt;a href="https://observablehq.com/@daformat" rel="noopener noreferrer"&gt;@daformat&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@daformat/svg-flowers" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fb6c1fb335483e4581b8012fe6be646b8259eab773aef5c63089740a3e2683214.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I stumbled on this tweet by @wks_jp and since spring is coming, I was inspired to create the same kind of floral patterns using svg &lt;code&gt;stroke-dasharray\&lt;/code&gt;. There are two patterns that we randomly draw: Variable density dotted path: a &lt;code&gt;stroke-dasharray\&lt;/code&gt; is used with a 0 length for the stroke so it becomes rounded to evenly spaced circles when &lt;code&gt;stroke-linecap\&lt;/code&gt; which is set to &lt;code&gt;round\&lt;/code&gt;. Additionally, using this pattern, the resulting flower may or may not have a solid center drawn.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@hellonearthis/processing-https-10print-org-p5-js" rel="noopener noreferrer"&gt;Processing https://10print.org/ (p5.js)&lt;/a&gt; by &lt;a href="https://observablehq.com/@hellonearthis" rel="noopener noreferrer"&gt;@hellonearthis&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@hellonearthis/processing-https-10print-org-p5-js" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F2eebfe8a59e969a4c1ab9a15cfe427626223fb961c0bb233b4667c65f04cf202.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get started with creative coding. Learn more in the tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@ambassadors/whats-that-noise" rel="noopener noreferrer"&gt;What's That Noise?&lt;/a&gt; by &lt;a href="https://observablehq.com/@ambassadors" rel="noopener noreferrer"&gt;@ambassadors&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@ambassadors/whats-that-noise" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd29ad7b7aee4d6324050ed1ddf65bc44ae9c129c2073258048ade4c7091c9696.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A closer look at various types of 2D noise and how to massage their output. Move your cursor over a noise field (or click/tap) to update its line. (Note: currently not optimized for narrow screens. If you're on mobile, please switch your phone to landscape.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mast4461/guided-tour-of-an-infinite-sphere-grid" rel="noopener noreferrer"&gt;Guided Tour of an Infinite Sphere Grid&lt;/a&gt; by &lt;a href="https://observablehq.com/@mast4461" rel="noopener noreferrer"&gt;@mast4461&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mast4461/guided-tour-of-an-infinite-sphere-grid" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F39cb24c6f01580f928304736d2de4a18772b22bec0ac026c1967144f3575144c.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An Exploration of Parameter Space Best viewed in fullscreen landscape mode, if your GPU can handle all the ray marching. Shortcuts: I've split the fragment shader into  small chunks so that I can move the canvas cell next to the chunk I'm working on, to see both the canvas and the code. This would otherwise be difficult when working on GLSL code in the middle of a large cell.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@zekenie/playing-with-hilbert" rel="noopener noreferrer"&gt;Playing with Hilbert&lt;/a&gt; by &lt;a href="https://observablehq.com/@zekenie" rel="noopener noreferrer"&gt;@zekenie&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@zekenie/playing-with-hilbert" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe092e72d048e05d75d080009c01e3ea59f4b16be8cb9e64d24280e0d80662d6a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Space filling curves are fractals that weave through every point in space. They're useful when you are building a system that needs to&lt;br&gt;&lt;br&gt;&lt;code&gt;&amp;lt;twilight-zone-voice&amp;gt;\&lt;/code&gt;&lt;br&gt;    jump dimensions &lt;br&gt;&lt;code&gt;&amp;lt;/twilight-zone-voice&amp;gt;\&lt;/code&gt;. Let's say you have a piece of string that represents 1-dimentional space. You could mark that string with a sharpie and represent a point.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@migueldeicaza/raytracing-in-40-minutes-the-observable-port" rel="noopener noreferrer"&gt;Raytracing in 40 minutes, the Observable port&lt;/a&gt; by &lt;a href="https://observablehq.com/@migueldeicaza" rel="noopener noreferrer"&gt;@migueldeicaza&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@migueldeicaza/raytracing-in-40-minutes-the-observable-port" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F7085dc9def8569f727c8a1bdf1d286ebd97873c185c06cebeb7cf52886cfc717.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an ObservableHQ port of the "Raytracing in 40 minutes" from Peter Shirley, one cute feature is that when you update any of the values of this port, it automatically updates all the renderings as you change the functions and variables in this file. Peter announced that he would be doing a short lecture explaining this on his twitter feed. 3D Vectors and Colors in this notebook are represented as arrays of three values, there is no special data type for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mathematics
&lt;/h2&gt;

&lt;p&gt;There is something about the reactive interactivity of Observable that makes it useful for intuition building. There is an avid group of mathmaticians on Observable producing engaging mathmatical teaching materials. Prof. &lt;a href="https://observablehq.com/@mcmcclur" rel="noopener noreferrer"&gt;McClure&lt;br&gt;
&lt;/a&gt; in particular has an &lt;a href="https://observablehq.com/@mcmcclur?tab=collections" rel="noopener noreferrer"&gt;extensive set of notebooks&lt;/a&gt; to support his classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@enjalot/hilbert-curve-plot" rel="noopener noreferrer"&gt;Hilbert Curve Plot&lt;/a&gt; by &lt;a href="https://observablehq.com/@enjalot" rel="noopener noreferrer"&gt;@enjalot&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@enjalot/hilbert-curve-plot" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F13398cccab514eaabac6e119598250a2cace03920184a74e2f465b4f4a461eff.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple representation of the Hilbert curve, calculated with d3-hilbert and rendered with Plot. Stacking Plots When you stack several orders of Hilbert curves on top of each other the pattern between orders can be a bit clearer. It helped me to notice the multiples of 4, and how each level "up" creates 4 corners of a box surrounding a point at the level below.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@cscheid/aliasing" rel="noopener noreferrer"&gt;Aliasing&lt;/a&gt; by &lt;a href="https://observablehq.com/@cscheid" rel="noopener noreferrer"&gt;@cscheid&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@cscheid/aliasing" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F65f42a4b2e30a33ff7803e37d4a5fe04d11fee23e8c52aba4ec92965527dde97.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tweet shows the phenomenon of aliasing  through a very effective animation. The notebook port here provides a little bit of interactivity so you can explore what happens when a signal is not sampled often enough in a reconstruction: A well-sampled signal must be sampled at least twice as often as the highest frequency in the original signal. It's not obvious yet always possible to reconstruct a signal exactly if it's been sampled at a sufficiently-high rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mast4461/wrap-your-eye-around-rubiks-cube" rel="noopener noreferrer"&gt;Wrap Your Eye Around Rubik's Cube 👁&lt;/a&gt; by &lt;a href="https://observablehq.com/@mast4461" rel="noopener noreferrer"&gt;@mast4461&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mast4461/wrap-your-eye-around-rubiks-cube" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd68b9e7296b336229419b7fabd50de1aca0fe6301ec6539fc76ed29ff070d7ee.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Controls: A technique called ray marching is used to render the cube. Usually in ray marching, rays project out from a single point. Here, we don't just project rays in different directions, but we let them start at different points! The graphic below illustrates how the ray origins and ray directions change with the wrap factor. Scroll it to wrap! Implementation Notes to self: Here's a nice example of how to color objects in ray marching: &lt;a href="https://www.shadertoy" rel="noopener noreferrer"&gt;https://www.shadertoy&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@d3/contourdensity-linear-binning" rel="noopener noreferrer"&gt;Density contours with linear binning&lt;/a&gt; by &lt;a href="https://observablehq.com/@d3" rel="noopener noreferrer"&gt;@d3&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@d3/contourdensity-linear-binning" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F4acc9d139e55a4784c334094b451c6d92527a06026681d32da66bb0672d9fa23.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;new in d3-contour@3 and d3@7! This brownian motion system with   particles is visualized with the contours from d3.contourDensity. The first chart uses the traditional nearest neighbor method; the second chart shows the improved method—linear binning—adopted by &lt;a href="mailto:d3-contour@3"&gt;d3-contour@3&lt;/a&gt;. Density contours are calculated in three stages: first, each of the data points registers its weight onto a grid. The total weights on that grid are then blurred, and contours are computed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@saehrimnir/dimensionality-reduction-drawings" rel="noopener noreferrer"&gt;Dimensionality Reduction drawings&lt;/a&gt; by &lt;a href="https://observablehq.com/@saehrimnir" rel="noopener noreferrer"&gt;@saehrimnir&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@saehrimnir/dimensionality-reduction-drawings" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F403e7296d577d780b11f79d9c90830690c7e40eb5036bfdb42397e2d05f22bcf.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;UMAP and t-SNE are iterative dimensionality reduction methods. The picture gets drawn by drawing the voronoi cells of the intermediate results above each other with some opacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@jrus/hexround" rel="noopener noreferrer"&gt;Hexagon grid rounding&lt;/a&gt; by &lt;a href="https://observablehq.com/@jrus" rel="noopener noreferrer"&gt;@jrus&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@jrus/hexround" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3bcbb13ae1247750fd52aaab0fdb5f56fbcd013316fc64b0a660ed1bb1d39857.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we have points in a hexagon grid represented using “axial coordinates” –   and   coordinates aligned with two grid axes oriented 1/6 turn (60°) apart – we can round to the nearest hexagon with the pseudocode below: def axial_round(x, y):&lt;br&gt;
    xgrid = round(x); ygrid = round(y)&lt;br&gt;
    x -= xgrid; y -= ygrid # remainder&lt;br&gt;
    if abs(x) &amp;gt;= abs(y):&lt;br&gt;
        return xgrid + round(x + 0.5*y), ygrid&lt;br&gt;
    else:&lt;br&gt;
        return xgrid, ygrid + round(y + 0.&lt;/p&gt;




&lt;h2&gt;
  
  
  Maps
&lt;/h2&gt;

&lt;p&gt;Maps are a specialised subset of dataviz dealing with realworld spatial data. Maps are a loved topic on Observable. In 2021 &lt;a href="https://observablehq.com/@floledermann" rel="noopener noreferrer"&gt;@floledermann&lt;/a&gt; produced the &lt;a href="https://observablehq.com/@floledermann/projection-playground" rel="noopener noreferrer"&gt;projection notebook&lt;/a&gt; to end all projection notebooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@michelleeesi/local-spatial-autocorrelation-interactive-tutorial" rel="noopener noreferrer"&gt;Local Spatial Autocorrelation Interactive Tutorial&lt;/a&gt; by &lt;a href="https://observablehq.com/@michelleeesi" rel="noopener noreferrer"&gt;@michelleeesi&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@michelleeesi/local-spatial-autocorrelation-interactive-tutorial" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe0a6a8a06d73a85c7a86f0cba3bdadab7c036a207243568b9cef301cf8dbfa00.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Text in this notebook is adapted from Luc Anselin's GeoDa workbook, available here. GeoDa is a free and open-source program for exploratory spatial data analysis and basic spatial regression modeling that you can download here. When we look at maps, we spot concentrations and groupings of high and low values, start to recognize patterns, and construct stories about what might be going on.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@floledermann/projection-playground" rel="noopener noreferrer"&gt;Map Projection Playground&lt;/a&gt; by &lt;a href="https://observablehq.com/@floledermann" rel="noopener noreferrer"&gt;@floledermann&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@floledermann/projection-playground" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F2a9f315d2dd931acdde2067a6a47d85dc83de17e583b48275856a2e7e69b5702.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adapted for my course "Cartographic and Geodetic Foundations for Planners". You can find even more static examples map projections in this overview. Map Style: ⚠️ This causes rendering errors for some projections: For some interrupted projections, you can set the number of lobes: 1 The circles for visualizing distortions are taken from here.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visionscarto/aires-d-accueil-les-donnees" rel="noopener noreferrer"&gt;Aires d’accueil des « Gens du voyage »&lt;/a&gt; by &lt;a href="https://observablehq.com/@visionscarto" rel="noopener noreferrer"&gt;@visionscarto&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visionscarto/aires-d-accueil-les-donnees" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F6a4cbb9040229000bebc9108cae1df0ec6b004da7a8ab2d905a02e544d31276e.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Export officiel, par @visionscarto, des données collectées par William Acker dans le cadre de l’inventaire critique publié sous le titre « Où sont les “gens du voyage” ? ». URL de référence : &lt;a href="https://visionscarto.net/aires-d-accueil-les-donnees" rel="noopener noreferrer"&gt;https://visionscarto.net/aires-d-accueil-les-donnees&lt;/a&gt; (Date de mise à jour :  ) \* Les aires d’accueil sont classées par grandes caractéristiques : aires d’habitat, de petit passage, de grand passage (GP).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visionscarto/five-oceans-topojson" rel="noopener noreferrer"&gt;Five Oceans&lt;/a&gt; by &lt;a href="https://observablehq.com/@visionscarto" rel="noopener noreferrer"&gt;@visionscarto&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visionscarto/five-oceans-topojson" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd118041b9fd7ee32bb8e3c521172fa86f75d147c2acecfe9552558ec8a40bcfb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  30DayMapChallenge — Day 18. Water. Attached to this notebook is a topojson file that I crafted more or less by hand. As I was trying to polish my “Born in Antarctica” notebook, I wasted quite a bit of time in an elusive search for a simple shapefile of the Antarctic Convergence. Unable to find a base map of the Oceans, I decided to create this one and add it to my toolbox.
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@ambassadors/earth-nose-direction-game" rel="noopener noreferrer"&gt;The Earth-Nose-Direction game&lt;/a&gt; by &lt;a href="https://observablehq.com/@ambassadors" rel="noopener noreferrer"&gt;@ambassadors&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@ambassadors/earth-nose-direction-game" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F25d9e0fdeea160b358c0982ba95bb7c5325327a98e790b77530a4dd54147e6ef.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How many friends can you find? SHARE! &lt;a href="https://bit.ly/earth-nose-game" rel="noopener noreferrer"&gt;https://bit.ly/earth-nose-game&lt;/a&gt; code starts below Acknowledgments Notebook developed and edited by: &lt;a class="mentioned-user" href="https://dev.to/fil"&gt;@fil&lt;/a&gt; @mootari &lt;a class="mentioned-user" href="https://dev.to/radames"&gt;@radames&lt;/a&gt; Thanks for the fantastic feedback/ideas from all our ambassadors friends who almost broke their noses/necks testing the notebook.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@neocartocnrs/getting-routes-with-osrm" rel="noopener noreferrer"&gt;Getting routes with OSRM&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/getting-routes-with-osrm" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F5b75c41fabf8ff9f3cef90c26db12c0eac81e8316c4696d56c399a6d6b77722b.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The distance between   and   is   km. It takes   hours to cover this distance by car. OSRM (Open Source Routing Machine) is a C++ implementation of a high performance route search engine to obtain the shortest paths in a road network. Available under a simplified BSD license, OSRM is an open source service. See &lt;a href="http://project-osrm.org/docs/v5.24.0/api/" rel="noopener noreferrer"&gt;http://project-osrm.org/docs/v5.24.0/api/&lt;/a&gt; Preliminary remarks: To query the OSRM API, you need to have CORS enabled. For that, you can use CORS Anywhere Observable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mourner/latitude-and-longitude-precision" rel="noopener noreferrer"&gt;Latitude and longitude precision&lt;/a&gt; by &lt;a href="https://observablehq.com/@mourner" rel="noopener noreferrer"&gt;@mourner&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mourner/latitude-and-longitude-precision" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fa54a3bbf18eb19ebeae2831ec5644449f6093768a97bab30620461ebebd5e304.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How many decimal digits do you need for longitude and latitude? Following the famous XKCD comic on coordinate precision, let's see how this looks on a map. There's a location defined at the bottom of this page, but we've rounded the coordinates to integers, losing all precision. Now it looks like ** ,  **. Where could it originally point to? The map below shows the exact area of all locations that round to these numbers. So, with zero decimal digits, we can place the location somewhere around London.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visionscarto/deriving-pantelleria" rel="noopener noreferrer"&gt;Deriving Pantelleria&lt;/a&gt; by &lt;a href="https://observablehq.com/@visionscarto" rel="noopener noreferrer"&gt;@visionscarto&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visionscarto/deriving-pantelleria" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F2a2769d0eb2a54d83a8ab5daccac267944096c28b53faf251eea3c29baaf7934.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  30DayMapChallenge — Day 21. Elevation. DEM This notebook starts with a Digital elevation model (DEM) of the Italian island of Pantelleria, halfway between Sicily and the coast of Tunisia in the Mediterranean Sea. I’ve never been there, but it looks like the perfect shape of an island… anyway… let’s begin. I captured this DEM on nextzen’s terrarium. The DEM is   pixels wide by   pixels high. For reference, it corresponds to the following projection (we won’t use it in the notebook).
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visionscarto/30-days-and-as-many-maps" rel="noopener noreferrer"&gt;30 days and as many maps&lt;/a&gt; by &lt;a href="https://observablehq.com/@visionscarto" rel="noopener noreferrer"&gt;@visionscarto&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visionscarto/30-days-and-as-many-maps" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F9836403f9d8cffa8257bb3c39b680937b8a841ff4eff8350c4b0229bb9da564d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A look back on the #30DayMapChallenge, 2021. Embarking on this challenge at the last minute was a bit stupid, but it felt like an opportunity to finish a few open projects, to try a few ideas, and to showcase a part of my usual toolbox. Day 1. Points. “Dotted Oceans” This globe uses: a backface projection that allows to “see through” the Earth, and a nice-looking random point distribution. Although it’s a very simple notebook, it was quite successful. Day 2. Lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@severo/download-the-shape-of-a-country" rel="noopener noreferrer"&gt;Download the shape of a country&lt;/a&gt; by &lt;a href="https://observablehq.com/@severo" rel="noopener noreferrer"&gt;@severo&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@severo/download-the-shape-of-a-country" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fbe6f99630d1a2f6e1eed7bd2711c8ec14c677f3ad8aacbb934ec4c2ae1f8b3fc.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select a country (or Antartica) #### Settings You think France is weird? Maybe look at Map Template France Lambert-93 or d3-composite-projections instead. Also, the precision is very bad for small countries. Credits Developed by Sylvain Lesage Data from Natural Earth. Code from Countries by area by Mike Bostock.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mattdzugan/phyllo-fork" rel="noopener noreferrer"&gt;PHYLLO FORK 🌻🍴 How much warmer? (BBC)&lt;/a&gt; by &lt;a href="https://observablehq.com/@mattdzugan" rel="noopener noreferrer"&gt;@mattdzugan&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mattdzugan/phyllo-fork" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F29fcee9867f39c4ccbee8983f3f40d9247ab0a315049c8e84aa81d1f0f1bfba8.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a fork of Fil's notebook. I simply replaced the regular lat/lon grid with a spherical Phyllotaxis sampling. How much warmer? (BBC) A recreation, for educational purposes, of this beautiful map by the BBC Visual and Data Journalism team (31 July 2019). You can learn more on the original project in this very detailed and complete write-up by the BBC team. Made with D3 (the original visualization uses THREE.js).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@chrispahm/hello-spatialmerge" rel="noopener noreferrer"&gt;Hello, spatialmerge&lt;/a&gt; by &lt;a href="https://observablehq.com/@chrispahm" rel="noopener noreferrer"&gt;@chrispahm&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@chrispahm/hello-spatialmerge" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fcbb5fe4dc750dece7847da10b78803ed909f10f3a8937330dd62c98e5a6e2cd1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;spatialmerge is a (relatively) fast library for spatial joining and merging data in JavaScript 🚀 This notebook will walk you through different ways to combine spatial datasets. It is a (blatant) copy of the "merging data" tutorial from geopandas, and uses the same underlying datasets. Merging data There are two ways to combine datasets in &lt;code&gt;spatialmerge&lt;/code&gt; – attribute joins and spatial joins. In an attribute join, a GeoJSON feature collection is combined with an array of objects (e.g.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@neocartocnrs/world-grids" rel="noopener noreferrer"&gt;World Grids&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/world-grids" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F2ddc9257d48669b7e0ae895524775b6ac838e23cde636db9cbbf095f236a1a3c.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data Import 2. Map Layout ** 3.
### &lt;a href="https://observablehq.com/@neocartocnrs/world-elevation-line-map" rel="noopener noreferrer"&gt;World Elevation Line Map&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/world-elevation-line-map" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3b1d8240d18ba5fe76550304654aaa77e84e83717412580ab43e275e9ebd9be8.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data import. We do... ** 2. Data Handling &amp;amp; stuffs for cartography*&lt;em&gt;. Here, we... 3. Lines *&lt;/em&gt; 3.
### &lt;a href="https://observablehq.com/@oliviafvane/macromap" rel="noopener noreferrer"&gt;Macromap&lt;/a&gt; by &lt;a href="https://observablehq.com/@oliviafvane" rel="noopener noreferrer"&gt;@oliviafvane&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@oliviafvane/macromap" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff9d59f68bf77442e7b43bf52c8ebc9003c6f4b0fb3927f334cec91b2972abbe1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An interactive ‘small multiples’ visualisation for historical map collections. How can we get a handle on very large, partially-digitised collections of maps? We need a map of the maps. Macromap is designed to help researchers understand what map sheets the British Ordnance Survey (OS) made, when and where. In addition, it illuminates what’s been digitised: a lot, but not everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@ambassadors/30-day-map-challenge-day-8-blue" rel="noopener noreferrer"&gt;30 Day Map Challenge: Day 8 - Blue&lt;/a&gt; by &lt;a href="https://observablehq.com/@ambassadors" rel="noopener noreferrer"&gt;@ambassadors&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@ambassadors/30-day-map-challenge-day-8-blue" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F956abb9cfd1ffb60c36b836aa2948fd60470d81c1ad6f7b828294773e0b0ba54.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Ocean blue This literally took about 15 minutes to build, in spite of the fact that I never used the drop shadow technique before.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@neocartocnrs/lets-make-an-extruded-map" rel="noopener noreferrer"&gt;Let's make an extruded map&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/lets-make-an-extruded-map" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe426f26f6aabd5d8038d814e22caa8136492d46283fd5666b7431cf4a428dcba.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data Import &amp;amp; handling **2.
### &lt;a href="https://observablehq.com/@ambassadors/day-11-3d-30-day-map-challenge-2021" rel="noopener noreferrer"&gt;Day 11: 3D, 30 Day Map Challenge 2021&lt;/a&gt; by &lt;a href="https://observablehq.com/@ambassadors" rel="noopener noreferrer"&gt;@ambassadors&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@ambassadors/day-11-3d-30-day-map-challenge-2021" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F7313dc40a95cabd03eedd33ab341b6942f23e84db264a7563e8b9d0f8b8b74c1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://data.linz.govt.nz/layer/50768-nz-contours-topo-150k/" rel="noopener noreferrer"&gt;https://data.linz.govt.nz/layer/50768-nz-contours-topo-150k/&lt;/a&gt; This is a little slow load as it needs to extract the data files from a zip file. The contours Shapefile contains two types of geometry types, LineString and MultiLineString The Mouse can interact with the maps orientation. Add colours based on elevation,  there are   zones over a range of   to ✅ The objective here is to convert the contours of Taranaki into a three.js objects. ✅ Fix map orientation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Apps
&lt;/h2&gt;

&lt;p&gt;At its core, Observable is a front end development platform, and a few of the community have built fully fledged web applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/sign-a-pdf" rel="noopener noreferrer"&gt;Sign a PDF and Adobe: Go F* Yourself&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/sign-a-pdf" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F277ab47fb860bf5f048c4e1e443f0d6fe3232e917c0c0393bcfbab7e527f2e78.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Allows super-imposing an image of your signature on a PDF. ** Note: you can also do this using Mac Preview, see Tools &amp;gt; Annotate &amp;gt; Signature (docs)** Why not use Adobe? I once had a Adobe subscription exactly for this purpose but I was unable to cancel it. So it cost me $200 to sign a document. They are predatory, and its not just me. Why not use the free tools on the internet? I want to sign financial documents and privacy is critical. Unfortunately, I cannot audit most free software on the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@visnup/morse-code-trainer" rel="noopener noreferrer"&gt;Morse code trainer&lt;/a&gt; by &lt;a href="https://observablehq.com/@visnup" rel="noopener noreferrer"&gt;@visnup&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@visnup/morse-code-trainer" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F5ffabe0327755a1f7a37d1b7727946ebe7151a8261f6013e17c0b012efeb377d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the button below to practice your Morse code. Sound on 🔊. Once you’ve focused the button by pressing it, you can also use the spacebar. If you’re finding it difficult to encode a dah (a.k.a. dash −), try adjusting the dit (a.k.a. dot ·) duration. Built with &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars0.githubusercontent.com%2Fu%2F841829%3Fv%3D4%26s%3D48" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars0.githubusercontent.com%2Fu%2F841829%3Fv%3D4%26s%3D48"&gt;&lt;/a&gt; @tophtucker while streaming.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/sequencer" rel="noopener noreferrer"&gt;Sequencer&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/sequencer" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F7be88159176597813f93770c92f90f95c8a1b6b61b3b6440b531deff0f5679a9.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sequence connects to the drum pads which connect to the samples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Development
&lt;/h2&gt;

&lt;p&gt;Building complex applications on Observable requires all kinds of supporting libraries. A number of development notebooks stood out in 2021. Databases were a strong theme over the year, due to additional SQL support provided by the platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@bmschmidt/duckdb-degrees" rel="noopener noreferrer"&gt;Interactive exploration with DuckDB: 5.2 million rows of college degrees in the browser&lt;/a&gt; by &lt;a href="https://observablehq.com/@bmschmidt" rel="noopener noreferrer"&gt;@bmschmidt&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@bmschmidt/duckdb-degrees" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F330262ce1561fca449e1e263e1230e7a4f8f6b21f76bd0214fbd481f89f1667d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've looked in the past at in-browser analytics of a granular database of college degrees using basic Apache Arrow functions and arquero. This notebook does the same thing using the new duckdb wasm module. At first pass it's a cut above in speed and flexibility, because: It can load from compressed parquet files, which allow it to work with larger datasets, faster; It can often be about 10x faster than equivalent in-browser solutions if the data is more than several thousand rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@cmudig/introducing-sql-with-duckdb" rel="noopener noreferrer"&gt;Introducing SQL with DuckDB in your browser&lt;/a&gt; by &lt;a href="https://observablehq.com/@cmudig" rel="noopener noreferrer"&gt;@cmudig&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@cmudig/introducing-sql-with-duckdb" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fed9a397387d588d64d00f2206a15abd3d35f2dbfce7d2c8d6413f11ce62d2835.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SQL is a declarative programming language transforming and querying data. It is declarative, meaning that you specify the data you want and not how the query should be computed. SQL is a great fit for a range of data transformation tasks including filter, sample, aggregation, window, and, join. SQL is the most popular query language for database management systems and essential to many business and data science workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mootari/codemirror" rel="noopener noreferrer"&gt;Hello, CodeMirror 5!&lt;/a&gt; by &lt;a href="https://observablehq.com/@mootari" rel="noopener noreferrer"&gt;@mootari&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mootari/codemirror" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F7dfb899207a23f128583a14347a8d7fccef12518f2cca5f10756a51c986eca89.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo Stuff&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/sqlite" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/sqlite" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F0f21d64a4700387e9057757954da01347d3af7a183f5c475ba9fc0ce211bd098.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observable has built-in support for SQLite, “a small, fast, self-contained, high-reliability, full-featured, SQL database engine” and “the most used database engine in the world.” Observable’s SQLite client uses sql.js, an Emscripten port of SQLite. After attaching a SQLite file to your notebook, create a SQLite database client by calling file.sqlite(). Here we’ll explore the Chinook sample database which represents a music library. You can also call SQLiteDatabaseClient.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/local-storage-view" rel="noopener noreferrer"&gt;localStorageView: Non-invasive local persistance&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/local-storage-view" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fd902f7ddc6c312707deea4a6dc5d852832af34faac643dde5d4a2cbba56922cf.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets make it simple to add local storage to a UI control (e.g. &lt;a class="mentioned-user" href="https://dev.to/observablehq"&gt;@observablehq&lt;/a&gt;/inputs) We exploit back-writability and input binding to avoid having to mess with existing UI control code. localStorageView(key) creates a read/write view of a safe-local-storage. Because it's a view it can be synchronized to any control we want to provide persistence for. We avoid having to write any setItem/getItem imperative wiring. If you want all users to share a networked value, consider shareview.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@asg017/hacking-observables-new-cell-modes-to-use-sql" rel="noopener noreferrer"&gt;Hacking Observable's new Cell Modes to use SQL&lt;/a&gt; by &lt;a href="https://observablehq.com/@asg017" rel="noopener noreferrer"&gt;@asg017&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@asg017/hacking-observables-new-cell-modes-to-use-sql" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3a85285180eb578a25623e9b4d444998d093ae940c95c6d83777a4ff3eeb5f50.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2021-12-01 Update: Rejoice! Observable now has SQL cells as a proper feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI, UX and Web Design
&lt;/h2&gt;

&lt;p&gt;Observble notebooks promote resuse. A number of useful components were developed which can be used in other Dataviz notebooks. &lt;a href="https://observablehq.com/@daformat" rel="noopener noreferrer"&gt;Mathieu Jouhet&lt;/a&gt; wrote a stunning library + tutorial notebook on &lt;a href="https://observablehq.com/@daformat/rounding-polygon-corners" rel="noopener noreferrer"&gt;Rounding polygon corners in SVG&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@kelleyvanevert/that-colorful-font" rel="noopener noreferrer"&gt;That colorful font&lt;/a&gt; by &lt;a href="https://observablehq.com/@kelleyvanevert" rel="noopener noreferrer"&gt;@kelleyvanevert&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@kelleyvanevert/that-colorful-font" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F719fd5c84183d3a20e1db18064b8fa2971914b8282c9566731f9f8269963de12.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That colorful font from the browser extension, What have you made today? This is the original font face, I think: Type With Pride&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@d3/charts" rel="noopener noreferrer"&gt;D3 Charts&lt;/a&gt; by &lt;a href="https://observablehq.com/@d3" rel="noopener noreferrer"&gt;@d3&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@d3/charts" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F8e02be2cb0444f15fc8b0b621839f74242b5a528df2c291ffa4f72b6f3abd875.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A guide to D3’s reusable example charts D3 Charts help you get started quickly with D3. To use these charts in your application, copy-paste or import the chart function into your code, then pass the chart function your data and any options. The chart function returns an SVG element that you can then insert into the page. The chart functions are designed to work out of the box: the options are all, well… optional, with reasonable defaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@daformat/rounding-polygon-corners" rel="noopener noreferrer"&gt;Rounding polygon corners&lt;/a&gt; by &lt;a href="https://observablehq.com/@daformat" rel="noopener noreferrer"&gt;@daformat&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@daformat/rounding-polygon-corners" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F4628a4720f261b7186a115be018f5db7edabb786dd0fcd52f2158cbc47bdf56f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my previous notebook about how to find the union polygon of overlapping rectangles I've included a polygon corner rounding algorithm as a post-processing step but didn't explain how it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@daformat/distributing-circles-around-a-shape" rel="noopener noreferrer"&gt;Stencil all the things! Distributing circles around a shape&lt;/a&gt; by &lt;a href="https://observablehq.com/@daformat" rel="noopener noreferrer"&gt;@daformat&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@daformat/distributing-circles-around-a-shape" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F51248a93bdbf534bd4e9ad0b2a2b327832cdbbe883d6e2487a88c23fa59903e9.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspired by a tweet form Chris Gannon, I decided to explore this stencil effect by recreating it in a generative way. Instead of manually placing and colorizing each circle, I wanted to be able to dynamically generate them given any svg path. In this notebook we'll learn how use the svg APIs, sprinkled with some vector math, to randomly distribute shapes along any given path. Let's stencil all the things! Learning how to fish Now, in the end, this effect is pretty simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@spren9er/upset-plots-with-observable-plot" rel="noopener noreferrer"&gt;Upset Plots with Observable Plot&lt;/a&gt; by &lt;a href="https://observablehq.com/@spren9er" rel="noopener noreferrer"&gt;@spren9er&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@spren9er/upset-plots-with-observable-plot" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F713c7c465ce814ed3b0d20e1513b3d1b76d0eca557f6f179fd79b0433cb2989f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Venn diagrams can be used for comparing two or three sets. They give you an overview of intersection(s) of sets. However, when you have more than three sets to compare, Venn diagrams are not appropriate anymore. One way of visualizing intersections of multiple sets is an Upset Plot. We don't go into detail of how you read Upset Plots here, you can take a look at the official website or  paper. There are libraries for R (UpSetR) and JavaScript (UpSet.js) available for creating your own Upset Plots.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/timechart" rel="noopener noreferrer"&gt;Observable TimeChart&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/timechart" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fb5c772e23e58a13c935d721ba487648b8c81be0f03530ccdfb126cf439fc5252.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TimeChart is an importable component for visualizing time-series data. It can show many signals time-aligned as space-efficient horizon charts. If you hover over a chart, all charts on the page show the value at that time, aiding interpretation. To use in your notebook: Then, call TimeChart and TimeAxis to create charts as described below. Configuration TimeChart expects a consistent x-axis across plots so that you can see coincident patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@analyzer2004/bcgmatrix" rel="noopener noreferrer"&gt;Interactive BCG Matrix Component&lt;/a&gt; by &lt;a href="https://observablehq.com/@analyzer2004" rel="noopener noreferrer"&gt;@analyzer2004&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@analyzer2004/bcgmatrix" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ffdb25174954be8c5ccd49bb8483f8e28c68992fd79002901a68a22be4d6d1a81.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The BCG Matrix (aka Growth-share matrix) provides a strategic view for analyzing business units, products or services by splitting them into four quadrants according to the growth and relative market share: question marks, stars, dogs and cows. Each quadrant has its own set of unique characteristics that represents a certain degree of profitability. This helps companies decide where to focus their resources to generate the most value, as well as where to cut their losses.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@analyzer2004/controlpanel-for-observable" rel="noopener noreferrer"&gt;Control Panel for Observable (Now with better responsiveness)&lt;/a&gt; by &lt;a href="https://observablehq.com/@analyzer2004" rel="noopener noreferrer"&gt;@analyzer2004&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@analyzer2004/controlpanel-for-observable" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F8adc3168c8c88792a6db66168cda136141c1564f4e7d7fb87fc79e0f90e1ec14.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Control Panel is a set of components to assist in easily building a compact and neat argument UI for visualizations. This is extremely useful when the chart has a large number of arguments. It allows the user to adjust all the arguments while simultaneously seeing the changes without the need to scroll back and forth.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/view" rel="noopener noreferrer"&gt;Composing viewofs with the view literal&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/view" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F596b8adbb62de11b1a6c5bf90a988950554b35dd2b312f85ac64c7a8c535162d.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets make custom UIs on Observable easy by composing views. We wrap the amazing hypertext literal with a interceptor that looks for key, view arguments. It uses the key to determine what field to map the view's value to in the container. The syntax of a 2 element array is inspired by Object.entries(...). By reusing the hypertext literal you are able to build your custom ui view using HTML, the best abstraction for layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@gianordoli/css-variables" rel="noopener noreferrer"&gt;CSS Variables #1: Basics&lt;/a&gt; by &lt;a href="https://observablehq.com/@gianordoli" rel="noopener noreferrer"&gt;@gianordoli&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@gianordoli/css-variables" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F44228e41994d8449ac8b1376efb5647256370038a7b59b4a6ac1168f2979a1a9.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CSS variables is a native way of creating ... variables in CSS! Their official name is actually “CSS custom properties” and they have been supported by all modern browsers since at least 2017. Unlike preprocessors variables, CSS custom properties have access to the DOM. A consequence of that, and the greatest advantage of them over preprocessor variables, is the possibility of declaring values that change based on media queries. More on that later.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/input-color" rel="noopener noreferrer"&gt;Color Input / Observable Inputs&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/input-color" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F24d8ffeeb6b5a1efd75656831644307fdfc3e5361d190bc669b63c248fc36ddd.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This input specifies an RGB color as a hexadecimal string &lt;code&gt;#rrggbb&lt;/code&gt;. The initial value defaults to black (&lt;code&gt;#000000&lt;/code&gt;) and can be specified with the value option. The color input is currently strict in regards to input: it does not accept any CSS color string. If you’d like greater flexibility, consider using D3 to parse colors and format them as hexadecimal. If you specify the datalist option as an array of hexadecimal color strings, the color picker will show this set of colors for convenient picking.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/input-form" rel="noopener noreferrer"&gt;Form Input / Observable Inputs&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/input-form" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F68d3e7d182f16aeaadbe0831cf32a00d8da6250f024804f17cd07b3c243b92d2.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form input combines a number of inputs into a single compound input. It’s intended for a more compact display of closely-related inputs, say for a color’s red, green, and blue channels. You can pass either an array of inputs to Inputs.form, as shown above, or a simple object with enumerable properties whose values are inputs. In the latter case, the value of the form input is an object with the same structure whose values are the respective input’s value.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/plot-box" rel="noopener noreferrer"&gt;Observable Plot: Box&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/plot-box" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff37f57de78af71bcdb6d90911938337a924aeed4cfdf79a233ea836dce825a28.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This notebook demonstrates how to use composite marks (arrays of marks) and transforms to create a box plot using Observable Plot. These functions (boxX and boxY) define a composite mark suitable for a box plot, applying the necessary&lt;br&gt;
statistical transforms. The boxes are grouped by y or x, respectively, if present. This map function returns only outliers, returning NaN for non-outliers (based on the whisker definitions below).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@fheyen/citation-js" rel="noopener noreferrer"&gt;Hello, citation-js!&lt;/a&gt; by &lt;a href="https://observablehq.com/@fheyen" rel="noopener noreferrer"&gt;@fheyen&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@fheyen/citation-js" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F068376a98b9241c583ee3b18ed4665ca67974c0dc5371482c562e1af1a1ca901.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;github.com/larsgw/citation.js Paste one or more DOIs, or other identifiers into the input textbox and this notebook will display the citation(s) as text and bibtex.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tophtucker/tree-input" rel="noopener noreferrer"&gt;Tree Input&lt;/a&gt; by &lt;a href="https://observablehq.com/@tophtucker" rel="noopener noreferrer"&gt;@tophtucker&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tophtucker/tree-input" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F12db56e2a50ce7c786b88a219c2c2833097e7fe2d48ee37b97c1a74a887e9466.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observable has a lovely default tree inspector for hierarchical data: But you can’t select a child and refer to it in code. With this you can: Options: children: accessor; defaults to &lt;code&gt;d =&amp;gt; d.children&lt;/code&gt; name: function returning string or DOM node; defaults to `d =&amp;gt; d.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/grid" rel="noopener noreferrer"&gt;Responsive grid&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/grid" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F64c43c3b27970e5eb239b73d0bd12227bfb67af5ff1dbd6e769706a860f49599.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Place views on responsive grid panels to create mobile friendly composite views quickly. Simple Uniform Coordinates... yet responsive The controls above are for building intuition over the coordinate system and grid parameters. Play with it! (you can instantiate it against your own grids too) Formally, there are n panels which rearrange to suit the screen resolutions. Typically three panels per row for desktop and 1 per row on mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@saneef/radar-chart" rel="noopener noreferrer"&gt;Radar Chart&lt;/a&gt; by &lt;a href="https://observablehq.com/@saneef" rel="noopener noreferrer"&gt;@saneef&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@saneef/radar-chart" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F9883dabe8ef2cdb892967681177827333dccfccd11455c66c8d2b99cab906664.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learning D3.js References Radar chart by Ben Welsh West Coast weather from Seattle to San Diego by Eric Lo Brand Personality Radar Chart by Jacob T.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@clhenrick/tooltip-d3-convention" rel="noopener noreferrer"&gt;Tooltip (D3 Convention)&lt;/a&gt; by &lt;a href="https://observablehq.com/@clhenrick" rel="noopener noreferrer"&gt;@clhenrick&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@clhenrick/tooltip-d3-convention" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fdec4952637a123be4dbc8dedcc0f907c44657ed1cd5d5c59ecd57330c4f863eb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This version of a Tooltip uses the D3JS convention of `selection.call()` to register the Tooltip's behavior. This is an improvement on my original version of a Tooltip Component.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@neocartocnrs/arrows" rel="noopener noreferrer"&gt;How to draw an arrow between 2 dots?&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/arrows" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F67129dcbeb2b9ad0c758b16fa0eca84cb3323140fecfbabf4e1da410c73e9bcf.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;undefined&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@oliviafvane/simple-pencil-ink-pen-effect-for-svg-path-using-filters" rel="noopener noreferrer"&gt;Simple pencil + ink pen effect for SVG path using filters&lt;/a&gt; by &lt;a href="https://observablehq.com/@oliviafvane" rel="noopener noreferrer"&gt;@oliviafvane&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@oliviafvane/simple-pencil-ink-pen-effect-for-svg-path-using-filters" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff078084bc101908eec43db4a0566c64f3835b599e1b01b0c4f124699ca045223.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fastrofella.files.wordpress.com%2F2019%2F12%2Fmouse.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fastrofella.files.wordpress.com%2F2019%2F12%2Fmouse.jpg"&gt;&lt;/a&gt; "By Quentin Blake" This is a simple way to do a pencil or ink pen effect for an SVG `&lt;code&gt;&amp;lt;path&amp;gt;\&lt;/code&gt;&lt;code&gt;, using the \&lt;/code&gt;&lt;code&gt;&amp;lt;feTurbulence/&amp;gt;\&lt;/code&gt;` noise filter. For more pencil effect examples on Observable, have a look at @matthewturk/pencil-effect-filters, and which also simplify and curve the path    @coreygirard/pencil-mercator, &lt;a class="mentioned-user" href="https://dev.to/d3"&gt;@d3&lt;/a&gt;/sketchy-earth and @visionscarto/pencil-globe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tutorials
&lt;/h2&gt;

&lt;p&gt;The Observable community likes to help others learn. There are tutorial notebooks dedicated to a wide variaty of topics. I write a lot about how to build applications on Observable, and a focus area for me in 2021 was &lt;a href="https://observablehq.com/@tomlarkworthy/ui-development" rel="noopener noreferrer"&gt;scaling UI development&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@neocartocnrs/tuto" rel="noopener noreferrer"&gt;Observable pour les géographes&lt;/a&gt; by &lt;a href="https://observablehq.com/@neocartocnrs" rel="noopener noreferrer"&gt;@neocartocnrs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@neocartocnrs/tuto" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F45b400b2de476e49141eb85e54fa8b1537f6cfbc93c5e7c095ea7eb7b340bcb0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depuis plus d'un an, je réalise des cartes et des visualisations de données avec Observable. Dans ce carnet, j'essaie d'expliquer ce que j'ai compris et pourquoi je considère que cet environnement est idéal pour créer des cartes. Je vais probablement améliorer ce matériel de présentation au fil du temps. N'hésitez pas à commenter et à suggérer des ajouts/changements. Commençons par une petite présentation introductive (Thanks Reveal.js). Ressources &lt;a href="https://towardsdatascience" rel="noopener noreferrer"&gt;https://towardsdatascience&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/analyzing-time-series-data" rel="noopener noreferrer"&gt;Analyzing Time Series Data&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/analyzing-time-series-data" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F51996f42707ec19bf33e84c3bb7b7a36cefc4444ab206f03c947a56b2c8d9828.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Approaches for working with data that changes over time Many of us work with data that is about time, tracking fluctuations by hour, day, week, month, etc. We need to know what caused a spike or dip, learn why the actuals diverged from the forecast, characterize typical daily, weekly, and seasonal patterns, or see how the correlation between two metrics depends on time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@tomlarkworthy/ui-development" rel="noopener noreferrer"&gt;Scaling User Interface Development&lt;/a&gt; by &lt;a href="https://observablehq.com/@tomlarkworthy" rel="noopener noreferrer"&gt;@tomlarkworthy&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/ui-development" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fbd72fc4663ce7c4d8ae9edbde0ba848ee67fc48b1efc45f875b8b49f210fa149.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Backend development is rapid, and yet... custom user interface development is still a huge timesink. UI development is a project bottleneck. This living document is a growing list of techniques I have learnt trying to overcome the UI development bottleneck. When I create complex applications, I need contextually sensitive user interfaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@daformat/union-polygon-and-area-of-rectangles-line-sweep-algorithm" rel="noopener noreferrer"&gt;Union polygon and area of rectangles (line sweep algorithm)&lt;/a&gt; by &lt;a href="https://observablehq.com/@daformat" rel="noopener noreferrer"&gt;@daformat&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@daformat/union-polygon-and-area-of-rectangles-line-sweep-algorithm" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff9fda5ccf9f74dcd190e79d06fdbffe75b0fdf376669aafd6946dc10d9d00e9f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given a set of unrotated and potentially overlapping rectangles, we want to compute the total area covered by their union and get their union polygon(s). One way to proceed is to first use a line sweep algorithm in order to subdivide the surface of the rectangles to a set of non-overlaping rectangles covering the same area. In other words, we want the resulting rectangles to be either contiguous or completely separated.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@jgaffuri/nice-scale-bar" rel="noopener noreferrer"&gt;How to make a nice scale bar&lt;/a&gt; by &lt;a href="https://observablehq.com/@jgaffuri" rel="noopener noreferrer"&gt;@jgaffuri&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@jgaffuri/nice-scale-bar" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F39c045801f34cecac591bfcaf4561fcf9a47c8efecee60dc69bdb416808e1d9f.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are some hints to make a scale bar with nice rounded values and divisions, for a map. Usually, a scale bar has to fit within a rectangular component, with limited dimensions. It is unlikely that the scale bar length corresponds to a nice rounded value (such as 100km) which can be easily divided into other nice values (such as 25km, 50km, 75km). In such case, it is more suitable to shorten a bit the scale bar to a lower length value which makes it nicer (see here for example).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mcmcclur/plot-for-mathematicians" rel="noopener noreferrer"&gt;Plot for mathematicians&lt;/a&gt; by &lt;a href="https://observablehq.com/@mcmcclur" rel="noopener noreferrer"&gt;@mcmcclur&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mcmcclur/plot-for-mathematicians" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff8520d72b8f2c5dca2a2ef12d8dff5d275d3652e11f6f0f47dc02c6be202f6c1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observable Plot is a free, open-source library from the folks at Observable designed to make it easy to generate many common types of charts for the visualization of data. The documentation is extensive and generally excellent. It's (understandably) focused on its usage with data, though, which obscures how easily it can be used to generate nice mathematical plots. The purpose of this notebook is to illustrate how we might use it to generate simple graphs of functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;The Observble community has built some impressive tooling to support data practitioners. The biggest news of the year was the introduction of &lt;a href="https://observablehq.com/@observablehq/introducing-observable-plot" rel="noopener noreferrer"&gt;Plot&lt;/a&gt; by the &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;Observable&lt;/a&gt; team, which allows rapid production of informative charts.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/plot" rel="noopener noreferrer"&gt;Observable Plot&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/plot" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observable Plot is a free, open-source JavaScript library to help you quickly visualize tabular data. It has a concise and (hopefully) memorable API to foster fluency — and plenty of examples to learn from and copy-paste. In the spirit of show don’t tell, below is a scatterplot of the height and weight of Olympic athletes (sourced from Matt Riggott), constructed using a dot mark. We assign columns of data (such as weight) to visual properties (such as the dot’s x), and Plot infers the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/data-wrangler" rel="noopener noreferrer"&gt;Data Wrangler&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/data-wrangler" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fc7bdee74b06348e673a39dfe28b048afd195cc8915bc7383471087c6a322553c.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data wrangling is often a complex and time consuming part of quantitative work.&lt;br&gt;
This (experimental) &lt;code&gt;Wrangler&lt;/code&gt; function provides an interface to guide code&lt;br&gt;
composition and also display the results. It uses the&lt;br&gt;
Arquero package, a&lt;br&gt;
robust data wrangling library based on the grammar of data manipulation. To get started, import the &lt;code&gt;Wrangler&lt;/code&gt; function from this notebook: Then, pass your data into the &lt;code&gt;Wrangler&lt;/code&gt; function, and access the results: As an example, here is an exploration of U.S. protest data&lt;br&gt;
(source).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/introducing-observable-plot" rel="noopener noreferrer"&gt;Introducing Observable Plot&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/introducing-observable-plot" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F1d5b86ef107a61b6cdcac8f4771621a449f7bc98ce327cab1c35d9ec3094a234.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are thrilled to announce Observable Plot, a new open-source JavaScript library for exploratory data visualization. Observable’s mission is to help everyone make sense of the world with data. To succeed, we need to make visualization easier and faster, both to learn and to practice. Less chore, more joy. We believe people will be more successful finding and communicating insights if they can “use vision to think” instead of wrestling with the intricacies of programming.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/summary-table" rel="noopener noreferrer"&gt;Summary Table&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/summary-table" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F003eb167f7e2fc6aa25532f878bb6176ce3cb46c977754aef2bfdad9642117e6.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want a quick visual overview of a dataset? To get started, import the new (experimental) &lt;code&gt;SummaryTable&lt;/code&gt; function from this notebook: Then, pass your data into the &lt;code&gt;SummaryTable&lt;/code&gt; function. As an example, here is a summary table of the Penguins dataset (described in detail here).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/future-of-data-work-collaboration-and-no-limits" rel="noopener noreferrer"&gt;Future of Data Work: Collaboration and No Limits&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/future-of-data-work-collaboration-and-no-limits" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fef0da55ceedda5d1c9bff7d452ddcc5097ac26524ae3a9fd4015a5d889cd80e2.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine all the ways that data flows through an organization.  People explore and analyze data.  People ask critical questions of data.  All of these people bring diverse skills when engaging with data - data scientists, data analysts, data engineers, developers, product managers and decision makers. All of these people seek insights from data for decisions toward better business outcomes, but they all use a different set of techniques and tools to do their jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@asg017/introducing-dataflow" rel="noopener noreferrer"&gt;Introducing Dataflow, a self-hosted Observable Notebook Editor&lt;/a&gt; by &lt;a href="https://observablehq.com/@asg017" rel="noopener noreferrer"&gt;@asg017&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@asg017/introducing-dataflow" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F1416d927846d192b5ce98acedddcdce372a1a38c96a3775d7b54e98d9480e90a.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dataflow is a new tool that lets you run, edit, and compile Observable notebooks locally on your own computer, with any text editor you want! If you have ever wanted to save + run Observable notebooks as files on your computer (for easier integration with git), then Dataflow will be very helpful! Quickstart Check out the documentation for more! In Dataflow, Observable notebooks are files on your computer, with a `.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@mkfreeman/plot-tooltip" rel="noopener noreferrer"&gt;Plot Tooltip&lt;/a&gt; by &lt;a href="https://observablehq.com/@mkfreeman" rel="noopener noreferrer"&gt;@mkfreeman&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@mkfreeman/plot-tooltip" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fde7e4276e777465b25562d23a0f10a2a12d5af35665e0a3eb4811b743ba36f96.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two approaches for easily adding tooltips to your plots Tooltips on all plots Want to add tooltips to all of your plots? Import &lt;code&gt;plot&lt;/code&gt; from this notebook, then simply set a &lt;code&gt;title&lt;/code&gt; attribute for any mark in your plot call (note, this apporach only works with &lt;code&gt;Plot.plot()&lt;/code&gt; calls, not `Plot.MARK().&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/future-of-data-work-q-a-with-mike-bostock" rel="noopener noreferrer"&gt;Future of Data Work: Q&amp;amp;A with Mike Bostock&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/future-of-data-work-q-a-with-mike-bostock" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2F3ce3bc5b4e1aa0129e028fe30e13126cfc541ac24173d12e83554e88a6b89214.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mike Bostock has been busy. Our CTO and co-founder is the author of D3.js, an open-source library for data visualization which celebrated its 10th anniversary this year, and Observable Plot, a new library for exploratory visualization that builds on D3. Not to mention designing and developing features for the Observable platform. For this week’s Future of Data Work series, I sat down with Mike for a Q&amp;amp;A about his motivations and how he hopes people work with data in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/whats-new-in-observable-plot-0-2-7" rel="noopener noreferrer"&gt;What’s new in Observable Plot (0.2.7)?&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/whats-new-in-observable-plot-0-2-7" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fa6f3caf2c994ba47438b2c97242a37edc080b6ea9ffc63a1bd73e2fc362ed0c4.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more on the latest releases, please see our Changelog. Plot now allows you to inspect scales! Rendered plots (either SVG or figure elements) now expose a plot.scale(name) method that returns an object describing the scale in-use, including any materialized defaults. For example, say you made a scatterplot showing the rise of global surface temperature anomaly using a quantile color scale. To retrieve the color scale for this plot, call plot.scale passing in the name “color”.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@yurivish/building-a-better-beeswarm" rel="noopener noreferrer"&gt;Building a Better Beeswarm&lt;/a&gt; by &lt;a href="https://observablehq.com/@yurivish" rel="noopener noreferrer"&gt;@yurivish&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@yurivish/building-a-better-beeswarm" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Fe7a1ce187487ba60e18347cf4461a9c8ec2855bc4c50aa2703a2f68ed9c8dccd.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beeswarm plots are a fun, visually pleasing alternative to histograms that can be used when data is sufficiently small and you want to plot every point. This notebook contains an algorithm for accurate beeswarm plots with circles that vary in radius, using a simple implementation of this algorithm by James Trimble. In addition to being faster to compute than a force-directed layout, it has the advantage of improved accuracy since it preserves the precise x position of each data point.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@me-momo/g2plot-2-3-23-released" rel="noopener noreferrer"&gt;G2Plot 2.3.23 Released 🎉&lt;/a&gt; by &lt;a href="https://observablehq.com/@me-momo" rel="noopener noreferrer"&gt;@me-momo&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@me-momo/g2plot-2-3-23-released" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Feac6fefc38b7df256dfecd25e1c3188e17f2bef9d98b0a6dbe9f3f6ad8c571e5.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;G2Plot is an interactive and responsive charting library based on the grammar of graphics, which enables users to generate high-quality statistical charts through a few lines of code easily. G2 in "G2Plot" means the grammar of graphics and pays homage to ggplot2. What's New in G2Plot 2.3.23 We've added two new plot types: &lt;code&gt;Facet&lt;/code&gt; and &lt;code&gt;Violin&lt;/code&gt;, support &lt;code&gt;localization&lt;/code&gt;, enhance several plots, like &lt;code&gt;Sunburst&lt;/code&gt;, &lt;code&gt;Stock&lt;/code&gt;, &lt;code&gt;Heatmap&lt;/code&gt; and &lt;code&gt;Box&lt;/code&gt; and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://observablehq.com/@observablehq/observable-github" rel="noopener noreferrer"&gt;Observable + GitHub 💞&lt;/a&gt; by &lt;a href="https://observablehq.com/@observablehq" rel="noopener noreferrer"&gt;@observablehq&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com/@observablehq/observable-github" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Fthumbnail%2Ff2bb6043bdab43d40f87f5a2ed01df92047dbb20e5e8ef594accd3846e744abf.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visualizing data is more important than ever There are ~56M+ developers on GitHub and growing. As more developers collaborate, complexity increases. It’s easy for technical matters to consume all our attention, but are we asking the right questions? Is this the right data to be looking at? What are we trying to understand? How to we stay focused on insight? On thinking? How do we - all of us - shift our behavior in order to find answers together? Visualizing data is ONE of the ways we find answers together.&lt;/p&gt;




</description>
      <category>programming</category>
      <category>design</category>
      <category>datascience</category>
      <category>news</category>
    </item>
    <item>
      <title>Starting Github Action Workflows From Observable</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Mon, 27 Dec 2021 09:06:51 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/starting-github-action-workflows-from-observable-2gfg</link>
      <guid>https://dev.to/tomlarkworthy/starting-github-action-workflows-from-observable-2gfg</guid>
      <description>&lt;p&gt;Using Observable as the front end for Github Actions is a great way to provide a nice interface without the hassle of hosting anything. Recently I developed a &lt;a href="https://observablehq.com/@tomlarkworthy/repository-dispatch"&gt;notebook library&lt;/a&gt; which allows authenticated &lt;em&gt;and&lt;/em&gt; unauthenticated users to kick off Action workflow via a repository dispatch.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#repository_dispatch"&gt;repository dispatch&lt;/a&gt; triggers &lt;a href="https://docs.github.com/en/actions"&gt;Github Action&lt;/a&gt; workflows via an authenticated HTTP request. See the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#repository_dispatch"&gt;documentation&lt;/a&gt; on Github. From there you can do all kinds of things, even programmatically creating commits.&lt;/p&gt;

&lt;p&gt;We document two major ways to trigger actions from an &lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt; notebook.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;authenticated, where notebook &lt;strong&gt;readers&lt;/strong&gt; provide the access token to trigger a workflow&lt;/li&gt;
&lt;li&gt;pre-authenticated, where the notebook &lt;strong&gt;author&lt;/strong&gt; configures a proxy using their own creds, so anybody can trigger workflows on the author's blessed path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {dispatch, createDispatchProxy} from '@tomlarkworthy/repository-dispatch'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Repository Dispatch Client Library for Observable
&lt;/h2&gt;

&lt;p&gt;The authenticated dispatch function takes an access token, plus the other options, to perform the dispatch&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--37fEW_0M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ne31cnluhffriull1edr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--37fEW_0M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ne31cnluhffriull1edr.png" alt="Image description" width="880" height="277"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function dispatch(
  token,
  { owner, repo, event_type = "event_type", client_payload = undefined } = {}
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Usecase 1: Pass Personal Token In Notebook
&lt;/h2&gt;

&lt;p&gt;To use our simple dispatch function, we need a Github API access token. A simple way is to ask the reader for one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ajRaRxHF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6y1rdvl4pjk4q1pqg1h8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ajRaRxHF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6y1rdvl4pjk4q1pqg1h8.png" alt="Image description" width="786" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If you use &lt;a href="https://observablehq.com/@tomlarkworthy/local-storage-view#localStorageView"&gt;localStorageView&lt;/a&gt; you can remember the input across page sessions in local storage.&lt;/p&gt;

&lt;p&gt;There are situations where you don't want the reader to provide a token. For this, we need to introduce a secure environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Usecase 2: Use Shared Secret Stored in &lt;a href="https://webcode.run"&gt;Webcode.run&lt;/a&gt; to pre-authorize a configuration
&lt;/h2&gt;

&lt;p&gt;Code executed on &lt;a href="https://webcode.run"&gt;webcode.run&lt;/a&gt; is remote, and so secrets are not exposed to notebook readers. By creating a proxy to do the request, we can expose Github workflows to the public without requiring them to supply tokens. Instead, the secret can be set in the inline UI (or at &lt;a href="https://observablehq.com/@endpointservices/secrets"&gt;@endpointservices/secrets&lt;/a&gt;). But default, the secret should be called &lt;code&gt;github_token&lt;/code&gt;, but you can override this in the &lt;code&gt;createDispatchProxy&lt;/code&gt; arguments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EoNVwhNP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nymisa4jwsbazl2cpaxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EoNVwhNP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nymisa4jwsbazl2cpaxu.png" alt="Image description" width="880" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which you can call to invoke a github workflow&lt;/p&gt;

&lt;p&gt;Note, configuration of the dispatch function has to be done when creating the proxy so that the endpoint cannot be used against arbitrary repositories. An opt-in exception is that when the &lt;code&gt;client_payload&lt;/code&gt; arg is set to undefined, the client can pass a value of &lt;code&gt;client_payload&lt;/code&gt; as the first argument (this is to enable passing data into a workflow).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;viewof remoteDispatch = createDispatchProxy({
  owner: "tomlarkworthy",
  repo: "octokit-test"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of a &lt;code&gt;createDispatchProxy&lt;/code&gt; call is a view. The UI is the default &lt;a href="https://webcode.run"&gt;webcode.run&lt;/a&gt; UI, which provides a method of setting the secret once you are logged in. You will need to create a stored secret called &lt;code&gt;github_token&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; bind it to the endpoint. If you forget to configure the secret, the underlying Octokit API will throw &lt;code&gt;HTTPError: Not Found&lt;/code&gt; exceptions. Your notebook needs to be published for it to work when you are logged out.&lt;/p&gt;

&lt;p&gt;The data channel of the returned &lt;code&gt;createDispatchProxy&lt;/code&gt; view is a dispatch function. When called, it will run the dispatch remotely, but importantly, you do not need to pass any authentication credentials. So you can just place it in a button for anybody to use. Try mine below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--26UUEErE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lmawy3o0kkzll1khbmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--26UUEErE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lmawy3o0kkzll1khbmd.png" alt="Image description" width="880" height="129"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Refer to the &lt;a href="https://observablehq.com/@tomlarkworthy/repository-dispatch"&gt;source notebook&lt;/a&gt; for the interactive demo&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The above workflow will trigger &lt;a href="https://github.com/tomlarkworthy/octokit-test/blob/main/.github/workflows/log-http.yml"&gt;this workflow&lt;/a&gt; which in turn write back into the repository as a log entry found &lt;a href="https://github.com/tomlarkworthy/octokit-test/blob/main/log/http.log"&gt;here&lt;/a&gt;. So you can see for yourself that your triggers are registered by inspecting the timestamps in the logs!&lt;/p&gt;

&lt;p&gt;The example here will cause a commit to occur in a repository you would not normally have permissions to! Now imagine what is possible when you can offload storage into git repositories for public users on &lt;a href="https://observablehq.com"&gt;Observablehq&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing workflows
&lt;/h2&gt;

&lt;p&gt;I recommend &lt;a href="https://dev.to/teamhive/triggering-github-actions-using-repository-dispatches-39d1"&gt;Triggering GitHub Actions Using Repository Dispatches&lt;/a&gt; for a good overview of the technique.&lt;/p&gt;

&lt;p&gt;At the beginning of a workflow file (stored in &lt;code&gt;/.github/workflows/&amp;lt;name&amp;gt;.yml&lt;/code&gt;) you announce how the workflow is triggered. For an externally HTTP triggered workflow you use &lt;code&gt;repository_dispatch&lt;/code&gt;. You also can &lt;em&gt;optionally&lt;/em&gt; restrict what the &lt;code&gt;event_type&lt;/code&gt; must be through the &lt;code&gt;types&lt;/code&gt; array property:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: HTTP triggered workflow
on:
  repository_dispatch:
  types: [start-example-workflow]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you list jobs with steps. Steps can include anything off the vast Github Actions &lt;a href="https://github.com/marketplace?type=actions"&gt;marketplace&lt;/a&gt; or just simple shell scripting!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  commit-timestamp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Log workflow execution in log
        run: |
          mkdir -p ./log
          echo "$(date '+%Y-%m-%d %H:%M:%S') inbound HTTP" &amp;gt;&amp;gt; ./log/http.log
      - name: Commit updated log
        run: |
          git config --global user.name 'Log External HTTP'
          git config --global user.email 'robot@webcode.run'
          git add ./log/http.log
          git commit -am "Log inbound HTTP"
          git push

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observable + Github Actions
&lt;/h3&gt;

&lt;p&gt;Observable is a great way to provide a lightweight interactive UI with minimal development effort. When paired with Github actions you can use Observable to create a nice GUI over the Action primitives. You can also use Actions to enhance the Observable experience, for example, auto-backing up notebooks into a Github repo using the &lt;a href="https://observablehq.com/@endpointservices/onversion"&gt;onversion hook&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>observable</category>
      <category>programming</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Creating long lasting content on Observable</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Tue, 07 Dec 2021 10:10:55 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/creating-long-lasting-content-on-observable-30b3</link>
      <guid>https://dev.to/tomlarkworthy/creating-long-lasting-content-on-observable-30b3</guid>
      <description>&lt;p&gt;I am 100% convinced &lt;a href="//observablehq.com"&gt;Observable&lt;/a&gt; is the right way to share computational thought. Sometimes though, it feels like notebooks are only read once during their birth on the &lt;a href="https://observablehq.com/trending"&gt;&lt;code&gt;/trending&lt;/code&gt;&lt;/a&gt; page. However, I am happy to report you &lt;em&gt;can&lt;/em&gt; create long-lasting content with lots of repeat visitors and it is surprisingly straightforward to do. In this article, I will share what I have learned on my &lt;a href="//observablehq.com"&gt;Observable&lt;/a&gt; journey.&lt;/p&gt;

&lt;p&gt;I took data-driven approach to find out what works. After wiring my pages to &lt;a href="https://observablehq.com/@endpointservices/plausible-analytics"&gt;Plausible Analytics&lt;/a&gt; I could finally get a birds-eye view of my readership. I found the results fascinating! Here are my page views in the last month (Nov. 2021):-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--It1f1CF0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze0xdj33o9zl4bu08xug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--It1f1CF0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze0xdj33o9zl4bu08xug.png" alt="Image description" width="880" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Collaborate and solve a platform need
&lt;/h2&gt;

&lt;p&gt;My #1 content by a long shot is &lt;em&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/view"&gt;Composing viewofs with the &lt;strong&gt;view&lt;/strong&gt; literal&lt;/a&gt;&lt;/em&gt;. This is a super useful utility that helps create complex UIs on the Observablehq platform. It fills a gap in the platform primitives, and clearly, I was not the only one who needed it. I collaborated with &lt;a href="https://dev.to/@mootari"&gt;@mootari&lt;/a&gt; and &lt;a href="https://dev.to/@mbostock"&gt;@mbostock&lt;/a&gt; on it, and it was developed to support a consulting job that needed a production-level UI.&lt;/p&gt;

&lt;p&gt;Now, I have written &lt;em&gt;many&lt;/em&gt; libraries for &lt;strong&gt;Observable&lt;/strong&gt;. None of them do as well as this one. I think this one resonated so much harder because it was the product of a business need, and it was refined via conversations with others. So my first tip for creating great content is: &lt;em&gt;talk to others in the community!&lt;/em&gt; Use the &lt;a href="https://talk.observablehq.com/"&gt;forum&lt;/a&gt; or leave comments on people's notebooks!&lt;/p&gt;

&lt;p&gt;However, most of my other utilities are not in my top ten, which implies that it is actually very hard to develop a library that others will use. It took me 6 months of full-time work on Observable to understand enough to craft that library. So generally, I would say it is not a very easy path. The &lt;a href="https://observablehq.com/@tomlarkworthy/saas-tutorial"&gt;saas-tutorial&lt;/a&gt; and &lt;a href="https://observablehq.com/@tomlarkworthy/firebase"&gt;Firebase&lt;/a&gt; are other examples of Observable utilities that have done quite well, but again, they took a lot of effort to produce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go viral!
&lt;/h2&gt;

&lt;p&gt;My next most popular content was created well over a year ago and is still going strong. The &lt;em&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/hacker-favourites-analysis"&gt;Most favorited Hacker News posts of all time&lt;/a&gt;&lt;/em&gt; went stratospheric on &lt;em&gt;&lt;a href="//news.ycombinator.com"&gt;Hackernews&lt;/a&gt;&lt;/em&gt;, getting &lt;a href="https://news.ycombinator.com/item?id=24351073"&gt;1260 upvotes&lt;/a&gt; and linked to all over the web.&lt;/p&gt;

&lt;p&gt;It is very satisfying to me that good content like that is rewarded with a steady stream of traffic continuously for years. It gives me the motivation to keep trying. The Hackenews Favourites notebook really is an excellent article as it surfaces the &lt;em&gt;crème de la crème&lt;/em&gt; of Hackernews which was previously unknown.&lt;/p&gt;

&lt;p&gt;Obviously writing good viral content is kind of hard. If I had the formula to do it I would have more, but I am satisfied that if you do figure one out, you will get good readership numbers. The notebook &lt;a href="https://www.facebook.com/hn.hiren.news/posts/2979774452265797"&gt;Sign a PDF and Adobe: Go Fuck Yourself&lt;/a&gt; also was a Hackernews hit, though not to the same level, but that's also why it has reached the top ten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promote your content outside of &lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The notebooks mentioned in the previous tips represent my &lt;em&gt;"smash hits"&lt;/em&gt;, which are a bit random to produce. They were generated when inspiration hit. On the other hand, the remaining notebooks are doing well, and did well for fairly simple and repeatable reasons. This section is the most important one as this is where luck no longer plays a role.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;notebook&lt;/th&gt;
&lt;th&gt;views&lt;/th&gt;
&lt;th&gt;likes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/switch-dataflow"&gt;switch-dataflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/merge-dataflow"&gt;merge-dataflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/mip"&gt;mip&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://observablehq.com/@tomlarkworthy/sound-cloud-reactive-audio-visualizer"&gt;sound-cloud-reactive-audio-visualizer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What's interesting with these notebooks is that their success is not dependant on Observable "likes". The real reason why these set of notebooks do so well is that they were all promoted &lt;em&gt;outside&lt;/em&gt; of Observable.&lt;/p&gt;

&lt;p&gt;The switch and merge dataflow tutorials are blog posts that were syndicated to &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt; and &lt;a href="https://medium.com"&gt;medium&lt;/a&gt; using the meta-notebook &lt;a href="https://observablehq.com/@tomlarkworthy/blogify"&gt;Blogify&lt;/a&gt;. The &lt;a href="https://observablehq.com/@tomlarkworthy/mip"&gt;Mixed Integer Programming (mip)&lt;/a&gt; notebook was shared on &lt;a href="https://www.reddit.com/r/optimization/comments/pil5ao/i_made_a_milp_frontend_in_a_reactive_javascript/"&gt;r/optimization&lt;/a&gt; and the &lt;a href="https://observablehq.com/@tomlarkworthy/sound-cloud-reactive-audio-visualizer"&gt;reactive visualizer&lt;/a&gt; on &lt;a href="https://www.reddit.com/r/edmproduction/comments/j0vj3o/reactive_audio_visuals_music_video_creator/"&gt;r/edmproduction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These notebooks were not big hits! But this is what is really cool about them. By simply cross-posting them off the platform has a &lt;em&gt;huge&lt;/em&gt; effect on their longevity, regardless of their popularity on Observable. Their SEO is good. &lt;em&gt;This is great news.&lt;/em&gt; Performance on &lt;code&gt;/trending&lt;/code&gt; is not indicative of performance elsewhere. You simply have to cross-post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I am not a gifted content producer, but you don't need to be! It's easy to share your work on &lt;a href="https://www.reddit.com/"&gt;Reddit&lt;/a&gt;, and that act alone makes a huge difference to the longevity of your work. I think most people on Observable produce much better work than me. &lt;em&gt;You are not doing it justice if it stays only on the platform&lt;/em&gt;. If you just go out and share it on places like &lt;a href="https://www.reddit.com/r/dataisbeautiful/"&gt;r/dataisbeautiful&lt;/a&gt; or &lt;a href="https://news.ycombinator.com"&gt;hackernews&lt;/a&gt; you will amplify your impact &lt;em&gt;massively&lt;/em&gt;. This is the simple secret of creating long-lasting content on &lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>observable</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>writing</category>
    </item>
    <item>
      <title>How to 1-of-n switch Dataflow streams on Observable</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Wed, 24 Nov 2021 12:14:59 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/how-to-1-of-n-switch-dataflow-streams-on-observable-5710</link>
      <guid>https://dev.to/tomlarkworthy/how-to-1-of-n-switch-dataflow-streams-on-observable-5710</guid>
      <description>&lt;p&gt;&lt;em&gt;In this series, I will explore programming techniques for the notebook platform &lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt;. Today I am looking at how to direct a reactive Dataflow stream to one-of-n downstream cells. This article assumes you are familiar with &lt;a href="https://observablehq.com/@observablehq/reactive-dataflow"&gt;Observable's non-linear reactive program flow&lt;/a&gt; already.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ec3dJ2D_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aco7hb1flie6v17y267a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ec3dJ2D_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aco7hb1flie6v17y267a.png" alt="Image description" width="880" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes we want control the dataflow with logic. If we have a cell called &lt;em&gt;data&lt;/em&gt; streaming values, we might like to direct those updates to either &lt;em&gt;cell_1&lt;/em&gt; or &lt;em&gt;cell_2&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JceiwPs_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7yil1wbiqhfkmf656i9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JceiwPs_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7yil1wbiqhfkmf656i9.png" alt="Image description" width="880" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can programatically trigger dataflow by calling &lt;em&gt;dispatchEvent&lt;/em&gt; on its enclosing view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Oa7PnZ0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwjn6gdz576vusn1uvjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Oa7PnZ0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwjn6gdz576vusn1uvjd.png" alt="Image description" width="880" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create a lightweight view with &lt;code&gt;Inputs.input&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D25Fsu_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/633fwcsdw7dl3rtnpq77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D25Fsu_4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/633fwcsdw7dl3rtnpq77.png" alt="Image description" width="880" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now dataflow is guided by the value of the &lt;em&gt;choice&lt;/em&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l1w6jtuH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kb3ya1upwxxbk279t816.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l1w6jtuH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kb3ya1upwxxbk279t816.png" alt="Image description" width="880" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it a try on the live notebook at &lt;a href="https://observablehq.com/@tomlarkworthy/switch-dataflow"&gt;@tomlarkworthy/switch-dataflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you find that useful and that you find other useful ways to manipulate dataflow!&lt;/p&gt;

</description>
      <category>observable</category>
      <category>functional</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Productionizing Observable Notebooks with 3rd party Active Monitoring</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Sun, 14 Nov 2021 11:43:13 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/productionizing-observable-notebooks-with-3rd-party-active-monitoring-4jkg</link>
      <guid>https://dev.to/tomlarkworthy/productionizing-observable-notebooks-with-3rd-party-active-monitoring-4jkg</guid>
      <description>&lt;h1&gt;
  
  
  Productionizing Observable Notebooks with 3rd party Active Monitoring
&lt;/h1&gt;

&lt;p&gt;I keep my &lt;a href="https://observablehq.com/@tomlarkworthy?tab=notebooks"&gt;200+ public notebooks&lt;/a&gt; on the &lt;a href="https://observablehq.com"&gt;observablehq.com&lt;/a&gt; platform running by actively monitoring them with a 3rd party monitoring tool &lt;a href="https://uptimerobot.com"&gt;uptimerobot&lt;/a&gt;. Here is how I got it connected, and how I did it unobtrusively without altering the original notebooks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pvNhG7mx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/e0682d6edd9f1b0114481a16ff6b8de4cb08ae6bc6673d6b76b0b01ba1b1f71a83e01e852f93df939157947a6edf100f1c4e175ff032871d1fd2fd8202b20d82" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pvNhG7mx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/e0682d6edd9f1b0114481a16ff6b8de4cb08ae6bc6673d6b76b0b01ba1b1f71a83e01e852f93df939157947a6edf100f1c4e175ff032871d1fd2fd8202b20d82" alt="" width="200" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Broken notebooks are a big problem. I have encountered many broken notebooks on &lt;a href="https://observablehq.com"&gt;observablehq.com&lt;/a&gt;, and then much later, I have written many more. The thing is, I did not know my old notebooks were broken until I randomly checked! I don't want to host broken notebooks, but I can't be checking 200 notebooks every week, it doesn't scale! Instead, I figured out a way to bring off-the-shelf monitoring solutions to the notebook ecosystem. The best thing is that it can be applied to any notebook. There is no special library or anything to depend upon &lt;em&gt;etc.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the &lt;a href="https://github.com/observablehq/runtime"&gt;Observablehq runtime&lt;/a&gt; to create a &lt;a href="https://observablehq.com/@endpointservices/healthcheck"&gt;Healthcheck&lt;/a&gt; Metabook
&lt;/h3&gt;

&lt;p&gt;Did you know that all notebooks are packed as ES6 modules, and you can run them inside the open-source &lt;a href="https://github.com/observablehq/runtime"&gt;Observablehq runtime&lt;/a&gt; programmatically? (see &lt;em&gt;&lt;a href="https://observablehq.com/@observablehq/downloading-and-embedding-notebooks"&gt;Advanced Embedding and Downloading&lt;/a&gt;&lt;/em&gt;) This means a notebook can find and run code found in another notebook. I call these types of notebooks &lt;em&gt;metabooks&lt;/em&gt; as they are higher-order notebooks (notebooks whose input is another notebook).&lt;/p&gt;

&lt;p&gt;So, the &lt;a href="https://observablehq.com/@endpointservices/healthcheck"&gt;healthcheck metabook&lt;/a&gt;, given a target notebook, runs the target on an internal &lt;a href="https://github.com/observablehq/runtime"&gt;Observablehq runtime&lt;/a&gt; and looks for errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetNotebook&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.observablehq.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.js?v=3`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetNotebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// cell observers&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="nx"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// cell threw an error&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the runtime hooks we can detect if a cell throws an error, which will pick up many common forms of notebook rot. To attach 3rd party monitoring though, this error detector needs to be exposed as a HTTP service for 3rd party consumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing a HTTP service with &lt;a href="https://webcode.run"&gt;WEBcode.run&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We use a Observable native functions-as-a-service runtime &lt;a href="https://webcode.run"&gt;WEBcode.run&lt;/a&gt; to expose an endpoint on the internet.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://webcode.run"&gt;WEBcode.run&lt;/a&gt; you define a handler for &lt;em&gt;requests&lt;/em&gt; that write back to a &lt;em&gt;response&lt;/em&gt;. Because it executes in the notebook, you get access to all the notebook functionality. In our case, we decode a target notebook from a URL parameter. The API is modelled after the &lt;a href="http://expressjs.com/en/api.html"&gt;Express 4.0 API&lt;/a&gt; (see &lt;a href="http://expressjs.com/en/api.html#req"&gt;req&lt;/a&gt;, &lt;a href="http://expressjs.com/en/api.html#res"&gt;res&lt;/a&gt; for details).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// For a simple URL we use the default name&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Read the target notebook.&lt;/span&gt;
    &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;excludes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// start health checking&lt;/span&gt;

    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// There is no clear stopping point so we just run it for X seconds&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewof&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// collect errors&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;
          &lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;reusable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This does not support concurrent operations&lt;/span&gt;
    &lt;span class="na"&gt;modifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orchistrator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// This endpoint can call other endpoints&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our handler starts the healthcheck process, and after 5 seconds reports, if errors are found.&lt;/p&gt;

&lt;p&gt;So, to check for errors on notebook &lt;a href="https://observablehq.com/@tomlarkworthy/view"&gt;@tomlarkworthy/view&lt;/a&gt;, we make a request using curl to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl 'https://webcode.run/observablehq.com/@endpointservices/healthcheck?target=@tomlarkworthy/view'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because 3rd party monitoring tools typically just check HTTP status codes, our handler returns a status code 503 (unavailable) if errors are detected, or 200 (OK) otherwise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attaching UptimeRobot
&lt;/h3&gt;

&lt;p&gt;UptimeRobot is a very simple to use active monitoring service and it will provide 50 monitoring jobs for FREE!&lt;/p&gt;

&lt;p&gt;Active monitoring will poll the provided URL on a schedule, log the status, and alert via email/SMS/webhook if an error code is seen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kA2VJYQX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/2253230f13912f8f402d77710dd58d91ee27451725ec8611d3955da5dc61114cbb903ac928be3ef3e3500d37976f636bee36217819c157e7efe82c2930ccd779" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kA2VJYQX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/2253230f13912f8f402d77710dd58d91ee27451725ec8611d3955da5dc61114cbb903ac928be3ef3e3500d37976f636bee36217819c157e7efe82c2930ccd779" alt="" width="880" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit tests, Sentry
&lt;/h3&gt;

&lt;p&gt;Being able to actively monitor my notebooks for errors has completely changed my anxiety levels. By looking at my dashboard I can see that all my main notebooks are running well. As I am building a business upon &lt;a href="https://observablehq.com/"&gt;Observablehq&lt;/a&gt;, I must keep everything working, so seeing my code has run successfully is reassuring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ILb1V8fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/2dccd49d91a9b918ebca77f540291631289666e86571005ab9b76ccdea1a389ba6e7370edbf2e51bc0a469dfea04643e029c76d4d524e6d48d29e0804b5fa72c" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ILb1V8fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static.observableusercontent.com/files/2dccd49d91a9b918ebca77f540291631289666e86571005ab9b76ccdea1a389ba6e7370edbf2e51bc0a469dfea04643e029c76d4d524e6d48d29e0804b5fa72c" alt="" width="880" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest change is that when an error is reported by an end-user, I now convert that issue into a &lt;a href="https://observablehq.com/@tomlarkworthy/testing"&gt;unit test&lt;/a&gt;. Because unit tests errors are runtime errors, a healthcheck over a notebook containing a test suite will alert on unit test failures. So now I can fairly confidently ensure that particular issues never happen again.&lt;/p&gt;

&lt;p&gt;I also use &lt;a href="https://sentry.io"&gt;sentry.io&lt;/a&gt; to automatically report errors, whether generated by user usage, or by healthcheck monitoring (see this &lt;a href="https://observablehq.com/@endpointservices/sentry-io"&gt;notebook&lt;/a&gt;). Again, for low usage, &lt;a href="https://sentry.io"&gt;Sentry.io&lt;/a&gt; is a FREE service. Sentry compliments &lt;a href="https://uptimerobot.com/"&gt;UptimeRobot&lt;/a&gt; by logging context around an error, and also it runs in user devices like iPhone which can sometimes have their unique issues.&lt;/p&gt;

&lt;p&gt;I am very excited that we can take a developer-friendly product like &lt;a href="https://observablehq.com/"&gt;Observablehq&lt;/a&gt; and still fit it into the existing DevOps infrastructure. It suggests that building high-quality software on &lt;a href="https://observablehq.com/"&gt;Observable&lt;/a&gt; is possible, and maybe preferable. I love &lt;a href="https://observablehq.com/"&gt;Observable&lt;/a&gt;, in that, for all its workflow cleverness, it's still just Javascript running idiomatically in a browser, and thus, we can leverage existing specialist tools that already exist in the ecosystem. There is no need to reinvent the wheel when using Observable! I hope this story helps you get to the reliability levels you want out of your software hosted on &lt;a href="https://observablehq.com/"&gt;Observablehq&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I tweet lots of Observable stuff at &lt;a href="https://twitter.com/tomlarkworthy"&gt;@tomlarkworthy&lt;/a&gt;, and occasionally launch things on &lt;a href="https://www.producthunt.com/@tomlarkworthy"&gt;Product Hunt&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>monitoring</category>
      <category>observable</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to dataflow merge on Observable</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Fri, 05 Nov 2021 19:34:44 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/how-to-dataflow-merge-on-observable-3965</link>
      <guid>https://dev.to/tomlarkworthy/how-to-dataflow-merge-on-observable-3965</guid>
      <description>&lt;h1&gt;
  
  
  How to dataflow merge on &lt;a href="https://observablehq.com" rel="noopener noreferrer"&gt;Observable&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;In this series I will explore programming techniques for the notebook platform &lt;a href="https://observablehq.com" rel="noopener noreferrer"&gt;Observable&lt;/a&gt;. Today I am looking at how to&lt;/em&gt; merge &lt;em&gt;multiple reactive Dataflow streams into a single stream. This article assumes you are familiar with &lt;a href="https://observablehq.com/@observablehq/reactive-dataflow" rel="noopener noreferrer"&gt;Observable's non-linear reactive program flow&lt;/a&gt; already.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A common situation is this: you have a notebook that performs some useful task, and you want to offer a few different ways to start that task. For example, starting something based on configuration obtained from either URL parameters, local storage or a manual UI. It's a little trickier than you would hope because, if each of these methods are in their own cell [1], then we have multiple distinct data flow pathways converging into a single flow.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;[1] &lt;em&gt;and they probably should be because of separation-of-concerns&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwjcvs9ydp8fvl7ajhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwjcvs9ydp8fvl7ajhm.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core difficulty is if there is no data in a source, its dataflow won't tick, so if the business logic depends on all the input sources, the business logic will not tick either. So this pattern described here will joins the business logic to the sources a different way so that if any of them tick, the business logic will tick too.&lt;/p&gt;

&lt;p&gt;In &lt;a href="http://reactivex.io/" rel="noopener noreferrer"&gt;ReactiveX&lt;/a&gt; terms &lt;a href="https://observablehq.com" rel="noopener noreferrer"&gt;Observable&lt;/a&gt;'s dataflow is a &lt;em&gt;&lt;a href="https://rxjs.dev/api/index/function/combineLatest" rel="noopener noreferrer"&gt;combineLatest&lt;/a&gt;&lt;/em&gt; across cell streams, but we want a &lt;em&gt;&lt;a href="https://rxjs.dev/api/index/function/merge" rel="noopener noreferrer"&gt;merge&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Ffiles%2F179fb6fdbc37ab4d1f8dbb73afd702956a35939d7d9965e21cb5285d993b117b746684e7c64c3be1ea8d8fcd6afa9ae96b13c810047ffdf3e09b69aa9d7f952e"&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.observableusercontent.com%2Ffiles%2F7ed8bcec1d34eda7fcd491dd5cfb4a2fc1e121cdf0ad865bf1a54b116270c0d0ae66003bbe20ef12cb622f2fba1029ef43e97f68ff7df054af615f81b3fbb388"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So here is how you can do it with &lt;em&gt;views&lt;/em&gt; and &lt;em&gt;Inputs.bind&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Create an unresolved view for the business logic to depend on
&lt;/h2&gt;

&lt;p&gt;We can quickly create an unresolved view with &lt;a href="https://github.com/observablehq/inputs/blob/main/README.md#inputsinputvalue" rel="noopener noreferrer"&gt;&lt;code&gt;Inputs.input()&lt;/code&gt;&lt;/a&gt;. We will fan-in sources to this view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xejmwjvh9vpzbz20ng5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xejmwjvh9vpzbz20ng5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the business logic extracts parameters from the &lt;em&gt;config&lt;/em&gt; variable, then it will not run until it is set to a non-&lt;em&gt;undefined&lt;/em&gt; value (note grey bar on the left of the cell after notebook startup).&lt;/p&gt;

&lt;p&gt;Our example business logic will flash the notebook in a color set by the config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04mgfu8o5mknrz45npoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04mgfu8o5mknrz45npoz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Setup all the sources to be views
&lt;/h2&gt;

&lt;p&gt;Next we need the sources to be emitting their config to a view's value.&lt;/p&gt;

&lt;p&gt;Often, a source is already a view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvokre0qezmjgf1e9xp6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvokre0qezmjgf1e9xp6f.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But even if it isn't, it is trivial to create a view from a dataflow variable by writing into an &lt;a href="https://github.com/observablehq/inputs/blob/main/README.md#inputsinputvalue" rel="noopener noreferrer"&gt;&lt;code&gt;Inputs.input()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrcainrgm41z2x7n8otl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrcainrgm41z2x7n8otl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Bind the source views to the fan-in view backwards
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4f31x5vlzhxbf49fzyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4f31x5vlzhxbf49fzyv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if either of the sources fire, the business logic is run. Give it a try on the live notebook on at &lt;a href="https://observablehq.com/@tomlarkworthy/merge-dataflow" rel="noopener noreferrer"&gt;@tomlarkworthy/merge-dataflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you find that useful and that you find other useful ways to manipulate dataflow!&lt;/p&gt;

</description>
      <category>observable</category>
      <category>programming</category>
      <category>webdev</category>
      <category>reactive</category>
    </item>
    <item>
      <title>The Fully Encapsulated Monitoring Notebook</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Sat, 23 Oct 2021 11:55:23 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/the-fully-encapsulated-monitoring-notebook-52eh</link>
      <guid>https://dev.to/tomlarkworthy/the-fully-encapsulated-monitoring-notebook-52eh</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Jd_jVup--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55aie6ifhlmg0mfyxeyd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Jd_jVup--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55aie6ifhlmg0mfyxeyd.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine, in a page of code, hosted entirely on the web, with zero tools to install or infra to manage, you could have a state-of-art &lt;em&gt;programmable&lt;/em&gt; data visualization system recording anything reachable on the internet. Bold claim? I have built two such systems and I am so excited by the possibilities I wish to excitedly share the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  IDE, Documentation, Code Host and Front End: &lt;a href="https://observablehq.com"&gt;Observablehq.com&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com"&gt;Observablehq.com&lt;/a&gt; is a mind-blowingly simpler platform that can replace: Github, CI, VScode, static hosting, Google docs, and containers. It hosts, runs, and provides an IDE for client-side Javascript. Furthermore, it provides a novel reactive notebook format, so code can be written non-linearly and the program output is interleaved with the code. It’s also a literate programming environment, so notebooks also support documentation, multiplayer commenting, and collaboration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Observable is a single tool that can replace numerous critical tools required in a software project. Including development, technical documentation, and hosting.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Charting: &lt;a href="https://observablehq.com/@observablehq/plot"&gt;@observablehq/plot&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://observablehq.com"&gt;Observablehq.com&lt;/a&gt;, is the hottest place for Dataviz, so it naturally hosts a number of state-of-the-art tools such as &lt;a href="https://observablehq.com/@observablehq/plot"&gt;plot&lt;/a&gt;. &lt;a href="https://observablehq.com/@observablehq/plot"&gt;Plot&lt;/a&gt; stands out by being so fast to develop with, but &lt;a href="https://observablehq.com/@observablehq/vega-lite"&gt;Vega-lite&lt;/a&gt; is another great option. Check out some of the workshops on how to use these tools effectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(Plot) &lt;a href="https://www.youtube.com/watch?v=bTQfm6gwngY"&gt;Analyzing Time Series Data in Observable: Hands-on Workshop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;(vega-lite) &lt;a href="https://www.youtube.com/watch?v=ZV_Yjcs5WtM"&gt;Observable: Vega-Lite: A Crash Course&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do queries on the client side I would use &lt;a href="https://observablehq.com/@uwdata/introducing-arquero"&gt;Arquero&lt;/a&gt; for data operations. There is an interactive intro to Arquero through &lt;a href="https://observablehq.com/@observablehq/data-wrangler"&gt;Data Wrangler&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI: &lt;a href="https://observablehq.com/@observablehq/inputs"&gt;@observablehq/inputs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;For simple controls, Observable has an excellent selection of off-the-shelf components through &lt;a href="https://observablehq.com/@observablehq/inputs"&gt;@observablehq/inputs&lt;/a&gt;. With some work, you can make complex UIs too (see &lt;a href="https://observablehq.com/@tomlarkworthy/ui-development"&gt;Scaling User Interface Development&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Realtime Database: &lt;a href="https://firebase.google.com"&gt;firebase.google.com&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As Observable is a reactive client-side environment, it works really well with reactive client-side databases like Firebase. When I am choosing for monitoring systems, I look for: cheap writes and low latency. So I tend to pick the &lt;a href="https://firebase.google.com/docs/database"&gt;Firebase Realtime Database&lt;/a&gt; over &lt;a href="https://firebase.google.com/docs/firestore"&gt;Firestore&lt;/a&gt; for monitoring applications. (FWIW I pick &lt;a href="https://firebase.google.com/docs/firestore"&gt;Firestore&lt;/a&gt; for online transaction processing applications).&lt;/p&gt;

&lt;h3&gt;
  
  
  API Endpoints: &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Monitoring solutions often need APIs, so they can respond to external services (e.g. queries, datapoint ingestion, &lt;a href="https://observablehq.com/@tomlarkworthy/oauth-examples"&gt;OAuth&lt;/a&gt; handshakes). &lt;a href="https://webcode.run"&gt;WEBCode.run&lt;/a&gt; extends Observable with an SDK that allows notebooks to host serverless endpoints. These endpoints can utilize &lt;a href="https://observablehq.com/@endpointservices/public-api-keys"&gt;secrets&lt;/a&gt; and &lt;a href="https://observablehq.com/@endpointservices/cron"&gt;scheduled functions&lt;/a&gt; so are a great fit when you need to interface with external systems and it can be all done without leaving &lt;a href="https://observablehq.com"&gt;observablehq.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Monitoring Notebooks
&lt;/h2&gt;

&lt;p&gt;I have built two notebooks so far that are self-enclosed systems, and they have both provided &lt;em&gt;immense&lt;/em&gt; value to me. I intend to do many more.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;a href="https://observablehq.com/@endpointservices/realtime-request-log"&gt;Realtime Inbound Request Log&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://observablehq.com/@endpointservices/realtime-request-log"&gt;Realtime Inbound Request Log&lt;/a&gt; notebook hosts an endpoint and puts all inbound requests in a log. When you view the notebook the log entries are queried and some summaries are provided. This notebook was developed to help me sniff request headers on a remote machine not under my control, by scripting the remote machine to query the instrumented endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://observablehq.com/@tomlarkworthy/serverless-cell-latency-monitor"&gt;Latency Monitor&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://observablehq.com/@tomlarkworthy/serverless-cell-latency-monitor"&gt;Latency Monitor&lt;/a&gt; notebook is a full end-to-end prober solution. It periodically schedules traffic to a system, measures the latency, and summarizes the results in a dashboard.&lt;/p&gt;

&lt;p&gt;This has been in operation for nearly a1 year and helped quantify latency performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything in one end-to-end environment.
&lt;/h2&gt;

&lt;p&gt;I am delighted that everything is in a single artifact that can be shared with a URL. This is not some low code compromise either, all the parts work together providing the end-user with a state-of-the-art experience. &lt;em&gt;Realtime&lt;/em&gt; data dashboards using state-of-the-art DataViz libraries, stitched together with a modern language using tools that support version control, step debuggers, &lt;em&gt;etc.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By having the tools to modify the monitoring pipeline next to the technical documentation and realtime output of the production data allows anyone to maintain it with no context switching.&lt;/p&gt;

&lt;p&gt;Overall it is a joyful development experience and operationally simple.&lt;/p&gt;

&lt;h4&gt;
  
  
  Credits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Article hosted on &lt;a href="https://observablehq.com/@tomlarkworthy/encapsulated-monitoring"&gt;Observable&lt;/a&gt; and syndicated with &lt;a href="https://observablehq.com/@tomlarkworthy/blogify"&gt;Blogify&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Photo by &lt;a href="https://unsplash.com/@mjessier?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Myriam Jessier&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/data?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Securing a Serverless Multi-Tenancy Puppeteer Service</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Thu, 10 Jun 2021 08:15:59 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/securing-a-serverless-multi-tenancy-puppeteer-service-19ce</link>
      <guid>https://dev.to/tomlarkworthy/securing-a-serverless-multi-tenancy-puppeteer-service-19ce</guid>
      <description>&lt;p&gt;If you ever try to stand up a Puppeteer service you will almost immediately find it is difficult to secure when running inside a Docker environment. &lt;/p&gt;

&lt;p&gt;I love my serverless, so I was not prepared to take no for an answer. And with a lot of sweat, I think I able to stand up a Puppeteer service with full customer isolation and protection again serverside scripting from within a multi-tenancy docker container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customer Isolation
&lt;/h2&gt;

&lt;p&gt;Customers should not be able to view each other's data.&lt;/p&gt;

&lt;h3&gt;
  
  
  --no-sandbox
&lt;/h3&gt;

&lt;p&gt;Chrome itself is natively very good at sandboxing tabs for security reasons. Ideally, we would simply just exploit the inbuilt security model, e.g. put each customer in their own tab(s). Not so fast though! Unfortunately, Chrome won't boot under that configuration. The way that sandbox is implemented does not work containers, as a result, nearly every Dockerfile for Puppeteer on the internet launches with the --no-sandbox flag.  &lt;/p&gt;

&lt;h3&gt;
  
  
  --cap-add=SYS_ADMIN
&lt;/h3&gt;

&lt;p&gt;The few Dockerfiles I could find without --no-sandbox have added the SYS_ADMIN security capability. This is one solution to keep the sandbox, but most managed docker environments don't expose this control, unfortunately. So I needed a different way to work on Serverless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux process isolation
&lt;/h3&gt;

&lt;p&gt;Normal Linux processes cannot mess with each other's memory. So the OS approach for customer isolation is to run a different browser for each customer. &lt;/p&gt;

&lt;h3&gt;
  
  
  Resource isolation
&lt;/h3&gt;

&lt;p&gt;You still need to be careful though, as even separate Chrome processes can still access common resources (e.g. filesystem). In particular, user sessions, cookies, and website cached data need to be stored in different directories for each customer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--disk-cache-dir
--media-cache-dir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Protection against Serverside Request Forgery
&lt;/h2&gt;

&lt;p&gt;A Puppeteer service essentially allows end-users to run code within our infrastructure. The big danger is that the Puppeteer instance will become a bastion for network intrusion. This is not an academic thing either, a ton of exploits observed in the wild used Puppeteer or similar to launch attacks via serverside request forgery attacks. Check out&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techkranti.com/ssrf-aws-metadata-leakage/"&gt;https://techkranti.com/ssrf-aws-metadata-leakage/&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/IAmMandatory/status/1196939247057457152"&gt;https://twitter.com/IAmMandatory/status/1196939247057457152&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hackerone.com/blog/spotlight-server-side"&gt;https://www.hackerone.com/blog/spotlight-server-side&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In particular, an easy attack is to have Puppeteer run a webpage that probes for Cloud metadata servers, which can then be used to obtain credentials. &lt;/p&gt;

&lt;p&gt;So, we must prevent Chrome from accessing certain URLs and local IP addresses. &lt;/p&gt;

&lt;p&gt;You can find a list of IP addressed to block on &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"&gt;owasp.org&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  --proxy-server
&lt;/h3&gt;

&lt;p&gt;Chrome can be configured to use an outbound HTTP proxy server, which we can use to intercept and filter traffic. For our service, we used TinyProxy as it has a very low resource overhead (2MB). &lt;/p&gt;

&lt;p&gt;The TinyProxy configuration then protects against access to sensitive IP addresses and domains.&lt;/p&gt;

&lt;p&gt;Our translation to TinyProxy configuration can be found on &lt;a href="https://github.com/endpointservices/serverlesscells/blob/main/tinyproxy.conf"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing the Chrome DevTools protocol port
&lt;/h3&gt;

&lt;p&gt;The most exciting thing for me is allowing users to script Chrome from within their browser environment remotely. This is enabled by exposing the Devtools debug WebSocket protocol. &lt;/p&gt;

&lt;p&gt;To allow us to meter access, we expose a WebSocket endpoint that requires an access_token to be in the path. We can then verify the access_token, boot Puppeteer, and then proxy the public WebSocket endpoint to the internal WebSocket endpoint on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in your Browser without installation
&lt;/h2&gt;

&lt;p&gt;With that in place, we are now able to offer Puppeteer access from right within a browser. Check it out, we are hosting it from within &lt;a class="mentioned-user" href="https://dev.to/observablehq"&gt;@observablehq&lt;/a&gt;
 in a &lt;a href="https://observablehq.com/@endpointservices/puppeteer"&gt;notebook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Full source code is available on &lt;a href="https://github.com/endpointservices/serverlesscells/blob/main/tinyproxy.conf"&gt;Github&lt;/a&gt; and it is designed to be run on Google Cloud Run.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>security</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Firestore's Technical advantages </title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Tue, 11 May 2021 07:42:44 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/firestore-s-technical-advantages-2l3i</link>
      <guid>https://dev.to/tomlarkworthy/firestore-s-technical-advantages-2l3i</guid>
      <description>&lt;p&gt;I have read some pretty poor articles bashing Firestore recently. Generally they completely miss the features set or cargo cult Postgres SQL. This article attempt to highlight the features of Firestore that you won't see with a Postgres solution (note I love Postgres), highlighting several area's where Firestore is the world #1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clientside first
&lt;/h2&gt;

&lt;p&gt;It's designed for a direct connection to a mobile/webapp. This means it has a number of features that are unmatched in the market.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency compensation
&lt;/h3&gt;

&lt;p&gt;Firestore maintains a local cache, so local writes and observable immediately. Greatly simplifying controller design. It even broadcasts writes to adjacent tabs in the browser for 0 latency across browser tabs. &lt;em&gt;You ain't got time to implement that!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline persistence
&lt;/h3&gt;

&lt;p&gt;The cache is backed by persistent storage, so your app works offline without much work. This is a huge feature that is difficult to get right and essential for a good mobile experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorisation rules
&lt;/h3&gt;

&lt;p&gt;The database has a layer of security rules which are very flexible and can depend on values in the database. &lt;/p&gt;

&lt;h3&gt;
  
  
  Causal Consistency
&lt;/h3&gt;

&lt;p&gt;Client SDKs observe their own writes first, and remote writes sometime later. Firestore guarantees that remote writes preserve their order. This is better than eventual consistency, its causal consistency and the best you can manage in a distributed setting.&lt;br&gt;
The fact write order is preserved makes the system very intuitive, but many Firestore competitors do not guarantee this property which leads to weird bugs and second guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1M concurrent clients
&lt;/h3&gt;

&lt;p&gt;Its not so easy to support 1M &lt;strong&gt;concurrent&lt;/strong&gt; connections, that's serious engineering work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spanner Backed
&lt;/h2&gt;

&lt;p&gt;Firestore somewhat outclasses Postgres on underlying database technology too, being based on Google Spanner. Firestore is the most affordable way to access a Google Spanner based database.&lt;/p&gt;

&lt;h3&gt;
  
  
  99.999% SLA
&lt;/h3&gt;

&lt;p&gt;Yes. You probably can't find a more reliable cross region database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-region yet strong consistency
&lt;/h3&gt;

&lt;p&gt;Writes are replicated across multiple regions. This is one of the reasons why it is so reliable, it is resistant to single data centre losses. It can achieve this AND still be strongly consistent. It is simply not possible to configure Postgres to be multi region and be strongly consistent. This is really what Spanner brings to the table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions
&lt;/h3&gt;

&lt;p&gt;Firestore can do atomic writes across documents, without &lt;em&gt;caveats&lt;/em&gt;, without &lt;em&gt;sharding&lt;/em&gt;. Very few distributed databases can achieve this in a multi-region setting. &lt;/p&gt;

&lt;h3&gt;
  
  
  array-contains-any joins
&lt;/h3&gt;

&lt;p&gt;I have read that noSQL databases do not support joins at all. This is true for many NoSQL solutions, but not the full truth in Firestore's case. It is true query expressivity is lower than SQL.&lt;/p&gt;

&lt;p&gt;However, thanks to the "array-contains-any" query you can retrieve a set of documents matching a set of ids in a single query. This is far more efficient than having to retrieve documents on the other side of a join one at a time. Thus a SQL with 3 joins can usually be performed with 3 queries in Firestore with the appropriate indexes. Though, to be fair Postgres has the upper hand here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;p&gt;Firestore is true serverless with essentially unbounded scalability thanks to its Spanner backend. It also scales to zero so you only pay for what you use, unlike Postgres which has a fixed provisioning cost and an associated performance ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Postgres is a great default choice for a startup. However, if your product is used on mobile or across the globe, you might find Firestore a better match due to its state-of-the-art backend and client SDKs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I used to work on the Firebase databases&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>database</category>
      <category>startup</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How Cloud Run changes Cloud Architecture</title>
      <dc:creator>Tom Larkworthy</dc:creator>
      <pubDate>Mon, 03 May 2021 10:57:42 +0000</pubDate>
      <link>https://dev.to/tomlarkworthy/how-cloud-run-changes-cloud-architecture-43in</link>
      <guid>https://dev.to/tomlarkworthy/how-cloud-run-changes-cloud-architecture-43in</guid>
      <description>&lt;p&gt;Cloud Run is interesting, it's a general-purpose elastic container hosting service, a bit like Fargate or Azure-Containers but with a few critical differences.&lt;/p&gt;

&lt;p&gt;Most interesting is that it scales to zero, and auto-scales horizontally, making it very cost-effective for low traffic jobs (e.g. overnight batch processes).&lt;/p&gt;

&lt;p&gt;It also runs arbitrary docker containers and can serve requests concurrently, meaning that for modest traffic you don't usually need more than 1 instance running (and you save money).&lt;/p&gt;

&lt;p&gt;Its flexibility comes at the cost of higher cold starts though. Take a look at our cold start latencies for an on-demand puppeteer service in a low traffic region:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cqof17cG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7e4aou5sxmz2ogzqw6af.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cqof17cG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7e4aou5sxmz2ogzqw6af.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are seeing cold start latencies of around 10 seconds, to boot up a 400MB container and start Chrome. This was annoyingly slow.&lt;/p&gt;

&lt;p&gt;Not all our regions were that slow though, in one of the busier regions we saw a bimodal latency graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JGRWQqq5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxnum8r2lmez57u3pfnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JGRWQqq5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxnum8r2lmez57u3pfnk.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;suggesting that 2.5 seconds is booting up a puppeteer instance and serving the request, and 5-7 seconds is booting the container. For busier regions often a container is running so that's why sometimes the cold latencies are much lower. (for completeness a warm latency measurement is 1.5 seconds, so probably 1 second is booting chrome, and 1.5 seconds is serving the request).&lt;/p&gt;

&lt;p&gt;So... how could we speed things up? 5-7 seconds is spent on container startup. It's our biggest spender of the latency budget, so that's what we should concentrate on reducing.&lt;/p&gt;

&lt;p&gt;One solution is to run a dedicated VM, though that loses the horizontal elasticity. Even so, let's do the numbers.&lt;/p&gt;

&lt;p&gt;A 2 vCPU 2GB RAM machine (e2-highcpu-2) is $36.11 per month&lt;/p&gt;

&lt;p&gt;Now Cloud Run has a relatively new feature called &lt;a href="https://cloud.google.com/run/docs/configuring/min-instances"&gt;min-instances&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This keeps some containers IDLE but with no CPU budget, so they can be flipped on quicker. IDLE instances are still charged, BUT, at around a 10x reduced cost. The cost for an IDLE 2 vCPU 2GB RAM Cloud Run is $26.28 per month. &lt;/p&gt;

&lt;p&gt;This gets pretty close to having your cake and eating it. You get lower latency like a dedicated machine, but also still horizontally elastic. It may even cost less.&lt;/p&gt;

&lt;p&gt;For our application, we tried a &lt;em&gt;min-instance&lt;/em&gt; of 1 and this was the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NeMqp-mJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqqlks3o00o9mohx9qpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NeMqp-mJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqqlks3o00o9mohx9qpz.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our cold start latencies from container startup are decimated! We have not had to change any code. &lt;/p&gt;

&lt;h2&gt;
  
  
  I think this min-instances feature is a game-changer for cloud architecture. You can now get the benefits of dedicated VMs at a comparable price to dedicated VMs but with elasticity and image-based deployments. The new min-instances feature broadens the range of applications that serverless compute can address. 
&lt;/h2&gt;

&lt;p&gt;Our latency monitoring infrastructure and data is &lt;a href="https://observablehq.com/@tomlarkworthy/serverless-cell-latency-monitor"&gt;public&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloudnative</category>
      <category>docker</category>
      <category>cloudskills</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
