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+ ; ;
0 commit comments