15  Cookbook

Practical plotting recipes β€” how to combine marks, overlay stats, and build publication-ready charts.

(ns napkinsketch-book.cookbook
  (:require
   ;; Shared datasets for these docs
   [napkinsketch-book.datasets :as data]
   ;; Tablecloth β€” dataset manipulation
   [tablecloth.api :as tc]
   ;; Kindly β€” notebook rendering protocol
   [scicloj.kindly.v4.kind :as kind]
   ;; Napkinsketch β€” composable plotting
   [scicloj.napkinsketch.api :as sk]
   ;; Fastmath β€” random number generation
   [fastmath.random :as rng]
   ;; Java-time β€” idiomatic date/time construction
   [java-time.api :as jt]))

Quick Recipes

Boxplot with jittered points

Overlay raw observations on a boxplot summary. The auto-jitter detects the categorical axis and constrains points to the band width.

(-> data/iris
    (sk/lay-boxplot :species :sepal_length)
    (sk/lay-point {:jitter true :alpha 0.3}))
sepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.0

Histogram with density overlay

Normalize the histogram to density scale so it is comparable with the KDE (kernel density estimation) curve.

(-> data/iris
    (sk/lay-histogram :sepal_length {:normalize :density :alpha 0.5})
    sk/lay-density)
sepal length3456789100.00.050.10.150.20.250.30.350.40.450.5

Scatter with regression lines

Fit a linear regression per group to reveal trends across species.

(-> data/iris
    (sk/view :sepal_length :sepal_width {:color :species})
    (sk/lay-point {:alpha 0.6})
    sk/lay-lm)
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Violin with jittered points

Show the density shape and every observation together.

(-> data/iris
    (sk/lay-violin :species :petal_width {:alpha 0.3})
    (sk/lay-point {:jitter true :alpha 0.4}))
petal widthspeciessetosaversicolorvirginica0.00.51.01.52.02.53.0

Time series with multiple layers

Combine area, line, and points. Date columns are detected automatically β€” ticks snap to calendar boundaries.

(def ts-dates (take 52 (jt/iterate jt/plus (jt/local-date 2020 1 6) (jt/weeks 1))))
(def ts-ds {:date ts-dates
            :value (map #(+ 100.0 (* 30.0 (Math/sin (* (double %) 0.12))))
                        (range 52))})
(-> ts-ds
    (sk/lay-area :date :value {:alpha 0.2})
    sk/lay-line
    (sk/lay-point {:alpha 0.5}))
valuedateFeb-01Mar-01Apr-01May-01Jun-01Jul-01Aug-01Sep-01Oct-01Nov-01Dec-01020406080100120

Faceted comparison

Split a scatter plot by species to compare patterns side by side.

(-> data/iris
    (sk/lay-point :sepal_length :sepal_width {:color :species})
    (sk/facet :species))
sepal widthsepal lengthspeciessetosaversicolorvirginica682.02.53.03.54.04.56868setosaversicolorvirginica

Annotated chart

Add reference lines and shaded bands to highlight regions of interest. Pass {:alpha …} to control band opacity.

(-> data/iris
    (sk/lay-point :sepal_length :sepal_width {:color :species})
    (sk/lay (sk/rule-h 3.0) (sk/band-v 5.5 6.5 {:alpha 0.3})))
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Ridgeline with color

Compare distribution shapes across categories with overlapping density curves. Grid lines at each baseline aid comparison.

(-> data/iris
    (sk/lay-ridgeline :species :sepal_length {:color :species}))
speciessepal lengthspeciessetosaversicolorvirginica456789setosaversicolorvirginica

Stacked bars (proportions)

Show the proportion of each species per island using 100% stacked bars.

(-> data/penguins
    (sk/lay-stacked-bar-fill :island {:color :species}))
islandspeciesAdelieChinstrapGentooTorgersenBiscoeDream0.00.10.20.30.40.50.60.70.80.91.0

Multi-Layer Compositions

Overall regression with per-group points

Color points by group, but fit a single overall regression line.

(-> data/iris
    (sk/lay-point :sepal_length :sepal_width {:color :species})
    sk/lay-lm)
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Points with Error Bars

Combining point and errorbar layers shows measurements with uncertainty.

(def experiment
  {:condition ["A" "B" "C" "D"]
   :mean [10.0 15.0 12.0 18.0]
   :ci_lo [8.0 12.0 9.5 15.5]
   :ci_hi [12.0 18.0 14.5 20.5]})
(-> experiment
    (sk/lay-point :condition :mean {:size 5})
    (sk/lay-errorbar {:ymin :ci_lo :ymax :ci_hi}))
meanconditionABCD8101214161820

Lollipop with error bars

Composing lollipop stems with error bars.

(-> experiment
    (sk/lay-lollipop :condition :mean)
    (sk/lay-errorbar {:ymin :ci_lo :ymax :ci_hi}))
meanconditionABCD0246810121416182022

Summary (Mean Β± SE) with Raw Data

The summary method computes mean and SE (standard error) per category.

(-> data/iris
    (sk/lay-point :species :sepal_length {:alpha 0.3 :jitter 5})
    (sk/lay-summary {:color :species}))
sepal lengthspeciesspeciessetosaversicolorvirginicasetosaversicolorvirginica4.55.05.56.06.57.07.58.0

Tipping behavior

Scatter + per-group regression to compare smoker tipping patterns.

(-> data/tips
    (sk/view :total_bill :tip {:color :smoker})
    sk/lay-point
    sk/lay-lm
    (sk/options {:title "Tipping Behavior"
                 :x-label "Total Bill ($)"
                 :y-label "Tip ($)"}))
Tipping BehaviorTip ($)Total Bill ($)smokerNoYes510152025303540455012345678910

More Recipes

Confidence ribbon

A scatter plot with per-group linear regressions and 95% confidence ribbons.

(-> data/iris
    (sk/view :sepal_length :sepal_width {:color :species})
    (sk/lay-point {:alpha 0.5})
    (sk/lay-lm {:se true})
    (sk/options {:title "Sepal Regression with Confidence Bands"}))
Sepal Regression with Confidence Bandssepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Stacked vs grouped bars

Side-by-side comparison: default dodged bars vs stacked bars.

(-> data/tips
    (sk/lay-bar :day {:color :sex})
    (sk/options {:title "Dodged Bars (default)"}))
Dodged Bars (default)daysexFemaleMaleSunSatThurFri0102030405060
(-> data/tips
    (sk/lay-stacked-bar :day {:color :sex})
    (sk/options {:title "Stacked Bars"}))
Stacked BarsdaysexFemaleMaleSunSatThurFri0102030405060708090

Step line

A step plot for discrete time series data β€” useful when values hold constant between observations.

(def daily-temps
  {:day (range 1 15)
   :temp [12 14 14 16 18 17 15 13 14 16 19 21 20 18]})
(-> daily-temps
    (sk/lay-step :day :temp {:color "#2196F3"})
    (sk/lay-point {:color "#2196F3" :size 3})
    (sk/options {:title "Daily Temperature (Step)"}))
Daily Temperature (Step)tempday246810121412131415161718192021

Contour + scatter

Density contour lines overlaid on a scatter plot β€” reveals high-density regions in a point cloud.

(-> data/iris
    (sk/lay-point :sepal_length :sepal_width {:color :species :alpha 0.4})
    (sk/lay-contour {:levels 5}))
sepal widthsepal lengthspeciessetosaversicolorvirginica3.03.54.04.55.05.56.06.57.07.58.08.59.01.52.02.53.03.54.04.55.0

Label marks

Annotate specific data points with text labels.

(def top5 (-> data/iris (tc/order-by :sepal_length :desc) (tc/head 5)))
(-> top5
    (sk/lay-point :sepal_length :sepal_width {:size 5})
    (sk/lay-label {:text :species :nudge-y 0.15}))
sepal widthsepal lengthvirginicavirginicavirginicavirginicavirginica7.77.727.747.767.787.87.827.847.867.887.92.62.83.03.23.43.63.8

Custom palette map

Assign specific colors to each category using a palette map.

(-> data/iris
    (sk/lay-point :sepal_length :sepal_width {:color :species})
    (sk/options {:palette {:setosa "#E91E63"
                           :versicolor "#4CAF50"
                           :virginica "#2196F3"}
                 :title "Custom Palette Map"}))
Custom Palette Mapsepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Fixed aspect ratio

Use sk/coord :fixed so one unit on x equals one unit on y. This makes the plot square when x and y have equal ranges.

(-> data/iris
    (sk/view :sepal_length :sepal_width {:color :species})
    sk/lay-point
    sk/lay-lm
    (sk/coord :fixed)
    (sk/options {:title "Fixed Aspect Ratio"}))
Fixed Aspect Ratiosepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Diverging color scale

Use :color-scale :diverging with :color-midpoint to center a red-white-blue gradient on a meaningful value (e.g., zero).

(-> {:x (range 20)
     :y (map #(Math/sin (/ % 3.0)) (range 20))
     :change (map #(- % 10) (range 20))}
    (sk/lay-point :x :y {:color :change})
    (sk/options {:color-scale :diverging
                 :color-midpoint 0
                 :title "Diverging Color Scale"}))
Diverging Color Scaleyxchange-10.009.000024681012141618-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

LOESS (Local Regression) Confidence Ribbon

Add {:se true} to a LOESS smoother for a bootstrap confidence band.

(-> data/iris
    (sk/view :sepal_length :sepal_width {:color :species})
    sk/lay-point
    (sk/lay-loess {:se true})
    (sk/options {:title "LOESS with 95% CI"}))
LOESS with 95% CIsepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Multi-plot dashboard

Use sk/arrange to combine independent plots into a grid layout.

(def iris-sepal
  (-> data/iris
      (sk/lay-point :sepal_length :sepal_width {:color :species})
      (sk/options {:title "Sepal" :width 300 :height 250})))
(def iris-petal
  (-> data/iris
      (sk/lay-point :petal_length :petal_width {:color :species})
      (sk/options {:title "Petal" :width 300 :height 250})))
(sk/arrange [iris-sepal iris-petal]
            {:title "Iris Dashboard" :cols 2})
Iris Dashboard
Sepalsepal widthsepal lengthspeciessetosaversicolorvirginica56782.02.53.03.54.04.5Petalpetal widthpetal lengthspeciessetosaversicolorvirginica2460.00.51.01.52.02.5

Labeled scatter

Combine points with text labels, using nudge to offset text from data points.

(def top-cities
  {:city ["Tokyo" "Delhi" "Shanghai" "SΓ£o Paulo" "Mumbai"]
   :population [37.4 32.9 29.2 22.4 21.7]
   :area [2194 1484 6341 1521 603]})
(-> top-cities
    (sk/lay-point :area :population)
    (sk/lay-text {:text :city :nudge-y 1.0})
    (sk/options {:title "Population vs Area"}))
Population vs AreapopulationareaTokyoDelhiShanghaiSΓ£o PauloMumbai500100015002000250030003500400045005000550060006500222426283032343638

Simulated Data

Generate data from a known model and verify the regression recovers it.

(let [r (rng/rng :jdk 77)
      xs (range 0 10 0.5)
      ys (map #(+ (* 3 %)
                  5
                  (* 2 (- (rng/drandom r) 0.5)))
              xs)]
  (-> {:x xs :y ys}
      (sk/lay-point :x :y)
      sk/lay-lm
      (sk/options {:title "Simulated: y = 3x + 5 + noise"})))
Simulated: y = 3x + 5 + noiseyx01234567895101520253035

Analytical Walkthroughs

Palmer Penguins

Bill dimensions separate the three species clearly.

(-> data/penguins
    (sk/lay-point :bill_length_mm :bill_depth_mm {:color :species})
    (sk/options {:title "Palmer Penguins: Bill Dimensions"}))
Palmer Penguins: Bill Dimensionsbill depth mmbill length mmspeciesAdelieChinstrapGentoo354045505560131415161718192021

Per-species regression reveals different slopes.

(-> data/penguins
    (sk/view :bill_length_mm :bill_depth_mm {:color :species})
    sk/lay-point
    sk/lay-lm
    (sk/options {:title "Bill Length vs Depth with Regression"}))
Bill Length vs Depth with Regressionbill depth mmbill length mmspeciesAdelieChinstrapGentoo354045505560131415161718192021

Without grouping, the overall trend appears negative β€” an example of Simpson’s paradox.

(-> data/penguins
    (sk/lay-point :bill_length_mm :bill_depth_mm {:color :species})
    sk/lay-lm
    (sk/options {:title "Simpson's Paradox: Overall vs Per-Group Trend"}))
Simpson's Paradox: Overall vs Per-Group Trendbill depth mmbill length mmspeciesAdelieChinstrapGentoo354045505560131415161718192021

Species distribution across islands.

(-> data/penguins
    (sk/lay-bar :island {:color :species})
    (sk/options {:title "Species by Island"}))
Species by IslandislandspeciesAdelieChinstrapGentooTorgersenBiscoeDream020406080100120

Flipper length vs body mass β€” a strong positive correlation.

(-> data/penguins
    (sk/view :flipper_length_mm :body_mass_g {:color :species})
    sk/lay-point
    sk/lay-lm
    (sk/options {:title "Flipper Length vs Body Mass"}))
Flipper Length vs Body Massbody mass gflipper length mmspeciesAdelieChinstrapGentoo1701801902002102202303000350040004500500055006000

Body mass distribution by species.

(-> data/penguins
    (sk/lay-histogram :body_mass_g {:color :species})
    (sk/options {:title "Body Mass Distribution"}))
Body Mass Distributionbody mass gspeciesAdelieChinstrapGentoo30003500400045005000550060000510152025

Tips

Tipping behavior: smokers vs non-smokers.

(-> data/tips
    (sk/view :total_bill :tip {:color :smoker})
    sk/lay-point
    sk/lay-lm
    (sk/options {:title "Tipping: Smokers vs Non-Smokers"
                 :x-label "Total Bill ($)" :y-label "Tip ($)"}))
Tipping: Smokers vs Non-SmokersTip ($)Total Bill ($)smokerNoYes510152025303540455012345678910

Tip amounts by day, colored by meal time.

(-> data/tips
    (sk/lay-bar :day {:color :time})
    (sk/options {:title "Visits by Day and Meal Time"}))
Visits by Day and Meal TimedaytimeDinnerLunchSunSatThurFri0102030405060708090

Stacked view of the same data.

(-> data/tips
    (sk/lay-stacked-bar :day {:color :time})
    (sk/options {:title "Visits by Day (Stacked)"}))
Visits by Day (Stacked)daytimeDinnerLunchSunSatThurFri0102030405060708090

Horizontal bar chart of party sizes.

(-> data/tips
    (sk/lay-bar :day {:color :sex})
    (sk/coord :flip)
    (sk/options {:title "Day by Gender (Horizontal)"}))
Day by Gender (Horizontal)daysexFemaleMale0102030405060SunSatThurFri

MPG

Horsepower vs fuel efficiency, colored by origin.

(-> data/mpg
    (sk/view :horsepower :mpg {:color :origin})
    sk/lay-point
    sk/lay-lm
    (sk/options {:title "Horsepower vs MPG by Origin"}))
Horsepower vs MPG by Originmpghorsepoweroriginusajapaneurope4060801001201401601802002201015202530354045

Displacement vs MPG β€” another negative correlation.

(-> data/mpg
    (sk/lay-point :displacement :mpg {:color :origin})
    (sk/options {:title "Engine Displacement vs Fuel Efficiency"}))
Engine Displacement vs Fuel Efficiencympgdisplacementoriginusajapaneurope501001502002503003504004501015202530354045

Count of cars by origin.

(-> data/mpg
    (sk/lay-bar :origin)
    (sk/options {:title "Cars by Origin"}))
Cars by Originoriginusajapaneurope050100150200250

What’s Next

  • Configuration β€” control dimensions, palettes, and themes at every scope
  • Customization β€” annotations, tooltips, and brush selection
source: notebooks/napkinsketch_book/cookbook.clj