Files
AX-Copilot-Codex/.decompiledproj/AxCopilot/Services/Agent/ChartSkill.cs

810 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class ChartSkill : IAgentTool
{
private sealed class Dataset
{
public string Name { get; init; } = "";
public List<double> Values { get; init; } = new List<double>();
public string Color { get; init; } = "#4B5EFC";
}
private static readonly string[] Palette = new string[10] { "#4B5EFC", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#06B6D4", "#EC4899", "#84CC16", "#F97316", "#6366F1" };
private const string ChartCss = "\n/* Vertical Bar Chart */\n.vbar-chart { margin: 16px 0; }\n.vbar-bars { display: flex; align-items: flex-end; gap: 8px; height: 220px; padding: 0 8px; border-bottom: 2px solid #E5E7EB; }\n.vbar-group { flex: 1; display: flex; gap: 3px; align-items: flex-end; position: relative; }\n.vbar-bar { flex: 1; min-width: 18px; border-radius: 4px 4px 0 0; transition: opacity 0.2s; cursor: default; }\n.vbar-bar:hover { opacity: 0.8; }\n.vbar-label { text-align: center; font-size: 11px; color: #6B7280; margin-top: 6px; position: absolute; bottom: -24px; left: 0; right: 0; }\n\n/* Horizontal Bar Chart */\n.hbar-chart { margin: 12px 0; }\n.hbar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }\n.hbar-label { min-width: 80px; text-align: right; font-size: 12px; color: #374151; font-weight: 500; }\n.hbar-track { flex: 1; height: 22px; background: #F3F4F6; border-radius: 6px; overflow: hidden; }\n.hbar-fill { height: 100%; border-radius: 6px; transition: width 0.6s ease; }\n.hbar-value { min-width: 50px; font-size: 12px; color: #6B7280; font-weight: 600; }\n\n/* Line/Area Chart */\n.line-chart-svg { width: 100%; max-width: 600px; height: auto; }\n\n/* Legend */\n.chart-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; padding-top: 8px; border-top: 1px solid #F3F4F6; }\n.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #374151; }\n.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }\n\n/* Pie Legend */\n.pie-legend { display: flex; flex-direction: column; gap: 6px; }\n.pie-legend-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #374151; }\n";
public string Name => "chart_create";
public string Description => "Create a styled HTML chart document with CSS/SVG-based charts. Supports chart types: bar, horizontal_bar, stacked_bar, line, area, pie, donut, radar, progress, comparison. Multiple charts can be placed in one document using the 'charts' array. Applies design mood from TemplateService (modern, professional, creative, etc.).";
public ToolParameterSchema Parameters
{
get
{
ToolParameterSchema obj = new ToolParameterSchema
{
Properties = new Dictionary<string, ToolProperty>
{
["path"] = new ToolProperty
{
Type = "string",
Description = "Output file path (.html). Relative to work folder."
},
["title"] = new ToolProperty
{
Type = "string",
Description = "Document title"
},
["charts"] = new ToolProperty
{
Type = "array",
Description = "Array of chart objects. Each chart: {\"type\": \"bar|horizontal_bar|stacked_bar|line|area|pie|donut|radar|progress|comparison\", \"title\": \"Chart Title\", \"labels\": [\"A\",\"B\",\"C\"], \"datasets\": [{\"name\": \"Series1\", \"values\": [10,20,30], \"color\": \"#4B5EFC\"}], \"unit\": \"%\"}",
Items = new ToolProperty
{
Type = "object"
}
},
["mood"] = new ToolProperty
{
Type = "string",
Description = "Design mood: modern, professional, creative, dark, dashboard, etc. Default: dashboard"
},
["layout"] = new ToolProperty
{
Type = "string",
Description = "Chart layout: 'single' (one per row) or 'grid' (2-column grid). Default: single"
}
}
};
int num = 3;
List<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
Span<string> span = CollectionsMarshal.AsSpan(list);
span[0] = "path";
span[1] = "title";
span[2] = "charts";
obj.Required = list;
return obj;
}
}
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
{
string path = args.GetProperty("path").GetString() ?? "chart.html";
string title = args.GetProperty("title").GetString() ?? "Chart";
JsonElement m;
string mood = (args.TryGetProperty("mood", out m) ? (m.GetString() ?? "dashboard") : "dashboard");
JsonElement l;
string layout = (args.TryGetProperty("layout", out l) ? (l.GetString() ?? "single") : "single");
string fullPath = FileReadTool.ResolvePath(path, context.WorkFolder);
if (context.ActiveTab == "Cowork")
{
fullPath = AgentContext.EnsureTimestampedPath(fullPath);
}
if (!fullPath.EndsWith(".html", StringComparison.OrdinalIgnoreCase))
{
fullPath += ".html";
}
if (!context.IsPathAllowed(fullPath))
{
return ToolResult.Fail("경로 접근 차단: " + fullPath);
}
if (!(await context.CheckWritePermissionAsync(Name, fullPath)))
{
return ToolResult.Fail("쓰기 권한 거부: " + fullPath);
}
string dir = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
if (!args.TryGetProperty("charts", out var chartsEl) || chartsEl.ValueKind != JsonValueKind.Array)
{
return ToolResult.Fail("charts 파라미터가 필요합니다 (배열 형식).");
}
int chartCount = chartsEl.GetArrayLength();
StringBuilder body = new StringBuilder();
StringBuilder stringBuilder = body;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder);
handler.AppendLiteral("<h1>");
handler.AppendFormatted(Escape(title));
handler.AppendLiteral("</h1>");
stringBuilder.AppendLine(ref handler);
if (layout == "grid" && chartCount > 1)
{
body.AppendLine("<div class=\"grid-2\">");
}
int chartIdx = 0;
foreach (JsonElement chartEl in chartsEl.EnumerateArray())
{
string chartHtml = RenderChart(chartEl, chartIdx);
body.AppendLine("<div class=\"card\" style=\"margin-bottom:20px;\">");
body.AppendLine(chartHtml);
body.AppendLine("</div>");
chartIdx++;
}
if (layout == "grid" && chartCount > 1)
{
body.AppendLine("</div>");
}
string css = TemplateService.GetCss(mood) + "\n\n/* Vertical Bar Chart */\n.vbar-chart { margin: 16px 0; }\n.vbar-bars { display: flex; align-items: flex-end; gap: 8px; height: 220px; padding: 0 8px; border-bottom: 2px solid #E5E7EB; }\n.vbar-group { flex: 1; display: flex; gap: 3px; align-items: flex-end; position: relative; }\n.vbar-bar { flex: 1; min-width: 18px; border-radius: 4px 4px 0 0; transition: opacity 0.2s; cursor: default; }\n.vbar-bar:hover { opacity: 0.8; }\n.vbar-label { text-align: center; font-size: 11px; color: #6B7280; margin-top: 6px; position: absolute; bottom: -24px; left: 0; right: 0; }\n\n/* Horizontal Bar Chart */\n.hbar-chart { margin: 12px 0; }\n.hbar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }\n.hbar-label { min-width: 80px; text-align: right; font-size: 12px; color: #374151; font-weight: 500; }\n.hbar-track { flex: 1; height: 22px; background: #F3F4F6; border-radius: 6px; overflow: hidden; }\n.hbar-fill { height: 100%; border-radius: 6px; transition: width 0.6s ease; }\n.hbar-value { min-width: 50px; font-size: 12px; color: #6B7280; font-weight: 600; }\n\n/* Line/Area Chart */\n.line-chart-svg { width: 100%; max-width: 600px; height: auto; }\n\n/* Legend */\n.chart-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; padding-top: 8px; border-top: 1px solid #F3F4F6; }\n.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #374151; }\n.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }\n\n/* Pie Legend */\n.pie-legend { display: flex; flex-direction: column; gap: 6px; }\n.pie-legend-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #374151; }\n";
string html = $"<!DOCTYPE html>\n<html lang=\"ko\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>{Escape(title)}</title>\n<style>\n{css}\n</style>\n</head>\n<body>\n<div class=\"container\">\n{body}\n</div>\n</body>\n</html>";
await File.WriteAllTextAsync(fullPath, html, Encoding.UTF8, ct);
return ToolResult.Ok($"차트 문서 생성 완료: {fullPath} ({chartCount}개 차트)", fullPath);
}
private string RenderChart(JsonElement chart, int idx)
{
JsonElement value;
string text = (chart.TryGetProperty("type", out value) ? (value.GetString() ?? "bar") : "bar");
JsonElement value2;
string text2 = (chart.TryGetProperty("title", out value2) ? (value2.GetString() ?? "") : "");
JsonElement value3;
string unit = (chart.TryGetProperty("unit", out value3) ? (value3.GetString() ?? "") : "");
List<string> labels = ParseStringArray(chart, "labels");
List<Dataset> list = ParseDatasets(chart);
StringBuilder stringBuilder = new StringBuilder();
if (!string.IsNullOrEmpty(text2))
{
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(37, 1, stringBuilder2);
handler.AppendLiteral("<h3 style=\"margin-bottom:12px;\">");
handler.AppendFormatted(Escape(text2));
handler.AppendLiteral("</h3>");
stringBuilder3.AppendLine(ref handler);
}
switch (text)
{
case "bar":
stringBuilder.Append(RenderBarChart(labels, list, unit, horizontal: false));
break;
case "horizontal_bar":
stringBuilder.Append(RenderBarChart(labels, list, unit, horizontal: true));
break;
case "stacked_bar":
stringBuilder.Append(RenderStackedBar(labels, list, unit));
break;
case "line":
case "area":
stringBuilder.Append(RenderLineChart(labels, list, unit, text == "area"));
break;
case "pie":
case "donut":
stringBuilder.Append(RenderPieChart(labels, list, text == "donut"));
break;
case "progress":
stringBuilder.Append(RenderProgressChart(labels, list, unit));
break;
case "comparison":
stringBuilder.Append(RenderComparisonChart(labels, list, unit));
break;
case "radar":
stringBuilder.Append(RenderRadarChart(labels, list));
break;
default:
stringBuilder.Append(RenderBarChart(labels, list, unit, horizontal: false));
break;
}
if (list.Count > 1)
{
stringBuilder.AppendLine("<div class=\"chart-legend\">");
foreach (Dataset item in list)
{
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(85, 2, stringBuilder2);
handler.AppendLiteral("<span class=\"legend-item\"><span class=\"legend-dot\" style=\"background:");
handler.AppendFormatted(item.Color);
handler.AppendLiteral("\"></span>");
handler.AppendFormatted(Escape(item.Name));
handler.AppendLiteral("</span>");
stringBuilder4.AppendLine(ref handler);
}
stringBuilder.AppendLine("</div>");
}
return stringBuilder.ToString();
}
private static string RenderBarChart(List<string> labels, List<Dataset> datasets, string unit, bool horizontal)
{
double num = datasets.SelectMany((Dataset d) => d.Values).DefaultIfEmpty(1.0).Max();
if (num <= 0.0)
{
num = 1.0;
}
StringBuilder stringBuilder = new StringBuilder();
if (horizontal)
{
stringBuilder.AppendLine("<div class=\"hbar-chart\">");
for (int num2 = 0; num2 < labels.Count; num2++)
{
double num3 = ((datasets.Count > 0 && num2 < datasets[0].Values.Count) ? datasets[0].Values[num2] : 0.0);
int value = (int)(num3 / num * 100.0);
string value2 = ((datasets.Count > 0) ? datasets[0].Color : Palette[0]);
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(54, 1, stringBuilder2);
handler.AppendLiteral("<div class=\"hbar-row\"><span class=\"hbar-label\">");
handler.AppendFormatted(Escape(labels[num2]));
handler.AppendLiteral("</span>");
stringBuilder3.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(88, 2, stringBuilder2);
handler.AppendLiteral("<div class=\"hbar-track\"><div class=\"hbar-fill\" style=\"width:");
handler.AppendFormatted(value);
handler.AppendLiteral("%;background:");
handler.AppendFormatted(value2);
handler.AppendLiteral(";\"></div></div>");
stringBuilder4.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(38, 2, stringBuilder2);
handler.AppendLiteral("<span class=\"hbar-value\">");
handler.AppendFormatted(num3, "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("</span></div>");
stringBuilder5.AppendLine(ref handler);
}
stringBuilder.AppendLine("</div>");
}
else
{
stringBuilder.AppendLine("<div class=\"vbar-chart\">");
stringBuilder.AppendLine("<div class=\"vbar-bars\">");
for (int num4 = 0; num4 < labels.Count; num4++)
{
stringBuilder.AppendLine("<div class=\"vbar-group\">");
StringBuilder stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler;
foreach (Dataset dataset in datasets)
{
double num5 = ((num4 < dataset.Values.Count) ? dataset.Values[num4] : 0.0);
int value3 = (int)(num5 / num * 100.0);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(67, 4, stringBuilder2);
handler.AppendLiteral("<div class=\"vbar-bar\" style=\"height:");
handler.AppendFormatted(value3);
handler.AppendLiteral("%;background:");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral(";\" title=\"");
handler.AppendFormatted(num5, "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("\"></div>");
stringBuilder6.AppendLine(ref handler);
}
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder7 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(30, 1, stringBuilder2);
handler.AppendLiteral("<div class=\"vbar-label\">");
handler.AppendFormatted(Escape(labels[num4]));
handler.AppendLiteral("</div>");
stringBuilder7.AppendLine(ref handler);
stringBuilder.AppendLine("</div>");
}
stringBuilder.AppendLine("</div></div>");
}
return stringBuilder.ToString();
}
private static string RenderStackedBar(List<string> labels, List<Dataset> datasets, string unit)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<div class=\"hbar-chart\">");
int i;
for (i = 0; i < labels.Count; i++)
{
double num = datasets.Sum((Dataset ds) => (i < ds.Values.Count) ? ds.Values[i] : 0.0);
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(54, 1, stringBuilder2);
handler.AppendLiteral("<div class=\"hbar-row\"><span class=\"hbar-label\">");
handler.AppendFormatted(Escape(labels[i]));
handler.AppendLiteral("</span>");
stringBuilder3.AppendLine(ref handler);
stringBuilder.AppendLine("<div class=\"hbar-track\" style=\"display:flex;\">");
foreach (Dataset dataset in datasets)
{
double num2 = ((i < dataset.Values.Count) ? dataset.Values[i] : 0.0);
int value = ((num > 0.0) ? ((int)(num2 / num * 100.0)) : 0);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(63, 5, stringBuilder2);
handler.AppendLiteral("<div style=\"width:");
handler.AppendFormatted(value);
handler.AppendLiteral("%;background:");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral(";height:100%;\" title=\"");
handler.AppendFormatted(dataset.Name);
handler.AppendLiteral(": ");
handler.AppendFormatted(num2, "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("\"></div>");
stringBuilder4.AppendLine(ref handler);
}
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(44, 2, stringBuilder2);
handler.AppendLiteral("</div><span class=\"hbar-value\">");
handler.AppendFormatted(num, "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("</span></div>");
stringBuilder5.AppendLine(ref handler);
}
stringBuilder.AppendLine("</div>");
return stringBuilder.ToString();
}
private static string RenderLineChart(List<string> labels, List<Dataset> datasets, string unit, bool isArea)
{
List<double> source = datasets.SelectMany((Dataset d) => d.Values).ToList();
double num = source.DefaultIfEmpty(1.0).Max();
double num2 = source.DefaultIfEmpty(0.0).Min();
if (num <= num2)
{
num = num2 + 1.0;
}
int num3 = 600;
int num4 = 300;
int num5 = 50;
int num6 = 20;
int num7 = 20;
int num8 = 40;
int num9 = num3 - num5 - num6;
int num10 = num4 - num7 - num8;
int count = labels.Count;
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(80, 2, stringBuilder2);
handler.AppendLiteral("<svg viewBox=\"0 0 ");
handler.AppendFormatted(num3);
handler.AppendLiteral(" ");
handler.AppendFormatted(num4);
handler.AppendLiteral("\" class=\"line-chart-svg\" preserveAspectRatio=\"xMidYMid meet\">");
stringBuilder3.AppendLine(ref handler);
for (int num11 = 0; num11 <= 4; num11++)
{
double num12 = (double)(num7 + num10) - (double)(num10 * num11) / 4.0;
double value = num2 + (num - num2) * (double)num11 / 4.0;
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(65, 4, stringBuilder2);
handler.AppendLiteral("<line x1=\"");
handler.AppendFormatted(num5);
handler.AppendLiteral("\" y1=\"");
handler.AppendFormatted(num12, "F0");
handler.AppendLiteral("\" x2=\"");
handler.AppendFormatted(num3 - num6);
handler.AppendLiteral("\" y2=\"");
handler.AppendFormatted(num12, "F0");
handler.AppendLiteral("\" stroke=\"#E5E7EB\" stroke-width=\"1\"/>");
stringBuilder4.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(71, 4, stringBuilder2);
handler.AppendLiteral("<text x=\"");
handler.AppendFormatted(num5 - 8);
handler.AppendLiteral("\" y=\"");
handler.AppendFormatted(num12 + 4.0, "F0");
handler.AppendLiteral("\" text-anchor=\"end\" fill=\"#6B7280\" font-size=\"11\">");
handler.AppendFormatted(value, "G3");
handler.AppendFormatted(unit);
handler.AppendLiteral("</text>");
stringBuilder5.AppendLine(ref handler);
}
for (int num13 = 0; num13 < count; num13++)
{
double value2 = (double)num5 + ((count > 1) ? ((double)(num9 * num13) / (double)(count - 1)) : ((double)num9 / 2.0));
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(74, 3, stringBuilder2);
handler.AppendLiteral("<text x=\"");
handler.AppendFormatted(value2, "F0");
handler.AppendLiteral("\" y=\"");
handler.AppendFormatted(num4 - 8);
handler.AppendLiteral("\" text-anchor=\"middle\" fill=\"#6B7280\" font-size=\"11\">");
handler.AppendFormatted(Escape(labels[num13]));
handler.AppendLiteral("</text>");
stringBuilder6.AppendLine(ref handler);
}
foreach (Dataset dataset in datasets)
{
List<(double, double)> list = new List<(double, double)>();
for (int num14 = 0; num14 < Math.Min(count, dataset.Values.Count); num14++)
{
double item = (double)num5 + ((count > 1) ? ((double)(num9 * num14) / (double)(count - 1)) : ((double)num9 / 2.0));
double item2 = (double)(num7 + num10) - (dataset.Values[num14] - num2) / (num - num2) * (double)num10;
list.Add((item, item2));
}
string text = string.Join(" ", list.Select<(double, double), string>(((double x, double y) p, int i) => $"{((i == 0) ? "M" : "L")}{p.x:F1},{p.y:F1}"));
if (isArea && list.Count > 1)
{
string value3 = text + $" L{list.Last().Item1:F1},{num7 + num10} L{list.First().Item1:F1},{num7 + num10} Z";
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder7 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(35, 2, stringBuilder2);
handler.AppendLiteral("<path d=\"");
handler.AppendFormatted(value3);
handler.AppendLiteral("\" fill=\"");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral("\" opacity=\"0.15\"/>");
stringBuilder7.AppendLine(ref handler);
}
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder8 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(100, 2, stringBuilder2);
handler.AppendLiteral("<path d=\"");
handler.AppendFormatted(text);
handler.AppendLiteral("\" fill=\"none\" stroke=\"");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral("\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>");
stringBuilder8.AppendLine(ref handler);
foreach (var item5 in list)
{
double item3 = item5.Item1;
double item4 = item5.Item2;
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder9 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(67, 3, stringBuilder2);
handler.AppendLiteral("<circle cx=\"");
handler.AppendFormatted(item3, "F1");
handler.AppendLiteral("\" cy=\"");
handler.AppendFormatted(item4, "F1");
handler.AppendLiteral("\" r=\"4\" fill=\"");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral("\" stroke=\"white\" stroke-width=\"2\"/>");
stringBuilder9.AppendLine(ref handler);
}
}
stringBuilder.AppendLine("</svg>");
return stringBuilder.ToString();
}
private static string RenderPieChart(List<string> labels, List<Dataset> datasets, bool isDonut)
{
List<double> list = ((datasets.Count > 0) ? datasets[0].Values : new List<double>());
double num = list.Sum();
if (num <= 0.0)
{
num = 1.0;
}
int num2 = 150;
int num3 = 150;
int num4 = 120;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<div style=\"display:flex;align-items:center;gap:24px;flex-wrap:wrap;\">");
stringBuilder.AppendLine("<svg viewBox=\"0 0 300 300\" width=\"260\" height=\"260\">");
double num5 = -90.0;
for (int i = 0; i < Math.Min(list.Count, labels.Count); i++)
{
double num6 = list[i] / num;
double num7 = num6 * 360.0;
double num8 = num5 + num7;
double value = (double)num2 + (double)num4 * Math.Cos(num5 * Math.PI / 180.0);
double value2 = (double)num3 + (double)num4 * Math.Sin(num5 * Math.PI / 180.0);
double value3 = (double)num2 + (double)num4 * Math.Cos(num8 * Math.PI / 180.0);
double value4 = (double)num3 + (double)num4 * Math.Sin(num8 * Math.PI / 180.0);
int value5 = ((num7 > 180.0) ? 1 : 0);
string value6 = ((i < Palette.Length) ? Palette[i] : Palette[i % Palette.Length]);
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(37, 10, stringBuilder2);
handler.AppendLiteral("<path d=\"M");
handler.AppendFormatted(num2);
handler.AppendLiteral(",");
handler.AppendFormatted(num3);
handler.AppendLiteral(" L");
handler.AppendFormatted(value, "F1");
handler.AppendLiteral(",");
handler.AppendFormatted(value2, "F1");
handler.AppendLiteral(" A");
handler.AppendFormatted(num4);
handler.AppendLiteral(",");
handler.AppendFormatted(num4);
handler.AppendLiteral(" 0 ");
handler.AppendFormatted(value5);
handler.AppendLiteral(",1 ");
handler.AppendFormatted(value3, "F1");
handler.AppendLiteral(",");
handler.AppendFormatted(value4, "F1");
handler.AppendLiteral(" Z\" fill=\"");
handler.AppendFormatted(value6);
handler.AppendLiteral("\"/>");
stringBuilder3.AppendLine(ref handler);
num5 = num8;
}
if (isDonut)
{
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(39, 3, stringBuilder2);
handler.AppendLiteral("<circle cx=\"");
handler.AppendFormatted(num2);
handler.AppendLiteral("\" cy=\"");
handler.AppendFormatted(num3);
handler.AppendLiteral("\" r=\"");
handler.AppendFormatted((double)num4 * 0.55);
handler.AppendLiteral("\" fill=\"white\"/>");
stringBuilder4.AppendLine(ref handler);
}
stringBuilder.AppendLine("</svg>");
stringBuilder.AppendLine("<div class=\"pie-legend\">");
for (int j = 0; j < Math.Min(list.Count, labels.Count); j++)
{
string value7 = ((j < Palette.Length) ? Palette[j] : Palette[j % Palette.Length]);
double value8 = list[j] / num * 100.0;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(142, 3, stringBuilder2);
handler.AppendLiteral("<div class=\"pie-legend-item\"><span class=\"legend-dot\" style=\"background:");
handler.AppendFormatted(value7);
handler.AppendLiteral("\"></span>");
handler.AppendFormatted(Escape(labels[j]));
handler.AppendLiteral(" <span style=\"color:#6B7280;font-size:12px;\">(");
handler.AppendFormatted(value8, "F1");
handler.AppendLiteral("%)</span></div>");
stringBuilder5.AppendLine(ref handler);
}
stringBuilder.AppendLine("</div></div>");
return stringBuilder.ToString();
}
private static string RenderProgressChart(List<string> labels, List<Dataset> datasets, string unit)
{
List<double> list = ((datasets.Count > 0) ? datasets[0].Values : new List<double>());
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < Math.Min(labels.Count, list.Count); i++)
{
double value = Math.Clamp(list[i], 0.0, 100.0);
string value2 = ((i < Palette.Length) ? Palette[i] : Palette[i % Palette.Length]);
if (datasets.Count > 0 && !string.IsNullOrEmpty(datasets[0].Color))
{
value2 = datasets[0].Color;
}
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(218, 3, stringBuilder2);
handler.AppendLiteral("<div style=\"margin-bottom:12px;\"><div style=\"display:flex;justify-content:space-between;margin-bottom:4px;\"><span style=\"font-size:13px;font-weight:600;\">");
handler.AppendFormatted(Escape(labels[i]));
handler.AppendLiteral("</span><span style=\"font-size:13px;color:#6B7280;\">");
handler.AppendFormatted(list[i], "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("</span></div>");
stringBuilder3.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(96, 2, stringBuilder2);
handler.AppendLiteral("<div class=\"progress\"><div class=\"progress-fill\" style=\"width:");
handler.AppendFormatted(value);
handler.AppendLiteral("%;background:");
handler.AppendFormatted(value2);
handler.AppendLiteral(";\"></div></div></div>");
stringBuilder4.AppendLine(ref handler);
}
return stringBuilder.ToString();
}
private static string RenderComparisonChart(List<string> labels, List<Dataset> datasets, string unit)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<table style=\"width:100%;border-collapse:collapse;\">");
stringBuilder.AppendLine("<tr><th style=\"text-align:left;padding:8px 12px;\">항목</th>");
foreach (Dataset dataset in datasets)
{
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(60, 2, stringBuilder2);
handler.AppendLiteral("<th style=\"text-align:center;padding:8px 12px;color:");
handler.AppendFormatted(dataset.Color);
handler.AppendLiteral(";\">");
handler.AppendFormatted(Escape(dataset.Name));
handler.AppendLiteral("</th>");
stringBuilder3.AppendLine(ref handler);
}
stringBuilder.AppendLine("</tr>");
for (int i = 0; i < labels.Count; i++)
{
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(93, 1, stringBuilder2);
handler.AppendLiteral("<tr style=\"border-top:1px solid #E5E7EB;\"><td style=\"padding:8px 12px;font-weight:500;\">");
handler.AppendFormatted(Escape(labels[i]));
handler.AppendLiteral("</td>");
stringBuilder4.Append(ref handler);
foreach (Dataset dataset2 in datasets)
{
double value = ((i < dataset2.Values.Count) ? dataset2.Values[i] : 0.0);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(53, 2, stringBuilder2);
handler.AppendLiteral("<td style=\"text-align:center;padding:8px 12px;\">");
handler.AppendFormatted(value, "G");
handler.AppendFormatted(unit);
handler.AppendLiteral("</td>");
stringBuilder5.Append(ref handler);
}
stringBuilder.AppendLine("</tr>");
}
stringBuilder.AppendLine("</table>");
return stringBuilder.ToString();
}
private static string RenderRadarChart(List<string> labels, List<Dataset> datasets)
{
int cx = 150;
int cy = 150;
int r = 110;
int n = labels.Count;
if (n < 3)
{
return "<p>레이더 차트는 최소 3개 항목이 필요합니다.</p>";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<svg viewBox=\"0 0 300 300\" width=\"300\" height=\"300\">");
for (int i = 1; i <= 4; i++)
{
double lr = (double)(r * i) / 4.0;
string value = string.Join(" ", Enumerable.Range(0, n).Select(delegate(int num5)
{
double num4 = (360.0 / (double)n * (double)num5 - 90.0) * Math.PI / 180.0;
return $"{(double)cx + lr * Math.Cos(num4):F1},{(double)cy + lr * Math.Sin(num4):F1}";
}));
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(66, 1, stringBuilder2);
handler.AppendLiteral("<polygon points=\"");
handler.AppendFormatted(value);
handler.AppendLiteral("\" fill=\"none\" stroke=\"#E5E7EB\" stroke-width=\"1\"/>");
stringBuilder3.AppendLine(ref handler);
}
for (int num = 0; num < n; num++)
{
double num2 = (360.0 / (double)n * (double)num - 90.0) * Math.PI / 180.0;
double value2 = (double)cx + (double)r * Math.Cos(num2);
double value3 = (double)cy + (double)r * Math.Sin(num2);
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(65, 4, stringBuilder2);
handler.AppendLiteral("<line x1=\"");
handler.AppendFormatted(cx);
handler.AppendLiteral("\" y1=\"");
handler.AppendFormatted(cy);
handler.AppendLiteral("\" x2=\"");
handler.AppendFormatted(value2, "F1");
handler.AppendLiteral("\" y2=\"");
handler.AppendFormatted(value3, "F1");
handler.AppendLiteral("\" stroke=\"#D1D5DB\" stroke-width=\"1\"/>");
stringBuilder4.AppendLine(ref handler);
double value4 = (double)cx + (double)(r + 16) * Math.Cos(num2);
double num3 = (double)cy + (double)(r + 16) * Math.Sin(num2);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(74, 3, stringBuilder2);
handler.AppendLiteral("<text x=\"");
handler.AppendFormatted(value4, "F0");
handler.AppendLiteral("\" y=\"");
handler.AppendFormatted(num3 + 4.0, "F0");
handler.AppendLiteral("\" text-anchor=\"middle\" fill=\"#374151\" font-size=\"11\">");
handler.AppendFormatted(Escape(labels[num]));
handler.AppendLiteral("</text>");
stringBuilder5.AppendLine(ref handler);
}
double maxVal = datasets.SelectMany((Dataset d) => d.Values).DefaultIfEmpty(1.0).Max();
if (maxVal <= 0.0)
{
maxVal = 1.0;
}
foreach (Dataset ds in datasets)
{
string value5 = string.Join(" ", Enumerable.Range(0, n).Select(delegate(int num5)
{
double num4 = ((num5 < ds.Values.Count) ? ds.Values[num5] : 0.0);
double num6 = (double)r * num4 / maxVal;
double num7 = (360.0 / (double)n * (double)num5 - 90.0) * Math.PI / 180.0;
return $"{(double)cx + num6 * Math.Cos(num7):F1},{(double)cy + num6 * Math.Sin(num7):F1}";
}));
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(74, 3, stringBuilder2);
handler.AppendLiteral("<polygon points=\"");
handler.AppendFormatted(value5);
handler.AppendLiteral("\" fill=\"");
handler.AppendFormatted(ds.Color);
handler.AppendLiteral("\" fill-opacity=\"0.2\" stroke=\"");
handler.AppendFormatted(ds.Color);
handler.AppendLiteral("\" stroke-width=\"2\"/>");
stringBuilder6.AppendLine(ref handler);
}
stringBuilder.AppendLine("</svg>");
return stringBuilder.ToString();
}
private static List<string> ParseStringArray(JsonElement parent, string prop)
{
if (!parent.TryGetProperty(prop, out var value) || value.ValueKind != JsonValueKind.Array)
{
return new List<string>();
}
return (from e in value.EnumerateArray()
select e.GetString() ?? "").ToList();
}
private List<Dataset> ParseDatasets(JsonElement chart)
{
if (!chart.TryGetProperty("datasets", out var value) || value.ValueKind != JsonValueKind.Array)
{
double value6;
if (chart.TryGetProperty("values", out var value2) && value2.ValueKind == JsonValueKind.Array)
{
return new List<Dataset>
{
new Dataset
{
Name = "Data",
Values = (from v in value2.EnumerateArray()
select v.TryGetDouble(out value6) ? value6 : 0.0).ToList(),
Color = Palette[0]
}
};
}
return new List<Dataset>();
}
List<Dataset> list = new List<Dataset>();
int num = 0;
foreach (JsonElement item in value.EnumerateArray())
{
JsonElement value3;
string name = (item.TryGetProperty("name", out value3) ? (value3.GetString() ?? $"Series{num + 1}") : $"Series{num + 1}");
JsonElement value4;
string color = (item.TryGetProperty("color", out value4) ? (value4.GetString() ?? Palette[num % Palette.Length]) : Palette[num % Palette.Length]);
List<double> values = new List<double>();
if (item.TryGetProperty("values", out var value5) && value5.ValueKind == JsonValueKind.Array)
{
values = (from e in value5.EnumerateArray()
select e.TryGetDouble(out var value6) ? value6 : 0.0).ToList();
}
list.Add(new Dataset
{
Name = name,
Values = values,
Color = color
});
num++;
}
return list;
}
private static string Escape(string s)
{
return s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
.Replace("\"", "&quot;");
}
private static string FormatSize(long bytes)
{
if (1 == 0)
{
}
string result = ((bytes < 1024) ? $"{bytes}B" : ((bytes >= 1048576) ? $"{(double)bytes / 1048576.0:F1}MB" : $"{(double)bytes / 1024.0:F1}KB"));
if (1 == 0)
{
}
return result;
}
}