【问题标题】:Align two cartesian charts with different axis label width?对齐两个具有不同轴标签宽度的笛卡尔图表?
【发布时间】:2026-02-01 03:10:01
【问题描述】:

我正在尝试使用实时图表构建财务图表,但我想要两个图表共享 x 轴或同步 x 轴。我目前正在尝试使用同步 x 轴的两个图表,但由于主图表的轴标签宽度而遇到麻烦,请参见下图。

我试图从辅助图表中减去轴标签宽度,但该属性始终为 0 :(

private void Grid_LayoutUpdated(object sender, EventArgs e)
{
   var axisWidth = MainChart.AxisY[1].ActualWidth; //always 0 :(
   IndicatorChart.Width -= axisWidth;
}

有人知道如何用实时图表解决这个问题吗?提前致谢!

这是 XAML:

<UserControl x:Class="TradeChart.GearedExample"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TradeChart"
         xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
        xmlns:geared="clr-namespace:LiveCharts.Geared;assembly=LiveCharts.Geared"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <Grid LayoutUpdated="Grid_LayoutUpdated" Loaded="Grid_Loaded">
        <Grid.RowDefinitions>
            <RowDefinition Height="4*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <lvc:CartesianChart Zoom="X" Pan="X" DisableAnimations="True" AnimationsSpeed="0:0:0.15" Series="{Binding SeriesCollection}" MouseMove="UIElement_OnMouseMove"  LayoutUpdated="MainChart_LayoutUpdated" x:Name="MainChart" DataTooltip="{x:Null}">
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Labels="{Binding Labels}" IsMerged="True">
                    <lvc:Axis.Sections>
                        <lvc:AxisSection Value="{Binding XPointer}"
                                     SectionWidth="1"
                                     SectionOffset="-0.5"
                                     Fill="#59FF5722"
                                     Stroke="#ff5722"
                                     StrokeThickness=".5"
                                     DataLabelForeground="White"
                                     DataLabel="True"/>
                    </lvc:Axis.Sections>
                </lvc:Axis>
            </lvc:CartesianChart.AxisX>
            <lvc:CartesianChart.AxisY>
                <lvc:Axis LabelFormatter="{Binding Formatter}">
                    <lvc:Axis.Sections>
                        <lvc:AxisSection Value="{Binding YPointer}" 
                                     DataLabel="True"
                                     StrokeThickness="1"
                                     Stroke="#ff5722"
                                     DisableAnimations="True"
                                     DataLabelForeground="White"
                                     Panel.ZIndex="1"/>
                    </lvc:Axis.Sections>
                </lvc:Axis>
                <lvc:Axis Position="RightTop" x:Name="VolumeAxis" LayoutUpdated="Axis_LayoutUpdated" Width="400">
                </lvc:Axis>
            </lvc:CartesianChart.AxisY>
        </lvc:CartesianChart>
        <lvc:CartesianChart Grid.Row="1" Zoom="X" Pan="X" DisableAnimations="True" AnimationsSpeed="0:0:0.15" Series="{Binding IndicatorSeriesCollection}" MouseMove="UIElement_OnMouseMove" x:Name="IndicatorChart" DataTooltip="{x:Null}">
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Labels="{Binding Labels}" IsMerged="True">
                    <lvc:Axis.Sections>
                        <lvc:AxisSection Value="{Binding XPointer}"
                                     SectionWidth="1"
                                     SectionOffset="-0.5"
                                     Fill="#59FF5722"
                                     Stroke="#ff5722"
                                     StrokeThickness=".5"
                                     DataLabelForeground="White"
                                     DataLabel="True"/>
                    </lvc:Axis.Sections>
                </lvc:Axis>
            </lvc:CartesianChart.AxisX>
            <lvc:CartesianChart.AxisY>
                <lvc:Axis LabelFormatter="{Binding Formatter}" MinValue="0" MaxValue="100">
                    <lvc:Axis.Sections>
                        <lvc:AxisSection Value="{Binding YPointer}" 
                                     DataLabel="True"
                                     StrokeThickness="1"
                                     Stroke="#ff5722"
                                     DisableAnimations="True"
                                     DataLabelForeground="White"
                                     Panel.ZIndex="1"/>
                    </lvc:Axis.Sections>
                </lvc:Axis>
            </lvc:CartesianChart.AxisY>
        </lvc:CartesianChart>
    </Grid>
</UserControl>

系列集合:

    private void CreateCandleSeries(IReadOnlyList<Candle> candles)
    {
        var values = new List<OhlcPoint>();
        foreach (var candle in candles)
        {
            values.Add(new OhlcPoint
            {
                Open = (double)candle.Open,
                Close = (double)candle.Close,
                High = (double)candle.High,
                Low = (double)candle.Low
            });
        }
        var gearValues = new GearedValues<OhlcPoint>();
        gearValues.AddRange(values);
        gearValues.WithQuality(Quality.Low);
        var candleSeries = new GCandleSeries()
        {
            Values = gearValues,
            ScalesYAt = 0,

        };

        var increaseBrush = candleSeries.IncreaseBrush;
        var decreaseBrush = candleSeries.DecreaseBrush;
        candleSeries.IncreaseBrush = decreaseBrush;
        candleSeries.DecreaseBrush = increaseBrush;

        SeriesCollection.Add(candleSeries);
    }

IndicatorSeriesCollection:

    private void CreateSingleLineOscilatorSeries(IReadOnlyList<Candle> candles, int period, Func<int, int?, int?, IReadOnlyList<AnalyzableTick<decimal?>>> indicator, SeriesCollection collectionToAddTo, int? startIndex = null, int? endIndex = null)
    {
        var indicatorCandleValues = indicator.Invoke(period, startIndex, endIndex);
        var indicatorValues = new List<double>();
        foreach (var value in indicatorCandleValues)
        {
            if (value.Tick.HasValue)
            {
                indicatorValues.Add((double)value.Tick.Value);
            }
            else
            {
                indicatorValues.Add(-1);
            }
        }
        var indicatorMapper = new CartesianMapper<double>().X((value, index) => index).Y((value, index) => value);
        var gearValues = new GearedValues<double>();
        gearValues.AddRange(indicatorValues);
        gearValues.WithQuality(Quality.High);
        var smaSeries = new GLineSeries(indicatorMapper)
        {
            Fill = Brushes.Transparent,
            Values = gearValues,
            LineSmoothness = 0,

            ScalesYAt = 0
        };
        collectionToAddTo.Add(smaSeries);
    }

【问题讨论】:

  • 你能贴出SeriesCollection和IndicatorSeriesCollection的代码吗?
  • 当然,现在发布
  • 您的 GCandleSeries 是具有高/低和开/关值的双系列,因此您在顶部图表中有两个 Y 轴,而 GLineSeries 在底部图表中是单个系列
  • 是的,我知道,这就是我想要的,我的问题是是否可以将顶部图表主窗口与底部图表主窗口对齐。例如,通过从底部图表宽度中减去顶部图表右侧 Y 轴宽度来计算底部图表宽度。抱歉,如果我的问题不清楚,我对 wcf 没有那么丰富的经验。

标签: c# wpf livecharts


【解决方案1】:

好的,我放弃了尝试对齐两个图表,但是我设法通过将底部图表放入主图表并使用 MinValue 和 MaxValue 以使其粘在底部来解决问题。

这是我设置 MinValue 和 MaxValue 的方法:

candlechart MinValue=candles.Min() - 0.2*candles.Min()
candlechart MaxValue=candles.Max()

volume MinValue=volumes.Min()
volume MaxValues=volumes.Max() * 2

振荡器处于 0 到 100 之间,因此我将最大值和最小值设置为:

oscilator MaxValue=600
oscilator MinValue=20

结果:

【讨论】:

    【解决方案2】:

    我遇到了类似的问题,只能使用 Axis 的 IsMerged 属性解决它:

    ...
    <lvc:Axis IsMerged="True"/>
    

    【讨论】:

      【解决方案3】:

      Width 属性未与Axis 关联,默认情况下将使用Axis 中的最大项目来决定Width 值,从而产生不同大小的图表。

      由于该项目看起来没有得到积极开发,我为 .net core 3.1 创建了一个项目的分支,并在那里提交了更改。

      https://github.com/matsydoodles/Live-Charts/commit/12349fd4e329a4a6a42ff2fa4d3362d546161aa4

      【讨论】:

        最近更新 更多