How can I transform a LineSeries in a WPF OxyPlot Plot? - c#

I have an OxyPlot graph, and I want to transform one of the LineSeries in this graph. I'd like to apply both rotation and translation transformations, but I can't get any kind of transformation to actually take place (I'm not including the model that holds the MyItems property, but it's MVVM and working correctly).
<oxy:Plot>
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Bottom" Title="X"/>
<oxy:LinearAxis Position="Left" Title="Y"/>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource={Binding Path=MyItems}/>
</oxy:Plot.Series>
</oxy:Plot>
So far, I've tried playing with the LayoutTransform property in the LineSeries, like so (the X and Y values are just arbitrary for testing if I can get it to move at all):
<oxy:LineSeries ItemsSource={Binding Path=MyItems}>
<oxy:LineSeries.LayoutTransform>
<TranslateTransform X="1" Y="2"/>
</oxy:LineSeries.LayoutTransform>
</oxy:LineSeries>
However, nothing happens - the series displays exactly the same way with and without the LayoutTransform property. I've read through the documentation, and I can't find any other properties that even appear to reference transformation, and I can't find any guide on how to actually use the LayoutTransform property. Does anyone know how to achieve this, short of manually recalculating each and every point in MyItems in the code-behind?
I'm willing to create a transformation object in the code-behind, but I would much rather work with the XAML directly because I'll be using binding.
Note: The points in MyItems all have both X and Y coordinates within 0 and 1, so the problem isn't that the move is too slight for me to see.

Related

C# 3D Helix Toolkit Shapes are transparent from only certain directions

I am trying to get some animations working where I smoothly change the material of various shapes so that they are transparent/different colors.
The issue is:
If I look into the shape from above I can see the inner corners of it (say if it's a cube I can see the inner surface of it), but anything outside/beyond the perimeter of the shape is occluded.
So far I am doing the following, which works great besides that problem:
Color c = new Color()
{
A = 16,
R = Colors.Transparent.R,
G = Colors.Transparent.G,
B = Colors.Transparent.B
};
(model as GeometryModel3D).Material = new DiffuseMaterial(new SolidColorBrush(c));
(model as GeometryModel3D).BackMaterial = new DiffuseMaterial(new SolidColorBrush(c));
If I drop the alpha of the color all the way to 0, the shape becomes opaque, seemingly because the shape is invisible but it's still occluding whatever is behind it.
The information I've found so far suggested that using emissive or specular materials would work because it didn't get written to the Z-buffer, but either diffuse materials don't work the same or I implemented that wrong.
Edit:
After coming across this question: StackOverflow, and seeing the comment under the first answer, I'm assuming being able to make objects truly transparent must be more involved than I first thought. That person seems to have had the same trouble as me.
Sounds like your surfaces might be oriented the wrong way. If so, you can fix it by reversing the order of the vertices for each element of the cube.
The standard rasterization pipeline typically draws "one-sided" primitives -- i.e., it will only draw triangles it thinks are facing the camera. That way, for instance, it doesn't even have to try to draw the back-facing sides of your cube.
One more solution that helped me is using Helix toolkit and this example helped a lot
http://helix-toolkit.github.io/demos/wpf/transparency
I imported the models from the stl files and added them as list inside of the
HelixToolkit:SortingVisual3D as Model3DGroup (from Microsoft)
an example of XAML:
<HelixToolkit:SortingVisual3D IsSorting="True" x:Name="sortingVisual1" Method="BoundingBoxCorners" SortingFrequency="2">
<ModelVisual3D Content="{Binding Model3DGroup1, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<ModelVisual3D Content="{Binding Model3DGroup2, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<ModelVisual3D Content="{Binding Model3DGroup3, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<ModelVisual3D Content="{Binding Model3DGroup4, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</HelixToolkit:SortingVisual3D>
Every group is one separate .stl file
My problem was that when uploaded stl files, the outer one wouldn't let the light through for the inner models no matter the opacity
this is the function for the bound model in VM:
private readonly HelixToolkit.Wpf.ModelImporter _importer = new HelixToolkit.Wpf.ModelImporter();
var model = _importer.Load(stlPath);
var m0 = (MeshGeometry3D)((GeometryModel3D)model.Children[0]).Geometry;
var insideMaterial = MaterialHelper.CreateMaterial(outsideColor, 0.6);
return new GeometryModel3D { Geometry = m0, Material = insideMaterial };

C# WPF element position not in correct place

I'm developing a vision processing application using WPF and EmguCV 3.0. My issue is that the element isn't positioned correctly on-screen. I have viewed what the padding is, and it returns all sides as 0. The ImageBox element from Emgu, which is what I am using to display the images, is encapsulated in a Windows Forms Host control. I have two other ImageBox elements, which display properly. Each of the ImageBox elements are within their own tab in a TabControl. On startup, I set the width and height properties of all the ImageBoxes and their canvases.
An additional thing to note is that the other two ImageBoxes also overflow out of their boundaries, but are reset back into the boundaries after switching back and forth between the tabs. This only happens once.
Here is a link to screenshots of what the UI looks like. http://imgur.com/a/RwG17
Additionally, here is the XAML and C# code for the ImageBoxes.
<TabItem x:Name="ImageTabControlHSV">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="HSV" />
</StackPanel>
</TabItem.Header>
<Canvas x:Name="HSVImageCanvas">
<WindowsFormsHost>
<emui:ImageBox x:Name="HSVImageBox"/>
</WindowsFormsHost>
</Canvas>
</TabItem>
//Width and height properties are gotten from camera image.
HSVImageBox.Width = ratioWidth;
HSVImageBox.Height = ratioHeight;
HSVImageCanvas.Width = width;
HSVImageCanvas.Height = height;
HSVImageCanvas.MaxHeight = height;
HSVImageCanvas.MaxWidth = width;
Any help or suggestions would be greatly appreciated.
UPDATE: Putting a counter for how many times the problematic ImageBox has been selected and using Canvas.SetTop() and Canvas.SetLeft() seems to be a workaround. I would still like to know why the canvas is changing its position.
You might try performing a Canvas.SetTop(HSVImageCanvas, HSVImageCanvas.Top) and Canvas.SetLeft(HSVImageCanvas, HSVImageCanvas.Left).
Doug

Why do I have have to use UIElement.UpdateLayout?

We have a rather large WPF business application and I am working on a retool of an existing WPF FixedPage/FixedDocument report.
It's a somewhat busy ecosystem. We have a built-in forms generator, with lots of different controls you can put on (think like a mini built-in visual studio). All that works fine. You fill in the form on the screen, and then you can print out (to XPS) the identical copy to standard 8.5x11 paper.
In the code, we break out this report into vertical chunks. Say each chunk would be an inch or two tall on a printed piece of paper. This is how we handle pagination. If the next chunk is too tall for the page, we do a NewPage() and repeat. As I mentioned, this was working fine.
WPF has an enormous learning curve and I've been going back over old code and refactoring things and happily working with DataTemplates, strongly typed ViewModels, and generic ContentControls in order to reduce the size of our code. The on-screen forms generator still works, but the FixedDocument report has gotten weird.
Going back to those vertical slices, we print the user's forms to paper as individual Grid controls. Nothing fancy. Each grid (as I mentioned above) may be an inch or two high, containing any random mixture of checkboxes, radiobuttons, textblocks, and so on.
When the grids contained these stock (standard) MS WPF controls, I could do this all day long:
System.Windows.Controls.Grid g = .....
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
And get back proper sizes, i.e. 100 x 67.
Now, sometimes the grids have just one control - a header if you will (i.e. "This Month's Schedule). The only child control added to that grid is a ContentControl.
The ContentControl is simply bound to a ViewModel:
<ContentControl Content="{Binding}" />
There's then two DataTemplates in the resource dictionary that picks up this binding. Here, I'll show that:
<UserControl.Resources>
<w:MarginConverter x:Key="boilerMargin" />
<DataTemplate DataType="{x:Type render:BoilerViewModel}">
<render:RtfViewer
Width="{Binding Path=Width}"
TextRTF="{Binding Path=Rtf}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type render:Qst2NodeViewModel}">
<ContentControl Content="{Binding Path=BoilerVm}">
<ContentControl.Margin>
<MultiBinding Converter="{StaticResource boilerMargin}">
<Binding Path="NodeCaptionVm.Height" />
<Binding Path="NodeLeft" />
</MultiBinding>
</ContentControl.Margin>
</ContentControl>
</DataTemplate>
</UserControl.Resources>
The ContentControl will pick up that bottom-most datatemplate. That template will then in turn use the smaller one above.
The fancy converter just sets a margin. It may be fugly to read, but this all displays correctly on the screen within the parent usercontrol. It's all the right size and justification and all that.
On the printed report side (XPS), I have to create these controls in code and measure them to see if they'll fit on the current FixedPage. When I go to do this step: (on the grid containing this ContentControl)
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
I get back 0,0 size. Even though it should be like 730x27 for instance. Again, on the screen, hosted in a UserControl, this all works fine. Just trying to instantiate it and measure it purely in code fails. I've confirmed that the control is added to the grid, has its row and col set, has been added to the Children collection, etc...
If I prepend those two statements with an UpdateLayout call, like this, then it works:
g.UpdateLayout(); //this fixes it
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
I've been reading that UpdateLayout is expensive and to be avoided, and I'd rather not be calling this on each grid section before I add it to my FixedPage of the FixedDocument report. There could be dozens or even hundreds of iterations. And, again, if the Grid has regular WPF controls in it, without any ContentControls and fancy finding and looking up datatemplates, the measuring works fine without the UpdateLayout call.
Any advice? Thank you!
I just don't understand why it became necessary to start calling it once I started utilizing the Xaml engine. It almost feels like I'm being punished for using the advanced features.
Its complicated to explain that but let me try using plain words... In wpf everything works with dispatcher. Futhermore like you may already know dispatcher deals with tasks ordered by priority.
For example first a control is being initalized, then binding is being triggered, then values are updated, in the end all that is being measured.. etc etc
What you managed somehow is by setting all those contentcontrol inside contentcontrol stuff, you screwed up that order
Calling UpdateLayout basically forces dispatcher to finish its pending work in layout so you can work with clean layout afterwards
Screwing with dispatcher is quite common in wpf since some controls or values may be added later which ends in remeasuring things.
In your case you seem to be creating all at once in one method call without letting dispatcher take a breath. Therefore you need UpdateLayout method to normalize dispatchers queue.
I hope this helps you. You can also solve your issue by using Dispatcher.BeginInvoke.
UpdateLayout does not work in my case. I had to wait until dispatcher finishes processing of the layout tasks.
toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));
I found another article about this approach.

Drawing a fading grid in WPF

I'm trying to draw a graph-paper like grid as the background of a Canvas. This grid is different from most explanations of how to do this that I've found because the canvas can be scaled to implement zooming. What I want to do is have a series of scales of grid lines, i.e. at every 10^n units. Then, the grid lines should fade out as they become close together due to zooming. In other words if n is large, the lines associated with that grid should be darker/heavier weight than those for a smaller n.
This was easy to do in WinForms, I implemented it by overriding OnPaint and defining the color of the line to be a function of the distance to the next grid line. Lines far apart were given a heavier weight than lines close together.
I have not figured out how to do this in WPF. I can sort of get this behavior by creating a line that has a StrokeThickness according to the spacing of the grid lines, but this only works for a small range of StrokeThickness and scaling values. It would work if it were possible to define a line as having a very heavy weight, but still a small StrokeThickness.
Even doing this via implementing a custom control with OnRender is difficult because I have not found a reliable way to get the scale of the control while rendering it (the ScaleTransform is part of one of the parent controls, not the immediate parent).
Any thoughts on how to accomplish this goal would be much appreciated!
I solved this by NOT adding the grid to the canvas but by stacking the canvas on top of another control that contains the grid:
<Grid>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="DrawingLayer" />
</Grid>
When zooming events occur I simply redraw the GridLayer.
This allowed me to only draw the lines that are needed, to draw them exactly how I want them and, in my case very important because I had potentially a gazillion grid lines, I did not need to draw the lines any longer/taller than needed. This way I conserved a lot of CPU time.
Another thing to note is that I implemented my own zoom code. I did not use a RenderTransform or a ViewBox because I wanted the line to stay at the same width. All I did was keep track of the coordinates of the top left corner to support panning and the zoomlevel. As soon as one of these changes I redraw the canvases. I wrote two functions: one transforms a coordinate on the Canvas to a graph coordinate and the other one does the reverse. The first method allows me to translate cursor coordinates to graph coordinates and the second one will turn the coordinates of the graph into points that can be used to draw on the canvas.
Untested code and making a lot of assumptions about the orientation of axis:
Point Graph2Canvas(Point graphPoint)
{
var canvasPoint = new Point(graphPoint);
canvasPoint.X *= zoomLevel;
canvasPoint.Y *= zoomLevel;
canvasPoint.X -= topLeft.X;
canvasPoint.Y -= topLeft.Y;
return canvasPoint;
}
This can be optimized and the truth is I created more functions that do the same thing for collections of points.
Extra:
I ended up with a far more complex setup that looked a bit like this:
<Grid>
<Canvas x:Name="BackgroundLayer"/>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="AxisLayer"/>
<Canvas x:Name="DrawingLayer" />
<Canvas x:Name="SelectionBoxLayer"/>
<Canvas x:Name="CursorLayer"/>
</Grid>

Binding to X Y coordinates of element on WPF Canvas

I have a Canvas with 2 "dots" drawn on it. See this (simplified) code:
<Canvas>
<Ellipse />
<Ellipse />
<Canvas.RenderTransform>
<RotateTransform x:Name="rotateEllipse" />
</Canvas.RenderTransform>
</Canvas>
As you can see, I want to rotate the canvas using the given RotateTransform.
Next, I want to put a TextBlock near to each Ellipse (a label). However, I don't want to include this TextBlock into the Canvas because it will then rotate also. I want the text to remain horizontal.
Any idea how to solve this in an elegant way?
Something like this, should work for you
<TextBlock RenderTransform="{Binding RelativeSource={RelativeSource AncestorType=Canvas},
Path=RenderTransform.Inverse}"/>
Assign to text box transformation matrix an inverse of the transformation matrix of the Canvas.
Good question! And I'm going to guess, so please take this answer with a pinch of salt.
I believe you are trying to place text annotations next to ellipses on a rotated canvas, but these annotations need to remain horizontal. Two things you could try:
Firstly, given the XY point that you know of each ellipse from Canvas.GetTop/GetLeft, you could find its new rotated XY location by applying the RotateTransform to the ellipse location, using the formula U = M*V, where U is the output point, V is the input point (XY location of ellipse) and M is the Rotation Matrix.
Secondly, you could place a second canvas over the first (assuming they are both in a grid, the second canvas is at higher Z-index and is the same size as the underlying canvas). Call it an annotation layer. Your annotations (text labels) can appear at the new transformed locations and unrotated using this approach.
You'd have to do this in code of course, not Xaml, although you might find a binding friendly approach by creating a value converter on the TextBlock that bound to the source RotateTransform/Ellipse and did this operation for you.
Another approach would be to take the .Inverse of the RotateTransform and apply that to the textblocks, however you may still need to translate to get to the new location. Either way I think it demands some experimentation.
Best regards,

Categories