I'm using C# charts to display/compare some data. I changed the graph scale to logarithmic (as my data points have huge differences) but since logarithmic scaling doesn't support zero values, I want to just add an empty point (or skip a data point) for such cases. I have tried the following but non works and all crashes:
if (/*the point is zero*/)
{
// myChart.Series["mySeries"].Points.AddY(null);
// or
// myChart.Series["mySeries"].Points.AddY();
// or just skip the point
}
Is it possible to add an empty point or just skip a point?
I found two ways to solve the problem.
One is using double.NaN (suddenly!):
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.AddY(double.NaN);
// or ...Points.Add(double.NaN)
This looks like zero
And in my case it didn't crash with following SeriesChartTypes:
Column, Doughnut, FastPoint, Funnel, Kagi, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedBar, StackedColumn, ThreeLineBreak
The other way is a built-in concept of an empty point:
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.Add(new DataPoint { IsEmpty = true });
This looks like a missing point (a gap):
And in my case it didn't crash with following SeriesChartTypes:
Area, Bar, Column, Doughnut, FastPoint, Funnel, Line, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedArea, StackedArea100, StackedBar, StackedBar100, StackedColumn, StackedColumn100, StepLine, ThreeLineBreak
The 2nd approach feels like the right (by design) one. The 1st one looks like a hack, that accidentally appears to work.
You can do a trick. You obviously have to clear you series. When you start adding points to an empty points collection x-coordinates are generated automatically starting at 1. You can create surrogate x yourself and skip some x-values.
int x = 0;
foreach(var y in yValues)
{
x++;
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
continue;
myChart.Series["mySeries"].Points.AddXY(x, y);
}
With bar-like chart types it will look like a missing value.
Related
I want to create a 2D map of tiles. Example:
Cell[,] cells;
for(int x = 0; x < columns; x++)
{
for(int y = 0; y < rows; y++)
{
cells[x, y] = new Cell();
}
}
The first cell would be at (0|0). What if I want to have this cell as my center and create new cells on the left and top side? These cells would have negative indices.
One way to fix this would be a value that determines the maximum length of one direction. Having a map of 100 tiles per side would place the center of the map at (50|50).
Let's say there would be no hardware limitations and no maximum length per side, what is the best way to create a 2D map with a (0|0) center? I can't image a better way than accessing a cell by its x and y coordinate in a 2D array.
Well, Arrays are logical constructs, not physical ones.
This means that the way we look at the the 0,0 as the top left corner, while might help visualize the content of a 2-D array (and in fact, a 2-D array is also somewhat of a visualization aid), is not accurate at all - the 0,0 "cell" is not a corner, and indexes are not coordinates, though it really helps to understand them when you think about them like they are.
That being said, there is nothing stopping you from creating your own class, that implement an indexer that can take both positive and negative values - in fact, according to Indexers (C# Programming Guide) -
Indexers do not have to be indexed by an integer value; it is up to you how to define the specific look-up mechanism.
Since you are not even obligated to use integers, you most certainly can use both positive and negative values as your indexer.
I was testing an idea to use a list of lists for storage and dynamically calculate the storage index based on the class indexer, but it's getting too late here and I guess I'm too tired to do it right. It's kinda like the solution on the other answer but I was attempting to do it without making you set the final size in the constructor.
Well, you can't use negative indices in an array or list, they're just not the right structure for a problem like this... You could, however, write your own class that handles something like this.
Simply pass in the size of the grid into the constructor, and then use the index operator to return a value based off of an an adjusted index... Something like this... Wrote it up really fast, so it probably isn't ideal in terms of optimization.
public class Grid<T> {
T[,] grid { get; }
int adjustment { get; }
int FindIndex(int provided) {
return provided + adjustment;
}
public Grid(int dimension) {
if (dimension <= 0)
throw new ArgumentException("Grid dimension cannot be <= 0");
if (dimension % 2 != 0)
throw new ArgumentException("Grid must be evenly divisible");
adjustment = dimension / 2;
grid = new T[dimension, dimension];
}
public T this[int key, int key2] {
get {
return grid[FindIndex(key), FindIndex(key2)];
}
set {
grid[FindIndex(key), FindIndex(key2)] = value;
}
}
}
I used these to test it:
var grid = new Grid<int>(100);
grid[-50, -50] = 5;
grid[0, 1] = 10;
You can just switch it to:
var grid = new Grid<Cell>(100);
This only works for a grid with equal dimensions... If you need separate dimensions, you'll need to adjust the constructor and the FindIndex method.
I think that an infinitely sized grid would be dangerous. If you increase the size to the right, you'd have to reposition the center.. Which means, what you think will be at 0,0 will now be shifted as the grid is no longer properly centered.
Additionally, performance of such a structure would be a nightmare as you cannot rely on an array to be infinite (as it inherently isn't). So you'd either have to continuously copy the array (like how a list works) or use a linked list.. If using a linked list, you would have to do enormous amounts of iteration to get whatever value you want.
I use the C# Chart in WinForms to plot a variety of variables in real time using the "line" chart type. That works well for analog values, but it's less than ideal for on/off flags.
I'd like to plot multiple flags as horizontal bars that are filled when the value is '1" and clear when the value is '0'.
Before I start coding a solution from scratch, do you have any suggestion on how I could take advantage of any features of the "chart" object to implement this more effectively?
EDIT: I am playing with the Area type, and it seems to be promising.
EDIT 2: That didn't work, because the area in the Area type always starts at the bottom of the chart, hiding the other rows. I am now trying the Range Column type
There are several ways to tackle this.: StackedBars, AreaChart, Annotations but I think by far the simplest is using a LineChartType.
The first issue is: How to create the gaps? The simplest way is to draw them as lines but with Color.Transparent. So instead of using the flag value as our y-value we use it to set the color..
So we could use a function like this:
void AddFlagLine(Chart chart, int series, int flag, int x)
{
Series s = chart.Series[series];
int px = s.Points.AddXY(x, series);
s.Points[px].Color = s.Color;
if (px > 0) s.Points[px - 1].Color = flag == 1 ? s.Color : Color.Transparent;
}
It takes the index of your Series and uses the flag to determine the color; note that the color of a line segment is controlled by the color of the end point.
So if you want to have the line going out from the new point to have its flag color, you need to set it when adding the next one..
This is simple enough and for lines as thick as 1-10 it works fine. But if you want larger widths things get a bit ugly..:
The rounded caps start to get bigger and bigger until they actually touch, flling the gaps more or less.
Unfortunately there seems to be no way to controls the caps-style of the lines. There are many CustomAttributes including DashStyles but not this one. So we have to resort to owner-drawing. This is rather simple for line charts. Here is an example:
The xxxPaint event looks like this:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Graphics g = e.ChartGraphics.Graphics;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
for (int si = 0; si < chart.Series.Count; si++ )
{
Series s = chart.Series[si];
for (int pi = 1; pi < s.Points.Count - 1; pi++)
{
DataPoint dp = s.Points[pi];
int y = (int) ay.ValueToPixelPosition(dp.YValues[0]+1); ///*1*
int x0 = (int)ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int)ax.ValueToPixelPosition(s.Points[pi-1].XValue); ///*2*
int x2 = (int)ax.ValueToPixelPosition(dp.XValue);
x1 = Math.Max(x1, x0);
x2 = Math.Max(x2, x0);
using (Pen pen = new Pen(dp.Color, 40) ///*3*
{ StartCap = System.Drawing.Drawing2D.LineCap.Flat,
EndCap = System.Drawing.Drawing2D.LineCap.Flat })
{
g.DrawLine(pen, x1, y, x2, y);
}
}
}
A few notes:
1 : I have decided to move the the series up by one; this is up to you just as using or turning off the y-axis labels or replacing them by custom labels..
2 : Here we use the previous point's x-position!
3 : Note that instead of hard coding a width of 40 pixels you really should decide on a calculated width. This is an example that almost fills up the area:
int width = (int)( ( ay.ValueToPixelPosition(ay.Minimum) -
ay.ValueToPixelPosition(ay.Maximum)) / (chart7.Series.Count + 2));
You can twist is to fill more or less by adding less or more than 2.
I have turned all BorderWidths to 0 so only the drawn lines show.
I got it:
It turned out to actually be pretty easy; I used the Range Column type.
A) Set-up (done once):
plotChart.Series[chanNo].ChartType = SeriesChartType.RangeColumn;
plotChart.Series[chanNo].CustomProperties = "PointWidth=" + noOfFlags;
PointWidth is required to set the relative width of each rectangle so that it fills the entire width of one data point (if too small, there are gaps in the horizontal bar; if too large, there is overlap). noOfFlags is the number of flags shown (in the example shown above, noOfFlags = 4). (By the way the MSDN documentation is wrong: PointWidth is not limited to 2.)
B) Plotting (done for each new data point):
baseLine--;
int barHeight = flagHigh ? 1 : 0;
plotChart.Series[chanNo].Points.AddXY(pointX, baseLine, baseLine + barHeight);
flagHigh is a bool that is equal to the flag being monitored.
baseLine is decremented for each trace. In the example above, baseLine starts at 4, and is decremented down to 0.
Note that for each data point, RangeColumn requires 2 "Y" values: one for the bottom of the rectangle, one for the top; in the code, I set the bottom Y to the bottom of the row that I use for that particular flag, and the top to 1 above the bottom, to give me a height of 1.
I am drawing a line on a graph from numbers read from a text file. There is a number on each line of the file which corresponds to the X co-ordinate while the Y co-ordinate is the line it is on.
The requirements have now changed to include "special events" where if the number on the line is followed by the word special a spike will appear like image below:
Currently the only way I can find is to use a line for each spike, however there could be a large of these special events and so needs to be modular. This seems an efficient and bad way to program it.
Is it possible to add the spikes to the same graph line? Or is it possible to use just one additional line and have it broken (invisible) and only show where the spikes are meant to be seen?
I have looked at using bar graphs but due to other items on the graph I cannot.
The DataPoints of a Line Chart are connected so it is not possble to really break it apart. However each segment leading to a DataPoint can have its own color and that includes Color.Transparent which lends itself to a simple trick..
Without adding extra Series or Annotations, your two questions can be solved like this:
To simply add the 'spikes' you show us in the 2nd graph, all you need to do is to insert 2 suitable datapoints, the 2nd being identical to the point the spike is connected to.
To add an unconnected line you need to 'jump' to its beginning by adding one extra point with a transparent color.
Here are two example methods:
void addSpike(Series s, int index, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
s.Points.Insert(index+1, dp1);
s.Points.Insert(index+2, dp);
}
void addLine(Series s, int index, double spikeDist, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeDist, dp.YValues[0]);
DataPoint dp2 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
DataPoint dp0 = dp.Clone();
dp1.Color = Color.Transparent;
dp2.Color = dp.Color;
dp2.BorderWidth = 2; // optional
dp0.Color = Color.Transparent;
s.Points.Insert(index + 1, dp1);
s.Points.Insert(index + 2, dp2);
s.Points.Insert(index + 3, dp0);
}
You can call them like this:
addSpike(chart1.Series[0], 3, 50d);
addLine(chart1.Series[0], 6, 30d, 80d);
Note that they add 2 or 3 DataPoints to the Points collection!
Of course you can set the Color and width (aka BorderWidth) of the extra lines as you wish and also include them in the params list..
If you want to keep the points collection unchanged you also can simply create one 'spikes series' and add the spike points there. The trick is to 'jump' to the new points with a transparent line!
Okay guys,
I am having a LOT trouble with this. I simply cannot figure out a way to implement tile picking in a hexagonal map in XNA. I have looked this up prior to asking this question, and all the answers involve complicated algorithms and diagrams my puny mind simply cannot comprehend. So my question for you guys is: How would i be able to hover over tiles, and select them if i wanted to?
If you need any reference as to how my program looks so far, just check out this link, its literally the same except i have a smaller map on mine.
http://www.xnaresources.com/default.asp?page=Tutorial:TileEngineSeries:3
Thanks!
This is code i had stored but never used and its for hex grid where one edge is looking up, so by some minor tweaks it could work in your example. It's not my code, not sure who wrote it.
Hexagon[][] hexagons = new Hexagon[100][100];
double hexagonHeight = 30;
double hexagonWidth = 40;
double halfWidth = hexagonWidth / 2;
// Find rough coordinates of Hexagon at mousepoint
private Hexagon getSelectedHexagon(MouseEvent mouse)
{
// These will represent which box the mouse is in, not which hexagon!
int row = (int) (mouse.y / hexagonHeight);
int column;
boolean rowIsOdd = row % 2 != 0;
// Is the row an even number?
if (rowIsOdd) // No: Calculate normally
column = (int) (mouse.x / hexagonWidth);
else // Yes: Offset mouse.x to match the offset of the row
column = (int) ((mouse.x + halfWidth) / hexagonWidth);
// column is more complex because it has to
// take into account that every other row
// is offset by half the width of a hexagon
return hexagons[row][column];
}
edit: i just found author
Hexagonal Grids, how do you find which hexagon a point is in?
I have a function where I try to find a matching Point between 2 collections of 4 Points each, but sometimes the function reports the collections do not share a common Point even though in the debugger I see they do. is the debugger not showing me the full precision of the points so I do not see the difference? or is there something else going on here? here's the code to blame:
public static Point CorrectForAllowedDrawArea(Point previousDisplayLocation, Point newDisplayLocation, Rect displayLimitedArea, Rect newBoundingBox)
{
// get area that encloses both rectangles
Rect enclosingRect = Rect.Union(displayLimitedArea, newBoundingBox);
// get corners of outer rectangle, index matters for getting opposite corner
var outsideCorners = new[] { enclosingRect.TopLeft, enclosingRect.TopRight, enclosingRect.BottomRight, enclosingRect.BottomLeft }.ToList();
// get corners of inner rectangle
var insideCorners = new[] { displayLimitedArea.TopLeft, displayLimitedArea.TopRight, displayLimitedArea.BottomRight, displayLimitedArea.BottomLeft }.ToList();
// get the first found corner that both rectangles share
Point sharedCorner = outsideCorners.FirstOrDefault((corner) => insideCorners.Contains(corner));
// find the index of the opposite corner
int oppositeCornerIndex = (outsideCorners.IndexOf(sharedCorner) + 2) % 4;
on the last line 'sharedCorner' is sometimes set to default(Point) even though both Point collections appear to share 1 Point.
EDIT: I should mention if I place the debugger back to the top of the function and restart it still does not find the matching point. I should also mention that this function uses the Point class of the System.Windows namespace and not of the System.Drawing namespace! Thanks for pointing this out to me in the comments.
We really need to see what the definition of insideCorners.Contains(corner) is, but I suspect that your problem is due to the inherent inaccuracies with floating point numbers.
You cannot compare two floating point values like this:
if (a == b)
{
// Values are equal
}
especially if either a or b are calculated values.
You'll need to implement something along the lines of:
if (Math.Abs(a - b) < some_small_value)
{
// Values are equal
}