Skip to content

Commit 2bbe736

Browse files
authored
Merge pull request #29 from moritz155/24-use-observables
24 use observables
2 parents cbc8904 + 44f1f10 commit 2bbe736

14 files changed

Lines changed: 739 additions & 166 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
*.jl.cov
33
*.jl.mem
44
.idea/
5+
6+
#Test output images
7+
*.png
8+
*.svg
9+
*.mp4
10+
511
/Manifest.toml
612
/docs/Manifest.toml
713
#/docs/build/

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
name = "ParallelPlots"
22
uuid = "79f8b2c9-78a2-4289-b087-49f7cf65a4c9"
33
authors = ["Moritz Schelten <moritz155@win.tu-berlin.de>", "Leon Haufe <leon.haufe@campus.tu-berlin.de>"]
4-
version = "1.0.0-DEV"
4+
version = "2.0.0-DEV"
55

66
[deps]
77
AbstractPlotting = "537997a7-5e4e-5d89-9595-2241ea00577e"
88
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
99
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
1010

1111
[compat]
12-
AbstractPlotting = "0.18.3"
1312
julia = "1.10"
13+
#CairoMakie = "0.12.18"

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,22 @@ You need to use the package (1-3) and install the dependencies (4-5)
3333

3434
### Usage
3535
```@example
36-
julia> ParallelPlots.create_parallel_coordinates_plot(DataFrame(height=160:180,weight=60:80,age=20:40))
37-
36+
julia> using ParallelPlots
37+
julia> parallelplot(DataFrame(height=160:180,weight=60:80,age=20:40))
38+
```
39+
```
3840
# If you want to normalize the Data, you can add the value normalized=true, default is false
39-
julia> ParallelPlots.create_parallel_coordinates_plot(DataFrame(height=160:180,weight=reverse(60:80),age=20:40),normalize=true)
40-
41+
julia> parallelplot(DataFrame(height=160:180,weight=reverse(60:80),age=20:40),normalize=true)
42+
```
43+
```
4144
# If you want to set the size of the plot (default width:800, height:600)
42-
julia> ParallelPlots.create_parallel_coordinates_plot( DataFrame(height=160:180,weight=60:80,age=20:40), scene_width=200, scene_height=200 )
45+
julia> parallelplot( DataFrame(height=160:180,weight=60:80,age=20:40), figure = (resolution = (300, 300),) )
46+
```
47+
```
48+
# You can update as well the Graph with Observables
4349
50+
julia> df_observable = Observable(DataFrame(height=160:180,weight=60:80,age=20:40))
51+
julia> fig, ax, sc = parallelplot(df_observable)
4452
```
4553

4654
Please read the [Docs](/docs/build/index.html) for further Information

src/ParallelPlots.jl

Lines changed: 112 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module ParallelPlots
22

3-
export create_parallel_coordinates_plot
4-
53
using CairoMakie
64
using DataFrames
75

@@ -34,15 +32,12 @@ end
3432

3533

3634

37-
3835
"""
39-
create_parallel_coordinates_plot(data::DataFrame, normalize::Bool)
40-
4136
- Julia version: 1.10.5
4237
4338
# Constructors
4439
```julia
45-
ParallelPlots.create_parallel_coordinates_plot(data::DataFrame; normalize::Bool=false, scene_width::Integer=800, scene_height::Integer=600)
40+
ParallelPlot(data::DataFrame; normalize::Bool=false)
4641
```
4742
4843
# Arguments
@@ -52,78 +47,128 @@ ParallelPlots.create_parallel_coordinates_plot(data::DataFrame; normalize::Bool=
5247
5348
# Examples
5449
```@example
55-
julia> ParallelPlots.create_parallel_coordinates_plot(DataFrame(height=160:180,weight=60:80,age=20:40))
50+
julia> using ParallelPlots
51+
julia> parallelplot(DataFrame(height=160:180,weight=60:80,age=20:40))
5652
5753
# If you want to normalize the Data, you can add the value normalized=true, default is false
58-
julia> ParallelPlots.create_parallel_coordinates_plot(DataFrame(height=160:180,weight=reverse(60:80),age=20:40),normalize=true)
54+
julia> parallelplot(DataFrame(height=160:180,weight=reverse(60:80),age=20:40),normalize=true)
5955
60-
# If you want to set the size of the plot (default width:800, height:600)
61-
julia> ParallelPlots.create_parallel_coordinates_plot( DataFrame(height=160:180,weight=60:80,age=20:40), scene_width=200, scene_height=200 )
56+
# If you want to set the size of the plot
57+
julia> parallelplot( DataFrame(height=160:180,weight=60:80,age=20:40), figure = (resolution = (300, 300),) )
6258
63-
```
59+
# You can update as well the Graph with Observables
6460
61+
julia> df_observable = Observable(DataFrame(height=160:180,weight=60:80,age=20:40))
62+
julia> fig, ax, sc = parallelplot(df_observable)
63+
```
6564
6665
"""
67-
function create_parallel_coordinates_plot(data::DataFrame; normalize::Bool=false, scene_width::Integer=800, scene_height::Integer=600)
68-
69-
# check the given DataFrame
70-
input_check(data)
71-
72-
# Normalize the data if required
73-
if normalize
74-
data = normalize_DF(data)
75-
end
66+
@recipe(ParallelPlot, df) do scene
67+
Attributes(
68+
# size, normalize attributes
69+
normalize = false
70+
)
71+
end
72+
73+
74+
function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
75+
76+
# our first parameter is the DataFrame-Observable
77+
df_observable = pp[1]
7678

77-
# Parse the DataFrame into a list of arrays
78-
parsed_data = [data[!, col] for col in names(data)]
79-
80-
# Compute limits for each column
81-
limits = [(minimum(col), maximum(col)) for col in parsed_data]
82-
83-
let
84-
# creates the Scene for the Plot
85-
scene = Scene(resolution = (scene_width, scene_height), camera=campixel!)
86-
numberFeatures = length(parsed_data) # Number of features, equivalent to the X Axis
87-
sampleSize = size(data, 1) # Number of samples, equivalent to the Y Axis
88-
89-
# Plot dimensions
90-
width = scene_width * 0.75 # 75% of scene width
91-
height = scene_height * 0.75 # 75% of scene width
92-
offset = min(scene_width, scene_height) * 0.15 # 15% of scene dimensions
93-
94-
# Create axes
95-
for i in 1:numberFeatures
96-
# x will be used to split the Scene for each feature
97-
x = (i - 1) / (numberFeatures - 1) * width
98-
# LineAxis will create one Axis Vertical, for each Feature one Axis
99-
MakieLayout.LineAxis(scene, limits=limits[i],
100-
spinecolor=:black, labelfont="Arial",
101-
ticklabelfont="Arial", spinevisible=true,
102-
minorticks=IntervalsBetween(2),
103-
# the lowest and highest point to maximize the Axis from Bottom to Top
104-
endpoints=Point2f0[(offset + x, offset), (offset + x, offset + height)],
105-
ticklabelalign=(:right, :center), labelvisible=true,
106-
# using the names of the dataframe for display the axis
107-
label=names(data)[i])
79+
# this helper function will update our observables
80+
# whenever df_observable change
81+
function update_plot(data)
82+
83+
# check the given DataFrame
84+
input_check(data)
85+
86+
# Normalize the data if required
87+
if pp.normalize[] # TODO: what happens when the parameter is an observeable to? will it update?
88+
data = normalize_DF(data)
10889
end
10990

110-
# Draw lines connecting points for each row
111-
for i in 1:sampleSize
112-
dataPoints = [
113-
# calcuating the point respectivly of the width and height in the Screen
114-
Point2f0(
115-
# calculates which feature the Point should be on
116-
offset + (j - 1) / (numberFeatures - 1) * width,
117-
# calculates the Y axis value
118-
(parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset
119-
)
120-
# iterates through the Features and creates for each feature the samplePoint (above)
121-
for j in 1:numberFeatures
122-
]
123-
lines!(scene, dataPoints, color=get(Makie.ColorSchemes.inferno, (i - 1) / (sampleSize - 1)),
124-
show_axis=false)
91+
# Parse the DataFrame into a list of arrays
92+
parsed_data = [data[!, col] for col in names(data)]
93+
94+
# Compute limits for each column
95+
limits = [(minimum(col), maximum(col)) for col in parsed_data]
96+
97+
let
98+
# get the scene
99+
fig = current_figure()
100+
scene = fig.scene
101+
102+
# reset scene
103+
empty!(fig.scene)
104+
trim!(fig.layout)
105+
empty!(fig.content)
106+
fig.current_axis[] = nothing
107+
108+
# get the widht and height
109+
scene_width,scene_height = size(scene)
110+
111+
112+
113+
numberFeatures = length(parsed_data) # Number of features, equivalent to the X Axis
114+
sampleSize = size(data, 1) # Number of samples, equivalent to the Y Axis
115+
116+
# Plot dimensions
117+
width = scene_width[] * 0.75 # 75% of scene width
118+
height = scene_height[] * 0.75 # 75% of scene width
119+
offset = min(scene_width[], scene_height[]) * 0.15 # 15% of scene dimensions
120+
121+
# Create axes
122+
for i in 1:numberFeatures
123+
# x will be used to split the Scene for each feature
124+
x = (i - 1) / (numberFeatures - 1) * width
125+
# LineAxis will create one Axis Vertical, for each Feature one Axis
126+
MakieLayout.LineAxis(scene, limits=limits[i],
127+
spinecolor=:black, labelfont="Arial",
128+
ticklabelfont="Arial", spinevisible=true,
129+
minorticks=IntervalsBetween(2),
130+
# the lowest and highest point to maximize the Axis from Bottom to Top
131+
endpoints=Point2f0[(offset + x, offset), (offset + x, offset + height)],
132+
ticklabelalign=(:right, :center), labelvisible=true,
133+
# using the names of the dataframe for display the axis
134+
label=names(data)[i])
135+
end
136+
137+
# Draw lines connecting points for each row
138+
for i in 1:sampleSize
139+
dataPoints = [
140+
# calcuating the point respectivly of the width and height in the Screen
141+
Point2f0(
142+
# calculates which feature the Point should be on
143+
offset + (j - 1) / (numberFeatures - 1) * width,
144+
# calculates the Y axis value
145+
(parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset
146+
)
147+
# iterates through the Features and creates for each feature the samplePoint (above)
148+
for j in 1:numberFeatures
149+
]
150+
lines!(scene, dataPoints, color=get(Makie.ColorSchemes.inferno, (i - 1) / (sampleSize - 1)),
151+
show_axis=false)
152+
end
153+
return scene
125154
end
126-
return scene
155+
127156
end
157+
158+
# connect `update_plot` so that it is called whenever the DataFrame changes
159+
Makie.Observables.onany(update_plot, df_observable)
160+
161+
# then call it once manually with the first dataFrame
162+
# contents so we prepopulate all observables with correct values
163+
update_plot(df_observable[])
164+
165+
# some Random Plot, else we get an error
166+
lines!(pp, 1:9, iseven.(1:9) .- 0; color = :tomato)
167+
168+
# lastly we return the new ParallelPlot
169+
pp
128170
end
171+
172+
173+
129174
end

0 commit comments

Comments
 (0)