#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 = GeographicDatasetsRecipe.Create("mercator", mode); var context = new VisualRenderContext( new VisualLayoutOptions(new VisualSize(800, 600)), theme, mode, new VisualFrame("Coverage and delivery routes", "Independent coverage, delay and traffic dimensions", showLegend: true, 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 /// Combines geographic service zones, independently colored weighted depots and retained route facts. public static class GeographicDatasetsRecipe { /// Creates a compact or wide geographic chart using an ordinary or Mercator source projection. public static Chart Create(string variant = "wide", VisualThemeMode mode = VisualThemeMode.Light) { if (variant is not ("wide" or "compact" or "mercator" or "compact-mercator")) throw new ArgumentOutOfRangeException(nameof(variant)); var projection = variant.EndsWith("mercator", StringComparison.Ordinal) ? ChartMapGeoJsonProjection.WebMercator : ChartMapGeoJsonProjection.Equirectangular; var definition = ChartMapGeoJson.ToMapDefinition("service-zones", "Service zones", GeoJson, new ChartMapGeoJsonOptions { Projection = projection }); var coverage = ChartSeries.CreateRegionMap(new ChartSeriesId("coverage"), "Service coverage", definition, new[] { new ChartRegionMapItem("west", 92), new ChartRegionMapItem("central", 74), new ChartRegionMapItem("north", 88) }).WithColorScale(ChartColorScale.Sequential(ChartColor.FromHex("#DBEAFE"), ChartColor.FromHex("#1D4ED8")).WithValueRange(0, 100)); coverage.ConfigureColorDimension(options => { options.Name = "Coverage"; options.ValueFormat = ChartValueFormat.Number("0 '%'", CultureInfo.InvariantCulture); }); var depots = ChartSeries.CreateDottedMap(new ChartSeriesId("depots"), "Depot delays", ChartMapGeoJson.ToMapPoints(GeoJson, new ChartMapGeoJsonOptions { WeightPropertyName = "traffic", ColorValuePropertyName = "delay" })) .WithObservationIds(new[] { new ChartObservationId("madrid"), new ChartObservationId("warsaw"), new ChartObservationId("oslo") }) .WithColorScale(ChartColorScale.Diverging(ChartColor.FromHex("#0D9488"), ChartColor.FromHex("#E2E8F0"), ChartColor.FromHex("#EA580C"), 0).WithValueRange(-10, 30)) .ReplaceMapConnectors(ChartMapGeoJson.ToMapConnectors(GeoJson)); depots.ConfigureColorDimension(options => { options.Name = "Delay"; options.ValueFormat = ChartValueFormat.Number("0 'min'", CultureInfo.InvariantCulture); }); depots.ConfigureMapPointSize(options => { options.WithSizeDomain(0, 100); options.MinimumRadius = 3; options.MaximumRadius = 10; options.SizeDimension.Name = "Traffic"; options.SizeDimension.ShowLegend = true; options.SizeDimension.ValueFormat = ChartValueFormat.Number("0 'routes'", CultureInfo.InvariantCulture); }); return Chart.Create().WithSize(variant.StartsWith("compact", StringComparison.Ordinal) ? 390 : 800, 600) .WithTheme(VisualTheme.Graphite(), mode).WithTitle("Coverage and delivery routes") .WithSubtitle("Regions show coverage; depot color shows delay and size shows traffic") .WithLegend().WithMapLabels(false).WithDataLabels().AddSeries(coverage).AddSeries(depots); } // The rectangles are authored service zones rather than administrative borders. Every importer reads the same source facts. private const string GeoJson = """ {"type":"FeatureCollection","features":[ {"type":"Feature","properties":{"code":"west","name":"West zone"},"geometry":{"type":"Polygon","coordinates":[[[-10,35],[5,35],[5,48],[-10,48],[-10,35]]]}}, {"type":"Feature","properties":{"code":"central","name":"Central zone"},"geometry":{"type":"Polygon","coordinates":[[[5,45],[25,45],[25,56],[5,56],[5,45]]]}}, {"type":"Feature","properties":{"code":"north","name":"North zone"},"geometry":{"type":"Polygon","coordinates":[[[5,56],[25,56],[25,66],[5,66],[5,56]]]}}, {"type":"Feature","properties":{"name":"Madrid","traffic":18,"delay":-5},"geometry":{"type":"Point","coordinates":[-3.7,40.4]}}, {"type":"Feature","properties":{"name":"Warsaw","traffic":74,"delay":20},"geometry":{"type":"Point","coordinates":[21,52.2]}}, {"type":"Feature","properties":{"name":"Oslo","traffic":42,"delay":7},"geometry":{"type":"Point","coordinates":[10.8,59.9]}}, {"type":"Feature","properties":{"name":"Depot route"},"geometry":{"type":"LineString","coordinates":[[-3.7,40.4],[10,45],[21,52.2],[17,56],[10.8,59.9]]}} ]} """; }