![]() |
---|
Hallo zusammen, ich bin neu hier, kein Mathematiker, hoffe aber mit meiner Frage hier richtig zu sein. Es geht um eine Bezierkurve 3. Grades, die einen Viertelkreis von 270 Grad bis 360 Grad darstellt, an den eine horizontale Gerade anschließt. -> Ein Beispiel ist als Excel_1.png angefügt. In mehreren Schritten soll die Gerade nun bis zum Kreismittelpunkt abgesenkt, und der Viertelkreis jeweils am Schnittpunkt der Geraden abgeschnitten werden. Die Stützpunkte X2 und Y3 der Ausgangsform berechne ich mit Radius r * (4/3) * Tan(45 Grad/2) Zum Absenken muss ich scheinbar X4,Y4 mit den Schnittpunkt-Koordinaten der Geraden auf dem Kreisbogen belegen, und X3,Y3 dann mit diesem Punkt gleichsetzen, damit ein eckiger Übergang entsteht. Alle anderen Punkte können unverändert bleiben, bis auf X2, der die Krümmung des restlichen Kreisbogens steuert. Aber hier liege ich leicht daneben, wie in Abbildung -> Excel_2.png ersichtlich, denn die Krümmung deckt sich nicht mit dem Viertelkreis. Die Frage ist im Zusammenhang mit einer Excel-Grafik entstanden, weshalb ich versuche, das Problem ebenfalls über Excel zu lösen, auch wenn es bestimmt geeignetere Software dafür gäbe. Zur Vereinfachung lasse ich die Grafik über ein VBA-Makro zeichnen. Den entsprechenden Modul-Code habe ich nachfolgend angefügt. Die Sub CreateScene zeichnet dann alles. (VBA nutzt X1,Y1 als separaten Startpunkt, und bezeichnet die restlichen Punkte der Kurve mit X1,Y1,X2,Y2,X3,Y3 wodurch sich die Bezeichner gegenüber Standard-Bezier-Bezeichnern verschieben) Falls ich wichtige Angaben nicht genannt haben sollte, bitte ich um Nachsicht. Dann bitte mitteilen, was fehlt... Vielen Dank schon mal! - - - - - - - - - - - - - 'Hier der Code: Option Explicit ' Gegebene Werte für den Viertelkreis Private Const radius As Single = 200 ' Radius des Kreises Private Const centerX As Single = 300 ' X-Koordinate des Kreismittelpunkts Private Const centerY As Single = 300 ' Y-Koordinate des Kreismittelpunkts Private ws As Worksheet Private LineY As Single ' Vertikale Position der Geraden Public Sub CreateScene() Dim shp As Shape Set ws = ThisWorkbook.Sheets(1) ' 1. Arbeitsblatt zum Zeichnen auswählen ' Alte Shapes mit Bezierkurven entfernen For Each shp In ThisWorkbook.Sheets(1).Shapes shp.Delete Next ' Neue Shapes mit Bezierkurven zeichnen For LineY = centerY - radius To centerY Step (centerY - radius) / 4 CreateQuarterCircleAndLine If LineY = centerY - radius Then ' Erstes Shape in Orange ws.Shapes(ws.Shapes.Count).Line.ForeColor.RGB = RGB(255, 60, 0) End If Next End Sub Private Function BerechneKontrollpunktVonLinie(ByVal radius As Double, ByVal yLinie As Double, ByVal yMitte As Double) As Double Dim winkelRad As Double Dim abstand As Double Dim diffY As Double Dim unterWurzel As Double ' Differenz zwischen yLinie und Kreismitte diffY = yLinie - yMitte unterWurzel = radius * radius - diffY * diffY ' Sonderfall: Falls die Linie genau am oberen oder unteren Rand des Kreises verläuft If unterWurzel <= 0 Then winkelRad = 1.57079632679 ' Pi/2 Radianten für 90 Grad (Grenzfall) Else ' Winkel des Schnittpunkts berechnen winkelRad = Atn(diffY / Sqr(unterWurzel)) End If ' Kontrollpunkt-Abstand berechnen abstand = radius * (4 / 3) * Tan(winkelRad / 4) ' Rückgabe des berechneten Abstands BerechneKontrollpunktVonLinie = abstand End Function Private Function HorizontalArcIntersectionPoints( _ ByVal circleCenterX As Double, _ ByVal circleCenterY As Double, _ ByVal circleRadius As Double, _ ByVal LineY As Double) As Variant ' Die Schnittpunkte einer horizontalen Geraden mit einem Kreis berechnen Dim DeltaY As Double Dim DeltaX As Double Dim point1 As Variant Dim point2 As Variant ' Überprüfen, ob die Linie überhaupt den Kreisbogen berührt DeltaY = Abs(circleCenterY - LineY) If DeltaY > circleRadius Then ' Kein Schnittpunkt, da die Linie außerhalb des Kreisbogens liegt HorizontalArcIntersectionPoints = Null Exit Function End If ' Sonst X-Koordinaten der Schnittpunkte berechnen DeltaX = Sqr(circleRadius ^ 2 - DeltaY ^ 2) point1 = Array(circleCenterX - DeltaX, LineY) point2 = Array(circleCenterX + DeltaX, LineY) ' Rückgabe beider Schnittpunkte HorizontalArcIntersectionPoints = Array(point1, point2) End Function Private Sub CreateQuarterCircleAndLine() Const PI = 3.14159265358979 Dim intersectionPointsLeft As Variant ' Punkt auf einem Kreisbogen Dim intersectionPointsRight As Variant ' Punkt auf einem Kreisbogen Dim X1 As Single, Y1 As Single Dim X2 As Single, Y2 As Single Dim X3 As Single, Y3 As Single Dim bezierPoint As Single Dim bezierOffset As Double Dim abstand As Double ' Ein echter Kreis kann durch Bézier-Kurven nicht exakt dargestellt, aber gut angenähert werden. ' Für einen mathematisch perfekten Viertelkreis beträgt der ideale Abstand des Kontrollpunkts ' etwa das 0.55228-fache des Radius ((4/3) * Tan(45 Grad/2) = 0.5522847498 gerundet). ' Bei einem Radius von 18.20544 kann die Distanz d wie folgt berechnet werden: ' d = r * (4/3) * Tan(45 Grad/2) ' d = 18.20544 * 1.33333 * 0.41421 ' d = 18.20544 * 0.55228 ' d = 10.054 bezierPoint = radius * 0.5522847498 ' Bézier-Kontrollpunkt abstand = BerechneKontrollpunktVonLinie(radius, LineY, centerY) ' Schnittpunkt auf Kreisbogen berechnen intersectionPointsLeft = HorizontalArcIntersectionPoints( _ centerX, _ centerY, _ radius, _ LineY) intersectionPointsRight = HorizontalArcIntersectionPoints( _ centerX, _ centerY, _ radius, _ LineY) If IsNull(intersectionPointsLeft) Then Exit Sub If IsNull(intersectionPointsRight) Then Exit Sub ' Berechnung der Kontrollpunkte und Endpunkte X1 = centerX - radius If LineY = centerY - radius Then Y1 = centerY - bezierPoint X2 = centerX - abstand Else bezierOffset = radius * (4 / 3) * Tan(PI / 8) Y1 = centerY - radius + bezierOffset X2 = intersectionPointsLeft(0)(0) End If Y2 = LineY X3 = intersectionPointsLeft(0)(0) Y3 = LineY ' Kurve endet auf Höhe von LineY ' Erstellung der FreeForm mit dem msoSegmentCurve-Segment Dim freeForm As FreeFormBuilder Set freeForm = ws.Shapes.BuildFreeform(msoEditingCorner, centerX - radius, centerY) ' Hinzufügen der Nodes für die Kurve freeForm.AddNodes msoSegmentCurve, msoEditingCorner, _ X1, Y1, _ X2, Y2, _ X3, Y3 ' Hinzufügen des horizontalen Liniensegments mit eckigem Übergang freeForm.AddNodes msoSegmentLine, msoEditingCorner, centerX + radius * 2, LineY ' Konvertieren in eine Shape Dim Shape As Shape Set Shape = freeForm.ConvertToShape End Sub Für alle, die mir helfen möchten (automatisch von OnlineMathe generiert): "Ich möchte die Lösung in Zusammenarbeit mit anderen erstellen." |
Hierzu passend bei OnlineMathe: Schnittpunkte bestimmen Online-Übungen (Übungsaufgaben) bei unterricht.de: Ebene Geometrie - Einführung Geraden im Raum Grundbegriffe der ebenen Geometrie Lagebeziehung Gerade - Ebene (in Normalenform) Lagebeziehung Gerade - Ebene (in Parameterform) Lineare Gleichungssysteme Ebene Geometrie - Einführung Geraden im Raum Grundbegriffe der ebenen Geometrie Lagebeziehung Gerade - Ebene (in Normalenform) Lagebeziehung Gerade - Ebene (in Parameterform) |
![]() |
![]() |
Also wenn den Kreisradius bezeichnet und die Höhe des Schnittpunkts der waagerechte Linie mit dem Kreis, dann erhalte ich für den gesuchten Abstand den hübschen Ausdruck Für als den kompletten Viertelkreis, stellt sich dann damit auch in der Tat der von dir angegebene Wert ein. Auf die Formel kommt man mit der Forderung, dass sich in der Darstellung der Bezierkurve mit der Kreispunkt in der Mitte des Kreisbogens einstellen soll, und die Endpunkte des Kreisbogens sind und und Punkte auf den Kreistangenten in und aus Symmetriegründen im gleichen Abstand von den jeweiligen Endpunkten. Dieser Abstand ist also natürlich auf den Kreistangenten in den Endpunkten des zu nähernden Kreisbogens abzutragen (siehe Skizze). Die Endpunkte haben im gezeigten KS die Koordinaten und die beiden Führungspunkte und . ![]() |
![]() |
@Roman-22 Vielen Dank für die gute Erklärung! Es müssen also auch für Kreissegmente, die kleiner als 90 Grad sind, immer alle 8 Werte der Bezierkurve ermittelt werden. Da lag ich komplett falsch. Hilfreich ist auch der Hinweis, dass wenn h = r, ein kompletter Viertelkreis vorliegt, da meine Prüfung dafür viel zu umständlich war. In der beschriebenen Problemstellung gilt sogar, wenn h >= r, liegt ein kompletter Viertelkreis vor. Das hilft mir weiter! |
![]() |
Ich habe die Formel jetzt als VBA-Code umgesetzt, und erhalte für eine Stichprobe im Überwachungsfenster jetzt die richtigen gerundeten Ergebnisse: Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Kontrollpunkt-Distanz wenn Radius und Höhe Als nächstes passe ich den urspünglichen Code an. Hier schon mal der Excel-VBA-Code mit der Formelumsetzung: Option Explicit Public Sub TestBezierPointDistance() Dim As Double Dim As Double Dim As Double Radius For To Step Höhe stufenweise um 1 erhöhen, und gerundeten Kontrollpunkt-Distanz ausgeben. GetBezierPointDistance(r, Debug.Print "Kontrollpunkt-Distanz wenn Radius " " und Höhe " ": " & Round(d, Next End Sub Private Function GetBezierPointDistance(ByVal radius As Double, ByVal height As Double) As Double ' Berechnet die Distanz eines Bezier-Kontrollpunkts ' Variablen: distanz radius height If radius Or height Then . Sonderfall: Der Radius ist oder die Höhe der Linie entspricht dem Kreis-Mittelpunkt GetBezierPointDistance Else If height radius Then . Sonderfall: Die Linie verläuft genau am oberen Rand des Kreises GetBezierPointDistance radius (Sqr(2) Abstand für einen kompletten Viertelkreis berechnen Else ' Abstand für einen Teilkreis kleiner Grad berechnen GetBezierPointDistance (height radius / (radius - Sqr(radius radius - height height)) ((Sqr(2 radius) / Sqr(radius Sqr(radius radius - height height)) End If End If End Function |
![]() |
So, hier noch der überarbeitete VBA-Code, der in Excel jetzt alles wie gewünscht zeichnet. Es ist wie immer, kaum macht man es richtig, schon geht es. Vielen Dank nochmal an Roman-22! Das Ergebnis wieder als Bild-Anhang: Option Explicit ' Werte für den Viertelkreis Private Const radius As Single Radius des Kreises Private Const centerX As Single X-Koordinate des Kreismittelpunkts Private Const centerY As Single Y-Koordinate des Kreismittelpunkts Private ws As Worksheet Private left As Double Private top As Double Public Sub Test() Dim height As Double Set ws = ThisWorkbook.Sheets(1) ' Zeichne im ersten Tabellenblatt der Arbeitsmappe Dim shp As Shape For Each shp In ws.Shapes shp.Delete Next left = centerX - radius ' Berechnung für den linken Kreisrand top = centerY - radius ' Berechnung für den oberen Kreisrand For height To radius Step DrawPartialCircleAndLine radius, height, left, top If height = radius Then ' Basis Shape in Farbe Orange und in den Hintergrund stellen ws.Shapes(ws.Shapes.Count).Line.ForeColor.RGB = RGB(255, ws.Shapes(ws.Shapes.Count).ZOrder msoSendToBack End If Next Set ws = Nothing ' Arbeitsspeicher freigeben End Sub Private Function GetBezierCurveSegmentPoints(ByVal radius As Double, ByVal height As Double) As Variant ' Berechne die Endpunkte und Führungspunkte einer Bezierkurve 3. Grades Dim point1 As Variant Dim point2 As Variant Dim point3 As Variant Dim point4 As Variant Dim distance As Double ' Berechne die Distanz der Bezier-Kontrollpunkte im Teilkreis If radius Or height Then . Sonderfall: Der Radius ist oder die Gerade liegt auf dem Kreis-Mittelpunkt distance ' Koordinaten von Startpunkt und Endpunkt berechnen point1 = Array(radius, -height) point4 = Array(-Sqr(radius radius - height height), -height) ' Koordinaten der Führungspunkte berechnen point2 = Array(radius, -height) point3 = Array(radius, -height) Else If height radius Then . Sonderfall: Die Linie verläuft genau am oberen Rand des Kreises distance radius (Sqr(2) Abstand für einen kompletten Viertelkreis If height radius Then ' Wenn die Gerade den Teilkreis nicht schneidet, von einem Viertelkreis ausgehen height = radius End If Else ' Abstand für einen Teilkreis kleiner Grad berechnen distance (height radius / (radius - Sqr(radius radius - height height)) ((Sqr(2 radius) / Sqr(radius Sqr(radius radius - height height)) End If ' Kreissegment links oben ' Koordinaten von Startpunkt und Endpunkt berechnen point1 = Array(-radius, point4 = Array(-Sqr(radius radius - height height), -height) ' Koordinaten der Führungspunkte berechnen point2 = Array(-radius, -distance) point3 = Array(-Sqr(radius radius - height height) - distance (height / radius), -height distance Sqr(radius radius - height height) / radius) End If GetBezierCurveSegmentPoints = Array(point1, point2, point3, point4) End Function Private Sub DrawPartialCircleAndLine(ByVal radius As Double, ByVal height As Double, ByVal left As Double, ByVal top As Double) ' Enpunkte und Kontrollpunkte des Bézier-Segments Dim As Single, As Single Dim As Single, As Single Dim As Single, As Single Dim As Single, As Single Dim Bezierpoints As Variant Bezierpoints = GetBezierCurveSegmentPoints(radius, height) If IsEmpty(Bezierpoints) Then Sub ' Berechnung des Startpunkts left Bezierpoints(0)(0) radius top Bezierpoints(0)(1) radius ' Berechnung der Kontrollpunkte für das Bézier-Segment left Bezierpoints(1)(0) radius top Bezierpoints(1)(1) radius left Bezierpoints(2)(0) radius top Bezierpoints(2)(1) radius ' Berechnung des Endpunkts für das Bézier-Segment left Bezierpoints(3)(0) radius top Bezierpoints(3)(1) radius ' Erstellung der FreeForm mit dem msoSegmentCurve-Segment Dim freeForm As FreeFormBuilder Set freeForm = ws.Shapes.BuildFreeform(msoEditingCorner, ' Hinzufügen der Nodes für die Kurve freeForm.AddNodes MsoSegmentType.msoSegmentCurve, MsoEditingType.msoEditingCorner, _ ' Hinzufügen des horizontalen Liniensegments mit eckigem Übergang oben freeForm.AddNodes MsoSegmentType.msoSegmentLine, MsoEditingType.msoEditingCorner, left radius ' Konvertieren in eine Shape Dim Shape As Shape Set Shape = freeForm.ConvertToShape End Sub |
![]() |
Worum geht es dir eigentlich im Endeffekt? Doch nicht nur um die Erstellung dieser Zeichnung, oder? Ich hab nicht viel Erfahrung mit VBA in Excel, aber Kreisbögen sollten sich doch einfach mit "<Shape-Objekt>.DrawCircularArc(...)" erstellen lassen und der dafür nötige Zentriwinkel errechnet sich mit . Warum also die Bezierkurven? |
![]() |
Ja, es geht tatsächlich um ein komplexeres Thema. Durch mein Hobby habe ich umgekehrt zwar Kenntnisse im Zusammenhang mit Excel und VBA, aber eher nicht in mathematischem Formelwerk. Vorhaben: Ich möchte ein Steuerelement zur Anzeige von Kraftstofftank-Füllständen in Excel-VBA erstellen. Wenn also beispielsweise eine Reihe von Kraftstoff-Tanks überwacht werden soll, um zu erkennen, das einer der Liter Tanks davon nur noch zu Prozent mit Diesel befüllt ist. Am einfachsten wäre, dies über die textuelle Ausgabe des Prozentwertes zu lösen, oder wenn es grafisch sein soll, über zwei übereinanderliegende, unterschiedlich gefärbte Rechtecke. Aber einigermaßen professionell wird es eben erst mit etwas Aufwand, durch den auch noch was dazugelernt wird. Ziel ist daher die Erstellung einer grafischen Tankfüllstands-Anzeige mit abgerundeten Ecken ungefähr so, wie im angehängten ersten Beispiel-Bild "Füllstand_1.png". Ausgehend von zwei übereinanderliegenden gerundeten Rechtecken wie im zweiten Beispiel-Bild "Füllstand_2.png". Es gibt in Excel wie von Dir vermutet zwar viele auf Bézierkurven basierende Standard-Formen wie Rechteck, Oval, Viertelkreis, und so weiter, jedoch kann bei diesen über Excel-VBA nur auf den Radius der Ecken, auf die Start- und Endpunkte der Bézierkurven, aber nicht direkt auf deren Führungspunkte zugegriffen werden (anders als in PowerPoint-VBA). In Excel können Führungspunkte von Standardformen tatsächlich nur manuell geändert werden. Wenn Start- oder Endpunkte einer Standard-Form gelöscht werden (beispielsweise um ein Oval zu halbieren), entsteht intern eine hybride Form, bei der die geänderten Punkte Teil einer Freiform werden, während die beibehaltenen Punkte weiterhin den Beschränkungen der ursprünglichen Standard-Form unterliegen. Es ist daher nicht empfehlenswert eine Standardform zu verwenden, wenn diese anschließend programmatisch nicht nur in Höhe und Breite, oder bezüglich des Radius der Ecken, sondern in deren generellen Form geändert werden soll. In solchen Fällen ist es besser, direkt eine Freiform mit den benötigten Eigenschaften zu erstellen, da dann auch die Führungspunkte über VBA kontrolliert werden können. Was hatte meine Frage nun damit zu tun? Die Grundform für das geplante Steuerelement ist ein gerundetes Rechteck, das mit VBA als Standard-Form erzeugt werden kann (oder eben manuell über die Werkzeugleiste). Beispiel-Code: Sub CreateRoundedRectangle Dim ws As Worksheet Dim shp As Shape Set ws = ThisWorkbook.Sheets(1) ' Ausgabe im ersten Excel-Tabellenblatt Set shp = ws.Shapes.AddShape(MsoAutoShapeType.msoShapeRoundedRectangle, End Sub Wenn die Höhe dieser Standard-Form nun verkleinert wird, versucht die Rendering-Engine von Excel die gerundeten Ecken so lange wie möglich aufrecht zu erhalten. Das hat für das genannte Vorhaben jedoch zwei Nachteile: 1. Wenn der Füllstand bei etwa Prozent liegt, soll die obere Rundung wie bei einer Flüssigkeit zunehmend in eine Ecke übergehen. (Das war das Problem hier, wie im dritten Beispiel-Bild "Füllstand_3.png") 2. Wenn der Füllstand bei etwa 2 Prozent liegt, entsteht eine fast schon rechteckige Form in voller Breite. (Das muss ich jetzt noch ableiten) Deshalb erstelle ich zunächst eine Standard-Form wie im obenstehenden Beispiel, nutze deren Bemaßungs-Komfort, aber wandle diese dann in eine Freiform, die wiederum alle denkbaren Konturänderungen erlaubt. |
![]() |
Ah, jetzt wird der Hintergrund klarer. Danke für die ausführlichen Erläuterungen. Dass es solche Unterschiede in VBA zwischen Excel und anderen Programmen aus dem M$ Office Paket we PP gibt, war nir nicht bewusst. Allerdings ist meine Erfahrung da auch sehr gering. Dann noch weiterhin gutes Gelingen! |