Here is a peace of code which draws 1/2/3/4 (depends on remarks) charts:
private void button1_Click(object sender, EventArgs e)
{
List<int> queue = new List<int>();
queue.Add(1); queue.Add(2); queue.Add(3); queue.Add(4);
chart1.ChartAreas.Add(queue[0].ToString());
chart1.ChartAreas.Add(queue[1].ToString());
chart1.ChartAreas.Add(queue[2].ToString());
chart1.ChartAreas.Add(queue[3].ToString());
chart1.Series.Add("test1");
chart1.Series.Add("test2");
chart1.Series.Add("test3");
chart1.Series.Add("test4");
chart1.Series["test1"].ChartArea = "1";
chart1.Series["test2"].ChartArea = "2";
chart1.Series["test3"].ChartArea = "3";
chart1.Series["test4"].ChartArea = "4";
Random rdn = new Random();
for (int i = 0; i < 50; i++)
{
chart1.Series["test1"].Points.AddXY(rdn.Next(0, 10), rdn.Next(0, 10));
chart1.Series["test2"].Points.AddXY(rdn.Next(0, 10), rdn.Next(0, 10));
chart1.Series["test3"].Points.AddXY(rdn.Next(0, 10), rdn.Next(0, 10));
chart1.Series["test4"].Points.AddXY(rdn.Next(0, 10), rdn.Next(0, 10));
}
chart1.Series["test1"].ChartType = SeriesChartType.FastLine;
chart1.Series["test2"].ChartType = SeriesChartType.FastLine;
chart1.Series["test3"].ChartType = SeriesChartType.FastLine;
chart1.Series["test4"].ChartType = SeriesChartType.FastLine;
}
If I draw two or three charts it appears horizontally something like:
............
............
or
............
............
............
When I add fourth chartarea it starts create second "column"
............ ............
............ ............
What to do to force layout with one column ? I have found "Position" property but couldn't find the way how to use it correctly :(
I think all the alignment properties actually are more about data aligning than about the areas themselves..
Looks like the default Position = Auto will win with its own ideas about how to use the space best, until you switch it off; so I believe that you have to set the Positions of the ChartAreas in code. Here is an example to play with:
float dist = 1f;
float h = 23f;
float w = 80f;
CA1.Position = new ElementPosition(dist, dist * 2 + h * 0, w, h);
CA2.Position = new ElementPosition(dist, dist * 3 + h * 1, w, h);
CA3.Position = new ElementPosition(dist, dist * 4 + h * 2, w, h);
CA4.Position = new ElementPosition(dist, dist * 5 + h * 3, w, h);
The four numbers of an ElementPosition are floats that hold percentages (!) of the total chart area. I have allowed a little distance and set the ChartAreas to 23% height and 80% width.
The good news is that these numbers will hold during resize..
Here is a screenshot (without data):
Isn't is strange that these things are so very hard to find out? (This is my 3rd try at it..)
Related
I am trying to save the triangle I drew and draw again while the previous triangle is still there. I did this in rectangle, square, circle and ellipse and it worked. I don't know why it won't in Triangle. Is there something wrong in the code?
This is how I draw and "save (not working)"
Shape Class
public void DrawTriangle(Color c, int stroke,PointF[] tpoints, float w, Graphics g)
{
this.width = w;
this.strokeThickness = stroke;
this.tPoints = tpoints;
g.InterpolationMode = InterpolationMode.High;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawPolygon(new Pen(c, stroke), tpoints);
}
Form 1
public void DrawTriangle()
{
tC = Color.Red;
strokeTriangle = trackBar_Stroke.Value;
tW = Convert.ToInt32((Convert.ToInt32(tbox_Width.Text) * 96) / 25.4);
tH = (Convert.ToInt32((tW * (Math.Sqrt(3))) / 2));
tX = (pictureBox_Canvass.Width - tW) / 2;
tY = ((pictureBox_Canvass.Width - (tH)) / 2) + tH;
float angle = 0;
t_Points[0].X = tX;
t_Points[0].Y = tY;
t_Points[1].X = (float)(tX + tW * Math.Cos(angle));
t_Points[1].Y = (float)(tY + tW * Math.Sin(angle));
t_Points[2].X = (float)(tX + tW * Math.Cos(angle - Math.PI / 3));
t_Points[2].Y = (float)(tY + tW * Math.Sin(angle - Math.PI / 3));
}
public void AcceptTriangle()
{
Shape shape = new Shape();
tC = Color.Gray;
shape.strokeThickness = strokeTriangle;
shape.width = tW;
shape.x = tX;
shape.y = tY;
shape.tPoints = t_Points;
s._triangle.Add(shape);
}
s.DrawTriangle(tC, strokeTriangle,t_Points, tX, tY, tW, e.Graphics);
This is how I iterate it.
public List<Shape> _triangle = new List<Shape>();
foreach(Shape shapes3 in s._triangle)
{
shapes3.DrawTriangle(shapes3.color, shapes3.strokeThickness, shapes3.tPoints, shapes3.width, e.Graphics);
}
At two points in your code you write an array assignment like this.:
this.tPoints = tpoints;
.. and this:
shape.tPoints = t_Points;
It is a common error to assume that this creates an array with data. It doesn't. All it does is make an array variable point to an array that was there before.
The data are not duplicated. So when you overwrite the data or clear them the 'new' array now points to the changed or cleared data.
So all your objects have the very same points.
To correct create actual copies of the data!
The simplest way is to add a ToArray() call like this:
this.tPoints = tpoints.ToArray();
.. and this:
shape.tPoints = t_Points.ToArray();
To reiterate:
int[] p1 = new int[2] { 23, 42 }; // Now we have one array.
int[] p2 = new int[2] { 3, 2 }; // Now we have two arrays.
p2 = p1; // Now we have only one array again.
p2[1]=1234; // Now p1[1] is 1234
Accepting the Triangle (Saving to the list)
var triangle = new Shape
{
strokeThickness = strokeTriangle,
color = tC,
tPoints = t_Points.ToArray(),
x=tX,
y=tY,
width = tW,
};
s._triangle.Add(triangle);
Iterating through the list
foreach(Shape shapes3 in s._triangle)
{
shapes3.DrawTriangle(shapes3.color, shapes3.strokeThickness,shapes3.tPoints.ToArray(), shapes3.x, shapes3.y, shapes3.width, e.Graphics);
}
There is an input of points with size of n like below:
S = {x1,y1,x2,y2,...,xn,yn}
I want to display scatter graph of S sequence in a picture box. So for transforming them into picture box dimensions, I have normalized them and multiplied them by width and height of picture box with respecting picture box left and top:
waveData= wave.GetWaveData();
normalizedData = GetSignedNormalized();
n = normalizedData.Count;
picW = pictureBox1.Width;
picH = pictureBox1.Height;
picL = pictureBox1.Left;
picT = pictureBox1.Top;
normalizedInPictureBox = new List<float>();
for (int i=0;i< n; i +=2)
{
float px = normalizedData[i];
float py = normalizedData[i+1];
px = px * (picW - picL);
py = py * (picH - picT) ;
normalizedInPictureBox.Add(px);
normalizedInPictureBox.Add(py);
}
Normalize Method is also:
public List<float> GetSignedNormalized()
{
List<float> data = new List<float>();
short max = waveData.Max();
int m = waveData.Count;
for(int i=0;i< m; i++)
{
data.Add((float)waveData[i] / (float)max);
}
return data;
}
Now I am thinking normalizedInPictureBox List contains vertices in the range of picture box, and here is the code for drawing them on picture box:
In the paint method of picture box:
Graphics gr = e.Graphics;
gr.Clear(Color.Black);
for(int i=0;i< n; i +=2)
{
float x = normalizedInPictureBox[i] ;
float y = normalizedInPictureBox[i+1];
gr.FillEllipse(Brushes.Green, new RectangleF(x, y, 2.25f, 2.25f));
}
But the result is shown below:
I don't Know whats going wrong here , but I think the graph should be horizontal not diagonal ,the desire result is something like this:
I know that I can transform it to center of picture box after this. but How can change my own result to the desire one?
Thanks in advance.
I don't really know why your code doesn't work correctly without having a look at the actual data and playing around with it, but having done chart drawing before, I suggest you go the full way and clearly define your axis ranges and do proper interpolating. It get's much clearer from there.
Here is what I came up with
static Bitmap DrawChart(float[] Values, int Width, int Height)
{
var n = Values.Count();
if (n % 2 == 1) throw new Exception("Invalid data");
//Split the data into lists for easy access
var x = new List<float>();
var y = new List<float>();
for (int i = 0; i < n - 1; i += 2)
{
x.Add(Values[i]);
y.Add(Values[i + 1]);
}
//Chart axis limits, change here to get custom ranges like -1,+1
var minx = x.Min();
var miny = y.Min();
var maxx = x.Max();
var maxy = y.Max();
var dxOld = maxx - minx;
var dyOld = maxy - miny;
//Rescale the y-Range to add a border at the top and bottom
miny -= dyOld * 0.2f;
maxy += dyOld * 0.2f;
var dxNew = (float)Width;
var dyNew = (float)Height;
//Draw the data
Bitmap res = new Bitmap(Width, Height);
using (var g = Graphics.FromImage(res))
{
g.Clear(Color.Black);
for (int i = 0; i < x.Count; i++)
{
//Calculate the coordinates
var px = Interpolate(x[i], minx, maxx, 0, dxNew);
var py = Interpolate(y[i], miny, maxy, 0, dyNew);
//Draw, put the ellipse center around the point
g.FillEllipse(Brushes.ForestGreen, px - 1.0f, py - 1.0f, 2.0f, 2.0f);
}
}
return res;
}
static float Interpolate(float Value, float OldMin, float OldMax, float NewMin, float NewMax)
{
//Linear interpolation
return ((NewMax - NewMin) / (OldMax - OldMin)) * (Value - OldMin) + NewMin;
}
It should be relatively self explanatory. You may consider drawing lines instead of single points, that depends on the look and feel you want to achive. Draw other chart elements to your liking.
Important: The y-Axis is actually inversed in the code above, so positive values go down, negative go up, it is scaled like the screen coordinates. You'll figure out how to fix that :-)
Example with 5000 random-y points (x is indexed):
I want to increase the height of the curve but its left and right position should remain same. Just want to lift up from center to give it a shape like curve as height changes.
Pen blackPen = new Pen(Color.Black, 3);
// Create coordinates of rectangle to bound ellipse.
int x = 93;
int y = 136;
int width = 320;
int height = 50;
// Create start and sweep angles on ellipse.
int startAngle = 0;
int sweepAngle = -180;
// Draw arc to screen.
e.Graphics.DrawArc(blackPen, x, y, width, height, startAngle, sweepAngle);
In the most direct way your problem can be solved like this:
int change = 0;
e.Graphics.DrawArc(blackPen, x, y-change, width, height+change, startAngle, sweepAngle);
By increasing the variable change the ellipse will curve up more and more:
private void button1_Click(object sender, EventArgs e)
{
change += 10;
panel1.Invalidate();
}
But maybe you want more control over the shape? Let's have a look at the options:
Here are examples of the three 'curve' drawing methods:
Curves, Beziers & Ellipses
And here is the code to draw that image.
Please ignore the Graphics.xxxTransform calls! They only are meant to shift the curves a little bit upwards so they don't overlap too much to see them properly.
Also note that the curves in the first image are not completely convex. See the last part of the answer to see a DrawCurve call that avoids the concave segments!
The important part are the Points! And just as the comments suggest, in the third part the ellipses are being changed by making the height larger and moving the top of the Rectangle up by the same amount.
The complexity DrawArc of and DrawCurve is pretty much equal; both are controlled by four integers with a rather clear meaning: They either make one rectangle or the corners of a symmetrical triangle. (Plus one counterpoint for the convex call.)
DrawBezier is more complex, especially since the controls point(s) are not actually on the resulting curve. They can be thought of force vectors that pull the line into a curved shape and are harder to calculate.
private void panel1_Paint(object sender, PaintEventArgs e)
{
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 0; i< 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50 + i * 10);
e.Graphics.DrawCurve(Pens.Maroon, new[] { a, b, c }, 0.7f);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point pc = new Point(350, 200 - i * 10);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, pc, pc, pb);
}
e.Graphics.ResetTransform();
int x = 500;
int y0 = 200;
int w = 200;
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - i * 10, w, 10 + i * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
}
e.Graphics.ResetTransform();
}
Notes:
The Curve (1st image) can be further controlled by the Tension parameter. The lower the tension the more pointed it gets, approaching 1f it makes the curve broader..
The Bezier curve (2nd image) is using only one control point. (Twice.) The curve gets a little pointed this way. You can make it broader and broader by using two different points the move apart little by little..
The Ellipse can't be controlled; it will always fill the bounding Rectangle.
Here is an example of varying the Curves and the Beziers:
The Curves are drawn with varying Tensions. Also I have used an overload that helps to get rid of the concave part at the start and end of the curve. The trick is to add a suitable extra point to the start and end and to tell the DrawCurve to leave out these 1st and last segments.
The simplest point to use (for both ends actually) is the counterpoint of the one at the top.
The Beziers are drawn using two control points, moving out and up a little.
Here is the code for the variations:
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 1; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50);
Point b0 = new Point(b.X, a.Y + (a.Y - b.Y));
e.Graphics.DrawCurve(Pens.Maroon, new[] { b0, a, b, c, b0 }, 1, 2, 0.1f * i);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point ca = new Point(350 - i * 9, 100 - i * 5);
Point cb = new Point(350 + i * 9, 100 - i * 5);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, ca, cb, pb);
}
e.Graphics.ResetTransform();
here was the solution just increase value of y0 as u increase the value of
y0-i here i=20
int x = 96;
int y0 = 260;
int w = 320;
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - 20 * 10, w, 10 + 20 * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
e.Graphics.ResetTransform();
I'm creating a silverlight application where I have to dynamically create buttons. But I need to place them in a circle around the button that I click to generate the other buttons (picture here, the buttons should go on the black line surrounding the 'test project' button)
I don't know how many buttons will be generated each time but I do know the size of each button is static. I'm not quite sure how to do this. Currently my button creation is as follows
foreach (Item a in itemList)
{
Button newButton = new Button();
newButton.Height = 50;
newButton.Width = 50;
newButton.Content = a.getName();
newButton.Click += new RoutedEventHandler(addedClick);
newButton.HorizontalAlignment = HorizontalAlignment.Left;
newButton.VerticalAlignment = VerticalAlignment.Top;
newButton.Margin = new Thickness(0, 0, 0, 0);
newButton.Style = (Style)Application.Current.Resources["RB"];
buttons.Add(newButton);
}
My biggest problem is that I'm not quite sure how to get the center point of the 'test project' button.
EDIT: Okay, now that I have a set of coordinates for each button, how exactly do I go about placing them? I'm not sure how to use a canvas. I tried to set one up but it keeps acting like a stackpanel (no .setLeft/.setTop).
You mean something like the circle equation:
Double distanceFromCenter = 5;
Double angleInDegrees = 90;
Double angleAsRadians = (angleInDegrees* Math.PI) / 180.0;
Double centerX = 100;
Double centerY = 100;
Double x = centerX + Math.Cos(angleAsRadians) * distanceFromCenter;
Double y = centerY + Math.Sin(angleAsRadians) * distanceFromCenter;
that should give you a point that is distanceFromCenter units away from (centerX, center), at an angle of 90-degrees. Note this only works with radians so we have to convert to radians.
var radius = 100;
var angle = 360 / itmeList.Count * Math.PI / 180.0f;
var center = new Point(100, 100);
for (int i = 0; i < itemList.Count; i++)
{
var x = center.X + Math.Cos(angle * i) * radius;
var y = center.Y + Math.Sin(angle * i) * radius;
Button newButton = new Button();
newButton.RenderTransformOrigin = new Point(x, y);
newButton.Height = 50;
newButton.Width = 50;
newButton.Content = a.getName();
newButton.Click += new RoutedEventHandler(addedClick);
newButton.HorizontalAlignment = HorizontalAlignment.Left;
newButton.VerticalAlignment = VerticalAlignment.Top;
newButton.Margin = new Thickness(0, 0, 0, 0);
newButton.Style = (Style)Application.Current.Resources["RB"];
buttons.Add(newButton);
}
Assuming you want your buttons evenly spaced on the circle, you should first generate the list of angles you want them at. E.g.
double eachSection = 2 * Math.PI / count;
var anglesInRadians = Enumerable.Range(0, count).Select(x => x * eachSection);
Then use this formula to find the x/y coordinates of each angle, and use a Canvas or something to position the buttons in those positions
public static Point PointOnCircle(double radius, double angleInRadians, Point origin)
{
double x = radius * Math.Cos(angleInRadians) + origin.X;
double y = radius * Math.Sin(angleInRadians) + origin.Y;
return new Point(x, y);
}
I'm using ZedGraph in my project and its awesome! But there is still one thing I can't figure out. Im looking for some possibility of plotting description of LineItem directly in chart, like on fig.:
http://www.imagesup.net/?di=113548312290
I tried to use TextObj, but still I have a problem correctly calculate the angle, it doesnt correspond to the slope of line. Can anyone tell my whats wrong? PS: maybe this could be caused by different ranges of X- and Y-Axis, or different length of these axes on the screen?
PointPair ptA = new PointPair(0, 100);
PointPair ptB = new PointPair(100, 0);
PointPairList ppl = new PointPairList();
ppl.Add(ptA);
ppl.Add(ptB);
LineItem myCurve = zedGraphControl1.GraphPane.AddCurve(string.Empty, ppl, Color.Red, SymbolType.Circle);
// centre of line
PointPair pt = new PointPair(0.5 * (ptA.X + ptB.X), 0.5 * (ptA.Y + ptB.Y));
TextObj text = new TextObj("desc", pt.X, pt.Y, CoordType.AxisXYScale, AlignH.Center, AlignV.Center);
text.ZOrder = ZOrder.A_InFront;
double dX = ptB.X - ptA.X;
double dY = ptB.Y - ptA.Y;
float alfa = (float)(Math.Atan2(dY, dX) * (180.0 / Math.PI));
text.FontSpec.Angle = alfa;
zedGraphControl1.GraphPane.GraphObjList.Add(text);
zedGraphControl1.AxisChange();
zedGraphControl1.Invalidate();
zedGraphControl1.Refresh();
// Call this method from the Form_Load method, passing your ZedGraphControl
public void CreateChart( ZedGraphControl zgc )
{
GraphPane myPane = zgc.GraphPane;
// Set the titles and axis labels
myPane.Title.Text = "Demo of Labeled Points";
myPane.XAxis.Title.Text = "Time, Seconds";
myPane.YAxis.Title.Text = "Pressure, Psia";
// Build a PointPairList with points based on Sine wave
PointPairList list = new PointPairList();
const int count = 15;
for ( int i = 0; i < count; i++ )
{
double x = i + 1;
double y = 21.1 * ( 1.0 + Math.Sin( (double)i * 0.15 ) );
list.Add( x, y );
}
// Hide the legend
myPane.Legend.IsVisible = false;
// Add a curve
LineItem curve = myPane.AddCurve( "label", list, Color.Red, SymbolType.Circle );
curve.Line.Width = 2.0F;
curve.Line.IsAntiAlias = true;
curve.Symbol.Fill = new Fill( Color.White );
curve.Symbol.Size = 7;
// Fill the axis background with a gradient
myPane.Chart.Fill = new Fill( Color.White, Color.FromArgb( 255, Color.ForestGreen ), 45.0F );
// Offset Y space between point and label
// NOTE: This offset is in Y scale units, so it depends on your actual data
const double offset = 1.0;
// Loop to add text labels to the points
for ( int i = 0; i < count; i++ )
{
// Get the pointpair
PointPair pt = curve.Points[i];
// Create a text label from the Y data value
TextObj text = new TextObj( pt.Y.ToString( "f2" ), pt.X, pt.Y + offset,
CoordType.AxisXYScale, AlignH.Left, AlignV.Center );
text.ZOrder = ZOrder.A_InFront;
// Hide the border and the fill
text.FontSpec.Border.IsVisible = false;
text.FontSpec.Fill.IsVisible = false;
//text.FontSpec.Fill = new Fill( Color.FromArgb( 100, Color.White ) );
// Rotate the text to 90 degrees
text.FontSpec.Angle = 90;
myPane.GraphObjList.Add( text );
}
// Leave some extra space on top for the labels to fit within the chart rect
myPane.YAxis.Scale.MaxGrace = 0.2;
// Calculate the Axis Scale Ranges
zgc.AxisChange();
}
Source: http://zedgraph.dariowiz.com/index9769.html?title=Point_Label_Demo
I have the same issue. Here's a partial solution I worked up in VB; should be easy enough to translate to C#:
' Calc deltas for x and y
Dim dX As Double = ptB.X - ptA.X
Dim dY As Double = ptB.Y - ptA.Y
' compensate delta x for graph resizing, which affects line slopes
Dim resizeCompensation As Double = 1.0 / (myPane.XAxis.Scale.Max - myPane.XAxis.Scale.Min)
dX = dX * resizeCompensation
' now calculate angle
Dim alfa As Double = Math.Atan2(dY, dX) * (180.0 / Math.PI)
text.FontSpec.Angle = alfa
While the above worked well enough for my purposes, a more comprehensive solution might be had here:
http://sourceforge.net/p/zedgraph/discussion/392232/thread/0d261bc7/