Is it possible to set width of existing Zedgraph line? Most examples I saw demonstrate following method:
LineItem myCurve1 = myPane.AddCurve("Sine Wave", spl1, Color.Blue, SymbolType.None);
myCurve1.Line.Width = 3.0F;
But as I see it can be done only at moment of adding new curve. Most obvious solution is to create List and add all curves there to access them later. I wonder is it right way or I am at wrong track?
UPDATE
My situation is following. I have several line curves and list of them in listBox. I want to make currently selected curve bold. That is why I need access to existing curves.
LineItem has constructors that support setting the line width, so you can create the curve first and then add it to your GraphPane, like this:
LineItem myCurve1 =
new LineItem("Sine Wave", spl1, Color.Blue, SymbolType.None, 3.0f);
myPane.CurveList.Add(myCurve1);
Which approach to recommend is more a matter of taste, I think, but personally I prefer to initialize my object as much as possible before adding it to any collection.
UPDATE If you later on would like to access your specific curve item, simply retrieve it from myPane.CurveList. The objects in CurveList are CurveItem:s, so you may need to cast to LineItem to modify line specific properties.
example
((LineItem)zedGraphControl1.GraphPane.CurveList[1]).Line.Width = 3.0F;
Pane in ZedGraph already have list of curves. Maybe you need redraw your pane after changing curves?
Related
I'm trying to make something similar to paint. I'm trying to figure out how make different brush styles. Like in Paint 3D you get a certain line fills when using the pen tool vs using the paint brush tool.
I have no idea where to even start. I've spent a good portion of the day looking through documentations, and watching YouTube videos. I'm more lost than when I started. The closest thing I came across was line caps, but that's definitely not what I'm looking for.
!!See the UPDATE below!!
Hans' link should point you in the right direction, namely toward TextureBrushes.
To help you further here a few points to observe:
TextureBrush is a brush, not a pen. So you can't follow a path, like the mouse movements to draw along that curve. Instead, you need to find an area to fill with the brush.
This also implies that you need to decide how and when to trigger the drawing; basic options are by time and/or by distance. Usually, the user can set parameters for these often called 'flow' and 'distance'..
Instead of filling a simple shape and drawing many of those, you can keep adding the shapes to a GraphicsPath and fill that path.
To create a TextureBrush you need a pattern file that has transparency. You can either make some or download them from the web where loads of them are around, many for free.
Most are in the Photoshop Brush format 'abr'; if they are not too recent (<=CS5) you can use abrMate to convert them to png files.
You can load a set of brushes to an ImageList, set up for large enough size (max 256x256) and 32bpp to allow alpha.
Most patterns are black with alpha, so if you want color you need to create a colored version of the current brush image (maybe using a ColorMatrix).
You may also want to change its transparency (best also with the ColorMatrix).
And you will want to change the size to the current brush size.
Update
After doing a few tests I have to retract the original assumption that a TextureBrush is a suitable tool for drawing with textured tips.
It is OK for filling areas, but for drawing free-hand style it will not work properly. There are several reasons..:
one is that the TextureBrush will always tile the pattern in some way, flipped or not and this will always look like you are revealing one big underlying pattern instead of piling paint with several strokes.
Another is that finding the area to fill is rather problematic.
Also, tips may or may not be square but unless you fill with a rectangle there will be gaps.
See here for an example of what you don't want at work.
The solution is really simple and much of the above still applies:
What you do is pretty much regular drawing but in the end, you do a DrawImage with the prepared 'brush' pattern.
Regular drawing involves:
A List<List<Point>> curves that hold all the finished mouse paths
A List<Point> curentCurve for the current path
In the Paint event you draw all the curves and, if it has any points, also the current path.
For drawing with a pattern, it is necessary to also know when to draw which pattern version.
If we make sure not to leak them we can cache the brush patterns..:
Bitmap brushPattern = null;
List<Tuple<Bitmap,List<Point>>> curves = new List<Tuple<Bitmap,List<Point>>>();
Tuple<Bitmap, List<Point>> curCurve = null;
This is a simple/simplistic caching method. For better efficiency you could use a Dictionary<string, Bitmap> with a naming scheme that produces a string from the pattern index, size, color, alpha and maybe a rotation angle; this way each pattern would be stored only once.
Here is an example at work:
A few notes:
In the MouseDown we create a new current curve:
curCurve = new Tuple<Bitmap, List<Point>>(brushPattern, new List<Point>());
curCurve.Item2.Add(e.Location);
In the MouseUp I add the current curve to the curves list:
curves.Add(new Tuple<Bitmap, List<Point>>(curCurve.Item1, curCurve.Item2.ToList()));
Since we want to clear the current curve, we need to copy its points list; this is achieved by the ToList() call!
In the MouseMove we simply add a new point to it:
if (e.Button == MouseButtons.Left)
{
curCurve.Item2.Add(e.Location);
panel1.Invalidate();
}
The Paint goes over all curves including the current one:
for (int c = 0; c < curves.Count; c++)
{
e.Graphics.TranslateTransform(-curves[c].Item1.Width / 2, -curves[c].Item1.Height / 2);
foreach (var p in curves[c].Item2)
e.Graphics.DrawImage(curves[c].Item1, p);
e.Graphics.ResetTransform();
}
if (curCurve != null && curCurve.Item2.Count > 0)
{
e.Graphics.TranslateTransform(-curCurve.Item1.Width / 2, -curCurve.Item1.Height / 2);
foreach (var p in curCurve.Item2)
e.Graphics.DrawImage(curCurve.Item1, p);
e.Graphics.ResetTransform();
}
It makes sure the patterns are drawn centered.
The ListView is set to SmallIcons and its SmallImageList points to a smaller copy of the original ImageList.
It is important to make the Panel Doublebuffered! to avoid flicker!
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Btw: The above quick and dirty example has only 200 (uncommented) lines. Adding brush rotation, preview, a stepping distance, a save button and implementing the brushes cache takes it to 300 lines.
I am currently working on a module to create charts to display data.
I use System.Windows.Forms.DataVisualization.Charting.Chart.
I have two striplines showing the the average result we got and another one showing what we want.
So far I was really happy with what I had but I want to add explicit arrow to point these lines. And I can't figure out how to do it.
I saw that Line Annotation might be of help but I couldn't find a way to do what I wanted.
Here is an example of what I would like to do :
You have a choice of using
Annotations or
GDI+ drawing
In both cases the challenge is to get the positions right.
The more natural way to go is using Annotations, so let's look at this first:
There are various types but different capabilities; text can be displayed by RectangleAnnotation or a TextAnnotation. Lines and arrowheads can only be displayed by LineAnnotations. So we need a pair of Line- plus TextAnnotation for each of your two lines.
Like many other chart elements annotations are positioned in percentages of their respective containers; this makes things rather tricky at times.
To place a line annotation all to the right of the chart you could set its X property to 100; to let it go to the left you set the width to a negative number. The problems are starting after that..
To find out where the right edge of the ChartArea is you need to code the Pre- or PostPaint event and use the ToRectangleF method.
To find out the y-value you will want to calculate it from a data value; for this you can use the AxisY.ValueToPixelPosition method, which converts to pixels, from which you can calculate the percantage using the chart's ClientArea along with the ChartArea percentage size.
Complicated? Yup. Annotations get a lot simpler to use if you can anchor them to a certain DataPoint; but yours are outside the ChartArea..
Here is a function that should help when doing the calculations:
double PercentFromValue(Chart chart, ChartArea ca, double value)
{
Axis ay = ca.AxisY;
RectangleF car = ca.Position.ToRectangleF();
double py = ay.ValueToPixelPosition(value);
int caHeight = (int)(chart.ClientRectangle.Height * car.Height / 100f);
return 100d * py / caHeight;
}
Note that it will only work reliably when called from of of the Pre/PostPaint events..
So this is an example of a PrePaint event that positions a LineAnnotation lAnn:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
Rectangle cr = chart1.ClientRectangle;
ChartArea ca = chart1.ChartAreas[0];
RectangleF car = ca.Position.ToRectangleF();
lAnn.Width = car.Width - lAnn.X;
lAnn.Y = PercentFromValue(chart1, ca, someDataValue);
}
When you insert a valid DataPoint YValue the starting y-position will be set. You can play with it until you find a nice combination of setting the four position properties..
When creating and adding the four(!) annotations you may want to keep class level references, so you won't have to refer to them from the Annotations collection..
For the LineAnnotation you will want to set the linewidth, color and the capstyle, either using the EndCap or the StartCap:
lAnn.EndCap = LineAnchorCapStyle.Arrow;
GDI+ drawing is more straight-forward, provided you know where you want to draw the lines and the text.
It is also done in the PrePaint event, again using the ValueToPixelPosition to find the pixelposition of the two data lines.. Other than that is all the usual stuff with Graphics.DrawLine, a Pen with and Start- or EndCap and Graphics.DrawString or maybe TextRenderer.DrawText..
I'm frankly not sure which way I would choose..
create triangle image and set the marker image as:
Chart1.Series.Points.AddXY(0, 10);
Chart1.Series.Points.AddXY(20,10);
Chart1.Series[0].Points[1].MarkerImage = "TriangleImage.bmp";
Chart1.Series[0].Points[1].MarkerImageTransparentColor = Color.White;
or
Chart1.Series[0].Points[1].MarkerStyle = MarkerStyle.Triangle;
I have a strange thing happening while using ZedGraph.
I am using the same item to add multiple curves. Like:
ZedGraph LineItem curve_3;
curve_3 = pane.AddCurve("", xx_1, yy, xxyy);
I call the above lines multiple times to add multiple points. But when I remove the curve, only the last added curve gets removed and left all stays on the pane.
this.zedGraph_RenderedTrack.GraphPane.CurveList.Remove(curve_3);
I am not finding a way that will clear all the curves added. Is there a to do it?
My actual requirement is that I have to add the different lines dynamically on the pane, but I don't need to display the label information and all of them should be plotted by a single click and removed by a single click.
You are holding only the last curve in this code:
ZedGraph LineItem curve_3;
curve_3 = pane.AddCurve("", xx_1, yy, xxyy);
Use collection like List<LineItem> to remember all the curves.
List<LineItem>.foreach(r => this.zedGraph_RenderedTrack.GraphPane.CurveList.Remove(r);
)
If you want to remove all curves from your graph pane, simply use the CurveList.Clear() method:
this.zedGraph_RenderedTrack.GraphPane.CurveList.Clear();
Original Question
I'm using Microsoft Chart Control to display some data points as a Line. I have a Legend with custom items used to display calculated information about the line (mean average and others).
Now I've enabled IsUserSelectionEnabled which allows the user to "zoom into" a range of values and I want the legend items to be calculated on only the data points that are currently in view.
I can use the AxisViewChanged event to be notified of the view change, but what I can't figure out is how to enumerate only those DataPoint currently in view.
Update
The zoom isn't going to work for my purpose. What I discovered is that the NewPosition and NewSize properties of the AxisViewChanged event do in fact contain the precise area selected by the user, but the resulting zoom contains points outside of that area. I need more precision than that. What I need are two cursors, but the control only gives you one.
So my question now is: How do I customize this thing to add another cursor? I'm not asking just yet and if I do I'll start a new question.
Though I do still need to figure out how to translate client coords into data coords...
Updated again
I found the coord translation functions right on the Axis. Seems obvious in retrospect.
ChartArea.Axis.PixelPositionToValue (for whichever axis you need)
ChartArea.Axis.ValueToPixelPosition
I am new to ZedGraph. So far I could draw curves and bars. How can I display just the dots without connecting them?
I am using C# and Windows Forms.
The Scatter Plot Demo from ZedGraph's site shows how to do it. There is an IsVisible property on the Line which you set to false to only show the points.
LineItem myCurve = myPane.AddCurve("Title", list, Color.Black, SymbolType.Diamond);
myCurve.Line.IsVisible = false;
I think your solution is on The Code Project. Have a look at the part where he talks about "The Fill class". There is a dot-only example.