Scale WPF DrawingGroup to fit in the current Element - c#

I have loaded a set of vector data into my application whose bounds extend from {-165835.328125, 6582072.5} to {-64674.02734375, 6609767} giving a Width of 101161.30078125 and a Height of 27694.5.
I want to show this data on my FrameworkElement which defaults to 663 pixels wide and 468 pixels high and obviously resize it when the element is resized. As far as I could judge in order scale my DrawingGroup I need to create a ScaleTransform and add it to a TransformGroup which I can then set as the Transform of the DrawingGroup - I figure I also need an offset in order to bring the origin location of my data to the top left corner of the view, although in retrospect I might be better off aligning the centres.
This is the code I have at the moment:
var transformWidth = this.drawingGroup.Bounds.Width;
var scale = width / transformWidth;
var left = this.drawingGroup.Bounds.Left * -1;
var top = this.drawingGroup.Bounds.Top * -1;
var translateTransform = new TranslateTransform(left, top);
var scaleTransform = new ScaleTransform();
scaleTransform.CenterX = (drawingGroup.Bounds.Width / 2);
scaleTransform.CenterY = (drawingGroup.Bounds.Height / 2);
scaleTransform.ScaleX = scale;
scaleTransform.ScaleY = scale;
var tg = new TransformGroup()
{
Children = new TransformCollection() { translateTransform, scaleTransform }
};
var oldTransform = this.drawingGroup.Transform;
this.drawingGroup.Transform = tg;
context.DrawDrawing(this.drawingGroup);
this.drawingGroup.Transform = oldTransform;
This is rough on multiple levels but the main one bothering me right now is that it doesn't work. I can adjust the data on input ( as in if I translate the points manually when generating the geometry ) and it will draw only slightly wrong so I think the geometry exists but is outside the visible area, as far as I can tell the translation just isn't doing anything. I tried pushing the transforms onto the DrawingContext too but that didn't seem to work either ( I would quite like to be able to maintain the original coordinates on the DrawingGroup so I can relate things back to them later, hence the possibly unnecessary Transform switcheroo ) and when I inspect the child collection of the DrawingGroup their geometry seems untranslated after the translate was applied- I don't know whether or not that is correct.
What do I need to do to get my data visible in my FrameworkElement?

The Bounds property takes the current transform into account.
Reset the Transform property before accessing the Bounds:
drawingGroup.Transform = Transform.Identity;
var bounds = drawingGroup.Bounds;
var scale = width / bounds.Width;
var matrix = new Matrix();
matrix.Translate(-bounds.X, -bounds.Y);
matrix.Scale(scale, scale);
drawingGroup.Transform = new MatrixTransform(matrix);
As a note, in order to keep the thickness of the Drawings' Pens, better transform the underlying Geometries instead of the Drawings.

Related

how to align polyline annotation with respect to graph point in ms chart

I had added Polyline annotation to graph control. But it is not aligned properly in given datapoint in Addline() method.
PolylineAnnotation annotation = new PolylineAnnotation();
annotation.AxisX = chart1.ChartAreas[0].AxisX;
annotation.AxisY = chart1.ChartAreas[0].AxisY;
annotation.AnchorX = 0;
annotation.AnchorY = 0;
annotation.Height = 30;
annotation.Width = -30;
annotation.LineWidth = 3;
annotation.StartCap = LineAnchorCapStyle.None;
annotation.EndCap = LineAnchorCapStyle.None;
annotation.Alignment = ContentAlignment.BottomLeft;
annotation.AnchorAlignment = ContentAlignment.BottomRight;
annotation.AnchorDataPoint = new DataPoint(this.chart1.Series[0]);
annotation.AllowAnchorMoving = true;
annotation.AllowMoving = true;
annotation.AllowPathEditing = true;
annotation.AllowResizing = true;
annotation.AllowSelecting = true;
annotation.GraphicsPath.AddLine(10, 20, 30, 30);
chart1.Annotations.Add(annotation);
Annotations are complex and anchoring them is too.
It starts rather simple: To anchor an Annotation you need to set its AnchorDataPoint to an existing DataPoint.
This line does nothing like that:
annotation.AnchorDataPoint = new DataPoint(this.chart1.Series[0]);
as the newly created DataPoint is empty. Its has been added and it has values of (0d, 0d), but you probably want to keep the Annotation aligned to a real DataPoint, maybe like this..:
annotation.AnchorDataPoint = chart1.Series[0].Points[someIndex];
But there is more: There actually are two ways to anchor an Annotation:
Anchor it to a DataPoint or
Anchor it with fixed AnchorX and AnchorY values.
(And then you can set them to fixed X & Y values, too.)
Your code actually does both! But: anchoring the coordinates takes precedence over anchoring to a DataPoint.
This is nice as you can combine them and first anchor to a DataPoint and then anchor one of the coordinates to a fixed value: say, x-value stays with the point but the y-value is maybe always at 0..
Also note that you are adding only one line to your polyline and you don't start it at (0,0) but at (10,20) which may be quite some way off the anchor..
And then there is the issue of size and alignment of the polyline itself!
It has a size which MSDN claims is given in pixels. This is nonsense. Instead it is given in value units of the two respective Axes. You can see this when you resize the Chart the Annotation will resize as well; see the screenshot!
Now for the GraphicsPath and its points: Those are given in percent of the Size of the Annotation. To get a feeling for this add a test annotation path that encloses the whole area:
annotation.GraphicsPath.AddRectangle(new Rectangle(0, 0, 100, 100));
Here is a screenshot of what we have got:
As you can see the most logical alignment is TopLeft and after translating the line to (0,0) it would stick right into the point.
Note that I have added a 2nd Series to make the anchor data point stand out.. - Also note that while the Annotation size is a square (10,10) is is stretched horizontally along with the whole chart.
This is the full code I used:
PolylineAnnotation annotation = new PolylineAnnotation();
annotation.AxisX = chart1.ChartAreas[0].AxisX;
annotation.AxisY = chart1.ChartAreas[0].AxisY;
annotation.Height = 10;
annotation.Width = 10;
annotation.LineWidth = 3;
annotation.StartCap = LineAnchorCapStyle.None;
annotation.EndCap = LineAnchorCapStyle.None;
annotation.Alignment = ContentAlignment.TopLeft;
annotation.AnchorAlignment = ContentAlignment.TopLeft;
annotation.X = annotation.Y = annotation.AnchorX = annotation.AnchorY = double.NaN;
DataPoint dp = chart1.Series[0].Points[33];
annotation.AnchorDataPoint = dp;
chart1.Series[1].Points.AddXY(dp.XValue, dp.YValues[0]); // my red points series
annotation.AllowAnchorMoving = true;
annotation.AllowMoving = true;
annotation.AllowPathEditing = true;
annotation.AllowResizing = true;
annotation.AllowSelecting = true;
annotation.GraphicsPath.AddLine(10, 20, 30, 30);
Rectangle r = new Rectangle(0, 0, 100, 100);
annotation.GraphicsPath.AddRectangle(r);
chart1.Annotations.Add(annotation);
Also note that just to make sure no wrong anchors are active I have reset the X/Y and the AnchorX/Y values by setting them to double.NaN! This is not really needed here, as those are the defaults anyway..
Here btw is another post on the topic!

Constrain aspect ratio in WindowsForms DataVisualization Chart

Using the charting control from System.Windows.Forms.DataVisualization.Charting.Chart, I am making a scatter plot.
How can I constrain it so that the scale of the X axis is the same as the scale of the Y axis?
Simply setting the control itself to be square is insufficient, because it has internal margins for drawing and labeling the axes which are not equal.
I could pick a specific size and tweak it to be square, but it needs to be both square and resizable.
I've searched high and low in the documentation and in the property browser, but I can't find anything or think of any ways to do it in the resize event.
This is a good question but unfortunately there is no simple solution like locking the two Axes or setting one value..
Let's start by looking at the relevant players:
The Chart control has an inner Size called ClientSize, which is the Chart.Size minus the borders. Both sizes are measured in pixels.
Inside there may be one or more ChartAreas. Each has a Position which is of type ElementPosition.
Inside each ChartArea the is an area which is used for the actual drawing of the points; it is called InnerPlotPosition.
The InnerPlotPosition property defines the rectangle within a chart
area element that is used for plotting data; it excludes tick marks,
axis labels, and so forth.
The coordinates used for this property (0,0 to 100,100) are related to
the ChartArea object, and not to the entire Chart.
The InnerPlotPosition property can be used to align multiple chart
areas. However, if one chart area has tick marks and axis labels and
another one does not, their axis lines cannot be aligned.
Both ChartArea.Position and ChartArea.InnerPlotPosition contain not just the location but also the size of the areas; all values are in percent of the outer area, ie ChartArea.InnerPlotPosition is relative to the ChartArea.Position and ChartArea.Position is relative to the Chart.ClientSize. All percentages go from 0-100.
So the ChartArea includes Labels and Legends as well as Axes and TickMarks..
What we want is to find a way to make the InnerPlotArea square, i.e. have the same width and height in pixels. The percentages won't do!
Let's start with a few simple calculations; if these are the data we have..:
// we'll work with one ChartArea only..:
ChartArea ca = chart1.ChartAreas[0];
ElementPosition cap = ca.Position;
ElementPosition ipp = ca.InnerPlotPosition;
.. then these are the pixel sizes of the two areas:
// chartarea pixel size:
Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f),
(int)( cap.Height * chart1.ClientSize.Height / 100f));
// InnerPlotArea pixel size:
Size IppSize = new Size((int)(ipp.Width * CaSize.Width / 100f),
(int)(ipp.Height * CaSize.Height / 100f));
Ideally we would like the InnerPlotArea to be square; since can't very well let the smaller side grow (or else the chart would overdraw,) we need to shrink the larger one. So the new pixel size of the InnerPlotArea is
int ippNewSide = Math.Min(IppSize.Width, IppSize.Height);
What next? Since the Chart.Size has just been set, we don't want to mess with it. Nor should we mess with the ChartArea: It still needs space to hold the Legend etc..
So we change the size of the InnerPlotArea..:
First create a class level variable to store the original values of the InnerPlotPosition :
ElementPosition ipp0 = null;
We will need it to keep the original percentages, i.e. the margins in order to use them when calculating the new ones. When we adapt the chart the then current ones will already have been changed/distorted..
Then we create a function to make the InnerPlotArea square, which wraps it all up:
void makeSquare(Chart chart)
{
ChartArea ca = chart.ChartAreas[0];
// store the original value:
if (ipp0 == null) ipp0 = ca.InnerPlotPosition;
// get the current chart area :
ElementPosition cap = ca.Position;
// get both area sizes in pixels:
Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f),
(int)( cap.Height * chart1.ClientSize.Height / 100f));
Size IppSize = new Size((int)(ipp0.Width * CaSize.Width / 100f),
(int)(ipp0.Height * CaSize.Height / 100f));
// we need to use the smaller side:
int ippNewSide = Math.Min(IppSize.Width, IppSize.Height);
// calculate the scaling factors
float px = ipp0.Width / IppSize.Width * ippNewSide;
float py = ipp0.Height / IppSize.Height * ippNewSide;
// use one or the other:
if (IppSize.Width < IppSize.Height)
ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, ipp0.Width, py);
else
ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, px, ipp0.Height);
}
You would call the function after or during resizing.
private void chart1_Resize(object sender, EventArgs e)
{
makeSquare(chart1);
}
Here the function is at work:
The original size:
Squeezed a little:
And made square again:
Note how the green ChartArea reserves enough space for the Labels and the Legend and how the automatic scaling for the axes still works.. But the X-Axis labels now don't fit in one row. Also note how the ChartArea.BackColor actually is the color of the InnerPlotArea only!
Note that you may have to refresh the variable ipp0 to reflect the changed percentages, after making modification to the ChartArea layout like enlarging or moving or removing Legends or changing the size or angle of Labels etc..
Of course you can modify the function to pass in any other ratio to keep instead of keeping the plot area a square..

Determine screen coordinates from point in DirectX world

I have a point in space represented by a 4x4 matrix. I'd like to get the screen coordinates for the point. Picking appears to be the exact opposite fo what I need. I'm using the screen coordinate to determine where to draw text.
Currently the text I draw is floating in space far in front of the points. I've attached a screenshot of zoomed-in and zoomed-out to better explain. As you can see in the screenshot, the distance between each point is the same when zoomed in, when it should be smaller.
Am I missing a transformation? World coordinates consider 0,0,0 to be the center of the grid. I'm using SlimDX.
var viewProj = mMainCamera.View * mMainCamera.Projection;
//Convert 4x4 matrix for point to Vector4
var originalXyz = Vector3.Transform(Vector3.Zero, matrix);
//Vector4 to Vector3
Vector3 worldSpaceCoordinates = new Vector3(originalXyz.X, originalXyz.Y, originalXyz.Z);
//Transform point by view projection matrix
var transformedCoords = Vector3.Transform(worldSpaceCoordinates, viewProj);
Vector3 clipSpaceCoordinates = new Vector3(transformedCoords.X, transformedCoords.Y, transformedCoords.Z);
Vector2 pixelPosition = new Vector2((float)(0.5 * (clipSpaceCoordinates.X + 1) * ActualWidth), (float)(0.5 * (clipSpaceCoordinates.Y + 1) * ActualHeight));
Turns out I was way overthinking this. Just project the point to the screen by passing Vector3.Project your viewport information. It's a 3 line solution.
var viewProj = mMainCamera.View * mMainCamera.Projection;
var vp = mDevice.ImmediateContext.Rasterizer.GetViewports()[0];
var screenCoords = Vector3.Project(worldSpaceCoordinates, vp.X, vp.Y, vp.Width, vp.Height, vp.MinZ, vp.MaxZ, viewProj);

Calculate new position values in C#

I have a document which containing some elements. The document is an INDD document. I am trying to show it in a webview but in a smaller size than the original size.
I am using ImageMapper (ASP.NET) to mark the different elements in the webview.
The problem is that I don´t get the correct positions in of the different spots in the webview.
I got the correct size of the new spots but not the position.
I made the calculations in the following way:
Original size (INDD document)
DocumentWidth = 768
DocumentHeight = 1024
New Size (Size of the webview)
Width = 522
Height = 696
percentW = newWidth(Webview)/DocumentWidth
percentH = newHeight(Webview)/DocumentHeight;
From these percent values I am calculating all the new values I will need in the ImageMapper (top,left,bottom,right).
Formula for that
myPrecent = (percentW/percentH) * 100;
result = myPrecent * ((top,left,right,bottom) / 100);
The result variable shall represent the new value which will be used in the spots within ImagMapper.
I suppose that I am thinking wrong in my calculation but I cant figured out what I´m doing wrong. So I will be appreciate if someone has any idea what I have doing wrong.
don't think of them as percentages, instead, think of them as scaling factors.
verticalScaling = Height/DocumentHeight
horizontalScaling = Width/DocumentWidth
newTop = Top * verticalScaling
newLeft = Left * horizontalScaling
newBottom = Bottom * verticalScaling
newRight = Right * horizontalScaling

Labelling and circle a specific point in zedgraph

I am currently doing a project in which I've managed to identify the peak I want. However, I wanted to do more like circling the particular point with a label attached to it. Is it possible to do that in Zedgraph?
I've attached a snippet of my code which only include a text label to that point, and I wanted to do more so people will identify the point more easily.
PointPair pt = myCurve.Points[i-1];
const double offset = 0.8;
TextObj text = new TextObj("P", pt.X, pt.Y + offset,
CoordType.AxisXYScale, AlignH.Left, AlignV.Center);
text.ZOrder = ZOrder.A_InFront;
text.FontSpec.Border.IsVisible = false;
text.FontSpec.Fill.IsVisible = false;
text.FontSpec.Fill = new Fill( Color.FromArgb( 100, Color.White ) );
myPane.GraphObjList.Add(text);
Any help is appreciated! Thanks!
Make a LineItem as follows
LineItem line = new LineItem("Point", new double[] {pt.x}, new double[] {pt.y}, Color.Black, SymbolType.Circle);
line.Symbol.Size = 20;
line.Symbol.Fill = new Fill(Color.Transparent);
myPane.CurveList.Add(line);
This should create a large empty circle centered around your point. Obviously, you can adjust color and size as you see fit, and the ZOrder if you need to. You might want to adjust your legend so it doesn't include this point. Alternatively, you can name this line with your label and leave it in the legend as a way of tagging it. The only other way for a label is to do what you're doing, as I'm not sure of a way to associate labels directly to a line.

Categories

Resources