<?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: cyl08</title>
    <description>The latest articles on DEV Community by cyl08 (@cyl08).</description>
    <link>https://dev.to/cyl08</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4170434%2Ffffb6e82-8929-4d01-b171-7b2880f08c56.png</url>
      <title>DEV Community: cyl08</title>
      <link>https://dev.to/cyl08</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cyl08"/>
    <language>en</language>
    <item>
      <title>5 Python scripts that turn a CSV into a publication-quality map</title>
      <dc:creator>cyl08</dc:creator>
      <pubDate>Thu, 08 Oct 2026 06:50:17 +0000</pubDate>
      <link>https://dev.to/cyl08/5-python-scripts-that-turn-a-csv-into-a-publication-quality-map-28mb</link>
      <guid>https://dev.to/cyl08/5-python-scripts-that-turn-a-csv-into-a-publication-quality-map-28mb</guid>
      <description>&lt;p&gt;If you have coordinates sitting in a spreadsheet and you want them on a map, the standard advice is "just use QGIS" or "go learn geopandas". Both are fine answers — but if all you need is one figure for a report, that's a lot of yak-shaving for one image.&lt;/p&gt;

&lt;p&gt;I ended up writing five small scripts, each self-contained, each doing one job. Here's what each is actually for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Choropleth — shaded regions from your data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The classic. You have a value per region (population, GDP, case counts) and a boundary file. The script joins your CSV to the GeoJSON on a key, then shades each region by value with a colour scale and a legend that doesn't look broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. POI distribution — points on a coordinate grid&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just "where are my things". Good for store locations, sampling sites, sensors. Plots your lat/lon with a light grid so you can eyeball clustering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Density heatmap — hexbin aggregation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you have &lt;em&gt;too many&lt;/em&gt; points and a scatter plot becomes a solid blob. Hexbin aggregates into hexagonal bins and colours by count — it shows clusters that individual points hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Bubble map — dot size encodes a value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same as POI, but each point carries a magnitude (revenue, volume, users). Size encodes the value, so one glance shows both where and how much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Flow map — curved origin→destination lines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For movement data: commutes, migration, shipments. Each row is an origin→destination pair with a volume, drawn as a curved line whose width is the volume. Curves (not straight lines) make overlapping routes readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why matplotlib maps are annoying
&lt;/h2&gt;

&lt;p&gt;The plotting itself is easy. The annoying parts are the ones nobody mentions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coordinate systems.&lt;/strong&gt; Your data might be WGS-84 lat/lon; your boundary file might not. If they disagree your map is subtly wrong, and you won't notice until someone else does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colour scales.&lt;/strong&gt; A linear scale on skewed data makes everything the same colour. Getting quantile vs linear right changes whether the map says anything at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legends and labels.&lt;/strong&gt; Making a colourbar, a title and region labels not collide is genuinely fiddly — and it's about 80% of the "why does my map look amateur" problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary files.&lt;/strong&gt; You need a GeoJSON. If you don't have one, half the tutorial is over before it starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each script handles those with a &lt;code&gt;CONFIGURATION&lt;/code&gt; block at the top: edit the paths and column names, run it, and the figure lands in your output folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pandas numpy matplotlib geopandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python 3.9+. Works on Windows, macOS and Linux. No ArcGIS, no QGIS, no account, no API keys.&lt;/p&gt;

&lt;p&gt;Every script ships with working sample data so you can run it before pointing it at your own CSV. There's a boundary GeoJSON example too, plus a troubleshooting section for the usual projection mismatches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the right map
&lt;/h2&gt;

&lt;p&gt;This is the part that actually matters, and it's not a code question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What your data looks like&lt;/th&gt;
&lt;th&gt;Which map&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A value per region&lt;/td&gt;
&lt;td&gt;Choropleth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Points, want to see clustering&lt;/td&gt;
&lt;td&gt;POI distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Too many points to see anything&lt;/td&gt;
&lt;td&gt;Density heatmap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Points with a magnitude&lt;/td&gt;
&lt;td&gt;Bubble map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin to destination pairs&lt;/td&gt;
&lt;td&gt;Flow map&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Getting that mapping right is worth more than any individual script.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather not write them
&lt;/h2&gt;

&lt;p&gt;Everything above is what I built for my own work, and I packaged the five scripts up with the sample data and notes. If it saves you an afternoon of fighting matplotlib projections, that's the whole point: &lt;a href="https://mochiway.com/templates.html" rel="noopener noreferrer"&gt;https://mochiway.com/templates.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Either way — the concepts are the useful part. Happy to answer questions about projections, geopandas joins, or hexbin parameters.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>datascience</category>
      <category>gis</category>
    </item>
    <item>
      <title>I built a budget app with no backend — everything lives in localStorage</title>
      <dc:creator>cyl08</dc:creator>
      <pubDate>Thu, 08 Oct 2026 06:46:28 +0000</pubDate>
      <link>https://dev.to/cyl08/i-built-a-budget-app-with-no-backend-everything-lives-in-localstorage-4lbb</link>
      <guid>https://dev.to/cyl08/i-built-a-budget-app-with-no-backend-everything-lives-in-localstorage-4lbb</guid>
      <description>&lt;p&gt;Every budgeting app I tried wanted a monthly subscription. Which is a bit ironic for software whose whole job is to help you spend less.&lt;/p&gt;

&lt;p&gt;So I built Mochi — and made one architectural decision that shaped everything else: &lt;strong&gt;no backend at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No server. No account. No database. It's a single HTML file, about 75 KB, and all your data lives in your browser's &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's what that constraint actually costs, and what it buys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you give up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No sync.&lt;/strong&gt; This is the big one, and I won't pretend otherwise. Your phone and your laptop don't know about each other. Want both? Export a JSON backup from one, import it on the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No recovery.&lt;/strong&gt; Because there's no account, there's no "forgot password" and no way for me to restore your data. Clear your browser storage and it's gone. That's a real risk, so the app nags you to export backups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No analytics, so no product intuition.&lt;/strong&gt; I genuinely cannot see how people use it — which features land, where they get stuck. Every improvement is a guess, or comes from someone emailing me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your financial data can't leak, because it never leaves.&lt;/strong&gt; Not "we don't sell your data" — there is literally no server holding it. That's a different kind of promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works offline, forever.&lt;/strong&gt; Save the page and it works on a plane. No API can go down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs me nothing to run, so it doesn't have to cost you monthly.&lt;/strong&gt; This is the part I like most. No infrastructure means no reason to charge a subscription — which is why it's a one-time purchase instead of $5/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Single &lt;code&gt;index.html&lt;/code&gt;, vanilla JS, zero dependencies, no build step&lt;/li&gt;
&lt;li&gt;Charts hand-rolled with plain CSS and SVG — no chart library&lt;/li&gt;
&lt;li&gt;Data in &lt;code&gt;localStorage&lt;/code&gt;, with JSON export/import for portability&lt;/li&gt;
&lt;li&gt;PWA manifest + service worker: installs to your home screen, works offline&lt;/li&gt;
&lt;li&gt;~75 KB total, boots instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most interesting constraint was the charts. Without a library, a six-month trend chart is an exercise in &lt;code&gt;viewBox&lt;/code&gt; arithmetic and remembering that SVG y-axes grow downward. Worth it though — the whole app loads faster than a single page of most apps I compared against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The annoyance that started it
&lt;/h2&gt;

&lt;p&gt;I built Mochi because of one specific thing: I had no idea what I was spending on AI tools each month. ChatGPT, Claude, Cursor, a couple of API top-ups — individually each felt small, together it was a real number I only saw when the card statement arrived.&lt;/p&gt;

&lt;p&gt;So Mochi keeps a dedicated AI-spend ledger: log subscriptions and top-ups, set a monthly cap, watch a six-month trend. It's the one feature I couldn't find in any other budget app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm posting this
&lt;/h2&gt;

&lt;p&gt;Partly because the no-backend architecture is genuinely interesting to argue about — the trade-offs are real and asymmetric, and I'd pick it again for this use case.&lt;/p&gt;

&lt;p&gt;And partly, honestly, because it's a paid product and I'd like people to find it. If the architecture is interesting to you, it's at &lt;a href="https://mochiway.com" rel="noopener noreferrer"&gt;https://mochiway.com&lt;/a&gt; — one-time price, no subscription, in keeping with the whole point.&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the localStorage trade-offs, the no-build-step decision, or what I'd do differently next time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
