Binding example for multiple SurfaceGridSeries3D?

A forum dedicated to WPF version of LightningChart Ultimate.

Moderator: Arction_LasseP

Greg9504
Posts: 38
Joined: Fri Dec 06, 2013 4:51 pm

Binding example for multiple SurfaceGridSeries3D?

Post by Greg9504 » Fri May 27, 2016 4:26 am

Do you have a binding sample that shows how to bind to the chart.View3D.SurfaceGridSeries3D collection? Not hard coding a single SurfaceGridSeries3D surface in the xaml.

Basically I have a ViewModel with an ObservableCollection of my SurfaceModel class (Surface have Data, Range, etc) and I'm unsure how to bind to the SurfaceGridSeries3D collection. I can't see how to do it without the ViewModel knowing about LightningChart. Perhaps a Behavior, but I haven't figured out how yet.

Thanks.

User avatar
mrcntp
Posts: 2
Joined: Fri May 27, 2016 7:39 am

Re: Binding example for multiple SurfaceGridSeries3D?

Post by mrcntp » Fri May 27, 2016 7:55 am

I assume you are using version 7. Have you taken a look at BindableExamples solution's Example3DSurfaceTracking example? It uses binding to bind to SurfaceGridSeries3D class' properties.

Just double-checking: chart object is not required in the view-model but you need to reference (i.e. "using Arction.Wpf.BindableCharting.xxx") LightningChartUltimate assembly so that you can get access to required classes for data handling etc.

Greg9504
Posts: 38
Joined: Fri Dec 06, 2013 4:51 pm

Re: Binding example for multiple SurfaceGridSeries3D?

Post by Greg9504 » Fri May 27, 2016 1:04 pm

Yes I am using Version 7. The problem is that the sample (ExampleSimpleSurfaceGrid3D) only shows how to bind to exactly One SurfaceGridSeries3D, plus the ExampleSimpleSurfaceGrid3DViewModel knows about "View" objects like SurfacePointMatrix, SurfacePointCollection and SurfacePoint. I need to bind to a collection of my generic "SurfaceViewModel" without infecting the SurfaceViewModel with LightningChart objects. If my SurfaceViewModel needs to know about LightningChart objects (which is the View) then it's not MVVM.

User avatar
mrcntp
Posts: 2
Joined: Fri May 27, 2016 7:39 am

Re: Binding example for multiple SurfaceGridSeries3D?

Post by mrcntp » Fri May 27, 2016 2:17 pm

LightningChartUltimate does not use generic collections for data etc. so some "infection" is required, I'm afraid. Better than having a direct reference to the chart object(s) in view-model. Not sure if there's any chart controls out there that would work with no references to the manufacturer's assemblies in view-model. I think it's more of a compromise between performance/usability and design patterns. Haven't played that much with View3D but the principle is the same on every view (ViewXY, ViewPolar, ...). :freak:

Greg9504
Posts: 38
Joined: Fri Dec 06, 2013 4:51 pm

Re: Binding example for multiple SurfaceGridSeries3D?

Post by Greg9504 » Sat May 28, 2016 6:42 am

I used a behavior that targets LightningChart and put all the LightningChart specific code in there. Not perfect but it does isolate the ViewModels from the View...
I'll try to post a full example in a week or two. There is some code in the behavior that doesn't need to be there (Chart setup).

in the xaml

Code: Select all

              xmlns:lcusb="http://schemas.arction.com/semibindablecharting/ultimate/"
              xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
              xmlns:mybehaviors="clr-namespace:_3DSurfacePlotting.Behaviors"

...

                   <lcusb:LightningChartUltimate Name="m_chart" ActiveView="View3D">
                    
                    <i:Interaction.Behaviors>
                        <mybehaviors:SufacesBehavior SurfaceModels="{Binding Surfaces}" SelectedSurface="{Binding SelectedSurface}"/>
                    </i:Interaction.Behaviors>

                    <lcusb:LightningChartUltimate.Title>
                        <lcusb:ChartTitle Text="{Binding Title}"/>
                    </lcusb:LightningChartUltimate.Title>
                </lcusb:LightningChartUltimate>
Behavior

Code: Select all

using Catel.Windows.Interactivity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Arction.Wpf.SemibindableCharting;
using System.Windows;

namespace _3DSurfacePlotting.Behaviors
{
    using Arction.Wpf.SemibindableCharting.Series3D;
    using Components;
    using Models;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Windows.Media;
    public class SufacesBehavior: BehaviorBase<LightningChartUltimate>
    {
        private float m_yscale = 25;
        private LightningChartUltimate m_chart;
        protected override void OnAttached()
        {
            base.OnAttached();
            m_chart = AssociatedObject;
        }
        public ObservableCollection<SurfaceModel> SurfaceModels
        {
            get { return (ObservableCollection<SurfaceModel>)GetValue(SurfaceModelsProperty); }
            set { SetValue(SurfaceModelsProperty, value); }
        }
        
        public static readonly DependencyProperty SurfaceModelsProperty =
            DependencyProperty.Register("SurfaceModels", typeof(ObservableCollection<SurfaceModel>), typeof(SufacesBehavior), new PropertyMetadata(null,OnSurfaceModelsCollectionChanged));
        
        public static void OnSurfaceModelsCollectionChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs args)
        {
            //this gets fired only when the Set method of the SurfaceModels property is called, when the reference changes.
            var sb = sender as SufacesBehavior;
            ObservableCollection<SurfaceModel> smc = args.OldValue as ObservableCollection<SurfaceModel>;
            if (smc != null)
            {
                smc.CollectionChanged -= sb.SurfaceCollectionChangedMethod;
            }
            smc = args.NewValue as ObservableCollection<SurfaceModel>;
            if (smc != null)
            {
                smc.CollectionChanged += sb.SurfaceCollectionChangedMethod;//called when items are added/removed etc from the collection
                sb.AddSeriesForSurfaceModels(smc, true);
            }
            
            return ;
        }

        public SurfaceModel SelectedSurface
        {
            get { return (SurfaceModel)GetValue(SelectedSurfaceProperty); }
            set { SetValue(SelectedSurfaceProperty, value); }
        }
       
        public static readonly DependencyProperty SelectedSurfaceProperty =
            DependencyProperty.Register("SelectedSurface", typeof(SurfaceModel), typeof(SufacesBehavior), new PropertyMetadata(null, OnSelectedSurfaceChanged));
        public static void OnSelectedSurfaceChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs args)
        {
            var sb = sender as SufacesBehavior;
            var surface = args.OldValue as SurfaceModel;
            if (surface != null)
            {
                surface.PropertyChanged -= sb.Surface_PropertyChanged;
            }
            surface = args.NewValue as SurfaceModel;
            if (surface != null)
            {
                surface.PropertyChanged += sb.Surface_PropertyChanged;
            }
        }

        private void Surface_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            var surface = sender as SurfaceModel;
            if (surface == null) return;
            //update the LightningChart series associated with this surface.
            //find the lc series
            foreach (SurfaceGridSeries3D ss in m_chart.View3D.SurfaceGridSeries3D)
            {
                var iss = ss as IntensitySurfaceGridSeries3D;
                if (iss == null) continue;
                if (iss.Tag == surface)
                {
                    m_chart.BeginUpdate();
                    UpdateSurface(surface, iss);
                    m_chart.EndUpdate();
                    break;
                }
            }
        }

        double RoundTo10(double i, int digits)
        {
            return (Math.Round(i / 10.0, digits)) * 10;
        }
        private void AddStepsToPalette(ValueRangePalette palette, Color? stepColor, double ymin, double YRange)
        {
            palette.Steps.Clear();

            double yRange = YRange - ymin;
            if (yRange < 0.01)
            {
                yRange = 1.0;
            }
            palette.Steps.Clear();
            int digits = 0;
            if (yRange < 10)
            {
                digits = 2;
            }
            else if (yRange < 60)
            {
                digits = 1;
            }
            palette.MinValue = RoundTo10(ymin, digits);
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.Magenta : stepColor.Value, RoundTo10(ymin, digits)));
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.Blue : stepColor.Value, RoundTo10(ymin + 20 * yRange / 100.0, digits)));
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.Cyan : stepColor.Value, RoundTo10(ymin + 40 * yRange / 100.0, digits)));
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.DarkGreen : stepColor.Value, RoundTo10(ymin + 60 * yRange / 100.0, digits)));
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.Yellow : stepColor.Value, RoundTo10(ymin + 80 * yRange / 100.0, digits)));
            palette.Steps.Add(new PaletteStep(palette, stepColor == null ? Colors.Red : stepColor.Value, RoundTo10(ymin + 100 * yRange / 100.0, digits)));

            palette.Steps.Add(new PaletteStep(palette, Color.FromArgb(0, 0, 0, 0), ymin - 10)); //Aux step
            palette.Steps.Add(new PaletteStep(palette, Color.FromArgb(0, 0, 0, 0), -1E9)); //Aux step

            palette.Type = PaletteType.Gradient;
        }

        private void UpdateSurface(SurfaceModel surface, IntensitySurfaceGridSeries3D gridSeries)
        {
            //update palettes 
            if (surface.SelectedColour != null)
            {
                AddStepsToPalette(gridSeries.ColorPalette, null, surface.SelectedColour.min, surface.SelectedColour.max);
            }
            else
            {
                AddStepsToPalette(gridSeries.ColorPalette, null, surface.Depth.min, surface.Depth.max);
            }
            if (surface.SelectedContour != null)
            {
                AddStepsToPalette(gridSeries.ContoursPalette, Colors.White, surface.SelectedContour.min, surface.SelectedContour.max);
            }
            else
            {
                AddStepsToPalette(gridSeries.ContoursPalette, Colors.White, surface.Depth.min, surface.Depth.max);
            }

            double[][] color = null;
            if (surface.SelectedColour == null)
            {
                color = surface.Depth.data;//color by depth by default
            }
            else
            {
                color = surface.SelectedColour.data;
            }
            double[][] contour = null;
            if (surface.SelectedContour == null)
            {
                contour = surface.Depth.data;//contour by depth by default
            }
            else
            {
                contour = surface.SelectedContour.data;
            }

            //Lightning Chart coords, our X = their Z, our Y = their X, our Z = their Y on 3D plots     
            for (int i = 0; i < surface.Geometry.nXY.Y; i++)
            {
                for (int j = 0; j < surface.Geometry.nXY.X; j++)
                {
                    gridSeries.Data[i, j].Y = surface.Depth.data[i][j];
                    gridSeries.ColorPalette.GetColorByValue(color[i][j], out gridSeries.Data[i, j].Color);
                    gridSeries.IntensityData[i, j].ColorValue = color[i][j];
                    gridSeries.IntensityData[i, j].ContourValue = contour[i][j];
                }
            }
            
            //Notify new values are ready
            gridSeries.InvalidateData();
            // gridSeries.CreateContourLines();
        }
        /// <summary>
        /// This method assumes all surfaces will have the same X,Y (X,Z in LightningChart speak) dimensions, 
        /// with a little bit of rework you could change the View3D.Dimensions and Axis range based on largest
        /// surface...
        /// </summary>
        /// <param name="surface"></param>
        private void SetupChartForSurface(SurfaceModel surface)
        {
            if (m_chart.View3D.SurfaceGridSeries3D.Count == 0)
            {
                //setup over all 3DView, ideally we loop through each surface and set these values to the larger surface
                //for now we assume all surfaces have the same bounds
                //scale X and Y (X and Z on lightning chart) for correct aspect ratio
                double lX = ((surface.Geometry.nXY.Y - 1) * surface.Geometry.dXY.Y);
                double lZ = ((surface.Geometry.nXY.X - 1) * surface.Geometry.dXY.X);

                double maxdie = lZ;
                if (lX > lZ) maxdie = lX;
                m_chart.View3D.Dimensions.X = Convert.ToSingle(lX * 100.0 / maxdie);
                m_chart.View3D.Dimensions.Z = Convert.ToSingle(lZ * 100.0 / maxdie);

                m_chart.View3D.Dimensions.Y = m_yscale;//make this a binded property

                //Lightning Chart coords, our X = their Z, our Y = their X, our Z = their Y on 3D plots
                m_chart.View3D.XAxisPrimary3D.SetRange(surface.Geometry.xy.Y, surface.Geometry.xy.Y + ((surface.Geometry.nXY.Y - 1) * surface.Geometry.dXY.Y));
                m_chart.View3D.XAxisPrimary3D.Title.Text = "Y Axis";
                m_chart.View3D.XAxisPrimary3D.Reversed = true;
                m_chart.View3D.ZAxisPrimary3D.SetRange(surface.Geometry.xy.X, surface.Geometry.xy.X + ((surface.Geometry.nXY.X - 1) * surface.Geometry.dXY.X));
                m_chart.View3D.ZAxisPrimary3D.Title.Text = "X Axis";

                m_chart.View3D.YAxisPrimary3D.SetRange(surface.Depth.min, surface.Depth.max);
                m_chart.View3D.YAxisPrimary3D.Reversed = true;//depths are positive values "Below Sea Level", so reverse
                m_chart.View3D.YAxisPrimary3D.Title.Text = "Burial Depth";

                //this doesn't seem to work as expected with reversed axis, seems to be lit from bottom
                //m_chart.View3D.SetPredefinedLightingScheme(LightingScheme.Default);
                //m_chart.View3D.ZoomPanOptions.AxisMouseWheelAction = AxisMouseWheelAction.ZoomAll;
            }
        }
        private void AddSurface(SurfaceModel surface)
        {
            SetupChartForSurface(surface);

            //Add 3D surface grid
            //IntensitySurfaceGridSeries3D descends from SurfaceGridSeries3D, adds a few methods for contours
            IntensitySurfaceGridSeries3D gridSeries = new IntensitySurfaceGridSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
            gridSeries.Tag = surface;//so we can find the LightningChart SurfaceGridSeries3D that is associated with the surface when the surface is updated.

            gridSeries.Title.Text = surface.Name;

            //gridSeries.ContourPalette = CreatePalette(gridSeries, fcolourz.Min(), fcolourz.Max());
            gridSeries.ShowInLegendBox = false;
            gridSeries.ContourLineType = ContourLineType.None;
            gridSeries.ContourPalette.Type = PaletteType.Gradient;
            gridSeries.WireframeType = SurfaceWireframeType.None;
            gridSeries.Fill = SurfaceFillStyle.FromSurfacePoints;

            gridSeries.SetRangesXZ(surface.Geometry.xy.Y, surface.Geometry.xy.Y + ((surface.Geometry.nXY.Y - 1) * surface.Geometry.dXY.Y),
                surface.Geometry.xy.X, surface.Geometry.xy.X + ((surface.Geometry.nXY.X - 1) * surface.Geometry.dXY.X));
            gridSeries.SetSize(surface.Geometry.nXY.Y, surface.Geometry.nXY.X);

            gridSeries.ColorSaturation = 65;

            m_chart.View3D.SurfaceGridSeries3D.Add(gridSeries);

            UpdateSurface(surface, gridSeries);
        }
        private void AddSeriesForSurfaceModels( IList newItems, bool clearSeries)
        {
            m_chart.BeginUpdate();
            if (clearSeries)
            {
                m_chart.View3D.SurfaceGridSeries3D.Clear();
            }
            foreach(var sm in newItems)
            {
                var surface = sm as SurfaceModel;
                if (sm != null)
                {
                    AddSurface(surface);
                }
            }
            m_chart.EndUpdate();
        }
        private void SurfaceCollectionChangedMethod(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {               
                AddSeriesForSurfaceModels(e.NewItems, false);                
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {

            }
        }
    }
}