D3 and MVVM?

Dec 19, 2009 at 1:22 AM

So I created a little test app to take the D3 stuff for a test drive, seems very nice!  I read and downloaded the sample app from this thread:

http://dynamicdatadisplay.codeplex.com/Thread/View.aspx?ThreadId=63633

And while it was very helpful I still have some questions.  The goal here is for me to load a list of LineGraphs onto a chart plotter.  I would like to have the list bound to my view model.   In the example, the binding is from an extended ChartPlotter class, and uses a button to load up the graphs- and works fine.

If however, I try to load the graphs up front they do not display.  For example if I call the method for loading up the LineGraphs in the ChartViewModel constructor, even though LineGraphs has valid content and is accessed by the WPF binding the data is not displayed. 

I think the solution is quite simple, but I cant seem to get it to work.

Also, since I dont really need the dependency property aspect, is there any way to accomplish my goal using more mvvm'ish syntax?  Something exactly like what Andre posted in the referenced thread?  I am only on Day 1 of using your chart package, so maybe there is an easy way to do this that I havent figured out yet?

 

<c:ChartPlotter x:Name="plotter">
    <ItemsControl 
        ItemsSource="{Binding LineGraphs}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <c:LineGraph />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</c:ChartPlotter>

The main point is that I dont want to do the work in the code behind (by AddLineGraph()) and I dont need the command...

 

<c:ChartPlotter x:Name="plotter
Dec 21, 2009 at 4:22 PM

Hi Nicros,

I'm using D3 and MVVM quite successful. I did like Ravi pointed out extending the ChartPotter class.

Here is the code:

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.NewValue == null)
                control.ClearItemsSource();
            else
                control.SetItemsSource(newValue);
        }

        private List<IPlotterElement> _elementsInItemsSource = new List<IPlotterElement>();
        private IEnumerable _itemsSource = null;

        public void ClearItemsSource() {
            if (_itemsSource != null && _itemsSource is INotifyCollectionChanged)
                (_itemsSource as INotifyCollectionChanged).CollectionChanged -= ItemsSourceCollectionChanged;

            //remove the elements that were added from the items source (create a copy since RemoveItemSourceChild changes the enumeration)
            List<IPlotterElement> removes = new List<IPlotterElement>(_elementsInItemsSource);
            foreach (IPlotterElement elem in removes)
                RemoveItemSourceChild(elem);

            _elementsInItemsSource.Clear();
        }

        private void SetItemsSource(IEnumerable list) {
            ClearItemsSource();

            foreach (object o in list) {
                if (o is IPlotterElement)
                    AddItemSourceChild(o as IPlotterElement);
            }

            _itemsSource = list;
            if (_itemsSource is INotifyCollectionChanged)
                (_itemsSource as INotifyCollectionChanged).CollectionChanged += ItemsSourceCollectionChanged;
        }

        private void RemoveItemSourceChild(IPlotterElement elem) {
            this.Children.Remove(elem);
            //it's not in the plotter anymore
            _elementsInItemsSource.Remove(elem);
        }

        private void AddItemSourceChild(IPlotterElement elem) {
            Children.Add(elem);
            //it's in the plotter not
            _elementsInItemsSource.Add(elem);
        }

        private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
            if (e.OldItems != null) {
                foreach (object o in e.OldItems) {
                    if (o is IPlotterElement)
                        RemoveItemSourceChild(o as IPlotterElement);
                }
            }

            if (e.NewItems != null) {
                foreach (object o in e.NewItems) {
                    if (o is IPlotterElement)
                        AddItemSourceChild(o as IPlotterElement);
                }
            }
        }
    }

 

Then you can use in your view:

 <customD3:DynamicLineChartPlotter x:Name="Chart" ItemsSource="{Binding Charts}">
                    <d3:VerticalAxisTitle FontSize="12">Value</d3:VerticalAxisTitle>
                    <d3:HorizontalAxisTitle FontSize="12">Date Time</d3:HorizontalAxisTitle>
 </customD3:DynamicLineChartPlotter>

Then just bind an "ObservableCollection<IPlotterElement> Charts"  from your ViewModel and everything will work like a charm.

Hope it helps,

André Carlucci

 

Dec 21, 2009 at 5:12 PM

Hi Andre,

Thanks for the info!  Very helpful indeed.  I had implemented the stuff Ravi posted, so was using his LineGraphViewModel.  I like this because it allows you to set markers, colors, etc for each of your graphs.  What are you using for your IPlotterElement?

Did you just implement this interface on your viewmodel or...?

Thanks again!

Dec 21, 2009 at 5:52 PM

In my case I don't have any fancy stuff, so it's just the LineGraph itself. I have a factory that creates it, adds a randow color and the FrequencyFilter for performance reasons.

The LineGraphViewModel is definitely the way to go if you have more than that.

Cheers,

André

Dec 21, 2009 at 6:26 PM

Yeah I like to have the view model.  I guess the problem Im really having is that I cant get the data to display properly on initialization.

In my ChartViewModel, I have an ObservableCollection<LineGraphViewModel> list, where LineGraphViewModel is from Ravi's stuff.  Im trying to load the data into this list, and have it displayed when the ChartViewModel class is initialized- so I have a LoadData method being called from the contstructor:

 

public ChartViewModel()
{
  LoadData();
}

private void LoadData()
{
	// Create LineGraphViewModels and add them to ObservableCollection list
}

When I do this though, I dont see anything on my chart- even though my LineGraphs property is being called and returning a valid list of LineGraphViewModels.  If I call LoadData from another property, then it works.  I dont understand why this is... any ideas?

 

public ChartViewModel()
{
  LoadData();
}
private void LoadData()
{
// Create LineGraphViewModels and add them to ObservableCollection listWhen 

 

Dec 22, 2009 at 1:02 PM

Hmm... that's really weird.

Did you try to call OnPropertyChanged("List");  (where List is your bound property) after changing it?

André

Dec 22, 2009 at 4:38 PM

Yep.  What seems to be happening is that if I populate the model list during initialization, even though the list property 'get' is called (and the list is valid) it doesnt display anything.  If I call the property 'get' after initialization, like on some other property change or event everything is just fine.

Im no wpf expert, so Im sure Im doing something silly, but Im not sure what.  Ill noodle with it a bit more to see what is going on.

Dec 22, 2009 at 6:49 PM
Edited Feb 18, 2010 at 7:18 PM

You could change the responsible for showing the chart.

1 - Make the default view of the ChartViewModel show an empty chart (that means, no LoadData in constructor)

2 - Make the LoadData() a public method, maybe passing parameters of which chart to show or whatever

3 - In your caller, put something like:

ChartViewModel vm = new ChartViewModel() { ... }
ShowView(vm);
vm.LoadData();

You can change the order (show vm first or LoadData first) to see what happens.

 

Dec 30, 2009 at 11:51 AM

hi, i don't know if i'm going in the wrong direction...

I create this event to know when the itemssource had changed.

 

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

            if (e.NewValue == null)
                control.ClearItemsSource();
            else
                control.SetItemsSource(newValue);

        //changes start here

            control.OnItemsSourceChanged(e);
        }

        #region ItemsSourceChanged

        protected virtual void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            if (null != ItemsSourceChanged)
            {
                ItemsSourceChanged(this, e);
            }
        }

        public event DependencyPropertyChangedEventHandler ItemsSourceChanged;

        #endregion