【Unity】绘制折线图和柱状图
绘制折线图和柱状图,主要包括如下效果:
背景网格的绘制;
折线和拐点的绘制;
长方形柱的绘制(柱宽可以修改);
X/Y轴的标签绘制(标签的单位可以修改、X轴的间距可以修改);
鼠标移动到折线拐点/长方形柱是显示对应数值Tooltip;
成果展示
Scene部分
脚本部分
定义折线图和柱状图的接口
//折线和拐点、长方形柱相关
private interface IGraphVisual
{
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText);
}
//更新数据、显示Tooltip|、删除等
private interface IGraphVisualObject
{
void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText);
void CleanUp();
}
折线图的类
折线图的特殊之处是需要知道上一个的值和当前值,才能连成线。
private class LineChartVisual : IGraphVisual
{
private RectTransform graphContainer;
private Sprite dotSprite;
private LineGraphVisualObject lastVisualObject;
private Color dotColor;
private Color dotConnectColor;
public LineChartVisual(RectTransform graphContainer, Sprite dotSprite, Color dotColor, Color dotConnectColor)
{
this.graphContainer = graphContainer;
this.lastVisualObject = null;
this.dotSprite = dotSprite;
this.dotColor = dotColor;
this.dotConnectColor = dotConnectColor;
}
//绘制单个结点
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
List<GameObject> gameObjectList = new List<GameObject>();
GameObject dotGameObject = CreateDot(graphPosition);
gameObjectList.Add(dotGameObject);
GameObject dotConnectionGameObject = null;
if (lastVisualObject != null)
{
dotConnectionGameObject = CreateDotConnection(lastVisualObject.GetGraphPosition(), dotGameObject.GetComponent<RectTransform>().anchoredPosition);
gameObjectList.Add(dotConnectionGameObject);
}
LineGraphVisualObject lineGraphVisualObject = new LineGraphVisualObject(dotGameObject, dotConnectionGameObject, lastVisualObject);
lineGraphVisualObject.SetGraphVisualObjectInfo(graphPosition, graphPositionWidth, tooltipText);
lastVisualObject = lineGraphVisualObject;
return lineGraphVisualObject;
}
private GameObject CreateDot(Vector2 anchoredPosition)
{
GameObject gameObject = new GameObject("dot", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().sprite = dotSprite;
gameObject.GetComponent<Image>().color = dotColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = anchoredPosition;
rectTransform.sizeDelta = new Vector2(7, 7);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
gameObject.AddComponent<Button_UI>();
return gameObject;
}
private GameObject CreateDotConnection(Vector2 dotPositionA, Vector2 dotPositionB)
{
GameObject gameObject = new GameObject("dotConnection", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().color = dotConnectColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
Vector2 dir = (dotPositionB - dotPositionA).normalized;
float distance = Vector2.Distance(dotPositionA, dotPositionB);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
rectTransform.sizeDelta = new Vector2(distance, 2f);
rectTransform.anchoredPosition = dotPositionA + dir * distance * .5f;
rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVectorFloat(dir));
return gameObject;
}
public class LineGraphVisualObject : IGraphVisualObject
{
private event EventHandler OnChangedGraphVisualObjectInfo;
private GameObject dotGameObject;
private GameObject dotConnectionGameObject;
private LineGraphVisualObject lastVisualObject;
public LineGraphVisualObject(GameObject dotGameObject, GameObject dotConnectionObject, LineGraphVisualObject lastVisualObject)
{
this.dotGameObject = dotGameObject;
this.dotConnectionGameObject = dotConnectionObject;
this.lastVisualObject = lastVisualObject;
if (lastVisualObject != null)
{
lastVisualObject.OnChangedGraphVisualObjectInfo += LastVisualObject_OnChangedGraphVisualObjectInfo;
}
}
private void LastVisualObject_OnChangedGraphVisualObjectInfo(object sender, EventArgs e)
{
UpdateDotConnection();
}
public void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
RectTransform dotRectTransform = dotGameObject.GetComponent<RectTransform>();
dotRectTransform.anchoredPosition = graphPosition;
UpdateDotConnection();
Button_UI lineBtnUi = dotGameObject.GetComponent<Button_UI>();
lineBtnUi.MouseOverOnceFunc = () =>
{
ShowTooltip_Static(tooltipText, graphPosition);
};
lineBtnUi.MouseOutOnceFunc = () =>
{
HideTooltip_Static();
};
if (OnChangedGraphVisualObjectInfo != null) OnChangedGraphVisualObjectInfo(this, EventArgs.Empty);
}
public void CleanUp()
{
Destroy(dotConnectionGameObject);
Destroy(dotGameObject);
}
public Vector2 GetGraphPosition()
{
RectTransform rectTransform = dotGameObject.GetComponent<RectTransform>();
return rectTransform.anchoredPosition;
}
private void UpdateDotConnection()
{
if (dotConnectionGameObject != null)
{
RectTransform dotConnectionRectTransform = dotConnectionGameObject.GetComponent<RectTransform>();
Vector2 dir = (lastVisualObject.GetGraphPosition() - GetGraphPosition()).normalized;
float distance = Vector2.Distance(GetGraphPosition(), lastVisualObject.GetGraphPosition());
dotConnectionRectTransform.anchorMin = new Vector2(0, 0);
dotConnectionRectTransform.anchorMax = new Vector2(0, 0);
dotConnectionRectTransform.sizeDelta = new Vector2(distance, 2f);
dotConnectionRectTransform.anchoredPosition = GetGraphPosition() + dir * distance * .5f;
dotConnectionRectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVectorFloat(dir));
}
}
}
}
柱状图的类
柱状图中需要的是长方形柱的宽度
private class BarChartVisual : IGraphVisual
{
private RectTransform graphContainer;
private Color barColor;
private float barWidthMultiplier;
public BarChartVisual(RectTransform graphContainer, Color barColor, float barWidthMultipliter)
{
this.graphContainer = graphContainer;
this.barColor = barColor;
this.barWidthMultiplier = barWidthMultipliter;
}
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
GameObject barGameObject = CreateBar(graphPosition, graphPositionWidth);
BarChartVisualObject barChartVisualObject = new BarChartVisualObject(barGameObject, barWidthMultiplier);
barChartVisualObject.SetGraphVisualObjectInfo(graphPosition, graphPositionWidth, tooltipText);
return barChartVisualObject;
}
private GameObject CreateBar(Vector2 graphPosition, float barWidth)
{
GameObject gameObject = new GameObject("bar", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().color = barColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
rectTransform.sizeDelta = new Vector2(barWidth * barWidthMultiplier, graphPosition.y);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
rectTransform.pivot = new Vector2(.5f, 0f);
gameObject.AddComponent<Button_UI>();
return gameObject;
}
public class BarChartVisualObject : IGraphVisualObject
{
private GameObject barGameObject;
private float barWidthMultiplier;
public BarChartVisualObject(GameObject barGameObject, float barWidthMultiplier)
{
this.barGameObject = barGameObject;
this.barWidthMultiplier = barWidthMultiplier;
}
public void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
RectTransform rectTransform = barGameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
rectTransform.sizeDelta = new Vector2(graphPositionWidth * barWidthMultiplier, graphPosition.y);
Button_UI barBtnUi = barGameObject.GetComponent<Button_UI>();
barBtnUi.MouseOverOnceFunc = () =>
{
ShowTooltip_Static(tooltipText, graphPosition);
};
barBtnUi.MouseOutOnceFunc = () =>
{
HideTooltip_Static();
};
}
public void CleanUp()
{
Destroy(barGameObject);
}
}
}
绘制完整的图表
graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>();
labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>();
labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>();
dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>();
dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>();
tooltipGameObject = graphContainer.Find("tooltip").gameObject;
startYScaleZero = true;
gameObjectList = new List<GameObject>();
graphVisualObjectList = new List<IGraphVisualObject>();
//绘制折线图
IGraphVisual lineChartVisual = new LineChartVisual(
graphContainer, dotSprite, Color.green, new Color(1, 1, 1, .5f));
List<int> valueList = new List<int>() { 5, 98, 56, 45, 30, 22, 17, 15, 13, 17, 25, 37, 40, 36, 33, 5, 75, 75, 24, 88, 98, 55, 3, 46, 99 };
ShowGraph(valueList, lineChartVisual, valueList.Count, (_i) => "D" + (_i + 1), (_f) => "$" + Mathf.RoundToInt(_f));
//绘制柱状图
IGraphVisual barChartVisual = new BarChartVisual(graphContainer, Color.green, .8f);
List<int> valueList = new List<int>() { 5, 98, 56, 45, 30, 22, 17, 15, 13, 17, 25, 37, 40, 36, 33, 5, 75, 75, 24, 88, 98, 55, 3, 46, 99 };
ShowGraph(valueList, barChartVisual, valueList.Count, (_i) => "D" + (_i + 1), (_f) => "$" + Mathf.RoundToInt(_f));
需要图表数据List<int> valueList
图表的类IGraphVisual graphVisual
显示在页面中的数值个数maxVisibleValueAmount
:页面中可以不显示全部的数据,此数据影响到X轴方向的间隔宽度
x/y轴文字标签的格式Func<int, string> getAxisLabelX
Func<float, string> getAxisLabelY
private void ShowGraph(List<int> valueList, IGraphVisual graphVisual, int maxVisibleValueAmount = -1, Func<int, string> getAxisLabelX = null, Func<float, string> getAxisLabelY = null)
{
this.valueList = valueList;
this.graphVisual = graphVisual;
this.getAxisLabelX = getAxisLabelX;
this.getAxisLabelY = getAxisLabelY;
//1、删除所有列表中的物体(网格、柱、点、线、标签等)
foreach (var item in gameObjectList)
{
Destroy(item);
}
gameObjectList.Clear();
foreach (var item in graphVisualObjectList)
{
item.CleanUp();
}
graphVisualObjectList.Clear();
//2、定义默认状态下X/Y标签的格式
if (getAxisLabelX == null)
{
getAxisLabelX = delegate (int _i) { return _i.ToString(); };
}
if (getAxisLabelY == null)
{
getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); };
}
//3、定义页面中显示的数据个数
if (maxVisibleValueAmount <= 0)
{
maxVisibleValueAmount = valueList.Count;
}
if (maxVisibleValueAmount > valueList.Count)
{
maxVisibleValueAmount = valueList.Count;
}
this.maxVisibleValueAmount = maxVisibleValueAmount;
//4、计算x/y取值范围和X轴的间隔宽度
float graphWeight = graphContainer.sizeDelta.x;
float graphHeight = graphContainer.sizeDelta.y;
float yMaximum, yMinimum;
CalculateYScale(out yMinimum, out yMaximum);
xSize = graphWeight / (maxVisibleValueAmount + 1);
//5、绘制Y轴背景网格、标签
int xIndex = 0;
//固定间隔显示Y轴
int separatorCount = 10;
for (int i = 0; i <= separatorCount; i++)
{
//标签
RectTransform labelY = Instantiate(labelTemplateY);
labelY.SetParent(graphContainer);
labelY.gameObject.SetActive(true);
float normalizedValue = i * 1f / separatorCount;
labelY.anchoredPosition = new Vector2(-12.6f, normalizedValue * graphHeight);
labelY.GetComponent<TextMeshProUGUI>().SetText(getAxisLabelY(yMinimum + normalizedValue * (yMaximum - yMinimum)));
gameObjectList.Add(labelY.gameObject);
//网格
RectTransform dashY = Instantiate(dashTemplateY);
dashY.SetParent(graphContainer);
dashY.gameObject.SetActive(true);
dashY.anchoredPosition = new Vector2(228.9f, normalizedValue * graphHeight);
gameObjectList.Add(dashY.gameObject);
}
//5、绘制X轴背景网格、标签和柱/折线元素
for (int i = Mathf.Max(valueList.Count - maxVisibleValueAmount, 0); i < valueList.Count; i++)
{
float xPosition = xSize + xIndex * xSize;
float yPosition = ((valueList[i] - yMinimum) / (yMaximum - yMinimum)) * graphHeight;
//标签
RectTransform labelX = Instantiate(labelTemplateX);
labelX.SetParent(graphContainer);
labelX.gameObject.SetActive(true);
labelX.anchoredPosition = new Vector2(xPosition, -4f);
labelX.GetComponent<TextMeshProUGUI>().SetText(getAxisLabelX(i));
gameObjectList.Add(labelX.gameObject);
//网格
RectTransform dashX = Instantiate(dashTemplateX);
dashX.SetParent(graphContainer);
dashX.gameObject.SetActive(true);
dashX.anchoredPosition = new Vector2(xPosition, 123.6f);
gameObjectList.Add(dashX.gameObject);
//柱、拐点和线元素
string tooltipText = getAxisLabelY(valueList[i]);
graphVisualObjectList.Add(graphVisual.CreateGraphVisualObject(new Vector2(xPosition, yPosition), xSize, tooltipText));
xIndex++;
}
}
private void CalculateYScale(out float yMinimum, out float yMaximum)
{
yMaximum = valueList[0];
yMinimum = valueList[0];
for (int i = Mathf.Max(valueList.Count - maxVisibleValueAmount, 0); i < valueList.Count; i++)
{
int value = valueList[i];
if (value > yMaximum)
{
yMaximum = value;
}
if (value < yMinimum)
{
yMinimum = value;
}
}
float yDifference = yMaximum - yMinimum;
if (yDifference <= 0)
{
yDifference = 5;
}
yMaximum = yMaximum + (yDifference * 0.2f);
yMinimum = yMinimum - (yDifference * 0.2f);
if (startYScaleZero)
{
yMinimum = 0f;
}
}
按钮切换柱状图/折线图
transform.Find("barChartBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetGraphVisual(barChartVisual);
};
transform.Find("lineChartBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetGraphVisual(lineChartVisual);
};
private void SetGraphVisual(IGraphVisual graphVisual)
{
ShowGraph(this.valueList, graphVisual, this.maxVisibleValueAmount, this.getAxisLabelX, this.getAxisLabelY);
}
按钮放大缩小图表
transform.Find("decreaseVisibleAmountBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
DecreaseVisibleAmount();
};
transform.Find("increaseVisibleAmountBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
IncreaseVisibleAmount();
};
private void IncreaseVisibleAmount()
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount + 1, this.getAxisLabelX, this.getAxisLabelY);
}
private void DecreaseVisibleAmount()
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount - 1, this.getAxisLabelX, this.getAxisLabelY);
}
按钮改变Y标签单位
transform.Find("dollorBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetAxisLabelY((_f) => "$" + Mathf.RoundToInt(_f));
};
transform.Find("rnbBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetAxisLabelY((_f) => "¥" + Mathf.RoundToInt(_f));
};
private void SetAxisLabelY(Func<float, string> getAxisLabelY)
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount, this.getAxisLabelX, getAxisLabelY);
}
tooltip的显示和隐藏
private static void ShowTooltip_Static(string tooltipText, Vector2 anchoredPosition)
{
instance.ShowTooltip(tooltipText, anchoredPosition);
}
private void ShowTooltip(string tooltipText, Vector2 anchoredPosition)
{
tooltipGameObject.SetActive(true);
tooltipGameObject.GetComponent<RectTransform>().anchoredPosition = anchoredPosition;
TextMeshProUGUI tooltipUIText = tooltipGameObject.transform.Find("text").GetComponent<TextMeshProUGUI>();
tooltipUIText.SetText(tooltipText);
float textPaddingSize = 4f;
Vector2 backgroundSize = tooltipUIText.GetPreferredValues() + 2 * (new Vector2(textPaddingSize, textPaddingSize));
tooltipGameObject.transform.Find("background").GetComponent<RectTransform>().sizeDelta = backgroundSize;
tooltipGameObject.transform.SetAsLastSibling();
}
private static void HideTooltip_Static()
{
instance.HideTooltip();
}
private void HideTooltip()
{
tooltipGameObject.SetActive(false);
}
完整代码
public class Window_Graph : MonoBehaviour
{
private static Window_Graph instance;
[SerializeField] private Sprite dotSprite;
private RectTransform graphContainer;
private RectTransform labelTemplateX;
private RectTransform labelTemplateY;
private RectTransform dashTemplateX;
private RectTransform dashTemplateY;
private GameObject tooltipGameObject;
private List<GameObject> gameObjectList;
private List<IGraphVisualObject> graphVisualObjectList;
private List<int> valueList;
private IGraphVisual graphVisual;
private int maxVisibleValueAmount;
private Func<int, string> getAxisLabelX;
private Func<float, string> getAxisLabelY;
private float xSize;
private bool startYScaleZero;
private void Awake()
{
instance = this;
graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>();
labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>();
labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>();
dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>();
dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>();
tooltipGameObject = graphContainer.Find("tooltip").gameObject;
startYScaleZero = true;
gameObjectList = new List<GameObject>();
graphVisualObjectList = new List<IGraphVisualObject>();
IGraphVisual lineChartVisual = new LineChartVisual(graphContainer, dotSprite, Color.green, new Color(1, 1, 1, .5f));
IGraphVisual barChartVisual = new BarChartVisual(graphContainer, Color.green, .8f);
List<int> valueList = new List<int>() { 5, 98, 56, 45, 30, 22, 17, 15, 13, 17, 25, 37, 40, 36, 33, 5, 75, 75, 24, 88, 98, 55, 3, 46, 99 };
ShowGraph(valueList, barChartVisual, valueList.Count, (_i) => "D" + (_i + 1), (_f) => "$" + Mathf.RoundToInt(_f));
transform.Find("barChartBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetGraphVisual(barChartVisual);
};
transform.Find("lineChartBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetGraphVisual(lineChartVisual);
};
//decreaseVisibleAmountBtn
transform.Find("decreaseVisibleAmountBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
DecreaseVisibleAmount();
};
transform.Find("increaseVisibleAmountBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
IncreaseVisibleAmount();
};
transform.Find("dollorBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetAxisLabelY((_f) => "$" + Mathf.RoundToInt(_f));
};
transform.Find("rnbBtn").GetComponent<Button_UI>().ClickFunc = () =>
{
SetAxisLabelY((_f) => "¥" + Mathf.RoundToInt(_f));
};
}
private static void ShowTooltip_Static(string tooltipText, Vector2 anchoredPosition)
{
instance.ShowTooltip(tooltipText, anchoredPosition);
}
private void ShowTooltip(string tooltipText, Vector2 anchoredPosition)
{
tooltipGameObject.SetActive(true);
tooltipGameObject.GetComponent<RectTransform>().anchoredPosition = anchoredPosition;
TextMeshProUGUI tooltipUIText = tooltipGameObject.transform.Find("text").GetComponent<TextMeshProUGUI>();
tooltipUIText.SetText(tooltipText);
float textPaddingSize = 4f;
Vector2 backgroundSize = tooltipUIText.GetPreferredValues() + 2 * (new Vector2(textPaddingSize, textPaddingSize));
tooltipGameObject.transform.Find("background").GetComponent<RectTransform>().sizeDelta = backgroundSize;
tooltipGameObject.transform.SetAsLastSibling();
}
private static void HideTooltip_Static()
{
instance.HideTooltip();
}
private void HideTooltip()
{
tooltipGameObject.SetActive(false);
}
private void SetAxisLabelX(Func<int, string> getAxisLabelX)
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount, getAxisLabelX, this.getAxisLabelY);
}
private void SetAxisLabelY(Func<float, string> getAxisLabelY)
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount, this.getAxisLabelX, getAxisLabelY);
}
private void IncreaseVisibleAmount()
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount + 1, this.getAxisLabelX, this.getAxisLabelY);
}
private void DecreaseVisibleAmount()
{
ShowGraph(this.valueList, this.graphVisual, this.maxVisibleValueAmount - 1, this.getAxisLabelX, this.getAxisLabelY);
}
private void SetGraphVisual(IGraphVisual graphVisual)
{
ShowGraph(this.valueList, graphVisual, this.maxVisibleValueAmount, this.getAxisLabelX, this.getAxisLabelY);
}
private void ShowGraph(List<int> valueList, IGraphVisual graphVisual, int maxVisibleValueAmount = -1, Func<int, string> getAxisLabelX = null, Func<float, string> getAxisLabelY = null)
{
this.valueList = valueList;
this.graphVisual = graphVisual;
this.getAxisLabelX = getAxisLabelX;
this.getAxisLabelY = getAxisLabelY;
foreach (var item in gameObjectList)
{
Destroy(item);
}
gameObjectList.Clear();
foreach (var item in graphVisualObjectList)
{
item.CleanUp();
}
graphVisualObjectList.Clear();
if (getAxisLabelX == null)
{
getAxisLabelX = delegate (int _i) { return _i.ToString(); };
}
if (getAxisLabelY == null)
{
getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); };
}
if (maxVisibleValueAmount <= 0)
{
maxVisibleValueAmount = valueList.Count;
}
if (maxVisibleValueAmount > valueList.Count)
{
maxVisibleValueAmount = valueList.Count;
}
this.maxVisibleValueAmount = maxVisibleValueAmount;
float graphWeight = graphContainer.sizeDelta.x;
float graphHeight = graphContainer.sizeDelta.y;
float yMaximum, yMinimum;
CalculateYScale(out yMinimum, out yMaximum);
xSize = graphWeight / (maxVisibleValueAmount + 1);
int xIndex = 0;
//固定间隔显示Y轴
int separatorCount = 10;
for (int i = 0; i <= separatorCount; i++)
{
RectTransform labelY = Instantiate(labelTemplateY);
labelY.SetParent(graphContainer);
labelY.gameObject.SetActive(true);
float normalizedValue = i * 1f / separatorCount;
labelY.anchoredPosition = new Vector2(-12.6f, normalizedValue * graphHeight);
labelY.GetComponent<TextMeshProUGUI>().SetText(getAxisLabelY(yMinimum + normalizedValue * (yMaximum - yMinimum)));
gameObjectList.Add(labelY.gameObject);
RectTransform dashY = Instantiate(dashTemplateY);
dashY.SetParent(graphContainer);
dashY.gameObject.SetActive(true);
dashY.anchoredPosition = new Vector2(228.9f, normalizedValue * graphHeight);
gameObjectList.Add(dashY.gameObject);
}
for (int i = Mathf.Max(valueList.Count - maxVisibleValueAmount, 0); i < valueList.Count; i++)
{
float xPosition = xSize + xIndex * xSize;
float yPosition = ((valueList[i] - yMinimum) / (yMaximum - yMinimum)) * graphHeight;
RectTransform labelX = Instantiate(labelTemplateX);
labelX.SetParent(graphContainer);
labelX.gameObject.SetActive(true);
labelX.anchoredPosition = new Vector2(xPosition, -4f);
labelX.GetComponent<TextMeshProUGUI>().SetText(getAxisLabelX(i));
gameObjectList.Add(labelX.gameObject);
RectTransform dashX = Instantiate(dashTemplateX);
dashX.SetParent(graphContainer);
dashX.gameObject.SetActive(true);
dashX.anchoredPosition = new Vector2(xPosition, 123.6f);
gameObjectList.Add(dashX.gameObject);
string tooltipText = getAxisLabelY(valueList[i]);
graphVisualObjectList.Add(graphVisual.CreateGraphVisualObject(new Vector2(xPosition, yPosition), xSize, tooltipText));
xIndex++;
}
}
private void UpdateValue(int index, int value)
{
valueList[index] = value;
float graphWeight = graphContainer.sizeDelta.x;
float graphHeight = graphContainer.sizeDelta.y;
float yMaximum, yMinimum;
CalculateYScale(out yMinimum, out yMaximum);
int xIndex = 0;
for (int i = Mathf.Max(valueList.Count - maxVisibleValueAmount, 0); i < valueList.Count; i++)
{
float xPosition = xSize + xIndex * xSize;
float yPosition = ((valueList[i] - yMinimum) / (yMaximum - yMinimum)) * graphHeight;
string tooltipText = getAxisLabelY(valueList[i]);
graphVisualObjectList[index].SetGraphVisualObjectInfo(new Vector2(xPosition, yPosition), xSize, tooltipText);
xIndex++;
}
}
private void CalculateYScale(out float yMinimum, out float yMaximum)
{
yMaximum = valueList[0];
yMinimum = valueList[0];
for (int i = Mathf.Max(valueList.Count - maxVisibleValueAmount, 0); i < valueList.Count; i++)
{
int value = valueList[i];
if (value > yMaximum)
{
yMaximum = value;
}
if (value < yMinimum)
{
yMinimum = value;
}
}
float yDifference = yMaximum - yMinimum;
if (yDifference <= 0)
{
yDifference = 5;
}
yMaximum = yMaximum + (yDifference * 0.2f);
yMinimum = yMinimum - (yDifference * 0.2f);
if (startYScaleZero)
{
yMinimum = 0f;
}
}
private interface IGraphVisual
{
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText);
}
private interface IGraphVisualObject
{
void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText);
void CleanUp();
}
private class LineChartVisual : IGraphVisual
{
private RectTransform graphContainer;
private Sprite dotSprite;
private LineGraphVisualObject lastVisualObject;
private Color dotColor;
private Color dotConnectColor;
public LineChartVisual(RectTransform graphContainer, Sprite dotSprite, Color dotColor, Color dotConnectColor)
{
this.graphContainer = graphContainer;
this.lastVisualObject = null;
this.dotSprite = dotSprite;
this.dotColor = dotColor;
this.dotConnectColor = dotConnectColor;
}
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
List<GameObject> gameObjectList = new List<GameObject>();
GameObject dotGameObject = CreateDot(graphPosition);
gameObjectList.Add(dotGameObject);
GameObject dotConnectionGameObject = null;
if (lastVisualObject != null)
{
dotConnectionGameObject = CreateDotConnection(lastVisualObject.GetGraphPosition(), dotGameObject.GetComponent<RectTransform>().anchoredPosition);
gameObjectList.Add(dotConnectionGameObject);
}
LineGraphVisualObject lineGraphVisualObject = new LineGraphVisualObject(dotGameObject, dotConnectionGameObject, lastVisualObject);
lineGraphVisualObject.SetGraphVisualObjectInfo(graphPosition, graphPositionWidth, tooltipText);
lastVisualObject = lineGraphVisualObject;
return lineGraphVisualObject;
}
private GameObject CreateDot(Vector2 anchoredPosition)
{
GameObject gameObject = new GameObject("dot", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().sprite = dotSprite;
gameObject.GetComponent<Image>().color = dotColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = anchoredPosition;
rectTransform.sizeDelta = new Vector2(7, 7);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
gameObject.AddComponent<Button_UI>();
return gameObject;
}
private GameObject CreateDotConnection(Vector2 dotPositionA, Vector2 dotPositionB)
{
GameObject gameObject = new GameObject("dotConnection", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().color = dotConnectColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
Vector2 dir = (dotPositionB - dotPositionA).normalized;
float distance = Vector2.Distance(dotPositionA, dotPositionB);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
rectTransform.sizeDelta = new Vector2(distance, 2f);
rectTransform.anchoredPosition = dotPositionA + dir * distance * .5f;
rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVectorFloat(dir));
return gameObject;
}
public class LineGraphVisualObject : IGraphVisualObject
{
private event EventHandler OnChangedGraphVisualObjectInfo;
private GameObject dotGameObject;
private GameObject dotConnectionGameObject;
private LineGraphVisualObject lastVisualObject;
public LineGraphVisualObject(GameObject dotGameObject, GameObject dotConnectionObject, LineGraphVisualObject lastVisualObject)
{
this.dotGameObject = dotGameObject;
this.dotConnectionGameObject = dotConnectionObject;
this.lastVisualObject = lastVisualObject;
if (lastVisualObject != null)
{
lastVisualObject.OnChangedGraphVisualObjectInfo += LastVisualObject_OnChangedGraphVisualObjectInfo;
}
}
private void LastVisualObject_OnChangedGraphVisualObjectInfo(object sender, EventArgs e)
{
UpdateDotConnection();
}
public void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
RectTransform dotRectTransform = dotGameObject.GetComponent<RectTransform>();
dotRectTransform.anchoredPosition = graphPosition;
UpdateDotConnection();
Button_UI lineBtnUi = dotGameObject.GetComponent<Button_UI>();
lineBtnUi.MouseOverOnceFunc = () =>
{
ShowTooltip_Static(tooltipText, graphPosition);
};
lineBtnUi.MouseOutOnceFunc = () =>
{
HideTooltip_Static();
};
if (OnChangedGraphVisualObjectInfo != null) OnChangedGraphVisualObjectInfo(this, EventArgs.Empty);
}
public void CleanUp()
{
Destroy(dotConnectionGameObject);
Destroy(dotGameObject);
}
public Vector2 GetGraphPosition()
{
RectTransform rectTransform = dotGameObject.GetComponent<RectTransform>();
return rectTransform.anchoredPosition;
}
private void UpdateDotConnection()
{
if (dotConnectionGameObject != null)
{
RectTransform dotConnectionRectTransform = dotConnectionGameObject.GetComponent<RectTransform>();
Vector2 dir = (lastVisualObject.GetGraphPosition() - GetGraphPosition()).normalized;
float distance = Vector2.Distance(GetGraphPosition(), lastVisualObject.GetGraphPosition());
dotConnectionRectTransform.anchorMin = new Vector2(0, 0);
dotConnectionRectTransform.anchorMax = new Vector2(0, 0);
dotConnectionRectTransform.sizeDelta = new Vector2(distance, 2f);
dotConnectionRectTransform.anchoredPosition = GetGraphPosition() + dir * distance * .5f;
dotConnectionRectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVectorFloat(dir));
}
}
}
}
private class BarChartVisual : IGraphVisual
{
private RectTransform graphContainer;
private Color barColor;
private float barWidthMultiplier;
public BarChartVisual(RectTransform graphContainer, Color barColor, float barWidthMultipliter)
{
this.graphContainer = graphContainer;
this.barColor = barColor;
this.barWidthMultiplier = barWidthMultipliter;
}
public IGraphVisualObject CreateGraphVisualObject(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
GameObject barGameObject = CreateBar(graphPosition, graphPositionWidth);
BarChartVisualObject barChartVisualObject = new BarChartVisualObject(barGameObject, barWidthMultiplier);
barChartVisualObject.SetGraphVisualObjectInfo(graphPosition, graphPositionWidth, tooltipText);
return barChartVisualObject;
}
private GameObject CreateBar(Vector2 graphPosition, float barWidth)
{
GameObject gameObject = new GameObject("bar", typeof(Image));
gameObject.transform.SetParent(graphContainer, false);
gameObject.GetComponent<Image>().color = barColor;
RectTransform rectTransform = gameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
rectTransform.sizeDelta = new Vector2(barWidth * barWidthMultiplier, graphPosition.y);
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
rectTransform.pivot = new Vector2(.5f, 0f);
gameObject.AddComponent<Button_UI>();
return gameObject;
}
public class BarChartVisualObject : IGraphVisualObject
{
private GameObject barGameObject;
private float barWidthMultiplier;
public BarChartVisualObject(GameObject barGameObject, float barWidthMultiplier)
{
this.barGameObject = barGameObject;
this.barWidthMultiplier = barWidthMultiplier;
}
public void SetGraphVisualObjectInfo(Vector2 graphPosition, float graphPositionWidth, string tooltipText)
{
RectTransform rectTransform = barGameObject.GetComponent<RectTransform>();
rectTransform.anchoredPosition = new Vector2(graphPosition.x, 0f);
rectTransform.sizeDelta = new Vector2(graphPositionWidth * barWidthMultiplier, graphPosition.y);
Button_UI barBtnUi = barGameObject.GetComponent<Button_UI>();
barBtnUi.MouseOverOnceFunc = () =>
{
ShowTooltip_Static(tooltipText, graphPosition);
};
barBtnUi.MouseOutOnceFunc = () =>
{
HideTooltip_Static();
};
}
public void CleanUp()
{
Destroy(barGameObject);
}
}
}
}