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
================================================
[](
https://mbaz.github.io/Gaston.jl/v2/
)
[](
LICENSE.txt
)
[](
https://github.com/mbaz/Gaston.jl/actions/workflows/ci.yml
)
[](
https://app.codecov.io/gh/mbaz/Gaston.jl
)
[](
https://juliaci.github.io/NanosoldierReports/pkgeval_badges/G/Gaston.html
)
[](
https://juliahub.com/ui/Packages/General/Gaston?t=2
)
[](
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)
```

# 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)
```

```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)
```

================================================
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")
```

## 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)
```

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`).

# 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")
```

#### 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")
```

#### 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")
```

================================================
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.

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:

* `block` uses Unicode characters to draw a plot in the terminal:

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:

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")
```

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")