I'm working on a simple drawing app to further advance my skills and I can't seem to get the logic down for an eraser tool. The app simply uses the Line class to create lines as the user moves their finger. For the eraser tool I tried using the VisualTreeHelper as follows:
List<UIElement> elements = (List<UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(tree), ContentPanelCanvas);
foreach (UIElement element in elements)
{
if (element is Line)
{
this.ContentPanelCanvas.Children.Remove(element);
}
}
It at some points but can be very slow and laggy. Sometimes I would have to touch the area more than 5 times to get rid of the line there.
Is there an alternative to this?
The e.GetPosition(tree) will be returning a point. Try instead using a Rect with the position as its center.
const double fingerMargin = 10.0;
Point p = e.GetPosition(tree);
Rect r = new Rect(p.X - fingerMargin, p.Y - fingerMargin, fingerMargin * 2, fingerMargin * 2);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(r, ContentPanelCanvas);
Line lineToRemove = elements.OfType<Line>().FirstOrDefault();
if (lineToRemove != null)
{
ContentPanelCanvas.Children.Remove(lineToRemove);
}
Note don't cast the result of FindElementsInHostCoordinates to List<T>, that is an implementation detail, the documentation only guarantees it to be an IEnumerable<UIElement>, besides which it is an unnecessary cast.
You are actually looking for the set of elements that match the hit test of a single pixel. If your lines are narrow, then it's like a needle in a haystack; it's very hard to hit the line precisely to remove it.
Instead you need to use a fuzzy match using a rectangle instead of a point. You can use the same API, just the rectangle version of it:
VisualTreeHelper.FindElementsInHostCoordinates Method (Rect, UIElement)
VisualTreeHelper.FindElementsInHostCoordinates(r, MainCanvas);
Is not returning any Elements.
Related
I'm using the Windows.Forms.DataVisualization.Charting class to draw a simple char plotting a simple courb.
My courb is correclty plotted, but the points are not shown. Like dots or crosses where the points are.
I tried using datapoint.BackImage, but that doesn't show anything.
I'm sure that the image is found because I store other images in the exact same folder, and they are correctly read when I use the same path.
The code where I feed the DataPoint:
foreach (MesureTaille mesureTaille in tailles)
{
DataPoint point = new DataPoint(mesureTaille.age, mesureTaille.taille);
point.BackImage = string.Concat(
Application.StartupPath.Remove(Application.StartupPath.IndexOf("\\bin\\Debug")),
"/BackgoundImage/dot.png");
Serie_Age_Taille.Points.Add(point);
}
As DavidG and TaW pointed out, what I needed was Series.MarkerStyle :
// This line sets the dots !
Serie_Age_Taille.MarkerStyle = MarkerStyle.Cross;
foreach (MesureTaille mesureTaille in tailles)
{
// And I don't need to do anything on the DataPoints
DataPoint point = new DataPoint(mesureTaille.age, mesureTaille.taille);
Serie_Age_Taille.Points.Add(point);
}
I am drawing a line on a graph from numbers read from a text file. There is a number on each line of the file which corresponds to the X co-ordinate while the Y co-ordinate is the line it is on.
The requirements have now changed to include "special events" where if the number on the line is followed by the word special a spike will appear like image below:
Currently the only way I can find is to use a line for each spike, however there could be a large of these special events and so needs to be modular. This seems an efficient and bad way to program it.
Is it possible to add the spikes to the same graph line? Or is it possible to use just one additional line and have it broken (invisible) and only show where the spikes are meant to be seen?
I have looked at using bar graphs but due to other items on the graph I cannot.
The DataPoints of a Line Chart are connected so it is not possble to really break it apart. However each segment leading to a DataPoint can have its own color and that includes Color.Transparent which lends itself to a simple trick..
Without adding extra Series or Annotations, your two questions can be solved like this:
To simply add the 'spikes' you show us in the 2nd graph, all you need to do is to insert 2 suitable datapoints, the 2nd being identical to the point the spike is connected to.
To add an unconnected line you need to 'jump' to its beginning by adding one extra point with a transparent color.
Here are two example methods:
void addSpike(Series s, int index, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
s.Points.Insert(index+1, dp1);
s.Points.Insert(index+2, dp);
}
void addLine(Series s, int index, double spikeDist, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeDist, dp.YValues[0]);
DataPoint dp2 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
DataPoint dp0 = dp.Clone();
dp1.Color = Color.Transparent;
dp2.Color = dp.Color;
dp2.BorderWidth = 2; // optional
dp0.Color = Color.Transparent;
s.Points.Insert(index + 1, dp1);
s.Points.Insert(index + 2, dp2);
s.Points.Insert(index + 3, dp0);
}
You can call them like this:
addSpike(chart1.Series[0], 3, 50d);
addLine(chart1.Series[0], 6, 30d, 80d);
Note that they add 2 or 3 DataPoints to the Points collection!
Of course you can set the Color and width (aka BorderWidth) of the extra lines as you wish and also include them in the params list..
If you want to keep the points collection unchanged you also can simply create one 'spikes series' and add the spike points there. The trick is to 'jump' to the new points with a transparent line!
I'm doing a Revit Macro to get the center point of a part (floor part) to check if it is inside a room or a space.
I couldn't get much of the BoundingBox object which is giving me a point outside the part, so I tried to use the Geometry element internal faces getting the mesh vertices but I'm stuck calculating the mid point.
I'm using a rather naive algorithm shown in the snippet below, but it's giving me false results as it seems to be affected by the initial default of min/max variables.
Any suggestions?
PS: DebugTools is a custom helper class of my own.
public void ZoneDetect()
{
Document doc = this.ActiveUIDocument.Document;
using (Transaction t = new Transaction(doc,"Set Rooms By Region"))
{
t.Start();
FilteredElementCollector fec =
new FilteredElementCollector(doc)
.OfClass(typeof(Part))
.OfCategory(BuiltInCategory.OST_Parts)
.Cast<Part>();
foreach (Part p in fec)
{
Options op = new Options();
op.View=doc.ActiveView;
op.ComputeReferences=true;
GeometryElement gm=p.get_Geometry(op);
Solid so = gm.First() as Solid;
PlanarFace fc=so.Faces.get_Item(0) as PlanarFace;
foreach (PlanarFace f in so.Faces)
{
if (f.Normal == new XYZ(0,0,-1)) fc=f;
}
XYZ max = new XYZ();
XYZ min = new XYZ();
int no = 0;
foreach (XYZ vx in fc.Triangulate().Vertices)
{
// Just for debugging
DebugTools.DrawModelTick(vx,doc,"Max");
doc.Regenerate();
TaskDialog.Show("Point:"+no.ToString(),vx.ToString());
no++;
//Comparing points
if (vx.X>max.X) max=new XYZ (vx.X,max.Y,0);
if (vx.Y>max.Y) max=new XYZ (max.X,vx.Y,0);
if (vx.X<min.X) min=new XYZ (vx.X,min.Y,0);
if (vx.Y<min.Y) min=new XYZ (min.X,vx.Y,0);
}
XYZ mid = new XYZ(max.X-min.X,max.Y-min.Y,0);
DebugTools.DrawModelTick(mid,doc,"Mid");
DebugTools.DrawModelTick(max,doc,"Max");
DebugTools.DrawModelTick(min,doc,"Min");
}
t.Commit();
}
}
It seems like you're looking for the center of gravity of a polygon. An algorithm for that can be found here: Center of gravity of a polygon
Once you have a Face object, you can enumerate its edges to receive a list of vertex points. Use the longest of the EdgeLoops in the face. Collect all the points and make sure that they are in the right order (the start and end points of the edges might need to be swapped).
Daren & Matt thanks a lot for your answers,
Since I'm dealing with rather simple shapes ( mainly rectangles ) I just needed to get a point roughly near the center to test whether it is inside a room, my problem was with the naive algorithm I was using which turned out to be wrong.
I corrected it as follows:
XYZ midSum = Max + Min;
XYZ mid = new XYZ(midSum.X/2 , midSum.Y/2,0);
I will look into refining it using the link you've provided, but as for now I will get into finishing my task in hand.
Many thanks
I'm using C# charts to display/compare some data. I changed the graph scale to logarithmic (as my data points have huge differences) but since logarithmic scaling doesn't support zero values, I want to just add an empty point (or skip a data point) for such cases. I have tried the following but non works and all crashes:
if (/*the point is zero*/)
{
// myChart.Series["mySeries"].Points.AddY(null);
// or
// myChart.Series["mySeries"].Points.AddY();
// or just skip the point
}
Is it possible to add an empty point or just skip a point?
I found two ways to solve the problem.
One is using double.NaN (suddenly!):
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.AddY(double.NaN);
// or ...Points.Add(double.NaN)
This looks like zero
And in my case it didn't crash with following SeriesChartTypes:
Column, Doughnut, FastPoint, Funnel, Kagi, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedBar, StackedColumn, ThreeLineBreak
The other way is a built-in concept of an empty point:
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.Add(new DataPoint { IsEmpty = true });
This looks like a missing point (a gap):
And in my case it didn't crash with following SeriesChartTypes:
Area, Bar, Column, Doughnut, FastPoint, Funnel, Line, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedArea, StackedArea100, StackedBar, StackedBar100, StackedColumn, StackedColumn100, StepLine, ThreeLineBreak
The 2nd approach feels like the right (by design) one. The 1st one looks like a hack, that accidentally appears to work.
You can do a trick. You obviously have to clear you series. When you start adding points to an empty points collection x-coordinates are generated automatically starting at 1. You can create surrogate x yourself and skip some x-values.
int x = 0;
foreach(var y in yValues)
{
x++;
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
continue;
myChart.Series["mySeries"].Points.AddXY(x, y);
}
With bar-like chart types it will look like a missing value.
I have a function where I try to find a matching Point between 2 collections of 4 Points each, but sometimes the function reports the collections do not share a common Point even though in the debugger I see they do. is the debugger not showing me the full precision of the points so I do not see the difference? or is there something else going on here? here's the code to blame:
public static Point CorrectForAllowedDrawArea(Point previousDisplayLocation, Point newDisplayLocation, Rect displayLimitedArea, Rect newBoundingBox)
{
// get area that encloses both rectangles
Rect enclosingRect = Rect.Union(displayLimitedArea, newBoundingBox);
// get corners of outer rectangle, index matters for getting opposite corner
var outsideCorners = new[] { enclosingRect.TopLeft, enclosingRect.TopRight, enclosingRect.BottomRight, enclosingRect.BottomLeft }.ToList();
// get corners of inner rectangle
var insideCorners = new[] { displayLimitedArea.TopLeft, displayLimitedArea.TopRight, displayLimitedArea.BottomRight, displayLimitedArea.BottomLeft }.ToList();
// get the first found corner that both rectangles share
Point sharedCorner = outsideCorners.FirstOrDefault((corner) => insideCorners.Contains(corner));
// find the index of the opposite corner
int oppositeCornerIndex = (outsideCorners.IndexOf(sharedCorner) + 2) % 4;
on the last line 'sharedCorner' is sometimes set to default(Point) even though both Point collections appear to share 1 Point.
EDIT: I should mention if I place the debugger back to the top of the function and restart it still does not find the matching point. I should also mention that this function uses the Point class of the System.Windows namespace and not of the System.Drawing namespace! Thanks for pointing this out to me in the comments.
We really need to see what the definition of insideCorners.Contains(corner) is, but I suspect that your problem is due to the inherent inaccuracies with floating point numbers.
You cannot compare two floating point values like this:
if (a == b)
{
// Values are equal
}
especially if either a or b are calculated values.
You'll need to implement something along the lines of:
if (Math.Abs(a - b) < some_small_value)
{
// Values are equal
}