2026-08-02 21:41:12 +02:00
|
|
|
using System.Text.Json;
|
2026-11-03 14:20:41 +01:00
|
|
|
using System.Text.Json.Nodes;
|
2026-08-02 21:41:12 +02:00
|
|
|
|
|
|
|
|
namespace AIStudio.Assistants.VisualBriefing;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Turns a validated chart specification into a branded chart-library option object.
|
|
|
|
|
/// </summary>
|
2026-11-03 14:20:41 +01:00
|
|
|
/// <remarks>
|
|
|
|
|
/// The option object is built as JSON nodes rather than as types of its own: it follows the options of Apache
|
|
|
|
|
/// ECharts, which differ for every kind of chart, and nothing in AI Studio reads it again. The order of its
|
|
|
|
|
/// properties is part of the compiled data, which gets hashed.
|
|
|
|
|
/// </remarks>
|
2026-08-02 21:41:12 +02:00
|
|
|
internal static class VisualBriefingChartCompiler
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Compiles one validated chart specification into an Apache ECharts option object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="chart">The validated chart specification.</param>
|
|
|
|
|
/// <returns>The branded chart option.</returns>
|
|
|
|
|
internal static JsonElement Compile(VisualBriefingChartSpec chart)
|
|
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
var series = chart.Kind switch
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT => new JsonArray(
|
|
|
|
|
[
|
|
|
|
|
.. chart.Categories.Select((category, index) => new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["name"] = category,
|
|
|
|
|
["value"] = chart.Series[0].Values[index],
|
|
|
|
|
}),
|
|
|
|
|
]),
|
2026-08-02 21:41:12 +02:00
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
VisualBriefingChartKind.RADAR => new JsonArray(
|
|
|
|
|
[
|
|
|
|
|
.. chart.Series.Select(item => new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["name"] = item.Name,
|
|
|
|
|
["type"] = "radar",
|
|
|
|
|
["data"] = new JsonArray(new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["value"] = Numbers(item.Values),
|
|
|
|
|
["name"] = item.Name,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
]),
|
2026-08-02 21:41:12 +02:00
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
_ => new JsonArray(
|
|
|
|
|
[
|
|
|
|
|
.. chart.Series.Select(item => new JsonObject
|
|
|
|
|
{
|
|
|
|
|
["name"] = item.Name,
|
|
|
|
|
["type"] = SeriesType(chart.Kind),
|
|
|
|
|
["stack"] = chart.Kind is VisualBriefingChartKind.STACKED_BAR ? "total" : null,
|
|
|
|
|
["areaStyle"] = chart.Kind is VisualBriefingChartKind.AREA ? new JsonObject { ["opacity"] = 0.18 } : null,
|
|
|
|
|
["smooth"] = chart.Kind is VisualBriefingChartKind.LINE or VisualBriefingChartKind.AREA,
|
|
|
|
|
["showSymbol"] = chart.Kind is VisualBriefingChartKind.SCATTER,
|
|
|
|
|
["symbolSize"] = chart.Kind is VisualBriefingChartKind.SCATTER ? 10 : 6,
|
|
|
|
|
["itemStyle"] = chart.Kind is VisualBriefingChartKind.BAR or VisualBriefingChartKind.STACKED_BAR
|
|
|
|
|
? new JsonObject { ["borderRadius"] = new JsonArray(6, 6, 0, 0) } : null,
|
|
|
|
|
["data"] = Numbers(item.Values),
|
|
|
|
|
}),
|
|
|
|
|
]),
|
2026-08-02 21:41:12 +02:00
|
|
|
};
|
|
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
var isCircular = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT;
|
|
|
|
|
var hasAxes = chart.Kind is not (VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR);
|
|
|
|
|
var option = new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["color"] = Texts("#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F"),
|
|
|
|
|
["backgroundColor"] = "transparent",
|
|
|
|
|
["textStyle"] = new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["color"] = "#172A24",
|
|
|
|
|
["fontFamily"] = "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
|
2026-08-02 21:41:12 +02:00
|
|
|
},
|
|
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["tooltip"] = new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["trigger"] = isCircular ? "item" : "axis",
|
|
|
|
|
["borderColor"] = "#D6E2DC",
|
|
|
|
|
["backgroundColor"] = "#FFFEFA",
|
|
|
|
|
["textStyle"] = new JsonObject { ["color"] = "#172A24" },
|
2026-08-02 21:41:12 +02:00
|
|
|
},
|
|
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["legend"] = new JsonObject { ["show"] = true, ["top"] = 0, ["textStyle"] = new JsonObject { ["color"] = "#4F635B" } },
|
|
|
|
|
["grid"] = hasAxes
|
|
|
|
|
? new JsonObject { ["left"] = 8, ["right"] = 16, ["top"] = 48, ["bottom"] = 8, ["containLabel"] = true }
|
|
|
|
|
: null,
|
2026-08-02 21:41:12 +02:00
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["xAxis"] = hasAxes
|
|
|
|
|
? new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["type"] = "category",
|
|
|
|
|
["data"] = Texts(chart.Categories),
|
|
|
|
|
["axisLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#B8C9C0" } },
|
|
|
|
|
["axisTick"] = new JsonObject { ["show"] = false },
|
|
|
|
|
["axisLabel"] = new JsonObject { ["color"] = "#5E7169" },
|
|
|
|
|
}
|
|
|
|
|
: null,
|
2026-08-02 21:41:12 +02:00
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["yAxis"] = hasAxes
|
|
|
|
|
? new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["type"] = "value",
|
|
|
|
|
["axisLine"] = new JsonObject { ["show"] = false },
|
|
|
|
|
["axisTick"] = new JsonObject { ["show"] = false },
|
|
|
|
|
["axisLabel"] = new JsonObject { ["color"] = "#5E7169" },
|
|
|
|
|
["splitLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#E1EAE5" } },
|
|
|
|
|
}
|
|
|
|
|
: null,
|
2026-08-02 21:41:12 +02:00
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["radar"] = chart.Kind is VisualBriefingChartKind.RADAR
|
|
|
|
|
? new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["indicator"] = new JsonArray([.. chart.Categories.Select(name => new JsonObject { ["name"] = name })]),
|
|
|
|
|
["splitArea"] = new JsonObject { ["areaStyle"] = new JsonObject { ["color"] = Texts("#FFFEFA", "#EAF1EC") } },
|
|
|
|
|
["axisName"] = new JsonObject { ["color"] = "#5E7169" },
|
|
|
|
|
["splitLine"] = new JsonObject { ["lineStyle"] = new JsonObject { ["color"] = "#B8C9C0" } },
|
2026-08-02 21:41:12 +02:00
|
|
|
}
|
|
|
|
|
: null,
|
|
|
|
|
|
2026-11-03 14:20:41 +01:00
|
|
|
["series"] = isCircular
|
|
|
|
|
? new JsonArray(new JsonObject
|
2026-08-02 21:41:12 +02:00
|
|
|
{
|
2026-11-03 14:20:41 +01:00
|
|
|
["type"] = "pie",
|
|
|
|
|
["radius"] = chart.Kind is VisualBriefingChartKind.DONUT ? Texts("45%", "70%") : Texts("0%", "70%"),
|
|
|
|
|
["padAngle"] = 2,
|
|
|
|
|
["itemStyle"] = new JsonObject { ["borderColor"] = "#FFFEFA", ["borderWidth"] = 2, ["borderRadius"] = 5 },
|
|
|
|
|
["label"] = new JsonObject { ["color"] = "#4F635B" },
|
|
|
|
|
["data"] = series,
|
|
|
|
|
})
|
2026-08-02 21:41:12 +02:00
|
|
|
: series,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return JsonSerializer.SerializeToElement(option, VisualBriefingJson.Canonical);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maps a semantic chart kind to its Apache ECharts series type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="kind">The semantic chart kind.</param>
|
|
|
|
|
/// <returns>The Apache ECharts series type.</returns>
|
|
|
|
|
private static string SeriesType(VisualBriefingChartKind kind) => kind switch
|
|
|
|
|
{
|
|
|
|
|
VisualBriefingChartKind.LINE or VisualBriefingChartKind.AREA => "line",
|
|
|
|
|
VisualBriefingChartKind.BAR or VisualBriefingChartKind.STACKED_BAR => "bar",
|
|
|
|
|
VisualBriefingChartKind.SCATTER => "scatter",
|
|
|
|
|
VisualBriefingChartKind.RADAR => "radar",
|
|
|
|
|
_ => "line",
|
|
|
|
|
};
|
2026-11-03 14:20:41 +01:00
|
|
|
|
|
|
|
|
private static JsonArray Texts(params IEnumerable<string> texts) => new([.. texts.Select(text => JsonValue.Create(text))]);
|
|
|
|
|
|
|
|
|
|
private static JsonArray Numbers(IEnumerable<decimal> numbers) => new([.. numbers.Select(number => JsonValue.Create(number))]);
|
2026-08-02 21:41:12 +02:00
|
|
|
}
|