Related
I am implementing radial layout drawing algorithm, according to the publication of mr.Andy Pavlo link [page 18]
The problem is, that my result contains crossed edges. Which is something that is unacceptable. I found some solution, similiar problem link but I was not able to implement them into this algorithm (I would have to change the whole approach to the solution). In addition, the algorithm by Mr. Andy Pavlo should be able to solve this problem. When we look at the result of its algorithm, there are no crossed edges here. What am I doing wrong? Am I missing something? Thank you in advance.
Mr.Pavlo pseudo code of algorithm
My implementation of algorithm
public void RadialPositions(Tree<string> rootedTree, Node<string> vertex, double alfa, double beta,
List<RadialPoint<string>> outputGraph)
{
//check if vertex is root of rootedTree
if (vertex.IsRoot)
{
vertex.Point.X = 0;
vertex.Point.Y = 0;
outputGraph.Add(new RadialPoint<string>
{
Node = vertex,
Point = new Point
{
X = 0,
Y = 0
},
ParentPoint = null
});
}
//Depth of vertex starting from 0
int depthOfVertex = vertex.Depth;
double theta = alfa;
double radius = Constants.CircleRadius + (Constants.Delta * depthOfVertex);
//Leaves number in the subtree rooted at v
int leavesNumber = BFS.BreatFirstSearch(vertex);
foreach (var child in vertex.Children)
{
//Leaves number in the subtree rooted at child
int lambda = BFS.BreatFirstSearch(child);
double mi = theta + ((double)lambda / leavesNumber * (beta - alfa));
double x = radius * Math.Cos((theta + mi) / 2.0);
double y = radius * Math.Sin((theta + mi) / 2.0);
//setting x and y
child.Point.X = x;
child.Point.Y = y;
outputGraph.Add(new RadialPoint<string>
{
Node = child,
Point = new Point
{
X = x,
Y = y,
Radius = radius
},
ParentPoint = vertex.Point
});
if (child.Children.Count > 0)
{
child.Point.Y = y;
child.Point.X = x;
RadialPositions(rootedTree, child, theta, mi, outputGraph);
}
theta = mi;
}
}
BFS algorithm for getting leaves
public static int BreatFirstSearch<T>(Node<T> root)
{
var visited = new List<Node<T>>();
var queue = new Queue<Node<T>>();
int leaves = 0;
visited.Add(root);
queue.Enqueue(root);
while (queue.Count != 0)
{
var current = queue.Dequeue();
if (current.Children.Count == 0)
leaves++;
foreach (var node in current.Children)
{
if (!visited.Contains(node))
{
visited.Add(node);
queue.Enqueue(node);
}
}
}
return leaves;
}
Initial call
var outputPoints = new List<RadialPoint<string>>();
alg.RadialPositions(tree, tree.Root,0, 360, outputPoints);
mr.Pavlo result
My result on simple sample
Math.Cos and Sin expect the input angle to be in radians, not degrees. In your initial method call, your upper angle limit (beta) should be 2 * Math.PI, not 360. This will ensure that all the angles you calculate will be in radians and not degrees.
* Update *
Found a solution using Clipper library. Solution added as answer. New / better / easier ideas are still welcome though!
Given a path like this:
I want to create a path surrounding this path with a given distance, e.g. 1 cm. The following sketch demonstrates that - the red path surrounds the black path with a distance of 1 cm.
How can this be done in a generic way using PDFSharp? (Meaning I want to finally draw it with PDFSharp, I don't care where the calculations are done)
Here is the code for the black path:
// helper for easily getting an XPoint in centimeters
private XPoint cmPoint(double x, double y)
{
return new XPoint(
XUnit.FromCentimeter(x),
XUnit.FromCentimeter(y)
);
}
// the path to be drawn
private XGraphicsPath getMyPath()
{
XGraphicsPath path = new XGraphicsPath();
XPoint[] points = new XPoint[3];
points[0] = cmPoint(0, 0);
points[1] = cmPoint(5, 2);
points[2] = cmPoint(10,0);
path.AddCurve(points);
path.AddLine(cmPoint(10, 0), cmPoint(10, 10));
path.AddLine(cmPoint(10, 10), cmPoint(0, 10));
path.CloseFigure();
return path;
}
// generate the PDF file
private void button3_Click(object sender, RoutedEventArgs e)
{
// Create a temporary file
string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
}
Thanks for any help on this topic!
You can use Widen() function, which replaces the path with curves that enclose the area that is filled when the path is drawn by a specified pen, adding an additional outline to the path.
This function receives as parameter a XPen, so you can create this XPen using the desired offset as width and an outer path will be added at a constant distance (pen's width).
XGraphicsPath class is in fact a wrapper of System.Drawing.Drawing2D.GraphicsPath, so you can use Widen() function in XGraphicsPath, get the internal object and iterate on it using GraphicsPathIterator class to get the path added.
This method will do the job:
public XGraphicsPath GetSurroundPath(XGraphicsPath path, double width)
{
XGraphicsPath container = new XGraphicsPath();
container.StartFigure();
container.AddPath(path, false);
container.CloseFigure();
var penOffset = new XPen(XColors.Black, width);
container.StartFigure();
container.Widen(penOffset);
container.CloseFigure();
var iterator = new GraphicsPathIterator(container.Internals.GdiPath);
bool isClosed;
var outline = new XGraphicsPath();
iterator.NextSubpath(outline.Internals.GdiPath, out isClosed);
return outline;
}
You can handle level of flatness in curves using the overload Widen(XPen pen, XMatrix matrix, double flatness). Doing this call container.Widen(penOffset, XMatrix.Identity, 0.05); results in more rounded edges.
Then draw an outer path using this function:
string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
var path = getMyPath();
// draw the desired path
gfx.DrawPath(penBlack, path);
gfx.DrawPath(penRed, GetSurroundPath(path, XUnit.FromCentimeter(1).Point));
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
This is what you get:
Another way may be using reflection to retrieve internal Pen in XPen and setup CompoundArray property. This allows you draws parallel lines and spaces. Using this property you can do something like this:
But the problem is that you can only use one color, anyway this is just an idea, I have not tried in PDFsharp
Also, you should search for offset polyline curves or offsetting polygon algorithms.
This can be done using Clipper
double scale = 1024.0;
List<IntPoint> points = new List<IntPoint>();
points.Add(new IntPoint(0*scale, 0*scale));
points.Add(new IntPoint(5*scale, 2*scale));
points.Add(new IntPoint(10*scale, 0*scale));
points.Add(new IntPoint(10*scale, 10*scale));
points.Add(new IntPoint(0*scale, 10*scale));
points.Reverse();
List<List<IntPoint>> solution = new List<List<IntPoint>>();
ClipperOffset co = new ClipperOffset();
co.AddPath(points, JoinType.jtMiter, EndType.etClosedPolygon);
co.Execute(ref solution, 1 * scale);
foreach (IntPoint point in solution[0])
{
Console.WriteLine("OUTPUT: " + point.X + "/" + point.Y + " -> " + point.X/scale + "/" + point.Y/scale);
}
And the output:
OUTPUT: 11264/11264 -> 11/11
OUTPUT: -1024/11264 -> -1/11
OUTPUT: -1024/-1512 -> -1/-1,4765625
OUTPUT: 5120/945 -> 5/0,9228515625
OUTPUT: 11264/-1512 -> 11/-1,4765625
Drawn original and offset path:
This is still not perfect for various mathematical reasons, but already quite good.
This is an updated Answer requested
The XGraphicPath is sealed class which was implemented with bad practices IMO, so the only way is to use a wrapper around it. I tried to make the code as self documented as possible
public class OGraphicPath
{
private readonly ICollection<XPoint[]> _curves;
private readonly ICollection<Tuple<XPoint, XPoint>> _lines;
public OGraphicPath()
{
_lines = new List<Tuple<XPoint, XPoint>>();
_curves = new List<XPoint[]>();
}
public XGraphicsPath XGraphicsPath
{
get
{
var path = new XGraphicsPath();
foreach (var curve in _curves)
{
path.AddCurve(curve);
}
foreach (var line in _lines)
{
path.AddLine(line.Item1, line.Item2);
}
path.CloseFigure();
return path;
}
}
public void AddCurve(XPoint[] points)
{
_curves.Add(points);
}
public void AddLine(XPoint point1, XPoint point2)
{
_lines.Add(new Tuple<XPoint, XPoint>(point1, point2));
}
// Finds Highest and lowest X and Y to find the Center O(x,y)
private XPoint FindO()
{
var xs = new List<double>();
var ys = new List<double>();
foreach (var point in _curves.SelectMany(points => points))
{
xs.Add(point.X);
ys.Add(point.Y);
}
foreach (var line in _lines)
{
xs.Add(line.Item1.X);
xs.Add(line.Item2.X);
ys.Add(line.Item1.Y);
ys.Add(line.Item2.Y);
}
var OX = xs.Min() + xs.Max()/2;
var OY = ys.Min() + ys.Max()/2;
return new XPoint(OX, OY);
}
// If a point is above O, it's surrounded point is even higher, if it's below O, it's surrunded point is below O too...
private double FindPlace(double p, double o, double distance)
{
var dp = p - o;
if (dp < 0)
{
return p - distance;
}
if (dp > 0)
{
return p + distance;
}
return p;
}
public XGraphicsPath Surrond(double distance)
{
var path = new XGraphicsPath();
var O = FindO();
foreach (var curve in _curves)
{
var points = new XPoint[curve.Length];
for (var i = 0; i < curve.Length; i++)
{
var point = curve[i];
var x = FindPlace(point.X, O.X, distance);
var y = FindPlace(point.Y, O.Y, distance);
points[i] = new XPoint(x, y);
}
path.AddCurve(points);
}
foreach (var line in _lines)
{
var ax = FindPlace(line.Item1.X, O.X, distance);
var ay = FindPlace(line.Item1.Y, O.Y, distance);
var a = new XPoint(ax, ay);
var bx = FindPlace(line.Item2.X, O.X, distance);
var by = FindPlace(line.Item2.Y, O.Y, distance);
var b = new XPoint(bx, by);
path.AddLine(a, b);
}
path.CloseFigure();
return path;
}
}
And is Consumed Like this
// draw the desired path
var path = getMyPath();
gfx.DrawPath(penBlack, path.XGraphicsPath);
gfx.DrawPath(penRed, path.Surrond(XUnit.FromCentimeter(1)));
What if we made a "DrawOutline" extension to xGraphics?
public static class XGraphicsExtentions
{
public static void DrawOutline(this XGraphics gfx, XPen pen, XGraphicsPath path, int offset)
{
// finding the size of the original path so that we know how much to scale it in x and y
var points = path.Internals.GdiPath.PathPoints;
float minX, minY;
float maxX, maxY;
GetMinMaxValues(points, out minX, out minY, out maxX, out maxY);
var deltaY = XUnit.FromPoint(maxY - minY);
var deltaX = XUnit.FromPoint(maxX - minX);
var offsetInPoints = XUnit.FromCentimeter(offset);
var scaleX = XUnit.FromPoint((deltaX + offsetInPoints)/deltaX);
var scaleY = XUnit.FromPoint((deltaY + offsetInPoints)/deltaY);
var transform = -offsetInPoints/2.0;
gfx.TranslateTransform(transform, transform);
gfx.ScaleTransform(scaleX, scaleY);
gfx.DrawPath(pen, path);
// revert changes to graphics object before exiting
gfx.ScaleTransform(1/scaleX,1/scaleY);
gfx.TranslateTransform(-transform, -transform);
}
private static void GetMinMaxValues(PointF[] points, out float minX, out float minY, out float maxX, out float maxY)
{
minX = float.MaxValue;
maxX = float.MinValue;
minY = float.MaxValue;
maxY = float.MinValue;
foreach (var point in points)
{
if (point.X < minX)
minX = point.X;
if (point.X > maxX)
maxX = point.X;
if (point.Y < minY)
minY = point.Y;
if (point.Y > maxY)
maxY = point.Y;
}
}
}
Usage:
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
gfx.DrawOutline(penRed, getMyPath(), 2);
Result:
Clipper is a great choice, but depending on your needs it will not result in a perfect offset.
offset from edge is not equal to offset from corner
A better solution, which will require you to remove any beizer curves and only use line primitives, is using CGAL library for contour offsets: http://doc.cgal.org/latest/Straight_skeleton_2/index.html
Another way of doing it, which is actually pretty cool (albeit taking a lot of memory), is to convert your path to a bitmap and then apply a dilate operation, https://en.wikipedia.org/wiki/Dilation_(morphology). This will give you a correct transformation, but in the bitmap resolution.
You can the convert the bitmap to vector graphics, using a tool like https://en.wikipedia.org/wiki/Potrace
A good image toolbox is OpenCV, and http://www.emgu.com/wiki/index.php/Main_Page for .NET/C#. It includes dilation.
This will give you a somewhat limited resolution approach, but the end result will be precise to the bitmap resolution (and actually a lot higher since you are using a contour offset that, in fact, is limiting the offsetted contour details).
try this:
public Lis<Point> Draw(Point[] points /*Current polygon*/, int distance /*distance to new polygon*/) {
List<Point> lResult = new List<Point>();
foreach(Point lPoint in points) {
Point lNewPoint = new Point(lPoint.X - distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X + distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y - distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y + distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
}
return lResult; // Points of new polygon
}
private static int Crs(Point a1, Point a2, Point p, ref bool ans) {
const double e = 0.00000000000001;
int lCrsResult = 0;
if (Math.Abs(a1.Y - a2.Y) < e)
if ((Math.Abs(p.Y - a1.Y) < e) && ((p.X - a1.X) * (p.X - a2.X) < 0.0))
ans = false;
if ((a1.Y - p.Y) * (a2.Y - p.Y) > 0.0)
return lCrsResult;
double lX = a2.X - (a2.Y - p.Y) / (a2.Y - a1.Y) * (a2.X - a1.X);
if (Math.Abs(lX - p.X) < e)
ans = false;
else if (lX < p.X) {
lCrsResult = 1;
if ((Math.Abs(a1.Y - p.Y) < e) && (a1.Y < a2.Y))
lCrsResult = 0;
else if ((Math.Abs(a2.Y - p.Y) < e) && (a2.Y < a1.Y))
lCrsResult = 0;
}
return lCrsResult;
}
private static bool CheckCurrentPoint(Point p /*Point of new posible polygon*/, Points[] points /*points of current polygon*/) {
if (points.Count == 0)
return false;
int lC = 0;
bool lAns = true;
for (int lIndex = 1; lIndex < points.Count; lIndex++) {
lC += Crs(points[lIndex - 1], points[lIndex], p, ref lAns);
if (!lAns)
return false;
}
lC += Crs(points[points.Count - 1], points[0], p, ref lAns);
if (!lAns)
return false;
return (lC & 1) > 0;
}
From mentioned sample in comments
I tried making this Mandelbrot fractal generator, but when I run this, I get an output like a circle.
Not sure exactly why this happens. I think something may be wrong with my coloring, but even if so the shape is also incorrect.
public static Bitmap Generate(
int width,
int height,
double realMin,
double realMax,
double imaginaryMin,
double imaginaryMax,
int maxIterations,
int bound)
{
var bitmap = new FastBitmap(width, height);
var planeWidth = Math.Abs(realMin) + Math.Abs(realMax); // Total width of the plane.
var planeHeight = Math.Abs(imaginaryMin) + Math.Abs(imaginaryMax); // Total height of the plane.
var realStep = planeWidth / width; // Amount to step by on the real axis per pixel.
var imaginaryStep = planeHeight / height; // Amount to step by on the imaginary axis per pixel.
var realScaling = width / planeWidth;
var imaginaryScaling = height / planeHeight;
var boundSquared = bound ^ 2;
for (var real = realMin; real <= realMax; real += realStep) // Loop through the real axis.
{
for (var imaginary = imaginaryMin; imaginary <= imaginaryMax; imaginary += imaginaryStep) // Loop through the imaginary axis.
{
var z = Complex.Zero;
var c = new Complex(real, imaginary);
var iterations = 0;
for (; iterations < maxIterations; iterations++)
{
z = z * z + c;
if (z.Real * z.Real + z.Imaginary * z.Imaginary > boundSquared)
{
break;
}
}
if (iterations == maxIterations)
{
bitmap.SetPixel(
(int)((real + Math.Abs(realMin)) * realScaling),
(int)((imaginary + Math.Abs(imaginaryMin)) * imaginaryScaling),
Color.Black);
}
else
{
var nsmooth = iterations + 1 - Math.Log(Math.Log(Complex.Abs(z))) / Math.Log(2);
var color = MathHelper.HsvToRgb(0.95f + 10 * nsmooth, 0.6, 1.0);
bitmap.SetPixel(
(int)((real + Math.Abs(realMin)) * realScaling),
(int)((imaginary + Math.Abs(imaginaryMin)) * imaginaryScaling),
color);
}
}
}
return bitmap.Bitmap;
}
Here's one error:
var boundSquared = bound ^ 2;
This should be:
var boundSquared = bound * bound;
The ^ operator means xor.
I have to do something like this.
When I click on a node, it expands, and this is OK (I am using Powercharts to do it).
My big problem is creating random coordinates so that when I open the subnode, it doesn't overlap with another node/subnode.
In the Powercharts I have to pass the coordinates, so the big problem is in passing it.
I have to do the random coordinates in C#.
//-------------------------------------------------------
This is what i did so far:
This is what i do, is not overlaping, but i have a problem.
how can i start do the circles from a starting point?
for example, starts in the middle (300,300) and then do circles around it. Is possible?
private void button2_Click(object sender, EventArgs e)
{
g = pictureBox1.CreateGraphics();
g.Clear(pictureBox1.BackColor);
double angle;
Circle item0 = new Circle();
item0.x=200;
item0.y=150;
item0.r=50;
listaCirculos.Add(item0);
Random randomMember = new Random();
g.DrawEllipse(pen1, 200, 150, 50, 50);
while(listaCirculos.Count!=11)
{
int[] it = GenerateNewCircle(600);
Circle item = new Circle();
item.x = it[0];
item.y = it[1];
item.r = 50;
if (circleIsAllowed(listaCirculos, item))
{
listaCirculos.Add(item);
g.DrawEllipse(pen1, Convert.ToInt32(item.x), Convert.ToInt32(item.y), 50, 50);
}
}
}
bool circleIsAllowed(List<Circle> circles, Circle newCircle)
{
foreach(Circle it in circles)
{
//double sumR = it.x + newCircle.r;
//double dx = it.x - newCircle.x;
//double dy = it.y - newCircle.y;
//double squaredDist = dx * dx + dy * dy;
double aX = Math.Pow(it.x - newCircle.x, 2);
double aY = Math.Pow(it.y - newCircle.y, 2);
double Dif = Math.Abs(aX - aY);
double ra1 = it.r / 2;
double ra2 = it.r / 2;
double raDif = Math.Pow(ra1 + ra2, 2);
if ((raDif + 1) > Dif) return false;
//if (squaredDist < sumR*sumR) return false;
}
return true; // no existing circle overlaps
}
public int[] GenerateNewCircle(int maxSize)
{
int x, y;
Random randomMember = new Random();
x = randomMember.Next(0,maxSize);
if (x - 50 < 0)
y = randomMember.Next(x + 50, maxSize);
else if (x + 50 > 600)
y = randomMember.Next(0, x - 50);
else
// in this case, x splits the range 0..n into 2 subranges.
// get a random number and skip the "gap" if necessary
y = randomMember.Next(0, maxSize - 50);
if (y > x - 50)
{
y += 20;
}
int[] abc = new int[2];
abc[0] = x;
abc[1] = y;
return abc;
}
Size sizeShape = new Size("SomeWidth" , "SomeHeight");
List<Retangle> rects = new List<Retangle>();
Random r = new Random();
while(rects.count != "someCount")
{
Point rPoint = new Point(r.Next(500) , r.Next(500))
bool isNew = true;
foreach(Rectangle r in rects)
{
if(r.contains(rPoint))
isNew = false;
}
if(isNew)
rects.Add(GetRect(rPoint , sizeShape));
}
private Rectangle GetRect(Point p , Size s)
{
return new Rectangle(p,s);
}
After than you can use from rects ,
rects has random coordinates.
Here's the link to let you know how to create Random Numbers in C#.
You have to keep in mind that you will generate random numbers, but you also have to make sure the numbers generated will not make objects overlap.
Despite this being pretty poorly worded, I believe what you are looking for is the Random class. It is instantiated as such:
Random thisRandom = new Random();
Then from there, if you want to generate a random coordinate, you need to know the maximum possible X and Y coordinates. Knowing these will allow you to generate X and Y coordinates within the given canvas as such:
int maxX = 500; //This is the maximum X value on your canvas
int maxY = 500; //This is the maximum Y value on your canvas
int newX, newY;
newX = thisRandom.Next(maxX);
newY = thisRandom.Next(maxY);
While its not the absolute best in terms of "true" randomization this should give you what you need.
Is there a simple way to retrieve the X/Y coordinates of ANY point in the chart Area (relative to that chart Axis of course)?
As of now, I just managed to retrieve coordinates when the mouse is on a Series (not outside)
private void chart_GetToolTipText(object sender, ToolTipEventArgs e)
{
if (e.HitTestResult.Series != null)
{
e.Text = e.HitTestResult.Series.Points[e.HitTestResult.PointIndex].YValues[0] + " \n " + DateTime.FromOADate(e.HitTestResult.Series.Points[e.HitTestResult.PointIndex].XValue);
}
}
Anyway, as always with MS Chart Controls, there is no easy way to do things, but a funky workaround to get things done. I am sadly getting used to it...
private void chart1_MouseWhatever(object sender, MouseEventArgs e)
{
chartArea1.CursorX.SetCursorPixelPosition(new Point(e.X, e.Y), true);
chartArea1.CursorY.SetCursorPixelPosition(new Point(e.X, e.Y), true);
double pX = chartArea1.CursorX.Position; //X Axis Coordinate of your mouse cursor
double pY = chartArea1.CursorY.Position; //Y Axis Coordinate of your mouse cursor
}
This works for my purposes and doesn't side effect the cursor.
private Tuple<double,double> GetAxisValuesFromMouse(int x, int y)
{
var chartArea = _chart.ChartAreas[0];
var xValue = chartArea.AxisX.PixelPositionToValue(x);
var yValue = chartArea.AxisY.PixelPositionToValue(y);
return new Tuple<double, double>(xValue, yValue);
}
I tried your answer, but it didn't work for me. It ended up putting the cursor in one spot and never moving. I believe this is because I use decimal/double values along both axes, and the cursor is being rounded to the nearest integer.
After several attempts, I was able to work out a method for determining where the cursor is inside the chart. The hard part was figuring out that all the "positions" for the chart elements are actually percentage values (from 0 to 100).
As per
http://msdn.microsoft.com/en-us/library/system.windows.forms.datavisualization.charting.elementposition.aspx:
"Defines the position of the chart element in relative coordinates, which range from (0,0) to (100,100)."
I hope you don't mind, I am posting this answer here just for posterity, in case anyone else comes across this problem, and your method also does not work for them. Its not pretty or elegant in any way, but so far it works for me.
private struct PointD
{
public double X;
public double Y;
public PointD(double X, double Y)
{
this.X = X;
this.Y = Y;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = LocationInChart(e.X, e.Y);
lblCoords.Text = string.Format("({0}, {1}) ... ({2}, {3})", e.X, e.Y, pos.X, pos.Y);
}
private PointD LocationInChart(double xMouse, double yMouse)
{
var ca = chart1.ChartAreas[0];
//Position inside the control, from 0 to 100
var relPosInControl = new PointD
(
((double)xMouse / (double)execDetailsChart.Width) * 100,
((double)yMouse / (double)execDetailsChart.Height) * 100
);
//Verify we are inside the Chart Area
if (relPosInControl.X < ca.Position.X || relPosInControl.X > ca.Position.Right
|| relPosInControl.Y < ca.Position.Y || relPosInControl.Y > ca.Position.Bottom) return new PointD(double.NaN, double.NaN);
//Position inside the Chart Area, from 0 to 100
var relPosInChartArea = new PointD
(
((relPosInControl.X - ca.Position.X) / ca.Position.Width) * 100,
((relPosInControl.Y - ca.Position.Y) / ca.Position.Height) * 100
);
//Verify we are inside the Plot Area
if (relPosInChartArea.X < ca.InnerPlotPosition.X || relPosInChartArea.X > ca.InnerPlotPosition.Right
|| relPosInChartArea.Y < ca.InnerPlotPosition.Y || relPosInChartArea.Y > ca.InnerPlotPosition.Bottom) return new PointD(double.NaN, double.NaN);
//Position inside the Plot Area, 0 to 1
var relPosInPlotArea = new PointD
(
((relPosInChartArea.X - ca.InnerPlotPosition.X) / ca.InnerPlotPosition.Width),
((relPosInChartArea.Y - ca.InnerPlotPosition.Y) / ca.InnerPlotPosition.Height)
);
var X = relPosInPlotArea.X * (ca.AxisX.Maximum - ca.AxisX.Minimum) + ca.AxisX.Minimum;
var Y = (1 - relPosInPlotArea.Y) * (ca.AxisY.Maximum - ca.AxisY.Minimum) + ca.AxisY.Minimum;
return new PointD(X, Y);
}
private void OnChartMouseMove(object sender, MouseEventArgs e)
{
var sourceChart = sender as Chart;
HitTestResult result = sourceChart.HitTest(e.X, e.Y);
ChartArea chartAreas = sourceChart.ChartAreas[0];
if (result.ChartElementType == ChartElementType.DataPoint)
{
chartAreas.CursorX.Position = chartAreas.AxisX.PixelPositionToValue(e.X);
chartAreas.CursorY.Position = chartAreas.AxisY.PixelPositionToValue(e.Y);
}
}
This works
private void chart1_MouseWhatever(object sender, MouseEventArgs e)
{
Point chartLocationOnForm = chart1.FindForm().PointToClient(chart1.Parent.PointToScreen(chart1.Location));
double x = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X - chartLocationOnForm.X);
double y = chart1.ChartAreas[0].AxisY.PixelPositionToValue(e.Y - chartLocationOnForm.Y);
}
And this works
private void chart1_MouseWhatever(object sender, MouseEventArgs e)
{
Point chartLocationOnForm = chart1.FindForm().PointToClient(chart1.Parent.PointToScreen(chart1.Location));
chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(new PointF(e.X - chartLocationOnForm.X, e.Y - chartLocationOnForm.Y), true);
chart1.ChartAreas[0].CursorY.SetCursorPixelPosition(new PointF(e.X - chartLocationOnForm.X, e.Y - chartLocationOnForm.Y), true);
double x = chart1.ChartAreas[0].CursorX.Position;
double y = chart1.ChartAreas[0].CursorY.Position;
}
This is what I got, I think many of us are along the same lines but with different interpretations of what it is you're looking for.
This will give you the coordinates at any location in the plotting area. I found the HitTest gives a clean and simple solution, but there are a few checks to make, whether the cursor is on a data point, a gridline, or in the plotting area (which seem to take precedence in that order). I assume you'll be interested in the coordinate regardless of which of those objects the mouse is over.
private void chart_GetToolTipText(object sender, ToolTipEventArgs e)
{
// If the mouse isn't on the plotting area, a datapoint, or gridline then exit
HitTestResult htr = chart.HitTest(e.X, e.Y);
if (htr.ChartElementType != ChartElementType.PlottingArea && htr.ChartElementType != ChartElementType.DataPoint && htr.ChartElementType != ChartElementType.Gridlines)
return;
ChartArea ca = chart.ChartAreas[0]; // Assuming you only have 1 chart area on the chart
double xCoord = ca.AxisX.PixelPositionToValue(e.X);
double yCoord = ca.AxisY.PixelPositionToValue(e.Y);
e.Text = "X = " + Math.Round(xCoord, 2).ToString() + "\nY = " + Math.Round(yCoord, 2).ToString();
}
VB.net version, with zoom correction:
Private Function LocationInChart(xMouse, yMouse) As PointF
Dim ca = Chart1.ChartAreas(0)
'Position inside the control, from 0 to 100
Dim relPosInControl = New PointF((xMouse / Chart1.Width) * 100, (yMouse / Chart1.Height) * 100)
'Verify we are inside the Chart Area
If (relPosInControl.X < ca.Position.X Or relPosInControl.X > ca.Position.Right Or relPosInControl.Y < ca.Position.Y Or relPosInControl.Y > ca.Position.Bottom) Then Return New PointF(Double.NaN, Double.NaN)
'Position inside the Chart Area, from 0 to 100
Dim relPosInChartArea = New PointF(((relPosInControl.X - ca.Position.X) / ca.Position.Width) * 100, ((relPosInControl.Y - ca.Position.Y) / ca.Position.Height) * 100)
'Verify we are inside the Plot Area
If (relPosInChartArea.X < ca.InnerPlotPosition.X Or relPosInChartArea.X > ca.InnerPlotPosition.Right Or relPosInChartArea.Y < ca.InnerPlotPosition.Y Or relPosInChartArea.Y > ca.InnerPlotPosition.Bottom) Then Return New PointF(Double.NaN, Double.NaN)
'Position inside the Plot Area, 0 to 1
Dim relPosInPlotArea = New PointF(((relPosInChartArea.X - ca.InnerPlotPosition.X) / ca.InnerPlotPosition.Width), ((relPosInChartArea.Y - ca.InnerPlotPosition.Y) / ca.InnerPlotPosition.Height))
Dim X = relPosInPlotArea.X * (ca.AxisX.Maximum - ca.AxisX.Minimum) + ca.AxisX.Minimum
Dim Y = (1 - relPosInPlotArea.Y) * (ca.AxisY.Maximum - ca.AxisY.Minimum) + ca.AxisY.Minimum
' zoomo korekcija
Dim zoomx = (ca.AxisX.ScaleView.ViewMaximum - ca.AxisX.ScaleView.ViewMinimum) / (ca.AxisX.Maximum - ca.AxisX.Minimum)
Dim zoomy = (ca.AxisY.ScaleView.ViewMaximum - ca.AxisY.ScaleView.ViewMinimum) / (ca.AxisY.Maximum - ca.AxisY.Minimum)
Dim xx = ca.AxisX.ScaleView.ViewMinimum + X * zoomx
Dim yy = ca.AxisY.ScaleView.ViewMinimum + Y * zoomy
Return New PointF(xx, yy)
End Function