Fossil SCM

fossil-scm / src / piechart.c
Blame History Raw 287 lines
1
/*
2
** Copyright (c) 2015 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code for generating pie charts on web pages.
19
**
20
*/
21
#include "config.h"
22
#include "piechart.h"
23
#include <math.h>
24
25
#ifndef M_PI
26
# define M_PI 3.1415926535897932385
27
#endif
28
29
/*
30
** Return an RGB color name given HSV values. The HSV values
31
** must each be between 0 and 255. The string
32
** returned is held in a static buffer and is overwritten
33
** on each call.
34
*/
35
const char *rgbName(unsigned char h, unsigned char s, unsigned char v){
36
static char zColor[8];
37
unsigned char A, B, C, r, g, b;
38
unsigned int i, m;
39
if( s==0 ){
40
r = g = b = v;
41
}else{
42
i = (h*6)/256;
43
m = (h*6)&0xff;
44
A = v*(256-s)/256;
45
B = v*(65536-s*m)/65536;
46
C = v*(65536-s*(256-m))/65536;
47
@ <!-- hsv=%d(h),%d(s),%d(v) i=%d(i) m=%d(m) ABC=%d(A),%d(B),%d(C) -->
48
switch( i ){
49
case 0: r=v; g=C; b=A; break;
50
case 1: r=B; g=v; b=A; break;
51
case 2: r=A; g=v; b=C; break;
52
case 3: r=A; g=B; b=v; break;
53
case 4: r=C; g=A; b=v; break;
54
default: r=v; g=A; b=B; break;
55
}
56
}
57
sqlite3_snprintf(sizeof(zColor),zColor,"#%02x%02x%02x",r,g,b);
58
return zColor;
59
}
60
61
/*
62
** Flags that can be passed into the pie-chart generator
63
*/
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
**
97
** The schema for the PIECHART table should be:
98
**
99
** CREATE TEMP TABLE piechart(amt REAL, label TEXT);
100
*/
101
void piechart_render(int width, int height, unsigned int pieFlags){
102
Stmt q;
103
double cx, cy; /* center of the pie */
104
double r, r2; /* Radius of the pie */
105
double x1,y1; /* Start of the slice */
106
double x2,y2; /* End of the slice */
107
double x3,y3; /* Middle point of the slice */
108
double x4,y4; /* End of line extending from x3,y3 */
109
double x5,y5; /* Text anchor */
110
double d1; /* radius to x4,y4 */
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
unsigned char h; /* Hue */
116
const char *zClr; /* Color */
117
int l; /* Large arc flag */
118
int j; /* Wedge number */
119
double rTotal; /* Total piechart.amt */
120
double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */
121
int nTotal; /* Total number of entries in piechart */
122
int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */
123
const char *zFg; /* foreground color for lines and text */
124
int nWedgeAlloc = 0; /* Slots allocated for aWedge[] */
125
int nWedge = 0; /* Slots used for aWedge[] */
126
WedgeLabel *aWedge = 0; /* Labels */
127
double rUprRight; /* Floor for next label in the upper right quadrant */
128
double rUprLeft; /* Floor for next label in the upper left quadrant */
129
double rLwrRight; /* Ceiling for label in the lower right quadrant */
130
double rLwrLeft; /* Ceiling for label in the lower left quadrant */
131
int i; /* Loop counter looping over wedge labels */
132
133
# define SATURATION 128
134
# define VALUE 192
135
# define OTHER_CUTOFF 90.0
136
# define TEXT_HEIGHT 15.0
137
138
cx = 0.5*width;
139
cy = 0.5*height;
140
r2 = cx<cy ? cx : cy;
141
r = r2 - 80.0;
142
if( r<0.33333*r2 ) r = 0.33333*r2;
143
h = 0;
144
zFg = skin_detail_boolean("white-foreground") ? "white" : "black";
145
146
db_prepare(&q, "SELECT sum(amt), count(*) FROM piechart");
147
if( db_step(&q)!=SQLITE_ROW ){
148
db_finalize(&q);
149
return;
150
}
151
rTotal = db_column_double(&q, 0);
152
nTotal = db_column_int(&q, 1);
153
db_finalize(&q);
154
rTooSmall = 0.0;
155
nTooSmall = 0;
156
if( (pieFlags & PIE_OTHER)!=0 && nTotal>1 ){
157
db_prepare(&q, "SELECT sum(amt), count(*) FROM piechart WHERE amt<:amt");
158
db_bind_double(&q, ":amt", rTotal/OTHER_CUTOFF);
159
if( db_step(&q)==SQLITE_ROW ){
160
rTooSmall = db_column_double(&q, 0);
161
nTooSmall = db_column_int(&q, 1);
162
}
163
db_finalize(&q);
164
}
165
if( nTooSmall>1 ){
166
db_prepare(&q, "SELECT amt, label FROM piechart WHERE amt>=:limit"
167
" UNION ALL SELECT %.17g, '%d others';",
168
rTooSmall, nTooSmall);
169
db_bind_double(&q, ":limit", rTotal/OTHER_CUTOFF);
170
nTotal += 1 - nTooSmall;
171
}else{
172
db_prepare(&q, "SELECT amt, label FROM piechart");
173
}
174
if( nTotal<=10 ) pieFlags |= PIE_CHROMATIC;
175
for(j=0; db_step(&q)==SQLITE_ROW; j++){
176
double x = db_column_double(&q,0)/rTotal;
177
const char *zLbl = db_column_text(&q,1);
178
/* @ <!-- x=%g(x) zLbl="%h(zLbl)" h=%d(h) --> */
179
if( x<=0.0 ) continue;
180
x1 = cx + sin(a1)*r;
181
y1 = cy - cos(a1)*r;
182
a2 = a1 + x*2.0*M_PI;
183
x2 = cx + sin(a2)*r;
184
y2 = cy - cos(a2)*r;
185
a3 = 0.5*(a1+a2);
186
if( nWedge+1>nWedgeAlloc ){
187
nWedgeAlloc = nWedgeAlloc*2 + 40;
188
aWedge = fossil_realloc(aWedge, sizeof(aWedge[0])*nWedgeAlloc);
189
}
190
if( pieFlags & PIE_PERCENT ){
191
int pct = (int)(x*100.0 + 0.5);
192
aWedge[nWedge].z = mprintf("%s (%d%%)", zLbl, pct);
193
}else{
194
aWedge[nWedge].z = fossil_strdup(zLbl);
195
}
196
aWedge[nWedge].rSin = sin(a3);
197
aWedge[nWedge].rCos = cos(a3);
198
nWedge++;
199
if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){
200
h = 256*j/nTotal;
201
}else if( j+2<nTotal ){
202
h = 256*(j+2)/nTotal;
203
}else{
204
h = 256*((j+2+(nTotal&1))%nTotal)/nTotal;
205
}
206
zClr = rgbName(h,SATURATION,VALUE);
207
l = x>=0.5;
208
a1 = a2;
209
@ <path class='piechartWedge'
210
@ stroke="black" stroke-width="1" fill="%s(zClr)"
211
@ 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'/>
212
}
213
qsort(aWedge, nWedge, sizeof(aWedge[0]), wedgeCompare);
214
rUprLeft = height;
215
rLwrLeft = 0;
216
rUprRight = height;
217
rLwrRight = 0;
218
d1 = r*1.1;
219
for(i=0; i<nWedge; i++){
220
WedgeLabel *p = &aWedge[i];
221
x3 = cx + p->rSin*r;
222
y3 = cy - p->rCos*r;
223
x4 = cx + p->rSin*d1;
224
y4 = cy - p->rCos*d1;
225
if( y4<=cy ){
226
if( x4>=cx ){
227
if( y4>rUprRight ){
228
y4 = rUprRight;
229
}
230
rUprRight = y4 - TEXT_HEIGHT;
231
}else{
232
if( y4>rUprLeft ){
233
y4 = rUprLeft;
234
}
235
rUprLeft = y4 - TEXT_HEIGHT;
236
}
237
}else{
238
if( x4>=cx ){
239
if( y4<rLwrRight ){
240
y4 = rLwrRight;
241
}
242
rLwrRight = y4 + TEXT_HEIGHT;
243
}else{
244
if( y4<rLwrLeft ){
245
y4 = rLwrLeft;
246
}
247
rLwrLeft = y4 + TEXT_HEIGHT;
248
}
249
}
250
if( x4<cx ){
251
x5 = x4 - 1.0;
252
zAnc = "end";
253
}else{
254
x5 = x4 + 1.0;
255
zAnc = "start";
256
}
257
y5 = y4 - 3.0 + 6.0*(1.0 - p->rCos);
258
@ <line stroke-width='1' stroke='%s(zFg)' class='piechartLine'
259
@ x1='%g(x3)' y1='%g(y3)' x2='%g(x4)' y2='%g(y4)'/>
260
@ <text text-anchor="%s(zAnc)" fill='%s(zFg)' class="piechartLabel"
261
@ x='%g(x5)' y='%g(y5)'>%h(p->z)</text>
262
fossil_free(p->z);
263
}
264
db_finalize(&q);
265
fossil_free(aWedge);
266
}
267
268
/*
269
** WEBPAGE: test-piechart
270
**
271
** Generate a pie-chart based on data input from a form.
272
*/
273
void piechart_test_page(void){
274
const char *zData;
275
Stmt ins;
276
int n = 0;
277
int width;
278
int height;
279
int i, j;
280
281
login_check_credentials();
282
style_set_current_feature("test");
283
style_header("Pie Chart Test");
284
db_multi_exec("CREATE TEMP TABLE piechart(amt REAL, label TEXT);");
285
db_prepare(&ins, "INSERT INTO piechart(amt,label) VALUES(:amt,:label)");
286
zData = PD("data","");
287
width = atoi(

Keyboard Shortcuts

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