I'm trying to produce a chart which would combine the looks of a PointChart3D and a HeatMapChart3D. See below: I'm trying to have the surface of the HeatMap (or some other mesh of sort) be shown "amid" the points of the point chart).
I get the impression that Composite charts' layers are only 2D layers. For example, Infragistics.UltraChart.Resources.Appearance.ChartLayerAppearance has only AxisX and AxisY members and also data binding to ChartLayerAppearance seems to be exclusively by way of Series...)
Note that my focus is on true 3D graph, i.e. graphs based on 3 data axis, not the "3D" graphs such as say PieChart3D which is essentially a 2D graph with some arbitrary thickness/perspective in the 3rd dimension.)
Is there a way to achieve my combined 3D graph, whether with the Composite approach or some other technique?
Hello MVeillet,
As far as I know at that moment our UltraChart is not able to combine HeatMapChart3D with PointChart3D. Our composite chart is suitable to combine different type of charts, but for scenario with two dimensions. More information about our UltraChart you could find in our online documentation :
http://help.infragistics.com/Help/NetAdvantage/WinForms/2011.1/CLR2.0/html/WinChart.html
Please let me know if you have any questions
Regards
Since it isn't possible to combine the two graph types, I used the following application-level kludge to produce a similar effect. In a nutshell, I add a "grid" of points to simulate the heat map's "surface data" and I display these points differently from the actual PointChart3D data points.
This is not ideal and in particular has the following drawbacks: - one needs to compute the points in the grid rather than only providing the datapoints and letting the built in logic spline fit the corresponding surface. - we show a grid of points rather than a surface - one needs to find a right balance of number of points for the mesh/grid : adding points make for a smoother and more self-evident "surface" but can tax unduly the PintChart3D (somewhere between 1,500 and 2,500 point, total, the graph rendering performance becomes rather jerky). Having too few points may make the "surface" less obvious with its points just blending in amid the plot datapoints.
Here's how it looks (the "surface data" is shown as a grid of slightly subdued "dots", while the actual plot data points are colored pyramids. By using the interactive point-of-view feature of the 3D graph the user is able to find where the data points stand relative to the surface data (above, below, near, far...). A typical use case is when the data point represent actual measurements and the surface data represent some kind of theoretical model.
In more details: - compute and add the data points for a "mesh" (or more precisely a "grid") of points which will be shown as these of the "surface" data. Add these points with a characteristic that will allow them to be differentiated from the plot data. (I used the "Empty" flag for that purpose, could be tag-based or some other attribute attached to the datapoint) - hook into the ChartDrawItem event to alter the looks of the points in this mesh
The core relevant code :
private void UltraChart1_ChartDrawItem(object sender, Infragistics.UltraChart.Shared.Events.ChartDrawItemEventArgs e) { if (e.HasData) { if (e.Primitive.Layer is Infragistics.UltraChart.Core.Layers.Point3DLayer) { if (e.Primitive is Infragistics.UltraChart.Core.Primitives.Path) { var pap = e.Primitive as Infragistics.UltraChart.Core.Primitives.Path; if (e.Primitive.DataPoint is Infragistics.UltraChart.Resources.Appearance.XYZDataPoint) { var xyzDp = e.Primitive.DataPoint as Infragistics.UltraChart.Resources.Appearance.XYZDataPoint; if (xyzDp.Empty) // that's how we tell the "mesh" points from the actual plot data point. { substitutePrimitive(e.Primitive); } } } } } } private XYZDataPoint prevPrimitiveDp = null; private void substitutePrimitive(Infragistics.UltraChart.Core.Primitives.Primitive p) { XYZDataPoint xyzDp; if (p.DataPoint is XYZDataPoint) xyzDp = p.DataPoint as XYZDataPoint; else { return; // obviously not a valid primitive for this kind of handling. } if (!(p is Infragistics.UltraChart.Core.Primitives.Path)) { prevPrimitiveDp = xyzDp; // seems a good idea to "reset" the prevPrimitive return; // another obvious case not fitting the type of primitive this methods concerns itself with. } var pap = p as Infragistics.UltraChart.Core.Primitives.Path; // *** Is this a subsequent primitive for a Datapoint that was just previously done ? if (prevPrimitiveDp != null && xyzDp.ValueX == prevPrimitiveDp.ValueX && xyzDp.ValueY == prevPrimitiveDp.ValueY && xyzDp.ValueZ == prevPrimitiveDp.ValueZ) { // Yes: same Datapoint => Ignore or rather remove this primitive. pap.GraphicsPath.Reset(); return; } var bnd = pap.GraphicsPath.GetBounds(); pap.GraphicsPath.Reset(); pap.GraphicsPath.AddPie(new Rectangle((int)Math.Round(bnd.X, 0), (int)Math.Round(bnd.Y, 0), 7, 7), 0, 360); // pap.GraphicsPath.AddRectangle(new Rectangle((int)Math.Round(bnd.X, 0), (int)Math.Round(bnd.Y, 0), 4, 4)); prevPrimitiveDp = xyzDp; }
And the production of the mesh/grid uses something like
private XYZSeries GetLine(XYZDataPoint start, XYZDataPoint end, int ptCount, Color ptColor) { XYZSeries retVal = new XYZSeries(); var pe = new PaintElement(ptColor); pe.FillOpacity = 76; int intervalCount = ptCount - 1; double deltaX = (end.ValueX - start.ValueX) / intervalCount; double deltaY = (end.ValueY - start.ValueY) / intervalCount; double deltaZ = (end.ValueZ - start.ValueZ) / intervalCount; double curX, curY, curZ; curX = start.ValueX; curY = start.ValueY; curZ = start.ValueZ; string lbl = "mesh"; // whatever for now... for (int i = 0; i < ptCount; i++) { // the "Mesh" points are set as "Empty" as this is characteristic is used to differentiate them from data points // in the plot data. var pt = new XYZDataPoint(curX, curY, curZ, lbl, true); pt.PE = pe; retVal.Points.Add(pt); curX += deltaX; curY += deltaY; curZ += deltaZ; } return retVal; }