11module ParallelPlots
22
3- export create_parallel_coordinates_plot
4-
53using CairoMakie
64using DataFrames
75
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
128170end
171+
172+
173+
129174end
0 commit comments