15  Change Over Time

Line charts and their variants – showing change over a sequence.

(ns plotje-book.change-over-time
  (:require
   ;; Tablecloth -- dataset manipulation
   [tablecloth.api :as tc]
   ;; Kindly -- notebook rendering protocol
   [scicloj.kindly.v4.kind :as kind]
   ;; Plotje -- composable plotting
   [scicloj.plotje.api :as pj]))

Line

Connected line through data points.

(def wave {:x (range 30)
           :y (map #(Math/sin (* % 0.3)) (range 30))})
(-> wave
    (pj/lay-line :x :y))
yx051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

Grouped Lines

Color separates multiple series.

(def waves {:x (concat (range 30) (range 30))
            :y (concat (map #(Math/sin (* % 0.3)) (range 30))
                       (map #(Math/cos (* % 0.3)) (range 30)))
            :fn (concat (repeat 30 :sin) (repeat 30 :cos))})
(-> waves
    (pj/lay-line :x :y {:color :fn}))
yxfnsincos051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

Thick Line

Constant stroke width via :size.

(-> wave
    (pj/lay-line :x :y {:size 4}))
yx051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

Line with Points

Overlay points on a grouped line plot.

(def growth
  {:day [1 2 3 4 5 1 2 3 4 5]
   :value [10 15 13 18 22 8 12 11 16 19]
   :group [:a :a :a :a :a :b :b :b :b :b]})
(-> growth
    (pj/pose :day :value {:color :group})
    pj/lay-line
    pj/lay-point)
valuedaygroupab1.01.52.02.53.03.54.04.55.0810121416182022

Step

Horizontal-then-vertical connected points.

(-> {:x [1 2 3 4 5]
     :y [2 4 1 5 3]}
    (pj/lay-step :x :y)
    pj/lay-point)
yx1.01.52.02.53.03.54.04.55.01.01.52.02.53.03.54.04.55.0

Step by Group

Grouped step lines.

(-> growth
    (pj/pose :day :value {:color :group})
    pj/lay-step
    pj/lay-point)
valuedaygroupab1.01.52.02.53.03.54.04.55.0810121416182022

Stacked Step

Each group stacks above the previous, with horizontal-then-vertical segments.

(-> {:x (concat (range 5) (range 5) (range 5))
     :y (concat [1 2 3 4 5] [2 2 2 2 2] [3 1 2 1 2])
     :group (concat (repeat 5 "A") (repeat 5 "B") (repeat 5 "C"))}
    (pj/lay-step :x :y {:position :stack :color :group}))
yxgroupABC0.00.51.01.52.02.53.03.54.01.01.52.02.53.03.54.04.55.0

Area

Filled area under a line.

(-> {:x (range 30)
     :y (map #(Math/sin (* % 0.3)) (range 30))}
    (pj/lay-area :x :y))
yx051015202530-1.0-0.8-0.6-0.4-0.20.00.20.40.60.81.0

Stacked Area

Each group fills above the previous.

(-> {:x (concat (range 10) (range 10) (range 10))
     :y (concat [1 2 3 4 5 4 3 2 1 0]
                [2 2 2 3 3 3 2 2 2 2]
                [1 1 1 1 2 2 2 1 1 1])
     :group (concat (repeat 10 "A") (repeat 10 "B") (repeat 10 "C"))}
    (pj/lay-area :x :y {:position :stack :color :group}))
yxgroupABC02468012345678910

Dates on the x-axis

Real time-series usually have an actual date column, not an integer step. Plotje detects temporal columns (java.util.Date via #inst, java.time.LocalDate, LocalDateTime, Instant) and picks calendar-aware tick labels automatically.

(-> {:date [#inst "2024-01-01" #inst "2024-02-01" #inst "2024-03-01"
            #inst "2024-04-01" #inst "2024-05-01" #inst "2024-06-01"]
     :temperature [3 5 9 14 19 23]}
    (pj/lay-line :date :temperature)
    pj/lay-point)
temperaturedateJan-02Jan-18Feb-03Feb-19Mar-06Mar-22Apr-07Apr-23May-09May-2524681012141618202224

Multiple Series Over Time

Pass {:color :group} to get one line per category. Rows are drawn in their given order, so pre-sort by date if your data is not already sorted.

(def months
  [#inst "2024-01-01" #inst "2024-02-01" #inst "2024-03-01"
   #inst "2024-04-01" #inst "2024-05-01" #inst "2024-06-01"])
(-> {:date        (concat months months)
     :temperature [3  5  9 14 19 23
                   15 17 19 22 25 28]
     :city        (concat (repeat 6 "Zurich")
                          (repeat 6 "Athens"))}
    (pj/lay-line :date :temperature {:color :city})
    pj/lay-point)
temperaturedatecityZurichAthensJan-02Jan-23Feb-13Mar-05Mar-26Apr-16May-07May-28510152025

Area Over Dates

Filled area also works on a date axis – useful for cumulative metrics where the volume below the curve carries meaning.

(-> {:date [#inst "2024-01-01" #inst "2024-02-01" #inst "2024-03-01"
            #inst "2024-04-01" #inst "2024-05-01" #inst "2024-06-01"]
     :sales [10 25 30 22 35 40]}
    (pj/lay-area :date :sales))
salesdateJan-02Jan-18Feb-03Feb-19Mar-06Mar-22Apr-07Apr-23May-09May-250510152025303540

See Inference Rules for details on how dates are detected and formatted.

Zero-Line Baseline

Time series with positive and negative values often benefit from a horizontal reference at zero, drawn with pj/lay-rule-h. The rule takes its position from the options map – not from a data column – so it’s an annotation, not a data layer.

(-> {:t (range 12)
     :delta [-3 -1 -2 0 2 4 -1 3 5 -2 1 4]}
    (pj/lay-line :t :delta)
    pj/lay-point
    (pj/lay-rule-h {:y-intercept 0 :color "#888"}))
deltat01234567891011-3-2-1012345

What’s Next

  • Timelines – events, intervals, and schedules on a time axis (Gantt charts, Marey diagrams, annotated time series)
  • Relationships – heatmaps, contours, and 2D density
  • Polar Coordinates – radial charts for cyclical data
  • Gallery – more chart variations with side-by-side code
source: notebooks/plotje_book/change_over_time.clj