Skip to content

Commit 3dd6703

Browse files
committed
Initial version 0.1.0-dev based on ModiaPlot_PyPlot
0 parents  commit 3dd6703

File tree

8 files changed

+371
-0
lines changed

8 files changed

+371
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/Manifest.toml

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021-2022 DLR Institute of System Dynamics and Control
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Project.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name = "SignalTablesInterface_PyPlot"
2+
uuid = "a24218ac-d749-4e8e-be24-21e617b5c7ba"
3+
authors = ["Martin.Otter@dlr.de <Martin.Otter@dlr.de>"]
4+
version = "0.1.0-dev"
5+
6+
[deps]
7+
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
8+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
9+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
10+
MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca"
11+
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
12+
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"
13+
SignalTables = "3201582d-3078-4276-ba5d-0a1254d79d7c"
14+
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
15+
16+
[compat]
17+
Colors = "0.12, 0.11, 0.10"
18+
DataFrames = "1, 0.22, 0.21, 0.20, 0.19"
19+
Measurements = "2"
20+
MonteCarloMeasurements = "1, 0.10"
21+
PyCall = "1.93, 1.92"
22+
PyPlot = "2.10, 2.9"
23+
Unitful = "1"
24+
julia = "1.7"
25+
26+
[extras]
27+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
28+
29+
[targets]
30+
test = ["Test"]

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SignalTablesInterface_PyPlot
2+
3+
SignalTablesInterface_PyPlot is part of [ModiaSim](https://modiasim.github.io/docs/)
4+
and provides convenient line plots of simulation results with package
5+
[PyPlot](https://github.com/JuliaPy/PyPlot.jl) (= a
6+
Julia interface to the [Matplotlib](http://matplotlib.org/) plotting library
7+
from Python, and specifically to the `matplotlib.pyplot` module).
8+
9+
SignalTablesInterface_PyPlot is typically not directly used, but is activated via package
10+
[SignalTables](https://github.com/ModiaSim/SignalTables.jl).
11+
For details of the installation and the usage,
12+
see the [SignalTables documentation](https://modiasim.github.io/SignalTables.jl/stable/index.html).
13+
14+
15+
## Example
16+
17+
Once a signal table `sigTable` with signals `sigA(t), sigB(t), sigC(t), r[3](t)`:
18+
is available and `PyPlot` selected for plotting,
19+
20+
```julia
21+
using SignalTables
22+
23+
usePlotPackage("PyPlot")
24+
@usingModiaPlot # = using SignalTablesInterface_PyPlot
25+
```
26+
27+
then the following command
28+
29+
```julia
30+
plot(result, [("sigA", "sigB", "sigC"), "r[2:3]"])
31+
```
32+
33+
generates the following image (layout and legends are automatically constructed):
34+
35+
![SegmentedSignalsPlot](docs/resources/images/signals-plot.png)
36+
37+
38+
## Main developer
39+
40+
[Martin Otter](https://rmc.dlr.de/sr/en/staff/martin.otter/),
41+
[DLR - Institute of System Dynamics and Control](https://www.dlr.de/sr/en)
49.1 KB
Loading
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
module SignalTablesInterface_PyPlot
2+
3+
# License for this file: MIT (expat)
4+
# Copyright 2017-2022, DLR Institute of System Dynamics and Control
5+
6+
# ToDo:
7+
# MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently
8+
# reuses the earlier instance. In a future version, a new instance will always be created and returned.
9+
# Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
10+
#
11+
# Description how to get rid of the warning:
12+
# https://stackoverflow.com/questions/46933824/matplotlib-adding-an-axes-using-the-same-arguments-as-a-previous-axes#
13+
#
14+
# ax1 = subplot(..)
15+
# plot(..)
16+
# subplot(ax1) # plot into this previously defined subplot
17+
# plot(..)
18+
# However, SignalTables.plot(..) has no internal state and has currently now way to pass the ax1 definition to the next SignalTables.plot(..) call.
19+
20+
21+
22+
# It seems that rcParams settings has only an effect, when set on PyPlot in Main
23+
import SignalTables
24+
import Measurements
25+
import MonteCarloMeasurements
26+
27+
# Determine whether pmean, pmaximum, pminimum is available (MonteCarlMeasurements, version >= 1.0)
28+
const pfunctionsDefined = isdefined(MonteCarloMeasurements, :pmean)
29+
30+
using Unitful
31+
32+
import PyCall
33+
import PyPlot
34+
35+
export plot, showFigure, saveFigure, closeFigure, closeAllFigures
36+
37+
38+
set_matplotlib_rcParams!(args...) =
39+
merge!(PyCall.PyDict(PyPlot.matplotlib."rcParams"), Dict(args...))
40+
41+
42+
include("$(SignalTables.path)/src/AbstractLinePlotInterface.jl")
43+
44+
45+
function plotOneSignal(xsig, ysig, ysigType, label, MonteCarloAsArea)
46+
xsig2 = ustrip.(xsig)
47+
ysig2 = ustrip.(ysig)
48+
if typeof(ysig2[1]) <: Measurements.Measurement
49+
# Plot mean value signal
50+
xsig_mean = Measurements.value.(xsig2)
51+
ysig_mean = Measurements.value.(ysig2)
52+
curve = PyPlot.plot(xsig_mean, ysig_mean, label=label)
53+
54+
# Plot area of uncertainty around mean value signal (use the same color, but transparent)
55+
color = PyPlot.matplotlib.lines.Line2D.get_color(curve[1])
56+
rgba = PyPlot.matplotlib.colors.to_rgba(color)
57+
rgba2 = (rgba[1], rgba[2], rgba[3], 0.2)
58+
ysig_u = Measurements.uncertainty.(ysig2)
59+
ysig_max = ysig_mean + ysig_u
60+
ysig_min = ysig_mean - ysig_u
61+
PyPlot.fill_between(xsig_mean, ysig_min, ysig_max, color=rgba2)
62+
63+
elseif typeof(ysig2[1]) <: MonteCarloMeasurements.StaticParticles ||
64+
typeof(ysig2[1]) <: MonteCarloMeasurements.Particles
65+
# Plot mean value signal
66+
if pfunctionsDefined
67+
# MonteCarlMeasurements, version >= 1.0
68+
xsig_mean = MonteCarloMeasurements.pmean.(xsig2)
69+
ysig_mean = MonteCarloMeasurements.pmean.(ysig2)
70+
else
71+
# MonteCarloMeasurements, version < 1.0
72+
xsig_mean = MonteCarloMeasurements.mean.(xsig2)
73+
ysig_mean = MonteCarloMeasurements.mean.(ysig2)
74+
end
75+
xsig_mean = ustrip.(xsig_mean)
76+
ysig_mean = ustrip.(ysig_mean)
77+
curve = PyPlot.plot(xsig_mean, ysig_mean, label=label)
78+
color = PyPlot.matplotlib.lines.Line2D.get_color(curve[1])
79+
rgba = PyPlot.matplotlib.colors.to_rgba(color)
80+
81+
if MonteCarloAsArea
82+
# Plot area of uncertainty around mean value signal (use the same color, but transparent)
83+
rgba2 = (rgba[1], rgba[2], rgba[3], 0.2)
84+
if pfunctionsDefined
85+
# MonteCarlMeasurements, version >= 1.0
86+
ysig_max = MonteCarloMeasurements.pmaximum.(ysig2)
87+
ysig_min = MonteCarloMeasurements.pminimum.(ysig2)
88+
else
89+
# MonteCarloMeasurements, version < 1.0
90+
ysig_max = MonteCarloMeasurements.maximum.(ysig2)
91+
ysig_min = MonteCarloMeasurements.minimum.(ysig2)
92+
end
93+
ysig_max = ustrip.(ysig_max)
94+
ysig_min = ustrip.(ysig_min)
95+
PyPlot.fill_between(xsig_mean, ysig_min, ysig_max, color=rgba2)
96+
else
97+
# Plot all particle signals (use the same color, but transparent)
98+
rgba2 = (rgba[1], rgba[2], rgba[3], 0.1)
99+
value = ysig[1].particles
100+
ysig3 = zeros(eltype(value), length(xsig))
101+
for j in 1:length(value)
102+
for i in eachindex(ysig)
103+
ysig3[i] = ysig[i].particles[j]
104+
end
105+
ysig3 = ustrip.(ysig3)
106+
PyPlot.plot(xsig, ysig3, color=rgba2)
107+
end
108+
end
109+
110+
else
111+
if typeof(xsig2[1]) <: Measurements.Measurement
112+
xsig2 = Measurements.value.(xsig2)
113+
elseif typeof(xsig2[1]) <: MonteCarloMeasurements.StaticParticles ||
114+
typeof(xsig2[1]) <: MonteCarloMeasurements.Particles
115+
if pfunctionsDefined
116+
# MonteCarlMeasurements, version >= 1.0
117+
xsig2 = MonteCarloMeasurements.pmean.(xsig2)
118+
else
119+
# MonteCarlMeasurements, version < 1.0
120+
xsig2 = MonteCarloMeasurements.mean.(xsig2)
121+
end
122+
xsig2 = ustrip.(xsig2)
123+
end
124+
if ysigType == SignalTables.Continuous
125+
PyPlot.plot(xsig2, ysig2, label=label)
126+
else # SignalTables.Clocked
127+
PyPlot.plot(xsig2, ysig2, ".", label=label)
128+
end
129+
end
130+
end
131+
132+
133+
134+
"""
135+
addPlot(names, result, grid, xLabel, xAxis, prefix, reuse, maxLegend, MonteCarloAsArea, figure, i, j, nsubFigures)
136+
137+
Add the time series of one name (if names is one symbol/string) or with
138+
several names (if names is a tuple of symbols/strings) to the current diagram
139+
"""
140+
function addPlot(collectionOfNames::Tuple, result, grid::Bool, xLabel::Bool, xAxis, prefix::AbstractString, reuse::Bool, maxLegend::Integer,
141+
MonteCarloAsArea::Bool, figure::Int, i::Int, j::Int, nsubFigures::Int)
142+
xsigLegend = ""
143+
nLegend = 0
144+
145+
for name in collectionOfNames
146+
name2 = string(name)
147+
(xsig, xsigLegend, ysig, ysigLegend, ysigType) = SignalTables.getPlotSignal(result, name2; xsigName = xAxis)
148+
if !isnothing(xsig)
149+
nLegend = nLegend + length(ysigLegend)
150+
if ndims(ysig) == 1
151+
plotOneSignal(xsig, ysig, ysigType, prefix*ysigLegend[1], MonteCarloAsArea)
152+
else
153+
for i = 1:size(ysig,2)
154+
plotOneSignal(xsig, ysig[:,i], ysigType, prefix*ysigLegend[i], MonteCarloAsArea)
155+
end
156+
end
157+
end
158+
end
159+
160+
PyPlot.grid(grid)
161+
if nLegend <= maxLegend
162+
PyPlot.legend()
163+
elseif nsubFigures == 1
164+
@info "plot(..): No legend in figure $figure, since curve number (= $nLegend) > maxLegend (= $maxLegend)\nCan be fixed by plot(..., maxLegend=$nLegend)"
165+
else
166+
@info "plot(..): No legend in subfigure ($i,$j) of figure $figure, since curve number (= $nLegend) > maxLegend (= $maxLegend)\nCan be fixed by plot(..., maxLegend=$nLegend)"
167+
end
168+
169+
if xLabel && !reuse && xsigLegend !== nothing
170+
PyPlot.xlabel(xsigLegend)
171+
end
172+
end
173+
174+
addPlot(name::AbstractString, args...) = addPlot((name,) , args...)
175+
addPlot(name::Symbol , args...) = addPlot((string(name),), args...)
176+
177+
178+
179+
#--------------------------- Plot function
180+
function plot(result, names::AbstractMatrix; heading::AbstractString="", grid::Bool=true, xAxis=nothing,
181+
figure::Int=1, prefix::AbstractString="", reuse::Bool=false, maxLegend::Integer=10,
182+
minXaxisTickLabels::Bool=false, MonteCarloAsArea=false)
183+
184+
set_matplotlib_rcParams!("axes.formatter.limits" => [-3,4],
185+
"font.size" => 8.0,
186+
"lines.linewidth" => 1.0,
187+
"grid.linewidth" => 0.5,
188+
"axes.grid" => true,
189+
"axes.titlesize" => "medium",
190+
"figure.titlesize" => "medium")
191+
192+
PyPlot.pygui(true) # Use separate plot windows (no inline plots)
193+
194+
195+
if isnothing(result)
196+
@info "The call of ModiaPlot.plot(result, ...) is ignored, since the first argument is nothing."
197+
return
198+
end
199+
xAxis2 = isnothing(xAxis) ? xAxis : string(xAxis)
200+
PyPlot.figure(figure)
201+
if !reuse
202+
PyPlot.clf()
203+
end
204+
heading2 = SignalTables.getHeading(result, heading)
205+
(nrow, ncol) = size(names)
206+
207+
# Add signals
208+
k = 1
209+
for i = 1:nrow
210+
xLabel = i == nrow
211+
for j = 1:ncol
212+
# "reuse" gives a warning
213+
# MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.
214+
# One can gid rid of it by the sequence
215+
# ax1 = subplot(..)
216+
# plot(..)
217+
# subplot(ax1) # plot into this previously defined subplot
218+
# plot(..)
219+
# However, SignalTables.plot(..) has no internal state and has currently now way to pass the ax1 definition to the next SignalTables.plot(..) call.
220+
ax = PyPlot.subplot(nrow, ncol, k)
221+
if minXaxisTickLabels && !xLabel
222+
# Remove xaxis tick labels, if not the last row
223+
ax.set_xticklabels([])
224+
end
225+
addPlot(names[i,j], result, grid, xLabel, xAxis2, prefix, reuse, maxLegend, MonteCarloAsArea, figure, i, j, nrow*ncol)
226+
k = k + 1
227+
if ncol == 1 && i == 1 && heading2 != "" && !reuse
228+
PyPlot.title(heading2)
229+
end
230+
end
231+
end
232+
233+
# Add overall heading in case of a matrix of diagrams (ncol > 1)
234+
if ncol > 1 && heading2 != "" && !reuse
235+
PyPlot.suptitle(heading2)
236+
end
237+
end
238+
239+
showFigure(figure::Int) = nothing
240+
241+
function saveFigure(figureNumber::Int, fileName)::Nothing
242+
fullFileName = joinpath(pwd(), fileName)
243+
println("... save plot in file: \"$fullFileName\"")
244+
PyPlot.figure(figureNumber)
245+
PyPlot.savefig(fileName)
246+
return nothing
247+
end
248+
249+
250+
closeFigure(figure::Int) = PyPlot.close(figure)
251+
252+
closeAllFigures() = PyPlot.close("all")
253+
254+
255+
end

test/Project.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[deps]
2+
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
3+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
4+
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
5+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
6+
SignalTables = "3201582d-3078-4276-ba5d-0a1254d79d7c"
7+
MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca"
8+
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
9+
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
10+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
11+
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

test/runtests.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Runtests
2+
3+
import SignalTables
4+
using Test
5+
6+
@testset "Test ModiaPlot_PyPlot/test" begin
7+
usePlotPackage("PyPlot")
8+
include("$(SignalTables.path)/test/runtests_withPlot.jl")
9+
usePreviousPlotPackage()
10+
end
11+
12+
end

0 commit comments

Comments
 (0)