<?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: Spec</title>
    <description>The latest articles on DEV Community by Spec (@spec_psx).</description>
    <link>https://dev.to/spec_psx</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%2F2432772%2F9a91f23e-0caf-45da-958c-20d35740d204.jpg</url>
      <title>DEV Community: Spec</title>
      <link>https://dev.to/spec_psx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spec_psx"/>
    <language>en</language>
    <item>
      <title>I Built an API That Turns Your GitHub Contributions Into a 3D Isometric Graph</title>
      <dc:creator>Spec</dc:creator>
      <pubDate>Sat, 25 Apr 2026 22:30:57 +0000</pubDate>
      <link>https://dev.to/spec_psx/i-built-an-api-that-turns-your-github-contributions-into-a-3d-isometric-graph-43dk</link>
      <guid>https://dev.to/spec_psx/i-built-an-api-that-turns-your-github-contributions-into-a-3d-isometric-graph-43dk</guid>
      <description>&lt;p&gt;Your GitHub contribution graph is flat and green. Mine is an isometric skyline.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Isometric Contributions&lt;/strong&gt; — a free API that renders your GitHub activity as a 3D isometric city, ready to drop into your README or portfolio with a single &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github Repo: &lt;strong&gt;&lt;a href="https://github.com/Spectrewolf8/isometric-contributions-embeds" rel="noopener noreferrer"&gt;LINK&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;URL Builder and Docs site: &lt;strong&gt;&lt;a href="https://isometric-contributions-spectrewolf8.onrender.com/" rel="noopener noreferrer"&gt;LINK&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Sample:&lt;br&gt;
&lt;a href="https://media2.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%2For3o5m19pjngre2qx3ql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2For3o5m19pjngre2qx3ql.png" alt="Main Example Image" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also comes with six themes, all generated server-side as crisp PNG images:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkzkm0q6wkviqowhiokgt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkzkm0q6wkviqowhiokgt.png" alt="All Designs" width="800" height="741"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;github&lt;/code&gt; - Classic green on dark&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dark&lt;/code&gt; - Sleek dark palette &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;light&lt;/code&gt; - Clean white background&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;neon&lt;/code&gt; - Cyberpunk glow&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minimal&lt;/code&gt; - Quiet and understated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ocean&lt;/code&gt;- Deep blue tones &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also add a &lt;strong&gt;stats overlay&lt;/strong&gt; showing your total contributions, current streak, longest streak, best day, and daily average — all baked right into the image.&lt;/p&gt;


&lt;h2&gt;
  
  
  Using it in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Drop this into your GitHub profile README and you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;My Contributions&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://https://isometric-contributions-spectrewolf8.onrender.com/api/graph?username=YOUR_USERNAME&amp;amp;theme=dark&amp;amp;stats=true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No tokens, no setup, no dependencies on your end.&lt;/p&gt;

&lt;h3&gt;
  
  
  All the options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/graph
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;username&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;required&lt;/td&gt;
&lt;td&gt;Your GitHub username&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;github&lt;/code&gt; · &lt;code&gt;dark&lt;/code&gt; · &lt;code&gt;light&lt;/code&gt; · &lt;code&gt;neon&lt;/code&gt; · &lt;code&gt;minimal&lt;/code&gt; · &lt;code&gt;ocean&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;year&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specific year like &lt;code&gt;2024&lt;/code&gt;, or &lt;code&gt;none&lt;/code&gt; for the last 365 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;true&lt;/code&gt; to include the stats overlay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;credit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;true&lt;/code&gt; to show your username watermark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;width&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image width in px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image height in px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Last 365 days, neon theme, with stats
/api/graph?username=torvalds&amp;amp;theme=neon&amp;amp;stats=true

# Specific year, wide format
/api/graph?username=torvalds&amp;amp;year=2023&amp;amp;width=1400&amp;amp;height=700

# Minimal, no extras
/api/graph?username=torvalds&amp;amp;theme=minimal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;The stack is intentionally small — no framework, just Node.js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data:&lt;/strong&gt; GitHub's official GraphQL &lt;code&gt;contributionsCollection&lt;/code&gt; API. Each query fetches up to 365 days of contribution data with contribution counts, levels, and dates. Requests retry automatically up to 3 times with exponential backoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rendering:&lt;/strong&gt; &lt;a href="https://github.com/nosir/obelisk.js" rel="noopener noreferrer"&gt;obelisk.js&lt;/a&gt; handles the isometric projection math, running inside a JSDOM environment on the server. &lt;a href="https://github.com/Automattic/node-canvas" rel="noopener noreferrer"&gt;node-canvas&lt;/a&gt; provides the actual pixel buffer. Each day becomes a cube whose height is proportional to that day's contribution count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching:&lt;/strong&gt; Generated images are cached in Supabase Storage keyed by &lt;code&gt;date/username/params&lt;/code&gt;. The same image is served instantly on repeat requests throughout the day without hitting the GitHub API or re-rendering. Images with suspiciously low data (a signal the API returned partial results) are intentionally not cached, so the next request gets a fresh attempt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; A raw PNG buffer streamed directly as the HTTP response with &lt;code&gt;Content-Type: image/png&lt;/code&gt; — which is exactly what &lt;code&gt;&amp;lt;img src="..."&amp;gt;&lt;/code&gt; expects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-hosting
&lt;/h2&gt;

&lt;p&gt;Want to run your own instance?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Spectrewolf8/isometric-contributions-embeds
&lt;span class="nb"&gt;cd &lt;/span&gt;isometric-contributions
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up your &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_TOKEN=ghp_yourtoken        # PAT with no scopes needed for public data
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_BUCKET_NAME=isometric-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On startup, the server verifies your GitHub token and prints which account it's authenticated as, so you know immediately if something's wrong.&lt;/p&gt;

&lt;p&gt;Deploy to &lt;a href="https://render.com" rel="noopener noreferrer"&gt;Render&lt;/a&gt;, &lt;a href="https://railway.app" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;, or any Docker-capable host — a &lt;code&gt;Dockerfile&lt;/code&gt; is included.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A few things I want to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVG output&lt;/strong&gt; so the graph can scale infinitely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom color schemes&lt;/strong&gt; via query params instead of fixed themes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animated version&lt;/strong&gt; — a time-lapse that builds the city week by week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those sound useful to you, or if you would like to add anything else, leave a comment — it helps me prioritize.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Node.js, obelisk.js, node-canvas, and Supabase. Contributions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>news</category>
    </item>
  </channel>
</rss>
