How-to: Joy plot
Since I created my first joy plot in autumn 2020 I have been obsessed. I will joy plot everything and anything. They are so much fun to make and are actually deceptively simple to create.
If you haven't come across joy plots before, they're essentially a series of layered line graphs which build up to give the impression of a 3D landscape. They owe their name to the cover of Joy Division's 1979 album Unknown Pleasures.
I first learned how to joy-plot by following this great tutorial by Travis White and I definitely recommend giving this a read too, particularly if you're newer to GIS: View of Cartographic Pleasures: Maps Inspired by Joy Division’s Unknown Pleasures Album Art (cartographicperspectives.org). This tutorial covers more around what I've learned since this great introduction.

You'll need
To map along with me, you're going to need just four things and they are all free - hooray!
QGIS
RStudio
Some elevation data*
A study area polygon - this will be the extent that you'll be clipping your data to. It could be a country, a city, a national park boundary or just a rectangle. I'm going to be creating my plot today for Yellowstone National Park, so I'll be using that boundary.
*For this how-to I'm going to be using SRTM data which is an almost-global (sorry Greenland) source of elevation data which you can download for free here: https://earthexplorer.usgs.gov/. You can merge these together to cover your study area in QGIS using the Merge Raster tool (under raster > misc.) - make sure you choose the right raster type. If you're using SRTM data it'll likely be Integer 16 but make sure you check this under the information tab of your layer properties. Any form of elevation data in a raster format will work just as well - I've created some really fun joy plots with LiDAR data before. It also doesn't have to be elevation data - you could take population data, jobs, covid rates - anything! It just needs to be something that communicates magnitude, and it needs to be in a raster format.
Get mapping
Like with all things in the world of data visualisation, there are multiple ways to get to a result you want. This tutorial shows my method, and it's by no means the best method in the world - just the best for me in terms of my resources and skills. As you practice, you'll learn what works best for you, too.
Prepare your transects
First of all, we need our transects (horizontal lines) that will eventually turn into our elevation line graphs. I typically think you need around 100 lines to make a great joy plot, but you may want more or less depending on the effect you're aiming for - fewer lines results in more dramatic changes between each line, whilst more closely spaced lines will generate a smoother effect.
Add your boundary polygon to QGIS and zoom to its extent.
Run the Create Grid tool (found under Vector > Research tools).
Change the grid type to line and set the grid extent to use map canvas extent (assuming you're zoomed to your boundary layer) or calculate the extent from your boundary layer.
The horizontal spacing of the lines doesn't matter as we'll be deleting those in a second.
For the vertical spacing, divide the height of your boundary layer by about 100 (depending on how many transects you want) and that is the spacing you need. For me, Yellowstone Park is around 100km "tall," so I want a horizontal line about every 1km.
Run this tool.
Delete the vertical lines (right-click your layer in the Layers panel, and select Toggle Editing, then select the features and hit 'delete' to delete them). You may want to edit your lines to make sure you catch any specific geographic features you want, such as mountain peaks - just make sure to keep the spacing even.
Clip the lines to the boundary layer (Vector > Geoprocessing Tools > Clip). The input layer is the grid layer you just created, and the overlay layer is your boundary layer.
You should end up with something like this.

Processing elevation data
Next we'll create a point dataset, with points running along each of these lines for every pixel of your elevation data. We'll append an elevation value - as well as coordinates - to each of these lines, and then export the data to a csv for plotting in R.
Firstly, import your elevation data (or whatever you're using for your height source).
Run the tool "Generate Points (Pixel Centroids) Along Line" - this can be found in the vector creation toolbox. This will create a point along your line for every pixel in your elevation data. Note that the more detailed your elevation data, the more intensive this will be - for my area I have 404,630 points to collect this data for so I can do a lot of cup-of-tea making while it runs.
Alternatively if you're finding that this is resulting in too large a dataset, run the "Points along geometry" tool (under Vector geometry) to achieve a similar - albeit less detailed - effect, but where you can specify the spacing of your points.
Next we need to join our elevation data to these points, for which we'll need to use a point sampling plugin. To install this, go Plugins > Manage and Install Plugins... > search for and install the "Point Sampling Tool."
Open this tool - it'll appear under Plugins > Analyses. Ensure the layer containing sampling points is the one you've just created, and that the layer/bands to get the fields from is set to your elevation data layer (a quirk of this tool is that the layers you want to analyse need to be turned on). Run the tool - you should now have an elevation value for each of your points - this field will be called the name of the elevation layer you used.
The final stage of this in QGIS is to add coordinates to each of your points - to do this run the "Add X/Y fields to layer" tool (under Vector table). Export this as a csv file either at the time of the analysis or afterwards by right-clicking on the layer and going Export > Save features as... I'm imaginatively calling my data "coords.csv."
And now we're ready to joyplot!
Let's joyplot!
The two main packages we'll be working with in R today are ggplot - an R stalwart for datavisualisation - and ggridges for working specifically with ridge plots. If you don't have these installed, open up a script in RStudio and use the code install.packages("ggplot2", "ggridges") to install them - and that's all you need!
The first thing we're going to do is load in our data and create a simple joyplot, shown in the code below.
Load the two packages we need - ggplot2 and ggridges
Read your csv that you previously created and create a layer with it - I'm calling mine "transects"
Load the names of your data fields and rename them something useful - make sure you're giving the fields the right names and not just copy mine!
Call a simple joyplot! You'll want longitude along the X axis and latitude along the Y, grouped by the latitude line each point originated on. The height of each line will be your elevation field, and by using stat = "identity" we're saying that we want to provide the Y values, rather than them be auto-calculated.

You should be left with something like this: your first joy-plot! It may not look pretty... or exciting... or that joy-ful, but it is a joyplot! Now we're going to go through how to style this Unknown-Pleasures-Style.

Unknown Pleasures Joyplot
There are two elements to this. Firstly, we're going to alter our actual ridges to make them a bit more exciting and stylised, as well as scaling them to make them a bit more exciting. Secondly, we're going to customise the theme of the overall plot to emulate the Unknown Pleasures style. What that means is a minimalist look, with a black background and white ridgelines, and not much else.
Within our geom_density_ridges section, we can scale our ridges (I chose to scale mine by 20, but what you choose will completely depend on your geography and desired effect) and then changing the fill to black and lines to white. Everything else we can change by adding a theme, stripping away all the marginalia and changing the panel and plot backgrounds to black.

And now you (should) have it! So fun, right? And surprisingly easy to do?

Now what?
Why not try putting your own stamp on this? Below are a few different styles I've used in the past. You can do quite a lot just in R by altering the colours and weights of your fills, lines and plot/panel backgrounds. Sometimes I like to take this even further, exporting my plot as a PDF and importing it into Adobe Illustrator (you could equally use any vector-based graphics package such as InkScape) and playing with the style in there. My absolute favourite style is a 'snow-capped peaks' style, where I use a dark background, no linework and a gradient fill with the lighter colour at the top of the peaks. I also think it can look really cool to add in a subtle drop-shadow between each ridge-plot which can make the graph look a bit more tactile, almost like it's been 3D printed.
I can't wait to see what cool effects you can come up with - and tag me at @helenmakesmaps so I can see! I hope you enjoyed my first how-to! Let me know what you thought and what you'd like me to cover next!



Great walkthrough on joy plots! I’ve used them to compare ending distributions in narrative games, and the layered ridges are perfect for showing how a single choice shifts the whole emotional curve. For anyone who enjoys that kind of analysis, I’d also suggest checking out the false sun silas; it’s a horror visual novel with 20 endings and unlock guides. Mapping its routes as joy plots would be a fascinating experiment. Thanks for the clear code and explanation.
Nice walkthrough — the joy plot is one of those charts that looks effortless but collapses fast if you don't tune the bandwidth and overlap. I usually reach for `geom_density_ridges()` in ggplot2 or the `joypy` package in Python, and ridge scaling matters more than people admit; too much overlap and you lose the tails entirely. Bookmarking this for the next time I need to show distribution drift across categories. When I'm not wrestling with plotting code, I decompress with endless word ladders on poople answer — no daily cap, no downloads. A good mental palate cleanser between figures.
Nice walkthrough — the ridge spacing and overlap tips in this Joy plot how-to are actually clear enough to follow without fiddling for an hour. I've been using ridgeline-style density curves for elevation profiles across regions, and it's striking how much geographic data suits this format. When I want a break from plotting, playworldguesser.com drops you into random street view spots and you guess where you are, which sharpens exactly the kind of regional intuition that makes these plots more readable. A ridgeline comparing guess accuracy by continent would be a fun follow-up.
Great walkthrough — the trick of ordering your factor by median before calling `geom_density_ridges()` is what finally made my ridgelines readable instead of a smear. I've been using that same stacked-density idea to compare lap-time spreads across courses, which pairs oddly well with low-poly racing games where every hill is basically a few flat polygons. When I need a break from R, I've been working through the beginner tips at https://playpolytrack.com/guides/polytrack-beginner-guide — the elevation-heavy custom tracks are surprisingly fun to sketch out as joy plots afterwards.
Such a gorgeous back hand mehndi design. The intricate patterns give the hands a stylish look without making the design feel too heavy or complicated.