1573 lines
68 KiB
Plaintext
1573 lines
68 KiB
Plaintext
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using EasyChart;
|
|
|
|
namespace EasyChart.Editor
|
|
{
|
|
public partial class EasyChartLibraryWindow
|
|
{
|
|
private const string JsonPanelCollapsedPrefsKey = "EasyChart.LibraryWindow.JsonPanelCollapsed";
|
|
private bool _jsonPanelCollapsed;
|
|
private bool _jsonExampleDirtyByUser;
|
|
|
|
private enum JsonDatasMode
|
|
{
|
|
Values,
|
|
Standard,
|
|
Full
|
|
}
|
|
|
|
private static readonly List<string> JsonDatasModeChoices = new List<string>
|
|
{
|
|
"Values",
|
|
"Standard",
|
|
"Full"
|
|
};
|
|
|
|
private JsonDatasMode _jsonDatasMode = JsonDatasMode.Standard;
|
|
private PopupField<string> _jsonDatasModeDropdown;
|
|
|
|
private struct JsonExampleModeOption
|
|
{
|
|
public string label;
|
|
public JsonExampleMode mode;
|
|
|
|
public JsonExampleModeOption(string label, JsonExampleMode mode)
|
|
{
|
|
this.label = label;
|
|
this.mode = mode;
|
|
}
|
|
}
|
|
|
|
private static readonly List<JsonExampleModeOption> JsonExampleModeOptions = new List<JsonExampleModeOption>
|
|
{
|
|
new JsonExampleModeOption("Lite", JsonExampleMode.Lite_Index),
|
|
new JsonExampleModeOption("Standard / ID", JsonExampleMode.Lite_ID),
|
|
new JsonExampleModeOption("Standard / Default", JsonExampleMode.Standard),
|
|
new JsonExampleModeOption("Standard / With Axes", JsonExampleMode.Standard_Axis),
|
|
new JsonExampleModeOption("Full", JsonExampleMode.Full)
|
|
};
|
|
|
|
private static readonly List<string> JsonExampleModeChoices = new List<string>
|
|
{
|
|
"Lite",
|
|
"Standard / ID",
|
|
"Standard / Default",
|
|
"Standard / With Axes",
|
|
"Full"
|
|
};
|
|
|
|
private void BuildInjectionJsonPanel(VisualElement leftPanel)
|
|
{
|
|
if (leftPanel == null) return;
|
|
|
|
_jsonPanelCollapsed = EditorPrefs.GetBool(JsonPanelCollapsedPrefsKey, false);
|
|
|
|
_jsonExampleContainer = new VisualElement();
|
|
void ApplyJsonPanelHeight()
|
|
{
|
|
_jsonExampleContainer.style.height = _jsonPanelCollapsed ? 200 : 520;
|
|
}
|
|
|
|
ApplyJsonPanelHeight();
|
|
_jsonExampleContainer.style.flexShrink = 0;
|
|
_jsonExampleContainer.style.flexDirection = FlexDirection.Column;
|
|
_jsonExampleContainer.style.minHeight = 0;
|
|
_jsonExampleContainer.style.borderTopWidth = 1;
|
|
_jsonExampleContainer.style.borderTopColor = new Color(0.15f, 0.15f, 0.15f);
|
|
_jsonExampleContainer.style.backgroundColor = new Color(0.20f, 0.20f, 0.20f);
|
|
_jsonExampleContainer.style.paddingLeft = 6;
|
|
_jsonExampleContainer.style.paddingRight = 6;
|
|
_jsonExampleContainer.style.paddingTop = 6;
|
|
_jsonExampleContainer.style.paddingBottom = 6;
|
|
|
|
var jsonHeaderRow = new VisualElement();
|
|
jsonHeaderRow.style.flexDirection = FlexDirection.Row;
|
|
jsonHeaderRow.style.alignItems = Align.Center;
|
|
jsonHeaderRow.style.paddingLeft = 10;
|
|
jsonHeaderRow.style.paddingRight = 10;
|
|
jsonHeaderRow.style.paddingTop = 5;
|
|
jsonHeaderRow.style.paddingBottom = 5;
|
|
jsonHeaderRow.style.backgroundColor = new Color(0.25f, 0.25f, 0.25f);
|
|
|
|
var jsonHeader = new Label("JSON Injection");
|
|
jsonHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
jsonHeaderRow.Add(jsonHeader);
|
|
|
|
var jsonHeaderSpacer = new VisualElement();
|
|
jsonHeaderSpacer.style.flexGrow = 1;
|
|
jsonHeaderRow.Add(jsonHeaderSpacer);
|
|
|
|
EnsureSharedIconsLoaded();
|
|
|
|
Button jsonCollapseBtn = null;
|
|
jsonCollapseBtn = new Button(() =>
|
|
{
|
|
_jsonPanelCollapsed = !_jsonPanelCollapsed;
|
|
EditorPrefs.SetBool(JsonPanelCollapsedPrefsKey, _jsonPanelCollapsed);
|
|
jsonCollapseBtn.text = _jsonPanelCollapsed ? "Max" : "Min";
|
|
ApplyJsonPanelHeight();
|
|
});
|
|
jsonCollapseBtn.text = _jsonPanelCollapsed ? "Max" : "Min";
|
|
jsonCollapseBtn.tooltip = "Toggle panel height";
|
|
jsonCollapseBtn.style.height = 18;
|
|
jsonCollapseBtn.style.minWidth = 44;
|
|
jsonCollapseBtn.style.marginLeft = 6;
|
|
jsonHeaderRow.Add(jsonCollapseBtn);
|
|
|
|
_jsonApplyToChartButton = CreateClickableIconImage(_applyToChartIcon, "ApplyToChart", () => ApplyInjectionJsonToSelectedProfile());
|
|
_jsonApplyToChartButton.style.marginLeft = 6;
|
|
_jsonApplyToChartButton.style.marginBottom = 0;
|
|
jsonHeaderRow.Add(_jsonApplyToChartButton);
|
|
|
|
_jsonExampleContainer.Add(jsonHeaderRow);
|
|
|
|
var jsonBtnRow = new VisualElement();
|
|
jsonBtnRow.style.flexDirection = FlexDirection.Row;
|
|
jsonBtnRow.style.marginTop = 6;
|
|
jsonBtnRow.style.paddingRight = 6;
|
|
jsonBtnRow.style.flexShrink = 0;
|
|
jsonBtnRow.style.flexWrap = Wrap.Wrap;
|
|
|
|
void UpdateApiToggleIcon()
|
|
{
|
|
if (_jsonApiToggleButton == null) return;
|
|
_jsonApiToggleButton.image = _jsonUseApiEnvelope ? _apiOnIcon : _apiOffIcon;
|
|
}
|
|
|
|
_jsonApiToggleButton = CreateClickableIconImage(_apiOffIcon, "API Envelope", () =>
|
|
{
|
|
_jsonUseApiEnvelope = !_jsonUseApiEnvelope;
|
|
UpdateApiToggleIcon();
|
|
_jsonExampleDirtyByUser = false;
|
|
UpdateInjectionJsonExample(forceOverwrite: true);
|
|
});
|
|
_jsonApiToggleButton.style.marginBottom = 4;
|
|
jsonBtnRow.Add(_jsonApiToggleButton);
|
|
|
|
UpdateApiToggleIcon();
|
|
|
|
_jsonModeDropdown = new PopupField<string>(JsonExampleModeChoices, 0);
|
|
_jsonModeDropdown.tooltip = "Feed Mode";
|
|
_jsonModeDropdown.style.width = 150;
|
|
_jsonModeDropdown.style.marginLeft = 6;
|
|
_jsonModeDropdown.style.marginBottom = 4;
|
|
_jsonModeDropdown.RegisterValueChangedCallback(evt =>
|
|
{
|
|
_jsonExampleMode = JsonExampleModeFromLabel(evt.newValue);
|
|
_jsonExampleDirtyByUser = false;
|
|
UpdateInjectionJsonExample(forceOverwrite: true);
|
|
});
|
|
|
|
InjectPopupIcon(_jsonModeDropdown, _feedIcon, "feed-icon");
|
|
jsonBtnRow.Add(_jsonModeDropdown);
|
|
|
|
_jsonDatasModeDropdown = new PopupField<string>(JsonDatasModeChoices, 1);
|
|
_jsonDatasModeDropdown.tooltip = "Datas Format";
|
|
_jsonDatasModeDropdown.style.width = 90;
|
|
_jsonDatasModeDropdown.style.marginLeft = 6;
|
|
_jsonDatasModeDropdown.style.marginBottom = 4;
|
|
_jsonDatasModeDropdown.RegisterValueChangedCallback(evt =>
|
|
{
|
|
_jsonDatasMode = JsonDatasModeFromLabel(evt.newValue);
|
|
_jsonExampleDirtyByUser = false;
|
|
UpdateInjectionJsonExample(forceOverwrite: true);
|
|
});
|
|
|
|
InjectPopupIcon(_jsonDatasModeDropdown, _dataIcon, "data-icon");
|
|
jsonBtnRow.Add(_jsonDatasModeDropdown);
|
|
|
|
var jsonBtnSpacer = new VisualElement();
|
|
jsonBtnSpacer.style.flexGrow = 1;
|
|
jsonBtnRow.Add(jsonBtnSpacer);
|
|
|
|
_jsonCopyButton = CreateClickableIconImage(_copyIcon, "Copy", () =>
|
|
{
|
|
if (_jsonExampleField == null) return;
|
|
GUIUtility.systemCopyBuffer = _jsonExampleField.value;
|
|
});
|
|
_jsonCopyButton.style.marginLeft = 6;
|
|
_jsonCopyButton.style.marginBottom = 4;
|
|
jsonBtnRow.Add(_jsonCopyButton);
|
|
|
|
_jsonExampleContainer.Add(jsonBtnRow);
|
|
|
|
_jsonExampleScroll = new ScrollView(ScrollViewMode.Vertical);
|
|
_jsonExampleScroll.style.flexGrow = 1;
|
|
_jsonExampleScroll.style.flexShrink = 1;
|
|
_jsonExampleScroll.style.flexBasis = 0;
|
|
_jsonExampleScroll.style.minHeight = 0;
|
|
_jsonExampleScroll.style.paddingLeft = 2;
|
|
_jsonExampleScroll.style.paddingRight = 2;
|
|
_jsonExampleScroll.style.paddingTop = 2;
|
|
_jsonExampleScroll.style.paddingBottom = 2;
|
|
_jsonExampleScroll.style.marginBottom = 0;
|
|
_jsonExampleScroll.verticalScrollerVisibility = ScrollerVisibility.AlwaysVisible;
|
|
_jsonExampleContainer.Add(_jsonExampleScroll);
|
|
|
|
_jsonExampleScroll.contentContainer.style.flexGrow = 0;
|
|
_jsonExampleScroll.contentContainer.style.flexShrink = 0;
|
|
_jsonExampleScroll.contentContainer.style.minHeight = 0;
|
|
|
|
_jsonExampleField = new TextField();
|
|
_jsonExampleField.multiline = true;
|
|
_jsonExampleField.isReadOnly = false;
|
|
_jsonExampleField.style.flexGrow = 0;
|
|
_jsonExampleField.style.flexShrink = 0;
|
|
_jsonExampleField.style.minHeight = 0;
|
|
_jsonExampleField.style.whiteSpace = WhiteSpace.Normal;
|
|
_jsonExampleField.style.unityTextAlign = TextAnchor.UpperLeft;
|
|
_jsonExampleField.RegisterValueChangedCallback(_ => { _jsonExampleDirtyByUser = true; });
|
|
_jsonExampleScroll.Add(_jsonExampleField);
|
|
|
|
leftPanel.Add(_jsonExampleContainer);
|
|
|
|
UpdateJsonModeDropdown();
|
|
_jsonExampleDirtyByUser = false;
|
|
UpdateInjectionJsonExample(forceOverwrite: true);
|
|
}
|
|
|
|
private static void InjectPopupIcon(PopupField<string> popup, Texture2D icon, string iconElementName)
|
|
{
|
|
if (popup == null) return;
|
|
if (icon == null) return;
|
|
|
|
var input = popup.Q<VisualElement>(className: "unity-base-popup-field__input");
|
|
if (input == null) return;
|
|
|
|
if (!string.IsNullOrEmpty(iconElementName) && input.Q<VisualElement>(iconElementName) != null) return;
|
|
|
|
input.style.position = Position.Relative;
|
|
input.style.paddingLeft = 22;
|
|
|
|
var img = new Image();
|
|
img.name = iconElementName;
|
|
img.image = icon;
|
|
img.scaleMode = ScaleMode.ScaleToFit;
|
|
img.style.position = Position.Absolute;
|
|
img.style.left = 4;
|
|
img.style.top = 2;
|
|
img.style.width = 16;
|
|
img.style.height = 16;
|
|
input.Add(img);
|
|
}
|
|
|
|
private void UpdateInjectionJsonExample(bool forceOverwrite = false)
|
|
{
|
|
if (_jsonExampleField == null) return;
|
|
if (!forceOverwrite && _jsonExampleDirtyByUser) return;
|
|
|
|
if (_selectedProfile == null)
|
|
{
|
|
_jsonExampleField.value = string.Empty;
|
|
return;
|
|
}
|
|
|
|
if (_selectedProfile.EnsureRuntimeData())
|
|
{
|
|
EditorUtility.SetDirty(_selectedProfile);
|
|
}
|
|
|
|
_jsonChartId = _selectedProfile.chartId;
|
|
|
|
var json = BuildInjectionJsonForSelectedProfile(_selectedProfile, _jsonChartId, _jsonExampleMode, _jsonDatasMode);
|
|
_jsonExampleField.value = _jsonUseApiEnvelope ? WrapAsApiResponse(json) : json;
|
|
_jsonExampleDirtyByUser = false;
|
|
}
|
|
|
|
private void ApplyInjectionJsonToSelectedProfile()
|
|
{
|
|
if (_selectedProfile == null) return;
|
|
if (_jsonExampleField == null) return;
|
|
|
|
if (_serializedProfile != null && _serializedProfile.hasModifiedProperties)
|
|
{
|
|
_serializedProfile.ApplyModifiedProperties();
|
|
}
|
|
|
|
string json = _jsonExampleField.value;
|
|
if (string.IsNullOrWhiteSpace(json)) return;
|
|
|
|
if (!TryDeserializeFeed(json, out var feed))
|
|
{
|
|
Debug.LogError("[EasyChartLibraryWindow] ApplyToChart failed: invalid JSON or unsupported format.");
|
|
return;
|
|
}
|
|
|
|
bool allowMetaOverwrite = _jsonExampleMode == JsonExampleMode.Full;
|
|
bool changed = ApplyFeedToProfile(_selectedProfile, feed, allowMetaOverwrite);
|
|
if (changed)
|
|
{
|
|
EditorUtility.SetDirty(_selectedProfile);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
_serializedProfile?.Update();
|
|
ScheduleRefreshSeriesList();
|
|
ScheduleUpdatePreview();
|
|
|
|
_jsonExampleDirtyByUser = false;
|
|
}
|
|
}
|
|
|
|
private static bool TryDeserializeFeed(string json, out ChartFeed feed)
|
|
{
|
|
feed = null;
|
|
|
|
if (TryExtractWrappedDataJson(json, out var dataJson) && !string.IsNullOrEmpty(dataJson))
|
|
{
|
|
json = dataJson;
|
|
}
|
|
|
|
if (ShouldPreferFlexibleFeedParser(json))
|
|
{
|
|
if (TryDeserializeFeedFlexibleNewtonsoft(json, out feed))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Prevent falling back to typed deserializers when datas is in tuple/values form.
|
|
// Those deserializers can silently produce default DataFeed entries, which then
|
|
// overwrites chart data (e.g. heatmap points become [0,0,0]).
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var jsonConvertType = Type.GetType("Newtonsoft.Json.JsonConvert, Newtonsoft.Json")
|
|
?? Type.GetType("Newtonsoft.Json.JsonConvert, Unity.Newtonsoft.Json");
|
|
if (jsonConvertType != null)
|
|
{
|
|
var deserialize = jsonConvertType.GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type) });
|
|
if (deserialize != null)
|
|
{
|
|
var obj = deserialize.Invoke(null, new object[] { json, typeof(ChartFeed) });
|
|
feed = obj as ChartFeed;
|
|
return feed != null;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
try
|
|
{
|
|
json = NormalizeJsonForUnityJsonUtility(json);
|
|
feed = JsonUtility.FromJson<ChartFeed>(json);
|
|
return feed != null;
|
|
}
|
|
catch
|
|
{
|
|
// Continue to flexible parse below.
|
|
}
|
|
|
|
return TryDeserializeFeedFlexibleNewtonsoft(json, out feed);
|
|
}
|
|
|
|
private static string WrapAsApiResponse(string payloadJson)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(payloadJson)) return string.Empty;
|
|
|
|
var payload = payloadJson.Trim();
|
|
if (payload.StartsWith("{", StringComparison.Ordinal) && payload.IndexOf("\"code\"", StringComparison.Ordinal) >= 0)
|
|
{
|
|
return payloadJson;
|
|
}
|
|
|
|
if (payload.Contains("\n"))
|
|
{
|
|
payload = IndentLines(payload, 2);
|
|
return "{\n \"code\": 200,\n \"message\": \"success\",\n \"data\":\n" + payload + "\n}";
|
|
}
|
|
|
|
return "{\n \"code\": 200,\n \"message\": \"success\",\n \"data\": " + payload + "\n}";
|
|
}
|
|
|
|
private static string IndentLines(string text, int spaces)
|
|
{
|
|
if (string.IsNullOrEmpty(text)) return text;
|
|
string pad = new string(' ', Mathf.Max(0, spaces));
|
|
var lines = text.Replace("\r\n", "\n").Split('\n');
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
lines[i] = pad + lines[i];
|
|
}
|
|
return string.Join("\n", lines);
|
|
}
|
|
|
|
private static bool TryExtractWrappedDataJson(string json, out string dataJson)
|
|
{
|
|
dataJson = null;
|
|
if (string.IsNullOrEmpty(json)) return false;
|
|
|
|
int dataKey = json.IndexOf("\"data\"", StringComparison.Ordinal);
|
|
if (dataKey < 0) return false;
|
|
|
|
int colon = json.IndexOf(':', dataKey + 6);
|
|
if (colon < 0) return false;
|
|
|
|
int start = -1;
|
|
for (int i = colon + 1; i < json.Length; i++)
|
|
{
|
|
char c = json[i];
|
|
if (char.IsWhiteSpace(c)) continue;
|
|
if (c == '{' || c == '[') { start = i; break; }
|
|
return false;
|
|
}
|
|
|
|
if (start < 0) return false;
|
|
char open = json[start];
|
|
char close = open == '{' ? '}' : ']';
|
|
|
|
bool inString = false;
|
|
bool escaped = false;
|
|
int depth = 0;
|
|
for (int i = start; i < json.Length; i++)
|
|
{
|
|
char c = json[i];
|
|
if (inString)
|
|
{
|
|
if (escaped) { escaped = false; continue; }
|
|
if (c == '\\') { escaped = true; continue; }
|
|
if (c == '"') { inString = false; }
|
|
continue;
|
|
}
|
|
|
|
if (c == '"') { inString = true; continue; }
|
|
if (c == open) depth++;
|
|
else if (c == close)
|
|
{
|
|
depth--;
|
|
if (depth == 0)
|
|
{
|
|
dataJson = json.Substring(start, i - start + 1);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool ShouldPreferFlexibleFeedParser(string json)
|
|
{
|
|
if (string.IsNullOrEmpty(json)) return false;
|
|
|
|
int idx = 0;
|
|
while (idx < json.Length)
|
|
{
|
|
idx = json.IndexOf("\"datas\"", idx, StringComparison.Ordinal);
|
|
if (idx < 0) return false;
|
|
|
|
int colon = json.IndexOf(':', idx + 6);
|
|
if (colon < 0) return false;
|
|
|
|
int openBracket = -1;
|
|
for (int i = colon + 1; i < json.Length; i++)
|
|
{
|
|
char c = json[i];
|
|
if (char.IsWhiteSpace(c)) continue;
|
|
if (c != '[') break;
|
|
openBracket = i;
|
|
break;
|
|
}
|
|
|
|
if (openBracket < 0)
|
|
{
|
|
idx = colon + 1;
|
|
continue;
|
|
}
|
|
|
|
int j = openBracket + 1;
|
|
while (j < json.Length && char.IsWhiteSpace(json[j])) j++;
|
|
if (j >= json.Length) return false;
|
|
|
|
char first = json[j];
|
|
if (first == '{' || first == ']')
|
|
{
|
|
idx = j + 1;
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryDeserializeFeedFlexibleNewtonsoft(string json, out ChartFeed feed)
|
|
{
|
|
feed = null;
|
|
|
|
try
|
|
{
|
|
var jObjectType = Type.GetType("Newtonsoft.Json.Linq.JObject, Newtonsoft.Json")
|
|
?? Type.GetType("Newtonsoft.Json.Linq.JObject, Unity.Newtonsoft.Json");
|
|
var jArrayType = Type.GetType("Newtonsoft.Json.Linq.JArray, Newtonsoft.Json")
|
|
?? Type.GetType("Newtonsoft.Json.Linq.JArray, Unity.Newtonsoft.Json");
|
|
if (jObjectType == null || jArrayType == null) return false;
|
|
|
|
var parse = jObjectType.GetMethod("Parse", new[] { typeof(string) });
|
|
if (parse == null) return false;
|
|
|
|
var root = parse.Invoke(null, new object[] { json });
|
|
if (root == null) return false;
|
|
|
|
feed = new ChartFeed();
|
|
|
|
var itemProp = jObjectType.GetProperty("Item", new[] { typeof(string) });
|
|
if (itemProp == null) return false;
|
|
|
|
string ReadString(object obj, string key)
|
|
{
|
|
if (obj == null) return null;
|
|
var token = itemProp.GetValue(obj, new object[] { key });
|
|
if (token == null) return null;
|
|
var s = token.ToString();
|
|
if (string.IsNullOrEmpty(s)) return null;
|
|
if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') s = s.Substring(1, s.Length - 2);
|
|
return s;
|
|
}
|
|
|
|
float ReadFloat(object obj, string key, float defaultValue = 0f)
|
|
{
|
|
var s = ReadString(obj, key);
|
|
if (string.IsNullOrEmpty(s)) return defaultValue;
|
|
if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v)) return v;
|
|
return defaultValue;
|
|
}
|
|
|
|
bool ReadBool(object obj, string key, bool defaultValue = false)
|
|
{
|
|
var s = ReadString(obj, key);
|
|
if (string.IsNullOrEmpty(s)) return defaultValue;
|
|
if (bool.TryParse(s, out var v)) return v;
|
|
return defaultValue;
|
|
}
|
|
|
|
object GetToken(object obj, string key)
|
|
{
|
|
if (obj == null) return null;
|
|
return itemProp.GetValue(obj, new object[] { key });
|
|
}
|
|
|
|
var rootChartId = ReadString(root, "chartId");
|
|
var rootChartName = ReadString(root, "chartName");
|
|
feed.chartId = rootChartId;
|
|
feed.chartName = rootChartName;
|
|
|
|
// Axes
|
|
var axesToken = GetToken(root, "axes");
|
|
if (axesToken != null && jArrayType.IsInstanceOfType(axesToken))
|
|
{
|
|
var axes = new List<AxisFeed>();
|
|
foreach (var axisObj in (System.Collections.IEnumerable)axesToken)
|
|
{
|
|
if (axisObj == null) continue;
|
|
|
|
var axisIdStr = ReadString(axisObj, "axisId");
|
|
if (!Enum.TryParse(axisIdStr, ignoreCase: true, out AxisId axisId)) continue;
|
|
|
|
var labelsToken = GetToken(axisObj, "labels");
|
|
string[] labels = null;
|
|
if (labelsToken != null && jArrayType.IsInstanceOfType(labelsToken))
|
|
{
|
|
var list = new List<string>();
|
|
foreach (var l in (System.Collections.IEnumerable)labelsToken)
|
|
{
|
|
if (l == null) continue;
|
|
var s = l.ToString();
|
|
if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') s = s.Substring(1, s.Length - 2);
|
|
list.Add(s);
|
|
}
|
|
labels = list.ToArray();
|
|
}
|
|
|
|
axes.Add(new AxisFeed { axisId = axisId, labels = labels });
|
|
}
|
|
feed.axes = axes.Count > 0 ? axes.ToArray() : null;
|
|
}
|
|
|
|
// Series
|
|
var seriesToken = GetToken(root, "series");
|
|
if (seriesToken != null && jArrayType.IsInstanceOfType(seriesToken))
|
|
{
|
|
var series = new List<SerieFeed>();
|
|
foreach (var serieObj in (System.Collections.IEnumerable)seriesToken)
|
|
{
|
|
if (serieObj == null) continue;
|
|
|
|
var sf = new SerieFeed();
|
|
sf.serieId = ReadString(serieObj, "serieId");
|
|
sf.name = ReadString(serieObj, "name");
|
|
|
|
var typeStr = ReadString(serieObj, "type");
|
|
if (!string.IsNullOrEmpty(typeStr) && int.TryParse(typeStr, out var tInt))
|
|
{
|
|
sf.type = (SerieType)tInt;
|
|
}
|
|
else if (!string.IsNullOrEmpty(typeStr) && Enum.TryParse(typeStr, ignoreCase: true, out SerieType tEnum))
|
|
{
|
|
sf.type = tEnum;
|
|
}
|
|
|
|
var datasToken = GetToken(serieObj, "datas");
|
|
if (datasToken != null && jArrayType.IsInstanceOfType(datasToken))
|
|
{
|
|
var datas = new List<DataFeed>();
|
|
|
|
object first = null;
|
|
foreach (var tmp in (System.Collections.IEnumerable)datasToken) { first = tmp; break; }
|
|
|
|
// Values mode: datas: [1,2,3]
|
|
if (first != null)
|
|
{
|
|
var firstText = first.ToString();
|
|
bool firstIsObject = firstText.StartsWith("{");
|
|
bool firstIsArray = firstText.StartsWith("[");
|
|
|
|
if (!firstIsObject && !firstIsArray)
|
|
{
|
|
int idx = 0;
|
|
foreach (var vToken in (System.Collections.IEnumerable)datasToken)
|
|
{
|
|
if (vToken == null) { idx++; continue; }
|
|
var vs = vToken.ToString();
|
|
if (!float.TryParse(vs, NumberStyles.Float, CultureInfo.InvariantCulture, out var vv)) vv = 0f;
|
|
datas.Add(new DataFeed { x = idx, y = 0f, value = vv });
|
|
idx++;
|
|
}
|
|
}
|
|
else if (firstIsArray)
|
|
{
|
|
// Tuple mode: datas: [[x,value], [x,value]] or [[x,y,value], ...]
|
|
foreach (var tupleToken in (System.Collections.IEnumerable)datasToken)
|
|
{
|
|
if (tupleToken == null) continue;
|
|
var tupleText = tupleToken.ToString();
|
|
if (string.IsNullOrEmpty(tupleText)) continue;
|
|
tupleText = tupleText.Trim();
|
|
if (tupleText.Length < 2) continue;
|
|
if (!tupleText.StartsWith("[") || !tupleText.EndsWith("]")) continue;
|
|
tupleText = tupleText.Substring(1, tupleText.Length - 2);
|
|
var parts = tupleText.Split(',');
|
|
if (parts.Length < 2) continue;
|
|
float tx = 0f;
|
|
float ty = 0f;
|
|
float tv = 0f;
|
|
float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out tx);
|
|
if (parts.Length == 2)
|
|
{
|
|
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out tv);
|
|
}
|
|
else
|
|
{
|
|
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out ty);
|
|
float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out tv);
|
|
}
|
|
datas.Add(new DataFeed { x = tx, y = ty, value = tv });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Standard/Full object mode
|
|
foreach (var dpObj in (System.Collections.IEnumerable)datasToken)
|
|
{
|
|
if (dpObj == null) continue;
|
|
var df = new DataFeed();
|
|
df.id = ReadString(dpObj, "id");
|
|
df.x = ReadFloat(dpObj, "x", 0f);
|
|
df.y = ReadFloat(dpObj, "y", 0f);
|
|
df.z = ReadFloat(dpObj, "z", 0f);
|
|
df.value = ReadFloat(dpObj, "value", 0f);
|
|
df.name = ReadString(dpObj, "name");
|
|
df.useColor = ReadBool(dpObj, "useColor", false);
|
|
if (df.useColor)
|
|
{
|
|
var colorToken = GetToken(dpObj, "color");
|
|
if (colorToken != null)
|
|
{
|
|
df.color = new Color(
|
|
ReadFloat(colorToken, "r", 1f),
|
|
ReadFloat(colorToken, "g", 1f),
|
|
ReadFloat(colorToken, "b", 1f),
|
|
ReadFloat(colorToken, "a", 1f));
|
|
}
|
|
}
|
|
datas.Add(df);
|
|
}
|
|
}
|
|
}
|
|
|
|
sf.datas = datas.Count > 0 ? datas.ToArray() : null;
|
|
}
|
|
|
|
series.Add(sf);
|
|
}
|
|
feed.series = series.Count > 0 ? series.ToArray() : null;
|
|
}
|
|
|
|
return feed != null;
|
|
}
|
|
catch
|
|
{
|
|
feed = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static string NormalizeJsonForUnityJsonUtility(string json)
|
|
{
|
|
if (string.IsNullOrEmpty(json)) return json;
|
|
|
|
// Unity JsonUtility cannot parse enum values from strings.
|
|
// Convert: "type": "Line" -> "type": 0
|
|
json = Regex.Replace(json, "\\\"type\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"", match =>
|
|
{
|
|
var label = match.Groups[1].Value;
|
|
if (Enum.TryParse(label, ignoreCase: true, out SerieType parsed))
|
|
{
|
|
return "\"type\": " + ((int)parsed).ToString();
|
|
}
|
|
|
|
return match.Value;
|
|
});
|
|
|
|
// Convert: "axisId": "XBottom" -> "axisId": 0
|
|
json = Regex.Replace(json, "\\\"axisId\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"", match =>
|
|
{
|
|
var label = match.Groups[1].Value;
|
|
if (Enum.TryParse(label, ignoreCase: true, out AxisId parsed))
|
|
{
|
|
return "\"axisId\": " + ((int)parsed).ToString();
|
|
}
|
|
|
|
return match.Value;
|
|
});
|
|
|
|
return json;
|
|
}
|
|
|
|
private static string ReplaceSerieTypeNumbersWithNames(string json)
|
|
{
|
|
if (string.IsNullOrEmpty(json)) return json;
|
|
|
|
// Convert: "type": 0 -> "type": "Line"
|
|
return Regex.Replace(json, "\\\"type\\\"\\s*:\\s*(\\d+)", match =>
|
|
{
|
|
if (!int.TryParse(match.Groups[1].Value, out var intValue)) return match.Value;
|
|
var name = ((SerieType)intValue).ToString();
|
|
return "\"type\": \"" + name + "\"";
|
|
});
|
|
}
|
|
|
|
private static bool ApplyFeedToProfile(ChartProfile profile, ChartFeed feed, bool allowMetaOverwrite)
|
|
{
|
|
if (profile == null || feed == null) return false;
|
|
|
|
bool changed = false;
|
|
if (profile.EnsureRuntimeData()) changed = true;
|
|
|
|
if (allowMetaOverwrite)
|
|
{
|
|
if (!string.IsNullOrEmpty(feed.chartId) && !string.Equals(profile.chartId, feed.chartId, StringComparison.Ordinal))
|
|
{
|
|
profile.chartId = feed.chartId;
|
|
changed = true;
|
|
}
|
|
if (!string.IsNullOrEmpty(feed.chartName) && !string.Equals(profile.chartName, feed.chartName, StringComparison.Ordinal))
|
|
{
|
|
profile.chartName = feed.chartName;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (feed.axes != null && feed.axes.Length > 0)
|
|
{
|
|
if (profile.axes == null) { profile.axes = new List<AxisConfig>(); changed = true; }
|
|
|
|
for (int i = 0; i < feed.axes.Length; i++)
|
|
{
|
|
var af = feed.axes[i];
|
|
if (af == null) continue;
|
|
|
|
AxisConfig axis = null;
|
|
for (int ai = 0; ai < profile.axes.Count; ai++)
|
|
{
|
|
var a = profile.axes[ai];
|
|
if (a != null && a.id == af.axisId) { axis = a; break; }
|
|
}
|
|
|
|
if (axis == null)
|
|
{
|
|
axis = new AxisConfig { id = af.axisId };
|
|
profile.axes.Add(axis);
|
|
changed = true;
|
|
}
|
|
|
|
if (af.labels != null)
|
|
{
|
|
axis.axisType = AxisType.Category;
|
|
if (axis.labels == null) { axis.labels = new List<string>(); changed = true; }
|
|
|
|
axis.labels.Clear();
|
|
axis.labels.AddRange(af.labels);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (feed.series != null && feed.series.Length > 0)
|
|
{
|
|
if (profile.series == null) { profile.series = new List<Serie>(); changed = true; }
|
|
|
|
for (int i = 0; i < feed.series.Length; i++)
|
|
{
|
|
var sf = feed.series[i];
|
|
if (sf == null) continue;
|
|
|
|
bool indexMode = string.IsNullOrEmpty(sf.serieId) && string.IsNullOrEmpty(sf.name);
|
|
|
|
Serie serie = null;
|
|
if (!string.IsNullOrEmpty(sf.serieId))
|
|
{
|
|
for (int si = 0; si < profile.series.Count; si++)
|
|
{
|
|
var s = profile.series[si];
|
|
if (s != null && string.Equals(s.id, sf.serieId, StringComparison.Ordinal)) { serie = s; break; }
|
|
}
|
|
}
|
|
if (serie == null && !string.IsNullOrEmpty(sf.name))
|
|
{
|
|
for (int si = 0; si < profile.series.Count; si++)
|
|
{
|
|
var s = profile.series[si];
|
|
if (s != null && string.Equals(s.name, sf.name, StringComparison.Ordinal)) { serie = s; break; }
|
|
}
|
|
}
|
|
|
|
if (serie == null && indexMode && i >= 0 && i < profile.series.Count)
|
|
{
|
|
serie = profile.series[i];
|
|
}
|
|
|
|
if (serie == null)
|
|
{
|
|
if (!allowMetaOverwrite && !indexMode) continue;
|
|
|
|
serie = new Serie { name = string.IsNullOrEmpty(sf.name) ? $"Series {i + 1}" : sf.name, type = sf.type, visible = true, seriesData = new List<SeriesData>() };
|
|
if (!string.IsNullOrEmpty(sf.serieId)) serie.id = sf.serieId;
|
|
serie.EnsureIntegrity();
|
|
profile.series.Add(serie);
|
|
changed = true;
|
|
}
|
|
|
|
if (allowMetaOverwrite && !indexMode)
|
|
{
|
|
if (!string.IsNullOrEmpty(sf.serieId) && string.IsNullOrEmpty(serie.id)) { serie.id = sf.serieId; changed = true; }
|
|
if (!string.IsNullOrEmpty(sf.name) && !string.Equals(serie.name, sf.name, StringComparison.Ordinal)) { serie.name = sf.name; changed = true; }
|
|
if (serie.type != sf.type) { serie.SetType(sf.type); changed = true; }
|
|
}
|
|
|
|
if (serie.seriesData == null) { serie.seriesData = new List<SeriesData>(); changed = true; }
|
|
|
|
if (sf.datas != null)
|
|
{
|
|
EnsureSeriesDataCount(serie.seriesData, sf.datas.Length);
|
|
for (int pi = 0; pi < sf.datas.Length; pi++)
|
|
{
|
|
var df = sf.datas[pi];
|
|
if (df == null) continue;
|
|
|
|
var p = serie.seriesData[pi] ?? (serie.seriesData[pi] = new SeriesData());
|
|
|
|
if (!string.IsNullOrEmpty(df.id)) p.id = df.id;
|
|
p.x = df.x;
|
|
p.y = df.y;
|
|
p.z = df.z;
|
|
p.value = df.value;
|
|
|
|
if (!string.IsNullOrEmpty(df.name)) p.name = df.name;
|
|
p.useColor = df.useColor;
|
|
if (df.useColor) p.color = df.color;
|
|
}
|
|
changed = true;
|
|
}
|
|
|
|
if (serie.EnsureIntegrity()) changed = true;
|
|
}
|
|
}
|
|
|
|
if (profile.EnsureRuntimeData()) changed = true;
|
|
return changed;
|
|
}
|
|
|
|
private static void EnsureSeriesDataCount(List<SeriesData> list, int count)
|
|
{
|
|
if (list == null) return;
|
|
while (list.Count < count) list.Add(new SeriesData());
|
|
if (list.Count > count) list.RemoveRange(count, list.Count - count);
|
|
}
|
|
|
|
private void UpdateJsonModeDropdown()
|
|
{
|
|
if (_jsonModeDropdown == null) return;
|
|
|
|
var label = JsonExampleModeToLabel(_jsonExampleMode);
|
|
_jsonModeDropdown.SetValueWithoutNotify(label);
|
|
}
|
|
|
|
private static string JsonExampleModeToLabel(JsonExampleMode mode)
|
|
{
|
|
for (int i = 0; i < JsonExampleModeOptions.Count; i++)
|
|
{
|
|
if (JsonExampleModeOptions[i].mode == mode) return JsonExampleModeOptions[i].label;
|
|
}
|
|
|
|
return JsonExampleModeOptions != null && JsonExampleModeOptions.Count > 0
|
|
? JsonExampleModeOptions[0].label
|
|
: "Lite_Index";
|
|
}
|
|
|
|
private static JsonExampleMode JsonExampleModeFromLabel(string label)
|
|
{
|
|
if (string.IsNullOrEmpty(label)) return JsonExampleMode.Lite_Index;
|
|
|
|
for (int i = 0; i < JsonExampleModeOptions.Count; i++)
|
|
{
|
|
if (string.Equals(JsonExampleModeOptions[i].label, label, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return JsonExampleModeOptions[i].mode;
|
|
}
|
|
}
|
|
|
|
return JsonExampleMode.Lite_Index;
|
|
}
|
|
|
|
private static string BuildInjectionJsonForSelectedProfile(ChartProfile profile, string chartId, JsonExampleMode mode)
|
|
{
|
|
return BuildInjectionJsonForSelectedProfile(profile, chartId, mode, JsonDatasMode.Standard);
|
|
}
|
|
|
|
private static string BuildInjectionJsonForSelectedProfile(ChartProfile profile, string chartId, JsonExampleMode mode, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
if (mode == JsonExampleMode.Lite_Index) return BuildLiteIndexInjectionJson(profile, datasMode);
|
|
if (mode == JsonExampleMode.Lite_Name) return BuildStandardInjectionJson(profile, datasMode);
|
|
if (mode == JsonExampleMode.Lite_ID) return BuildLiteIdInjectionJson(profile, chartId, datasMode);
|
|
if (mode == JsonExampleMode.Standard_Axis) return BuildStandardAxisInjectionJson(profile, datasMode);
|
|
if (mode == JsonExampleMode.Standard) return BuildStandardInjectionJson(profile, datasMode);
|
|
|
|
if (mode == JsonExampleMode.Full) return BuildFullInjectionJson(profile, chartId, datasMode);
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string BuildFullInjectionJson(ChartProfile profile, string chartId, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartId = string.IsNullOrWhiteSpace(chartId) ? profile.chartId : chartId;
|
|
string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;
|
|
|
|
var sb = new System.Text.StringBuilder(8192);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartId\": \"").Append(JsonEscape(resolvedChartId)).Append("\",");
|
|
sb.Append("\n \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");
|
|
|
|
// Axes (Category only)
|
|
if (profile.axes != null)
|
|
{
|
|
var axisList = new List<AxisConfig>();
|
|
for (int i = 0; i < profile.axes.Count; i++)
|
|
{
|
|
var a = profile.axes[i];
|
|
if (a == null) continue;
|
|
if (a.axisType != AxisType.Category) continue;
|
|
if (a.labels == null || a.labels.Count == 0) continue;
|
|
axisList.Add(a);
|
|
}
|
|
|
|
if (axisList.Count > 0)
|
|
{
|
|
sb.Append(",\n \"axes\": [\n");
|
|
for (int i = 0; i < axisList.Count; i++)
|
|
{
|
|
var a = axisList[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"axisId\": \"").Append(JsonEscape(a.id.ToString())).Append("\",\n");
|
|
sb.Append(" \"labels\": [");
|
|
for (int li = 0; li < a.labels.Count; li++)
|
|
{
|
|
if (li > 0) sb.Append(", ");
|
|
sb.Append("\"").Append(JsonEscape(a.labels[li])).Append("\"");
|
|
}
|
|
sb.Append("]\n");
|
|
sb.Append(" }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
}
|
|
|
|
if (profile.series != null && profile.series.Count > 0)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (s == null) continue;
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"serieId\": \"").Append(JsonEscape(s.id)).Append("\",\n");
|
|
sb.Append(" \"name\": \"").Append(JsonEscape(s.name)).Append("\",\n");
|
|
sb.Append(" \"type\": \"").Append(JsonEscape(s.type.ToString())).Append("\"");
|
|
|
|
int dataCount = s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(",\n \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(",\n \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
|
|
float x = dp != null ? dp.x : 0f;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values && !isHeatmap)
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
else if (datasMode == JsonDatasMode.Values && isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
|
|
if (dp != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(dp.id)) sb.Append(", \"id\": \"").Append(JsonEscape(dp.id)).Append("\"");
|
|
if (!string.IsNullOrEmpty(dp.name)) sb.Append(", \"name\": \"").Append(JsonEscape(dp.name)).Append("\"");
|
|
if (dp.useColor)
|
|
{
|
|
sb.Append(", \"useColor\": true");
|
|
sb.Append(", \"color\": { \"r\": ").Append(dp.color.r.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"g\": ").Append(dp.color.g.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"b\": ").Append(dp.color.b.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"a\": ").Append(dp.color.a.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]");
|
|
else sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildLiteIndexInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;
|
|
|
|
var sb = new System.Text.StringBuilder(2048);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");
|
|
|
|
bool hasSeries = profile.series != null && profile.series.Count > 0;
|
|
if (hasSeries)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
|
|
int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s != null && s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(" \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
|
|
float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values && !isHeatmap)
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
else if (datasMode == JsonDatasMode.Values && isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]\n");
|
|
else sb.Append("\n ]\n");
|
|
}
|
|
|
|
sb.Append(" }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildStandardInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;
|
|
|
|
var sb = new System.Text.StringBuilder(4096);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");
|
|
|
|
if (profile.series != null && profile.series.Count > 0)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");
|
|
|
|
int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(",\n \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(",\n \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
float x = dp != null ? dp.x : 0f;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values && !isHeatmap)
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
else if (datasMode == JsonDatasMode.Values && isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]");
|
|
else sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildLiteNameInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;
|
|
|
|
var sb = new System.Text.StringBuilder(2048);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");
|
|
|
|
bool hasSeries = profile.series != null && profile.series.Count > 0;
|
|
if (hasSeries)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");
|
|
|
|
int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s != null && s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(",\n \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(",\n \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
|
|
float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values && !isHeatmap)
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
else if (datasMode == JsonDatasMode.Values && isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]\n");
|
|
else sb.Append("\n ]\n");
|
|
}
|
|
|
|
sb.Append(" }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildLiteIdInjectionJson(ChartProfile profile, string chartId, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartId = string.IsNullOrWhiteSpace(chartId) ? profile.chartId : chartId;
|
|
|
|
var sb = new System.Text.StringBuilder(2048);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartId\": \"").Append(JsonEscape(resolvedChartId)).Append("\"");
|
|
|
|
bool hasSeries = profile.series != null && profile.series.Count > 0;
|
|
if (hasSeries)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"serieId\": \"").Append(JsonEscape(s != null ? s.id : string.Empty)).Append("\"");
|
|
|
|
int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s != null && s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(",\n \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(",\n \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
|
|
float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
if (isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]");
|
|
else sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildStandardAxisInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
|
|
{
|
|
if (profile == null) return string.Empty;
|
|
|
|
string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;
|
|
|
|
var sb = new System.Text.StringBuilder(4096);
|
|
sb.Append("{\n");
|
|
sb.Append(" \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");
|
|
|
|
// Axes (Category only)
|
|
if (profile.axes != null)
|
|
{
|
|
var axisList = new List<AxisConfig>();
|
|
for (int i = 0; i < profile.axes.Count; i++)
|
|
{
|
|
var a = profile.axes[i];
|
|
if (a == null) continue;
|
|
if (a.axisType != AxisType.Category) continue;
|
|
if (a.labels == null || a.labels.Count == 0) continue;
|
|
axisList.Add(a);
|
|
}
|
|
|
|
if (axisList.Count > 0)
|
|
{
|
|
sb.Append(",\n \"axes\": [\n");
|
|
for (int i = 0; i < axisList.Count; i++)
|
|
{
|
|
var a = axisList[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"axisId\": \"").Append(JsonEscape(a.id.ToString())).Append("\",\n");
|
|
sb.Append(" \"labels\": [");
|
|
for (int li = 0; li < a.labels.Count; li++)
|
|
{
|
|
if (li > 0) sb.Append(", ");
|
|
sb.Append("\"").Append(JsonEscape(a.labels[li])).Append("\"");
|
|
}
|
|
sb.Append("]\n");
|
|
sb.Append(" }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
}
|
|
|
|
// Series points (name aligned)
|
|
if (profile.series != null && profile.series.Count > 0)
|
|
{
|
|
sb.Append(",\n \"series\": [\n");
|
|
for (int i = 0; i < profile.series.Count; i++)
|
|
{
|
|
var s = profile.series[i];
|
|
if (i > 0) sb.Append(",\n");
|
|
|
|
sb.Append(" {\n");
|
|
sb.Append(" \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");
|
|
|
|
int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
|
|
if (dataCount > 0)
|
|
{
|
|
bool isHeatmap = s.type == SerieType.Heatmap;
|
|
if (datasMode == JsonDatasMode.Values)
|
|
{
|
|
sb.Append(",\n \"datas\": [");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(",\n \"datas\": [\n");
|
|
}
|
|
for (int pi = 0; pi < dataCount; pi++)
|
|
{
|
|
var dp = s.seriesData[pi];
|
|
if (pi > 0)
|
|
{
|
|
if (datasMode == JsonDatasMode.Values) sb.Append(", ");
|
|
else sb.Append(",\n");
|
|
}
|
|
float x = dp != null ? dp.x : 0f;
|
|
float y = dp != null ? dp.y : 0f;
|
|
float v = dp != null ? dp.value : 0f;
|
|
|
|
if (datasMode == JsonDatasMode.Values && !isHeatmap)
|
|
{
|
|
sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
else if (datasMode == JsonDatasMode.Values && isHeatmap)
|
|
{
|
|
sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append("]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
|
|
if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
|
|
sb.Append(" }");
|
|
}
|
|
}
|
|
if (datasMode == JsonDatasMode.Values) sb.Append("]");
|
|
else sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n }");
|
|
}
|
|
sb.Append("\n ]");
|
|
}
|
|
|
|
sb.Append("\n}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string JsonEscape(string s)
|
|
{
|
|
if (string.IsNullOrEmpty(s)) return string.Empty;
|
|
|
|
return s
|
|
.Replace("\\", "\\\\")
|
|
.Replace("\"", "\\\"")
|
|
.Replace("\r", "\\r")
|
|
.Replace("\n", "\\n")
|
|
.Replace("\t", "\\t");
|
|
}
|
|
|
|
private static string FloatArrayToJson(float[] values)
|
|
{
|
|
if (values == null || values.Length == 0) return "[]";
|
|
|
|
var sb = new System.Text.StringBuilder(values.Length * 6);
|
|
sb.Append("[");
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
if (i > 0) sb.Append(", ");
|
|
sb.Append(values[i].ToString("R", CultureInfo.InvariantCulture));
|
|
}
|
|
sb.Append("]");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static JsonDatasMode JsonDatasModeFromLabel(string label)
|
|
{
|
|
if (string.IsNullOrEmpty(label)) return JsonDatasMode.Standard;
|
|
if (string.Equals(label, "Values", StringComparison.OrdinalIgnoreCase)) return JsonDatasMode.Values;
|
|
if (string.Equals(label, "Full", StringComparison.OrdinalIgnoreCase)) return JsonDatasMode.Full;
|
|
return JsonDatasMode.Standard;
|
|
}
|
|
}
|
|
}
|