Skip to content

Commit

Permalink
Waterfall recipe (#2416)
Browse files Browse the repository at this point in the history
* implement waterfall recipe

* add plotting_functions examples for waterfall to the docs

* mention waterfall in NEWS.md

* add more `lift`s in waterfall recipe

Co-authored-by: Simon <[email protected]>
  • Loading branch information
daschw and SimonDanisch authored Dec 2, 2022
1 parent 39cc40f commit 1c8d098
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## v0.18.4

- Added the `waterfall` plotting function [#2416](https://github.com/JuliaPlots/Makie.jl/pull/2416).
- Add support for `AbstractPattern` in `WGLMakie` [#2432](https://github.com/MakieOrg/Makie.jl/pull/2432).
- Broadcast replaces deprecated method for quantile [#2430](https://github.com/MakieOrg/Makie.jl/pull/2430).
- Fix CairoMakie's screen re-using [#2440](https://github.com/MakieOrg/Makie.jl/pull/2440).
Expand Down
106 changes: 106 additions & 0 deletions docs/examples/plotting_functions/waterfall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# waterfall

{{doc waterfall}}

### Examples

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide

y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]

waterfall(y)
```
\end{examplefigure}

The direction of the bars might be easier to parse with some visual support.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide

y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]

waterfall(y, show_direction=true)
```
\end{examplefigure}

You can customize the markers that indicate the bar directions.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide

y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]

waterfall(y, show_direction=true, marker_pos=:cross, marker_neg=:hline, direction_color=:gold)
```
\end{examplefigure}

If the `dodge` attribute is provided, bars are stacked by `dodge`.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide
colors = Makie.wong_colors()

x = repeat(1:2, inner=5)
y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]
group = repeat(1:5, outer=2)

waterfall(x, y, dodge=group, color=colors[group])
```
\end{examplefigure}

It can be easier to compare final results of different groups if they are shown in the background.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide
colors = Makie.wong_colors()

x = repeat(1:2, inner=5)
y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]
group = repeat(1:5, outer=2)

waterfall(x, y, dodge=group, color=colors[group], show_direction=true, show_final=true)
```
\end{examplefigure}

The color of the final bars in the background can be modified.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide
colors = Makie.wong_colors()

x = repeat(1:2, inner=5)
y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]
group = repeat(1:5, outer=2)

waterfall(x, y, dodge=group, color=colors[group], show_final=true, final_color=(colors[6], 1//3))
```
\end{examplefigure}

You can also specify to stack grouped waterfall plots by `x`.

\begin{examplefigure}{}
```julia
using CairoMakie
CairoMakie.activate!() # hide
colors = Makie.wong_colors()

x = repeat(1:5, outer=2)
y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7]
group = repeat(1:2, inner=5)

waterfall(x, y, dodge=group, color=colors[group], show_direction=true, stack=:x)
```
\end{examplefigure}
1 change: 1 addition & 0 deletions src/Makie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ include("basic_recipes/streamplot.jl")
include("basic_recipes/timeseries.jl")
include("basic_recipes/tricontourf.jl")
include("basic_recipes/volumeslices.jl")
include("basic_recipes/waterfall.jl")
include("basic_recipes/wireframe.jl")
include("basic_recipes/tooltip.jl")

Expand Down
133 changes: 133 additions & 0 deletions src/basic_recipes/waterfall.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
waterfall(x, y; kwargs...)
Plots a [waterfall chart](https://en.wikipedia.org/wiki/Waterfall_chart) to visualize individual
positive and negative components that add up to a net result as a barplot with stacked bars next
to each other.
## Attributes
$(ATTRIBUTES)
Furthermore the same attributes as for `barplot` are supported.
"""
@recipe(Waterfall, x, y) do scene
return Attributes(;
dodge=automatic,
n_dodge=automatic,
gap=0.2,
dodge_gap=0.03,
width=automatic,
cycle=[:color => :patchcolor],
stack=automatic,
show_direction=false,
marker_pos=:utriangle,
marker_neg=:dtriangle,
direction_color=theme(scene, :backgroundcolor),
show_final=false,
final_color=plot_color(:grey90, 0.5),
final_gap=automatic,
final_dodge_gap=0,
)
end

conversion_trait(::Type{<:Waterfall}) = PointBased()

function Makie.plot!(p::Waterfall)
function stack_bars(xy, dodge, stack)
x, y = first.(xy), last.(xy)
if stack === automatic
stack = dodge === automatic ? :x : :dodge
end
i_dodge = dodge === automatic ? ones(Int, length(x)) : dodge
i_stack, i_group = stack === :dodge ? (i_dodge, x) : (x, i_dodge)
xy = similar(xy)
fillto = similar(x)
final = similar(xy)
groupby = StructArray(; grp=i_group)
for (grp, inds) in StructArrays.finduniquesorted(groupby)
fromto = stack_from_to_final(i_stack[inds], y[inds])
fillto[inds] .= fromto.from
xy[inds] .= Point2f.(x[inds], fromto.to)
final[inds] .= Point2f.(x[inds], fromto.final)
end
return (xy=xy, fillto=fillto, final=final)
end

fromto = lift(stack_bars, p[1], p.dodge, p.stack)

if p.show_final[]
final_gap = p.final_gap[] === automatic ? p.dodge[] == automatic ? 0 : p.gap : p.final_gap
barplot!(
p,
lift(x -> x.final, fromto);
dodge=p.dodge,
color=p.final_color,
dodge_gap=p.final_dodge_gap,
gap=final_gap,
)
end

barplot!(
p,
lift(x -> x.xy, fromto);
p.attributes...,
fillto=lift(x -> x.fillto, fromto),
stack=automatic,
)

if p.show_direction[]
function direction_markers(
fromto,
marker_pos,
marker_neg,
width,
gap,
dodge,
n_dodge,
dodge_gap,
)
xs = first(
compute_x_and_width(first.(fromto.xy), width, gap, dodge, n_dodge, dodge_gap)
)
xy = similar(fromto.xy)
shapes = fill(marker_pos, length(xs))
for i in eachindex(xs)
y = last(fromto.xy[i])
fillto = fromto.fillto[i]
xy[i] = (xs[i], (y + fillto) / 2)
if fillto > y
shapes[i] = marker_neg
end
end
return (xy=xy, shapes=shapes)
end

markers = lift(
direction_markers,
fromto,
p.marker_pos,
p.marker_neg,
p.width,
p.gap,
p.dodge,
p.n_dodge,
p.dodge_gap,
)

scatter!(
p,
lift(x -> x.xy, markers);
marker=lift(x -> x.shapes, markers),
color=p.direction_color)
end

return p
end

function stack_from_to_final(i_stack, y)
order = 1:length(y) # save current order
perm = sortperm(i_stack) # sort by i_stack
inv_perm = sortperm(order[perm]) # restore original order
from, to = stack_from_to_sorted(view(y, perm))
return (from=view(from, inv_perm), to=view(to, inv_perm), final=last(to))
end

10 comments on commit 1c8d098

@jkrumbiegel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/73325

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.18.4 -m "<description of version>" 1c8d098c13c8de868d240cf07664315dd1c314d3
git push origin v0.18.4

@jkrumbiegel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register subdir=CairoMakie

@jkrumbiegel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register subdir=GLMakie

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/73327

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a CairoMakie-v0.9.4 -m "<description of version>" 1c8d098c13c8de868d240cf07664315dd1c314d3
git push origin CairoMakie-v0.9.4

@jkrumbiegel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register subdir=WGLMakie

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/73328

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a GLMakie-v0.7.4 -m "<description of version>" 1c8d098c13c8de868d240cf07664315dd1c314d3
git push origin GLMakie-v0.7.4

@jkrumbiegel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register subdir=RPRMakie

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/73329

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a WGLMakie-v0.7.4 -m "<description of version>" 1c8d098c13c8de868d240cf07664315dd1c314d3
git push origin WGLMakie-v0.7.4

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/73330

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a RPRMakie-v0.4.4 -m "<description of version>" 1c8d098c13c8de868d240cf07664315dd1c314d3
git push origin RPRMakie-v0.4.4

Please sign in to comment.