Ejemplo n.º 1
0
        public static GraphManager <PointI> CreatePolygonGridManager(
            GraphDialog dialog, RegularPolygon polygon)
        {
            int         width = 20, height = 20;
            PolygonGrid grid = new PolygonGrid(polygon);

            grid.Size = new SizeI(width, height);

            // shrink grid until it fits in output box
            double outputWidth  = dialog.OutputBox.Width - 16;
            double outputHeight = dialog.OutputBox.Height - 16;

            while (grid.DisplayBounds.Width > outputWidth)
            {
                grid.Size = new SizeI(--width, height);
            }
            while (grid.DisplayBounds.Height > outputHeight)
            {
                grid.Size = new SizeI(width, --height);
            }

            var manager = new GraphManager <PointI>(dialog, 4);

            manager.Graph    = grid;
            manager.Renderer = new GraphRenderer <PointI>(manager);
            return(manager);
        }
Ejemplo n.º 2
0
        private void OnGridChanged(object sender, EventArgs args)
        {
            RoutedEventArgs routedArgs = args as RoutedEventArgs;

            if (routedArgs != null)
            {
                routedArgs.Handled = true;
            }
            if (_ignoreControls)
            {
                return;
            }

            Grid = new PolygonGrid(Element, GridShift);

            // determine number of columns and rows
            int width  = (int)ColumnsUpDown.Value;
            int height = (int)RowsUpDown.Value;

            Grid.Size = new SizeI(width, height);

            // show resulting output size
            RectD bounds = Grid.DisplayBounds;

            WidthBox.Text  = bounds.Width.ToString("N0");
            HeightBox.Text = bounds.Height.ToString("N0");
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Draws the specified <see cref="PolygonGrid"/> to the specified <see
        /// cref="StreamGeometry"/>, with optimization to remove duplicate lines.</summary>
        /// <param name="grid">
        /// The <see cref="PolygonGrid"/> to draw.</param>
        /// <param name="context">
        /// The <see cref="StreamGeometryContext"/> that receives <paramref name="grid"/>.</param>
        /// <param name="offset">
        /// The offset by which to shift <paramref name="grid"/>.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="grid"/> or <paramref name="context"/> is a null reference.</exception>
        /// <remarks><para>
        /// <b>DrawOptimized</b> shifts the <see cref="RegularPolygon.Vertices"/> of each <see
        /// cref="PolygonGrid.Element"/> in the specified <paramref name="grid"/> by the
        /// corresponding <see cref="PolygonGrid.GridToDisplay"/> result plus the specified
        /// <paramref name="offset"/>.
        /// </para><para>
        /// <b>DrawOptimized</b> skips any <see cref="RegularPolygon"/> edges that coincide with an
        /// edge drawn for a previous <see cref="PolygonGrid.Element"/>. This optimization results
        /// in a total number of lines drawn to the specified <paramref name="context"/> that is
        /// about half that of the unoptimized <see cref="Draw"/> method.
        /// </para><para>
        /// There are two disadvantages. First, <b>DrawOptimized</b> requires more time and memory
        /// than <see cref="Draw"/> due to the additional vertex comparisons. Second, most polygons
        /// are not represented by closed figures within the specified <paramref name="context"/>,
        /// and so the area covered by the specified <paramref name="grid"/> is never considered
        /// filled for hit-testing, rendering, and clipping.</para></remarks>

        public static void DrawOptimized(this PolygonGrid grid,
                                         StreamGeometryContext context, PointD offset)
        {
            HashSet <LineI> lines   = new HashSet <LineI>();
            double          epsilon = grid.Element.Length / 4.0;

            PointD[] vertices = grid.Element.Vertices;

            int width = grid.Size.Width, height = grid.Size.Height;

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    PointD element = grid.GridToDisplay(x, y) + offset;

                    // get & remember first element vertex
                    PointD start        = vertices[0] + element;
                    PointD elementStart = start;
                    bool   lineSkipped  = true;

                    for (int i = 1; i <= vertices.Length; i++)
                    {
                        // close figure by returning to first vertex
                        PointD next = (i == vertices.Length ? elementStart : vertices[i] + element);

                        // reduce line to unique hash coordinates
                        LineI line = new LineI(
                            (int)(start.X / epsilon), (int)(start.Y / epsilon),
                            (int)(next.X / epsilon), (int)(next.Y / epsilon));

                        // skip lines that were drawn (in either direction)
                        if (lines.Contains(line) ||
                            lines.Contains(new LineI(line.End, line.Start)))
                        {
                            lineSkipped = true;
                        }
                        else
                        {
                            // restart figure after skipping lines
                            if (lineSkipped)
                            {
                                context.BeginFigure(start.ToWpfPoint(), false, false);
                                lineSkipped = false;
                            }

                            // draw & remember current line
                            context.LineTo(next.ToWpfPoint(), true, true);
                            lines.Add(line);
                        }

                        // prepare for next vertex
                        start = next;
                    }
                }
            }
        }
Ejemplo n.º 4
0
 public bool Equals(FilterResult other)
 {
     if (ReferenceEquals(null, other))
     {
         return(false);
     }
     if (ReferenceEquals(this, other))
     {
         return(true);
     }
     return(Id.Equals(other.Id) &&
            Uid.Equals(other.Uid) &&
            string.Equals(Name, other.Name) &&
            string.Equals(Description, other.Description) &&
            StartUtc.Equals(other.StartUtc) &&
            EndUtc.Equals(other.EndUtc) &&
            OnMachineDesignId == other.OnMachineDesignId &&
            OnMachineDesignName == other.OnMachineDesignName &&
            AssetIDs.ScrambledEquals(other.AssetIDs) &&
            VibeStateOn == other.VibeStateOn &&
            CompactorDataOnly.Equals(other.CompactorDataOnly) &&
            ElevationType == other.ElevationType &&
            PolygonLL.ScrambledEquals(other.PolygonLL) &&
            PolygonGrid.ScrambledEquals(other.PolygonGrid) &&
            ForwardDirection == other.ForwardDirection &&
            (AlignmentFile == null ? other.AlignmentFile == null : AlignmentFile.Equals(other.AlignmentFile)) &&
            StartStation.Equals(other.StartStation) &&
            EndStation.Equals(other.EndStation) &&
            LeftOffset.Equals(other.LeftOffset) &&
            RightOffset.Equals(other.RightOffset) &&
            LayerType.Equals(other.LayerType) &&
            (LayerDesignOrAlignmentFile == null
        ? other.LayerDesignOrAlignmentFile == null
        : LayerDesignOrAlignmentFile.Equals(other.LayerDesignOrAlignmentFile)) &&
            BenchElevation.Equals(other.BenchElevation) &&
            LayerNumber == other.LayerNumber &&
            LayerThickness.Equals(other.LayerThickness) &&
            ContributingMachines.ScrambledEquals(other.ContributingMachines) &&
            SurveyedSurfaceExclusionList.ScrambledEquals(other.SurveyedSurfaceExclusionList) &&
            ExcludedSurveyedSurfaceUids.ScrambledEquals(other.ExcludedSurveyedSurfaceUids) &&
            ReturnEarliest.Equals(other.ReturnEarliest) &&
            GpsAccuracy.Equals(other.GpsAccuracy) &&
            GpsAccuracyIsInclusive.Equals(other.GpsAccuracyIsInclusive) &&
            BladeOnGround.Equals(other.BladeOnGround) &&
            TrackMapping.Equals(other.TrackMapping) &&
            WheelTracking.Equals(other.WheelTracking) &&
            (DesignFile == null ? other.DesignFile == null : DesignFile.Equals(other.DesignFile)) &&
            AutomaticsType == other.AutomaticsType &&
            TemperatureRangeMin.Equals(other.TemperatureRangeMin) &&
            TemperatureRangeMax.Equals(other.TemperatureRangeMax) &&
            PassCountRangeMin.Equals(other.PassCountRangeMin) &&
            PassCountRangeMax.Equals(other.PassCountRangeMax));
 }
Ejemplo n.º 5
0
        /// <summary>
        /// Sets the <see cref="MapGrid"/> property.</summary>
        /// <param name="grid">
        /// The new value for the <see cref="MapGrid"/> property.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="grid"/> is a null reference.</exception>
        /// <remarks><para>
        /// <b>SetMapGrid</b> assigns a copy of the specified <paramref name="grid"/> to the <see
        /// cref="MapGrid"/> property, so that both objects can be changed independently.
        /// </para><para>
        /// <b>SetMapGrid</b> also recreates the <see cref="TileCopyBuffer"/> and <see
        /// cref="TileDrawBuffer"/> bitmaps with the resulting <see cref="TileWidth"/> and <see
        /// cref="TileHeight"/>. Any existing bitmap contents are lost.</para></remarks>

        protected void SetMapGrid(PolygonGrid grid)
        {
            if (grid == null)
            {
                ThrowHelper.ThrowArgumentNullException("grid");
            }

            this._mapGrid = new PolygonGrid(grid);

            // create tile buffers for one grid element
            TileCopyBuffer = CreateTileCopyBuffer(0, 0);
            TileDrawBuffer = CreateTileDrawBuffer(0, 0);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Handles the <see cref="ToggleButton.Checked"/> event for the "Grid Shift" <see
        /// cref="RadioButton"/> controls on the <see cref="StructureTab"/> page.</summary>
        /// <param name="sender">
        /// The <see cref="Object"/> where the event handler is attached.</param>
        /// <param name="args">
        /// A <see cref="RoutedEventArgs"/> object containing event data.</param>
        /// <remarks>
        /// <b>OnShiftChecked</b> changes the <see cref="PolygonGrid.GridShift"/> value of the <see
        /// cref="MapGrid"/> to that indicated by the checked "Grid Shift" radio button, and sets
        /// the <see cref="DataChanged"/> flag if the value has changed.</remarks>

        private void OnShiftChecked(object sender, RoutedEventArgs args)
        {
            args.Handled = true;
            if (this._ignoreEvents)
            {
                return;
            }

            // get new grid shifting
            PolygonGridShift gridShift;

            if (ShiftNoneToggle.IsChecked == true)
            {
                gridShift = PolygonGridShift.None;
            }
            else if (ColumnUpToggle.IsChecked == true)
            {
                gridShift = PolygonGridShift.ColumnUp;
            }
            else if (ColumnDownToggle.IsChecked == true)
            {
                gridShift = PolygonGridShift.ColumnDown;
            }
            else if (RowLeftToggle.IsChecked == true)
            {
                gridShift = PolygonGridShift.RowLeft;
            }
            else if (RowRightToggle.IsChecked == true)
            {
                gridShift = PolygonGridShift.RowRight;
            }
            else
            {
                return;
            }

            // sanity check for element shape
            if (!PolygonGrid.AreCompatible(MapGrid.Element, gridShift))
            {
                return;
            }

            // assign new grid shifting
            if (gridShift != MapGrid.GridShift)
            {
                this._mapGrid.GridShift = gridShift;

                // broadcast data changes
                DataChanged = true;
            }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Draws the specified <see cref="PolygonGrid"/> to the specified <see
        /// cref="StreamGeometry"/>.</summary>
        /// <param name="grid">
        /// The <see cref="PolygonGrid"/> to draw.</param>
        /// <param name="context">
        /// The <see cref="StreamGeometryContext"/> that receives <paramref name="grid"/>.</param>
        /// <param name="isFilled">
        /// <c>true</c> to use the area covered by <paramref name="grid"/> for hit-testing,
        /// rendering, and clipping; otherwise, <c>false</c>.</param>
        /// <param name="offset">
        /// The offset by which to shift <paramref name="grid"/>.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="grid"/> is a null reference.</exception>
        /// <remarks><para>
        /// <b>Draw</b> invokes the <see cref="RegularPolygon"/> overload of <see cref="Draw"/> for
        /// each <see cref="PolygonGrid.Element"/> in the specified <paramref name="grid"/>, shifted
        /// by the corresponding <see cref="PolygonGrid.GridToDisplay"/> result. Each polygonal <see
        /// cref="PolygonGrid.Element"/> is therefore represented by one figure.
        /// </para><para>
        /// All coordinates are also shifted by the specified <paramref name="offset"/>.
        /// </para></remarks>

        public static void Draw(this PolygonGrid grid,
                                StreamGeometryContext context, PointD offset, bool isFilled)
        {
            int width = grid.Size.Width, height = grid.Size.Height;

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    PointD element = grid.GridToDisplay(x, y);
                    grid.Element.Draw(context, element + offset, isFilled);
                }
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Converts the specified <see cref="PolygonGrid"/> to a <see
        /// cref="PathFigureCollection"/>.</summary>
        /// <param name="grid">
        /// The <see cref="PolygonGrid"/> to convert.</param>
        /// <returns>
        /// A frozen <see cref="PathFigureCollection"/> containing one <see cref="PathFigure"/> for
        /// each polygonal <see cref="PolygonGrid.Element"/> in the specified <paramref
        /// name="grid"/>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="grid"/> is a null reference.</exception>
        /// <remarks>
        /// <b>ToFigures</b> returns the results of <see cref="ToFigure"/> for each <see
        /// cref="PolygonGrid.Element"/> in the specified <paramref name="grid"/>, shifted by the
        /// corresponding <see cref="PolygonGrid.GridToDisplay"/> result. Each polygonal <see
        /// cref="PolygonGrid.Element"/> is therefore represented by one <see cref="PathFigure"/>.
        /// </remarks>

        public static PathFigureCollection ToFigures(this PolygonGrid grid)
        {
            int width = grid.Size.Width, height = grid.Size.Height;
            var figures = new PathFigureCollection(width * height);

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    PointD     offset = grid.GridToDisplay(x, y);
                    PathFigure figure = grid.Element.ToFigure(offset);
                    figures.Add(figure);
                }
            }

            figures.Freeze();
            return(figures);
        }
Ejemplo n.º 9
0
        internal void UpdateGrid(Size layoutSize)
        {
            Grid      = new PolygonGrid(StandardPolygon, GridShift);
            Grid.Size = _gridSize;

            // check for available space when using standard size
            double ratioX = layoutSize.Width / Grid.DisplayBounds.Width;
            double ratioY = layoutSize.Height / Grid.DisplayBounds.Height;

            // increase grid size accordingly if possible
            Grid.Size = new SizeI(
                (int)(_gridSize.Width * ratioX) - 1,
                (int)(_gridSize.Height * ratioY) - 1);

            // show resulting actual grid size
            ColumnsBox.Text = Grid.Size.Width.ToString();
            RowsBox.Text    = Grid.Size.Height.ToString();
        }
Ejemplo n.º 10
0
        public bool SetVertexNeighbors(bool value)
        {
            PolygonGrid grid = _graph as PolygonGrid;

            if (grid == null)
            {
                return(false);
            }

            RegularPolygon element = grid.Element;

            if (element.Sides != 4 || element.VertexNeighbors == value)
            {
                return(false);
            }

            grid.Element = new RegularPolygon(element.Length, 4, element.Orientation, value);
            return(true);
        }
Ejemplo n.º 11
0
        private void DrawDecoration(DrawingContext dc)
        {
            if (_showElement == PolygonGrid.InvalidLocation)
            {
                return;
            }

            // always draw current element
            DrawElement(dc, _showElement, Brushes.Red, -1);
            PolygonGrid grid = PolygonGridTest.Instance.Grid;

            // show grid distances if desired
            if (_showDistances)
            {
                for (int x = 0; x < grid.Size.Width; x++)
                {
                    for (int y = 0; y < grid.Size.Height; y++)
                    {
                        PointI target = new PointI(x, y);
                        if (target != _showElement)
                        {
                            int distance = grid.GetStepDistance(_showElement, target);

                            // use different brushes to highlight distances
                            int index = (distance - 1) % _neighborBrushes.Length;
                            DrawElement(dc, target, _neighborBrushes[index], distance);
                        }
                    }
                }
            }

            // highlight neighbors if desired
            if (_showNeighbors > 0)
            {
                IList <PointI> neighbors = grid.GetNeighbors(_showElement, _showNeighbors);
                foreach (PointI neighbor in neighbors)
                {
                    DrawElement(dc, neighbor, Brushes.Yellow, -1);
                }
            }
        }
Ejemplo n.º 12
0
        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            // reserve border around centered polygon grid
            PolygonGrid grid = _manager.Graph as PolygonGrid;

            if (grid != null)
            {
                Canvas output       = _manager.Dialog.OutputBox;
                double borderWidth  = (output.Width - grid.DisplayBounds.Width) / 2;
                double borderHeight = (output.Height - grid.DisplayBounds.Height) / 2;
                dc.PushTransform(new TranslateTransform(borderWidth, borderHeight));
            }
            DrawNodes(dc);

            // draw clipped Delaunay edges to indicate connections
            Subdivision division = _manager.Graph as Subdivision;

            if (division != null)
            {
                foreach (SubdivisionEdge edge in division.Edges.Values)
                {
                    if (edge.Key > edge.Twin.Key)
                    {
                        continue;
                    }
                    PointD p = edge.Origin.Move(edge.Destination, 10);
                    PointD q = edge.Destination.Move(edge.Origin, 10);
                    dc.DrawLine(_edgePen, p.ToWpfPoint(), q.ToWpfPoint());
                }
            }

            if (grid != null)
            {
                dc.Pop();
            }
        }
Ejemplo n.º 13
0
        public void PolygonGrid()
        {
            var polygon = new RegularPolygon(5, 4, PolygonOrientation.OnEdge);
            var grid    = new PolygonGrid(polygon)
            {
                Size = new SizeI(10, 10)
            };
            Subdivision division = grid.ToSubdivision(PointD.Empty).Source;

            CheckSearch(division);

            grid.Element = new RegularPolygon(5, 4, PolygonOrientation.OnVertex);
            division     = grid.ToSubdivision(PointD.Empty).Source;
            CheckSearch(division);

            grid.Element = new RegularPolygon(5, 6, PolygonOrientation.OnEdge);
            division     = grid.ToSubdivision(PointD.Empty).Source;
            CheckSearch(division);

            grid.Element = new RegularPolygon(5, 6, PolygonOrientation.OnVertex);
            division     = grid.ToSubdivision(PointD.Empty).Source;
            CheckSearch(division);
        }
Ejemplo n.º 14
0
        public void PolygonGrid()
        {
            var grid = new PolygonGrid(new RegularPolygon(10, 4, PolygonOrientation.OnEdge));

            grid.Size = new SizeI(6, 4);
            var division = grid.ToSubdivision(PointD.Empty);

            CheckGridDivision(division);

            grid      = new PolygonGrid(new RegularPolygon(10, 4, PolygonOrientation.OnVertex));
            grid.Size = new SizeI(4, 6);
            division  = grid.ToSubdivision(PointD.Empty);
            CheckGridDivision(division);

            grid      = new PolygonGrid(new RegularPolygon(10, 6, PolygonOrientation.OnEdge));
            grid.Size = new SizeI(6, 4);
            division  = grid.ToSubdivision(PointD.Empty);
            CheckGridDivision(division);

            grid      = new PolygonGrid(new RegularPolygon(10, 6, PolygonOrientation.OnVertex));
            grid.Size = new SizeI(4, 6);
            division  = grid.ToSubdivision(PointD.Empty);
            CheckGridDivision(division);
        }
Ejemplo n.º 15
0
 public void SetUp()
 {
     grid     = new PolygonGrid(element);
     readOnly = grid.AsReadOnly();
 }
Ejemplo n.º 16
0
        private void SubdivisionSearchTest(bool random)
        {
            Stopwatch timer     = new Stopwatch();
            var       testCases = _subdivSearchTestCases;

            Output(String.Format("{0,6}", " "));
            foreach (TestCase test in testCases)
            {
                Output(String.Format("{0,12}", test.Name));
            }
            Output("\n");

            const int outerLoop = 100, innerLoop = 200;
            const int iterations = outerLoop * innerLoop;

            PointD[] query = new PointD[innerLoop];

            PolygonGrid grid = null;
            int         sizeMin, sizeMax, sizeStep;

            if (random)
            {
                sizeMin = 100; sizeMax = 1200; sizeStep = 100;
            }
            else
            {
                sizeMin = 6; sizeMax = 30; sizeStep = 2;
                RegularPolygon polygon = new RegularPolygon(10, 4, PolygonOrientation.OnEdge);
                grid = new PolygonGrid(polygon);
            }

            for (int size = sizeMin; size <= sizeMax; size += sizeStep)
            {
                Subdivision division;
                if (random)
                {
                    // create subdivision from random lines (few faces)
                    division = CreateSubdivision(size, 1e-10);
                }
                else
                {
                    // create subdivision from grid of diamonds (many faces)
                    grid.Element = new RegularPolygon(900 / size, 4, PolygonOrientation.OnEdge);
                    grid.Size    = new SizeI(size, size);
                    division     = grid.ToSubdivision(PointD.Empty).Source;
                }
                var ordered    = new SubdivisionSearch(division, true);
                var randomized = new SubdivisionSearch(division, false);

                // test cases: BruteForce, Ordered, Randomized
                testCases[0].FindSubdivision = (q) => division.Find(q, division.Epsilon);
                testCases[1].FindSubdivision = (q) => ordered.Find(q);
                testCases[2].FindSubdivision = (q) => randomized.Find(q);

                // trigger JIT compilation and reset ticks
                foreach (TestCase test in testCases)
                {
                    test.FindSubdivision(PointD.Empty);
                    test.Ticks = 0;
                }

                for (int j = 0; j < outerLoop; j++)
                {
                    for (int k = 0; k < query.Length; k++)
                    {
                        query[k] = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000);
                    }

                    foreach (TestCase test in testCases)
                    {
                        timer.Restart();
                        for (int k = 0; k < query.Length; k++)
                        {
                            test.FindSubdivision(query[k]);
                        }

                        timer.Stop();
                        test.Ticks += timer.ElapsedTicks;
                    }
                }

                Output(String.Format("{0,6:N0}", division.Edges.Count / 2));
                foreach (TestCase test in testCases)
                {
                    Output(String.Format("{0,12:N2}", AverageMicrosecs(test.Ticks, iterations)));
                }
                Output("\n");
            }

            Output("\nTimes are µsec averages for subdivisions of the indicated edge count,\n");
            if (random)
            {
                Output("based on random line sets (few faces, completely random edges).\n");
            }
            else
            {
                Output("based on grids of squares (many faces, strictly ordered edges).\n");
            }
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Draws all arrows in the specified collection.</summary>
        /// <param name="arrows">
        /// A <see cref="List{LineI}"/> containing the arrows to draw.</param>
        /// <param name="brush">
        /// The <see cref="Brush"/> with which to draw all <paramref name="arrows"/>.</param>

        private void DrawArrows(List <LineI> arrows, LinearGradientBrush brush)
        {
            Debug.Assert(arrows != null);
            Debug.Assert(brush != null);

            Canvas      canvas  = this._mapView.Control.MapCanvas;
            PolygonGrid mapGrid = this._mapView.MapGrid;

            // draw all arrows defined by map view
            for (int i = 0; i < arrows.Count; i++)
            {
                PointI start = arrows[i].Start;
                PointI end   = arrows[i].End;

                // skip arrows with zero length
                if (start == end)
                {
                    continue;
                }

                // skip invalid map coordinates
                if (!mapGrid.Contains(start) || !mapGrid.Contains(end))
                {
                    continue;
                }

                // create arrow with specified brush
                Polygon arrow = new Polygon();
                arrow.Stroke          = Brushes.White;
                arrow.StrokeThickness = 1.0;
                arrow.Fill            = brush;

                // convert map to display coordinates
                PointD viewStart = this._mapView.SiteToView(start);
                PointD viewEnd   = this._mapView.SiteToView(end);
                LineD  viewArrow = new LineD(viewStart, viewEnd);

                // scale arrow thickness to current polygon size
                double dx     = Math.Max(2.0, this._mapView.MapGrid.Element.OuterRadius * 0.4);
                double length = viewArrow.Length;

                arrow.Points = new PointCollection {
                    new Point(dx * 1.4, 0),
                    new Point(dx * 0.8, dx * 0.8),
                    new Point(length - dx * 1.4, dx * 0.4),
                    new Point(length - dx * 1.6, dx),
                    new Point(length - dx * 0.4, 0),
                    new Point(length - dx * 1.6, -dx),
                    new Point(length - dx * 1.4, -dx * 0.4),
                    new Point(dx * 0.8, -dx * 0.8),
                };

                // rotate into direction of arrow
                arrow.RenderTransform = new RotateTransform(viewArrow.Angle * Angle.RadiansToDegrees);

                // show arrow at display coordinates
                canvas.Children.Add(arrow);
                Canvas.SetLeft(arrow, viewStart.X);
                Canvas.SetTop(arrow, viewStart.Y);
            }
        }