I have created a custom shape that draws an ObservableCollection'Point using a StreamGeometry. When the signal surpasses the width of the canvas it is drawn on, it wraps around to the begining. Two polyLines are used; one from the beginning of the canvas to the last added point and one from the first added point in the current collection till the end of the canvas:
protected override Geometry DefiningGeometry
{
get
{
if (this.Points == null || !this.Points.Any())
{
return Geometry.Empty;
}
var firstGeometryPoints = this.Points.Where(p => p.Point.X >= this.Points.First().Point.X);
var secondGeometryPoints = this.Points.Except(firstGeometryPoints);
var geometry = new StreamGeometry();
using (var context = geometry.Open())
{
context.BeginFigure(firstGeometryPoints.First().Point, true, false);
context.PolyLineTo(firstGeometryPoints.Skip(1).Select(x => x.Point).ToArray(), true, true);
if (secondGeometryPoints.Any())
{
context.BeginFigure(secondGeometryPoints.First().Point, true, false);
context.PolyLineTo(secondGeometryPoints.Skip(1).Select(x => x.Point).ToArray(), true, true);
}
geometry.Freeze();
}
return geometry;
}
}
Whenever the Points collection changes I use InvalidateVisual() to force a redraw of the shape. But since data points are added at 500 samples per second, the application starts to lag. Is there another way of creating this type of graph without redrawing the entire shape on every sample?
Related
I'm using OxyPlot to show a data chart and to allow the user to select an interval of data he wants to do calculation on.
It looks like this:
Now, I would like the user to be able to set the data interval used for calculation himself by resizing the chart. For example, if he resized the chart on this particular interval it would only take the points located between the furthest left and the furthest right on screen.
I already found the event that triggers whenever the chart is resized :
plotModel.Updated += (s, e) =>
{
//reset interval used for calculation
};
What I couldn't find in OxyPlot documentation is a way to retrieve a certain set of points currently shown. It doesn't need to be points in particular, I could also use only the x components of each extremum.
You could use Series.GetScreenRectangle().Contains() method after transforming your points using the Transform() method to detect if the point is currently in display.
For example,
model.Updated += (s,e)=>
{
if(s is PlotModel plotModel)
{
var series = plotModel.Series.OfType<OxyPlot.Series.LineSeries>().Single();
var pointCurrentlyInDisplay = new List<DataPoint>();
foreach (var point in series.ItemsSource.OfType<DataPoint>())
{
if (series.GetScreenRectangle().Contains(series.Transform(point)))
{
pointCurrentlyInDisplay.Add(point);
}
}
}
};
You are iterating over the point collection and verifying if the Transformed point (Transform method transform DataPoint to screen point) falls within the Screen Rectangle used by the series.
Update
If you have used Series.Points.AddRange()/Add() for adding points instead of Series.ItemSource, use the following for retrieving points.
foreach (var point in series.Points.OfType<DataPoint>())
{
if (series.GetScreenRectangle().Contains(series.Transform(point)))
{
pointCurrentlyInDisplay.Add(point);
}
}
I am working with a WPF control derived from the Canvas that I am drawing a selection of geometry to, through a process that works along these lines, but is far more complex so although this illustrates the process, it is closer to pseudo-code:
public class LineView: Canvas
{
private List<GeometryLine> lines;
public LineView()
{
lines = new List<GeometryLine>();
}
private PathFigure GetPathFigure(List<Point> line, bool close)
{
var size = line.Count;
var points = new PathSegmentCollection(size);
var first = line.First();
for (int i = 1; i < size; i++)
{
points.Add(new LineSegment(line[i], true));
}
var figure = new PathFigure(first, points, close);
return new PathGeometry(new List<PathFigure>(figure));
}
public DrawingGroup DrawLine(GeometryLine line)
{
var geometry = new GeometryGroup();
geometry.Children.Add(GetPathGeometry(line.Points, false));
var d = new GeometryDrawing();
d.Pen = GetPen();
d.Geometry = geometry;
DrawingGroup group = new DrawingGroup();
group.Append();
group.Children.Add(d);
return group;
}
public override void OnRender(DrawingContext drw)
{
if ( 0 < lines.Count )
{
foreach ( var line in lines )
{
drw.DrawDrawing(DrawLine(line));
}
}
}
public void SetLines(List<GeometryLine> newLines)
{
lines = newLines;
this.InvalidateVisual();
}
}
This all works fine to render geometry content on the canvas. However, when the data changes and SetLines is called with a new set of data, it doesn't always clear the canvas - sometimes it will draw the new set of lines over the old set, other times it will clear the canvas. I can't see any pattern as to when it draws over as opposed to when it clears the canvas.
If I call InvalidateVisual from the Render method it will empty the canvas reliably, but it also forces the context to render again. Past questions on this topic indicate that either this.Children.Clear() or InvalidateVisual are the recommended strategies, but neither of them prevents this overdrawing problem. When I look at the Children collection it is always empty.
What do I need to do in order to ensure it will clear the previous geometry and then draw the updated geometry every time it changes?
I am currently following this tutorial for adding a polygon to a map. I need to be able to add multiple polygons to my map, so I have slightly altered the code so that I can use addOverlays which takes in an array of IMKOverlay objects instead of one addOverlay which just takes in a single IMKOverlay object.
This doesn't work however... It only draws the first polygon on the map!
void addPolygonsToMap()
{
overlayList = new List<IMKOverlay>();
for (int i = 0; i < polygons.Count; i++)
{
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[polygons[i].Count];
int index=0;
foreach (var position in polygons[i])
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var blockOverlay = MKPolygon.FromCoordinates(coords);
overlayList.Add(blockOverlay);
}
IMKOverlay[] imko = overlayList.ToArray();
nativeMap.AddOverlays(imko);
}
In this discussion, it would appear that I have to call a new instance of MKPolygonRenderer each time I need to add another polygon to my map, but I'm unsure how this example translates to my code. Here is my MKPolygonRenderer function:
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (polygonRenderer == null && !Equals(overlayWrapper, null)) {
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
polygonRenderer = new MKPolygonRenderer(overlay as MKPolygon) {
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
}
return polygonRenderer;
}
Create a new renderer instance each time OverlayRenderer is called, there is no need to cache the renderer in a class level variable as the MKMapView will cache the renderers as needed.
Subclass MKMapViewDelegate:
class MyMapDelegate : MKMapViewDelegate
{
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
switch (overlay)
{
case MKPolygon polygon:
var prenderer = new MKPolygonRenderer(polygon)
{
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
return prenderer;
default:
throw new Exception($"Not supported: {overlay.GetType()}");
}
}
}
Instance and assign the delegate to your map:
mapDelegate = new MyMapDelegate();
map.Delegate = mapDelegate;
Note: Store the instance of your MyMapDelegate in a class level variable as you do not want to get GC'd
Update:
MKMapView has two steps involved to display an overlay on its map.
1. Calling `AddOverlay` and `AddOverlays`
First you add overlays to the map that conform to IMKOverlay. There are basic built-in types such as MKCircle, MKPolygon, etc... but you can also design your own overlays; i.e. overlays that define the location of severe weather (lightning, storm clouds, tornados, etc..). These MKOverlays describe the geo-location of the item but not how to draw it.
2. Responding to `OverlayRenderer` requests
When the display area of the map intersects with one of the overlays, the map need to draw it on the screen. The map's delegate (your MKMapViewDelegate subclass) is called to supply a MKOverlayRenderer that defines the drawing routines to paint the overlay on the map.
This drawing involves converting the geo-coordinates of the overlay to local display coordinates (helper methods are available) using Core Graphics routines (UIKit can be used with some limitations). There are basic built-in renderers for MKCircleRenderer, MKPolygonRenderer, etc.. that can be used or you can write your own MKOverlayRenderer subclass.
You could supply a custom way to renderer a MKCircle overlay, maybe a target-style red/white multi-ringed bullseye, instead of the way the default circle renderer draws it, or custom renderers that draw severe storm symbols within the bounds of a MKPolygon to match your custom severe storm overlays.
My Example code:
Since you are using MKPolygon to build your overlays, you can use the MKPolygonRenderer to display them. In my example, I provide a pattern matching switch (C# 6) that returns a semi-transparent Red/Blue MKPolygonRenderer for every MKPolygon that you added to the map (if you added a non-MKPolygon based overlay it will throw an exception).
I was also stuck in this issue and I have found the way to create the sub class of MKPolygon.
I have checked it with my example and it works like a charm. But not sure that Apple may reject my app or not.
public class CvPolyon : MKPolygon
{
public CustomObject BoundaryOption { get; }
public CvPolyon1(MKPolygon polygon, CustomObject boundaryOption)
:base(polygon.Handle)
{
BoundaryOption = boundaryOption;
}
}
We can add polygon on map like this.
var polygon = MKPolygon.FromCoordinates(coordinates);
var overlay = new CvPolyon(polygon, new CustomObject());
mapView.AddOverlay(overlay);
We can recognize our polygon in the class which extends MKMapViewDelegate like this.
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
if (overlay is CvPolyon polygon)
{
var polygonRenderer = new MKPolygonRenderer(polygon)
{
FillColor = polygon.BoundaryOption.AreaColor,
StrokeColor = polygon.BoundaryOption.LineColor,
Alpha = polygon.BoundaryOption.Alpha,
LineWidth = polygon.BoundaryOption.LineWidth
};
if (polygon.BoundaryOption.IsDashedLine)
polygonRenderer.LineDashPattern = new[] { new NSNumber(2), new NSNumber(5) };
return polygonRenderer;
}
return mapView.RendererForOverlay(overlay);
}
I have a scene with a skybox and I would like to get the point the user clicked projected onto the skybox.
I'm using HelixViewport3D.FindNearestPoint(Point pt) to get the point, which works very well, except when there's anything between the click and the skybox. In this situation it returns the point projected onto the object in front of the skybok.
Is there any way to flag an element so it would be ignored in HitTests?
You can catch point on any Visual3D or Geometry3D
Give names to your Visual3D objects.
ModelVisual3D modelVisual3D = new ModelVisual3D();
modelVisual3D.SetName("ModelName");
You can use FindHits method with your HelixViewPort3D
Point3D point3D;
var hitList = yourHelixViewPort.ViewPort.FindHits(Point point);
foreach (var hit in hitList)
{
if (hit.Visual != null)
{
if (hit.Visual.GetName() == "ModelName")
{
point3D = hit.Position;
// You can use also hit.Mesh
// also hit.Model
// also hit.Visual
// also hit.Normal
}
}
}
I'm trying to allow the user to be able to set one corner of a rectangle to draw on the mouse down event, when the mouse up event triggers i'd like to set the opposing corner coordinates and draw the rectangle. I have tried the following in my *.ASPX javascript:
var oneCorner;
var TwoCroner;
map.on('mousedown', setOneCorner);
map.on('mouseup', setTwoCorner);
function setOneCorner(e)
{
oneCorner = e.latlng;
}
function setTwoCorner(e)
{
twoCorner = e.latlng;
var bounds = [oneCorner.latlng, twoCorner.latlng];
L.rectangle(bounds, {color:"#ff7800", weight:1}).addTo(map);
}
my tiles can still pan on the mouse down event but I'd like to be able to draw a rectangle where ever i want. how should i go bout doing this?
If you dont want your map to pan., then you can add
map.dragging.disable()
to your code. Also your array with bounds should be var bounds = [oneCorner, twoCorner];
because corners variables are already LatLng objects.
Full code would be:
var oneCorner;
var TwoCroner;
map.on('mousedown', setOneCorner);
map.on('mouseup', setTwoCorner);
map.dragging.disable();
function setOneCorner(e)
{
oneCorner = e.latlng;
}
function setTwoCorner(e)
{
twoCorner = e.latlng;
var bounds = [oneCorner, twoCorner];
L.rectangle(bounds, {color:"#ff7800", weight:1}).addTo(map);
}
But I dont think its good idea to prevent map pan. What about to use it with Ctrl, or you can use this plugin for drawing: https://github.com/Leaflet/Leaflet.draw
// EDIT:
Version with only Ctrl pressed.:
var oneCorner;
var TwoCroner;
map.on('mousedown', setOneCorner);
map.on('mouseup', setTwoCorner);
function setOneCorner(e)
{
if (e.originalEvent.ctrlKey) {
map.dragging.disable();
oneCorner = e.latlng;
}
}
function setTwoCorner(e)
{
if (e.originalEvent.ctrlKey) {
twoCorner = e.latlng;
var bounds = [oneCorner, twoCorner];
L.rectangle(bounds, {color:"#ff7800", weight:1}).addTo(map);
}
map.dragging.enable();
}