增加桑基图相关支持

This commit is contained in:
monitor1394
2023-12-09 23:22:00 +08:00
parent 1c6904f074
commit e8c8ab87aa
23 changed files with 948 additions and 34 deletions

View File

@@ -6,19 +6,35 @@ namespace XCharts.Runtime
[System.Serializable]
public class Level : ChildComponent
{
[SerializeField][Since("v3.10.0")] private int m_Depth = 0;
[SerializeField] private LabelStyle m_Label = new LabelStyle();
[SerializeField] private LabelStyle m_UpperLabel = new LabelStyle();
[SerializeField][Since("v3.10.0")] private LineStyle m_LineStyle = new LineStyle();
[SerializeField] private ItemStyle m_ItemStyle = new ItemStyle();
/// <summary>
/// 文本标签样式。
/// the depth of level.
/// ||层级深度。
/// </summary>
public int depth { get { return m_Depth; } set { m_Depth = value; } }
/// <summary>
/// the label style of level.
/// ||文本标签样式。
/// </summary>
public LabelStyle label { get { return m_Label; } }
/// <summary>
/// 上方的文本标签样式。
/// the upper label style of level.
/// ||上方的文本标签样式。
/// </summary>
public LabelStyle upperLabel { get { return m_UpperLabel; } }
/// <summary>
/// 数据项样式。
/// the line style of level.
/// ||线条样式。
/// </summary>
public LineStyle lineStyle { get { return m_LineStyle; } }
/// <summary>
/// the item style of level.
/// ||数据项样式。
/// </summary>
public ItemStyle itemStyle { get { return m_ItemStyle; } }
}

View File

@@ -222,14 +222,15 @@ namespace XCharts.Runtime
public virtual void ClearData()
{
ClearSerieData();
ClearSerieLinks();
ClearComponentData();
}
[Since("v3.4.0")]
/// <summary>
/// Clear the data of all series.
/// ||清空所有serie的数据。
/// </summary>
[Since("v3.4.0")]
public virtual void ClearSerieData()
{
foreach (var serie in m_Series)
@@ -238,11 +239,24 @@ namespace XCharts.Runtime
RefreshChart();
}
[Since("v3.4.0")]
/// <summary>
/// Clear the link data of all series.
/// ||清空所有serie的link数据。
/// </summary>
[Since("v3.10.0")]
public virtual void ClearSerieLinks()
{
foreach (var serie in m_Series)
serie.ClearLinks();
m_CheckAnimation = false;
RefreshChart();
}
/// <summary>
/// Clear the data of all components.
/// ||清空所有组件的数据。
/// </summary>
[Since("v3.4.0")]
public virtual void ClearComponentData()
{
foreach (var component in m_Components)
@@ -625,13 +639,13 @@ namespace XCharts.Runtime
return theme.GetBackgroundColor(background);
}
[Since("v3.4.0")]
/// <summary>
/// 获得Serie的标识颜色。
/// </summary>
/// <param name="serie"></param>
/// <param name="serieData"></param>
/// <returns></returns>
[Since("v3.4.0")]
public Color32 GetMarkColor(Serie serie, SerieData serieData)
{
var itemStyle = SerieHelper.GetItemStyle(serie, serieData);

View File

@@ -442,6 +442,27 @@ namespace XCharts.Runtime
return null;
}
/// <summary>
/// Add a link data to serie.
/// ||添加一个关系图的关系数据。
/// </summary>
/// <param name="serieIndex">the index of serie</param>
/// <param name="sourceName">the source name of link</param>
/// <param name="targetName">the target name of link</param>
/// <param name="value">the value of link</param>
/// <returns></returns>
public SerieDataLink AddLink(int serieIndex, string sourceName, string targetName, double value)
{
var serie = GetSerie(serieIndex);
if (serie != null)
{
var link = serie.AddLink(sourceName, targetName, value);
RefreshPainter(serie.painter);
return link;
}
return null;
}
/// <summary>
/// Update serie data by serie name.
/// ||更新指定系列中的指定索引数据。

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fcb8d4f1ad56b432f8a8eae9fa5941b3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,412 @@
using System.Collections.Generic;
using UnityEngine;
namespace XCharts.Runtime
{
/// <summary>
/// the data struct of graph.
/// ||数据结构-图。
/// </summary>
public class Graph
{
public bool directed;
public List<GraphNode> nodes = new List<GraphNode>();
public List<GraphEdge> edges = new List<GraphEdge>();
public Dictionary<string, GraphNode> nodeMap = new Dictionary<string, GraphNode>();
public Dictionary<string, GraphEdge> edgeMap = new Dictionary<string, GraphEdge>();
public Graph(bool directed)
{
this.directed = directed;
}
public void Clear()
{
nodes.Clear();
edges.Clear();
nodeMap.Clear();
edgeMap.Clear();
}
public void Refresh()
{
foreach (var node in nodes)
{
node.depth = GetNodeDepth(node);
}
}
public static double GetNodesTotalValue(List<GraphNode> nodes)
{
double totalValue = 0;
foreach (var node in nodes)
{
totalValue += node.totalValues;
}
return totalValue;
}
public List<List<GraphNode>> GetDepthNodes()
{
List<List<GraphNode>> depthNodes = new List<List<GraphNode>>();
var maxDepth = GetMaxDepth();
for (int i = 0; i <= maxDepth; i++)
{
depthNodes.Add(new List<GraphNode>());
}
foreach (var node in nodes)
{
if (node.inDegree == 0)
{
depthNodes[0].Add(node);
}
else
{
int deep = GetNodeDepth(node);
depthNodes[maxDepth - deep].Add(node);
}
}
return depthNodes;
}
public List<GraphNode> GetRootNodes()
{
List<GraphNode> rootNodes = new List<GraphNode>();
foreach (var node in nodes)
{
if (node.inDegree == 0)
{
rootNodes.Add(node);
}
}
return rootNodes;
}
public int GetMaxDepth()
{
int maxDepth = 0;
foreach (var node in nodes)
{
int deep = GetNodeDepth(node);
if (deep > maxDepth)
{
maxDepth = deep;
}
}
return maxDepth;
}
// public int GetNodeDepth(GraphNode node)
// {
// int depth = 0;
// GetNodeDepth(node, ref depth);
// return depth;
// }
// public void GetNodeDepth(GraphNode node, ref int depth, int recursiveCount = 0)
// {
// if (recursiveCount > 50)
// {
// XLog.Error("Graph.GetNodeDeep(): recursiveCount > 50, maybe graph is ring");
// return;
// }
// if (node.inDegree == 0)
// {
// return;
// }
// else
// {
// depth += 1;
// foreach (var edge in node.inEdges)
// {
// GetNodeDepth(edge.node1, ref depth, recursiveCount + 1);
// }
// }
// }
public int GetNodeDepth(GraphNode node, int recursiveCount = 0)
{
if (recursiveCount > 50)
{
XLog.Error("Graph.GetNodeDeep(): recursiveCount > 50, maybe graph is ring");
return 0;
}
int depth = 0;
if (node.outDegree == 0)
{
return depth;
}
else
{
foreach (var edge in node.outEdges)
{
int otherDeep = GetNodeDepth(edge.node2, recursiveCount + 1);
if (otherDeep > depth)
{
depth = otherDeep;
}
}
return depth + 1;
}
}
public GraphNode GetNode(string nodeId)
{
if (nodeMap.ContainsKey(nodeId))
{
return nodeMap[nodeId];
}
else
{
return null;
}
}
public GraphEdge GetEdge(string nodeId1, string nodeId2)
{
if (directed)
{
return edgeMap[nodeId1 + "_" + nodeId2];
}
else
{
var key = nodeId1 + "_" + nodeId2;
if (edgeMap.ContainsKey(key))
{
return edgeMap[key];
}
else
{
key = nodeId2 + "_" + nodeId1;
if (edgeMap.ContainsKey(key))
{
return edgeMap[key];
}
else
{
return null;
}
}
}
}
public GraphNode AddNode(string nodeId, string nodeName, int dataIndex)
{
if (nodeMap.ContainsKey(nodeId))
{
return nodeMap[nodeId];
}
else
{
GraphNode node = new GraphNode(nodeId, nodeName, dataIndex);
node.hostGraph = this;
nodeMap.Add(nodeId, node);
nodes.Add(node);
return node;
}
}
public GraphEdge AddEdge(string nodeId1, string nodeId2, double value)
{
GraphNode node1, node2;
if (!nodeMap.TryGetValue(nodeId1, out node1))
{
XLog.Warning("Graph.AddEdge(): " + nodeId1 + " not exist");
return null;
}
if (!nodeMap.TryGetValue(nodeId2, out node2))
{
XLog.Warning("Graph.AddEdge(): " + nodeId2 + " not exist");
return null;
}
if (node1 == null)
{
XLog.Warning("Graph.AddEdge(): node1 is null");
return null;
}
if (node2 == null)
{
XLog.Warning("Graph.AddEdge(): node2 is null");
return null;
}
if (node1 == node2)
{
XLog.Warning("Graph.AddEdge(): node1 == node2");
return null;
}
string edgeKey = nodeId1 + "_" + nodeId2;
if (edgeMap.ContainsKey(edgeKey))
{
return edgeMap[edgeKey];
}
else
{
GraphEdge edge = new GraphEdge(node1, node2, value);
edge.key = edgeKey;
edge.hostGraph = this;
if (directed)
{
node1.outEdges.Add(edge);
node2.inEdges.Add(edge);
}
node1.edges.Add(edge);
if (node1 != node2)
{
node2.edges.Add(edge);
}
edgeMap.Add(edgeKey, edge);
edges.Add(edge);
return edge;
}
}
public void EachNode(System.Action<GraphNode> onEach)
{
if (onEach == null) return;
foreach (var node in nodes)
{
onEach(node);
}
}
public void BreadthFirstTraverse(GraphNode startNode, System.Action<GraphNode> onTraverse)
{
if (startNode == null) return;
foreach (var node in nodes)
{
node.visited = false;
}
onTraverse(startNode);
startNode.visited = true;
Queue<GraphNode> queue = new Queue<GraphNode>();
queue.Enqueue(startNode);
while (queue.Count > 0)
{
var currentNode = queue.Dequeue();
foreach (var edge in currentNode.edges)
{
var otherNode = edge.node1 == currentNode ? edge.node2 : edge.node1;
if (!otherNode.visited)
{
onTraverse(otherNode);
otherNode.visited = true;
queue.Enqueue(otherNode);
}
}
}
}
public void DeepFirstTraverse(GraphNode startNode, System.Action<GraphNode> onTraverse)
{
if (startNode == null) return;
foreach (var node in nodes)
{
node.visited = false;
}
Stack<GraphNode> stack = new Stack<GraphNode>();
stack.Push(startNode);
while (stack.Count > 0)
{
var currentNode = stack.Pop();
if (!currentNode.visited)
{
onTraverse(currentNode);
currentNode.visited = true;
}
foreach (var edge in currentNode.edges)
{
var otherNode = edge.node1 == currentNode ? edge.node2 : edge.node1;
if (!otherNode.visited)
{
stack.Push(otherNode);
}
}
}
}
}
/// <summary>
/// The node of graph.
/// ||图的节点。
/// </summary>
public class GraphNode
{
public string id;
public string name;
public List<GraphEdge> edges = new List<GraphEdge>();
public List<GraphEdge> inEdges = new List<GraphEdge>();
public List<GraphEdge> outEdges = new List<GraphEdge>();
public Graph hostGraph;
public int dataIndex;
public bool visited;
public int depth = -1;
public GraphNode(string id, string name, int dataIndex)
{
this.id = id;
this.name = name;
this.dataIndex = dataIndex;
}
public int degree { get { return edges.Count; } }
public int inDegree { get { return inEdges.Count; } }
public int outDegree { get { return outEdges.Count; } }
public double totalValues
{
get
{
double totalValue = 0;
if (inEdges.Count == 0)
{
foreach (var edge in outEdges)
{
totalValue += edge.value;
}
}
else
{
foreach (var edge in inEdges)
{
totalValue += edge.value;
}
}
return totalValue;
}
}
public override string ToString()
{
return name;
}
}
/// <summary>
/// The edge of graph.
/// ||图的边。
/// </summary>
public class GraphEdge
{
public string key;
public GraphNode node1;
public GraphNode node2;
public double value;
public Graph hostGraph;
public GraphEdge(GraphNode node1, GraphNode node2, double value)
{
this.node1 = node1;
this.node2 = node2;
this.value = value;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d778205a63524227a09c7e5b0e8736f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -309,6 +309,7 @@ namespace XCharts.Runtime
[SerializeField] private AnimationStyle m_Animation = new AnimationStyle();
[SerializeField] private ItemStyle m_ItemStyle = new ItemStyle();
[SerializeField] private List<SerieData> m_Data = new List<SerieData>();
[SerializeField] private List<SerieDataLink> m_Links = new List<SerieDataLink>();
[NonSerialized] internal int m_FilterStart;
[NonSerialized] internal int m_FilterEnd;
@@ -966,6 +967,10 @@ namespace XCharts.Runtime
/// </summary>
public List<SerieData> data { get { return m_Data; } }
/// <summary>
/// 数据节点的边。
/// </summary>
public List<SerieDataLink> links { get { return m_Links; } }
/// <summary>
/// 取色策略是否为按数据项分配。
/// </summary>
public bool colorByData { get { return colorBy == SerieColorBy.Data; } }
@@ -1271,6 +1276,15 @@ namespace XCharts.Runtime
SetVerticesDirty();
}
/// <summary>
/// 清空所有Link数据
/// </summary>
public void ClearLinks()
{
m_Links.Clear();
SetVerticesDirty();
}
/// <summary>
/// 移除指定索引的数据
/// </summary>
@@ -1504,6 +1518,26 @@ namespace XCharts.Runtime
}
}
/// <summary>
/// Add a link data.
/// ||添加一个关系图的关系数据。
/// </summary>
/// <param name="sourceName"></param>
/// <param name="targetName"></param>
/// <param name="value"></param>
/// <returns></returns>
public SerieDataLink AddLink(string sourceName, string targetName, double value)
{
var link = new SerieDataLink();
link.source = sourceName;
link.target = targetName;
link.value = value;
m_Links.Add(link);
SetVerticesDirty();
labelDirty = true;
return link;
}
private void CheckMaxCache()
{
if (m_MaxCache <= 0) return;

View File

@@ -69,6 +69,8 @@ namespace XCharts.Runtime
}
private bool m_Highligth;
public bool selected;
public double inTotalValue;
public double outTotalValue;
public void Reset()
{

View File

@@ -0,0 +1,47 @@
using UnityEngine;
namespace XCharts.Runtime
{
/// <summary>
/// the link of serie data. Used for sankey chart. Sankey chart only supports directed acyclic graph. make sure the data link is directed acyclic graph.
/// ||数据节点之间的连线。可用于桑基图等,桑基图只支持有向无环图,请保证数据的连线是有向无环图。
/// </summary>
[System.Serializable]
[Since("v3.10.0")]
public class SerieDataLink : ChildComponent
{
[SerializeField] private string m_Source;
[SerializeField] private string m_Target;
[SerializeField] private double m_Value;
/// <summary>
/// the source node name.
/// ||边的源节点名称。
/// </summary>
public string source
{
get { return m_Source; }
set { m_Source = value; }
}
/// <summary>
/// the target node name.
/// ||边的目标节点名称。
/// </summary>
public string target
{
get { return m_Target; }
set { m_Target = value; }
}
/// <summary>
/// the value of link. decide the width of link.
/// ||边的值。决定边的宽度。
/// </summary>
public double value
{
get { return m_Value; }
set { m_Value = value; }
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7402f12ebc4aa4421939efecae53624d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -31,6 +31,7 @@ namespace XCharts.Runtime
public virtual void OnBeginDrag(PointerEventData eventData) { }
public virtual void OnEndDrag(PointerEventData eventData) { }
public virtual void OnScroll(PointerEventData eventData) { }
public virtual void OnDataUpdate() { }
public virtual void RefreshLabelNextFrame() { }
public virtual void RefreshLabelInternal() { }
public virtual void ForceUpdateSerieContext() { }
@@ -38,7 +39,8 @@ namespace XCharts.Runtime
public virtual void UpdateTooltipSerieParams(int dataIndex, bool showCategory,
string category, string marker,
string itemFormatter, string numericFormatter, string ignoreDataDefaultContent,
ref List<SerieParams> paramList, ref string title) { }
ref List<SerieParams> paramList, ref string title)
{ }
public virtual void OnLegendButtonClick(int index, string legendName, bool show) { }
public virtual void OnLegendButtonEnter(int index, string legendName) { }
public virtual void OnLegendButtonExit(int index, string legendName) { }
@@ -76,7 +78,7 @@ namespace XCharts.Runtime
internal override void SetSerie(Serie serie)
{
this.serie = (T) serie;
this.serie = (T)serie;
this.serie.context.param.serieType = typeof(T);
m_NeedInitComponent = true;
AnimationStyleHelper.UpdateSerieAnimation(serie);
@@ -107,6 +109,7 @@ namespace XCharts.Runtime
}
if (serie.dataDirty)
{
OnDataUpdate();
SeriesHelper.UpdateSerieNameList(chart, ref chart.m_LegendRealShowName);
chart.OnSerieDataUpdate(serie.index);
serie.OnDataUpdate();
@@ -387,7 +390,7 @@ namespace XCharts.Runtime
return;
}
InitRoot();
var dataAutoColor = (Color) chart.GetLegendRealShowNameColor(serie.legendName);
var dataAutoColor = (Color)chart.GetLegendRealShowNameColor(serie.legendName);
m_EndLabel = ChartHelper.AddChartLabel(s_SerieEndLabelObjectName, m_SerieRoot.transform, serie.endLabel,
chart.theme.common, "", dataAutoColor, TextAnchor.MiddleLeft);
m_EndLabel.SetActive(serie.endLabel.show);
@@ -599,7 +602,7 @@ namespace XCharts.Runtime
var colorIndex = serie.colorByData ? serieData.index : serie.index;
Color32 color, toColor;
SerieHelper.GetItemColor(out color, out toColor, serie, serieData, chart.theme, colorIndex, SerieState.Normal, false);
return (Color) color;
return (Color)color;
}
protected void UpdateCoordSerieParams(ref List<SerieParams> paramList, ref string title,