绘制椭圆 Line/PolyLine 双

Drawing Ellipse Line/PolyLine with a double

我一直在使用 示例来绘制条形图。条形部分已完成,它代表交易中的 volume/quantity。它是 20 - 30 范围内的相关价格。我现在想要的是绘制点来表示与交易量相关的价格并连接这些点。我在链接示例的编辑部分中进行了两项更改(1)从 ItemsControlDataTemplate 中删除了 TextBlock 并添加了 Ellipse 并且(2)进行了编辑canvas 添加price/volume 轴标签。这是现在的样子:

如何将 Ellipse 添加到正确的位置并将它们与 Line/PolyLine 连接起来?

编辑

这是我现在在 ItemsControl 中的内容:

<ItemsControl ScrollViewer.CanContentScroll="True"
            Height="135"
            ItemsSource="{Binding RectCollection}"
            Margin="50 0 50 0">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Canvas Width="20">
                <Canvas.LayoutTransform>
                    <ScaleTransform ScaleY="-1"/>
                </Canvas.LayoutTransform>
                <Rectangle Width="18" 
                Margin="0 0 2 0" 
                VerticalAlignment="Bottom"
                Opacity=".5" Fill="LightGray">
                    <Rectangle.Height>
                        <MultiBinding Converter="{StaticResource VConverter}">
                            <Binding Path="ActualHeight"
                                    RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
                            <Binding Path="DataContext.HighestPoint"
                                    RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
                            <Binding Path="Volume"/>
                        </MultiBinding>
                    </Rectangle.Height>
                </Rectangle>

                <Line Stroke="DarkGreen" StrokeThickness="1"
                    X1="10" X2="30"
                    Y2="{Binding PreviousPrice, Converter={StaticResource PConverter}}"
                    Y1="{Binding CurrentPrice, Converter={StaticResource PConverter}}">
                    <Line.Style>
                        <Style TargetType="Line">
                            <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=PreviousPrice}" Value="{x:Null}">
                                    <Setter Property="Visibility" Value="Collapsed"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Line.Style>
                </Line>
                <Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
                    Canvas.Top="{Binding CurrentPrice, Converter={StaticResource PConverter}}"/>
            </Canvas>
        </DataTemplate>
    </ItemsControl.ItemTemplate>

    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer
            VerticalScrollBarVisibility="Hidden"
            Background="{TemplateBinding Panel.Background}">
                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

哦!我忘了添加那些 ValueConverters,这里是那些:

public class VolumeConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var height = (double)values[0];
        var higest = (double)values[1];
        var value = (double)values[2];
        return value * height / higest;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class PriceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is double)) return null;
        var price = (double)value;
        var remainingHeight = 90;
        var priceRange = 30 - 20.0;
        return 45 + ((price - 20) * remainingHeight / priceRange);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

这是它的样子:

根据 @Clemens 的建议,我必须有另一个 double?,以防在 ObservableCollection 上使用 Insert(0, ... ) 而不是 Add(...)首先添加最后一项并删除 AternationCount/Index 内容。

以下示例使用垂直翻转 Canvas 反转 y 轴顺序,使其向上。所以 PConverter 应该 return 正 y 值。

除了 Rectangle 和 Ellipse 元素之外,它还通过值绑定中的 RelativeSource={RelativeSource PreviousData} 从前一个数据值绘制一个 Line 元素到当前数据值。它还在 AlternationIndex 上使用 DataTrigger 来隐藏第一行。

<ItemsControl ... AlternationCount="2147483647">
...

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Canvas Width="20">
            <Canvas.LayoutTransform>
                <ScaleTransform ScaleY="-1"/>
            </Canvas.LayoutTransform>

            <Rectangle Fill="LightGray" Margin="1" Width="18"
                       Height="{Binding Value1, Converter={StaticResource PConverter}}"/>

            <Line Stroke="DarkGreen" StrokeThickness="3"
                  X1="-10" X2="10"
                  Y1="{Binding Price,
                       Converter={StaticResource PConverter},
                       RelativeSource={RelativeSource PreviousData}}"
                  Y2="{Binding Price,
                       Converter={StaticResource PConverter}}">
                <Line.Style>
                    <Style TargetType="Line">
                        <Style.Triggers>
                            <DataTrigger
                                Binding="{Binding Path=(ItemsControl.AlternationIndex),
                                    RelativeSource={RelativeSource
                                        AncestorType=ContentPresenter}}"
                                Value="0">
                                <Setter Property="Visibility" Value="Collapsed"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Line.Style>
            </Line>

            <Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
                     Canvas.Top="{Binding Price, Converter={StaticResource PConverter}}"/>
        </Canvas>
    </DataTemplate>
</ItemsControl.ItemTemplate>

由于现在也为不存在的值调用值转换器(对于第一项的 PreviousData),您必须确保它检查传递的值是否实际上是双精度值:

public object Convert(
    object value, Type targetType, object parameter, CultureInfo culture)
{
    if (!(value is double)) return 0d;
    ...
}