"Parallel Coordinates" Charts
Moderator: Queue Moderators
"Parallel Coordinates" Charts
To visualize high-dimensional multivariate data, a "Parallel Coordinates" chart can be used. See Wikipedia's page for Parallel Coordinates here: http://en/wikipedia.org/wiki/Parallel_coordinates
I am wondering if anyone has suggestions for an easy way to implement such a chart using LightningChart, and/or if you already have an implementation that you wouldn't mind sharing, or at least the concepts of how you might go about implementing it with LightningChart.
I am interested in 2D multi-Y axis implementations and 3D multi-Z plane implementations.
I am wondering if anyone has suggestions for an easy way to implement such a chart using LightningChart, and/or if you already have an implementation that you wouldn't mind sharing, or at least the concepts of how you might go about implementing it with LightningChart.
I am interested in 2D multi-Y axis implementations and 3D multi-Z plane implementations.
- ArctionPasi
- Posts: 1367
- Joined: Tue Mar 26, 2013 10:57 pm
- Location: Finland
- Contact:
Re: "Parallel Coordinates" Charts
Hi Deanius,
That can be done with LC. We'll first make a parallel coordinates 2D example by beginning of next week.
That can be done with LC. We'll first make a parallel coordinates 2D example by beginning of next week.
LightningChart Support Team, PT
- ArctionPasi
- Posts: 1367
- Joined: Tue Mar 26, 2013 10:57 pm
- Location: Finland
- Contact:
Re: "Parallel Coordinates" Charts
Here's an example of 2D parallel coordinates chart, made with LightningChart component. it's for WinForms. If you are using WPF, the code is almost identical.
The axes by categories are draggable and the view is zoomable vertically.
Does this help?
Code: Select all
// Chart.
private LightningChartUltimate m_chart;
//Attribute count
private const int AttributeCount = 6;
//List of persons
private List<BasicPersonInfo> m_listPersons = new List<BasicPersonInfo>();
/// <summary>
/// Constructor.
/// </summary>
public ExampleParallelCoordinates()
{
m_chart = null;
InitializeComponent();
//Create person attributes data, some info gathered when visiting a nurse
m_listPersons.Add(new BasicPersonInfo(70, 175, EyeColor.Brown, 25, 125, 70));
m_listPersons.Add(new BasicPersonInfo(85, 160, EyeColor.Blue, 48, 168, 92));
m_listPersons.Add(new BasicPersonInfo(68, 169, EyeColor.Blue, 32, 151, 84));
m_listPersons.Add(new BasicPersonInfo(90, 186, EyeColor.Gray, 40, 132, 77));
m_listPersons.Add(new BasicPersonInfo(48, 155, EyeColor.Green, 22, 115, 68));
CreateChart();
}
public enum EyeColor
{
Blue = 1,
Brown,
Green,
Gray,
}
public class BasicPersonInfo
{
public double Weight_kg;
public double Height_cm;
public EyeColor ColorOfEyes;
public int Age;
public double BloodPressureSystolic_mmHg;
public double BloodPressureDiastolic_mmHg;
public BasicPersonInfo(double weight, double height, EyeColor colorOfEyes, int age, double bpSys, double bpDia)
{
this.Weight_kg = weight;
this.Height_cm = height;
this.ColorOfEyes = colorOfEyes;
this.Age = age;
this.BloodPressureSystolic_mmHg = bpSys;
this.BloodPressureDiastolic_mmHg = bpDia;
}
public double[] GetValues()
{
return new double[] {this.Weight_kg, this.Height_cm, (double)this.ColorOfEyes, this.Age, this.BloodPressureSystolic_mmHg, this.BloodPressureDiastolic_mmHg};
}
}
/// <summary>
/// Create chart.
/// </summary>
private void CreateChart()
{
//Create new chart
m_chart = new LightningChartUltimate(LicenseKeys.LicenseKeyStrings.LightningChartUltimate);
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
//Chart parent must be set
m_chart.Parent = this;
//Fill parent area with chart
m_chart.Dock = DockStyle.Fill;
//Chart name
m_chart.Name = "Parallel coordinates chart";
//Hide legend box
m_chart.ViewXY.LegendBox.Visible = true;
//Create an Y axis for each attribute
m_chart.ViewXY.YAxes.Clear();
double dPositionInterval = 100.0 / ((double) AttributeCount - 1.0); //interval of categories in range 0...100
//Weight axis. This is the 'master' axis
AxisY axisYWeight = new AxisY(m_chart.ViewXY);
axisYWeight.SetRange(40, 100);
axisYWeight.Title.Text = "Weight / kg";
axisYWeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYWeight);
//Height axis, 'slave' axis
AxisY axisYHeight = new AxisY(m_chart.ViewXY);
axisYHeight.SetRange(140, 200);
axisYHeight.Title.Text = "Height / cm";
axisYHeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYHeight);
//Eye color axis 'slave' axis
AxisY axisYEyeColor = new AxisY(m_chart.ViewXY);
axisYEyeColor.SetRange(1, 4);
axisYEyeColor.Title.Text = "Eye color";
axisYEyeColor.CustomTicks.AddRange(new List<CustomAxisTick>()
{
new CustomAxisTick (axisYEyeColor, 1, "Blue"),
new CustomAxisTick(axisYEyeColor,2, "Brown"),
new CustomAxisTick(axisYEyeColor,3, "Green"),
new CustomAxisTick(axisYEyeColor,4, "Gray"),
});
axisYEyeColor.CustomTicksEnabled = true;
axisYEyeColor.AutoFormatLabels = false;
axisYEyeColor.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYEyeColor);
//Age axis 'slave' axis
AxisY axisYAge = new AxisY(m_chart.ViewXY);
axisYAge.SetRange(10, 90);
axisYAge.Title.Text = "Age / years";
axisYAge.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYAge);
//Blood pressure, systolic, 'slave' axis
AxisY axisYBpSys = new AxisY(m_chart.ViewXY);
axisYBpSys.SetRange(60, 180);
axisYBpSys.Title.Text = "Blood pressure (sys)/ mmHg ";
axisYBpSys.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpSys);
//Blood pressure, diastolic, 'slave' axis
AxisY axisYBpDia = new AxisY(m_chart.ViewXY);
axisYBpDia.SetRange(60, 180);
axisYBpDia.Title.Text = "Blood pressure (dia)/ mmHg ";
axisYBpDia.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpDia);
foreach(AxisY axisY in m_chart.ViewXY.YAxes)
{
axisY.MajorGrid.Visible = false;
axisY.AxisThickness = 1;
axisY.RangeChanged += new AxisBase.RangeChangedHandler(axisY_RangeChanged);
}
//Setup x-axis
m_chart.ViewXY.XAxes[0].Visible = false;
m_chart.ViewXY.XAxes[0].SetRange(0, 5);
//Disable X zooming and panning
m_chart.ViewXY.ZoomPanOptions.RectangleZoomDirectionLayered = RectangleZoomDirectionLayered.Vertical;
m_chart.ViewXY.ZoomPanOptions.PanDirection = PanDirection.Vertical;
m_chart.ViewXY.ZoomPanOptions.MouseWheelZooming = MouseWheelZooming.Vertical;
//Turn of automatic Y axis placement so we can place them manually with Position property
m_chart.ViewXY.AxisLayout.YAxisAutoPlacement = YAxisAutoPlacement.Off;
int iPerson = 0;
//Add series for each person and bind them to the first Y axis
foreach (BasicPersonInfo person in m_listPersons)
{
PointLineSeries pls = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);
pls.PointsVisible = true;
pls.Title.Text = "Person" + (iPerson + 1).ToString();
SeriesPoint[] aPoints = new SeriesPoint[AttributeCount];
double[] attributeValues = person.GetValues();
for(int iAttrib = 0; iAttrib < AttributeCount; iAttrib++)
{
aPoints[iAttrib].X = iAttrib;
aPoints[iAttrib].Y = ScaleToMasterAxis(iAttrib, attributeValues[iAttrib]);
}
pls.LineStyle.Color = DefaultColors.SeriesForBlackBackground[iPerson];
pls.PointStyle.Color1 = DefaultColors.SeriesForBlackBackground[iPerson];
pls.Points = aPoints;
//Add the created point line series into PointLineSeries list
m_chart.ViewXY.PointLineSeries.Add(pls);
iPerson++;
}
//Allow chart rendering
m_chart.EndUpdate();
}
/// <summary>
/// Scale given attribute to master axis scale, by converting the attribute value to screen coordinate and then to master axis range
/// </summary>
/// <param name="attributeIndex">Attribute index</param>
/// <param name="value">Attribute value</param>
/// <returns>Scaled value</returns>
double ScaleToMasterAxis(int attributeIndex, double value)
{
AxisY masterAxis = m_chart.ViewXY.YAxes[0];
AxisY attributeAxis = m_chart.ViewXY.YAxes[attributeIndex];
float fYCoord = attributeAxis.ValueToCoord(value);
double dYValueOnMasterAxis = 0;
masterAxis.CoordToValue(fYCoord, out dYValueOnMasterAxis);
return dYValueOnMasterAxis;
}
void axisY_RangeChanged(double newMin, double newMax, AxisBase axis, ref bool cancelRendering)
{
m_chart.BeginUpdate();
int iPerson = 0;
foreach (BasicPersonInfo person in m_listPersons)
{
double[] attribValues = person.GetValues();
PointLineSeries series = m_chart.ViewXY.PointLineSeries[iPerson];
//Update all other attributes, to master axis scale
for (int iAttrib = 1; iAttrib < AttributeCount; iAttrib++)
{
series.Points[iAttrib].Y = ScaleToMasterAxis(iAttrib, attribValues[iAttrib]);
}
series.InvalidateData();
iPerson++;
}
m_chart.EndUpdate();
}
Does this help?
LightningChart Support Team, PT
Re: "Parallel Coordinates" Charts
Yes, this helps out a lot. And, yes, we are using WPF.
I'm assuming that a 3D implementation wouldn't be too much different. If it's not too much trouble, can you put together a 3D sample as well? No hurry...
I'm really impressed by your quick response for a sample. Thank you very much.
I'm assuming that a 3D implementation wouldn't be too much different. If it's not too much trouble, can you put together a 3D sample as well? No hurry...
I'm really impressed by your quick response for a sample. Thank you very much.
- ArctionPasi
- Posts: 1367
- Joined: Tue Mar 26, 2013 10:57 pm
- Location: Finland
- Contact:
Re: "Parallel Coordinates" Charts
Hi Deanius,
I made this kind of prototype...
This is for WPF
I hope this helps...
I made this kind of prototype...
This is for WPF
Code: Select all
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Arction.WPF.LightningChartUltimate;
using Arction.WPF.LightningChartUltimate.Series3D;
using Arction.WPF.LightningChartUltimate.Views.View3D;
using Arction.WPF.LightningChartUltimate.Axes;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//Chart
private LightningChartUltimate m_chart = null;
public MainWindow()
{
InitializeComponent();
CreateChart();
}
/// <summary>
/// Create chart.
/// </summary>
private void CreateChart()
{
//Create new chart
m_chart = new LightningChartUltimate();
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
//Set active view
m_chart.ActiveView = ActiveView.View3D;
//Chart parent must be set
gridMain.Children.Add(m_chart);
//Set camera, 3D world dimensions etc.
m_chart.View3D.Camera.RotationY -= 40;
m_chart.View3D.Dimensions.Z = 150;
m_chart.View3D.XAxisPrimary3D.Reversed = true;
m_chart.View3D.ZAxisPrimary3D.Title.Visible = false;
m_chart.View3D.LegendBox.ShowCheckboxes = false;
//Set data
SetData();
//Allow chart rendering
m_chart.EndUpdate();
}
private void SetData()
{
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
View3D v = m_chart.View3D;
//Clear existing series and their data
v.PointLineSeries3D.Clear();
int iAttribCount = 5;
int iGroupCount = 10;
int iLinesPerGroup = 50;
double dVariationX = 40;
double dVariationY = 30;
double xMin = v.XAxisPrimary3D.Minimum;
double xMax = v.XAxisPrimary3D.Maximum;
double yMin = v.YAxisPrimary3D.Minimum;
double yMax = v.YAxisPrimary3D.Maximum;
double dXShiftPerGroup = (xMax - xMin) / (double)(iGroupCount + 1);
Random rand = new Random();
for (int iGroup = 0; iGroup < iGroupCount; iGroup++)
{
Color colorGroupLine = DefaultColors.SeriesForBlackBackgroundWPF[iGroup];
for (int iLine = 0; iLine < iLinesPerGroup; iLine++)
{
PointLineSeries3D series = new PointLineSeries3D(v, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
series.LineStyle.Color = colorGroupLine;
series.LineStyle.Width = 2;
series.LineStyle.AntiAliasing = LineAntialias.None;
series.PointsVisible = false;
series.Title.ShowInLegendBox = iLine == 0; //Only show the first line in the legend box of each group
series.Title.Text = "Data group " + (iGroup + 1).ToString();
v.PointLineSeries3D.Add(series);
PointDouble3D[] points = new PointDouble3D[iAttribCount];
for (int i = 0; i < iAttribCount; i++)
{
points[i].X = (double)(iGroup + 1) * dXShiftPerGroup + dVariationX / 2.0 * (rand.NextDouble() - 0.5) + (rand.NextDouble() - 0.5) * Math.Sin((double)iLine);
points[i].Y = 30 + dVariationY * rand.NextDouble() + dVariationY * Math.Sin(iGroup / 3.0) * Math.Sin((double)i * 5.0);// (Math.Sin((double)i/3.0) * (double)(iGroup) / 3.0 *Math.Sin(iGroup)/(double)iGroupCount);
points[i].Z = i;
}
series.Points = points;
}
}
//Set Z axis range according to attribute count
Axis3DBase zAxis = v.ZAxisPrimary3D;
zAxis.SetRange(-0.5, iAttribCount - 0.5);
//Set custom ticks to explain the attributes and planes
for (int i = 0; i < iAttribCount; i++)
{
zAxis.CustomTicks.Add(new CustomAxisTick(zAxis, i, "Attribute " + (i + 1).ToString()));
}
zAxis.CustomTicksEnabled = true;
zAxis.AutoFormatLabels = false;
//Create XY plane for each attribute
v.Rectangles.Clear();
for (int i = 0; i < iAttribCount; i++)
{
Rectangle3D rect = new Rectangle3D(v, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
rect.Fill.Color = Color.FromArgb(120, 64, 64, 64);
//These are 3D world coordinates
rect.Center.X = (xMax + xMin) / 2.0;
rect.Center.Y = (yMax + yMin) / 2.0;
rect.Center.Z = i;
rect.Size.Width = v.Dimensions.X;
rect.Size.Height = v.Dimensions.Y;
rect.Rotation.X = 90;
v.Rectangles.Add(rect);
}
//Allow chart rendering
m_chart.EndUpdate();
}
}
}
LightningChart Support Team, PT
Re: "Parallel Coordinates" Charts
Yes, this is exactly what I was looking for to get an idea of what it would take to implement Parallel coordinates in 3D space.
Thanks again.
Thanks again.
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
namespace ParallelCoordinates
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
//List of persons
private List<BasicPersonInfo> m_listPersons = new List<BasicPersonInfo>();
private int AttributeCount = 6;
public MainWindow()
{
InitializeComponent();
//Create person attributes data, some info gathered when visiting a nurse
m_listPersons.Add(new BasicPersonInfo(70, 175, EyeColor.Brown, 25, 125, 70));
m_listPersons.Add(new BasicPersonInfo(85, 160, EyeColor.Blue, 48, 168, 92));
m_listPersons.Add(new BasicPersonInfo(68, 169, EyeColor.Blue, 32, 151, 84));
m_listPersons.Add(new BasicPersonInfo(90, 186, EyeColor.Gray, 40, 132, 77));
m_listPersons.Add(new BasicPersonInfo(48, 155, EyeColor.Green, 22, 115, 68));
CreateChart();
}
private void CreateChart()
{
m_chart.BeginUpdate();
//Chart parent must be set
// m_chart.Parent = this;
//Fill parent area with chart
//(Content as Grid).Children.Add(m_chart); ;
//Chart name
// m_chart.Name = "Parallel coordinates chart";
//Hide legend box
m_chart.ViewXY.AutoSpaceLegendBoxes = true;
//Create an Y axis for each attribute
m_chart.ViewXY.YAxes.Clear();
double dPositionInterval = 100.0 / ((double)AttributeCount - 1.0); //interval of categories in range 0...100
//Weight axis. This is the 'master' axis
AxisY axisYWeight = new AxisY(m_chart.ViewXY);
axisYWeight.SetRange(40, 100);
axisYWeight.Title.Text = "Weight / kg";
axisYWeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYWeight);
//Height axis, 'slave' axis
AxisY axisYHeight = new AxisY(m_chart.ViewXY);
axisYHeight.SetRange(140, 200);
axisYHeight.Title.Text = "Height / cm";
axisYHeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYHeight);
//Eye color axis 'slave' axis
AxisY axisYEyeColor = new AxisY(m_chart.ViewXY);
axisYEyeColor.SetRange(1, 4);
axisYEyeColor.Title.Text = "Eye color";
axisYEyeColor.CustomTicks.AddRange(new List<CustomAxisTick>()
{
new CustomAxisTick (axisYEyeColor, 1, "Blue"),
new CustomAxisTick(axisYEyeColor,2, "Brown"),
new CustomAxisTick(axisYEyeColor,3, "Green"),
new CustomAxisTick(axisYEyeColor,4, "Gray"),
});
axisYEyeColor.CustomTicksEnabled = true;
axisYEyeColor.AutoFormatLabels = false;
axisYEyeColor.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYEyeColor);
//Age axis 'slave' axis
AxisY axisYAge = new AxisY(m_chart.ViewXY);
axisYAge.SetRange(10, 90);
axisYAge.Title.Text = "Age / years";
axisYAge.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYAge);
//Blood pressure, systolic, 'slave' axis
AxisY axisYBpSys = new AxisY(m_chart.ViewXY);
axisYBpSys.SetRange(60, 180);
axisYBpSys.Title.Text = "Blood pressure (sys)/ mmHg ";
axisYBpSys.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpSys);
//Blood pressure, diastolic, 'slave' axis
AxisY axisYBpDia = new AxisY(m_chart.ViewXY);
axisYBpDia.SetRange(60, 180);
axisYBpDia.Title.Text = "Blood pressure (dia)/ mmHg ";
axisYBpDia.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpDia);
foreach (AxisY axisY in m_chart.ViewXY.YAxes)
{
axisY.MajorGrid.Visible = false;
axisY.AxisThickness = 1;
axisY.RangeChanged += AxisY_RangeChanged;
//axisY.RangeChanged += new AxisBase.RangeChangedHandler(axisY_RangeChanged);
}
//Setup x-axis
m_chart.ViewXY.XAxes[0].Visible = false;
m_chart.ViewXY.XAxes[0].SetRange(0, 5);
//Disable X zooming and panning
m_chart.ViewXY.ZoomPanOptions.RectangleZoomDirection = RectangleZoomDirection.Vertical;
m_chart.ViewXY.ZoomPanOptions.PanDirection = PanDirection.Vertical;
m_chart.ViewXY.ZoomPanOptions.MouseWheelZooming = MouseWheelZooming.Vertical;
//Turn of automatic Y axis placement so we can place them manually with Position property
m_chart.ViewXY.AxisLayout.YAxisAutoPlacement = YAxisAutoPlacement.Off;
int iPerson = 0;
//Add series for each person and bind them to the first Y axis
foreach (BasicPersonInfo person in m_listPersons)
{
PointLineSeries pls = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);
pls.PointsVisible = true;
pls.Title.Text = "Person" + (iPerson + 1).ToString();
SeriesPointCollection aPoints = new SeriesPointCollection();
double[] attributeValues = person.GetValues();
for (int iAttrib = 0; iAttrib < AttributeCount; iAttrib++)
{
SeriesPoint seriesPoint = new SeriesPoint();
seriesPoint.X = iAttrib;
seriesPoint.Y = ScaleToMasterAxis(iAttrib, attributeValues[iAttrib]);
aPoints.Add(seriesPoint);
}
pls.LineStyle.Color = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.PointStyle.Color1 = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.Points = aPoints;
//Add the created point line series into PointLineSeries list
m_chart.ViewXY.PointLineSeries.Add(pls);
iPerson++;
}
//Allow chart rendering
m_chart.EndUpdate();
}
private void AxisY_RangeChanged(object sender, RangeChangedEventArgs e)
{
m_chart.BeginUpdate();
int iPerson = 0;
foreach (BasicPersonInfo person in m_listPersons)
{
double[] attribValues = person.GetValues();
PointLineSeries series = m_chart.ViewXY.PointLineSeries[iPerson];
//Update all other attributes, to master axis scale
for (int iAttrib = 1; iAttrib < AttributeCount; iAttrib++)
{
series.Points[iAttrib].Y = ScaleToMasterAxis(iAttrib, attribValues[iAttrib]);
}
series.InvalidateData();
iPerson++;
}
}
public class BasicPersonInfo
{
public double Weight_kg;
public double Height_cm;
public EyeColor ColorOfEyes;
public int Age;
public double BloodPressureSystolic_mmHg;
public double BloodPressureDiastolic_mmHg;
public BasicPersonInfo(double weight, double height, EyeColor colorOfEyes, int age, double bpSys, double bpDia)
{
this.Weight_kg = weight;
this.Height_cm = height;
this.ColorOfEyes = colorOfEyes;
this.Age = age;
this.BloodPressureSystolic_mmHg = bpSys;
this.BloodPressureDiastolic_mmHg = bpDia;
}
public double[] GetValues()
{
return new double[] { this.Weight_kg, this.Height_cm, (double)this.ColorOfEyes, this.Age, this.BloodPressureSystolic_mmHg, this.BloodPressureDiastolic_mmHg };
}
}
public enum EyeColor
{
Blue = 1,
Brown,
Green,
Gray,
}
double ScaleToMasterAxis(int attributeIndex, double value)
{
AxisY masterAxis = m_chart.ViewXY.YAxes[0];
AxisY attributeAxis = m_chart.ViewXY.YAxes[attributeIndex];
float fYCoord = attributeAxis.ValueToCoord(value);
double dYValueOnMasterAxis = 0;
masterAxis.CoordToValue(fYCoord, out dYValueOnMasterAxis);
return dYValueOnMasterAxis;
}
}
}
This is my code I am trying to make Parallel coordinates but my Scaletomatrix is always returning 100 due to which lines are not creating please help me
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
//List of persons
private List<BasicPersonInfo> m_listPersons = new List<BasicPersonInfo>();
private int AttributeCount = 6;
public MainWindow()
{
InitializeComponent();
//Create person attributes data, some info gathered when visiting a nurse
m_listPersons.Add(new BasicPersonInfo(70, 175, EyeColor.Brown, 25, 125, 70));
m_listPersons.Add(new BasicPersonInfo(85, 160, EyeColor.Blue, 48, 168, 92));
m_listPersons.Add(new BasicPersonInfo(68, 169, EyeColor.Blue, 32, 151, 84));
m_listPersons.Add(new BasicPersonInfo(90, 186, EyeColor.Gray, 40, 132, 77));
m_listPersons.Add(new BasicPersonInfo(48, 155, EyeColor.Green, 22, 115, 68));
CreateChart();
}
private void CreateChart()
{
m_chart.BeginUpdate();
//Chart parent must be set
// m_chart.Parent = this;
//Fill parent area with chart
//(Content as Grid).Children.Add(m_chart); ;
//Chart name
// m_chart.Name = "Parallel coordinates chart";
//Hide legend box
m_chart.ViewXY.AutoSpaceLegendBoxes = true;
//Create an Y axis for each attribute
m_chart.ViewXY.YAxes.Clear();
double dPositionInterval = 100.0 / ((double)AttributeCount - 1.0); //interval of categories in range 0...100
//Weight axis. This is the 'master' axis
AxisY axisYWeight = new AxisY(m_chart.ViewXY);
axisYWeight.SetRange(40, 100);
axisYWeight.Title.Text = "Weight / kg";
axisYWeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYWeight);
//Height axis, 'slave' axis
AxisY axisYHeight = new AxisY(m_chart.ViewXY);
axisYHeight.SetRange(140, 200);
axisYHeight.Title.Text = "Height / cm";
axisYHeight.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYHeight);
//Eye color axis 'slave' axis
AxisY axisYEyeColor = new AxisY(m_chart.ViewXY);
axisYEyeColor.SetRange(1, 4);
axisYEyeColor.Title.Text = "Eye color";
axisYEyeColor.CustomTicks.AddRange(new List<CustomAxisTick>()
{
new CustomAxisTick (axisYEyeColor, 1, "Blue"),
new CustomAxisTick(axisYEyeColor,2, "Brown"),
new CustomAxisTick(axisYEyeColor,3, "Green"),
new CustomAxisTick(axisYEyeColor,4, "Gray"),
});
axisYEyeColor.CustomTicksEnabled = true;
axisYEyeColor.AutoFormatLabels = false;
axisYEyeColor.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYEyeColor);
//Age axis 'slave' axis
AxisY axisYAge = new AxisY(m_chart.ViewXY);
axisYAge.SetRange(10, 90);
axisYAge.Title.Text = "Age / years";
axisYAge.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYAge);
//Blood pressure, systolic, 'slave' axis
AxisY axisYBpSys = new AxisY(m_chart.ViewXY);
axisYBpSys.SetRange(60, 180);
axisYBpSys.Title.Text = "Blood pressure (sys)/ mmHg ";
axisYBpSys.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpSys);
//Blood pressure, diastolic, 'slave' axis
AxisY axisYBpDia = new AxisY(m_chart.ViewXY);
axisYBpDia.SetRange(60, 180);
axisYBpDia.Title.Text = "Blood pressure (dia)/ mmHg ";
axisYBpDia.Position = m_chart.ViewXY.YAxes.Count * dPositionInterval;
m_chart.ViewXY.YAxes.Add(axisYBpDia);
foreach (AxisY axisY in m_chart.ViewXY.YAxes)
{
axisY.MajorGrid.Visible = false;
axisY.AxisThickness = 1;
axisY.RangeChanged += AxisY_RangeChanged;
//axisY.RangeChanged += new AxisBase.RangeChangedHandler(axisY_RangeChanged);
}
//Setup x-axis
m_chart.ViewXY.XAxes[0].Visible = false;
m_chart.ViewXY.XAxes[0].SetRange(0, 5);
//Disable X zooming and panning
m_chart.ViewXY.ZoomPanOptions.RectangleZoomDirection = RectangleZoomDirection.Vertical;
m_chart.ViewXY.ZoomPanOptions.PanDirection = PanDirection.Vertical;
m_chart.ViewXY.ZoomPanOptions.MouseWheelZooming = MouseWheelZooming.Vertical;
//Turn of automatic Y axis placement so we can place them manually with Position property
m_chart.ViewXY.AxisLayout.YAxisAutoPlacement = YAxisAutoPlacement.Off;
int iPerson = 0;
//Add series for each person and bind them to the first Y axis
foreach (BasicPersonInfo person in m_listPersons)
{
PointLineSeries pls = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);
pls.PointsVisible = true;
pls.Title.Text = "Person" + (iPerson + 1).ToString();
SeriesPointCollection aPoints = new SeriesPointCollection();
double[] attributeValues = person.GetValues();
for (int iAttrib = 0; iAttrib < AttributeCount; iAttrib++)
{
SeriesPoint seriesPoint = new SeriesPoint();
seriesPoint.X = iAttrib;
seriesPoint.Y = ScaleToMasterAxis(iAttrib, attributeValues[iAttrib]);
aPoints.Add(seriesPoint);
}
pls.LineStyle.Color = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.PointStyle.Color1 = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.Points = aPoints;
//Add the created point line series into PointLineSeries list
m_chart.ViewXY.PointLineSeries.Add(pls);
iPerson++;
}
//Allow chart rendering
m_chart.EndUpdate();
}
private void AxisY_RangeChanged(object sender, RangeChangedEventArgs e)
{
m_chart.BeginUpdate();
int iPerson = 0;
foreach (BasicPersonInfo person in m_listPersons)
{
double[] attribValues = person.GetValues();
PointLineSeries series = m_chart.ViewXY.PointLineSeries[iPerson];
//Update all other attributes, to master axis scale
for (int iAttrib = 1; iAttrib < AttributeCount; iAttrib++)
{
series.Points[iAttrib].Y = ScaleToMasterAxis(iAttrib, attribValues[iAttrib]);
}
series.InvalidateData();
iPerson++;
}
}
public class BasicPersonInfo
{
public double Weight_kg;
public double Height_cm;
public EyeColor ColorOfEyes;
public int Age;
public double BloodPressureSystolic_mmHg;
public double BloodPressureDiastolic_mmHg;
public BasicPersonInfo(double weight, double height, EyeColor colorOfEyes, int age, double bpSys, double bpDia)
{
this.Weight_kg = weight;
this.Height_cm = height;
this.ColorOfEyes = colorOfEyes;
this.Age = age;
this.BloodPressureSystolic_mmHg = bpSys;
this.BloodPressureDiastolic_mmHg = bpDia;
}
public double[] GetValues()
{
return new double[] { this.Weight_kg, this.Height_cm, (double)this.ColorOfEyes, this.Age, this.BloodPressureSystolic_mmHg, this.BloodPressureDiastolic_mmHg };
}
}
public enum EyeColor
{
Blue = 1,
Brown,
Green,
Gray,
}
double ScaleToMasterAxis(int attributeIndex, double value)
{
AxisY masterAxis = m_chart.ViewXY.YAxes[0];
AxisY attributeAxis = m_chart.ViewXY.YAxes[attributeIndex];
float fYCoord = attributeAxis.ValueToCoord(value);
double dYValueOnMasterAxis = 0;
masterAxis.CoordToValue(fYCoord, out dYValueOnMasterAxis);
return dYValueOnMasterAxis;
}
}
}
Code: Select all
This is my code I am trying to make Parallel coordinates but my Scaletomatrix is always returning 100 due to which lines are not creating please help me
-
- Posts: 141
- Joined: Wed Mar 27, 2019 1:05 pm
Re: "Parallel Coordinates" Charts
Hello,
We checked the code you posted and managed to get it working. The main issue that stopped it from working was that there is m_chart.BeginUpdate() inside the AxisY_RangeChanged -event but no EndUpdate(). This means that every time axis range is changed, the chart disables rendering, and since it never properly enables it, the PointLineSeries defined inside the event most likely won't get rendered or updated at all. InvalidateData() called inside the event notifies the chart that there is something new to render but doesn't help here as the rendering is disabled. The solution for this is to either add m_chart.EndUpdate() or remove the BeginUpdate().
Another small note is the lack of AfterRendering -event that was used in our original parallel coordinates example. Without it the series might not be rendered unless there is some interaction with the axes.
Hope this helps.
Best regards,
Lasse
We checked the code you posted and managed to get it working. The main issue that stopped it from working was that there is m_chart.BeginUpdate() inside the AxisY_RangeChanged -event but no EndUpdate(). This means that every time axis range is changed, the chart disables rendering, and since it never properly enables it, the PointLineSeries defined inside the event most likely won't get rendered or updated at all. InvalidateData() called inside the event notifies the chart that there is something new to render but doesn't help here as the rendering is disabled. The solution for this is to either add m_chart.EndUpdate() or remove the BeginUpdate().
Another small note is the lack of AfterRendering -event that was used in our original parallel coordinates example. Without it the series might not be rendered unless there is some interaction with the axes.
Hope this helps.
Best regards,
Lasse
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
private void AxisY_RangeChanged(object sender, RangeChangedEventArgs e)
{
m_chart.BeginUpdate();
int iPerson = 0;
foreach (BasicPersonInfo person in m_listPersons)
{
double[] attribValues = person.GetValues();
PointLineSeries series = m_chart.ViewXY.PointLineSeries[iPerson];
//Update all other attributes, to master axis scale
for (int iAttrib = 1; iAttrib < AttributeCount; iAttrib++)
{
series.Points[iAttrib].Y = ScaleToMasterAxis(iAttrib, attribValues[iAttrib]);
}
series.InvalidateData();
iPerson++;
}
m_chart.EndUpdate();
}
I have tried this and the other way around by removing m_chart.BeginUpdate(); the lines are not generating.....
{
m_chart.BeginUpdate();
int iPerson = 0;
foreach (BasicPersonInfo person in m_listPersons)
{
double[] attribValues = person.GetValues();
PointLineSeries series = m_chart.ViewXY.PointLineSeries[iPerson];
//Update all other attributes, to master axis scale
for (int iAttrib = 1; iAttrib < AttributeCount; iAttrib++)
{
series.Points[iAttrib].Y = ScaleToMasterAxis(iAttrib, attribValues[iAttrib]);
}
series.InvalidateData();
iPerson++;
}
m_chart.EndUpdate();
}
I have tried this and the other way around by removing m_chart.BeginUpdate(); the lines are not generating.....
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
I also don't understand where are you using afterrendering?
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
This is the outcome i am getting.
- Attachments
-
- This is outcome i am getting
- pic.PNG (117.42 KiB) Viewed 45235 times
-
- Posts: 141
- Joined: Wed Mar 27, 2019 1:05 pm
Re: "Parallel Coordinates" Charts
Hello,
If you check our ParallelCoordinates -demo source code, you can see that it adds the PointLineSeries to the chart inside AfterRendering -event, not in CreateChart() -method as in your example code. Usually it is better to add the series when creating the chart, but this particular example is a special case; it uses screen coordinates to define the positions of the PointLineSeries. To use screen coordinates, the chart has to be rendered first.
Note that we unsubscribe from the event immediately after it's been triggered, as it is no longer needed.
Another possible reason for this issue is that in your code a collection is used instead of an array:
SeriesPointCollection aPoints = new SeriesPointCollection();
Unless you are using WPF Fully-bindable platform instead of for example WPF Non-bindable, it is better to use arrays to assign data points as shown also in the code above.
Best regards,
Lasse
If you check our ParallelCoordinates -demo source code, you can see that it adds the PointLineSeries to the chart inside AfterRendering -event, not in CreateChart() -method as in your example code. Usually it is better to add the series when creating the chart, but this particular example is a special case; it uses screen coordinates to define the positions of the PointLineSeries. To use screen coordinates, the chart has to be rendered first.
Code: Select all
// In CreateChart() subscribe to the event
m_chart.AfterRendering += m_chart_AfterRendering;
private void m_chart_AfterRendering(object sender, AfterRenderingEventArgs e)
{
// We no longer need to handle this event so unsubscibe from it.
m_chart.AfterRendering -= m_chart_AfterRendering;
m_chart.BeginUpdate();
int iPerson = 0;
//Add series for each person and bind them to the first Y axis
foreach (BasicPersonInfo person in m_listPersons)
{
PointLineSeries pls = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);
pls.PointsVisible = true;
pls.Title.Text = "Person" + (iPerson + 1).ToString();
//SeriesPointCollection aPoints = new SeriesPointCollection();
SeriesPoint[] aPoints = new SeriesPoint[AttributeCount];
double[] attributeValues = person.GetValues();
for (int iAttrib = 0; iAttrib < AttributeCount; iAttrib++)
{
SeriesPoint seriesPoint = new SeriesPoint();
seriesPoint.X = iAttrib;
seriesPoint.Y = ScaleToMasterAxis(iAttrib, attributeValues[iAttrib]);
//aPoints.Add(seriesPoint);
aPoints[iAttrib] = seriesPoint;
}
pls.LineStyle.Color = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.PointStyle.Color1 = DefaultColors.SeriesForBlackBackgroundWpf[iPerson];
pls.Points = aPoints;
//Add the created point line series into PointLineSeries list
m_chart.ViewXY.PointLineSeries.Add(pls);
iPerson++;
}
m_chart.EndUpdate();
}
Another possible reason for this issue is that in your code a collection is used instead of an array:
SeriesPointCollection aPoints = new SeriesPointCollection();
Unless you are using WPF Fully-bindable platform instead of for example WPF Non-bindable, it is better to use arrays to assign data points as shown also in the code above.
Best regards,
Lasse
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
Thank you so much for your help that works for me. Just one more thing in your example above you are showing titles with person 1 , person 2 and so on how are you doing it as i am following exactly the same code but it does not come up in my code.
-
- Posts: 6
- Joined: Tue Jul 23, 2019 5:52 am
Re: "Parallel Coordinates" Charts
Plus if I wanted to add some lines with same color how can i do that where are you handling the color because when I add the one more line with same eye color it always create a new line with different color.
m_listPersons.Add(new BasicPersonInfo(70, 175, EyeColor.Brown, 25, 125, 70));
m_listPersons.Add(new BasicPersonInfo(60, 135, EyeColor.Brown, 15, 105, 50));
m_listPersons.Add(new BasicPersonInfo(85, 160, EyeColor.Blue, 48, 168, 92));
m_listPersons.Add(new BasicPersonInfo(68, 169, EyeColor.Blue, 32, 151, 84));
m_listPersons.Add(new BasicPersonInfo(90, 186, EyeColor.Gray, 40, 132, 77));
m_listPersons.Add(new BasicPersonInfo(48, 155, EyeColor.Green, 22, 115, 68));
m_listPersons.Add(new BasicPersonInfo(70, 175, EyeColor.Brown, 25, 125, 70));
m_listPersons.Add(new BasicPersonInfo(60, 135, EyeColor.Brown, 15, 105, 50));
m_listPersons.Add(new BasicPersonInfo(85, 160, EyeColor.Blue, 48, 168, 92));
m_listPersons.Add(new BasicPersonInfo(68, 169, EyeColor.Blue, 32, 151, 84));
m_listPersons.Add(new BasicPersonInfo(90, 186, EyeColor.Gray, 40, 132, 77));
m_listPersons.Add(new BasicPersonInfo(48, 155, EyeColor.Green, 22, 115, 68));
-
- Posts: 557
- Joined: Mon Mar 14, 2016 9:22 am
Re: "Parallel Coordinates" Charts
Hi,
This start to look like we are building proof-of-concept application. This would be part of technical support which requires active LightningChart subscription. Therefore, I would like to ask you to send subscription ID directly to Arction Support (support [at] arction com). If you don't have one please introduce yourself (company you represent, how Chart is used etc.).
All the best.
This start to look like we are building proof-of-concept application. This would be part of technical support which requires active LightningChart subscription. Therefore, I would like to ask you to send subscription ID directly to Arction Support (support [at] arction com). If you don't have one please introduce yourself (company you represent, how Chart is used etc.).
All the best.