This commit is contained in:
Luc Didry
2014-02-20 00:27:23 +01:00
parent 8eb2c200c8
commit b2a408a207
20 changed files with 4354 additions and 60 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ lutim.db
script/hypnotoad.pid
local/*
files/*
templates/data.html.ep

View File

@@ -107,7 +107,7 @@ vim de.pm
There's just a few sentences, so it will be quick to translate. Please consider to send me you language file in order to help the other users :smile:.
##Others projects dependancies
LUTIm is written in Perl with the Mojolicious framework, uses the Twitter bootstrap framework to look not too ugly, JQuery and JQuery File Uploader (<https://github.com/danielm/uploader/>) to add some modernity.
LUTIm is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [SimpleGraph](http://benaskins.github.io/simplegraph/) for stats graphs.
##Official instance
You can see it working at http://lut.im.

View File

@@ -362,6 +362,7 @@
"css": [
"print.less",
"type.less",
"tables.less",
"forms.less",
"buttons.less",
"input-groups.less",

View File

@@ -4,3 +4,4 @@ requires 'Mojolicious::Plugin::I18N';
requires 'ORLite';
requires 'File::Type';
requires 'Text::Unidecode';
requires 'DateTime';

File diff suppressed because it is too large Load Diff

View File

@@ -137,6 +137,22 @@ sub startup {
}
)->name('about');
$r->get('/stats' => sub {
my $c = shift;
$c->render(
template => 'stats',
total => LutimModel::Lutim->count('WHERE path IS NOT NULL')
);
# Check provisioning
$c->on(finish => sub {
shift->provisioning();
}
);
}
)->name('stats');
$r->post('/' => sub {
my $c = shift;
my $upload = $c->param('file');

View File

@@ -24,32 +24,35 @@ my $inf_body = <<EOF;
EOF
our %Lexicon = (
'license' => 'License:',
'fork-me' => 'Fork me on Github !',
'share-twitter' => 'Share on Twitter',
'informations' => 'Informations',
'informations-body' => $inf_body,
'view-link' => 'View link:',
'download-link' => 'Download link:',
'twitter-link' => 'Link for put in a tweet:',
'some-bad' => 'Something bad happened',
'delete-first' => 'Delete at first view?',
'delete-day' => 'Delete after 24 hours?',
'upload_image' => 'Send an image',
'image-only' => 'Only images are allowed',
'go' => 'Let\'s go!',
'drag-n-drop' => 'Drag & drop images here',
'or' => '-or-',
'file-browser' => 'Click to open the file browser',
'image_not_found' => 'Unable to find the image',
'no_more_short' => 'There is no more available URL. Retry or contact the administrator. [_1]',
'no_valid_file' => 'The file [_1] is not an image.',
'file_too_big' => 'The file exceed the size limit ([_1])',
'no_time_limit' => 'No time limit',
'24_hours' => '24 hours',
'7_days' => '7 days',
'30_days' => '30 days',
'1_year' => 'One year',
'license' => 'License:',
'fork-me' => 'Fork me on Github !',
'share-twitter' => 'Share on Twitter',
'informations' => 'Informations',
'informations-body' => $inf_body,
'view-link' => 'View link:',
'download-link' => 'Download link:',
'twitter-link' => 'Link for put in a tweet:',
'some-bad' => 'Something bad happened',
'delete-first' => 'Delete at first view?',
'delete-day' => 'Delete after 24 hours?',
'upload_image' => 'Send an image',
'image-only' => 'Only images are allowed',
'go' => 'Let\'s go!',
'drag-n-drop' => 'Drag & drop images here',
'or' => '-or-',
'file-browser' => 'Click to open the file browser',
'image_not_found' => 'Unable to find the image',
'no_more_short' => 'There is no more available URL. Retry or contact the administrator. [_1]',
'no_valid_file' => 'The file [_1] is not an image.',
'file_too_big' => 'The file exceed the size limit ([_1])',
'no_time_limit' => 'No time limit',
'24_hours' => '24 hours',
'7_days' => '7 days',
'30_days' => '30 days',
'1_year' => 'One year',
'pushed-images' => ' sent images on this instance from beginning.',
'graph-data-once-a-day' => 'The graph\'s datas are not updated in real-time.',
'lutim-stats' => 'LUTIm\'s statistics',
'back-to-index' => 'Back to homepage'
);

View File

@@ -24,32 +24,35 @@ my $inf_body = <<EOF;
EOF
our %Lexicon = (
'license' => 'Licence :',
'fork-me' => 'Fork me on Github',
'share-twitter' => 'Partager sur Twitter',
'informations' => 'Informations',
'informations-body' => $inf_body,
'view-link' => 'Lien d\'affichage :',
'download-link' => 'Lien de téléchargement :',
'twitter-link' => 'Lien pour mettre dans un tweet :',
'some-bad' => 'Un problème est survenu',
'delete-first' => 'Supprimer au premier accès ?',
'delete-day' => 'Supprimer après 24 heures ?',
'upload_image' => 'Envoyez une image',
'image-only' => 'Seules les images sont acceptées',
'go' => 'Allons-y !',
'drag-n-drop' => 'Déposez vos images ici',
'or' => '-ou-',
'file-browser' => 'Cliquez pour utiliser le navigateur de fichier',
'image_not_found' => 'Impossible de trouver l\'image',
'no_more_short' => 'Il n\'y a plus d\'URL disponible. Veuillez réessayer ou contactez l\'administrateur. [_1].',
'no_valid_file' => 'Le fichier [_1] n\'est pas une image.',
'file_too_big' => 'Le fichier dépasse la limite de taille ([_1])',
'no_time_limit' => 'Pas de limitation de durée',
'24_hours' => '24 heures',
'7_days' => '7 jours',
'30_days' => '30 jours',
'1_year' => 'Un an',
'license' => 'Licence :',
'fork-me' => 'Fork me on Github',
'share-twitter' => 'Partager sur Twitter',
'informations' => 'Informations',
'informations-body' => $inf_body,
'view-link' => 'Lien d\'affichage :',
'download-link' => 'Lien de téléchargement :',
'twitter-link' => 'Lien pour mettre dans un tweet :',
'some-bad' => 'Un problème est survenu',
'delete-first' => 'Supprimer au premier accès ?',
'delete-day' => 'Supprimer après 24 heures ?',
'upload_image' => 'Envoyez une image',
'image-only' => 'Seules les images sont acceptées',
'go' => 'Allons-y !',
'drag-n-drop' => 'Déposez vos images ici',
'or' => '-ou-',
'file-browser' => 'Cliquez pour utiliser le navigateur de fichier',
'image_not_found' => 'Impossible de trouver l\'image',
'no_more_short' => 'Il n\'y a plus d\'URL disponible. Veuillez réessayer ou contactez l\'administrateur. [_1].',
'no_valid_file' => 'Le fichier [_1] n\'est pas une image.',
'file_too_big' => 'Le fichier dépasse la limite de taille ([_1])',
'no_time_limit' => 'Pas de limitation de durée',
'24_hours' => '24 heures',
'7_days' => '7 jours',
'30_days' => '30 jours',
'1_year' => 'Un an',
'pushed-images' => ' images envoyées sur cette instance depuis le début.',
'graph-data-once-a-day' => 'Les données du graphique ne sont pas mises à jour en temps réél.',
'lutim-stats' => 'Statistiques de LUTIm',
'back-to-index' => 'Retour à la page d\'accueil'
);

View File

@@ -0,0 +1,26 @@
package Mojolicious::Command::cron;
use Mojo::Base 'Mojolicious::Commands';
has description => 'Execute tasks.';
has hint => <<EOF;
See 'script/lutim cron help TASK' for more information on a specific task.
EOF
has message => sub { shift->extract_usage . "\nCron tasks:\n" };
has namespaces => sub { ['Mojolicious::Command::cron'] };
sub help { shift->run(@_) }
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::cron - Cron commands
=head1 SYNOPSIS
Usage: script/lutim cron TASK [OPTIONS]
=cut

View File

@@ -0,0 +1,64 @@
package Mojolicious::Command::cron::stats;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::DOM;
use Mojo::Util qw(slurp spurt decode);
use DateTime;
use Mojolicious::Plugin::Config;
has description => 'Generate statistics about LUTIm.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = Mojolicious::Plugin::Config->parse(decode('UTF-8', slurp 'lutim.conf'), 'lutim.conf');
$config->{stats_day_num} = (defined($config->{stats_day_num})) ? $config->{stats_day_num} : 365;
my $text = slurp('templates/data.html.ep.template');
my $dom = Mojo::DOM->new($text);
my $thead_tr = $dom->at('table thead tr');
my $tbody_tr = $dom->at('table tbody tr');
my $tbody_t2 = $tbody_tr->next;
my $separation = time() - $config->{stats_day_num} * 86400;
my %data;
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND created_at > = ?', $separation)) {
my $time = DateTime->from_epoch(epoch => $img->created_at);
my ($year, $month, $day) = ($time->year(), $time->month(), $time->day());
if (defined($data{$year}->{$month}->{$day})) {
$data{$year}->{$month}->{$day} += 1;
} else {
$data{$year}->{$month}->{$day} = 1;
}
}
my $total = LutimModel::Lutim->count('WHERE path IS NOT NULL AND created_at < ?', $separation);
for my $year (sort keys %data) {
for my $month (sort keys %{$data{$year}}) {
for my $day (sort keys %{$data{$year}->{$month}}) {
$thead_tr->append_content('<th>'."$day/$month/$year".'</th>'."\n");
$tbody_tr->append_content('<td>'.$data{$year}->{$month}->{$day}.'</td>'."\n");
$total += $data{$year}->{$month}->{$day};
$tbody_t2->append_content('<td>'.$total.'</td>'."\n");
}
}
}
spurt $dom, 'templates/data.html.ep';
}
=encoding utf8
=head1 NAME
Mojolicious::Command::cron::stats - Stats generator
=head1 SYNOPSIS
Usage: script/lutim cron stats
=cut
1;

View File

@@ -12,8 +12,9 @@
length => 8, # optional
provis_step => 5, # optional
provisioning => 100, # optional
hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">', # optional
tweet_card_via => '@framasky', # optional
max_file_size => 10485760, # optional, size in octets, you can write it 10*1024*1024
https => 0, # optional, set to 1 if you use Lutim behind a secure web server
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">', # optional
#https => 0, # optional, set to 1 if you use Lutim behind a secure web server
#stats_day_num => 365, # optional, number of days shown in /stats page
};

View File

@@ -680,6 +680,235 @@ address {
font-style: normal;
line-height: 1.42857143;
}
table {
max-width: 100%;
background-color: transparent;
}
th {
text-align: left;
}
.table {
width: 100%;
margin-bottom: 20px;
}
.table > thead > tr > th,
.table > tbody > tr > th,
.table > tfoot > tr > th,
.table > thead > tr > td,
.table > tbody > tr > td,
.table > tfoot > tr > td {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #dddddd;
}
.table > thead > tr > th {
vertical-align: bottom;
border-bottom: 2px solid #dddddd;
}
.table > caption + thead > tr:first-child > th,
.table > colgroup + thead > tr:first-child > th,
.table > thead:first-child > tr:first-child > th,
.table > caption + thead > tr:first-child > td,
.table > colgroup + thead > tr:first-child > td,
.table > thead:first-child > tr:first-child > td {
border-top: 0;
}
.table > tbody + tbody {
border-top: 2px solid #dddddd;
}
.table .table {
background-color: #ffffff;
}
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
.table-condensed > tfoot > tr > th,
.table-condensed > thead > tr > td,
.table-condensed > tbody > tr > td,
.table-condensed > tfoot > tr > td {
padding: 5px;
}
.table-bordered {
border: 1px solid #dddddd;
}
.table-bordered > thead > tr > th,
.table-bordered > tbody > tr > th,
.table-bordered > tfoot > tr > th,
.table-bordered > thead > tr > td,
.table-bordered > tbody > tr > td,
.table-bordered > tfoot > tr > td {
border: 1px solid #dddddd;
}
.table-bordered > thead > tr > th,
.table-bordered > thead > tr > td {
border-bottom-width: 2px;
}
.table-striped > tbody > tr:nth-child(odd) > td,
.table-striped > tbody > tr:nth-child(odd) > th {
background-color: #f9f9f9;
}
.table-hover > tbody > tr:hover > td,
.table-hover > tbody > tr:hover > th {
background-color: #f5f5f5;
}
table col[class*="col-"] {
position: static;
float: none;
display: table-column;
}
table td[class*="col-"],
table th[class*="col-"] {
position: static;
float: none;
display: table-cell;
}
.table > thead > tr > td.active,
.table > tbody > tr > td.active,
.table > tfoot > tr > td.active,
.table > thead > tr > th.active,
.table > tbody > tr > th.active,
.table > tfoot > tr > th.active,
.table > thead > tr.active > td,
.table > tbody > tr.active > td,
.table > tfoot > tr.active > td,
.table > thead > tr.active > th,
.table > tbody > tr.active > th,
.table > tfoot > tr.active > th {
background-color: #f5f5f5;
}
.table-hover > tbody > tr > td.active:hover,
.table-hover > tbody > tr > th.active:hover,
.table-hover > tbody > tr.active:hover > td,
.table-hover > tbody > tr.active:hover > th {
background-color: #e8e8e8;
}
.table > thead > tr > td.success,
.table > tbody > tr > td.success,
.table > tfoot > tr > td.success,
.table > thead > tr > th.success,
.table > tbody > tr > th.success,
.table > tfoot > tr > th.success,
.table > thead > tr.success > td,
.table > tbody > tr.success > td,
.table > tfoot > tr.success > td,
.table > thead > tr.success > th,
.table > tbody > tr.success > th,
.table > tfoot > tr.success > th {
background-color: #dff0d8;
}
.table-hover > tbody > tr > td.success:hover,
.table-hover > tbody > tr > th.success:hover,
.table-hover > tbody > tr.success:hover > td,
.table-hover > tbody > tr.success:hover > th {
background-color: #d0e9c6;
}
.table > thead > tr > td.info,
.table > tbody > tr > td.info,
.table > tfoot > tr > td.info,
.table > thead > tr > th.info,
.table > tbody > tr > th.info,
.table > tfoot > tr > th.info,
.table > thead > tr.info > td,
.table > tbody > tr.info > td,
.table > tfoot > tr.info > td,
.table > thead > tr.info > th,
.table > tbody > tr.info > th,
.table > tfoot > tr.info > th {
background-color: #d9edf7;
}
.table-hover > tbody > tr > td.info:hover,
.table-hover > tbody > tr > th.info:hover,
.table-hover > tbody > tr.info:hover > td,
.table-hover > tbody > tr.info:hover > th {
background-color: #c4e3f3;
}
.table > thead > tr > td.warning,
.table > tbody > tr > td.warning,
.table > tfoot > tr > td.warning,
.table > thead > tr > th.warning,
.table > tbody > tr > th.warning,
.table > tfoot > tr > th.warning,
.table > thead > tr.warning > td,
.table > tbody > tr.warning > td,
.table > tfoot > tr.warning > td,
.table > thead > tr.warning > th,
.table > tbody > tr.warning > th,
.table > tfoot > tr.warning > th {
background-color: #fcf8e3;
}
.table-hover > tbody > tr > td.warning:hover,
.table-hover > tbody > tr > th.warning:hover,
.table-hover > tbody > tr.warning:hover > td,
.table-hover > tbody > tr.warning:hover > th {
background-color: #faf2cc;
}
.table > thead > tr > td.danger,
.table > tbody > tr > td.danger,
.table > tfoot > tr > td.danger,
.table > thead > tr > th.danger,
.table > tbody > tr > th.danger,
.table > tfoot > tr > th.danger,
.table > thead > tr.danger > td,
.table > tbody > tr.danger > td,
.table > tfoot > tr.danger > td,
.table > thead > tr.danger > th,
.table > tbody > tr.danger > th,
.table > tfoot > tr.danger > th {
background-color: #f2dede;
}
.table-hover > tbody > tr > td.danger:hover,
.table-hover > tbody > tr > th.danger:hover,
.table-hover > tbody > tr.danger:hover > td,
.table-hover > tbody > tr.danger:hover > th {
background-color: #ebcccc;
}
@media (max-width: 767px) {
.table-responsive {
width: 100%;
margin-bottom: 15px;
overflow-y: hidden;
overflow-x: scroll;
-ms-overflow-style: -ms-autohiding-scrollbar;
border: 1px solid #dddddd;
-webkit-overflow-scrolling: touch;
}
.table-responsive > .table {
margin-bottom: 0;
}
.table-responsive > .table > thead > tr > th,
.table-responsive > .table > tbody > tr > th,
.table-responsive > .table > tfoot > tr > th,
.table-responsive > .table > thead > tr > td,
.table-responsive > .table > tbody > tr > td,
.table-responsive > .table > tfoot > tr > td {
white-space: nowrap;
}
.table-responsive > .table-bordered {
border: 0;
}
.table-responsive > .table-bordered > thead > tr > th:first-child,
.table-responsive > .table-bordered > tbody > tr > th:first-child,
.table-responsive > .table-bordered > tfoot > tr > th:first-child,
.table-responsive > .table-bordered > thead > tr > td:first-child,
.table-responsive > .table-bordered > tbody > tr > td:first-child,
.table-responsive > .table-bordered > tfoot > tr > td:first-child {
border-left: 0;
}
.table-responsive > .table-bordered > thead > tr > th:last-child,
.table-responsive > .table-bordered > tbody > tr > th:last-child,
.table-responsive > .table-bordered > tfoot > tr > th:last-child,
.table-responsive > .table-bordered > thead > tr > td:last-child,
.table-responsive > .table-bordered > tbody > tr > td:last-child,
.table-responsive > .table-bordered > tfoot > tr > td:last-child {
border-right: 0;
}
.table-responsive > .table-bordered > tbody > tr:last-child > th,
.table-responsive > .table-bordered > tfoot > tr:last-child > th,
.table-responsive > .table-bordered > tbody > tr:last-child > td,
.table-responsive > .table-bordered > tfoot > tr:last-child > td {
border-bottom: 0;
}
}
fieldset {
padding: 0;
margin: 0;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,346 @@
function SimpleGraph(data, labels, canvas, settings) {
this.settings = settings;
setStyleDefaults(settings);
this.dataSet = new DataSet(data, labels, this.settings);
this.grid = new Grid(this.dataSet, this.settings);
this.canvas = canvas;
this.draw = function() {
if (this.settings.drawGrid) {
this.grid.draw(this.canvas);
}
if (this.settings.yAxisCaption) {
this.dataSet.labelYAxis(this.grid, this.canvas);
}
this.dataSet.labelXAxis(this.grid, this.canvas);
this.dataSet.plot(this.grid, this.canvas);
};
this.replaceDataSet = function(dataSet) {
this.dataSet = new DataSet(dataSet, dataSet.labels, this.settings);
this.grid = new Grid(this.dataSet, this.settings);
};
this.plotCurrentDataSet = function() {
this.dataSet.plot(this.grid, this.canvas);
};
function setStyleDefaults(settings) {
var targets = ["xAxisLabel", "yAxisLabel", "yAxisCaption", "hoverLabel", "hoverValue"];
var types = ["Color", "Font", "FontSize", "FontWeight"];
jQuery.each(targets, function(index, target) {
jQuery.each(types, function(index, type) {
if (!settings[target + type]) {
settings[target + type] = settings["label" + type];
}
});
});
settings.labelStyle = {
font: settings.labelFontSize + '"' + settings.labelFont + '"',
fill: settings.labelColor
};
jQuery.each(targets, function(index, target) {
settings[target + "Style"] = {
font: settings[target + "FontSize"] + ' ' + settings[target + "Font"],
fill: settings[target + "Color"],
"font-weight": settings[target + "FontWeight"]
};
});
}
}
// Holds the data and labels to be plotted, provides methods for labelling the x and y axes,
// and for plotting it's own points. Each method requires a grid object for translating values to
// x,y pixel coordinates and a canvas object on which to draw.
function DataSet(data, labels, settings) {
this.data = data;
this.labels = labels;
this.settings = settings;
this.labelXAxis = function(grid, canvas) {
(function(ds) {
jQuery.each(ds.labels, function(i, label) {
var x = grid.x(i);
canvas.text(x + ds.settings.xAxisLabelOffset, ds.settings.height - 6, label).attr(ds.settings.xAxisLabelStyle);
});
})(this);
};
this.labelYAxis = function(grid, canvas) {
// Legend
canvas.rect(
grid.leftEdge - (30 + this.settings.yAxisOffset), //TODO PARAM - Label Colum Width
grid.topEdge,
30, //TODO PARAM - Label Column Width
grid.height
).attr({stroke: this.settings.lineColor, fill: this.settings.lineColor, opacity: 0.3}); //TODO PARAMS - legend border and fill style
for (var i = 1, ii = (grid.rows); i < (ii - this.settings.lowerBound/2); i = i + 2) {
var value = (ii - i)*2,
y = grid.y(value) + 4, // TODO: Value of 4 works for default dimensions, expect will need to scale
x = grid.leftEdge - (6 + this.settings.yAxisOffset);
canvas.text(x, y, value).attr(this.settings.yAxisLabelStyle);
}
var caption = canvas.text(
grid.leftEdge - (20 + this.settings.yAxisOffset),
(grid.height/2) + (this.settings.yAxisCaption.length / 2),
this.settings.yAxisCaption + " (" + this.settings.units + ")").attr(this.settings.yAxisCaptionStyle).rotate(270);
// Increase the offset for the next caption (if any)
this.settings.yAxisOffset = this.settings.yAxisOffset + 30;
};
this.plot = function(grid, canvas) {
var line_path = canvas.path({
stroke: this.settings.lineColor,
"stroke-width": this.settings.lineWidth,
"stroke-linejoin": this.settings.lineJoin
});
var fill_path = canvas.path({
stroke: "none",
fill: this.settings.fillColor,
opacity: this.settings.fillOpacity
}).moveTo(this.settings.leftGutter, this.settings.height - this.settings.bottomGutter);
var bars = canvas.group(),
dots = canvas.group(),
cover = canvas.group();
var hoverFrame = dots.rect(10, 10, 100, 40, 5).attr({
fill: "#fff", stroke: "#474747", "stroke-width": 2}).hide(); //TODO PARAM - fill colour, border colour, border width
var hoverText = [];
hoverText[0] = canvas.text(60, 25, "").attr(this.settings.hoverValueStyle).hide();
hoverText[1] = canvas.text(60, 40, "").attr(this.settings.hoverLabelStyle).hide();
// Plot the points
(function(dataSet) {
jQuery.each(dataSet.data, function(i, value) {
var y = grid.y(value),
x = grid.x(i),
label = dataSet.labels ? dataSet.labels[i] : " ";
if (dataSet.settings.drawPoints) {
var dot = dots.circle(x, y, dataSet.settings.pointRadius).attr({fill: dataSet.settings.pointColor, stroke: dataSet.settings.pointColor});
}
if (dataSet.settings.drawBars) {
bars.rect(x + dataSet.settings.barOffset, y, dataSet.settings.barWidth, (dataSet.settings.height - dataSet.settings.bottomGutter) - y).attr({fill: dataSet.settings.barColor, stroke: "none"});
}
if (dataSet.settings.drawLine) {
line_path[i == 0 ? "moveTo" : "cplineTo"](x, y, 5);
}
if (dataSet.settings.fillUnderLine) {
fill_path[i == 0 ? "lineTo" : "cplineTo"](x, y, 5);
}
if (dataSet.settings.addHover) {
var rect = canvas.rect(x - 50, y - 50, 100, 100).attr({stroke: "none", fill: "#fff", opacity: 0}); //TODO PARAM - hover target width / height
jQuery(rect[0]).hover( function() {
jQuery.fn.simplegraph.hoverIn(canvas, value, label, x, y, hoverFrame, hoverText, dot, dataSet.settings);
},
function() {
jQuery.fn.simplegraph.hoverOut(canvas, hoverFrame, hoverText, dot, dataSet.settings);
});
}
});
})(this);
if (this.settings.fillUnderLine) {
fill_path.lineTo(grid.x(this.data.length - 1), this.settings.height - this.settings.bottomGutter).andClose();
}
hoverFrame.toFront();
};
}
// Holds the dimensions of the grid, and provides methods to convert values into x,y
// pixel coordinates. Also, provides a method to draw a grid on a supplied canvas.
function Grid(dataSet, settings) {
this.dataSet = dataSet;
this.settings = settings;
this.calculateMaxYAxis = function() {
var max = Math.max.apply(Math, this.dataSet.data),
maxOveride = this.settings.minYAxisValue;
if (maxOveride && maxOveride > max) {
max = maxOveride;
}
return max;
};
this.setYAxis = function() {
this.height = this.settings.height - this.settings.topGutter - this.settings.bottomGutter;
this.maxValueYAxis = this.calculateMaxYAxis();
this.Y = this.height / (this.maxValueYAxis - this.settings.lowerBound);
};
this.setXAxis = function() {
this.X = (this.settings.width - this.settings.leftGutter) / (this.dataSet.data.length - 0.4);
};
this.setDimensions = function() {
this.leftEdge = this.settings.leftGutter;
this.topEdge = this.settings.topGutter;
this.width = this.settings.width - this.settings.leftGutter - this.X;
this.columns = this.dataSet.data.length - 1;
this.rows = (this.maxValueYAxis - this.settings.lowerBound) / 2; //TODO PARAM - steps per row
};
this.draw = function(canvas) {
canvas.drawGrid(
this.leftEdge,
this.topEdge,
this.width,
this.height,
this.columns,
this.rows,
this.settings.gridBorderColor
);
};
this.x = function(value) {
return this.settings.leftGutter + this.X * value;
};
this.y = function(value) {
return this.settings.height - this.settings.bottomGutter - this.Y * (value - this.settings.lowerBound);
};
this.setYAxis();
this.setXAxis();
this.setDimensions();
};
(function($) {
//- required to implement hover function
var isLabelVisible;
var leaveTimer;
$.fn.simplegraph = function(data, labels, options) {
var settings = $.extend({}, $.fn.simplegraph.defaults, options);
setPenColor(settings);
return this.each( function() {
var canvas = Raphael(this, settings.width, settings.height);
var simplegraph = new SimpleGraph(data, labels, canvas, settings);
simplegraph.draw();
// Stash simplegraph object away for future reference
$.data(this, "simplegraph", simplegraph);
});
};
// Plot another set of values on an existing graph, use it like this:
// $("#target").simplegraph(data, labels).simplegraph_more(moreData);
$.fn.simplegraph_more = function(data, options) {
return this.each( function() {
var sg = $.data(this, "simplegraph");
sg.dataSet = new DataSet(data, sg.dataSet.labels, sg.settings);
sg.settings.penColor = options.penColor;
setPenColor(sg.settings);
sg.settings = $.extend(sg.settings, options);
sg.grid = new Grid(sg.dataSet, sg.settings);
sg.dataSet.labelYAxis(sg.grid, sg.canvas);
sg.dataSet.plot(sg.grid, sg.canvas);
});
};
// Public
$.fn.simplegraph.defaults = {
drawGrid: false,
units: "",
// Dimensions
width: 600,
height: 250,
leftGutter: 30,
bottomGutter: 20,
topGutter: 20,
// Label Style
labelColor: "#000",
labelFont: "Helvetica",
labelFontSize: "10px",
labelFontWeight: "normal",
// Grid Style
gridBorderColor: "#ccc",
// -- Y Axis Captions
yAxisOffset: 0,
// -- Y Axis Captions
xAxisLabelOffset: 0,
// Graph Style
// -- Points
drawPoints: false,
pointColor: "#000",
pointRadius: 3,
activePointRadius: 5,
// -- Line
drawLine: true,
lineColor: "#000",
lineWidth: 3,
lineJoin: "round",
// -- Bars
drawBars: false,
barColor: "#000",
barWidth: 10,
barOffset: 0,
// -- Fill
fillUnderLine: false,
fillColor: "#000",
fillOpacity: 0.2,
// -- Hover
addHover: true,
// Calculations
lowerBound: 0
};
// Default hoverIn callback, this is public and as such can be overwritten. You can write your
// own call back with the same signature if you want different behaviour.
$.fn.simplegraph.hoverIn = function(canvas, value, label, x, y, frame, hoverLabel, dot, settings) {
clearTimeout(leaveTimer);
var newcoord = {x: x * 1 + 7.5, y: y - 19};
if (newcoord.x + 100 > settings.width) {
newcoord.x -= 114;
}
hoverLabel[0].attr({text: value}).show().animate({x : newcoord.x + 50, y : newcoord.y + 15}, (isLabelVisible ? 100 : 0));
hoverLabel[1].attr({text: label}).show().animate({x : newcoord.x + 50, y : newcoord.y + 30}, (isLabelVisible ? 100 : 0));
frame.show().animate({x: newcoord.x, y: newcoord.y}, (isLabelVisible ? 100 : 0));
if (settings.drawPoints) {
dot.attr("r", settings.activePointRadius);
}
isLabelVisible = true;
canvas.safari();
};
// Default hoverOut callback, this is public and as such can be overwritten. You can write your
// own call back with the same signature if you want different behaviour.
$.fn.simplegraph.hoverOut = function(canvas, frame, label, dot, settings) {
if (settings.drawPoints) {
dot.attr("r", settings.pointRadius);
}
canvas.safari();
leaveTimer = setTimeout(function () {
isLabelVisible = false;
frame.hide();
label[0].hide();
label[1].hide();
canvas.safari();
}, 1);
};
// Private
function setPenColor(settings) {
if (settings.penColor) {
settings.lineColor = settings.penColor;
settings.pointColor = settings.penColor;
settings.fillColor = settings.penColor;
settings.barColor = settings.penColor;
}
}
})(jQuery);

2215
public/js/raphael.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
Raphael.el.isAbsolute = true;
Raphael.el.absolutely = function () {
this.isAbsolute = 1;
return this;
};
Raphael.el.relatively = function () {
this.isAbsolute = 0;
return this;
};
Raphael.el.moveTo = function (x, y) {
this._last = {x: x, y: y};
return this.attr({path: this.attrs.path + ["m", "M"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
};
Raphael.el.lineTo = function (x, y) {
this._last = {x: x, y: y};
return this.attr({path: this.attrs.path + ["l", "L"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
};
Raphael.el.arcTo = function (rx, ry, large_arc_flag, sweep_flag, x, y, angle) {
this._last = {x: x, y: y};
return this.attr({path: this.attrs.path + ["a", "A"][+this.isAbsolute] + [parseFloat(rx), parseFloat(ry), +angle, large_arc_flag, sweep_flag, parseFloat(x), parseFloat(y)].join(" ")});
};
Raphael.el.curveTo = function () {
var args = Array.prototype.splice.call(arguments, 0, arguments.length),
d = [0, 0, 0, 0, "s", 0, "c"][args.length] || "";
this.isAbsolute && (d = d.toUpperCase());
this._last = {x: args[args.length - 2], y: args[args.length - 1]};
return this.attr({path: this.attrs.path + d + args});
};
Raphael.el.cplineTo = function (x, y, w) {
this.attr({path: this.attrs.path + ["C", this._last.x + w, this._last.y, x - w, y, x, y]});
this._last = {x: x, y: y};
return this;
};
Raphael.el.qcurveTo = function () {
var d = [0, 1, "t", 3, "q"][arguments.length],
args = Array.prototype.splice.call(arguments, 0, arguments.length);
if (this.isAbsolute) {
d = d.toUpperCase();
}
this._last = {x: args[args.length - 2], y: args[args.length - 1]};
return this.attr({path: this.attrs.path + d + args});
};
Raphael.el.addRoundedCorner = function (r, dir) {
var rollback = this.isAbsolute;
rollback && this.relatively();
this._last = {x: r * (!!(dir.indexOf("r") + 1) * 2 - 1), y: r * (!!(dir.indexOf("d") + 1) * 2 - 1)};
this.arcTo(r, r, 0, {"lu": 1, "rd": 1, "ur": 1, "dl": 1}[dir] || 0, this._last.x, this._last.y);
rollback && this.absolutely();
return this;
};
Raphael.el.andClose = function () {
return this.attr({path: this.attrs.path + "z"});
};

View File

@@ -0,0 +1,14 @@
<div id="stats-data">
<table class="table table-striped table-bordered" summary="LUTIm stats">
<thead>
<tr>
</tr>
</thead>
<tbody>
<tr>
</tr>
<tr>
</tr>
</tbody>
</table>
</div>

View File

@@ -83,6 +83,7 @@
</div>
<!-- /D&D Zone -->
%= javascript 'js/dmuploader.min.js'
%= javascript begin
function link(url, dl) {
if (dl !== '') {

View File

@@ -34,7 +34,6 @@
% end
%= javascript 'js/jquery-2.1.0.min.js'
%= javascript 'js/bootstrap.min.js'
%= javascript 'js/dmuploader.min.js'
</head>
<body>
<div class="container">

63
templates/stats.html.ep Normal file
View File

@@ -0,0 +1,63 @@
% # vim:set sts=4 sw=4 ts=4 ft=html.epl expandtab:
%= javascript 'js/raphael.js'
%= javascript 'js/raphael.path.methods.js'
%= javascript 'js/jquery.simplegraph.js'
%= include 'data'
<div id="evol-holder"></div>
<div id="total-holder"></div>
<p><small><%=l 'graph-data-once-a-day' %></small></p>
<h4><%= $total %><%=l 'pushed-images' %></h4>
%= javascript begin
function graph(stats_data, stats_labels, stats_total) {
// Plot the data
// - Temperature Graph - adds colour, fill, and a minimum value for the y axis
$("#evol-holder").simplegraph(
stats_data,
stats_labels,
{
addHover: true,
penColor: "#f00",
fillUnderLine: true,
drawPoints: true,
width: document.body.offsetWidth - 50
}
)
$("#total-holder").simplegraph(
stats_total,
stats_labels,
{
addHover: true,
penColor: "#00f",
fillUnderLine: true,
drawPoints: true,
width: document.body.offsetWidth - 50
}
);
}
$(document).ready(function() {
// Get the data
var stats_labels = [], stats_data = [], stats_total = [];
$("#stats-data thead th").each(function () {
stats_labels.push($(this).html());
});
$("#stats-data tbody tr:first-child td").each(function () {
stats_data.push($(this).html());
});
$("#stats-data tbody tr:nth-child(2) td").each(function () {
stats_total.push($(this).html());
});
// Hide the data
$("#stats-data").hide();
graph(stats_data, stats_labels, stats_total);
$(window).resize(function() {
$("#evol-holder").empty();
$("#total-holder").empty();
graph(stats_data, stats_labels, stats_total);
});
});
% end