Chart: issue with stacked areas order - c#

I would like to display the 2 different stacked area elements according to their parameters. But the chart area displays it not as specified and puts the second block at the top right corner of the first stacked area. They should be displayed side by side not stacked.
...
using System.Windows.Forms.DataVisualization.Charting;
namespace Gantt_Tool
{
public partial class ReadModel : Form
{
public ReadModel()
{
InitializeComponent();
CreateChart();
}
private void ReadModel_Load(object sender, EventArgs e)
{
}
private void CreateChart()
{
chart1.Series.Add($"a");
chart1.Series[$"a"].Points.Add(new DataPoint(0, 2));
chart1.Series[$"a"].Points.Add(new DataPoint(2, 2));
chart1.Series[$"a"].ChartType = SeriesChartType.StackedArea;
chart1.Series.Add($"b");
chart1.Series[$"b"].Points.Add(new DataPoint(2, 3));
chart1.Series[$"b"].Points.Add(new DataPoint(5, 3));
chart1.Series[$"b"].ChartType = SeriesChartType.StackedArea;
}
}
How can I set the blocks to a side by side order or placed freely? And how can I get unfilled rectangles?
Update: Here is an example of how it should look like:

From your comments, I take that you want to have a chart with freely-placed, unfilled rectangles and labels.
None of the MSChart types will do that.
Here is how to use a Point chart with a few lines of owner-drawing. Note how nicely this will behave when resizing the chart...
Here is the setup:
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
ax.Maximum = 9; // pick or calculate
ay.Maximum = 6; // minimum and..
ax.Interval = 1; // maximum values..
ay.Interval = 1; // .. needed
ax.MajorGrid.Enabled = false;
ay.MajorGrid.Enabled = false;
Series s1 = chart1.Series.Add("A");
s1.ChartType = SeriesChartType.Point;
Now we add your five boxes. I use a sepcial function that adds the points and stuffs the box size into the Tag of each point..:
AddBox(s1, 1, 0, 3, 1, "# 1");
AddBox(s1, 2, 1, 2, 2, "# 2");
AddBox(s1, 4, 0, 4, 2, "# 3");
AddBox(s1, 4, 2, 2, 2, "# 4");
AddBox(s1, 4, 4, 1, 1, "# 5");
int AddBox(Series s, float x, float y, float w, float h, string label)
{
return AddBox(s, new PointF(x, y), new SizeF(w, h), label);
}
int AddBox(Series s, PointF pt, SizeF sz, string label)
{
int i = s.Points.AddXY(pt.X, pt.Y);
s.Points[i].Tag = sz;
s.Points[i].Label = label;
s.Points[i].LabelForeColor = Color.Transparent;
s.Points[i].Color = Color.Transparent;
return i;
}
The drawing is also simple; only the use of the Axes function ValueToPixelPosition is noteworthy..:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (chart1.Series[0].Points.Count <= 0) return;
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
Graphics g = e.ChartGraphics.Graphics;
using (StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center})
foreach (Series s in chart1.Series)
{
foreach (DataPoint dp in s.Points)
{
if (dp.Tag == null) break;
SizeF sz = (SizeF)dp.Tag;
double vx2 = dp.XValue + sz.Width;
double vy2 = dp.YValues[0] + sz.Height;
int x1 = (int)ax.ValueToPixelPosition(dp.XValue);
int y1 = (int)ay.ValueToPixelPosition(dp.YValues[0]);
int x2 = (int)ax.ValueToPixelPosition(vx2);
int y2 = (int)ay.ValueToPixelPosition(vy2);
Rectangle rect = Rectangle.FromLTRB(x1, y2, x2, y1);
using (Pen pen = new Pen(s.Color, 2f))
g.DrawRectangle(pen, rect);
g.DrawString(dp.Label, Font, Brushes.Black, rect, fmt);
}
}
}
Here is a little Linq to calculate the Minimum and Maximum values for the Axes to hold just the right size; chart won't do it by itself since the size in the tags of the points is not known...
private void setMinMax(Chart chart, ChartArea ca)
{
var allPoints = chart.Series.SelectMany(x => x.Points);
double minx = allPoints.Select(x => x.XValue).Min();
double miny = allPoints.Select(x => x.YValues[0]).Min();
double maxx = allPoints.Select(x => x.XValue + ((SizeF)x.Tag).Width).Max();
double maxy = allPoints.Select(x => x.YValues[0] + ((SizeF)x.Tag).Height).Max();
ca.AxisX.Minimum = minx;
ca.AxisX.Maximum = maxx;
ca.AxisY.Minimum = miny;
ca.AxisY.Maximum = maxy;
}

Related

Saving triangle object in a list and paint again

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);
}

Resize graphics to fit in PictureBox

I need to draw a Fermat spiral in C#. I did it, but I want my drawing to be filled in PictureBox, no matter how big real size is.
public void DrawSpiral(double delta, double numCycles, int oX, int oY, SpiralType spiralType, Color color, Graphics g)
{
double a = Convert.ToInt32(textBox1.Text);
Pen p = new Pen(color, 1);
double prevX = oX;
double prevY = oY;
double X = oX;
double Y = oY;
double fi = Convert.ToInt32(textBox2.Text);
double radius = 0;
while (fi <= (numCycles * 360))
{
fi += delta;
if (spiralType == SpiralType.FermaPlus)
{
radius = a * Math.Sqrt(fi);
}
else if (spiralType == SpiralType.FermaMinus)
{
radius = -a * Math.Sqrt(fi);
}
prevX = X;
prevY = Y;
X = (radius * Math.Cos(fi / 180 * Math.PI)) + oX;
Y = (radius * Math.Sin(fi / 180 * Math.PI)) + oY;
g.DrawLine(p, (float)prevX, (float)prevY, (float)X, (float)Y);
}
}
private void DrawButton_Click(object sender, EventArgs e)
{
pictureBox1.Refresh();
Graphics g = pictureBox1.CreateGraphics();
DrawSpiral(2, 5, 150, 150, SpiralType.FermaPlus, Color.Blue, g);
DrawSpiral(2, 5, 150, 150, SpiralType.FermaMinus, Color.Red, g);
}
So, what should I do to have my drawing to be full filled in the PictureBox.
Here is one way to do it:
Change the signature of the DrawSpiral to include the ClientSize of the PictureBox instead of some center coordinates:
public void DrawSpiral(double delta, double numCycles, int spiralType,
Color color, Graphics g, Size sz)
Then calculate the center dynamically:
int oX = sz.Width / 2;
int oY = sz.Height / 2;
double prevX = oX;
double prevY = oY;
double X = oX;
double Y = oY;
Next calculate the factor a :
a = sz.Width / 2 / Math.Sqrt( numCycles * 360);
Finally call the method only from the Paint event, passing out the valid Graphics object:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Size sz = pictureBox1.ClientSize;
DrawSpiral(2, 5, SpiralType.FermaPlus, Color.Blue, g, sz);
DrawSpiral(2, 5, SpiralType.FermaMinus, Color.Red, g, sz);
}
Upon resizing the PictureBox it will still fill the area with the same number of loops..:
A few notes:
The quality and performance could be improved by first collecting the data in a List<Point> pointsand then using DrawLines(pen, points.ToArray())
I used just the width when I calculated the factor a. Use Math.Min(sz.Width, sz.Height) to always fit it into a non-square box!
I left your offset calculation in place; but you could instead do a g.TranslateTransform()..
The PictureBox will Invalidate/Refresh itself upon resizing. If you change any parameters do call Invalidate to pick them up!

How to create control that draws a table in panel

I want create a control that draws a table in panel . My code is:
public class PanelZ : System.Windows.Forms.Panel
{
public static void Draw()
{
Panel p = new Panel();
p.Width = 200;
p.Height = 200;
Graphics g = p.CreateGraphics();
Pen mypen = new Pen(Brushes.Black, 1);
Font myfont = new Font("tahoma", 10);
int lines = 9;
float x = 0;
float y = 0;
float xSpace = p.Width / lines;
float yspace = p.Height / lines;
for (int i = 0; i < lines + 1; i++)
{
g.DrawLine(mypen, x, y, x, p.Height);
x += xSpace;
}
x = 0f;
for (int i = 0; i < lines + 1; i++)
{
g.DrawLine(mypen, x, y, p.Width, y);
y += yspace;
}
}
..but it dosen't draw a table; so what should I do?
This will work. But the numbers ought to be properties, as should the pen and then some.. Also: Properties ought to start with an uppercase letter.
public class PanelZ : System.Windows.Forms.Panel
{
public PanelZ() // a constructor
{
Width = 200;
Height = 200;
DoubleBuffered = true;
lines = 9;
}
public int lines { get; set; } // a property
protected override void OnPaint(PaintEventArgs e) // the paint event
{
base.OnPaint(e);
Graphics g = e.Graphics;
Pen mypen = new Pen(Brushes.Black, 1);
Font myfont = new Font("tahoma", 10);
float x = 0;
float y = 0;
float xSpace = Width / lines;
float yspace = Height / lines;
for (int i = 0; i < lines + 1; i++)
{
g.DrawLine(mypen, x, y, x, Height);
x += xSpace;
}
for (int i = 0; i < lines + 1; i++)
{
g.DrawLine(mypen, 0, y, Width, y);
y += yspace;
}
}
}
At work in VS:
Note that this only colors pixels. There is no useful grid there, just pixels with color.. So, if you actually want to use the Font you define you will have to calculate the coodordinates and the bounding boxes.

Drawing a polygon according to the input coordinates

How can i draw a polygon according to the input coordinates which are given in C#.
You didn't show any code because based on those coordinate, you are applying some form of scaling to the image.
Using the Paint event of a PictureBox, here is an example using those coordinates on the screen. It fills in the polygon, then draws the border, then it loops through all the points to draw the red circle:
void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(Color.White);
// draw the shading background:
List<Point> shadePoints = new List<Point>();
shadePoints.Add(new Point(0, pictureBox1.ClientSize.Height));
shadePoints.Add(new Point(pictureBox1.ClientSize.Width, 0));
shadePoints.Add(new Point(pictureBox1.ClientSize.Width,
pictureBox1.ClientSize.Height));
e.Graphics.FillPolygon(Brushes.LightGray, shadePoints.ToArray());
// scale the drawing larger:
using (Matrix m = new Matrix()) {
m.Scale(4, 4);
e.Graphics.Transform = m;
List<Point> polyPoints = new List<Point>();
polyPoints.Add(new Point(10, 10));
polyPoints.Add(new Point(12, 35));
polyPoints.Add(new Point(22, 35));
polyPoints.Add(new Point(24, 22));
// use a semi-transparent background brush:
using (SolidBrush br = new SolidBrush(Color.FromArgb(100, Color.Yellow))) {
e.Graphics.FillPolygon(br, polyPoints.ToArray());
}
e.Graphics.DrawPolygon(Pens.DarkBlue, polyPoints.ToArray());
foreach (Point p in polyPoints) {
e.Graphics.FillEllipse(Brushes.Red,
new Rectangle(p.X - 2, p.Y - 2, 4, 4));
}
}
}
You may use Graphics.DrawPolygon. You can store the coordinates in an array of Point and then you can pass that to DrawPolygon method. You may wanna see:
Drawing with Graphics in WinForms using C#
private System.Drawing.Graphics g;
System.Drawing.Point[] p = new System.Drawing.Point[6];
p[0].X = 0;
p[0].Y = 0;
p[1].X = 53;
p[1].Y = 111;
p[2].X = 114;
p[2].Y = 86;
p[3].X = 34;
p[3].Y = 34;
p[4].X = 165;
p[4].Y = 7;
g = PictureBox1.CreateGraphics();
g.DrawPolygon(pen1, p);
This simple function is able to generate an array of PointF equal to the vertices of the regular polygon to be drawn, where "center" is the center of the polygon, "sides" is its number of sides, "sideLength" is the size of each side in pixels and "offset" is its slope.
public PointF[] GetRegularPolygonScreenVertex(Point center, int sides, int sideLength, float offset)
{
var points = new PointF[sides];
for (int i = 0; i < sides; i++)
{
points[i] = new PointF(
(float)(center.X + sideLength * Math.Cos((i * 360 / sides + offset) * Math.PI / 180f)),
(float)(center.Y + sideLength * Math.Sin((i * 360 / sides + offset) * Math.PI / 180f))
);
}
return points;
}
The result obtained can be used to draw a polygon, e.g. with the function:
GraphicsObject.DrawPolygon(new Pen(Brushes.Black, GetRegularPolygonScreenVertex(new Point(X, Y), 6, 30, 60f));
Which will generate a regular hexagon with a side of 30 pixels inclined by 30°.
hex

Scaling a signal on a 2D Graphic pane

I am scratching my head to figure out a way to scale a signal on a 2D graphic pane. The story is: I connect my application to a microcontroller and on fixed intervals I read a data value (A voltage point). Now I want to draw this on my graphic pane. Example:
So up in the picture you see at time 0, the voltage is also 0 and this goes on and after 6 data points I will clear the pane and redo the whole stuff.
The question is, how can I translate this voltage into pixel values, having in mind I want the middle of the graphic pane to be my signals 0, just like a normal cartesian graph. Can someone please help me to figure out the scaling algorithm in this case?
Seems like simple math: just add the width/2 to all X coordinates which you are passing into drawing functions. Suppose you have an array of 6 points you can do the following:
var g = this.CreateGraphics();
var points = new Point[6]{new Point(0, 0), new Point(10, 10), new Point(30, 0), new Point(40,20), new Point(50, 0), new Point(60,30)};
for (int i = 0; i < points.Length-1; i++)
{
g.DrawLine(Pens.Black, points[i].X + Width / 2, Height / 2 - points[i].Y, points[i + 1].X + Width / 2, Height / 2 - points[i + 1].Y);
}
Alternatively you can invoke TranslateTransform function to move all further drawing to some amount by X and Y axes. Example:
var g = this.CreateGraphics();
var points = new Point[6]{new Point(0, 0), new Point(10, 10), new Point(30, 0), new Point(40,20), new Point(50, 0), new Point(60,30)};
g.TranslateTransform(Width / 2, 0, System.Drawing.Drawing2D.MatrixOrder.Append);
for (int i = 0; i < points.Length-1; i++)
{
g.DrawLine(Pens.Black, points[i].X, Height / 2 - points[i].Y, points[i + 1].X, Height / 2 - points[i + 1].Y);
}
Maybe this will be useful (remember that scale and translate functions are changing points in array):
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var points = new PointF[6] { new PointF(0, 0), new PointF(30, 3), new PointF(90, 0), new PointF(190, 3.1f), new PointF(270, -0.5f), new PointF(360, 3.5f) };
float maxX = (from p in points select p).Max(t => t.X);
float maxY = (from p in points select p).Max(t => t.Y);
float xSizeToFit = pictureBox1.Width;
float ySizeToFit = pictureBox1.Height/2;
float scaleX = xSizeToFit / maxX;
float scaleY = ySizeToFit / maxY;
// scale to fit to given size
ScalePoints(points, scaleX, scaleY);
// translate to center
TranslatePoints(points, this.pictureBox1.Width / 2 - 0.5f * xSizeToFit, this.pictureBox1.Height / 2 + 0.5f * ySizeToFit);
DrawAxis(e.Graphics, this.pictureBox1.Size);
e.Graphics.DrawLines(Pens.Black, points);
}
private void TranslatePoints(PointF[] points, float transX, float transY)
{
for (int i = 0; i < points.Length; i++)
{
points[i].X += transX;
points[i].Y = transY - points[i].Y;
}
}
private void ScalePoints(PointF[] points, float scaleX, float scaleY)
{
for (int i = 0; i < points.Length; i++)
{
points[i].X *= scaleX;
points[i].Y *= scaleY;
}
}
public void DrawAxis(Graphics g, Size size)
{
//x
g.DrawLine(Pens.Black, 0, size.Height / 2, size.Width, size.Height / 2);
//y
g.DrawLine(Pens.Black, size.Width / 2, size.Height, size.Width / 2, 0);
}
private void pictureBox1_Resize(object sender, EventArgs e)
{
pictureBox1.Invalidate();
}

Categories

Resources