using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; using XUGL; namespace XCharts.Runtime { [UnityEngine.Scripting.Preserve] internal sealed class TooltipHandler : MainComponentHandler { private List m_IndicatorLabels = new List(); private GameObject m_LabelRoot; private ISerieContainer m_PointerContainer; public override void InitComponent() { InitTooltip(component); } public override void Update() { UpdateTooltip(component); UpdateTooltipIndicatorLabelText(component); if (component.view != null) component.view.Update(); } public override void DrawTop(VertexHelper vh) { DrawTooltipIndicator(vh, component); } private void InitTooltip(Tooltip tooltip) { tooltip.painter = chart.m_PainterTop; tooltip.refreshComponent = delegate() { var objName = ChartCached.GetComponentObjectName(tooltip); tooltip.gameObject = ChartHelper.AddObject(objName, chart.transform, chart.chartMinAnchor, chart.chartMaxAnchor, chart.chartPivot, chart.chartSizeDelta); var tooltipObject = tooltip.gameObject; tooltipObject.transform.localPosition = Vector3.zero; tooltipObject.hideFlags = chart.chartHideFlags; var parent = tooltipObject.transform; ChartHelper.HideAllObject(tooltipObject.transform); tooltip.view = TooltipView.CreateView(tooltip, chart.theme, parent); tooltip.SetActive(false); m_LabelRoot = ChartHelper.AddObject("label", tooltip.gameObject.transform, chart.chartMinAnchor, chart.chartMaxAnchor, chart.chartPivot, chart.chartSizeDelta); ChartHelper.HideAllObject(m_LabelRoot); m_IndicatorLabels.Clear(); for (int i = 0; i < 2; i++) { var labelName = "label_" + i; var item = ChartHelper.AddTooltipIndicatorLabel(component, labelName, m_LabelRoot.transform, chart.theme, TextAnchor.MiddleCenter); item.SetActive(false); m_IndicatorLabels.Add(item); } }; tooltip.refreshComponent(); } private ChartLabel GetIndicatorLabel(int index) { if (m_LabelRoot == null) return null; if (index < m_IndicatorLabels.Count) return m_IndicatorLabels[index]; else { var labelName = "label_" + index; var item = ChartHelper.AddTooltipIndicatorLabel(component, labelName, m_LabelRoot.transform, chart.theme, TextAnchor.MiddleCenter); m_IndicatorLabels.Add(item); return item; } } private void UpdateTooltip(Tooltip tooltip) { if (tooltip.trigger == Tooltip.Trigger.None) return; if (!chart.isPointerInChart || !tooltip.show) { if (tooltip.IsActive()) { tooltip.ClearValue(); tooltip.SetActive(false); } return; } var showTooltip = false; for (int i = chart.series.Count - 1; i >= 0; i--) { var serie = chart.series[i]; if (!(serie is INeedSerieContainer)) { if (SetSerieTooltip(tooltip, serie)) { showTooltip = true; chart.RefreshTopPainter(); return; } } } var containerSeries = ListPool.Get(); m_PointerContainer = GetPointerContainerAndSeries(tooltip, containerSeries); if (containerSeries.Count > 0) { if (SetSerieTooltip(tooltip, containerSeries)) showTooltip = true; } ListPool.Release(containerSeries); if (!showTooltip) { if (tooltip.type == Tooltip.Type.Corss && m_PointerContainer != null && m_PointerContainer.IsPointerEnter()) { tooltip.SetActive(true); tooltip.SetContentActive(false); } else { tooltip.SetActive(false); } } else { chart.RefreshTopPainter(); } } private void UpdateTooltipIndicatorLabelText(Tooltip tooltip) { if (!tooltip.show) return; if (tooltip.type == Tooltip.Type.None) return; if (m_PointerContainer != null) { if (tooltip.type == Tooltip.Type.Corss) { var labelCount = 0; if (m_PointerContainer is GridCoord) { var grid = m_PointerContainer as GridCoord; ChartHelper.HideAllObject(m_LabelRoot); foreach (var component in chart.components) { if (component is XAxis || component is YAxis) { var axis = component as Axis; if (axis.gridIndex == grid.index) { var label = GetIndicatorLabel(labelCount++); SetTooltipIndicatorLabel(tooltip, axis, label); } } } } else if (m_PointerContainer is PolarCoord) { var polar = m_PointerContainer as PolarCoord; ChartHelper.HideAllObject(m_LabelRoot); foreach (var component in chart.components) { if (component is AngleAxis || component is RadiusAxis) { var axis = component as Axis; if (axis.polarIndex == polar.index) { var label = GetIndicatorLabel(labelCount++); SetTooltipIndicatorLabel(tooltip, axis, label); } } } } } } } private void SetTooltipIndicatorLabel(Tooltip tooltip, Axis axis, ChartLabel label) { if (label == null) return; if (double.IsPositiveInfinity(axis.context.pointerValue)) return; label.SetActive(true); label.SetTextActive(true); label.SetPosition(axis.context.pointerLabelPosition); if (axis.IsCategory()) label.SetText(axis.GetData((int) axis.context.pointerValue)); else label.SetText(axis.context.pointerValue.ToString("f2")); var textColor = axis.axisLabel.textStyle.GetColor(chart.theme.axis.textColor); if (ChartHelper.IsClearColor(tooltip.indicatorLabelStyle.background.color)) label.color = textColor; else label.color = tooltip.indicatorLabelStyle.background.color; label.SetTextColor(Color.white); } private ISerieContainer GetPointerContainerAndSeries(Tooltip tooltip, List list) { list.Clear(); for (int i = chart.components.Count - 1; i >= 0; i--) { var component = chart.components[i]; if (component is ISerieContainer) { var container = component as ISerieContainer; if (container.IsPointerEnter()) { foreach (var serie in chart.series) { if (serie is INeedSerieContainer && (serie as INeedSerieContainer).containterInstanceId == component.instanceId) { var isTriggerAxis = tooltip.IsTriggerAxis(); if (container is GridCoord) { var xAxis = chart.GetChartComponent(serie.xAxisIndex); var yAxis = chart.GetChartComponent(serie.yAxisIndex); serie.context.pointerEnter = true; UpdateAxisPointerDataIndex(serie, xAxis, yAxis, container as GridCoord, isTriggerAxis); } else if (container is PolarCoord) { var m_AngleAxis = ComponentHelper.GetAngleAxis(chart.components, container.index); tooltip.context.angle = (float) m_AngleAxis.context.pointerValue; } list.Add(serie); if (!isTriggerAxis) chart.RefreshTopPainter(); } } return container; } } } return null; } private void UpdateAxisPointerDataIndex(Serie serie, XAxis xAxis, YAxis yAxis, GridCoord grid, bool isTriggerAxis) { serie.context.pointerAxisDataIndexs.Clear(); if (yAxis.IsCategory()) { serie.context.pointerAxisDataIndexs.Add((int) yAxis.context.pointerValue); yAxis.context.axisTooltipValue = yAxis.context.pointerValue; } else if (yAxis.IsTime()) { if (isTriggerAxis) GetSerieDataIndexByAxis(serie, yAxis, grid); else GetSerieDataIndexByItem(serie, yAxis, grid); } else if (xAxis.IsCategory()) { serie.context.pointerAxisDataIndexs.Add((int) xAxis.context.pointerValue); xAxis.context.axisTooltipValue = xAxis.context.pointerValue; } else { if (isTriggerAxis) GetSerieDataIndexByAxis(serie, xAxis, grid); else GetSerieDataIndexByItem(serie, xAxis, grid); } } private void GetSerieDataIndexByAxis(Serie serie, Axis axis, GridCoord grid, int dimension = 0) { var currValue = 0d; var lastValue = 0d; var nextValue = 0d; var axisValue = axis.context.pointerValue; var isTimeAxis = axis.IsTime(); var dataCount = serie.dataCount; var themeSymbolSize = chart.theme.serie.scatterSymbolSize; var data = serie.data; if (!isTimeAxis) { serie.context.sortedData.Clear(); for (int i = 0; i < dataCount; i++) { var serieData = serie.data[i]; serieData.index = i; serie.context.sortedData.Add(serieData); } serie.context.sortedData.Sort(delegate(SerieData a, SerieData b) { return a.GetData(dimension).CompareTo(b.GetData(dimension)); }); data = serie.context.sortedData; } serie.context.pointerAxisDataIndexs.Clear(); for (int i = 0; i < dataCount; i++) { var serieData = data[i]; currValue = serieData.GetData(dimension); if (i == 0) { nextValue = data[i + 1].GetData(dimension); if (axisValue <= currValue + (nextValue - currValue) / 2) { serie.context.pointerAxisDataIndexs.Add(serieData.index); break; } } else if (i == dataCount - 1) { if (axisValue > lastValue + (currValue - lastValue) / 2) { serie.context.pointerAxisDataIndexs.Add(serieData.index); break; } } else { nextValue = data[i + 1].GetData(dimension); if (axisValue > (currValue - (currValue - lastValue) / 2) && axisValue <= currValue + (nextValue - currValue) / 2) { serie.context.pointerAxisDataIndexs.Add(serieData.index); break; } } lastValue = currValue; } if (serie.context.pointerAxisDataIndexs.Count > 0) { var index = serie.context.pointerAxisDataIndexs[0]; serie.context.pointerItemDataIndex = index; axis.context.axisTooltipValue = serie.GetSerieData(index).GetData(dimension); } else { serie.context.pointerItemDataIndex = -1; axis.context.axisTooltipValue = 0; } } private void GetSerieDataIndexByItem(Serie serie, Axis axis, GridCoord grid, int dimension = 0) { if (serie.context.pointerItemDataIndex >= 0) { axis.context.axisTooltipValue = serie.GetSerieData(serie.context.pointerItemDataIndex).GetData(dimension); } else if (component.type == Tooltip.Type.Corss) { axis.context.axisTooltipValue = axis.context.pointerValue; } else { axis.context.axisTooltipValue = 0; } } private bool SetSerieTooltip(Tooltip tooltip, Serie serie) { if (tooltip.trigger == Tooltip.Trigger.None) return false; if (serie.context.pointerItemDataIndex < 0) return false; tooltip.context.data.param.Clear(); tooltip.context.data.title = serie.serieName; tooltip.context.pointer = chart.pointerPos; serie.handler.UpdateTooltipSerieParams(serie.context.pointerItemDataIndex, false, null, tooltip.marker, tooltip.itemFormatter, tooltip.numericFormatter, ref tooltip.context.data.param, ref tooltip.context.data.title); TooltipHelper.ResetTooltipParamsByItemFormatter(tooltip, chart); tooltip.SetActive(true); tooltip.view.Refresh(); TooltipHelper.LimitInRect(tooltip, chart.chartRect); return true; } private bool SetSerieTooltip(Tooltip tooltip, List series) { if (tooltip.trigger == Tooltip.Trigger.None) return false; if (series.Count <= 0) return false; string category = null; var showCategory = false; var isTriggerByAxis = false; var dataIndex = -1; tooltip.context.data.param.Clear(); tooltip.context.pointer = chart.pointerPos; if (m_PointerContainer is GridCoord) { if (tooltip.trigger == Tooltip.Trigger.Axis) { isTriggerByAxis = true; GetAxisCategory(m_PointerContainer.index, ref dataIndex, ref category); if (series.Count <= 1) { showCategory = true; tooltip.context.data.title = series[0].serieName; } else tooltip.context.data.title = category; } } for (int i = 0; i < series.Count; i++) { var serie = series[i]; serie.context.isTriggerByAxis = isTriggerByAxis; if (isTriggerByAxis && dataIndex >= 0) serie.context.pointerItemDataIndex = dataIndex; serie.handler.UpdateTooltipSerieParams(dataIndex, showCategory, category, tooltip.marker, tooltip.itemFormatter, tooltip.numericFormatter, ref tooltip.context.data.param, ref tooltip.context.data.title); } TooltipHelper.ResetTooltipParamsByItemFormatter(tooltip, chart); if (tooltip.context.data.param.Count > 0) { tooltip.SetActive(true); if (tooltip.view != null) tooltip.view.Refresh(); TooltipHelper.LimitInRect(tooltip, chart.chartRect); return true; } return false; } private bool GetAxisCategory(int gridIndex, ref int dataIndex, ref string category) { foreach (var component in chart.components) { if (component is Axis) { var axis = component as Axis; if (axis.gridIndex == gridIndex && axis.IsCategory()) { dataIndex = (int) axis.context.pointerValue; category = axis.GetData(dataIndex); return true; } } } return false; } private void DrawTooltipIndicator(VertexHelper vh, Tooltip tooltip) { if (!tooltip.show) return; if (tooltip.type == Tooltip.Type.None) return; if (m_PointerContainer is GridCoord) { var grid = m_PointerContainer as GridCoord; if (!grid.context.isPointerEnter) return; if (IsYCategoryOfGrid(grid.index)) DrawYAxisIndicator(vh, tooltip, grid); else DrawXAxisIndicator(vh, tooltip, grid); } else if (m_PointerContainer is PolarCoord) { DrawPolarIndicator(vh, tooltip, m_PointerContainer as PolarCoord); } } private bool IsYCategoryOfGrid(int gridIndex) { var yAxes = chart.GetChartComponents(); foreach (var component in yAxes) { var yAxis = component as YAxis; if (yAxis.gridIndex == gridIndex && yAxis.IsCategory()) return true; } return false; } private void DrawXAxisIndicator(VertexHelper vh, Tooltip tooltip, GridCoord grid) { var xAxes = chart.GetChartComponents(); var lineType = tooltip.lineStyle.GetType(chart.theme.tooltip.lineType); var lineWidth = tooltip.lineStyle.GetWidth(chart.theme.tooltip.lineWidth); foreach (var component in xAxes) { var xAxis = component as XAxis; if (xAxis.gridIndex == grid.index) { var dataZoom = chart.GetDataZoomOfAxis(xAxis); int dataCount = chart.series.Count > 0 ? chart.series[0].GetDataList(dataZoom).Count : 0; float splitWidth = AxisHelper.GetDataWidth(xAxis, grid.context.width, dataCount, dataZoom); switch (tooltip.type) { case Tooltip.Type.Corss: case Tooltip.Type.Line: float pX = grid.context.x; pX += xAxis.IsCategory() ? (float) (xAxis.context.pointerValue * splitWidth + (xAxis.boundaryGap ? splitWidth / 2 : 0)) : xAxis.GetDistance(xAxis.context.axisTooltipValue, grid.context.width); Vector2 sp = new Vector2(pX, grid.context.y); Vector2 ep = new Vector2(pX, grid.context.y + grid.context.height); var lineColor = TooltipHelper.GetLineColor(tooltip, chart.theme); // if (xAxis.IsCategory() && tooltip.type == Tooltip.Type.Corss) // { // float tooltipSplitWid = splitWidth < 1 ? 1 : splitWidth; // pX = (float)(grid.context.x + splitWidth * xAxis.context.pointerValue - // (xAxis.boundaryGap ? 0 : splitWidth / 2)); // float pY = grid.context.y + grid.context.height; // Vector3 p1 = new Vector3(pX, grid.context.y); // Vector3 p2 = new Vector3(pX, pY); // Vector3 p3 = new Vector3(pX + tooltipSplitWid, pY); // Vector3 p4 = new Vector3(pX + tooltipSplitWid, grid.context.y); // UGL.DrawQuadrilateral(vh, p1, p2, p3, p4, chart.theme.tooltip.areaColor); // } // else { ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); } if (tooltip.type == Tooltip.Type.Corss) { sp = new Vector2(grid.context.x, chart.pointerPos.y); ep = new Vector2(grid.context.x + grid.context.width, chart.pointerPos.y); ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); } break; case Tooltip.Type.Shadow: if (xAxis.IsCategory()) { float tooltipSplitWid = splitWidth < 1 ? 1 : splitWidth; pX = (float) (grid.context.x + splitWidth * xAxis.context.pointerValue - (xAxis.boundaryGap ? 0 : splitWidth / 2)); float pY = grid.context.y + grid.context.height; Vector3 p1 = new Vector3(pX, grid.context.y); Vector3 p2 = new Vector3(pX, pY); Vector3 p3 = new Vector3(pX + tooltipSplitWid, pY); Vector3 p4 = new Vector3(pX + tooltipSplitWid, grid.context.y); UGL.DrawQuadrilateral(vh, p1, p2, p3, p4, chart.theme.tooltip.areaColor); } break; } } } } private void DrawYAxisIndicator(VertexHelper vh, Tooltip tooltip, GridCoord grid) { var yAxes = chart.GetChartComponents(); var lineType = tooltip.lineStyle.GetType(chart.theme.tooltip.lineType); var lineWidth = tooltip.lineStyle.GetWidth(chart.theme.tooltip.lineWidth); foreach (var component in yAxes) { var yAxis = component as YAxis; if (yAxis.gridIndex == grid.index) { var dataZoom = chart.GetDataZoomOfAxis(yAxis); int dataCount = chart.series.Count > 0 ? chart.series[0].GetDataList(dataZoom).Count : 0; float splitWidth = AxisHelper.GetDataWidth(yAxis, grid.context.height, dataCount, dataZoom); switch (tooltip.type) { case Tooltip.Type.Corss: case Tooltip.Type.Line: float pY = (float) (grid.context.y + yAxis.context.pointerValue * splitWidth + (yAxis.boundaryGap ? splitWidth / 2 : 0)); Vector2 sp = new Vector2(grid.context.x, pY); Vector2 ep = new Vector2(grid.context.x + grid.context.width, pY); var lineColor = TooltipHelper.GetLineColor(tooltip, chart.theme); // if (yAxis.IsCategory() && tooltip.type == Tooltip.Type.Corss) // { // float tooltipSplitWid = splitWidth < 1 ? 1 : splitWidth; // float pX = grid.context.x + grid.context.width; // pY = (float)(grid.context.y + splitWidth * yAxis.context.pointerValue - // (yAxis.boundaryGap ? 0 : splitWidth / 2)); // Vector3 p1 = new Vector3(grid.context.x, pY); // Vector3 p2 = new Vector3(grid.context.x, pY + tooltipSplitWid); // Vector3 p3 = new Vector3(pX, pY + tooltipSplitWid); // Vector3 p4 = new Vector3(pX, pY); // UGL.DrawQuadrilateral(vh, p1, p2, p3, p4, chart.theme.tooltip.areaColor); // } // else { ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); } if (tooltip.type == Tooltip.Type.Corss) { sp = new Vector2(chart.pointerPos.x, grid.context.y); ep = new Vector2(chart.pointerPos.x, grid.context.y + grid.context.height); ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); } break; case Tooltip.Type.Shadow: if (yAxis.IsCategory()) { float tooltipSplitWid = splitWidth < 1 ? 1 : splitWidth; float pX = grid.context.x + grid.context.width; pY = (float) (grid.context.y + splitWidth * yAxis.context.pointerValue - (yAxis.boundaryGap ? 0 : splitWidth / 2)); Vector3 p1 = new Vector3(grid.context.x, pY); Vector3 p2 = new Vector3(grid.context.x, pY + tooltipSplitWid); Vector3 p3 = new Vector3(pX, pY + tooltipSplitWid); Vector3 p4 = new Vector3(pX, pY); UGL.DrawQuadrilateral(vh, p1, p2, p3, p4, chart.theme.tooltip.areaColor); } break; } } } } private void DrawPolarIndicator(VertexHelper vh, Tooltip tooltip, PolarCoord m_Polar) { if (tooltip.context.angle < 0) return; var theme = chart.theme; var m_AngleAxis = ComponentHelper.GetAngleAxis(chart.components, m_Polar.index); var lineColor = TooltipHelper.GetLineColor(tooltip, theme); var lineType = tooltip.lineStyle.GetType(theme.tooltip.lineType); var lineWidth = tooltip.lineStyle.GetWidth(theme.tooltip.lineWidth); var cenPos = m_Polar.context.center; var radius = m_Polar.context.radius; var sp = m_Polar.context.center; var tooltipAngle = m_AngleAxis.GetValueAngle(tooltip.context.angle); var ep = ChartHelper.GetPos(sp, radius, tooltipAngle, true); switch (tooltip.type) { case Tooltip.Type.Corss: ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); var dist = Vector2.Distance(chart.pointerPos, cenPos); if (dist > radius) dist = radius; var outsideRaidus = dist + tooltip.lineStyle.GetWidth(theme.tooltip.lineWidth) * 2; UGL.DrawDoughnut(vh, cenPos, dist, outsideRaidus, lineColor, Color.clear); break; case Tooltip.Type.Line: ChartDrawer.DrawLineStyle(vh, lineType, lineWidth, sp, ep, lineColor); break; case Tooltip.Type.Shadow: UGL.DrawSector(vh, cenPos, radius, lineColor, tooltipAngle - 2, tooltipAngle + 2, chart.settings.cicleSmoothness); break; } } } }