├── sample.png ├── README.md ├── dots.js ├── ChangeGraphs.php └── Raphael.js /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/MediaWiki-Changelog-Graphs/master/sample.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MediaWiki Changelog Graphs 2 | ========================== 3 | 4 | Adds a special page showing a chart of wiki edits broken down by day, hour and month 5 | 6 |  7 | 8 | 9 | How to Install 10 | ============== 11 | * Download this tarball and extract the contents to `$IP/extensions/CollapsableText` where `$IP` is your root wiki install 12 | * Add `require( "extensions/ChangeGraphs/ChangeGraphs.php");` to your LocalSettings 13 | * Visit Special:ChangeGraphs 14 | 15 | License 16 | ======= 17 | 18 | Copyright 2011 by Aaron Parecki 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining 21 | a copy of this software and associated documentation files (the 22 | "Software"), to deal in the Software without restriction, including 23 | without limitation the rights to use, copy, modify, merge, publish, 24 | distribute, sublicense, and/or sell copies of the Software, and to 25 | permit persons to whom the Software is furnished to do so, subject to 26 | the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be 29 | included in all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 32 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 33 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 34 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 35 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 36 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 37 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | 39 | 40 | Raphael.js 41 | ========== 42 | 43 | Copyright © 2008 Dmitry Baranovskiy 44 | 45 | http://raphaeljs.com/license.html 46 | 47 | -------------------------------------------------------------------------------- /dots.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){ 3 | $.fn.dotChart = function(targetID) { 4 | // Grab the data 5 | var data = [], 6 | axisx = [], 7 | axisy = [], 8 | table = $(this); 9 | $("tbody td", table).each(function (i) { 10 | data.push(parseFloat($(this).text(), 10)); 11 | }); 12 | table.hide(); 13 | $("tbody th", table).each(function () { 14 | axisy.push($(this).text()); 15 | }); 16 | $("tfoot th", table).each(function () { 17 | axisx.push($(this).text()); 18 | }); 19 | // Draw 20 | var width = 700, 21 | height = 300, 22 | leftgutter = 30, 23 | bottomgutter = 20, 24 | r = Raphael(targetID, width, height), 25 | txt = {"font": '10px Fontin-Sans, Arial', stroke: "none", fill: "#000"}, 26 | X = (width - leftgutter) / axisx.length, 27 | Y = (height - bottomgutter) / axisy.length, 28 | color = $("#"+targetID).css("color"); 29 | max = Math.round(X / 2) - 1; 30 | // r.rect(0, 0, width, height, 5).attr({fill: "#000", stroke: "none"}); 31 | for (var i = 0, ii = axisx.length; i < ii; i++) { 32 | r.text(leftgutter + X * (i + .5), 294, axisx[i]).attr(txt); 33 | } 34 | for (var i = 0, ii = axisy.length; i < ii; i++) { 35 | r.text(10, Y * (i + .5), axisy[i]).attr(txt); 36 | } 37 | var o = 0; 38 | for (var i = 0, ii = axisy.length; i < ii; i++) { 39 | for (var j = 0, jj = axisx.length; j < jj; j++) { 40 | var R = data[o] && Math.min(Math.round(Math.sqrt(data[o] / Math.PI) * 4), max); 41 | if (R) { 42 | (function (dx, dy, R, value) { 43 | // var color = "hsb(" + [((R / max) * .59 + 0.16) % 1, 1, .75] + ")"; 44 | var color = "hsb(" + [((R / max) * .25), 1, .75] + ")"; 45 | var dt = r.circle(dx + 60 + R, dy + 10, R).attr({stroke: "none", fill: color}); 46 | if (R < 6) { 47 | var bg = r.circle(dx + 60 + R, dy + 10, 6).attr({stroke: "none", fill: "#000", opacity: .4}).hide(); 48 | } 49 | var lbl = r.text(dx + 60 + R, dy + 10, data[o]) 50 | .attr({"font": '10px Fontin-Sans, Arial', stroke: "none", fill: "#fff"}).hide(); 51 | var dot = r.circle(dx + 60 + R, dy + 10, max).attr({stroke: "none", fill: "#000", opacity: 0}); 52 | dot[0].onmouseover = function () { 53 | if (bg) { 54 | bg.show(); 55 | } else { 56 | var clr = Raphael.rgb2hsb(color); 57 | clr.b = .5; 58 | dt.attr("fill", Raphael.hsb2rgb(clr).hex); 59 | } 60 | lbl.show(); 61 | }; 62 | dot[0].onmouseout = function () { 63 | if (bg) { 64 | bg.hide(); 65 | } else { 66 | dt.attr("fill", color); 67 | } 68 | lbl.hide(); 69 | }; 70 | })(leftgutter + X * (j + .5) - 60 - R, Y * (i + .5) - 10, R, data[o]); 71 | } 72 | o++; 73 | } 74 | } 75 | 76 | }; 77 | 78 | })(jQuery); 79 | 80 | -------------------------------------------------------------------------------- /ChangeGraphs.php: -------------------------------------------------------------------------------- 1 | addScriptFile($wgScriptPath . '/extensions/ChangeGraphs/Raphael.js'); 20 | $wgOut->addScriptFile($wgScriptPath . '/extensions/ChangeGraphs/dots.js'); 21 | $wgOut->addInlineScript(' 22 | $(function(){ 23 | $("#dotchart1").dotChart("chart1"); 24 | $("#dotchart2").dotChart("chart2"); 25 | }); 26 | '); 27 | return true; 28 | } 29 | 30 | class pkSpecialChangeGraphs extends SpecialPage 31 | { 32 | function __construct() 33 | { 34 | SpecialPage::SpecialPage('ChangeGraphs'); 35 | } 36 | 37 | function execute() 38 | { 39 | global $wgOut, $wgAction, $wgRequest; 40 | 41 | $wgOut->setPageTitle('Changelog Graphs'); 42 | 43 | $this->dayVsHour(); 44 | $this->monthVsDay(); 45 | } 46 | 47 | 48 | function monthVsDay() 49 | { 50 | global $wgOut; 51 | 52 | $db = wfGetDB(DB_SLAVE); 53 | $res = $db->select('revision', 54 | array('DAYOFWEEK(rev_timestamp) AS `day`', 'MONTH(rev_timestamp) AS `month`', 'COUNT(1) AS `num`'), 55 | '', 56 | __METHOD__, 57 | array('GROUP BY' => 'DAYOFWEEK(rev_timestamp), MONTH(rev_timestamp)')); 58 | 59 | ob_start(); 60 | 61 | $data = array(); 62 | for($d=1; $d<=7; $d++) 63 | for($m=1; $m<=12; $m++) 64 | $data[$d][$m] = 0; 65 | 66 | foreach($res as $row) { 67 | $data[$row->day][$row->month] = $row->num; 68 | } 69 | 70 | echo '
| ' . $this->monthName($m) . ' | '; 75 | echo '|
|---|---|
| ' . $this->dayName($day) . ' | '; 81 | foreach($row as $month=>$num) { 82 | echo '' . $num . ' | '; 83 | } 84 | echo '
| '; 120 | for($h=0; $h<24; $h++) 121 | echo ' | ' . $this->hourName($h) . ' | '; 122 | echo '
|---|---|
| ' . $this->dayName($day) . ' | '; 128 | foreach($row as $hour=>$num) { 129 | echo '' . $num . ' | '; 130 | } 131 | echo '
1){x=y.sqrt(x);c=x*c;d=x*d}var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=a e){if(c&&!l.start){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k;k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v]();n+=j;g=+i[5];h=+i[6];continue}if(!b&&!c){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j;g=+i[5];h=+i[6]}k+=i}l.end=k;m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1);m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cs=cr(1),ct=cr(),cu=cr(0,1);bO.getTotalLength=function(){if(this.type!="path")return;if(this.node.getTotalLength)return this.node.getTotalLength();return cs(this.attrs.path)};bO.getPointAtLength=function(a){if(this.type!="path")return;return ct(this.attrs.path,a)};bO.getSubpath=function(a,b){if(this.type!="path")return;if(B(this.getTotalLength()-b)<"1e-6")return cu(this.attrs.path,a).end;var c=cu(this.attrs.path,b,1);return a?cu(c,a).end:c};a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=0.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*(2*D)/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;if(a<1/c)d=b*a*a;else if(a<2/c){a-=1.5/c;d=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;d=b*a*a+0.9375}else{a-=2.625/c;d=b*a*a+0.984375}return d}};var cv=[],cw=function(){var b=+(new Date);for(var c=0;c