Repository: mbaz/Gaston.jl Branch: master Commit: c1c21e9a6c12 Files: 55 Total size: 338.2 KB Directory structure: gitextract__535gg22/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── tagbot.yml ├── .gitignore ├── CHANGELOG.md ├── COPYING ├── LICENSE.txt ├── Project.toml ├── README.md ├── docs/ │ ├── notebook/ │ │ ├── how-to-plot-a-torus.jl │ │ ├── tutorial-3d.jl │ │ └── tutorial-essentials.jl │ ├── v1/ │ │ ├── 2d-gallery.md │ │ ├── 2dplots.md │ │ ├── 3d-gallery.md │ │ ├── 3dplots.md │ │ ├── api.md │ │ ├── examples.md │ │ ├── extending.md │ │ ├── extguide.md │ │ ├── faq.md │ │ ├── figures.md │ │ ├── index.md │ │ ├── introduction.md │ │ └── plotguide.md │ └── v2/ │ ├── .gitignore │ ├── Project.toml │ ├── _extensions/ │ │ └── jjallaire/ │ │ └── code-visibility/ │ │ ├── _extension.yml │ │ └── code-visibility.lua │ ├── _quarto.yml │ ├── assets/ │ │ ├── cairolatex.tex │ │ ├── lorenz.jl │ │ └── test.tex │ ├── examples.qmd │ ├── index.qmd │ ├── manual.qmd │ ├── migrate.qmd │ ├── recipes.qmd │ ├── reference.qmd │ ├── styles.css │ └── tutorial.qmd ├── src/ │ ├── Gaston.jl │ ├── gaston_aux.jl │ ├── gaston_builtinthemes.jl │ ├── gaston_figures.jl │ ├── gaston_llplot.jl │ ├── gaston_options.jl │ ├── gaston_plot.jl │ └── gaston_recipes.jl └── test/ ├── Project.toml ├── downstream.jl ├── downstream_dev.jl ├── manualtests.txt ├── preferences.jl └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" - package-ecosystem: "julia" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/ci.yml ================================================ name: ci on: pull_request: workflow_dispatch: push: branches: [master] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: actions: write contents: read jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} continue-on-error: ${{ matrix.experimental }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: version: - 'lts' - '1' experimental: - false os: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] include: - os: ubuntu-latest experimental: true version: 'pre' arch: x64 steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - uses: julia-actions/cache@v3 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest - uses: julia-actions/julia-processcoverage@latest - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false files: lcov.info ================================================ FILE: .github/workflows/tagbot.yml ================================================ name: tagbot on: issue_comment: types: ['created'] workflow_dispatch: inputs: lookback: default: 10 jobs: tagbot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ssh: ${{ secrets.DOCUMENTER_KEY }} ================================================ FILE: .gitignore ================================================ docs/build Manifest-v*.toml Manifest.toml docs/v2/.quarto/ docs/v2/_site/ docs/v1/_site/ docs/v1/assets ================================================ FILE: CHANGELOG.md ================================================ New in version 2 ================ Gaston v2 is a breaking release. A [migration guide](https://mbaz.github.io/Gaston.jl/v2/migrate.html) is available. * A recipe system for plotting arbitrary Julia types. * Recipes can be written without depending on Gaston using a new package, GastonRecipes, which is very small, has only one dependency (`MacroTools`), and no exports. * New, simpler but more flexible syntax. * Axis settings and curve appearance are distinguished by their position in the `plot` command (before and after data, respectively). Example: `plot("set grid", x, y, "linecolor 'blue'")` * `@plot` and `@splot` macros that take key-value arguments, as in: `@plot {title = Q"A Plot"} x y {with = "lp"}` * The string macro `Q_str` converts string `Q"A b"` to `"'A b'"` * The macro `@gpkw` allows any plot command to take key-value arguments: `@gpkw surf({grid}, x, y, z)` * Support for gnuplot datasets, including reading back data after plotting `with table`. * Overhauled multiplot support. * Axes are placed by indexing a figure: `plot(f[2], x, y)` will place the plot as the second axis in figure `f`. * Curves can be pushed into axes and axes into figures arbitrarily. * `p1 = plot(...); p2 = plot(...); plot(p1, p2)` is also supported (creates a multiplot with figures `p1` and `p2`. * Automatic layout keeps a square aspect ratio, or user may take complete control over layout. * Every figure is backed up by a separate gnuplot process. * Simpler interace for saving plots: `save([figure,], filename, terminal)`. * Better support for animations in notebooks. * Support for themes. * More than 20 pre-defined plotting styles (`surf`, `heatmap`, `histogram`, etc) based on built-in themes. * Simpler configuration, by changing values of a few `Gaston.config` fields. * Re-written [documentation](https://mbaz.github.io/Gaston.jl/v2/). # version 1.1.2 * Bug fixes # version 1.1.1 * Bug fixes # version 1.1 * Require Julia 1.6 # version 1.0.6 * Bug fixes # version 1.0.5 * Bug fixes # version 1.0.4 * Bug fixes # version 1.0.3 * Bug fixes # version 1.0.2 * Bug fixes # version 1.0.1 * Bug fixes # version 1.0 * New plot syntax, using key-value pairs for line configuration and Axes() for axis settings. * Parse key-value pairs to extend/simplify gnuplot's syntax * Use palettes from colorschemes * Generate linetypes from colorschemes * Gaston now does not validate that commands/data sent to gnuplot is valid. This opens the door to a much simplified and more flexible design. * Extended support for multiplot. * Bug fixes * Debug mode * New plot styles * Support for more terminals # version 0.10 * Bug fixes * Introduce precompilation * Refactor exceptions to use correct types * Improve terminal configuration * Extended support for gnuplot terminals * More plot styles # version 0.9 * Bug fixes * Add ranges to imagesc * Default to svg in notebooks # version 0.7.4 * Add support for `dumb` text terminal * Add a `null` terminal that does not display anything * Tests for 0.7 pass # version 0.7.3 * fix default size for pdf-like terminals. # version 0.7.2 * add `linestyle` option (corresponds to gnuplot's `dashtype`) * update docs * fix indexing bug in image plotting # version 0.7.1 * add `palette` option * Use empty string as defaults for axis labels and title * Add missing `plot!()` commands * Update .travis.yml * Fix tempdir problem in Windows * Update changelog # version 0.7 * Require Julia 0.6 * New tutorial * New syntax for plotting * New `set` command to set defaults * Add support for plotting complex vectors * Improve and add tests * Many internal fixes and code optimization # version 0.6 * Add support for grids * Fix deprecations * Restore histogram functionality broken by Julia update * Remove support for Julia 0.3 # version 0.5.7 * Update tests to use Julia's infrastructure # version 0.5.6 * Require Julia 0.5. # version 0.5.5 * Update syntax again. Convert into a Julia package. # version 0.5.4 * Update syntax to keep up with Julia. # version 0.5.3: User visible: * New terminals: aqua (OS X only) and EPS. * Improved documentation. * Compatibility with latest Julia syntax changes. Under the hood: * A few bug fixes and performance improvements. # version 0.5: User visible: * New high-level command imagesc. * New high-level command surf. * Support for printing to file; SVG, PNG, GIF and PDF formats supported. * Add support for 'dots' plotstyle. * Add a test framework and 93 tests. * Remove artificial restrictions on mixing many images and curves on the same figure. * Images support explicit x, y coordinates. * Updated and improved documentation. Under the hood: * A few small bug fixes. * Code has been simplified in many places. # version 0.4: User visible: * Add support for x11 terminal * Add support for printing to files (gif and svg) * Add support for setting default values for all plot properties * Improved documentation Under the hood: * Improved detection of invalid configurations * No longer require 'figs' global variable * Add new 'config' type to store configuration: this will allow the user to configure many aspects of Gaston's behavior * File organization has been completely revamped # version 0.3: * Add 'high-level' plot() and histogram() functions * Add error checking of arguments and types, to minimise risk of gnuplot barfing on us on misconfigured plots * Change type names to conform to Julia conventions (no underscores) * Improved PDF documentation * Fixed a few bugs # version 0.2: * Add support for histograms, via the "boxes" plot style. * Add example histogram to demos. * Add support for rgbimage plot style * Add rgbimage example to demos. * Fix bug (issue #1 on bitbucket) in the way figure handles were used. ================================================ FILE: COPYING ================================================ Copyright (c) 2012 Miguel Bazdresch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2013 Miguel Bazdresch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Project.toml ================================================ name = "Gaston" uuid = "4b11ee91-296f-5714-9832-002c20994614" version = "2.0.2" [deps] ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" GastonRecipes = "39356fd2-1f9e-4efe-8abf-5745c7d9f608" Gnuplot_jll = "e5af9688-3aeb-5ed5-8c7e-253e55323d3e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Preferences = "21216c6a-2e73-6563-6e65-726566657250" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" [compat] GastonRecipes = "1" Gnuplot_jll = "60.0.4000" ColorSchemes = "3.27" DelimitedFiles = "1" MacroTools = "0.5" PrecompileTools = "1" Preferences = "1" StatsBase = "0.34" julia = "1.10" ================================================ FILE: README.md ================================================ [![Docs stable](https://img.shields.io/badge/docs-stable-blue.svg)]( https://mbaz.github.io/Gaston.jl/v2/ ) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)]( LICENSE.txt )
[![CI](https://github.com/mbaz/Gaston.jl/actions/workflows/ci.yml/badge.svg)]( https://github.com/mbaz/Gaston.jl/actions/workflows/ci.yml ) [![Coverage Status](https://codecov.io/gh/mbaz/Gaston.jl/branch/master/graphs/badge.svg?branch=master)]( https://app.codecov.io/gh/mbaz/Gaston.jl ) [![PkgEval](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/G/Gaston.named.svg)]( https://juliaci.github.io/NanosoldierReports/pkgeval_badges/G/Gaston.html )
[![JuliaHub deps](https://juliahub.com/docs/General/Gaston/stable/deps.svg)]( https://juliahub.com/ui/Packages/General/Gaston?t=2 ) [![Downloads](https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Fmonthly_downloads%2FGaston&query=total_requests&suffix=%2Fmonth&label=Downloads)]( https://juliapkgstats.com/pkg/Gaston ) Gaston: Julia plotting using gnuplot ==================================== Gaston is a [Julia](https://julialang.org) package for plotting. It provides an interface to [gnuplot](http://gnuplot.info), a powerful plotting package available on all major platforms. Current stable release is v2.0, and it has been tested with Julia LTS (1.10) and stable (1.11), on Linux. Gaston _should_ work on any platform that runs julia and gnuplot. Version 1.1.2 runs with Julia 1.6 and later, but it is no longer maintained. All users are encouraged to move to version 2. Documentation ------------- `Gaston.jl`'s documentation can be found [here](https://mbaz.github.io/Gaston.jl/v2/). The documentation for the older v1.x releases is [here](https://mbaz.github.io/Gaston.jl/v1/). Why use Gaston? -------------- Why use Gaston, when there are powerful alternatives such as Plots.jl and Makie.jl? These are some Gaston features that may be attractive to you: * Gaston can plot: * Using graphical windows, and keeping multiple plots active at a time, with mouse interaction * Arbitrary Julia types, using recipes. * Directly to the REPL, using text (ASCII) or sixels * In Jupyter, Pluto and other IDEs * Supports popular 2-D plots: regular function plots, stem, step, histograms, images, etc. * Supports surface, contour and heatmap 3-D plots. * Can save plots to multiple formats, including pdf, png and svg. * Provides a simple interface for knowledgeable users to access gnuplot features. * It is fast. Installation ------------ Gaston requires gnuplot to be installed in your system. It has been tested with versions 5.4 and above, but it should work with any recent version. Gnuplot version 6 is recommended. To install Gaston using Julia's packaging system, enter Julia's package manager prompt with `]`, and run pkg> add Gaston Contributing ------------ Contributions are encouraged, in the form of issue reports, pull requests, new tests, and new recipes. ================================================ FILE: docs/notebook/how-to-plot-a-torus.jl ================================================ ### A Pluto.jl notebook ### # v0.20.10 using Markdown using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) #! format: off return quote local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end local el = $(esc(element)) global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el end #! format: on end # ╔═╡ 4bd21a1f-900c-4837-b121-6e708ed9e178 import Pkg # ╔═╡ 4a857a54-37f5-45d5-a715-95614ee569fe # ╠═╡ show_logs = false Pkg.add("PlutoUI") # ╔═╡ fd4dbf2c-3e9d-4a9e-8d45-0976c6fbd46c # ╠═╡ show_logs = false Pkg.develop(path="/home/miguel/rcs/jdev/Gaston") # ╔═╡ 2fd048bc-2e87-490a-afc2-5c216d260e77 using Gaston # ╔═╡ 4ccbcb36-2fb7-495a-ac80-3d4eab0f4471 using PlutoUI # ╔═╡ 05614214-fb38-4170-acfe-66b90b88c283 md"# Gaston demo/tutorial ## How to plot one (or two) torus This demo/tutorial shows how to use Gaston in a Pluto notebook, including interactive plots. Some auxiliary functions used throughout the tutorial are defined at the end of the notebook. Let's start by loading Gaston and PlutoUI." # ╔═╡ 8fa8505a-98c8-46bf-a716-dba45626b98a PlutoUI.TableOfContents(title = "Contents") # ╔═╡ a87cb7e3-18e4-49d3-a8db-31b7bbeacae0 import LinearAlgebra: norm, cross, dot # ╔═╡ 65a53ffa-c8c0-448b-957a-ea9c55928057 md"""### Step 1: plotting a circle in three dimensions Our first task is to draw circles of arbitrary size at arbitrary postions in space. Consider a circle of radius $r$ centered at $p \in \mathbb{R}^3$ on the plane defined by the orthonormal vectors $\mathbf{v}_1$ and $\mathbf{v}_2$. The parametric equation of this circle is $[x, y, z] = p + r\cos(t)\mathbf{v}_1 + r\sin(t)\mathbf{v}_2.$ Let's test this out: """ # ╔═╡ 053a7600-c582-4498-a848-e704fc1dd927 md"""#### Parameters center x: $(@bind cx Slider(-5:0.1:5, default = 0, show_value = true)) center y: $(@bind cy Slider(-5:0.1:5, default = 0, show_value = true)) center z: $(@bind cz Slider(-5:0.1:5, default = 0, show_value = true)) radius: $(@bind r Slider(0.1:0.1:3, default = 1, show_value = true)) orientation θ: $(@bind θ Slider(0:0.05:π, default = 0, show_value = true)) orientation ρ: $(@bind ρ Slider(0:0.05:2π, default = 0, show_value = true)) """ # ╔═╡ 3480c76e-a643-4856-a51a-a4b621c7b545 md"""### Step 2: Plotting a donut frame Now that we know how to plot arbitrary circles, we'll draw a bunch of small circles making a ring around a larger one. These will form the frame of "skeleton" of the torus. Let's draw the frame of a torus of major radius $r_M$ and minor radius $r_m$.""" # ╔═╡ e7906ae5-e460-4d28-af8b-948415310d3d md"#### Radii rM $(@bind rM Slider(0.1:0.1:2, default = 1, show_value = true)) rm $(@bind rm Slider(0.1:0.1:1, default = 0.1, show_value = true))" # ╔═╡ 036ae8fa-e6ba-4d06-94f9-c64c7b87507f md"""### Step 3: Plotting the torus' surface In order to "fill" the torus' frame, we need to stack the coordinates of all the small circles and pass them to `splot`. Gnuplot will automatically create a wireframe connecting the vertices of neighboring circles. Using the `pm3d` plotstyle, gnuplot will apply surface and lighting effects to the torus frame. To see the wireframe, plot using `with lines`. """ # ╔═╡ 1de39361-2264-4bdd-8694-462d41436a81 md"""#### Center cx: $(@bind c2x Slider(-5:0.1:5, default = 0, show_value = true)) cy: $(@bind c2y Slider(-5:0.1:5, default = 0, show_value = true)) cz: $(@bind c2z Slider(-5:0.1:5, default = 0, show_value = true)) """ # ╔═╡ 4a0cdeda-1d93-4be9-8e79-e1510a9fe1e7 md"""### Step 4: Rotate the torus Now, we will apply a rotation to the torus using angles along the x, y and z axes.""" # ╔═╡ 07294d8b-b01c-4528-8217-0572466f9eaa md"""#### Center cx: $(@bind c3x Slider(-5:0.1:5, default = 0, show_value = true)) cy: $(@bind c3y Slider(-5:0.1:5, default = 0, show_value = true)) cz: $(@bind c3z Slider(-5:0.1:5, default = 0, show_value = true)) #### Angle θx: $(@bind θx Slider(0:0.05:3.15, default = 0, show_value = true)) θy: $(@bind θy Slider(0:0.05:3.15, default = 0, show_value = true)) θz: $(@bind θz Slider(0:0.05:6.3, default = 0, show_value = true)) """ # ╔═╡ 7e89c619-c951-45eb-adfc-8e91f42d7e60 md"""### Conclusion Now that we know how to plot arbitrary torii, let's draw two of them with high resolution.""" # ╔═╡ e47bb743-75ba-427d-9301-a15cea857be3 md"Support code" # ╔═╡ 22941298-cc85-45d1-9fc7-96b2a1e6d863 begin ft = Figure("torus") fti = Figure("torii") nothing end # ╔═╡ 9d71d2ee-c194-49cf-ab41-c99500d36298 "Circle parametric equation" function paramcircle(p, r, v1, v2, t) x = p[1] + r*cos(t)*v1[1] + r*sin(t)*v2[1] y = p[2] + r*cos(t)*v1[2] + r*sin(t)*v2[2] z = p[3] + r*cos(t)*v1[3] + r*sin(t)*v2[3] return (x, y, z) end # ╔═╡ f55bf207-fad4-44b1-b5d9-d7efe686bf42 """Given 3-D orientation vector `o`, return orthonormal vectors `v1`, `v2` that span the plane to which `o` is normal.""" function normals(o) ex = [1., 0., 0.]; ey = [0., 1., 0.]; ez = [0., 0., 1.] ox = o[1]; oy = o[2]; oz = o[3] if all(iszero, o) v1 = ey elseif (ox == 0) && (oy == 0) v1 = sign(oz)*ey elseif (ox == 0) && (oz == 0) v1 = -sign(oy)*ex elseif (oy == 0) && (oz == 0) v1 = sign(ox)*ey else # if (oz > 0) # v1 = [-sign(ox)*oy/ox, sign(ox), 0] # else # v1 = [oy/ox, -1, 0] # end v1 = [ox == 0 ? 0 : -sign(ox)*oy/ox, sign(ox), 0] end v2 = cross(collect(o), v1) return (v1./norm(v1), v2./norm(v2)) end # ╔═╡ 5cdd317a-de26-4093-8651-615bcb46ffa9 let c = [cx, cy, cz]; ox, oy, oz = sin(θ)*cos(ρ), sin(θ)*sin(ρ), cos(θ) o_ = [ox, oy, oz] o = 0.35 .* o_ ./ norm(o_) v1, v2 = normals(o) v1p = 0.35 .* v1; v2p = 0.35 .* v2; cir = stack(t -> paramcircle(c, r, v1, v2, t), range(0, 2π, 20), dims=1) @splot(:unitranges, {xyplane = "at 0"}, cx, cy, cz, ox, oy, oz, "w vectors lc 'black'") splot!(cx, cy, cz, v1p[1], v1p[2], v1p[3], "w vectors lc 'blue'") splot!(cx, cy, cz, v2p[1], v2p[2], v2p[3], "w vectors lc 'dark-green'") splot!(cir[:,1], cir[:,2], cir[:,3], "lc 'black'") cr = cross(v1, v2) splot!(cx, cy, cz, cr[1], cr[2], cr[3], "w vectors lc 'red'") end # ╔═╡ f4334764-5ada-424d-a0eb-8a9f7ee55c32 """Central diff of `f` at `t` with step `h = 1e-6`""" function cendiff(f, t, h = 1e-6) (f(t.+h/2) .- f(t.-h/2))./h end # ╔═╡ 398a6f8b-b783-4a38-82c9-b01012b347d4 let ft = ft N = 16 # number of circles to draw #c = [c2x, c2y, c2z] # torus center #ox, oy, oz = sin(θt)*cos(ρt), sin(θt)*sin(ρt), cos(θt) #o_ = [ox, oy, oz] #o = 0.2 .* o_ ./ norm(o_) #v1, v2 = normals(o) c = [0,0,0]; v1 = [1,0,0]; v2 = [0,1,0] pc(t) = paramcircle(c, rM, v1, v2, t) # torus core cir = stack(pc, range(0, 2π, 20), dims=1) @splot(ft, :notics, :labels, {view=(60,30), view = "equal xy", ranges = (-3,3), xyplane = "at 0"}, cir[:,1], cir[:,2], cir[:,3]) # center of each circle in the frame pcenters = range(0, 2π-2π/N, N) centers = [paramcircle(c, rM, v1, v2, t) for t in pcenters] for n in 1:N # orientation of each circle -- tangent to torus core oc = cendiff(pc, pcenters[n]) n1, n2 = normals(oc); #display((oc, n1,n2)) d = stack(t -> paramcircle(centers[n], rm, n1, n2, t), range(0,2π,10), dims=1) # plots splot!(ft, d[:,1], d[:,2], d[:,3], "lc 'black'") #splot!(ft, d[1,1], d[1,2], d[1,3], "w p pt '0'") #splot!(ft, d[3,1], d[3,2], d[3,3], "w p pt '3'") #splot!(ft, d[:,1], d[:,2], d[:,3], "lc 'black'") #splot!(ft, centers[n][1], centers[n][2], centers[n][3], centers[n][1]+oc[1], centers[n][2]+oc[2], centers[n][3]+oc[3], "w vectors lc 'green'") end ft end # ╔═╡ d25d04f9-c8c9-4b25-af01-1b67b4c1999f let ft = ft res = 64 # points per circle N = 64 # number of circles to draw c = [c2x, c2y, c2z] # torus center ox, oy, oz = sin(θ)*cos(ρ), sin(θ)*sin(ρ), cos(θ) # torus orientation o_ = [ox, oy, oz] o = 0.2 .* o_ ./ norm(o_) v1, v2 = normals(o) pc(t) = paramcircle(c, rM, v1, v2, t) # torus core cir = stack(pc, range(0, 2π, res), dims=1) # center of each circle in the frame pcenters = range(0, 2π, N) centers = [paramcircle(c, rM, v1, v2, t) for t in pcenters] x = zeros(res, N); y = zeros(res, N); z = zeros(res, N) for n in 1:N # orientation of each circle -- tangent to torus core oc = cendiff(pc, pcenters[n]) n1, n2 = normals(oc) d = stack(t -> paramcircle(centers[n], rm, n1, n2, t), range(0,2π,res), dims=1) x[:,n] .= d[:,1]; y[:,n] .= d[:,2]; z[:,n] .= d[:,3] end @splot(ft, {pm3d = "depthorder", pm3d = "lighting", ranges=(-3,3), hidden3d, cbrange = (-rm,rm), palette = :plasma, xyplane = "at 0", colorbox = false}, :notics, x, y, z, "w pm3d fillcolor 'dark-turquoise'") end # ╔═╡ e6c5661f-c864-4834-9ac5-1fc1b799bed0 """ torus Calculate torus coordinates.""" function torus(c, θx, θy, θz, rM, rm, N, n) # step 1: calculate a torus in the origin x, y, z = torus(rM, rm, N, n) # step 2: calculate rotation matrices Rx = [1 0 0 ; 0 cos(θx) -sin(θx); 0 sin(θx) cos(θx)] Ry = [ cos(θy) 0 sin(θy); 0 1 0 ; -sin(θy) 0 cos(θy)] Rz = [cos(θz) -sin(θz) 0; sin(θz) cos(θz) 0; 0 0 1] R = Rz*Ry*Rx # step 3: rotate and translate each torus point p = zeros(3); q = zeros(3) for col in 1:N for row = 1:n p[1] = x[row, col]; p[2] = y[row, col]; p[3] = z[row, col] q = R*p; x[row, col] = q[1] + c[1] y[row, col] = q[2] + c[2] z[row, col] = q[3] + c[3] end end x, y, z end # ╔═╡ 1a407189-602e-4f79-9c68-8c0ec9527336 function torus(rM, rm, N, n) c = (0, 0, 0) v1, v2 = normals([0, 0, 1]) # calculate torus core core(t) = paramcircle(c, rM, v1, v2, t) # find center of each torus slice pcenters = range(0, 2π, N) centers = [paramcircle(c, rM, v1, v2, t) for t in range(0, 2π, N)] x = zeros(n, N); y = zeros(n, N); z = zeros(n, N) d = zeros(n, 3) for k in 1:N # orientation of each circle -- tangent to torus core oc = cendiff(core, pcenters[k]) n1, n2 = normals(oc) d .= stack(t -> paramcircle(centers[k], rm, n1, n2, t), range(0, 2π, n), dims = 1) x[:,k] .= d[:,1]; y[:,k] .= d[:,2]; z[:,k] .= d[:,3] end x, y, z end # ╔═╡ 45904d7b-3b2e-411d-8715-372220b5cd00 let all(iszero, (θx, θy, θz)) && (θz = 1.0) c = [c3x, c3y, c3z] # torus center x, y, z = torus(c, θx, θy, θz, rM, rm, 32, 32) splot("set xrange [-3:3]", "set yrange [-3:3]", "set zrange [-2:2]", :labels, "set pm3d depthorder", "set view 60,30", "set view equal xyz", "unset colorbox", "set pm3d lighting", x, y, z, "w pm3d fillcolor 'dark-turquoise'") end # ╔═╡ 6b476e91-aa7d-48ad-8cee-00630818238a let c1 = [0,0,0]; x1 = 0; y1 = 0; z1 = 1; rM1 = 1; rm1 = 0.4; c2 = [1,0,0]; x2 = π/2; y2 = 0; z2 = 0; rM2 = 1; rm2 = 0.4; t1x, t1y, t1z = torus(c1, x1, y1, z1, rM1, rm1, 128, 128) t2x, t2y, t2z = torus(c2, x2, y2, z2, rM2, rm2, 128, 128) @splot(fti, {pm3d = "depthorder", pm3d = "lighting", tics = false, colorbox = false, view = "equal xyz", view = (40, 20, 2)}, t1x, t1y, t1z, "w pm3d fillcolor 'dark-turquoise'") splot!(fti, t2x, t2y, t2z, "w pm3d fillcolor 'salmon'") end # ╔═╡ Cell order: # ╟─05614214-fb38-4170-acfe-66b90b88c283 # ╠═4bd21a1f-900c-4837-b121-6e708ed9e178 # ╠═4a857a54-37f5-45d5-a715-95614ee569fe # ╠═fd4dbf2c-3e9d-4a9e-8d45-0976c6fbd46c # ╠═2fd048bc-2e87-490a-afc2-5c216d260e77 # ╠═4ccbcb36-2fb7-495a-ac80-3d4eab0f4471 # ╠═8fa8505a-98c8-46bf-a716-dba45626b98a # ╠═a87cb7e3-18e4-49d3-a8db-31b7bbeacae0 # ╟─65a53ffa-c8c0-448b-957a-ea9c55928057 # ╟─053a7600-c582-4498-a848-e704fc1dd927 # ╠═5cdd317a-de26-4093-8651-615bcb46ffa9 # ╟─3480c76e-a643-4856-a51a-a4b621c7b545 # ╟─e7906ae5-e460-4d28-af8b-948415310d3d # ╠═398a6f8b-b783-4a38-82c9-b01012b347d4 # ╟─036ae8fa-e6ba-4d06-94f9-c64c7b87507f # ╟─1de39361-2264-4bdd-8694-462d41436a81 # ╠═d25d04f9-c8c9-4b25-af01-1b67b4c1999f # ╟─4a0cdeda-1d93-4be9-8e79-e1510a9fe1e7 # ╟─07294d8b-b01c-4528-8217-0572466f9eaa # ╠═45904d7b-3b2e-411d-8715-372220b5cd00 # ╟─7e89c619-c951-45eb-adfc-8e91f42d7e60 # ╠═6b476e91-aa7d-48ad-8cee-00630818238a # ╟─e47bb743-75ba-427d-9301-a15cea857be3 # ╟─22941298-cc85-45d1-9fc7-96b2a1e6d863 # ╟─9d71d2ee-c194-49cf-ab41-c99500d36298 # ╟─f55bf207-fad4-44b1-b5d9-d7efe686bf42 # ╟─f4334764-5ada-424d-a0eb-8a9f7ee55c32 # ╟─e6c5661f-c864-4834-9ac5-1fc1b799bed0 # ╟─1a407189-602e-4f79-9c68-8c0ec9527336 ================================================ FILE: docs/notebook/tutorial-3d.jl ================================================ ### A Pluto.jl notebook ### # v0.20.10 using Markdown using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) #! format: off return quote local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end local el = $(esc(element)) global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el end #! format: on end # ╔═╡ 93b3b71e-0a4e-4165-9f92-b770c06a5964 # ╠═╡ show_logs = false begin import Pkg Pkg.add("PlutoUI") Pkg.develop(path="/home/miguel/rcs/jdev/Gaston") using Revise using Gaston using PlutoUI pkgversion(Gaston) end # ╔═╡ a86a096a-f66b-11ed-3c0d-f3dce992f2d7 md"# Gaston demo/tutorial: 3-D Plots Let's start by loading needed packages: Gaston." # ╔═╡ 198a01e0-fa6c-426f-b485-ae2922da121f PlutoUI.TableOfContents(title = "Contents") # ╔═╡ af9f5b7f-84ec-4f53-a4ba-df4dd73933b6 Gaston.config.term = "pngcairo font ',10' size 700,400" # ╔═╡ 1851bc4f-c625-4c14-96b3-c8fce30b2182 md"## 3-D plots To create a 3-D plot, use the `splot` function or the `@splot` macro. The following examples illustrate their use." # ╔═╡ 9a92ee79-8db5-4532-ad7b-046700d1cd84 md"##### z values as an array" # ╔═╡ 5e3d28f4-9500-44ec-82fb-dc6ee8f8d239 let z = [10 10 10 10 ; 10 5 1 0 ; 10 10 10 10 ] splot("set title 'A Valley' set hidden3d set view 75, 38", z, "lc 'dark-green'") end # ╔═╡ 97dc9773-310d-44bf-b518-baead81cd252 md"##### x, y vectors or ranges; z an array" # ╔═╡ 4586bc09-8556-4627-b473-8f168f62bfb5 let x = -2:1 y = [2, 3, 4] z = [10 10 10 10 ; 10 5 1 0 ; 10 10 10 10 ] splot("set title 'A Valley' set hidden3d set view 75, 38 set xlabel 'x' offset -1.5,-1.5 set ylabel 'y' offset 1,1 set zlabel 'z'", x, y, z, "lc 'dark-green'") end # ╔═╡ 6d34cafc-164e-4731-b0fc-0f586bf7e994 md"##### x and y vectors or ranges; z a function" # ╔═╡ e7697a24-f587-4215-9cfa-5416e0b2627f let x = y = -15:0.4:15 f1(x,y) = @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) splot("set title 'Sombrero' set hidden3d", x, y, f1, "lc 'turquoise'") end # ╔═╡ 3dc0145f-49df-4c29-a5e1-12d158f901bc md"If `x` and `y` are not provided, then `range(-10, 10, 100)` is used by default." # ╔═╡ b09cfaf6-b56b-4818-bda8-83a9a98e7b6d md"##### x and/or y tuples; z a function" # ╔═╡ a73fbdfa-452a-4fbc-821a-1e00046c602d let splot("set hidden3d", (-6, 6, 20), (-3, 3, 20), (x,y) -> cos.(x./2).*sin.(y./2)) end # ╔═╡ 470f890e-9010-4ee4-abc3-f5c5c73c233b md"The format is either `(min, max)` or `(min, max, samples)`. The default number of samples is 100. If only one tuple is provided, it's assumed to specify values for both `x` and `y`. The default is `(-10, 10, 100)`." # ╔═╡ 1ceb96e8-fecb-4f1e-9457-3717532766d9 md"## Plot styles The following plot styles are provided: * `surf` and `surf!` to plot surfaces. * `contour` for contour plots. * `surfcontour` combines the previous two styles. * `scatter3` and `scatter3!` for scatter plots. * `wireframe` and `wireframe!` for wireframe plots. * `wiresurf` and `wiresurf!` combine a surface and a wireframe. * `heatmap` is a projection of a surface or 3-D histogram to a plane. The following examples illustrate these styles." # ╔═╡ b67cf328-1659-46f4-afcd-d393836035bc md"##### Surface plots" # ╔═╡ ebe88c25-eded-4643-aac5-175013a8bd3d let f1(x,y) = sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) @gpkw surf({title = "'Sombrero Surface'", hidden3d, palette = :matter}, (-15, 15, 200), f1) end # ╔═╡ 24893b90-a180-4df2-a5cb-c252920f74d6 md"##### Contour plots" # ╔═╡ de4c0f15-e0b9-406f-b777-a8eea53491dc let f1(x,y) = cos(x/2)*sin(y/2) contour("set title 'Sombrero Contours'", (-10, 10, 50), f1) end # ╔═╡ 3ba30e9c-3ce5-4e02-8225-b611f9631675 md"Labels can be removed with the `labels = false` argument:" # ╔═╡ 426d3c69-d7c2-4bfa-a63d-79da7d0f4c8f let f1(x,y) = cos(x/2)*sin(y/2) contour("set title 'Sombrero Contours; no labels'", (-10, 10, 50), f1, labels = false) end # ╔═╡ 57e27251-9723-429c-976e-61208dcd61f0 let f1(x,y) = @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) surfcontour("set title 'Sombrero Wireframe and Contours'", (-15, 15, 40), f1, "lc 'orange'") end # ╔═╡ 59efd1da-b92d-4edf-b8e1-4ac375cf9485 md"##### Scatter plots" # ╔═╡ 015d489d-a51d-47ae-8899-4ee04a064ec0 md"Scatter plots use the `points` pointstyle." # ╔═╡ dbeeaaa0-d0da-455d-b3c5-e1a3405014e0 let scatter3("set title 'A 3-D scatter plot", randn(10), randn(10), randn(10)) end # ╔═╡ 070eb594-80f9-48ed-a416-ef7eeb53a97e let x = 0:0.1:6π @splot({title = "'Trigonometric spiral'", colorbox = false, palette = :matter}, x, x.*cos.(x), x.*sin.(x), x./10, "with p pt 7 lc palette ps variable") end # ╔═╡ 09901ab7-298f-456b-aa92-db1d642c36ce md"##### Wireframe plots" # ╔═╡ ebd8dc6f-3d5b-4ce6-992e-41be7a19767d md"Wireframe plots use the `lines` plotstyle, which is `gnuplot`'s default and is illustraded above. The following example shows how to plot a surface and a wireframe:" # ╔═╡ c76118f4-9e8f-4c8c-8cce-df23126551c9 let f1(x, y) = cos(x/2) * sin(y/2) theme = @gpkw {palette = :matter, title = Q"Wiresurf plot"} wiresurf(theme, :notics, :labels, (-10, 10, 30), f1) end # ╔═╡ d10136b4-b397-4388-9bc6-b3194341a918 md"##### Heatmap plots" # ╔═╡ 729f87a6-e861-425a-a405-cad1ec4fb320 let f1(x, y) = cos(x/2) * sin(y/2) theme = @gpkw {palette = :matter, title = Q"Heatmap"} heatmap(theme, :notics, :labels, (-10, 10, 70), f1) end # ╔═╡ 5f3c4090-417f-4616-b268-735be74fc3ae md"## Interactivity" # ╔═╡ 7bc6cd91-ce7c-4800-a36a-ec12dedf5a9d md"Interaction with notebook sliders and other widgets works in a similar way to regular 2-D plots." # ╔═╡ dca8473a-dac9-438b-9d86-987bdc1631ba md"Azimuth: $(@bind az Slider(0:180, default = 115, show_value = true))" # ╔═╡ 094b2a61-30b8-4739-a125-cb1e1a37dff7 md"Altitude: $(@bind al Slider(0:90, default = 55, show_value = true))" # ╔═╡ 77843582-1942-471d-987b-4b4a03adfecf md"""Palette: $(@bind p Select([:viridis => "viridis", :matter => "matter", :ice => "ice", :thermal => "thermal"]))""" # ╔═╡ 5a5a8dae-526d-4122-856a-f712cfaaf858 let al = al, az = az f1(x, y) = cos(x/2) * sin(y/2) theme = @gpkw {view = (al, az), palette = p, title = Q"Wiresurf plot"} wiresurf(theme, :notics, :labels, (-8, 8, 30), f1) end # ╔═╡ 28b107c9-4f20-415e-b68e-d204798c94c8 md"## Animation Animation works in a similar way to 1-D plots." # ╔═╡ 40b076e3-92ba-48fe-90bf-f94078370b20 let f = Figure() function z(x, y, i) if 1.8 < atan(y, x)+π < 2.7 return NaN end d = sqrt(x*x+y*y) i*sin(2d) / d end x = y = range(-10, 10, 35) theme = @gpkw {zrange = (-1.5, 1.5), cbrange = (-0.5, 1), colorbox = false, palette = :matter} frames = 20 for (idx, i) in enumerate(range(-1, 1, frames)) wiresurf(f[idx], theme, :notics, x, y, (x, y) -> z(x, y, i)) end for (idx, i) in enumerate(range(1, -1, frames)) wiresurf(f[idx+frames], theme, :notics, x, y, (x, y) -> z(x, y, i)) end animate(f, "gif animate loop 0 size 700,400") end # ╔═╡ Cell order: # ╟─a86a096a-f66b-11ed-3c0d-f3dce992f2d7 # ╠═93b3b71e-0a4e-4165-9f92-b770c06a5964 # ╠═198a01e0-fa6c-426f-b485-ae2922da121f # ╠═af9f5b7f-84ec-4f53-a4ba-df4dd73933b6 # ╟─1851bc4f-c625-4c14-96b3-c8fce30b2182 # ╟─9a92ee79-8db5-4532-ad7b-046700d1cd84 # ╠═5e3d28f4-9500-44ec-82fb-dc6ee8f8d239 # ╟─97dc9773-310d-44bf-b518-baead81cd252 # ╠═4586bc09-8556-4627-b473-8f168f62bfb5 # ╟─6d34cafc-164e-4731-b0fc-0f586bf7e994 # ╠═e7697a24-f587-4215-9cfa-5416e0b2627f # ╟─3dc0145f-49df-4c29-a5e1-12d158f901bc # ╟─b09cfaf6-b56b-4818-bda8-83a9a98e7b6d # ╠═a73fbdfa-452a-4fbc-821a-1e00046c602d # ╟─470f890e-9010-4ee4-abc3-f5c5c73c233b # ╟─1ceb96e8-fecb-4f1e-9457-3717532766d9 # ╟─b67cf328-1659-46f4-afcd-d393836035bc # ╠═ebe88c25-eded-4643-aac5-175013a8bd3d # ╟─24893b90-a180-4df2-a5cb-c252920f74d6 # ╠═de4c0f15-e0b9-406f-b777-a8eea53491dc # ╟─3ba30e9c-3ce5-4e02-8225-b611f9631675 # ╠═426d3c69-d7c2-4bfa-a63d-79da7d0f4c8f # ╠═57e27251-9723-429c-976e-61208dcd61f0 # ╟─59efd1da-b92d-4edf-b8e1-4ac375cf9485 # ╟─015d489d-a51d-47ae-8899-4ee04a064ec0 # ╠═dbeeaaa0-d0da-455d-b3c5-e1a3405014e0 # ╠═070eb594-80f9-48ed-a416-ef7eeb53a97e # ╟─09901ab7-298f-456b-aa92-db1d642c36ce # ╟─ebd8dc6f-3d5b-4ce6-992e-41be7a19767d # ╠═c76118f4-9e8f-4c8c-8cce-df23126551c9 # ╟─d10136b4-b397-4388-9bc6-b3194341a918 # ╠═729f87a6-e861-425a-a405-cad1ec4fb320 # ╟─5f3c4090-417f-4616-b268-735be74fc3ae # ╟─7bc6cd91-ce7c-4800-a36a-ec12dedf5a9d # ╟─dca8473a-dac9-438b-9d86-987bdc1631ba # ╟─094b2a61-30b8-4739-a125-cb1e1a37dff7 # ╟─77843582-1942-471d-987b-4b4a03adfecf # ╠═5a5a8dae-526d-4122-856a-f712cfaaf858 # ╟─28b107c9-4f20-415e-b68e-d204798c94c8 # ╠═40b076e3-92ba-48fe-90bf-f94078370b20 ================================================ FILE: docs/notebook/tutorial-essentials.jl ================================================ ### A Pluto.jl notebook ### # v0.20.10 using Markdown using InteractiveUtils # This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). macro bind(def, element) #! format: off return quote local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end local el = $(esc(element)) global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) el end #! format: on end # ╔═╡ ccdce6f8-f1d8-11ed-19e9-fddf2407113b import Pkg # ╔═╡ 7076fc20-8f0c-449c-9cfc-edf9801e85cd # ╠═╡ show_logs = false Pkg.add("PlutoUI") # ╔═╡ c55fca39-9251-4593-b7f4-4342119e76e1 # ╠═╡ show_logs = false Pkg.develop(path="/home/miguel/rcs/jdev/Gaston") # ╔═╡ a64f470c-a8d3-4c28-99f4-8149ac65ff58 using Revise # ╔═╡ 93d7c222-6755-4b71-b4cb-3ad82f4515f1 using Gaston # ╔═╡ dab403c8-f4ae-4029-a8c8-ef5ae161142c using PlutoUI # ╔═╡ d4e94a70-866a-46e2-98c2-6babc7745fd2 md"# Gaston demo/tutorial: Essentials Let's start by loading Gaston and PlutoUI." # ╔═╡ ff57c40b-615d-4902-9810-8d874220f626 pkgversion(Gaston) # ╔═╡ a66e7e1d-b3a4-4504-be15-2324808607be PlutoUI.TableOfContents(title = "Contents") # ╔═╡ 1a521882-7ee0-413d-9738-3a025499883e md"## Introduction to Gaston Gaston is a plotting package for Julia. It provides an interface between Julia and [gnuplot](https://gnuplot.info). Gaston has two main functions: * Convert Julia data variables to gnuplot's format. * Provide plot commands with familiar syntax, e.g. `plot(x, y)`. It also provides some additional functionality: * Manage multiple figures simultaneously. * Work seamlessly in notebooks. * Convenient syntax for configuring gnuplot. * Recipes (styles) for common plot types. * Lightweight themes. * Can be extended to plot user-defined data types. Familiarity with both Julia and gnuplot is assumed throughout the documentation. " # ╔═╡ 8f50fd19-5ce4-447e-a7f9-7b16d59af6c0 md"""## Basic syntax The `plot` function is Gaston's workhorse for 1-D plotting. It takes three different kinds of arguments, in order: axis settings, data, and curve settings. Axis settings correspond to gnuplot's `set` commands, while curve settings determine how a curve is plotted (its color, width, markers, etc.) Axis settings and curve settings are strings; any number of them can be used. The signature of `plot` is: plot(axis_settings..., data..., curve_settings...) For example, the command plot("set title 'a plot'", "set grid", x, y, "w p lc 'green'") is converted to the following sequence of gnuplot commands: set title 'a plot' set grid plot '/tmp/data' w p lc 'green' where `x` and `y` are written to the temporary file `data` in the format gnuplot expects. """ # ╔═╡ ab0aba7c-d841-44bb-8e6a-2e5515ef9aa5 md"## Configuring the terminal When working in a notebook (whether Juno, IJulia, Pluto or VS Code), the terminal is automatically set to `pngcairo`. This terminal is very good for plotting on a notebook, since it's very fast and produces small files. We still want to configure the terminal ourselves, though, to set a good plot size (in pixels) and font size. This is done as follows:" # ╔═╡ 0e8236b4-6d1f-46ae-9c0e-ee1d4f605c0d Gaston.config.term = "pngcairo font ',10' size 700,400" # ╔═╡ fc31bafd-eb7b-4204-b781-6e06cf95fd25 md"## 2-D plots ### Plotting coordinates Following are some examples of basic plots, with different data arguments. ##### Only `y` is given The `x` coordinates are implicitly defined as the range `firstindex(y):lastindex(y)`." # ╔═╡ 804b8b60-8976-43a3-a5b7-86cedf9118e4 plot(rand(10)) # ╔═╡ e467671a-62a2-406d-99e4-9ecfa7f55b5e md"##### Both `x` and `y` are given" # ╔═╡ a25f483f-f5c7-4949-9690-3d126a12e86a plot(range(0, 1, length=10), rand(10)) # ╔═╡ 26fe4805-3b08-4750-b0f8-f210a72a502b md"##### Multiple columns Many `gnuplot` plot styles take multiple columns. Each data argument to `plot` is interpreted as a column, and you can use as many as you need:" # ╔═╡ 21e1f17f-4fb2-490e-ade7-77725fb24f61 let x = 1:10 boxmin = 0.1*rand(10) .+ 0.2 whiskmin = boxmin .- 0.1*rand(10) boxmax = boxmin .+ 0.5*rand(10) whiskmax = boxmax .+ 0.2*rand(10) ym = minimum(whiskmin)-0.1 yM = maximum(whiskmax)+0.1 plot("set yrange [$ym:$yM] set boxwidth 0.3 relative set style fill solid 0.25 set title 'A random candlesticks plot'", x, boxmin, whiskmin, whiskmax, boxmax, "w candlesticks whiskerbars 0.75") end # ╔═╡ cedb6fa7-e8c7-4a48-8935-1ce6385f1185 md"### Plotting functions ##### Only a function is given When a function is passed to `plot`, the function is first evaluated in the range from -10 to 10, with 100 samples." # ╔═╡ 8a50be7a-bcfa-468c-914a-9b1aa2c970ad fun(x) = exp(-abs(x/8))*cos(x) # ╔═╡ 6d72504e-fede-41a6-bfef-0aef52936e1b plot(fun) # ╔═╡ e15e224f-834c-459e-b355-15d1fb8975fb md"##### A function and a range The range can be given explicitly as `(start, stop)`; the function is still sampled 100 times." # ╔═╡ 162ccb40-92b0-43fe-a89e-676864ac9883 plot((-1, 5), fun) # ╔═╡ 0b229ed3-868a-42d3-b669-6cacaf213c90 md"The number of samples can be specified with `(start, stop, samples)`." # ╔═╡ fbce3eb9-6691-4945-ac6b-4b0bcf31e003 plot((-1, 5, 10), fun) # ╔═╡ 262faf7b-ac62-45d9-bc18-3860e6e10fb7 md"### Plotting multiple curves To plot multiple curves on the same axis, use `plot!`. (Note that `plot!` ignores any provided axis settings, which must be specified with `plot`)." # ╔═╡ 57f66b17-8c2d-4737-84e1-c456a7dacc89 begin plot("set key box top left", fun, "lc 'dark-green' title 'fun'") plot!(cos, "lc 'orange' title 'cos'") end # ╔═╡ a0762a24-26be-40a6-a333-92bd8999a5a3 md"### Plot styles The following commands plot data with a specific plot style: * `scatter`, `scatter!` * `stem`, `stem!` * `bar`, `bar!` * `barerror`, `barerror!` * `histogram` * `imagesc` The following examples illustrate these styles." # ╔═╡ d66d3ef3-130f-45a7-81d6-0cac24e9b1d2 md"##### Scatter plot" # ╔═╡ 4f145f29-f99d-4f36-9ebf-5cc632f956c1 let xg = randn(20) yg = randn(20) scatter("set title 'Scatter plot' set key outside", xg, yg, "title 'gaussian'") xu = rand(20) yu = rand(20) scatter!(xu, yu, "title 'uniform'") end # ╔═╡ 970fd5da-91e2-44bb-a427-6823b03c79e9 md"##### Stem plot" # ╔═╡ 881e794d-bc5e-49d0-b240-2cb58abe82ce stem("set title 'stem plot'", fun) # ╔═╡ 996ef179-f5fd-4345-a6af-a74709bd2a6b md"Avoid printing the circular marks with the `onlyimpulses` argument." # ╔═╡ f395e87b-39f6-4f79-83b1-1b44bcb37feb stem("set title 'only impulses'", fun, onlyimpulses = true) # ╔═╡ 9db5812e-47a3-4b17-b3f0-9e74a38e3f54 md"##### Bar plots `bar` uses gnuplot's `with boxes` style." # ╔═╡ 061a881c-220c-4183-8f5b-12fd91a8f358 bar("set title 'bar plot'", rand(10), "lc 'turquoise'") # ╔═╡ 8c14838e-fcb6-4b49-83cd-a3ef5f267b24 let bar("set title 'Two bar plots'", rand(10), "lc 'dark-violet'") bar!(1.5:10.5, 0.5*rand(10), "lc 'plum' fill pattern 4") end # ╔═╡ be09ba73-cabb-40ce-aa06-6bfe0705f6e2 md"`barerror` uses gnuplot's `boxerrorbars` style." # ╔═╡ 33731005-bb8c-48a9-863a-93e25d579930 barerror("set title 'Error bars'", 1:10, rand(10), 0.1*rand(10).+0.1, "lc 'sandybrown'") # ╔═╡ 906770c9-07bd-4447-af21-de487c7a1e02 md"##### Histograms The `histogram` function takes these keyword arguments: * `nbins`: specifies the number of bins. Defaults to 10. * `norm::Bool`: if `true`, the area of the histogram is normalized. * `mode::Symbol`: Controls histogram normalization mode; passed to [`StatsBase.normalize`](https://juliastats.org/StatsBase.jl/stable/empirical/#LinearAlgebra.normalize). Defaults to `:pdf`, which makes the histogram area equal to 1. * `edges`: a vector or a range specifying the bin edges; takes precedence over `nbins`. * `horizontal::Bool`: if `true`, the histogram is drawn horizontally. 2-D histograms are supported, by passing two datasets. " # ╔═╡ 38a71681-dc2d-4af5-be14-1682924abf51 histogram("set title 'histogram (nbins)'", randn(10_000), nbins = 20, norm = true) # ╔═╡ 5c25b0e3-9a45-4ad5-92ae-314cfce5c117 histogram("set title 'histogram (edges)'", randn(10_000), edges = -5:5, "lc 'dark-khaki'") # ╔═╡ 4f4fb009-cf62-4e64-a519-77f340f83f69 histogram("set title 'horizontal histogram'", rand(1000), nbins = 10, horizontal = true, "lc 'orchid'") # ╔═╡ 84ccf2ff-472b-4b34-9472-2006f70684e6 md"In the case of 2-D histograms, `nbins` or `egdes` may be a tuple; otherwise, both axes use the same configuration." # ╔═╡ b42213a1-f27a-4c96-a6a7-b5a8a200ef19 let x = randn(100_000) y = randn(100_000) histogram("set palette gray unset colorbox set title '2-D histogram'", x, y, nbins = 50, norm = true) end # ╔═╡ e1e93d06-4565-4625-8fcf-fe8dbc9fe55e md"##### Images" # ╔═╡ 8ab2a66a-df03-4aeb-a736-1336b75dadaa md"Arrays may be plotted as images using `imagesc`. Note that, in contrast to other plotting packages, the array columns are plotted with the first row at the top." # ╔═╡ ca7554bc-6f9c-4574-bf78-20afd42dc647 let X = [0 1 2 3; 0 3 2 1; 0 2 2 0; 3 0 0 0] imagesc("unset tics", X) end # ╔═╡ 5acd956b-e22d-4638-be19-1ad090cac7d2 md"""## Plot options Gnuplot provides many possibilities to fine-tune a plot. There are two main kinds of configuration options: * Axes-wide options, corresponding to `set` commands; for example, `set grid` turns on the grid. * Specific curve settings, such as line width and color; for example, `with points linecolor 'red'`. Gaston provides a few different ways to specify these plot options. In all cases, axes-wide options come before the data to plot, and curve-specific options come afterward. One way to specify the options is with strings enclosing `gnuplot` commands: """ # ╔═╡ 20a9dc74-0324-4d76-88fa-1c55ac281e5e plot("set title 'A nice sinusoid' set key box left unset grid set xlabel 'time' set ylabel 'voltage'", sin, "w points lc 'orange' title 'sin'") # ╔═╡ a0720ae4-8da5-4ed6-acd9-0471b840fa1d md"Options may also be given using the following syntax:" # ╔═╡ dc989e43-20ad-4f03-86a3-655c00c02a4e @plot({title = "'Another sinusoid'", key = "box left", grid = false, xlabel = "'time'", ylabel = "'voltage'" }, sin, {w = "points", lc = "'orange'", title = "'sin'" }) # ╔═╡ 564caa1c-222f-4655-9505-162f5e23b234 md"""##### Axis-wide options For axis-wide options (first argument to `@plot` above), `{option = value}` is converted to `"set option value"`. To unset an option, set it to false: `{tics = false}` is converted to `unset tics`. Curve-specific options (last argument to `@plot` above) are handled similary, but they are used to "build" the plot command given to `gnuplot`. This syntax offers some convenience features: * Specify a range as a tuple: `{xrange = (1, 2)}` is converted to `set xrange [1:2]`, while `{yrange = (-Inf, 5)}` is converted to `set yrange [*:5]`. * To set all ranges (`x`, `y`, `z` and `cb`) simultaneously, use `{ranges = (min, max)}`. * Specify tics conveniently: * `{xtics = 1:2}` is equivalent to `set xtics 1,1,2` * `{ztics = 1:2:7})` to `set ztics 1,2,7` * `{tics = (0,5)}` to `set tics (0, 5)` * `{tics = (labels = ("one", "two"), positions = (0, 5))}` to `set tics ('one' 0, 'two' 5, )` * Specify a color palette from [Colorschemes.jl](https://juliapackages.com/p/colorschemes). For example, `{palette = :viridis}` is converted to a `set palette` command with the appropriate colors. The palette name must be given as a symbol. * To reverse the palette order, specify it as `{palette = (:viridis, :reverse)}`. * You may also use any custom palette (for example, one created with `ColorSchemes.resample)` * A line type may be specified similary: `{lt = :viridis}`. * In 3D plots, specify the view as a tuple: `{view = (50, 60)}` is converted to `set view 50, 60`. * Specify margins, useful for multiplot with arbitrary layouts: `{margins = (0.33, 0.66, 0.2, 0.8)}` is converted to `set lmargin at screen 0.33...`. The margins are specified in the order left, right, bottom, top. An option may be specified more than one time: `{tics, tics = 1:2}` is converted to set tics set tics 1,1,2 Any number of option arguments may be given before the data to plot, and they will be combined. """ # ╔═╡ 52a654cf-9aac-47ba-82d4-6d7b4320d79e md"Options can also be given as `gnuplot` commands in strings:" # ╔═╡ 44930880-7dd2-4120-b097-6c19f8e7022a @plot({title = "'A nice sinusoid'"}, "set key box left", {grid = false}, "set xlabel 'time'\nset ylabel 'voltage'", sin, {w = "points"}, "lc 'orange' title 'sin'") # ╔═╡ 5809cde8-fa4d-49a5-bc4f-4baa0133b21f md"""##### Quoted strings String arguments given to gnuplot must be quoted. To simplify this, the `@Q_str` string macro might come in handy. The string `Q"a title"` is converted to `"'a title'"`.""" # ╔═╡ 1b7dfb67-6047-40fa-9b91-c48aa8e8aa9c @plot({title = Q"A nice sinusoid"}, "set key box left", {grid = false}, {xlabel = Q"time", ylabel = Q"voltage"}, sin, {w = "points"}, "lc 'orange' title 'sin'") # ╔═╡ beb2b6cd-ee6b-4bd8-bf8e-5d071adbd857 md"""##### Curve-specific options These options affect the way a curve is plotted (line style, color, etcetera), and may be specified as a string (which is passed directly to `gnuplot`) and/or as a set of options inside `{}` brackets. These options are passed to `gnuplot` as part of the `plot` command. For example, `@plot sin {with = "points", lc = Q"red"}` is converted to a `gnuplot` command `plot with points lc 'red'`. When using the bracketed options format, the following convenient syntax is available: * `marker` is available as synonym of `pointtype` * `markersize` and `ms` are available as synonyms of `pointsize`. * `legend` is available as a synonym of `title`. * `plotstyle` is a synonym of `with`. * A point type may be specified by name (instead of just by number as in native `gnuplot`): | name | gnuplot pt number | |-----:|:------------------| | dot | 0 | | ⋅ | 0 | | + | 1 | | plus | 1 | | x | 2 | | * | 3 | | star | 3 | | esquare | 4 | | fsquare | 5 | | ecircle | 6 | | fcircle | 7 | | etrianup | 8 | | ftrianup | 9 | | etriandn | 10 | | ftriandn | 11 | | edmd | 12 | | fdmd | 13 | Here a prefix "e" stands for "empty", "f" for "full"; "up" and "dn" stand for "pointing up" and "pointing down"; "trian" is a triangle and "dmd" is a diamond (rhombus). Note that this mapping of marker shapes to numbers is compatible with the most popular `gnuplot` terminals, but the specific mapping may be different for a given terminal. """ # ╔═╡ ea4e84ab-4820-4f91-8782-3bfc3ef4ba6a md"""##### Themes It is possible to create themes with the `@gpkw` macro (stands for "gnuplot keywords"):""" # ╔═╡ 1c0d72b4-992a-4a0e-b9bc-24a62a26da02 let theme = @gpkw {grid, xlabel = "'x'", ylabel = Q"voltage"} @plot(theme, sin) end # ╔═╡ 59fd1827-6592-473a-9baf-e17c4c93adb4 md"""A couple of lightweight themes are included: |Axis themes | Description | |-----------:|:------------| | :notics | Removes all tics | | :labels | Generic axis labels | | :nocb | Removes the colorbox | | :unitranges | Set all ranges to `[-1:1]` | Note that `plot` accepts any number of option arguments. Arguments before data are assumed to correspond to `gnuplot` `set` commands; arguments after the data affect the curve attributes. """ # ╔═╡ 83d697a8-3a33-4d37-a173-c31309c112e3 md"## Interactivity" # ╔═╡ afc4dfba-eb72-42de-aec0-d68cf20514be md"In a notebook, it is easy to tie plot variables to sliders or other UI elements." # ╔═╡ 8c124c3d-d444-41d6-8187-6058f4b5808f md"""Frequency: $(@bind freq Slider(1:10, default = 5, show_value = true))""" # ╔═╡ c2bc72a9-e96d-4216-92dd-71a0e51a647d md"""Line color: $(@bind color Select( ["'red'" => "red", "'blue'" => "blue", "'orange'" => "orange", "'dark-green'" => "dark green", "'chartreuse'" => "chartreuse"]))""" # ╔═╡ d253f4ea-b633-4255-8308-e1182c680df2 plot((-1, 1, 200), t -> sin(2π*freq*t), "lw 2 lc $(color)") # ╔═╡ 6245bfd6-a945-4e84-825d-57fbaa63ffd1 md"## Multiplot" # ╔═╡ 80e46def-fb34-426a-9883-18a8e99e72ce md"A multiplot can be easily generated and automatically laid out by adding more axes to an existing figure:" # ╔═╡ 52e760d6-3848-4705-a9d2-5968e2ad9b03 begin f = Figure() # create an empty figure plot(sin) plot(f[2], cos) # figures are row-major plot(f[3], (1:10).^2) # Gaston adjusts the layout as the number of plot(f[4], rand(10)) # suplots grows. end # ╔═╡ 91cbc979-b191-45cd-8aba-2ecc31f09a38 md"Add another plot to a subplot using indexing:" # ╔═╡ 379428f6-1007-457c-95ad-7f090947f72a plot!(f[4], randn(10)) # ╔═╡ 4779cbbb-9b02-4ffa-b393-c19d0423a967 md"Full control over gnuplot multiplot options can be obtained using margins, as follows:" # ╔═╡ 6ae49346-1bc5-46dc-9fb1-7530f6606474 let f = Figure(multiplot = "title 'Arbitrary layout demo'", autolayout = false) x = randn(100) y = randn(100) @plot({margins = (0.1, 0.65, 0.1, 0.65)}, x, y, "w p pt '+' lc 'dark-green'") @gpkw histogram(f[2], {margins = (0.7, 0.9, 0.1, 0.65), tics = false}, y, {lc = Q"dark-green"}, nbins = 10, horizontal = true) @gpkw histogram(f[3], {margins = (0.1, 0.65, 0.7, 0.9), boxwidth = "1 relative"}, x, {lc = "'dark-green'"}, nbins = 10) end # ╔═╡ c860dea5-1a8a-4516-98d6-97122962345d md"## Animations Animations require use of a terminal that supports it (such as `gif` or `webp`). Make sure your notebook supports the `webp` file format before using it. Gif is supported almost everywhere. Creating an animation is similar to multiplotting: multiple axes are drawn on the same figure. When using the `animate` option of the `gif` or `webp` terminals, however, the plot is rendered as an animation. Note that `gnuplot` will output a message to STDERR indicating how many frames were recorded; this message is purely informative and not actually an error. A difficulty arises when mixing plot formats in a notbook (say, `png` and `gif`): the terminal is specified in the global variable `Gaston.config.term`; however, Pluto executes cells in arbitrary order. This means that changing the terminal in one cell may affect other cells. To solve this problem, `Gaston` provides a way to ignore the global terminal configuration when rendering a plot. A figure `f` can be rendered with a given terminal by calling `animate(f, term)`. The default value of `term` is stored in `Gaston.config.altterm` and defaults to `gif animate loop 0`. The following examples illustrate how to create and display animations in a notebook:" # ╔═╡ 5895f542-8a5b-42a4-a1d6-6e4103a71308 let f = Figure() frames = 20 x = range(-1, 1, 200) ϕ = range(0, 2π-1/frames, frames) for i = 1:frames plot(f[i], x, sin.(5π*x .+ ϕ[i]), "lw 2") end animate(f, "gif animate loop 0 size 700,400") end # ╔═╡ 4f0391b9-fdf3-48f1-9cb5-9c722ff89c6c md"A background can be included in an animation as follows:" # ╔═╡ 42da1eb0-23f6-4f13-8107-a8a6063e87ef let f = Figure() frames = 75 x_bckgnd = range(-1, 1, 200) bckgnd = Gaston.Plot(x_bckgnd, sin.(2π*2*x_bckgnd), "lc 'black'") x = range(-1, 1, frames) for i in 1:frames plot(f[i], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") push!(f[i], bckgnd) end for i in frames:-1:1 plot(f[2frames-i+1], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") push!(f[2frames-i+1], bckgnd) end animate(f, "gif animate loop 0 size 700,400") end # ╔═╡ Cell order: # ╟─d4e94a70-866a-46e2-98c2-6babc7745fd2 # ╠═ccdce6f8-f1d8-11ed-19e9-fddf2407113b # ╠═7076fc20-8f0c-449c-9cfc-edf9801e85cd # ╠═c55fca39-9251-4593-b7f4-4342119e76e1 # ╠═a64f470c-a8d3-4c28-99f4-8149ac65ff58 # ╠═93d7c222-6755-4b71-b4cb-3ad82f4515f1 # ╠═ff57c40b-615d-4902-9810-8d874220f626 # ╠═dab403c8-f4ae-4029-a8c8-ef5ae161142c # ╠═a66e7e1d-b3a4-4504-be15-2324808607be # ╟─1a521882-7ee0-413d-9738-3a025499883e # ╟─8f50fd19-5ce4-447e-a7f9-7b16d59af6c0 # ╟─ab0aba7c-d841-44bb-8e6a-2e5515ef9aa5 # ╠═0e8236b4-6d1f-46ae-9c0e-ee1d4f605c0d # ╟─fc31bafd-eb7b-4204-b781-6e06cf95fd25 # ╠═804b8b60-8976-43a3-a5b7-86cedf9118e4 # ╟─e467671a-62a2-406d-99e4-9ecfa7f55b5e # ╠═a25f483f-f5c7-4949-9690-3d126a12e86a # ╟─26fe4805-3b08-4750-b0f8-f210a72a502b # ╠═21e1f17f-4fb2-490e-ade7-77725fb24f61 # ╟─cedb6fa7-e8c7-4a48-8935-1ce6385f1185 # ╠═8a50be7a-bcfa-468c-914a-9b1aa2c970ad # ╠═6d72504e-fede-41a6-bfef-0aef52936e1b # ╟─e15e224f-834c-459e-b355-15d1fb8975fb # ╠═162ccb40-92b0-43fe-a89e-676864ac9883 # ╟─0b229ed3-868a-42d3-b669-6cacaf213c90 # ╠═fbce3eb9-6691-4945-ac6b-4b0bcf31e003 # ╟─262faf7b-ac62-45d9-bc18-3860e6e10fb7 # ╠═57f66b17-8c2d-4737-84e1-c456a7dacc89 # ╟─a0762a24-26be-40a6-a333-92bd8999a5a3 # ╟─d66d3ef3-130f-45a7-81d6-0cac24e9b1d2 # ╠═4f145f29-f99d-4f36-9ebf-5cc632f956c1 # ╟─970fd5da-91e2-44bb-a427-6823b03c79e9 # ╠═881e794d-bc5e-49d0-b240-2cb58abe82ce # ╟─996ef179-f5fd-4345-a6af-a74709bd2a6b # ╠═f395e87b-39f6-4f79-83b1-1b44bcb37feb # ╟─9db5812e-47a3-4b17-b3f0-9e74a38e3f54 # ╠═061a881c-220c-4183-8f5b-12fd91a8f358 # ╠═8c14838e-fcb6-4b49-83cd-a3ef5f267b24 # ╟─be09ba73-cabb-40ce-aa06-6bfe0705f6e2 # ╠═33731005-bb8c-48a9-863a-93e25d579930 # ╟─906770c9-07bd-4447-af21-de487c7a1e02 # ╠═38a71681-dc2d-4af5-be14-1682924abf51 # ╠═5c25b0e3-9a45-4ad5-92ae-314cfce5c117 # ╠═4f4fb009-cf62-4e64-a519-77f340f83f69 # ╟─84ccf2ff-472b-4b34-9472-2006f70684e6 # ╠═b42213a1-f27a-4c96-a6a7-b5a8a200ef19 # ╟─e1e93d06-4565-4625-8fcf-fe8dbc9fe55e # ╟─8ab2a66a-df03-4aeb-a736-1336b75dadaa # ╠═ca7554bc-6f9c-4574-bf78-20afd42dc647 # ╟─5acd956b-e22d-4638-be19-1ad090cac7d2 # ╠═20a9dc74-0324-4d76-88fa-1c55ac281e5e # ╟─a0720ae4-8da5-4ed6-acd9-0471b840fa1d # ╟─dc989e43-20ad-4f03-86a3-655c00c02a4e # ╟─564caa1c-222f-4655-9505-162f5e23b234 # ╟─52a654cf-9aac-47ba-82d4-6d7b4320d79e # ╠═44930880-7dd2-4120-b097-6c19f8e7022a # ╟─5809cde8-fa4d-49a5-bc4f-4baa0133b21f # ╠═1b7dfb67-6047-40fa-9b91-c48aa8e8aa9c # ╟─beb2b6cd-ee6b-4bd8-bf8e-5d071adbd857 # ╟─ea4e84ab-4820-4f91-8782-3bfc3ef4ba6a # ╟─1c0d72b4-992a-4a0e-b9bc-24a62a26da02 # ╟─59fd1827-6592-473a-9baf-e17c4c93adb4 # ╟─83d697a8-3a33-4d37-a173-c31309c112e3 # ╟─afc4dfba-eb72-42de-aec0-d68cf20514be # ╟─8c124c3d-d444-41d6-8187-6058f4b5808f # ╟─c2bc72a9-e96d-4216-92dd-71a0e51a647d # ╠═d253f4ea-b633-4255-8308-e1182c680df2 # ╟─6245bfd6-a945-4e84-825d-57fbaa63ffd1 # ╟─80e46def-fb34-426a-9883-18a8e99e72ce # ╠═52e760d6-3848-4705-a9d2-5968e2ad9b03 # ╟─91cbc979-b191-45cd-8aba-2ecc31f09a38 # ╠═379428f6-1007-457c-95ad-7f090947f72a # ╟─4779cbbb-9b02-4ffa-b393-c19d0423a967 # ╠═6ae49346-1bc5-46dc-9fb1-7530f6606474 # ╟─c860dea5-1a8a-4516-98d6-97122962345d # ╠═5895f542-8a5b-42a4-a1d6-6e4103a71308 # ╟─4f0391b9-fdf3-48f1-9cb5-9c722ff89c6c # ╠═42da1eb0-23f6-4f13-8107-a8a6063e87ef ================================================ FILE: docs/v1/2d-gallery.md ================================================ # [2-D Gallery](@id twodeegal) (Many of these examples taken from, or inspired by, [@lazarusa's amazing gallery](https://lazarusa.github.io/gnuplot-examples/gallery/)) # Glowing curves ```@example 2dgal using Gaston # hide set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide x = 0:0.3:4 a = exp.(- x) b = exp.(- x.^2) plot(x, a, curveconf = "w lp lw 1 lc '#08F7FE' pt 7 t 'e^{-x}'", Axes(object="rectangle from screen 0,0 to screen 1,1 behind fc 'black' fs solid noborder", border="lw 1 lc 'white'", xtics="textcolor rgb 'white'", ytics="textcolor rgb 'white'", ylabel="'y' textcolor 'white'", xlabel="'x' textcolor 'white'", grid="ls 1 lc '#2A3459' dt 4", key="t r textcolor 'white'", style="fill transparent solid 0.08 noborder") ) plot!(x, b, curveconf = "w lp lw 1 lc '#FFE64D' pt 7 t 'e^{-x^2}'") for i in 1:10 plot!(x,a,w="l lw $(1 + 1.05*i) lc '#F508F7FE' t ''") plot!(x,b,w="l lw $(1 + 1.05*i) lc '#F5FFE64D' t ''") end plot!(x, a, curveconf = "w filledcu y=0 lw 1 lc '#08F7FE' t ''") plot!(x, a, supp= b , curveconf = "w filledcu lw 1 lc '#FFE64D' t ''") ``` # Volcano data ```@example 2dgal using RDatasets volcano = Matrix{Float64}(dataset("datasets", "volcano")) imagesc(volcano, Axes(palette = :inferno, auto="fix", size="ratio -1", title = "'Aukland s Maunga Whau Volcano'") ) ``` # Animation An animation can be produced by pushing new plots into an existing plot, and then saving the result as a GIF the the `animate` option. ```julia closeall() #hide t = 0:0.01:2π f(t,i) = sin.(t .+ i/10) ac = Axes(title = :Animation, xlabel = :x, ylabel = :y); # axes configuration cc = "w l lc 'black' notitle" # curve configuration F = plot(t, f(t,1), curveconf = cc, ac); # create the first frame, with handle 1 for i = 2:50 pi = plot(t, f(t,i), curveconf = cc, ac, handle=2) # frames, with handle 2 push!(F, pi) # push the frame to F end save(term = "gif", saveopts = "animate size 600,400 delay 1", output="anim.gif", handle=1) ``` ![](assets/anim.gif) # Color from palette ```@example 2dgal x = -2π:0.05:2π plot(x, sin.(3x), supp = x, curveconf = "w l notitle lw 3 lc palette", Axes(palette = :ice) ) ``` # Categorical data ```@example 2dgal using RDatasets dset = dataset("datasets", "iris") byCat = dset.Species categ = unique(byCat) ac = Axes(linetype = :tab10, xlabel = "'Sepal length'", ylabel = "'Sepal width'", auto = "fix", title = "'Iris dataset'", key = "b r font ',9' tit 'Species' box") c = categ[1] indc = findall(x -> x == c, byCat) p = plot(dset.SepalLength[indc], dset.SepalWidth[indc], ac, curveconf = "w p tit '$(c)' pt 7 ps 1.4 ") c = categ[1] indc = findall(x -> x == c, byCat) P = plot(dset.SepalLength[indc], dset.SepalWidth[indc], ac, curveconf = "w p tit '$(c)' pt 7 ps 1.4 "); c = categ[2] indc = findall(x -> x == c, byCat) plot!(dset.SepalLength[indc], dset.SepalWidth[indc], curveconf = "w p tit '$(c)' pt 7 ps 1.4 "); c = categ[3] indc = findall(x -> x == c, byCat) plot!(dset.SepalLength[indc], dset.SepalWidth[indc], curveconf = "w p tit '$(c)' pt 7 ps 1.4 "); P ``` # Vector fields (arrow plots) Vector fields can be plotted with the `vectors` plot style. The arrows' `x` and `y` coordinates need to be specified in supplementary columns. ```@example 2dgal x = range(0, 6π, length = 50) A = range(0, 2, length = 50) xdelta = A./7.0.*rand(50) ydelta = A./3.0.*rand(50) plot(A.*cos.(x), A.*sin.(x), supp = [xdelta ydelta], w = :vectors, lc = "'dark-turquoise'") ``` # Violin plots A violin plot is created by plotting `(y, x)` instead of `(x, y)`, using the `filledcurves` style, and mirroring the plot. ```@example 2dgal x = range(0, 5, length=100) y = 2cos.(x) + sin.(2x) + 0.5cos.(3x) - sin.(4x) .+ 3 M = maximum(y) plot(y, x, w = "filledcurves x = $M", lc = :turquoise, Axes(title = "'Violin plot'")) plot!(-y .+ 2M, x, w = "filledcurves x = $M", lc = :turquoise) ``` Violin plot with a superimposed boxplot: ```@example 2dgal plot(y, x, w = "filledcurves x = $M", lc = :turquoise, Axes(title = "'Violin plot'", style = "fill solid bo -1", boxwidth = 0.075, errorbars = "lt black lw 1")) plot!(-y .+ 2M, x, w="filledcurves x = $M", lc=:turquoise) plot!(x, y, w = "boxplot", u = "($M):2", fc = :white, lw = 2) ``` # Plotting times/dates The key to plotting dates and times is converting them to strings, and then telling gnuplot what the format is, using `timefmt`. In Julia, dates and times are defined in module `Dates`. This example is inspired by [this gnuplot demo](http://gnuplot.sourceforge.net/demo_5.2/timedat.html). ```@example 2dgal using Dates dates = [DateTime(2013,6,1,0,0), DateTime(2013,6,10,9,30), DateTime(2013,6,18,13,05), DateTime(2013,7,4,20,35), DateTime(2013,7,13,17,18)] concentrations = [0.2, 0.3, 0.5, 0.38, 0.18] x = Dates.format.(dates, "dd/mm/yy HHMM") plot(x, values, u = "1:3", Axes(xdata = "time", timefmt = "'%d/%m/%y %H%M'", style = "data fsteps", format = "x \"%d/%m\\n%H:%M\"", xlabel = "\"Date\\nTime\"", ylabel = "\"Concentration\\nmg/l\"", title = "'Plot with date and time as x-values'", key = "right")) plot!(x, values, u="1:3", w=:p, marker="esquare", legend = "'Total P'") ``` # Displaying a flag with bars This example shows how one can use `set palette defined` to associate particular colors with numerical values. ```@example 2dgal imagesc([0 0.5 1 ; 0 0.5 1], Axes(palette="defined (0 'blue', 0.5 'white', 1 'red')", colorbox=:off)) ``` ================================================ FILE: docs/v1/2dplots.md ================================================ # [2-D plotting tutorial](@id twodeetut) This section provides a brief tutorial on 2-D plotting, with examples on how to obtain common plot types. For full details, we refer the reader to gnuplot's documentation. ## Basics of plotting A call to `plot` looks like this: plot(x, y, z, supp, curvekwargs..., Axes(axeskwargs...)) `x`, `y`, `z` and `supp` are the data to plot. Only `y` is mandatory for 2-D plots. For most plots, vectors are plotted, but plotting images requires a matrix or 3-D array. `supp` is a keyword argument used for supplementary data, which are additional columns that gnuplot can use, such as the errorbar length, or the marker size. Gaston translates the provided data to the format that gnuplot requires, and writes it to a temporary file. `curvekwargs` is a set of keyword arguments that are related to the appearance of the plotted data. These typically specify the plot style, the line color, the marker type, etcetera. These arguments are used to build a `plot` command for gnuplot. Note that, instead of using a bunch of individual keyword arguments, you can pass gnuplot a complete plot command using the keyword `curveconf`. `axeskwargs` is a set of keyword arguments wrapped in `Axes()`, which specify the look of the axes, or figure; this refers to things like the plot title, tics, ranges, grid, etcetera. Essentially, anything that can be `set` in gnuplot, can be configured from Gaston by wrapping it in `Axes()`. The special keyword `axesconf` is used to provide a string with commands that are passed literally to gnuplot. To add a new curve to an existing figure, use `plot!`. It accepts the same arguments as `plot`, except for `Axes()` arguments, which can only be set from `plot`. The `plot` command has enough flexibility to plot everything that Gaston is capable of. However, Gaston provides a few specialized commands that make certain plots easier. These are illustrated below. | Command | Purpose | |-------------|:---------------------------------| | `scatter`, `scatter!` | Plot point clouds | | `stem` | Plot discrete (sampled) signals | | `bar` | Plot bar charts | | `histogram` | Plot histograms | | `imagesc` | Plot images | (Some of the examples below are taken from lazarusa's [excellent gallery](https://lazarusa.github.io/gnuplot-examples/gallery/)). ## Debug mode If you want to see exactly what commands Gaston is sending to gnuplot, you can turn on debug mode: set(debug = true) Use `set(debug = false)` to turn this mode off. ## Set the plot style, line color, line pattern, line width, and markers The plot style is set with the keys `w`, `with`, or `plotstyle`. Gnuplot supports many different plot styles; for example, `lines` means plotting a line, `points` is just the markers, and `linespoints` is a line with markers. See all the details in gnuplot's documentation. The line color is set with `lc` or `linecolor`; while the line width is specified with `linewidth` or `lw`. The marker type is configured with `pointtype`, `pt` or `marker`. Usually gnuplot identifies each marker type by a number, but Gaston provides some equivalent names (see [Introduction to plotting](@ref). The marker size is configured with `pointsize`, `ps` or `ms`. The number of markers may be configured with `pointnumber` or `pn`. The line style can be configured in multiple ways; one is to specify `linestyle` or `ls` followed by a pattern of dashes and points such as `'-.-'`. The plotted curve can be given a legend with `title` or `legend`. The following examples use all these options. ```@example 2dtut using Gaston # hide set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide # plot with lines and markers t = -5:0.05:5 plot(t, sin, # linespoints plot style w = :lp, # line color lc = :turquoise, # line width lw = 3, # empty circles marker = "ecircle", # marker size ms = 1.5, # plot only ten markers pn = 10, # legend legend = :A_sine_wave ) ``` ```@example 2dtut # plot with dashed line plot(t, sin, # lines plot style w = :l, # line width lw = 3, # dashed line ls = "'-.-'" ) ``` ## Set the plot title, axis labels, tics, legends and grid Since these are attributes of the entire figure, they must be wrapped by `Axes()`. The title is set with `title`, the axis labels with `xlabel` and `ylabel`. The tics are configured with `xtics` and `ytics`. The grid can be turned on with `grid`. The position and shape of the legend box is configured with `key`. The following example shows how to use these attributes. ```@example 2dtut plot(t, sin, w = :lp, lc = :turquoise, lw = 3, marker = "ecircle", ms = 1.5, pn = 10, legend = :A_sine_wave, Axes(# set the title title = "'Example plot'", # turn on the grid grid = :on, # specify tics xtics = -5:2:5, ytics = ([-1 0 1], ["- one", "zero", "+ one"]), # configure legend box key = "outside center bottom") ) ``` ## Logarithmic plots The axes can be configured to have a logarithmic scale, using `axis = semilogy`, `semilogx`, or `loglog`. ```@example 2dtut using SpecialFunctions Q(x) = 0.5erfc(x/sqrt(2)) SNR = 1:15 plot(10log10.(SNR), Q.(sqrt.(SNR)), Axes(axis = "semilogy", xlabel = "'Signal to Noise Ratio (dB)'", ylabel = "'Bit Error Rate'", ytics = "out format '10^{%T}'", grid = "xtics mytics", title = "'BPSK Bit Error Rate'") ) ``` ## Step plots In step plots, data points are joined with a horizontal line. To obtain a step plot, set the plot style to `steps`, `fsteps`, or `fillsteps`. ```@example 2dtut t = -2:0.06:2 plot(t, sin.(2π*t), plotstyle = :steps, Axes(title = "'Steps plot'") ) ``` ```@example 2dtut plot(t, sin.(2π*t), w = :fillsteps, Axes(style = "fill solid 0.5", title = "'Fillsteps plot'") ) ``` The color can be specified with `fillcolor`: ```@example 2dtut plot(t, sin.(2π*t), w = :fillsteps, fc = :plum, Axes(style = "fill solid 0.5", title = "'Fillsteps plot'") ) ``` ## Plotting with financial and error bars Gaston supports plotting using financial and error bars, by setting the plot style appropriately. Supplementary data is passed to gnuplot using the argument `supp`. ```@example 2dtut x = 1:0.5:8 open = 3*(0.5 .- rand(length(x))) close = open .+ 1; low = open .- 1; high = open .+ 1.5; fin = [low high close] plot(x, open, supp = fin, plotstyle = "financebars", Axes(title = "'Example of financial bars'") ) ``` ```@example 2dtut x = 0:2:50 y = @. 2.5x/(5.67+x)^2 err = 0.05*rand(length(x)) plot(x, y, supp = err, plotstyle = :errorlines, Axes(title = "'Example of error lines'") ) ``` ## Plotting filled curves To "fill" the area below a curve, use the plot style "filledcurves". In the example below, we use `curveconf` to pass a full plot command to gnuplot. The `style` is set to `transparent`, so one plot will not obscure those behind it. ```@example 2dtut x = LinRange(-10,10,200) fg(x,μ,σ) = exp.(.-(x.-μ).^2 ./(2σ^2))./(σ*√(2π)) plot(x, fg(x, 0.25, 1.5), curveconf = "w filledcu lc '#E69F00' dt 1 t '0.25,1.5'", Axes(style = "fill transparent solid 0.3 noborder", key = "title 'μ,σ' box 3", xlabel = "'x'", ylabel="'P(x)'", title = "'Example of filled curves'")) plot!(x, fg(x, 2, 1), curveconf = "w filledcu lc '#56B4E9' dt 1 t '2,1'") plot!(x, fg(x, -1, 2), curveconf ="w filledcu lc '#009E73' dt 1 t '-1,2'") ``` ## Filling the space between two curves It is possible to fill the space between two curves by providing the second curve as a supplementary column. In this example, gnuplot will fill the space between `sin.(x)` and `sin.(x) .+ 1`. ```@example 2dtut x = LinRange(-10,10,200) plot(x, sin.(x) .- 0.2, supp = sin.(x) .+ 0.2, curveconf = "w filledcu lc '#56B4E9' fs transparent solid 0.3", Axes(title = :Filling_the_space_between_two_curves)) plot!(x, sin.(x), lc = :blue) ``` ## Box plots This example shows the use of supplementary data with the "boxerrorbars" style. the vector `yerr` controls the length of the error bar for each box, while `lcval` assigns each box a color (since `lc palette` is given in `curveconf`). Finally, a color palette is specified using a symbol (`:summer`), which refers to a color scheme from ColorSchemes.jl. ```@example 2dtut using Random x = 1:2:20 y = 5*rand(10) yerr = 0.4*abs.(randn(10)) lcval = 1:10 plot(x, y, supp=[yerr lcval], curveconf = "w boxerrorbars notit lc palette fs solid 0.5", Axes(palette = :summer, xrange=(0,22), yrange=(0,6)) ) ``` ## Scatter plots (point clouds) A scatter plot can be generated with the `scatter` command: ```@example 2dtut c = 2rand(1000).-1 .+ im*(2rand(1000).-1) p = filter(x->abs(x)<1, c) scatter(p, marker = "fsquare", pointsize = 0.25, Axes(object = "ellipse at 0,0 size 2,2", title = "'Random points within the unit circle'") ) ``` Note that, when the data to plot is complex, the real part is interpreted as the `x` coordinate and the imaginary part as the `y` coordinate. Besides the standard markers, any UTF-8 character may be used: ```@example 2dtut scatter(randn(30), randn(30), marker = "λ") ``` ### Bubble plots This example shows how to generate a scatter plot where the color and size of each point is specified with supplementary data. This example also shows how to turn off the colorbox. ```@example 2dtut n = 40 x, y, z = randn(n), randn(n), randn(n) plot(x, y, supp = [5z z], curveconf = "w p notit pt 7 ps var lc palette", Axes(palette = :ice, xrange = (-2.2, 2.5), yrange = (-2.2, 2.2), colorbox = :off) ) ``` !!! info "`scatter` with gnuplot" Behind the scenes, `scatter` calls `plot` with the `points` plotstyle. ## Stem plots Stem plots make it obvious one is plotting a discrete-time signal. The `stem` command replicates the behavior of `stem` in Matlab, Octave, et al: ```@example 2dtut t = -2:0.06:2 stem(t, sin.(2π*t)) ``` By default, the line color is blue and the lines are made sligthly thicker. If only the vertical lines ("impulses") are desired, pass the option `onlyimpulses=true` to `stem`: ```@example 2dtut stem(t, sin.(2π*t), onlyimpulses = true) ``` !!! info "`stem` with gnuplot" Behind the scenes, `stem` calls `plot` with the `impulses` plotstyle, followed (if `onlyimpulses == true`) by a call to `plot!` with the `points` plotstyle and the pointtype set to `"ecircle"`. ## Bar plots Bar plots can be generated with the `bar` command: ```@example 2dtut year = range(1985, length=20); data = 0.5 .- rand(20) bar(year, data, fc = "'dark-goldenrod'", legend = "'Random number'", Axes(xtics = "rotate", key = "box under", boxwidth = 0.66, style = "fill pattern 2") ) ``` !!! info "`bar` with gnuplot" Behind the scenes, `bar` uses gnuplot's `boxes` plotstyle, with a default box width of 0.8 and solid fill. ## Histograms To plot histograms, use the `histogram` command. This command takes the same properties as `bar`. In addition, `histogram` accepts a `bins` parameter, used to specify the number of bins, and a `norm` parameter that can be used to normalize the area under the histogram. ```@example 2dtut histogram(rand(10000), bins = 15, norm = 1, Axes(title = :Histogram, yrange = "[0:1.8]") ) ``` It is of course possible to use `histogram` (or any other plot command) along with `plot!` to produce different kinds of plots in the same figure: ```@example 2dtut x = -5:0.05:5 data = randn(10000) gaussian = @. exp(-x^2/2)/sqrt(2π) histogram(data, bins = 25, norm = 1, legend = "'Experimental'", linecolor = :turquoise, Axes(boxwidth = "0.8 relative", title = "'Experimental and Theoretical Gaussian distributions'", key = "box top left")) plot!(x, gaussian, linecolor = :black, legend = "'Theoretical'") ``` ## Images The command to plot an image is `imagesc`. It can plot a scaled or RGB image, depending on whether the provided coordinates are an array with two or with three dimensions. Note that `imagesc` interprets the `x` axis as the columns of the matrix. In other words, element `[1,1]` is located in the top-left corner of the plot, and element `[end:1]` is in the bottom-left corner. ### Scaled image A scaled image is a plot of a matrix whose elements are interpreted as grayscale values (which may be displayed in color with a given palette). ```@example 2dtut Z = [5 4 3 1 0 ; 2 2 0 0 1 ; 0 0 0 1 0 ; 0 1 2 4 3] imagesc(Z, Axes(title = "'Simple scaled image'", palette = :summer)) ``` To display the image as grayscale, use the `gray` palette. ```@example 2dtut using Images using TestImages img = testimage("lake_gray"); ii = channelview(img)[1,:,:].*255; imagesc(ii, Axes(palette = :gray)) ``` ### RGB image An RGB image is a plot of a 3-D array whose elements are interpreted as the red, green, and blue components of each image pixel. The array's `[1,;,:]` elements are a matrix representing the red channel, while `[2,:,:]` and `[3,:,:]` are the green and blue channels respectively. ```@example 2dtut img = testimage("lake_color") imagesc(channelview(img).*255, Axes(size = "square", autoscale = "fix")) ``` ================================================ FILE: docs/v1/3d-gallery.md ================================================ # [3-D Gallery](@id threedeegal) (Many of these examples taken from, or inspired by, [@lazarusa's amazing gallery](https://lazarusa.github.io/gnuplot-examples/gallery/)) # Interlocking Tori ```@example 3dgal using Gaston # hide set(reset=true) # hide set(termopts="size 500,500 font 'Consolas,11'") # hide U = LinRange(-pi, pi, 100) V = LinRange(-pi, pi, 20) x = [cos(u) + .5 * cos(u) * cos(v) for u in U, v in V] y = [sin(u) + .5 * sin(u) * cos(v) for u in U, v in V] z = [.5 * sin(v) for u in U, v in V] surf(x', y', z', w = :pm3d, Axes(palette = :dense, pm3d = "depthorder", colorbox = :off, key = :false, tics = :false, border = 0, view = "60, 30, 1.5, 0.9", style = "fill transparent solid 0.7")) x = [1 + cos(u) + .5 * cos(u) * cos(v) for u in U, v in V] y = [.5 * sin(v) for u in U, v in V] z = [sin(u) + .5 * sin(u) * cos(v) for u in U, v in V] surf!(x', y' ,z' , w = :pm3d) ``` # Fill a curve in 3-D ```@example 3dgal set(saveopts="size 550,325 font 'Consolas,11'") # hide x = 0.:0.05:3; y = 0.:0.05:3; z = @. sin(x) * exp(-(x+y)) surf(x, y, z, supp = [z.*0 z], curveconf = "w zerror t 'Data'", lw = 3, Axes(xlabel = :X, ylabel = :Y, linetype = :Set1_5, style = "fill transparent solid 0.3", xyplane = "at 0", grid = :on) ) surf!(x.*0, y, z, w = :l, lw = 3) surf!(x, y.*0, z, w = :l, lw = 3) ``` # Variable marker size and color ```@example 3dgal x = 0:0.1:6π; scatter3(x, cos.(x), sin.(x), supp = x./10, ps = "variable", pt = "fcircle", lc = "palette", Axes(colorbox = :off)) ``` # Surface with contours ```@example 3dgal x = y = -10:0.5:10 f1 = (x,y) -> cos.(x./2).*sin.(y./2) surf(x, y, f1, lc = :turquoise, Axes(hidden3d = :on, contour = "base", cntrparam = "levels 10", key = :off)) ``` # Egg-shaped contours ```@example 3dgal x = -1:0.05:1 y = -1.5:0.05:2 egg(x,y) = x^2 + y^2/(1.4 + y/5)^2 segg = [egg(x,y) for x in x, y in y] contour(x, y, segg', labels = false, curveconf = "w l lc palette", Axes(palette = :cool, cntrparam = "levels incremental 0,0.01,1", auto = "fix", xrange = (-1.2, 1.2), yrange = (-1.5, 2), cbrange = (0, 1), xlabel = :x, ylabel = :y, size="ratio -1")) ``` # Tubes ```@example 3dgal U = LinRange(0,10π, 80) V = LinRange(0,2π, 20) x = [(1-0.1*cos(v))*cos(u) for u in U, v in V] y = [(1-0.1*cos(v))*sin(u) for u in U, v in V] z = [0.1*(sin(v) + u/1.7 - 10) for u in U, v in V] surf(x, y, z, w="pm3d", Axes(pm3d = "depthorder", style = "fill transparent solid 0.7", view = "equal xyz", xyplane = -0.05, palette = :ice, xrange = (-1.2, 1.2), yrange = (-1.2, 1.2), colorbox = :off)) ``` # Spheres ```@example 3dgal Θ = LinRange(0, 2π, 100) # 50 Φ = LinRange(0, π, 20) r = 0.8 x = [r * cos(θ) * sin(ϕ) for θ in Θ, ϕ in Φ] y = [r * sin(θ) * sin(ϕ) for θ in Θ, ϕ in Φ] z = [r * cos(ϕ) for θ in Θ, ϕ in Φ] surf(x, y, z, w = :l, lc = :turquoise, Axes(view = "equal xyz", pm3d = "depthorder", hidden3d = :on)) ``` ```@example 3dgal surf(x, y, z, w = :pm3d, Axes(style = "fill transparent solid 0.5", xyplane = 0, palette = :summer, view = "equal xyz", pm3d = "depthorder")) ``` # Torus ```@example 3dgal U = LinRange(-π,π, 50) V = LinRange(-π,π, 100) r = 0.5 x = [1 + cos(u) + r * cos(u) * cos(v) for u in U, v in V] y = [r * sin(v) for u in U, v in V] z = [sin(u) + r * sin(u) * cos(v) for u in U, v in V] axesconf = """set object rectangle from screen 0,0 to screen 1,1 behind fillcolor 'black' fillstyle solid noborder set pm3d depthorder set style fill transparent solid 0.5 set pm3d lighting primary 0.05 specular 0.2 set view 108,2 unset border set xyplane 0 unset tics unset colorbox""" surf(x, y, z, w = :pm3d, Axes(palette = :cool, axesconf = axesconf)) ``` # Animation ```julia closeall() # hide z=0:0.1:10pi; step = 5; cc = "w l lc 'turquoise' lw 3 notitle" ac = Axes(zrange = (0,30), xrange = (-1.2, 1.2), yrange = (-1.2, 1.2), tics = :off, xlabel = :x, ylabel = :y, zlabel = :z) F = scatter3(cos.(z[1:step]), sin.(z[1:step]), z[1:step], curveconf = cc, ac); for i = 2:60 pi = scatter3(cos.(z[1:i*step]), sin.(z[1:i*step]), z[1:i*step], curveconf = cc, ac, handle = 2); push!(F, pi) end for i = 60:-1:1 pi = scatter3(cos.(z[1:i*step]), sin.(z[1:i*step]), z[1:i*step], curveconf = cc, ac, handle = 2); push!(F, pi) end save(term="gif", saveopts = "animate size 600,400 delay 1", output="anim3d.gif", handle=1) ``` ![](assets/anim3d.gif) ```julia closeall() # hide x = y = -15:0.4:15 ac = Axes(title = :Sombrero_Surface, palette = :cool, cbrange = (-0.2, 1), zrange = (-0.3, 1), hidden3d = :on) F = surf(x, y, (x,y) -> (@. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y)), ac, w = :pm3d); for i = 1:-0.1:-1 pi = surf(x, y, (x,y) -> (@. i*sin(sqrt(x*x+y*y))/sqrt(x*x+y*y)), ac, w = :pm3d, handle = 2); push!(F, pi) end for i = -0.9:0.1:1 pi = surf(x, y, (x,y) -> (@. i*sin(sqrt(x*x+y*y))/sqrt(x*x+y*y)), ac, w = :pm3d, handle = 2); push!(F, pi) end save(term = "gif", saveopts = "animate size 600,400 delay 1", output = "anim3db.gif", handle=1) ``` ![](assets/anim3db.gif) ================================================ FILE: docs/v1/3dplots.md ================================================ # [3-D plotting tutorial](@id threedeetut) Three dimensional plots can be created with the commands `surf` and `surf!`. These are very similar to [plot](@ref twodeetut), except for the `z` coordinate, which is a matrix that specifies the z-coordinate of the points specified in `x` and `y`. In addition, the following commands are specialized for different types of 3-D plots: | Command | Purpose | |-------------|:---------------------------------| | `scatter3`, `scatter3!` | 3-D point clouds | | `contour` | Contour plots | | `heatmap` | Heatmap plots | ## How to plot a set of 3-D coordinates A set of 3-D coordinates may be explicitly given. Plotting them is just a matter of providing the data to gnuplot. Note that the data may be plotted as a wireframe (with plot style `lines`, which is the default), or as a surface (`pm3d`) with an optional palette. ```@example 3dtut using Gaston # hide set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide # plot a wireframe x = [0,1,2,3] y = [0,1,2] z = [10 10 10 10 ; 10 5 1 0 ; 10 10 10 10] surf(x, y, z, Axes(title = "'3D: Valley of the Gnu from gnuplot manual'")) ``` Note that the matrix of `z` coordinates is defined so that the columns are indexed by the `x` coordinates. In other words, `z[1,1]` is the surface at `x = 0, y = 0` and `z[3,1]` is the coordinate at `x = 0, y = 2`. ```@example 3dtut surf(x, y, z, w = :pm3d, Axes(title = "'Surface with palette'", palette = :summer)) ``` ## How to plot a 3-D function A 3-D function defines a surface for a given set of `x` and `y` samples. Consider the function `(x,y) -> @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y)`. It may be plotted with ```@example 3dtut x = y = -15:0.4:15 f1 = (x,y) -> @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) surf(x, y, f1, lc = :turquoise, Axes(title = :Sombrero_Wireframe, hidden3d = :on)) ``` ```@example 3dtut surf(x, y, f1, w = :pm3d, Axes(title = :Sombrero_Surface, palette = :cool, cbrange = (-0.2, 1), hidden3d = :on)) ``` ## Plotting multiple surfaces with `surf!` The equivalent to `plot!` is `surf!`: ```@example 3dtut surf(x,y,f1,w=:pm3d,Axes(title=:Sombrero_Surface,palette=:cool,cbrange=(-0.2,1),hidden3d=:on)) # hide surf!(x, y , (x,y) -> cos.(x./2).*sin.(y./2)-3, lc = :orange, w = :l) ``` ## Plotting contours Gnuplot's contour support is quite flexible. The `contour` command sets up pretty generic contours, which hopefully are useful in many cases. For much more detail, see gnuplot's documentation. The contours of a surface can be plotted using: ```@example 3dtut x = y = -5:0.1:5 contour(x, y, (x,y) -> 5cos.(x/2).*sin.(y/2)) ``` The labels can be disabled if `labels=false` is passed as an argument: ```@example 3dtut contour(x, y, (x,y) -> 5cos.(x/2).*sin.(y/2), labels=false) ``` ## Plotting heatmaps Heatmaps can be plotted using `heatmap`. Just like `contour`, this command sets up a pretty basic heatmap; for more control, see gnuplot's documentation. ```@example 3dtut heatmap(x, y, (x,y)->cos.(x/2).*sin.(y/2)) ``` ================================================ FILE: docs/v1/api.md ================================================ # API Reference ================================================ FILE: docs/v1/examples.md ================================================ # Examples The plots below have been rendered in a png terminal with the following configuration: ```julia Gaston.config.term = "pngcairo font ',10' size 640,480" ``` In addition, gnuplot's start up file is as described in the Introduction: [Gnuplot startup file](@ref). ## 2-D Plots ```@setup 2dt using Gaston Gaston.config.output = :echo Gaston.config.term = "pngcairo font ',10' size 640,480" closeall() # hide ``` Let us start with a simple sine wave plot: ```@example 2dt x = range(0, 0.5, length = 100) y = sin.(2*pi*10*x) plot(x, y) ``` Now, let us add a grid and some annotations: ```@example 2dt @plot {grid, title = Q"{/:Bold A sine wave}", xlabel = Q"Time", ylabel = Q"Volts"} x y ``` Here we have used `@plot` instead of `plot`, which allows us to specify the plot settings as a list of keyword arguments. These arguments can be stored in a "theme" using `@gpkw`: ```julia settings = @gpkw {grid, title = Q"{/:Bold A sine wave}", xlabel = Q"Time", ylabel = Q"Volts"} ``` In addition, we have used the `Q` string macro to avoid typing single quotes; `Q"Time"` is converted to `"'Time'"`. Now let us change the line color and markers: ```@example 2dt settings = @gpkw {grid, title = Q"A sine wave", xlabel = Q"Time", ylabel = Q"Volts"}; # hide @plot settings x y {with = "lp", lc = Q"sea-green", pt = :fcircle, ps = 1.5} ``` Parameters that affect how the curve is plotted are specified *after* the data. These can also be stored and reused, so that ```julia plotline = @gpkw {with = "lp", lc = Q"sea-green", pt = :fcircle, ps = 2} @plot settings x y plotline ``` would produce the same plot. Settings and plotline parameters can also be specified as strings; see the [Manual](@ref) for all the details. Gaston also has a number of built-in [Themes](@ref). Use `plot!` or `@plot!` to plot a second curve: ```@example 2dt plotline = @gpkw {with = "lp", lc = Q"sea-green", pt = :fcircle, ps = 2} # hide @plot(settings, {title = Q"Two sinusoids", key = "columns 1", key = "box outside right top"}, x, y, plotline, {title = "'sin'"}) y2 = cos.(2*pi*10*x) @plot! x y2 {dashtype = Q".-.", title = Q"cos"} ``` Here we see how multiple settings and plotline arguments can be combined. Note that new settings cannot be declared in `plot!` commands; only the plotline for the new curve can be specified. #### Plotting functions In the examples above, the data given to `plot` is stored in vectors. Functions can be plotted directly, with a given range and number of samples, as follows: ```@example 2dt g(x) = exp(-abs(x/5))*cos(x) tt = "set title 'g = x -> exp(-abs(x/5))*cos(x))'" plot(tt, (-10, 10, 200), g) # plot from x = -10 to 10, using 200 samples ``` Ranges can be specified in the following alternative ways: ```julia plot(g) # 100 samples, from -10 to 9.99 plot((a, b), g) # 100 samples, from a to b plot((a, b, c), g) # c samples, from a to b plot(x, g) # plot g.(x) ``` #### Multiplots To plot multiple sets of axes in a single figure, we use indexing into the figure as follows: ```@example 2dt f = plot(x, y) # f is of type Gaston.Figure plot(f[2], x, sinc.(10x)) ``` It is possible to have empty "slots": ```@example 2dt plot(f[4], x, sinc.(20x), "w lp pn 12") ``` Gaston tries to keep a square figure aspect ratio as more and more axes are included. Add another plot to a subplot using indexing: ```@example 2dt plot!(f[2], x, 0.3randn(length(x))) ``` To gain full control of gnuplot's multiplot options, instantiate a new `Gaston.Figure` with the string keyword argument `multiplot`; the string is passed to gnuplot's `set multiplot`: ```@example 2dt closeall() # hide f = Figure(multiplot = "title 'Arbitrary multiplot layout demo'") x = randn(100) y = randn(100) @plot({margins = (0.1, 0.65, 0.1, 0.65)}, x, y, "w p pt '+' lc 'dark-green'") @gpkw histogram(f[2], {margins = (0.7, 0.95, 0.1, 0.65), tics = false}, y, {lc = Q"dark-green"}, nbins = 10, horizontal = true) @gpkw histogram(f[3], {margins = (0.1, 0.65, 0.7, 0.9), boxwidth = "1 relative"}, x, {lc = Q"dark-green"}, nbins = 10) ``` Note that margins can be specified as a tuple. The macro `@gpkw` allows us to use keyword settings in the `histogram` plot command (described below). The figure's `multiplot` field can be modified *post-hoc*: ```julia f.multiplot = "title 'New title' layout 2,2" ``` !!! info Gaston takes care of the multiplot layout automatically **only** if the figure's `multiplot` setting is an empty string (this is the default value). If it's not empty, then the user is in charge of handling the layout. !!! info Gaston never clears a figure's `multiplot` setting. If re-using a figure for subsequent plots, this setting must be adjusted manually. ## 3-D Plots Plotting in 3-D is similar to 2-D, except that `splot` (and `@splot`, `splot!`, `@splot!`) are used instead of `plot`. This example shows how to plot the surface defined by function `s`: ```@example 2dt x = y = -15:0.2:15 s = (x,y) -> @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) @splot("set title 'Sombrero'\nset hidden3d", {palette = :cool}, x, y, s, "w pm3d") ``` The palette `cool` is defined in [ColorSchemes](https://github.com/JuliaGraphics/ColorSchemes.jl). ## Animations Animations require use of the `gif` or `webp` terminals (make sure your notebook supports the `image/webp` MIME type before using it). Creating an animation is similar to multiplotting: multiple axes are drawn on the same figure. When using the `animate` option of the `gif` or `webp` terminals, however, the plot is rendered as an animation. Note that gnuplot will output a message to `STDERR` indicating how many frames were recorded; this message is purely informative and not actually an error. A difficulty arises when mixing plot formats in a notbook (say, `png` and `gif`): the terminal is specified in the configuration variable `Gaston.config.term`. However, some notebook programs (such as Pluto) execute cells in arbitrary order. This means that changing the terminal in one cell may affect other cells. To solve this problem, Gaston provides a way to ignore the global terminal configuration when rendering a plot. A figure `f` can be rendered with a given terminal by calling `animate(f, term)`. The default value of `term` is stored in `Gaston.config.altterm` and defaults to `gif animate loop 0`. The following examples illustrate how to create and display animations, in this case with a background image: ```@example 2dt f = Figure() # new, empty figure frames = 75 x_bckgnd = range(-1, 1, 200) # x values for the background image bckgnd = Plot(x_bckgnd, sin.(2π*2*x_bckgnd), "lc 'black'") # background image x = range(-1, 1, frames) for i in 1:frames plot(f[i], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") # first plot the function... push!(f[i], bckgnd) # ... then add the background end for i in frames:-1:1 # in reverse plot(f[2frames-i+1], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") push!(f[2frames-i+1], bckgnd) end save(f, output = "2DAnim.webp", term = "webp animate loop 0 size 640,480") ``` ![An animation](2DAnim.webp) ## Themes Gaston includes several themes for common plot styles. The easiest way to use them is through the specialized plot commands described below. For more details, see the [Manual](@ref). Themes are divided into _settings themes_, which specify gnuplot `set` commands, and _plotline themes_, which specify how a particular curve is displayed (color, thickness, etc.) Settings themes are stored in the dictionary `Gaston.sthemes`, and plotline themes are stored in `Gaston.pthemes`. The themed commands described below use combinations of these themes to create a specific type of plot. In gnuplot, plotlines (as in `plot with lines`) are especially difficult to theme, because repeated options are errors, and options given in the wrong order may also cause errors. As an example, consider using `scatter` to plot some points; we want to use `pointtype` number 4: ```julia scatter(rand(10), rand(10), "pointtype = 4") ``` This command causes an error because the plotline theme `:scatter` already specifies the pointtype! To plot a scatter plot using the desired point type, use plain `plot` with the appropriate settings, create your own theme, or modify the built-in theme. Here is an example where the theme is modified. First find out how the theme is set up: ```@example 2dt Gaston.pthemes[:scatter] ``` Then, modify the entry for the pointtype: ```@example 2dt Gaston.pthemes[:scatter][2] = "pointtype" => 4 scatter("set title 'Scatter plot with modified theme", rand(10), rand(10), "lc 'dark-green'") ``` Note how the linecolor was specified without causing an error, since it is not included in the theme. #### Scatter plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`scatter` | none | `:scatter` | |`scatter3` | `:scatter3` | `:scatter` | ```@example 2dt # reset theme # hide @gpkw Gaston.pthemes[:scatter] = {with = "points", pointtype = :fcircle, pointsize = 1.5} # hide xg = randn(20) yg = randn(20) scatter("set title 'Scatter plot' set key outside", xg, yg, "title 'gaussian'") xu = rand(20) yu = rand(20) scatter!(xu, yu, "title 'uniform'") ``` A 3-D scatter plot (the default settings theme (`:scatter3`) draws all the borders): ```@example 2dt scatter3("set title 'A 3-D scatter plot", randn(10), randn(10), randn(10)) ``` #### Stem plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`stem` | none | `:stem`, `:impulses` | Stem plots are often used in digital signal processing applications to represent a discrete-time (sampled) signal. ```@example 2dt stem("set title 'Stem plot'", g) ``` To generate a stem plot, gnuplot actually plots twice: once with style `impulses` and once with `points` (set to empty circles). Normally, each of these plots would have a different color. To use the same color for both, use the `color` keyword argument: ```@example 2dt stem("set title 'Stem plot'", g, color = "'goldenrod'") ``` The circular marks can be omitted with the `onlyimpulses` keyword argument: ```@example 2dt stem("set title 'Stem plot with onlyimpulses'", g, onlyimpulses = true) ``` #### Bar plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`bar` | `:boxplot` | `:box` | |`barerror` | `:boxerror` | `:box` | ```@example 2dt bar("set title 'Bar plot'", rand(10), "lc 'turquoise'") ``` This example shows how to plot two sets of bars, using `bar!`: ```@example 2dt bar("set title 'Two bar plots'", rand(10), "lc 'dark-violet'") bar!(1.5:10.5, 0.5*rand(10), "lc 'plum' fill pattern 4") ``` Error bars are handled by `barerror`; there is also `barerror!`. ```@example 2dt barerror("set title 'Error bars plot'", 1:10, rand(10), 0.1*rand(10).+0.1, "lc 'sandybrown'") ``` #### Histograms | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`histogram` | `:histplot` | `:box`, `:horhist` (1-D); `:image` (2-D) | The `histogram` function takes these optional keyword arguments: * `nbins`: specifies the number of bins. Defaults to 10. * `mode::Symbol`: Controls histogram normalization mode; passed to [`StatsBase.normalize`](https://juliastats.org/StatsBase.jl/stable/empirical/#LinearAlgebra.normalize). Defaults to `:none`. * `edges`: a vector or a range specifying the bin edges; if specified, takes precedence over `nbins`. Defaults to `nothing`. * `horizontal::Bool`: if `true`, the histogram is drawn horizontally. Defaults to `false`. `histogram` uses the settings theme `:histplot`, and plotline themes `:box` or `:horhist`. 2-D histograms are supported, by passing two datasets. Using `nbins`: ```@example 2dt histogram("set title 'Histogram (nbins)'", randn(10_000), nbins = 20, mode = :pdf) ``` Using `edges`: ```@example 2dt histogram("set title 'Histogram (edges)'", 0.75*randn(10_000), edges = -2:0.75:3, "lc 'dark-khaki'") ``` A horizontal histogram: ```@example 2dt histogram("set title 'horizontal histogram'", rand(1000), nbins = 15, horizontal = true, "lc 'orchid'") ``` In the case of 2-D histograms, `nbins` or `egdes` may be a tuple; otherwise, both axes use the same settings. The plotline theme is `:image`. ```@example 2dt x = 2.5*randn(100_000) y = 2.5*randn(100_000) th = @gpkw {palette = :matter, colorbox = false, title = Q"2-D histogram", xrange = (-10, 10), yrange = (-10, 10)} histogram(th, x, y, nbins = 50, mode = :pdf) ``` #### Images | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`imagesc` | `:imagesc` | `:image`, `:rgbimage` | Arrays may be plotted as images using `imagesc`. Note that, in contrast to other plotting packages, the first row is plotted at the top. ```@example 2dt X = [0 1 2 3; 0 3 2 1; 0 2 2 0; 3 0 0 0] imagesc("unset xtics\nunset ytics", X) ``` To display the image as grayscale, use the `gray` palette. ```@example 2dt using Images, TestImages img = testimage("lake_gray"); ii = channelview(img)[1,:,:].*255; @gpkw imagesc({palette = :gray}, ii) ``` An RGB image is a plot of a 3-D array, where `[1,;,:]` is the red channel, `[2,:,:]` is the green channel, and `[3,:,:]` is the blue channels. ```@example 2dt img = testimage("lake_color") @gpkw imagesc({size = "square", autoscale = "fix"}, channelview(img).*255) ``` #### Surfaces | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`wireframe` | `:hidden3d` | none | |`surf` | `:hidden3d` | `:pm3d` | A surface can be plotted as a "wireframe" (or a "mesh") with the `wireframe` command. By default, `hidden3d` is active, so that elements behind the surface are not plotted. ```@example 2dt f1(x,y) = sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) th = @gpkw {title = Q"Sombrero Wireframe", palette = :matter} @gpkw wireframe(th, (-15, 15, 30), f1) ``` Solid surfaces can be plot with `surf`: ```@example 2dt th = @gpkw {title = Q"Sombrero Surface", palette = :matter} @gpkw surf(th, (-15, 15, 200), f1) ``` When plotting a function and a single range (such as `(-15, 15, 200)` above) is given, it is used for both `x` and `y` coordinates. Two ranges may be given as well to control the `x` and `y` ranges separately: ```@example 2dt @gpkw surf(th, (-15, 15, 200), (-25, 5, 200), f1) ``` #### Contour plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`contour` | `:contour` | `:labels` | | `surfcountour` | `:contourproj` | `:labels` | By default, contour plots include numerical labels: ```@example 2dt f2(x,y) = cos(x/2)*sin(y/2) contour("set title 'Contour Plot'", (-10, 10, 50), f2) ``` To plot contours without labels, use the keyword argument `labels = false`: ```@example 2dt contour("set title 'Contour Plot Without Labels'", (-10, 10, 50), f2, labels = false) ``` It's possible to plot a wireframe surface and a contour projected on the base of the plot using `surfcountour`: ```@example 2dt surfcontour("set title 'Surface With Projected Contours'", (-5, 5, 40), f2, "lc 'orange'") ``` The same plot without contour labels: ```@example 2dt surfcontour("set title 'Surface With Contours, No Labels'", (-5, 5, 40), f2, "lc 'orange'", labels = false) ``` #### Heatmap plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`heatmap` | `:heatmap` | `:pm3d` | ```@example 2dt theme = @gpkw {palette = :matter, title = Q"Heatmap"} heatmap(theme, :notics, :nocb, :labels, (-10, 10, 70), f2) ``` ##### Contour lines on heatmap It is possible to include contour lines in a heatmap plot. The following example is taken from [this gnuplot blog post] (https://gnuplot-tricks.blogspot.com/2009/07/maps-contour-plots-with-labels.html). The function `Gaston.plotwithtable` returns a `Gaston.DataTable`, which wraps `IOBuffer`. It can be used as an argument to `plot`. ```@example 2dt # define function to plot x = y = range(-5, 5, 100) f4(x,y) = sin(1.3*x)*cos(0.9*y)+cos(.8*x)*sin(1.9*y)+cos(y*.2*x) # obtain function contours using 'plot with table' settings = """set contour base set cntrparam level incremental -3, 0.5, 3 unset surface""" contours = Gaston.plotwithtable(settings, x, y, f4) # calculate meshgrid for heatmap plot z = Gaston.meshgrid(x, y, f4) # plot heatmap and contours plot("""unset key unset colorbox set palette rgbformulae 33,13,10""", x, y, z, "with image") plot!(contours, "w l lw 1.5 lc 'slategray'") ``` ## Other examples #### [3-D Euler spiral (Clothoid)](https://en.wikipedia.org/wiki/Euler_spiral) ```@example 2dt using QuadGK z = range(-5, 5, 200) fx(z) = sin(z^2) fy(z) = cos(z^2) x = [quadgk(fx, 0, t)[1] for t in z] y = [quadgk(fy, 0, t)[1] for t in z] splot("""unset zeroaxis set tics border set xyplane at -5 set view 65,35 set border 4095""", x, y, z, "w l lc 'black' lw 1.5") ``` #### Waterfall Inspired by this [Julia Discourse discussion](https://discourse.julialang.org/t/how-to-produce-a-waterfall-plot-in-julia/93441). ```@example 2dt x = -15:0.1:15 y = 0:30 u1data = [exp(-(x-0.5*(y-15))^2) for x in x, y in y] Zf = fill(0.0, length(x)) f = Figure() Gaston.set!(f(1), """set zrange [0:2] set tics out set ytics border set xyplane at 0 set view 45,5 set zrange [0:3] set xlabel 'ξ' offset -0,-2 set ylabel 't' set zlabel '|u|' set border 21""") for i in reverse(eachindex(y)) Y = fill(y[i], length(x)) Z = u1data[:,i] splot!(x, Y, Z, Zf, Z, "w zerrorfill lc 'black' fillstyle solid 1.0 fc 'white'") end f ``` ## Plot recipes There are two ways to extend Gaston to plot arbitrary types. The first is to define a new function that takes the required type, and returns a `Gaston.Figure`. For example, we may wish to plot complex data as two subplots, with the magnitude and phase of the data. This can be done as follows: ```@example 2dt function myplot(data::Vector{<:Complex}; kwargs...) x = 1:length(data) y1 = abs2.(data) y2 = angle.(data) Gaston.sthemes[:myplot1] = @gpkw {grid, ylabel = Q"Magnitude"} Gaston.sthemes[:myplot2] = @gpkw {grid, ylabel = Q"Angle"} Gaston.pthemes[:myplot1] = @gpkw {w = "lp"} Gaston.pthemes[:myplot2] = @gpkw {w = "p", lc = "'black'"} f = Figure(multiplot = "layout 2,1") plot(f[1], x, y1, stheme = :myplot1, ptheme = :myplot1) plot(f[2], x, y2, stheme = :myplot2, ptheme = :myplot2) return f end t = range(0, 1, 20) myplot(exp.(t) .* cis.(2*pi*7.3*t)) ``` The use of themes allows the user to modify the default properties of the plot, by modifying the themes (such as `Gaston.sthemes[:myplot1]`) instead of having to re-define `myplot`. The second way to plot an arbitrary type is to define a new method of `Gaston.convert_args` for that type (or `Gaston.convert_args3` for 3-D plots). Here's an example: ```@example 2dt using Gaston: PlotObject, TimeSeries, TSBundle import Gaston: convert_args struct MyType end function convert_args(x::MyType) t1 = range(0, 1, 40) t2 = range(-5, 5, 50) z = Gaston.meshgrid(t2, t2, (x,y) -> cos(x)*cos(y)) @gpkw PlotObject( TSBundle( TimeSeries(1:10, rand(10)), settings = {title = Q"First Axis"} ), TSBundle( TimeSeries(t1, sin.(5t1), pl = {lc = Q"black"}), TimeSeries(t1, cos.(5t1), pl = {w = "p", pt = 16}), settings = {title = Q"Trig"} ), TSBundle( TimeSeries(t2, t2, z, pl = {w = "pm3d"}, is3d = true), settings = {title = Q"3D", tics = false, palette = (:matter, :reverse)} ), TSBundle( TimeSeries(1:10, 1:10, rand(10,10), pl = "w image"), settings = {tics, title = false} ), mp_settings = "title 'A Four-Axes Recipe' layout 2,2" ) end figure().multiplot = "" # hide plot(MyType()) ``` ================================================ FILE: docs/v1/extending.md ================================================ # Extending Gaston Gaston offers multiple plotting commands that cover most cases of general data plotting. However, it is sometimes convenient to extend its capabilities to cover more specific use cases. One simple case is the generation of consistent plots of a certain type. A more complex example is plotting of new types, not just numerical data. We illustrate these two cases with examples. ## Consistent plots in a specific application Consider this situation: we are designing and simulating a new communications algorithms and we need to plot the resulting bit error rate (BER). BER plots have certain characteristics: * The x label defaults to `E_b/N_0 (dB)`. * The y label defaults to `Bit Error Rate`, but could also be `Symbol Error Rate`. * The y axis is logarithmic, and we want the tics to be negative powers of ten. * The grid should be visible. * The plot title defaults to `BER as a function of SNR`. * We wish to have markers at each SNR value, defaulting to full diamonds. * We like to use a thick line (width 2) that defaults to color blue. Let us define a new plot command, called `berplot`, that allows us to do this. ```@example ext using Gaston # hide set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide function berplot(snr, ber, axes::Axes = Axes() ; ser = false, args...) # support an optional Boolean argument to control the y label ylab = "'Bit Error Rate'" if ser ylab = "'Symbol Error Rate'" end # Build the default axes configuration a = Axes(title = "'BER as a function of SNR'", xlabel = "'E_b/N_0 (dB)'", ylabel = ylab, # use the label specified by ser axis = "semilogy", grid = "on", ytics = "out format '10^{%T}'" ) # Execute the plot command with the default curve configuration. Note that # the default axes configuration is merged with the one provided by the # user, giving preference to the latter. plot(snr, ber, Gaston.merge(a, axes) ; w = :lp, lc = :blue, lw = 2, marker = "fdmd", args... ) end ``` Let us try it out: ```@example ext using SpecialFunctions Q(x) = 0.5erfc(x/sqrt(2)) snr = 3:15 snr_dB = 10log10.(snr) ber = 4Q.(sqrt.(snr)) berplot(snr_dB, ber) ``` Let us verify that we can control the y label: ```@example ext berplot(snr_dB, ber, ser = true) ``` And verify that we can override the defaults: ```@example ext berplot(snr_dB, ber, Axes(grid = "xtics mytics"), lc = :orange) ``` ## Plotting a new type As an example, let us extend `plot` to display the frequency and phase response of a filter designed with [DSP.jl](https://docs.juliadsp.org/stable/filters/). The idea is to plot magnitude and phase responses in two subplots, Matlab-style. ```@example ext using DSP, FFTW fs = 200. df = digitalfilter(Lowpass(50, fs=fs), Chebyshev1(21, 0.5)) typeof(df) ``` We cannot run `plot(df)` directly, since neither Gaston nor gnuplot know what to do with data of this type.. We need to extend `plot` to type `ZeroPoleGain`, wee can also define a default plot configuration. ```@example ext # We need to explicitly import plot, since we're extending it. import Gaston.plot function plot(x::ZeroPoleGain, axes::Axes = Axes() ; fs = π, n = 250, args...) # The filter's frequency response is obtained with freqz. f = range(0, fs/2, length = n) fz = freqz(x, f, fs) mg = abs.(fz) ph = angle.(fz) # magnitude plot a = Axes(title = "'Magnitude response'", grid = :on, xlabel = "'Frequency'", ylabel = "'Magnitude'") p1 = plot(f, mg, merge(a, axes) ; handle = Gaston.nexthandle(), args...) # phase plot a = Axes(title = "'Phase response'", grid = :on, xlabel = "'Frequency'", ylabel = "'Phase'") p2 = plot(f, ph, merge(a, axes) ; handle = Gaston.nexthandle(), args...) plot([p1 ; p2]) end ``` Note that, when creating plots for the magnitude and phase response, the function `Gaston.nexthandle()` is used to select a new, unused handle. The reason is that `plot` overwrites the last created plot. So, when defining `p1` we run the risk of overwriting the last plot the user created, and when defining `p2` we run the risk of overwriting `p1`. These are avoided by choosing handles that are not shared with any other plots. Let us test it: ```@example ext set(termopts = "size 550, 600 font 'Consolas,11'") # hide plot(df, fs=fs) ``` ================================================ FILE: docs/v1/extguide.md ================================================ # Extension Guide ================================================ FILE: docs/v1/faq.md ================================================ # Usage notes and FAQ ## How to set the terminal Gnuplot supports a huge amount of terminals. Most modern gnuplot installations should support the `qt` terminal. A different terminal can be selected with set(term = t::String) where `newterm` is the desired terminal. Most terminals accept a configuration string, which can be set with set(termopts = opt::String) For example, to set the font on the qt terminal to Consolas size 11, one could do set(term = "qt") # not needed in most installations set(termopts = "font 'Consolas,11') !!! info "Choosing a terminal on Windows" On Windows, Gaston selects the `windows` terminal by default. Changing the terminal is not recommended, since they tend to be very slow and have other issues (for example, see [here](https://github.com/mbaz/Gaston.jl/issues/136) and [here](https://sourceforge.net/p/gnuplot/bugs/2279/)). ## What settings are available in Gaston? The `set` command can be used to configure Gaston's behavior. The following settings are available: | Setting | Purpose | |:--------|:--------| | term | Sets gnuplot's terminal. | | termopts | Sets the terminal's options. | | mode | If set to "null", plots are not shown. | | preamble | A string that is sent to gnuplot for every plot. | | debug | If set to `true`, all data sent to gnuplot is printed on the screen. | | saveopts | A string that specifies options when saving a plot. | | showable | For IJulia, Documenter.jl, Juno and similar uses. Defaults to `"png"`; all plots are generated in PNG format only. Set to `"svg"` to enable SVG plots, or `"png+svg"` to enable both. | timeout | How long Gaston waits for gnuplot to complete a plot. Defaults to 10 seconds (20 on Windows and Mac); you may need to set it higher if your computer is slow or you're plotting lots of data. ## How to plot text or sixels? Plots can be rendered using text on the console by setting the terminal to `dumb`: ```julia using Gaston # hide set(reset=true) # hide set(term = "dumb", termopts = "size 80,25 ansirgb") t = -5:0.1:5 plot(t, sin); plot!(t, cos) ``` ![](assets/dumb.png) To plot with sixels, use set(term = "sixelgd") A terminal that supports sixels is required (for example, xterm in mode vt340 (invoked as `xterm -ti vt340`). ![](assets/sixels.png) # How to configure plot size in Documenter.jl, IJulia, etc? In these environments, the front-end chooses among all supported MIME types. Gaston supports PNG and SVG images. Some of these front-ends, though, ask Gaston to produce plots in both formats, and then choose SVG. This is a waste of resources, and combined with the fact that plots in SVG format can grow very large, it is recommeded to configure Gaston to produce only PNG files. This is achieved with set(ijulia = "png") The `png` terminal can be configured with set(termopts=...) For example, the plots in this document are created with these settings: set(termopts="size 550,325 font 'Consolas,11'") ## I run `plot` inside a `for` loop and no plots are produced! (or: Julia's display system) Julia separates the calculation of a result from the display of the result (see [custom pretty-printing](https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing-1) and [multimedia I/O](https://docs.julialang.org/en/v1/base/io-network/#Multimedia-I/O-1) in Julia's documentation). This mechanism is very powerful; in Gaston, it enables plotting to the REPL, Jupyter, Juno, or in Documenter.jl with just a few lines of code. In other words, plotting is not a side effect of running `plot`, the way it is in, say, Matlab; rather, a plot is produced when a result of type `Gaston.Figure` is returned by some code. While elegant and powerful, this mechanism can also be surprising if you're used to side-effect plotting. None of the following code samples display any plots: ```julia y = rand(20) plot(y); # note the final ; suppreses displaying the result ``` ```julia # nothing is returned by the for loop for k = 1:5 plot(k*y) end ``` ```julia # function that does not return a figure function f(y) plot(sin.(y)) println("Sum = $(sum(y))") end ``` The common problem in the code samples above is that a figure is never returned; in consequence, no figure is displayed. This can be fixed by making sure your code returns a figure; or alternatively, save the figure in a variable and display it when it is convenient. For example: ```julia p = Gaston.Figure[] for k = 1:5 push!(p, plot(k*y)) end ``` Now, `p[3]` returns the third plot (for example). Another way to force the figure to be rendered is to call `display()`: ```julia # all five figures are displayed closeall() for k = 1:5 figure() display(plot(k*y)) end ``` ## How does gnuplot report errors? Gnuplot's main drawback, from a usability standpoint, is that it is not a library; it is designed to be used interactively. Gaston simulates a user typing interactive commands in a gnuplot session. Gaston tries to catch any errors reported back by gnuplot. An example of an error returned by gnuplot and caught by Gaston: ```julia using Gaston # hide y = rand(20) plot(y, plotstyle="linepoints") # missing an 's' ``` results in an error message like: ```julia ┌ Warning: Gnuplot returned an error message: │ │ gnuplot> plot '/tmp/jl_d8yIs9' i 0 with linepoints │ ^ │ line 0: unrecognized plot type │ └ @ Gaston ~/.julia/dev/Gaston/src/gaston_llplot.jl:172 ``` Gaston does its best effort to read and display any warnings or errors produced by gnuplot, and to recover gracefully. In some corner cases, it might happen that the communication link enters an unforeseen state and a restart is required. Please file a Gaston issue if you experience this. ## Support Please post support questions to [Julia's discuss forum](https://discourse.julialang.org/tag/plotting). ## Contributing Bug reports, suggestions and pull requests are welcome at [Gaston's github page](https://github.com/mbaz/Gaston.jl) ================================================ FILE: docs/v1/figures.md ================================================ # Managing multiple figures When using a graphical terminal such as `qt` or `wxt`, it is possible to have multiple figures on the screen at one time. Gaston provides a few commands to help manage them. Each figure is identified by its handle, which must be an integer larger than zero. Handles don't have to be consecutive. Plotting commands accept an optional handle argument, which directs the plot to the specified figure. For example, in this code: ```julia t = -5:0.01:5 plot(t, sin) figure() # creates a new figure plot(t, cos) plot!(t, sin.(t).^2, handle = 1) ``` the cosine will be plotted in a second figure, while the squared sine will be appended to the first figure. ```@docs figure closefigure closeall ``` ================================================ FILE: docs/v1/index.md ================================================ ```@meta Author = "Miguel Bazdresch" ``` # Gaston.jl Gaston (source code [here](https://github.com/mbaz/Gaston.jl)) is a Julia package for plotting. It provides an interface to [gnuplot](http://www.gnuplot.info), a mature, powerful, and actively developed plotting package available on all major platforms. Gaston emphasizes easy and fast plotting on the screen, notebook or IDE. Knowledge of gnuplot is not required, but some familiarity is beneficial. Gaston also exposes the full power of gnuplot, for more expert users. ```@example t2 using Gaston, SpecialFunctions set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide x = y = 0:0.075:10 surf(x, y, (x,y) -> besselj0(y)*x^2, with = "pm3d", Axes(view = (45, 45), pm3d = "lighting primary 0.5 specular 0.4", key = :off) ) ``` (Image inspired by [What's new in gnuplot 5.2?](https://lwn.net/Articles/723818/)) ## Gaston features * Plot using graphical windows, and keeping multiple plots active at a time, with mouse interaction. A browser is not required to show plots. * Plot also directly to the REPL, using text (ASCII) or [sixels](https://en.wikipedia.org/wiki/Sixel). * Plot in Jupyter, Juno or VS Code. * "Recipes" to generate common 2-D and 3-D plots, such as stem plots, histograms, images, surfaces, contour and heatmaps. * Easy definition of custom plotting commands for specific types, or with specific defaults. * Save plots to multiple formats, including pdf, png and svg. * Color palettes from [ColorSchemes.jl](https://github.com/JuliaGraphics/ColorSchemes.jl). * Export plots for integration into Latex documents. * A simple interface to almost the full power of gnuplot, for users who have more experience with it. * Fast first plot: load package, plot, and save to pdf in less than six seconds. Subsequent plots take a few hundreds of milliseconds. * A simple interface to manage multiple plots, using commands such as `figure()`, `closeall()`, etc. ### Gaston and Gnuplot.jl: two philosophies [Gnuplot.jl](https://github.com/gcalderone/Gnuplot.jl) is another front-end for gnuplot, with comparable capabilities to Gaston. An example serves to illustrate the differences in how the two packages approach the interface problem. Consider [this example plot](https://gcalderone.github.io/Gnuplot.jl/v1.3.0/basic/#Multiple-datasets,-logarithmic-axis,-labels-and-colors,-etc.-1): ```julia x = 1:0.1:10 @gp "set grid" "set key left" "set logscale y" @gp :- "set title 'Plot title'" "set label 'X label'" "set xrange [0:*]" @gp :- x x.^0.5 "w l tit 'Pow 0.5' dt 2 lw 2 lc rgb 'red'" @gp :- x x "w l tit 'Pow 1' dt 1 lw 3 lc rgb 'blue'" @gp :- x x.^2 "w l tit 'Pow 2' dt 3 lw 2 lc rgb 'purple'" ``` This shows that Gnuplot.jl essentially allows one to write gnuplot commands directly in Julia. The same plot in Gaston would be: ```@example t2 x = 1:0.1:10 plot(x, x.^0.5, w = "l", legend = "'Pow 0.5'", dt = 2, lw = 2, lc = :red, Axes(grid = :on, key = "left", axis = "semilogy")) plot!(x, x, w = :l, leg = :Pow_1, dt = 1, lw = 3, lc = :blue) plot!(x, x.^2, curveconf = "w l tit 'Pow 2' dt 3 lw 2 lc 'purple'") ``` In summary, Gaston offers a function-based interface, and gnuplot commands can be specified in a few different ways, with convenient notation, such as the optional use of "legend" instead of gnuplot's "title", symbols to avoid typing quote marks (") all the time, and others that are described later in this document. ## Installation Gaston requires Julia version 1.3.0 or above, and requires Gnuplot version 5.0 or above (version 5.2.8 is recommended). You should install gnuplot on your system prior to using Gaston. On Linux, it is highly recommended that you select a version with support for Qt: on Debian and Ubuntu, you will need `gnuplot-qt`. To install Gaston from the Julia REPL, run ```julia julia> ]add Gaston ``` Typing `]` switches the Julia REPL to the package manager, and the `add` command installs the package. To exit the package manager, hit the backspace key. ## Gnuplot configuration Gaston respects user configuration settings in gnuplot's startup file. Left un-configured, gnuplot's plots are less than attractive. The following minimum configuration is suggested (and was used to generate the plots in this document): set linetype 1 lc rgb "blue" pt 3 set linetype 2 lc rgb "red" pt 4 set linetype 3 lc rgb "green" pt 6 set linetype 4 lc rgb "black" pt 12 set linetype 5 lc rgb "blue" pt 5 set linetype 6 lc rgb "red" pt 1 set linetype 7 lc rgb "green" pt 2 set linetype 8 lc rgb "black" pt 7 set linetype cycle 8 set style data lines set key noautotitle set auto fix set offsets graph .05, graph .05, graph .05, graph .05 The configuration file is `~/.gnuplot` in Unix-like systems, and `%APPDATA%\GNUPLOT.INI` in Windows. ## Next steps Load Gaston into your Julia session with ```julia using Gaston ``` The [Introduction to plotting](@ref) has more information about basic use and configuration. There is a [2-D plotting tutorial](@ref twodeetut) and a [3-D plotting tutorial](@ref threedeetut). The [Extending Gaston](@ref) section explains how to extend Gaston by creating your own "recipes", both for specific kinds of plots, and for plotting data of specific types. There is a section on [Managing multiple figures](@ref) and all related commands. The [2-D Gallery](@ref twodeegal) and [3-D Gallery](@ref threedeegal) show many plotting examples. The [Usage notes and FAQ](@ref) section includes additional usage examples and answers frequent questions. ## Gnuplot resources These websites have more information on gnuplot and how to use it: * [Official website](http://www.gnuplot.info/) * [Official demo gallery](http://gnuplot.sourceforge.net/demo_5.2/) * [PDF documentation for 5.2](http://www.gnuplot.info/docs_5.2/Gnuplot_5.2.pdf) * [A good blog on gnuplot](http://www.gnuplotting.org/) ## Running tests Gaston includes an extensive test suite, which can executed with: ```julia julia> ]test Gaston ``` All tests should pass (but a few may be skipped). ## Support Please post support questions to [Julia's discuss forum](https://discourse.julialang.org/tag/plotting). ## Contributing Bug reports, suggestions and pull requests are welcome at [Gaston's github page](https://github.com/mbaz/Gaston.jl) ================================================ FILE: docs/v1/introduction.md ================================================ ```@meta Author = "Miguel Bazdresch" ``` # Introduction to plotting Gaston supports essentially all 2-D plots styles that gnuplot is capable of, including regular function plots, plots with logarithmic axes, scatter, stem and step plots, bar plots and histograms, images, etcetera. It can also create 3-D plots, including wireframe, surface, scatter and contour plots. This section presents the basic usage of `plot` and `plot!`. Examples of specific plot types, such as cloud points, stem plots, and images, are presented in [2-D plotting tutorial](@ref twodeetut). For 3-D plots, see [3-D plotting tutorial](@ref threedeetut). ## `plot` and `plot!` commands The main 2-D plotting commands are `plot` and `plot!`. To plot a vector `y` against a vector `x`, use `plot(x,y)`: ```@example intro using Gaston # hide set(reset=true) # hide set(termopts="size 550,325 font 'Consolas,11'") # hide t = 0:0.01:1 plot(t, sin.(2π*5*t), linecolor = :coral, Axes(title = :First_Plot) ) ``` To add a second curve, use `plot!`: ```@example intro plot!(t, cos.(2π*5*t), plotstyle = "linespoints", pointtype = "ecircle", linecolor = "'blue'" ) ``` Curves are added to a figure one by one; the first curve is plotted with `plot`, and the rest with succesive `plot!` commands. The commands `plot` and `plot!` take three kinds of arguments: * Data, in the form of vectors `x`, `y`, etcetera. * Configuration related to the data's appearance: line color, line width, markers, line style, etcetera. These are passed to `plot` as regular arguments (for example, `linecolor = :coral` above). * Configuration related to the entire figure: title, tics, ranges, grid, etcetera. These must be wrapped in `Axes()`; for example, `Axes(title = :First_Plot)`. Only `plot` accepts these arguments. ## Figure and curve configuration An example can be worth a thousand words. The following commands are all exactly equivalent: ```julia plot(t, sin.(2π*5*t), with = "linespoints", linecolor = :coral, Axes(title = :First_Plot, xtics = "(0.25, 0.5, 0.75)") ) ``` ```julia plot(t, sin.(2π*5*t), curveconf = "with linespoints linecolor 'coral'", Axes(title = :First_Plot, xtics = "(0.25, 0.5, 0.75)") ) ``` ```julia A = Axes(title = :First_Plot, xtics = "(0.25, 0.5, 0.75)") plot(t, sin.(2π*5*t), A, plotstyle = :linespoints, lc = "'coral'" ) ``` ```julia plot(t, sin.(2π*5*t), curveconf = "w lp lc 'coral'", Axes(axesconf = """set title 'First Plot' set xtics (0.25, 0.5, 0.75)""") ) ``` ```julia plot(t, sin.(2π*5*t), curveconf = "w lp lc 'coral'", Axes(axesconf = "set title 'First Plot'", xtics = "(0.25, 0.5, 0.75)") ) ``` ## How arguments are handled Gaston has a few rules to handle arguments, and supports special syntax to make passing commands to gnuplot more convenient. All configuration commands are given as key-value arguments. * Values can be given in quotes (`"'red'"`) or as symbols (`:red`). * Values in quotes are passed directly to gnuplot. * Symbol values are passed to gnuplot wrapped in single quotes. For example, `linecolor = :blue` in the example above is translated as `linecolor 'blue'`. * In symbols, underscores are converted to spaces. For example, `title = :First_Plot` is translated as `set title 'First Plot'`. * When an argument is a vector, each element is handled as a separate argument. For example, `xtics = [1:2:5, "reverse"]` is translated to two separate gnuplot commands, `set xtics 1, 2, 5` and `set xtics reverse`. * To send a `set` command without options, like `set grid`, use (for example) `grid = :on` (or `"on"`, or `true`). * To send an `unset` command, use (for example) `tics = :off` (or `"off"`, or `false`). !!! info "Interaction with gnuplot" Keyword arguments wrapped in `Axes()` are converted to gnuplot `set` commands. For example, Axes(pm3d = "lighting primary 0.5") is sent to gnuplot as set pm3d lighting primary 0.5 Other keyword arguments are used as plot elements; for example, w = :lp, u = "1:3" is sent to gnuplot as plot 'filename' w lp u 1:3 ## Configuring a figure's appearance As explained above, a figure's configuration is given as key-value pairs wrapped in `Axes()`. Some arguments have special syntax for convenience: * The `axis` argument sets the axis type: * `axis = "semilogx"` → `set logscale x` * `axis = "semilogy"` → `set logscale y` * `axis = "semilogz"` → `set logscale z` * `axis = "loglog"` → `set logscale xyz` * Tics are set with `xtics`, `ytics`, `ztics` or `tics`: * `tics = a:b:c` → `set tics a, b, c` * `tics = (a:b:c, ["l1" "l2" ... "lN"])` → `set tics ("l1", a, ..., "lN", c)` * `tics = ([t1 t2 ... tN], ["l1" "l2" ... "lN"])` → `set tics ("l1", t1, ..., "lN", tN)` In the last two cases, the first element in the tuple represents the numerical tics, and the second element is the set of labels. Example: ```@example intro plot(t, sin.(2π*5*t), linecolor = :coral, Axes(title = "'Tics Example'", xtics = [(0.25:0.5:1, ["1/4" "3/4"]), "rotate"]) ) ``` * Ranges are specified with `xrange`, `yrange`, `zrange` or `cbrange`: * `{x,y,z,cb}range = (low, high)` → `set {x,y,z,cb}range [low|high]` * `{x,y,z,cb}range = (-Inf, high)` → `set {x,y,z,cb}range [*|high]` * `{x,y,z,cb}range = (low, Inf)` → `set {x,y,z,cb}range [low|*]` Example: ```@example intro plot(t, sin.(2π*5*t), linecolor = :coral, Axes(title = :Range_Example, yrange = (-Inf, 2)) ) ``` * A set of linetypes with the colors specified by a palette from [ColorSchemes.jl](https://github.com/JuliaGraphics/ColorSchemes.jl). The palette name must be specified as a symbol. For example, ```@example intro t = range(-2, 2, length = 100) f(t, σ) = exp.(-σ*abs.(t)) A = Axes(title = :Linetypes_Example, linetype = :sunset) plot(t, f(t,0.5), lw = 3, A) plot!(t, f(t, 1), lw = 3) plot!(t, f(t, 1.5), lw = 3) plot!(t, f(t, 2), lw = 3) plot!(t, f(t, 2.5), lw = 3) ``` * A string containing gnuplot commands may be passed as argument `axesconf`. This string is sent to gnuplot without modification. * In addition, a string of gnuplot commands may be specified using Gaston's configuration setting `preamble`. This string will be used in all subsequent plots, before the commands specified in `Axes()`. This may be useful to configure gnuplot in environments where it is not feasible to have a permanent gnuplot configuration file. For example, ```julia set(preamble = "set offsets graph .05, graph .05, graph .05, graph .05") ``` ## Configuring a curve's appearance All key-value arguments provided to `plot` and not wrapped in `Axes()` are interpreted as a curve configuration. Some of them have offer some convenient syntax: * The plot style can be specified with the `with` key. The keys `with`, `w` and `plotstyle` are synonyms. * The point type is specified with the key `pointtype`. This key is synonym with `pt` and `marker`. Gnuplot accepts markers specified as numbers. In addition, Gaston accepts the following descriptive strings: | Value | Meaning | |-------|---------| | `"dot"` | Single pixel | | `"+"` | A plus sign| | `"x"` | A cross | | `"*"` | An asterisk| | `"ecircle"` | Empty circle | | `"fcircle"` | Full circle | | `"esquare"` | Empty square | | `"fsquare"` | Full square | | `"etrianup"` | Empty up triangle | | `"ftrianup"` | Full up triangle | | `"etriandn"` | Empty down triangle | | `"ftriandn"` | Full down triangle | | `"edmd"` | Empty diamond | | `"fdmd"` | Full diamond | Other strings are passed to gnuplot wrapped in single quotes; for example, `pt = "λ"` is translated as `pointtype 'λ'`. * A legend can be specified with the keys `legend`, `leg`, `title` or `t`. * A full plot specification can be provided with the key `curveconf`. This overrides all other provided arguments. For example, this plot ```julia t = 0:0.05:10pi plot(t, cos, w=:lp, leg = :A_sine_wave, marker = "fdmd", pi = -20) ``` can equivalently be specified as: ```julia cc = "w lp t 'A sine wave' pt 13 pi -20" plot(t, cos, curveconf = cc) ``` ## Data arguments Most plotting commands accept data in a few different formats: * `plot(y, args...)` assumes that `x = 1:length(y)` * `plot(x, f::Function, args...)` applies function `f` to `x`. * `plot(c, args...)` where `c` is complex, plots `real(c)` vs `imag(c)`. In addition, gnuplot may use additional data to, for example, set a marker's size or color. These are called "supplementary data" by Gaston, and are provided to `plot` using the `supp` keyword argument. For example, ```@example intro c = rand(30) .+ im*rand(30) plot(c, supp = 3abs.(c), w = :p, marker = "ecircle", markersize = "variable") ``` ## 3-D plotting Gaston and gnuplot are fully capable of plotting surfaces and other kinds of 3-D plots such as contours and heatmaps. See the [3-D plotting tutorial](@ref threedeetut). ## Multiplot Multiple plots can be included in the same figure. This is accomplished by calling `plot` with a matrix made up of other figures. If a matrix elements is `nothing`, then the corresponding subplot is left empty. An example: ```@example intro t = 0.01:0.01:10pi p1 = plot(t, cos, Axes(title = :Plot_1), handle = 1) p2 = plot(t, t.^2, Axes(title = :Plot_2), handle = 2) p4 = plot(t, exp.(-t), Axes(title = :Plot_4), handle = 4) plot([p1 p2 ; nothing p4]) ``` The `handle` argument is necessary because a `plot` command, by default, overwrites the previous plot. See the section on [Managing multiple figures](@ref) for more details on how handles work. ## Saving plots To save a plot (or "print" it, in gnuplot's parlance), use the `save` command, which requires `term` and `output` arguments. Optionally, arguments specifying the `font`, `size`, `linewidth`, and `background` color may be given. These may be specified in a gnuplot command string with `saveopts`, which may also be specified in advance using `set(saveopts = "...")`. The following two examples are equivalent: ```julia save(term = "png", output= "myfigure.png", font = "Consolas,10", size = "1280,900", linewidth = 1, background = "blue") ``` ```julia save(term = "png", output = "myfigure.png", saveopts = "font 'Consolas,10' size 1280,900 lw 1 background 'blue'") ``` ================================================ FILE: docs/v1/plotguide.md ================================================ # Manual This manual covers all aspects of using Gaston. ## Gaston Settings ### The terminal By default, gnuplot chooses an appropriate terminal: `qt` or `wxt` on Linux, `windows` on Windows, and `aqua` on MacOS. The terminal can be set by changing the value of `Gaston.config.term`; for example: ```julia Gaston.config.term = "pngcairo font ',10' size 700,400" ``` The terminals supported by gnuplot can be listed by running: ```julia Gaston.terminals() ``` ### Other settings * `Gaston.config.output`: controls how plots are displayed. Possible values are: * `:external`: plots are displayed in GUI windows. This is the default value. * `:echo`: sends text-based plots (like `png` and `sixelgd`) back to the terminal. Useful for notebooks and IDEs, and for plotting on the terminal. * `:null`: execute all plot commands but do not actually produce a plot. If Gaston detects it is running in a notebook environment, it automatically sets the terminal to `pngcairo` and `config.output` to `:echo`. * `Gaston.config.embedhtml`: `Bool`, defaults to `false`. Enables embedding plots in HTML; useful to enable interactivity in Pluto and Jupyter notebooks. See examples in the included Pluto notebooks. ## Plotting A `plot` command takes three different kinds of arguments: settings, data, and plotline, in that order. ```julia plot([settings...], data..., [plotline...]) ``` Further curves may be added using `plot!`. (For 3-D plots, use `splot` instead.) More specifically, a `plot` command takes: * Zero or more **settings** arguments, which get converted to gnuplot `set` commands. * One or more **data** arguments, which are written to a file in the format gnuplot expects. * Zero or more **plotline** arguments, which are appended to gnuplot's `plot` or `splot` commands. Gaston provides several alternative ways to specify these. ### Settings and Plotlines All the following are equivalent. * One single string ```julia plot("set grid unset key set title 'A Sinusoid'", x, y, "with linespoints lc 'green'") ``` * One string per setting ```julia plot("set grid", "unset key", "set title 'A Sinusoid'", x, y, "with linespoints", "lc 'green'") ``` * Keywords with `@plot` ```julia @plot({grid = true, key = false, title = "'A Sinusoid'"}, x, y, {with = "linespoints", lc = "'green'"}) ``` Keyword options are enclosed in curly brackets `{}`. To set an option without arguments, such as `set grid`, use either a lone `grid`, or `grid = true`. To unset an option, such as in `unset grid`, use ` grid = false`. Options can be repeated; each one will be converted to a separate `set` line. `@plot` also accepts strings, and in fact strings and keywords may be combined: ```julia @plot({grid, key = false}, "set title 'A Sinusoid'", x, y, "with linespoints", {lc = "'green'"}) ``` It is possible to omit the parenthesis, but in this case the command must fit in a single line. ```julia @plot {grid, key = false, title = "'A Sinusoid'"} x y {with = "lp", lc = "'green'"} ``` For 3-D plots, use the macro `@splot`. #### Quoted strings All strings passed to gnuplot must be enclosed in single quotes, such as in `lc = "'green'"` in the example above. The `Q` string macro can help reduce the number of quotes needed: ```julia @plot {grid = true, key = false, title = sqs"A Sinusoid"} x y {with = "lp", lc = Q"green"} ``` This macro turns `"abc"` into `"'abc'"`. ### Data Data to be plotted can be provided as vectors and/or matrices. Gaston converts the data to a format compatible with gnuplot. Three cases are supported: * All data arguments are vectors. * The first two arguments are vectors of length `n` and `m`, and the third argument is a matrix of size `n x m`; further arguments are optional. * All provided arguments are matrices of size `n x m`. #### Functions Functions can be plotted directly, with a given range and number of samples, which can be specified in the following alternative ways: ```julia # g is a function plot(g) # plots `g` evaluated at 100 samples, from -10 to 9.99 plot((a, b), g) # plots `g` evaluated at 100 samples, from a to b plot((a, b, c), g) # plots `g` evaluated at c samples, from a to b plot(x, g) # plots g.(x) ``` #### Plot with table In some cases, it is useful to have gnuplot produce plot data in a "table" format, which can then be plotted. See an example in [Contour lines on heatmap](@ref). The function `Gaston.plotwithtable` returns a `Gaston.DataTable` storing the table. All plot commands accept this type. ### Simple themes Frequently-used settings or plotlines may be stored in a theme; the `@gpkw` macro processes keyword arguments wrapped in curly brackets. ```julia theme = @gpkw {grid, key = false} plot(theme, x, y) ``` Themes may be combined with other themes and/or with strings: ```julia theme2 = @gpkw {xlabel = Q"X"} plot(theme, "set title 'A Sinusoid'", theme2, x, y) ``` Themes can also be used for plotlines, and these may also be combined with other themes and/or strings. ```julia pltheme = @gpkw {w = "lp", pt = "'o'", ps = 3} plot(theme, "set title 'A Sinusoid'", theme2, x, y, pltheme) ``` Gaston includes a few generic themes: |Axis themes | Description | |-----------:|:------------| | :notics | Removes all tics | | :labels | Generic axis labels (`x`, `y`, `z`) | | :nocb | Removes colorbox | | :unitranges | Set all ranges to `[-1:1]` | For example, the following command plots a sine wave with no tics and generic `x` and `y` axis labels: ```julia plot(:notics, :labels, "set title 'Example'", (-1, 1), sin) ``` Themes are also used to provide common plot types (illustrated in the [Themes](@ref) section). These specialized plot commands and the themes they use are: | Commands | Settings theme | Plotline theme | |----------|----------------|----------------| | `scatter`, `scatter!` | `:scatter`, `:scatter3` | `:scatter` | | `stem`, `stem!` | None | `:stem`, `:impulses` | | `bar`, `bar!` | `:boxplot` | `:box` | | `barerror`, `barerror!` | `:boxerror` | `:box` | | `histogram` | `:histplot` | `:box`, `:horhist` (1-D); `:image` (2-D) | | `imagesc` | `:imagesc` | `:image`, `:rgbimage` | | `surf`, `surf!` | | `contour` | | `surfcontour` | | `wireframe`, `wireframe!` | | `wiresurf`, `wiresurf!` | | `heatmap` | !!! note "Plotline themes" Plotline themes must be handled with care: gnuplot requires plotline options to be specified in a certain order, may not be repeated, and some combinations are invalid. It is very easy to create erroneous plotlines. !!! note "Gaston lacks a parser" Gaston does not validate that the settings and plotline given to gnuplot are valid. When gnuplot returns an error or warning, it is echoed to the terminal. ## Multiplot ## Managing multiple figures Gaston has the ability to create and manage multiple GUI plot windows simultaneously. Each window is backed up by its own gnuplot process. The following commands can be used to create and control multiple windows. #### Creating and selecting figures ``` Figure() ``` Creates a new, empty figure. All figures are of type `Gaston.Figure`. Gaston keeps an internal pointer to the figure, so that its not removed by the garbage collector. When creating a figure intended for multiplot, a setting string can be included, with the `multiplot` keyword. ``` Figure(multiplot = "title 'A Multiplot'") ``` When a figure is created, it becomes active, meaning that subsequent plot commands will go to this figure. Of course, it is possible to keep figures in different variables: ``` fig1 = Figure() fig2 = Figure() ``` and then redirect plot commands to the desired figure: ``` plot(fig1, ...) # plot goes to fig1 plot!(fig2, ...) # new plot added to fig2 ``` It is also possible to select figures using _handles_: ``` Figure("density") # figure with handle "density" Figure(:volume) # figure with handle :volume Figure(33) # figure with handle 33 ``` Handles can be of any type. If not specified, handles are integers assigned in increasing order starting from 1. To plot in a specific figure, specify its handle using the keyword argument `handle`: ``` plot(..., handle = :volume) plot!(..., handle = 33) scatter(..., handle = "density") ``` To activate a figure given its handle, use: ``` figure(handle) ``` or, given its index number `i`, use: ``` figure(index = i) ``` With no arguments, `figure()` returns the current figure. To obtain the list of all current figures and their handles, and to identify the active figure, use the unexported function `Gaston.listfigures()`. #### Closing figures To close the active figure, run ``` closefigure() ``` The figure with handle `h` can be closed with `closefigure(h)`. Likewise, to close figure `f` use `closefigure(f)`. Closing a figure quits the underlying gnuplot process. To close all figures, use `closeall()`. ## Saving plots A plot can be saved to a file in any format supported by gnuplot, with the function ``` save(f ; output, term) ``` where the arguments are: * `f`, which can be either a `Figure`, or an arbitrary value that is taken to be the handle of the figure to save. Defaults to the active figure. * `output`, a string that specifies the filename. If empty, it defaults to `figure-` followed by the figure's handle; the filename extension is set to the first three characters of the gnuplot terminal (see next argument). * `term`, specifies the gnuplot terminal used to save the plot; defaults to `"pngcairo font ',7'"`. ## Interacting with gnuplot ================================================ FILE: docs/v2/.gitignore ================================================ /.quarto/ ================================================ FILE: docs/v2/Project.toml ================================================ [deps] ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" Gaston = "4b11ee91-296f-5714-9832-002c20994614" GastonRecipes = "39356fd2-1f9e-4efe-8abf-5745c7d9f608" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" QuartoNotebookRunner = "4c0109c6-14e9-4c88-93f0-2b974d3468f4" QuartoTools = "5fded309-f5a0-485a-9129-b3749510da85" TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" ================================================ FILE: docs/v2/_extensions/jjallaire/code-visibility/_extension.yml ================================================ title: Code Visibility author: fast.ai version: 1.0.0 contributes: filters: - code-visibility.lua ================================================ FILE: docs/v2/_extensions/jjallaire/code-visibility/code-visibility.lua ================================================ -- remove any lines with the hide_line directive. function CodeBlock(el) if el.classes:includes('cell-code') then el.text = filter_lines(el.text, function(line) return not line:match("#| ?hide_line%s*$") end) return el end end -- apply filter_stream directive to cells function Div(el) if el.classes:includes("cell") then local filters = el.attributes["filter_stream"] if filters then -- process cell-code return pandoc.walk_block(el, { CodeBlock = function(el) -- CodeBlock that isn't `cell-code` is output if not el.classes:includes("cell-code") then for filter in filters:gmatch("[^%s,]+") do el.text = filter_lines(el.text, function(line) return not line:find(filter, 1, true) end) end return el end end }) end end end function filter_lines(text, filter) local lines = pandoc.List() local code = text .. "\n" for line in code:gmatch("([^\r\n]*)[\r\n]") do if filter(line) then lines:insert(line) end end return table.concat(lines, "\n") end ================================================ FILE: docs/v2/_quarto.yml ================================================ project: type: website website: title: "Gaston.jl" navbar: left: - href: index.qmd text: Introduction - href: tutorial.qmd text: Tutorial - href: examples.qmd text: Examples - href: recipes.qmd text: Recipes - href: manual.qmd text: Manual - href: migrate.qmd text: Migration Guide - href: reference.qmd text: API Reference tools: - icon: github href: https://github.com/mbaz/Gaston.jl search: true pinned: true reader-mode: true back-to-top-navigation: true format: html: theme: - minty css: styles.css toc: true embed-resources: true engines: ['julia'] execute: daemon: 600 filters: - code-visibility ================================================ FILE: docs/v2/assets/cairolatex.tex ================================================ \documentclass{article} \usepackage{amsmath} \usepackage{graphicx} \usepackage{color} \begin{document} \begin{figure} \input{test.tex} \end{figure} \end{document} ================================================ FILE: docs/v2/assets/lorenz.jl ================================================ using Gaston using ColorSchemes Base.@kwdef mutable struct Lorenz dt::Float64 = 0.01 σ::Float64 = 10 ρ::Float64 = 28 β::Float64 = 8/3 x::Float64 = 1 y::Float64 = 1 z::Float64 = 1 end function step!(l::Lorenz) dx = l.σ * (l.y - l.x) dy = l.x * (l.ρ - l.z) - l.y dz = l.x * l.y - l.β * l.z l.x += l.dt * dx l.y += l.dt * dy l.z += l.dt * dz return (l.x, l.y, l.z) end Nframes = 120 Npoints = 50 attractor = Lorenz() x = Float64[]; y = Float64[]; z = Float64[]; s = @gpkw {xrange = (-30, 30), yrange = (-30, 30), zrange = (0, 60), xtics = "offset -1.2,0", xtics = "add ('' -30, '' 30)", ytics = "offset 1.2,0", ytics = "add ('' -30, '' 30)", origin = "-0.1, -0.1", size = "1.2, 1.2", object = "rectangle from screen 0,0 to screen 1,1 fillcolor 'black' behind", border = "back lc rgb '#eeeeee' lt 1 lw 1.5", view = "equal xyz", xyplane = "at 0"} f = splot(s, 1, 1, 1) for i = 1:Nframes for j = 1:Npoints step!(attractor) push!(x, attractor.x); push!(y, attractor.y); push!(z, attractor.z) end cs = resample(ColorSchemes.inferno, length(x)) splot(f[i], s, "set view 70, $(45 + 17 * sin(2pi * i / Nframes))", x, y, z, Gaston.cs2dec(cs), "w l notitle lc rgb variable") end save(f, filename = "lorenz.webp", term = "webp animate loop 0 size 640,480") ================================================ FILE: docs/v2/assets/test.tex ================================================ % GNUPLOT: LaTeX picture with Postscript \begingroup \makeatletter \providecommand\color[2][]{% \GenericError{(gnuplot) \space\space\space\@spaces}{% Package color not loaded in conjunction with terminal option `colourtext'% }{See the gnuplot documentation for explanation.% }{Either use 'blacktext' in gnuplot or load the package color.sty in LaTeX.}% \renewcommand\color[2][]{}% }% \providecommand\includegraphics[2][]{% \GenericError{(gnuplot) \space\space\space\@spaces}{% Package graphicx or graphics not loaded% }{See the gnuplot documentation for explanation.% }{The gnuplot epslatex terminal needs graphicx.sty or graphics.sty.}% \renewcommand\includegraphics[2][]{}% }% \providecommand\rotatebox[2]{#2}% \@ifundefined{ifGPcolor}{% \newif\ifGPcolor \GPcolortrue }{}% \@ifundefined{ifGPblacktext}{% \newif\ifGPblacktext \GPblacktexttrue }{}% % define a \g@addto@macro without @ in the name: \let\gplgaddtomacro\g@addto@macro % define empty templates for all commands taking text: \gdef\gplbacktext{}% \gdef\gplfronttext{}% \makeatother \ifGPblacktext % no textcolor at all \def\colorrgb#1{}% \def\colorgray#1{}% \else % gray or color? \ifGPcolor \def\colorrgb#1{\color[rgb]{#1}}% \def\colorgray#1{\color[gray]{#1}}% \expandafter\def\csname LTw\endcsname{\color{white}}% \expandafter\def\csname LTb\endcsname{\color{black}}% \expandafter\def\csname LTa\endcsname{\color{black}}% \expandafter\def\csname LT0\endcsname{\color[rgb]{1,0,0}}% \expandafter\def\csname LT1\endcsname{\color[rgb]{0,1,0}}% \expandafter\def\csname LT2\endcsname{\color[rgb]{0,0,1}}% \expandafter\def\csname LT3\endcsname{\color[rgb]{1,0,1}}% \expandafter\def\csname LT4\endcsname{\color[rgb]{0,1,1}}% \expandafter\def\csname LT5\endcsname{\color[rgb]{1,1,0}}% \expandafter\def\csname LT6\endcsname{\color[rgb]{0,0,0}}% \expandafter\def\csname LT7\endcsname{\color[rgb]{1,0.3,0}}% \expandafter\def\csname LT8\endcsname{\color[rgb]{0.5,0.5,0.5}}% \else % gray \def\colorrgb#1{\color{black}}% \def\colorgray#1{\color[gray]{#1}}% \expandafter\def\csname LTw\endcsname{\color{white}}% \expandafter\def\csname LTb\endcsname{\color{black}}% \expandafter\def\csname LTa\endcsname{\color{black}}% \expandafter\def\csname LT0\endcsname{\color{black}}% \expandafter\def\csname LT1\endcsname{\color{black}}% \expandafter\def\csname LT2\endcsname{\color{black}}% \expandafter\def\csname LT3\endcsname{\color{black}}% \expandafter\def\csname LT4\endcsname{\color{black}}% \expandafter\def\csname LT5\endcsname{\color{black}}% \expandafter\def\csname LT6\endcsname{\color{black}}% \expandafter\def\csname LT7\endcsname{\color{black}}% \expandafter\def\csname LT8\endcsname{\color{black}}% \fi \fi \setlength{\unitlength}{0.0500bp}% \ifx\gptboxheight\undefined% \newlength{\gptboxheight}% \newlength{\gptboxwidth}% \newsavebox{\gptboxtext}% \fi% \setlength{\fboxrule}{0.5pt}% \setlength{\fboxsep}{1pt}% \definecolor{tbcol}{rgb}{1,1,1}% \begin{picture}(7200.00,4740.00)% \gplgaddtomacro\gplbacktext{% }% \gplgaddtomacro\gplfronttext{% \csname LTb\endcsname%% \put(1512,3921){\makebox(0,0)[r]{\strut{}n=0}}% \csname LTb\endcsname%% \put(1512,3716){\makebox(0,0)[r]{\strut{}n=1}}% \csname LTb\endcsname%% \put(2937,3921){\makebox(0,0)[r]{\strut{}n=2}}% \csname LTb\endcsname%% \put(2937,3716){\makebox(0,0)[r]{\strut{}n=3}}% \csname LTb\endcsname%% \put(4363,3921){\makebox(0,0)[r]{\strut{}sin(x)}}% \csname LTb\endcsname%% \put(616,409){\makebox(0,0)[r]{\strut{}$-1.5$}}% \csname LTb\endcsname%% \put(616,1025){\makebox(0,0)[r]{\strut{}$-1$}}% \csname LTb\endcsname%% \put(616,1641){\makebox(0,0)[r]{\strut{}$-0.5$}}% \csname LTb\endcsname%% \put(616,2257){\makebox(0,0)[r]{\strut{}$0$}}% \csname LTb\endcsname%% \put(616,2873){\makebox(0,0)[r]{\strut{}$0.5$}}% \csname LTb\endcsname%% \put(616,3489){\makebox(0,0)[r]{\strut{}$1$}}% \csname LTb\endcsname%% \put(616,4105){\makebox(0,0)[r]{\strut{}$1.5$}}% \csname LTb\endcsname%% \put(1257,204){\makebox(0,0){\strut{}$-\pi$}}% \csname LTb\endcsname%% \put(2521,204){\makebox(0,0){\strut{}$-\pi/2$}}% \csname LTb\endcsname%% \put(3786,204){\makebox(0,0){\strut{}0}}% \csname LTb\endcsname%% \put(5050,204){\makebox(0,0){\strut{}$\pi/2$}}% \csname LTb\endcsname%% \put(6314,204){\makebox(0,0){\strut{}$\pi$}}% \csname LTb\endcsname%% \put(4519,1148){\makebox(0,0){\strut{}\begin{minipage}[c]{\textwidth}\begin{equation*}\sin(x) = \sum_0^{+\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n+1}\end{equation*} \end{minipage}}}% \csname LTb\endcsname%% \put(3785,4412){\makebox(0,0){\strut{}Polynomial approximation of sin(x)}}% }% \gplbacktext \put(0,0){\includegraphics[width={360.00bp},height={237.00bp}]{test}}% \gplfronttext \end{picture}% \endgroup ================================================ FILE: docs/v2/examples.qmd ================================================ --- title: "Examples" --- ```{julia} #| echo: false #| output: false using Gaston Gaston.config.term = "pngcairo font ',10' size 640,480" Gaston.config.output = :echo ``` ### 3-D Euler spiral [(Clothoid)](https://en.wikipedia.org/wiki/Euler_spiral) ```{julia} using QuadGK z = range(-5, 5, 200) fx(z) = sin(z^2) fy(z) = cos(z^2) x = [quadgk(fx, 0, t)[1] for t in z] y = [quadgk(fy, 0, t)[1] for t in z] splot("""unset zeroaxis set tics border set xyplane at -5 set view 65,35 set border 4095 set xtics offset 0, -0.5""", x, y, z, "w l lc 'black' lw 1.5") ``` ### Waterfall Inspired by this [Julia Discourse discussion](https://discourse.julialang.org/t/how-to-produce-a-waterfall-plot-in-julia/93441). ```{julia} x = -15:0.1:15 y = 0:30 u1data = [exp(-(x-0.5*(y-15))^2) for x in x, y in y] Zf = fill(0.0, length(x)) f = Figure() Gaston.set!(f(1), """set zrange [0:1.5] set tics out set ytics border set xyplane at 0 set view 45,17 set xlabel 'ξ' set ylabel 't' offset -2.5 set zlabel '|u|' offset -0.85 set border 21 set size 1, 1.3""") for i in reverse(eachindex(y)) Y = fill(y[i], length(x)) Z = u1data[:,i] splot!(x, Y, Z, Zf, Z, "w zerrorfill lc 'black' fillstyle solid 1.0 fc 'white'") end f ``` ### Vector field Inspired by this [post in gnuplotting.org](https://gnuplotting.org/vector-field-from-function/index.html). ```{julia} xr = 15 # samples in x direction yr = 15 # samples in y direction # parameters x01 = 1 y01 = 0 q1 = 1 x02 = -1 y02 = 0 q2 = -1 scaling = 0.22 # equations r(x,y) = sqrt(x*x+y*y) v1(x,y) = q1/(r(x-x01,y-y01)) v2(x,y) = q2/(r(x-x02,y-y02)) v(x,y) = v1(x,y)+v2(x,y) e1x(x,y) = q1*x/r(x,y)^3 e1y(x,y) = q1*y/r(x,y)^3 e2x(x,y) = q2*x/r(x,y)^3 e2y(x,y) = q2*y/r(x,y)^3 ex(x,y) = e1x(x-x01,y-y01)+e2x(x-x02,y-y02) ey(x,y) = e1y(x-x01,y-y01)+e2y(x-x02,y-y02) enorm(x,y) = sqrt(ex(x,y)^2 + ey(x,y)^2) dx(x,y) = scaling*ex(x,y)/enorm(x,y) dy(x,y) = scaling*ey(x,y)/enorm(x,y) # initialize data vectors d1 = zeros(xr*yr) d2 = zeros(xr*yr) d3 = zeros(xr*yr) d4 = zeros(xr*yr) d5 = zeros(xr*yr) # calculations for X in range(-2, 2, length=xr) for Y in range(-1.8, 1.8, length=yr) push!(d1, X-dx(X,Y)/2) push!(d2, Y-dy(X,Y)/2) push!(d3, dx(X,Y)) push!(d4, dy(X,Y)) push!(d5, v(X,Y)) end end @plot({palette = :linear_kry_5_95_c72_n256}, :nocb, d1, d2, d3, d4, d5, "with vectors head size 0.08,20,60 filled lc palette") ``` ### Line color from palette ```{julia} x = -2π:0.05:2π @plot {palette = :ice} x sin.(3x) x "w l notitle lw 3 lc palette" ``` ### Variable marker size and color ```{julia} x = 0:0.1:6π splot("unset colorbox", x, cos.(x), sin.(x), x./10, "w p", "ps variable", "pt 7", "lc palette") ``` ### Filled curves #### Filled transparent curves in 2-D ```{julia} pois(λ, k) = (λ^k)*exp(-λ)/factorial(k) s = "set style fill transparent solid 0.4 noborder \nset title 'Poisson PMF'" plot(s, 0:15, k -> pois(4, k), "w filledcu x1 lc 'cyan' t 'λ = 4'") plot!(0:15, k -> pois(6, k), "w filledcu x1 lc 'blue' t 'λ = 6'") plot!(0:15, k -> pois(8, k), "w filledcu x1 lc 'pink' t 'λ = 8'") ``` #### Fill between two curves ```{julia} x = range(-10, 10, 100) y1 = sin.(x) .- 0.5 y2 = sin.(x) .+ 0.5 plot(x, y1, y2, "w filledcu lc 'turquoise'") ``` #### Filled curve in 3-D ```{julia} x = 0.:0.05:3; y = 0.:0.05:3; z = @. sin(x) * exp(-(x+y)) @gpkw splot(:labels, {style = "fill transparent solid 0.3", xyplane = "at 0", grid, lt = :Set1_5}, x, y, z, z.*0, z, "w zerror t 'Data'") splot!(x.*0, y, z, "w l lw 3") splot!(x, y.*0, z, "w l lw 3") ``` Here, `Set1_5` is a color scheme from [ColorSchemes.jl](https://github.com/JuliaGraphics/ColorSchemes.jl). ### Spheres #### Wireframe ```{julia} Θ = range(0, 2π, length = 100) Φ = range(0, π, length = 20) rd = 0.8 x = [rd*cos(θ)*sin(ϕ) for θ in Θ, ϕ in Φ] y = [rd*sin(θ)*sin(ϕ) for θ in Θ, ϕ in Φ] z = [rd*cos(ϕ) for θ in Θ, ϕ in Φ] @gpkw splot({view = "equal xyz", pm3d = "depthorder", hidden3d}, x, y, z, {w = "l", lc = Q"turquoise"}) ``` #### Surface ```{julia} Θ = range(0, 2π, length = 100) Φ = range(0, π, length = 100) rd = 0.8 x = [rd*cos(θ)*sin(ϕ) for θ in Θ, ϕ in Φ] y = [rd*sin(θ)*sin(ϕ) for θ in Θ, ϕ in Φ] z = [rd*cos(ϕ) for θ in Θ, ϕ in Φ] @splot({style = "fill transparent solid 1", palette = :summer, view = "equal xyz", pm3d = "depthorder"}, x, y, z, "w pm3d") ``` ### Torus ```{julia} U = range(-π, π, length = 50) V = range(-π, π, length = 100) rd = 0.5 x = [1+cos(u)+rd*cos(u)*cos(v) for u in U, v in V] y = [rd*sin(v) for u in U, v in V] z = [sin(u)+rd*sin(u)*cos(v) for u in U, v in V] settings = """set object rectangle from screen 0,0 to screen 1,1 behind fillcolor 'black' fillstyle solid noborder set pm3d depthorder set style fill transparent solid 0.5 set pm3d lighting primary 0.05 specular 0.2 set view 108,2 unset border set xyplane 0 unset tics unset colorbox""" @splot(settings, {palette = :cool}, x, y, z, "w pm3d") ``` #### Interlocking torii ```{julia} U = LinRange(-π, pi, 100) V = LinRange(-π, pi, 20) x = [cos(u) + .5 * cos(u) * cos(v) for u in U, v in V] y = [sin(u) + .5 * sin(u) * cos(v) for u in U, v in V] z = [.5 * sin(v) for u in U, v in V] @gpkw surf({palette = :dense, pm3d = "depthorder", colorbox = false, key = :false, tics = :false, border = 0, view = "60, 30, 1.5, 0.9", style = "fill transparent solid 0.7"}, x', y', z') x = [1 + cos(u) + .5 * cos(u) * cos(v) for u in U, v in V] y = [.5 * sin(v) for u in U, v in V] z = [sin(u) + .5 * sin(u) * cos(v) for u in U, v in V] surf!(x', y', z') ``` See more torus examples in the included Pluto notebook. ### Contours #### Surface with contours ```{julia} x = y = -10:0.5:10 f1(x, y) = cos.(x./2).*sin.(y./2) surf("""set hidden3d set contour base set cntrparam levels 10 unset key""", x, y, f1, "lc 'turquoise'") ``` #### Egg-shaped contours ```{julia} x = -1:0.05:1 y = -1.5:0.05:2 egg(x,y) = x^2 + y^2/(1.4 + y/5)^2 segg = [egg(x,y) for x in x, y in y] @gpkw contour({palette = :cool, cntrparam = "levels incremental 0,0.01,1", auto = "fix", xrange = (-1.2, 1.2), yrange = (-1.5, 2), cbrange = (0, 1), xlabel = "'x'", ylabel = "'y'", size = "ratio -1"}, x, y, segg', "w l lc palette", labels = false) ``` ### 3D Tubes #### Wireframe ```{julia} U = range(0, 10π, length = 80) V = range(0, 2π, length = 10) x = [(1-0.1*cos(v))*cos(u) for u in U, v in V] y = [(1-0.1*cos(v))*sin(u) for u in U, v in V] z = [0.2*(sin(v) + u/1.7 - 10) for u in U, v in V] settings = @gpkw {pm3d = "depthorder", style = "fill transparent solid 1", view = "equal xyz", xyplane = -0.05, palette = :ice, xrange = (-1.2, 1.2), yrange = (-1.2, 1.2), colorbox = false, hidden3d, view = (70, 79)} @splot(settings, x, y, z, "w l lc 'turquoise'") ``` #### Surface ```{julia} @splot(settings, x, y, z, "w pm3d") ``` ### Animations #### Lorenz attractor This example is adapted from [https://docs.makie.org/stable/#example](Makie's documentation). A few notes on the adaptation to Gaston: * The camera animation is achieved by changing the `view` setting every frame. * Each frame, `Npoints` coordinates are added to the plot. The animation consists of `Nframes` frames. * The coloring of the attractor is different than in most 3-D plots. Normally, the color of a point depends on its `z` coordinate. In this case, the `z` coordinate is not an amplitude, since the curve lives in a state space. Therefore, the color palette will be applied along the length of the curve, with points nearer the start given colors at the start of the palette. To achieve this, we use `ColorSchemes.resample` to create a new palette with the same number of colors as there are points in the curve. Then, `Gaston.hex` is used to convert these colors to decimal numbers. Finally, the colors are used as a fourth column of data and the line color is set to `lc rgb variable`. ```{.julia} using ColorSchemes Base.@kwdef mutable struct Lorenz dt::Float64 = 0.01 σ::Float64 = 10 ρ::Float64 = 28 β::Float64 = 8/3 x::Float64 = 1 y::Float64 = 1 z::Float64 = 1 end function step!(l::Lorenz) dx = l.σ * (l.y - l.x) dy = l.x * (l.ρ - l.z) - l.y dz = l.x * l.y - l.β * l.z l.x += l.dt * dx l.y += l.dt * dy l.z += l.dt * dz return (l.x, l.y, l.z) end Nframes = 120 Npoints = 50 attractor = Lorenz() x = Float64[]; y = Float64[]; z = Float64[]; s = @gpkw {xrange = (-30, 30), yrange = (-30, 30), zrange = (0, 60), xtics = "offset -1.2,0", xtics = "add ('' -30, '' 30)", ytics = "offset 1.2,0", ytics = "add ('' -30, '' 30)", origin = "-0.1, -0.1", size = "1.2, 1.2", object = "rectangle from screen 0,0 to screen 1,1 fillcolor 'black' behind", border = "back lc rgb '#eeeeee' lt 1 lw 1.5", view = "equal xyz", xyplane = "at 0"} f = splot(s, 1, 1, 1) for i = 1:Nframes for j = 1:Npoints step!(attractor) push!(x, attractor.x); push!(y, attractor.y); push!(z, attractor.z) end cs = resample(ColorSchemes.inferno, length(x)) splot(f[i], s, "set view 70, $(45 + 17 * sin(2pi * i / Nframes))", x, y, z, Gaston.hex(cs), "w l notitle lc rgb variable") end save(f, filename = "lorenz.webp", term = "webp animate loop 0 size 640,480") ``` ![](assets/lorenz.webp) #### 3-D spiral ```{.julia} z = 0:0.1:10pi step = 5 cc = "lc 'turquoise' lw 3 notitle" ac = @gpkw {zrange = (0,30), xrange = (-1.2, 1.2), yrange = (-1.2, 1.2)} F = scatter3(ac, :notics, :labels, cos.(z[1:step]), sin.(z[1:step]), z[1:step], cc) frame = Figure() for i = 2:60 frame = scatter3(ac, :notics, :labels, cos.(z[1:i*step]), sin.(z[1:i*step]), z[1:i*step], cc) push!(F, frame) end for i = 60:-1:1 frame = scatter3(ac, :notics, :labels, cos.(z[1:i*step]), sin.(z[1:i*step]), z[1:i*step], cc) push!(F, frame) end save(F, filename = "3dspiral.webp", term = "webp animate loop 0 size 640,480") ``` ![](assets/3dspiral.webp) #### Splash ```{.julia} x = y = -15:0.4:15 ac = @gpkw {title = Q"Splash", palette = :cool, cbrange = (-0.2, 1), zrange = (-0.3, 1), hidden3d = true} F = splot(ac, x, y, (x, y) -> sin(sqrt(x*x+y*y))/sqrt(x*x+y*y), "w pm3d") frame = Figure() for i = 1:-0.1:-1 frame = splot(ac, x, y, (x,y) -> i*sin(sqrt(x*x+y*y))/sqrt(x*x+y*y), "w pm3d"); push!(F, frame) end for i = -0.9:0.1:1 frame = splot(ac, x, y, (x,y) -> i*sin(sqrt(x*x+y*y))/sqrt(x*x+y*y), "w pm3d"); push!(F, frame) end save(F, filename = "3dsplash.webp", term = "webp animate loop 0 size 640,480") ``` ![](assets/3dsplash.webp) ================================================ FILE: docs/v2/index.qmd ================================================ --- title: "Introduction" --- [Gnuplot](http://www.gnuplot.info/) is a venerable (but actively developed), very powerful and fast program for plotting. [Julia](https://julialang.org) is a very powerful language for numerical computing. [Gaston](https://github.com/mbaz/Gaston.jl) is a package for plotting Julia data and functions using gnuplot. The following diagram illustrates how Gaston works. On the left there is a gnuplot script; the equivalent Gaston function call is on the right. ![](assets/comparison.png) The data to plot is in green color. In gnuplot, the data can be provided inline as a _data block_ (as in this example), or it can be provided in a separate file. Gaston can plot data stored in arrays (`x` and `y` in this example). Gaston also supports recipes to plot arbitrary Julia types. The lines in red specify the _axes settings_, affecting things like the presence of a grid, the title, the legend box, tics positions/labels, etc. Finally, in blue color, the _plot settings_ or _plotline_ specify the properties of one specific curve within the axes (for example, the line color, thickness and style, which marker to use, etc.) There is a close correspondence between gnuplot and Gaston commands; in fact, the main purpose of Gaston is to translate the Julia code on the right to the gnuplot commands on the left. Gaston has two main features: * It provides convenient, flexible syntax for plotting, along with common 2-D and 3-D plot styles. * It provides a simple mechanism to add user-defined recipes for plotting arbitrary Julia types. Other features are: * Support for plotting in separate GUI windows, in the terminal (with text or [sixels](https://en.wikipedia.org/wiki/Sixel)), in VS Code, or in notebooks (such as Jupyter and Pluto). * Handling multiple co-existing interactive GUI plot windows. * Support for (user-defined) themes. * Convenient commands for common types of plots (like histograms, contour plots, surface plots, heatmaps, etcetera). * Convenient syntax for creating multiplots and animations. * Saving plots to multiple formats, including pdf, png and svg. * Integrated color palettes from [ColorSchemes.jl](https://github.com/JuliaGraphics/ColorSchemes.jl). ::: {.callout-note} This manual is for Gaston version 2. The manual for Gaston v1 (no longer maintained) is [here](https://mbaz.github.io/Gaston.jl/v1/index.html). ::: ## Learning gnuplot This documentation assumes at least a basic understanding of gnuplot. Some pointers to get started with gnuplot are: * The [official documentation](http://www.gnuplot.info/documentation.html). * The [official list of tutorials](http://www.gnuplot.info/help.html). * [Plotting data](http://www.gnuplotting.org/plotting-data/) article on [gnuplotting.org](http://www.gnuplotting.org/). * [A blog post about Gaston by Julia Frank](https://juliaifrank.com/gnuplot-with-julia-for-beautiful-graphics/). * Stackoverflow has a [gnuplot tag](https://stackoverflow.com/questions/tagged/gnuplot) with answers to more than 6,500 questions. The following are interesting plot galleries: * [Official gnuplot demo gallery](http://www.gnuplot.info/screenshots/index.html#demos). * [Wikimedia commons gallery](https://commons.wikimedia.org/wiki/Category:Gnuplot_diagrams). * [Nice collection of volumetric plots](https://ayapin-film.sakura.ne.jp/Gnuplot/pm3d.html). ## Installation Gaston v2.x requires Julia version 1.8.0 or above (Gaston v1.x supports Julia 1.6 and above), and has been tested with gnuplot versions 5 and 6. You should manually install gnuplot on your system prior to using Gaston. On Linux, it is highly recommended that you select a version with Qt support: on Debian and Ubuntu, you will need `gnuplot-qt`. On Arch and its derivatives, a simple `pacman -S gnuplot` suffices. Gnuplot also supports Windows and Mac. This author does not use these systems much, but it is said that gnuplot Windows executables are available at [Sourceforge's gnuplot repository](https://sourceforge.net/projects/gnuplot/files/gnuplot/). On Mac, gnuplot is supposed to be available using Homebrew. To install Gaston from the Julia REPL, run ```julia julia> ]add Gaston ``` Typing `]` switches the Julia REPL to the package manager, and the `add` command installs the package. To exit the package manager, hit the backspace key. Load Gaston into your Julia session with ```julia using Gaston ``` ::: {.callout-note} ## Specifying the location of gnuplot The location of the gnuplot executable can be specified with the environmental variable `JULIA_GNUPLOT_EXE`. If gnuplot is in the system's path, setting this variable is not necessary. ::: ## Support Here are some ideas on what to do if you need help with Gaston: * Post a question in [Julia's discuss forum](https://discourse.julialang.org/tag/plotting) in the "plotting" category. * Chat with the author (@mbaz) on [Julia's Zulip chat forum](https://julialang.zulipchat.com/), in the "plotting" or "helpdesk" channels. * Bug reports, suggestions and pull requests are welcome at [Gaston's github page](https://github.com/mbaz/Gaston.jl). ## Contributing Contributions are welcome! Examples of things you can do are bug reports, improvements to the documentation, new examples and tutorials, and new features or suggestions. ## Gnuplot startup file Gnuplot reads and executes a startup file, if it exists, before every plot. Since an un-configured gnuplot produces plots that are less than attractive, the following minimum configuration is suggested (and was used to generate the plots in this document): set linetype 1 lc rgb "blue" pt 3 ps 1.2 set linetype 2 lc rgb "red" pt 4 ps 1.2 set linetype 3 lc rgb "dark-green" pt 6 ps 1.2 set linetype 4 lc rgb "orange-red" pt 12 ps 1.2 set linetype 5 lc rgb "gold" pt 5 ps 1.2 set linetype 6 lc rgb "dark-violet" pt 1 ps 1.2 set linetype 7 lc rgb "gray50" pt 2 ps 1.2 set linetype 8 lc rgb "black" pt 7 ps 1.2 set linetype cycle 8 set style data lines set key noautotitle set auto fix set offsets graph .05, graph .05, graph .05, graph .05 The configuration file is `~/.gnuplot` on Unix-like systems, and `%APPDATA%\GNUPLOT.INI` on Windows. ## Next steps * Read the [Tutorial](tutorial.qmd). * See plot examples in the [Examples](examples.qmd) section. * Learn how to extend Gaston to plot arbitrary Julia types in the [Recipes](recipes.qmd) section. * Learn all the details about how to plot with Gaston in the [Manual](manual.qmd). * For specific information about migrating from Gaston v1 to v2, see the [Migration Guide](migrate.qmd). * Consult the full [API Reference](reference.qmd). Gaston's documentation includes three [Pluto](https://plutojl.org/) notebooks: * An overview of the [essential plotting concepts](https://github.com/mbaz/Gaston.jl/tree/master/docs/v2/tutorial-essentials.jl). * An overview of [3-D plotting](https://github.com/mbaz/Gaston.jl/tree/master/docs/v2/tutorial-3d.jl). * A tutorial on how to [plot a torus](https://github.com/mbaz/Gaston.jl/tree/master/docs/how-to-plot-a-torus.jl) (which aims to showcase Gaston in interactive notebooks). ## Acknowledgments When developing Gaston, I've been inspired by the excellent features and ideas behind other Julia plotting packages, including [Plots.jl](https://github.com/JuliaPlots/Plots.jl), [Gnuplot.jl](https://github.com/gcalderone/Gnuplot.jl), [PGFPlotsX.jl](https://github.com/KristofferC/PGFPlotsX.jl), and [Makie.jl](https://github.com/MakieOrg/Makie.jl). Many thanks to their multiple authors for freely sharing their code and their APIs! ================================================ FILE: docs/v2/manual.qmd ================================================ --- title: "Manual" --- This manual covers all aspects of using Gaston. ## Configuration ### The terminal By default, gnuplot chooses an appropriate terminal: `qt` or `wxt` on Linux, `windows` on Windows, and `aqua` on MacOS. The terminal can be set by changing the value of `Gaston.config.term`; for example: ```julia Gaston.config.term = "pngcairo font ',10' size 700,400"; ``` To show the terminals supported by gnuplot, run: ```julia Gaston.terminals() ``` ### Other settings * `Gaston.config.output`: controls how plots are displayed. Possible values are: * `:external`: plots are displayed in GUI windows. This is the default value. * `:echo`: sends text-based plots (like `png` and `sixelgd`) back to the terminal. Useful for notebooks and IDEs, and for plotting on the terminal. * `:null`: execute all plot commands but do not actually produce a plot. If Gaston detects it is running in a notebook environment, it automatically sets the terminal to `pngcairo` and `config.output` to `:echo`. When the automatic detection does not work, these setting have to be set manually. * `Gaston.config.embedhtml`: `Bool`, defaults to `false`. Enables embedding plots in HTML; useful to enable interactivity in Pluto and Jupyter notebooks. ### Location of gnuplot executable If gnuplot is not in the system's path, its location can be set using the environmental variable `JULIA_GNUPLOT_EXE`. This variable must be set before Gaston is loaded. ## Plotting The `plot` function is used to plot one curve, while `plot!` is used to add a curve to the same plot. 3-D plots are created with `splot` and `splot!`. `plot` returns a value of type `Figure`, which contains a vector of `Axis` (each containing one or more `Plot`s, or curves. A `plot` command takes four different kinds of arguments: a figure (possibly indexed), settings, data, and plotline, in that order. ```julia plot([figure,] [settings...,] data..., [plotline...]) ``` Further curves may be added using `plot!`. (For 3-D plots, use `splot` and `splot!` instead.) More specifically, a `plot` command takes: * Optionally, a figure where the plot is to be produced. * If the figure is indexed, then the plot will be produced in the specified axis (if it doesn't exist, it will be created). * Zero or more **settings** arguments, which get converted to gnuplot `set` commands. * One or more **data** arguments, which are written to a file in the format gnuplot expects. * Zero or more **plotline** arguments, which are appended to gnuplot's `plot` or `splot` commands. Gaston provides several alternative ways to specify settings and plotlines. ### Settings and Plotlines All the following are equivalent. * One single string ```julia plot("set grid unset key set title 'A Sinusoid'", x, y, "with linespoints lc 'green'") ``` * Multiple strings ```julia plot("set grid", "unset key \n set title 'A Sinusoid'", x, y, "with linespoints", "lc 'green'") ``` * Keywords with `@plot` ```julia @plot({grid = true, key = false, title = "'A Sinusoid'"}, x, y, {with = "linespoints", lc = "'green'"}) ``` * Keywords with `@gpkw`: ```julia @gpkw plot({grid = true, key = false, title = "'A Sinusoid'"}, x, y, {with = "linespoints", lc = "'green'"}) ``` Keyword options are enclosed in curly brackets `{}`. To set an option without arguments, such as `set grid`, use either a lone `grid`, or `grid = true`. To unset an option, such as in `unset grid`, use ` grid = false`. Options can be repeated; each one will be converted to a separate `set` line. `@plot` also accepts strings, and in fact strings and keywords may be combined: ```julia @plot({grid, key = false}, "set title 'A Sinusoid'", x, y, "with linespoints", {lc = "'green'"}) ``` It is possible to omit the parenthesis, but in this case the command must fit in a single line. ```julia @plot {grid, key = false, title = "'A Sinusoid'"} x y {with = "lp", lc = "'green'"} ``` For 3-D plots, use the macro `@splot`. #### Quoted strings All strings passed to gnuplot must be enclosed in single quotes, such as in `lc = "'green'"` in the example above. The `@Q_str` string macro can help reduce the number of quotes needed: ```julia @plot {grid = true, key = false, title = Q"A Sinusoid"} x y {with = "lp", lc = Q"green"} ``` This macro turns `"abc"` into `"'abc'"`. #### Keyword parsing Some `@plot` (or `@gpkw`) keyword arguments are parsed by Gaston, providing syntax that may be more convenient than gnuplot's. The following list is for keywords that specify axis settings: * For any keyword argument, `{arg}` or `{arg = true}` is parsed to `set arg`, while `{arg = false}` is parsed to `unset arg`. * For `xtics`, `ytics`, `ztics` or `tics`: * `{tics = R}` where `R` is an `AbstractRange` is parsed as `set tics $(first(R)), $(step(R)), $(last(R))`. * `{tics = T}` where `T` is a `Tuple` is parsed as `set tics $T`. * `{tics = NT}` where `NT` is a `NamedTuple` is parsed as in this example: `{tics = (labels = ("one", "two"), positions = (0, 2))}` is equivalent to `set tics ('one' 0, 'two' 2, )` * For `xrange`, `yrange`, `zrange`, `cbrange`: * `{xrange = R}` where `R` is a vector or tuple is parsed as `set xrange [$R[1]:$R[2]]` * If `R` contains an `Inf`, then it is replaced with `*`. * If the keyword is `ranges`, then all four ranges listed above are set. * For `palette`, if the value is a symbol, then the corresponding color scheme from `ColorSchemes.jl` is converted to gnuplot's format. If a tuple of two symbols is provided and the second one is `:reverse`, then the order of the palette is reversed. * For `{view = V}`, if `V` is a `Tuple`, then it is parsed as `set view $V[1], $V[2]`. * For `{linetype = S}` where `S` is a `Symbol`, then the corresponding color scheme is converted to a set of line types, one per color in the scheme. * For `{margins = T}` where `T` is a `Tuple` is parsed as in this example: `{margins = (1, 2, 3, 4)}` is equivalent to `set lmargin at screen 1`, `set rmargin at screen 2`, `set bmargin at screen 3`, `set tmargin at screen 4`. The following list is for keywords that specify plotline elements: * `plotstyle` is equivalent to `with`. * `markersize` and `ms` are equivalent to `pointsize`. * `legend` is equivalent to `title`. * `marker` is equivalent `pointtype`. In addition, `marker`, `pointtype` and `pt` accept symbolic names for the marker types, according to the following table: | name | gnuplot pointtype | |------|-------------------| | :dot | 0 | | :⋅ | 0 | | :+ | 1 | | :plus | 1 | | :x | 2 | | :* | 3 | | :star | 3 | | :esquare | 4 | | :fsquare | 5 | | :ecircle | 6 | | :fcircle | 7 | | :etrianup | 8 | | :ftrianup | 9 | | :etriandn | 10 | | :ftriandn | 11 | | :edmd | 12 | | :fdmd | 13 | ### Data Data to be plotted can be provided as vectors and/or matrices. Gaston converts the data to a format compatible with gnuplot. Three cases are supported: * All data arguments are vectors. * The first two arguments are vectors of length `n` and `m`, and the third argument is a matrix of size `n x m`; further arguments are optional. * All provided arguments are matrices of size `n x m`. #### Recipes [Recipes](manual.qmd#defining-new-plot-types-and-recipes) can be provided to convert arbitrary types to data that gnuplot understands. #### Functions Functions can be plotted directly, with a given range and number of samples, which can be specified in the following alternative ways: ```julia # g is a function plot(g) # plots `g` evaluated at 100 samples, from -10 to 9.99 plot((a, b), g) # plots `g` evaluated at 100 samples, from a to b plot((a, b, c), g) # plots `g` evaluated at c samples, from a to b plot(x, g) # plots g.(x) ``` #### Plot with table In some cases, it is useful to have gnuplot produce plot data in a "table" format, which can then be plotted. See an example in [contour lines on heatmap](tutorial.qmd#gnuplot-datasets-and-tables). The (non-exported) function `Gaston.plotwithtable` returns a `Gaston.DataTable` storing the table. All plot commands accept this type. The following `DataTable` constructors are provided: * `DataTable(vs::Vector{<:AbstractString}...)` * `DataTable(ts::T) where T <: Tuple`; the tuple is assumed to contain strings. * `DataTable(args::Matrix...)`; each matrix is a datablock. ### Simple themes Frequently-used settings or plotlines may be stored in a "theme"; the `@gpkw` macro processes keyword arguments wrapped in curly brackets. ```julia theme = @gpkw {grid, key = false} plot(theme, x, y) ``` Themes may be combined with other themes and/or with strings: ```julia theme2 = @gpkw {xlabel = Q"X"} plot(theme, "set title 'A Sinusoid'", theme2, x, y) ``` Themes can also be used for plotlines, and these may also be combined with other themes and/or strings. ```julia pltheme = @gpkw {w = "lp", pt = "'o'", ps = 3} plot(theme, "set title 'A Sinusoid'", theme2, x, y, pltheme) ``` Gaston includes a few generic themes: |Axis themes | Description | |-----------:|:------------| | :notics | Removes all tics | | :labels | Generic axis labels (`x`, `y`, `z`) | | :nocb | Removes colorbox | | :unitranges | Set all ranges to `[-1:1]` | For example, the following command plots a sine wave with no tics and generic `x` and `y` axis labels: ```julia plot(:notics, :labels, "set title 'Example'", (-1, 1), sin) ``` Themes are also used to provide common plot types (illustrated in [Themes](examples.qmd#themes)). The following are the provided specialized plot commands and the themes they use: | Command | Settings theme | Plotline theme | |----------|----------------|----------------| | `scatter`, `scatter!` | `:scatter`, `:scatter3` | `:scatter` | | `stem`, `stem!` | None | `:stem`, `:impulses` (optional) | | `bar`, `bar!` | `:boxplot` | `:box` | | `barerror`, `barerror!` | `:boxerror` | `:box` | | `histogram` | `:histplot` | `:box`, `:horhist` (1-D); `:image` (2-D) | | `imagesc` | `:imagesc` | `:image`, `:rgbimage` | | `surf`, `surf!` | `:hidden3d` | `:pm3d` | | `contour` | `:contour` | `:labels` (optional) | | `surfcontour` | `:contourproj` | `:labels` (optional) | | `wireframe`, `wireframe!` | `:hidden3d` | None | | `wiresurf`, `wiresurf!` | `:wiresurf` | None | | `heatmap` | `:heatmap` | `:pm3d` | ::: {.callout-warning} # Plotline themes Plotline themes must be handled with care: gnuplot requires plotline options to be specified in a certain order, which may not be repeated, and some combinations are invalid. It is very easy to create erroneous plotlines. ::: ::: {.callout-note} # Gaston is not a gnuplot parser Gaston does not validate that the settings and plotline given to gnuplot are valid. When gnuplot returns an error or warning, it is echoed to the terminal. ::: ## Multiplot As mentioned above, a `Figure` contains a vector of `Axis`. Any figure with more than one axis is plotted using gnuplot's `multiplot` feature (except in the case where the terminal configuration contains `animate`). There are several ways to insert axes into a figure. The first is to index into the figure: ```julia f = Figure() plot(f[2], sin) plot(f[4], cos) ``` Note that: * Indexing into a non-existing axis creates an empty axis at that index. * It's possible to have empty axes (`f[1]` and `f[3]` above are empty). * By default, Gaston will manage the figure's layout, trying to keep a square aspect ratio. In the example above, the figure will have dimensions 2x2. The second method is to `push!` a figure into another: ```julia f1 = plot(sin) f2 = plot(cos) push!(f1, f2) ``` Here, the axis at `f[2]` will be inserted into the axes vector of `f1`. It is possible to index into a figure to obtain a specific axis: ```julia f1 = plot(sin) plot(f1[2], cos) # f1 now contains two axes f2 = Figure() plot(tan) # plot goes into f2 since it is the active figure push!(f2, f1[2]) # the axis with a plot of cos is inserted into f2 ``` The third and final method is to plot multiple figures together: ```julia plot(f1, f2, multiplot = "...", autolayout = ...) ``` This will return a new figure with all axes from `f1` and `f2`. Any number of figures may be provided as arguments. The `Figure` constructor takes a couple of options to control how multiplot behaves: * `multiplot`: a string that is appended to `set multiplot`, such as `"title 'A multiplot'"`. Defaults to `""`. * `autolayout`: a boolean that controls whether Gaston should manage the figure's layout. Defaults to `true`. ## Managing multiple figures Gaston has the ability to create and manage multiple GUI plot windows simultaneously. Each window is backed up by its own gnuplot process. The following commands can be used to create and control multiple windows. #### Creating and selecting figures ```julia Figure() ``` Creates a new, empty figure. All figures are of type `Gaston.Figure`. Gaston keeps internal references to all figures, to prevent them from being garbage collected. As described above, `Figure` takes two optional arguments, `multiplot` and `autolayout`. When a figure is created, it becomes the active figure, meaning that subsequent plot commands will go to this figure by default. It is possible to keep figures in different variables: ```julia fig1 = Figure() fig2 = Figure() ``` and then redirect plot commands to the desired figure: ```julia plot(fig1, ...) # plot goes to fig1 plot!(fig2, ...) # new curve added to fig2 ``` By default, `plot` resets the contents of a figure. Usually it is more convenient to keep figures in variables, but it is also possible to manage figures using _handles_: ```julia Figure("density") # figure with handle "density" Figure(:volume) # figure with handle :volume Figure(33) # figure with handle 33 ``` Handles can be of any type. All figures have a handle. By default, handles are integers in increasing order starting from 1. The keyword argument `handle` allows specifying the destination of a `plot` command: ```julia plot(..., handle = :volume) plot!(..., handle = 33) scatter(..., handle = "density") ``` To activate a figure given its handle, use: ```julia figure(handle) ``` It is possible to make an existing figure `f` the active figure with: ```julia figure(f) ``` With no arguments, `figure()` returns the current figure. To obtain the list of all current figures and their handles, and to identify the active figure, use the unexported function `Gaston.listfigures()`. #### Closing figures To close the active figure, run ```julia closefigure() ``` The figure with handle `h` can be closed with `closefigure(h)`. Likewise, to close figure `f` use `closefigure(f)`. Closing a figure quits the underlying gnuplot process. To close all figures, use `closeall()`. ## Saving plots A plot can be saved to a file in any format supported by gnuplot, with the function ```julia save([f] ; filename, term) ``` where the arguments are: * `f`, which can be either a `Figure`, or an arbitrary value that is taken to be the handle of the figure to save. Defaults to the active figure. * `filename`, a string that specifies the filename. If empty, it defaults to `figure-` followed by the figure's handle; the filename extension is set to the first three characters of the gnuplot terminal (see next argument). * `term`, specifies the gnuplot terminal used to save the plot; defaults to `"pngcairo font ',7'"`. ## Defining new plot types and recipes There are several ways to extend Gaston to create new plot types or to plot arbitrary types. One is to define a new function that returns a `Gaston.Figure`. The rest involve extending `Gaston.convert_args` in various ways. ### Functions that return a `Gaston.Figure` The first way to extend Gaston to handle arbitrary types is to define a new function (and optionally new themes) that returns a `Gaston.Figure`. See an example [here](recipes.qmd#functions-that-return-a-gaston.figure). For 3-D plot commands such as `splot`, the function `convert_args3` should be used instead. The recommended way to proceed is to: 0. Define new themes if necessary, by adding key-value pairs to `Gaston.sthemes` and/or `Gaston.pthemes`. 2. Process the function arguments as required. 1. Create a new figure inside the function, using either `Figure` or `MultiFigure`. 3. Use `plot` to add new axes and curves to the figure, possibly using the new themes. 4. Return the figure. ### Adding new methods to `Gaston.convert_args` When the data provided to `plot` is not of a type that gnuplot directly understands, Gaston calls the function `convert_args`, defined in the package `GastonRecipes`. This function returns a value of one of three different types: * `GastonRecipes.PlotRecipe`, used to define a single curve (coordinates and a plotline). See and example [here](recipes.qmd#plotrecipe). * `GastonRecipes.AxisRecipe`, used to define an axis (settings, a vector of `PlotRecipe`s, and a boolean to define if the axis is three dimensional). See an example [here](recipes.qmd#axisrecipe). * `GastonRecipes.FigureRecipe`, used to define a full figure (a vector of `AxisRecipes`, plus multiplot and autolayout settings). See an example [here](recipes.qmd#figurerecipe). The function `convert_args` is called with all data and all keyword arguments given to the `plot` command. Keyword arguments can be used to control the recipe's behavior. Note that these functions and types are not exported by Gaston. ## Internals In Gaston, the basic building block is the `Plot` type. This type has two fields: the plotline, a string; and the name of a file where the coordinates are stored. When a `Plot` is constructed, the data provided is immediately written to a file; a `Plot` does not store any coordinates. On top of `Plot` we have the `Axis` type, which contains a vector of `Plot`s, a string with the axis settings, and a boolean that indicates whether the axis should be rendered with `plot` (2-D) or `splot` (3-D). Finally, on top of `Axis` we have the `Figure` type, which contains a vector of `Axis` plus multiplot configuration. Besides, a `Figure` contains a figure handle, and most importantly, a gnuplot process with which it communicates. Each figure is associated with a different gnuplot process. When a `Figure` is displayed by Julia, the `show` function builds a set of commands that are sent to gnuplot. Gnuplot is instructed to send a sentinel string back to the figure, which indicates that gnuplot is done displaying the figure (and also prevents race conditions). Each `Figure` is associated with a finalizer that makes sure its associated gnuplot process exits gracefully. Gaston uses the following functions to set up and communicate with gnuplot: * `gp_start` initializes a new gnuplot process and connects to its stdin, stdout and stderr streams. * `gp_quit` terminates a gnuplot process. * `gp_send` sends a string of commands to an existing gnuplot process. * `gp_exec` starts a new gnuplot process, sends it commands, and quits the process. Gaston also keeps some internal state: * `Gaston.state.figures` stores pointers to all existing figures. * `Gaston.state.enabled` is `true` if gnuplot is runnable. * `Gaston.state.activefig` stores the handle of the currently active figure. ================================================ FILE: docs/v2/migrate.qmd ================================================ --- title: "Migration guide" --- ```{julia} #| echo: false #| output: false using Gaston Gaston.config.term = "pngcairo font ',10' size 640,480" Gaston.config.output = :echo ``` This guide provides hints on how to migrate from Gaston v1 to v2. In side-by-side code comparisons, Gaston v1 is always on the left, and v2 on the right. ### Axis settings In v1, axes settings are wrapped in a type called `Axes`, and given as key-value pairs. In v2, settings always come before data, and may be given as strings and/or key-value pairs enclosed in curly brackets (which require either `@plot` or `@gpkw`). ::: {layout-ncol=2} ```julia using SpecialFunctions x = y = 0:0.075:10 surf(x, y, (x,y) -> besselj0(y)*x^2, with = "pm3d", Axes(view = (45, 45), pm3d = "lighting primary 0.5 specular 0.4", key = :off) ) ``` ```julia using SpecialFunctions x = y = 0:0.075:10 @gpkw surf({view = (45, 45), pm3d = "lighting primary 0.5 specular 0.4", key = :off}, x, y, (x,y) -> besselj0(y)*x^2) ``` ::: Two other differences: * Using `key = false` instead of `key = :off` is valid syntax to produce `unset key`. * In v2, `surf` is a plot style that includes `with pm3d`. The generic 3-D plot command is `splot`, so `splot(..., x, y, z, "with pm3d")` in v2 is equivalent to `surf` in v1. ### Plotline (or curve appearance settings) In v1, a curve's appearance is configured with key-value arguments that are not data and not `Axes`. Values can be symbols or strings, which are interpreted differently, and is some cases spaces had to be written as underscores. In v2, all curve settings (or _plotline_) are given after the data. Just like axis settings, they may be strings and/or key-value pairs enclosed in curly brackets. ::: {layout-ncol=2} ```julia t = 0:0.01:1 plot(t, sin.(2π*5*t), linecolor = :coral, plotstyle = "linespoints", pointtype = "ecircle" Axes(title = :First_Plot)) ``` ```julia t = 0:0.01:1 plot("title = 'First Plot'", t, sin.(2π*5*t), "w lp lc 'coral' pt 6") ``` ::: In v2, the following plot commands (and variations thereof) can also be used: * `plot(..., "w lp", "lc 'coral", "pt 6")` * `@plot ... {w = "lp", lc = "'coral'", pt = :ecircle}` * `@gpkw plot(..., {w = "lp", lc = "'coral'", pt = :ecircle})` * `@plot ... {w = "lp"} "lc 'coral'" {pt = :ecircle}` Finally, `lc = "'coral'"` may be written as `lc = Q"coral"`. The `Q_str` string macro inserts the single quotes. ### Multiplot Multiplot support was completely overhauled in v2. ::: {layout-ncol=2} ```julia t = 0.01:0.01:10pi p1 = plot(t, cos, Axes(title = :Plot_1), handle = 1) p2 = plot(t, t.^2, Axes(title = :Plot_2), handle = 2) p4 = plot(t, exp.(-t), Axes(title = :Plot_4), handle = 4) plot([p1 p2 ; nothing p4]) ``` ```julia t = 0.01:0.01:10pi f = plot("set title 'Plot 1'", t, cos) plot(f[2], "set title 'Plot 2'", t, t.^2) plot(f[4], "set title 'Plot 4'", t, exp.(-t)) ``` ::: In v2, `f` is a value of type `Figure`, and it can be indexed inside a `plot` command to create a multiplot. Indices without a plot create an empty "slot". More details on multiplots and their settings and layout are given in the [tutorial](tutorial.qmd). ### Saving plots The command to save plots has been streamlined. ::: {layout-ncol=2} ```julia save(term = "png", output= "myfigure.png", font = "Consolas,10", size = "1280,900", linewidth = 1, background = "blue") ``` ```julia save(filename = "myfigure.png", term = "png font 'Consolas,10' size 1280,900 lw 1 background 'blue'") ``` ::: To save a specific figure `f`, just run `save(f, ...)`. ### Other differences * The `set` command is no longer available. * To set the terminal, use `Gaston.config.term`, for example `Gaston.config.term = "gif"`. * To enable debug mode, run `Gaston.debug(true)` (use `false` to disable). * To prevent plots from being produced, run `Gaston.config.output = :echo`. * To enable notebook mode (for Jupyter, Pluto, VS Code, etc), use `Gaston.config.output = :echo` (Gaston should detect when running in a notebook, but sometimes this needs to be manually configured, for example when generating Quarto documents). * The `axis` key inside `Axes()` is no longer supported. ================================================ FILE: docs/v2/recipes.qmd ================================================ --- title: "Recipes" --- ```{julia} #| echo: false #| output: false using Gaston Gaston.config.term = "pngcairo font ',10' size 640,480" Gaston.config.output = :echo ``` There are several ways to extend Gaston to plot data of arbitrary types. ## Functions that return a `Gaston.Figure` A straightforward way to extend (or customize) Gaston's functionality is by defining functions that return a value of type `Figure`. The example below shows how to plot complex vectors as two subplots, one of the magnitude and the other of phase of the data. This example defines new themes. ```{julia} # define new themes Gaston.sthemes[:myplot_mag] = @gpkw {grid, ylabel = Q"Magnitude"} Gaston.sthemes[:myplot_ph] = @gpkw {grid, ylabel = Q"Angle"} Gaston.pthemes[:myplot_mag] = @gpkw {w = "lp", marker = :ecircle} Gaston.pthemes[:myplot_ph] = @gpkw {w = "l", lc = "'black'"} # define new function function myplot(f::Figure, data::AbstractVector{<:Complex}; kwargs...)::Figure # convert data to a format gnuplot understands x = 1:length(data) magnitude = abs.(data) phase = angle.(data) # make sure figure f is empty Gaston.reset!(f) # fixed layout (two rows, one col) f.multiplot = "layout 2,1" f.autolayout = false # add two plots to f, using the themes defined above plot(f[1], x, magnitude, stheme = :myplot_mag, ptheme = :myplot_mag) plot(f[2], x, phase, stheme = :myplot_ph, ptheme = :myplot_ph) return f end # plot on active figure if none specified, or new figure if none exist myplot(data::AbstractVector{<:Complex}; kwargs...) = myplot(figure(), data; kwargs...) # plot example: complex damped sinusoid t = range(0, 1, 20) y = exp.(-t) .* cis.(2*pi*7.3*t) myplot(y) # plot ``` Note that the function `myplot` has two methods: * The main method takes an existing figure as first argument, and then the data. * A second method handles the case where only data is provided by selecting a new figure and then forwarding execution to the main method. Here, `myplot` takes a base Julia type. A simple modification handles the case where the data is of a user-defined type: ```{.julia} #define new type struct ComplexData{T <: Complex} samples :: Vector{T} end # define new function function myplot(data::ComplexData; kwargs...)::Figure # convert data to a format gnuplot understands x = 1:length(data.samples) magnitude = abs.(data.samples) phase = angle.(data.samples) [...] end # create new Figure if not provided myplot(data::ComplexData; kwargs...) = myplot(Figure(), data; kwargs...) # plot example: complex damped sinusoid t = range(0, 1, 20) y = ComplexData(exp.(-t) .* cis.(2*pi*7.3*t)) myplot(y) # plot ``` The use of themes allows the user to modify the default properties of the plot, by modifying the themes (such as `Gaston.sthemes[:myplot_mag]`) instead of having to re-define `myplot`. Of course, similar functionality can be achieved with the use of keyword arguments. The main drawback of this method of extending Gaston is that it requires an environement where Gaston has been installed This may be undesirable when sharing code with others, which may prefer to use a different plotting package, or when developing a package, which would burden all users with a relatively large, unneeded dependency. The solution to this problem is to use "recipes". ## Adding new methods to `Gaston.convert_args` The package `GastonRecipes` is a tiny package that allows extending Gaston to plot arbitrary types. It provides three types of recipes: * `PlotRecipe`, which return a single curve that can inserted into an axis. * `AxisRecipe`, which return a single axis that can be inserted into a figure. * `FigureRecipe`, which consists of one or more axes, mostly useful for multiplots or for animations. Recipes work as follows: Gaston's `plot` function calls function `Gaston.convert_args` (or `convert_args3` for 3-D plots), if an appropriate method exists. This function takes the data provided to `plot` and returns an appropriate object (of type `PlotRecipe`, `AxisRecipe` or `FigureRecipe`) containing data of a type and format that gnuplot understands. The idea then is to add methods to `convert_args` to handle any arbitrary type we wish to plot. ### `PlotRecipe` The following example shows how to extend `Gaston.convert_args` to plot a custom type `Data1`. This simple example returns a `PlotRecipe` object (essentially a curve), which contains data and a plotline. ```{julia} using GastonRecipes: PlotRecipe import GastonRecipes: convert_args # define custom type struct Type1 samples end # add method to convert_args function convert_args(d::Type1) x = 1:length(d.samples) y = d.samples PlotRecipe((x, y), "") # all coordinates are in a Tuple end # create some data data = Type1(rand(20)) # plot plot("set title 'Simple data conversion recipe'", "set grid", data, "w lp pt 7 lc 'olive'") ``` Note that this kind of recipe will also seamlessly work with `plot!`, which adds the curve to the current axis. ```{julia} data2 = Type1(rand(20)) plot!(data2, "lc 'red'") ``` Finally, note that `Gaston` calls `convert_args` with all data and keyword arguments given to `plot`. This may be used to control the recipe's behavior, as illustrated in the next example. ### `AxisRecipe` A recipe may also return an entire `AxisRecipe` object, with its own settings and curves. The following example returns an axis with two curves. Keyword arguments are used to control the linecolor of each curve. ```{julia} using GastonRecipes: AxisRecipe struct Type2 end function convert_args(::Type2, args... ; lc_first = "'blue'", lc_second = "'red'", kwargs...) x = range(0, 1, 100) # build first curve p1 = @gpkw PlotRecipe((x, cos.(4x)), {dt = "'-'", lc = lc_first, t = "'cosine'"}) # build second curve p2 = PlotRecipe((x, sin.(5x)), "dt '.' lc $(lc_second) t 'sine'") # build AxisRecipe, using a vector of PlotRecipes AxisRecipe("set grid\nset title 'Full axis recipe'", [p1, p2]) end plot(Type2()) ``` By default, the line colors will be blue and red for the first and second curve, respectively, but these can be specified by the user with the keyword arguments `lc_first` and `lc_second`. Note that the axis returned by a recipe can be inserted directly into a multiplot: ```{julia} f = Figure(multiplot = "title 'Recipe example'") plot(f[1], randn(100), "w p") plot(f[2], Type2()) ``` Finally, the following example shows how to create a recipe for `splot`, using `convert_args3`. Note that now `AxisRecipe` takes a third argument, which indicates a 3-D plot when set to `true` (it is `false` by default). ```{julia} import Gaston: convert_args3 function convert_args3(::Type2) p1 = PlotRecipe((1:20, 1:20, randn(20,20)), "w pm3d") @gpkw s = {palette = :matter, title = Q"A Surface"} AxisRecipe(s, [p1], true) end splot(Type2()) ``` ### `FigureRecipe` Finally, a recipe can also generate a full multiplot, with multiple axes, as illustrated in the example below: ```{julia} using GastonRecipes: FigureRecipe struct Type3 end function convert_args(::Type3) # first axis p1 = PlotRecipe((1:10, rand(10)), "") @gpkw a1 = AxisRecipe({title = Q"First Axis"}, [p1]) # axis #2 t1 = range(0, 1, 40) p2 = @gpkw PlotRecipe((t1, sin.(5t1)), {lc = Q"black"}) p3 = @gpkw PlotRecipe((t1, cos.(5t1)), {w = "p", pt = 16}) a2 = @gpkw AxisRecipe({title = Q"Trig"}, [p2, p3]) # axis #3 t2 = range(-5, 5, 50) z = Gaston.meshgrid(t2, t2, (x,y) -> cos(x)*cos(y)) p4 = @gpkw PlotRecipe((t2, t2, z), {w = "pm3d"}) @gpkw a3 = AxisRecipe({title = Q"Surface", tics = false, palette = (:matter, :reverse)}, [p4], true) # axis the fourth @gpkw a4 = AxisRecipe({tics, title = false, title = Q"Last Axis"}, [PlotRecipe((1:10, 1:10, rand(10,10)), "w image")]) # return named tuple with four axes FigureRecipe([a1, a2, a3, a4], "title 'A Four-Axes Recipe' layout 2,2", false) end plot(Type3()) ``` ## Recipes for types owned by other packages Let's say we want to create a recipe for `Base.Vector`. We don't own either `convert_args` nor `Base.Vector`, so creating a recipe would be [type piracy](https://docs.julialang.org/en/v1.11/manual/style-guide/#avoid-type-piracy). The solution is to define a new type and dispatch on it. Here's an example: we want to plot the elements of a matrix as lines from the coordinates specified in the first row to to each of the other row. ```{julia} struct StarPlot end import GastonRecipes: DataBlock function convert_args(::Type{StarPlot}, x::Matrix) center = x[1,:] points = DataBlock([stack((center, x[n,:]), dims=1) for n in 2:size(x,1)]...) return PlotRecipe((points,), "w l") end x = rand(10, 2) plot(StarPlot, x) ``` (Of course, the same functionality could be achieved by wrapping `x` in `StarPlot`. The choice of approach is a matter of taste). This recipe also illustrates the use of `DataBlock`. In this case, the data that is given to gnuplot has the following form: ``` x[1,:] x[2,:] x[1,:] x[3,:] x[1,:] x[4,:] .... ``` ================================================ FILE: docs/v2/reference.qmd ================================================ --- title: "API Reference" --- ```{julia} #| echo: false #| output: false using Gaston using QuartoTools: Cell, MarkdownCell function quartodoc(title, doc) docs = string(doc) docs = replace(docs, "\n# Examples\n" => "\n**Examples**\n") docs = replace(docs, "\n# Example\n" => "\n**Example**\n") q = MarkdownCell(""" ::: {.callout-note icon=false title=$(title)} $docs ::: """) end ``` ## Types and constructors ```{julia} #| echo: false quartodoc("Figure", @doc Figure) ``` ## Plot commands ```{julia} #| echo: false quartodoc("plot", @doc plot) ``` ```{julia} #| echo: false quartodoc("plot!", @doc plot!) ``` ```{julia} #| echo: false quartodoc("splot", @doc splot) ``` ```{julia} #| echo: false quartodoc("splot!", @doc splot!) ``` ```{julia} #| echo: false quartodoc("@plot", @doc @plot) ``` ```{julia} #| echo: false quartodoc("@plot!", @doc @plot!) ``` ```{julia} #| echo: false quartodoc("@splot", @doc @splot) ``` ```{julia} #| echo: false quartodoc("@splot!", @doc @splot!) ``` ### Plotting with built-in themes These functions call `plot` behind the scenes, with settings and plotline taken from a built-in theme. ```{julia} #| echo: false quartodoc("scatter", @doc scatter) ``` ```{julia} #| echo: false quartodoc("scatter!", @doc scatter!) ``` ```{julia} #| echo: false quartodoc("stem", @doc stem) ``` ```{julia} #| echo: false quartodoc("stem!", @doc stem!) ``` ```{julia} #| echo: false quartodoc("bar", @doc bar) ``` ```{julia} #| echo: false quartodoc("bar!", @doc bar!) ``` ```{julia} #| echo: false quartodoc("barerror", @doc barerror) ``` ```{julia} #| echo: false quartodoc("barerror!", @doc barerror!) ``` ```{julia} #| echo: false quartodoc("histogram", @doc histogram) ``` ```{julia} #| echo: false quartodoc("imagesc", @doc imagesc) ``` ```{julia} #| echo: false quartodoc("surf", @doc surf) ``` ```{julia} #| echo: false quartodoc("surf!", @doc surf!) ``` ```{julia} #| echo: false quartodoc("scatter3", @doc scatter3) ``` ```{julia} #| echo: false quartodoc("scatter3", @doc scatter3!) ``` ```{julia} #| echo: false quartodoc("wireframe", @doc wireframe) ``` ```{julia} #| echo: false quartodoc("wireframe!", @doc wireframe!) ``` ```{julia} #| echo: false quartodoc("wiresurf", @doc wiresurf) ``` ```{julia} #| echo: false quartodoc("wiresurf!", @doc wiresurf!) ``` ```{julia} #| echo: false quartodoc("surfcontour", @doc surfcontour) ``` ```{julia} #| echo: false quartodoc("contour", @doc contour) ``` ```{julia} #| echo: false quartodoc("heatmap", @doc heatmap) ``` ## Recipes ```{julia} #| echo: false quartodoc("Gaston.convert_args", @doc Gaston.convert_args) ``` ```{julia} #| echo: false quartodoc("Gaston.convert_args3", @doc Gaston.convert_args3) ``` ## Figure management ```{julia} #| echo: false quartodoc("figure", @doc figure) ``` ```{julia} #| echo: false quartodoc("closefigure", @doc closefigure) ``` ```{julia} #| echo: false quartodoc("closeall", @doc closeall) ``` ## Utility functions and macros ```{julia} #| echo: false quartodoc("@Q_str", @doc @Q_str) ``` ```{julia} #| echo: false quartodoc("@gpkw", @doc @gpkw) ``` ```{julia} #| echo: false quartodoc("push!", @doc push!(::Figure, ::Figure)) quartodoc("push!", @doc push!(::Gaston.Axis, ::Gaston.Plot)) quartodoc("push!", @doc push!(::Figure, ::Gaston.Axis)) quartodoc("push!", @doc push!(::Gaston.FigureAxis, ::Gaston.Plot)) quartodoc("push!", @doc push!(::Figure, ::Gaston.FigureAxis)) ``` ## Saving plots ```{julia} #| echo: false quartodoc("save", @doc save) ``` ## Animations ```{julia} #| echo: false quartodoc("animate", @doc animate) ``` ## Non-exported functions and types The following may be useful when extending or developing Gaston. These functions are not part of the official API and may be modified or removed in future versions. ```{julia} #| echo: false quartodoc("Gaston.terminals", @doc Gaston.terminals) ``` ```{julia} #| echo: false quartodoc("Gaston.listfigures", @doc Gaston.listfigures) ``` ```{julia} #| echo: false quartodoc("Gaston.gp_start", @doc Gaston.gp_start) ``` ```{julia} #| echo: false quartodoc("Gaston.gp_quit", @doc Gaston.gp_quit) ``` ```{julia} #| echo: false quartodoc("Gaston.gp_send", @doc Gaston.gp_send) ``` ```{julia} #| echo: false quartodoc("Gaston.gp_exec", @doc Gaston.gp_exec) ``` ```{julia} #| echo: false quartodoc("Gaston.Plot", @doc Gaston.Plot) ``` ```{julia} #| echo: false quartodoc("Gaston.Axis", @doc Gaston.Axis) ``` ```{julia} #| echo: false quartodoc("Gaston.cs2dec", @doc Gaston.cs2dec) ``` ```{julia} #| echo: false quartodoc("Gaston.set!", @doc Gaston.set!) ``` ```{julia} #| echo: false quartodoc("Gaston.meshgrid", @doc Gaston.meshgrid) ``` ================================================ FILE: docs/v2/styles.css ================================================ /* css styles */ ================================================ FILE: docs/v2/tutorial.qmd ================================================ --- title: "Tutorial" --- If you have not installed Gaston yet, then run the following commands in the Julia REPL: ```{.julia} import Pkg.add Pkg.add("Gaston") ``` Load Gaston with: ```{julia} #| output: false using Gaston ``` The plots below have been rendered in a `png` terminal with the following configuration: ```{julia} #| output: false Gaston.config.term = "pngcairo font ',10' size 640,480" ``` We need to specify that the plot examples below will be rendered to a document and not a GUI window: ```{julia} #| output: false Gaston.config.output = :echo ``` In addition, gnuplot's startup file is as [described in the Introduction](index.qmd#gnuplot-startup-file). ## Getting started Let's start with a simple sine wave plot: ```{julia} x = range(0, 0.5, length = 100) y = sin.(2*pi*10*x) plot(x, y) ``` Now, let us add a grid and some annotations: ```{julia} @plot {grid, title = Q"{/:Bold A sine wave}", xlabel = Q"Time", ylabel = Q"Volts"} x y ``` Here we have used `@plot` instead of `plot`, which allows us to specify the plot settings as a list of keyword arguments. These arguments can be stored in a "theme" using the `@gpkw` macro: ```{julia} #| output: false settings = @gpkw {grid, title = Q"{/:Bold A sine wave}", xlabel = Q"Time", ylabel = Q"Volts"} ``` In addition, we have used the `Q` string macro to avoid typing single quotes; `Q"Time"` is converted to `"'Time'"`. Now let us change the line color and markers: ```{julia} @plot settings x y {with = "lp", lc = Q"sea-green", pt = :ecircle, pn = -16, ps = 1.5} ``` Parameters that affect how the curve is plotted are specified *after* the data. These can also be stored and reused, so that ```{julia} #| output: false plotline = @gpkw {with = "lp", lc = Q"sea-green", pt = :ecircle, pn = -16, ps = 1.5} @plot settings x y plotline ``` would produce the exact same plot. Settings and plotline parameters can also be specified as strings; see the [Manual](manual.qmd#settings-and-plotlines) for all the details. Gaston also has a number of built-in [plot styles](tutorial.qmd#included-plot-styles), showcased below. One of the keyword arguments used above is a little peculiar: `pt = :ecircle`. Gnuplot wants point types to be specified as integers, so `:ecircle` (stands for "empty circle") should not be valid. The explanation is that Gaston parses some keyword arguments to provide a more comfortable syntax. In this case, Gaston converts point types specified as symbols according to the table [provided here](manual.qmd#keyword-parsing). It is easier to remember that an empty circle is specified as `:ecircle` (or a full square as `:fsquare`) than as "6" (or "5"). A `plot` command can only generate a single curve. Use `plot!` or `@plot!` to append a curve: ```{julia} @plot(settings, {title = Q"Two sinusoids", key = "columns 1", key = "box outside right top"}, x, y, plotline, {title = "'sin'"}) y2 = cos.(2*pi*10*x) @plot! x y2 {dashtype = Q".-.", lw = 2, lc = Q"orchid", title = Q"cos"} ``` Here we see how multiple settings and plotline arguments can be combined. Note that any axis settings used in `plot!` are ignored. ## Plotting functions In the examples above, the data given to `plot` is stored in vectors. Functions can be plotted directly, with a given range and number of samples, as follows: ```{julia} g(x) = exp(-abs(x/5))*cos(x) # function to plot tt = "set title 'g = x -> exp(-abs(x/5))*cos(x))'" plot(tt, (-10, 10, 200), g) # plot of g from x = -10 to 10, using 200 samples ``` Ranges can be specified in the following alternative ways: ```julia plot(g) # 101 samples, from -10 to 10 plot((a, b), g) # 101 samples, from a to b plot((a, b, c), g) # c samples, from a to b plot(x, g) # g.(x) ``` ## A note on side effects Plot commands return a value of type `Gaston.Figure`. When values of this type are displayed, Julia's display system calls gnuplot behind the scenes to generate the actual plot. Plots are never generated as side effects the way they are, for example, in Matlab. This means that, for example, the following code does not display any plots: ```{.julia} i = 1 while (i < 10) plot(x, i.*y) i += 1 end ``` Calling the function `g` below does not produce any plots either: ```{.julia} function g(x, y) plot(x, y) println("Done.") end ``` The easiest way to "force" a piece of code to generate a plot is to call `display` explicitly: ```{.julia} i = 1 while (i < 10) plot(x, i.*y) |> display i += 1 end ``` ## Variables that store plots Naturally, values of type `Figure` can be stored in variables and manipulated as any other Julia value: ```{.julia} function g(x, y) f = plot(x, y) println("Done.") return f end ``` Internally, Gaston keeps references to all `Figure` values it has produced. If there are one or more, one of them is _active_ in the sense that all subsequent `plot` and `plot!` commands will target that figure. The active figure can be obtained with: ```{.julia} figure() ``` and the figure `f` can be made active with: ```{.julia} figure(f) ``` A new empty figure can be instantiated with the `Figure` constructor, as in `f = Figure()`. The new figure is automatically made the active figure. ## Multiplots A convenient, automatic method to create multiplot figures is provided. First, instantiate a new figure like this: ```{julia} f = Figure(multiplot = "title 'Auto Layout'"); ``` When a figure contains more than one axis, it is rendered using `set multiplot`. The `multiplot` keyword argument provides a flexible way to specify additional settings specific to multiplots. The figure `f` will be rendered by gnuplot using set multiplot title 'Auto Layout' Axes can be added by indexing into the figure: ```{julia} plot(f[1], x, y) # plot x vs y in the first axis plot(f[2], x, sinc.(10x)) # plot sinc(10x) in the second axis ``` It is possible to have empty "slots": ```{julia} plot(f[4], x, sinc.(20x), "w lp pn 12") # the third axis is empty ``` Note that Gaston tries to keep a square figure aspect ratio as more and more axes are included. Add another curve to an axis using indexing: ```{julia} plot!(f[2], x, 0.3randn(length(x))) # add noise curve to second axis ``` A different way to generate a multiplot is to call `plot` on two or more figures, creating a new figure that contains the axes of all arguments, as in this example: ```{julia} f1 = Figure() f2 = Figure() plot(f1, "set title 'sin'", sin) plot(f2, "set title 'cos'", cos) plot(f1, f2) ``` A trick that may be useful in some cases is pushing an axis from one figure to a different figure: ```{julia} plot(f1, "set title 'sin'", sin) plot(f2, "set title 'cos'", cos) plot(f2[2], "set title 'tan'", tan) push!(f1, f2[2]) # inserts plot of tan into f1 ``` To get full control of the layout, pass the argument `autolayout = false` to `Figure`: ```{julia} f = Figure("title 'Arbitrary multiplot layout demo'", autolayout = false) x = randn(100) y = randn(100) @plot(f[1], {margins = (0.1, 0.65, 0.1, 0.65)}, x, y, "w p pt '+' lc 'dark-green'") @gpkw histogram(f[2], {margins = (0.7, 0.95, 0.1, 0.65), tics = false}, y, {lc = Q"dark-green"}, nbins = 10, horizontal = true) @gpkw histogram(f[3], {margins = (0.1, 0.65, 0.7, 0.9), boxwidth = "1 relative"}, x, {lc = Q"dark-green"}, nbins = 10) ``` Note that the `margins` keyword followed by a tuple is parsed as ([see here](manual.qmd#keyword-parsing)): ```julia """set lmargin at screen 0.7 set rmargin at screen 0.95 set bmargin at screen 0.1 set tmargin at screen 0.65 """ ``` ## Closing figures We can close all figures created so far with ```{julia} closeall() ``` This command closes all gnuplot processes started by Gaston and closes all figures. Close figure `f` with ```{.julia} close(f) ``` ## 3-D Plots Plotting in 3-D is similar to 2-D, except that `splot` (and `@splot`, `splot!`, `@splot!`) are used instead of `plot`. This example shows how to plot the surface defined by function `s`: ```{julia} x = y = -15:0.2:15 s = (x,y) -> @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) @splot "set title 'Sombrero'" "set hidden3d" {palette = :cool} x y s "w pm3d" ``` The palette `cool` is defined in [ColorSchemes](https://github.com/JuliaGraphics/ColorSchemes.jl). Together with the `palette` keywork, any color scheme from that package can be used simply by prepending its name with `:`. ## Plotting in text terminals It is often convenient to generate plots directly in the terminal. Gnuplot supports a few different ways to do this: * `sixelgd` uses [sixels](https://en.wikipedia.org/wiki/Sixel) to generate plots almost identical to those produced by regular GUI terms: ![sixel](assets/sixelgd.png) * `block` uses Unicode characters to draw a plot in the terminal: ![block](assets/block.png) Note that, in all cases, Gaston must be configured for terminal output with ```{.julia} Gaston.config.output = :echo ``` There are other text terms, such as `dumb`, but in general those produce output of worse quality than `sixelgd` and `block`. ## Plotting with strings and dates Besides numerical arrays and ranges, Gaston can also plot arrays of strings, as shown in the following example: ```{julia} x = 10*rand(10) y = 10*rand(10) w = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"] plot(x, y, w, "w labels") ``` Julia's `Date` type can be plotted by converting it to strings, and using gnuplot's date support. The following code is inspired in [this example](https://lazarusa.github.io/gnuplot-examples/examples/2d/lines/dates). ```{julia} using Dates dates = Date(2018, 1, 1):Day(1):Date(2019, 12, 31) ta = 0.5*rand(length(dates)) timefmt = "%Y-%m-%d" pfmt = "%Y-%m-%d" tempo = string.(dates) # convert dates to strings xmin1 = "2018-02-01" xmax1 = "2019-04-01" @gpkw settings = {xdata = "time", timefmt = "'$(timefmt)'", grid, format = "x '$(pfmt)'", xtics = "rotate by -35", xrange = "['$(xmin1)':'$(xmax1)']", yrange = (-0.25, 0.75)} plot(settings, tempo, ta, "u 1:2 w l t 'Activity per day'") ``` ## Plotting using Latex Gnuplot provides a few different ways to interface with Latex. This example shows how to do it using the `cairolatex` terminal. The process is as follows: first, generate a plot and save it using `term = cairolatex`. This generates two files: the plot without any text, tics, or markings, in pdf format, and a `.tex` file that can be included in a Latex document. This file will add text to the pdf image using Latex, resulting in a plot that integrates well with the rest of the document. First, generate the plot. Note that the Latex string `equation` is added as a label to the plot, causing Latex to render it. ```{.julia} equation = raw"""\begin{minipage}[c]{\textwidth}\begin{equation*}""" * raw"""\sin(x) = \sum_0^{+\infty} \frac{(-1)^n}{(2n + 1)!} x^{2n+1}""" * raw"""\end{equation*} \end{minipage}""" s = @gpkw { title = Q"Polynomial approximation of sin(x)", style = "fill transparent solid 0.6 noborder", xtics = (positions = [-pi, -pi/2, 0, pi/2, pi], labels = [raw"$-\pi$", raw"$-\pi/2$", "0", raw"$\pi/2$", raw"$\pi$"]), xrange = (-3.8, 3.8), yrange = (-1.5, 1.5), key = "box opaque left horiz", linetype = :YlOrBr_7, grid = "front", label = "at graph 0.62,0.2 front center '$equation'" } x = range(-2pi, 2pi, 1000) y = sin.(x) @plot s x y x "w filledcurve t 'n=0' lt 1" @plot! x y x .- x.^3/6 "w filledcurve t 'n=1' lt 2" @plot! x y x .- x.^3/6 .+ x.^5/120 "w filledcurve t 'n=2' lt 3" @plot! x y x .- x.^3/6 .+ x.^5/120 .- x.^7/5040 "w filledcurve t 'n=3' lt 4" @plot! x y "w l t 'sin(x)' lw 2 lc rgb 'black'" save(term="cairolatex pdf input color dashed size 5in,3.3in", filename = "test.tex") ``` This code creates two files, `test.tex` and `test.pdf`, which can be used to generate a final pdf plot by compiling the following script with `pdflatex`: ```{.latex} \documentclass{article} \usepackage{amsmath} \usepackage{graphicx} \usepackage{color} \begin{document} \begin{figure} \input{test.tex} \end{figure} \end{document} ``` The finished plot looks like this: ![](assets/cairolatex.png) Other gnuplot terminals, such as `tikz`, `epslatex`, etc, can be used with similar workflows. This example is inspired in [this code](https://gcalderone.github.io/Gnuplot.jl/stable/terminals/#The-cairolatex-terminal). ## Gnuplot datasets and tables Some kinds of plots require a way to specify exactly what points to plot in a specific format (instead of relying on Gaston to format the data the right way). This can be accomplished with `Gaston.Datatable`, which wraps `IOBuffer`. The contents of `Datatable` are provided to gnuplot without any further processing. One example is drawing contour lines on a heatmap (taken from [this gnuplot blog post](https://gnuplot-tricks.blogspot.com/2009/07/maps-contour-plots-with-labels.html)). The function `Gaston.plotwithtable` returns a `Gaston.DataBlock`, which can be used as an argument to `plot`. ```{julia} # define function to plot x = y = range(-5, 5, 100) f4(x,y) = sin(1.3x) * cos(0.9y) + cos(0.8x) * sin(1.9y) + cos(0.2x*y) # obtain function contours using 'plot with table' settings = """set contour base set cntrparam level incremental -3, 0.5, 3 unset surface""" contours = Gaston.plotwithtable(settings, x, y, f4) # calculate meshgrid for heatmap plot z = Gaston.meshgrid(x, y, f4) # plot heatmap and contours plot("""unset key unset colorbox set palette rgbformulae 33,13,10""", x, y, z, "with image") plot!(contours, "w l lw 1.5 lc 'slategray'") ``` Another example is drawing polygons or polytopes, where the data provided to gnuplot are the coordinates of the vertices of each face, and faces are separated by newlines. The following example (adapted from the demos in gnuplot's official site) draws an icosahedron using the plotline `with polygon`. ```{julia} # The icosahedron has twelve vertices, given by these coordinates phi = (1 + sqrt(5)) / 2 ico = ( ( 0, 1, phi), #1 ( 0, 1, -phi), #2 ( 0, -1, phi), #3 ( 0, -1, -phi), #4 ( 1, phi, 0), #5 ( 1, -phi, 0), #6 (-1, phi, 0), #7 (-1, -phi, 0), #8 ( phi, 0, 1), #9 (-phi, 0, 1), #10 ( phi, 0, -1), #11 (-phi, 0, -1) #12 ) # plot settings s = """unset border unset key set view equal xyz unset xtics unset ytics unset ztics set pm3d depthorder set pm3d interpolate 1,1 flush begin noftriangles border linecolor rgb "black" linewidth 2 dashtype solid corners2color mean set title "An icosahedron drawn as 20 individual faces" set style fill transparent solid 0.8 set view 69, 33 """ # write the vertices that make up each face in a Datatable faces = Gaston.DataBlock(stack([ico[i] for i in (1, 5, 7)], dims=1), stack([ico[i] for i in (1, 7, 10)], dims=1), stack([ico[i] for i in (1, 10, 3)], dims=1), stack([ico[i] for i in (1, 3, 9)], dims=1), stack([ico[i] for i in (1, 9, 5)], dims=1), stack([ico[i] for i in (2, 5, 11)], dims=1), stack([ico[i] for i in (2, 11, 4)], dims=1), stack([ico[i] for i in (2, 4, 12)], dims=1), stack([ico[i] for i in (2, 12, 7)], dims=1), stack([ico[i] for i in (2, 7, 5)], dims=1), stack([ico[i] for i in (8, 3, 10)], dims=1), stack([ico[i] for i in (8, 10, 12)], dims=1), stack([ico[i] for i in (8, 12, 4)], dims=1), stack([ico[i] for i in (8, 4, 6)], dims=1), stack([ico[i] for i in (8, 6, 3)], dims=1), stack([ico[i] for i in (7, 12, 10)], dims=1), stack([ico[i] for i in (6, 4, 11)], dims=1), stack([ico[i] for i in (5, 9, 11)], dims=1), stack([ico[i] for i in (9, 6, 11)], dims=1), stack([ico[i] for i in (9, 3, 6)], dims=1)) # plotline wp = "with polygons fc rgb 'gray'" # splot is used since the icosahedron is 3-D splot(s, faces, wp) ``` Gaston provides a few `Datatable` constructors, described in the [Manual](manual.qmd#plot-with-table). ## Animations Animations require using a terminal that support them; the `gif` or `webp` terminals are the most popular ones (make sure your notebook supports the `image/webp` MIME type before using it). Creating an animation is similar to multiplotting: multiple axes are drawn on the same figure. When the gnuplot terminal contains the `animate` option, however, the plot is rendered as an animation. Note that gnuplot will output a message to `STDERR` indicating how many frames were recorded; this message is purely informative and not actually an error. The following examples illustrate how to create and display animations, in this case with a background image: ```{.julia} frames = 75 # number of animation frames # new, empty figure f = Figure() # create a background curve that is shown in all frames x_bckgnd = range(-1, 1, 200) # x values for the background image y_bckgnd = sin.(2π*2*x_bckgnd) bckgnd = Gaston.Plot(x_bckgnd, y_bckgnd, "lc 'black'") # background curve # generate all frames x = range(-1, 1, frames) for i in 1:frames # first plot the function... plot(f[i], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") # ... then add the background push!(f[i], bckgnd) end for i in frames:-1:1 # in reverse plot(f[2frames-i+1], x[i], sin(2π*2*x[i]), "w p lc 'orange' pt 7 ps 7") push!(f[2frames-i+1], bckgnd) end save(f, filename = "2DAnim.webp", term = "webp animate loop 0 size 640,480") ``` ![](assets/2DAnim.webp) First, an empty figure `f` is created with `Figure()`. Then, [`Gaston.Plot`](reference.qmd) is used to create an object, `bckgnd`, which contains a curve (a black sine wave) and which can be inserted into an axis. Every loop iteration, a new axis is inserted into `f` with `plot(f[i], ...)` with a curve consisting of an orange circle somewhere along the sine wave. Then, the background curve is inserted into the same axis with `push!(f[i], bckgnd)`. Finally, the plot is saved in a format that supports animation (`webp` in this case). A difficulty arises when mixing plot formats in a notbook (say, `png` and `webp`): the terminal is specified in the configuration variable `Gaston.config.term`. However, some notebook programs (such as Pluto) execute cells in arbitrary order. This means that changing the terminal in one cell may affect other cells. To solve this problem, Gaston provides a way to ignore the global terminal configuration when rendering a plot. A figure `f` can be rendered with a given terminal by calling `animate(f, term)`. The default value of `term` is stored in `Gaston.config.altterm` and defaults to `gif animate loop 0`. Examples are provided in these [interactive Pluto notebooks](https://github.com/mbaz/Gaston.jl/tree/master/notebooks). ## Themes Gaston supports _themes_, which are pre-configured plot styles. There are two kinds of themes: _settings themes_, which specify gnuplot `set` commands, and _plotline themes_, which specify how a particular curve is displayed (color, thickness, etc.) Settings themes are stored in the dictionary `Gaston.sthemes`, and plotline themes are stored in `Gaston.pthemes`. The themes in these dictionaries can be modified, and new themes can be stored in them. Built-in themes used to create common plot styles (such as `scatter` and `stem`) are described in [the next section](tutorial.qmd#included-plot-styles). Gaston also includes a few settings themes that conveniently define common configurations: | theme | purpose | |-------|---------| | `:notics` | Remove all tics from the plot. | | `:labels` | Label axes using `x`, `y`, and `z` (for `splot`) | | `:unitranges` | Set `x`, `y`, and `z` ranges to `[-1:1]` | | `:nocb` | Disable the colorbox | This example shows how to use these themes: ```{julia} plot(:notics, :labels, "set grid", sin) ``` ## Included plot styles Gaston includes several themes for common plot styles. The easiest way to use them is through the specialized plot commands described below. For more details, see the [manual](manual.qmd#simple-themes). The themed commands described below use combinations of these themes to create a specific type of plot. Note that, in gnuplot, plotlines (as in `with lines linecolor 'green'`) are especially difficult to theme, because repeated options (and options given in the wrong order) are errors. It is recommended to keep plotline themes very simple, and specify the plotline manually as part of the plot command. The following subsections cover all included plot styles, along with the list of built-in themes they rely on, as well as some examples. ### Scatter plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`scatter` | none | `:scatter` | |`scatter3` | `:scatter3` | `:scatter` | ```{julia} # reset theme #| hide_line @gpkw Gaston.pthemes[:scatter] = {with = "points", pointtype = :fcircle, pointsize = 1.5} #| hide_line xg = randn(20) yg = randn(20) scatter("set title 'Scatter plot' set key outside", xg, yg, "title 'gaussian'") xu = rand(20) yu = rand(20) scatter!(xu, yu, "title 'uniform'") ``` A 3-D scatter plot (the default settings theme (`:scatter3`) draws all the borders): ```{julia} scatter3("set title 'A 3-D scatter plot", randn(10), randn(10), randn(10)) ``` ### Stem plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`stem` | none | `:stem`, `:impulses` | Stem plots are often used in digital signal processing applications to represent a discrete-time (sampled) signal. ```{julia} stem("set title 'Stem plot'", g) ``` To generate a stem plot, gnuplot actually plots twice: once with style `impulses` and once with `points` (set to empty circles). Normally, each of these plots would have a different color. To use the same color for both, use the `color` keyword argument: ```{julia} stem("set title 'Stem plot'", g, color = "'goldenrod'") ``` The circular marks can be omitted with the `onlyimpulses` keyword argument: ```{julia} stem("set title 'Stem plot with onlyimpulses'", g, onlyimpulses = true) ``` ### Bar plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`bar` | `:boxplot` | `:box` | |`barerror` | `:boxerror` | `:box` | ```{julia} bar("set title 'Bar plot'", rand(10), "lc 'turquoise'") ``` This example shows how to plot two sets of bars, using `bar!`: ```{julia} bar("set title 'Two bar plots'", rand(10), "lc 'dark-violet'") bar!(1.5:10.5, 0.5*rand(10), "lc 'plum' fill pattern 4") ``` Error bars are handled by `barerror`; there is also `barerror!`. ```{julia} barerror("set title 'Error bars plot'", 1:10, rand(10), 0.1*rand(10).+0.1, "lc 'sandybrown'") ``` ### Histograms | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`histogram` | `:histplot` | `:box`, `:horhist` (1-D); `:image` (2-D) | The `histogram` function takes these optional keyword arguments: * `nbins`: specifies the number of bins. Defaults to 10. * `mode::Symbol`: Controls histogram normalization mode; passed to [`StatsBase.normalize`](https://juliastats.org/StatsBase.jl/stable/empirical/#LinearAlgebra.normalize). Defaults to `:none`. * `edges`: a vector or a range specifying the bin edges; if specified, takes precedence over `nbins`. Defaults to `nothing`. * `horizontal::Bool`: if `true`, the histogram is drawn horizontally. Defaults to `false`. `histogram` uses the settings theme `:histplot`, and plotline themes `:box` or `:horhist`. 2-D histograms are supported, by passing two datasets. Using `nbins`: ```{julia} histogram("set title 'Histogram (nbins)'", randn(10_000), nbins = 20, mode = :pdf) ``` Using `edges`: ```{julia} histogram("set title 'Histogram (edges)'", 0.75*randn(10_000), edges = -2:0.75:3, "lc 'dark-khaki'") ``` A horizontal histogram: ```{julia} histogram("set title 'horizontal histogram'", rand(1000), nbins = 15, horizontal = true, "lc 'orchid'") ``` In the case of 2-D histograms, `nbins` or `egdes` may be a tuple; otherwise, both axes use the same settings. The plotline theme is `:image`. ```{julia} x = 2.5*randn(100_000) y = 2.5*randn(100_000) th = @gpkw {palette = :matter, colorbox = false, title = Q"2-D histogram", xrange = (-10, 10), yrange = (-10, 10)} histogram(th, x, y, nbins = 50, mode = :pdf) ``` ### Images | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`imagesc` | `:imagesc` | `:image`, `:rgbimage` | Arrays may be plotted as images using `imagesc`. Note that, in contrast to other plotting packages, the first data row is plotted horizontally and at the top. ```{julia} X = [0 1 2 3; 0 3 2 1; 0 2 2 0; 3 0 0 0] imagesc("unset xtics", "unset ytics", X) ``` To display the image as grayscale, use the `gray` palette. ```{julia} using Images, TestImages img = testimage("lake_gray"); ii = channelview(img)[1,:,:].*255; @gpkw imagesc({palette = :gray}, ii) ``` An RGB image is a plot of a 3-D array, where `[1,;,:]` is the red channel, `[2,:,:]` is the green channel, and `[3,:,:]` is the blue channels. ```{julia} img = testimage("lake_color") @gpkw imagesc({size = "square", autoscale = "fix"}, channelview(img).*255) ``` ### Surfaces | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`wireframe` | `:hidden3d` | none | |`surf` | `:hidden3d` | `:pm3d` | A surface can be plotted as a "wireframe" (or a "mesh") with the `wireframe` command. By default, `hidden3d` is active, so that elements behind the surface are not plotted. ```{julia} g(x,y) = sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) th = @gpkw {title = Q"Sombrero Wireframe", palette = :matter} wireframe(th, (-15, 15, 30), g) ``` Solid surfaces are plotted with `surf`: ```{julia} th = @gpkw {title = Q"Sombrero Surface", palette = :matter} surf(th, (-15, 15, 200), g) ``` When plotting a function and a single range (such as `(-15, 15, 200)` above) is given, it is used for both `x` and `y` coordinates. Two ranges may be given as well to control the `x` and `y` ranges separately: ```{julia} surf(th, (-15, 15, 200), (-25, 5, 200), g) ``` ### Contour plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`contour` | `:contour` | `:labels` | | `surfcountour` | `:contourproj` | `:labels` | By default, contour plots include numerical labels: ```{julia} h(x,y) = cos(x/2)*sin(y/2) contour("set title 'Contour Plot'", (-10, 10, 50), h) ``` To plot contours without labels, use the keyword argument `labels = false`: ```{julia} contour("set title 'Contour Plot Without Labels'", (-10, 10, 50), h, labels = false) ``` It's possible to plot a wireframe surface and a contour projected on the base of the plot using `surfcountour`: ```{julia} surfcontour("set title 'Surface With Projected Contours'", (-5, 5, 40), h, "lc 'orange'") ``` The same plot without contour labels: ```{julia} surfcontour("set title 'Surface With Contours, No Labels'", (-5, 5, 40), h, "lc 'orange'", labels = false) ``` ### Heatmap plots | command | settings theme | plotline theme | |:--------|:---------------|:---------------| |`heatmap` | `:heatmap` | `:pm3d` | ```{julia} theme = @gpkw {palette = :matter, title = Q"Heatmap"} heatmap(theme, :notics, :nocb, :labels, (-10, 10, 70), h) ``` ================================================ FILE: src/Gaston.jl ================================================ ## Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. module Gaston # Basic commands export @Q_str#, @gpkw export @plot, @plot!, @splot, @splot! export plot, plot!, splot, splot! export figure, closefigure, closeall # Types export Figure # 2-D styled plots export scatter, stem, bar, barerror, histogram, imagesc export scatter!, stem!, bar!, barerror! # 3-D styled plots export surf, scatter3, wireframe, wiresurf, surfcontour, contour, heatmap export surf!, scatter3!, wireframe!, wiresurf! # Saving and animations export save, animate # New methods are added to these functions import Base: show, showable, getindex, isempty, push!, setindex!, length # Methods for recipes using GastonRecipes import GastonRecipes: PlotRecipe, AxisRecipe, FigureRecipe, AbstractFigure, DataBlock, convert_args, convert_args3, @gpkw, expand, prockey, procopts export @gpkw # Methods from other packages #using MacroTools: postwalk, @capture using Base: keys using StatsBase: fit, Histogram, normalize using DelimitedFiles: writedlm using ColorSchemes: colorschemes, get, resample, ColorScheme, RGB, RGBA using PrecompileTools import Preferences import Gnuplot_jll const GNUPLOT_VERSION = Ref(v"0.0.0") # URL for web-hosted javascript files, for svg and canvas interactivity const JSDIR = "'https://cdn.jsdelivr.net/gh/mbaz/gnuplot-js@1.0/'" # load files include("gaston_options.jl") include("gaston_figures.jl") include("gaston_aux.jl") include("gaston_plot.jl") include("gaston_builtinthemes.jl") include("gaston_recipes.jl") include("gaston_llplot.jl") # Figure storage struct FigureStore figs::Vector{Figure} end getindex(fs::FigureStore, args...) = getindex(fs.figs, args...) length(fs::FigureStore) = length(fs.figs) # State Base.@kwdef mutable struct State figures = FigureStore(Figure[]) # figure storage enabled = false # is gnuplot installed on this system? activefig = nothing # handle of active figure end state = State() getindex(s::State, args...) = getindex(s.figures, args...) length(s::State) = length(s.figures) activefig() = state.activefig # Configuration # embedhtml::Bool controls whether a figure is embedded in html when displayed in a notebook # output::Symbol # :external -> display figures in separate gnuplot windows # :null -> do not display anything # :echo -> display plot as text back to terminal (for notebooks, sixel and text) # term::String -> terminal to use # exec::Cmd -> gnuplot executable Base.@kwdef mutable struct Config embedhtml :: Bool = false output :: Symbol = :external term :: String = "" altterm :: String = "gif animate loop 0" alttoggle :: Bool = false exec :: Union{Nothing,Cmd} = gnuplot_path() end config = Config() # Determine if running in a notebook environment function isnotebook() # Comment the following two lines, and uncomment the two below, to run JET tests if (isdefined(Main, :IJulia) && Main.IJulia.inited) || (isdefined(Main, :Juno) && Main.Juno.isactive()) || # if isdefined(Main, :IJulia) || # isdefined(Main, :Juno) || isdefined(Main, :VSCodeServer) || isdefined(Main, :PlutoRunner) return true end return false end # initialize gnuplot function __init__() global config, state if "JULIA_GNUPLOT_EXE" in keys(ENV) config.exec = Cmd([ENV["JULIA_GNUPLOT_EXE"]]) end try ver_str = replace(read(`$(config.exec) --version`, String), "gnuplot" => "", "patchlevel" => "", "." => " ") |> strip |> split GNUPLOT_VERSION[] = VersionNumber(parse.(Int, ver_str)...) state.enabled = true catch @warn "Gnuplot is not available on this system. Gaston will be unable to produce any plots." end # This configuration depends on run-time state config.output = isnotebook() ? :echo : :external config.term = isnotebook() ? "pngcairo" : "" return nothing end @compile_workload begin if state.enabled config.output = :null f = Figure() y = 1.1:0.5:10.6 plot(y) plot!(y) plot(f[2], y) @plot({grid}, y, {w = "l", lc = Q"red"}) f1 = (x,y) -> sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) splot(f, (-5,5), f1) save(f, term = "png", filename = "test.png") rm("test.png") closeall() gp_quit(f) end end end ================================================ FILE: src/gaston_aux.jl ================================================ # Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. # Auxiliary, non-exported functions are declared here. # from github.com/JuliaPackaging/Preferences.jl/blob/master/README.md: # "Preferences that are accessed during compilation are automatically marked as compile-time preferences" # ==> this must always be done during precompilation, otherwise # the cache will not invalidate when preferences change const gnuplot_binary = Preferences.load_preference(Gaston, "gnuplot_binary", "artifact") const max_lines = string(typemax(Int32) ÷ 2) const magic = "Gaston-4b11ee91-296f-5714-9832-002c20994614" function gnuplot_path() return if gnuplot_binary in ("artifact", "jll") addenv( Gnuplot_jll.gnuplot(), "LINES" => max_lines, "PAGER" => nothing, "GNUPLOT_DRIVER_DIR" => dirname(Gnuplot_jll.gnuplot_fake_path) ) elseif Sys.isexecutable(gnuplot_binary) addenv(Cmd([gnuplot_binary]), "LINES" => max_lines) else @debug gnuplot_binary nothing end end """ Gaston.gp_start()::Base.Process Returns a new gnuplot process. """ function gp_start() if state.enabled inp = Base.PipeEndpoint() out = Base.PipeEndpoint() err = Base.PipeEndpoint() process = run(config.exec, inp, out, err, wait=false) return process else @warn "gnuplot is not available on this system." end end """ Gaston.gp_quit(process::Base.Process) End gnuplot process `process`. Returns the exit code." """ function gp_quit(process::Base.Process) if state.enabled if process_running(process) write(process, "exit gnuplot\n") close(process.in) wait(process) end return process.exitcode else @warn "gnuplot is not available on this system." end end """ Gaston.gp_quit(f::Figure) End the gnuplot process associated with `f`. Returns the process exit code. """ gp_quit(f::Figure) = gp_quit(f.gp_proc) """ gp_send(process::Base.Process, message::String) Send string `message` to `process` and handle its response. """ function gp_send(process::Base.Process, message::String) if state.enabled if process_running(process) message *= '\n' write(process, message) # send user input to gnuplot @debug "String sent to gnuplot:" message # ask gnuplot to return sigils when it is done write(process, """\ set print '-' print '$magic' printerr '$magic' """ ) gpout = readuntil(process, magic) |> rstrip gperr = readuntil(process.err, magic) |> rstrip # handle errors process_running(process) || @warn "gnuplot crashed." isempty(gperr) || @info "gnuplot returned a message in STDERR:$gperr" return gpout * '\n', gperr * '\n' else @warn "Tried to send a message to a process that is not running" return nothing end else @warn "gnuplot is not available on this system." end end """ gp_send(f::Figure, message) Send string `message` to the gnuplot process associated with `f` and handle its response. """ gp_send(f::Figure, message) = gp_send(f.gp_proc, message) """ gp_exec(message::AbstractString) Run an arbitrary gnuplot command and return gnuplot's stdout. """ function gp_exec(message::AbstractString) if state.enabled p = gp_start() (gpout, gperr) = gp_send(p, message) gp_quit(p) return gpout[1:end-11] else @warn "gnuplot is not available on this system." end end """ save(f::Figure = figure(); filename = nothing, term = "pngcairo font ',7'")::Nothing Save figure `f` using the specified terminal `term` and `filename`. If `filename` is not provided, the filename used is `"figure-handle.ext`, where `handle = f.handle` and `ext` is given by the first three characters of the current terminal. """ function save(f::Figure = figure() ; filename = nothing, term = "pngcairo font ',7'") # determine file name if isnothing(filename) # determine extension ext = split(term)[1] if length(ext) > 2 ext = ext[1:3] end filename = "figure-$(f.handle)."*ext end producefigure(f ; filename, term) return nothing end """ save(handle; filename = nothing, term = "pngcairo font ',7'")::Nothing Save the figure with the given handle. """ function save(handle ; kwargs...) f = figure(handle) save(f::Figure ; kwargs...) end """ Gaston.terminals() Return a list of available gnuplot terminals""" function terminals() t = gp_exec("set term") if !(Gaston.config.output == :null) print(t) end end """ Gaston.debug(d::Bool) Enable or disable debug mode.""" function debug(flag) if flag ENV["JULIA_DEBUG"] = Gaston else ENV["JULIA_DEBUG"] = "" end return nothing end "Restore default configuration." function reset() global config config.embedhtml = false config.output = :external config.term = "" config.exec = gnuplot_path() end """ meshgrid(x, y, z) Return a z-coordinate matrix from `x`, `y` coordinates and a function `f`, such that `z[row,col] = f(x[row], y[col])` """ meshgrid(x, y, f::F) where {F<:Function} = [f(xx,yy) for yy in y, xx in x] """ hist(s::Vector{T}, nbins=10, norm=false) where {T<:Real} Return the histogram of values in `s` using `nbins` bins. If `normalize` is true, the values of `s` are scaled so that `sum(s) == 1.0`. """ function hist(s ; edges = nothing, nbins = 10, norm::Bool = false, mode::Symbol = :pdf) if edges === nothing # Unfortunately, `StatsBase.fit` regards `nbins` as a mere suggestion. # Therefore, we need to give it the actual edges. (ms, Ms) = extrema(s) if Ms == ms # compute a "natural" scale g = (10.0^floor(log10(abs(ms)+eps()))) / 2 ms, Ms = ms - g, ms + g end edges = range(ms, Ms, length=nbins+1) end h = fit(Histogram, s, edges) norm && (h = normalize(h, mode=mode)) @debug "hist():" nbins collect(edges) h.weights return h end # 2D histogram function hist(s1, s2 ; edges = nothing, nbins = (10, 10), mode :: Symbol = :none) edg = edges if edges === nothing (ms1, Ms1) = extrema(s1) if Ms1 == ms1 g = (10.0^floor(log10(abs(ms1)+eps()))) / 2 ms1, Ms1 = ms1 - g, ms1 + g end edges1 = range(ms1, Ms1, length=nbins[1]+1) (ms2, Ms2) = extrema(s2) if Ms2 == ms2 g = (10.0^floor(log10(abs(ms2)+eps()))) / 2 ms2, Ms2 = ms2 - g, ms2 + g end edges2 = range(ms2, Ms2, length=nbins[2]+1) edg = (edges1, edges2) elseif !(edges isa Tuple) edg = (edges, edges) end h = fit(Histogram, (s1, s2), edg) h = normalize(h, mode = mode) @debug "hist():" nbins collect(edges) h.weights return h end function showable(::MIME{mime}, f::Figure) where {mime} rv = false if config.alttoggle t = split(config.altterm)[1] else t = split(config.term)[1] end h = config.embedhtml if (mime == Symbol("image/gif")) && (t == "gif") rv = true end if (mime == Symbol("image/webp")) && (t == "webp") rv = true end if (mime == Symbol("image/png")) && (t == "png" || t == "pngcairo") rv = true end if (mime == Symbol("image/svg+xml")) && (t == "svg") rv = true end if (mime == Symbol("text/html")) && (t == "svg" || t == "canvas") && h rv = true end @debug "showable():" mime config.term config.embedhtml t rv return rv end function show(io::IO, figax::FigureAxis) println(io, "Gaston.Axis with $(length(figax.f.axes)) ax(es),") println(io, " stored in figure with handle ", figax.f.handle) end function show(io::IO, a::Axis) p = length(a) == 1 ? "plot" : "plots" println(io, "Gaston.Axis ($(a.is3d ? "3D" : "2D")) with $(length(a.plots)) $p") end function show(io::IO, p::Plot) println(io, "Gaston.Plot with plotline: \"$(p.plotline)\"") end function internal_show(io::IO, f::Figure) @debug "internal_show()" config.term config.output state.enabled # handle cases where no output is produced state.enabled || return nothing config.output == :null && return nothing # verify figure's gnuplot process is running if !process_running(f.gp_proc) error("gnuplot process associated with figure handle $(f.handle) has exited.") end if isempty(f) println(io, "Empty Gaston.Figure with handle ", f.handle) return nothing end # echo mode: save plot, read it and write to io if config.output == :echo @debug "Notebook plotting" config.term config.embedhtml tmpfile = tempname() producefigure(f, filename = tmpfile, term = config.term) while !isfile(tmpfile) end # avoid race condition with read in next line if config.embedhtml println(io, "") end write(io, read(tmpfile)) if config.embedhtml println(io, "") end rm(tmpfile, force=true) # external mode: create graphical window elseif config.output == :external producefigure(f) end return nothing end function producefigure(f::Figure ; filename::String = "", term = config.term) iob = IOBuffer() # Determine which terminal to use. Precedence order is: # * `config.altterm` if `config.alttoggle` (highest) # * function kw argument `term` # * default global value `config.term` (lowest) if config.alttoggle term = config.altterm config.alttoggle = false end if term isa Symbol term = String(term) end animterm = contains(term, "animate") # this is an animation, not a multiplot # auto-calculate multiplot layout if mp_auto is true # note: here I'm trying to be clever, there may be undiscovered edge cases autolayout = "" if f.autolayout if length(f) <= 2 rows = 1 cols = length(f) elseif length(f) <= 4 rows = 2 cols = 2 else cols = ceil(Int, sqrt(length(f))) rows = ceil(Int, length(f)/cols) end autolayout = " layout $rows, $cols " end write(iob, "reset session\n") # if term is different than config.term, push it, and pop it after plotting is done term != config.term && write(iob, "set term push\n") term != "" && write(iob, "set term $(term)\n") # if saving the plot filename != "" && write(iob, "set output '$(filename)'\n") # handle multiplot (length(f) > 1 && !animterm) && write(iob, "set multiplot " * autolayout * f.multiplot * "\n") for axis in f.axes if isempty(axis) if f.autolayout write(iob, "set multiplot next\n") end continue else write(iob, axis.settings*"\n") write(iob, plotstring(axis)*"\n") # send plotline end end (length(f) > 1 && !animterm) && write(iob, "unset multiplot\n") filename != "" && write(iob, "set output\n") term != config.term && write(iob, "set term pop\n") seekstart(iob) gp_send(f, String(read(iob))) end Base.show(io::IO, ::MIME"text/plain", x::Figure) = internal_show(io, x) Base.show(io::IO, ::MIME"text/html", x::Figure) = internal_show(io, x) Base.show(io::IO, ::MIME"image/png", x::Figure) = internal_show(io, x) Base.show(io::IO, ::MIME"image/gif", x::Figure) = internal_show(io, x) Base.show(io::IO, ::MIME"image/webp", x::Figure) = internal_show(io, x) Base.show(io::IO, ::MIME"image/svg+xml", x::Figure) = internal_show(io, x) Base.show(io::IO, x::Figure) = internal_show(io, x) # build a string with plot commands according to configuration function plotstring(a::Axis) # We have to insert "," between plot commands. pstring = Vector{String}() for p in a.plots push!(pstring, " '$(p.datafile)' " * p.plotline) end command = "plot " a.is3d && (command = "splot ") command * join(pstring, ", ") end # Calculating palettes is expensive, so store them in a cache. The cache is # pre-populated with gnuplot's gray palette. The key is `(name, rev)`, where # `name` is the palette name and `rev` is true if the palette is reversed. Palette_cache = Dict{Tuple{Symbol, Bool}, String}((:gray, false) => "set palette gray") # Calculating linetypes from a colorscheme is expensive, so we use a cache. Linetypes_cache = Dict{Symbol, String}() # Convert a symbol to string, converting all '_' to spaces and surrounding it with ' ' function symtostr(s) s isa Symbol || return s s = string(s) return "'$(join(split(s,"_")," "))'" end function strstr(s) s isa String || return s return "'$s'" end parse_settings(x) = x function parse_settings(s::Vector{<:Pair})::String @debug "parse_settings" s # Initialize string that will be returned settings = String[] for (key::String, v) in s if v isa Bool v && push!(settings, "set $key") v || push!(settings, "unset $key") elseif key ∈ ("xtics", "ytics", "ztics", "tics") # tics if v isa AbstractRange push!(settings,"set $key $(first(v)),$(step(v)),$(last(v))") elseif v isa Tuple push!(settings, "set $key $v") elseif v isa NamedTuple ticstr = "(" for i in eachindex(v.positions) ticstr *= string("'", v.labels[i], "' ", v.positions[i], ", ") end ticstr *= ")" push!(settings,"set $key $ticstr") else push!(settings,"set $key $v") end elseif key ∈ ("xrange", "yrange", "zrange", "cbrange") # ranges if v isa Vector || v isa Tuple push!(settings, "set $key [$(ifelse(isinf(v[1]),*,v[1])):$(ifelse(isinf(v[2]),*,v[2]))]") else push!(settings, "set $key $v") end elseif key == "ranges" if v isa Vector || v isa Tuple r = "[$(ifelse(isinf(v[1]),*,v[1])):$(ifelse(isinf(v[2]),*,v[2]))]" push!(settings, "set xrange $r\nset yrange $r\nset zrange $r\nset cbrange $r") else push!(settings, "set xrange $v\nset yrange $v\nset zrange $v\nset cbrange $v") end elseif key ∈ ("pal", "palette") # palette; code inspired by @gcalderone's Gnuplot.jl rev = false if v isa Tuple if v[2] == :reverse rev = true end v = v[1] end if v isa ColorScheme || v isa Symbol if v isa Symbol if haskey(Palette_cache, (v, rev)) push!(settings, Palette_cache[(v, rev)]) continue else cm = colorschemes[v] end else cm = v end colors = String[] if rev r = range(1, 0, length(cm)) else r = range(0, 1, length(cm)) end for i in 1:length(cm) c = get(cm, r[i]) push!(colors, "$i $(c.r) $(c.g) $(c.b)") end cols = join(colors, ", ") pal = "set palette defined (" * cols * ")\nset palette maxcolors $(length(cm))" if v isa Symbol push!(Palette_cache, ((v, rev) => pal)) end push!(settings, pal) else push!(settings, "set palette $v") end elseif key == "view" # view if v isa Tuple push!(settings, "set view $(join(v, ", "))") else push!(settings, "set view $v") end elseif key ∈ ("lt", "linetype") # linetype definitions; code inspired by @gcalderone's Gnuplot.jl if v isa Symbol if haskey(Linetypes_cache, v) push!(settings, Linetypes_cache[v]) else cm = colorschemes[v] linetypes = String[] for i in 1:length(cm) c = cm[i] s = join(string.( round.(Int, 255 .*(c.r, c.g, c.b)), base=16, pad=2)) push!(linetypes, "set lt $i lc rgb '#$s'") end s = join(linetypes,"\n")*"\nset linetype cycle $(length(cm))" push!(Linetypes_cache, (v => s)) push!(settings, s) end else push!(settings, "set linetype $v") end elseif key == "margins" && v isa Tuple # margin definitions using at screen: left, right, bottom top push!(settings, """set lmargin at screen $(v[1]) set rmargin at screen $(v[2]) set bmargin at screen $(v[3]) set tmargin at screen $(v[4])""") else push!(settings, "set $key $v") end end return join(settings, "\n") end # parse plot configuration parse_plotline(x) = x function parse_plotline(pl::Vector{<:Pair})::String @debug "parse_plotline()" pl # Initialize string that will be returned plotline = String[] count::Int = 0 for (k, v) in pl # ensure that the same key does not appear later flag = true count += 1 for z in pl[(count+1):end] if k == z[1] flag = false break end end if flag if v == true push!(plotline, k) else if k == "marker" || k == "pointtype" || k == "pt" k = "pointtype" if v isa Symbol v = get(pointtypes, v, "'$v'") end @debug k v elseif k == "plotstyle" k = "with" elseif k == "markersize" || k == "ms" k = "pointsize" elseif k == "legend" k = "title" end push!(plotline, k*" "*string(v)) end end end return join(plotline, " ") end """ cs2dec(cs::ColorScheme) Convert a colorsheme to a vector of integers, where each number corresponds to each color in `cs`, expressed in base 10. Meant to be used with `lc rgb variable`. This function accepts color schemes made up of `RGB` or `RGBA` values. """ function cs2dec(cs::ColorScheme) colors = cs.colors s = Int[] str(c) = string(c, base = 16, pad = 2) if colors[1] isa RGB # no alpha for c in colors push!(s, parse(Int, string(str(round(Int, 255*c.r)) * str(round(Int, 255*c.g)) * str(round(Int, 255*c.b))), base = 16)) end else # alpha for c in colors push!(s, parse(Int, string(str(round(Int, 255*c.alpha)) * str(round(Int, 255*c.r)) * str(round(Int, 255*c.g)) * str(round(Int, 255*c.b))), base = 16)) end end s end # Define pointtype synonyms pointtypes = (dot = 0, ⋅ = 0, + = 1, plus = 1, x = 2, * = 3, star = 3, esquare = 4, fsquare = 5, ecircle = 6, fcircle = 7, etrianup = 8, ftrianup = 9, etriandn = 10, ftriandn = 11, edmd = 12, fdmd = 13, ) ================================================ FILE: src/gaston_builtinthemes.jl ================================================ # Themes in use are stored in this dictionary. # Each theme is identified by a name. # settings themes sthemes = @gpkw Dict( :none => Pair[], :notics => {tics = false}, :labels => {xlabel = "'x'", ylabel = "'y'", zlabel = "'z'"}, :unitranges => {xrange = (-1,1), yrange = (-1,1), zrange = (-1,1)}, :nocb => {colorbox = false}, :boxplot => {boxwidth = "0.8 relative", style = "fill solid 0.5"}, :histplot => {boxwidth = "0.8 relative", style = "fill solid 0.5", yrange = "[0:*]"}, :imagesc => {yrange = "reverse"}, :hidden3d => {hidden3d}, :wiresurf => {hidden3d, pm3d = "implicit depthorder border lc 'black' lw 0.3"}, :heatmap => {view = "map"}, :contour => {key = false, view = "map", contour = "base", surface = false, cntrlabel = "font ',7'", cntrparam = "levels auto 10", }, :contourproj => {hidden3d, key = false, contour = "base", cntrlabel = "font ',7'", cntrparam = "levels auto 10" }, :scatter3 => {border = "4095", grid = "xtics ytics ztics vertical", xyplane = "relative 0.05" }, ) # plotline themes pthemes = @gpkw Dict( :none => Pair[], :scatter => {with = "points"}, :impulses => {with = "impulses"}, :stem => {with = "points", pointtype = :ecircle}, :box => {with = "boxes"}, :boxerror => {with = "boxerrorbars"}, :boxxyerror => {with = "boxxyerror"}, :image => {with = "image"}, :rgbimage => {with = "rgbimage"}, :horhist => {u = "2:1:(0):2:(\$0-0.5):(\$0+0.5)", with = "boxxyerror"}, :pm3d => {with = "pm3d"}, :labels => {with = "labels"}, ) ================================================ FILE: src/gaston_figures.jl ================================================ ## Copyright (c) 2013-2021 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. # Code related to figures. """ Gaston.Plot(datafile::String, plotline::String) Type that stores the data needed to plot a curve. Gaston.Plot(args..., plotline = "") Construct a new Plot. The curve data (for instance, `x` and `y` coordinates) are provided first. The curve plotline is the last argument. """ mutable struct Plot const datafile :: String plotline :: String function Plot(args...) if isempty(args) || all(i -> isa(i, Union{String, Vector{<:Pair}}), args) throw(ArgumentError("No plot data provided.")) end if args[end] isa String plotline = args[end] args = args[1:end-1] elseif args[end] isa Vector{<:Pair} plotline = parse_plotline(args[end]) args = args[1:end-1] else plotline = "" end datafile = tempname() writedata(datafile, args...) new(datafile, plotline) end end """ Gaston.Plot!(P, args...)::Nothing Update existing plot `P` with provided `args`. The existing data file is overwritten; no new data file is created. A quick benchmark shows that this has no advantage over creating new Plots; however, in some cases avoiding the creation of lots of small files might be desired. """ function Plot!(P::Plot, args...) if isempty(args) return P end if args[end] isa String plotline = args[end] args = args[1:end-1] elseif args[end] isa Vector{<:Pair} plotline = parse_plotline(args[end]) args = args[1:end-1] else plotline = P.plotline end datafile = P.datafile writedata(datafile, args...) P.plotline = plotline return nothing end """ Axis(settings::String = "", plots::Vector{Gaston.Plot} = Gaston.Plot[], is3d::Bool = false) Type that stores the data required to create a 2-D or 3-D axis. The constructor takes the following arguments: * `settings`: stores the axis settings. * `plots`: a vector of Plot, one per curve. * `is3d`: determines if axis is generated with `plot` or `splot`. """ mutable struct Axis settings :: String # axis settings plots :: Vector{Plot} # one Plot per curve in the axis is3d :: Bool # if true, 'splot' is used; otherwise, 'plot' is used end # Axis constructors Axis(x, y) = Axis(x, y, false) Axis3(x, y) = Axis(x, y, true) Axis() = Axis("", Plot[]) Axis3() = Axis3("", Plot[]) Axis(p::Plot) = Axis("", [p]) Axis3(p::Plot) = Axis3("", [p]) Axis(s::String) = Axis(s, Plot[]) Axis3(s::String) = Axis3(s, Plot[]) Axis(s::String, p::Plot) = Axis(s, [p]) Axis3(s::String, p::Plot) = Axis3(s, [p]) Axis(s::Vector{T}) where T <: Pair = Axis(parse_settings(s), Plot[]) Axis3(s::Vector{T}) where T <: Pair = Axis3(parse_settings(s), Plot[]) Axis(s::Vector{T}, p::Plot) where T <: Pair = Axis(parse_settings(s), [p]) Axis3(s::Vector{T}, p::Plot) where T <: Pair = Axis3(parse_settings(s), [p]) Axis(s::Vector{T}, p::Vector{Plot}) where T <: Pair = Axis(parse_settings(s), p) Axis3(s::Vector{T}, p::Vector{Plot}) where T <: Pair = Axis3(parse_settings(s), p) # Figures """ Figure Type that stores a figure. It has the following fields: * `handle`: the figure's unique identifier (it may be of any type). * `gp_proc`: the gnuplot process associated with the figure (type `Base.Process`). * `axes`: a vector of `Gaston.Axis`. * `multiplot`: a string with arguments to `set multiplot`. * `autolayout`: `true` if Gaston should handle the figure's layout (`Bool`). """ mutable struct Figure <: AbstractFigure handle gp_proc :: Base.Process axes :: Vector{Axis} multiplot :: String autolayout :: Bool end """ Figure(h = nothing, autolayout = true, multiplot = "")::Figure Return an empty a figure with given handle. If `h === nothing`, automatically assign the next available numerical handle. A new gnuplot process is started and associated with the new figure, which becomes the active figure. If the handle provided already exists, an error is thrown. # Examples ```{.julia} fig = Figure() # new figure with next available numerical handle fig = Figure(5) # new figure with handle 5 (if it was available) fig = Figure(multiplot = "'title 'test'") # new figure with multiplot settings ``` """ function Figure(handle = nothing ; autolayout = true, multiplot = "") global state handle === nothing && (handle = nexthandle()) if handle ∈ gethandles() error("Figure with given handle already exists. Handle: ", handle) else f = finalizer(finalize_figure, Figure(handle, gp_start(), Axis[], multiplot, autolayout)) push!(state.figures.figs, f) state.activefig = handle end @debug "Returning figure with handle: " handle return f end """ Gaston.FigureAxis When indexing a figure, a FigureAxis is returned. It contains the figure itself along with the index. This is required because `plot(figure[index])` modifies the axis at `figure.axis[index]`, but it must return `figure`. """ struct FigureAxis f :: Figure idx :: Int end """ (f::Figure)(index)::Gaston.Axis Return the axis stored at the specified index. If the axis does not exist, an empty axis is created. """ function (f::Figure)(idx)::Axis ensure(f.axes, idx) return f.axes[idx] end function finalize_figure(f::Figure) #@async @info "Finalizing figure with handle $(f.handle)." for i in eachindex(f.axes) empty!(f(i)) end @async gp_quit(f) end # functions to push stuff into figures/axis/plots """ push!(a::Axis, p::Plot) Push plot (curve) `p` into axis `a`. """ function push!(a::Axis, p::Plot) push!(a.plots, p) return a end """ push!(f::Figure, a::Axis) Push axis `a` into figure `f`. """ function push!(f::Figure, a::Axis) push!(f.axes, a) return f end """ push!(f::FigureAxis, p::Plot) Push plot (curve) `p` into the indexed axis of figure `f`. """ function push!(fa::FigureAxis, p::Plot) a = fa.f.axes[fa.idx] push!(a, p) return fa.f end """ push!(f1::Figure, f2::Figure)::Figure Insert the first axis of f2 into f1. # Example ```julia f1 = plot(sin) f2 = Figure() histogram(randn(100), bins = 10) # plots on f2 push!(f1, f2) # insert the histogram as second axis of f1 ``` """ function push!(f1::Figure, f2::Figure)::Figure push!(f1, f2(1)) end """ push!(f1::Figure, an::FigureAxis)::Figure Insert the axis in `an` into `f`. # Example ``` julia f1 = plot(sin) f2 = Figure() plot(f2, cos) plot(f2[2], tan) push!(f1, f2[2]) # insert the plot of tan into f1 """ function push!(f1::Figure, f2::FigureAxis)::Figure push!(f1.axes, f2.f.axes[f2.idx]) return f1 end """ set!(a::Gaston.Axis, s) set!(f::Gaston.FigureAxis, s) Set the settings of the axis or indexed figure. `s` can be a string or a vector of pairs. """ function set!(a::Axis, s::S)::Axis where {S <: AbstractString} a.settings = s return a end function set!(fa::FigureAxis, s::S)::Figure where {S <: AbstractString} set!(fa.f.axes[fa.idx], s) return fa.f end function set!(a::Axis, s::Vector{<:Pair})::Axis a.settings = parse_settings(s) return a end function set!(fa::FigureAxis, s::Vector{<:Pair})::Figure set!(fa.f.axes[fa.idx], s) return fa.f end ## Indexing into figures/axes # It is possible to set/get plots and axes using indexing. # Important: accessing a non-assigned location in a figure will create that location (like in Makie). # Notation, assuming `f::Figure`, `a::Axis`, and `p::Plot`. # `f[3]` # `f.axes[3]`, creating it (and `f[1]` and `f[2]` as well) if necessary # `f[3] = a` # `f.axes[3] = a`, setting `f[1]` and `f[2]` to `Axis()` if necessary # `f[i,j] = p` # Replace `f.axes[i].plots[j]` with `p`. `f.axes[i]` will be created if necessary. # `f[i] = p` # Replace `f.axes[1].plots[i]` with `p`. # `f[] = p` # Replace `f.axes[1].plots[1]` with `p`. """ getindex(f::Figure, index)::Gaston.FigureAxis Return `Gaston.FigureAxis(f, index)`. If the axis at the specified index does not exist, one is created. To obtain (or create) an axis, use `f(index)`. # Example ```julia f1 = Figure() plot(f1[3], sin) # Figure f1 contains three axes: the first two are empty, # and the third one contains a sine wave. ``` """ function getindex(f::Figure, idx)::FigureAxis ensure(f.axes, idx) return FigureAxis(f, idx) end getindex(a::Axis, idx)::Plot = a.plots[idx] # Index into a Plot function (f::Figure)(idx1, idx2)::Plot return f.axes[idx1][idx2] end # Replace an axis function setindex!(f::Figure, a::Axis, idx) ensure(f.axes, idx) f.axes[idx] = a end # Replace a plot. Plot must already exist. (see `push!` to insert plots). function setindex!(f::Figure, p::Plot, idx...) if isempty(idx) plotidx, axisidx = 1, 1 elseif length(idx) == 1 plotidx, axisidx = idx[1], 1 else plotidx, axisidx = idx end f[axisidx].a[plotidx] = p end # Replace a plot in an axis. function setindex!(a::Axis, p::Plot, idx) a.plots[idx] = p return a end # utility functions isempty(f::Figure) = all(isempty(a) for a in f.axes) isempty(a::Axis) = isempty(a.plots) length(f::Figure) = length(f.axes) length(a::Axis) = length(a.plots) function empty!(a::Axis) a.settings = "" a.plots = Plot[] a end function ensure(v::Vector{T}, idx) where T <: Union{Axis, Plot} if !isassigned(v, idx) for i in (length(v)+1):idx push!(v, T()) end end end """ figure(handle = ; index = nothing)::Figure Return specified figure (by handle or index) and make it the active figure. If no figures exist, then a new figure is returned. If no arguments are given, the current active figure is returned. """ function figure(handle = state.activefig ; index = nothing)::Figure global state if isnothing(handle) && isnothing(index) return Figure() end if isnothing(index) for fig in state.figures.figs if fig.handle == handle state.activefig = handle return fig end end error("No figure with handle: ", handle) end if isassigned(state.figures.figs, index) fig = state.figures.figs[index] state.activefig = fig.handle return fig else error("No figure stored in index: " , index) end end """ Gaston.listfigures() Display a list of all existing figures. """ function listfigures(io::IO = stdin) L = length(state.figures.figs) if L == 0 println(io, "Currently managing no figures.") elseif L == 1 println(io, "Currently managing 1 figure:") else println(io, "Currently managing $L figures:") end for idx in 1:length(state.figures.figs) h = state.figures.figs[idx].handle s = "Figure with index: $idx and handle: $h" if h == state.activefig s = " (Active) "*s else s = " "*s end println(io, s) end end """ reset!(f::Figure) Reset figure `f` to its initial state, without restarting its associated gnuplot process. """ function reset!(f::Figure) f.axes = Axis[] f.multiplot = "" f.autolayout = true end """ closefigure(h = nothing)::Nothing Close figure with handle `h`. If no arguments are given, the active figure is closed. The most recent remaining figure (if any) is made active. The associated gnuplot process is also terminated. # Examples ```{.julia} plot(sin, handle = :first); plot(cos, handle = :second); plot(tan, handle = :third); closefigure() # close figure with handle `:third` closefigure(:first) # close figure with handle `:first` closefigure() # close figure with handle `:second` ``` """ function closefigure(handle = nothing) handle === nothing && (handle = state.activefig) if handle ∉ gethandles() error("Attempted to close figure with non-existing handle ", handle) end closefigure(figure(handle)) nothing end """ closefigure(fig::Figure)::Nothing Closes the specified figure. The associated gnuplot process is also terminated. # Example ```{.julia} p = plot(1:10); closefigure(p) ``` """ function closefigure(fig::Figure) @debug "closefigure(): closing figure with handle: " fig.handle #gp_quit(fig) finalize(fig) deleteat!(state.figures.figs, getidx(fig)) state.activefig = isempty(state.figures.figs) ? nothing : state.figures.figs[end].handle nothing end """ closeall()::Nothing Close all existing figures. """ function closeall() while true if isempty(state.figures.figs) break end closefigure(state.figures.figs[end]) end nothing end """ Gaston.nexthandle()::Int Return the next available handle (smallest not-yet-used positive integer). """ function nexthandle() isempty(state.figures.figs) && return 1 handles = filter(t->isa(t, Int), gethandles()) # remove non-integer handles mh = maximum(handles, init=1) # largest handle, or 1 if handles is empty if mh <= 0 return 1 else for i = 1:mh+1 i ∉ handles && return i end end end """ Gaston.gethandles()::Vector{Any} Return a vector with the handles of all existing figures. """ gethandles() = [figure.handle for figure in state.figures.figs] """ Gaston.getidx(fig::Figure) Return the index (in Gaston's internal state) of given figure """ function getidx(fig::Figure) h = fig.handle idx = 1 for f in state.figures.figs h == f.handle && return idx idx += 1 end end ================================================ FILE: src/gaston_llplot.jl ================================================ ## Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. # Write plot data to a file. # # Valid arguments: # # Case 1: n×1... # In this format, there are N n×1 vectors. Data is written as one block, # with N coordinates per line. # # Case 2: n×1 m×1 n×m... # In this format, there are N n×m matrices. Data is written as n blocks, # each block made up of triplets, with the first coordinate constant in # each block. # function writedata(file, args... ; append=false) @debug "writedata()" size.(args) @debug "writedata()" args mode = "w" append && (mode = "a") nl = "\n" iob = IOBuffer() # Case 1: all args are 1-D: mx1 mx1 mx1... if all(ndims.(args) .== 1) if minimum(length.(args)) == maximum(length.(args)) d = hcat(args...) writedlm(iob, d) write(iob, nl*nl) else error("Incompatible vector lengths") end # Case 2: n×1 m×1 n×m... elseif (length(args) >= 3) && (ndims(args[1]) == 1) && (ndims(args[2]) == 1) && (ndims(args[3]) == 2) n::Int = size(args[2])[1] m::Int = size(args[1])[1] al::Int = length(args) if size(args[3]) == (n, m) block = zeros(n, al) for xi in eachindex(args[1]) block[:,1] .= args[1][xi] block[:,2] .= args[2] for k in 3:al block[:,k] .= args[k][:,xi] end writedlm(iob, block) write(iob, nl) end write(iob, nl*nl) else @debug "writedata()" n m size(args[3]) error("Incompatible array lengths") end # Case 3: n×m n×m n×m... elseif (ndims(args[1]) == 2) && (ndims(args[2]) == 2) && (ndims(args[3]) == 2) (n, m) = size(args[1]) al = length(args) if (size(args[2]) == (n, m)) || size(args[3] == (n, m)) block = zeros(n, al) for xi in axes(args[1], 2) block[:,1] .= args[1][:,xi] block[:,2] .= args[2][:,xi] block[:,3] .= args[3][:,xi] for k in 4:al block[:,k] .= args[k][:,xi] end writedlm(iob, block) write(iob, nl) end else @debug "writedata()" n m size(args[3]) error("Incompatible array lengths") end end open(file, mode, lock = false) do io seekstart(iob) write(io, iob) end nothing end "Handle data stored in a DataBlock" function writedata(file, table::DataBlock) seekstart(table.data) write(file, table.data) end ================================================ FILE: src/gaston_options.jl ================================================ # Macro and functions to handle options in brackets """ @Q_str Inserts single quotation marks around a string. When passing options to gnuplot, some arguments should be quoted and some should not. For example: * `set title Example # gnuplot errors` * `set title 'Example' # the title must be quoted` * `set pointtype 7 # the point type is not quoted` Gaston allows setting options using keyword arguments: ```julia @plot {title = "Example"} x y # converted to "set title Example" ``` Here, the keyword argument should be `{title = "'Example'"}`, which is correctly converted to `set title 'Example'`. To avoid having to type the single quotes, this macro allows us to write: ```julia @plot {title = Q"Example"} x y # converted to "set title 'Example'" ``` """ macro Q_str(s) "'$s'" end """ @plot args... @plot provides an alternative syntax for plotting. The arguments are interpreted similarly to `plot`: first, a figure or axis may be specified; then, data is provided, and finally a plotline may be given. This macro allows specifying gnuplot settings as `setting = value`, which is converted to `set setting value` before passing it to gnuplot. These key, value pairs must be surrounded by curly brackets. # Examples ```{.julia} # Plot a sine wave with title `example` and with a grid, with a red line @plot {title = "'example'", grid = true} sin {lc = 'red'} ``` In this example, `grid = true` is converted to `set grid`. To disable a setting, use (for example) `grid = false` (converted to `unset grid`). """ macro plot(ex...) args = [] kwargs = Pair{Symbol,Any}[] for el in ex Meta.isexpr(el, :(=)) ? push!(kwargs, Pair(el.args...)) : push!(args, el) end args2 = (esc(procopts(v)) for v in args) :( plot($(args2...) ; $kwargs...) ) end """ @plot! args... Alternative Convenient syntax for `plot!`. See the documentation for `@plot`. """ macro plot!(ex...) args = [] kwargs = Pair{Symbol,Any}[] for el in ex Meta.isexpr(el, :(=)) ? push!(kwargs, Pair(el.args...)) : push!(args, el) end args2 = (esc(procopts(v)) for v in args) :( plot!($(args2...) ; $kwargs...) ) end """ @splot args... Alternative Convenient syntax for `splot`. See the documentation for `@plot`. """ macro splot(ex...) args = [] kwargs = Pair{Symbol,Any}[] for el in ex Meta.isexpr(el, :(=)) ? push!(kwargs, Pair(el.args...)) : push!(args, el) end args2 = (esc(procopts(v)) for v in args) :( splot($(args2...) ; $kwargs...) ) end """ @splot! args... Alternative Convenient syntax for `splot!`. See the documentation for `@plot`. """ macro splot!(ex...) args = [] kwargs = Pair{Symbol,Any}[] for el in ex Meta.isexpr(el, :(=)) ? push!(kwargs, Pair(el.args...)) : push!(args, el) end args2 = (esc(procopts(v)) for v in args) :( splot!($(args2...) ; $kwargs...) ) end ================================================ FILE: src/gaston_plot.jl ================================================ ## Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. """ plot([f::Figure,] [indexed figure,], [settings...,] data..., [plotline...,] [kwargs...])::Figure Plot the provided data, returning a figure. Arguments (in order from left to right): * `f::Figure` (optional). If a figure `f` is given as argument, the figure is reset (all previous axes are removed), and the new plot is created in the first axis of `f`. * An indexed figure (e.g. `f[3]`) (optional). The axis at the given index is cleared (or created if it does not exist), and the plot is added to it. * Axis settings (optional, default `""`). See documentation for details on how to specify these settings. * The data to be plotted. Data may be provided as vectors, ranges, matrices, functions, etcetera (see documentation). * A plotline (optional, default `""`) specifiying the plot formatting. See documentation for details on how to specify these settings. The figure to use for plotting may also be specified using the keyword argument `handle`. Other keyword arguments are passed to `convert_args`, documented under [Recipes](reference.qmd#recipes). # Examples ```{.julia} plot(1:10) # A simple plot plot("set title 'test'}, 1:10) # Configure the axes plot("set title 'test'}, 1:10, "w p") # Configure the axes and plotline plot(sin) # plot the sine function from -10 to 10 plot(0:0.01:1, sin) # plot the sine function at the given time instants ``` See also `plot!` and `splot`. """ function plot(args... ; handle = state.activefig, splot :: Bool = false, # true if called by splot stheme :: Symbol = :none, ptheme :: Symbol = :none, kwargs... ) :: Figure @debug args ### 1. Determine figure and axis to use, and reset them as appropriate # if no index is provided, the whole figure is reset and the first axis is used (f, idx, args) = whichfigaxis(handle, args...) if ismissing(idx) reset!(f) idx = 1 end @debug args ### 2. settings -- loop over all strings, symbols or Vector{Pairs} and join them settings = Union{Vector{<:Pair},AbstractString}[sthemes[stheme]] while true if args[1] isa AbstractString || args[1] isa Vector{<:Pair} push!(settings, args[1]) args = Base.tail(args) elseif args[1] isa Symbol push!(settings, sthemes[args[1]]) args = Base.tail(args) else break end end @debug args ### 3. plotline -- check arguments from last to first plotline = Union{Vector{<:Pair}, AbstractString}[pthemes[ptheme]] while true if args[end] isa AbstractString || args[end] isa Vector{<:Pair} insert!(plotline, 2, args[end]) args = Base.front(args) elseif args[end] isa Symbol insert!(plotline, 2, pthemes[args[end]]) args = Base.front(args) else break end end @debug args ### 4. Apply recipe to arguments, if one exists if splot && applicable(convert_args3, args...) po = convert_args3(args... ; kwargs...) elseif !splot && applicable(convert_args, args...) po = convert_args(args...; kwargs...) else try # if there is no conversion function, try to parse data directly po = Plot(args...) catch err = "Gaston does not know how to plot this.\n" * "The data provided has the following type(s):\n" for i in eachindex(args) err *= " argument $i of type $(typeof(args[i]))\n" end error(err) end end ### 5. Build axis and place it in figure if po isa Plot ensure(f.axes, idx) push!(plotline, po.plotline) po.plotline = merge_plotline(plotline) setts = merge_settings(settings) f.axes[idx] = Axis(setts, [po], splot) elseif po isa PlotRecipe ensure(f.axes, idx) push!(plotline, po.plotline) pl = merge_plotline(plotline) setts = merge_settings(settings) f.axes[idx] = Axis(setts, [Plot(po.data..., pl)], splot) elseif po isa AxisRecipe push!(settings, po.settings) setts = merge_settings(settings) P = Plot[] for p in po.plots push!(P, Plot(p.data..., parse_plotline(p.plotline))) end a = Axis(setts, P, po.is3d) if isempty(f) push!(f, a) else f.axes[idx] = a end elseif po isa FigureRecipe A = Axis[] for a in po.axes P = Plot[] for p in a.plots push!(P, Plot(p.data..., parse_plotline(p.plotline))) end push!(A, Axis(parse_settings(a.settings), P, a.is3d)) end f.axes = A f.multiplot = po.multiplot f.autolayout = po.autolayout end return f end """ plot(f1::Figure, f2::Figure,... ; multiplot = "", autolayout = false, kwargs...)::Figure Return a new figure whose axes come from the figures provided in the arguments. """ function plot(fs::Figure...; multiplot = "", autolayout = true, kwargs...) f = Figure(;multiplot, autolayout) for fig in fs for ax in fig.axes push!(f, ax) end end f end """ plot!(...)::Figure Similar to `plot`, but adds a new curve to an axis. If the axis does not exist, it is created. However, `plot!` does not support specification of the axis settings. # Examples ```{.julia} plot(1:10) # plot a curve plot!((1:10.^2)) # add a second curve f = plot(sin) # store new plot in f plot!(f, cos) # add second curve to plot ``` See documentation to `plot` for more details. """ function plot!(args... ; splot = false, handle = state.activefig, ptheme = :none, kwargs...)::Figure # determine figure and axis to use (f, idx, args) = whichfigaxis(handle, args...) if ismissing(idx) idx = 1 end # remove stray settings while true if args[1] isa AbstractString || args[1] isa Vector{<:Pair} || args[1] isa Symbol args = Base.tail(args) else break end end # parse plotline plotline = Union{Vector{<:Pair}, AbstractString}[pthemes[ptheme]] while true if args[end] isa AbstractString || args[end] isa Vector{<:Pair} insert!(plotline, 2, args[end]) args = Base.front(args) elseif args[end] isa Symbol insert!(plotline, 2, pthemes[args[end]]) args = Base.front(args) else break end end # apply recipe if one exists if splot && applicable(convert_args3, args...) po = convert_args3(args... ; kwargs...) elseif !splot && applicable(convert_args, args...) po = convert_args(args...; kwargs...) else try # if there is no conversion function, try to parse data directly po = Plot(args...) catch err = "Gaston does not know how to plot this.\n" * "The data provided has the following type(s):\n" for i in eachindex(args) err *= " argument $i of type $(typeof(args[i]))\n" end error(err) end end if po isa Plot ensure(f.axes, idx) # For flexibility, we want to allow splot! before any splot commands. We want to make sure # that, in this case, the axis is set to 3D. if splot f.axes[idx].is3d = true end push!(plotline, po.plotline) po.plotline = merge_plotline(plotline) push!(f.axes[idx], po) elseif po isa PlotRecipe ensure(f.axes, idx) splot && (f.axes[idx].is3d = true) push!(plotline, po.plotline) pl = merge_plotline(plotline) push!(f.axes[idx], Plot(po.data..., pl)) else error("Argument to plot! must be a single curve.") end return f end """ splot(...)::Figure Similar to plot, but creates a 3D plot. # Example ```{.julia} splot(-1:0.1:1, -1:0.1:1, (x,y)->sin(x)*cos(y)) # Plot an equation in the specified range ``` See documentation to `plot` for more details. """ splot(args... ; kwargs...) = plot(args... ; splot = true, handle = state.activefig, kwargs...) """ splot!(...) -> Figure Similar to `splot`, but adds a new surface to an existing plot. See documentation to `plot!` for more details. """ splot!(args... ; kwargs...) = plot!(args... ; splot = true, handle = state.activefig, kwargs...) """ plotwithtable(settings, args... ; splot = true) Create and generate a table. 3D is assumed, so `splot` defaults to `true`. """ function plotwithtable(settings::AbstractString, args... ; splot = true) if applicable(convert_args3, args...) po = convert_args3(args...) tmpf = tempname() writedata(tmpf, po.data...) elseif applicable(convert_args, args...) po = convert_args(args...) tmpf = tempname() writedata(tmpf, po.data...) else tmpf = tempname() writedata(tmpf, args...) end tblf = tempname() cmd = splot ? "splot" : "plot" s = "set term unknown\n" * settings * "\nset table '$tblf'\n" * "$cmd '$tmpf'\n" * "unset table\n" gp_exec(s) table = readlines(tblf) rm(tmpf) rm(tblf) return DataBlock(table) end """ whichfigaxis(handle, args...) Return a figure and an index into its axes. Provided arguments and return values may be: * f::Figure. Returns (f, missing, remaining args) * f::FigureAxis. Returns (f, index, remaining args) * else, returns: * (f(handle), missing, remaining args) if f(handle) exists * (Figure(handle), missing, remaining args) if it does not """ function whichfigaxis(handle, args...) index_provided = false if args[1] isa FigureAxis # plot(fig[1], ...) (; f, idx) = args[1] args = Base.tail(args) elseif args[1] isa Figure # plot(fig, ...) f = args[1] idx = missing args = Base.tail(args) else # neither a figure nor an axis were given as first argument if handle ∈ gethandles() f = figure(handle) else f = Figure(handle) end idx = missing end (f, idx, args) end function merge_settings(s) ans = "" for a in s if !isempty(a) if a isa AbstractString !isempty(ans) && (ans *= '\n') ans *= a else !isempty(ans) && (ans *= '\n') ans *= parse_settings(a) end end end return ans end function merge_plotline(p) ans = "" for a in p if !isempty(a) if a isa AbstractString !isempty(ans) && (ans *= ' ') ans *= a else !isempty(ans) && (ans *= ' ') ans *= parse_plotline(a) end end end return ans end """ animate(f::Figure, term = config.altterm) Render an animated plot in notebooks such as Pluto and Jupyter. This function is meant to be used to render an animation within a notebook environment. Normally, all plots are rendered in a terminal such as `png`. However, rendering an animation requires switching to `gif`, `webp` or other terminal that supports animations. Changing the global terminal configuration wil cause all other plots in the notebook to be re-rendered with the wrong terminal. This function allows changing the terminal on a plot-by-plot basis, without changing the global terminal configuration. """ function animate(f::Figure, term = config.altterm) global config.alttoggle = true global config.altterm = term return f end ================================================ FILE: src/gaston_recipes.jl ================================================ ## Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. # Recipes: argument conversion and specialized plot commands """ convert_args(args...) Convert values of specific types to data that gnuplot can plot. Users should add methods to this function for their own types. The returned value must be one of the following types: * A `Gaston.PlotRecipe`, which describes a curve (i.e. it contains coordinates and a plotline). * A `Gaston.AxisRecipe`, which contains multiple `PlotRecipe`s and axis settings. * A `Gaston.FigureRecipe`, which contains multiple `AxisRecipe`s and multiplot settings. See the Gaston documentation for full details and examples. To add a recipe for 3-D plotting, use `convert_args3`. """ convert_args """ convert_args3(args...) Convert values of specific types to data that gnuplot can plot using `splot`. See documentation for `convert_args` for more details. """ convert_args3 # 2-D conversions ### 1-argument # one number function convert_args(r::R, args... ; pl = "", kwargs...)::PlotRecipe where R <: Real PlotRecipe(([1], [r], args...), pl) end function convert_args(c::C, args... ; pl = "", kwargs...)::PlotRecipe where C <: Complex PlotRecipe(([real(c)], [imag(c)], args...), pl) end # complex vector function convert_args(c::AbstractVector{<:Complex}, args... ; pl = "", kwargs...)::PlotRecipe PlotRecipe((collect(real(c)), collect(imag(c)), args...), pl) end # functions function convert_args(f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F <: Function} r = range(-10, stop = 10, length = 101) PlotRecipe((r, f.(r), args...), pl) end ### 2-argument function convert_args(x::R1, y::R2, args... ; pl = "", kwargs...)::PlotRecipe where {R1 <: Real, R2 <: Real} PlotRecipe(([x], [y], args...), pl) end function convert_args(x::Tuple, f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F<:Function} samples = length(x) == 3 ? x[3] : 101 r = range(x[1], x[2], length=samples) PlotRecipe((r, f.(r), args...), pl) end function convert_args(r::AbstractVector, f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F<:Function} PlotRecipe((r, f.(r), args...), pl) end # for use with "w image" function convert_args(a::Matrix{<:Real}, args... ; pl = "", kwargs...)::PlotRecipe x = collect(axes(a,2)) y = collect(axes(a,1)) PlotRecipe((x, y, a, args...), pl) end function convert_args(a::Array{<:Real, 3}, args... ; pl = "", kwargs...)::PlotRecipe x = collect(axes(a, 3)) y = collect(axes(a, 2)) PlotRecipe((x, y, a[1,:,:], a[2,:,:], a[3,:,:], args...), pl) end # histogram function convert_args(h::Histogram, args... ; pl = "", kwargs...)::PlotRecipe # convert from StatsBase histogram to gnuplot x, y values if h.weights isa Vector xx = collect(h.edges[1]) x = (xx[1:end-1]+xx[2:end])./2 y = h.weights return PlotRecipe((x, y, args...), pl) else xx = collect(h.edges[1]) x = (xx[1:end-1]+xx[2:end])./2 yy = collect(h.edges[2]) y = (yy[1:end-1]+yy[2:end])./2 z = permutedims(h.weights) return PlotRecipe((x, y, z, args...), pl) end end ### 3-D conversions function convert_args3(x::R1, y::R2, z::R3, args... ; pl = "", kwargs...)::PlotRecipe where {R1 <: Real, R2 <: Real, R3 <: Real} PlotRecipe(([x], [y], [z], args...), pl) end function convert_args3(a::Matrix{<:Real} ; pl = "", kwargs...)::PlotRecipe x = axes(a, 2) y = axes(a, 1) PlotRecipe((x, y, a), pl) end function convert_args3(x::AbstractVector{<:Real}, y::AbstractVector{<:Real}, f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F <: Function} PlotRecipe((x, y, meshgrid(x, y, f), args...), pl) end function convert_args3(f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F <: Function} x = y = range(-10, 10, length = 100) PlotRecipe((x, y, meshgrid(x, y, f), args...), pl) end function convert_args3(xy::Tuple, f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F <: Function} convert_args3(xy, xy, f, args... ; pl, kwargs...) end function convert_args3(xr::Tuple, yr::Tuple, f::F, args... ; pl = "", kwargs...)::PlotRecipe where {F <: Function} samples_x = samples_y = 100 length(xr) == 3 && (samples_x = xr[3]) length(yr) == 3 && (samples_y = yr[3]) xx = range(xr[1], xr[2], length = samples_x) yy = range(yr[1], yr[2], length = samples_y) PlotRecipe((xx, yy, meshgrid(xx, yy, f), args...), pl) end ### Plot recipes """ scatter(args...; kwargs...)::Figure Generate a scatter plot with built-in plotline theme `scatter`. See the `plot` documentation for more information on the arguments. """ scatter(args... ; kwargs...) = plot(args... ; kwargs..., ptheme = :scatter) """ scatter!(args...; kwargs...)::Figure Insert a scatter plot. See the `scatter` documentation for more details. """ scatter!(args... ; kwargs...) = plot!(args... ; kwargs..., ptheme = :scatter) """ stem(args...; onlyimpulses::Bool = false, color = "'blue'", kwargs...)::Figure Generate a stem plot with built-in plotline themes `impulses` and `stem`. This function takes the following keyword arguments: * `onlyimpulses`: if `true`, plot using only impulses and omit the dots. * `color`: specify line color to use. If not specified, the impulses and the dots may be plotted with different colors. See the `plot` documentation for more information on the arguments. """ function stem(args... ; onlyimpulses = false, color = "'blue'", kwargs...) clr = color != "" ? "linecolor $(color)" : "" plot(args..., clr; kwargs..., ptheme = :impulses) if !onlyimpulses plot!(args..., clr ; kwargs..., ptheme = :stem) end figure() end """ stem!(args... ; onlyimpulses = false, color = "'blue'", kwargs...)::Figure Insert a stem plot. See the `stem` documentation for more details. """ function stem!(args... ; onlyimpulses = false, color = "'blue'", kwargs...) clr = color != "" ? "linecolor $(color)" : "" plot!(args..., clr; kwargs..., ptheme = :impulses) if !onlyimpulses plot!(args..., clr ; kwargs..., ptheme = :stem) end end """ bar(args...; kwargs...)::Figure Generate a bar plot with built-in settings theme `boxplot` and plotline theme `box`. See the `plot` documentation for more information on the arguments. """ bar(args... ; kwargs...) = plot(args... ; kwargs..., stheme = :boxplot, ptheme = :box) """ bar!(args...; kwargs...)::Figure Insert a bar plot. See the `bar` documentation for more details. """ bar!(args... ; kwargs...) = plot!(args... ; kwargs..., ptheme = :box) """ barerror(args...; kwargs...)::Figure Generate a barerror plot with built-in settings theme `boxplot` and plotline theme `boxerror`. See the `plot` documentation for more information on the arguments. """ barerror(args... ; kwargs...) = plot(args... ; kwargs..., stheme = :boxplot, ptheme = :boxerror) """ barerror!(args...; kwargs...)::Figure Insert a barerror plot. See the `barerror` documentation for more details. """ barerror!(args... ; kwargs...) = plot!(args... ; kwargs..., ptheme = :boxerror) ## Histograms """ histogram(args...,[bins = 10,] [mode = :pdf,] [edges = nothing,] [horizontal = false]; kwargs...)::Figure Plot a histogram of the provided data, using `StatsBase.fit`. This function takes the following keyword arguments: * `bins` specifies the number of bins (default 10) * `mode` specifies how the histogram area is normalized (see `StatsBase.fit`) """ function histogram(args... ; edges = nothing, nbins = 10, mode :: Symbol = :pdf, horizontal :: Bool = false, kwargs...) # Extract data and non-data from args... data = [] front = [] back = [] i = 1 while i <= length(args) a = args[i] if typeof(a) in (Axis, Figure, FigureAxis, String, Symbol) || a isa Vector{T} where T<:Pair push!(front, a) i = i + 1 else break end end while i <= length(args) a = args[i] if !(typeof(a) in (Axis, Figure, FigureAxis, String, Symbol) || a isa Vector{T} where T<:Pair) push!(data, a) i = i + 1 else break end end while i <= length(args) a = args[i] if typeof(a) in (Axis, Figure, FigureAxis, String, Symbol) || a isa Vector{T} where T<:Pair push!(back, a) i = i + 1 else break end end if length(data) == 1 h = edges === nothing ? hist(data[1] ; nbins, mode) : hist(data[1] ; edges, mode) if horizontal return plot(front..., h, back... ; kwargs..., stheme = :histplot, ptheme = :horhist) else return plot(front..., h, back... ; kwargs..., stheme = :histplot, ptheme = :box) end else nbins isa Number && (nbins = (nbins, nbins)) h = edges === nothing ? hist(data[1], data[2] ; nbins, mode) : hist(data[1], data[2] ; edges, mode) return plot(front..., h, back... ; kwargs..., ptheme = :image) end end """ imagesc(args...; kwargs...)::Figure Plot an array as an image. If the array is a matrix, a grayscale image is assumed. If the given array `z` is three-dimensional, an rgbimage is assumed, with `z[1,:,:]` the red channel, `z[2,:,:]` the blue channel, and `z[3,:,:]` the blue channel. See the documentation to `plot` for more details. """ function imagesc(args... ; kwargs...) rgb = false for a in args if a isa AbstractArray && ndims(a) == 3 rgb = true break end end if rgb plot(args... ; kwargs..., stheme = :imagesc, ptheme = :rgbimage) else plot(args... ; kwargs..., stheme = :imagesc, ptheme = :image) end end ### 3-D recipes ## Wireframes """ wireframe(args...; kwargs...)::Figure Plot the provided data using a wireframe, using the settings theme `hidden3d`. See the `plot` documentation for more information on the arguments. """ wireframe(args... ; kwargs...) = splot(args... ; kwargs..., stheme = :hidden3d) """ wireframe!(args...; kwargs...)::Figure Insert a wireframe plot. See the `wireframe` documentation for more details. """ wireframe!(args... ; kwargs...) = splot!(args... ; kwargs...) ## Surfaces """ surf(args...; kwargs...)::Figure Plot the provided data as a surface, using the settings theme `hidden3d` and the plotline theme `pm3d`. See the `plot` documentation for more information on the arguments. """ surf(args... ; kwargs...) = splot(args... ; kwargs..., stheme = :hidden3d, ptheme = :pm3d) """ surf!(args...; kwargs...)::Figure Insert a surface plot. See the `surf` documentation for more details. """ surf!(args... ; kwargs...) = splot!(args... ; kwargs..., ptheme = :pm3d) # Surface with contours on the base """ surfcontour(args...; [labels::Bool = true,] kwargs...)::Figure Plot the provided data as a surface with contour lines at the base, using the settings theme `contourproj` and the plotline theme `labels`. If the keyword argument `labels` is `true`, then numerical labels are added to the contour lines. See the `plot` documentation for more information on the arguments. """ function surfcontour(args... ; labels = true, kwargs...) splot(args... ; kwargs..., stheme = :contourproj) if labels splot!(args... ; kwargs..., ptheme = :labels) end figure() end # surface with superimposed wireframe """ wiresurf(args...; kwargs...)::Figure Plot the provided data as a surface with a superimposed wireframe, using the settings theme `wiresurf`. See the `plot` documentation for more information on the arguments. """ wiresurf(args... ; kwargs...) = splot(args... ; kwargs..., stheme = :wiresurf) """ wiresurf!(args...; kwargs...)::Figure Insert a wiresurf plot. See the `wiresurf` documentation for more details. """ wiresurf!(args... ; kwargs...) = splot!(args... ; kwargs...) # 3D scatter plots """ scatter3(args...; kwargs...)::Figure Generate a scatter plot of the provided data, using the settings theme `scatter3` and the plotline theme `scatter`. See the `plot` documentation for more information on the arguments. """ scatter3(args... ; kwargs...) = splot(args... ; kwargs..., stheme = :scatter3, ptheme = :scatter) """ scatter3!(args...; kwargs...)::Figure Insert a scatter plot. See the `scatter3` documentation for more details. """ scatter3!(args... ; kwargs...) = splot!(args... ; kwargs..., ptheme = :scatter) """ contour(args...; [labels::Bool = true,] kwargs...)::Figure Plot the provided data using contour lines, with settings themes `countour` and `labels`. If the keyword argument `labels` is `true`, then the contour lines are labeled. See the documentation to `plot` for more details. """ function contour(args... ; labels = true, kwargs...) splot(args... ; kwargs..., stheme = :contour) if labels splot!(args... ; kwargs..., ptheme = :labels) end figure() end """ heatmap(args...; kwargs...) Plot the data provided as a heatmap, using the settings theme `heatmap` and the plotline theme `pm3d`. See the documentation to `plot` for more details. """ heatmap(args... ; kwargs...) = splot(args... ; kwargs..., stheme = :heatmap, ptheme = :pm3d) ================================================ FILE: test/Project.toml ================================================ [deps] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Preferences = "21216c6a-2e73-6563-6e65-726566657250" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Aqua = "0.8" Downloads = "1" JET = "0.9 - 0.10" JSON = "1" Pkg = "1" Preferences = "1" Test = "1" ================================================ FILE: test/downstream.jl ================================================ using Downloads, JSON, Test function available_channels() juliaup = "https://julialang-s3.julialang.org/juliaup" for i ∈ 1:6 buf = PipeBuffer() Downloads.download("$juliaup/DBVERSION", buf) dbversion = VersionNumber(readline(buf)) dbversion.major == 1 || continue buf = PipeBuffer() Downloads.download( "$juliaup/versiondb/versiondb-$dbversion-x86_64-unknown-linux-gnu.json", buf, ) json = JSON.parse(buf) haskey(json, "AvailableChannels") || continue return json["AvailableChannels"] sleep(10i) end return end """ julia> is_latest(:lts) julia> is_latest(:release) """ function is_latest(variant) channels = available_channels() ver = let var::String = ( release = "release", rel = "release", lts = "lts", release_candidate = "rc", alpha = "alpha", beta = "beta", rc = "rc", )[variant] VersionNumber(split(channels[var]["Version"], '+') |> first) end dev = occursin("DEV", string(VERSION)) # or length(VERSION.prerelease) < 2 return !dev && ( VersionNumber(ver.major, ver.minor, 0, ("",)) ≤ VERSION < VersionNumber(ver.major, ver.minor + 1) ) end (is_ci() && Sys.islinux() && is_latest(:release)) && @testset "downstream" begin tmpd = mktempdir() Plots_jl = joinpath(tmpd, "Plots.jl") @test Cmd(`$(Base.julia_cmd()) $(joinpath(@__DIR__, "downstream_dev.jl")) $tmpd`) |> run |> success script = tempname() write( script, """ using Pkg Pkg.activate(joinpath("$Plots_jl", "PlotsBase")) Pkg.develop(path="$(joinpath(@__DIR__, ".."))") import Gaston # trigger `PlotsBase` extension Pkg.status(["Gaston", "PlotsBase"]) # test basic plots creation and bitmap or vector exports using PlotsBase, Test prefix = tempname() @time for i ∈ 1:length(PlotsBase._examples) i ∈ PlotsBase._backend_skips[:gaston] && continue # skip unsupported examples PlotsBase._examples[i].imports ≡ nothing || continue # skip examples requiring optional test deps pl = PlotsBase.test_examples(:gaston, i; disp = false) for ext in (".png", ".pdf") # TODO: maybe more ? fn = string(prefix, i, ext) PlotsBase.savefig(pl, fn) @test filesize(fn) > 1_000 end end """ ) @test Cmd(`$(Base.julia_cmd()) --project=@. $script`; dir = Plots_jl) |> run |> success end ================================================ FILE: test/downstream_dev.jl ================================================ using Pkg LibGit2 = Pkg.GitTools.LibGit2 TOML = Pkg.TOML failsafe_clone_checkout(path, url, pkg = nothing; stable = true) = begin local repo for i in 1:6 try repo = Pkg.GitTools.ensure_clone(stdout, path, url) break catch err @warn err sleep(20i) end end name, _ = splitext(basename(url)) registries = joinpath(first(DEPOT_PATH), "registries") general = joinpath(registries, "General") versions = joinpath(general, name[1:1], name, "Versions.toml") if !isfile(versions) mkpath(general) run(setenv(`tar xf $general.tar.gz`; dir = general)) end @assert isfile(versions) if stable v_stable = maximum(VersionNumber.(keys(TOML.parse(read(versions, String))))) obj = LibGit2.GitObject(repo, "v$v_stable") hash = if isa(obj, LibGit2.GitTag) LibGit2.target(obj) else LibGit2.GitHash(obj) end |> string LibGit2.checkout!(repo, hash) end toml = if pkg ≢ nothing && (fn = joinpath(path, pkg, "Project.toml")) |> isfile # monorepo layout fn elseif (fn = joinpath(path, "Project.toml")) |> isfile # single package toplevel fn end @assert isfile(toml) "$toml does not exist, bailing out !" toml end fake_supported_version!(toml) = begin # fake the supported Gaston version for testing (for `Pkg.develop`) Gaston_version = Pkg.Types.read_package(normpath(@__DIR__, "..", "Project.toml")).version parsed_toml = TOML.parse(read(toml, String)) parsed_toml["compat"]["Gaston"] = string(Gaston_version) open(toml, "w") do io TOML.print(io, parsed_toml) end nothing end dn = joinpath(ARGS[1], "Plots.jl") toml = failsafe_clone_checkout(dn, "https://github.com/JuliaPlots/Plots.jl", "PlotsBase"; stable = false) docs = joinpath(dn, "docs") isdir(docs) && rm(docs; recursive=true) # ERROR: LoadError: empty intersection between Gaston@2.0.1 and project compatibility ∅ fake_supported_version!(toml) ================================================ FILE: test/manualtests.txt ================================================ Tests to be run manually. * Produce a sixelgd plot, using `xterm -ti vt340`, `wezterm` or other sixelgd-capable terminal. using Gaston Gaston.config.term = "sixelgd size 300,200" Gaston.config.output = :echo plot(1:10) -- * In the repl, produce an ascii plot Gaston.config.term = "dumb" Gaston.config.output = :echo plot(1:10) -- * In the repl, produce an svg script Gaston.config.term = "svg" Gaston.config.output = :echo plot(1:10) -- * In the repl, produce an html page Gaston.config.term = "canvas" Gaston.config.output = :echo plot(1:10) -- * In the repl, produce no output Gaston.config.output = :null plot(1:10) -- * In the repl, produce a qt plot Gaston.config.output = :external Gaston.config.term = "qt" plot(1:10) -- * In the repl, save a plot plot(1:10); save("test.gif", term = "gif size 300,200") save("test.pdf", term = "pdfcairo") save("test.png", term = "png enhanced background 'blue' size 300,200") -- * In Pluto, load the notebooks under notebeook/* ================================================ FILE: test/preferences.jl ================================================ using Preferences, Gaston const PREVIOUS_DEFAULT_GNUPLOT = load_preference(Gaston, "gnuplot_binary") invalidate_compiled_cache!(m::Module) = # make sure the compiled cache is removed rm.(Base.find_all_in_cache_path(Base.PkgId(m, string(nameof(m))))) @testset "Invalid path" begin script = tempname() set_preferences!(Gaston, "gnuplot_binary" => "/_some_non_existent_invalid_path_"; force = true) invalidate_compiled_cache!(Gaston) write( script, """ using Gaston, Test res = @testset "[subtest] invalid gnuplot_binary path" begin @test Gaston.config.exec ≡ nothing end exit(res.n_passed == 1 ? 0 : 123) """ ) @test run(`$(Base.julia_cmd()) $script`) |> success end const sys_gnuplot = Sys.which("gnuplot") (Sys.islinux() && sys_gnuplot ≢ nothing) && @testset "System gnuplot" begin script = tempname() set_preferences!(Gaston, "gnuplot_binary" => sys_gnuplot; force = true) invalidate_compiled_cache!(Gaston) write( script, """ using Gaston, Test res = @testset "[subtest] system gnuplot path" begin @test Gaston.config.exec.exec == ["$sys_gnuplot"] end exit(res.n_passed == 1 ? 0 : 123) """ ) @test run(`$(Base.julia_cmd()) $script`) |> success end if PREVIOUS_DEFAULT_GNUPLOT ≡ nothing # restore the absence of a preference delete_preferences!(Gaston, "gnuplot_binary"; force = true) else # reset to previous state set_preferences!(Gaston, "gnuplot_binary" => PREVIOUS_DEFAULT_GNUPLOT; force = true) end ================================================ FILE: test/runtests.jl ================================================ ## Copyright (c) 2013 Miguel Bazdresch ## ## This file is distributed under the 2-clause BSD License. using Test, Gaston, Aqua, JET using Gaston: Axis, Axis3, Plot import Gaston: convert_args, convert_args3, PlotRecipe, AxisRecipe, FigureRecipe gh = Gaston.gethandles reset = Gaston.reset null() = Gaston.config.output = :null is_pkgeval() = Base.get_bool_env("JULIA_PKGEVAL", false) is_ci() = Base.get_bool_env("CI", false) @testset "Gnuplot version" begin @test Gaston.GNUPLOT_VERSION[] ≥ v"6" end @testset "Available terminals" begin @test Gaston.terminals() ≡ nothing end @testset "JULIA_GNUPLOT_EXE" begin withenv("JULIA_GNUPLOT_EXE" => "gnuplot") do @test read(`$(Base.julia_cmd()) -e 'using Gaston; print(Gaston.config.exec.exec[1])'`, String) == "gnuplot" end end @testset "AQUA" begin # Aqua.test_all(Gaston) # Aqua.test_ambiguities(Gaston) # disabled -- fails with ambiguities from StatsBase Aqua.test_unbound_args(Gaston) Aqua.test_undefined_exports(Gaston) Aqua.test_project_extras(Gaston) Aqua.test_stale_deps(Gaston) Aqua.test_deps_compat(Gaston) Aqua.test_piracies(Gaston, treat_as_own = [convert_args, convert_args3]) Aqua.test_persistent_tasks(Gaston) end @testset "JET" begin if Gaston.state.enabled null() #JET.test_package("Gaston"; toplevel_logger=nothing) JET.@test_call target_modules=(Gaston,) Figure(1) JET.@test_call target_modules=(Gaston,) plot(rand(10), rand(10)) JET.@test_call target_modules=(Gaston,) @gpkw plot({grid, title = Q"test"}, rand(10), rand(10), {lc = "'red'"}) f = Figure() x = 1:10; y = rand(10); z = rand(10,10) JET.@test_call target_modules=(Gaston,) plot(f, x, y, z) JET.@test_call target_modules=(Gaston,) plot(f[2], x, y, z) JET.@test_call target_modules=(Gaston,) plot!(f, x, y, z) closeall() end end @testset "Options" begin if Gaston.state.enabled w = 5.3 x = @gpkw {a = :summer, b, c = 3, d = Q"long title", e = w} @test x[1] == Pair("a", :summer) @test x[2] == Pair("b", true) @test x[3] == Pair("c", 3) @test x[4] == Pair("d", "'long title'") @test x[5] == Pair("e", 5.3) end end @testset "Handles" begin if Gaston.state.enabled closeall() reset() null() f1 = Figure(1); f2 = Figure("a"); f3 = Figure(3.14); f4 = Figure(:a); @test Gaston.nexthandle() == 2 @test Gaston.getidx(f1) == 1 @test Gaston.getidx(f2) == 2 @test Gaston.getidx(f3) == 3 @test Gaston.getidx(f4) == 4 closefigure("a") @test Gaston.getidx(f3) == 2 closefigure(f4) @test Gaston.nexthandle() == 2 closeall() @test Gaston.nexthandle() == 1 # negative handles f1 = Figure(-5) f2 = Figure("a") f3 = Figure(0) @test Gaston.nexthandle() == 1 closefigure(-5) @test Gaston.nexthandle() == 1 f4 = Figure() @test f4.handle == 1 closeall() p1 = plot(1:10, handle = :a) p2 = plot(1:10, handle = :b) p3 = plot(1:10, handle = :c) @test begin closefigure(:b) gh() end == [:a, :c] @test begin closefigure() gh() end == [:a] @test begin closefigure(:a) gh() end == Any[] Figure() Figure() @test Gaston.activefig() == 2 @test Gaston.state.figures.figs[1].handle == 1 @test Gaston.state.figures.figs[2].handle == 2 closefigure(1) Figure() @test Gaston.state.figures.figs[1].handle == 2 @test Gaston.state.figures.figs[2].handle == 1 closeall() @test gh() == Any[] closeall() Figure(1) Figure(4) @test length(Gaston.state.figures.figs) == 2 @test closefigure(1) == nothing @test length(Gaston.state.figures.figs) == 1 @test Gaston.activefig() == 4 closefigure(4) @test isempty(Gaston.state.figures.figs) @test Gaston.activefig() === nothing closeall() Figure(1) Figure(10) f = figure() @test f.handle == 10 f = figure(1) @test f.handle == 1 f = figure(index = 1) @test f.handle == 1 f = figure(index = 2) @test f.handle == 10 @test_throws ErrorException figure(3) @test_throws ErrorException figure(index = 3) end end @testset "Configuration commands" begin if Gaston.state.enabled closeall() reset() @test Gaston.config.term == "" @test Gaston.config.embedhtml == false @test Gaston.config.output == :external @test run(`$(Gaston.config.exec) --version`) |> success null() @test Gaston.config.output == :null end end @testset "Plot" begin if Gaston.state.enabled closeall() reset() null() @test_throws ArgumentError Plot() @test_throws ArgumentError Plot("w l") p = Plot(1:10, "w l") @test p.plotline == "w l" p = Plot(1:10, 1:10, 1:10, "w l") @test p.plotline == "w l" p = Plot(1:10) @test p.plotline == "" p = @gpkw Plot(1:10, {with="lines"}) @test p.plotline == "with lines" #test Plot! pp = Gaston.Plot!(p) @test p.plotline == "with lines" pp = Gaston.Plot!(p, 1:10, 1:10, "w p") @test p.plotline == "w p" # test plot(figs...) f1 = Figure() f2 = Figure() plot(f1,sin) plot(f2,cos) f3 = plot(f1,f2) @test f3 isa Figure @test length(f3) == 2 # test that an existing f.multiplot is not overwritten f1 = Figure(multiplot = "title '1'") plot(1:10) @test f1.multiplot == "" f2 = Figure(multiplot = "title '1'") plot(f2, 1:10) @test f2.multiplot == "" f3 = Figure(multiplot = "title '1'") plot(f3[1], 1:10) @test f3.multiplot == "title '1'" f4 = Figure(multiplot = "title '1'") plot(f4[2], 1:10) @test f4.multiplot == "title '1'" f5 = Figure() @test f5.multiplot == "" closeall() end end @testset "Axis" begin if Gaston.state.enabled a = Axis() @test a.settings == "" @test a.plots == Plot[] @test isempty(a) p = Plot(1:10) push!(a, p) @test !isempty(a) @test length(a.plots) == 1 push!(a, p) @test length(a.plots) == 2 Gaston.set!(a, "s") @test a.settings == "s" Gaston.set!(a, ["title" => "1"]) @test a.settings == "set title 1" Gaston.empty!(a) @test isempty(a) a = Axis(p) @test !isempty(a) @test length(a.plots) == 1 Gaston.empty!(a) a = Axis("s") @test a.settings == "s" @test a.plots == Plot[] a = Axis("s", p) @test a.settings == "s" @test length(a.plots) == 1 a = Axis(["title" => "1"], p) @test a.settings == "set title 1" @test a.plots == [p] a = Axis(["title" => "1"], p) @test a.settings == "set title 1" @test length(a.plots) == 1 @gpkw a = Axis({title = "1", grid}) @test a.settings == "set title 1\nset grid" a = Axis() @test a.is3d == false a = Axis3() @test a.is3d == true end end @testset "push! and set! with FigureAxis" begin if Gaston.state.enabled closeall() f1 = plot(sin) f2 = Figure() histogram(randn(100), bins = 10) # plots on f2 push!(f1, f2) @test f1 isa Figure plot(f2[2], cos) push!(f1, f2[2]) @test f1 isa Figure push!(f1, f2) @test f1 isa Figure push!(f1, f2[2]) @test f1 isa Figure Gaston.set!(f2[2], "testing") @test f2(2).settings == "testing" Gaston.set!(f2[2], ["grid" => true]) @test f2(2).settings == "set grid" p = Plot(1:10, 1:10) push!(f2[2], p) @test length(f2(2)) == 2 end end @testset "Figure and figure" begin if Gaston.state.enabled closeall() reset() null() @test Figure() isa Figure @test length(Gaston.state.figures) == 1 @test Gaston.state.figures.figs[1].handle == 1 @test_throws ErrorException Figure(1) @test_throws ErrorException figure(2) closeall() f = Figure(π) @test f.handle == π @test f.gp_proc isa Base.Process @test f.multiplot == "" @test f.axes == Axis[] @test isempty(f) @test length(f) == 0 Figure("a") @test Gaston.activefig() == "a" f = figure(index = 2) @test f.handle == "a" @test f[1] isa Gaston.FigureAxis f = figure(π) @test f.handle == π @test length(f) == 0 # test pushes f = Figure(1) p = Plot([1]) push!(f(2), p) @test f[2] isa Gaston.FigureAxis @test f(2, 1) == p end end @testset "Parsing settings" begin if Gaston.state.enabled ps = Gaston.parse_settings @test ps("x") == "x" # booleans @test @gpkw ps({g}) == "set g" @test @gpkw ps({g=true}) == "set g" @test @gpkw ps({g=false}) == "unset g" @test @gpkw ps({g,g=false}) == "set g\nunset g" # tics @test @gpkw ps({tics="axis border"}) == "set tics axis border" @test @gpkw ps({xtics=1:2}) == "set xtics 1,1,2" @test @gpkw ps({ytics=1:2}) == "set ytics 1,1,2" @test @gpkw ps({ztics=1:2:7}) == "set ztics 1,2,7" @test @gpkw ps({tics=1:2}) == "set tics 1,1,2" @test @gpkw ps({tics,tics=1:5}) == "set tics\nset tics 1,1,5" @test @gpkw ps({tics=(0,5)}) == "set tics (0, 5)" @test @gpkw ps({tics=(labels=("one", "two"), positions=(0, 5))}) == "set tics ('one' 0, 'two' 5, )" # ranges #@test @gpkw ps({}) @test @gpkw ps({xrange=(-5,5)}) == "set xrange [-5:5]" @test @gpkw ps({xrange=(-5.1,5.6)}) == "set xrange [-5.1:5.6]" @test @gpkw ps({xrange=[-Inf,0]}) == "set xrange [*:0.0]" @test @gpkw ps({yrange=[0,Inf]}) == "set yrange [0.0:*]" @test @gpkw ps({zrange=[-Inf,Inf]}) == "set zrange [*:*]" @test @gpkw ps({cbrange=(1,2)}) == "set cbrange [1:2]" @test @gpkw ps({cbrange=(0,Inf)}) == "set cbrange [0:*]" @test @gpkw ps({zrange=(-Inf,Inf)}) == "set zrange [*:*]" @test @gpkw ps({ranges=(-3,3)}) == "set xrange [-3:3]\nset yrange [-3:3]\nset zrange [-3:3]\nset cbrange [-3:3]" # palette @test @gpkw ps({palette=:jet}) == "set palette defined (1 0.0 0.0 0.498, 2 0.0 0.0 1.0, 3 0.0 0.498 1.0, 4 0.0 1.0 1.0, 5 0.498 1.0 0.498, 6 1.0 1.0 0.0, 7 1.0 0.498 0.0, 8 1.0 0.0 0.0, 9 0.498 0.0 0.0)\nset palette maxcolors 9" @test @gpkw ps({palette=(:jet,:reverse)}) == "set palette defined (1 0.498 0.0 0.0, 2 1.0 0.0 0.0, 3 1.0 0.498 0.0, 4 1.0 1.0 0.0, 5 0.498 1.0 0.498, 6 0.0 1.0 1.0, 7 0.0 0.498 1.0, 8 0.0 0.0 1.0, 9 0.0 0.0 0.498)\nset palette maxcolors 9" @test @gpkw ps({palette="x"}) == "set palette x" # view @test @gpkw ps({view=(50,60)}) == "set view 50, 60" @test @gpkw ps({view=5}) == "set view 5" # linetype @test @gpkw ps({linetype = 5}) == "set linetype 5" @test @gpkw ps({lt = :jet}) == "set lt 1 lc rgb '#00007f'\nset lt 2 lc rgb '#0000ff'\nset lt 3 lc rgb '#007fff'\nset lt 4 lc rgb '#00ffff'\nset lt 5 lc rgb '#7fff7f'\nset lt 6 lc rgb '#ffff00'\nset lt 7 lc rgb '#ff7f00'\nset lt 8 lc rgb '#ff0000'\nset lt 9 lc rgb '#7f0000'\nset linetype cycle 9" # margins @test ps(@gpkw {margins = (.2, .3, .4, .5)}) == "set lmargin at screen 0.2\nset rmargin at screen 0.3\nset bmargin at screen 0.4\nset tmargin at screen 0.5" @test ps(@gpkw {margins = "1,2,3,4"}) == "set margins 1,2,3,4" end end @testset "Parsing plotlines" begin if Gaston.state.enabled pp = Gaston.parse_plotline @test @gpkw pp({w=1,w=2}) == "w 2" @test @gpkw pp({marker=:dot}) == "pointtype 0" @test @gpkw pp({pointtype=:⋅}) == "pointtype 0" @test @gpkw pp({pt=:+}) == "pointtype 1" @test @gpkw pp({marker=Q"λ"}) == "pointtype 'λ'" @test @gpkw pp({marker="'k'"}) == "pointtype 'k'" @test @gpkw pp({plotstyle="lt"}) == "with lt" @test @gpkw pp({markersize=8}) == "pointsize 8" @test @gpkw pp({ms=5}) == "pointsize 5" @test @gpkw pp({legend=Q"title"}) == "title 'title'" end end @testset "Argument conversion" begin if Gaston.state.enabled for arg in (1, (1,2), ComplexF64[1,2,3], (1:10, sin), ((1,10), sin), ((1,10,10), sin), [1 2; 3 4], rand(4,3,4)) p = convert_args(arg...) @test p isa PlotRecipe @test p.plotline == "" end for arg in (1, (1,2), ComplexF64[1,2,3], (1:10, sin), ((1,10), sin), ((1,10,10), sin), [1 2; 3 4], rand(4,3,4)) p = convert_args(arg..., pl = "test") @test p isa PlotRecipe @test p.plotline == "test" end for arg in ((1,2,3), rand(2,2), ([1,2], [3,4], (x,y)->y*sin(x)), ((x,y)->y*sin(x),)) p = convert_args3(arg..., pl = "test") @test p isa PlotRecipe @test p.plotline == "test" end struct TestType end tt = TestType() Gaston.convert_args(x::TestType, args... ; pl = "", kwargs...) = true @test convert_args(tt) Gaston.convert_args3(x::TestType, args... ; pl = "", kwargs...) = true @test convert_args3(tt) # TODO: test histograms end end @testset "plot" begin if Gaston.state.enabled closeall() reset() null() f = plot(1:10) @test f isa Figure @test f.handle == 1 @test f(1).settings == "" @test f(1,1).plotline == "" f = @gpkw plot({grid}, 1:10) @test f.handle == 1 @test f(1).settings == "set grid" @test f(1,1).plotline == "" f = @gpkw plot({grid}, 1:10, "w l") @test f(1,1).plotline == "w l" f = @gpkw plot({grid}, 1:10, {w="l"}) @test f(1,1).plotline == "w l" plot(f[2], (1:10).^2) @test length(f) == 2 @test f(2).settings == "" @test f(2,1).plotline == "" plot(f[2], "set view", (1:10).^2, "w p") @test f(2).settings == "set view" @test f(2,1).plotline == "w p" plot!(f[2], 1:10, "w lp") @test f(2,2).plotline == "w lp" plot!(f[2], 1:10, "w lp pt 1") @test f(2,3).plotline == "w lp pt 1" @gpkw plot!(f[2], 1:10, {w="p", marker=Q"λ"}) @test f(2,4).plotline == "w p pointtype 'λ'" plot(1:10) @test length(f) == 1 f = plot(1:10, handle="aa") @test f isa Figure @test f.handle == "aa" # test plotwithtable x = y = range(-5, 5, 100) f4(x,y) = sin(1.3x) * cos(0.9y) + cos(0.8x) * sin(1.9y) + cos(0.2x*y) settings = "set contour base\nset cntrparam level incremental -3, 0.5, 3\nunset surface" contours = Gaston.plotwithtable(settings, x, y, f4) z = Gaston.meshgrid(x, y, f4) plot("unset key\nunset colorbox\nset palette rgbformulae 33,13,10", x, y, z, "with image") pwt = plot!(contours, "w l lw 1.5 lc 'slategray'") @test pwt isa Figure end end @testset "plot with 'themes'" begin if Gaston.state.enabled closeall() reset() null() f = @gpkw plot({grid},{xtics}, 1:10) @test f(1).settings == "set grid\nset xtics" f = @gpkw plot({grid},"set xtics", 1:10) @test f(1).settings == "set grid\nset xtics" f = @gpkw plot("set grid",{xtics}, 1:10) @test f(1).settings == "set grid\nset xtics" f = @gpkw plot("set grid",{xtics},"set view", 1:10) @test f(1).settings == "set grid\nset xtics\nset view" f = plot(1:10, "1", "2", "3") @test f(1,1).plotline == "1 2 3" plot!(f, 1:10, "1", "2", "3") @test f(1,2).plotline == "1 2 3" f = @gpkw plot(1:10, "1", {2}, "3") @test f(1,1).plotline == "1 2 3" @gpkw plot!(f, 1:10, "1", {2}, "3") @test f(1,2).plotline == "1 2 3" f = @gpkw plot(1:10, {1}, {2}, {w="l"}) @test f(1,1).plotline == "1 2 w l" @gpkw plot!(f, 1:10, {1}, {2}, {w="l"}) @test f(1,2).plotline == "1 2 w l" f = @gpkw plot(1:10, {1}, {2}, :scatter) @test f(1,1).plotline == "1 2 with points" @gpkw plot!(f, 1:10, {1}, {2}, :scatter) @test f(1,2).plotline == "1 2 with points" f = @gpkw plot({grid}, :heatmap, 1:10) @test f(1).settings == "set grid\nset view map" end end @testset "2d plot plot styles" begin if Gaston.state.enabled closeall() reset() null() f = @gpkw scatter({grid}, rand(2), rand(2), {1}, "2") @test f isa Figure @test f(1).settings == "set grid" @test f(1,1).plotline == "with points 1 2" @gpkw scatter!(rand(2), rand(2), {3}, "4") @test f isa Figure @test f(1).settings == "set grid" @test f(1,2).plotline == "with points 3 4" f = @gpkw stem({grid}, rand(2), {1}, "2") @test f isa Figure @test f(1).settings == "set grid" @test f(1,1).plotline == "with impulses 1 2 linecolor 'blue'" @test f(1,2).plotline == "with points pointtype 6 1 2 linecolor 'blue'" @gpkw stem!(rand(2), rand(2), {3}, "4") @test f isa Figure @test f(1).settings == "set grid" @test f(1,3).plotline == "with impulses 3 4 linecolor 'blue'" @test f(1,4).plotline == "with points pointtype 6 3 4 linecolor 'blue'" f = bar(1:10, rand(10)) bar!(1.5:10.5, 0.5*rand(10), "lc 'green'") @test f(1).settings == "set boxwidth 0.8 relative\nset style fill solid 0.5" @test f(1,1).plotline == "with boxes" @test f(1,2).plotline == "with boxes lc 'green'" f = barerror(1:10, rand(10), rand(10)) barerror!(1.5:10.5, 0.5*rand(10), rand(10), "lc 'green'") @test f(1).settings == "set boxwidth 0.8 relative\nset style fill solid 0.5" @test f(1,1).plotline == "with boxerrorbars" @test f(1,2).plotline == "with boxerrorbars lc 'green'" f = histogram(rand(10), nbins = 20, mode = :pdf) @test f isa Figure @test f(1).settings == "set boxwidth 0.8 relative\nset style fill solid 0.5\nset yrange [0:*]" @test f(1,1).plotline == "with boxes" f = histogram(rand(10), edges = [-1, 0, 1]) @test f isa Figure @test f(1).settings == "set boxwidth 0.8 relative\nset style fill solid 0.5\nset yrange [0:*]" @test f(1,1).plotline == "with boxes" f = histogram(rand(10), edges = -5:0.5:5) @test f isa Figure @test f(1).settings == "set boxwidth 0.8 relative\nset style fill solid 0.5\nset yrange [0:*]" @test f(1,1).plotline == "with boxes" f = histogram(rand(10), rand(10)) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" f = histogram(rand(10), rand(10), nbins = 20) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" f = histogram(rand(10), rand(10), nbins = (20,10)) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" f = histogram(rand(10), rand(10), edges = [1,2,3]) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" f = histogram(rand(10), rand(10), edges = ([1,2,3],[1,2,3])) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" f = histogram(rand(10), rand(10), nbins = 20, mode = :pdf) @test f isa Figure @test f(1).settings == "" @test f(1,1).plotline == "with image" Z = [5 4 3 1 0 ; 2 2 0 0 1 ; 0 0 0 1 0 ; 0 1 2 4 3] f = imagesc(Z) @test f isa Figure @test f(1,1).plotline == "with image" Z = 255*randn(3,10,10) f = imagesc(Z) @test f isa Figure @test f(1,1).plotline == "with rgbimage" end end @testset "3d plot plot styles" begin if Gaston.state.enabled closeall() reset() null() x = y = -15:0.4:15 f1 = (x,y) -> @. sin(sqrt(x*x+y*y))/sqrt(x*x+y*y) f = @gpkw wireframe({title="'wireframe'"},x,y,f1,"lc 'turquoise'") @test f isa Figure @test f(1).settings == "set hidden3d\nset title 'wireframe'" @test f(1,1).plotline == "lc 'turquoise'" @gpkw wireframe!({title="'wireframe'"},x.-5,y,f1,"lc 'orange'") @test length(f) == 1 @test length(f(1)) == 2 @test f(1,2).plotline == "lc 'orange'" plot(f[2], 1:10) @test length(f) == 2 f = @gpkw surf({title="'surf'"},x,y,f1,"lc 'turquoise'") @test f isa Figure @test f(1).settings == "set hidden3d\nset title 'surf'" @test f(1,1).plotline == "with pm3d lc 'turquoise'" @gpkw surf!({title="'surf'"},x.-5,y,f1,"lc 'orange'") @test length(f) == 1 @test length(f(1)) == 2 @test f(1,2).plotline == "with pm3d lc 'orange'" f = @gpkw surfcontour(x,y,f1,"lc 'turquoise'") @test f isa Figure @test f(1).settings == "set hidden3d\nunset key\nset contour base\nset cntrlabel font ',7'\nset cntrparam levels auto 10" @test f(1,1).plotline == "lc 'turquoise'" @test length(f) == 1 @test length(f(1)) == 2 @test f(1,2).plotline == "with labels lc 'turquoise'" f = @gpkw surfcontour(x,y,f1,labels=false) @test length(f(1)) == 1 f = wiresurf(x,y,f1) @test f isa Figure @test f(1).settings == "set hidden3d\nset pm3d implicit depthorder border lc 'black' lw 0.3" f = scatter3(rand(10),rand(10),rand(10)) scatter3!(f,rand(10),rand(10),rand(10)) @test f isa Figure @test length(f(1)) == 2 f = contour(x,y,f1,labels=false) @test f isa Figure @test length(f(1)) == 1 f = contour(x,y,f1) @test f isa Figure @test length(f(1)) == 2 f = @gpkw heatmap({palette=:summer},x,y,f1) @test f isa Figure @test f(1,1).plotline == "with pm3d" end end @testset "Multiplot" begin if Gaston.state.enabled closeall() reset() null() f = plot(1:10) plot(f[10], 1:10) @test f isa Figure @test length(f) == 10 @test imagesc(f[1], rand(5,5)) isa Figure @test stem(f[2], rand(5)) isa Figure @test stem!(f[2], rand(5)) isa Figure @test scatter(f[4], rand(5), rand(5)) isa Figure @test stem!(f[4], rand(5)) isa Figure @test scatter3(f[5], rand(5), rand(5), rand(5)) isa Figure @test contour(f[7], rand(5,5)) isa Figure @test surfcontour(f[6], rand(5,5)) isa Figure @test contour(f[7], rand(5,5)) isa Figure end end @testset "Saving plots" begin if Gaston.state.enabled closeall() null() reset() t = mktempdir() cd(t) f1 = plot(1:10) save() @test isfile("figure-1.png") rm("figure-1.png", force=true) save(filename = "test.png") @test isfile("test.png") rm("test.png", force=true) save(term="pdf enhanced") @test isfile("figure-1.pdf") rm("figure-1.pdf", force=true) Figure() f2 = plot(1:10) @test f2.handle == 2 save(f2) @test isfile("figure-2.png") rm("figure-2.png", force=true) save(f2, filename = "test2.png") @test isfile("test2.png") rm("test2.png", force=true) save(f2,term="pdf") @test isfile("figure-2.pdf") rm("figure-2.pdf", force=true) save(f2, filename="test.pdf",term="pdf") @test isfile("test.pdf") rm("test.pdf", force=true) end end @testset "Recipes" begin if Gaston.state.enabled null() struct Data1 samples end x = Data1(rand(10)) function Gaston.convert_args(d::Data1, args... ; pl = "", kwargs...) x = 1:length(d.samples) y = d.samples PlotRecipe((x, y), pl) end r1 = plot(x) @test r1 isa Figure struct Data2 end function Gaston.convert_args(::Data2) x = range(0,1,10) p1 = PlotRecipe((x, sin.(x)), "") p2 = PlotRecipe((x, cos.(x)), "") @gpkw s = {"grid"} AxisRecipe(s, [p1, p2], false) end r2 = plot(Data2()) @test r2 isa Figure struct Data3 end function Gaston.convert_args(::Data3) t1 = range(0, 1, 40) t2 = range(-5, 5, 50) z = Gaston.meshgrid(t2, t2, (x,y) -> cos(x)*cos(y)) @gpkw a1 = AxisRecipe({title = Q"First Axis"}, [PlotRecipe((1:10, rand(10)))]) @gpkw a2 = AxisRecipe({title = Q"Trig"}, [PlotRecipe((t1, sin.(5t1)), {lc = Q"black"}), PlotRecipe((t1, cos.(5t1)), {w = "p", pt = 16})]) @gpkw a3 = AxisRecipe({title = Q"Surface", tics = false, palette = (:matter, :reverse)}, [PlotRecipe((t2, t2, z), {w = "pm3d"})], true) @gpkw a4 = AxisRecipe({tics, title = false, title = Q"Last Axis"}, [PlotRecipe((1:10, 1:10, rand(10,10)), "w image")]) # return named tuple with four axes FigureRecipe([a1, a2, a3, a4], "title 'A Four-Axes Recipe' layout 2,2", false) end r3 = plot(Data3()) @test r3 isa Figure end end @testset "Misc" begin if Gaston.state.enabled @test Gaston.gp_exec("set grid") == "" null() t = Gaston.terminals() @test isnothing(t) end end closeall() include("preferences.jl") include("downstream.jl")