WPF OxyPlot Zooming issue - c#

In order to have the same scale on both axes, X and Y, I used PlotType.Cartesian which ensures that:
_model = new PlotModel();
_model.PlotType = PlotType.Cartesian;
I also have possibility to zoom in and out charts.
In order to control zooming I need to set AbsoluteMinimum and AbsoluteMaximum on both axes and specify minimum and maximum range.
Issues I have: how to keep the same scale when zooming? Because axes are zooming independently and often one axis is getting out of sync with the other axis (when one reaches its limits and the other still can expand).
Also, how to set appropriate values for both axes, because if I set all minimums and maximums, I expected correpsonding values on the other axes to be set, if I use PlotType.Cartesian, but it does not happen - this is the reason the issue arises, because i can't set appropriate values for both axes.

The closest I could get is:
subscribe to Loaded event of PlotView (_model field in this case)
in that method get ActualHeight and ActualWidth of PlotView
Having size of plot area, one can choose min and max of desired axe, then do all calculations required to keep scale the same on both axes. For example:
double xMin = -500;
double xMax = 800;
double xRange = xMax - xMin;
double yRange = xRange / ActualWidth * ActualHeight;
double yMin = 58;
double yMax = yMin + yRange;
_model.Axes[0].Minimum = xMin;
_model.Axes[0].AbsoluteMinimum = xMin;
_model.Axes[0].Maximum = xMax;
_model.Axes[0].AbsoluteMaximum = xMax;
// Analogically, define limits of Y axe
Also it is important to zoom both axes with the same zooming factor!
This will guarantee equal scales on both axes and keeping aspect ration through zooming.

Related

Grid Layout group - Child's positions and sorting

Hey fellow programmers,
Firstly, i want to explain the purpose of my task, which is to create an interactable UI grid where the user can select whatever grid element he is interested in. (This is already done, using Grid Layout Group - see attached image)
The whole grid is supposed to represent a real-life-sized squared area, which I have from data, consisting of length and width. From data, I also have an event list, which contains certain real-life events and positional data (x,y) of each event. So when a user selects a grid elemental, I want it to represent all events that is equal to within that positional range.
My idea: is so far is to find a calculation method to get the size of the UI grid relative to the real life size. But i need a way to actually get the size of the grid (Not hardcoding). And then access and sort each child element, so if the element which has been clicked on the attached image, would equal to (0,0), (0,1) and (0,2), so i can loop through each child element
I know this is a messy explanation, but I hope it makes somekind of sense.
I guess you need the following calculations:
Given the TotalWidth, TotalHeight and CellWidth, CellHeight of your grid and a Position within TotalWidth/TotalHeight. Where TotalWidth is the X-Width of your grid, CellWidth the X-Width of your cells, Position the position within your grid.
CellIndexX = Position.X % (TotalWidth / CellWidth);
CellIndexY = Position.Y / (TotalHeight / CellHeight);
TotalWidth / CellWidth basically calculates the amount of Cells on the X-Axis, and the same happens for the amount of cells for the Y-Axis.
This should then allow you to get the cell by Index like:
int TotalWidth = 4000;
int TotalHeight = 2000;
int CellWidth = 20; // TotalWidth / CellWidth = 4000/20 = 200 grid tiles on the x-Axis
int CellHeight = 20; // TotalWidth / CellWidth = 4000/20 = 100 grid tiles on the y-Axis
Cell[,] myGrid;
Cell GetCellForPosition(Vector3 Position)
{
CellIndexX = Position.X % (TotalWidth / CellWidth);
CellIndexY = Position.Y / (TotalHeight / CellHeight);
Cell result = myGrid[CellIndexY, CellIndexX];
return result;
}
For the real world size versus the grid size, you can use the rule of three. Or more simplified, you just come up with a factor
If the RealWorldSize is: 120X meters * 90Y meters
and you want the grid to be 100X meters, your factor would be: 100/120 = 0.8333, which you can then use to calculate the Y height of your grid by: YGridHeight = 90 * 0.833 = 75;

normalizing real world data inside the canvas wpf

I am working in a project in which I have to visualize certain points in real world coordinates into canvas inside ViewBox. Below is a sample of the data collection:
X Y
-40085.119 266560.373
-40084.72 266560.736
-40083.51165 266559.4097
-41606.37001 263437.3891
-40098.72351 266327.5417
-40075.57653 266399.5039
-40076.09771 266398.6382
-40629.41856 265374.2896
-40698.41477 265214.1637
To convert the real world data to fit inside the canvas. I am calling the following function.
public double changeScale(double point, double min, double max, double size){
double convertedValue;
convertedValue = (point - min) / (max - min);
convertedValue *= size;
return convertedValue;
}
I use the stated function as follows:
sc.changeScale(x, xmax, xmax, mycanvas.Width)
sc.changeScale(y, ymax, ymin, mycanavs.Height)
xmin, xmax and ymin, ymax are taken out from the table of real time data. Size of my canvas is 1280 X 720. For example: if an element has coordinate (XMax, YMax) then, it will be plotted on (1280,720) and likewise, if an element has coordinate (XMin, XMax), then it will be plotted on (0,0).
This is still working but I found that this is not correct because the noramlized map I am getting is stretched which is inaccurate visualization of real world. I have compared mine with another software's result. The following image is what I have created:
My expected result is:
You need to maintain the aspect ratio. This means you'll have to change the scale with the same size for both X and Y.
First find the Maximum-X size and Maximum-Y size, then take the larger one and use it as size.
UPDATE
Find the minimum X, maximum X, minimum Y and maximum Y of the entire set.
Find the maximum between (MaxX - MinX) and (MaxY - MinY).
Use that value as scale.

Specifying boundary points on GUI using c#

I'm working on simulator, it has number of points. what i need is knowing how to specify the points which is the nearest one to any border of the four borders. I.e connect closed shape and ignore the points in the middle
Any suggestions?
If the boundaries form a rectangle shape which is axis aligned (like a monitor screen, for example), then you can take the four points with maximum and minimum x and y values.
You can enumerate all of the points and find the points nearest the boundaries.
Pseudocode:
var minimumX = int.MaxValue
var maximumX = int.MinValue
var minimumY = int.Maxvalue
var maximumY = int.MinValue
foreach(var point in points)
{
if (point.x < minimumX) minimumX = point.x;
if (point.x > maximumX) maximumX = point.x;
if (point.y < minimumY) minimumY = point.y;
if (point.y > maximumY) maximumY = point.y;
}
You can now use minimum and maximum x and y to create a bounding rectangle that contains all points.
A slightly more performant method would track the minimum and maximum x and y as each point is added to the field. This way, there would be no need to enumerate all points.

Histogram / column chart not showing bars all the way to the bottom

I'm trying to plot a histogram using the C# .NET Chart control (System.Windows.Forms.DataVisualization.Charting).
I have it set up as a Column chart. The data is retrieved via a Histogram object using the NMath libraries, so it does all the sorting into bins and such. Everything looks fine until I switch the y axis to a log scale. To get things to even show up, I set the DataPoint of any bin with 0 entries to have a y-value of 0.001 instead of 0. I then set the minimum of the y axis to 0.1 and the max to something beyond the biggest bin. The result is that all of the columns start at a y-value of 1 instead of at the minimum. Any bin with 0 entries has a column which extends downward (toward the 0.001). Screenshot available here
The code which sets the min/max/intervals on the axis is below.
double ymin = FindMinimumYValue();
double mag = Math.Floor(Math.Log10(ymin));
ymin = Math.Pow(10, mag);
yAxis.Minimum = ymin;
double ymax = FindMaximumYValue();
mag = Math.Ceiling(Math.Log10(ymax));
ymax = Math.Pow(10, mag);
yAxis.Maximum = ymax;
yAxis.Interval = 1;
yAxis.MajorGrid.Interval = 1;
yAxis.MajorTickMark.Interval = 1;
yAxis.MinorGrid.Interval = 1;
yAxis.MinorTickMark.Interval = 1;
I'm probably not setting a property on the axis I need to, but is there a way to have the columns all extend upward from the minimum on the y axis, even if that minimum is less than 1?
ETA: If I remove the DataPoints with 0 counts from the Series, I no longer get the downward bars between 0.1 and 1. But, all the other bars still start at 1 and go upwards, instead of starting at the minimum.
ETA again: I'm thinking I could use a RangeColumn type of chart, and specify the min and max y values for each bin. That doesn't seem very elegant, as I'll need to change between RangeColumn and Column type when the user switches the axis to log mode and back, or keep adjusting the minimum y value of the RangeColumn data points (from 0 to 0.1 and back). And that seems like more of a workaround and not a solution.
A workaround would be to add a datapoint with Y value 0 for each x value.
Series = chart1.Series.Add("Test");
Axis yAxis = chart1.ChartAreas[0].AxisY;
yAxis.IsLogarithmic = true;
double ymin = 0.1;
yAxis.Minimum = ymin;
double ymax = 100;
yAxis.Maximum = ymax;
Series.Points.Add(new DataPoint(1, 3));
Series.Points.Add(new DataPoint(1, 0));
Series.Points.Add(new DataPoint(2, 3));
Series.Points.Add(new DataPoint(2, 0));
Series.Points.Add(new DataPoint(3, 4));
Series.Points.Add(new DataPoint(3, 0));
Series.Points.Add(new DataPoint(4, 5));
Series.Points.Add(new DataPoint(4, 0));
I wound up changing it to a RangeColumn type of chart, and just set the min and max of the range as needed.
It's a little messed up, but you need to set the crossing value to the absolute minimum for a Double value.
i.e.
yAxis.Crossing = -1.7976931348623157E+308;

Calculate how much to Pan/Tilt image

I am currently implementing Pan/Tilt/Zoom support in my application. I pass in an image and then calculate zoom in the following manner
int widthPercent = width / 100;
int heightPercent = height / 100;
int zoomX = width - (widthPercent * (int)Zoom);
int zoomY = height - (heightPercent * (int)Zoom);
where width is width of original image, height is height of original image and Zoom is a value passed in from the UI, ranging from 0 to 100.
I now wish to implement Pan/Tilt support while an image is zoomed in so that the whole image can still be accessed. Again Pan and Tilt will be controlled from the UI(again 0 to 100) but I want it to stay within the boundaries of the image so that the image does not repeat which it is currently doing.
I am current calculating the Pan and Tilt like so:
// Calculate Pan
int panWidthPercent = width / 100;
int finalPan = (int)Pan * panWidthPercent;
// Calculate Tilt
int tiltHeightPercent = height / 100;
int finalTilt = (int)Tilt * tiltHeightPercent;
This works to an extent however it seems to keep repeating the image after panning for a small time(usually when Pan = 10 or more). I wish it to stop after it renders all the image i.e it reaches the width but adding something like the following doesnt seem to stop this
if(finalPan >= width) finalPan = width;
Once the values are calculate I create a source rectangle using these values
mClippingRectangle = new Int32Rect(finalPan, finalTilt, zoomX, zoomY);
which is then used against the base image for rendering only that rectangle
In short how can I calculate how much I should pan/tilt when I already know the zoom of the image
Managed to accomplish it by checking whether the Pan/Tilt Amount added to the zoom width/height is greater than the overall image width/height then stop it from panning/tilting
// Calculate Pan
double finalPan = Pan * widthPercent;
if ((finalPan + zoomX) >= width)
{
finalPan = width - zoomX;
}
// Calculate Tilt
double finalTilt = Tilt * heightPercent;
if ((finalTilt + zoomY) >= height)
{
finalTilt = height - zoomY;
}

Categories