Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
In einem meiner älteren Programme verwende ich eine automatische Einteilung einer Skala für die grafische Darstellung von Daten. Solange die Schrittweite auf der Skala eine ganze Zahl ist hat das auch halbwegs brauchbar funktioniert. Aber jetzt möchte ich auch Schrittweiten kleiner als 1 haben. Im Prinzip funktioniert das auch (ansatzweise).
Allerdings bekomme ich es nicht hin, Teilungen in 1/4 oder 1/8 Schritten zu realisieren. Mein Algorithmus erzeugt nur 0.5 oder 0.2 -er Schritte. Das Problem liegt in der Erkennung der benötigten Nachkommastellen. Die Idee dahinter ist, das das Darstellungsprogramm selbständig eine sinnvolle Einteilung finden soll, die sich idealerweise an der Pixellänge der Skala und der Schriftgröße orientiert (was noch nicht realisiert ist). Zur Demonstration hier mal der Code eines einfachen Testprogramms, als Diskussionsgrundlage:
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | /* autoscale.c (demo version)
**
** gcc -Wall -lm -o autoscale autoscale.c
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#define STRATEGIE_1
double round( double );
int num_steps = 8; /* Ausgangswert für die Skalenunterteilung (Empfehlung)
** sollte der (Pixel-)Länge der Skala angepasst werden
*/
double scale( double min, double max, int steps_wanted, int * l, int * r )
{
double step_width;
double scale_unit;
int frac_cnt = 0;
int n_steps = steps_wanted;
double lft, rgt;
double factor = 1.0;
int toggle = 1;
step_width = (max - min) / n_steps;
#ifdef STRATEGIE_1
while( step_width < 1.0 ) {
if( toggle ) {
step_width *= 2.0;
factor *= 2.0;
frac_cnt++;
toggle = 0;
} else {
step_width *= 5.0;
factor *= 5.0;
toggle = 1;
}
rgt = modf( step_width, &lft );
printf( ">> %6.4f -> %2.0f & %5.4f\n", step_width, lft, rgt );
}
#endif
#ifdef STRATEGIE_2
/* verworfen wg. geht nicht */
#endif
scale_unit = pow( 10, floor( log10( step_width ) ) );
/* neue Schrittweite berechnen */
step_width = scale_unit * floor( step_width / scale_unit );
step_width /= factor;
*l = 1 + (int)(floor( log10(max) ));
*r = frac_cnt;
return step_width;
}
/*
** Der Ankerwert dient als Fixpunkt od. Ausgangswert, wenn der Anfangswert
** der Skala nicht ins Raster passt oder sonst der Nullpunkt nicht getroffen
** wird.
*/
double anchor_val( double min, double max ) {
double avg;
double anc;
double lim;
int sign;
if( min <= 0.0 && max >= 0.0 ) {
return 0.0; // zero line included
}
avg = (min + max) / 2.0;
sign = copysign( 1.0, avg );
avg = fabs( avg );
lim = fabs( min );
fprintf( stderr, "avg: %f lim: %f\n", avg, lim );
if( avg >= 1.0 ) {
anc = round( avg );
while ( anc >= (lim + 1.0) ) {
anc -= 1.0;
}
return copysign( anc, sign );
}
return min;
}
int main( int argc, char * argv[] )
{
double step;
double min;
double max;
int l_digits;
int r_digits;
char format_string[16];
char output_string[64];
if( argc < 3 ) {
printf( "Usage: %s min_value max_value [num]\n\n", argv[0] );
exit( -1 );
}
min = atof( argv[1] );
max = atof( argv[2] );
if( argc > 3 )
num_steps = atoi( argv[3] );
if( min >= max ) {
printf( "Input error\n" );
exit( -1 );
}
printf( "Anchor: %4.3f\n", anchor_val( min, max ) );
step = scale( min, max, num_steps, &l_digits, &r_digits );
if( l_digits < 1 )
l_digits = 1;
sprintf( format_string, "%%%d.%df", l_digits + r_digits, r_digits );
sprintf( output_string, "scale step is: %s\n", format_string );
printf( "format is: \"%s\"\n", format_string );
printf( output_string, step );
printf( "num steps: %2.1f\n", (max-min) / step );
return 0;
}
|
Hat jemand eine Idee für einen besseren Ansatz?
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Dakuan schrieb:
Aber jetzt möchte ich auch Schrittweiten kleiner als 1 haben. Im Prinzip funktioniert das auch (ansatzweise).
Allerdings bekomme ich es nicht hin, Teilungen in 1/4 oder 1/8 Schritten zu realisieren. Mein Algorithmus erzeugt nur 0.5 oder 0.2 -er Schritte. Das Problem liegt in der Erkennung der benötigten Nachkommastellen.
Würde man nicht einfach den Wert mit der double eigenen Präzision bestimmen und nur für die Ausgabe Stellen abschneiden? Oder ist letzteres genau Dein Problem?
Die Idee dahinter ist, das das Darstellungsprogramm selbständig eine sinnvolle Einteilung finden soll, die sich idealerweise an der Pixellänge der Skala und der Schriftgröße orientiert (was noch nicht realisiert ist).
Was sollen den zulässige Einteilungen sein? Sehe ich das richtig, dass Du entweder
möchtest?
Zur Demonstration hier mal der Code eines einfachen Testprogramms, als Diskussionsgrundlage:
Ich stelle mal wieder fest, wie umständlich ich C für so etwas finde - es gibt, verglichen mit den üblichen Skriptsprachen, einfach so viel Code, der nur dazu dient, die Technik zu befriedigen (Speicher zu holen usw.) und nicht die Logik zu implementieren. ☺
Hat jemand eine Idee für einen besseren Ansatz?
Mal sehen...
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Würde man nicht einfach den Wert mit der double eigenen Präzision bestimmen und nur für die Ausgabe Stellen abschneiden?
Ich möchte nicht einfach Stellen abschneiden. Ich möchte das die Beschriftung genau zu den Markierungen der Skala passt.
Was sollen den zulässige Einteilungen sein?
Eigentlich sollen 10-er Potenzen oder ganzzahlige Vielfache davon bevorzugt werden. Also wenn der Wertebereich von 0 bis 23000 geht sind 11 Schritte zu 2000 eine brauchbare Einteilung. Der letzte Wert bekommt dann keine Beschriftung, da nicht im Raster. Das Beispiel liefert genau dieses Ergebnis, auch wenn als Ausgangswert 8 angegeben wurde. Wenn die Schritte kleiner als 1 sind, währen die 2er-Potenzen-Bruchteile eine zusätzliche Option, da 1/4 und 1/8 relativ häufig vorkommen können. Das Problem dabei ist, das ich dafür mehr Stellen benötige. Mein Beispielprogramm liefert nur Einteilungen, die bei jedem zweiten Durchlauf um eine Stelle wachsen (2*5=10). Die kann ich dann einfach abzählen.
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
Verstehe ich das richtig, dass es weniger ein Problem der Programmierung ist als ein Problem der Implementierung, also wie man das vernünftig hinkriegt, mit der Skaleneinteilung und Lesbarkeit ? Dakuan schrieb: Was sollen den zulässige Einteilungen sein?
Eigentlich sollen 10-er Potenzen oder ganzzahlige Vielfache davon bevorzugt werden. Also wenn der Wertebereich von 0 bis 23000 geht sind 11 Schritte zu 2000 eine brauchbare Einteilung.
Da wäre es eigentlich nur konsequent, wenn Du diese Serie nach unten mit Schritten zu 0,5 - 0,2 - 0,1 - 0,05 - ... fortsetzt. Rein formal ist es ein Bruch, wenn Du unter 1 jetzt auf einmal auf ½ - ¼ - ⅛ umschaltest. - wobei: das wäre ja an sich auch noch eine Möglichkeit, solche utf8-Sonderzeichen der "Vulgar Fraction"-Serie zu benutzen ... (was natürlich noch etwas Zusatzaufwand beim umcodieren kostet. Dafür ist es aber schön kompakt: 3⅞) LG, track
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Verstehe ich das richtig, dass es weniger ein Problem der Programmierung ist als ein Problem der Implementierung, also wie man das vernünftig hinkriegt, mit der Skaleneinteilung und Lesbarkeit ?
Ja, genau. Ich suche einen Algorithmus der das macht. Die Beschriftung als Bruch zu machen, währe auch eine interessante Option. Dabei müsste ich noch nicht einmal auf die Sonderzeichen zurückgreifen, denn ich zeichne ja ein Bild im Speicher. Die Darstellung als Bruch währe auch die einzig akzeptable Möglichkeit eine 1/3 Teilung so zu machen, das der Benutzer das auch sofort erkennt. Aber im Moment erscheint mir das noch zu kompliziert. Das Programm muss ja den Bruch auch erkennen oder ich muss ihm das sagen. Ideal währe dann natürlich, wenn das Programm auch erkennen würde das Beispielsweise 0,34 sehr nahe bei 1/3 liegt ... Aber ich glaube das ist unrealistisch oder zumindest sehr aufwändig.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Dakuan schrieb: Würde man nicht einfach den Wert mit der double eigenen Präzision bestimmen und nur für die Ausgabe Stellen abschneiden?
Ich möchte nicht einfach Stellen abschneiden. Ich möchte das die Beschriftung genau zu den Markierungen der Skala passt.
Diese Aussage verstehe ich nicht. Entweder Du hast irrsinnig große Genauigkeit und die Zahl passt genau zur Skala oder Du schneidest ab, oder rundest, so dass die Zahl darstellbar bleibt. Aber natürlich soll die Zahl immer nahe genug dran sein - das ist ja klar. Was meinst Du also mit "genau zu den Markierungen" passen?
Was sollen den zulässige Einteilungen sein?
Eigentlich sollen 10-er Potenzen oder ganzzahlige Vielfache davon bevorzugt werden. Also wenn der Wertebereich von 0 bis 23000 geht sind 11 Schritte zu 2000 eine brauchbare Einteilung. Der letzte Wert bekommt dann keine Beschriftung, da nicht im Raster. Das Beispiel liefert genau dieses Ergebnis, auch wenn als Ausgangswert 8 angegeben wurde. Wenn die Schritte kleiner als 1 sind, währen die 2er-Potenzen-Bruchteile eine zusätzliche Option, da 1/4 und 1/8 relativ häufig vorkommen können. Das Problem dabei ist, das ich dafür mehr Stellen benötige.
Es sei denn, Du wählst genau diese Darstellung ("1/4", "2/4" usw.). ☺
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ok, ich habe mir erstmal ein Bier geholt. Vielleicht fällt mir dann eine verständlichere Formulierung ein.
Entweder Du hast irrsinnig große Genauigkeit ...
Das mit der Genauigkeit hält sich in Grenzen, da ich den Wertebereich auf Fensterbreite abbilde, also irgendwas um die 1000 Pixel. Damit sind 0.5 Pixel Abweichung immer drin. Der Wertebereich wird aber teilweise vom Programm bestimmt oder dadurch das der Benutzer nur einen Teilbereich sehen will. Das bedeutet, das die Einteilung oft nicht vorher bekannt ist.
Der Programmteil zum Zeichnen soll dann selbständig eine stimmige Einteilung und Beschriftung finden. Hier mal ein Beispiel, das zwar brauchbar ist, aber insofern nicht perfekt als die "1.0" keinen Skalenstrich abbekommen hat. Die Einteilung der y-Achse ist hier übrigens noch hart codiert, was sich als zu unflexibel erwiesen hat.
- Bilder
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Dakuan schrieb:
Entweder Du hast irrsinnig große Genauigkeit ...
Das mit der Genauigkeit hält sich in Grenzen, da ich den Wertebereich auf Fensterbreite abbilde, also irgendwas um die 1000 Pixel. Damit sind 0.5 Pixel Abweichung immer drin.
"Genauigkeit" im Satz oben bezieht sich nicht auf Pixel sondern auf die Zahlenwerte, die Du an der Skala darstellst.
Der Wertebereich wird aber teilweise vom Programm bestimmt oder dadurch das der Benutzer nur einen Teilbereich sehen will. Das bedeutet, das die Einteilung oft nicht vorher bekannt ist.
Der Programmteil zum Zeichnen soll dann selbständig eine stimmige Einteilung und Beschriftung finden.
Das habe ich schon verstanden, aber Du musst Dir überlegen, was "stimmig" eigentlich bedeuten soll. Anders gesagt: wie viel Ungenauigkeit in der Skalenbeschriftung willst Du hinnehmen? Reicht es, wenn sich jeder Wert von beiden Nachbarn in mindestens einem Zeichen unterscheidet oder möchtest Du eine maximale Differenz zwischen dem dargestellten und dem eigentlichen Wert an der Stelle einhalten? Oder ist Dir das selbst nicht klar und möchtest Du, dass wir dafür Vorschläge machen?
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Also gut, nochmal anders herum. Ich möchte nur solche Skalenteilungen haben, die mit möglichst wenig Stellen auskommen ohne dabei runden zu müssen. Also bei einer exakten 1/3 Teilung ist eine Beschriftung mit 0.3 oder 0.33 ein NO-GO. Momentan versuche ich gerade die n*(1/Zweierpotenz) Teilung in den Griff zu bekommen. Ich bin mir allerdings noch nicht klar darüber ob ich da über die Bitmuster in der Variablen herankomme (sprintf( "%a"...)) oder ob mir der log2() da helfen kann (log2(1/8)=-3).
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Auch wenn das Interesse eher gering ist, möchte ich das Thema doch zu einem Abschluss bringen. Ich habe jetzt beide Verfahren kombiniert, also die dekadische und die binäre Teilung. Das Programm kann jetzt die Teilung mit der geringsten Anzahl von Teilungsschritten oder der geringsten Stellenzahl auswählen (letzteres noch nicht realisiert aber leicht machbar). Leider ist der Code dadurch um 50% länger geworden. Anmerkung: Wenn die Teilungsschritte nicht exakt passen, werden sie erhöht, bis ein passendes Raster gefunden wurde.
|