From 52b9b0a03aa6bc13353ce919d6fc834eadc7f9aa Mon Sep 17 00:00:00 2001 From: monitor1394 Date: Thu, 26 Feb 2026 22:31:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0`Serie`=E7=9A=84`ignoreZeroOc?= =?UTF-8?q?cupy`=E5=8F=AF=E8=AE=BE=E7=BD=AE0=E6=95=B0=E6=8D=AE=E7=9A=84Bar?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=8D=A0=E4=BD=8D=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documentation~/zh/changelog.md | 1 + Editor/Series/BarEditor.cs | 1 + Runtime/Serie/Bar/BarHandler.cs | 111 ++++++++++++++++++++++++++++++-- Runtime/Serie/Serie.cs | 17 ++++- 4 files changed, 124 insertions(+), 6 deletions(-) diff --git a/Documentation~/zh/changelog.md b/Documentation~/zh/changelog.md index 03c42c88..bea260a7 100644 --- a/Documentation~/zh/changelog.md +++ b/Documentation~/zh/changelog.md @@ -80,6 +80,7 @@ slug: /changelog ## master +* (2026.02.26) 增加`Serie`的`ignoreZeroOccupy`可设置0数据的Bar是否占位 (#286) * (2026.02.26) 修复`SaveAsImage`被其他组件遮挡时无法正常保存的问题 (#337) * (2026.02.26) 增加`Axis`的`mainAxis`参数设置主轴可控制柱图的朝向 (#331) * (2026.02.03) 修复`UITable`的`viewport`在不同的锚点下可能会绘制异常的问题 diff --git a/Editor/Series/BarEditor.cs b/Editor/Series/BarEditor.cs index d1ec01b1..24ee16b7 100644 --- a/Editor/Series/BarEditor.cs +++ b/Editor/Series/BarEditor.cs @@ -22,6 +22,7 @@ namespace XCharts.Editor PropertyField("m_BarWidth"); PropertyField("m_BarGap"); PropertyField("m_BarMaxWidth"); + PropertyField("m_IgnoreZeroOccupy"); PropertyField("m_RealtimeSort"); if(serie.useSortData) { diff --git a/Runtime/Serie/Bar/BarHandler.cs b/Runtime/Serie/Bar/BarHandler.cs index 1b9f079c..84472dde 100644 --- a/Runtime/Serie/Bar/BarHandler.cs +++ b/Runtime/Serie/Bar/BarHandler.cs @@ -262,8 +262,14 @@ namespace XCharts.Runtime var pX = 0f; var pY = 0f; + var runtimeBarWidth = barWidth; + var runtimeGap = gap; + if (serie.ignoreZeroOccupy) + { + UpdateActiveBarLayout(serie, dataZoom, i, categoryWidth, barGap, runtimeBarWidth, ref runtimeGap); + } UpdateXYPosition(m_SerieGrid, isY, axis, relativedAxis, i, categoryWidth, relativedCategoryWidth, - barWidth, isStack, value, backgroundGap, ref pX, ref pY); + runtimeBarWidth, isStack, value, backgroundGap, ref pX, ref pY); if (serie.useSortData) { serieData.context.UpdateExchangePosition(ref pX, ref pY, exchangeDuration); @@ -280,7 +286,7 @@ namespace XCharts.Runtime } float currHig = AnimationStyleHelper.CheckDataAnimation(chart, serie, i, barHig); Vector3 plb, plt, prt, prb, top; - UpdateRectPosition(m_SerieGrid, isY, relativedValue, pX, pY, gap, borderWidth, barWidth, currHig, + UpdateRectPosition(m_SerieGrid, isY, relativedValue, pX, pY, runtimeGap, borderWidth, runtimeBarWidth, currHig, out plb, out plt, out prt, out prb, out top); serieData.context.stackHeight = barHig; serieData.context.position = top; @@ -306,11 +312,11 @@ namespace XCharts.Runtime { case BarType.Normal: case BarType.Capsule: - DrawNormalBar(vh, serie, serieData, itemStyle, backgroundColor, gap, barWidth, + DrawNormalBar(vh, serie, serieData, itemStyle, backgroundColor, runtimeGap, runtimeBarWidth, pX, pY, plb, plt, prt, prb, isY, m_SerieGrid, axis, areaColor, areaToColor, relativedValue); break; case BarType.Zebra: - DrawZebraBar(vh, serie, serieData, itemStyle, backgroundColor, gap, barWidth, + DrawZebraBar(vh, serie, serieData, itemStyle, backgroundColor, runtimeGap, runtimeBarWidth, pX, pY, plb, plt, prt, prb, isY, m_SerieGrid, axis, areaColor, areaToColor); break; } @@ -331,6 +337,103 @@ namespace XCharts.Runtime } } + List m_SlotOrder = new List(); + Dictionary m_ActiveSlot = new Dictionary(); + private void UpdateActiveBarLayout(Bar currentSerie, DataZoom dataZoom, int dataIndex, + float categoryWidth, float barGap, float barWidth, ref float gap) + { + m_SlotOrder.Clear(); + m_ActiveSlot.Clear(); + for (int n = 0; n < chart.series.Count; n++) + { + var serie = chart.series[n] as Bar; + if (serie == null || !serie.show || serie.placeHolder) + continue; + if (!IsSerieInGrid(serie, m_SerieGrid.index)) + continue; + + var slotKey = GetBarSlotKey(serie); + if (!m_ActiveSlot.ContainsKey(slotKey)) + { + m_ActiveSlot[slotKey] = false; + m_SlotOrder.Add(slotKey); + } + + if (IsSerieDataActiveForLayout(serie, dataZoom, dataIndex)) + { + m_ActiveSlot[slotKey] = true; + } + } + + var currentSlotKey = GetBarSlotKey(currentSerie); + if (!m_ActiveSlot.ContainsKey(currentSlotKey) || !m_ActiveSlot[currentSlotKey]) + return; + + var activeCount = 0; + var activeSlotIndex = -1; + for (int n = 0; n < m_SlotOrder.Count; n++) + { + var slotKey = m_SlotOrder[n]; + if (!m_ActiveSlot[slotKey]) + continue; + + if (slotKey == currentSlotKey) + activeSlotIndex = activeCount; + + activeCount++; + } + + if (activeCount <= 0 || activeSlotIndex < 0) + return; + + var actualGap = ChartHelper.GetActualValue(barGap, barWidth); + var totalBarWidth = barGap == -1 + ? barWidth + : activeCount * barWidth + (activeCount - 1) * actualGap; + var offset = (categoryWidth - totalBarWidth) * 0.5f; + gap = barGap == -1 + ? offset + : offset + activeSlotIndex * (barWidth + actualGap); + } + + private string GetBarSlotKey(Bar serie) + { + return string.IsNullOrEmpty(serie.stack) ? "s_" + serie.index : "k_" + serie.stack; + } + + private bool IsSerieDataActiveForLayout(Bar serie, DataZoom dataZoom, int dataIndex) + { + var dataList = serie.GetDataList(dataZoom, true); + if (dataList == null || dataIndex < 0 || dataIndex >= dataList.Count) + return false; + + var serieData = dataList[dataIndex]; + if (serieData == null || !serieData.show || serie.IsIgnoreValue(serieData)) + return false; + + if (!serie.ignoreZeroOccupy) + return true; + + return !MathUtil.Approximately(serieData.GetData(1), 0); + } + + private bool IsSerieInGrid(Bar serie, int gridIndex) + { + XAxis xAxis; + if (chart.TryGetChartComponent(out xAxis, serie.xAxisIndex)) + { + if (xAxis.gridIndex != gridIndex) + return false; + } + YAxis yAxis; + if (chart.TryGetChartComponent(out yAxis, serie.yAxisIndex)) + { + if (yAxis.gridIndex != gridIndex) + return false; + } + return true; + } + private void UpdateXYPosition(GridCoord grid, bool isY, Axis axis, Axis relativedAxis, int i, float categoryWidth, float relativedCategoryWidth, float barWidth, bool isStack, double value, float backgroundGap, ref float pX, ref float pY) diff --git a/Runtime/Serie/Serie.cs b/Runtime/Serie/Serie.cs index 2cbf28b9..4fd9cb2b 100644 --- a/Runtime/Serie/Serie.cs +++ b/Runtime/Serie/Serie.cs @@ -264,6 +264,7 @@ namespace XCharts.Runtime [SerializeField] private float m_BarGap = 0.1f; [SerializeField] private float m_BarZebraWidth = 4f; [SerializeField] private float m_BarZebraGap = 2f; + [SerializeField] [Since("v3.15.0")]private bool m_IgnoreZeroOccupy = false; [SerializeField] private float m_Min; [SerializeField] private float m_Max; @@ -632,7 +633,8 @@ namespace XCharts.Runtime set { if (PropertyUtil.SetStruct(ref m_BarGap, value)) SetVerticesDirty(); } } /// - /// 斑马线的粗细。 + /// The width of zebra bar. It is the width of each zebra stripe. When the value is 0, there is no zebra stripe. + /// ||斑马线的粗细。 /// public float barZebraWidth { @@ -640,13 +642,24 @@ namespace XCharts.Runtime set { if (PropertyUtil.SetStruct(ref m_BarZebraWidth, value < 0 ? 0 : value)) SetVerticesDirty(); } } /// - /// 斑马线的间距。 + /// The gap of zebra bar. It is the distance between two zebra stripes. When the value is 0, there is no gap between stripes. + /// ||斑马线的间距。 /// public float barZebraGap { get { return m_BarZebraGap; } set { if (PropertyUtil.SetStruct(ref m_BarZebraGap, value < 0 ? 0 : value)) SetVerticesDirty(); } } + /// + /// Whether to ignore the zero value bar occupy. When enabled, the bar with zero value will not occupy space, + /// and the gap between bars will be automatically adjusted according to the actual displayed bars. Generally used in bar chart. + /// ||柱图是否忽略值为0的柱子占位。开启后,值为0的柱子将不会占用空间,柱子之间的间距会根据实际显示的柱子自动调整。一般用在柱状图中。 + /// + public bool ignoreZeroOccupy + { + get { return m_IgnoreZeroOccupy; } + set { if (PropertyUtil.SetStruct(ref m_IgnoreZeroOccupy, value)) SetVerticesDirty(); } + } /// /// Whether offset when mouse click pie chart item.