Generated the JSON contracts of visual briefings and model answers at compile time (#1151)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-03 14:20:41 +01:00
1 parent 24e3c22db3
commit 770c16e20a
101 files changed
+2333 -392

No files matched your search

@@ -1,10 +1,16 @@
using System.Text.Json;
using System.Text.Json.Nodes;
namespace AIStudio.Assistants.VisualBriefing;
/// <summary>
/// Turns a validated chart specification into a branded chart-library option object.
/// </summary>
/// <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>
internal static class VisualBriefingChartCompiler
{
/// <summary>
@@ -14,114 +20,116 @@ internal static class VisualBriefingChartCompiler
/// <returns>The branded chart option.</returns>
internal static JsonElement Compile(VisualBriefingChartSpec chart)
{
object series = chart.Kind switch
var series = chart.Kind switch
{
VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT =>
chart.Categories.Select((category, index) => new
VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT => new JsonArray(
[
.. chart.Categories.Select((category, index) => new JsonObject
{
name = category,
value = chart.Series[0].Values[index],
}).ToArray(),
["name"] = category,
["value"] = chart.Series[0].Values[index],
}),
]),
VisualBriefingChartKind.RADAR => chart.Series.Select(item => new
{
name = item.Name,
type = "radar",
data = new[]
VisualBriefingChartKind.RADAR => new JsonArray(
[
.. chart.Series.Select(item => new JsonObject
{
new
["name"] = item.Name,
["type"] = "radar",
["data"] = new JsonArray(new JsonObject
{
value = item.Values,
name = item.Name,
},
},
}).ToArray(),
["value"] = Numbers(item.Values),
["name"] = item.Name,
}),
}),
]),
_ => chart.Series.Select(item => new
{
name = item.Name,
type = SeriesType(chart.Kind),
stack = chart.Kind is VisualBriefingChartKind.STACKED_BAR ? "total" : null,
areaStyle = chart.Kind is VisualBriefingChartKind.AREA ? new { 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 { borderRadius = new[] { 6, 6, 0, 0 } } : null,
data = item.Values,
}).ToArray(),
_ => 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),
}),
]),
};
var option = new
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
{
color = new[] { "#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F" },
backgroundColor = "transparent",
textStyle = new
["color"] = Texts("#236A50", "#F2D264", "#79AE90", "#C97857", "#4E7894", "#9B6B8F"),
["backgroundColor"] = "transparent",
["textStyle"] = new JsonObject
{
color = "#172A24",
fontFamily = "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
["color"] = "#172A24",
["fontFamily"] = "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
},
tooltip = new
["tooltip"] = new JsonObject
{
trigger = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT ? "item" : "axis",
borderColor = "#D6E2DC",
backgroundColor = "#FFFEFA",
textStyle = new { color = "#172A24" },
["trigger"] = isCircular ? "item" : "axis",
["borderColor"] = "#D6E2DC",
["backgroundColor"] = "#FFFEFA",
["textStyle"] = new JsonObject { ["color"] = "#172A24" },
},
legend = new { show = true, top = 0, textStyle = new { color = "#4F635B" } },
grid = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
? null
: new { left = 8, right = 16, top = 48, bottom = 8, containLabel = true },
["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,
xAxis = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
? null
: new
["xAxis"] = hasAxes
? new JsonObject
{
type = "category",
data = chart.Categories,
axisLine = new { lineStyle = new { color = "#B8C9C0" } },
axisTick = new { show = false },
axisLabel = new { color = "#5E7169" },
},
yAxis = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT or VisualBriefingChartKind.RADAR
? null
: new
{
type = "value",
axisLine = new { show = false },
axisTick = new { show = false },
axisLabel = new { color = "#5E7169" },
splitLine = new { lineStyle = new { color = "#E1EAE5" } },
},
radar = chart.Kind is VisualBriefingChartKind.RADAR
? new
{
indicator = chart.Categories.Select(name => new { name }).ToArray(),
splitArea = new { areaStyle = new { color = new[] { "#FFFEFA", "#EAF1EC" } } },
axisName = new { color = "#5E7169" },
splitLine = new { lineStyle = new { color = "#B8C9C0" } },
["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,
series = chart.Kind is VisualBriefingChartKind.PIE or VisualBriefingChartKind.DONUT
? new[]
["yAxis"] = hasAxes
? new JsonObject
{
new
{
type = "pie",
radius = chart.Kind is VisualBriefingChartKind.DONUT
? new[] { "45%", "70%" }
: new[] { "0%", "70%" },
padAngle = 2,
itemStyle = new { borderColor = "#FFFEFA", borderWidth = 2, borderRadius = 5 },
label = new { color = "#4F635B" },
data = series,
},
["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,
["radar"] = chart.Kind is VisualBriefingChartKind.RADAR
? new JsonObject
{
["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" } },
}
: null,
["series"] = isCircular
? new JsonArray(new JsonObject
{
["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,
})
: series,
};
@@ -141,4 +149,8 @@ internal static class VisualBriefingChartCompiler
VisualBriefingChartKind.RADAR => "radar",
_ => "line",
};
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))]);
}