1

最終的に一連の長方形 (異なるサイズと位置、重ならない) になるユーザーの入力データをグラフにプロットしようとしています。私が読んだすべての例は、可変数のポイントを持つプロット ラインのみか、XAML の形状にハードコードされています。しかし、データに必要な四角形の数はわかりません。私の理想的なケースは、MVVM に従い、単純に XAML から変更可能な ObservableCollection にバインドすることですが、私が目にするほとんどの例では、ChartPlotter に直接アクセスする代わりにコード ビハインドを使用しているようです。これは、いくつかの単純な長方形を描画し、1 つを修正したものです。これは機能します。

VisualizationWindow.xaml

<Window x:Class="View.VisualizationWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
        Title="VisualizationWindow" MinHeight="300" MinWidth="500" Height="300" Width="500">
    <Grid>
        <d3:ChartPlotter Name="Chart">
            <d3:RectangleHighlight Name="Rect1" Bounds="-1,-1.5,.5,2" StrokeThickness="3" Fill="Blue" ToolTip="Blue!"></d3:RectangleHighlight>
            <d3:RectangleHighlight Name="Rect2" Bounds="1,1.5,2,.5" StrokeThickness="1" Fill="Red" ToolTip="Red!"></d3:RectangleHighlight>
        </d3:ChartPlotter>
    </Grid>
</Window>

VisualizationWindow.xaml.cs

public partial class VisualizationWindow : Window
{
    public VisualizationWindow(ListViewModel vm)
    {
        InitializeComponent();
        this.DataContext = vm;

        Chart.Viewport.Visible = new Rect(-1, -1, .5, .5);

        Rect1.Bounds = new Rect(0,0,.3,.3);
    }
}

動的データ表示に関するドキュメントはほとんど存在しません。D3がエレガントにできない場合、他のライブラリがこれをより簡単にできるかどうか知りたい.

4

1 に答える 1

4

ItemsSource 処理を既存のクラスに追加します。

ChartPlotter が IPlotterElements のみを受け入れるようにするためにあらゆることを行ったようです。ItemsSource プロパティがないため、Children は常に実際の要素を返します。RectangleHighlight の Bounds プロパティはバインド可能ではなく、クラスは封印されており、プロパティをオーバーライドするメソッドが禁止されています。

このクラスから派生して、ItemsSource 処理を「注入」できます。Children プロパティにデータバインディングを反映させる非ハックな方法がないため、実際の取引のようには機能しません。ただし、この方法で ItemsSource を割り当てることはできます。

いくつか必要なものがあります。実際の ItemsSource プロパティが必要です。設定されていることに反応する方法。また、dataObjects にバインドする場合は、DataTemplates を処理する方法です。私はまだ既存のソースコードを掘り下げていません。ただし、DataTemplateSelector を使用せずに DataTemplates を処理する方法を思い付きました。ただし、私の例を変更しない限り、DataTemplateSelector でも機能しません。

この回答は、バインディングのしくみを知っていることを前提としているため、あまり詳しく説明せずに最初のクラスにスキップします。

最初の Xaml:

<local:DynamicLineChartPlotter Name="Chart" ItemsSource="{Binding DataCollection}">
    <local:DynamicLineChartPlotter .Resources>
        <DataTemplate DataType{x:Type local:RectangleHighlightDataObject}>
            <d3:RectangleHighlight 
                Bounds="{Binding Bounds}" 
                StrokeThickness="{Binding StrokeThickness}"
                Fill="{Binding Fill}"
                ToolTip="{Binding ToolTip}"
            />
        </DataTemplate>
    </local:DynamicLineChartPlotter .Resources>
</local:DynamicLineChartPlotter >

クラス:

public class RectangleHighlightDataObject
{
    public Rect Bounds { get; set; }
    public double StrokeThickness { get; set; }
    public Brush Fill { get; set; }
    public String ToolTip { get; set; }
}


public class VisualizationWindow
{
    public VisualizationWindow() 
    {
         DataCollection.Add(new RectangleHighlightDataObject()
         {
             Bounds = new Rect(-1,-1.5,.5,2),
             StrokeThickness = 3,
             Fill = Brushes.Blue,
             ToolTip = "Blue!"
         });

         DataCollection.Add(new RectangleHighlightDataObject()
         {
             Bounds = new Rect(1,1.5,2,.5),
             StrokeThickness = 1,
             Fill = Brushes.Red,
             ToolTip = "Red!"
         });
    }
    public ObservableCollection<RectangleHighlightDataObject> DataCollection = 
             new ObservableCollection<RectangleHighlightDataObject>();
}

ItemsSource を実装するよりも、ChartPlotter の派生クラスを使用する必要があります。

動的 D3 型を実装する方法に関する議論から集められた例。実際のオブジェクト要素の代わりに DataTemplates を使用するように変更しました。これは、OP のデータバインディングをサポートするためです。

public class DynamicLineChartPlotter : Microsoft.Research.DynamicDataDisplay.ChartPlotter
{
    public static DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource",
                                        typeof(IEnumerable),
                                        typeof(DynamicLineChartPlotter),
                                        new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Bindable(true)]
    public IEnumerable ItemsSource
    {
        get
        {
            return (IEnumerable)GetValue(ItemsSourceProperty);
        }
        set
        {
            if (value == null)
                ClearValue(ItemsSourceProperty);
            else
                SetValue(ItemsSourceProperty, value);
        }
    }

    private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
        IEnumerable oldValue = (IEnumerable)e.OldValue;
        IEnumerable newValue = (IEnumerable)e.NewValue;

        if (e.OldValue != null)
        {
            control.ClearItems();
        }
        if (e.NewValue != null)
        {
            control.BindItems((IEnumerable)e.NewValue);
        }
    }

    private void ClearItems()
    {
        Children.Clear();
    }

    private void BindItems(IEnumerable items)
    {
        foreach (var item in items)
        {
            var template = GetTemplate(item);
            if (template == null) continue;

            FrameworkElement obj = template.LoadContent() as FrameworkElement;
            obj.DataContext = item;
            Children.Add((IPlotterElement)obj);
        }
    }

    private DataTemplate GetTemplate(object item)
    {
        foreach (var key in this.Resources.Keys)
        {
            if (((DataTemplateKey)key).DataType as Type == item.GetType())
            {
                return (DataTemplate)this.Resources[key];
            }
        }
        return null;
    }
}

ここでレンガの壁にぶつかります。

RectangleHighlight Bounds プロパティはデータ バインドできません。また、この問題を回避するためにそれらから派生することもできません。

データ テンプレートを取り出して静的な RectangleHighlight を生成することでハックすることもできますが、データ値が変更された場合は sol です。

それで、これを修正する方法は?

添付プロパティを使用できます。

添付プロパティの使用

添付プロパティを処理する静的クラスを作成します。OnPropertyChanged に応答して、実際のプロパティを手動で作成および設定します。さて、これは一方向にしか機能しません。プロパティを変更しても、添付プロパティは更新されません。ただし、データ オブジェクトのみを更新する必要があるため、これは問題にはなりません。

このクラスを追加

public class BindableRectangleBounds : DependencyObject
{
    public static DependencyProperty BoundsProperty = DependencyProperty.RegisterAttached("Bounds", typeof(Rect), typeof(BindableRectangleBounds), new PropertyMetadata(new Rect(), OnBoundsChanged));

    public static void SetBounds(DependencyObject dp, Rect value)
    {
        dp.SetValue(BoundsProperty, value);
    }
    public static void GetBounds(DependencyObject dp)
    {
        dp.GetValue(BoundsProperty);
    }

    public static void OnBoundsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
    {
        var property = dp.GetType().GetProperty("Bounds");
        if (property != null)
        {
            property.SetValue(dp, args.NewValue, null);
        }
    }
}

次に、XAML 行を次のように変更します。

                Bounds="{Binding Bounds}" 

                local:BindableRectangleBounds.Bounds="{Binding Bounds}" 

コレクションへの対応が変更されました。

ここまでは順調ですね。しかし、OP は、ItemsSource を割り当てたコレクションに変更を加えても、コントロールに何も変更がないことに気付きました。これは、ItemsSource が割り当てられている場合にのみ子を追加するためです。ここで、ItemsControl が ItemsSource を実装する方法を正確に知ることを禁じます。コレクションが変更されたときに ObservableCollection のイベントに登録することで、これを回避できることはわかっています。コレクションが変更されるたびにコントロールを再バインドする簡単な方法を示します。これは、ItemsSource が ObservableCollection によって割り当てられている場合にのみ機能します。しかし、これには、ObservableCollection を持たない ItemsControl と同じ問題があると思います。

ただし、まだ dataContext を再割り当てしていません。したがって、Children を変更すると正しく再バインドされるとは思わないでください。ただし、ItemsControl の ItemsSource ではなく、子を直接変更すると、バインディングが失われます。だから私は順調に進んでいるかもしれません。私たちが失う唯一の癖は、ItemsSource を設定した後に Children を参照すると、ItemsControl が ItemsSource を返すことです。私はまだこれを回避していません。私がすぐに考えられる唯一のことは、children プロパティを非表示にすることですが、それは良いことではなく、コントロールを ChartPlotter として参照すると機能しません。

以下を追加

    public DynamicLineChartPlotter()
    {
        _HandleCollectionChanged = new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
    }

    private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
        IEnumerable oldValue = (IEnumerable)e.OldValue;
        IEnumerable newValue = (IEnumerable)e.NewValue;
        INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
        INotifyCollectionChanged oldCollection = e.OldValue as INotifyCollectionChanged;

        if (e.OldValue != null)
        {
            control.ClearItems();
        }
        if (e.NewValue != null)
        {
            control.BindItems((IEnumerable)e.NewValue);
        }
        if (oldCollection != null)
        {
            oldCollection.CollectionChanged -= control._HandleCollectionChanged;
            control._Collection = null;
        }
        if (collection != null)
        {
            collection.CollectionChanged += control._HandleCollectionChanged;
            control._Collection = newValue;
        }
    }

    void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        ClearItems();
        BindItems(_Collection);
    }

    NotifyCollectionChangedEventHandler _HandleCollectionChanged;
    IEnumerable _Collection;
于 2013-03-13T21:51:29.890 に答える