#region Setup
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using ChartForgeX.Core;
using ChartForgeX.Primitives;
using ChartForgeX.Rendering;
using ChartForgeX.Themes;
using ChartForgeX.Topology;
using ChartForgeX.Typography;
using ChartForgeX.VisualArtifacts;
using ChartForgeX.VisualBlocks;
var mode = VisualThemeMode.Dark;
var theme = VisualTheme.Graphite();
var model = HistogramRecipe.Create("mean", mode);
var context = new VisualRenderContext(
new VisualLayoutOptions(new VisualSize(800, 440)), theme, mode,
new VisualFrame("Measurements across intervals", "Arithmetic means; the empty interval has no mean", showLegend: false, showSurface: false));
var prepared = model.Prepare(context);
File.WriteAllText("visual.svg", prepared.ToSvg());
File.WriteAllBytes("visual.png", prepared.ToPng());
File.WriteAllText("visual.html", prepared.ToArtifact("example", VisualArtifactKind.Chart).ToHtmlPage());
#endregion
/// Ticket resolution times as counts, shares and densities, and signed or weighted means, over unequal intervals.
public static class HistogramRecipe {
/// Creates a native histogram while retaining its source observations and raw bin aggregates.
public static Chart Create(string variant = "wide", VisualThemeMode mode = VisualThemeMode.Light) {
var aggregation = variant.StartsWith("compact-", StringComparison.Ordinal) ? variant.Substring(8) : variant;
if (aggregation == "compact") aggregation = "wide";
var chart = Chart.Create().WithDataLabels();
ChartSeries observations;
if (aggregation is "wide" or "density" or "probability" or "probability-density") {
var encoding = aggregation switch {
"density" => ChartHistogramEncoding.Density,
"probability" => ChartHistogramEncoding.Probability,
"probability-density" => ChartHistogramEncoding.ProbabilityDensity,
_ => ChartHistogramEncoding.Value
};
// Hours to resolve each service desk ticket closed in October 2026; the intervals widen as tickets get rarer.
var hours = new[] {
.2, .3, .4, .5, .6, .8, .9, 1.1, 1.2, 1.4, 1.5, 1.6, 1.8, 1.9,
2.1, 2.2, 2.3, 2.5, 2.6, 2.7, 2.8, 3, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9,
4.2, 4.4, 4.7, 5, 5.2, 5.5, 5.8, 6, 6.3, 6.5, 6.8, 7, 7.2, 7.5, 7.8,
8.5, 9, 9.6, 10.4, 11, 12, 13, 14.5, 15.5, 17, 18.5, 20, 22.5
};
observations = ChartSeries.CreateHistogram(new ChartSeriesId("resolution-time"), "Tickets", hours,
ChartHistogramBinLayout.FromBoundaries(new[] { 0d, 2, 4, 8, 16, 24 }), encoding);
chart.WithXAxis("Resolution time (hours)").WithYAxis(aggregation switch {
"density" => "Tickets per hour", "probability" => "Share of tickets",
"probability-density" => "Share of tickets per hour", _ => "Tickets"
});
chart.Options.XAxis.WithBounds(0, 24);
foreach (var bin in observations.HistogramBins) observations.WithPointLabel(bin.Index, bin.Count.ToString(CultureInfo.InvariantCulture));
return chart.AddSeries(observations);
}
var layout = ChartHistogramBinLayout.FromBoundaries(new[] { 0d, 1, 3, 6, 10 });
chart.WithXAxis("Measurement").Options.XAxis.WithBounds(0, 10);
if (aggregation == "weighted-mean") {
observations = ChartSeries.CreateHistogram(new ChartSeriesId("weighted-quantity"), "Weighted quantity",
new[] { new ChartHistogramObservation(.2, 10, 1), new ChartHistogramObservation(.4, 20, 3),
new ChartHistogramObservation(1.2, -4, 2), new ChartHistogramObservation(2.5, 12, 1),
new ChartHistogramObservation(6.1, 9, 0), new ChartHistogramObservation(9, 5, 4) },
layout, ChartHistogramAggregation.WeightedMean);
chart.WithYAxis("Weighted mean quantity");
} else if (aggregation is "sum" or "mean") {
observations = ChartSeries.CreateHistogram(new ChartSeriesId("quantity"), "Quantity",
new[] { new ChartPoint(.2, 10), new ChartPoint(.4, -4), new ChartPoint(1.2, 8),
new ChartPoint(2.5, 12), new ChartPoint(6.1, -4), new ChartPoint(9, -8) }, layout,
aggregation == "sum" ? ChartHistogramAggregation.Sum : ChartHistogramAggregation.Mean);
chart.WithYAxis(aggregation == "sum" ? "Total quantity" : "Mean quantity");
} else throw new ArgumentOutOfRangeException(nameof(variant));
return chart.AddSeries(observations);
}
}