Skip to content

Commit 58199e2

Browse files
committed
add a post about time series data visualization
1 parent 9ceab9a commit 58199e2

2 files changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
^{:kindly/hide-code true
2+
:clay
3+
{:title "Exploring Time Series Data Visualization"
4+
:quarto {:author :syungb
5+
:description "A simple exploration to create time-series data visualization"
6+
:image "overlayplot.png"
7+
:type :post
8+
:date "2025-08-30"
9+
:category :clojure
10+
:tags [:dataviz :computationalfluiddynamics :cfd :vega-lite]}}}
11+
(ns scicloj.cfd.data-viz.interactive-visualization
12+
(:require
13+
[scicloj.kindly.v4.api :as kindly]
14+
[scicloj.kindly.v4.kind :as kind]
15+
[scicloj.cfd.intro.linear-1d-convection-with-array :as lin-conv]))
16+
17+
;; In my [earlier post](./scicloj/cfd/intro/linear_1d_convection_with_array.html),
18+
;; I demonstrated how to implement a linear fluid dynamics simulation (linear convection equation)
19+
;; using primitive Java arrays in a one-dimensional setting.
20+
;; The example showed how the flow velocity array gets recalculated and overwritten
21+
;; during each loop iteration, with each loop representing a time step in our simulation.
22+
;;
23+
;; While some readers might be satisfied with just seeing the final result,
24+
;; others are likely curious the process that produces the final outcome.
25+
;; What if we could watch this process in real-time? What patterns emerge as the simulation progresses?
26+
;;
27+
;; This time, I want to show how to create a simple, but more engaging and interactive visualization.
28+
;; Instead of simply overwriting arrays as we iterate through our simulation loop,
29+
;; we will accumulate each result into a sequence.
30+
;;
31+
;; Once gain, we'll use the 1-D linear convection equation from fluid dynamics as our playground
32+
;; (and reuse some code from the previous post).
33+
;; The main focus of this post is on creating interactive plots that make simulation results
34+
;; more visually interesting, so we won't dive too deep into physical implications (though the visual results
35+
;; might just inspire you to explore that side too! 😉).
36+
;;
37+
;; ### Understanding the Data
38+
;;
39+
;; Before we jump into the data visualization, let's briefly set our initial data and what it represents.
40+
;;
41+
;; ### The Setup
42+
;;
43+
;; - **x**: Sliced positions along a one-dimensional space (think of points along a line)
44+
;; - **y**: The flow velocity(`u`) at each position `x`
45+
;; - **nt**: The number of time steps we'll simulate
46+
;;
47+
;; ### A Real World Analogy (Just to add a bit of fun)
48+
;;
49+
;; Imagine a rain gutter on a rainy day with water steadily flowing through it.
50+
;; Now, picture slicing a section of this gutter and marking specific positions along its length-
51+
;; these become our `x` coordinates. The water continues to flow, then suddenly, a bird overhead drops
52+
;; a stone into the gutter, creating a sudden disturbance or **"shock"** in the water flow.
53+
;; And this becomes our **initial condition**.
54+
;;
55+
;; **Initial Conditions**: The stone drop becomes our starting point(`t = 0`), and
56+
;; at the moment of the impact, and the velocity at affected points jumps to 2 as a localized region
57+
;; (in our case, between `0.5` and `1.0` of `x` is where the imaginary bird dropped a stone),
58+
;; while the rest of the gutter maintains
59+
;; its normal flow velocity of `1`.
60+
;;
61+
^:kindly/hide-code
62+
(def init-params (assoc lin-conv/init-params
63+
:nt 100
64+
:array-x lin-conv/array-x))
65+
;;
66+
;; Our initial parameters and plot configuration are:
67+
^:kindly/hide-code (dissoc init-params :array-x)
68+
69+
^:kindly/hide-code
70+
(def array-u
71+
(let [nx (:nx lin-conv/init-params)
72+
u (float-array nx)]
73+
(dotimes [i nx]
74+
(let [x-i (aget lin-conv/array-x i)
75+
u-i (lin-conv/init-cond-fn x-i)]
76+
(aset u i u-i)))
77+
u))
78+
79+
^:kindly/hide-code
80+
(def initial-arr-u (float-array array-u))
81+
82+
^:kindly/hide-code
83+
(def default-y-domain [0.8 2.1])
84+
85+
^:kindly/hide-code
86+
(def default-plot-width-height-map {:width 500 :height 300})
87+
88+
^:kindly/hide-code
89+
(kind/vega-lite (merge default-plot-width-height-map
90+
{:mark :line
91+
:data {:values (into [] (map #(hash-map :x % :y %2) lin-conv/array-x initial-arr-u))}
92+
:encoding {:x {:title "Position X" :field "x" :type "quantitative"}
93+
:y {:title "Flow Velocity" :field "y" :type "quantitative" :scale {:domain default-y-domain}}}}))
94+
95+
;; ## Simulation Code
96+
;;
97+
;; Here is a simulation function we will use to accumulate the results:
98+
(defn simulate-accumulate!
99+
"Simulate linear convection for given a given numer of times(nt) and accumulate
100+
all intermediate states.
101+
102+
Args:
103+
- array-u: initial velocity field as a mutable array
104+
(note: we're reusing some codes from the previous post)
105+
- params: a config map with required keys: nt, nx, c, dx, and dt
106+
107+
Returns a vector of (nt + 1) velocity field vectors, where each vector represents
108+
the velocity field at a specific time step."
109+
[array-u {:keys [nt]
110+
:as params}]
111+
(loop [n 0
112+
!cum (transient [(vec array-u)])]
113+
(if (= n nt)
114+
(persistent! !cum)
115+
(recur (inc n) (conj! !cum (vec (lin-conv/mutate-linear-convection-u array-u params)))))))
116+
117+
;; Then we run the simulation and store the accumulated results:
118+
(def accumulated-u (simulate-accumulate! initial-arr-u init-params))
119+
;;
120+
;; ## Data Visualization
121+
;;
122+
;; We have a vector of numbers representing velocity values.
123+
;; With certain conditions, we want to visualize how these numbers change over time.
124+
;; We will use [Vega-Lite](https://vega.github.io/vega-lite/)'s line plots for our visualization.
125+
;;
126+
^:kindly/hide-code
127+
(def plot-params (assoc init-params :accumulated-u accumulated-u))
128+
;;
129+
;; First, we need a function that transforms accumulated results into Vega-Lite data format
130+
;; by flattening time-series velocity fields into individual data points with time step indices:
131+
(defn accumulated-u->vega-data-values [{:keys [array-x accumulated-u]}]
132+
(apply concat
133+
(map-indexed
134+
(fn [idx u-i]
135+
(map #(hash-map :idx idx :x % :y %2) array-x u-i))
136+
accumulated-u)))
137+
;;
138+
;; ### Multi-Series Overlay Plot
139+
;;
140+
;; In this first visualization, we show the simulation results as changes over time in one plot.
141+
;; Since showing all 101 time steps in one plot may not be very useful,
142+
;; we take every 10th result, and overlay each of those line plots into a single one.
143+
;;
144+
;; The plotting data is grouped by `idx`(time step), which produces
145+
;; a [multi-series colored line plot](https://vega.github.io/vega-lite/docs/line.html#multi-series-colored-line-chart).
146+
(let [[init-u & rest-u] accumulated-u
147+
plot-data (->> rest-u
148+
(partition-all 10)
149+
(map last)
150+
(into [init-u])
151+
(assoc plot-params :accumulated-u)
152+
(accumulated-u->vega-data-values))]
153+
(kind/vega-lite (merge default-plot-width-height-map
154+
{:data {:values plot-data}
155+
:mark "line"
156+
:encoding {:x {:field "x" :type "quantitative" :title "X"}
157+
:y {:field "y" :type "quantitative" :title "Flow Velocity" :scale {:domain default-y-domain}}
158+
:color {:field "idx" :type "nominal" :legend nil}}})))
159+
;;
160+
;; ### Interactive Time Step Plot
161+
;;
162+
;; In this second visualization, the plot includes an interactive slider that allows you to select
163+
;; a particular time step(`idx`) to visualize the velocity field at that specific moment.
164+
;;
165+
;; This interactive slider is implemented using Vega-Lite's
166+
;; [parameter binding feature](https://vega.github.io/vega-lite/docs/bind.html#input-element-binding).
167+
(let [select-dt-name "selectedDt"
168+
initial-dt 0
169+
dt-step 1
170+
dt-field "idx"]
171+
(kind/vega-lite (merge
172+
default-plot-width-height-map
173+
{:data {:values (accumulated-u->vega-data-values plot-params)}
174+
:transform [{:filter (str "datum." dt-field " == " select-dt-name)}]
175+
:params [{:name select-dt-name
176+
:value initial-dt
177+
:bind {:input "range"
178+
:name "Time interval idx: "
179+
:min initial-dt
180+
:max (:nt plot-params)
181+
:step dt-step}}]
182+
:mark "line"
183+
:encoding {:x {:field "x"
184+
:type "quantitative"
185+
:title "X"}
186+
:y {:field "y"
187+
:type "quantitative"
188+
:title "Flow Velocity"
189+
:scale {:domain default-y-domain}}}})))
190+
;;
191+
;; ## Wrapping Up
192+
;;
193+
;; What started as simple primitive Java array manipulation has evolved into something much more engaging!
194+
;; By accumulating our simulation states instead of overwriting those, we've made some progress to create
195+
;; interactive visualizations to be able to describe the temporal dynamics of our fluid dynamics simulation.
196+
;;
197+
;; This exploration originally began while I was preparing [a talk](https://scicloj.github.io/scinoj-light-1/sessions.html#d-viscous-fluid-flow-data-analysis-using-burgers-equation)
198+
;; for the SciCloj Light conference, where I wanted to explore better storytelling through data visualization.
199+
;;
200+
;; So far, I've only scratched the surface of what's possible. Beyond Vega-Lite, many other visualization tools
201+
;; offer diverse options to be creative and make data come alive. I believe interactive visualizations can
202+
;; transform dry numbers into compelling stories.
203+
;;
204+
;; I'll continue exploring these possibilities and share more discoveries along the way if I find any! ✨
205+
;;
94.1 KB
Loading

0 commit comments

Comments
 (0)