diff --git a/src/FSharp.Charting.Gtk.fs b/src/FSharp.Charting.Gtk.fs index 6a7fe11..2d57676 100644 --- a/src/FSharp.Charting.Gtk.fs +++ b/src/FSharp.Charting.Gtk.fs @@ -208,6 +208,9 @@ namespace FSharp.Charting module ChartTypes = + /// An implementation of a histogram bin. + type Bin = { LowerBound: float; UpperBound: float; Count: int} + /// An implementation type for items on a chart. This type should not be used directly. type public LineChartItem(X: key, Y: value) = member __.X = X @@ -616,6 +619,14 @@ namespace FSharp.Charting // ---------------------------------------------------------------------------------- // Data operations + let internal binData data lowerBound upperBound intervals = + seq{lowerBound .. (upperBound - lowerBound)/intervals .. upperBound } + |> Seq.pairwise + |> Seq.map (fun (l,u) -> + let cnt = data |> Seq.filter (fun e -> e >= l && e < u) |> Seq.length + {LowerBound = l; UpperBound = u; Count = cnt} + ) + let internal allowNull x = match x with None -> null | Some v -> v let internal listen data = NotifySeq.notifyOrOnce data @@ -694,7 +705,7 @@ namespace FSharp.Charting type internal Helpers() = /// Use a DateTime axis if the input key data is DateTime - static member ApplyStaticAxis(xty, pos) = (fun (ch:('T :> GenericChart)) -> + static member ApplyStaticAxis(xty, pos, ?gap) = (fun (ch:('T :> GenericChart)) -> let model = ch.Model match model.DefaultXAxis with | null -> @@ -703,7 +714,12 @@ namespace FSharp.Charting if xty = typeof then model.Axes.Add (Axes.TimeSpanAxis(Position=pos)) if xty = typeof then - model.Axes.Add (Axes.CategoryAxis(Position=pos)) + match gap with + | Some g -> + let a = Axes.CategoryAxis(Position=pos) + a.GapWidth <- g + model.Axes.Add (a) + | _ -> model.Axes.Add (Axes.CategoryAxis(Position=pos)) | _ -> () ch) @@ -731,6 +747,7 @@ namespace FSharp.Charting AxisYTitle |> Option.iter (fun t -> ensureDefaultYAxis().Title <- t) ch) + /// Provides a set of static methods for creating charts. type Chart = /// Register a function that is used to automatically transform X values (keys) @@ -912,9 +929,13 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data:seq<('key :> key)*#value>,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle) = + /// The width of columns versus whitespace as a percentage. + static member Column(data:seq<('key :> key)*#value>,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle,?ColumnWidth:float) = + let gap = match ColumnWidth with + | Some columnWidth -> Some (1.0 - columnWidth) + | _ -> None GenericChart.Create(data |> listen |> mergeLabels Labels |> makeItems (fun ((k,v),labelOpt) -> ColumnItem(valueToDouble v)), ColumnSeries(ValueField="Value")) - |> Helpers.ApplyStaticAxis(typeof<'key>, Axes.AxisPosition.Bottom) + |> Helpers.ApplyStaticAxis(typeof<'key>, Axes.AxisPosition.Bottom, ?gap = gap) |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) /// Uses a sequence of columns to compare values across categories. @@ -925,8 +946,35 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data:seq<#value>,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle) = - Chart.Column(indexData data,?Name=Name,?Title=Title,?Labels=Labels, ?Color=Color,?XTitle=XTitle,?YTitle=YTitle) + /// The width of columns versus whitespace as a percentage. + static member Column(data:seq<#value>,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle, ?ColumnWidth) = + Chart.Column(indexData data,?Name=Name,?Title=Title,?Labels=Labels, ?Color=Color,?XTitle=XTitle,?YTitle=YTitle, ?ColumnWidth=ColumnWidth) + + + /// Generates a Histogram with reasonable defaults. + /// The data for the chart. + /// The name of the data set. + /// The title of the chart. + /// The color for the data. + /// The title of the X-axis. + /// The title of the Y-axis. + /// The lower bound of the histogram. + /// The upper bound of the histogram. + /// The number of intervals in the histogram. + static member Histogram(data:seq<#value>,?Name,?Title,?Color,?XTitle,?YTitle, ?LowerBound, ?UpperBound, ?Intervals) = + let data' = data |> Seq.map valueToDouble + let lowerBound = match LowerBound with + | Some lowerBound -> lowerBound + | _ -> Seq.min data + let upperBound = match UpperBound with + | Some upperBound -> upperBound + | _ -> Seq.max data + let intervals = match Intervals with + | Some intervals -> intervals + | _ -> 30. // corresponds to what ggplot does + let bins = binData data' lowerBound upperBound intervals + let data'' = bins |> Seq.map (fun b -> ( sprintf "%.2f" b.LowerBound), b.Count) + Chart.Column(data'',?Name=Name,?Title=Title,?Color=Color,?XTitle=XTitle,?YTitle=YTitle, ?ColumnWidth=Some 0.95) #if INCOMPLETE_API /// Similar to the Pie chart type, except that it has a hole in the center. @@ -1785,8 +1833,9 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data:IObservable<#seq<#key * #value>>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle) = - Chart.Column(NotifySeq.ofObservableReplacing data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle) + /// The width of columns versus whitespace as a percentage. + static member Column(data:IObservable<#seq<#key * #value>>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle,?ColumnWidth) = + Chart.Column(NotifySeq.ofObservableReplacing data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle,?ColumnWidth=ColumnWidth) /// Uses a sequence of columns to compare values across categories. /// The data for the chart. Each observation adds a data element to the chart. @@ -1796,8 +1845,9 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member ColumnIncremental(data:IObservable<#key * #value>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle) = - Chart.Column(NotifySeq.ofObservableIncremental data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle) + /// The width of columns versus whitespace as a percentage. + static member ColumnIncremental(data:IObservable<#key * #value>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle,?ColumnWidth) = + Chart.Column(NotifySeq.ofObservableIncremental data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle, ?ColumnWidth=ColumnWidth) #if INCOMPLETE_API /// Similar to the Pie chart type, except that it has a hole in the center. @@ -2578,4 +2628,3 @@ namespace FSharp.Charting #endif - diff --git a/src/FSharp.Charting.fs b/src/FSharp.Charting.fs index 6ae14a4..58b3fe8 100644 --- a/src/FSharp.Charting.fs +++ b/src/FSharp.Charting.fs @@ -261,6 +261,10 @@ namespace FSharp.Charting do registerConvertor (fun (dto:DateTimeOffset) -> dto.DateTime :> key) module ChartTypes = + + /// An implementation of a histogram bin. + type Bin = { LowerBound: float; UpperBound: float; Count: int} + /// An implementation type for labelled points on chart. This type should not be used directly. type public DataPoint(X: key, Y: value, Label: string) = member __.Label = Label @@ -830,6 +834,13 @@ namespace FSharp.Charting // stacked values | StackedXYValues of seq + let internal binData data lowerBound upperBound intervals = + seq{lowerBound .. (upperBound - lowerBound)/intervals .. upperBound } + |> Seq.pairwise + |> Seq.map (fun (l,u) -> + let cnt = data |> Seq.filter (fun e -> e >= l && e < u) |> Seq.length + {LowerBound = l; UpperBound = u; Count = cnt} + ) // ---------------------------------------------------------------------------------- // Utilities for working with enumerable and tuples @@ -847,6 +858,8 @@ namespace FSharp.Charting // Converts Y value of a chart (defines the type too) let culture = System.Globalization.CultureInfo.InvariantCulture + let valueToDouble (x:value) = x.ToDouble(culture) + open System.Collections.Specialized let private convertKeys (selector:_ -> key) (transform:(key -> key) -> _) data = @@ -2395,6 +2408,9 @@ namespace FSharp.Charting GenericChart.Create(mergeDataAndLabelsForY4 data Labels, fun() -> CandlestickChart() ) |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) + static member internal ConfigureColumn(c:GenericChart, property, vPointWidth) = + vPointWidth |> Option.iter (fun v -> c.SetCustomProperty(property, v)) + /// Uses a sequence of columns to compare values across categories. /// The data for the chart. /// The name of the data set. @@ -2403,10 +2419,14 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle) = - GenericChart.Create(mergeDataAndLabelsForXY data Labels, fun () -> GenericChart(SeriesChartType.Column)) - |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) - + /// The width of columns versus whitespace as a percentage. + static member Column(data,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle,?ColumnWidth) = + let c = + GenericChart.Create(mergeDataAndLabelsForXY data Labels, fun () -> GenericChart(SeriesChartType.Column)) + |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) + Chart.ConfigureColumn(c, "PointWidth", ColumnWidth) + c + /// Uses a sequence of columns to compare values across categories. /// The data for the chart. /// The name of the data set. @@ -2415,10 +2435,13 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data,?Name,?Title,?Labels, ?Color,?XTitle,?YTitle) = - GenericChart.Create(mergeDataAndLabelsForY data Labels, fun () -> GenericChart(SeriesChartType.Column)) - |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) - + /// The width of columns versus whitespace as a percentage. + static member Column(data,?Name,?Title,?Labels,?Color,?XTitle,?YTitle,?ColumnWidth) = + let c = + GenericChart.Create(mergeDataAndLabelsForY data Labels, fun () -> GenericChart(SeriesChartType.Column)) + |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) + Chart.ConfigureColumn(c, "PointWidth", ColumnWidth) + c /// Similar to the Pie chart type, except that it has a hole in the center. /// The data for the chart. @@ -2529,6 +2552,31 @@ namespace FSharp.Charting GenericChart.Create(mergeDataAndLabelsForY data Labels, fun () -> FunnelChart () ) |> Helpers.ApplyStyles(?Name=Name,?Title=Title,?Color=Color,?AxisXTitle=XTitle,?AxisYTitle=YTitle) + /// Generates a Histogram with reasonable defaults. + /// The data for the chart. + /// The name of the data set. + /// The title of the chart. + /// The color for the data. + /// The title of the X-axis. + /// The title of the Y-axis. + /// The lower bound of the histogram. + /// The upper bound of the histogram. + /// The number of intervals in the histogram. + static member Histogram(data:seq<#value>,?Name,?Title,?Color,?XTitle,?YTitle, ?LowerBound, ?UpperBound, ?Intervals) = + let data' = data |> Seq.map valueToDouble + let lowerBound = match LowerBound with + | Some lowerBound -> lowerBound + | _ -> Seq.min data + let upperBound = match UpperBound with + | Some upperBound -> upperBound + | _ -> Seq.max data + let intervals = match Intervals with + | Some intervals -> intervals + | _ -> 30. // corresponds to what ggplot does + let bins = binData data' lowerBound upperBound intervals + let data'' = bins |> Seq.map (fun b -> ( sprintf "%.2f" b.LowerBound), b.Count) + Chart.Column(data'',?Name=Name,?Title=Title,?Color=Color,?XTitle=XTitle,?YTitle=YTitle,?ColumnWidth=Some 0.95) + /// Displays a series of connecting vertical lines where the thickness and direction of the lines are dependent on the action of the price value. /// The data for the chart. /// The name of the data set. @@ -3292,8 +3340,9 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member Column(data:IObservable<#seq<#key * #value>>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle) = - Chart.Column(NotifySeq.ofObservableReplacing data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle) + /// The width of columns versus whitespace as a percentage. + static member Column(data:IObservable<#seq<#key * #value>>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle,?ColumnWidth) = + Chart.Column(NotifySeq.ofObservableReplacing data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle,?ColumnWidth=ColumnWidth) /// Uses a sequence of columns to compare values across categories. /// The data for the chart. Each observation adds a data element to the chart. @@ -3303,8 +3352,9 @@ namespace FSharp.Charting /// The color for the data. /// The title of the X-axis. /// The title of the Y-axis. - static member ColumnIncremental(data:IObservable<#key * #value>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle) = - Chart.Column(NotifySeq.ofObservableIncremental data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle) + /// The width of columns versus whitespace as a percentage. + static member ColumnIncremental(data:IObservable<#key * #value>,?Name,?Title,(* ?Labels, *) ?Color,?XTitle,?YTitle,?ColumnWidth) = + Chart.Column(NotifySeq.ofObservableIncremental data,?Name=Name,?Title=Title(* ,?Labels=Labels *),?Color=Color,?XTitle=XTitle,?YTitle=YTitle,?ColumnWidth=ColumnWidth) /// Similar to the Pie chart type, except that it has a hole in the center. /// The data for the chart. Each observation replaces the entire data on the chart.