Autor |
Beitrag |
AXMD
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 18:26
Hi!
Da ich gerade am Basteln bin und wieder einmal Achsenbeschriftungen zeichnen muss, suche ich nach einer Möglichkeit, die Zahlen unter den Achsen (also die Achsenbeschriftungen) "intelligent" runden zu lassen - wobei nicht nur die Nachkommastellen berücksichtigt werden sollen. Beispiel:
Quelltext 1: 2: 3: 4:
| 19,9999 -> 20 100000,001 -> 100000 0,0000000000001 -> 0 2,49999975 -> 2,5 |
Jemand eine Idee? Bietet das .NET-Framework hier evtl. irgendetwas an?
AXMD
|
|
UGrohne
      

Beiträge: 5502
Erhaltene Danke: 220
Windows 8 , Server 2012
D7 Pro, VS.NET 2012 (C#)
|
Verfasst: Sa 29.07.06 19:56
Nur mal zur Vorgehensweise: Du willst also auf eine bestimmte Anzahl Stellen runden? Sieht jedenfalls so aus
Dann multipliziere die Zahl mit 10^(Anzahl der gewünschten Stellen), runde Sie (unter Delphi mit Round) und teile sie wieder durch die obige Zahl. Nullen am Ende werden ja bei der Ausgabe normalerweise automatisch abgeschnitten, außer man macht es entsprechend (z.B. mittels FloatToStrF in Delphi).
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 20:04
UGrohne hat folgendes geschrieben: | Nur mal zur Vorgehensweise: Du willst also auf eine bestimmte Anzahl Stellen runden? |
Nein  . Die Anzahl der Stellen kenne ich nicht - genau das ist ja das Problem
AXMD
|
|
UGrohne
      

Beiträge: 5502
Erhaltene Danke: 220
Windows 8 , Server 2012
D7 Pro, VS.NET 2012 (C#)
|
Verfasst: Sa 29.07.06 20:08
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 20:15
UGrohne hat folgendes geschrieben: | Nenne mal einen Sonderfall, wo meine Vorgehensweise nicht funktionieren würde. Und eben, was noch berücksichtig werden soll. Das ist aus Deinen Beispielen nämlich nicht ersichtlich. |
Es kann vorkommen, dass eine Zahl um so viel von einem gerundeten Wert abweicht, dass sie gerade noch als "nicht gerundet" betrachtet wird. Sowas wie z.B. 1,5000000000001 ö.ä. - eine Achse, die so beschriftet ist, ist absolut unbrauchbar - da sollte einfach nur 1,5 stehen - eventuell auch abhängig vom aktuellen Zoom. Wenn ich mein Koordinatensystem von (X-Achse) 1,49999999999999 bis 1,500000000000003 betrachte, sollte 1,5000000000001 schon voll ausgeschrieben werden, aber nicht, wenn ich mein Koordinatensystem von -60 bis +20 betrachte und eine der Beschriftungen eben bei 1,5000000000001 wäre  . Das wäre das "intelligente" daran
AXMD
|
|
Holgerwa
      
Beiträge: 325
WIN XP Pro, Vista Business
Delphi 7 Pro, BDS 2006 Pro
|
Verfasst: Sa 29.07.06 20:20
Hallo,
nur mal ins Unreine geschrieben, könntest Du doch zuerst den Bereich Deiner Achse (max - min) feststellen und mit Hilfe der Anzahl der Beschriftungspunkte festlegen, wie viele Nachkommastellen sinnvoll sind. Danach rundest Du entsprechend.
Holger
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 20:20
Ja, entsprechende Funktionen sind dazu schon implementiert. Die Frage ist eben nur, was "sinnvoll" ist
AXMD
|
|
Holgerwa
      
Beiträge: 325
WIN XP Pro, Vista Business
Delphi 7 Pro, BDS 2006 Pro
|
Verfasst: Sa 29.07.06 20:23
Hallo,
bei einer Achsenbeschriftung ist eine sinnvolle Anzahl Nachkommastellen doch dann erreicht, wenn es keine gleichen Werte bei benachbarten Beschriftungen gibt. Ist das der Fall, dann muß die Anzahl erhöht werden.
Oder sehe ich das zu einfach?
Holger
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 20:26
Ich bräuchte mehr die Anzahl Nachkommastellen, auf die gerundet werden muss, wobei zusätzlich (wie oben erwähnt) nicht nur die Nachkommastellen berücksichtigt werden sollen.
AXMD
|
|
Jetstream
      
Beiträge: 222
|
Verfasst: Sa 29.07.06 20:29
Also willst du die Anzahl der gerundeten Stellen von der Größe des ausgewählten Intervalls abhängig machen.
Ich versuch mal Pseudo-Code:
Delphi-Quelltext 1: 2: 3: 4:
| function myRound(zahl,intervall:Real):Real; begin result := intervall * round( 10 * zahl / intervall ) / 10; end; |
Probiers mal aus, müsste jeweils auf eine Nachkommastelle weiter Runden als dein Intervall groß ist.
myRound(5.00000001, 10) = 5
myRound(234567, 100000) = 230000
myRound(0.00023, 0.001) = 0.0002
Wenn du mehr Nachkommastellen haben willst, musst du Nullen an die beiden Zehnen dranhängen.
Zuletzt bearbeitet von Jetstream am Sa 29.07.06 20:32, insgesamt 1-mal bearbeitet
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 20:30
Werd ich mir mal ansehn, danke
AXMD
|
|
Holgerwa
      
Beiträge: 325
WIN XP Pro, Vista Business
Delphi 7 Pro, BDS 2006 Pro
|
Verfasst: Sa 29.07.06 20:33
Hallo,
wenn Du z.B. von 1.49 bis 1.51 beschriften mußt, und 10 Beschriftungen möchtest, dann würde ich so vorgehen:
Abstand = 1.51 - 1.49 = 0.02
Angefangen mit z.B. 2 Nachkommastellen, bekommst Du:
1. Punkt = 1.49
2. Punkt = 1.49 + 0.002 = 1.492, gerundet auf 2 NK also wieder 1.49
Daraus schließt Du, daß 2 NK zu wenig sind.
Nächster Schritt: 3 NK
1. Punkt = 1.490
2. Punkt = 1.490 + 0.002 = 1.492 -> funktioniert.
So hatte ich das gedacht, ein "herantasten" an die minimal notwendige NK Anzahl.
Holger
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Sa 29.07.06 23:05
Ich denk mal, er meint sowas:
0,123456789 --> Nicht runden
0,12345678901 --> Runden auf 0,123456789
2,499975 --> 2,5
2,49994 --> 2,49994
Also in dem Sinne eine Art "intelligenz", dass die Funktion automatisch erkennt, wann eine Rundung des Wertes sinnvoll ist.
1200 Byte --> 1,2kB
1024 Byte --> nicht 1,024 Byte, sondern 1KB ...
Aber ich denk, wir sollten hier mal noch ein par Informationen zum Genauen Verwendungshintergrund anfordern 
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: Sa 29.07.06 23:13
Wie bereits gesagt: Achsenbeschriftung (Dezimalwerte). Es sieht einfach unschön aus, wenn man da zu lesen bekommt: "1.00000001 2.00000001 3.00000002" nur weil durch irgendwelche Rechenoperationen hinten nicht mehr alle Kommastellen ganz exakt sind. Ich wünsche mir eine Achsenbeschriftung wie "1 2 3", aber zB auch eine Achsenbeschriftung wie "1.42258 1.84906 ..." - die Kommastellen sollten so weit angezeigt werden, wie sie "sinnvoll" sind. Noch ein Beispiel: 1.4225800001 sollte wie im vorigen Beispiel einfach als 1.42258 angezeigt werden. Ich hoffe, ihr versteht, was ich meine ^^
AXMD
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: So 30.07.06 00:20
Damit man "schöne" Achsenbeschriftungen bekommt würde ich nur Zahlen aus der 10er, 2er bzw. 5er Reihe akzeptieren (in meinem Plotter ist das jedenfalls so):
Beispiele:
2er: -1.0 -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8 1.0
5er: -1, -0.5, 0, 0.5, 1.0
5er: -1.005, -1.000, -9.995, -9.990
10er: -3, -2, -1, 0, 1, 2, 3
Hier der entsprechende Source:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
| var min, max, idealinterval, fittedinterval_10, fittedinterval_5, fittedinterval_2, x, fittedinterval: Extended; idealcount, fittedcount_10, fittedcount_5, fittedcount_2, fittedcount: Integer; begin min:=random*100-50; max:=min+random*random*100; idealcount:=10; idealinterval:=(max-min)/idealcount; x := ln(idealinterval)/ln(10); if floor(x) < -ln(2/1.1*idealcount/(max-min))/ln(10) then fittedinterval_10:=power(10,ceil(x)) else fittedinterval_10:=power(10,floor(x)); fittedcount_10:=trunc((max-min)/fittedinterval_10);
fittedinterval := fittedinterval_10; fittedcount := fittedcount_10;
if fittedcount_10<idealcount then begin fittedinterval_5 := fittedinterval_10/2; fittedinterval_2 := fittedinterval_10/5; end else begin fittedinterval_5 := fittedinterval_10*5; fittedinterval_2 := fittedinterval_10*2; end; fittedcount_2 := trunc((max-min)/fittedinterval_2); fittedcount_5 := trunc((max-min)/fittedinterval_5);
if abs(fittedcount_2-idealcount)<abs(fittedcount-idealcount) then begin fittedcount := fittedcount_2; fittedinterval := fittedinterval_2; end; if abs(fittedcount_5-idealcount)<abs(fittedcount-idealcount) then begin fittedcount := fittedcount_5; fittedinterval := fittedinterval_5; end;
memo1.lines.add(format('[%s,%s] => ticks:[%s,%s] +%s (%d)',[floattostr(min),floattostr(max),floattostr(ceil(min/fittedinterval)*fittedinterval),floattostr(floor(max/fittedinterval)*fittedinterval),floattostr(fittedinterval),fittedcount])); end; |
Beispiele 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| min,max ticks von,bis tick intervall anzahl tickintervalle [-50.00,-47.30] => ticks:[-49.8,-47.4] 0.20 13 [-29.74,-11.41] => ticks:[-28,-12] 2.00 9 [-18.13,-12.11] => ticks:[-18,-12.5] 0.50 12 [-7.43,-3.54] => ticks:[-7,-4] 0.50 7 [-42.94,-37.92] => ticks:[-42.5,-38] 0.50 10 [-20.67,13.08] => ticks:[-20,10] 5.00 6 [27.47,50.35] => ticks:[28,50] 2.00 11 [34.42,56.43] => ticks:[36,56] 2.00 11 [-33.74,-18.38] => ticks:[-32,-20] 2.00 7 [-25.33,-2.30] => ticks:[-24,-4] 2.00 11 [-1.82,11.22] => ticks:[-1,11] 1.00 13 [-21.27,54.19] => ticks:[-20,50] 10.00 7 [-0.75,72.71] => ticks:[0,70] 10.00 7 |
Edit: Kommentare im Code
Zuletzt bearbeitet von delfiphan am Mi 02.08.06 14:36, insgesamt 3-mal bearbeitet
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: So 30.07.06 00:40
@delphifan: sieht sehr gut aus - an eine variable Tickanzahl habe ich noch gar nicht gedacht. Ich werde mir das mal ansehn - momentan ist die Anzahl der Ticks vordefiniert (konstant) - hast du dafür zufällig auch eine Lösung?
AXMD
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: So 30.07.06 02:03
Das Problem mit Ticks an willkürlichen float-Positionen ist, dass du keine genaue Angaben machen kannst. Sobald du nämlich rundest ist die Beschriftung genau genommen am falschen Ort. Beim oberen Algorithmus hast du das Rundeproblem nicht, denn in der Beschriftung kommen nur "schöne" Zahlen vor.
Noch eine kurze Beschreibung zu oben:
Man kann beim Algorithmus oben die gewünschte Anzahl Ticks vorschlagen (Edit: Eigentlich sind es Anzahl Intervalle zwischen den Ticks, nicht Anzahl Ticks). Der Algorithmus findet dann die Lösung, bei der die Beschriftung aus schönen Zahlen bestehen und die Anzahl Ticks möglichst der gewünschten Anzahl entspricht.
Hier noch meine Überlegungen, wenn du die Anzahl der Ticks fest vorgeben willst:
Das Intervall von einem Tick zum anderen beträgt:
interval = (max-min)/intervalcount
Der Zehnerlogarithmus daraus gibt dir an, wieviel Stellen sich von einem Tick zum anderen ändern. Ist bspw. interval = 0.01, so beträgt log10(interval) = -2. Das heisst die Zahl ändert sich bis 2 Stellen nach dem Komma. Du könntest in dem Fall also bis auf 2 Stellen runden. Anderes Beispiel: Ist interval = 0.03, erhält man log10(0.03) = -1.5. Sinnvollerweise rundet man immer ab und erhält -2. Das heisst auch hier würde man auf 2 Stellen runden.
Allgemein wäre es deswegen meiner Meinung nach sinnvoll, auf -floor(log10(interval))=-floor(ln(interval)/ln(10)) Stellen zu runden.
Als Code:
Delphi-Quelltext 1: 2:
| x=power(10,floor(log(interval)/log(10))) gerundetezahl = round(zahl/x)*x |
Mit diesem Algorithmus wird aber "23" in gewissen Fällen nach "20" abgerundet (könnte man mit einer if-Abfrage verhindern: Nur runden, wenn x<1). Wie du diese Fälle genau haben willst hast du uns nicht verraten.
|
|
AXMD 
      
Beiträge: 4006
Erhaltene Danke: 7
Windows 10 64 bit
C# (Visual Studio 2019 Express)
|
Verfasst: So 30.07.06 10:05
delfiphan hat folgendes geschrieben: | Mit diesem Algorithmus wird aber "23" in gewissen Fällen nach "20" abgerundet (könnte man mit einer if-Abfrage verhindern: Nur runden, wenn x<1). Wie du diese Fälle genau haben willst hast du uns nicht verraten. |
Bei diesen Fällen bin ich mir selbst noch nicht 100% sicher. Wenn ich mir das recht überlege, ist eine variable Tickanzahl vielleicht besser. Wie machen das denn die "großen" wie Matlab, Mathematica...?
AXMD
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: So 30.07.06 12:54
Ich weiss nicht, wie das 'die Großen' machen, ist mir auch egal, dann ich mache es immer so:
1. Interval normalisieren
2. Aus einer Liste vordefinierter Tics den Besten raussuchen.
3. Normalisiertes Interval den Tics entsprechend anpassen
4. Interval zurückrechnen
Hier der hingerotzte Code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| Procedure GetAxis(aMin, aMax: Extended; aTics: Integer; Var aNewMin, aNewMax, aNewTics: Extended); Const Tics: Array[0..8] Of Extended = (0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5, 1); Var nMult, nMin, nMax, nDiff, nTics: Extended; i, n, j, m: Integer;
Begin Assert(aMax > aMin, ' Lower bound > upper bound'); nMin := aMin; nMax := aMax; If nMax < 0 Then nMult := -1 Else nMult := 1; While abs(nMax / nMult) < 1 Do nMult := nMult / 10; While abs(nMax / nMult) > 1 Do nMult := nMult * 10;
nMin := nMin / nMult; nMax := nMax / nMult; nDiff := nMax - nMin;
m := 9999; For i := low(Tics) To High(Tics) Do Begin n := Trunc(nDiff / tics[i]); If abs(n - aTics) < m Then Begin m := abs(n - aTics); j := i; End End; nTics := Tics[j]; aNewMin := (Trunc(nMin / nTics) * nTics) * nMult; aNewMax := (Trunc(0.9999 + nMax / nTics) * nTics) * nMult; aNewTics := nTics * nMult; End; |
aMin und aMax sind die Min/Max-Were, aTics die gewünschte Anzahl von Tics. Rückgabe sind die angepassten Min/Max-Werte sowie der Ticabstand.
Beispiel: GetAxis (2.3, 11.2, 6, NewMin, NewMax, NewTics)
Liefert NewMin=2.0, NewMax = 12 und NewTics = 2.0
Edit:
Der Teil:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| ... If nMax < 0 Then nMult := -1 Else nMult := 1; While abs(nMax / nMult) < 1 Do nMult := nMult / 10; While abs(nMax / nMult) > 1 Do nMult := nMult * 10;
nMin := nMin / nMult; nMax := nMax / nMult; ... |
lässt sich bestimmt durch Log-Berechnungen einfacher gestalten ..
_________________ Na denn, dann. Bis dann, denn.
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: So 30.07.06 13:46
Mathematica hab ich grad nicht zur Hand. Matlab macht auch nur 10er, 5er und 2er-Schritte (*10^a). Von dort hab ich ursprünglich die Idee übernommen.
Dort wird (bei der Standardeinstellung) der eingegebene Bereich noch soweit vergrössert, bis die obere/untere/rechte/linke Kante genau auf einem Tick liegt. Das gefällt mir jedoch persönlich weniger... (Das heisst wenn man bspw. den Sinus von 0 bis 2pi plottet geht der Plot nicht von 0 bis 2*pi (6.28...) sondern bis 7)
|
|
|