Plots.jl: Legend doesn't work with histogram

Created on 12 May 2016  Â·  16Comments  Â·  Source: JuliaPlots/Plots.jl

Or am I doing something wrong?
image

Most helpful comment

The pieces are (mostly) in place. Here's an example. Enjoy!

using Plots
pyplot(fmt=:png)

@userplot type StackHist
    vecs::Tuple
    StackHist(vecs::AVec...) = new(vecs)
end

@recipe function f(sh::StackHist)
    # get one set of edges to bin with
    alldata = vcat(sh.vecs...)
    edges,_ = hist(alldata, get(d,:bins,default(:bins)))

    # common attributes
    fillalpha --> 0.3    # optional override
    seriestype := :bar  # forced override

    # for each vector, we create a series... bar chart starting at the
    # last total height of the last series
    lastcnts = 0
    for v in sh.vecs
        @series begin
            # force the fillrange to the last heights
            fillrange := lastcnts

            # get the new hist counts, then add to total
            cnts = hist(v, edges)[2]
            lastcnts = lastcnts + cnts

            # compute bar widths
            dffs = diff(edges)
            bar_width := 0.8dffs

            # return centers and heights... this is the input data to the series
            centers = edges[1:end-1] + dffs
            centers, cnts
        end
    end
end

stackhist(rand(100), rand(200), randn(100), bins=20)

tmp

All 16 comments

124 (which you also opened :) ) was closed when the alpha part was fixed, but not legend.

At some point we agreed that histograms (along with some other series
types) shouldn't be part of the legend. I don't remember the reasons and
I'm happy to revisit the discussion.

On Thursday, May 12, 2016, Patrick Kofod Mogensen [email protected]
wrote:

124 https://github.com/tbreloff/Plots.jl/issues/124 (which you also

opened :) ) was closed when the alpha part was fixed, but not legend AFAIC.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
https://github.com/tbreloff/Plots.jl/issues/254#issuecomment-218666075

I don't remember that... I really miss the histogram legends and also the not having equal bins by default (https://github.com/tbreloff/Plots.jl/issues/186). Even if legend is false by default in histograms, the user should be able to use it.

To me, a useful histogram for grouped data is something like the _ggplot2_ default (lines aren't a problem to me):

This sounds like a job for... recipes! :)

To expand, it would be good if we can do something like:

plot(GroupedHist(), inputdata...)

And that recipe would allow additional functionality/default behaviors that are maybe different than other histogram plots.

I find that as I explore the recipe idea more, that everything can fit into it. It's kinda like a grammar of graphics on steroids. "Shakespeare of Graphics"?

Using types for dispaching plot kinds (recipes) is a good and julian idea :)

plot(GroupedHist, inputdata...) # ::Type{GroupedHist} looks cleaner ;)

But... How can it play with @recipe? Would be keyword arguments easy to manage inside other recipes than type parameters?

The WIP idea at the moment is that recipes create one or more "Series", which is just an attribute dictionary and the unhandled arguments left after the recipe. So a low-level recipe would completely fill the dictionary with everything needed for a plot and return an empty tuple (signifying it's ready to be plotted) Something higher-level like a grouped histogram would set some useful attributes (bins, bar_position, etc) and then return the input data for further processing:

@recipe function f(::GroupedHist, args...)
  bins --> calcbins(args...)
  args
end

So in this case, it's really just short circuiting early in the pipeline to set some better defaults for this specific visualization, and then continuing on as if the GroupedHist type was missing.

This may not seem as powerful to you as it does to me, but it opens up possibilities of making 3rd party types and logic first-class and allows for implementations reaching into any part of the pipeline without changing core Plots code. Really cool.

Is that meaning that I can define my own serie DotPlot just defining

immutable DotPlot # <: SerieRecipe ?
end

@recipe function plot(::DotPlot, seq, args...)
    # fill with dots 
    x, y
end

Will

GroupedHist(inputdata...)

work as

plot(GroupedHist(), inputdata...)

?

The recipe would just create step plots after some precalculations of the counts right? Makes sense, as we could then support the grouped histogram with legends without any changes to Plots. It is also easier to implement it this way across backends (I think?).

Will ... work as ...

Sure... if you define it ;)

# <: SerieRecipe ?

unnecessary

I can define my own serie DotPlot just defining

exactly... you do a simple mapping and set some attributes, and then let Plots recursively handle whatever you give it.

Great!
I think that having something like

abstract SerieRecipe
call(x::SerieRecipe, args...) = plot(x, args...)

Will be great ;)

Right... likely what will happen is that there will be recipes to convert:

grouped hists --> hists with predefined bins
hists --> bars
bars --> paths with (or without) fill

And then some backends may natively support some cool operations/features with bar plots so that backend will tell Plots that it supports bars natively and the pipeline will short-circuit before applying that last "bars to paths" recipe.

Going on your SeriesRecipe idea, it would be nice to have a decorator macro which defines a lowercase function version:

@seriestype type GroupedHist end

groupedhist(args...)  # maps to plot(GroupedHist(), args...)
groupedhist!(args...)  # maps to plot!(GroupedHist(), args...)

Good, lowercase functions are more Plots' style ;)

The pieces are (mostly) in place. Here's an example. Enjoy!

using Plots
pyplot(fmt=:png)

@userplot type StackHist
    vecs::Tuple
    StackHist(vecs::AVec...) = new(vecs)
end

@recipe function f(sh::StackHist)
    # get one set of edges to bin with
    alldata = vcat(sh.vecs...)
    edges,_ = hist(alldata, get(d,:bins,default(:bins)))

    # common attributes
    fillalpha --> 0.3    # optional override
    seriestype := :bar  # forced override

    # for each vector, we create a series... bar chart starting at the
    # last total height of the last series
    lastcnts = 0
    for v in sh.vecs
        @series begin
            # force the fillrange to the last heights
            fillrange := lastcnts

            # get the new hist counts, then add to total
            cnts = hist(v, edges)[2]
            lastcnts = lastcnts + cnts

            # compute bar widths
            dffs = diff(edges)
            bar_width := 0.8dffs

            # return centers and heights... this is the input data to the series
            centers = edges[1:end-1] + dffs
            centers, cnts
        end
    end
end

stackhist(rand(100), rand(200), randn(100), bins=20)

tmp

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SebastianM-C picture SebastianM-C  Â·  4Comments

crstnbr picture crstnbr  Â·  3Comments

kersulis picture kersulis  Â·  5Comments

Cody-G picture Cody-G  Â·  3Comments

apalugniok picture apalugniok  Â·  3Comments