From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/piechart.c | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 src/piechart.c (limited to 'src/piechart.c') diff --git a/src/piechart.c b/src/piechart.c new file mode 100644 index 0000000..7883636 --- /dev/null +++ b/src/piechart.c @@ -0,0 +1,335 @@ +/* +** Copyright (c) 2015 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code for generating pie charts on web pages. +** +*/ +#include "config.h" +#include "piechart.h" +#include + +#ifndef M_PI +# define M_PI 3.1415926535897932385 +#endif + +/* +** Return an RGB color name given HSV values. The HSV values +** must each be between 0 and 255. The string +** returned is held in a static buffer and is overwritten +** on each call. +*/ +const char *rgbName(unsigned char h, unsigned char s, unsigned char v){ + static char zColor[8]; + unsigned char A, B, C, r, g, b; + unsigned int i, m; + if( s==0 ){ + r = g = b = v; + }else{ + i = (h*6)/256; + m = (h*6)&0xff; + A = v*(256-s)/256; + B = v*(65536-s*m)/65536; + C = v*(65536-s*(256-m))/65536; + @ + switch( i ){ + case 0: r=v; g=C; b=A; break; + case 1: r=B; g=v; b=A; break; + case 2: r=A; g=v; b=C; break; + case 3: r=A; g=B; b=v; break; + case 4: r=C; g=A; b=v; break; + default: r=v; g=A; b=B; break; + } + } + sqlite3_snprintf(sizeof(zColor),zColor,"#%02x%02x%02x",r,g,b); + return zColor; +} + +/* +** Flags that can be passed into the pie-chart generator +*/ +#if INTERFACE +#define PIE_OTHER 0x0001 /* No wedge less than 1/60th of the circle */ +#define PIE_CHROMATIC 0x0002 /* Wedge colors are in chromatic order */ +#define PIE_PERCENT 0x0004 /* Add "(XX%)" marks on each label */ +#endif + +/* +** A pie-chart wedge label +*/ +struct WedgeLabel { + double rCos, rSin; /* Sine and Cosine of center angle of wedge */ + char *z; /* Label to draw on this wedge */ +}; +typedef struct WedgeLabel WedgeLabel; + +/* +** Comparison callback for qsort() to sort labels in order of increasing +** distance above and below the horizontal centerline. +*/ +static int wedgeCompare(const void *a, const void *b){ + const WedgeLabel *pA = (const WedgeLabel*)a; + const WedgeLabel *pB = (const WedgeLabel*)b; + double rA = fabs(pA->rCos); + double rB = fabs(pB->rCos); + if( rArB ) return +1; + return 0; +} + +/* +** Output HTML that will render a pie chart using data from +** the PIECHART temporary table. +** +** The schema for the PIECHART table should be: +** +** CREATE TEMP TABLE piechart(amt REAL, label TEXT); +*/ +void piechart_render(int width, int height, unsigned int pieFlags){ + Stmt q; + double cx, cy; /* center of the pie */ + double r, r2; /* Radius of the pie */ + double x1,y1; /* Start of the slice */ + double x2,y2; /* End of the slice */ + double x3,y3; /* Middle point of the slice */ + double x4,y4; /* End of line extending from x3,y3 */ + double x5,y5; /* Text anchor */ + double d1; /* radius to x4,y4 */ + const char *zAnc; /* Anchor point for text */ + double a1 = 0.0; /* Angle for first edge of slice */ + double a2; /* Angle for second edge */ + double a3; /* Angle at middle of slice */ + unsigned char h; /* Hue */ + const char *zClr; /* Color */ + int l; /* Large arc flag */ + int j; /* Wedge number */ + double rTotal; /* Total piechart.amt */ + double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */ + int nTotal; /* Total number of entries in piechart */ + int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */ + const char *zFg; /* foreground color for lines and text */ + int nWedgeAlloc = 0; /* Slots allocated for aWedge[] */ + int nWedge = 0; /* Slots used for aWedge[] */ + WedgeLabel *aWedge = 0; /* Labels */ + double rUprRight; /* Floor for next label in the upper right quadrant */ + double rUprLeft; /* Floor for next label in the upper left quadrant */ + double rLwrRight; /* Ceiling for label in the lower right quadrant */ + double rLwrLeft; /* Ceiling for label in the lower left quadrant */ + int i; /* Loop counter looping over wedge labels */ + +# define SATURATION 128 +# define VALUE 192 +# define OTHER_CUTOFF 90.0 +# define TEXT_HEIGHT 15.0 + + cx = 0.5*width; + cy = 0.5*height; + r2 = cx1 ){ + db_prepare(&q, "SELECT sum(amt), count(*) FROM piechart WHERE amt<:amt"); + db_bind_double(&q, ":amt", rTotal/OTHER_CUTOFF); + if( db_step(&q)==SQLITE_ROW ){ + rTooSmall = db_column_double(&q, 0); + nTooSmall = db_column_int(&q, 1); + } + db_finalize(&q); + } + if( nTooSmall>1 ){ + db_prepare(&q, "SELECT amt, label FROM piechart WHERE amt>=:limit" + " UNION ALL SELECT %.17g, '%d others';", + rTooSmall, nTooSmall); + db_bind_double(&q, ":limit", rTotal/OTHER_CUTOFF); + nTotal += 1 - nTooSmall; + }else{ + db_prepare(&q, "SELECT amt, label FROM piechart"); + } + if( nTotal<=10 ) pieFlags |= PIE_CHROMATIC; + for(j=0; db_step(&q)==SQLITE_ROW; j++){ + double x = db_column_double(&q,0)/rTotal; + const char *zLbl = db_column_text(&q,1); + /* @ */ + if( x<=0.0 ) continue; + x1 = cx + sin(a1)*r; + y1 = cy - cos(a1)*r; + a2 = a1 + x*2.0*M_PI; + x2 = cx + sin(a2)*r; + y2 = cy - cos(a2)*r; + a3 = 0.5*(a1+a2); + if( nWedge+1>nWedgeAlloc ){ + nWedgeAlloc = nWedgeAlloc*2 + 40; + aWedge = fossil_realloc(aWedge, sizeof(aWedge[0])*nWedgeAlloc); + } + if( pieFlags & PIE_PERCENT ){ + int pct = (int)(x*100.0 + 0.5); + aWedge[nWedge].z = mprintf("%s (%d%%)", zLbl, pct); + }else{ + aWedge[nWedge].z = fossil_strdup(zLbl); + } + aWedge[nWedge].rSin = sin(a3); + aWedge[nWedge].rCos = cos(a3); + nWedge++; + if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){ + h = 256*j/nTotal; + }else if( j+2=0.5; + a1 = a2; + @ + } + qsort(aWedge, nWedge, sizeof(aWedge[0]), wedgeCompare); + rUprLeft = height; + rLwrLeft = 0; + rUprRight = height; + rLwrRight = 0; + d1 = r*1.1; + for(i=0; irSin*r; + y3 = cy - p->rCos*r; + x4 = cx + p->rSin*d1; + y4 = cy - p->rCos*d1; + if( y4<=cy ){ + if( x4>=cx ){ + if( y4>rUprRight ){ + y4 = rUprRight; + } + rUprRight = y4 - TEXT_HEIGHT; + }else{ + if( y4>rUprLeft ){ + y4 = rUprLeft; + } + rUprLeft = y4 - TEXT_HEIGHT; + } + }else{ + if( x4>=cx ){ + if( y4rCos); + @ + @ %h(p->z) + fossil_free(p->z); + } + db_finalize(&q); + fossil_free(aWedge); +} + +/* +** WEBPAGE: test-piechart +** +** Generate a pie-chart based on data input from a form. +*/ +void piechart_test_page(void){ + const char *zData; + Stmt ins; + int n = 0; + int width; + int height; + int i, j; + + login_check_credentials(); + style_set_current_feature("test"); + style_header("Pie Chart Test"); + db_multi_exec("CREATE TEMP TABLE piechart(amt REAL, label TEXT);"); + db_prepare(&ins, "INSERT INTO piechart(amt,label) VALUES(:amt,:label)"); + zData = PD("data",""); + width = atoi(PD("width","800")); + height = atoi(PD("height","400")); + i = 0; + while( zData[i] ){ + double rAmt; + char *zLabel; + while( fossil_isspace(zData[i]) ){ i++; } + j = i; + while( fossil_isdigit(zData[j]) ){ j++; } + if( zData[j]=='.' ){ + j++; + while( fossil_isdigit(zData[j]) ){ j++; } + } + if( i==j ) break; + rAmt = atof(&zData[i]); + i = j; + while( zData[i]==',' || fossil_isspace(zData[i]) ){ i++; } + n++; + zLabel = mprintf("label%02d-%g", n, rAmt); + db_bind_double(&ins, ":amt", rAmt); + db_bind_text(&ins, ":label", zLabel); + db_step(&ins); + db_reset(&ins); + fossil_free(zLabel); + } + db_finalize(&ins); + if( n>1 ){ + @ + piechart_render(width,height, PIE_OTHER|PIE_PERCENT); + @ + @
+ } + @
+ @

Comma-separated list of slice widths:
+ @
+ @ Width: + @ Height:
+ @ + @

+ @

Interesting test cases: + @

+ style_finish_page(); +} -- cgit v1.3.1