#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 = SankeyOptionsRecipe.Create("quantitative-colors", mode); var context = new VisualRenderContext( new VisualLayoutOptions(new VisualSize(800, 440)), theme, mode, new VisualFrame("Requests across processing stages", "Request counts size ribbons; independent latency measurements color them", 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 /// Aligned and ordered flows retain parallel-link identities and independent link paint. public static class SankeyOptionsRecipe { public static Chart Create(string variant = "options", VisualThemeMode mode = VisualThemeMode.Light) { var requests = ChartSeries.CreateSankey(new ChartSeriesId("request-flows"), "Requests", new[] { new ChartNode("received", "Received"), new ChartNode("north", "North"), new ChartNode("south", "South"), new ChartNode("north-support", "Support"), new ChartNode("south-support", "Support"), new ChartNode("completed", "Completed"), new ChartNode("review", "Review"), new ChartNode("imported", "Imported") }, new[] { new ChartFlowLink("received-north", "received", "north", 72), new ChartFlowLink("received-south", "received", "south", 28), new ChartFlowLink("north-standard", "north", "north-support", 52), new ChartFlowLink("north-priority", "north", "north-support", 20), new ChartFlowLink("south-standard", "south", "south-support", 28), new ChartFlowLink("north-completed", "north-support", "completed", 72), new ChartFlowLink("south-completed", "south-support", "completed", 28), new ChartFlowLink("received-review", "received", "review", 5), new ChartFlowLink("imported-completed", "imported", "completed", 10) }); var quantitative = variant.EndsWith("quantitative-colors", StringComparison.Ordinal); if (quantitative) { // Illustrative latency is an independent color fact; request weights and flow identities remain unchanged. var latency = new double?[] { 180, 95, 125, 40, 0, null, 65, 110, 210 }; requests.ReplaceFlowData(requests.Nodes, requests.FlowLinks.Select((link, index) => new ChartFlowLink(link.Id, link.SourceId, link.TargetId, link.Value, latency[index]))); } requests.WithNodeState("north-support", ChartSeriesState.Warning).WithPointColor(0, ChartColor.FromHex("#7356BD")) .WithFlowStyle("north-priority", new ChartFlowStyle(fill: ChartColor.FromHex("#AF6B24"), fillOpacity: .7, fillPattern: ChartFillPattern.DiagonalForward, stroke: ChartColor.FromHex("#764415"), strokeWidth: 1)) .WithFlowState("north-priority", ChartSeriesState.Warning); var chart = Chart.Create().AddSeries(requests).ConfigureSankey(options => { options.Alignment = ChartSankeyAlignment.Center; options.VerticalAlignment = ChartSankeyVerticalAlignment.Top; options.NodeOrder = ChartSankeyNodeOrder.LabelAscending; options.LabelPlacement = ChartSankeyLabelPlacement.Center; options.EdgeLabelPlacement = ChartSankeyEdgeLabelPlacement.Outside; options.NodeWidth = 16; options.NodeGap = 12; options.NodeCornerRadius = 1; options.RibbonFill = ChartColor.FromHex("#607DAA"); options.RibbonOpacity = .45; }); if (variant.EndsWith("source-colors", StringComparison.Ordinal) || variant.EndsWith("target-colors", StringComparison.Ordinal) || quantitative) { chart.ConfigureSankey(options => { options.RibbonFill = null; // Let the selected endpoint or measurement policy supply ribbon paint. options.FlowColorMode = quantitative ? ChartSankeyFlowColorMode.Quantitative : variant.EndsWith("target-colors", StringComparison.Ordinal) ? ChartSankeyFlowColorMode.Target : ChartSankeyFlowColorMode.Source; if (quantitative) { options.ColorScale = ChartColorScale.Sequential(ChartColor.FromHex("#2166AC"), ChartColor.FromHex("#B2182B")) .WithValueRange(0, 240).WithNoDataColor(ChartColor.FromHex("#9CA3AF")); options.ColorDimension.Name = "Illustrative latency"; options.ColorDimension.ValueFormat = ChartValueFormat.Number("0 'ms'", CultureInfo.InvariantCulture); options.ColorDimension.LegendStyle = ChartColorLegendStyle.Gradient; } }); if (quantitative) chart.WithLegend(true).ConfigureLabels(labels => labels.NoData = "Not sampled"); } return chart; } }