Laden...

[WPF-Control] Graphzeichner

Erstellt von ANSI_code vor 15 Jahren Letzter Beitrag vor 15 Jahren 6.701 Views
ANSI_code Themenstarter:in
467 Beiträge seit 2007
vor 15 Jahren
[WPF-Control] Graphzeichner

Ich arbeite jetzt schon länger an einem Control für WPF, das Funktionen, aber auch graphen allgemein abbilden kann. Es geht um eine Property Points, eine PointCollection (ich habe auch soweit alle Properties als Dependenca Props implementiert), welche relativ zum koordinatenursprung angegeben werden soll, und dann endsprechend vom Programm transformiert wird. Dann werden sie gezeichnet - entweder als winziger Punkt an jeder Position, oder als Polyline. Da gibt es Möglichkeiten (jeder Achse einzeln) andere Maßstäbe zuzuweisen (das Programm unterstützt auch beschriftung der Achsen, die sich auch an die Maßstäbe anpasst, Mit festlegbaren Strichabständen in Absoluten Maßen (natürlich alle Beschriftungen ausschaltbar). Bin sehr froh, wenn ihr mir etwas bei meinen misratenen engl. Kommentaren helft.

Übrigens möchte ich mich an dieser Stelle (auch wenn es nicht dazupasst, in der Hoffnung das hier liest überhaupt jemand) für eine Möglichkeit aussprechen, Text im Beitrag so zu markieren (zb den code), dass er eingeklappt werden kann. Wäre sehr übersichtlich.

Beachtet, ich bin Anfänger mit WPF, und bei allgemein C# bin ich auch noch nicht so gut, obwohl ich das wohl schon etwas länger mache - Bin immer sehr froh, über Verbesserungsvorschläge oder Anmerkungen zu meinem Programierstil.

Noch eine Anmerkung: bei diesem Control empfehle ich es nicht, an einem Template rumzufummeln. Ich habe das Standarttempate dafür verwendet, wobei ich das setzen des Backgrounds gelöscht habe.
Das Ding ist auch kein ContentControl, was bedeuet, dass kein anderer Innhalt eingefügt werden kann. Bei Bildchen im Hintergrund etc. empfehle ich (in dem Fall ImageBrush) auf andere Möglichkeiten zurückzugreifen, obwohl ich keinen großen Sinn in solchen veränderungen sehe.

für WPF 3

aktuell, Verbesserungsvorschläge umgesetzt
22.1.08 ein Paar zum Teil gravierende Fehler entfernt

CODE:


//Component By: ANSI-code
//Version 1.3.1


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfApplication1
{
    
    public enum KindsOfDrawing { Points, Lines, Curve }

    public class FunktionPlotter : Control
    {


        public static DependencyProperty LineBrushProperty = DependencyProperty.Register("LineBrush", typeof(Brush), typeof(FunktionPlotter), new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty XUnitProperty = DependencyProperty.Register("XUnit", typeof(Double), typeof(FunktionPlotter), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, UntitChanged), IsAxisValue0);
        public static DependencyProperty YUnitProperty = DependencyProperty.Register("YUnit", typeof(Double), typeof(FunktionPlotter), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, UntitChanged), IsAxisValue0);
        public static DependencyProperty TickFrequencyXProperty = DependencyProperty.Register("TickFrequencyX", typeof(Double), typeof(FunktionPlotter), new FrameworkPropertyMetadata(10d, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty TickFrequencyYProperty = DependencyProperty.Register("TickFrequencyY", typeof(Double), typeof(FunktionPlotter), new FrameworkPropertyMetadata(10d, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty ShowInscriptionsProperty = DependencyProperty.Register("ShowInscriptions", typeof(bool), typeof(FunktionPlotter), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty ShowTicksProperty = DependencyProperty.Register("ShowTicks", typeof(bool), typeof(FunktionPlotter), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty KindOfDrawingProperty = DependencyProperty.Register("KindOfDrawing", typeof(KindsOfDrawing), typeof(FunktionPlotter), new FrameworkPropertyMetadata(KindsOfDrawing.Points, FrameworkPropertyMetadataOptions.AffectsRender));
        public static DependencyProperty InscriptionSizeProperty = DependencyProperty.Register("InscriptionSize", typeof(double), typeof(FunktionPlotter), new FrameworkPropertyMetadata(6d, FrameworkPropertyMetadataOptions.AffectsRender));

        public static DependencyProperty PointsProperty;

        Polyline PLines = new Polyline();
        private PointCollection RealPoints { get; set; }

        /// <summary>
        /// defines the Brush of the Line
        /// </summary>
        public Brush LineBrush { get { return (Brush)this.GetValue(LineBrushProperty); } set { this.SetValue(LineBrushProperty, value); } }
        //gets or sets the Points relative to the Middle of the Control.
        public PointCollection Points { get { return (PointCollection)this.GetValue(PointsProperty); } set { this.SetValue(PointsProperty, value); } }
        /// <summary>
        /// defines how meny logical units "one" is on the X axis. no 0 allowed
        /// </summary>
        public Double XUnit { get { return (Double)this.GetValue(XUnitProperty); } set { this.SetValue(XUnitProperty, value); } }
        /// <summary>
        /// defines how meny logical units "one" is on the X axis. no 0 allowed
        /// </summary>
        public Double YUnit { get { return (Double)this.GetValue(YUnitProperty); } set { this.SetValue(YUnitProperty, value); } }
        /// <summary>
        /// sets the frequency of the Matking of the X-Axis and also the InscriptionFrequency for the x-axis
        /// </summary>
        public Double TickFrequencyX { get { return (Double)this.GetValue(TickFrequencyXProperty); } set { this.SetValue(TickFrequencyXProperty, value); } }
        /// <summary>
        /// sets the frequency of the Matking of the Y-Axis and also the InscriptionFrequency for the Y-axis
        /// </summary>
        public Double TickFrequencyY { get { return (Double)this.GetValue(TickFrequencyXProperty); } set { this.SetValue(TickFrequencyXProperty, value); } }
        /// <summary>
        /// sets the TickFrequencyX and the TickFrequencyY Properties. returns the TickFrequencyX Property.
        /// </summary>
        public Double TickFrequency { get { return (Double)this.GetValue(TickFrequencyXProperty); } set { this.SetValue(TickFrequencyYProperty, value); this.SetValue(TickFrequencyXProperty, value); } }
        /// <summary>
        /// if true, the Inscriptions are drawn next to the ticks. Also if ShowTicks is false.
        /// </summary>
        public bool ShowInscriptions { get { return (bool)this.GetValue(ShowInscriptionsProperty); } set { this.SetValue(ShowInscriptionsProperty, value); } }
        /// <summary>
        /// if true, the ticks are drawn.
        /// </summary>
        public bool ShowTicks { get { return (bool)this.GetValue(ShowTicksProperty); } set { this.SetValue(ShowTicksProperty, value); } }


        public KindsOfDrawing KindOfDrawing { get { return (KindsOfDrawing)this.GetValue(KindOfDrawingProperty); } set { this.SetValue(KindOfDrawingProperty, value); } }
        public Double InscriptionSize { get { return (double)this.GetValue(InscriptionSizeProperty); } set { this.SetValue(InscriptionSizeProperty, value); } }




        static FunktionPlotter()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FunktionPlotter), new FrameworkPropertyMetadata(typeof(FunktionPlotter)));


            FrameworkPropertyMetadata Meta = new FrameworkPropertyMetadata(new PointCollection(), PropChanged, null);

            PointsProperty=DependencyProperty.Register("Points", typeof(PointCollection), typeof(FunktionPlotter),Meta);
        }




        static bool IsAxisValue0(object o)
        {
            return (Double)o != 0;
        }

        static void PropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is FunktionPlotter)
            {
                ((FunktionPlotter)d).TransformPoints();
            }
        }

        static void UntitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is FunktionPlotter)
                ((FunktionPlotter)d).TransformPoints();
        }

        void TransformPoints()
        {
            PointCollection Ps = this.Points.CloneCurrentValue();

            RotateTransform RT = new RotateTransform(180);
            RT.CenterX = this.ActualWidth / 2;
            RT.CenterY = this.ActualHeight / 2;

            for (int i = 0; i < Ps.Count; i++)
            {
                Point temp = new Point(Ps[i].X, Ps[i].Y * -1);
                Ps[i] = new Point(temp.X / this.XUnit + this.ActualWidth / 2, temp.Y / this.YUnit + this.ActualHeight / 2);
            }
            RealPoints = Ps;
            this.RefreshLine();
        }

            

        
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            RealPoints = new PointCollection();
        }

        protected void RefreshLine()
        {
            PLines.Points = RealPoints;
            PLines.Measure(new Size(this.ActualWidth, this.ActualHeight));
            PLines.Arrange(new Rect(new Size(this.ActualWidth, this.ActualHeight)));
        }


        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            //Draw Background
            drawingContext.DrawRectangle(this.Background, null, new Rect(RenderSize));

            drawingContext.DrawLine(new Pen(LineBrush, 1), new Point(0, ActualHeight / 2), new Point(ActualWidth, ActualHeight / 2));
            drawingContext.DrawLine(new Pen(LineBrush, 1), new Point(ActualWidth / 2, 0), new Point(Width / 2, ActualHeight));

            
            DrawInscriptions(drawingContext);

            if (KindOfDrawing == KindsOfDrawing.Points && RealPoints.Count > 0)
            {
                foreach (Point P in RealPoints)
                {
                    drawingContext.DrawEllipse(LineBrush, new Pen(LineBrush, 0.4), P, 0.4, 0.4);
                }
            }

            if (KindOfDrawing == KindsOfDrawing.Curve && RealPoints.Count > 0)
            {
                PathSegmentCollection c = new PathSegmentCollection();
                for (int i = 3; i < this.RealPoints.Count; i++)
                    c.Add(new BezierSegment(RealPoints[i - 2], RealPoints[i - 1], RealPoints[i], true));
                drawingContext.DrawGeometry(null, new Pen(LineBrush, 0.4),
                    new PathGeometry(new PathFigure[] { new PathFigure(RealPoints[0], c, false) }));
            }

            if (KindOfDrawing == KindsOfDrawing.Lines && RealPoints.Count > 0)
            {
                RefreshLine();
                drawingContext.DrawGeometry(null, new Pen(LineBrush, 0.8),PLines.RenderedGeometry );
            }

        }

        void DrawInscriptions(DrawingContext drawingContext)
        {
            int overUnder = 0;
            int Counter = 0;
            double valToDraw = 0;
            string ValueToDrawAsString = "";

            #region horizontal
            if (TickFrequencyX != 0)
            {
                //left
                overUnder = 0;
                Counter = 1;//1 to avoid overpainting of other inscriptions
                valToDraw = 0;
                //ValueAsString will be reset automatically

                for (double d = ActualWidth/2; d >= 0; d -= TickFrequencyX)
                {
                    if(ShowTicks)
                    drawingContext.DrawLine(new Pen(Brushes.Black, 2), new Point(d, this.ActualHeight / 2 - 2), new Point(d, this.ActualHeight / 2 + 2));

                    if (ShowInscriptions)
                    {

                        valToDraw = Math.Round((d - ActualHeight / 2) * XUnit, 2);
                        ValueToDrawAsString = valToDraw == 0 ? "" : valToDraw.ToString();

                        overUnder = Counter % 2 == 0 ? (int)(InscriptionSize-5) : (int)(-InscriptionSize - 10 - valToDraw.ToString().Length*2);
                        Counter++;

                        drawingContext.DrawText(new FormattedText(ValueToDrawAsString, new System.Globalization.CultureInfo("DE-de"), FlowDirection.LeftToRight, new Typeface("Areal"), InscriptionSize, Brushes.Black), new Point(d - 5, this.ActualHeight / 2 + overUnder));
                    }
                }
                //right
                overUnder = 0;
                Counter = 0;
                valToDraw = 0;
                ValueToDrawAsString = "";
                for (double d = ActualWidth/2; d <= ActualWidth; d += TickFrequencyX)
                {
                    if (ShowTicks)
                    drawingContext.DrawLine(new Pen(Brushes.Black, 2), new Point(d, this.ActualHeight / 2 - 2), new Point(d, this.ActualHeight / 2 + 2));

                    if (ShowInscriptions)
                    {

                        valToDraw = Math.Round((d-ActualHeight/2)*XUnit, 2);
                        ValueToDrawAsString = valToDraw == 0 ? "" : valToDraw.ToString();

                        overUnder = Counter % 2 == 0 ? (int)(InscriptionSize-5) : (int)(-InscriptionSize - 10 - valToDraw.ToString().Length*2);
                        Counter++;

                        drawingContext.DrawText(new FormattedText(ValueToDrawAsString, new System.Globalization.CultureInfo("DE-de"), FlowDirection.LeftToRight, new Typeface("Areal"), InscriptionSize, Brushes.Black), new Point(d - 5, this.ActualHeight / 2 + overUnder));
                    }
                }
            }
            #endregion
            #region vertical
            if (TickFrequencyY != 0)
            {
                //up
                overUnder = 0;
                Counter = 0;
                valToDraw = 0;
                for (double d = ActualHeight/2; d >=0; d -= TickFrequencyY)
                {
                    drawingContext.DrawLine(new Pen(Brushes.Black, 2), new Point(this.Width / 2 - 2, d), new Point(this.ActualWidth / 2 + 2, d));
                    if (ShowInscriptions)
                    {
                        valToDraw = Math.Round((d - ActualWidth / 2) * YUnit, 2)*-1;
                        ValueToDrawAsString = valToDraw == 0 ? "" : valToDraw.ToString();

                        overUnder = Counter % 2 == 0 ? (int)(InscriptionSize) : (int)(-InscriptionSize - 10 - valToDraw.ToString().Length*2.5);
                        Counter++;

                        drawingContext.DrawText(new FormattedText(ValueToDrawAsString, new System.Globalization.CultureInfo("DE-de"), FlowDirection.LeftToRight, new Typeface("Areal"), InscriptionSize, Brushes.Black), new Point(this.ActualWidth / 2 + overUnder, d - 5));

                    }
                }
                //down
                overUnder = 0;
                Counter = 0;
                valToDraw = 0;
                for (double d = ActualHeight/2; d <= this.ActualHeight; d += TickFrequencyY)
                {
                    drawingContext.DrawLine(new Pen(Brushes.Black, 2), new Point(this.Width / 2 - 2, d), new Point(this.ActualWidth / 2 + 2, d));
                    if (ShowInscriptions)
                    {
                        valToDraw = Math.Round((d - ActualWidth / 2) * YUnit, 2)*-1;
                        ValueToDrawAsString = valToDraw == 0 ? "" : valToDraw.ToString();

                        overUnder = Counter % 2 == 0 ? (int)(InscriptionSize) : (int)(-InscriptionSize - 10 - valToDraw.ToString().Length*3);
                        Counter++;

                        drawingContext.DrawText(new FormattedText(ValueToDrawAsString, new System.Globalization.CultureInfo("DE-de"), FlowDirection.LeftToRight, new Typeface("Areal"), InscriptionSize, Brushes.Black), new Point(this.ActualWidth / 2 + overUnder, d - 5));

                    }
                }
            }
            #endregion
        }
        /// <summary>
        /// calculates the Points from a Funktion
        /// </summary>
        /// <param name="Funcion">Sets the Funktion, which calculates the y value from the X value</param>
        /// <param name="StartVal">sets the x-value to start with.</param>
        /// <param name="EndVal">sets the x-value to end with.</param>
        /// <param name="Accuracy">sets the Interval of Points to Caculate</param>
        public void GetPointsFromFunc(Func<double, double> Funcion, double StartVal, double EndVal, double Interval)
        {
            PointCollection PC = new PointCollection();
            if (Interval == 0)
                throw new InvalidOperationException("The interval must not be 0");

            for (double i = StartVal; i <= EndVal; i += Interval)
            {
                PC.Add(new Point(i, Funcion(i)));
            }
            Points=PC;
        }
    }
}

Schlagwörter: Control Graphen zeichnen

K
205 Beiträge seit 2008
vor 15 Jahren

Hallo,

kann man das auch irgendwo live zum Schnelltest sehen? (download als exe oder so?)

danke und gruß
kk3003

1.985 Beiträge seit 2004
vor 15 Jahren

Hallo ANSI_code,

Screenshots sind auch immer gut, damit sich die Leute einen ersten Eindruck machen können, ob sie es sich überhaupt weiter angucken möchte oder nicht.

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

946 Beiträge seit 2008
vor 15 Jahren

Hallo Ansi_code

Deine "Lines"-Einstellung funktioniert nicht besonders gut bei Potenzen (siehe Screenshot).

Ich habe zusätzlich noch eine "Kurfen"-Einstellung gemacht

if (KindOfDrawing == KindsOfDrawing.Curve && RealPoints.Count > 0)
{
    PathSegmentCollection c = new PathSegmentCollection();
    for (int i = 3; i < this.RealPoints.Count; i++)
        c.Add(new BezierSegment(RealPoints[i - 2], RealPoints[i - 1], RealPoints[i], true));
    drawingContext.DrawGeometry(null, new Pen(LineBrush, 0.4),
        new PathGeometry(new PathFigure[] { new PathFigure(RealPoints[0], c, false) }));
}

Übrigens würde ich gesamthaft auf ActualHeigth bzw. ActualWidth umsteigen, da das Kontrol nur mit gesetzter Grösse funktioniert. Also lässt sich kein Margin anwenden.

See Sharp

PS: Den Screenshot entferne ich, sobald du einen postest. [EDIT]Nachgeholt[/EDIT]

ANSI_code Themenstarter:in
467 Beiträge seit 2007
vor 15 Jahren

Hallo zusammen!
Ich kann es mir gar nicht erklären, dass diese Sache sich so plötzlich einer derart großen Beachtung erfreut. Nach 2 Tagen, eine sehr angenehme Überraschung 😁

Ich werde heute ncoh einen Screenshot posten (der von Seesharp war schon gut, stellt aber nicht den vollen Funktionsummfang dar. Den Tip mit ActualWith etc. habe ich sofort umgesetzt. -peinlich peinlich, dass ich sowas mache 👅

den code mit den Beziersegmenten auch in den Beitrag übernommen - meine Ausrede : damals hatte ich das 2D Kapittel in meinem Buch noch nicht gelesen, und diese ganzen Geometries, Drawings, Segmente usw. hatten mich niedergeschmettert. Wer sich wundert: die Lines-option ist zb. sinnvoll, wenn man einen Aktienkurs darstellen möchte.

Vielen Dank

ANSI_code Themenstarter:in
467 Beiträge seit 2007
vor 15 Jahren

mein PC ist abgeschmiert, also erst heute: (nicht ganz so gut geworden, wie ich wollte. Beachtet: Man kann die häufigkeit der Striche verändern und sie auch ausschalten