Selecting data intervals on Datachart with Mouse - c#

I use stacked area chart and I want to select a datapoint interval with the mouse like below.
I know that some applications offer this feature however, I couldn't find how to do it.
Could you please show me the right way?

The term you needed is DataVisualization.Charting.Cursor
You can use this combination of properties:
// a few short references:
ChartArea ca = chart1.ChartAreas[0];
Axis ax = ca.AxisX;
var cx = ca.CursorX;
cx.IsUserEnabled = true; // allow a cursor to be placed
cx.IsUserSelectionEnabled = true; // allow it to be used for selecting
ax.ScaleView.Zoomable = false; // prevent from automatically zooming in
Here are the first and last values selected:
var x1 = cx.SelectionStart;
var x2 = cx.SelectionEnd;
Here are the first and last DataPoints selected:
var p1 = s.Points.Select(x => x).Where(x => x.XValue >= x1).First();
var p2 = s.Points.Select(x => x).Where(x => x.XValue <= x2).Last();
And the indices of the first and last DataPointsselected:
var i1 = s.Points.IndexOf(p1);
var i2 = s.Points.IndexOf(p2);
Now you can tell which points were selected:
textBox1.Text += (i2 - i1) + " points selected.\r\n\r\n";
for (int i = i1; i < i2; i++)
{
textBox1.Text += i + ". " + chart1.Series[0].Points[i].ToString() + "\r\n";
chart1.Series[0].Points[i].Color = Color.Red;
}
Note: The code to identify the starting and end points assumes that all DataPoints are added in increasing x-value order. Since you can add DataPoints in any order it will fail for instance when you insert out of order points! In that case you would instead collect the points in the selection (testing for both sides) in a List<DataPoint> and then enumerate this list.

Related

Is it possible to clear polylines from xamarin.forms.maps?

I have not been able to find a solution regarding removing polylines from xamarin.forms.maps, there are plenty instances for googlemaps however.
Is it at all possible? Im not seeing anything obvious in the docs. I suppose that Im early enough into this project that I could probably switch over to googlemaps if need be.
int PointsCount = PointsInfoObj.Count;
for (int i = 0; i < PointsCount; i++)
{
//
// we want to ensure that the count is less than the final item, since the second point in a poly lands on the final
if (i < (PointsCount - 1))
{
Polyline polyLine = new Polyline
{
StrokeColor = Color.Blue,
StrokeWidth = 12,
Geopath =
{
//
// retrieve the current item and the next item for poyline drawing
new Position((double)PointsInfoObj[i].gpxPoints_Lattitude, (double)PointsInfoObj[i].gpxPoints_Longitude),
new Position((double)PointsInfoObj[(i+1)].gpxPoints_Lattitude, (double)PointsInfoObj[(i+1)].gpxPoints_Longitude)
}
};
//
// populate the polyline method
GPSMap.MapElements.Add(polyLine);
}
else continue;
}
//
// gather the first lat and last lon to do a proper MapSpan
double FirstLat = (double)PointsInfoObj[0].gpxPoints_Lattitude;
double LastLon = (double)PointsInfoObj[(PointsCount - 1)].gpxPoints_Longitude;
Console.WriteLine("lat: " + FirstLat + "---------------------------------------------");
Console.WriteLine("lon: " + LastLon + "---------------------------------------------");
GPSMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(FirstLat, LastLon), Distance.FromMiles(2.5)));

Find points with maximum visibility in a room-as-grid

I have a 2D grid of cells, like so:
I want to find the "centroids," or the places in each room that can see the most other cells. For example, "centroid" 1 can see 500 other cells, "centroid" 2 can see 400 other cells (NOT included in the first 500), and so on (if there's a better name for this let me know).
I'm currently doing this with the code below.
public void SetCentroids(CellWalkableState[,] grid)
{
centroids = new List<((int, int), int)>();
List<(int, int)> cellsCopy = new List<(int, int)>();
for (int i = 0; i < cells.Count; i++)
{
cellsCopy.Add(cells[i]);
}
Debug.Log(DateTime.Now.ToString("o") + " - Setting centroids for room with " + cells.Count + " cells");
var perCellInView = cellsCopy.AsParallel().Select(x => (x, StaticClass.FindInView(x, grid))).ToList();
var force_start = perCellInView.First();
Debug.Log(DateTime.Now.ToString("o") + " - got in view");
var perCellInViewOrdered = perCellInView.AsParallel().OrderByDescending(xxx => xxx.Item2.Count);
var force_start_1 = perCellInViewOrdered.First();
Debug.Log(DateTime.Now.ToString("o") + " - sorted");
List<(int, int)> roomCellsAdded = new List<(int, int)>();
while(roomCellsAdded.Count < (cells.Count*0.9))
{
if(cellsCopy.Count == 0)
{
Debug.LogError("something is wrong here.");
}
var centroid = perCellInViewOrdered.First().x;
var centroidCells = perCellInViewOrdered.First().Item2;
if(centroidCells.Count == 0)
{
Debug.Log("this shouldnt be happening");
break;
}
roomCellsAdded.AddRange(centroidCells);
centroids.Add((centroid, centroidCells.Count));
Debug.Log(DateTime.Now.ToString("o") + " - added centroids, " + roomCellsAdded.Count + " cells in view");
var loopPerCellInView = perCellInView.AsParallel().Where(x => centroids.Select(y => y.Item1).Contains(x.x) == false).Select(x => (x.x, x.Item2.Except(roomCellsAdded).ToList())).ToList();
Debug.Log(DateTime.Now.ToString("o") + " - excluded");
perCellInViewOrdered = loopPerCellInView.AsParallel().OrderByDescending(xxx => xxx.Item2.Count);
Debug.Log(DateTime.Now.ToString("o") + " - resorted");
}
}
public static List<(int, int)> FindInView((int,int) start, CellWalkableState[,] grid)
{
List<(int, int)> visible = new List<(int, int)>() { start };
bool alive = true;
int r = 1;
var length_x = grid.GetLength(0);
var length_y = grid.GetLength(1);
List<(int, int)> searched = new List<(int, int)>();
List<double> angles = new List<double>();
while(alive)
{
//alive = false;
int newR = r;
int count = CountFromR(newR);
var angleInc = 360.0 / count;
var rNexts = Enumerable.Repeat(1, count).ToArray();
for (int i = 0; i < count; i++)
{
var angle = angleInc * i;
if(angles.Contains(angle) == false)
{
angles.Add(angle);
float cos = Mathf.Cos((float)(Mathf.Deg2Rad * angle));
float sin = Mathf.Sin((float)(Mathf.Deg2Rad * angle));
var b = r;
var p = i % (r * 2);
var d = math.sqrt(math.pow(b, 2) + math.pow(p, 2));
var dScaled = d / r;
bool keepGoing = true;
while(keepGoing)
{
var rCur = dScaled * (rNexts[i]);
var loc = (start.Item1 + Mathf.RoundToInt(rCur * cos), start.Item2 + Mathf.RoundToInt(rCur * sin));
if (searched.Contains(loc) == false)
{
searched.Add(loc);
if (loc.Item1 >= 0 && loc.Item1 < length_x && loc.Item2 >= 0 && loc.Item2 < length_y)
{
if (grid[loc.Item1, loc.Item2] == CellWalkableState.Interactive || grid[loc.Item1, loc.Item2] == CellWalkableState.Walkable)
{
visible.Add(loc);
}
else
{
keepGoing = false;
}
}
else
{
keepGoing = false; // invalid, stop
}
}
else
{
if (visible.Contains(loc) == false)
{
keepGoing = false; // can stop, because we can't see past this place
}
}
if(keepGoing)
{
rNexts[i]++;
}
}
}
}
angles = angles.Distinct().ToList();
searched = searched.Distinct().ToList();
visible = visible.Distinct().ToList();
if(rNexts.All(x => x <= r))
{
alive = false;
}
else
{
r = rNexts.Max();
}
}
return visible;
}
static int CountFromR(int r)
{
return 8 * r;
}
The "short" summary of the code above is that each location first determines what cells around itself it can see. That becomes a list of tuples, List<((int,int), List<(int,int)>)>, where the first item is the location and the second is all cells it views. That main list is sorted by the count of the sublist, such that the item with the most cells-it-can-vew is first. That's added as a centroid, and all cells it can view are added to a second ("already handled") list. A modified "main list" is formed, with each sublist now excluding anything in the second list. It loops doing this until 90% of the cells have been added.
Some output:
2021-04-27T15:24:39.8678545-04:00 - Setting centroids for room with 7129 cells
2021-04-27T15:45:26.4418515-04:00 - got in view
2021-04-27T15:45:26.4578551-04:00 - sorted
2021-04-27T15:45:27.3168517-04:00 - added centroids, 4756 cells in view
2021-04-27T15:45:27.9868523-04:00 - excluded
2021-04-27T15:45:27.9868523-04:00 - resorted
2021-04-27T15:45:28.1058514-04:00 - added centroids, 6838 cells in view
2021-04-27T15:45:28.2513513-04:00 - excluded
2021-04-27T15:45:28.2513513-04:00 - resorted
2021-04-27T15:45:28.2523509-04:00 - Setting centroids for room with 20671 cells
This is just too slow for my purposes. Can anyone suggest alternate methods of doing this? For all of the cells essentially the only information one has is whether they're "open" or one can see through them or not (vs something like a wall).
An approximate solution with fixed integer slopes
For a big speedup (from quadratic in the number of rooms to linear), you could decide to check just a few integer slopes at each point. These are equivalence classes of visibility, i.e., if cell x can see cell y along such a line, and cell y can see cell z along the same line, then all 3 cells can see each other. Then you only need to compute each "visibility interval" along a particular line once, rather than per-cell.
You would probably want to check at least horizontal, vertical and both 45-degree diagonal slopes. For horizontal, start at cell (1, 1), and move right until you hit a wall, let's say at (5, 1). Then the cells (1, 1), (2, 1), (3, 1) and (4, 1) can all see each other along this slope, so although you started at (1, 1), there's no need to repeat the computation for the other 3 cells -- just add a copy of this list (or even a pointer to it, which is faster) to the visibility lists for all 4 cells. Keep heading right, and repeat the process as soon as you hit a non-wall. Then begin again on the next row.
Visibility checking for 45-degree diagonals is slightly harder, but not much, and checking for other slopes in which we advance 1 horizontally and some k vertically (or vice versa) is about the same. (Checking for for general slopes, like for every 2 steps right go up 3, is perhaps a bit trickier.)
Provided you use pointers rather than list copies, for a given slope, this approach spends amortised constant time per cell: Although finding the k horizontally-visible neighbours of some cell takes O(k) time, it means no further horizontal processing needs to be done for any of them. So if you check a constant number of slopes (e.g., the four I suggested), this is O(n) time overall to process n cells. In contrast, I think your current approach takes at least O(nq) time, where q is the average number of cells visible to a cell.

setting fixed values to X axis of a chart

The problem is that I want to set some fixed values for 3 types of charts:
First is a week chart so it need to have Sunday to Saturday values on X axis.
Second is month so it have to set the days of the current month
The last one its a year chart that need to show months from 1 to 12 or jan to dec.
I did watch a lot of tutorials but any of those set points like I want and most of them teach how to set X and Y points, but I want a fixed X point and get the Y point from the DB.
To get these fixed ranges :
I have set up the chart like so:
private void rb_range_CheckedChanged(object sender, EventArgs e)
{
Chart chart = chart8;
Series s = chart.Series[0];
s.ChartType = SeriesChartType.Line;
s.XValueType = ChartValueType.DateTime;
s.YValueType = ChartValueType.Double;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
//ax.IsMarginVisible = true; // max or may be necessary
ax.Interval = 1;
if (rb_week.Checked)
{
setValues('w', 123);
ax.IntervalType = DateTimeIntervalType.Days;
ax.LabelStyle.Format = "dddd";
}
else if (rb_month.Checked)
{
setValues('m', 123);
ax.IntervalType = DateTimeIntervalType.Days;
ax.LabelStyle.Format = "dd";
}
else if (rb_year.Checked)
{
setValues('y', 123);
ax.IntervalType = DateTimeIntervalType.Months;
ax.LabelStyle.Format = "MMMM";
}
s.Points.Clear();
foreach (var dp in points) s.Points.Add(dp);
// after the points are added or bound to you may want to..
// set the minimum&maximum, but if the data fit you don't have to!
ax.Minimum = points.Min(x => x.XValue);
ax.Maximum = points.Max(x => x.XValue);
}
A few Notes :
It is important to select or bind only those dates that should go into the chart! If you make mistakes here the limits will be off!
If your data are dense enough, that is, if they inclused the 1st and last day of the interval they refer to, you can omit setting the Minimum and Maximum on the x-axis; in that case also include ax.IsMarginVisible = false; to avoid points from the neighboring ranges showing up.
If you data are sparse you may need to determine the Minimum and Maximum values differently than simply picking the first and last x-values. Instead you should pick the correct DateTime values. Note that you need to pick real DateTimes and convert them to double with the ToOADate() function, as the axis properties expect value units.
You can study the code I used to create my data for hints on how to get the Date of the 1st and last day of a given week, month or year..
Note how I use DateTime.DaysInMonth to get the correct number of days in a given month
If you chose Column as ChartType the 1st and last columns may get cut. For this case you can expand the range by adding half a unit to the Maximum and subtracting the same from the Minimum. You may also need to add one such amount to the IntervalOffset.
Here is how I set up the points:
List<DataPoint> points = new List<DataPoint>();
void setValues(char time, int rand)
{
Random rnd = new Random(rand); // random data values
points = new List<DataPoint>();
DateTime dn = DateTime.Now;
DateTime dw = new DateTime(dn.Year, dn.Month, dn.Day % 7 + 1); //my weeks start on monday
DateTime dm = new DateTime(dn.Year, dn.Month, 1);
DateTime dy = new DateTime(dn.Year, 1, 1);
if (time == 'w') for (int i = 0; i < 7; i++)
points.Add(new DataPoint(dw.AddDays(i).ToOADate(), rnd.Next(100) + 50));
if (time == 'm') for (int i = 0; i < DateTime.DaysInMonth(dn.Year, dn.Month); i++)
points.Add(new DataPoint(dm.AddDays(i).ToOADate(), rnd.Next(100) + 50));
if (time == 'y') for (int i = 0; i < 12; i++)
points.Add(new DataPoint(dy.AddMonths(i).ToOADate(), rnd.Next(100) + 50));
}

C# Update chart values

How do I update values that have already been created?
Like, I have this chart:
And then I want to update the chart values with other values. I've already tried many things but I don't manage to make it work...
My code:
int ratio = (int)PlayerStats.Kills - PlayerStats.Deaths;
string[] seriesArray = { "Kills", "Assists", "Deaths", "MVPS" , "Headshots", "Ratio" };
int[] pointsArray = { PlayerStats.Kills, PlayerStats.Assists, PlayerStats.Deaths, PlayerStats.MVPS, PlayerStats.Headshots, ratio };
this.chart1.Titles.Add("Statistics Chart");
for (int i = 0; i < seriesArray.Length; i++)
{
Series series = series = this.chart1.Series.Add(seriesArray[i]);
series.Points.Add(pointsArray[i]);
}
chart1.ChartAreas[0].AxisX.Enabled = AxisEnabled.False;
chart1.ChartAreas[0].AxisY.Enabled = AxisEnabled.False;
double realRatio = Math.Round((double)PlayerStats.Kills / PlayerStats.Deaths, 2);
double hsRatio = Math.Round((double)PlayerStats.Headshots / PlayerStats.Kills, 2);
label6.Text = PlayerStats.Kills.ToString();
label7.Text = PlayerStats.Assists.ToString();
label8.Text = PlayerStats.Deaths.ToString();
label11.Text = PlayerStats.MVPS.ToString();
label10.Text = String.Format("{0:P2}", hsRatio);
label9.Text = realRatio.ToString();
When I try to run the function a second time (to update the values) it gives me a exception: (inside the loop, first line)
Additional information: There is already a chart element with the name 'Kills' in 'SeriesCollection'.
I've managed to that that specific function (chart1.Series.Add(...)) only called once on the startup, but then the other function inside the loop gave me exceptions too.
Well, I hope that you understand my english :P
Are you clearing out the chart elements before looping through to add them again? Something like
this.chart1.Series.Clear();

Can I draw the area of an integral with Oxyplot?

Is it possible to draw the area of a certain integral in Oxyplot?
With the MathNet.Numerics library it is possible to calculate these integrals but I am wondering if I am able to draw it in my plot?
I am no expert in the matter, but I may have found something helpfull for you...
Take a look at AreaSeries. I think this is what you need.
Example:
var model = new PlotModel { Title = "AreaSeries" };
var series = new AreaSeries { Title = "integral" };
for (double x = -10; x <= 10; x++)
{
series.Points.Add(new DataPoint(x, (-1 * (x * x) + 50)));
}
model.Series.Add(series);
Then you set the model to your PlotView.Model and you should see a plot similar at what you posted in the comments above.
I hope this works for you.
----- EDIT (because of comment in the answer) -----
It turns out that you actually can do what you asked in the comments. You just need to fill AreaSeries.Points2 with the points you want to restrict your Area. For example, in my previous sample add inside the for the following line
series.Points2.Add(new DataPoint(x, x));
Then you will have the area defined by two lines: y = -x^2 + 50 and y = x. You can even set the second line transparent.
Complete Example:
var model = new PlotModel { Title = "AreaSeries" };
var series = new AreaSeries { Title = "series" };
for (double x = -10; x <= 10; x++)
{
series.Points.Add(new DataPoint(x, (-1 * (x * x) + 50)));
series.Points2.Add(new DataPoint(x, x));
}
series.Color2 = OxyColors.Transparent;
model.Series.Add(series);
plotView.Model = model;
Now you just have to add the formulas that you need, and it should show a similar graph to the one you put in your comment.

Categories