diff --git a/Project.toml b/Project.toml
index d56dc16..88ee9d6 100644
--- a/Project.toml
+++ b/Project.toml
@@ -6,10 +6,8 @@ version = "2.0.1-DEV"
[deps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
-Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
[compat]
CairoMakie = "0.12.18"
DataFrames = "1.7.0"
-Interpolations = "0.15.1"
julia = "1.10"
diff --git a/README.md b/README.md
index 22361b1..6f5bbfd 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# ParallelPlots
-[](https://moritz155.github.io/ParallelPlots.jl/stable/)
-[](https://moritz155.github.io/ParallelPlots.jl/dev/)
+[](https://moritz155.github.io/ParallelPlots/stable/)
+[](https://moritz155.github.io/ParallelPlots/dev/)
[](https://github.com/moritz155/ParallelPlots/actions/workflows/CI.yml?query=branch%3Amain)
[](https://codecov.io/gh/moritz155/ParallelPlots)
@@ -10,27 +10,21 @@ This Project is for the TU-Berlin Course "Julia Programming for Machine Learning
Please make sure, that Julia `1.10` is used!
This Module will return you a nice Scene you can use to display your Data with [Parallel Coordinates](https://en.wikipedia.org/wiki/Parallel_coordinates)
-
+
_This Module was created with PkgTemplates.jl_
## Getting Started
-### Install Dependencies & Use Package
-Please refer to this [Link](https://adrianhill.de/julia-ml-course/lectures/E1_Installation.html) for Installation of Julia
-
-You need to use the package (1-3) and install the dependencies (4-5)
-1. Open Julia with `julia` in your command prompt
-2. Open the package manager with `]`
-3. Using our Package
- * `activate /path/to/package`
- or
- `activate .` when Julia was opened with command prompt already in package path
-
- * _you will then see: `(ParallelPlots) pkg>`_
-4. go back to `julia>` by pressing `CMD`+`C`
-5. `Import ParallelPlots` to download Dependencies and use the Package from Command Line
-
+### Install Dependencies & Use ParallelPlots
+#### Script/REPL
+`Pkg> add https://github.com/moritz155/ParallelPlots`
+#### Notebook
+```
+using Pkg
+Pkg.add(url="https://github.com/moritz155/ParallelPlots")
+using ParallelPlots
+```
### Usage
#### Available Parameter
@@ -92,23 +86,23 @@ parallelplot(df,
Please read the [Docs](/docs/build/index.html) for further Information
-### Working on this Package / Cheatsheet
-1. Using the Package
- * `activate /path/to/package`
- or
- `activate .` when Julia was opened with command prompt already in package path
-
- * _you will then see: `(ParallelPlots) pkg>`_
+### Working on ParallelPlots / Cheatsheet
+1. Using ParallelPlots
+ * Moving to the project folder
+ * `julia --project`
+ * You will see `julia>`
+ * To move to the pkg, type in `]`
+
2. Running commands
* Adding external Dependencies
- - `add DepName`
- * Run Tests to check if Package is still working as intended
- - `test`
+ - `(ParallelPlots) pkg>add 'DepName'`
+ * Run Tests to check if ParallelPlots is still working as intended
+ - `(ParallelPlots) pkg>test`
* Build
- - `build`
+ - `(ParallelPlots) pkg>build`
* Precompile
- - `precompile`
+ - `(ParallelPlots) pkg>precompile`
#### Create Docs
diff --git a/src/ParallelPlots.jl b/src/ParallelPlots.jl
index e759df1..c0879a5 100644
--- a/src/ParallelPlots.jl
+++ b/src/ParallelPlots.jl
@@ -2,7 +2,6 @@ module ParallelPlots
using CairoMakie
using DataFrames
-#using Interpolations
function normalize_DF(data::DataFrame)
@@ -16,17 +15,16 @@ end
function input_data_check(data::DataFrame)
- if data === nothing
+ if isnothing(data)
throw(ArgumentError("Data cannot be nothing"))
end
if size(data, 2) < 2 # otherwise there will be a nullpointer exception later
- throw(ArgumentError("Data must have at least two columns"))
+ throw(ArgumentError("Data must have at least two columns, currently ("*string(size(data, 2))*")"))
end
if size(data, 1) < 2 # otherwise there will be a nullpointer exception later
- throw(ArgumentError("Data must have at least two lines"))
+ throw(ArgumentError("Data must have at least two lines, currently ("*string(size(data, 1))*") Rows"))
end
if any(collect(any(ismissing.(c)) for c in eachcol(data))) # checks for missing values
- println("There are missing values in the DataFrame.")
throw(ArgumentError("Data cannot have missing values"))
end
end
@@ -34,7 +32,6 @@ end
"""
-- Julia version: 1.10.5
# Constructors
```julia
@@ -43,12 +40,17 @@ ParallelPlot(data::DataFrame; normalize::Bool=false)
# Arguments
-- `data::DataFrame`:
-- `normalize::Bool`:
-- `color_feature::String || nothing`: select, which axis/feature should be used for the coloring (e.g. 'weight') (default: last)
-- `title::String`:
-- `feature_labels::String`:
-- `curve::Bool`:
+| Parameter | Default | Example | Description |
+|-------------------|----------|------------------------------------|------------------------------------------------------------------------------------------------------------------------|
+| normalize::Bool | false | normalize=true | If the Data should be normalized (min/max) |
+| title::String | "" | title="My Title" | The Title of The Figure, |
+| colormap | :viridis | colormap=:thermal | The Colors of the [Lines](https://docs.makie.org/dev/explanations/colors) |
+| color_feature | nothing | color_feature="weight" | The Color of the Lines will be based on the values of this selected feature. If nothing, the last feature will be used |
+| feature_labels | nothing | feature_labels=["Weight","Age"] | Add your own Axis labels, just use the exact amount of labes as you have axis |
+| feature_selection | nothing | feature_selection=["weight","age"] | Select, which features should be Displayed. If color_feature is not in this List, use the last one |
+| curve | false | curve=true | Show the Lines Curved |
+| show_color_legend | nothing | show_color_legend=true | Show the Color Legend. If parameter not set & color_feature not shown, it will be displayed automaticly |
+
# Examples
```@example
@@ -105,11 +107,7 @@ parallelplot(df,
end
-function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
-
- # our first parameter is the DataFrame-Observable
- df_observable = pp[1]
-
+function Makie.plot!(pp::ParallelPlot)
# this helper function will update our observables
# whenever df_observable change
@@ -128,51 +126,19 @@ function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
empty!(fig)
scene = fig.scene
- # get the parent scene dimensions
- scene_width, scene_height = size(scene)
-
# Create Overlaying, invisible Axis
# set hight to fit Label
ax = Axis(fig[1, 1],
- title = pp.title,
- height=(scene_height-
- (
- 2*Makie.default_attribute_values(Axis, nothing)[:titlegap]+
- Makie.default_attribute_values(Axis, nothing)[:titlesize]
- )
- )
+ title = pp.title
)
- # make the Axis invisible
- hidespines!(ax)
- hidedecorations!(ax)
-
# set the Color of the Color Feature
- color_col = if isnothing(pp.color_feature[]) # check if colorFeature is set
- # Its not Set, use the last feature
- # therefore we need to check if user selected features
- if !isnothing(pp.feature_selection[])
- # use the last seleted feature as color_col
- @assert pp.feature_selection[][end] in names(data) "Feature Selection ("*repr(pp.feature_selection[][end])*") is not available in DataFrame ("*string(names(data))*")"
- pp.feature_selection[][end]
- else
- names(data)[end] # no columns selected, use the last one
- end
-
- else
- # check if name is available
- @assert pp.color_feature[] in names(data) "Color Feature ("*repr(pp.color_feature[])*") is not available in DataFrame ("*string(names(data))*")"
- pp.color_feature[]
- end
- color_values = data[:,color_col] # Get all values for selected feature
- color_min = minimum(color_values)
- color_max = maximum(color_values)
+ color_col, color_values, color_min, color_max = calculate_color(pp, data)
# Select the Columns, the user wants to show (feature_selection)
if !isnothing(pp.feature_selection[])
# check if all given selections are in the DF
for selection in pp.feature_selection[]
- println(selection)
@assert selection in names(data) "Feature Selection ("*selection*") is not available in DataFrame ("*string(names(data))*")"
end
data = data[:, pp.feature_selection[]]
@@ -187,41 +153,33 @@ function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
pp.feature_labels[]
end
- # Plot dimensions
- width = scene_width[] * 0.80 #% of scene width
- height = scene_height[] * 0.80 #% of scene width
- offset = min(scene_width[], scene_height[]) * 0.10 #% of scene dimensions
# COLOR FEATURE
# If set, use the setted value
# Show, when color_feature is not in feature_selection
- show_color_legend = if pp.show_color_legend[] == true
- true
- elseif pp.show_color_legend[] == false
- false
- elseif !isnothing(pp.feature_selection[]) && !(pp.color_feature[] in pp.feature_selection[])
- true
- else
- false
- end
+ show_color_legend = show_color_legend!(pp)
# set the Color Bar on the side if it should be set
if show_color_legend[]
- # update the width, combined -> 75%
- bar_width = scene_width[] * 0.05 #% of scene width
- width = scene_width[] * 0.75 #% of scene width
-
Colorbar(
fig[1, 2],
limits = (color_min, color_max),
colormap = pp.colormap[],
- height = height,
- width = bar_width,
-
- label = color_col
+ label = color_col,
)
end
+ # get the parent scene dimensions
+ scene_width, scene_height = size(ax.scene)
+
+ # Plot dimensions
+ width = scene_width[] * 0.95 #% of scene width
+ height = scene_height[] * 0.95 #% of scene width
+ offset = min(scene_width[], scene_height[]) * 0.1 #% of scene dimensions
+
+ # make the Axis invisible
+ hidespines!(ax)
+ hidedecorations!(ax)
# Parse the DataFrame into a list of arrays
parsed_data = [data[!, col] for col in names(data)]
@@ -237,99 +195,55 @@ function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
# # # # # # # # # #
# Draw lines connecting points for each row
- for i in 1:sampleSize
- # If Curved, Interpolate
- if(pp.curve[] == false)
- # calcuating the point respectivly of the width and height in the Screen
- dataPoints = [
- Point2f(
- # calculates which feature the Point should be on
- offset + (j - 1) / (numberFeatures - 1) * width,
- # calculates the Y axis value
- (parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset,
- )
- # iterates through the Features/Axis and creates for each feature the samplePoint (above)
- for j in 1:numberFeatures
- ]
- else
- # Interpolate
- dataPoints = []
-
- # iterates through the Features/Axis
- # Start at 2, bc we check the precious axis/feature f
- for j in 2:numberFeatures
- last_x = offset + ((j-1) - 1) / (numberFeatures - 1) * width
- current_x = offset + ((j) - 1) / (numberFeatures - 1) * width
-
- last_y = (parsed_data[j-1][i] - limits[j-1][1]) / (limits[j-1][2] - limits[j-1][1]) * height + offset
- current_y = (parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset
-
- # interpolate points between the current and the last point
- for x in range(last_x, current_x, step = ( (current_x-last_x) / 30 ) )
- # calculate the interpolated Y Value
- y = interpolate(last_x, current_x, last_y, current_y, x)
- # create a new Point
- push!(dataPoints, Point2f(x,y))
- end
- end
-
- end
-
- # Color
- color_val = color_values[i]
-
- # Create the Line
- lines!(scene, dataPoints,
- color = color_val,
- colormap = pp.colormap[],
- colorrange = (color_min, color_max)
- )
- end
+ draw_lines(
+ scene,
+ pp,
+ data,
+ width,
+ height,
+ offset,
+ limits,
+ numberFeatures,
+ sampleSize,
+ parsed_data,
+ color_values,
+ color_min,
+ color_max
+ )
# # # # # # # # # #
# # # A X I S # # #
# # # # # # # # # #
-
# Create the new Parallel Axis
- for i in 1:numberFeatures
- # x will be used to split the Scene for each feature
- x = numberFeatures==1 ? width/2 : (i - 1) / (numberFeatures - 1) * width
-
- # get default
- def = Makie.default_attribute_values(Axis, nothing)
-
- # LineAxis will create one Axis Vertical, for each Feature one Axis
- axis = Makie.LineAxis(
- scene,
- limits = limits[i],
- dim_convert = Makie.NoDimConversion(),
- # the lowest and highest point to maximize the Axis from Bottom to Top
- endpoints = Point2f[(offset + x, offset), (offset + x, offset + height)],
- tickformat = Makie.automatic,
- spinecolor = :black,
- spinevisible = true,
- labelfont = def[:ylabelfont],
- labelrotation = def[:ylabelrotation],
- labelvisible = false,
- ticklabelfont = def[:yticklabelfont],
- ticklabelsize = def[:yticklabelsize],
- minorticks = def[:yminorticks],
- )
-
- # Create Lable for the Axis
- axis_title!(
- scene,
- axis.attributes.endpoints,
- string(labels[i]);
- titlegap = def[:titlegap],
- )
- end
+ draw_axis(
+ scene,
+ width,
+ height,
+ offset,
+ limits,
+ labels,
+ numberFeatures
+ )
end
+ # our first parameter is the DataFrame-Observable
+ df_observable = pp[1]
+
+ # add listener to Observable Arguments and trigger an update on change
+ # loop thorough the given Arguments
+ for kw in pp.kw
+ # e.g. normalize
+ attribute_key = kw[1]
+ on(pp[attribute_key]) do x
+ # trigger update
+ notify(df_observable)
+ end
+ end
+
# connect `update_plot` so that it is called whenever the DataFrame changes
Makie.Observables.onany(update_plot, df_observable)
@@ -341,6 +255,159 @@ function Makie.plot!(pp::ParallelPlot{<:Tuple{<:DataFrame}})
pp
end
+function get_color_col(pp::ParallelPlot, data::DataFrame) :: AbstractString
+ color_col = if isnothing(pp.color_feature[]) # check if colorFeature is set
+ # Its not Set, use the last feature
+ # therefore we need to check if user selected features
+ if !isnothing(pp.feature_selection[])
+ # use the last seleted feature as color_col
+ @assert pp.feature_selection[][end] in names(data) "Feature Selection ("*repr(pp.feature_selection[][end])*") is not available in DataFrame ("*string(names(data))*")"
+ pp.feature_selection[][end]
+ else
+ names(data)[end] # no columns selected, use the last one
+ end
+
+ else
+ # check if name is available
+ @assert pp.color_feature[] in names(data) "Color Feature ("*repr(pp.color_feature[])*") is not available in DataFrame ("*string(names(data))*")"
+ pp.color_feature[]
+ end
+ return color_col
+end
+
+# Calculates the Color for the colorfeature
+function calculate_color(pp::ParallelPlot, data::DataFrame) :: Tuple{AbstractString, Vector{Real}, Real, Real}
+ color_col = get_color_col(pp, data)
+ color_values = data[:,color_col] # Get all values for selected feature
+ color_min = minimum(color_values)
+ color_max = maximum(color_values)
+
+ return color_col, color_values, color_min, color_max
+
+end
+
+# COLOR FEATURE
+# If set, use the setted value
+# Show, when color_feature is not in feature_selection
+function show_color_legend!(pp) :: Bool
+ if pp.show_color_legend[] == true
+ return true
+ elseif pp.show_color_legend[] == false
+ return false
+ elseif !isnothing(pp.feature_selection[]) && !(pp.color_feature[] in pp.feature_selection[])
+ return true
+ else
+ return false
+ end
+end
+
+# Draw lines connecting points for each row
+function draw_lines(
+ scene,
+ pp,
+ data,
+ width::Number,
+ height::Number,
+ offset::Number,
+ limits,
+ numberFeatures::Number,
+ sampleSize::Number,
+ parsed_data,
+ color_values,
+ color_min,
+ color_max
+ )
+ for i in 1:sampleSize
+ # If Curved, Interpolate
+ if(pp.curve[] == false)
+ # calcuating the point respectivly of the width and height in the Screen
+ dataPoints = [
+ Point2f(
+ # calculates which feature the Point should be on
+ offset + (j - 1) / (numberFeatures - 1) * width,
+ # calculates the Y axis value
+ (parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset,
+ )
+ # iterates through the Features/Axis and creates for each feature the samplePoint (above)
+ for j in 1:numberFeatures
+ ]
+ else
+ # Interpolate
+ dataPoints = []
+
+ # iterates through the Features/Axis
+ # Start at 2, bc we check the precious axis/feature f
+ for j in 2:numberFeatures
+ last_x = offset + ((j-1) - 1) / (numberFeatures - 1) * width
+ current_x = offset + ((j) - 1) / (numberFeatures - 1) * width
+ last_y = (parsed_data[j-1][i] - limits[j-1][1]) / (limits[j-1][2] - limits[j-1][1]) * height + offset
+ current_y = (parsed_data[j][i] - limits[j][1]) / (limits[j][2] - limits[j][1]) * height + offset
+ # interpolate points between the current and the last point
+ for x in range(last_x, current_x, step = ( (current_x-last_x) / 30 ) )
+ # calculate the interpolated Y Value
+ y = interpolate(last_x, current_x, last_y, current_y, x)
+ # create a new Point
+ push!(dataPoints, Point2f(x,y))
+ end
+ end
+
+ end
+
+ # Create the Line
+ lines!(scene, dataPoints,
+ color = color_values[i],
+ colormap = pp.colormap[],
+ colorrange = (color_min, color_max)
+ )
+ end
+end
+
+function draw_axis(
+ scene,
+ width::Number,
+ height::Number,
+ offset::Number,
+ limits,
+ labels,
+ numberFeatures::Number,
+ )
+ for i in 1:numberFeatures
+ # x will be used to split the Scene for each feature
+ x = numberFeatures==1 ? width/2 : (i - 1) / (numberFeatures - 1) * width
+
+ # get default
+ def = Makie.default_attribute_values(Axis, nothing)
+
+ # LineAxis will create one Axis Vertical, for each Feature one Axis
+ axis = Makie.LineAxis(
+ scene,
+ limits = limits[i],
+ dim_convert = Makie.NoDimConversion(),
+ # the lowest and highest point to maximize the Axis from Bottom to Top
+ endpoints = Point2f[(offset + x, offset), (offset + x, offset + height)],
+ tickformat = Makie.automatic,
+ spinecolor = :black,
+ spinevisible = true,
+ labelfont = def[:ylabelfont],
+ labelrotation = def[:ylabelrotation],
+ labelvisible = false,
+ ticklabelfont = def[:yticklabelfont],
+ ticklabelsize = def[:yticklabelsize],
+ minorticks = def[:yminorticks],
+ )
+
+ # Create Lable for the Axis
+ axis_title!(
+ scene,
+ axis.attributes.endpoints,
+ string(labels[i]);
+ titlegap = def[:titlegap],
+ )
+ end
+end
+
+
+# Creates an Axis on top of each feature/axis
function axis_title!(
topscene,
endpoints::Observable,
diff --git a/test/parallel_coordinates_plot.png b/test/parallel_coordinates_plot.png
deleted file mode 100644
index 1715d0c..0000000
Binary files a/test/parallel_coordinates_plot.png and /dev/null differ
diff --git a/test/projectile_simulation.png b/test/projectile_simulation.png
new file mode 100644
index 0000000..43f2212
Binary files /dev/null and b/test/projectile_simulation.png differ
diff --git a/test/runtests.jl b/test/runtests.jl
index 815e7e0..8454a75 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -8,4 +8,7 @@ include("test_call_with_normalize.jl")
include("test_custom_dimensions.jl")
include("test_default_call.jl")
include("test_recipe_observable.jl")
-include("test_lines_count.jl")
\ No newline at end of file
+include("test_lines_count.jl")
+
+# Watson
+include("watson_example.jl")
\ No newline at end of file
diff --git a/test/test_call_with_color_feature.jl b/test/test_call_with_color_feature.jl
index 52499be..aabf037 100644
--- a/test/test_call_with_color_feature.jl
+++ b/test/test_call_with_color_feature.jl
@@ -14,6 +14,7 @@ using Test: @testset, @test, @test_throws
save("parallel_coordinates_plot_color_axis_weight.png", fig)
fig = parallelplot(df,
+ title="Based on Weight",
color_feature="weight",
feature_selection=["height","age","income"],
feature_labels=["Height","Age","Income"],
@@ -37,10 +38,11 @@ using Test: @testset, @test, @test_throws
)
save("parallel_coordinates_plot_color_no_selection.png", fig)
- fig = parallelplot(df,
+ fig = parallelplot(create_person_df(20),
color_feature="weight",
colormap=:thermal,
- show_color_legend = true
+ show_color_legend = true,
+ curve= true
)
save("parallel_coordinates_plot_color_with_bar.png", fig)
diff --git a/test/test_call_with_feature_labels.jl b/test/test_call_with_feature_labels.jl
index 50eb673..dfbf98e 100644
--- a/test/test_call_with_feature_labels.jl
+++ b/test/test_call_with_feature_labels.jl
@@ -4,7 +4,7 @@ using Test: @testset, @test, @test_throws
# Generate sample multivariate data
df = create_person_df(3)
# Create set with correct Axis Labels
- fig = parallelplot(df, feature_labels=["Height","Weight","Age","Income","Education Years"])
+ fig = parallelplot(df, feature_labels=["Height","Weight","Age","Income","Ed-Years"])
# TODO: do not Test agains nothing ;)
@test fig !== nothing
save("parallel_coordinates_plot_feature_labels.png", fig)
diff --git a/test/test_recipe_observable.jl b/test/test_recipe_observable.jl
index d0298d1..a6a58af 100644
--- a/test/test_recipe_observable.jl
+++ b/test/test_recipe_observable.jl
@@ -8,29 +8,36 @@ using DataFrames
# create the Data
df_observable = Observable(create_person_df(2))
+ title_observable = Observable("")
normalize_observable = Observable(true)
+ curve_observable = Observable(true)
# create the Plot
- fig, ax, sc = parallelplot(df_observable, normalize=normalize_observable)
+ fig, ax, sc = parallelplot(df_observable, normalize=normalize_observable, title=title_observable, curve = curve_observable)
save("pcp_initialized.png", fig)
+ # we can change a parameter and the graph will be automaticly changed
+ curve_observable[] = false
+ title_observable[] = "No Curve"
+ save("pcp_initialized_curve_Changed.png", fig)
+
# Record for Debug purpose
record(fig, "PCP_recipe_animation.mp4", 2:60, framerate = 2) do t
# Update Dataframe
if(iseven(t))
- df_observable[] = create_person_df(5)
normalize_observable[] = false
+ curve_observable[] = false
+ title_observable[] = ""
+ df_observable[] = create_person_df(5)
else
- df_observable[] = create_car_df(t)
normalize_observable[] = true
+ curve_observable[] = true
+ title_observable[] = "Normalize"
+ df_observable[] = create_car_df(t)
end
end
- # TODO: Write Testcases
- # e.g. Test the Size for Changes
- # w,h = size(scene)
-
end
diff --git a/test/test_utils.jl b/test/test_utils.jl
index 764023a..9a6e4d8 100644
--- a/test/test_utils.jl
+++ b/test/test_utils.jl
@@ -10,9 +10,10 @@ function create_person_df(n_samples = 10)
df = DataFrame(
height=rand(150:180, n_samples),
weight=rand(40:130, n_samples),
- age=rand(0:70, n_samples), # random numbers between 0 and 70
income=rand(450:5000, n_samples),
- education_years=rand(0:25, n_samples) # random numbers between 0 and 70
+ education_years=rand(0:25, n_samples), # random numbers between 0 and 70
+ age=rand(0:70, n_samples), # random numbers between 0 and 70
+
)
return df
diff --git a/test/watson_example.jl b/test/watson_example.jl
index d3ecaac..8c12061 100644
--- a/test/watson_example.jl
+++ b/test/watson_example.jl
@@ -1,7 +1,7 @@
using DrWatson: display, @unpack, push!, first, Dict, dict_list
using DataFrames: DataFrame, nrow
using ParallelPlots: parallelplot
-using CairoMakie: save
+using CairoMakie: save, Observable, record
function projectile_simulation()
dicts = prepare_simulation()
@@ -83,17 +83,17 @@ function find_minimal_distinct_params(arr_of_dicts)
param => unique([Float64(d[param]) for d in arr_of_dicts]) # Ensure all values are Float64
for param in params
)
-
+
# Only two different values for each parameter
minimal_values = Dict(
param => param_values[param][1:min(2, length(param_values[param]))]
for param in params
)
-
+
# Create minimal set with proper types
result = Vector{Dict{String, Float64}}() # Specify the exact type of the result
push!(result, Dict(param => minimal_values[param][1] for param in params))
-
+
for param in params
if length(minimal_values[param]) > 1
new_dict = copy(result[1]) # Copy the base dict
@@ -101,7 +101,7 @@ function find_minimal_distinct_params(arr_of_dicts)
push!(result, new_dict)
end
end
-
+
return result
end
@@ -111,8 +111,14 @@ function main()
println("Total parameter combinations: ", nrow(results))
println("\nSample results:")
display(first(results, 5))
- fig = parallelplot(results)
- save("projectile_simulation_final.png", fig)
+ fig = parallelplot(results,
+ figure = (size = (1300, 700),),
+ curve=true,
+ color_feature="max_height",
+ feature_selection=["initial_velocity","launch_angle","air_resistance","gravity","total_distance","time_of_flight"],
+ feature_labels=["Initial Velocity","Launch Angle","Air Resistance","Gravity","Total Distance","Time of Flight"],
+ )
+ save("projectile_simulation.png", fig)
end
# Run the simulation