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

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;

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;

WPF OxyPlot Zooming issue

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.

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.

How can I get the X value if I know the Y value of a point on a chart?

if I know the x & y values of 2 points on a chart, and I know the Y value of a position in between those 2 points, how do I get that Y value's corresponding X value?
Basically I would like to get the X position at which that value first occurs, in between the 2 original points.
The Y axis is in doubles, and the X axis uses DateTimes.
It's probable that a data point at exactly the Y value may not exist as an exact point on the chart (it's a line chart) however, but I would need to find the exact X value, not the nearest actual point to it i'm afraid.
For a LineChart the calculation is really just simply interpolation math.
But it still does take some knowledge about how Chart works.
You have normal numbers for the Y-Values but have DateTimes for the X-Values.
This wouldn't work well with math so we'd expect the need to transform the dates to numbers.
But Chart does just that internally, using calls to FromOADate and ToOADate().
The nice thing is that this means that what you add as a DateTime value internally is stored as a number, in fact as a double.
So you can indeed do the math straight forward. Here is a code example that shows how to find a point on the same line as two given points with a given y-value.
To make it look nice I add the calculated point as a new DataPoint to a second series of type Point..
First I prepare my chart:
chart1.ChartAreas.Clear();
chart1.Series.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
Series S1 = chart1.Series.Add("S1");
Series S2 = chart1.Series.Add("S2");
S1.ChartType = SeriesChartType.Line;
S2.ChartType = SeriesChartType.Point;
S1.Points.AddXY(new DateTime(2015, 12, 10), 10);
S1.Points.AddXY(new DateTime(2015, 12, 31), 31);
DataPoint dp1 = S1.Points[0];
DataPoint dp2 = S1.Points[1];
Now I set the Y-Value for which I search the X-Value:
double y3 = 24; // X-Mas ;-)
Now I calculate the deltas and the slope. This can, of course be done all in one but I spell it out for clarity..:
double deltaY = dp2.YValues[0] - dp1.YValues[0];
double deltaX = dp2.XValue - dp1.XValue;
double slope = deltaY / deltaX;
Finally I calculate the X-Value you are looking for:
double x3 = dp1.XValue + (y3 - dp1.YValues[0]) * slope;
Now I can show that the new point indeed sits on the line between the first two points:
S2.Points.AddXY(x3, y3);
S2.Points[0].Color = Color.Red;

Rounding Doubles to int C#

I want to calcuate the distance of a BorderControl to the Grid in which it is added, but in rounded percentage.
I use this method:
internal static tPosition GetControlTPosition(Border tempInnerControl, Grid tempOuterGrid)
{
tPosition tempTPosition = new tPosition();
Point tempInnerControlCornerPoint = tempInnerControl.PointToScreen(new Point(0, 0));
Point tempOuterGridCornerPoint = tempOuterGrid.PointToScreen(new Point(0, 0));
//
//Top Left Corner
//Fist we calculate the Distance of the Top left Corners of both elements
double distanceLeft = tempInnerControlCornerPoint.X - tempOuterGridCornerPoint.X;
double distanceTop = tempInnerControlCornerPoint.Y - tempOuterGridCornerPoint.Y;
//Then we set the percentage of our position accordingly
tempTPosition.PositionLeft = (int)(((distanceLeft) * 100 / tempOuterGrid.ActualWidth));
tempTPosition.PositionTop = (int)((distanceTop) * 100 / tempOuterGrid.ActualHeight);
//
// Bottom Right Corner
//Now we calculate the distance of the bottom right corner to the outher grids bottom right corner
double distanceRight = (tempOuterGridCornerPoint.X + tempOuterGrid.ActualWidth) - (tempInnerControlCornerPoint.X + tempInnerControl.ActualWidth);
double distanceBottom = (tempOuterGridCornerPoint.Y + tempOuterGrid.ActualHeight) - (tempInnerControlCornerPoint.Y + tempInnerControl.ActualHeight);
tempTPosition.PositionRight = (int)((distanceRight)*100/tempOuterGrid.ActualWidth);
tempTPosition.PositionBottom = (int)((distanceBottom) * 100 / tempOuterGrid.ActualHeight);
return tempTPosition;
}
My problem is that the BorderControl keeps getting bigger, meaning there is a problem with the percentages being to low, i guess this happen because i lose precision. How can i avoid this?
I need the numbers as int, for various reasons.
tPosition is just left,top,right,bottom as ints
You most likely lose your precision when finding tempTPosition, instead of using double as you have been you are using int, which will round the value to a whole number
try it with doubles
//Then we set the percentage of our position accordingly
tempTPosition.PositionLeft = (double)(((distanceLeft) * 100 / tempOuterGrid.ActualWidth));
tempTPosition.PositionTop = (double)((distanceTop) * 100 / tempOuterGrid.ActualHeight);
Remember to also do this for PositionRight & PositionBottom

Categories