#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 = AvailabilityHeatmapRecipe.Create("availability", mode); var context = new VisualRenderContext( new VisualLayoutOptions(new VisualSize(800, 440)), theme, mode, new VisualFrame("Daily availability per target", "14\u201327 October 2026; empty cells are days without measurements", showLegend: null, 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 /// Shows two weeks of daily availability for ten targets, with days that have no measurements left empty. public static class AvailabilityHeatmapRecipe { /// Creates the availability heatmap; the compact variant keeps the same data. public static Chart Create(string variant = "availability", VisualThemeMode mode = VisualThemeMode.Light) { if (variant is not ("availability" or "compact-availability")) throw new ArgumentOutOfRangeException(nameof(variant)); var colors = VisualTheme.Graphite().Resolve(mode); var start = new DateTime(2026, 10, 14); var chart = Chart.Create().ConfigureLabels(labels => labels.NoData = "No measurements") .WithXLabels(Enumerable.Range(0, 14).Select(day => start.AddDays(day).ToString("d MMM", CultureInfo.InvariantCulture)).ToArray()) .WithHeatmapCellGap(2).WithValueUnit("%").WithValueFormat(ChartValueFormat.Number("0.#", CultureInfo.InvariantCulture)); var targets = new[] { "dc1", "dc2", "dc3", "dc4", "dc5", "adfs", "dns", "exchange", "1.1.1.1", "8.8.8.8" }; for (var row = 0; row < targets.Length; row++) { var days = new double?[14]; for (var day = 0; day < days.Length; day++) { // Most days are complete; a few lose minutes or hours, and a new probe has no history yet. var loss = (row * 7 + day * 3) % 23 == 0 ? 6.5 : (row + day) % 11 == 0 ? 1.2 : (row * day) % 17 == 5 ? .4 : 0; days[day] = row == 7 && day < 3 ? null : Math.Round(100 - loss, 1); } chart.AddHeatmapRow(targets[row], days, id: new ChartSeriesId("availability-" + targets[row])); } // Named bands read faster than a gradient: a complete day, a short loss, and an outage. return chart.WithHeatmapColorScale(ChartColorScale.Discrete(new[] { new ChartColorBand(95, colors.Status.Critical.Fill, "Outage"), new ChartColorBand(100, colors.Status.Medium.Fill, "Short loss"), new ChartColorBand(null, colors.Status.Pass.Fill, "Complete") }).WithNoDataColor(colors.Border)); } }