diff --git a/NEWS.md b/NEWS.md index 396f0b7c5..02236f37a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ Each release typically has a number of minor bug fixes beyond what is listed her # Version 1.x * Add `Geom.blank` (#1345) * Support DataFrames.jl 0.19 changes in indexing (#1318) + * Add `Geom.hband` and `Geom.vband` geometries (#1264) + * Allow elements of type `Measure` to pass through `coord.jl` and `statistics.jl` (#1264) @@ -12,7 +14,7 @@ Each release typically has a number of minor bug fixes beyond what is listed her * Add `alpha` aesthetic, `Scale.alpha_continuous` and `Scale.alpha_discrete` (#1252) * Add `limits=(min= , max= )` to `Stat.histogram` (#1249) * Add dodged boxplots (#1246) - * Add `Stat.dodge` (#1240) + * Add `Stat.dodge` (#1240) * `Stat.smooth(method=:lm)` confidence bands (#1231) * Support AbstractVectors everywhere (e.g. `Guide.xticks(ticks=1:10)`) (#1293) @@ -24,7 +26,7 @@ Each release typically has a number of minor bug fixes beyond what is listed her # Version 0.8.0 * Add `linestyle` aesthetic (#1181) * Add `Guide.shapekey` (#1156) - * `Geom.contour`: add support for `DataFrame` (#1150) + * `Geom.contour`: add support for `DataFrame` (#1150) # Version 0.7.0 @@ -43,7 +45,7 @@ Each release typically has a number of minor bug fixes beyond what is listed her # Version 0.6.4 * Regression testing tools (#1020) - + # Version 0.6.3 * Wide format data (#1013) @@ -234,5 +236,3 @@ Each release typically has a number of minor bug fixes beyond what is listed her keys are wrapped automatically. * Default Theme changes. - - diff --git a/docs/src/gallery/geometries.md b/docs/src/gallery/geometries.md index 3c0700d36..073d47035 100644 --- a/docs/src/gallery/geometries.md +++ b/docs/src/gallery/geometries.md @@ -13,13 +13,30 @@ p1 = plot(dataset("ggplot2", "mpg"), Guide.annotation(compose(context(), text(6,4, "y=x", hleft, vtop), fill("red")))) x = [20*rand(20); exp(-3)] -D = DataFrame(x=x, y= exp.(-0.5*asinh.(x).+5) .+ 2*randn(length(x))) +D = DataFrame(x=x, y= exp.(-0.5*asinh.(x).+5) .+ 2*randn(length(x))) abline = Geom.abline(color="red", style=:dash) p2 = plot(D, x=:x, y=:y, Geom.point, Scale.x_asinh, Scale.y_log, intercept=[148], slope=[-0.5], abline) hstack(p1, p2) ``` +## [`Geom.band`](@ref), [`Geom.hband`](@ref), [`Geom.vband`](@ref) + + +```@example +using Colors, Dates, Gadfly, RDatasets + +Dp = dataset("ggplot2","presidential")[3:end,:] +De = dataset("ggplot2","economics") +De.Unemploy /= 10^3 + +plot(De, x=:Date, y=:Unemploy, Geom.line, + layer(Dp, xmin=:Start, xmax=:End, Geom.vband, color=:Party), + Scale.color_discrete_manual("deepskyblue", "lightcoral"), + Coord.cartesian(xmin=Date("1965-01-01"), ymax=12), + Guide.xlabel("Time"), Guide.ylabel("Unemployment (x10³)"), Guide.colorkey(title=""), + Theme(default_color="black", key_position=:top)) +``` ## [`Geom.bar`](@ref) @@ -75,7 +92,7 @@ set_default_plot_size(21cm, 8cm) singers, salaries = dataset("lattice", "singer"), dataset("car","Salaries") salaries.Salary /= 1000.0 salaries.Discipline = ["Discipline $(x)" for x in salaries.Discipline] -p1 = plot(singers, x=:VoicePart, y=:Height, Geom.boxplot, +p1 = plot(singers, x=:VoicePart, y=:Height, Geom.boxplot, Theme(default_color="MidnightBlue")) p2 = plot(salaries, x=:Discipline, y=:Salary, color=:Rank, Scale.x_discrete(levels=["Discipline A", "Discipline B"]), @@ -128,7 +145,7 @@ using Gadfly, RDatasets, Distributions set_default_plot_size(14cm, 8cm) dist = MixtureModel(Normal, [(0.5, 0.2), (1, 0.1)]) xs = rand(dist, 10^5) -plot(layer(x=xs, Geom.density, Theme(default_color="orange")), +plot(layer(x=xs, Geom.density, Theme(default_color="orange")), layer(x=xs, Geom.density(bandwidth=0.0003), Theme(default_color="green")), layer(x=xs, Geom.density(bandwidth=0.25), Theme(default_color="purple")), Guide.manual_color_key("bandwidth", ["auto", "bw=0.0003", "bw=0.25"], @@ -185,9 +202,9 @@ ys = mean.(rand.(Normal.(0, sds), n)) df = DataFrame(x=1:length(sds), y=ys, mins=ys.-(1.96*sds/sqrt(n)), maxs=ys.+(1.96*sds/sqrt(n)), g=repeat(["a","b"], inner=3)) -p1 = plot(df, x=1:length(sds), y=:y, ymin=:mins, ymax=:maxs, color=:g, +p1 = plot(df, x=1:length(sds), y=:y, ymin=:mins, ymax=:maxs, color=:g, Geom.point, Geom.errorbar) -p2 = plot(df, y=1:length(sds), x=:y, xmin=:mins, xmax=:maxs, color=:g, +p2 = plot(df, y=1:length(sds), x=:y, xmin=:mins, xmax=:maxs, color=:g, Geom.point, Geom.errorbar) hstack(p1, p2) ``` @@ -202,18 +219,18 @@ df = by(salaries, [:Rank,:Discipline], :Salary=>mean, :Salary=>std) df.ymin, df.ymax = df.Salary_mean.-df.Salary_std, df.Salary_mean.+df.Salary_std df.label = string.(round.(Int, df.Salary_mean)) -p1 = plot(df, x=:Discipline, y=:Salary_mean, color=:Rank, +p1 = plot(df, x=:Discipline, y=:Salary_mean, color=:Rank, Scale.x_discrete(levels=["Discipline A", "Discipline B"]), ymin=:ymin, ymax=:ymax, Geom.errorbar, Stat.dodge, - Geom.bar(position=:dodge), + Geom.bar(position=:dodge), Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]), Guide.colorkey(title="", pos=[0.76w, -0.38h]), Theme(bar_spacing=0mm, stroke_color=c->"black") ) -p2 = plot(df, y=:Discipline, x=:Salary_mean, color=:Rank, +p2 = plot(df, y=:Discipline, x=:Salary_mean, color=:Rank, Coord.cartesian(yflip=true), Scale.y_discrete, xmin=:ymin, xmax=:ymax, Geom.errorbar, Stat.dodge(axis=:y), - Geom.bar(position=:dodge, orientation=:horizontal), + Geom.bar(position=:dodge, orientation=:horizontal), Scale.color_discrete(levels=["Prof", "AssocProf", "AsstProf"]), Guide.yticks(orientation=:vertical), Guide.ylabel(nothing), Theme(bar_spacing=0mm, stroke_color=c->"gray") @@ -440,7 +457,7 @@ x, y = cumsum(randn(n)), cumsum(randn(n)) D = DataFrame(x1=x[1:end-1], y1=y[1:end-1], x2=x[2:end], y2=y[2:end], colv=1:n-1) palettef(c::Float64) = get(ColorSchemes.viridis, c) -plot(D, x=:x1, y=:y1, xend=:x2, yend=:y2, +plot(D, x=:x1, y=:y1, xend=:x2, yend=:y2, color = :colv, Geom.segment, Coord.cartesian(aspect_ratio=1.0), Scale.color_continuous(colormap=palettef, minvalue=0, maxvalue=1000) ) @@ -557,8 +574,8 @@ using Gadfly, RDatasets set_default_plot_size(21cm, 8cm) coord = Coord.cartesian(xmin=-2, xmax=2, ymin=-2, ymax=2) -p1 = plot(coord, z=(x,y)->x*exp(-(x^2+y^2)), - xmin=[-2], xmax=[2], ymin=[-2], ymax=[2], +p1 = plot(coord, z=(x,y)->x*exp(-(x^2+y^2)), + xmin=[-2], xmax=[2], ymin=[-2], ymax=[2], # or: x=-2:0.25:2.0, y=-2:0.25:2.0, Geom.vectorfield(scale=0.4, samples=17), Geom.contour(levels=6), Scale.x_continuous(minvalue=-2.0, maxvalue=2.0), @@ -566,7 +583,7 @@ p1 = plot(coord, z=(x,y)->x*exp(-(x^2+y^2)), Guide.xlabel("x"), Guide.ylabel("y"), Guide.colorkey(title="z")) volcano = Matrix{Float64}(dataset("datasets", "volcano")) -volc = volcano[1:4:end, 1:4:end] +volc = volcano[1:4:end, 1:4:end] coord = Coord.cartesian(xmin=1, xmax=22, ymin=1, ymax=16) p2 = plot(coord, z=volc, x=1.0:22, y=1.0:16, Geom.vectorfield(scale=0.05), Geom.contour(levels=7), diff --git a/src/coord.jl b/src/coord.jl index 5738c9d5f..4a2aeccfa 100644 --- a/src/coord.jl +++ b/src/coord.jl @@ -156,7 +156,7 @@ function apply_coordinate(coord::Cartesian, aess::Vector{Gadfly.Aesthetics}, for var in coord.xvars for aes in aess vals = getfield(aes, var) - vals === nothing && continue + (vals === nothing || eltype(vals) <: Measure) && continue if !isa(vals, AbstractArray) vals = [vals] @@ -172,7 +172,7 @@ function apply_coordinate(coord::Cartesian, aess::Vector{Gadfly.Aesthetics}, for var in coord.yvars for aes in aess vals = getfield(aes, var) - vals === nothing && continue + (vals === nothing || eltype(vals) <: Measure) && continue # Outliers is an odd aesthetic that needs special treatment. if var == :outliers @@ -223,12 +223,12 @@ function apply_coordinate(coord::Cartesian, aess::Vector{Gadfly.Aesthetics}, ymin = coord.ymin === nothing ? ymin : coord.ymin ymax = coord.ymax === nothing ? ymax : coord.ymax - if xmin === nothing || !isfinite(xmin) + if xmin === nothing || isa(xmin, Measure) || !isfinite(xmin) xmin = 0.0 xmax = 1.0 end - if ymin === nothing || !isfinite(ymin) + if ymin === nothing || isa(ymin, Measure) || !isfinite(ymin) ymin = 0.0 ymax = 1.0 end diff --git a/src/geom/hvband.jl b/src/geom/hvband.jl new file mode 100644 index 000000000..230e9794a --- /dev/null +++ b/src/geom/hvband.jl @@ -0,0 +1,28 @@ +""" + Geom.band[(; orientation=:vertical)] + +Draw bands across the plot canvas with a horizontal span specifed by `xmin` and `xmax` if `orientation` is `:vertical`, or a vertical span specified by `ymin` and `ymax` if the `orientation` is `:horizontal`. + +This geometry is equivalent to [`Geom.rect`](@ref) with [`Stat.band`](@ref). +""" +band(; orientation=:vertical) = RectangularBinGeometry(Stat.band(orientation)) #TODO: use RectangularGeometry when it becomes available. + + +""" + Geom.hband[()] + +Draw horizontal bands across the plot canvas with a vertical span specified by `ymin` and `ymax` aesthetics. + +This geometry is equivalent to [`Geom.band`](@ref) with `orientation` set to `:vertical`. +""" +hband() = band(orientation = :horizontal) + + +""" + Geom.vband[()] + +Draw vertical bands across the plot canvas with a horizontal span specified by `xmin` and `xmax` aesthetics. + +This geometry is equivalent to [`Geom.band`](@ref). +""" +const vband = band diff --git a/src/geometry.jl b/src/geometry.jl index 27ed20dc3..f695b81fb 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -55,6 +55,7 @@ include("geom/boxplot.jl") include("geom/errorbar.jl") include("geom/hexbin.jl") include("geom/hvabline.jl") +include("geom/hvband.jl") include("geom/label.jl") include("geom/line.jl") include("geom/point.jl") diff --git a/src/statistics.jl b/src/statistics.jl index 5e3bae132..7d890b39a 100644 --- a/src/statistics.jl +++ b/src/statistics.jl @@ -53,6 +53,49 @@ apply_statistic(stat::Identity, """ const identity = Identity +struct BandStatistic <: Gadfly.StatisticElement + orientation::Symbol # :horizontal or :vertical +end + +function BandStatistic(; orientation=:vertical) + return BandStatistic(orientation) +end + +input_aesthetics(stat::BandStatistic) = [:xmin, :xmax, :ymin, :ymax] +output_aesthetics(stat::BandStatistic) = [:xmin, :xmax, :ymin, :ymax] +default_scales(stat::BandStatistic) = [Scale.x_continuous(), Scale.y_continuous()] + +""" + Stat.band[(; orientation=:vertical)] + +Transform points in $(aes2str(input_aesthetics(band()))) into rectangles in +$(aes2str(output_aesthetics(band()))). Used by [`Geom.band`](@ref Gadfly.Geom.band). +""" +const band = BandStatistic + +function apply_statistic(stat::BandStatistic, + scales::Dict{Symbol, Gadfly.ScaleElement}, + coord::Gadfly.CoordinateElement, + aes::Gadfly.Aesthetics) + + if stat.orientation == :horizontal + + n = max(length(aes.ymin)) #Note: already passed check for equal lengths. + + aes.xmin = fill(0w, n) + aes.xmax = fill(1w, n) + + elseif stat.orientation == :vertical + + n = max(length(aes.xmin)) #Note: already passed check for equal lengths. + + aes.ymin = fill(0h, n) + aes.ymax = fill(1h, n) + + else + error("Orientation must be :horizontal or :vertical") + end +end # Determine bounds of bars positioned at the given values. function barminmax(vals, iscontinuous::Bool) @@ -779,7 +822,7 @@ function apply_statistic(stat::TickStatistic, for var in in_vars categorical && !in(var,[:x,:y]) && continue vals = getfield(aes, var) - if vals != nothing && eltype(vals) != Function + if vals != nothing && eltype(vals) != Function && !(eltype(vals) <: Measure) if minval == nothing minval = first(vals) end diff --git a/test/testscripts/hvband.jl b/test/testscripts/hvband.jl new file mode 100644 index 000000000..169a09095 --- /dev/null +++ b/test/testscripts/hvband.jl @@ -0,0 +1,8 @@ +using Gadfly +set_default_plot_size(21cm, 8cm) + +p1 = plot(xmin=[1.0, 5.0, 7.0], xmax=[2.0, 6.5, 8.0] , Geom.vband, Theme(default_color="green")); + +p2 = plot(ymin=[2.5], ymax=[7.5], Geom.hband, Theme(default_color="red")); + +hstack(p1, p2)