This may be simple, but I'm unable to figure it out. I have a linear graph for each of the OS performance counters (Y->value, x->Time). Now I want to add a straight horizontal line for a threshold Y value in, say, Red so that it shows in the graph along with the actual data. I already know the threshold value for each of my counters.
How do I do this?
I currently do this to show a perfcounter in its own tabPage:
Cursor = Cursors.WaitCursor;
var perfCounter = PerfDictValues.Value.First(pc => pc.Counter == counter);
var tPage = new TabPage((tabControl1.TabPages.Count + 1).ToString());
tPage.Tag = perfCounter;
tPage.Padding = new Padding { All = 8 };
var zedGraph = new ZedGraphControl();
zedGraph.Dock = DockStyle.Fill;
var graphPane = zedGraph.GraphPane;
graphPane.Title.Text = counter;
graphPane.XAxis.Title.Text = String.Format("Max: {0}, Min: {1}, Avg: {2}", perfCounter.Maxm, perfCounter.Min, perfCounter.Average);
var curve = graphPane.AddCurve(counter, perfCounter.PointList, Color.Blue, SymbolType.Diamond); //Want to add a threshold value from perfCounter.Threshold property
graphPane.XAxis.Type = AxisType.Linear;
graphPane.AxisChange();
tPage.Controls.Add(zedGraph);
tabControl1.TabPages.Add(tPage);
tabControl1.SelectedTab = tPage;
grpOutput.Visible = true;
You can draw a simple red line on the graph by adding a LineObj to the GraphObj list, i.e. this draws a horizontal line
double threshHoldY = 2;
LineObj threshHoldLine = new LineObj(
Color.Red,
graphPane.XAxis.Scale.Min,
threshHoldY,
graphPane.XAxis.Scale.Max,
threshHoldY);
graphPane.GraphObjList.Add(threshHoldLine);
Related
I'm going to draw a chart using c# in Windows Form Application. I need to draw a circle on line chart and show this data point value on a label which is nearest data point from the x-axis of the mouse pointer when mouse is moved over the chart area.
I write a code as follows......
private void Chart1_MouseMove(object sender, MouseEventArgs e)
{
HitTestResult result = Chart1.HitTest(e.X, e.Y);
DataPoint nearestPoint = null;
if (prevPosition!=null)
{
Chart1.Series[0].Points[prevPosition.PointIndex].MarkerStyle = MarkerStyle.None;
}
if (result.ChartElementType == ChartElementType.DataPoint)
{
string xValue = DateTime.FromOADate(Chart1.Series[0].Points[result.PointIndex].XValue).ToString("yyyy/MM/dd");
string yValue = Convert.ToString(Chart1.Series[0].Points[result.PointIndex].YValues[0]);
Chart1.Series[0].Points[result.PointIndex].MarkerStyle = MarkerStyle.Circle;
Chart1.Series[0].Points[result.PointIndex].MarkerSize = 7;
Chart1.Series[0].Points[result.PointIndex].MarkerColor = Color.Green;
label1.Text = "Date:" + xValue;
label2.Text = "Price:" + yValue;
prevPosition = result;
}
But this code shows the value and corresponding circle over line when mouse is moved near to the depicted line. When mouse is far away from line but within chart area it does not show the circle and value. I need to draw the circle over the line point nearest to the X-axis of the mouse pointer and show this data on a label
You can find the closest point measuring only the x-values or the y-values or measuring the absolute distances. Or you could simple output the values under the mouse cursor, no matter the points. For this last one see here!
For each of the 1st three options this should help:
Class level variables used to set and reset colors..:
DataPoint dpXaxis = null;
DataPoint dpYaxis = null;
DataPoint dpAbs = null;
And a list of points for keeping the pixel locations of all points:
List<Point> pixPoints = null;
The MouseMove event:
private void chart_MouseMove(object sender, MouseEventArgs e)
{
ChartArea ca = chart.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Series s = chart.Series[0];
if (!s.Points.Any()) return; // no data, no action!
// option 1:
// the values at the mouse pointer:
double valx = ax.PixelPositionToValue(e.X);
double valy = ay.PixelPositionToValue(e.Y);
// the deltas on the x-axis (with index):
var ix = s.Points.Select((x, i) => new {delta = Math.Abs(x.XValue - valx), i})
.OrderBy(x => x.delta).First().i;
var dpx = s.Points[ix];
// option 2:
// the deltas on the y-axis (with index):
var iy = s.Points.Select((x, i) =>
new {delta = Math.Abs(x.YValues[0] - valy), i })
.OrderBy(x => x.delta).First().i;
var dpy = s.Points[iy];
// option 3:
// the absolute distances (with index):
var ind = pixPoints.Select((x, i) =>
new { delta = Math.Abs(x.X - e.X) + Math.Abs(x.Y - e.Y), i}).
OrderBy(x => x.delta).First().i;
// find index of smallest delta
var dpca = s.Points[ind];
// set/reset colors
if (dpXaxis != null) dpXaxis.Color = s.Color;
dpXaxis = dpx;
dpXaxis.Color = Color.LawnGreen;
// set/reset colors
if (dpYaxis != null) dpYaxis.Color = s.Color;
dpYaxis = dpy;
dpYaxis.Color = Color.Cyan;
if (dpAbs != null) dpAbs.Color = s.Color;
dpAbs = dpca;
dpAbs.Color = Color.Red;
}
To find the point closest in both directions you will need to either include the scales of the axes or, probably easier, create a List<PointF> from the DataPoints that hold the locations of the points in pixels. For this use the reverse axis functions. Then I calculate the deltas in a similar fashion as the Linq above.
The list gets filled/updated like so:
List<Point> getPixPoints(Series s, ChartArea ca)
{
List<Point> points = new List<Point>();
foreach (DataPoint dp in s.Points)
{
points.Add(new Point(
(int)ca.AxisX.ValueToPixelPosition(dp.XValue),
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
}
return points;
}
Let's see it at work:
I want to add text(say, annotations) in MS chart(winforms) like (10, 20) , (30, 40) where I already have a scroll bar.
I can able to draw strings(graphics.drawstring) in Chart, but on scrolling the horizontal scroll bar, the text which I have drawn remains static and immovable.
On scrolling the scrollbar, the text which I have drawn also should move along with my horizontal scrolling.
My code follows:
chart2.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
chart2.BorderlineColor = System.Drawing.Color.FromArgb(26, 59, 105);
chart2.BorderlineWidth = 3;
chart2.BackColor = Color.White;
chart2.ChartAreas.Add("chtArea");
chart2.ChartAreas[0].AxisX.Title = "Category Name";
chart2.ChartAreas[0].AxisX.TitleFont =
new System.Drawing.Font("Verdana", 11, System.Drawing.FontStyle.Bold);
chart2.ChartAreas[0].AxisY.Title = "UnitPrice";
chart2.ChartAreas[0].AxisY.TitleFont =
new System.Drawing.Font("Verdana", 11, System.Drawing.FontStyle.Bold);
chart2.ChartAreas[0].BorderDashStyle = ChartDashStyle.Solid;
chart2.ChartAreas[0].BorderWidth = 2;
chart2.ChartAreas["chtArea"].AxisX.ScrollBar.Enabled = true;
chart2.ChartAreas["chtArea"].CursorX.IsUserEnabled = true;
chart2.ChartAreas["chtArea"].CursorX.IsUserSelectionEnabled = true;
chart2.ChartAreas["chtArea"].AxisX.ScaleView.Zoomable = false;
chart2.ChartAreas["chtArea"].AxisX.ScrollBar.IsPositionedInside = true;
chart2.ChartAreas["chtArea"].AxisX.ScaleView.Size = 20;
chart2.ChartAreas[0].AxisX.ScaleView.SmallScrollSizeType = DateTimeIntervalType.Seconds;
chart2.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = 1;
chart2.Legends.Add("UnitPrice");
chart2.Series.Add("UnitPrice");
chart2.Series[0].ChartType = SeriesChartType.Line;
Random rand = new Random();
var valuesArray = Enumerable.Range(0, 500).Select(x => rand.Next(0, 100)).ToArray();
for (int i = 0; i < 500; i++)
{
chart2.Series["UnitPrice"].Points.AddXY(i+10, valuesArray[i]);
}
I tried TextAnnotaions, Line annotations, etc Nothing helped me.
Then I tried drawing dynamic labels inside MS chart also. Labels remain immovable while scrolling horizontal scroll bar.
This code works perfectly in your machine also.
Sounds a lot as if you want to add TextAnnotations.
If you want them to stick with your data points you should anchor them to the points they shall stay with.
Here are a few examples:
// directly anchored to a point
TextAnnotation TA1 = new TextAnnotation();
TA1.Text = "DataPoint 222";
TA1.SetAnchor(chart2.Series["UnitPrice"].Points[222]);
chart2.Annotations.Add(TA1);
// anchored to a point but shifted down
TextAnnotation TA2 = new TextAnnotation();
TA2.Text = "DataPoint 111";
TA2.AnchorDataPoint = chart2.Series["UnitPrice"].Points[111];
TA2.AnchorY = 0;
chart2.Annotations.Add(TA2);
// this one is not anchored on a point:
TextAnnotation TA3 = new TextAnnotation();
TA3.Text = "At 50% width BC";
TA3.AnchorX = 50; // 50% of chart width
TA3.AnchorY = 20; // 20% of chart height, from top!
TA3.Alignment = ContentAlignment.BottomCenter; // try a few!
chart2.Annotations.Add(TA3);
By default they either anchor to DataPoints or are positioned in % of the chart size.
It is also possible to set the positions according to pixel coordinates, but for this you need to calculate the positions each time the chart changes its view!
See here for an example how to transform chart data positions to chart control coordinates and vice versa.. (Not really recommended, though)
I am trying to draw a vertical line that is anchored to a point. I tried to use the height of my Y axis which is fixed to draw the line, but it wasn't centered correctly. So right now I have an infinite line, but that I want is the line to just fill the graph like so
VerticalLineAnnotation lineannot = new VerticalLineAnnotation();
lineannot.AnchorDataPoint = chart.Series[item].Points.Last();
lineannot.LineColor = Color.Red;
lineannot.Width = 3;
lineannot.Visible = true;
lineannot.IsInfinitive = true;
chart.Annotations.Add(lineannot);
IsInfinitive is complemented by ClipToChartArea; you can set the line to be clipped to a ChartArea like this:
lineannot.ClipToChartArea = chart.ChartAreas[item].Name;
assuming item is the right area name or index..
Note that ClipToChartArea takes the name of the chart area!
This is the simplest way to do it.
It is also possible to control an annotation's position and size directly:
// Note that directly after adding points this will return NaN:
double maxDataPoint = chart1.ChartAreas[0].AxisY.Maximum;
double minDataPoint = chart1.ChartAreas[0].AxisY.Minimum;
LineAnnotation annotation2 = new LineAnnotation();
annotation2.IsSizeAlwaysRelative = false;
annotation2.AxisX = chart1.ChartAreas[0].AxisX;
annotation2.AxisY = chart1.ChartAreas[0].AxisY;
annotation2.AnchorY = minDataPoint;
annotation2.Height = maxDataPoint - minDataPoint;;
annotation2.Width = 0;
annotation2.LineWidth = 2;
annotation2.StartCap = LineAnchorCapStyle.None;
annotation2.EndCap = LineAnchorCapStyle.None;
annotation2.AnchorX = 21; // <- your point
annotation2.LineColor = Color.Pink; // <- your color
chart1.Annotations.Add(annotation2);
I have a charting application that has an overlay function which reassigns the 'from' chart series to the 'to' chart using this code :
chTo.Series.Add(chFrom.Series[s]); //Reassign series to new chart
chTo.Legends.Add(chFrom.Legends[s]); //Reassign legend to new chart
Works great. However, I am trying to implement tooltips for the legends and am running into an issue where only the first legend in the chart will show tooltips. When I do a hittest only the first legend is recognized. All subsequent legends, while visible on the chart, aren't 'seen' to the hittest method. I'm thinking this is why the tooltips aren't showing as there is no object to trigger the mouseover event for the tooltip.
I have been unable to find a way to 'expand' the legend area (as detected by the hittest method) to make this work.
Does anyone have any ideas? Thanks!
Responding to King King --
The original legend is created in the same method as the chart thus:
//Create the series legend
chartSel.Series[ySeries.Name].ChartArea = "ChartArea1";
chartSel.Legends.Remove(chartSel.Legends.FindByName("Legend1"));
chartSel.Legends.Add(ySeries.Name);
chartSel.Legends[0].Name = ySeries.Name;
//Format the series legend
chartSel.Legends[ySeries.Name].Docking = Docking.Right;
chartSel.Legends[ySeries.Name].DockedToChartArea = "ChartArea1";
chartSel.Legends[ySeries.Name].Alignment = StringAlignment.Near;
chartSel.Legends[ySeries.Name].IsDockedInsideChartArea = false;
chartSel.Legends[ySeries.Name].LegendStyle = LegendStyle.Table; //.Row;
chartSel.Legends[ySeries.Name].TableStyle = LegendTableStyle.Tall;
chartSel.Legends[ySeries.Name].IsEquallySpacedItems = false;
chartSel.Legends[ySeries.Name].Font = new Font("Segoe UI", 7, FontStyle.Bold);
//chartSel.Legends[ySeries.Name].TextWrapThreshold = 17; // 19;
chartSel.Legends[ySeries.Name].Position.Auto = false;
chartSel.Legends[ySeries.Name].Position.X = 80;
chartSel.Legends[ySeries.Name].Position.Y = 2;
chartSel.Legends[ySeries.Name].Position.Width = 18;
chartSel.Legends[ySeries.Name].Position.Height = 12;
//Format series data point value cell
chartSel.Legends[ySeries.Name].CellColumns.Add(new LegendCellColumn("", LegendCellColumnType.Text, ""));
chartSel.Legends[ySeries.Name].CellColumns[0].Alignment = ContentAlignment.MiddleLeft; //.TopLeft;
chartSel.Legends[ySeries.Name].CellColumns[0].Margins = new System.Windows.Forms.DataVisualization.Charting.Margins(10, 10, 1, 1);
chartSel.Legends[ySeries.Name].CellColumns[0].MinimumWidth = 500;
chartSel.Legends[ySeries.Name].CellColumns[0].MaximumWidth = 500;
chartSel.Legends[ySeries.Name].CellColumns[0].BackColor = Color.FromArgb(120, chartSel.Series[ySeries.Name].Color);
//Format legend cell spacer
chartSel.Legends[ySeries.Name].CellColumns.Add(new LegendCellColumn("", LegendCellColumnType.Text, ""));
chartSel.Legends[ySeries.Name].CellColumns[1].Alignment = ContentAlignment.TopLeft;
chartSel.Legends[ySeries.Name].CellColumns[1].Margins = new System.Windows.Forms.DataVisualization.Charting.Margins(0, 0, 0, 0);
chartSel.Legends[ySeries.Name].CellColumns[1].MinimumWidth = 25;
chartSel.Legends[ySeries.Name].CellColumns[1].MaximumWidth = 25;
chartSel.Legends[ySeries.Name].CellColumns[1].BackColor = Color.Black;
//Format series title cell
chartSel.Legends[ySeries.Name].CellColumns.Add(new LegendCellColumn("", LegendCellColumnType.Text, ySeries.Name));
chartSel.Legends[ySeries.Name].CellColumns[2].Alignment = ContentAlignment.MiddleLeft;
chartSel.Legends[ySeries.Name].CellColumns[2].Margins = new System.Windows.Forms.DataVisualization.Charting.Margins(0, 0, 1, 1);
chartSel.Legends[ySeries.Name].CellColumns[2].MinimumWidth = 1475; //1500;
chartSel.Legends[ySeries.Name].CellColumns[2].MaximumWidth = 1475; //1500;
chartSel.Legends[ySeries.Name].CellColumns[2].ToolTip = ySeries.Name;
After the series and legends have been reassigned (using the code in my original post) I then set the legend values based on the cursor position located by the following hittest in response to a mouse-down event:
pt = activePanel.PointToClient(Control.MousePosition);
ch = activePanel.GetChildAtPoint(pt) as Chart;
if (ch != null)
{
HitTestResult ht = ch.HitTest(e.X, e.Y, false);
if (ht.ChartElementType == ChartElementType.PlottingArea)
{
SetLegendValueText(ht, ch);
}
}
private void SetLegendValueText(HitTestResult ht, Chart ch)
{
//Get the datapoint 'x' index value
int dpIndex = 0;
if (ht != null)
{
switch (ht.ChartElementType)
{
case ChartElementType.DataPoint: //Cursor is on a series line
DataPoint dp = ht.Object as DataPoint;
if (dp != null)
{
dpIndex = ht.PointIndex;
}
break;
case ChartElementType.PlottingArea: //Cursor is somewhere in the plot area of the chart
dpIndex = (int)ht.ChartArea.CursorX.Position;
break;
}
}
//Set legend value and legend tooltip
for (int x = 0; x < ch.Legends.Count; x++) //foreach (Series s in ch.Series)
{
if (dpIndex > 0)
{
ch.Legends[x].Name = "Legend_" + x;
ch.Legends[x].CellColumns[0].Text = ch.Series[x].Points[dpIndex - 1].YValues[0].ToString();
ch.Legends[x].CellColumns[0].ToolTip = ch.Legends[x].CellColumns[0].Text;
}
}
}
So, I end up with the legends looking the way I want them, but the tooltips only show for the first legend item. I've tried to do custom items as well. With them I get the tooltips, but I lose the formatting. This has been driving me crazy for weeks (off and on) and I would really like to move on to other issues. Clearly (to me anyway), I am not doing something right simply because I don't know everything there is to know about the charts, and the MSChart Samples are of very limited benefit.
I'd be most grateful if I could be pointed in the right direction.
I am creating a C# visual studio forms application that uses zedgraph to chart the data that the program collects but I am running into the following issue when plotting the data:
My y-axis values are usually in the 100,000+ range so when zed graph plots the value it labels the y-axis labels with stuff like 0, 10, 15, 20, 25 and then on the y-axis label it will append "(10^3)" to the title and will plot the values accordingly. What I want to do is have it label the y-axis either with values like 0, 10,000, 15,000, 20,000 etc or 0, 10k, 15k, 20k and so on and not have it adjust the y-axis title.
I tried setting YAxis.Scale.MajorStep = double.Parse("10000"); but the only effect that has is to add a ton of more tick lines on the y-axis but no other effect. Here is my code that graphs the data:
private void createGraph()
{
GraphPane myPane = zdc_graph.GraphPane;
myPane.CurveList.Clear();
myPane.GraphObjList.Clear();
myPane.Title.Text = this.monitoredHost.hostName + "\nWorkState[" +
this.monitoredHost.currentWorkState + "]";
myPane.XAxis.Title.Text = "";
myPane.YAxis.Title.Text = "OPS Per Second";
myPane.YAxis.Scale.FontSpec.FontColor = Color.Blue;
myPane.YAxis.Title.FontSpec.FontColor = Color.Blue;
myPane.YAxis.Scale.MaxAuto = true;
myPane.Y2Axis.Title.Text = "Reading";
myPane.Y2Axis.IsVisible = true;
myPane.Y2Axis.Scale.FontSpec.FontColor = Color.Red;
myPane.Y2Axis.Title.FontSpec.FontColor = Color.Red;
myPane.XAxis.Type = AxisType.Date;
myPane.XAxis.Scale.Format = "T";
myPane.XAxis.Scale.MajorUnit = DateUnit.Second;
myPane.YAxis.Scale.Min = 0;
myPane.YAxis.Scale.MajorStep = double.Parse("10000");
myPane.Y2Axis.Scale.Min = 0;
LineItem kpiCurve = myPane.AddCurve("OPS Per Second",
this.monitoredHost.graphKpiList,
Color.Blue,SymbolType.Circle);
LineItem pwrCurve = myPane.AddCurve("Reading",
this.monitoredHost.graphPwrList, Color.Red,
SymbolType.Circle);
kpiCurve.Line.Width = 2.0F;
kpiCurve.Symbol.Size = 4.0F;
kpiCurve.Symbol.Fill = new Fill(Color.White);
pwrCurve.Line.Width = 2.0F;
pwrCurve.Symbol.Size = 4.0F;
pwrCurve.Symbol.Fill = new Fill(Color.White);
pwrCurve.IsY2Axis = true;
myPane.Chart.Fill = new Fill(Color.White, Color.FromArgb(255, 255, 210), -45F);
zdc_graph.AxisChange();
zdc_graph.Refresh();
}
I hope this makes sense. Thanks for the help.
ZedGraph is attempting to detect magnitude and simplify the graph. You can turn this off with the following:
myPane.YAxis.Scale.MagAuto = false;
This will result in y-axis labels like 100000.
If you want to format the label with a separator comma like 100,000:
myPane.YAxis.Scale.Format = "#,#";
Finally, if you prefer to show 100k, you'll need to subscribe to the ScaleFormatEvent and return your own format, like this:
myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(YAxis_ScaleFormatEvent);
string YAxis_ScaleFormatEvent(GraphPane pane, Axis axis, double val, int index)
{
return String.Format("{0}k", val / 1000);
}
I am having similar problem. So applying your method it works on the application but i also want to print out the graph in a PDF file (using MigraDoc) but it does work.
public Bitmap printGraphPane()
{
ZedGraphControl graph = new ZedGraphControl();
GraphPane newGP = myPane.GraphPane;
//newGP.YAxis.Scale.Mag = 0;
//newGP.YAxis.Scale.Format = "#";
//newGP.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(YAxis_ScaleFormatEvent);
Bitmap bit = new Bitmap(newGraph.Width, newGraph.Height);
newGraph.ClientSize = bit.Size;
newGraph.DrawToBitmap(bit, new Rectangle(0, 0, newGraph.Width, newGraph.Height));
return bit;
}