timbod7 / haskell-chart

A 2D charting library for haskell
429 stars 85 forks source link

Bar graph w/ error bars #70

Open mitchellwrosen opened 9 years ago

mitchellwrosen commented 9 years ago

I'm struggling to put together a graph like this one: http://mathbench.umd.edu/modules/prob-stat_bargraph/graphics-final/graph-male-female.jpg

Is this possible with the current API? I don't think there's any way to "hook" into a PlotBars render to capture the correct x-axis values to feed into a PlotErrBars.

timbod7 commented 9 years ago

That should be possible. What is the type of your x axis? Can you post your example code? On 23 Jan 2015 06:43, "Mitchell Rosen" notifications@github.com wrote:

I'm struggling to put together a graph like this one: http://mathbench.umd.edu/modules/prob-stat_bargraph/graphics-final/graph-male-female.jpg

Is this possible with the current API? I don't think there's any way to "hook" into a PlotBars render to capture the correct x-axis values to feed into a PlotErrBars.

— Reply to this email directly or view it on GitHub https://github.com/timbod7/haskell-chart/issues/70.

mitchellwrosen commented 9 years ago

@timbod7 I believe it was PlotIndex. Unfortunately I just deleted all of my sample code, but it was some combination of the bar-graph example code and error-bars example code (first time using Chart). Tonight I'll re-create it for you. In the meantime, what should the x axis type be?

EDIT: For the record, the above image was a bad example. The graph shape I'm actually going for is this: http://www.ats.ucla.edu/Stat/stata/faq/barcap1.gif. I was indeed able to superimpose error bars on top of a one-bar-per-x-axis-group graph, but beyond that I could only get the error bars to stack on top of each other in the center of the bars. Hope that makes sense.

mitchellwrosen commented 9 years ago

Here you go @timbod7:

module Main where

import Control.Applicative
import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo

titles :: [String]
titles = ["foo", "bar"]

values :: [(String, [Double], [Double])]
values =
    [ ("group 1", [1, 2], [0.5, 0.5])
    , ("group 2", [3, 4], [1, 2])
    ]

main :: IO ()
main = toFile def "out.png" ec
  where
    ec :: EC (Layout PlotIndex Double) ()
    ec = do
        layout_x_axis.laxis_generate .= autoIndexAxis (values ^.. traverse._1)
        plot barplot
        plot errplot

barplot :: EC (Layout PlotIndex Double) (Plot PlotIndex Double)
barplot = plotBars <$> bars titles (addIndexes (values ^.. traverse._2))

errplot :: EC (Layout PlotIndex Double) (PlotErrBars PlotIndex Double)
errplot = liftEC $
    plot_errbars_values .= [symErrPoint x y 0 dy | (_, ys, dys) <- values
                                                 , (x, (y, dy)) <- addIndexes (zip ys dys)]

out

The problem is I'm not sure how to capture the correct dx by which the bar graphs are shifted, when creating the error bars.

timbod7 commented 9 years ago

In the chart library, bar plots can show:

In clustered bar plots, extra horizontal space is added and each value is x offset such that they are shown side by side. In stacked bar plots, extra vertical space is added and each value is y offset.

As you have discovered, the logic to offset the bars is contained within the bar rendering code, and hence is not accessible to other plots (eg error markers). I think that the only solution to this would be to extend the bar plot function so that it understands and can render error ranges. I can provide suggestions if this is something you are interested in pursuing.

However, your original example has only a single bar per x-coordinate. Because there are no offsets in this case, you can overlay an errbars plot over a bar plot to get what you need. It's a bit fiddly, for a couple of reasons. An example:

import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo

alabels = [ "Jun", "Jul", "Aug", "Sep", "Oct" ]

values :: [ (PlotIndex,(Double,Double)) ] 
values = addIndexes [ (20,6), (45,5), (30,6), (10,2), (20,7) ]

fillStyle = solidFillStyle (opaque lightblue)
lineStyle = solidLine 1 (opaque black)

main = toFile def "errbars.png" $ do
  layout_title .= "Example Bars"
  layout_x_axis . laxis_generate .= autoIndexAxis alabels
  layout_y_axis . laxis_override .= axisGridHide
  layout_left_axis_visibility . axis_show_ticks .= False
  plot $ fmap plotBars $ liftEC $ do
    plot_bars_values .= [ (x,[y]) | (x,(y,yerr)) <- values ]
    plot_bars_item_styles .= [ (fillStyle, Just lineStyle) ]
  plot $ liftEC $ do
    plot_errbars_values .=  [ symErrPoint x y 0 yerr | (x,(y,yerr)) <- values ]
    plot_errbars_line_style .= lineStyle

errbars

timbod7 commented 9 years ago

Ah - I didn't notice your edit in the second comment. To get this sort of chart to work would required error ranges to be added as a specific feature to bar charts.

mitchellwrosen commented 9 years ago

Sounds good!

I think that the only solution to this would be to extend the bar plot function so that it understands and can render error ranges. I can provide suggestions if this is something you are interested in pursuing.

I'm interested in pursuing this, time permitting. If you wouldn't mind posting a quick brain-dump here on this ticket, perhaps I or someone else can pick it up at some point.

timbod7 commented 9 years ago

A possible revised API:

data PlotBars x y = PlotBars {
   _plot_bars_style           :: PlotBarsStyle,
   _plot_bars_item_styles     :: [ (FillStyle,Maybe LineStyle) ],
   _plot_bars_titles          :: [String],
   _plot_bars_spacing         :: PlotBarsSpacing,
   _plot_bars_alignment       :: PlotBarsAlignment,
   _plot_bars_reference       :: y,
   _plot_bars_singleton_width :: Double,
   _plot_bars_values          :: [ (x,[y]) ],
   _plot_bars_errbars         :: [ (x,[Maybe (y,y)]) ]   -- Added
}

This would allow you to have error bars on some values and not others.

I think it only makes sense to render the error bars should only be shown in BarsClustered mode. Hence, you'd need to update the clusteredBars function (inside renderPlotBars) to actually render the error bars. You'd also need to update allBarPoints to ensure that the correct range is calculated to include the errors.

PeteRegan commented 8 months ago

I'd like to resurface this issue, now that @clayrat has refactored bar charts. I realize that it would be somewhat involved, but clustered bar charts with error bars are everywhere, and it would be extremely useful to be able to make them.