hittest in polar chart with multiple series - c#

I have a polar chart with multiple series. I want to have a functionality to click on one of the datapoints in any series and perform something. I tried to use the HitTest and it works on a single series. The problem is when I used in on a chart with multiple series and sometimes when I click on a datapoint, it returns a different point. Please help.
This is the snippet that I used.
HitTestResult result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (result.ChartElementType == ChartElementType.DataPoint)
{
var xVal = result.Series.Points[result.PointIndex].XValue;
var yVal = result.Series.Points[result.PointIndex].YValues;
result.Series.Points[result.PointIndex].MarkerColor = Color.Black;
}
update:
Thanks you so much for bearing with me. Anyway, this is the code incorporating what you suggested.
DataPoint dpCurrent = null;
int normalMarkerSize = 10;
int largeMarkerSize = 15;
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
HitTestResult result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (result.ChartElementType == ChartElementType.DataPoint)
{
dpCurrent = result.Series.Points[result.PointIndex];
if (distance(PolarValueToPixelPosition(dpCurrent, chart1, result.ChartArea), e.Location) <= 5)
result.Series.Points[result.PointIndex].MarkerColor = Color.Black;
}
}
However, I noticed that the value of "phi" in the PolarValueToPixelPosition is always returning NaN

Here are two ways to solve the problem; none can actually make the HitTest ignore clicking on the connecting lines.
But they should be fine, espacially when you implement them both.
The first provides feedback to the user so he can see in advance which point the mouse is over and he is about to click:
DataPoint dpCurrent = null;
int normalMarkerSize = 8;
int largeMarkerSize = 12;
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
HitTestResult hit = chart1.HitTest(e.X, e.Y);
if (hit.ChartElementType == ChartElementType.DataPoint)
{
dpCurrent = hit.Series.Points[hit.PointIndex];
dpCurrent.MarkerSize = largeMarkerSize;
}
else
{
if (dpCurrent != null) dpCurrent.MarkerSize = normalMarkerSize;
dpCurrent = null;
}
Unfortunately the HitTest will still trigger a DataPoint-hit even if you only hit the connecting lines, no matter how thin you make them or what color they have..
..enter solution two:
One can write a custom HitTest by calculating the pixel-coordinates of the DataPoints; this is not as simple as calling the Axis.ValueToPixelPosition methods as it involves some modest amount of math..:
Now before processing the hit you would do an additional check, maybe like this:
if (distance(PolarValueToPixelPosition(dpCurrent, chart1,
hit.ChartArea), e.Location) <= markerRadius) ...//do the hit stuff
Here is the coordinate transformation function:
PointF PolarValueToPixelPosition(DataPoint dp, Chart chart, ChartArea ca)
{
RectangleF ipp = InnerPlotPositionClientRectangle(chart, ca);
double crossing = ca.AxisX.Crossing != double.NaN ? ca.AxisX.Crossing : 0;
// for RangeChart change 90 zo 135 !
float phi = (float)(360f / ca.AxisX.Maximum / 180f * Math.PI *
(dp.XValue - 90 + crossing ) );
float yMax = (float)ca.AxisY.Maximum;
float yMin = (float)ca.AxisY.Minimum;
float radius = ipp.Width / 2f;
float len = (float)(dp.YValues[0] - yMin) / (yMax - yMin);
PointF C = new PointF(ipp.X + ipp.Width / 2f, ipp.Y + ipp.Height / 2f);
float xx = (float)(Math.Cos(phi) * radius * len);
float yy = (float)(Math.Sin(phi) * radius * len);
return new PointF(C.X + xx, C.Y + yy);
}
It makes use of a simple distance function..:
float distance(PointF pt1, PointF pt2)
{
float d = (float)Math.Sqrt((pt1.X - pt2.X) * (pt1.X - pt2.X)
+ (pt1.Y - pt2.Y) * (pt1.Y - pt2.Y));
return d;
}
Also of two other useful functions to calculate the pixel size of the InnerPlotPosition, which I have used in quite a few answers now..:
RectangleF ChartAreaClientRectangle(Chart chart, ChartArea CA)
{
RectangleF CAR = CA.Position.ToRectangleF();
float pw = chart.ClientSize.Width / 100f;
float ph = chart.ClientSize.Height / 100f;
return new RectangleF(pw * CAR.X, ph * CAR.Y, pw * CAR.Width, ph * CAR.Height);
}
RectangleF InnerPlotPositionClientRectangle(Chart chart, ChartArea CA)
{
RectangleF IPP = CA.InnerPlotPosition.ToRectangleF();
RectangleF CArp = ChartAreaClientRectangle(chart, CA);
float pw = CArp.Width / 100f;
float ph = CArp.Height / 100f;
return new RectangleF(CArp.X + pw * IPP.X, CArp.Y + ph * IPP.Y,
pw * IPP.Width, ph * IPP.Height);
}

Related

Rotate curve (serie of DataPoints) X degrees defined their new Baseline

I am using MSchart to plot serie into it, i need to rotate Curve (serie of datapoint) X degrees, P1 is fisrt click (mouseDown) and P2 second click (mouseUp), the angle between P1 and P2 represent the angle to rotate (i have this calculated), the problem is the curve deform when i apply different methods to do this
for (int x = 1; x < curve.nPoints; x++)
{
double X0 = curve.get_array_X[x];
double Y0 = curve.get_array_Y[x];
System.Windows.Media.Matrix m = new System.Windows.Media.Matrix();
m.Rotate(45 * (Math.PI / 180.0));
System.Windows.Vector v = new System.Windows.Vector(X0, Y0);
v = System.Windows.Vector.Multiply(v, m);
sAus.Points.AddXY(v.X, v.Y);
}
and this other code
public Series nueva(float X, float Y)
{
Series sAus = new Series(curvaActual.Tag);
int nearest1 = curvaActual.FindNearestXCV(new DataPoint(chartWorking1.ChartAreas[0].AxisX.PixelPositionToValue(X),
chartWorking1.ChartAreas[0].AxisY.PixelPositionToValue(Y)).XValue,
new DataPoint(chartWorking1.ChartAreas[0].AxisX.PixelPositionToValue(X), chartWorking1.ChartAreas[0].AxisY.PixelPositionToValue(Y)).YValues[0], 0, curvaActual.nPoints);
int nearest2 = curvaActual.FindNearestXCV(new DataPoint(chartWorking1.ChartAreas[0].AxisX.PixelPositionToValue(X),
chartWorking1.ChartAreas[0].AxisY.PixelPositionToValue(Y)).XValue,
new DataPoint(chartWorking1.ChartAreas[0].AxisX.PixelPositionToValue(X), chartWorking1.ChartAreas[0].AxisY.PixelPositionToValue(Y)).YValues[0], 0, curvaActual.nPoints);
double x1 = (double)curvaActual.get_array_X[nearest1];
double y1 = (double)curvaActual.get_array_Y[nearest1];
double x2 = (double)curvaActual.get_array_X[nearest2];
double y2 = (double)curvaActual.get_array_Y[nearest2];
double pendiente = Math.Atan2(y2 - y1, x2 - x1);
double anguloF = pendiente;
double deg = Math.PI * 45 / 180.0;
double coseno = Math.Cos(deg);
double seno = Math.Sin(deg);
double[] arrayX = new double[curvaActual.nPoints];
double[] arrayY = new double[curvaActual.nPoints];
for (int x = 1; x < curvaActual.nPoints; x++)
{
PointF rotatedPoint = RotatePoint(new PointF((float)curvaActual.get_array_X[x], (float)curvaActual.get_array_Y[x]), new PointF((float)curvaActual.get_array_X[x - 1], (float)curvaActual.get_array_Y[x - 1]), 45);
double angleRotatedPoint = angleFromPoint(rotatedPoint, new PointF((float)curvaActual.get_array_X[x - 1], (float)curvaActual.get_array_Y[x - 1]));
PointF pointROtado = RotatePoint(new PointF((float)curvaActual.get_array_X[x], (float)curvaActual.get_array_Y[x]), new PointF((float)0, (float)0), 45);
sAus.Points.AddXY((double)pointROtado.X, (double)pointROtado.Y);
}
return sAus;
}
[proof]
original serie
rotate 33
using your code (modify point to pointF)
void rotateSeries(Series src, Series tgt, DataPoint center, float angle)
{
PointF c = new PointF((float)center.XValue, (float)center.YValues[0]);
tgt.Points.Clear();
foreach (DataPoint dp in src.Points)
{
PointF p0 = new PointF((float)dp.XValue, (float)dp.YValues[0]);
PointF p = RotatePoint(p0, c, angle);
tgt.Points.AddXY(p.X, p.Y);
}
}
static PointF RotatePoint(PointF pointToRotate, PointF centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new PointF
{
X = (float)
(cosTheta * (pointToRotate.X - centerPoint.X) -
sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
Y = (float)
(sinTheta * (pointToRotate.X - centerPoint.X) +
cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
};
}
Here is one example of how you can do it. It..
assumes you have found the DataPoint center around which you want to rotate the graphic
assumes you have calculated the angle by which it shall be rotated
uses two Series s1 and s2, the first being the source and the latter the rotated target.
uses a version of RotatePoint from Fraser's anwser, slightly updated for PointF.
Here is the result for rotating around the 5th point by 33 degrees:
private void button1_Click(object sender, EventArgs e)
{
rotateSeries(s1, s2, s1.Points[4], 33);
}
void rotateSeries(Series src, Series tgt, DataPoint center, float angle)
{
PointF c = new PointF((float)center.XValue, (float)center.YValues[0]);
tgt.Points.Clear();
foreach (DataPoint dp in src.Points)
{
PointF p0 = new Point((float)dp.XValue, (float)dp.YValues[0]);
PointF p = RotatePoint(p0, c, angle);
tgt.Points.AddXY(p.X, p.Y);
}
}
static PointF RotatePoint(PointF pointToRotate, PointF centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new PointF
{
X = (float)
(cosTheta * (pointToRotate.X - centerPoint.X) -
sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
Y = (float)
(sinTheta * (pointToRotate.X - centerPoint.X) +
cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
};
}
If you want to use the MouseClick to determine the angle you can use the Points from the e parameter directly. But to find out the center you will need to convert the pixel coordinates to chart value coordinates.
Here is a call you can use during the MouseClick event:
DataPoint clickedValuePoint(ChartArea ca, Point pt)
{
return new DataPoint(ca.AxisX.PixelPositionToValue(pt.X),
ca.AxisY.PixelPositionToValue(pt.Y));
}
Now let's see it at work, using a DataPoint:
DataPoint centerPoint = null;
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
ChartArea ca = chart1.ChartAreas[0];
centerPoint = clickedValuePoint(ca, e.Location);
rotateSeries(s1, s2, centerPoint, 33);
}

CreateGraphics(): Graphics is shown only on re-size

Instantiating the form.
public TwoDPlot()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", true);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US", true);
InitializeComponent();
DrawCircle();
}
Method to draw the circle.
private void DrawCircle()
{
var radius = 0.5 * ClientRectangle.Width;
var height = ClientRectangle.Height - 0.1 * ClientRectangle.Height;
var width = ClientRectangle.Width - 0.1 * ClientRectangle.Width;
var scaledRadius = width > height ? radius * (height / radius) : radius * (width / radius);
var xlocation = ClientRectangle.Width / 2.0 - scaledRadius * 0.5;
var ylocation = ClientRectangle.Height / 2.0 - scaledRadius * 0.5;
m_Graphics = CreateGraphics();
m_Graphics?.Clear(DefaultBackColor);
m_Graphics?.DrawEllipse(new Pen(Color.Red), new Rectangle((int)xlocation, (int)ylocation, (int)scaledRadius, (int)scaledRadius));
m_Graphics.Dispose();
}
On instance, it shows empty form and upon re-sizing it shows the circle. I expect to show during the first instance.
This is the direct correction:
private void TwoDPlot_Paint(object sender, PaintEventArgs e)
{
DrawCircle(e.Graphics);
}
private void DrawCircle(Graphics m_Graphics)
{
var radius = 0.5 * ClientRectangle.Width;
var height = ClientRectangle.Height - 0.1 * ClientRectangle.Height;
var width = ClientRectangle.Width - 0.1 * ClientRectangle.Width;
var scaledRadius = width > height ? radius * (height / radius) : radius * (width / radius);
var xlocation = ClientRectangle.Width / 2.0 - scaledRadius * 0.5;
var ylocation = ClientRectangle.Height / 2.0 - scaledRadius * 0.5;
m_Graphics.Clear(DefaultBackColor);
m_Graphics.DrawEllipse(new Pen(Color.Red), new Rectangle((int)xlocation, (int)ylocation, (int)scaledRadius, (int)scaledRadius));
}
Note that to be more flexible you will want to move the variables maybe to class level variables or to parameters to the DrawCircle function..
When you have done that and changed the variables values you can trigger the Paint event by calling TwoDPlot.Invalidate().
The system will also call it whenever it needs to, e.g. upon many resize, maximize and other events..

How to Reset Time of an analog clock by dragging it's handles C#

I'm very new to C#, the aim here is to edit the Time of an analog Clock by dragging it's handles. https://code.msdn.microsoft.com/windowsapps/Analog-Clock-Control-0e8ffcab#content this code has inpired me. I have three simple functions MouseDown, MouseMove and MouseUp but still I can not get Drag to work. Any suggestions please ?
public partial class Form1 : Form
{
#region Construct the clock
public Point Start { get; set; }
public Point End { get; set; }
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
//Create the timer and start it
ClockTimer.Tick += ClockTimer_Tick;
ClockTimer.Enabled = true;
ClockTimer.Interval = 1;
ClockTimer.Start();
Start = p1;
End = p2;
}
#endregion
#region Update the clock
private void ClockTimer_Tick(object sender, EventArgs e)
{
Refresh();
}
private Timer ClockTimer = new Timer();
private Pen circle = new Pen(Color.Black, 2);
private Pen secondHandle = new Pen(Color.Red, 1);
private Pen minHandle = new Pen(Color.Black, 5);
private Pen hrHandle = new Pen(Color.Black, 5);
private Point p1;
private Point p2;
#endregion
#region On paint
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//Clear the graphics to the back color of the control
pe.Graphics.Clear(BackColor);
//Draw the border of the clock
pe.Graphics.DrawEllipse(circle, 0, 0, 300, 300);
//Find the radius of the control by dividing the width by 2
float radius = (300 / 2);
//Find the origin of the circle by dividing the width and height of the control
PointF origin = new PointF(300 / 2, 300 / 2);
//Draw only if ShowMajorSegments is true;
if (ShowMajorSegments)
{
//Draw the Major segments for the clock
for (float i = 0f; i != 390f; i += 30f)
{
pe.Graphics.DrawLine(Pens.White, PointOnCircle(radius - 1, i, origin), PointOnCircle(radius - 21, i, origin));
}
}
//Draw only if ShowMinorSegments is true
if (ShowMinorSegments)
{
//Draw the minor segments for the control
for (float i = 0f; i != 366f; i += 6f)
{
pe.Graphics.DrawLine(Pens.Black, PointOnCircle(radius, i, origin), PointOnCircle(radius - 10, i, origin));
}
}
//Draw only if ShowSecondHand is true
if (ShowSecondhand)
//Draw the second hand
pe.Graphics.DrawLine(secondHandle, origin, PointOnCircle(radius, DateTime.Now.Second * 6f, origin));
//Draw only if ShowMinuteHand is true
if (ShowMinuteHand)
//Draw the minute hand
pe.Graphics.DrawLine(minHandle, origin, PointOnCircle(radius * 0.75f, DateTime.Now.Minute * 6f, origin));
minHandle.StartCap = LineCap.RoundAnchor;
minHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Draw only if ShowHourHand is true
if (ShowHourHand)
//Draw the hour hand
pe.Graphics.DrawLine(hrHandle, origin, PointOnCircle(radius * 0.50f, DateTime.Now.Hour * 30f, origin));
hrHandle.StartCap = LineCap.RoundAnchor;
hrHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
#endregion
#region On size changed
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//Make sure the control is square
if (Size.Height != Size.Width)
Size = new Size(Size.Width, Size.Width);
//Redraw the control
Refresh();
}
#endregion
#region Point on circle
private PointF PointOnCircle(float radius, float angleInDegrees, PointF origin)
{
//Find the x and y using the parametric equation for a circle
float x = (float)(radius * Math.Cos((angleInDegrees - 90f) * Math.PI / 180F)) + origin.X;
float y = (float)(radius * Math.Sin((angleInDegrees - 90f) * Math.PI / 180F)) + origin.Y;
return new PointF(x, y);
}
#endregion
#region Show Minor Segments
private bool showMinorSegments = true;
public bool ShowMinorSegments
{
get
{
return showMinorSegments;
}
set
{
showMinorSegments = value;
Refresh();
}
}
#endregion
#region Show Major Segments
private bool showMajorSegments = true;
public bool ShowMajorSegments
{
get
{
return showMajorSegments;
}
set
{
showMajorSegments = value;
Refresh();
}
}
#endregion
#region Show Second Hand
private bool showSecondHand = false;
public bool ShowSecondhand
{
get
{
return showSecondHand;
}
set
{
showSecondHand = value;
Refresh();
}
}
#endregion
#region Show Minute Hand
private bool showMinuteHand = true;
public bool ShowMinuteHand
{
get
{
return showMinuteHand;
}
set
{
showMinuteHand = value;
Refresh();
}
}
#endregion
#region Show Hour Hand
private bool showHourHand = true;
public bool ShowHourHand
{
get
{
return showHourHand;
}
set
{
showHourHand = value;
Refresh();
}
}
#endregion
public float slope
{
get
{
return (((float)p2.Y - (float)p1.Y) / ((float)p2.X - (float)p1.X));
}
}
public float YIntercept
{
get
{
return p1.Y - slope * p1.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y - cushion) && temp <= (p.Y + cushion))
{
return true;
}
else
{
return false;
}
}
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(p1.X - e.Location.X, p1.Y - e.Location.Y);
deltaEnd = new Point(p2.X - e.Location.X, p2.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null)
{
p1 = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
p2 = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
}
I give a partial answer about translating a X, Y coordinate to an angle (in degree) based on a circle, where the 0° angle is located at the top.
(Scroll down for a compact solution)
Following the directions of typical GUI coordinates, the absolute 0,0 Point is located top left, positive X values stretch to the right and positive Y values stretch to the bottom.
In order to simplify the math, I use a virtual 0,0 point at the center of the circle, so all coordinates need to be translated to locals before calculation and to globals before actual drawing.
Coordinate overview (imagine the circle around 0; 0):
(0;-1)
(-1; 0) (0; 0) (1; 0)
(0; 1)
Now the task is for any coordinate (X; Y) to find the clock-wise angle between the line (0; 0) - (0; -1) and the line (0; 0) - (X; Y)
The circle can be divided into 4 quarter-circles, each covering a combination of signed (X; Y) values.
Quarter 1 contains the angle values 0° to 90° and is represented by positive X values and negative Y values.
Quarter 2 contains the angle values 90° to 180° and is represented by positive X values and positive Y values.
Quarter 3 contains the angle values 180° to 270° and is represented by negative X values and positive Y values.
Quarter 4 contains the angle values 270° to 360° and is represented by negative X values and negative Y values.
Note that for the corner cases 0°, 90°, 180°, 270°, 360° it doesn't really matter which of the two quarters they are assigned to.
The easiest way to understand such problems is to stick to the normal circle -> read: to normalize the X; Y coordinate to a length of 1. Additionally I go with positive values (it would also work without, but a bit differently in the + and - combinations):
var len = Math.Sqrt(X * X + Y * Y);
var xNorm = Math.Abs(X) / len;
var yNorm = Math.Abs(Y) / len;
Now, the reverse sine / cosine can be used to translate the normalized coordinates back into angle values (there's some redundancy in my calculation for the sake of simplicity and completeness):
var angleFromX = Math.Asin(xNorm) * 180.0 / Math.PI;
var angleFromY = Math.Asin(yNorm) * 180.0 / Math.PI;
Now lets apply the appropriate angle for each of the quarter circle areas
var resultAngle = 0.0;
if (quarter_1)
{
resultAngle = 0 + angleFromX;
// same as
resultAngle = 90 - angleFromY;
}
if (quarter_2)
{
resultAngle = 90 + angleFromY;
// same as
resultAngle = 180 - angleFromX;
}
if (quarter_3)
{
resultAngle = 180 + angleFromX;
// same as
resultAngle = 270 - angleFromY;
}
if (quarter_4)
{
resultAngle = 270 + angleFromY;
// same as
resultAngle = 360 - angleFromX;
}
Ofcourse, the quarter_1 - quarter_4 are pseudo-variables that represent the quarter selection as explained.
A more compact solution can be found by analyzing the different properties of the full solution.
var angleFromYAxis = Math.Asin(Y / Math.Sqrt(X * X + Y * Y)) * 180.0 / Math.PI;
var resultAngle = 0.0;
if (X >= 0)
{
resultAngle = 90 + angleFromYAxis;
}
else
{
resultAngle = 270 - angleFromYAxis;
}

Place dynamically generated items in circle around a button

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

Galaxy Generation Algorithm

I'm trying to generate a set of points (represented by a Vector struct) that roughly models a spiral galaxy.
The C# code I've been playing with is below; but I can only seem to get it to generate a single 'arm' of the galaxy.
public Vector3[] GenerateArm(int numOfStars, int numOfArms, float rotation)
{
Vector3[] result = new Vector3[numOfStars];
Random r = new Random();
float fArmAngle = (float)((360 / numOfArms) % 360);
float fAngularSpread = 180 / (numOfArms * 2);
for (int i = 0; i < numOfStars; i++)
{
float fR = (float)r.NextDouble() * 64.0f;
float fQ = ((float)r.NextDouble() * fAngularSpread) * 1;
float fK = 1;
float fA = ((float)r.NextDouble() % numOfArms) * fArmAngle;
float fX = fR * (float)Math.Cos((MathHelper.DegreesToRadians(fA + fR * fK + fQ)));
float fY = fR * (float)Math.Sin((MathHelper.DegreesToRadians(fA + fR * fK + fQ)));
float resultX = (float)(fX * Math.Cos(rotation) - fY * Math.Sin(rotation));
float resultY = (float)(fY * Math.Cos(rotation) - fX * Math.Sin(rotation));
result[i] = new Vector3(resultX, resultY, 1.0f);
}
return result;
}
Check this. It's a simulation of galaxy using density wave theory. Code is available.
http://beltoforion.de/galaxy/galaxy_en.html
I liked this idea so much i had to play around with it on my own and here is my result.
Note that i used PointF instead of Vector3, but you should be able to search and replace and add , 0) in a few places.
PointF[] points;
private void Render(Graphics g, int width, int height)
{
using (Brush brush = new SolidBrush(Color.FromArgb(20, 150, 200, 255)))
{
g.Clear(Color.Black);
foreach (PointF point in points)
{
Point screenPoint = new Point((int)(point.X * (float)width), (int)(point.Y * (float)height));
screenPoint.Offset(new Point(-2, -2));
g.FillRectangle(brush, new Rectangle(screenPoint, new Size(4, 4)));
}
g.Flush();
}
}
public PointF[] GenerateGalaxy(int numOfStars, int numOfArms, float spin, double armSpread, double starsAtCenterRatio)
{
List<PointF> result = new List<PointF>(numOfStars);
for (int i = 0; i < numOfArms; i++)
{
result.AddRange(GenerateArm(numOfStars / numOfArms, (float)i / (float)numOfArms, spin, armSpread, starsAtCenterRatio));
}
return result.ToArray();
}
public PointF[] GenerateArm(int numOfStars, float rotation, float spin, double armSpread, double starsAtCenterRatio)
{
PointF[] result = new PointF[numOfStars];
Random r = new Random();
for (int i = 0; i < numOfStars; i++)
{
double part = (double)i / (double)numOfStars;
part = Math.Pow(part, starsAtCenterRatio);
float distanceFromCenter = (float)part;
double position = (part * spin + rotation) * Math.PI * 2;
double xFluctuation = (Pow3Constrained(r.NextDouble()) - Pow3Constrained(r.NextDouble())) * armSpread;
double yFluctuation = (Pow3Constrained(r.NextDouble()) - Pow3Constrained(r.NextDouble())) * armSpread;
float resultX = (float)Math.Cos(position) * distanceFromCenter / 2 + 0.5f + (float)xFluctuation;
float resultY = (float)Math.Sin(position) * distanceFromCenter / 2 + 0.5f + (float)yFluctuation;
result[i] = new PointF(resultX, resultY);
}
return result;
}
public static double Pow3Constrained(double x)
{
double value = Math.Pow(x - 0.5, 3) * 4 + 0.5d;
return Math.Max(Math.Min(1, value), 0);
}
Example:
points = GenerateGalaxy(80000, 2, 3f, 0.1d, 3);
Result:
I would abstract that function out into a createArm function.
Then you can store each arm as its own galaxy (temporarily).
So if you want 2 arms, do 2 galaxies of 5000. Then, rotate one of them 0 degrees around the origin (so doesn't move) and the other 180 degrees around the origin.
With this you can do an arbitrary number of arms by using different rotation amounts. You could even add some "naturalization" to it by making the rotation distance more random, like with a range instead of straight (360 / n). For example, 5 arms would be 0, 72, 144, 216, 288. But with some randomization you could make it 0, 70, 146, 225, 301.
Edit:
Some quick google-fu tells me (source)
q = initial angle, f = angle of rotation.
x = r cos q
y = r sin q
x' = r cos ( q + f ) = r cos q cos f - r sin q sin f
y' = r sin ( q + w ) = r sin q cos f + r cos q sin f
hence:
x' = x cos f - y sin f
y' = y cos f + x sin f

Categories

Resources