#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.Light; var theme = VisualTheme.Graphite(); var model = MixedScheduleRecipe.Create("time", mode); var context = new VisualRenderContext( new VisualLayoutOptions(new VisualSize(900, 444)), theme, mode, new VisualFrame("Shared schedule evidence", "Source-ordered ranges, tasks, state intervals and packed resource items retain one native time axis", 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 /// One native time axis retains real ranges, tasks, state intervals and packed resource items. public static class MixedScheduleRecipe { /// Creates eight numeric/date and ordinary/reversed compact/wide schedule presentations. public static Chart Create(string variant = "wide", VisualThemeMode mode = VisualThemeMode.Light) { if (variant is not ("wide" or "compact" or "reversed" or "compact-reversed" or "time" or "compact-time" or "time-reversed" or "compact-time-reversed")) throw new ArgumentOutOfRangeException(nameof(variant)); var compact = variant.StartsWith("compact", StringComparison.Ordinal); var time = variant.Contains("time", StringComparison.Ordinal); var reversed = variant.EndsWith("reversed", StringComparison.Ordinal); var origin = time ? new DateTime(2026, 10, 20, 0, 0, 0, DateTimeKind.Utc).ToOADate() : 0; var chart = Chart.Create().WithSize(compact ? 390 : 900, 660).WithTheme(VisualTheme.Graphite(), mode) .WithTitle("One release, several kinds of schedule evidence").WithSubtitle("Reversal changes presentation; source instants and resource identities remain unchanged") .WithXAxisBounds(origin, origin + 10).WithDataLabels().WithStateCategories( new ChartStateCategory("ready", "Ready", ChartColor.FromHex("#218A63")), new ChartStateCategory("review", "Review", ChartColor.FromHex("#BD771C"), ChartStatePattern.Outlined)); if (time) chart.WithXAxisTimeScale(TimeZoneInfo.Utc); chart.AddSeries(new ChartSeries(new ChartSeriesId("window"), "Delivery window", ChartSeriesKind.Timeline, new[] { new ChartPoint(origin + 1, origin + 9) }).WithObservationId(0, new ChartObservationId("delivery-window"))); var build = ChartSeries.CreateGanttTask(new ChartSeriesId("build"), "Implementation", origin + 2, origin + 4, .65) .WithObservationId(0, new ChartObservationId("implementation")); chart.AddSeries(build); chart.AddSeries(ChartSeries.CreateStateTimelineLane(new ChartSeriesId("service"), "Service state", new[] { new ChartStateTimelineSegment(origin + 1, origin + 3, "ready"), new ChartStateTimelineSegment(origin + 4, origin + 7, "review") }, summary: "One source gap", group: "Resources").WithObservationId(0, new ChartObservationId("ready-period"))); chart.AddSeries(ChartSeries.CreateGanttLane(new ChartSeriesId("reviewers"), "Review activity", new[] { new ChartGanttLaneItem(origin + 2, origin + 6, "review", "Review"), new ChartGanttLaneItem(origin + 3, null, "ready", "Follow-up") }, group: "Resources", summary: "Two overlapping items").WithObservationId(0, new ChartObservationId("review-item"))); var gate = ChartSeries.CreateGanttMilestone(new ChartSeriesId("gate"), "Acceptance", origin + 8) .WithObservationId(0, new ChartObservationId("acceptance")); chart.AddSeries(gate).AddGanttDependency(build, gate); chart.AddSeries(ChartSeries.CreateGanttMarker(new ChartSeriesId("checkpoint"), "Current checkpoint", origin + 9)); chart.WithGanttToday(origin + 9); chart.ConfigureXAxis(axis => axis.Reversed = reversed); chart.ConfigureYAxis(axis => axis.Reversed = reversed); return chart; } }