I have a requirement to create line chart graphs that involve data from several sources, and I need to identify the sources in the x-axis legend (see the attached image for an example)
How would I achieve this effect with your graphing tools?
Hi Richard,
The easiest way to accomplish this is probably to combine the data sources together and plot one line. An alternative approach could be to set up each source as a separate series, and plot them each at a different location.
Please try this out and let me know if you need any further explanation.
To clarify, I am populating the grid with a NumberSeries object.
How can I use a NumberSeries object to achieve this effect?
You can create a simple legend using the CompositeLegend class and adding to the Chart via something like:
CompositeLegend legend = new CompositeLegend();legend.ChartLayers.Add(chartLayer);chart.CompositeChart.Legends.Add(legend);
You can then position it via the Bounds. Our installed samples browser in the Legacy samples has an example of it. You can also see it statically online here:https://www.infragistics.com/samples/windows-forms/chart/composite-chart-legends
If you are talking about putting just a text string next to the line. You can use the FillSceneGraph to do that, Find the line and add a Text primitive to the left of it would be my guess on doing it that way.
As to removing the DataItem highlighting for the average lines markers, handle the DataItemOver event with something like:private void Chart_DataItemOver(object sender, ChartDataEventArgs e){ if (e.Primitive is DataPoint) { DataPoint pt = (DataPoint)e.Primitive; switch (pt.Series.Key) { case _labelMin: case _labelMax: case _labelAverage: case _labelLCL: case _labelUCL: pt.Caps = PCaps.None; break; default: break; } } }
Let me know if that helps,
Thanks for the help. The mouse-over code works fine, but I am having an issue with the legend labels...
I am trying to position the labels on the right-hand side of the chart, as shown in the attached image, and am using the FillSceneGraph event to do it (code is shown below). However, I can't get the text to show up. What am I doing wrong?
private void OnChart_FillSceneGraph(object sender, FillSceneGraphEventArgs e) { if (sender is UltraChart) { UltraChart chart = (UltraChart)sender; IAdvanceAxis xAxis = (IAdvanceAxis)chart.CompositeChart.ChartLayers[0].ChartLayer.Grid["X"]; IAdvanceAxis yAxis = (IAdvanceAxis)chart.CompositeChart.ChartLayers[0].ChartLayer.Grid["Y"]; if (xAxis == null || yAxis == null) return; int yMin = (int)yAxis.MapMinimum; int yMax = (int)yAxis.MapMaximum; foreach (NumericSeries series in chart.CompositeChart.ChartLayers[0].Series) { if (series.Key == _labelValue) { int searchValue = 0; for (int index = 0; index < series.Points.Count; index++) { NumericDataPoint point = series.Points[index]; if (UtilityHelper.ToInteger(point.Label) > searchValue) { int xValue = (int)xAxis.Map(index); Line line = new Line(new Point(xValue, yMin), new Point(xValue, yMax)); line.PE.Fill = System.Drawing.Color.Gray; line.PE.StrokeWidth = 1; line.lineStyle = new LineStyle(LineCapStyle.NoAnchor, LineCapStyle.NoAnchor, LineDrawStyle.DashDot); e.SceneGraph.Add(line); searchValue++; } } break; } else { int xValue = (int)xAxis.Map(series.Points.Count + 5); int yValue = (int)xAxis.Map(series.Points[series.Points.Count - 1].Value); Text text = new Text(); text.PE.Fill = System.Drawing.Color.Black; text.bounds = new Rectangle(xValue, yValue, 40, 20); text.SetTextString(series.Label); e.SceneGraph.Add(text); } } } }
The primary issue here is how you are computing the xValue and the yValue. First the Map function gives you a relative pixel location of a value in the axes. Where you are computing your xValue. You last value is a series.Points.Count - 1. But you are supplying it series.Points.Count +5. For the xAxis the value is the number of categories, so you are going out the width of 6 categories beyond the edge of your line chart. My guess is you intended something more like:
int xValue = (int)xAxis.Map(series.Points.Count -1 ) + 5;
Which instead goes 5 pixels past the location of your last category value.
Similarly for your yValue, you are mapping it using the xAxis instead of the yAxis, so you are getting completely wrong values. First you would want to use the yAxis, second, this is going to get you the top edge, so you may want to adjust it based on 1/2 your desired height in pixels. Something like:
int yValue = (int)yAxis.Map(series.Points[series.Points.Count - 1].Value) - 10;
Hope that helps,
Thanks for all of your help - we have been able to resolve all of the above issues.
However, there is a remaining irritation. This application requires that the order number associated with the results be displayed below the graph, with vertical lines showing where the breaks are. Formerly I was using the TitleBottom string for that purpose, but a more accurate way is to insert both the lines and the text in the FillSceneGraph event (code is shown below). This works fine as long as the there are a lot of points for each order, but if not, the order numbers overlay each other as shown below.
I am using a text area for the order numbers of an arbitrary size (200), but what I need is to determine the actual size of the order number string, so that I can move it more to the right if necessary. How would I calculate the size of the text string, which, of course, depends on the font type and font size?
private void OnChart_FillSceneGraph(object sender, FillSceneGraphEventArgs e) { if (sender is UltraChart) { UltraChart chart = (UltraChart)sender; IAdvanceAxis xAxis = (IAdvanceAxis)chart.CompositeChart.ChartLayers[0].ChartLayer.Grid["X"]; IAdvanceAxis yAxis = (IAdvanceAxis)chart.CompositeChart.ChartLayers[0].ChartLayer.Grid["Y"]; if (xAxis == null || yAxis == null) return; int yMin = (int)yAxis.MapMinimum; int yMax = (int)yAxis.MapMaximum; List<int> yValues = new List<int>(); foreach (NumericSeries series in chart.CompositeChart.ChartLayers[0].Series) { // Emit order number lines and order numbers if (series.Key == _labelVAL) { string orderNumber = ""; int orderIndex = 0; for (int index = 0; index < series.Points.Count; index++) { NumericDataPoint point = series.Points[index]; if (point.Label != orderNumber) { orderNumber = point.Label; orderIndex++; int xValue = (int)xAxis.Map(index); int yValue = (orderIndex % 2 == 0) ? yMin + 15 : yMin; Text text = new Text(); text.PE.Fill = System.Drawing.Color.Black; text.bounds = new Rectangle(xValue, yValue, 200, 20); text.SetTextString(orderNumber); e.SceneGraph.Add(text); if (orderIndex > 1) { Line line = new Line(new Point(xValue, yMin), new Point(xValue, yMax)); line.PE.Fill = System.Drawing.Color.Gray; line.PE.StrokeWidth = 1; line.lineStyle = new LineStyle(LineCapStyle.NoAnchor, LineCapStyle.NoAnchor, LineDrawStyle.DashDot); e.SceneGraph.Add(line); } } } break; } // Emit the line labels else { int xValue = (int)xAxis.Map(series.Points.Count - 1) + 5; int yValue = (int)yAxis.Map(series.Points[series.Points.Count - 1].Value) - 10; if (series.Label == _labelLSL || series.Label == _labelUSL) yValues.Add(yValue); bool emit = true; if (series.Label == _labelAVG || series.Label == _labelLCL || series.Label == _labelUCL) { foreach (int y in yValues) { if (yValue >= y - 8 && yValue <= y + 8) { emit = false; break; } } } if (emit) { Text text = new Text(); text.PE.Fill = System.Drawing.Color.Black; text.bounds = new Rectangle(xValue, yValue, 40, 20); text.SetTextString(series.Label); e.SceneGraph.Add(text); } } } } }
I would use the Graphics.MeasureString method. So something like:
using (var grfx = chart.CreateGraphics()) { var size = grfx.MeasureString(orderNumber, text.labelStyle.Font); text.bounds = new Rectangle(xValue, yValue, (int)size.Width, (int)size.Height); }