using System.Collections.Generic; using System; using UnityEngine; using UnityEngine.UI; namespace XCharts { /// /// VisualMap component. /// 视觉映射组件。用于进行『视觉编码』,也就是将数据映射到视觉元素(视觉通道)。 /// [System.Serializable] public class VisualMap { /// /// 类型。分为连续型和分段型。 /// public enum Type { /// /// 连续型。 /// Continuous, /// /// 分段型。 /// Piecewise } /// /// 选择模式 /// public enum SelectedMode { /// /// 多选。 /// Multiple, /// /// 单选。 /// Single } [SerializeField] private bool m_Enable = false; [SerializeField] private bool m_Show = true; [SerializeField] private Type m_Type = Type.Continuous; [SerializeField] private SelectedMode m_SelectedMode = SelectedMode.Multiple; [SerializeField] private float m_Min = 0; [SerializeField] private float m_Max = 100f; [SerializeField] private float[] m_Range = new float[2] { 0, 100f }; [SerializeField] private string[] m_Text = new string[2] { "", "" }; [SerializeField] private float[] m_TextGap = new float[2] { 10f, 10f }; [SerializeField] private int m_SplitNumber = 5; [SerializeField] private bool m_Calculable = false; [SerializeField] private bool m_Realtime = true; [SerializeField] private float m_ItemWidth = 20f; [SerializeField] private float m_ItemHeight = 140f; [SerializeField] private float m_BorderWidth = 0; [SerializeField] private int m_Dimension = 0; [SerializeField] private bool m_HoverLink = true; [SerializeField] private Orient m_Orient = Orient.Horizonal; [SerializeField] private Location m_Location = Location.defaultLeft; [SerializeField] private List m_InRange = new List(); [SerializeField] private List m_OutOfRange = new List(); /// /// 是否开启组件功能。 /// public bool enable { get { return m_Enable; } set { m_Enable = value; } } /// /// 是否显示组件。如果设置为 false,不会显示,但是数据映射的功能还存在。 /// public bool show { get { return m_Show; } set { m_Show = value; } } /// /// 组件类型。 /// public Type type { get { return m_Type; } set { m_Type = value; } } /// /// 选择模式。 /// public SelectedMode selectedMode { get { return m_SelectedMode; } set { m_SelectedMode = value; } } /// /// 允许的最小值。'min' 必须用户指定。[visualMap.min, visualMap.max] 形成了视觉映射的『定义域』。 /// public float min { get { return m_Min; } set { m_Min = value; } } /// /// 允许的最大值。'max' 必须用户指定。[visualMap.min, visualMax.max] 形成了视觉映射的『定义域』。 /// public float max { get { return m_Max; } set { m_Max = value < min ? min + 1 : value; } } /// /// 指定手柄对应数值的位置。range 应在 min max 范围内。 /// public float[] range { get { return m_Range; } } /// /// 两端的文本,如 ['High', 'Low']。 /// public string[] text { get { return m_Text; } } /// /// 两端文字主体之间的距离,单位为px。 /// public float[] textGap { get { return m_TextGap; } } /// /// 对于连续型数据,自动平均切分成几段,默认为0时自动匹配inRange颜色列表大小。 /// /// public int splitNumber { get { return m_SplitNumber; } set { m_SplitNumber = value; } } /// /// 是否显示拖拽用的手柄(手柄能拖拽调整选中范围)。 /// public bool calculable { get { return m_Calculable; } set { m_Calculable = value; } } /// /// 拖拽时,是否实时更新。 /// public bool realtime { get { return m_Realtime; } set { m_Realtime = value; } } /// /// 图形的宽度,即颜色条的宽度。 /// public float itemWidth { get { return m_ItemWidth; } set { m_ItemWidth = value; } } /// /// 图形的高度,即颜色条的高度。 /// public float itemHeight { get { return m_ItemHeight; } set { m_ItemHeight = value; } } /// /// 边框线宽,单位px。 /// public float borderWidth { get { return m_BorderWidth; } set { m_BorderWidth = value; } } /// /// 指定用数据的『哪个维度』,映射到视觉元素上。『数据』即 series.data。从1开始,默认为0取 data 中最后一个维度。 /// public int dimension { get { return m_Dimension; } set { m_Dimension = value; } } /// /// 打开 hoverLink 功能时,鼠标悬浮到 visualMap 组件上时,鼠标位置对应的数值 在 图表中对应的图形元素,会高亮。 /// 反之,鼠标悬浮到图表中的图形元素上时,在 visualMap 组件的相应位置会有三角提示其所对应的数值。 /// /// public bool hoverLink { get { return m_HoverLink; } set { m_HoverLink = value; } } /// /// Specify whether the layout of component is horizontal or vertical. /// 布局方式是横还是竖。 /// public Orient orient { get { return m_Orient; } set { m_Orient = value; } } /// /// The location of component. /// 组件显示的位置。 /// public Location location { get { return m_Location; } set { m_Location = value; } } /// /// 定义 在选中范围中 的视觉颜色。 /// public List inRange { get { return m_InRange; } set { if (value != null) m_InRange = value; } } /// /// 定义 在选中范围外 的视觉颜色。 /// public List outOfRange { get { return m_OutOfRange; } set { if (value != null) m_OutOfRange = value; } } /// /// 鼠标悬停选中的index /// /// public int rtSelectedIndex { get; set; } public float rtSelectedValue { get; set; } /// /// the current pointer position. /// 当前鼠标位置。 /// public Vector2 pointerPos { get; set; } public bool isVertical { get { return orient == Orient.Vertical; } } public float rangeMin { get { if (m_Range[0] < min || m_Range[0] > max) return min; else return m_Range[0]; } set { if (value >= min && value <= m_Range[1]) m_Range[0] = value; } } public float rangeMax { get { if (m_Range[1] >= m_Range[0] && m_Range[1] < max) return m_Range[1]; else return max; } set { if (value >= m_Range[0] && value <= max) m_Range[1] = value; } } public int rtSplitNumber { get { if (splitNumber > 0 && splitNumber <= m_InRange.Count) return splitNumber; else return inRange.Count; } } public float rangeMinHeight { get { return (rangeMin - min) / (max - min) * itemHeight; } } public float rangeMaxHeight { get { return (rangeMax - min) / (max - min) * itemHeight; } } private List m_RtInRange = new List(); public List rtInRange { get { if (splitNumber == 0 || m_InRange.Count >= splitNumber || m_InRange.Count < 1) return m_InRange; else { if (m_RtInRange.Count != rtSplitNumber) { m_RtInRange.Clear(); var total = max - min; var diff1 = total / (m_InRange.Count - 1); var diff2 = total / splitNumber; var inCount = 0; var inValue = min; var rtValue = min; for (int i = 0; i < splitNumber; i++) { rtValue += diff2; if (rtValue > inValue + diff1) { inValue += diff1; inCount++; } if (i == splitNumber - 1) { m_RtInRange.Add(m_InRange[m_InRange.Count - 1]); } else { var rate = (rtValue - inValue) / diff1; m_RtInRange.Add(Color.Lerp(m_InRange[inCount], m_InRange[inCount + 1], rate)); } } } return m_RtInRange; } } } public Color GetColor(float value) { int splitNumber = rtInRange.Count; if (splitNumber <= 0) return Color.clear; value = Mathf.Clamp(value, min, max); var diff = (max - min) / (splitNumber - 1); var index = GetIndex(value); var nowMin = min + index * diff; var rate = (value - nowMin) / diff; if (index == splitNumber - 1) return rtInRange[index]; else return Color.Lerp(rtInRange[index], rtInRange[index + 1], rate); } public int GetIndex(float value) { int splitNumber = rtInRange.Count; if (splitNumber <= 0) return -1; value = Mathf.Clamp(value, min, max); var diff = (max - min) / (splitNumber - 1); var index = -1; for (int i = 0; i < splitNumber; i++) { if (value <= min + (i + 1) * diff) { index = i; break; } } return index; } public bool IsInSelectedValue(float value) { if (rtSelectedIndex < 0) return true; else { return rtSelectedIndex == GetIndex(value); } } public float GetValue(Vector3 pos, float chartWidth, float chartHeight) { var centerPos = location.GetPosition(chartWidth, chartHeight); var pos1 = centerPos + (isVertical ? Vector3.down : Vector3.left) * itemHeight / 2; var pos2 = centerPos + (isVertical ? Vector3.up : Vector3.right) * itemHeight / 2; if (isVertical) { if (pos.y < pos1.y) return min; else if (pos.y > pos2.y) return max; else return min + (pos.y - pos1.y) / (pos2.y - pos1.y) * (max - min); } else { if (pos.x < pos1.x) return min; else if (pos.x > pos2.x) return max; else return min + (pos.x - pos1.x) / (pos2.x - pos1.x) * (max - min); } } public bool IsInRect(Vector3 local, float chartWidth, float chartHeight, float triangleLen = 20) { var centerPos = location.GetPosition(chartWidth, chartHeight); var diff = calculable ? triangleLen : 0; if (local.x >= centerPos.x - itemWidth / 2 - diff && local.x <= centerPos.x + itemWidth / 2 + diff && local.y >= centerPos.y - itemHeight / 2 - diff && local.y <= centerPos.y + itemHeight / 2 + diff) { return true; } else { return false; } } public bool IsInRangeRect(Vector3 local, float chartWidth, float chartHeight) { var centerPos = location.GetPosition(chartWidth, chartHeight); if (orient == Orient.Vertical) { var pos1 = centerPos + Vector3.down * itemHeight / 2; return local.x >= centerPos.x - itemWidth / 2 && local.x <= centerPos.x + itemWidth / 2 && local.y >= pos1.y + rangeMinHeight && local.y <= pos1.y + rangeMaxHeight; } else { var pos1 = centerPos + Vector3.left * itemHeight / 2; return local.x >= pos1.x + rangeMinHeight && local.x <= pos1.x + rangeMaxHeight && local.y >= centerPos.y - itemWidth / 2 && local.y <= centerPos.y + itemWidth / 2; } } public bool IsInRangeMinRect(Vector3 local, float chartWidth, float chartHeight, float triangleLen) { var centerPos = location.GetPosition(chartWidth, chartHeight); if (orient == Orient.Vertical) { var radius = triangleLen / 2; var pos1 = centerPos + Vector3.down * itemHeight / 2; var cpos = new Vector3(pos1.x + itemWidth / 2 + radius, pos1.y + rangeMinHeight - radius); return local.x >= cpos.x - radius && local.x <= cpos.x + radius && local.y >= cpos.y - radius && local.y <= cpos.y + radius; } else { var radius = triangleLen / 2; var pos1 = centerPos + Vector3.left * itemHeight / 2; var cpos = new Vector3(pos1.x + rangeMinHeight, pos1.y + itemWidth / 2 + radius); return local.x >= cpos.x - radius && local.x <= cpos.x + radius && local.y >= cpos.y - radius && local.y <= cpos.y + radius; } } public bool IsInRangeMaxRect(Vector3 local, float chartWidth, float chartHeight, float triangleLen) { var centerPos = location.GetPosition(chartWidth, chartHeight); if (orient == Orient.Vertical) { var radius = triangleLen / 2; var pos1 = centerPos + Vector3.down * itemHeight / 2; var cpos = new Vector3(pos1.x + itemWidth / 2 + radius, pos1.y + rangeMaxHeight + radius); return local.x >= cpos.x - radius && local.x <= cpos.x + radius && local.y >= cpos.y - radius && local.y <= cpos.y + radius; } else { var radius = triangleLen / 2; var pos1 = centerPos + Vector3.left * itemHeight / 2; var cpos = new Vector3(pos1.x + rangeMaxHeight + radius, pos1.y + itemWidth / 2 + radius); return local.x >= cpos.x - radius && local.x <= cpos.x + radius && local.y >= cpos.y - radius && local.y <= cpos.y + radius; } } } }