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);
}
Related
Problem
I have a list of points in C# that I draw on a panel on a Windows Forms object. The lists variables are two listA and listB. The points in listB are the points in listA except that they have gone through some transformation to deform it to resemble the shape formed by points in listA and then added some outliers to make them look different. If you can try these on your visual studio then this is the code...
class Form1 : Form
{
//declare the list to hold points for
//shapes
List<Point> listA = new List<Point>();
List<Point> listB = new List<Point>();
//this methods transforms,applies outliers and draws the shapes on panel1
private void button1_click(EventArgs e, object sender)
{
//clear the lists for initializing
listA.Clear();
listB.Clear();
Point p1a = new Point(20, 30);
Point p2a = new Point(120, 50);
Point p3a = new Point(160, 80);
Point p4a = new Point(180, 300);
Point p5a = new Point(100, 220);
Point p6a = new Point(50, 280);
Point p7a = new Point(20, 140);
//Hold the Points in an array
Point[] mypoints = new Point[] { p1a, p2a, p3a, p4a, p5a, p6a, p7a };
//add the points to the List with one call
listA.AddRange(mypoints);
//define a new Transformation
//that will translate shapeA to have a slightly different imageB
Transformation t2 = new Transformation();
t2.A = 1.05; t2.B = 0.05; t2.T1 = 15; t2.T2 = 22;
//assign the new translated points to listB
listB = applytransformation(t2, listA);
//Add outliers to listb by manipulating the values in the list
Shape2[2] = new Point(Shape2[2].X + 10, Shape2[2].Y + 3);
//create a new instance of pen
//for drawing imageA in blue
Pen penner = new Pen(Brushes.Blue, 3);
//Create a new instance of pen for
//drawing imageB in red
Pen mypen = new Pen(Brushes.Red, 3);
//get the graphic context
Graphics g = panel1.CreateGraphics();
//draw both shapes
DisplayShape(listA, penner, g);
DisplayShape(listB, mypen, g);
}
//the method below does the transformation of imagea into imageb by manipulating the points and the transformation
List<Point> applytransformation(Transformation x, List<Point> shape)
{
List<Point> Tlist = new List<Point>();
foreach (Point c in shape) {
double xprime = x.A * c.X + x.B * c.Y + x.T1;
double yprime = x.B * c.X * -1 + x.A * c.Y + x.T2;
Point ptrans = new Point((int)xprime, (int)yprime);
Tlist.Add(ptrans);
}
//it returns the points that will be used to draw imageB
return Tlist;
}
//this method draws the points on the panel
void DisplayShape(List<Point> Shp, Pen pen, Graphics G)
{
Point? prevPoint = null;//nullable
foreach (Point pt in Shp) {
G.DrawEllipse(pen, new Rectangle(pt.X - 2, pt.Y - 2, 4, 4));
if (prevPoint != null) {
G.DrawLine(pen, (Point)prevPoint, pt);
}
prevPoint = pt;
}
G.DrawLine(pen, Shp[0], Shp[Shp.Count - 1]);
}
}
public class Transformation
{
public double A { get; set; }
public double B { get; set; }
public double T1 { get; set; }
public double T2 { get; set; }
}
Goal
I want to remove all the outliers in imageB so that it resembles imageA even if it won't be perfect. All methods or algorithms are welcome ie RANSAC,minimum cost function. I have tried to find an authoritative source online that can guide or help me achieve this in C# with zero success. The code I have provided is a minimum reproducible example that can be replicated on any visual studio IDE.Please help, Thank You for your time and contribution.
Expected Output
I added an image to make it clear the result I want
If you have many points forming a cloud of points where the line defining the shape goes through, then you can remove outliers. As an example see Removing outliers. But in this case, every point in the list seems to be a vertex of the shape. Removing a point will alter the shape considerably.
Can you explain what these shapes represent? hat should happen if you remove an outlier? Should it be replaced by another point?
While this is not an answer to your question, here is an improved and simplified version of the code:
List<Point> listA, listB; // Initialization not required.
private void button1_click(EventArgs e, object sender)
{
// Simplify initialization with collection and object initializers.
listA = new List<Point> {
new Point(20, 30), new Point(120, 50),
new Point(160, 80), new Point(180, 300),
new Point(100, 220), new Point(50, 280),
new Point(20, 140)
};
var t2 = new Transformation { A = 1.05, B = 0.05, T1 = 15, T2 = 22 };
listB = ApplyTransformation(t2, listA);
// Simplify shifting point.
Shape2[2] += new Size(10, 3);
// Invalidate panel and let Panel1_Paint draw it.
// Never create your own Graphics object.
panel1.Invalidate();
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
if (listA != null && listB != null) {
// Use predefined pens instead of creating brushes.
DisplayShape(listA, Pens.Blue, e.Graphics);
DisplayShape(listB, Pens.Red, e.Graphics);
}
}
List<Point> ApplyTransformation(Transformation x, List<Point> shape)
{
// Prevent list resizing by specifying initial size.
var transformedList = new List<Point>(shape.Count);
foreach (Point c in shape) {
double xprime = x.A * c.X + x.B * c.Y + x.T1;
double yprime = x.B * c.X * -1 + x.A * c.Y + x.T2;
transformedList.Add(new Point((int)xprime, (int)yprime));
}
return transformedList;
}
void DisplayShape(List<Point> shape, Pen pen, Graphics g)
{
// By using "for" instead of "foreach" we have indexes we can use to
// simplify closing the shape, since we always have a previous point.
for (int i = 0; i < shape.Count; i++) {
Point prevPoint = i > 0 ? shape[i - 1] : shape[shape.Count - 1];
Point pt = shape[i];
// No need to create a rectangle,
// there is an overload accepting location and size.
g.DrawEllipse(pen, pt.X - 2, pt.Y - 2, 4, 4);
g.DrawLine(pen, prevPoint, pt);
}
}
Since C# 8.0 and in .NET Core projects we can also write shape[^1] to get the last point instead of shape[shape.Count - 1].
I am trying to Use the QuadTree code to develop an Octree code. However I am stuck when it comes to changing the Rectangle3d to a Box. Basically I have a function to split the nodes, when slitting the rectangle I used the width and height and divided them, then use the constructor - Rectangle3d(Plane, Double, Double) but I am stuck as to which constructor to use and how to calculate it when I change from Rectangle3d to Box. Can anyone help me with this?
public static Octree oct;
public static DataTree < Point3d > psOUT;
public static List<Line> lns = new List<Line>();
//////////Octree////////
public class Octree
{
public int MAX_OBJECTS = 10;
public int MAX_LEVELS = 8;
private int level;
private List<Point3d>objects;
private Box bounds;
private Octree[] nodes;
/*
* Constructor
*/
public Octree(int pLevel, Box pBounds)
{
level = pLevel;
objects = new List<Point3d>();
bounds = pBounds;
nodes = new Octree[8];
}
// implement the five methods of a Octree: clear, split, getIndex, insert, and retrieve.
/*
* Clears the Octree
*/
public void clear()
{
objects.Clear();
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i] != null)
{
nodes[i].clear();
nodes[i] = null;
}
}
}
/*
* Splits the node into 8 subnodes
*/
private void split()
{
double subWidth = bounds.X * 0.5;
double subDepth = bounds.Y * 0.5;
double subHeight = bounds.Z *0.5;
double x = bounds.X.T0;
double y = bounds.Y.T0;
double z = bounds.Z.T0;
nodes[3] = new Quadtree(level + 1, new Box(Plane.WorldXY, new Point3d(x + subWidth, y, 0), new Point3d(x + 2 * subWidth, y + subHeight, 0)));
nodes[2] = new Quadtree(level + 1, new Box(Plane.WorldXY, new Point3d(x, y, 0), new Point3d(x + subWidth, y + subHeight, 0)));
nodes[1] = new Quadtree(level + 1, new Box(Plane.WorldXY, new Point3d(x, y + subHeight, 0), new Point3d(x + subWidth, y + 2 * subHeight, 0)));
nodes[0] = new Quadtree(level + 1, new Box(Plane.WorldXY, new Point3d(x + subWidth, y + subHeight, 0), new Point3d(x + 2 * subWidth, y + 2 * subHeight, 0)));
}
Maybe a little late but here it goes:
Considering you have a Box in 3D space already constructed. In order to divide it into 8 smaller boxes you can use the X,Y,Z coordinate interval between each of the corners and the box center point. So, you would have something like this:
private List<Box> Split(Box box)
{
List<Box> boxes = new List<Box>();
foreach(Point3d corner in box.GetCorners())
{
Box newbox = CreateBoxFromPlaneAndTwoCorners(box.Plane, box.Center, corner);
boxes.Add(newbox);
}
return boxes;
}
private Box CreateBoxFromPlaneAndTwoCorners(Plane plane, Point3d cornerA, Point3d cornerB) {
plane.RemapToPlaneSpace(cornerA, out Point3d remapA);
plane.RemapToPlaneSpace(cornerB, out Point3d remapB);
Interval intX = new Interval(remapA.X,remapB.X);
Interval intY = new Interval(remapA.Y,remapB.Y);
Interval intZ = new Interval(remapA.Z,remapB.Z);
return new Box(plane,intX,intY,intZ);
}
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;
}
I am using the below code to draw the circles(reading centres from a csv file) with increasing radius. The increase in radius is 5 units per circle.
namespace MATLAB_file
{
public partial class Form1 : Form
{
string[] read;
float th;
int c = 0;
int r;
public List<PointF> circleCoordinates = new List<PointF>();
int rl;
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
Pen linePen = new Pen(System.Drawing.Color.CornflowerBlue);
Graphics grphx = this.CreateGraphics();
grphx.Clear(this.BackColor);
foreach (PointF point in this.circleCoordinates)
{
Pen redPen1 = new Pen(Color.Red, 100);
e.Graphics.DrawArc(Pens.Red, point.X, point.Y, 1, 1, 0, 120F);
}
linePen.Dispose();
base.OnPaint(e);
}
private void Form1_Load(object sender, EventArgs e)
{
double xx, yy;
int i;
int n = 0;
float[] centre1 = new float[1000];
System.IO.StreamReader sr;
sr = new System.IO.StreamReader("centers.txt", true);
char[] seperators = { ',' };
string data = sr.ReadLine();
read = data.Split(new Char[] { ',' });
rl = read.Length;
int a1 = rl / 2;
for (c = 0; c < rl; c++)
{
centre1[c] = float.Parse(read[c]);
}
while (r < 200)
{
for (i = 0; i < a1; i++)
{
while (th < 360)
{
xx = r * Math.Cos(th) + centre1[2 * i] + 100;
xx1 = (float)xx;
yy = r * Math.Sin(th) + centre1[2 * i + 1] + 100;
yy1 = (float)yy;
this.circleCoordinates.Add(new PointF(xx1, yy1));
this.Invalidate();
th = th + .360F;
}
th = 0;
}
r = r + 5;
}
}
}
}
The above code is displaying all the circles but I do not want all circles to be displayed on canvas, rather only one circle should show with gradual increase in radius
Please suggest how to delete the previous drawn circle on drawing new one. Is there any other way to do it, if my later use includes removal of certain section of circles based on "th" values?
If you move the Clear call within the foreach you will see only the last drawn circle, though this could be achieved with drawing only the Last circleCoordinates too.
foreach (PointF point in this.circleCoordinates)
{
grphx.Clear(this.BackColor);
Pen redPen1 = new Pen(Color.Red, 100);
e.Graphics.DrawArc(Pens.Red, point.X, point.Y, 1, 1, 0, 120F);
}
An alternative interpretation:
You want animation, which generates the onPaint events (by timer ticks or on user input) and you increase a counter to select the circle to draw.
For that, you will need a new member in your program, like int Index; and you could select the circle based on this, using something the following code snippet (assuming you always have at least 1 circleCoordinates, animation restarts after it finished):
PointF point = this.circleCoordinates[Index % circleCoordinates.Length];
or (last circle remain on the screen after the animation)
PointF point = this.circleCoordinates[Math.Min(Index, circleCoordinates.Length - 1)];
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..)