Fossil SCM

Get the derivation graph working for individual file histories.

drh 2010-03-17 00:40 trunk
Commit c19467d68ecfc90d774aad670ba46ca4d79a2d69
3 files changed +44 -9 +61 -38 +38 -29
+44 -9
--- src/finfo.c
+++ src/finfo.c
@@ -100,36 +100,49 @@
100100
void finfo_page(void){
101101
Stmt q;
102102
const char *zFilename;
103103
char zPrevDate[20];
104104
Blob title;
105
+ GraphContext *pGraph;
105106
106107
login_check_credentials();
107108
if( !g.okRead ){ login_needed(); return; }
108109
style_header("File History");
109110
login_anonymous_available();
110111
111112
zPrevDate[0] = 0;
112113
zFilename = PD("name","");
113114
db_prepare(&q,
114
- "SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
115
- " coalesce(event.ecomment, event.comment),"
116
- " coalesce(event.euser, event.user),"
117
- " mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid"
115
+ "SELECT"
116
+ " substr(b.uuid,1,10),"
117
+ " datetime(event.mtime,'localtime'),"
118
+ " coalesce(event.ecomment, event.comment),"
119
+ " coalesce(event.euser, event.user),"
120
+ " mlink.pid,"
121
+ " mlink.fid,"
122
+ " mlink.mid,"
123
+ " mlink.fnid,"
124
+ " ci.uuid,"
125
+ " event.bgcolor,"
126
+ " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
127
+ " AND tagxref.rid=mlink.mid)"
118128
" FROM mlink, blob b, event, blob ci"
119129
" WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
120130
" AND b.rid=mlink.fid"
121131
" AND event.objid=mlink.mid"
122132
" AND event.objid=ci.rid"
123133
" ORDER BY event.mtime DESC",
134
+ TAG_BRANCH,
124135
zFilename
125136
);
126137
blob_zero(&title);
127138
blob_appendf(&title, "History of ");
128139
hyperlinked_path(zFilename, &title);
129140
@ <h2>%b(&title)</h2>
130141
blob_reset(&title);
142
+ pGraph = graph_init();
143
+ @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
131144
@ <table cellspacing=0 border=0 cellpadding=0>
132145
while( db_step(&q)==SQLITE_ROW ){
133146
const char *zUuid = db_column_text(&q, 0);
134147
const char *zDate = db_column_text(&q, 1);
135148
const char *zCom = db_column_text(&q, 2);
@@ -137,21 +150,33 @@
137150
int fpid = db_column_int(&q, 4);
138151
int frid = db_column_int(&q, 5);
139152
int mid = db_column_int(&q, 6);
140153
int fnid = db_column_int(&q, 7);
141154
const char *zCkin = db_column_text(&q,8);
155
+ const char *zBgClr = db_column_text(&q, 9);
156
+ const char *zBr = db_column_text(&q, 10);
157
+ int gidx;
158
+ char zTime[10];
142159
char zShort[20];
143160
char zShortCkin[20];
161
+ if( zBr==0 ) zBr = "trunk";
162
+ gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr);
144163
if( memcmp(zDate, zPrevDate, 10) ){
145164
sprintf(zPrevDate, "%.10s", zDate);
146
- @ <tr><td colspan=3>
147
- @ <div class="divider">%s(zPrevDate)</div>
165
+ @ <tr><td>
166
+ @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
148167
@ </td></tr>
149168
}
150
- @ <tr><td valign="top">%s(&zDate[11])</td>
151
- @ <td width="20"></td>
152
- @ <td valign="top" align="left">
169
+ memcpy(zTime, &zDate[11], 5);
170
+ zTime[5] = 0;
171
+ @ <tr><td valign="top" align="right">%s(zTime)</td>
172
+ @ <td width="20" align="left" valign="top"><div id="m%d(gidx)"></div></td>
173
+ if( zBgClr && zBgClr[0] ){
174
+ @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
175
+ }else{
176
+ @ <td valign="top" align="left">
177
+ }
153178
sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
154179
sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
155180
if( g.okHistory ){
156181
@ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a>
157182
}else{
@@ -170,8 +195,18 @@
170195
@ [annotate]</a>
171196
@ </td>
172197
}
173198
}
174199
db_finalize(&q);
200
+ if( pGraph ){
201
+ graph_finish(pGraph, 1);
202
+ if( pGraph->nErr ){
203
+ graph_free(pGraph);
204
+ pGraph = 0;
205
+ }else{
206
+ @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
207
+ }
208
+ }
175209
@ </table>
210
+ timeline_output_graph_javascript(pGraph);
176211
style_footer();
177212
}
178213
--- src/finfo.c
+++ src/finfo.c
@@ -100,36 +100,49 @@
100 void finfo_page(void){
101 Stmt q;
102 const char *zFilename;
103 char zPrevDate[20];
104 Blob title;
 
105
106 login_check_credentials();
107 if( !g.okRead ){ login_needed(); return; }
108 style_header("File History");
109 login_anonymous_available();
110
111 zPrevDate[0] = 0;
112 zFilename = PD("name","");
113 db_prepare(&q,
114 "SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
115 " coalesce(event.ecomment, event.comment),"
116 " coalesce(event.euser, event.user),"
117 " mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid"
 
 
 
 
 
 
 
 
 
118 " FROM mlink, blob b, event, blob ci"
119 " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
120 " AND b.rid=mlink.fid"
121 " AND event.objid=mlink.mid"
122 " AND event.objid=ci.rid"
123 " ORDER BY event.mtime DESC",
 
124 zFilename
125 );
126 blob_zero(&title);
127 blob_appendf(&title, "History of ");
128 hyperlinked_path(zFilename, &title);
129 @ <h2>%b(&title)</h2>
130 blob_reset(&title);
 
 
131 @ <table cellspacing=0 border=0 cellpadding=0>
132 while( db_step(&q)==SQLITE_ROW ){
133 const char *zUuid = db_column_text(&q, 0);
134 const char *zDate = db_column_text(&q, 1);
135 const char *zCom = db_column_text(&q, 2);
@@ -137,21 +150,33 @@
137 int fpid = db_column_int(&q, 4);
138 int frid = db_column_int(&q, 5);
139 int mid = db_column_int(&q, 6);
140 int fnid = db_column_int(&q, 7);
141 const char *zCkin = db_column_text(&q,8);
 
 
 
 
142 char zShort[20];
143 char zShortCkin[20];
 
 
144 if( memcmp(zDate, zPrevDate, 10) ){
145 sprintf(zPrevDate, "%.10s", zDate);
146 @ <tr><td colspan=3>
147 @ <div class="divider">%s(zPrevDate)</div>
148 @ </td></tr>
149 }
150 @ <tr><td valign="top">%s(&zDate[11])</td>
151 @ <td width="20"></td>
152 @ <td valign="top" align="left">
 
 
 
 
 
 
153 sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
154 sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
155 if( g.okHistory ){
156 @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a>
157 }else{
@@ -170,8 +195,18 @@
170 @ [annotate]</a>
171 @ </td>
172 }
173 }
174 db_finalize(&q);
 
 
 
 
 
 
 
 
 
175 @ </table>
 
176 style_footer();
177 }
178
--- src/finfo.c
+++ src/finfo.c
@@ -100,36 +100,49 @@
100 void finfo_page(void){
101 Stmt q;
102 const char *zFilename;
103 char zPrevDate[20];
104 Blob title;
105 GraphContext *pGraph;
106
107 login_check_credentials();
108 if( !g.okRead ){ login_needed(); return; }
109 style_header("File History");
110 login_anonymous_available();
111
112 zPrevDate[0] = 0;
113 zFilename = PD("name","");
114 db_prepare(&q,
115 "SELECT"
116 " substr(b.uuid,1,10),"
117 " datetime(event.mtime,'localtime'),"
118 " coalesce(event.ecomment, event.comment),"
119 " coalesce(event.euser, event.user),"
120 " mlink.pid,"
121 " mlink.fid,"
122 " mlink.mid,"
123 " mlink.fnid,"
124 " ci.uuid,"
125 " event.bgcolor,"
126 " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
127 " AND tagxref.rid=mlink.mid)"
128 " FROM mlink, blob b, event, blob ci"
129 " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
130 " AND b.rid=mlink.fid"
131 " AND event.objid=mlink.mid"
132 " AND event.objid=ci.rid"
133 " ORDER BY event.mtime DESC",
134 TAG_BRANCH,
135 zFilename
136 );
137 blob_zero(&title);
138 blob_appendf(&title, "History of ");
139 hyperlinked_path(zFilename, &title);
140 @ <h2>%b(&title)</h2>
141 blob_reset(&title);
142 pGraph = graph_init();
143 @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
144 @ <table cellspacing=0 border=0 cellpadding=0>
145 while( db_step(&q)==SQLITE_ROW ){
146 const char *zUuid = db_column_text(&q, 0);
147 const char *zDate = db_column_text(&q, 1);
148 const char *zCom = db_column_text(&q, 2);
@@ -137,21 +150,33 @@
150 int fpid = db_column_int(&q, 4);
151 int frid = db_column_int(&q, 5);
152 int mid = db_column_int(&q, 6);
153 int fnid = db_column_int(&q, 7);
154 const char *zCkin = db_column_text(&q,8);
155 const char *zBgClr = db_column_text(&q, 9);
156 const char *zBr = db_column_text(&q, 10);
157 int gidx;
158 char zTime[10];
159 char zShort[20];
160 char zShortCkin[20];
161 if( zBr==0 ) zBr = "trunk";
162 gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr);
163 if( memcmp(zDate, zPrevDate, 10) ){
164 sprintf(zPrevDate, "%.10s", zDate);
165 @ <tr><td>
166 @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
167 @ </td></tr>
168 }
169 memcpy(zTime, &zDate[11], 5);
170 zTime[5] = 0;
171 @ <tr><td valign="top" align="right">%s(zTime)</td>
172 @ <td width="20" align="left" valign="top"><div id="m%d(gidx)"></div></td>
173 if( zBgClr && zBgClr[0] ){
174 @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
175 }else{
176 @ <td valign="top" align="left">
177 }
178 sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
179 sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
180 if( g.okHistory ){
181 @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a>
182 }else{
@@ -170,8 +195,18 @@
195 @ [annotate]</a>
196 @ </td>
197 }
198 }
199 db_finalize(&q);
200 if( pGraph ){
201 graph_finish(pGraph, 1);
202 if( pGraph->nErr ){
203 graph_free(pGraph);
204 pGraph = 0;
205 }else{
206 @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
207 }
208 }
209 @ </table>
210 timeline_output_graph_javascript(pGraph);
211 style_footer();
212 }
213
+61 -38
--- src/graph.c
+++ src/graph.c
@@ -35,19 +35,19 @@
3535
/* The graph appears vertically beside a timeline. Each row in the
3636
** timeline corresponds to a row in the graph.
3737
*/
3838
struct GraphRow {
3939
int rid; /* The rid for the check-in */
40
- int isLeaf; /* True if the check-in is an open leaf */
4140
int nParent; /* Number of parents */
4241
int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/
4342
char *zBranch; /* Branch name */
4443
4544
GraphRow *pNext; /* Next row down in the list of all rows */
4645
GraphRow *pPrev; /* Previous row */
4746
4847
int idx; /* Row index. First is 1. 0 used for "none" */
48
+ int isLeaf; /* True if no direct child nodes */
4949
int iRail; /* Which rail this check-in appears on. 0-based.*/
5050
int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */
5151
int bDescender; /* Raiser from bottom of graph to here. */
5252
u32 mergeIn; /* Merge in from other rails */
5353
int mergeOut; /* Merge out to this rail */
@@ -63,11 +63,14 @@
6363
int mxRail; /* Number of rails required to render the graph */
6464
GraphRow *pFirst; /* First row in the list */
6565
GraphRow *pLast; /* Last row in the list */
6666
int nBranch; /* Number of distinct branches */
6767
char **azBranch; /* Names of the branches */
68
+ int nRow; /* Number of rows */
6869
int railMap[GR_MAX_RAIL]; /* Rail order mapping */
70
+ int nHash; /* Number of slots in apHash[] */
71
+ GraphRow **apHash; /* Hash table of rows */
6972
};
7073
7174
#endif
7275
7376
/*
@@ -99,12 +102,39 @@
99102
p->pFirst = pRow->pNext;
100103
free(pRow);
101104
}
102105
for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
103106
free(p->azBranch);
107
+ free(p->apHash);
104108
free(p);
105109
}
110
+
111
+/*
112
+** Insert a row into the hash table. If there is already another
113
+** row with the same rid, the other row is replaced.
114
+*/
115
+static void hashInsert(GraphContext *p, GraphRow *pRow){
116
+ int h;
117
+ h = pRow->rid % p->nHash;
118
+ while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){
119
+ h++;
120
+ if( h>=p->nHash ) h = 0;
121
+ }
122
+ p->apHash[h] = pRow;
123
+}
124
+
125
+/*
126
+** Look up the row with rid.
127
+*/
128
+static GraphRow *hashFind(GraphContext *p, int rid){
129
+ int h = rid % p->nHash;
130
+ while( p->apHash[h] && p->apHash[h]->rid!=rid ){
131
+ h++;
132
+ if( h>=p->nHash ) h = 0;
133
+ }
134
+ return p->apHash[h];
135
+}
106136
107137
/*
108138
** Return the canonical pointer for a given branch name.
109139
** Multiple calls to this routine with equivalent strings
110140
** will return the same pointer.
@@ -122,34 +152,35 @@
122152
}
123153
124154
/*
125155
** Add a new row t the graph context. Rows are added from top to bottom.
126156
*/
127
-void graph_add_row(
157
+int graph_add_row(
128158
GraphContext *p, /* The context to which the row is added */
129159
int rid, /* RID for the check-in */
130
- int isLeaf, /* True if the check-in is an leaf */
131160
int nParent, /* Number of parents */
132161
int *aParent, /* Array of parents */
133162
const char *zBranch /* Branch for this check-in */
134163
){
135164
GraphRow *pRow;
136165
137
- if( p->nErr ) return;
138
- if( nParent>GR_MAX_PARENT ){ p->nErr++; return; }
166
+ if( p->nErr ) return 0;
167
+ if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; }
139168
pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) );
140169
pRow->rid = rid;
141
- pRow->isLeaf = isLeaf;
142170
pRow->nParent = nParent;
143171
pRow->zBranch = persistBranchName(p, zBranch);
144172
memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
145173
if( p->pFirst==0 ){
146174
p->pFirst = pRow;
147175
}else{
148176
p->pLast->pNext = pRow;
149177
}
150178
p->pLast = pRow;
179
+ p->nRow++;
180
+ pRow->idx = p->nRow;
181
+ return pRow->idx;
151182
}
152183
153184
/*
154185
** Return the index of a rail currently not in use for any row between
155186
** top and bottom, inclusive.
@@ -188,51 +219,59 @@
188219
/*
189220
** Compute the complete graph
190221
*/
191222
void graph_finish(GraphContext *p, int omitDescenders){
192223
GraphRow *pRow, *pDesc;
193
- Bag allRids;
194
- Bag notLeaf;
195224
int i;
196
- int nRow;
197225
u32 mask;
198226
u32 inUse;
199227
200228
if( p==0 || p->pFirst==0 || p->nErr ) return;
201229
202230
/* Initialize all rows */
203
- bag_init(&allRids);
204
- bag_init(&notLeaf);
205
- nRow = 0;
231
+ p->nHash = p->nRow*2 + 1;
232
+ p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash );
206233
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
207234
if( pRow->pNext ) pRow->pNext->pPrev = pRow;
208
- pRow->idx = ++nRow;
209235
pRow->iRail = -1;
210236
pRow->mergeOut = -1;
211
- bag_insert(&allRids, pRow->rid);
237
+ hashInsert(p, pRow);
212238
}
213239
p->mxRail = -1;
214240
215241
/* Purge merge-parents that are out-of-graph
216242
*/
217243
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
218244
for(i=1; i<pRow->nParent; i++){
219
- if( !bag_find(&allRids, pRow->aParent[i]) ){
245
+ if( hashFind(p, pRow->aParent[i])==0 ){
220246
pRow->aParent[i] = pRow->aParent[--pRow->nParent];
221247
i--;
222248
}
223249
}
224
- if( pRow->nParent>0 && bag_find(&allRids, pRow->aParent[0]) ){
225
- bag_insert(&notLeaf, pRow->aParent[0]);
250
+ }
251
+
252
+ /* Figure out which nodes have no direct children (children on
253
+ ** the same rail). Mark such nodes is isLeaf.
254
+ */
255
+ memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash);
256
+ for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1;
257
+ for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
258
+ GraphRow *pParent;
259
+ hashInsert(p, pRow);
260
+ if( pRow->nParent>0
261
+ && (pParent = hashFind(p, pRow->aParent[0]))!=0
262
+ && pRow->zBranch==pParent->zBranch
263
+ ){
264
+ pParent->isLeaf = 0;
226265
}
227266
}
228267
229268
/* Identify rows where the primary parent is off screen. Assign
230269
** each to a rail and draw descenders to the bottom of the screen.
231270
*/
232271
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
233
- if( pRow->nParent==0 || !bag_find(&allRids,pRow->aParent[0]) ){
272
+ if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
234273
if( omitDescenders ){
235274
pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0);
236275
}else{
237276
pRow->iRail = ++p->mxRail;
238277
}
@@ -257,11 +296,10 @@
257296
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
258297
int parentRid;
259298
if( pRow->iRail>=0 ) continue;
260299
assert( pRow->nParent>0 );
261300
parentRid = pRow->aParent[0];
262
- assert( bag_find(&allRids, parentRid) );
263301
for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){}
264302
if( pDesc==0 ){
265303
/* Time skew */
266304
pRow->iRail = ++p->mxRail;
267305
pRow->railInUse = 1<<pRow->iRail;
@@ -272,14 +310,14 @@
272310
}else{
273311
pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0);
274312
}
275313
pDesc->aiRaiser[pRow->iRail] = pRow->idx;
276314
mask = 1<<pRow->iRail;
277
- if( bag_find(&notLeaf, pRow->rid) ){
278
- inUse |= mask;
315
+ if( pRow->isLeaf ){
316
+ inUse &= ~mask;
279317
}else{
280
- inUse &= ~mask;
318
+ inUse |= mask;
281319
}
282320
for(pDesc = pRow; ; pDesc=pDesc->pNext){
283321
assert( pDesc!=0 );
284322
pDesc->railInUse |= mask;
285323
if( pDesc->rid==parentRid ) break;
@@ -309,29 +347,14 @@
309347
pRow->mergeIn |= 1<<pDesc->mergeOut;
310348
}
311349
}
312350
313351
/*
314
- ** Sort the rail numbers
352
+ ** Find the maximum rail number.
315353
*/
316
-#if 0
317
- p->mxRail = -1;
318
- mask = 0;
319
- for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
320
- if( (mask & (1<<pRow->iRail))==0 ){
321
- p->railMap[pRow->iRail] = ++p->mxRail;
322
- mask |= 1<<pRow->iRail;
323
- }
324
- if( pRow->mergeOut>=0 && (mask & (1<<pRow->mergeOut))==0 ){
325
- p->railMap[pRow->mergeOut] = ++p->mxRail;
326
- mask |= 1<<pRow->mergeOut;
327
- }
328
- }
329
-#else
330354
for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i;
331355
p->mxRail = 0;
332356
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
333357
if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
334358
if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
335359
}
336
-#endif
337360
}
338361
--- src/graph.c
+++ src/graph.c
@@ -35,19 +35,19 @@
35 /* The graph appears vertically beside a timeline. Each row in the
36 ** timeline corresponds to a row in the graph.
37 */
38 struct GraphRow {
39 int rid; /* The rid for the check-in */
40 int isLeaf; /* True if the check-in is an open leaf */
41 int nParent; /* Number of parents */
42 int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/
43 char *zBranch; /* Branch name */
44
45 GraphRow *pNext; /* Next row down in the list of all rows */
46 GraphRow *pPrev; /* Previous row */
47
48 int idx; /* Row index. First is 1. 0 used for "none" */
 
49 int iRail; /* Which rail this check-in appears on. 0-based.*/
50 int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */
51 int bDescender; /* Raiser from bottom of graph to here. */
52 u32 mergeIn; /* Merge in from other rails */
53 int mergeOut; /* Merge out to this rail */
@@ -63,11 +63,14 @@
63 int mxRail; /* Number of rails required to render the graph */
64 GraphRow *pFirst; /* First row in the list */
65 GraphRow *pLast; /* Last row in the list */
66 int nBranch; /* Number of distinct branches */
67 char **azBranch; /* Names of the branches */
 
68 int railMap[GR_MAX_RAIL]; /* Rail order mapping */
 
 
69 };
70
71 #endif
72
73 /*
@@ -99,12 +102,39 @@
99 p->pFirst = pRow->pNext;
100 free(pRow);
101 }
102 for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
103 free(p->azBranch);
 
104 free(p);
105 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
107 /*
108 ** Return the canonical pointer for a given branch name.
109 ** Multiple calls to this routine with equivalent strings
110 ** will return the same pointer.
@@ -122,34 +152,35 @@
122 }
123
124 /*
125 ** Add a new row t the graph context. Rows are added from top to bottom.
126 */
127 void graph_add_row(
128 GraphContext *p, /* The context to which the row is added */
129 int rid, /* RID for the check-in */
130 int isLeaf, /* True if the check-in is an leaf */
131 int nParent, /* Number of parents */
132 int *aParent, /* Array of parents */
133 const char *zBranch /* Branch for this check-in */
134 ){
135 GraphRow *pRow;
136
137 if( p->nErr ) return;
138 if( nParent>GR_MAX_PARENT ){ p->nErr++; return; }
139 pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) );
140 pRow->rid = rid;
141 pRow->isLeaf = isLeaf;
142 pRow->nParent = nParent;
143 pRow->zBranch = persistBranchName(p, zBranch);
144 memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
145 if( p->pFirst==0 ){
146 p->pFirst = pRow;
147 }else{
148 p->pLast->pNext = pRow;
149 }
150 p->pLast = pRow;
 
 
 
151 }
152
153 /*
154 ** Return the index of a rail currently not in use for any row between
155 ** top and bottom, inclusive.
@@ -188,51 +219,59 @@
188 /*
189 ** Compute the complete graph
190 */
191 void graph_finish(GraphContext *p, int omitDescenders){
192 GraphRow *pRow, *pDesc;
193 Bag allRids;
194 Bag notLeaf;
195 int i;
196 int nRow;
197 u32 mask;
198 u32 inUse;
199
200 if( p==0 || p->pFirst==0 || p->nErr ) return;
201
202 /* Initialize all rows */
203 bag_init(&allRids);
204 bag_init(&notLeaf);
205 nRow = 0;
206 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
207 if( pRow->pNext ) pRow->pNext->pPrev = pRow;
208 pRow->idx = ++nRow;
209 pRow->iRail = -1;
210 pRow->mergeOut = -1;
211 bag_insert(&allRids, pRow->rid);
212 }
213 p->mxRail = -1;
214
215 /* Purge merge-parents that are out-of-graph
216 */
217 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
218 for(i=1; i<pRow->nParent; i++){
219 if( !bag_find(&allRids, pRow->aParent[i]) ){
220 pRow->aParent[i] = pRow->aParent[--pRow->nParent];
221 i--;
222 }
223 }
224 if( pRow->nParent>0 && bag_find(&allRids, pRow->aParent[0]) ){
225 bag_insert(&notLeaf, pRow->aParent[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
226 }
227 }
228
229 /* Identify rows where the primary parent is off screen. Assign
230 ** each to a rail and draw descenders to the bottom of the screen.
231 */
232 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
233 if( pRow->nParent==0 || !bag_find(&allRids,pRow->aParent[0]) ){
234 if( omitDescenders ){
235 pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0);
236 }else{
237 pRow->iRail = ++p->mxRail;
238 }
@@ -257,11 +296,10 @@
257 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
258 int parentRid;
259 if( pRow->iRail>=0 ) continue;
260 assert( pRow->nParent>0 );
261 parentRid = pRow->aParent[0];
262 assert( bag_find(&allRids, parentRid) );
263 for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){}
264 if( pDesc==0 ){
265 /* Time skew */
266 pRow->iRail = ++p->mxRail;
267 pRow->railInUse = 1<<pRow->iRail;
@@ -272,14 +310,14 @@
272 }else{
273 pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0);
274 }
275 pDesc->aiRaiser[pRow->iRail] = pRow->idx;
276 mask = 1<<pRow->iRail;
277 if( bag_find(&notLeaf, pRow->rid) ){
278 inUse |= mask;
279 }else{
280 inUse &= ~mask;
281 }
282 for(pDesc = pRow; ; pDesc=pDesc->pNext){
283 assert( pDesc!=0 );
284 pDesc->railInUse |= mask;
285 if( pDesc->rid==parentRid ) break;
@@ -309,29 +347,14 @@
309 pRow->mergeIn |= 1<<pDesc->mergeOut;
310 }
311 }
312
313 /*
314 ** Sort the rail numbers
315 */
316 #if 0
317 p->mxRail = -1;
318 mask = 0;
319 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
320 if( (mask & (1<<pRow->iRail))==0 ){
321 p->railMap[pRow->iRail] = ++p->mxRail;
322 mask |= 1<<pRow->iRail;
323 }
324 if( pRow->mergeOut>=0 && (mask & (1<<pRow->mergeOut))==0 ){
325 p->railMap[pRow->mergeOut] = ++p->mxRail;
326 mask |= 1<<pRow->mergeOut;
327 }
328 }
329 #else
330 for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i;
331 p->mxRail = 0;
332 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
333 if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
334 if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
335 }
336 #endif
337 }
338
--- src/graph.c
+++ src/graph.c
@@ -35,19 +35,19 @@
35 /* The graph appears vertically beside a timeline. Each row in the
36 ** timeline corresponds to a row in the graph.
37 */
38 struct GraphRow {
39 int rid; /* The rid for the check-in */
 
40 int nParent; /* Number of parents */
41 int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/
42 char *zBranch; /* Branch name */
43
44 GraphRow *pNext; /* Next row down in the list of all rows */
45 GraphRow *pPrev; /* Previous row */
46
47 int idx; /* Row index. First is 1. 0 used for "none" */
48 int isLeaf; /* True if no direct child nodes */
49 int iRail; /* Which rail this check-in appears on. 0-based.*/
50 int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */
51 int bDescender; /* Raiser from bottom of graph to here. */
52 u32 mergeIn; /* Merge in from other rails */
53 int mergeOut; /* Merge out to this rail */
@@ -63,11 +63,14 @@
63 int mxRail; /* Number of rails required to render the graph */
64 GraphRow *pFirst; /* First row in the list */
65 GraphRow *pLast; /* Last row in the list */
66 int nBranch; /* Number of distinct branches */
67 char **azBranch; /* Names of the branches */
68 int nRow; /* Number of rows */
69 int railMap[GR_MAX_RAIL]; /* Rail order mapping */
70 int nHash; /* Number of slots in apHash[] */
71 GraphRow **apHash; /* Hash table of rows */
72 };
73
74 #endif
75
76 /*
@@ -99,12 +102,39 @@
102 p->pFirst = pRow->pNext;
103 free(pRow);
104 }
105 for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
106 free(p->azBranch);
107 free(p->apHash);
108 free(p);
109 }
110
111 /*
112 ** Insert a row into the hash table. If there is already another
113 ** row with the same rid, the other row is replaced.
114 */
115 static void hashInsert(GraphContext *p, GraphRow *pRow){
116 int h;
117 h = pRow->rid % p->nHash;
118 while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){
119 h++;
120 if( h>=p->nHash ) h = 0;
121 }
122 p->apHash[h] = pRow;
123 }
124
125 /*
126 ** Look up the row with rid.
127 */
128 static GraphRow *hashFind(GraphContext *p, int rid){
129 int h = rid % p->nHash;
130 while( p->apHash[h] && p->apHash[h]->rid!=rid ){
131 h++;
132 if( h>=p->nHash ) h = 0;
133 }
134 return p->apHash[h];
135 }
136
137 /*
138 ** Return the canonical pointer for a given branch name.
139 ** Multiple calls to this routine with equivalent strings
140 ** will return the same pointer.
@@ -122,34 +152,35 @@
152 }
153
154 /*
155 ** Add a new row t the graph context. Rows are added from top to bottom.
156 */
157 int graph_add_row(
158 GraphContext *p, /* The context to which the row is added */
159 int rid, /* RID for the check-in */
 
160 int nParent, /* Number of parents */
161 int *aParent, /* Array of parents */
162 const char *zBranch /* Branch for this check-in */
163 ){
164 GraphRow *pRow;
165
166 if( p->nErr ) return 0;
167 if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; }
168 pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) );
169 pRow->rid = rid;
 
170 pRow->nParent = nParent;
171 pRow->zBranch = persistBranchName(p, zBranch);
172 memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
173 if( p->pFirst==0 ){
174 p->pFirst = pRow;
175 }else{
176 p->pLast->pNext = pRow;
177 }
178 p->pLast = pRow;
179 p->nRow++;
180 pRow->idx = p->nRow;
181 return pRow->idx;
182 }
183
184 /*
185 ** Return the index of a rail currently not in use for any row between
186 ** top and bottom, inclusive.
@@ -188,51 +219,59 @@
219 /*
220 ** Compute the complete graph
221 */
222 void graph_finish(GraphContext *p, int omitDescenders){
223 GraphRow *pRow, *pDesc;
 
 
224 int i;
 
225 u32 mask;
226 u32 inUse;
227
228 if( p==0 || p->pFirst==0 || p->nErr ) return;
229
230 /* Initialize all rows */
231 p->nHash = p->nRow*2 + 1;
232 p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash );
 
233 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
234 if( pRow->pNext ) pRow->pNext->pPrev = pRow;
 
235 pRow->iRail = -1;
236 pRow->mergeOut = -1;
237 hashInsert(p, pRow);
238 }
239 p->mxRail = -1;
240
241 /* Purge merge-parents that are out-of-graph
242 */
243 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
244 for(i=1; i<pRow->nParent; i++){
245 if( hashFind(p, pRow->aParent[i])==0 ){
246 pRow->aParent[i] = pRow->aParent[--pRow->nParent];
247 i--;
248 }
249 }
250 }
251
252 /* Figure out which nodes have no direct children (children on
253 ** the same rail). Mark such nodes is isLeaf.
254 */
255 memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash);
256 for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1;
257 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
258 GraphRow *pParent;
259 hashInsert(p, pRow);
260 if( pRow->nParent>0
261 && (pParent = hashFind(p, pRow->aParent[0]))!=0
262 && pRow->zBranch==pParent->zBranch
263 ){
264 pParent->isLeaf = 0;
265 }
266 }
267
268 /* Identify rows where the primary parent is off screen. Assign
269 ** each to a rail and draw descenders to the bottom of the screen.
270 */
271 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
272 if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
273 if( omitDescenders ){
274 pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0);
275 }else{
276 pRow->iRail = ++p->mxRail;
277 }
@@ -257,11 +296,10 @@
296 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
297 int parentRid;
298 if( pRow->iRail>=0 ) continue;
299 assert( pRow->nParent>0 );
300 parentRid = pRow->aParent[0];
 
301 for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){}
302 if( pDesc==0 ){
303 /* Time skew */
304 pRow->iRail = ++p->mxRail;
305 pRow->railInUse = 1<<pRow->iRail;
@@ -272,14 +310,14 @@
310 }else{
311 pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0);
312 }
313 pDesc->aiRaiser[pRow->iRail] = pRow->idx;
314 mask = 1<<pRow->iRail;
315 if( pRow->isLeaf ){
316 inUse &= ~mask;
317 }else{
318 inUse |= mask;
319 }
320 for(pDesc = pRow; ; pDesc=pDesc->pNext){
321 assert( pDesc!=0 );
322 pDesc->railInUse |= mask;
323 if( pDesc->rid==parentRid ) break;
@@ -309,29 +347,14 @@
347 pRow->mergeIn |= 1<<pDesc->mergeOut;
348 }
349 }
350
351 /*
352 ** Find the maximum rail number.
353 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354 for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i;
355 p->mxRail = 0;
356 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
357 if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
358 if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
359 }
 
360 }
361
+38 -29
--- src/timeline.c
+++ src/timeline.c
@@ -253,11 +253,39 @@
253253
memcpy(zTime, &zDate[11], 5);
254254
zTime[5] = 0;
255255
@ <tr>
256256
@ <td valign="top" align="right">%s(zTime)</td>
257257
@ <td width="20" align="left" valign="top">
258
- @ <div id="m%d(rid)"></div>
258
+ if( pGraph ){
259
+ int nParent = 0;
260
+ int aParent[32];
261
+ const char *zBr;
262
+ int gidx;
263
+ static Stmt qparent;
264
+ static Stmt qbranch;
265
+ db_static_prepare(&qparent,
266
+ "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
267
+ );
268
+ db_static_prepare(&qbranch,
269
+ "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
270
+ TAG_BRANCH
271
+ );
272
+ db_bind_int(&qparent, ":rid", rid);
273
+ while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
274
+ aParent[nParent++] = db_column_int(&qparent, 0);
275
+ }
276
+ db_reset(&qparent);
277
+ db_bind_int(&qbranch, ":rid", rid);
278
+ if( db_step(&qbranch)==SQLITE_ROW ){
279
+ zBr = db_column_text(&qbranch, 0);
280
+ }else{
281
+ zBr = "trunk";
282
+ }
283
+ gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr);
284
+ db_reset(&qbranch);
285
+ @ <div id="m%d(gidx)"></div>
286
+ }
259287
if( zBgClr && zBgClr[0] ){
260288
@ <td valign="top" align="left" bgcolor="%h(zBgClr)">
261289
}else{
262290
@ <td valign="top" align="left">
263291
}
@@ -290,37 +318,10 @@
290318
int i;
291319
for(i=0; i<nTag; i++){
292320
@ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
293321
}
294322
}
295
- if( pGraph ){
296
- int nParent = 0;
297
- int aParent[32];
298
- const char *zBr;
299
- static Stmt qparent;
300
- static Stmt qbranch;
301
- db_static_prepare(&qparent,
302
- "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
303
- );
304
- db_static_prepare(&qbranch,
305
- "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
306
- TAG_BRANCH
307
- );
308
- db_bind_int(&qparent, ":rid", rid);
309
- while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
310
- aParent[nParent++] = db_column_int(&qparent, 0);
311
- }
312
- db_reset(&qparent);
313
- db_bind_int(&qbranch, ":rid", rid);
314
- if( db_step(&qbranch)==SQLITE_ROW ){
315
- zBr = db_column_text(&qbranch, 0);
316
- }else{
317
- zBr = "trunk";
318
- }
319
- graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr);
320
- db_reset(&qbranch);
321
- }
322323
}else if( (tmFlags & TIMELINE_ARTID)!=0 ){
323324
hyperlink_to_uuid(zUuid);
324325
}
325326
db_column_blob(pQuery, commentColumn, &comment);
326327
if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -358,19 +359,27 @@
358359
}else{
359360
@ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
360361
}
361362
}
362363
@ </table>
364
+ timeline_output_graph_javascript(pGraph);
365
+}
366
+
367
+/*
368
+** Generate all of the necessary javascript to generate a timeline
369
+** graph.
370
+*/
371
+void timeline_output_graph_javascript(GraphContext *pGraph){
363372
if( pGraph && pGraph->nErr==0 ){
364373
GraphRow *pRow;
365374
int i;
366375
char cSep;
367376
@ <script type="text/JavaScript">
368377
cgi_printf("var rowinfo = [\n");
369378
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
370379
cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
371
- pRow->rid,
380
+ pRow->idx,
372381
pRow->iRail,
373382
pRow->bDescender,
374383
pRow->mergeOut,
375384
pRow->mergeUpto,
376385
pRow->aiRaiser[pRow->iRail]
377386
--- src/timeline.c
+++ src/timeline.c
@@ -253,11 +253,39 @@
253 memcpy(zTime, &zDate[11], 5);
254 zTime[5] = 0;
255 @ <tr>
256 @ <td valign="top" align="right">%s(zTime)</td>
257 @ <td width="20" align="left" valign="top">
258 @ <div id="m%d(rid)"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259 if( zBgClr && zBgClr[0] ){
260 @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
261 }else{
262 @ <td valign="top" align="left">
263 }
@@ -290,37 +318,10 @@
290 int i;
291 for(i=0; i<nTag; i++){
292 @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
293 }
294 }
295 if( pGraph ){
296 int nParent = 0;
297 int aParent[32];
298 const char *zBr;
299 static Stmt qparent;
300 static Stmt qbranch;
301 db_static_prepare(&qparent,
302 "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
303 );
304 db_static_prepare(&qbranch,
305 "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
306 TAG_BRANCH
307 );
308 db_bind_int(&qparent, ":rid", rid);
309 while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
310 aParent[nParent++] = db_column_int(&qparent, 0);
311 }
312 db_reset(&qparent);
313 db_bind_int(&qbranch, ":rid", rid);
314 if( db_step(&qbranch)==SQLITE_ROW ){
315 zBr = db_column_text(&qbranch, 0);
316 }else{
317 zBr = "trunk";
318 }
319 graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr);
320 db_reset(&qbranch);
321 }
322 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
323 hyperlink_to_uuid(zUuid);
324 }
325 db_column_blob(pQuery, commentColumn, &comment);
326 if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -358,19 +359,27 @@
358 }else{
359 @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
360 }
361 }
362 @ </table>
 
 
 
 
 
 
 
 
363 if( pGraph && pGraph->nErr==0 ){
364 GraphRow *pRow;
365 int i;
366 char cSep;
367 @ <script type="text/JavaScript">
368 cgi_printf("var rowinfo = [\n");
369 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
370 cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
371 pRow->rid,
372 pRow->iRail,
373 pRow->bDescender,
374 pRow->mergeOut,
375 pRow->mergeUpto,
376 pRow->aiRaiser[pRow->iRail]
377
--- src/timeline.c
+++ src/timeline.c
@@ -253,11 +253,39 @@
253 memcpy(zTime, &zDate[11], 5);
254 zTime[5] = 0;
255 @ <tr>
256 @ <td valign="top" align="right">%s(zTime)</td>
257 @ <td width="20" align="left" valign="top">
258 if( pGraph ){
259 int nParent = 0;
260 int aParent[32];
261 const char *zBr;
262 int gidx;
263 static Stmt qparent;
264 static Stmt qbranch;
265 db_static_prepare(&qparent,
266 "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
267 );
268 db_static_prepare(&qbranch,
269 "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
270 TAG_BRANCH
271 );
272 db_bind_int(&qparent, ":rid", rid);
273 while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
274 aParent[nParent++] = db_column_int(&qparent, 0);
275 }
276 db_reset(&qparent);
277 db_bind_int(&qbranch, ":rid", rid);
278 if( db_step(&qbranch)==SQLITE_ROW ){
279 zBr = db_column_text(&qbranch, 0);
280 }else{
281 zBr = "trunk";
282 }
283 gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr);
284 db_reset(&qbranch);
285 @ <div id="m%d(gidx)"></div>
286 }
287 if( zBgClr && zBgClr[0] ){
288 @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
289 }else{
290 @ <td valign="top" align="left">
291 }
@@ -290,37 +318,10 @@
318 int i;
319 for(i=0; i<nTag; i++){
320 @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
321 }
322 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
324 hyperlink_to_uuid(zUuid);
325 }
326 db_column_blob(pQuery, commentColumn, &comment);
327 if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -358,19 +359,27 @@
359 }else{
360 @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
361 }
362 }
363 @ </table>
364 timeline_output_graph_javascript(pGraph);
365 }
366
367 /*
368 ** Generate all of the necessary javascript to generate a timeline
369 ** graph.
370 */
371 void timeline_output_graph_javascript(GraphContext *pGraph){
372 if( pGraph && pGraph->nErr==0 ){
373 GraphRow *pRow;
374 int i;
375 char cSep;
376 @ <script type="text/JavaScript">
377 cgi_printf("var rowinfo = [\n");
378 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
379 cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
380 pRow->idx,
381 pRow->iRail,
382 pRow->bDescender,
383 pRow->mergeOut,
384 pRow->mergeUpto,
385 pRow->aiRaiser[pRow->iRail]
386

Keyboard Shortcuts

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