Fossil SCM

New algorithm for positioning the labels on a piechart.

drh 2015-05-09 19:08 trunk
Commit 2018ad2cc5c829563ccf452f458745517dcfb814
1 file changed +95 -38
+95 -38
--- src/piechart.c
+++ src/piechart.c
@@ -64,10 +64,33 @@
6464
#if INTERFACE
6565
#define PIE_OTHER 0x0001 /* No wedge less than 1/60th of the circle */
6666
#define PIE_CHROMATIC 0x0002 /* Wedge colors are in chromatic order */
6767
#define PIE_PERCENT 0x0004 /* Add "(XX%)" marks on each label */
6868
#endif
69
+
70
+/*
71
+** A pie-chart wedge label
72
+*/
73
+struct WedgeLabel {
74
+ double rCos, rSin; /* Sine and Cosine of center angle of wedge */
75
+ char *z; /* Label to draw on this wedge */
76
+};
77
+typedef struct WedgeLabel WedgeLabel;
78
+
79
+/*
80
+** Comparison callback for qsort() to sort labels in order of increasing
81
+** distance above and below the horizontal centerline.
82
+*/
83
+static int wedgeCompare(const void *a, const void *b){
84
+ const WedgeLabel *pA = (const WedgeLabel*)a;
85
+ const WedgeLabel *pB = (const WedgeLabel*)b;
86
+ double rA = fabs(pA->rCos);
87
+ double rB = fabs(pB->rCos);
88
+ if( rA<rB ) return -1;
89
+ if( rA>rB ) return +1;
90
+ return 0;
91
+}
6992
7093
/*
7194
** Output HTML that will render a pie chart using data from
7295
** the PIECHART temporary table.
7396
**
@@ -88,24 +111,32 @@
88111
const char *zAnc; /* Anchor point for text */
89112
double a1 = 0.0; /* Angle for first edge of slice */
90113
double a2; /* Angle for second edge */
91114
double a3; /* Angle at middle of slice */
92115
int rot; /* Text rotation angle */
93
- double sina3, cosa3; /* sin(a3) and cos(a3) */
94116
unsigned char h; /* Hue */
95117
const char *zClr; /* Color */
96118
int l; /* Large arc flag */
97119
int j; /* Wedge number */
98120
double rTotal; /* Total piechart.amt */
99121
double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */
100122
int nTotal; /* Total number of entries in piechart */
101123
int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */
102124
const char *zFg; /* foreground color for lines and text */
125
+ int nWedgeAlloc = 0; /* Slots allocated for aWedge[] */
126
+ int nWedge = 0; /* Slots used for aWedge[] */
127
+ WedgeLabel *aWedge = 0; /* Labels */
128
+ double rUprRight; /* Floor for next label in the upper right quadrant */
129
+ double rUprLeft; /* Floor for next label in the upper left quadrant */
130
+ double rLwrRight; /* Ceiling for label in the lower right quadrant */
131
+ double rLwrLeft; /* Ceiling for label in the lower left quadrant */
132
+ int i; /* Loop counter looping over wedge labels */
103133
104134
# define SATURATION 128
105135
# define VALUE 192
106136
# define OTHER_CUTOFF 90.0
137
+# define TEXT_HEIGHT 15.0
107138
108139
cx = 0.5*width;
109140
cy = 0.5*height;
110141
r2 = cx<cy ? cx : cy;
111142
r = r2 - 80.0;
@@ -148,35 +179,23 @@
148179
y1 = cy - cos(a1)*r;
149180
a2 = a1 + x*2.0*M_PI;
150181
x2 = cx + sin(a2)*r;
151182
y2 = cy - cos(a2)*r;
152183
a3 = 0.5*(a1+a2);
153
- sina3 = sin(a3);
154
- cosa3 = cos(a3);
155
- x3 = cx + sina3*r;
156
- y3 = cy - cosa3*r;
157
- d1 = r*1.1;
158
- x4 = cx + sina3*d1;
159
- y4 = cy - cosa3*d1;
160
- y5 = y4 - 3.0 + 6.0*(1.0 -cosa3);
161
- rot = ((int)(a3*180/M_PI))%180;
162
- if( a2-a1 > 0.6 ){
163
- rot = 0; /* Never rotate text on fat slices */
164
- }else if( rot<60 ){
165
- rot = (rot - 60)/3;
166
- }else if( rot>120 ){
167
- rot = (rot - 120)/3;
168
- }else{
169
- rot = 0;
170
- }
171
- if( x4<=cx ){
172
- x5 = x4 - 5.0;
173
- zAnc = "end";
174
- }else{
175
- x5 = x4 + 4.0;
176
- zAnc = "start";
177
- }
184
+ if( nWedge+1>nWedgeAlloc ){
185
+ nWedgeAlloc = nWedgeAlloc*2 + 40;
186
+ aWedge = fossil_realloc(aWedge, sizeof(aWedge[0])*nWedgeAlloc);
187
+ }
188
+ if( pieFlags & PIE_PERCENT ){
189
+ int pct = (int)(x*100.0 + 0.5);
190
+ aWedge[nWedge].z = mprintf("%s (%d%%)", zLbl, pct);
191
+ }else{
192
+ aWedge[nWedge].z = fossil_strdup(zLbl);
193
+ }
194
+ aWedge[nWedge].rSin = sin(a3);
195
+ aWedge[nWedge].rCos = cos(a3);
196
+ nWedge++;
178197
if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){
179198
h = 256*j/nTotal;
180199
}else if( j+2<nTotal ){
181200
h = 256*(j+2)/nTotal;
182201
}else{
@@ -185,26 +204,64 @@
185204
zClr = rgbName(h,SATURATION,VALUE);
186205
l = x>=0.5;
187206
a1 = a2;
188207
@ <path stroke="%s(zFg)" stroke-width="1" fill="%s(zClr)"
189208
@ d='M%g(cx),%g(cy)L%g(x1),%g(y1)A%g(r),%g(r) 0 %d(l),1 %g(x2),%g(y2)z'/>
209
+ }
210
+ qsort(aWedge, nWedge, sizeof(aWedge[0]), wedgeCompare);
211
+ rUprLeft = height;
212
+ rLwrLeft = 0;
213
+ rUprRight = height;
214
+ rLwrRight = 0;
215
+ d1 = r*1.1;
216
+ for(i=0; i<nWedge; i++){
217
+ WedgeLabel *p = &aWedge[i];
218
+ x3 = cx + p->rSin*r;
219
+ y3 = cy - p->rCos*r;
220
+ x4 = cx + p->rSin*d1;
221
+ y4 = cy - p->rCos*d1;
222
+ if( y4<=cy ){
223
+ if( x4>=cx ){
224
+ if( y4>rUprRight ){
225
+ y4 = rUprRight;
226
+ }
227
+ rUprRight = y4 - TEXT_HEIGHT;
228
+ }else{
229
+ if( y4>rUprLeft ){
230
+ y4 = rUprLeft;
231
+ }
232
+ rUprLeft = y4 - TEXT_HEIGHT;
233
+ }
234
+ }else{
235
+ if( x4>=cx ){
236
+ if( y4<rLwrRight ){
237
+ y4 = rLwrRight;
238
+ }
239
+ rLwrRight = y4 + TEXT_HEIGHT;
240
+ }else{
241
+ if( y4<rLwrLeft ){
242
+ y4 = rLwrLeft;
243
+ }
244
+ rLwrLeft = y4 + TEXT_HEIGHT;
245
+ }
246
+ }
247
+ if( x4<=cx ){
248
+ x5 = x4 - 1.0;
249
+ zAnc = "end";
250
+ }else{
251
+ x5 = x4 + 1.0;
252
+ zAnc = "start";
253
+ }
254
+ y5 = y4 - 3.0 + 6.0*(1.0 - p->rCos);
190255
@ <line stroke='%s(zFg)' stroke-width='1'
191256
@ x1='%g(x3)' y1='%g(y3)' x2='%g(x4)' y2='%g(y4)''/>
192
- if( rot!=0 ){
193
- @ <text text-anchor="%s(zAnc)" transform='rotate(%d(rot),%g(x5),%g(y5))'
194
- }else{
195
- @ <text text-anchor="%s(zAnc)" transform='rotate(%d(rot),%g(x5),%g(y5))'
196
- }
197
- if( pieFlags & PIE_PERCENT ){
198
- int p = (int)(x*100.0 + 0.5);
199
- @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(zLbl) (%d(p)%%)</text>
200
- }else{
201
- @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(zLbl)</text>
202
- }
257
+ @ <text text-anchor="%s(zAnc)"
258
+ @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(p->z)</text>
259
+ fossil_free(p->z);
203260
}
204261
db_finalize(&q);
205
-
262
+ fossil_free(aWedge);
206263
}
207264
208265
/*
209266
** WEBPAGE: test-piechart
210267
**
211268
--- src/piechart.c
+++ src/piechart.c
@@ -64,10 +64,33 @@
64 #if INTERFACE
65 #define PIE_OTHER 0x0001 /* No wedge less than 1/60th of the circle */
66 #define PIE_CHROMATIC 0x0002 /* Wedge colors are in chromatic order */
67 #define PIE_PERCENT 0x0004 /* Add "(XX%)" marks on each label */
68 #endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
70 /*
71 ** Output HTML that will render a pie chart using data from
72 ** the PIECHART temporary table.
73 **
@@ -88,24 +111,32 @@
88 const char *zAnc; /* Anchor point for text */
89 double a1 = 0.0; /* Angle for first edge of slice */
90 double a2; /* Angle for second edge */
91 double a3; /* Angle at middle of slice */
92 int rot; /* Text rotation angle */
93 double sina3, cosa3; /* sin(a3) and cos(a3) */
94 unsigned char h; /* Hue */
95 const char *zClr; /* Color */
96 int l; /* Large arc flag */
97 int j; /* Wedge number */
98 double rTotal; /* Total piechart.amt */
99 double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */
100 int nTotal; /* Total number of entries in piechart */
101 int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */
102 const char *zFg; /* foreground color for lines and text */
 
 
 
 
 
 
 
 
103
104 # define SATURATION 128
105 # define VALUE 192
106 # define OTHER_CUTOFF 90.0
 
107
108 cx = 0.5*width;
109 cy = 0.5*height;
110 r2 = cx<cy ? cx : cy;
111 r = r2 - 80.0;
@@ -148,35 +179,23 @@
148 y1 = cy - cos(a1)*r;
149 a2 = a1 + x*2.0*M_PI;
150 x2 = cx + sin(a2)*r;
151 y2 = cy - cos(a2)*r;
152 a3 = 0.5*(a1+a2);
153 sina3 = sin(a3);
154 cosa3 = cos(a3);
155 x3 = cx + sina3*r;
156 y3 = cy - cosa3*r;
157 d1 = r*1.1;
158 x4 = cx + sina3*d1;
159 y4 = cy - cosa3*d1;
160 y5 = y4 - 3.0 + 6.0*(1.0 -cosa3);
161 rot = ((int)(a3*180/M_PI))%180;
162 if( a2-a1 > 0.6 ){
163 rot = 0; /* Never rotate text on fat slices */
164 }else if( rot<60 ){
165 rot = (rot - 60)/3;
166 }else if( rot>120 ){
167 rot = (rot - 120)/3;
168 }else{
169 rot = 0;
170 }
171 if( x4<=cx ){
172 x5 = x4 - 5.0;
173 zAnc = "end";
174 }else{
175 x5 = x4 + 4.0;
176 zAnc = "start";
177 }
178 if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){
179 h = 256*j/nTotal;
180 }else if( j+2<nTotal ){
181 h = 256*(j+2)/nTotal;
182 }else{
@@ -185,26 +204,64 @@
185 zClr = rgbName(h,SATURATION,VALUE);
186 l = x>=0.5;
187 a1 = a2;
188 @ <path stroke="%s(zFg)" stroke-width="1" fill="%s(zClr)"
189 @ d='M%g(cx),%g(cy)L%g(x1),%g(y1)A%g(r),%g(r) 0 %d(l),1 %g(x2),%g(y2)z'/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190 @ <line stroke='%s(zFg)' stroke-width='1'
191 @ x1='%g(x3)' y1='%g(y3)' x2='%g(x4)' y2='%g(y4)''/>
192 if( rot!=0 ){
193 @ <text text-anchor="%s(zAnc)" transform='rotate(%d(rot),%g(x5),%g(y5))'
194 }else{
195 @ <text text-anchor="%s(zAnc)" transform='rotate(%d(rot),%g(x5),%g(y5))'
196 }
197 if( pieFlags & PIE_PERCENT ){
198 int p = (int)(x*100.0 + 0.5);
199 @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(zLbl) (%d(p)%%)</text>
200 }else{
201 @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(zLbl)</text>
202 }
203 }
204 db_finalize(&q);
205
206 }
207
208 /*
209 ** WEBPAGE: test-piechart
210 **
211
--- src/piechart.c
+++ src/piechart.c
@@ -64,10 +64,33 @@
64 #if INTERFACE
65 #define PIE_OTHER 0x0001 /* No wedge less than 1/60th of the circle */
66 #define PIE_CHROMATIC 0x0002 /* Wedge colors are in chromatic order */
67 #define PIE_PERCENT 0x0004 /* Add "(XX%)" marks on each label */
68 #endif
69
70 /*
71 ** A pie-chart wedge label
72 */
73 struct WedgeLabel {
74 double rCos, rSin; /* Sine and Cosine of center angle of wedge */
75 char *z; /* Label to draw on this wedge */
76 };
77 typedef struct WedgeLabel WedgeLabel;
78
79 /*
80 ** Comparison callback for qsort() to sort labels in order of increasing
81 ** distance above and below the horizontal centerline.
82 */
83 static int wedgeCompare(const void *a, const void *b){
84 const WedgeLabel *pA = (const WedgeLabel*)a;
85 const WedgeLabel *pB = (const WedgeLabel*)b;
86 double rA = fabs(pA->rCos);
87 double rB = fabs(pB->rCos);
88 if( rA<rB ) return -1;
89 if( rA>rB ) return +1;
90 return 0;
91 }
92
93 /*
94 ** Output HTML that will render a pie chart using data from
95 ** the PIECHART temporary table.
96 **
@@ -88,24 +111,32 @@
111 const char *zAnc; /* Anchor point for text */
112 double a1 = 0.0; /* Angle for first edge of slice */
113 double a2; /* Angle for second edge */
114 double a3; /* Angle at middle of slice */
115 int rot; /* Text rotation angle */
 
116 unsigned char h; /* Hue */
117 const char *zClr; /* Color */
118 int l; /* Large arc flag */
119 int j; /* Wedge number */
120 double rTotal; /* Total piechart.amt */
121 double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */
122 int nTotal; /* Total number of entries in piechart */
123 int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */
124 const char *zFg; /* foreground color for lines and text */
125 int nWedgeAlloc = 0; /* Slots allocated for aWedge[] */
126 int nWedge = 0; /* Slots used for aWedge[] */
127 WedgeLabel *aWedge = 0; /* Labels */
128 double rUprRight; /* Floor for next label in the upper right quadrant */
129 double rUprLeft; /* Floor for next label in the upper left quadrant */
130 double rLwrRight; /* Ceiling for label in the lower right quadrant */
131 double rLwrLeft; /* Ceiling for label in the lower left quadrant */
132 int i; /* Loop counter looping over wedge labels */
133
134 # define SATURATION 128
135 # define VALUE 192
136 # define OTHER_CUTOFF 90.0
137 # define TEXT_HEIGHT 15.0
138
139 cx = 0.5*width;
140 cy = 0.5*height;
141 r2 = cx<cy ? cx : cy;
142 r = r2 - 80.0;
@@ -148,35 +179,23 @@
179 y1 = cy - cos(a1)*r;
180 a2 = a1 + x*2.0*M_PI;
181 x2 = cx + sin(a2)*r;
182 y2 = cy - cos(a2)*r;
183 a3 = 0.5*(a1+a2);
184 if( nWedge+1>nWedgeAlloc ){
185 nWedgeAlloc = nWedgeAlloc*2 + 40;
186 aWedge = fossil_realloc(aWedge, sizeof(aWedge[0])*nWedgeAlloc);
187 }
188 if( pieFlags & PIE_PERCENT ){
189 int pct = (int)(x*100.0 + 0.5);
190 aWedge[nWedge].z = mprintf("%s (%d%%)", zLbl, pct);
191 }else{
192 aWedge[nWedge].z = fossil_strdup(zLbl);
193 }
194 aWedge[nWedge].rSin = sin(a3);
195 aWedge[nWedge].rCos = cos(a3);
196 nWedge++;
 
 
 
 
 
 
 
 
 
 
 
 
197 if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){
198 h = 256*j/nTotal;
199 }else if( j+2<nTotal ){
200 h = 256*(j+2)/nTotal;
201 }else{
@@ -185,26 +204,64 @@
204 zClr = rgbName(h,SATURATION,VALUE);
205 l = x>=0.5;
206 a1 = a2;
207 @ <path stroke="%s(zFg)" stroke-width="1" fill="%s(zClr)"
208 @ d='M%g(cx),%g(cy)L%g(x1),%g(y1)A%g(r),%g(r) 0 %d(l),1 %g(x2),%g(y2)z'/>
209 }
210 qsort(aWedge, nWedge, sizeof(aWedge[0]), wedgeCompare);
211 rUprLeft = height;
212 rLwrLeft = 0;
213 rUprRight = height;
214 rLwrRight = 0;
215 d1 = r*1.1;
216 for(i=0; i<nWedge; i++){
217 WedgeLabel *p = &aWedge[i];
218 x3 = cx + p->rSin*r;
219 y3 = cy - p->rCos*r;
220 x4 = cx + p->rSin*d1;
221 y4 = cy - p->rCos*d1;
222 if( y4<=cy ){
223 if( x4>=cx ){
224 if( y4>rUprRight ){
225 y4 = rUprRight;
226 }
227 rUprRight = y4 - TEXT_HEIGHT;
228 }else{
229 if( y4>rUprLeft ){
230 y4 = rUprLeft;
231 }
232 rUprLeft = y4 - TEXT_HEIGHT;
233 }
234 }else{
235 if( x4>=cx ){
236 if( y4<rLwrRight ){
237 y4 = rLwrRight;
238 }
239 rLwrRight = y4 + TEXT_HEIGHT;
240 }else{
241 if( y4<rLwrLeft ){
242 y4 = rLwrLeft;
243 }
244 rLwrLeft = y4 + TEXT_HEIGHT;
245 }
246 }
247 if( x4<=cx ){
248 x5 = x4 - 1.0;
249 zAnc = "end";
250 }else{
251 x5 = x4 + 1.0;
252 zAnc = "start";
253 }
254 y5 = y4 - 3.0 + 6.0*(1.0 - p->rCos);
255 @ <line stroke='%s(zFg)' stroke-width='1'
256 @ x1='%g(x3)' y1='%g(y3)' x2='%g(x4)' y2='%g(y4)''/>
257 @ <text text-anchor="%s(zAnc)"
258 @ x='%g(x5)' y='%g(y5)' fill='%s(zFg)'>%h(p->z)</text>
259 fossil_free(p->z);
 
 
 
 
 
 
 
 
260 }
261 db_finalize(&q);
262 fossil_free(aWedge);
263 }
264
265 /*
266 ** WEBPAGE: test-piechart
267 **
268

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button