← Back to list

ObservableHQ Plot maps for beginners — with some AI help

Part 4: Lat Lon points + a few extras

Bryony Miles in Artificial Intelligence in Plain English · 2026-06-28 17:31 · 0 claps · 6.7 min read paywalled
#observablehq #ai #maps #geojson
Open on Medium ↗
Wiki topics: AI · AI · General

ObservableHQ Plot maps for beginners — with some AI help

Part 4: Lat Lon points + a few extras

This is a follow on from Part 3.

Step 1: Draw an LSOA map Step 2: Generate some random co-ordinates Step 3: Add the points to the map Step 4: Refine your map further Step 5: Adding your own font

Step 1: Draw an LSOA map

First step was to choose and LSOA and make a filtered dataset, like I did in Part 2 with Claude prompt

Can you use this code as a basis to create me a filtered GeoJSON from my base file banes_lsoa_geojson where the property LSOA21CD equals E01014388.

chew_valley = ({type: “FeatureCollection”,features: banes_wards.features.filter((d) => d.properties.Ward_name === “Chew Valley”)})

filtered_lsoa = ({
  type: "FeatureCollection",
  features: banes_lsoa_geojson.features.filter(
    (d) => d.properties.LSOA21CD === "E01014388"
  )
})

and follow with Claude prompt:

and can you alter this Plot code for my new cell results

Plot.plot({ width: 800, height: 600, projection: { type: “equal-earth”, domain: chew_valley }, marks: [Plot.geo(chew_valley, { stroke: “black”, fill: “#ddd” })] })

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" })]
})

If you’re interested in the coding side of things, you’ll see that all that was really needed here was changing some field names, filter variables and file names — pretty achievable stuff if you’ve got the ambition start coding and wean yourself off AI help and do the work yourself…

Step 2: Generate some random co-ordinates

I asked this question to Claude and it wasn’t able to help so I tried ChatGPT and got the results I needed- ChatGPT prompt “can you generate me an array of 5 random lat lon co-ordinates within the LSOA E01014388”.

After the 1st answer it offered to calculate points based on precise geometry of the area and I said yes.

[
  { "lat": 51.362341, "lon": -2.344891 },
  { "lat": 51.361102, "lon": -2.343517 },
  { "lat": 51.363008, "lon": -2.346225 },
  { "lat": 51.359842, "lon": -2.345119 },
  { "lat": 51.364575, "lon": -2.342983 }
]

Step 3: Add the points to the map

I started with this Claude prompt:

I have a set of lat, lon coordinates in an array called randomPositions — field names are “lat” and “lon”.

Could you alter this ObservableHQ cell code to add the points onto the map

Plot.plot({ width: 800, height: 600, projection: { type: “equal-earth”, domain: filtered_lsoa }, marks: [Plot.geo(filtered_lsoa, { stroke: “black”, fill: “#ddd” })] })

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 4,
      fill: "red",
      stroke: "white"
    })
  ]
})

You’ll notice that one of the random points generated by ChatGPT is not within the boundaries of the LSOA — AI is not always right! Since this is for demo purposes only and the points mean nothing then it doesn’t worry me.

Step 4: Refine your map further

This is the point where the map becomes your own. Observable Plot has so many capabilities and it becomes a matter of asking the right questions and remembering that there will be some things it can’t do so you might have to compromise.

As a demo, I’ve asked it a few questions:

Can you make the dots a bit bigger and add numbers based on the order of the array?

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 8,
      fill: "red",
      stroke: "white"
    }),
    Plot.text(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      text: (d, i) => i + 1,
      fill: "white",
      fontSize: 10,
      fontWeight: "bold"
    })
  ]
})

Can you join the dots with arrow lines?

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.arrow(randomPositions.slice(0, -1), {
      x1: (d) => d.lon,
      y1: (d) => d.lat,
      x2: (d, i) => randomPositions[i + 1].lon,
      y2: (d, i) => randomPositions[i + 1].lat,
      stroke: "blue",
      strokeWidth: 1.5,
      headLength: 8
    }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 8,
      fill: "red",
      stroke: "white"
    }),
    Plot.text(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      text: (d, i) => i + 1,
      fill: "white",
      fontSize: 10,
      fontWeight: "bold"
    })
  ]
})

I can’t see the arrows — any compromise?

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.arrow(randomPositions.slice(0, -1), {
      x1: (d) => d.lon,
      y1: (d) => d.lat,
      x2: (d, i) => randomPositions[i + 1].lon,
      y2: (d, i) => randomPositions[i + 1].lat,
      stroke: "orange",
      strokeWidth: 2,
      headLength: 10,
      insetStart: 10,
      insetEnd: 10
    }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 8,
      fill: "red",
      stroke: "white"
    }),
    Plot.text(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      text: (d, i) => i + 1,
      fill: "white",
      fontSize: 10,
      fontWeight: "bold"
    })
  ]
})

can you make the arrow points smaller and make the arrows the same color as the dots — maybe purple?

Plot.plot({
  width: 800,
  height: 600,
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.arrow(randomPositions.slice(0, -1), {
      x1: (d) => d.lon,
      y1: (d) => d.lat,
      x2: (d, i) => randomPositions[i + 1].lon,
      y2: (d, i) => randomPositions[i + 1].lat,
      stroke: "purple",
      strokeWidth: 2,
      headLength: 5,
      insetStart: 10,
      insetEnd: 10
    }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 8,
      fill: "purple",
      stroke: "white"
    }),
    Plot.text(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      text: (d, i) => i + 1,
      fill: "white",
      fontSize: 10,
      fontWeight: "bold"
    })
  ]
})

This is where I’m going to stop — I feel happy with the result.

You’ll notice that Claude also gives you tips and hints regarding what it’s done so that you can understand the coding behind the chart if you so wish.

Step 5: Adding your own font

Depending one where you want to use your chart, you might want to add your own font.

I asked for a cell for the Google font Figjam but you can customise to your needs — although fonts can be tricky so you may need to compromise.

Claude prompt:

can you give me an ObservableHQ cell which allows me to use the Google Font Figjam in my notebook cells?

html`<style>
@import url('https://fonts.googleapis.com/css2?family=FigJam&display=swap');
</style>`

REMEMBER: this is an HTML cell not a Javascript cell — so when you press the + button, choose HTML for this code cell

Then you need to update the chart. I used another Claude prompt:

can you alter this ObservableHQ cell code to reference my new font and add a title — My chart — and subtitle — my subtitle.

Plot.plot({ width: 800, height: 600, projection: { type: “equal-earth”, domain: filtered_lsoa }, marks: [ Plot.geo(filtered_lsoa, { stroke: “black”, fill: “#ddd” }), Plot.arrow(randomPositions.slice(0, -1), { x1: (d) => d.lon, y1: (d) => d.lat, x2: (d, i) => randomPositions[i + 1].lon, y2: (d, i) => randomPositions[i + 1].lat, stroke: “purple”, strokeWidth: 2, headLength: 5, insetStart: 10, insetEnd: 10 }), Plot.dot(randomPositions, { x: (d) => d.lon, y: (d) => d.lat, r: 8, fill: “purple”, stroke: “white” }), Plot.text(randomPositions, { x: (d) => d.lon, y: (d) => d.lat, text: (d, i) => i + 1, fill: “white”, fontSize: 10, fontWeight: “bold” }) ] })

Plot.plot({
  width: 800,
  height: 600,
  title: "My chart",
  subtitle: "my subtitle",
  style: {
    fontFamily: "FigJam, sans-serif"
  },
  projection: { type: "equal-earth", domain: filtered_lsoa },
  marks: [
    Plot.geo(filtered_lsoa, { stroke: "black", fill: "#ddd" }),
    Plot.arrow(randomPositions.slice(0, -1), {
      x1: (d) => d.lon,
      y1: (d) => d.lat,
      x2: (d, i) => randomPositions[i + 1].lon,
      y2: (d, i) => randomPositions[i + 1].lat,
      stroke: "purple",
      strokeWidth: 2,
      headLength: 5,
      insetStart: 10,
      insetEnd: 10
    }),
    Plot.dot(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      r: 8,
      fill: "purple",
      stroke: "white"
    }),
    Plot.text(randomPositions, {
      x: (d) => d.lon,
      y: (d) => d.lat,
      text: (d, i) => i + 1,
      fill: "white",
      fontSize: 10,
      fontWeight: "bold"
    })
  ]
})

Step 6: Exporting your map to use elsewhere

Unless you’re going through this process as a learning exercise, you’ll want to use your maps elsewhere.

Luckily this is an easy one. Click the ellipsis by your map and click Download as PNG

That’s it. Series finished. Hopefully this should be a useful enough intro that will allow you to explore and discover on your own —with less AI assistance in the future should you so wish.

Here’s the link to the notebook.

Thanks for reading and please do reach out if you’re looking for a d3 solution — big or small — www.bmdata.co.uk


메타데이터
post_id
1272035ed130
slug
observablehq-plot-maps-for-beginners-with-some-ai-help-1272035ed130
url
https://ai.plainenglish.io/observablehq-plot-maps-for-beginners-with-some-ai-help-1272035ed130
canonical_url
https://ai.plainenglish.io/observablehq-plot-maps-for-beginners-with-some-ai-help-1272035ed130
author_url
https://medium.com/@bryony_17728
status
ok
fetched_at
2026-07-30 14:02:18