I am currently reading Real-World Functional Programming: With Examples in F# and C# by Tomas Petricek and Jon Skeet. I am a bit puzzled by a particular example in which we generate an application that displays a pie chart with some population statistics along with labels.
Now it is the part of drawing the label or rather setting the coordinates of the label that I am confused by. I hope that the author doesn't mind that I attach the excerpt here but it would be hard to get clarification on the code without showing it.
let centerX, centerY = 300.0, 200.0
let labelDistance = 150.0
let drawLabel (gr: Graphics) title startAngle angle =
let lblAngle = float(startAngle + angle / 2)
let ra = Math.PI * 2.0 * lblAngle / 360.0
let x = centerX + labelDistance * cos(ra)
let y = centerY + labelDistance * sin(ra)
let size = gr.MeasureString(title, fnt)
let rc = new PointF(float32(x) - size.Width / 2.0f,
float32(y) - size.Height / 2.0f)
gr.DrawString(title, fnt, Brushes.Black, new RectangleF(rc, size))
It seems that labelDistance and centerX, centerY define some standard "offset" from the center of the drawing surface and I'm guessing that the trigonometric functions define the angle of the label because if I omit those then all labels are placed on top of eachother in the bottom right corner. But I don't quite understand how this works. What exactly happens here?
Giving this a go by adding comments, not necessarily worked out in this order:
// startAngle is the angle in degrees of this segment, angle is the angle of
// the segment itself.
let drawLabel (gr: Graphics) title startAngle angle =
// So this is the angle of the centre of this segment.
let lblAngle = float(startAngle + angle / 2)
// And ra is the same angle, now in radians.
let ra = Math.PI * 2.0 * lblAngle / 360.0
// So these work out the position of the label in the usual
// way, using cosine(angle-in-radians) and then scaling for the X
// and using sine for the Y. Both relative to the centre of the
// circle.
let x = centerX + labelDistance * cos(ra)
let y = centerY + labelDistance * sin(ra)
// How long, in pixels, is the text?
let size = gr.MeasureString(title, fnt)
// Create an instance of the right data structure adjusting
// so the calculated point is the centre of the rectangle
// in which the text will be drawn.
let rc = new PointF(float32(x) - size.Width / 2.0f,
float32(y) - size.Height / 2.0f)
// And, thus, we can now draw the text.
gr.DrawString(title, fnt, Brushes.Black, new RectangleF(rc, size))
Related
I want to create an arc in an SceneView. People told me I have to use EllipticArcSegment, and it works in MapView, but I can use it in SceneView. When I use EllipticArcSegment in ScenView I get a line not an arc. See the picture.
public MainWindow()
{
InitializeComponent();
MySceneView.Scene = new Scene(BasemapStyle.ArcGISImageryStandard);
MyMapView.Map = new Map(BasemapStyle.ArcGISTopographic);
MapPoint _spain = new MapPoint(-4.00475, 40.637861, SpatialReferences.Wgs84);
Graphic _arc = CreateArc(_spain, 0.080, 90.0, 45.0);
Graphic _arc1 = CreateArc(_spain, 0.080, 90.0, 45.0);
_AiminginZone_GO.Graphics.Add(_arc);
_AiminginZone_GO1.Graphics.Add(_arc1);
MyMapView.GraphicsOverlays.Add(_AiminginZone_GO);
MySceneView.GraphicsOverlays.Add(_AiminginZone_GO1);
}
private Graphic CreateArc(MapPoint center, double radius, double arcAngle, double azimuth)
{
_AiminginZone_GO.Graphics.Clear();
_AiminginZone_GO1.Graphics.Clear();
// set up spatial references
SpatialReference wgs84 = SpatialReferences.Wgs84;
SpatialReference webMercator = SpatialReferences.WebMercator;
// make a point in lat/long (WGS84)
MapPoint centrePoint = new MapPoint(center.X, center.Y, 56, wgs84);
MapPoint centrePoint1 = new MapPoint(center.X+0.05, center.Y, 56, wgs84);
// project it to a mercator projection where the unit of measure is metres
MapPoint mercatorPoint = (MapPoint)GeometryEngine.Project(centrePoint, webMercator);
// create an arc segment which is 50Kilometers radius, starting at 90 degrees, the arc angle is 45 degrees clockwise
//EllipticArcSegment arcSegment = new EllipticArcSegment(centrePoint, mercatorPoint, (Math.PI / 180) * 90, true, true, (Math.PI / 180) * 90, (Math.PI / 180) * -45, wgs84);
EllipticArcSegment arcSegment = EllipticArcSegment.CreateCircularEllipticArc(mercatorPoint, 50000, (Math.PI / 180) * 90, (Math.PI / 180) * -45, webMercator);
// make a part with the segment
Part part = new Part(webMercator);
part.Add(arcSegment);
// create the line from the part
Polyline line = new Polyline(part);
// add it to a graphic and graphics overlay to allow us to visualise it
var polylineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Dash, System.Drawing.Color.Red, 3.0);
Graphic arcGraphic = new Graphic(line, polylineSymbol);
return arcGraphic;
}
enter image description here
There is a bug using EllipticArcSegment.CreateCircularEllipticArc, please see the link to have more informat.
enter link description here
I have a canvas with a background image:
var bi = new BitmapImage(new Uri(imgLocFull));
var ib = new ImageBrush(bi) {Stretch = Stretch.UniformToFill};
MyCanvas.Background = ib;
I am overlaying various shapes on the image, and want the position of the shapes relative to the background image to be fixed.
If my application window is resized, the amount of the image that is cropped, horizontally and vertically, changes, and when my shapes are redrawn, they do not appear in the same position on the background image.
How can I determine how much of the image has been cropped (to apply an adjustment factor to the overlaid objects' positions?) Or is there a better way of fixing the location of a shape relative to the background image?
Here is my present drawing code:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetBottom(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
EDIT: Further Clarification
Here's a concrete example of what I am trying to achieve. My image is an aerial photograph, and I want to mark a particular geographical feature (with, say, an ellipse.)
When the window is resized, the ellipse doesn't stay on the feature, it stays relative to the left and top of the canvas.
I can get it closer to the right place by moving it using a factor (newx = newheight/oldheight * oldx) but this doesn't quite work because of the UniformToFill stretch mode, which sees some of the image clipped off the canvas.
The Top and Left of the Canvas are 'anchored', while the Bottom and Right move when resizing... try setting the Canvas.Top Attached Property instead, along with the Canvas.Left Attached Property as you are:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetTop(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
UPDATE >>>
You asked Or is there a better way of fixing the location of a shape relative to the background image?
I answered this question, so I don't understand why you would need to do anything else... your objects will not move when the screen in resized *if you only set the Grid.Top and Grid.Left properties.
So, if this is the code I'm using to draw a circle on my canvas:
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
ctx.lineWidth = 3;
ctx.strokeStyle = "black";
ctx.stroke();
... how could I get an array of coordinates of the points that make up this circle so I can save them in a database and load on canvas later using the context.moveTo() and context.lineTo() methods to connect the dots, drawing the same circle?
I guess I'm asking if it's possible to draw this kind of circle using not .arc() method but by connecting dots with lines, if I only know my center coordinates and circle radius (and of course the width of the line and color). This should enable me to save each dot coordinates in an array as I loop through.
#Octopus is on the right track:
var centerX=100;
var centerY=100;
var radius=40;
// an array to save your points
var points=[];
for(var degree=0;degree<360;degree++){
var radians = degree * Math.PI/180;
var x = centerX + radius * Math.cos(radians);
var y = centerY + radius * Math.sin(radians);
points.push({x:x,y:y});
}
Then you can draw the circle using the point objects in the points array:
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.closePath();
ctx.fillStyle="skyblue";
ctx.fill();
ctx.strokeStyle="lightgray";
ctx.lineWidth=3;
ctx.stroke()
A suggestion, however...
Instead of saving all the points in the database, just save the centerX/Y and radius in the database.
Then you can use this same math to create the points and draw the circle.
You are asking for the formula for a circle which is:
radius*radius = (x-centerX)*(x-centerX) + (y-centerY)*(y-centerY)
Or if you want to generate n points do something like this:
for (i=0;i<n;i++) {
x[i] = centerX + radius* Math.cos( (i*2*Math.PI)/n );
y[i] = centerY + radius*-Math.sin( (i*2*Math.PI)/n );
}
How can I scale PictureBox components to best fit the given space on the screen while keeping their aspect ratio (interdependent of the actual image or its SizeMode) ?
I tested setting the Dock of the FlowLayout and the PictureBox to Fill. I also tested using a Panel as a wrapper and tested different settings for AutoSize and AutoSizeMode.
To give more information about the background: I want to dynamically add and remove images in the viewport of the application, so a TableLayout is in the first step sort of to static. I have to admit I'm was also thinking of calculating the size an position manually - or to dynamically adapt the row and column count of the TableLayout - but it seems to me prone to errors. I thought having a FlowLayout and automatically sized components should be the correct way - but it seems not to work that way. (To speak as web developer, I simply want to "float the images left", "with width and height set to 'auto'" and no scrolling.)
The images should visualize this a bit: the first figure should point out the layout, if there is only one PictureBox - it takes the whole space (or as big as possible with the given aspect ratio). The second shows how I would like the Layout to be, if there are two (three or four) images. The third figure is showing basically a resized window with three (to six) images.
Is there some point I'm missing?
This code snippet do this:
It arranges the visible controls inside a container in respect of the aspect ratio (see R variable in the code), and uses the container margin values to get horizontal and vertical gaps between items. The padding of the container is also handled.
public static void Arrange(Control container)
{
var H = container.DisplayRectangle.Height;
var W = container.DisplayRectangle.Width;
var N = container.Controls.OfType<Control>().Count(c => c.Visible);
var R = 4 / 3d; // item aspect ratio
var margin = container.Margin;
var padding = container.Padding;
var horizontalGap = margin.Left + margin.Right;
var verticalGap = margin.Top + margin.Bottom;
if (N == 0)
return;
var bestSizedItem = (
// Try n rows
Enumerable.Range(1, N).Select(testRowCount =>
{
var testItemHeight = (H - verticalGap * (testRowCount - 1)) / testRowCount;
return new
{
testColCount = (int)Math.Ceiling((double)N / testRowCount),
testRowCount = testRowCount,
testItemHeight = (int)testItemHeight,
testItemWidth = (int)(testItemHeight * R)
};
})
// Try n columns
.Concat(
Enumerable.Range(1, N).Select(testColCount =>
{
var testItemWidth = (W - horizontalGap * (testColCount - 1)) / testColCount;
return new
{
testColCount = testColCount,
testRowCount = (int)Math.Ceiling((double)N / testColCount),
testItemHeight = (int)(testItemWidth / R),
testItemWidth = (int)testItemWidth
};
})))
// Remove when it's too big
.Where(item => item.testItemWidth * item.testColCount + horizontalGap * (item.testColCount - 1) <= W &&
item.testItemHeight * item.testRowCount + verticalGap * (item.testRowCount - 1) <= H)
// Get the biggest area
.OrderBy(item => item.testItemHeight * item.testItemWidth)
.LastOrDefault();
Debug.Assert(bestSizedItem != null);
if (bestSizedItem == null)
return;
int x = container.DisplayRectangle.X;
int y = container.DisplayRectangle.Y;
foreach (var control in container.Controls.OfType<Control>().Where(c => c.Visible))
{
control.SetBounds(x, y,
bestSizedItem.testItemWidth,
bestSizedItem.testItemHeight);
x += bestSizedItem.testItemWidth + horizontalGap;
if (x + bestSizedItem.testItemWidth - horizontalGap > W)
{
x = container.DisplayRectangle.X;
y += bestSizedItem.testItemHeight + verticalGap;
}
}
}
I put this snippet on Gist so you can contribute if you wish.
Are there any working piemenu controls for WPF?
I've found this in my favorite , you can take a look at :
This
have a nice day.
This question is probably long dead, but just a note that the control Thomas M posted, while awesome, has a major issue: You need to mouse over and click on the actual item instead of the pie slice. This means that the pie slices are not completely adjacent and IMO defeats a lot of the clickability (Frits's law) advantages of the control. So while it looks like a pie menu, it really just positions everything radially.
I ended up doing this:
private static Path makeDeliciousKeyLimePieSlice(double innerRadius, double outerRadius,
double startAngle, double endAngle, Vector ofs)
{
Point p1 = new Point(Math.Cos(endAngle) * innerRadius, Math.Sin(endAngle) * innerRadius) + ofs;
Point p2 = new Point(Math.Cos(startAngle) * innerRadius, Math.Sin(startAngle) * innerRadius) + ofs;
Point p3 = new Point(Math.Cos(startAngle) * outerRadius, Math.Sin(startAngle) * outerRadius) + ofs;
Point p4 = new Point(Math.Cos(endAngle) * outerRadius, Math.Sin(endAngle) * outerRadius) + ofs;
PathFigure fig = new PathFigure(p1, new PathSegment[] {
new ArcSegment(p2, new Size(innerRadius, innerRadius), endAngle - startAngle, false, SweepDirection.Counterclockwise, true),
new LineSegment(p3, true),
new ArcSegment(p4, new Size(outerRadius, outerRadius), startAngle - endAngle, false, SweepDirection.Clockwise, true),
}, true).GetAsFrozen();
return new Path { Data = new PathGeometry(new[] { fig }).GetAsFrozen() };
}
This will create a "slice" of the pie. You can style this how you want if you want a true pie menu. Another option is to make it transparent (set the fill to Brushes.Transparent; it must have a fill to be hit-test visible), which looks good for radial context menus. Here's my WIP after about half an hour's work (I know the spacing sucks):
alt text http://public.blu.livefilestore.com/y1pdW5ibqWquKGosMSch9C5KmOTKkiZ35mAI7iFKKUKf3cm7TGSquXhO8hkkL9Ln6Z3tKn74u67C27Qb_AIWQxzhg/radial.png?psid=1
EDIT: ah; the cursor doesn't appear in the shot -- basically, if you use the path overlay, you can have the mouse outside the actual control but still have it highlighted.
This control needs a bit of work still but it's a great starting point and supports multiple levels of items. (ie: a hierarchy) Check it out here